Repository: TandoorRecipes/recipes
Branch: develop
Commit: be4301e79b04
Files: 1489
Total size: 22.5 MB
Directory structure:
gitextract_yzw4jm7n/
├── .coveragerc
├── .devcontainer/
│ ├── Dockerfile
│ └── devcontainer.json
├── .dockerignore
├── .flake8
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md.bak
│ │ ├── bug_report.yml
│ │ ├── config.yml
│ │ ├── doc_issue.yml
│ │ ├── feature_request.md.bak
│ │ ├── feature_request.yml
│ │ ├── help-request.md.bak
│ │ ├── help_request.yml
│ │ ├── url_import.md.bak
│ │ └── website_import.yml
│ ├── dependabot.yml
│ └── workflows/
│ ├── build-docker.yml
│ ├── ci.yml
│ ├── codeql-analysis.yml
│ └── docs.yml
├── .gitignore
├── .idea/
│ ├── codeStyles/
│ │ └── codeStyleConfig.xml
│ ├── dictionaries/
│ │ ├── vaben.xml
│ │ └── vabene1111_PC.xml
│ ├── inspectionProfiles/
│ │ └── profiles_settings.xml
│ ├── modules.xml
│ ├── prettier.xml
│ ├── recipes.iml
│ └── watcherTasks.xml
├── .prettierignore
├── .prettierrc
├── .vscode/
│ ├── launch.json
│ ├── settings.json
│ └── tasks.json
├── CONTRIBUTERS.md
├── Dockerfile
├── LICENSE.md
├── README.md
├── SECURITY.md
├── boot.sh
├── cookbook/
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── connectors/
│ │ ├── __init__.py
│ │ ├── connector.py
│ │ ├── connector_manager.py
│ │ └── homeassistant.py
│ ├── fixtures/
│ │ └── german_example_tags.json
│ ├── forms.py
│ ├── helper/
│ │ ├── AllAuthCustomAdapter.py
│ │ ├── CustomStorageClass.py
│ │ ├── HelperFunctions.py
│ │ ├── __init__.py
│ │ ├── ai_helper.py
│ │ ├── automation_helper.py
│ │ ├── batch_edit_helper.py
│ │ ├── cache_helper.py
│ │ ├── context_processors.py
│ │ ├── cooklang_parser.py
│ │ ├── drf_spectacular_hooks.py
│ │ ├── fdc_helper.py
│ │ ├── image_processing.py
│ │ ├── ingredient_parser.py
│ │ ├── mdx_attributes.py
│ │ ├── mdx_urlize.py
│ │ ├── open_data_importer.py
│ │ ├── permission_config.py
│ │ ├── permission_helper.py
│ │ ├── property_helper.py
│ │ ├── recipe_search.py
│ │ ├── recipe_url_import.py
│ │ ├── scope_middleware.py
│ │ ├── shopping_helper.py
│ │ ├── template_helper.py
│ │ └── unit_conversion_helper.py
│ ├── integration/
│ │ ├── cheftap.py
│ │ ├── chowdown.py
│ │ ├── cookbookapp.py
│ │ ├── cooklang.py
│ │ ├── cookmate.py
│ │ ├── copymethat.py
│ │ ├── default.py
│ │ ├── domestica.py
│ │ ├── gourmet.py
│ │ ├── integration.py
│ │ ├── mealie.py
│ │ ├── mealie1.py
│ │ ├── mealmaster.py
│ │ ├── melarecipes.py
│ │ ├── nextcloud_cookbook.py
│ │ ├── openeats.py
│ │ ├── paprika.py
│ │ ├── pdfexport.py
│ │ ├── pepperplate.py
│ │ ├── plantoeat.py
│ │ ├── recettetek.py
│ │ ├── recipekeeper.py
│ │ ├── recipesage.py
│ │ ├── rezeptsuitede.py
│ │ ├── rezkonv.py
│ │ └── saffron.py
│ ├── locale/
│ │ ├── ar/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── bg/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── ca/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── cs/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── da/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── de/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── el/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── en/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── es/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── fi/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── fr/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── he/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── hr/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── hu_HU/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── hy/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── id/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── it/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── ko/
│ │ │ └── LC_MESSAGES/
│ │ │ └── django.po
│ │ ├── lv/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── nb_NO/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── nl/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── nn/
│ │ │ └── LC_MESSAGES/
│ │ │ └── django.po
│ │ ├── pl/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── pt/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── pt_BR/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── rn/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── ro/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── ru/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── sl/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── sv/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── tr/
│ │ │ ├── LC_MESSAGES/
│ │ │ │ ├── django.mo
│ │ │ │ └── django.po
│ │ │ └── id/
│ │ │ └── LC_MESSAGES/
│ │ │ └── django.po
│ │ ├── uk/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── vi/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── zh_CN/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ └── zh_Hant/
│ │ └── LC_MESSAGES/
│ │ ├── django.mo
│ │ └── django.po
│ ├── management/
│ │ └── commands/
│ │ ├── export.py
│ │ ├── fix_duplicate_properties.py
│ │ ├── import.py
│ │ ├── rebuildindex.py
│ │ └── seed_basic_data.py
│ ├── managers.py
│ ├── migrations/
│ │ ├── 0001_initial.py
│ │ ├── 0001_squashed_0227_space_ai_default_provider_and_more.py
│ │ ├── 0002_auto_20191119_2035.py
│ │ ├── 0003_enable_pgtrm.py
│ │ ├── 0004_storage_created_by.py
│ │ ├── 0005_recipebook_recipebookentry.py
│ │ ├── 0006_recipe_image.py
│ │ ├── 0007_auto_20191226_0852.py
│ │ ├── 0008_mealplan.py
│ │ ├── 0009_auto_20200130_1056.py
│ │ ├── 0010_auto_20200130_1059.py
│ │ ├── 0011_remove_recipeingredients_unit.py
│ │ ├── 0012_auto_20200130_1116.py
│ │ ├── 0013_userpreference.py
│ │ ├── 0014_auto_20200213_2332.py
│ │ ├── 0015_auto_20200213_2334.py
│ │ ├── 0016_auto_20200213_2335.py
│ │ ├── 0017_auto_20200216_2257.py
│ │ ├── 0018_auto_20200216_2303.py
│ │ ├── 0019_ingredient.py
│ │ ├── 0020_recipeingredient_ingredient.py
│ │ ├── 0021_auto_20200216_2309.py
│ │ ├── 0022_remove_recipeingredient_name.py
│ │ ├── 0023_auto_20200216_2311.py
│ │ ├── 0024_auto_20200216_2313.py
│ │ ├── 0025_userpreference_nav_color.py
│ │ ├── 0026_auto_20200219_1605.py
│ │ ├── 0027_ingredient_recipe.py
│ │ ├── 0028_auto_20200317_1901.py
│ │ ├── 0029_auto_20200317_1901.py
│ │ ├── 0030_recipeingredient_note.py
│ │ ├── 0031_auto_20200407_1841.py
│ │ ├── 0032_userpreference_default_unit.py
│ │ ├── 0033_userpreference_default_page.py
│ │ ├── 0034_auto_20200426_1614.py
│ │ ├── 0035_auto_20200427_1637.py
│ │ ├── 0036_auto_20200427_1800.py
│ │ ├── 0037_userpreference_search_style.py
│ │ ├── 0038_auto_20200502_1259.py
│ │ ├── 0039_recipebook_shared.py
│ │ ├── 0040_auto_20200502_1433.py
│ │ ├── 0041_auto_20200502_1446.py
│ │ ├── 0042_cooklog.py
│ │ ├── 0043_auto_20200507_2302.py
│ │ ├── 0044_viewlog.py
│ │ ├── 0045_userpreference_show_recent.py
│ │ ├── 0046_auto_20200602_1133.py
│ │ ├── 0047_auto_20200602_1133.py
│ │ ├── 0048_auto_20200602_1140.py
│ │ ├── 0049_mealtype_created_by.py
│ │ ├── 0050_auto_20200611_1509.py
│ │ ├── 0051_auto_20200611_1518.py
│ │ ├── 0052_userpreference_ingredient_decimals.py
│ │ ├── 0053_auto_20200611_2217.py
│ │ ├── 0054_sharelink.py
│ │ ├── 0055_auto_20200616_1236.py
│ │ ├── 0056_auto_20200625_2118.py
│ │ ├── 0056_auto_20200625_2157.py
│ │ ├── 0057_auto_20200625_2127.py
│ │ ├── 0058_auto_20200625_2128.py
│ │ ├── 0059_auto_20200625_2137.py
│ │ ├── 0060_auto_20200625_2144.py
│ │ ├── 0061_merge_20200625_2209.py
│ │ ├── 0062_auto_20200625_2219.py
│ │ ├── 0063_auto_20200625_2230.py
│ │ ├── 0064_auto_20200625_2329.py
│ │ ├── 0065_auto_20200626_1444.py
│ │ ├── 0066_auto_20200626_1455.py
│ │ ├── 0067_auto_20200629_1508.py
│ │ ├── 0068_auto_20200629_2127.py
│ │ ├── 0069_auto_20200629_2134.py
│ │ ├── 0070_auto_20200701_2007.py
│ │ ├── 0071_auto_20200701_2048.py
│ │ ├── 0072_step_show_as_header.py
│ │ ├── 0073_auto_20200708_2311.py
│ │ ├── 0074_remove_keyword_created_by.py
│ │ ├── 0075_shoppinglist_shoppinglistentry_shoppinglistrecipe.py
│ │ ├── 0076_shoppinglist_entries.py
│ │ ├── 0077_invitelink.py
│ │ ├── 0078_invitelink_used_by.py
│ │ ├── 0079_invitelink_group.py
│ │ ├── 0080_auto_20200921_2331.py
│ │ ├── 0081_auto_20200921_2349.py
│ │ ├── 0082_auto_20200922_1143.py
│ │ ├── 0083_space.py
│ │ ├── 0084_auto_20200922_1233.py
│ │ ├── 0085_auto_20200922_1235.py
│ │ ├── 0086_auto_20200929_1143.py
│ │ ├── 0087_auto_20200929_1152.py
│ │ ├── 0088_shoppinglist_finished.py
│ │ ├── 0089_auto_20201117_2222.py
│ │ ├── 0090_auto_20201214_1359.py
│ │ ├── 0091_auto_20201226_1551.py
│ │ ├── 0092_recipe_servings.py
│ │ ├── 0093_auto_20201231_1236.py
│ │ ├── 0094_auto_20201231_1238.py
│ │ ├── 0095_auto_20210107_1804.py
│ │ ├── 0096_auto_20210109_2044.py
│ │ ├── 0097_auto_20210113_1315.py
│ │ ├── 0098_auto_20210113_1320.py
│ │ ├── 0099_auto_20210113_1518.py
│ │ ├── 0100_recipe_servings_text.py
│ │ ├── 0101_storage_path.py
│ │ ├── 0102_auto_20210125_1147.py
│ │ ├── 0103_food_ignore_shopping.py
│ │ ├── 0104_auto_20210125_2133.py
│ │ ├── 0105_auto_20210126_1604.py
│ │ ├── 0106_shoppinglist_supermarket.py
│ │ ├── 0107_auto_20210128_1535.py
│ │ ├── 0108_auto_20210219_1410.py
│ │ ├── 0109_auto_20210221_1204.py
│ │ ├── 0110_auto_20210221_1406.py
│ │ ├── 0111_space_created_by.py
│ │ ├── 0112_remove_synclog_space.py
│ │ ├── 0113_auto_20210317_2017.py
│ │ ├── 0114_importlog.py
│ │ ├── 0115_telegrambot.py
│ │ ├── 0116_auto_20210319_0012.py
│ │ ├── 0117_space_max_recipes.py
│ │ ├── 0118_auto_20210406_1805.py
│ │ ├── 0119_auto_20210411_2101.py
│ │ ├── 0120_bookmarklet.py
│ │ ├── 0121_auto_20210518_1638.py
│ │ ├── 0122_auto_20210527_1712.py
│ │ ├── 0123_invitelink_email.py
│ │ ├── 0124_alter_userpreference_theme.py
│ │ ├── 0125_space_demo.py
│ │ ├── 0126_alter_userpreference_theme.py
│ │ ├── 0127_remove_invitelink_username.py
│ │ ├── 0128_userfile.py
│ │ ├── 0129_auto_20210608_1233.py
│ │ ├── 0130_alter_userfile_file_size_kb.py
│ │ ├── 0131_auto_20210608_1929.py
│ │ ├── 0132_sharelink_request_count.py
│ │ ├── 0133_sharelink_abuse_blocked.py
│ │ ├── 0134_space_allow_sharing.py
│ │ ├── 0135_auto_20210615_2210.py
│ │ ├── 0136_auto_20210617_1343.py
│ │ ├── 0137_auto_20210617_1501.py
│ │ ├── 0138_auto_20210617_1602.py
│ │ ├── 0139_space_created_at.py
│ │ ├── 0140_userpreference_created_at.py
│ │ ├── 0141_auto_20210713_1042.py
│ │ ├── 0142_alter_userpreference_search_style.py
│ │ ├── 0143_build_full_text_index.py
│ │ ├── 0144_create_searchfields.py
│ │ ├── 0145_alter_userpreference_search_style.py
│ │ ├── 0146_alter_userpreference_use_fractions.py
│ │ ├── 0147_keyword_to_tree.py
│ │ ├── 0148_auto_20210813_1829.py
│ │ ├── 0149_fix_leading_trailing_spaces.py
│ │ ├── 0150_food_to_tree.py
│ │ ├── 0151_auto_20210915_1037.py
│ │ ├── 0152_automation.py
│ │ ├── 0153_auto_20210915_2327.py
│ │ ├── 0154_auto_20210922_1705.py
│ │ ├── 0155_mealtype_default.py
│ │ ├── 0156_searchpreference_trigram_threshold.py
│ │ ├── 0157_alter_searchpreference_trigram.py
│ │ ├── 0158_userpreference_use_kj.py
│ │ ├── 0159_add_shoppinglistentry_fields.py
│ │ ├── 0160_delete_shoppinglist_orphans.py
│ │ ├── 0161_alter_shoppinglistentry_food.py
│ │ ├── 0162_userpreference_csv_delim.py
│ │ ├── 0163_auto_20220105_0758.py
│ │ ├── 0164_space_show_facet_count.py
│ │ ├── 0165_remove_step_type.py
│ │ ├── 0166_alter_userpreference_shopping_add_onhand.py
│ │ ├── 0167_userpreference_left_handed.py
│ │ ├── 0168_add_unit_searchfields.py
│ │ ├── 0169_exportlog.py
│ │ ├── 0170_auto_20220207_1848.py
│ │ ├── 0171_alter_searchpreference_trigram_threshold.py
│ │ ├── 0172_ingredient_original_text.py
│ │ ├── 0173_recipe_source_url.py
│ │ ├── 0174_alter_food_substitute_userspace.py
│ │ ├── 0175_remove_userpreference_space.py
│ │ ├── 0176_alter_searchpreference_icontains_and_more.py
│ │ ├── 0177_recipe_show_ingredient_overview.py
│ │ ├── 0178_remove_userpreference_search_style_and_more.py
│ │ ├── 0179_recipe_private_recipe_shared.py
│ │ ├── 0180_invitelink_reusable.py
│ │ ├── 0181_space_image.py
│ │ ├── 0182_userpreference_image.py
│ │ ├── 0183_alter_space_image.py
│ │ ├── 0184_alter_userpreference_image.py
│ │ ├── 0185_food_plural_name_ingredient_always_use_plural_food_and_more.py
│ │ ├── 0186_automation_order_alter_automation_type.py
│ │ ├── 0187_alter_space_use_plural.py
│ │ ├── 0188_space_no_sharing_limit.py
│ │ ├── 0189_property_propertytype_unitconversion_food_fdc_id_and_more.py
│ │ ├── 0190_auto_20230525_1506.py
│ │ ├── 0191_foodproperty_property_import_food_id_and_more.py
│ │ ├── 0192_food_food_unique_open_data_slug_per_space_and_more.py
│ │ ├── 0193_space_internal_note.py
│ │ ├── 0194_alter_food_properties_food_amount.py
│ │ ├── 0195_invitelink_internal_note_userspace_internal_note_and_more.py
│ │ ├── 0196_food_url.py
│ │ ├── 0197_step_show_ingredients_table_and_more.py
│ │ ├── 0198_propertytype_order.py
│ │ ├── 0199_alter_propertytype_options_alter_automation_type_and_more.py
│ │ ├── 0200_alter_propertytype_options_remove_keyword_icon_and_more.py
│ │ ├── 0201_rename_date_mealplan_from_date_mealplan_to_date.py
│ │ ├── 0202_remove_space_show_facet_count.py
│ │ ├── 0203_alter_unique_contstraints.py
│ │ ├── 0204_propertytype_fdc_id.py
│ │ ├── 0205_alter_food_fdc_id_alter_propertytype_fdc_id.py
│ │ ├── 0206_rename_sticky_navbar_userpreference_nav_sticky_and_more.py
│ │ ├── 0207_space_logo_color_128_space_logo_color_144_and_more.py
│ │ ├── 0208_space_app_name_userpreference_max_owned_spaces.py
│ │ ├── 0209_remove_space_use_plural.py
│ │ ├── 0210_shoppinglistentry_updated_at.py
│ │ ├── 0211_recipebook_order.py
│ │ ├── 0212_alter_property_property_amount.py
│ │ ├── 0213_remove_property_property_unique_import_food_per_space_and_more.py
│ │ ├── 0214_cooklog_comment_cooklog_updated_at_and_more.py
│ │ ├── 0215_connectorconfig.py
│ │ ├── 0216_delete_shoppinglist.py
│ │ ├── 0217_alter_userpreference_default_page.py
│ │ ├── 0218_alter_mealplan_from_date_alter_mealplan_to_date.py
│ │ ├── 0219_connectorconfig_supports_description_field.py
│ │ ├── 0220_shoppinglistrecipe_created_by_and_more.py
│ │ ├── 0221_migrate_shoppinglistrecipe_space_created_by.py
│ │ ├── 0222_alter_shoppinglistrecipe_created_by_and_more.py
│ │ ├── 0223_auto_20250831_1111.py
│ │ ├── 0224_space_ai_credits_balance_space_ai_credits_monthly_and_more.py
│ │ ├── 0225_space_ai_enabled.py
│ │ ├── 0226_aiprovider_log_credit_cost_and_more.py
│ │ ├── 0227_space_ai_default_provider_and_more.py
│ │ ├── 0228_space_space_setup_completed.py
│ │ ├── 0229_alter_ailog_options_alter_aiprovider_options_and_more.py
│ │ ├── 0230_auto_20250925_2056.py
│ │ ├── 0231_alter_aiprovider_options_alter_automation_options_and_more.py
│ │ ├── 0232_shoppinglist.py
│ │ ├── 0233_food_shopping_lists_shoppinglistentry_shopping_lists_and_more.py
│ │ ├── 0234_alter_shoppinglist_options_and_more.py
│ │ ├── 0235_recipe_diameter_recipe_diameter_text.py
│ │ ├── 0236_household_userspace_household_inventorylocation_and_more.py
│ │ ├── 0237_remove_mealtype_mt_unique_name_per_space_and_more.py
│ │ ├── 0238_auto_20260312_1920.py
│ │ └── __init__.py
│ ├── models.py
│ ├── provider/
│ │ ├── __init__.py
│ │ ├── dropbox.py
│ │ ├── local.py
│ │ ├── nextcloud.py
│ │ └── provider.py
│ ├── serializer.py
│ ├── signals.py
│ ├── static/
│ │ ├── custom/
│ │ │ └── css/
│ │ │ └── markdown_blockquote.css
│ │ └── pdfjs/
│ │ ├── LICENSE
│ │ └── web/
│ │ ├── cmaps/
│ │ │ ├── 78-EUC-H.bcmap
│ │ │ ├── 78-EUC-V.bcmap
│ │ │ ├── 78-H.bcmap
│ │ │ ├── 78-RKSJ-H.bcmap
│ │ │ ├── 78-RKSJ-V.bcmap
│ │ │ ├── 78-V.bcmap
│ │ │ ├── 78ms-RKSJ-H.bcmap
│ │ │ ├── 78ms-RKSJ-V.bcmap
│ │ │ ├── 83pv-RKSJ-H.bcmap
│ │ │ ├── 90ms-RKSJ-H.bcmap
│ │ │ ├── 90ms-RKSJ-V.bcmap
│ │ │ ├── 90msp-RKSJ-H.bcmap
│ │ │ ├── 90msp-RKSJ-V.bcmap
│ │ │ ├── 90pv-RKSJ-H.bcmap
│ │ │ ├── 90pv-RKSJ-V.bcmap
│ │ │ ├── Add-H.bcmap
│ │ │ ├── Add-RKSJ-H.bcmap
│ │ │ ├── Add-RKSJ-V.bcmap
│ │ │ ├── Add-V.bcmap
│ │ │ ├── Adobe-CNS1-0.bcmap
│ │ │ ├── Adobe-CNS1-1.bcmap
│ │ │ ├── Adobe-CNS1-2.bcmap
│ │ │ ├── Adobe-CNS1-3.bcmap
│ │ │ ├── Adobe-CNS1-4.bcmap
│ │ │ ├── Adobe-CNS1-5.bcmap
│ │ │ ├── Adobe-CNS1-6.bcmap
│ │ │ ├── Adobe-CNS1-UCS2.bcmap
│ │ │ ├── Adobe-GB1-0.bcmap
│ │ │ ├── Adobe-GB1-1.bcmap
│ │ │ ├── Adobe-GB1-2.bcmap
│ │ │ ├── Adobe-GB1-3.bcmap
│ │ │ ├── Adobe-GB1-4.bcmap
│ │ │ ├── Adobe-GB1-5.bcmap
│ │ │ ├── Adobe-GB1-UCS2.bcmap
│ │ │ ├── Adobe-Japan1-0.bcmap
│ │ │ ├── Adobe-Japan1-1.bcmap
│ │ │ ├── Adobe-Japan1-2.bcmap
│ │ │ ├── Adobe-Japan1-3.bcmap
│ │ │ ├── Adobe-Japan1-4.bcmap
│ │ │ ├── Adobe-Japan1-5.bcmap
│ │ │ ├── Adobe-Japan1-6.bcmap
│ │ │ ├── Adobe-Japan1-UCS2.bcmap
│ │ │ ├── Adobe-Korea1-0.bcmap
│ │ │ ├── Adobe-Korea1-1.bcmap
│ │ │ ├── Adobe-Korea1-2.bcmap
│ │ │ ├── Adobe-Korea1-UCS2.bcmap
│ │ │ ├── B5-H.bcmap
│ │ │ ├── B5-V.bcmap
│ │ │ ├── B5pc-H.bcmap
│ │ │ ├── B5pc-V.bcmap
│ │ │ ├── CNS-EUC-H.bcmap
│ │ │ ├── CNS-EUC-V.bcmap
│ │ │ ├── CNS1-H.bcmap
│ │ │ ├── CNS1-V.bcmap
│ │ │ ├── CNS2-H.bcmap
│ │ │ ├── CNS2-V.bcmap
│ │ │ ├── ETHK-B5-H.bcmap
│ │ │ ├── ETHK-B5-V.bcmap
│ │ │ ├── ETen-B5-H.bcmap
│ │ │ ├── ETen-B5-V.bcmap
│ │ │ ├── ETenms-B5-H.bcmap
│ │ │ ├── ETenms-B5-V.bcmap
│ │ │ ├── EUC-H.bcmap
│ │ │ ├── EUC-V.bcmap
│ │ │ ├── Ext-H.bcmap
│ │ │ ├── Ext-RKSJ-H.bcmap
│ │ │ ├── Ext-RKSJ-V.bcmap
│ │ │ ├── Ext-V.bcmap
│ │ │ ├── GB-EUC-H.bcmap
│ │ │ ├── GB-EUC-V.bcmap
│ │ │ ├── GB-H.bcmap
│ │ │ ├── GB-V.bcmap
│ │ │ ├── GBK-EUC-H.bcmap
│ │ │ ├── GBK-EUC-V.bcmap
│ │ │ ├── GBK2K-H.bcmap
│ │ │ ├── GBK2K-V.bcmap
│ │ │ ├── GBKp-EUC-H.bcmap
│ │ │ ├── GBKp-EUC-V.bcmap
│ │ │ ├── GBT-EUC-H.bcmap
│ │ │ ├── GBT-EUC-V.bcmap
│ │ │ ├── GBT-H.bcmap
│ │ │ ├── GBT-V.bcmap
│ │ │ ├── GBTpc-EUC-H.bcmap
│ │ │ ├── GBTpc-EUC-V.bcmap
│ │ │ ├── GBpc-EUC-H.bcmap
│ │ │ ├── GBpc-EUC-V.bcmap
│ │ │ ├── H.bcmap
│ │ │ ├── HKdla-B5-H.bcmap
│ │ │ ├── HKdla-B5-V.bcmap
│ │ │ ├── HKdlb-B5-H.bcmap
│ │ │ ├── HKdlb-B5-V.bcmap
│ │ │ ├── HKgccs-B5-H.bcmap
│ │ │ ├── HKgccs-B5-V.bcmap
│ │ │ ├── HKm314-B5-H.bcmap
│ │ │ ├── HKm314-B5-V.bcmap
│ │ │ ├── HKm471-B5-H.bcmap
│ │ │ ├── HKm471-B5-V.bcmap
│ │ │ ├── HKscs-B5-H.bcmap
│ │ │ ├── HKscs-B5-V.bcmap
│ │ │ ├── Hankaku.bcmap
│ │ │ ├── Hiragana.bcmap
│ │ │ ├── KSC-EUC-H.bcmap
│ │ │ ├── KSC-EUC-V.bcmap
│ │ │ ├── KSC-H.bcmap
│ │ │ ├── KSC-Johab-H.bcmap
│ │ │ ├── KSC-Johab-V.bcmap
│ │ │ ├── KSC-V.bcmap
│ │ │ ├── KSCms-UHC-H.bcmap
│ │ │ ├── KSCms-UHC-HW-H.bcmap
│ │ │ ├── KSCms-UHC-HW-V.bcmap
│ │ │ ├── KSCms-UHC-V.bcmap
│ │ │ ├── KSCpc-EUC-H.bcmap
│ │ │ ├── KSCpc-EUC-V.bcmap
│ │ │ ├── Katakana.bcmap
│ │ │ ├── LICENSE
│ │ │ ├── NWP-H.bcmap
│ │ │ ├── NWP-V.bcmap
│ │ │ ├── RKSJ-H.bcmap
│ │ │ ├── RKSJ-V.bcmap
│ │ │ ├── Roman.bcmap
│ │ │ ├── UniCNS-UCS2-H.bcmap
│ │ │ ├── UniCNS-UCS2-V.bcmap
│ │ │ ├── UniCNS-UTF16-H.bcmap
│ │ │ ├── UniCNS-UTF16-V.bcmap
│ │ │ ├── UniCNS-UTF32-H.bcmap
│ │ │ ├── UniCNS-UTF32-V.bcmap
│ │ │ ├── UniCNS-UTF8-H.bcmap
│ │ │ ├── UniCNS-UTF8-V.bcmap
│ │ │ ├── UniGB-UCS2-H.bcmap
│ │ │ ├── UniGB-UCS2-V.bcmap
│ │ │ ├── UniGB-UTF16-H.bcmap
│ │ │ ├── UniGB-UTF16-V.bcmap
│ │ │ ├── UniGB-UTF32-H.bcmap
│ │ │ ├── UniGB-UTF32-V.bcmap
│ │ │ ├── UniGB-UTF8-H.bcmap
│ │ │ ├── UniGB-UTF8-V.bcmap
│ │ │ ├── UniJIS-UCS2-H.bcmap
│ │ │ ├── UniJIS-UCS2-HW-H.bcmap
│ │ │ ├── UniJIS-UCS2-HW-V.bcmap
│ │ │ ├── UniJIS-UCS2-V.bcmap
│ │ │ ├── UniJIS-UTF16-H.bcmap
│ │ │ ├── UniJIS-UTF16-V.bcmap
│ │ │ ├── UniJIS-UTF32-H.bcmap
│ │ │ ├── UniJIS-UTF32-V.bcmap
│ │ │ ├── UniJIS-UTF8-H.bcmap
│ │ │ ├── UniJIS-UTF8-V.bcmap
│ │ │ ├── UniJIS2004-UTF16-H.bcmap
│ │ │ ├── UniJIS2004-UTF16-V.bcmap
│ │ │ ├── UniJIS2004-UTF32-H.bcmap
│ │ │ ├── UniJIS2004-UTF32-V.bcmap
│ │ │ ├── UniJIS2004-UTF8-H.bcmap
│ │ │ ├── UniJIS2004-UTF8-V.bcmap
│ │ │ ├── UniJISPro-UCS2-HW-V.bcmap
│ │ │ ├── UniJISPro-UCS2-V.bcmap
│ │ │ ├── UniJISPro-UTF8-V.bcmap
│ │ │ ├── UniJISX0213-UTF32-H.bcmap
│ │ │ ├── UniJISX0213-UTF32-V.bcmap
│ │ │ ├── UniJISX02132004-UTF32-H.bcmap
│ │ │ ├── UniJISX02132004-UTF32-V.bcmap
│ │ │ ├── UniKS-UCS2-H.bcmap
│ │ │ ├── UniKS-UCS2-V.bcmap
│ │ │ ├── UniKS-UTF16-H.bcmap
│ │ │ ├── UniKS-UTF16-V.bcmap
│ │ │ ├── UniKS-UTF32-H.bcmap
│ │ │ ├── UniKS-UTF32-V.bcmap
│ │ │ ├── UniKS-UTF8-H.bcmap
│ │ │ ├── UniKS-UTF8-V.bcmap
│ │ │ ├── V.bcmap
│ │ │ └── WP-Symbol.bcmap
│ │ ├── debugger.css
│ │ ├── debugger.mjs
│ │ ├── iccs/
│ │ │ ├── CGATS001Compat-v2-micro.icc
│ │ │ └── LICENSE
│ │ ├── locale/
│ │ │ ├── ach/
│ │ │ │ └── viewer.ftl
│ │ │ ├── af/
│ │ │ │ └── viewer.ftl
│ │ │ ├── an/
│ │ │ │ └── viewer.ftl
│ │ │ ├── ar/
│ │ │ │ └── viewer.ftl
│ │ │ ├── ast/
│ │ │ │ └── viewer.ftl
│ │ │ ├── az/
│ │ │ │ └── viewer.ftl
│ │ │ ├── be/
│ │ │ │ └── viewer.ftl
│ │ │ ├── bg/
│ │ │ │ └── viewer.ftl
│ │ │ ├── bn/
│ │ │ │ └── viewer.ftl
│ │ │ ├── bo/
│ │ │ │ └── viewer.ftl
│ │ │ ├── br/
│ │ │ │ └── viewer.ftl
│ │ │ ├── brx/
│ │ │ │ └── viewer.ftl
│ │ │ ├── bs/
│ │ │ │ └── viewer.ftl
│ │ │ ├── ca/
│ │ │ │ └── viewer.ftl
│ │ │ ├── cak/
│ │ │ │ └── viewer.ftl
│ │ │ ├── ckb/
│ │ │ │ └── viewer.ftl
│ │ │ ├── cs/
│ │ │ │ └── viewer.ftl
│ │ │ ├── cy/
│ │ │ │ └── viewer.ftl
│ │ │ ├── da/
│ │ │ │ └── viewer.ftl
│ │ │ ├── de/
│ │ │ │ └── viewer.ftl
│ │ │ ├── dsb/
│ │ │ │ └── viewer.ftl
│ │ │ ├── el/
│ │ │ │ └── viewer.ftl
│ │ │ ├── en-CA/
│ │ │ │ └── viewer.ftl
│ │ │ ├── en-GB/
│ │ │ │ └── viewer.ftl
│ │ │ ├── en-US/
│ │ │ │ └── viewer.ftl
│ │ │ ├── eo/
│ │ │ │ └── viewer.ftl
│ │ │ ├── es-AR/
│ │ │ │ └── viewer.ftl
│ │ │ ├── es-CL/
│ │ │ │ └── viewer.ftl
│ │ │ ├── es-ES/
│ │ │ │ └── viewer.ftl
│ │ │ ├── es-MX/
│ │ │ │ └── viewer.ftl
│ │ │ ├── et/
│ │ │ │ └── viewer.ftl
│ │ │ ├── eu/
│ │ │ │ └── viewer.ftl
│ │ │ ├── fa/
│ │ │ │ └── viewer.ftl
│ │ │ ├── ff/
│ │ │ │ └── viewer.ftl
│ │ │ ├── fi/
│ │ │ │ └── viewer.ftl
│ │ │ ├── fr/
│ │ │ │ └── viewer.ftl
│ │ │ ├── fur/
│ │ │ │ └── viewer.ftl
│ │ │ ├── fy-NL/
│ │ │ │ └── viewer.ftl
│ │ │ ├── ga-IE/
│ │ │ │ └── viewer.ftl
│ │ │ ├── gd/
│ │ │ │ └── viewer.ftl
│ │ │ ├── gl/
│ │ │ │ └── viewer.ftl
│ │ │ ├── gn/
│ │ │ │ └── viewer.ftl
│ │ │ ├── gu-IN/
│ │ │ │ └── viewer.ftl
│ │ │ ├── he/
│ │ │ │ └── viewer.ftl
│ │ │ ├── hi-IN/
│ │ │ │ └── viewer.ftl
│ │ │ ├── hr/
│ │ │ │ └── viewer.ftl
│ │ │ ├── hsb/
│ │ │ │ └── viewer.ftl
│ │ │ ├── hu/
│ │ │ │ └── viewer.ftl
│ │ │ ├── hy-AM/
│ │ │ │ └── viewer.ftl
│ │ │ ├── hye/
│ │ │ │ └── viewer.ftl
│ │ │ ├── ia/
│ │ │ │ └── viewer.ftl
│ │ │ ├── id/
│ │ │ │ └── viewer.ftl
│ │ │ ├── is/
│ │ │ │ └── viewer.ftl
│ │ │ ├── it/
│ │ │ │ └── viewer.ftl
│ │ │ ├── ja/
│ │ │ │ └── viewer.ftl
│ │ │ ├── ka/
│ │ │ │ └── viewer.ftl
│ │ │ ├── kab/
│ │ │ │ └── viewer.ftl
│ │ │ ├── kk/
│ │ │ │ └── viewer.ftl
│ │ │ ├── km/
│ │ │ │ └── viewer.ftl
│ │ │ ├── kn/
│ │ │ │ └── viewer.ftl
│ │ │ ├── ko/
│ │ │ │ └── viewer.ftl
│ │ │ ├── lij/
│ │ │ │ └── viewer.ftl
│ │ │ ├── lo/
│ │ │ │ └── viewer.ftl
│ │ │ ├── locale.json
│ │ │ ├── lt/
│ │ │ │ └── viewer.ftl
│ │ │ ├── ltg/
│ │ │ │ └── viewer.ftl
│ │ │ ├── lv/
│ │ │ │ └── viewer.ftl
│ │ │ ├── meh/
│ │ │ │ └── viewer.ftl
│ │ │ ├── mk/
│ │ │ │ └── viewer.ftl
│ │ │ ├── ml/
│ │ │ │ └── viewer.ftl
│ │ │ ├── mr/
│ │ │ │ └── viewer.ftl
│ │ │ ├── ms/
│ │ │ │ └── viewer.ftl
│ │ │ ├── my/
│ │ │ │ └── viewer.ftl
│ │ │ ├── nb-NO/
│ │ │ │ └── viewer.ftl
│ │ │ ├── ne-NP/
│ │ │ │ └── viewer.ftl
│ │ │ ├── nl/
│ │ │ │ └── viewer.ftl
│ │ │ ├── nn-NO/
│ │ │ │ └── viewer.ftl
│ │ │ ├── oc/
│ │ │ │ └── viewer.ftl
│ │ │ ├── pa-IN/
│ │ │ │ └── viewer.ftl
│ │ │ ├── pl/
│ │ │ │ └── viewer.ftl
│ │ │ ├── pt-BR/
│ │ │ │ └── viewer.ftl
│ │ │ ├── pt-PT/
│ │ │ │ └── viewer.ftl
│ │ │ ├── rm/
│ │ │ │ └── viewer.ftl
│ │ │ ├── ro/
│ │ │ │ └── viewer.ftl
│ │ │ ├── ru/
│ │ │ │ └── viewer.ftl
│ │ │ ├── sat/
│ │ │ │ └── viewer.ftl
│ │ │ ├── sc/
│ │ │ │ └── viewer.ftl
│ │ │ ├── scn/
│ │ │ │ └── viewer.ftl
│ │ │ ├── sco/
│ │ │ │ └── viewer.ftl
│ │ │ ├── si/
│ │ │ │ └── viewer.ftl
│ │ │ ├── sk/
│ │ │ │ └── viewer.ftl
│ │ │ ├── skr/
│ │ │ │ └── viewer.ftl
│ │ │ ├── sl/
│ │ │ │ └── viewer.ftl
│ │ │ ├── son/
│ │ │ │ └── viewer.ftl
│ │ │ ├── sq/
│ │ │ │ └── viewer.ftl
│ │ │ ├── sr/
│ │ │ │ └── viewer.ftl
│ │ │ ├── sv-SE/
│ │ │ │ └── viewer.ftl
│ │ │ ├── szl/
│ │ │ │ └── viewer.ftl
│ │ │ ├── ta/
│ │ │ │ └── viewer.ftl
│ │ │ ├── te/
│ │ │ │ └── viewer.ftl
│ │ │ ├── tg/
│ │ │ │ └── viewer.ftl
│ │ │ ├── th/
│ │ │ │ └── viewer.ftl
│ │ │ ├── tl/
│ │ │ │ └── viewer.ftl
│ │ │ ├── tr/
│ │ │ │ └── viewer.ftl
│ │ │ ├── trs/
│ │ │ │ └── viewer.ftl
│ │ │ ├── uk/
│ │ │ │ └── viewer.ftl
│ │ │ ├── ur/
│ │ │ │ └── viewer.ftl
│ │ │ ├── uz/
│ │ │ │ └── viewer.ftl
│ │ │ ├── vi/
│ │ │ │ └── viewer.ftl
│ │ │ ├── wo/
│ │ │ │ └── viewer.ftl
│ │ │ ├── xh/
│ │ │ │ └── viewer.ftl
│ │ │ ├── zh-CN/
│ │ │ │ └── viewer.ftl
│ │ │ └── zh-TW/
│ │ │ └── viewer.ftl
│ │ ├── pdf.mjs
│ │ ├── pdf.sandbox.mjs
│ │ ├── pdf.worker.mjs
│ │ ├── standard_fonts/
│ │ │ ├── FoxitDingbats.pfb
│ │ │ ├── FoxitFixed.pfb
│ │ │ ├── FoxitFixedBold.pfb
│ │ │ ├── FoxitFixedBoldItalic.pfb
│ │ │ ├── FoxitFixedItalic.pfb
│ │ │ ├── FoxitSerif.pfb
│ │ │ ├── FoxitSerifBold.pfb
│ │ │ ├── FoxitSerifBoldItalic.pfb
│ │ │ ├── FoxitSerifItalic.pfb
│ │ │ ├── FoxitSymbol.pfb
│ │ │ ├── LICENSE_FOXIT
│ │ │ └── LICENSE_LIBERATION
│ │ ├── viewer.css
│ │ ├── viewer.html
│ │ ├── viewer.mjs
│ │ └── wasm/
│ │ ├── LICENSE_OPENJPEG
│ │ ├── LICENSE_PDFJS_OPENJPEG
│ │ ├── LICENSE_PDFJS_QCMS
│ │ ├── LICENSE_QCMS
│ │ ├── openjpeg.wasm
│ │ ├── openjpeg_nowasm_fallback.js
│ │ └── qcms_bg.wasm
│ ├── templates/
│ │ ├── 404.html
│ │ ├── account/
│ │ │ ├── email.html
│ │ │ ├── email_confirm.html
│ │ │ ├── login.html
│ │ │ ├── logout.html
│ │ │ ├── password_change.html
│ │ │ ├── password_reset.html
│ │ │ ├── password_reset_done.html
│ │ │ ├── password_reset_from_key.html
│ │ │ ├── password_reset_from_key_done.html
│ │ │ ├── password_set.html
│ │ │ ├── signup.html
│ │ │ └── signup_closed.html
│ │ ├── base.html
│ │ ├── frontend/
│ │ │ └── tandoor.html
│ │ ├── index.html
│ │ ├── manifest.json
│ │ ├── markdown_info.html
│ │ ├── no_groups_info.html
│ │ ├── no_perm_info.html
│ │ ├── offline.html
│ │ ├── openid/
│ │ │ └── login.html
│ │ ├── pdf_viewer.html
│ │ ├── rest_framework/
│ │ │ └── api.html
│ │ ├── search_info.html
│ │ ├── setup.html
│ │ ├── socialaccount/
│ │ │ ├── authentication_error.html
│ │ │ ├── connections.html
│ │ │ ├── login.html
│ │ │ ├── signup.html
│ │ │ └── snippets/
│ │ │ └── provider_list.html
│ │ ├── space_overview.html
│ │ ├── system.html
│ │ ├── test.html
│ │ └── test2.html
│ ├── templatetags/
│ │ ├── __init__.py
│ │ ├── custom_tags.py
│ │ └── theming_tags.py
│ ├── tests/
│ │ ├── __init__.py
│ │ ├── api/
│ │ │ ├── __init__.py
│ │ │ ├── docs/
│ │ │ │ └── reports/
│ │ │ │ └── tests/
│ │ │ │ ├── assets/
│ │ │ │ │ └── style.css
│ │ │ │ ├── pytest.xml
│ │ │ │ └── tests.html
│ │ │ ├── test_api_access_token.py
│ │ │ ├── test_api_ai_provider.py
│ │ │ ├── test_api_ai_timeout.py
│ │ │ ├── test_api_connector_config.py
│ │ │ ├── test_api_cook_log.py
│ │ │ ├── test_api_food.py
│ │ │ ├── test_api_food_shopping.py
│ │ │ ├── test_api_import_log.py
│ │ │ ├── test_api_ingredient.py
│ │ │ ├── test_api_invitelinke.py
│ │ │ ├── test_api_keyword.py
│ │ │ ├── test_api_meal_plan.py
│ │ │ ├── test_api_meal_type.py
│ │ │ ├── test_api_property.py
│ │ │ ├── test_api_property_type.py
│ │ │ ├── test_api_recipe.py
│ │ │ ├── test_api_recipe_book.py
│ │ │ ├── test_api_recipe_book_entry.py
│ │ │ ├── test_api_related_recipe.py
│ │ │ ├── test_api_share_link.py
│ │ │ ├── test_api_shopping_list_entryv2.py
│ │ │ ├── test_api_shopping_list_recipe.py
│ │ │ ├── test_api_shopping_recipe.py
│ │ │ ├── test_api_space.py
│ │ │ ├── test_api_step.py
│ │ │ ├── test_api_storage.py
│ │ │ ├── test_api_supermarket.py
│ │ │ ├── test_api_sync.py
│ │ │ ├── test_api_sync_log.py
│ │ │ ├── test_api_unit.py
│ │ │ ├── test_api_unit_conversion.py
│ │ │ ├── test_api_user.py
│ │ │ ├── test_api_userpreference.py
│ │ │ ├── test_api_userspace.py
│ │ │ └── test_api_view_log.py
│ │ ├── conftest.py
│ │ ├── docs/
│ │ │ └── reports/
│ │ │ └── tests/
│ │ │ ├── assets/
│ │ │ │ └── style.css
│ │ │ ├── pytest.xml
│ │ │ └── tests.html
│ │ ├── factories/
│ │ │ └── __init__.py
│ │ ├── other/
│ │ │ ├── __init__.py
│ │ │ ├── _recipes.py
│ │ │ ├── docs/
│ │ │ │ └── reports/
│ │ │ │ └── tests/
│ │ │ │ └── assets/
│ │ │ │ └── style.css
│ │ │ ├── test_automations.py
│ │ │ ├── test_connector_manager.py
│ │ │ ├── test_cooklang_integration.py
│ │ │ ├── test_data/
│ │ │ │ ├── Cooklang/
│ │ │ │ │ ├── American Pancakes.cook
│ │ │ │ │ ├── Another Lemon Blueberry Bread.cook
│ │ │ │ │ └── Butter Swirl Shortbread Cookies.cook
│ │ │ │ ├── allrecipes.html
│ │ │ │ ├── americastestkitchen.html
│ │ │ │ ├── chefkoch.html
│ │ │ │ ├── chefkoch2.html
│ │ │ │ ├── cookpad.html
│ │ │ │ ├── cookscountry.html
│ │ │ │ ├── delish.html
│ │ │ │ ├── foodnetwork.html
│ │ │ │ ├── giallozafferano.html
│ │ │ │ ├── journaldesfemmes.html
│ │ │ │ ├── madamedessert.html
│ │ │ │ ├── madamedessert.json
│ │ │ │ ├── marmiton.html
│ │ │ │ ├── regex_recipe.html
│ │ │ │ ├── tasteofhome.html
│ │ │ │ ├── thespruceeats.html
│ │ │ │ └── tudogostoso.html
│ │ │ ├── test_food_property.py
│ │ │ ├── test_ingredient_editor_performance.py
│ │ │ ├── test_ingredient_parser.py
│ │ │ ├── test_local_provider.py
│ │ │ ├── test_makenow_filter.py
│ │ │ ├── test_nested_serializer.py
│ │ │ ├── test_permission_helper.py
│ │ │ ├── test_recipe_full_text_search.py
│ │ │ ├── test_schemas.py
│ │ │ ├── test_social_auth.py
│ │ │ ├── test_theming.py
│ │ │ ├── test_unit_conversion.py
│ │ │ └── test_url_import.py
│ │ ├── resources/
│ │ │ └── websites/
│ │ │ ├── ld_json_1.html
│ │ │ ├── ld_json_2.html
│ │ │ ├── ld_json_3.html
│ │ │ ├── ld_json_4.html
│ │ │ ├── ld_json_itemList.html
│ │ │ ├── ld_json_multiple.html
│ │ │ ├── micro_data_1.html
│ │ │ ├── micro_data_2.html
│ │ │ ├── micro_data_3.html
│ │ │ └── micro_data_4.html
│ │ └── views/
│ │ ├── __init__.py
│ │ ├── test_views_api.py
│ │ └── test_views_general.py
│ ├── urls.py
│ ├── version_info.py
│ └── views/
│ ├── __init__.py
│ ├── api.py
│ ├── import_export.py
│ ├── telegram.py
│ └── views.py
├── docs/
│ ├── CNAME
│ ├── contribute/
│ │ ├── contribute.md
│ │ ├── documentation.md
│ │ ├── feature_contrib/
│ │ │ ├── Integration.md
│ │ │ └── featureguides.md
│ │ ├── guidelines.md
│ │ ├── installation.md
│ │ ├── pycharm.md
│ │ ├── related.md
│ │ ├── translations.md
│ │ └── vscode.md
│ ├── faq.md
│ ├── features/
│ │ ├── ai.md
│ │ ├── authentication.md
│ │ ├── automation.md
│ │ ├── connectors.md
│ │ ├── external_recipes.md
│ │ ├── import_export.md
│ │ ├── shopping.md
│ │ ├── telegram_bot.md
│ │ └── templating.md
│ ├── index.md
│ ├── install/
│ │ ├── archlinux.md
│ │ ├── docker/
│ │ │ ├── apache-proxy/
│ │ │ │ └── docker-compose.yml
│ │ │ ├── ipv6_plain/
│ │ │ │ └── docker-compose.yml
│ │ │ ├── nginx-proxy/
│ │ │ │ └── docker-compose.yml
│ │ │ ├── plain/
│ │ │ │ └── docker-compose.yml
│ │ │ └── traefik-nginx/
│ │ │ └── docker-compose.yml
│ │ ├── docker.md
│ │ ├── helmChart.md
│ │ ├── homeassistant.md
│ │ ├── k8s/
│ │ │ ├── 10-configmap.yaml
│ │ │ ├── 15-secrets.yaml
│ │ │ ├── 20-service-account.yaml
│ │ │ ├── 30-pvc.yaml
│ │ │ ├── 40-sts-postgresql.yaml
│ │ │ ├── 45-service-db.yaml
│ │ │ ├── 50-deployment.yaml
│ │ │ ├── 60-service.yaml
│ │ │ └── 70-ingress.yaml
│ │ ├── kubernetes.md
│ │ ├── kubesail.md
│ │ ├── manual.md
│ │ ├── other.md
│ │ ├── swag.md
│ │ ├── synology.md
│ │ ├── truenas_portainer.md
│ │ ├── unraid.md
│ │ └── wsl.md
│ ├── preview.xcf
│ ├── stylesheets/
│ │ └── extra.css
│ ├── system/
│ │ ├── backup.md
│ │ ├── configuration.md
│ │ ├── migration_sqlite-postgres.md
│ │ ├── permissions.md
│ │ └── updating.md
│ └── tests/
│ ├── assets/
│ │ └── style.css
│ ├── pytest.xml
│ └── tests.html
├── generate_api_client.py
├── http.d/
│ ├── Recipes.conf.template
│ └── errorpages/
│ └── http502.html
├── make_compile_messages.py
├── manage.py
├── mkdocs.yml
├── nginx/
│ └── conf.d/
│ ├── Recipes.conf
│ └── errorpages/
│ └── http502.html
├── openapitools.json
├── plugin.py
├── pyproject.toml
├── pytest.ini
├── recipes/
│ ├── __init__.py
│ ├── locale/
│ │ ├── ar/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── bg/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── ca/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── cs/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── da/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── de/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── el/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── en/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── es/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── fi/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── fr/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── he/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── hr/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── hu_HU/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── hy/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── id/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── it/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── lv/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── nb_NO/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── nl/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── pl/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── pt/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── pt_BR/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── rn/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── ro/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── ru/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── sl/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── sv/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── tr/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── uk/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── vi/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ ├── zh_CN/
│ │ │ └── LC_MESSAGES/
│ │ │ ├── django.mo
│ │ │ └── django.po
│ │ └── zh_Hant/
│ │ └── LC_MESSAGES/
│ │ ├── django.mo
│ │ └── django.po
│ ├── middleware.py
│ ├── settings.py
│ ├── test_settings.py
│ ├── urls.py
│ └── wsgi.py
├── requirements.txt
├── version.py
└── vue3/
├── env.d.ts
├── package.json
├── src/
│ ├── apps/
│ │ └── tandoor/
│ │ ├── Tandoor.vue
│ │ └── main.ts
│ ├── assets/
│ │ ├── bookmarklet_v3.js
│ │ └── vueform.css
│ ├── components/
│ │ ├── buttons/
│ │ │ ├── AiActionButton.vue
│ │ │ └── BtnCopy.vue
│ │ ├── dialogs/
│ │ │ ├── AddToShoppingDialog.vue
│ │ │ ├── AutoPlanDialog.vue
│ │ │ ├── BatchDeleteDialog.vue
│ │ │ ├── BatchEditFoodDialog.vue
│ │ │ ├── BatchEditRecipeDialog.vue
│ │ │ ├── DeleteConfirmDialog.vue
│ │ │ ├── FdcSearchDialog.vue
│ │ │ ├── FreezerExpiryDialog.vue
│ │ │ ├── HelpDialog.vue
│ │ │ ├── ImportTandoorDialog.vue
│ │ │ ├── InventoryEntryLogDialog.vue
│ │ │ ├── MealPlanIcalDialog.vue
│ │ │ ├── MessageListDialog.vue
│ │ │ ├── ModelEditDialog.vue
│ │ │ ├── ModelMergeDialog.vue
│ │ │ ├── RecipeScalingDialog.vue
│ │ │ ├── RecipeShareDialog.vue
│ │ │ ├── ShoppingExportDialog.vue
│ │ │ ├── ShoppingLineItemDialog.vue
│ │ │ ├── StepIngredientSorterDialog.vue
│ │ │ ├── SyncDialog.vue
│ │ │ └── VClosableCardTitle.vue
│ │ ├── display/
│ │ │ ├── BookEntryCard.vue
│ │ │ ├── ClosableHelpAlert.vue
│ │ │ ├── DatabaseLinkCol.vue
│ │ │ ├── DatabaseModelCol.vue
│ │ │ ├── ExternalRecipeViewer.vue
│ │ │ ├── HelpView.vue
│ │ │ ├── HorizontalMealPlanWindow.vue
│ │ │ ├── HorizontalRecipeWindow.vue
│ │ │ ├── ImportLogViewer.vue
│ │ │ ├── IngredientString.vue
│ │ │ ├── IngredientsTable.vue
│ │ │ ├── IngredientsTableRow.vue
│ │ │ ├── Instructions.vue
│ │ │ ├── InventoryEntryTable.vue
│ │ │ ├── KeywordsBar.vue
│ │ │ ├── MealPlanCalendarHeader.vue
│ │ │ ├── MealPlanCalendarItem.vue
│ │ │ ├── MealPlanView.vue
│ │ │ ├── NavigationDrawerContextMenu.vue
│ │ │ ├── PrivateRecipeBadge.vue
│ │ │ ├── PropertyView.vue
│ │ │ ├── RandomIcon.vue
│ │ │ ├── RecipeActivity.vue
│ │ │ ├── RecipeCard.vue
│ │ │ ├── RecipeImage.vue
│ │ │ ├── RecipeView.vue
│ │ │ ├── ScalableNumber.vue
│ │ │ ├── ShoppingLineItem.vue
│ │ │ ├── ShoppingListView.vue
│ │ │ ├── ShoppingListsBar.vue
│ │ │ ├── SpaceLimitsInfo.vue
│ │ │ ├── StepView.vue
│ │ │ ├── StepsOverview.vue
│ │ │ ├── ThankYouNote.vue
│ │ │ ├── Timer.vue
│ │ │ └── VSnackbarQueued.vue
│ │ ├── inputs/
│ │ │ ├── BaseUnitSelect.vue
│ │ │ ├── CategorySelectChip.vue
│ │ │ ├── GlobalSearchDialog.vue
│ │ │ ├── HierarchyEditor.vue
│ │ │ ├── LanguageSelect.vue
│ │ │ ├── ModelSelect.vue
│ │ │ ├── ModelSelectVuetify.vue
│ │ │ ├── NumberScalerDialog.vue
│ │ │ ├── PropertiesEditor.vue
│ │ │ ├── RatingField.vue
│ │ │ ├── RecipeContextMenu.vue
│ │ │ ├── ShoppingListEntryInput.vue
│ │ │ ├── ShoppingListSelectChip.vue
│ │ │ ├── StepEditor.vue
│ │ │ ├── StepMarkdownEditor.vue
│ │ │ └── UserFileField.vue
│ │ ├── model_editors/
│ │ │ ├── AccessTokenEditor.vue
│ │ │ ├── AiProviderEditor.vue
│ │ │ ├── AutomationEditor.vue
│ │ │ ├── ConnectorConfigEditor.vue
│ │ │ ├── CookLogEditor.vue
│ │ │ ├── CustomFilterEditor.vue
│ │ │ ├── FoodEditor.vue
│ │ │ ├── HouseholdEditor.vue
│ │ │ ├── InventoryLocationEditor.vue
│ │ │ ├── InviteLinkEditor.vue
│ │ │ ├── KeywordEditor.vue
│ │ │ ├── MealPlanEditor.vue
│ │ │ ├── MealTypeEditor.vue
│ │ │ ├── ModelEditorBase.vue
│ │ │ ├── PropertyEditor.vue
│ │ │ ├── PropertyTypeEditor.vue
│ │ │ ├── RecipeBookEditor.vue
│ │ │ ├── RecipeEditor.vue
│ │ │ ├── ShoppingListEditor.vue
│ │ │ ├── ShoppingListEntryEditor.vue
│ │ │ ├── SpaceEditor.vue
│ │ │ ├── StorageEditor.vue
│ │ │ ├── SupermarketCategoryEditor.vue
│ │ │ ├── SupermarketEditor.vue
│ │ │ ├── SyncEditor.vue
│ │ │ ├── UnitConversionEditor.vue
│ │ │ ├── UnitEditor.vue
│ │ │ ├── UserFileEditor.vue
│ │ │ └── UserSpaceEditor.vue
│ │ ├── settings/
│ │ │ ├── AccountSettings.vue
│ │ │ ├── ApiSettings.vue
│ │ │ ├── CosmeticSettings.vue
│ │ │ ├── ExportDataSettings.vue
│ │ │ ├── MealPlanDeviceSettings.vue
│ │ │ ├── MealPlanSettings.vue
│ │ │ ├── OpenDataImportSettings.vue
│ │ │ ├── SearchSettings.vue
│ │ │ ├── ShoppingSettings.vue
│ │ │ └── SpaceSettings.vue
│ │ └── tables/
│ │ └── InventoryEntryLogTable.vue
│ ├── composables/
│ │ ├── useDjangoUrls.ts
│ │ ├── useFileApi.ts
│ │ ├── useModelEditorFunctions.ts
│ │ └── useNavigation.ts
│ ├── i18n.ts
│ ├── locales/
│ │ ├── ar.json
│ │ ├── bg.json
│ │ ├── ca.json
│ │ ├── cs.json
│ │ ├── da.json
│ │ ├── de.json
│ │ ├── el.json
│ │ ├── en.json
│ │ ├── es.json
│ │ ├── fi.json
│ │ ├── fr.json
│ │ ├── he.json
│ │ ├── hr.json
│ │ ├── hu.json
│ │ ├── hy.json
│ │ ├── id.json
│ │ ├── is.json
│ │ ├── it.json
│ │ ├── ko.json
│ │ ├── lt.json
│ │ ├── lv.json
│ │ ├── nb_NO.json
│ │ ├── nl.json
│ │ ├── nn.json
│ │ ├── pl.json
│ │ ├── pt.json
│ │ ├── pt_BR.json
│ │ ├── ro.json
│ │ ├── ru.json
│ │ ├── sl.json
│ │ ├── sv.json
│ │ ├── tr.json
│ │ ├── uk.json
│ │ ├── zh_Hans.json
│ │ └── zh_Hant.json
│ ├── openapi/
│ │ ├── .openapi-generator/
│ │ │ ├── FILES
│ │ │ └── VERSION
│ │ ├── .openapi-generator-ignore
│ │ ├── apis/
│ │ │ ├── ApiApi.ts
│ │ │ ├── ApiTokenAuthApi.ts
│ │ │ └── index.ts
│ │ ├── index.ts
│ │ ├── models/
│ │ │ ├── AccessToken.ts
│ │ │ ├── AiLog.ts
│ │ │ ├── AiProvider.ts
│ │ │ ├── AlignmentEnum.ts
│ │ │ ├── AuthToken.ts
│ │ │ ├── AutoMealPlan.ts
│ │ │ ├── Automation.ts
│ │ │ ├── AutomationTypeEnum.ts
│ │ │ ├── BaseUnitEnum.ts
│ │ │ ├── BookingTypeEnum.ts
│ │ │ ├── BookmarkletImport.ts
│ │ │ ├── BookmarkletImportList.ts
│ │ │ ├── ConnectorConfig.ts
│ │ │ ├── ConnectorConfigConfig.ts
│ │ │ ├── ConnectorConfigTypeEnum.ts
│ │ │ ├── CookLog.ts
│ │ │ ├── CustomFilter.ts
│ │ │ ├── DefaultPageEnum.ts
│ │ │ ├── DeleteEnum.ts
│ │ │ ├── EnterpriseKeyword.ts
│ │ │ ├── EnterpriseSocialEmbed.ts
│ │ │ ├── EnterpriseSocialEmbedTypeEnum.ts
│ │ │ ├── EnterpriseSocialRecipeSearch.ts
│ │ │ ├── EnterpriseSpace.ts
│ │ │ ├── ExportLog.ts
│ │ │ ├── ExportRequest.ts
│ │ │ ├── FdcQuery.ts
│ │ │ ├── FdcQueryFoods.ts
│ │ │ ├── Food.ts
│ │ │ ├── FoodBatchUpdate.ts
│ │ │ ├── FoodInheritField.ts
│ │ │ ├── FoodShopping.ts
│ │ │ ├── FoodShoppingUpdate.ts
│ │ │ ├── FoodSimple.ts
│ │ │ ├── GenericModel.ts
│ │ │ ├── GenericModelReference.ts
│ │ │ ├── Group.ts
│ │ │ ├── Household.ts
│ │ │ ├── ImportImage.ts
│ │ │ ├── ImportLog.ts
│ │ │ ├── ImportOpenData.ts
│ │ │ ├── ImportOpenDataMetaData.ts
│ │ │ ├── ImportOpenDataResponse.ts
│ │ │ ├── ImportOpenDataResponseDetail.ts
│ │ │ ├── ImportOpenDataVersionMetaData.ts
│ │ │ ├── Ingredient.ts
│ │ │ ├── IngredientParserRequest.ts
│ │ │ ├── IngredientParserResponse.ts
│ │ │ ├── IngredientSimple.ts
│ │ │ ├── IngredientString.ts
│ │ │ ├── InventoryEntry.ts
│ │ │ ├── InventoryLocation.ts
│ │ │ ├── InventoryLog.ts
│ │ │ ├── InviteLink.ts
│ │ │ ├── Keyword.ts
│ │ │ ├── KeywordLabel.ts
│ │ │ ├── Localization.ts
│ │ │ ├── MealPlan.ts
│ │ │ ├── MealType.ts
│ │ │ ├── MethodEnum.ts
│ │ │ ├── NutritionInformation.ts
│ │ │ ├── OpenDataCategory.ts
│ │ │ ├── OpenDataConversion.ts
│ │ │ ├── OpenDataFood.ts
│ │ │ ├── OpenDataFoodProperty.ts
│ │ │ ├── OpenDataProperty.ts
│ │ │ ├── OpenDataStore.ts
│ │ │ ├── OpenDataStoreCategory.ts
│ │ │ ├── OpenDataUnit.ts
│ │ │ ├── OpenDataUnitTypeEnum.ts
│ │ │ ├── OpenDataVersion.ts
│ │ │ ├── PaginatedAiLogList.ts
│ │ │ ├── PaginatedAiProviderList.ts
│ │ │ ├── PaginatedAutomationList.ts
│ │ │ ├── PaginatedBookmarkletImportListList.ts
│ │ │ ├── PaginatedConnectorConfigConfigList.ts
│ │ │ ├── PaginatedConnectorConfigList.ts
│ │ │ ├── PaginatedCookLogList.ts
│ │ │ ├── PaginatedCustomFilterList.ts
│ │ │ ├── PaginatedEnterpriseSocialEmbedList.ts
│ │ │ ├── PaginatedEnterpriseSocialRecipeSearchList.ts
│ │ │ ├── PaginatedEnterpriseSpaceList.ts
│ │ │ ├── PaginatedExportLogList.ts
│ │ │ ├── PaginatedFoodList.ts
│ │ │ ├── PaginatedGenericModelList.ts
│ │ │ ├── PaginatedGenericModelReferenceList.ts
│ │ │ ├── PaginatedHouseholdList.ts
│ │ │ ├── PaginatedImportLogList.ts
│ │ │ ├── PaginatedIngredientList.ts
│ │ │ ├── PaginatedInventoryEntryList.ts
│ │ │ ├── PaginatedInventoryLocationList.ts
│ │ │ ├── PaginatedInventoryLogList.ts
│ │ │ ├── PaginatedInviteLinkList.ts
│ │ │ ├── PaginatedKeywordList.ts
│ │ │ ├── PaginatedMealPlanList.ts
│ │ │ ├── PaginatedMealTypeList.ts
│ │ │ ├── PaginatedOpenDataCategoryList.ts
│ │ │ ├── PaginatedOpenDataConversionList.ts
│ │ │ ├── PaginatedOpenDataFoodList.ts
│ │ │ ├── PaginatedOpenDataPropertyList.ts
│ │ │ ├── PaginatedOpenDataStoreList.ts
│ │ │ ├── PaginatedOpenDataUnitList.ts
│ │ │ ├── PaginatedOpenDataVersionList.ts
│ │ │ ├── PaginatedPropertyList.ts
│ │ │ ├── PaginatedPropertyTypeList.ts
│ │ │ ├── PaginatedRecipeBookEntryList.ts
│ │ │ ├── PaginatedRecipeBookList.ts
│ │ │ ├── PaginatedRecipeImportList.ts
│ │ │ ├── PaginatedRecipeOverviewList.ts
│ │ │ ├── PaginatedRecipeSimpleList.ts
│ │ │ ├── PaginatedShoppingListEntryList.ts
│ │ │ ├── PaginatedShoppingListList.ts
│ │ │ ├── PaginatedShoppingListRecipeList.ts
│ │ │ ├── PaginatedSpaceList.ts
│ │ │ ├── PaginatedStepList.ts
│ │ │ ├── PaginatedStorageEntryList.ts
│ │ │ ├── PaginatedStorageList.ts
│ │ │ ├── PaginatedStorageLocationList.ts
│ │ │ ├── PaginatedSupermarketCategoryList.ts
│ │ │ ├── PaginatedSupermarketCategoryRelationList.ts
│ │ │ ├── PaginatedSupermarketList.ts
│ │ │ ├── PaginatedSyncList.ts
│ │ │ ├── PaginatedSyncLogList.ts
│ │ │ ├── PaginatedUnitConversionList.ts
│ │ │ ├── PaginatedUnitList.ts
│ │ │ ├── PaginatedUserFileList.ts
│ │ │ ├── PaginatedUserSpaceList.ts
│ │ │ ├── PaginatedViewLogList.ts
│ │ │ ├── ParsedIngredient.ts
│ │ │ ├── PatchedAccessToken.ts
│ │ │ ├── PatchedAiProvider.ts
│ │ │ ├── PatchedAutomation.ts
│ │ │ ├── PatchedBookmarkletImport.ts
│ │ │ ├── PatchedConnectorConfig.ts
│ │ │ ├── PatchedConnectorConfigConfig.ts
│ │ │ ├── PatchedCookLog.ts
│ │ │ ├── PatchedCustomFilter.ts
│ │ │ ├── PatchedEnterpriseSocialEmbed.ts
│ │ │ ├── PatchedEnterpriseSpace.ts
│ │ │ ├── PatchedExportLog.ts
│ │ │ ├── PatchedFood.ts
│ │ │ ├── PatchedHousehold.ts
│ │ │ ├── PatchedImportLog.ts
│ │ │ ├── PatchedIngredient.ts
│ │ │ ├── PatchedInventoryEntry.ts
│ │ │ ├── PatchedInventoryLocation.ts
│ │ │ ├── PatchedInviteLink.ts
│ │ │ ├── PatchedKeyword.ts
│ │ │ ├── PatchedMealPlan.ts
│ │ │ ├── PatchedMealType.ts
│ │ │ ├── PatchedOpenDataCategory.ts
│ │ │ ├── PatchedOpenDataConversion.ts
│ │ │ ├── PatchedOpenDataFood.ts
│ │ │ ├── PatchedOpenDataProperty.ts
│ │ │ ├── PatchedOpenDataStore.ts
│ │ │ ├── PatchedOpenDataUnit.ts
│ │ │ ├── PatchedOpenDataVersion.ts
│ │ │ ├── PatchedProperty.ts
│ │ │ ├── PatchedPropertyType.ts
│ │ │ ├── PatchedRecipe.ts
│ │ │ ├── PatchedRecipeBook.ts
│ │ │ ├── PatchedRecipeBookEntry.ts
│ │ │ ├── PatchedRecipeImport.ts
│ │ │ ├── PatchedSearchPreference.ts
│ │ │ ├── PatchedShoppingList.ts
│ │ │ ├── PatchedShoppingListEntry.ts
│ │ │ ├── PatchedShoppingListRecipe.ts
│ │ │ ├── PatchedSpace.ts
│ │ │ ├── PatchedStep.ts
│ │ │ ├── PatchedStorage.ts
│ │ │ ├── PatchedStorageEntry.ts
│ │ │ ├── PatchedStorageLocation.ts
│ │ │ ├── PatchedSupermarket.ts
│ │ │ ├── PatchedSupermarketCategory.ts
│ │ │ ├── PatchedSupermarketCategoryRelation.ts
│ │ │ ├── PatchedSync.ts
│ │ │ ├── PatchedUnit.ts
│ │ │ ├── PatchedUnitConversion.ts
│ │ │ ├── PatchedUser.ts
│ │ │ ├── PatchedUserPreference.ts
│ │ │ ├── PatchedUserSpace.ts
│ │ │ ├── PatchedViewLog.ts
│ │ │ ├── Property.ts
│ │ │ ├── PropertyType.ts
│ │ │ ├── Recipe.ts
│ │ │ ├── RecipeBatchUpdate.ts
│ │ │ ├── RecipeBook.ts
│ │ │ ├── RecipeBookEntry.ts
│ │ │ ├── RecipeFlat.ts
│ │ │ ├── RecipeFromSource.ts
│ │ │ ├── RecipeFromSourceResponse.ts
│ │ │ ├── RecipeImage.ts
│ │ │ ├── RecipeImport.ts
│ │ │ ├── RecipeOverview.ts
│ │ │ ├── RecipeShoppingUpdate.ts
│ │ │ ├── RecipeSimple.ts
│ │ │ ├── ScalingEnum.ts
│ │ │ ├── SearchEnum.ts
│ │ │ ├── SearchFields.ts
│ │ │ ├── SearchPreference.ts
│ │ │ ├── ServerSettings.ts
│ │ │ ├── ShareLink.ts
│ │ │ ├── ShoppingList.ts
│ │ │ ├── ShoppingListEntry.ts
│ │ │ ├── ShoppingListEntryBulk.ts
│ │ │ ├── ShoppingListEntryBulkCreate.ts
│ │ │ ├── ShoppingListEntrySimpleCreate.ts
│ │ │ ├── ShoppingListRecipe.ts
│ │ │ ├── SourceImportDuplicate.ts
│ │ │ ├── SourceImportFood.ts
│ │ │ ├── SourceImportIngredient.ts
│ │ │ ├── SourceImportKeyword.ts
│ │ │ ├── SourceImportProperty.ts
│ │ │ ├── SourceImportPropertyType.ts
│ │ │ ├── SourceImportRecipe.ts
│ │ │ ├── SourceImportStep.ts
│ │ │ ├── SourceImportUnit.ts
│ │ │ ├── Space.ts
│ │ │ ├── SpaceNavTextColorEnum.ts
│ │ │ ├── SpaceThemeEnum.ts
│ │ │ ├── Step.ts
│ │ │ ├── Storage.ts
│ │ │ ├── Supermarket.ts
│ │ │ ├── SupermarketCategory.ts
│ │ │ ├── SupermarketCategoryRelation.ts
│ │ │ ├── Sync.ts
│ │ │ ├── SyncLog.ts
│ │ │ ├── ThemeEnum.ts
│ │ │ ├── TypeEnum.ts
│ │ │ ├── Unit.ts
│ │ │ ├── UnitConversion.ts
│ │ │ ├── User.ts
│ │ │ ├── UserFile.ts
│ │ │ ├── UserFileView.ts
│ │ │ ├── UserPreference.ts
│ │ │ ├── UserPreferenceNavTextColorEnum.ts
│ │ │ ├── UserSpace.ts
│ │ │ ├── ViewLog.ts
│ │ │ └── index.ts
│ │ ├── openapi.json
│ │ ├── openapitools.json
│ │ ├── runtime.ts
│ │ └── templates/
│ │ ├── apis.mustache
│ │ └── modelGeneric.mustache
│ ├── pages/
│ │ ├── 404Page.vue
│ │ ├── BookViewPage.vue
│ │ ├── BooksPage.vue
│ │ ├── DatabasePage.vue
│ │ ├── HelpPage.vue
│ │ ├── IngredientEditorPage.vue
│ │ ├── InventoryBookingPage.vue
│ │ ├── MealPlanPage.vue
│ │ ├── ModelDeletePage.vue
│ │ ├── ModelEditPage.vue
│ │ ├── ModelListPage.vue
│ │ ├── PantryPage.vue
│ │ ├── PropertyEditorPage.vue
│ │ ├── RecipeImportPage.vue
│ │ ├── RecipeViewPage.vue
│ │ ├── SearchPage.vue
│ │ ├── SettingsPage.vue
│ │ ├── ShoppingListPage.vue
│ │ ├── SpaceSetupPage.vue
│ │ ├── StartPage.vue
│ │ ├── TestPage.vue
│ │ └── WelcomePage.vue
│ ├── service-worker.ts
│ ├── stores/
│ │ ├── MealPlanStore.ts
│ │ ├── MessageStore.ts
│ │ ├── ShoppingStore.ts
│ │ └── UserPreferenceStore.ts
│ ├── types/
│ │ ├── FoodFilters.ts
│ │ ├── MealPlan.ts
│ │ ├── Models.ts
│ │ ├── Plugins.ts
│ │ ├── SearchTypes.ts
│ │ ├── Shopping.ts
│ │ └── settings.ts
│ ├── utils/
│ │ ├── breakpoint_utils.ts
│ │ ├── cookie.ts
│ │ ├── date_utils.ts
│ │ ├── fdc.ts
│ │ ├── integration_utils.ts
│ │ ├── logic_utils.ts
│ │ ├── model_utils.ts
│ │ ├── number_utils.ts
│ │ ├── step_utils.ts
│ │ └── utils.ts
│ └── vuetify.ts
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
├── tsconfig.vitest.json
└── vite.config.ts
================================================
FILE CONTENTS
================================================
================================================
FILE: .coveragerc
================================================
[run]
omit =
*/apps.py,
*/migrations/*,
*/settings*,
*/test*,
*/tests/*,
*urls.py,
*/wsgi*,
manage.py,
*__init__*
================================================
FILE: .devcontainer/Dockerfile
================================================
FROM python:3.13-alpine3.22
#Install all dependencies.
RUN apk add --no-cache postgresql-libs postgresql-client gettext zlib libjpeg libwebp libxml2-dev libxslt-dev openldap git yarn libgcc libstdc++ nginx tini envsubst nodejs npm
#Print all logs without buffering it.
ENV PYTHONUNBUFFERED 1
#This port will be used by gunicorn.
EXPOSE 8000
#This port will be used by vue
EXPOSE 8080
#Install all python dependencies to the image
COPY requirements.txt /tmp/pip-tmp/
RUN \
if [ `apk --print-arch` = "armv7" ]; then \
printf "[global]\nextra-index-url=https://www.piwheels.org/simple\n" > /etc/pip.conf ; \
fi
RUN apk add --no-cache --virtual .build-deps gcc musl-dev postgresql-dev zlib-dev jpeg-dev libwebp-dev openssl-dev libffi-dev cargo openldap-dev python3-dev xmlsec-dev xmlsec build-base g++ curl rust && \
python -m pip install --upgrade pip && \
pip debug -v && \
pip install wheel==0.45.1 && \
pip install setuptools_rust==1.10.2 && \
pip install -r /tmp/pip-tmp/requirements.txt --no-cache-dir &&\
apk --purge del .build-deps
================================================
FILE: .devcontainer/devcontainer.json
================================================
// For format details, see https://aka.ms/devcontainer.json.
{
"name": "Tandoor Dev Container",
"build": { "context": "..", "dockerfile": "Dockerfile" },
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [8000, 8080],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "pip3 install --user -r requirements.txt"
// Configure tool-specific properties.
"customizations": {
"vscode": {
"extensions": [
"ms-python.debugpy",
"ms-python.python"
]
}
},
"containerEnv": {
"CSRF_TRUSTED_ORIGINS": "http://localhost:8000,http://localhost:8080"
}
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}
================================================
FILE: .dockerignore
================================================
**/node_modules
npm-debug.log
Dockerfile*
docker-compose*
.dockerignore
.gitignore
README.md
LICENSE
.vscode
.env
.env.template
.github
.idea
.prettierignore
LICENSE.md
docs
update.sh
.pytest_cache
cookbook/tests
mediafiles
staticfiles
db.sqlite3
pytest.ini
vue/**/*.vue
vue/**/*.ts
**/.openapi-generator
mkdocs.yml
vue/babel.config*
vue/package.json
vue/tsconfig.json
vue/src/utils/openapi
venv
================================================
FILE: .flake8
================================================
[flake8]
extend-ignore =
# Whitespace before ':' - Required for black compatibility
E203,
# Line break occurred before a binary operator - Required for black compatibility
W503,
# Comparison to False should be 'if cond is False:' or 'if not cond:'
E712
exclude =
.git,
**/__pycache__,
**/.git,
**/.svn,
**/.hg,
**/CVS,
**/.DS_Store,
.vscode,
**/*.pyc
per-file-ignores=
cookbook/apps.py:F401
max-line-length = 179
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: [vabene1111]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md.bak
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
## Version
**Tandoor-Version:**
## Setup configuration
### Setup
- [ ] Docker / Docker-Compose
- [ ] Unraid
- [ ] Synology
- [ ] Kubernetes
- [ ] Manual setup
- [ ] Others (please state below)
### Reverse Proxy
- [ ] No reverse proxy
- [ ] jwilder's nginx proxy
- [ ] Nginx proxy manager (NPM)
- [ ] SWAG
- [ ] Caddy
- [ ] Traefik
- [ ] Others (please state below)
**Additional information:**
## Bug description
A clear and concise description of what the bug is.
## Logs
Website •
Installation •
Docs •
Demo •
Community •
Discord
go to http://example.com (www.example.us/path/?name=val) go to http://example.com now! del.icio.us
Web-Container-Logs
```
Replace me with logs
```
DB-Container-Logs
```
Replace me with logs
```
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: Bug Report
description: "Create a report to help us improve"
#title: ""
#labels: ["Bug"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: input
id: version
attributes:
label: Tandoor Version
description: "What version of Tandoor are you using? (can be found on the system page since v0.8.4)"
validations:
required: true
- type: dropdown
id: setup
attributes:
label: Setup
description: "How is your Tandoor instance set up?"
options:
- Docker / Docker-Compose
- Unraid
- Synology
- Kubernetes
- Manual Setup
- Others (please state below)
validations:
required: true
- type: dropdown
id: reverse-proxy
attributes:
label: "Reverse Proxy"
description: "What reverse proxy do you use with Tandoor?"
options:
- No reverse proxy
- jwilder's nginx proxy
- Nginx Proxy Manager (NPM)
- SWAG
- Caddy
- Traefik
- Apache2
- Others (please state below)
validations:
required: true
- type: input
id: other
attributes:
label: Other
description: "In case you chose 'Others' above, please provide more info here."
- type: textarea
id: bug-descr
attributes:
label: Bug description
description: "Please accurately describe the bug you encountered."
validations:
required: true
- type: textarea
id: logs
attributes:
label: Relevant logs
description: Please copy and paste any relevant logs. This will be automatically formatted into code, so no need for backticks.
render: shell
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: true
contact_links:
- name: FAQs
url: https://docs.tandoor.dev/faq/
about: Please take a look at the FAQs before creating a bug ticket.
================================================
FILE: .github/ISSUE_TEMPLATE/doc_issue.yml
================================================
name: Documentation Issue
description: "Create a report to help us improve"
#title: ""
labels: ["documentation"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this documentation issue report!
- type: input
id: docs-link
attributes:
label: Documentation link
description: "Please provide a link to the corresponding documentation site on docs.tandoor.dev"
- type: dropdown
id: section
attributes:
label: Affected section
description: "What part of the documentation is the issue about?"
options:
- Installation
- Features
- System
- FAQ
- Does not exist yet
- Other (please state below)
validations:
required: true
- type: input
id: other
attributes:
label: Other
description: "In case you chose 'Other' above, please provide more info here."
- type: textarea
id: descr
attributes:
label: Issue description
description: "Please accurately describe the documentation issue you are seeing."
validations:
required: true
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md.bak
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
name: Feature Request
description: "Suggest an idea for this project"
#title: ""
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this feature request!
- type: textarea
id: problem
attributes:
label: "Is your feature request related to a problem? Please describe."
description: "A clear and concise description of what the problem is. Ex. I'm always frustrated when..."
- type: textarea
id: solution
attributes:
label: "Describe the solution you'd like"
description: "A clear and concise description of what you want to happen."
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: "Describe alternatives you've considered"
description: "A clear and concise description of any alternative solutions or features you've considered."
- type: textarea
id: additional
attributes:
label: "Additional context"
description: "Add any other context or screenshots about the feature request here."
================================================
FILE: .github/ISSUE_TEMPLATE/help-request.md.bak
================================================
---
name: Help request
about: If there is anything wrong with your setup
title: ''
labels: setup issue
assignees: ''
---
## Issue
Please describe your problem here
## Setup Info
Version: (can be found on the system page since v0.8.4)
OS: e.g. Ubuntu 20.02
Other relevant information regarding your problem (proxies, firewalls, etc.)
### `.env`
Please include your `.env` config file (**make sure to remove/replace all secrets**)
```
env content
```
### `docker-compose.yml`
When running with docker compose please provide your `docker-compose.yml`
```
docker-compose.yml content
```
### Logs
If you feel like there is anything interesting please post the output of `docker-compose logs` at
container startup and when the issue happens.
================================================
FILE: .github/ISSUE_TEMPLATE/help_request.yml
================================================
name: Help request
description: "If there is anything wrong with your setup"
#title: ""
labels: ["setup issue"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this help request!
- type: textarea
id: issue
attributes:
label: Issue
description: "Please describe your problem here."
validations:
required: true
- type: input
id: version
attributes:
label: Tandoor Version
description: "What version of Tandoor are you using? (can be found on the system page since v0.8.4)"
validations:
required: true
- type: input
id: os
attributes:
label: OS Version
description: "E.g. Ubuntu 20.02"
validations:
required: true
- type: dropdown
id: setup
attributes:
label: Setup
description: "How is your Tandoor instance set up?"
options:
- Docker / Docker-Compose
- Unraid
- Synology
- Kubernetes
- Manual Setup
- Others (please state below)
validations:
required: true
- type: dropdown
id: reverse-proxy
attributes:
label: "Reverse Proxy"
description: "What reverse proxy do you use with Tandoor?"
options:
- No reverse proxy
- jwilder's nginx proxy
- Nginx Proxy Manager (NPM)
- SWAG
- Caddy
- Traefik
- Others (please state below)
validations:
required: true
- type: input
id: other
attributes:
label: Other
description: "In case you chose 'Others' above or have more info, please provide additional details here."
- type: textarea
id: env
attributes:
label: Environment file
description: "Please include your `.env` config file (**make sure to remove/replace all secrets**)"
render: shell
- type: textarea
id: docker-compose
attributes:
label: Docker-Compose file
description: "When running with docker compose please provide your `docker-compose.yml`"
render: shell
- type: textarea
id: logs
attributes:
label: Relevant logs
description: "If you feel like there is anything interesting please post the output of `docker-compose logs` at container startup and when the issue happens."
render: shell
================================================
FILE: .github/ISSUE_TEMPLATE/url_import.md.bak
================================================
---
name: Website Import
about: Anything related to website imports
title: ''
labels: enhancement, url_import
assignees: ''
---
### Version
Please provide your current version (can be found on the system page since v0.8.4)
Version:
### Information
Exact URL you are trying to import from:
When did the issue happen: When pressing the search button with the url / when importing after the page has loaded
Response/Message shown
```
Message
```
================================================
FILE: .github/ISSUE_TEMPLATE/website_import.yml
================================================
name: Website Import
description: "Anything related to website imports"
#title: ""
#labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this website import form!
- type: input
id: version
attributes:
label: Tandoor Version
description: "What version of Tandoor are you using? (can be found on the system page since v0.8.4)"
validations:
required: true
- type: input
id: url
attributes:
label: Import URL
description: "Exact URL you are trying to import from."
validations:
required: true
- type: textarea
id: bug-descr
attributes:
label: "When did the issue happen?"
description: "When pressing the search button with the url / when importing after the page has loaded / ..."
validations:
required: true
- type: textarea
id: logs
attributes:
label: Response / message shown
description: Please copy and paste any relevant logs or responses / messages which are shown in Tandoor. This will be automatically formatted into code, so no need for backticks.
render: shell
================================================
FILE: .github/dependabot.yml
================================================
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "devcontainers"
directory: "/"
schedule:
interval: weekly
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "monthly"
- package-ecosystem: "npm"
directory: "/vue3/"
schedule:
interval: "monthly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
================================================
FILE: .github/workflows/build-docker.yml
================================================
name: Build Docker Container
on: push
jobs:
build-container:
name: Build ${{ matrix.name }} Container
runs-on: ubuntu-latest
if: github.repository_owner == 'TandoorRecipes'
continue-on-error: ${{ matrix.continue-on-error }}
permissions:
contents: read
packages: write
strategy:
matrix:
include:
# Standard build config
- name: Standard
dockerfile: Dockerfile
platforms: linux/amd64,linux/arm64
suffix: ""
continue-on-error: false
steps:
- uses: actions/checkout@v6
- name: Get version number
id: get_version
run: |
if [[ "$GITHUB_REF" = refs/tags/* ]]; then
echo "VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT
elif [[ "$GITHUB_REF" = refs/heads/beta ]]; then
echo VERSION=beta >> $GITHUB_OUTPUT
else
echo VERSION=develop >> $GITHUB_OUTPUT
fi
# Build Vue 3 frontend
- uses: actions/setup-node@v6
with:
node-version: '22'
cache: yarn
cache-dependency-path: vue3/yarn.lock
- name: Install dependencies
working-directory: ./vue3
run: yarn install --frozen-lockfile
- name: Build dependencies
working-directory: ./vue3
run: yarn build
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
if: github.secret_source == 'Actions'
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
if: github.secret_source == 'Actions'
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: |
vabene1111/recipes
ghcr.io/TandoorRecipes/recipes
flavor: |
latest=false
suffix=${{ matrix.suffix }}
tags: |
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/') }}
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=ref,event=branch
- name: Build and Push
uses: docker/build-push-action@v5
with:
context: .
file: ${{ matrix.dockerfile }}
pull: true
push: ${{ github.secret_source == 'Actions' }}
platforms: ${{ matrix.platforms }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
notify-stable:
name: Notify Stable
runs-on: ubuntu-latest
needs: build-container
if: startsWith(github.ref, 'refs/tags/')
steps:
- name: Set tag name
run: |
# Strip "refs/tags/" prefix
echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
# Send stable discord notification
- name: Discord notification
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_RELEASE_WEBHOOK }}
uses: Ilshidur/action-discord@0.4.0
with:
args: '🚀 Version {{ VERSION }} of tandoor has been released 🥳 Check it out https://github.com/vabene1111/recipes/releases/tag/{{ VERSION }}'
notify-beta:
name: Notify Beta
runs-on: ubuntu-latest
needs: build-container
if: github.ref == 'refs/heads/beta'
steps:
# Send beta discord notification
- name: Discord notification
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_BETA_WEBHOOK }}
uses: Ilshidur/action-discord@0.4.0
with:
args: '🚀 The Tandoor 2 Image has been updated! 🥳'
================================================
FILE: .github/workflows/ci.yml
================================================
name: Continuous Integration
on: [push, pull_request]
jobs:
build:
if: github.repository_owner == 'TandoorRecipes'
runs-on: ubuntu-latest
strategy:
max-parallel: 4
matrix:
python-version: ["3.12"]
node-version: ["22"]
steps:
- uses: actions/checkout@v6
- uses: awalsh128/cache-apt-pkgs-action@v1.6.0
with:
packages: libsasl2-dev python3-dev libxml2-dev libxmlsec1-dev libxslt-dev libxmlsec1-openssl libxslt-dev libldap2-dev libssl-dev gcc musl-dev postgresql-dev zlib-dev jpeg-dev libwebp-dev openssl-dev libffi-dev cargo openldap-dev python3-dev xmlsec-dev xmlsec build-base g++ curl
version: 1.0
# Setup python & dependencies
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
cache: "pip"
- name: Install Python Dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Cache StaticFiles
uses: actions/cache@v5
id: django_cache
with:
path: |
./cookbook/static
./staticfiles
key: |
${{ runner.os }}-${{ matrix.python-version }}-${{ matrix.node-version }}-collectstatic-${{ hashFiles('**/*.css', '**/*.js', 'vue3/src/*') }}
# Build Vue frontend & Dependencies
- name: Set up Node ${{ matrix.node-version }}
if: steps.django_cache.outputs.cache-hit != 'true'
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
cache: "yarn"
cache-dependency-path: ./vue3/yarn.lock
- name: Install Vue dependencies
if: steps.django_cache.outputs.cache-hit != 'true'
working-directory: ./vue3
run: yarn install
- name: Build Vue dependencies
if: steps.django_cache.outputs.cache-hit != 'true'
working-directory: ./vue3
run: yarn build
- name: Compile Django StaticFiles
if: steps.django_cache.outputs.cache-hit != 'true'
run: |
python3 manage.py collectstatic --noinput
- uses: actions/cache/save@v5
if: steps.django_cache.outputs.cache-hit != 'true'
with:
path: |
./cookbook/static
./staticfiles
key: |
${{ runner.os }}-${{ matrix.python-version }}-${{ matrix.node-version }}-collectstatic-${{ hashFiles('**/*.css', '**/*.js', 'vue/src/*') }}
- name: Django Testing project
run: pytest --junitxml=junit/test-results-${{ matrix.python-version }}.xml
================================================
FILE: .github/workflows/codeql-analysis.yml
================================================
name: "Code scanning - action"
on:
push:
pull_request:
schedule:
- cron: '0 13 * * 2'
jobs:
CodeQL-Build:
if: github.repository_owner == 'TandoorRecipes'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
# Override language selection by uncommenting this and choosing your languages
with:
languages: python, javascript
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
# - name: Autobuild
# uses: github/codeql-action/autobuild@v1
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
with:
languages: javascript, python
================================================
FILE: .github/workflows/docs.yml
================================================
name: Make Docs
on:
# the 1st condition
workflow_run:
workflows: ["Continuous Integration"]
branches: [master]
types:
- completed
jobs:
deploy:
if: github.repository_owner == 'TandoorRecipes' && ${{ github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: 3.x
- run: pip install mkdocs-material mkdocs-include-markdown-plugin
- run: mkdocs gh-deploy --force
================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
docs/reports/**
# Django stuff:
*.log
mediafiles/
*.sqlite3*
staticfiles/
postgresql/
data/
# Sphinx documentation
docs/_build/
# PyBuilder
target/
\.idea/dataSources/
\.idea/dataSources\.xml
\.idea/dataSources\.local\.xml
\.idea/workspace\.xml
\.idea/misc\.xml
# Deployment
\.env
cookbook/static/vue
vue/webpack-stats.json
/docker-compose.override.yml
vue/node_modules
/recipes/plugins
vetur.config.js
cookbook/static/vue
vue/webpack-stats.json
cookbook/templates/sw.js
vue/.yarn
vue3/.vite
# Configs
vetur.config.js
venv/
.idea/easy-i18n.xml
cookbook/static/vue3
vue3/node_modules
cookbook/tests/other/docs/reports/tests/tests.html
cookbook/tests/other/docs/reports/tests/pytest.xml
vue3/src/plugins
docs/reference/
.idea/vcs.xml
.idea/db-forest-config.xml
================================================
FILE: .idea/codeStyles/codeStyleConfig.xml
================================================
Nginx-Container-Logs
```
Replace me with logs
```
Tandoor Recipes
The recipe manager that allows you to manage your ever growing collection of digital recipes.
## License
Beginning with version 0.10.0 the code in this repository is licensed under the [GNU AGPL v3](https://www.gnu.org/licenses/agpl-3.0.de.html) license with a
[common clause](https://commonsclause.com/) selling exception. See [LICENSE.md](https://github.com/vabene1111/recipes/blob/develop/LICENSE.md) for details.
> NOTE: There appears to be a whole range of legal issues with licensing anything other than the standard completely open licenses.
> I am in the process of getting some professional legal advice to sort out these issues.
> Please also see [Issue 238](https://github.com/vabene1111/recipes/issues/238) for some discussion and **reasoning** regarding the topic.
**Reasoning**
**This software and *all* its features are and will always be free for everyone to use and enjoy.**
The reason for the selling exception is that a significant amount of time was spend over multiple years to develop this software.
A paid hosted version which will be identical in features and code base to the software offered in this repository will
likely be released in the future (including all features needed to sell a hosted version as they might also be useful for personal use).
This will not only benefit me personally but also everyone who self-hosts this software as any profits made through selling the hosted option
allow me to spend more time developing and improving the software for everyone. Selling exceptions are [approved by Richard Stallman](http://www.gnu.org/philosophy/selling-exceptions.en.html) and the
common clause license is very permissive (see the [FAQ](https://commonsclause.com/)).
================================================
FILE: SECURITY.md
================================================
# Security Policy
## Supported Versions
Since this software is still considered beta/WIP support is always only given for the latest version. There are no backports of security or any other fixes.
## Reporting a Vulnerability
Please use GitHub Security Advisories to report any kind of security vulnerabilities.
================================================
FILE: boot.sh
================================================
#!/bin/sh
source venv/bin/activate
# these are envsubst in the nginx config, make sure they default to something sensible when unset
export TANDOOR_PORT="${TANDOOR_PORT:-80}"
export MEDIA_ROOT=${MEDIA_ROOT:-/opt/recipes/mediafiles};
export STATIC_ROOT=${STATIC_ROOT:-/opt/recipes/staticfiles};
GUNICORN_WORKERS="${GUNICORN_WORKERS:-3}"
GUNICORN_THREADS="${GUNICORN_THREADS:-2}"
GUNICORN_LOG_LEVEL="${GUNICORN_LOG_LEVEL:-'info'}"
PLUGINS_BUILD="${PLUGINS_BUILD:-0}"
display_warning() {
echo "[WARNING]"
echo -e "$1"
}
# prepare nginx config
envsubst '$MEDIA_ROOT $STATIC_ROOT $TANDOOR_PORT' < /opt/recipes/http.d/Recipes.conf.template > /opt/recipes/http.d/Recipes.conf
# start nginx early to display error pages with writable location as non-root
echo "Starting nginx"
nginx -g 'pid /tmp/nginx.pid;'
echo "Checking configuration..."
# SECRET_KEY (or a valid file at SECRET_KEY_FILE) must be set in .env file
if [ -f "${SECRET_KEY_FILE}" ]; then
export SECRET_KEY=$(cat "$SECRET_KEY_FILE")
fi
if [ -z "${SECRET_KEY}" ]; then
display_warning "The environment variable 'SECRET_KEY' (or 'SECRET_KEY_FILE' that points to an existing file) is not set but REQUIRED for running Tandoor!"
fi
if [ -f "${AUTH_LDAP_BIND_PASSWORD_FILE}" ]; then
export AUTH_LDAP_BIND_PASSWORD=$(cat "$AUTH_LDAP_BIND_PASSWORD_FILE")
fi
if [ -f "${EMAIL_HOST_PASSWORD_FILE}" ]; then
export EMAIL_HOST_PASSWORD=$(cat "$EMAIL_HOST_PASSWORD_FILE")
fi
if [ -f "${SOCIALACCOUNT_PROVIDERS_FILE}" ]; then
export SOCIALACCOUNT_PROVIDERS=$(cat "$SOCIALACCOUNT_PROVIDERS_FILE")
fi
if [ -f "${S3_SECRET_ACCESS_KEY_FILE}" ]; then
export S3_SECRET_ACCESS_KEY=$(cat "$S3_SECRET_ACCESS_KEY_FILE")
fi
echo "Waiting for database to be ready..."
attempt=0
max_attempts=20
if [ "${DB_ENGINE}" == 'django.db.backends.postgresql' ] || [ "${DATABASE_URL}" == 'postgres'* ]; then
# POSTGRES_PASSWORD (or a valid file at POSTGRES_PASSWORD_FILE) must be set in .env file
if [ -f "${POSTGRES_PASSWORD_FILE}" ]; then
export POSTGRES_PASSWORD=$(cat "$POSTGRES_PASSWORD_FILE")
fi
if [ -z "${POSTGRES_PASSWORD}" ]; then
display_warning "The environment variable 'POSTGRES_PASSWORD' (or 'POSTGRES_PASSWORD_FILE' that points to an existing file) is not set but REQUIRED for running Tandoor!"
fi
while pg_isready --host=${POSTGRES_HOST} --port=${POSTGRES_PORT} --user=${POSTGRES_USER} -q; status=$?; attempt=$((attempt+1)); [ $status -ne 0 ] && [ $attempt -le $max_attempts ]; do
sleep 5
done
fi
if [ $attempt -gt $max_attempts ]; then
echo -e "\nDatabase not reachable. Maximum attempts exceeded."
echo "Please check logs above - misconfiguration is very likely."
echo "Make sure the DB container is up and POSTGRES_HOST is set properly."
echo "Shutting down container."
exit 1 # exit with error to make the container stop
fi
echo "Database is ready"
echo "Migrating database"
python manage.py migrate
if [ "${PLUGINS_BUILD}" -eq 1 ]; then
echo "Running yarn build at startup because PLUGINS_BUILD is enabled"
python plugin.py
fi
echo "Collecting static files, this may take a while..."
python manage.py collectstatic --noinput --clear
echo "Done"
chmod -R 755 ${MEDIA_ROOT:-/opt/recipes/mediafiles}
ipv6_disable=$(cat /sys/module/ipv6/parameters/disable)
echo "Starting gunicorn"
# --umask parameter isn't respected when gunicorn is running in foreground, needed for when users change to non root users
# use /tmp as directory since that is writable as a non-root user
# https://github.com/benoitc/gunicorn/issues/2245
umask 0 && exec gunicorn --bind unix:/tmp/tandoor.sock --workers $GUNICORN_WORKERS --threads $GUNICORN_THREADS --timeout ${GUNICORN_TIMEOUT:-30} --access-logfile - --error-logfile - --log-level $GUNICORN_LOG_LEVEL recipes.wsgi
================================================
FILE: cookbook/__init__.py
================================================
================================================
FILE: cookbook/admin.py
================================================
from django.conf import settings
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import Group, User
from django.contrib.postgres.search import SearchVector
from django.utils import translation
from django_scopes import scopes_disabled
from treebeard.admin import TreeAdmin
from treebeard.forms import movenodeform_factory
from allauth.account.decorators import secure_admin_login
from cookbook.managers import DICTIONARY
from .models import (BookmarkletImport, Comment, CookLog, CustomFilter, Food, ImportLog, Ingredient, InviteLink,
Keyword, MealPlan, MealType, NutritionInformation, Property, PropertyType,
Recipe, RecipeBook, RecipeBookEntry, RecipeImport, SearchPreference, ShareLink,
ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage,
Supermarket, SupermarketCategory, SupermarketCategoryRelation, Sync, SyncLog,
TelegramBot, Unit, UnitConversion, UserFile, UserPreference, UserSpace,
ViewLog, ConnectorConfig, AiProvider, AiLog)
admin.site.login = secure_admin_login(admin.site.login)
class CustomUserAdmin(UserAdmin):
def has_add_permission(self, request, obj=None):
return False
admin.site.unregister(User)
admin.site.register(User, CustomUserAdmin)
admin.site.unregister(Group)
@admin.action(description='Delete all data from a space')
def delete_space_action(modeladmin, request, queryset):
for space in queryset:
space.safe_delete()
class SpaceAdmin(admin.ModelAdmin):
list_display = ('name', 'created_by', 'max_recipes', 'max_users', 'max_file_storage_mb', 'allow_sharing')
search_fields = ('name', 'created_by__username')
autocomplete_fields = ('created_by',)
filter_horizontal = ('food_inherit',)
list_filter = ('max_recipes', 'max_users', 'max_file_storage_mb', 'allow_sharing')
date_hierarchy = 'created_at'
actions = [delete_space_action]
admin.site.register(Space, SpaceAdmin)
class UserSpaceAdmin(admin.ModelAdmin):
list_display = ('user', 'space',)
search_fields = ('user__username', 'space__name',)
filter_horizontal = ('groups',)
autocomplete_fields = ('user', 'space',)
admin.site.register(UserSpace, UserSpaceAdmin)
class UserPreferenceAdmin(admin.ModelAdmin):
list_display = ('name', 'theme', 'default_page')
search_fields = ('user__username',)
list_filter = ('theme', 'default_page',)
date_hierarchy = 'created_at'
filter_horizontal = ('plan_share', 'shopping_share',)
@staticmethod
def name(obj):
return obj.user.get_user_display_name()
admin.site.register(UserPreference, UserPreferenceAdmin)
class SearchPreferenceAdmin(admin.ModelAdmin):
list_display = ('name', 'search', 'trigram_threshold',)
search_fields = ('user__username',)
list_filter = ('search',)
@staticmethod
def name(obj):
return obj.user.get_user_display_name()
admin.site.register(SearchPreference, SearchPreferenceAdmin)
class AiProviderAdmin(admin.ModelAdmin):
list_display = ('name', 'space', 'model_name',)
search_fields = ('name', 'space', 'model_name',)
admin.site.register(AiProvider, AiProviderAdmin)
class AiLogAdmin(admin.ModelAdmin):
list_display = ('ai_provider', 'function', 'credit_cost', 'created_by', 'created_at',)
admin.site.register(AiLog, AiLogAdmin)
class StorageAdmin(admin.ModelAdmin):
list_display = ('name', 'method')
search_fields = ('name',)
admin.site.register(Storage, StorageAdmin)
class ConnectorConfigAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'type', 'enabled', 'url')
search_fields = ('name', 'url')
admin.site.register(ConnectorConfig, ConnectorConfigAdmin)
class CustomFilterAdmin(admin.ModelAdmin):
list_display = ('id', 'type', 'name')
admin.site.register(CustomFilter, CustomFilterAdmin)
class SyncAdmin(admin.ModelAdmin):
list_display = ('storage', 'path', 'active', 'last_checked')
search_fields = ('storage__name', 'path')
admin.site.register(Sync, SyncAdmin)
class SupermarketCategoryInline(admin.TabularInline):
model = SupermarketCategoryRelation
class SupermarketAdmin(admin.ModelAdmin):
list_display = ('name', 'space',)
inlines = (SupermarketCategoryInline,)
class SupermarketCategoryAdmin(admin.ModelAdmin):
list_display = ('name', 'space',)
admin.site.register(Supermarket, SupermarketAdmin)
admin.site.register(SupermarketCategory, SupermarketCategoryAdmin)
class SyncLogAdmin(admin.ModelAdmin):
list_display = ('sync', 'status', 'msg', 'created_at')
admin.site.register(SyncLog, SyncLogAdmin)
@admin.action(description='Temporarily ENABLE sorting on Foods and Keywords.')
def enable_tree_sorting(modeladmin, request, queryset):
Food.node_order_by = ['name']
Keyword.node_order_by = ['name']
with scopes_disabled():
Food.fix_tree(fix_paths=True)
Keyword.fix_tree(fix_paths=True)
@admin.action(description='Temporarily DISABLE sorting on Foods and Keywords.')
def disable_tree_sorting(modeladmin, request, queryset):
Food.node_order_by = []
Keyword.node_order_by = []
@admin.action(description='Fix problems and sort tree by name')
def sort_tree(modeladmin, request, queryset):
orginal_value = modeladmin.model.node_order_by[:]
modeladmin.model.node_order_by = ['name']
with scopes_disabled():
modeladmin.model.fix_tree(fix_paths=True)
modeladmin.model.node_order_by = orginal_value
class KeywordAdmin(TreeAdmin):
form = movenodeform_factory(Keyword)
ordering = ('space', 'path',)
search_fields = ('name',)
actions = [sort_tree, enable_tree_sorting, disable_tree_sorting]
admin.site.register(Keyword, KeywordAdmin)
@admin.action(description='Delete Steps not part of a Recipe.')
def delete_unattached_steps(modeladmin, request, queryset):
with scopes_disabled():
Step.objects.filter(recipe=None).delete()
class StepAdmin(admin.ModelAdmin):
list_display = ('recipe_and_name', 'order', 'space')
ordering = ('recipe__name', 'name', 'space',)
search_fields = ('name', 'recipe__name')
actions = [delete_unattached_steps]
@staticmethod
@admin.display(description="Name")
def recipe_and_name(obj):
if not obj.recipe_set.exists():
return "Orphaned Step" + ('' if not obj.name else f': {obj.name}')
return f"{obj.recipe_set.first().name}: {obj.name}" if obj.name else obj.recipe_set.first().name
admin.site.register(Step, StepAdmin)
@admin.action(description='Rebuild index for selected recipes')
def rebuild_index(modeladmin, request, queryset):
language = DICTIONARY.get(translation.get_language(), 'simple')
with scopes_disabled():
Recipe.objects.all().update(
name_search_vector=SearchVector('name__unaccent', weight='A', config=language),
desc_search_vector=SearchVector('description__unaccent', weight='B', config=language)
)
Step.objects.all().update(search_vector=SearchVector('instruction__unaccent', weight='B', config=language))
class RecipeAdmin(admin.ModelAdmin):
list_display = ('name', 'internal', 'created_by', 'storage', 'space')
search_fields = ('name', 'created_by__username')
ordering = ('name', 'created_by__username',)
list_filter = ('internal',)
date_hierarchy = 'created_at'
@staticmethod
def created_by(obj):
return obj.created_by.get_user_display_name()
if settings.DATABASES['default']['ENGINE'] == 'django.db.backends.postgresql':
actions = [rebuild_index]
admin.site.register(Recipe, RecipeAdmin)
class UnitAdmin(admin.ModelAdmin):
list_display = ('name', 'space')
ordering = ('name', 'space',)
search_fields = ('name',)
admin.site.register(Unit, UnitAdmin)
# admin.site.register(FoodInheritField)
class FoodAdmin(TreeAdmin):
form = movenodeform_factory(Keyword)
ordering = ('space', 'path',)
search_fields = ('name',)
actions = [sort_tree, enable_tree_sorting, disable_tree_sorting]
admin.site.register(Food, FoodAdmin)
class UnitConversionAdmin(admin.ModelAdmin):
list_display = ('base_amount', 'base_unit', 'food', 'converted_amount', 'converted_unit')
search_fields = ('food__name', 'unit__name')
admin.site.register(UnitConversion, UnitConversionAdmin)
@admin.action(description='Delete Ingredients not part of a Recipe.')
def delete_unattached_ingredients(modeladmin, request, queryset):
with scopes_disabled():
Ingredient.objects.filter(step__recipe=None).delete()
class IngredientAdmin(admin.ModelAdmin):
list_display = ('recipe_name', 'amount', 'unit', 'food', 'space')
search_fields = ('food__name', 'unit__name', 'step__recipe__name')
actions = [delete_unattached_ingredients]
@staticmethod
@admin.display(description="Recipe")
def recipe_name(obj):
recipes = obj.step_set.first().recipe_set.all() if obj.step_set.exists() else None
return recipes.first().name if recipes else 'Orphaned Ingredient'
admin.site.register(Ingredient, IngredientAdmin)
class CommentAdmin(admin.ModelAdmin):
list_display = ('recipe', 'name', 'created_at')
search_fields = ('text', 'created_by__username')
date_hierarchy = 'created_at'
@staticmethod
def name(obj):
return obj.created_by.get_user_display_name()
admin.site.register(Comment, CommentAdmin)
class RecipeImportAdmin(admin.ModelAdmin):
list_display = ('name', 'storage', 'file_path')
admin.site.register(RecipeImport, RecipeImportAdmin)
class RecipeBookAdmin(admin.ModelAdmin):
list_display = ('name', 'user_name', 'space')
search_fields = ('name', 'created_by__username')
@staticmethod
def user_name(obj):
return obj.created_by.get_user_display_name()
admin.site.register(RecipeBook, RecipeBookAdmin)
class RecipeBookEntryAdmin(admin.ModelAdmin):
list_display = ('book', 'recipe')
admin.site.register(RecipeBookEntry, RecipeBookEntryAdmin)
class MealPlanAdmin(admin.ModelAdmin):
list_display = ('user', 'recipe', 'meal_type', 'from_date', 'to_date')
@staticmethod
def user(obj):
return obj.created_by.get_user_display_name()
admin.site.register(MealPlan, MealPlanAdmin)
class MealTypeAdmin(admin.ModelAdmin):
list_display = ('name', 'space', 'created_by', 'order')
search_fields = ('name', 'space', 'created_by__username')
admin.site.register(MealType, MealTypeAdmin)
class ViewLogAdmin(admin.ModelAdmin):
list_display = ('recipe', 'created_by', 'created_at')
admin.site.register(ViewLog, ViewLogAdmin)
class InviteLinkAdmin(admin.ModelAdmin):
list_display = (
'group', 'valid_until', 'space',
'created_by', 'created_at', 'used_by'
)
admin.site.register(InviteLink, InviteLinkAdmin)
class CookLogAdmin(admin.ModelAdmin):
list_display = ('recipe', 'created_by', 'created_at', 'rating', 'servings')
search_fields = ('recipe__name', 'space__name',)
admin.site.register(CookLog, CookLogAdmin)
class ShoppingListRecipeAdmin(admin.ModelAdmin):
list_display = ('id', 'recipe', 'servings')
admin.site.register(ShoppingListRecipe, ShoppingListRecipeAdmin)
class ShoppingListEntryAdmin(admin.ModelAdmin):
list_display = ('id', 'food', 'unit', 'list_recipe', 'created_by', 'created_at', 'checked')
admin.site.register(ShoppingListEntry, ShoppingListEntryAdmin)
class ShareLinkAdmin(admin.ModelAdmin):
list_display = ('recipe', 'created_by', 'uuid', 'created_at',)
admin.site.register(ShareLink, ShareLinkAdmin)
@admin.action(description='Delete all properties with type')
def delete_properties_with_type(modeladmin, request, queryset):
for pt in queryset:
Property.objects.filter(property_type=pt).delete()
class PropertyTypeAdmin(admin.ModelAdmin):
search_fields = ('name',)
list_display = ('id', 'space', 'name', 'fdc_id')
actions = [delete_properties_with_type]
admin.site.register(PropertyType, PropertyTypeAdmin)
class PropertyAdmin(admin.ModelAdmin):
list_display = ('property_amount', 'property_type')
admin.site.register(Property, PropertyAdmin)
class NutritionInformationAdmin(admin.ModelAdmin):
list_display = ('id',)
admin.site.register(NutritionInformation, NutritionInformationAdmin)
class ImportLogAdmin(admin.ModelAdmin):
list_display = ('id', 'type', 'running', 'created_by', 'created_at',)
admin.site.register(ImportLog, ImportLogAdmin)
class TelegramBotAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'created_by',)
admin.site.register(TelegramBot, TelegramBotAdmin)
class BookmarkletImportAdmin(admin.ModelAdmin):
list_display = ('id', 'url', 'created_by', 'created_at',)
admin.site.register(BookmarkletImport, BookmarkletImportAdmin)
class UserFileAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'file_size_kb', 'created_at',)
admin.site.register(UserFile, UserFileAdmin)
================================================
FILE: cookbook/apps.py
================================================
import traceback
from django.apps import AppConfig
from django.conf import settings
from django.db import OperationalError, ProgrammingError
from django.db.models.signals import post_save, post_delete
from django_scopes import scopes_disabled
from recipes.settings import DEBUG
class CookbookConfig(AppConfig):
name = 'cookbook'
def ready(self):
import cookbook.signals # noqa
if not settings.DISABLE_EXTERNAL_CONNECTORS:
from cookbook.connectors.connector_manager import ConnectorManager # Needs to be here to prevent loading race condition of oauth2 modules in models.py
handler = ConnectorManager()
post_save.connect(handler, dispatch_uid="post_save-connector_manager")
post_delete.connect(handler, dispatch_uid="post_delete-connector_manager")
# if not settings.DISABLE_TREE_FIX_STARTUP:
# # when starting up run fix_tree to:
# # a) make sure that nodes are sorted when switching between sort modes
# # b) fix problems, if any, with tree consistency
# with scopes_disabled():
# try:
# from cookbook.models import Food, Keyword
# Keyword.fix_tree(fix_paths=True)
# Food.fix_tree(fix_paths=True)
# except OperationalError:
# if DEBUG:
# traceback.print_exc()
# pass # if model does not exist there is no need to fix it
# except ProgrammingError:
# if DEBUG:
# traceback.print_exc()
# pass # if migration has not been run database cannot be fixed yet
# except Exception:
# if DEBUG:
# traceback.print_exc()
# pass # dont break startup just because fix could not run, need to investigate cases when this happens
================================================
FILE: cookbook/connectors/__init__.py
================================================
================================================
FILE: cookbook/connectors/connector.py
================================================
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Optional
from cookbook.models import ShoppingListEntry, User, ConnectorConfig
@dataclass
class UserDTO:
username: str
first_name: Optional[str]
@staticmethod
def create_from_user(instance: User) -> 'UserDTO':
return UserDTO(
username=instance.username,
first_name=instance.first_name if instance.first_name else None
)
@dataclass
class ShoppingListEntryDTO:
food_name: str
amount: Optional[float]
base_unit: Optional[str]
unit_name: Optional[str]
created_by: UserDTO
@staticmethod
def try_create_from_entry(instance: ShoppingListEntry) -> Optional['ShoppingListEntryDTO']:
if instance.food is None or instance.created_by is None:
return None
return ShoppingListEntryDTO(
food_name=instance.food.name,
amount=instance.amount if instance.amount else None,
unit_name=instance.unit.name if instance.unit else None,
base_unit=instance.unit.base_unit if instance.unit and instance.unit.base_unit else None,
created_by=UserDTO.create_from_user(instance.created_by),
)
# A Connector is 'destroyed' & recreated each time 'any' ConnectorConfig in a space changes.
class Connector(ABC):
@abstractmethod
def __init__(self, config: ConnectorConfig):
pass
@abstractmethod
async def on_shopping_list_entry_created(self, instance: ShoppingListEntryDTO) -> None:
pass
# This method might not trigger on 'direct' entry updates: https://stackoverflow.com/a/35238823
@abstractmethod
async def on_shopping_list_entry_updated(self, instance: ShoppingListEntryDTO) -> None:
pass
@abstractmethod
async def on_shopping_list_entry_deleted(self, instance: ShoppingListEntryDTO) -> None:
pass
@abstractmethod
async def close(self) -> None:
pass
================================================
FILE: cookbook/connectors/connector_manager.py
================================================
import asyncio
import logging
import queue
import threading
from asyncio import Task
from dataclasses import dataclass
from enum import Enum
from logging import Logger
from types import UnionType
from typing import List, Any, Dict, Optional, Type
from django.conf import settings
from django_scopes import scope
from cookbook.connectors.connector import Connector, ShoppingListEntryDTO
from cookbook.connectors.homeassistant import HomeAssistant
from cookbook.models import ShoppingListEntry, Space, ConnectorConfig
REGISTERED_CLASSES: UnionType | Type = ShoppingListEntry
class ActionType(Enum):
CREATED = 1
UPDATED = 2
DELETED = 3
@dataclass
class Work:
instance: REGISTERED_CLASSES | ConnectorConfig
actionType: ActionType
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
# The way ConnectionManager works is as follows:
# 1. On init, it starts a worker & creates a queue for 'Work'
# 2. Then any time its called, it verifies the type of action (create/update/delete) and if the item is of interest, pushes the Work (non-blocking) to the queue.
# 3. The worker consumes said work from the queue.
# 3.1 If the work is of type ConnectorConfig, it flushes its cache of known connectors (per space.id)
# 3.2 If work is of type REGISTERED_CLASSES, it asynchronously fires of all connectors and wait for them to finish (runtime should depend on the 'slowest' connector)
# 4. Work is marked as consumed, and next entry of the queue is consumed.
# Each 'Work' is processed in sequential by the worker, so the throughput is about [workers * the slowest connector]
# The Singleton class is used for ConnectorManager to have a self-reference and so Python does not garbage collect it
class ConnectorManager(metaclass=Singleton):
_logger: Logger
_queue: queue.Queue
_listening_to_classes = REGISTERED_CLASSES | ConnectorConfig
def __init__(self):
self._logger = logging.getLogger("recipes.connector")
self._logger.debug("ConnectorManager initializing")
self._queue = queue.Queue(maxsize=settings.EXTERNAL_CONNECTORS_QUEUE_SIZE)
self._worker = threading.Thread(target=self.worker, args=(0, self._queue,), daemon=True)
self._worker.start()
# Called by post save & post delete signals
def __call__(self, instance: Any, **kwargs) -> None:
action_type: ActionType
if "created" in kwargs and kwargs["created"]:
action_type = ActionType.CREATED
elif "created" in kwargs and not kwargs["created"]:
action_type = ActionType.UPDATED
elif "origin" in kwargs:
action_type = ActionType.DELETED
else:
return
self._add_work(action_type, instance)
def _add_work(self, action_type: ActionType, *instances: REGISTERED_CLASSES):
for instance in instances:
if not isinstance(instance, self._listening_to_classes) or not hasattr(instance, "space"):
continue
try:
_force_load_instance(instance)
self._queue.put_nowait(Work(instance, action_type))
except queue.Full:
self._logger.info(f"queue was full, so skipping {action_type} of type {type(instance)}")
def stop(self):
self._queue.put(None)
self._worker.join(timeout=5)
@classmethod
def is_initialized(cls):
return cls in cls._instances
@staticmethod
def add_work(action_type: ActionType, *instances: REGISTERED_CLASSES):
"""
Manually inject work that failed to come in through the __call__ (aka Django signal)
Before the work is processed, we check if the connectionManager is initialized, because if it's not, we don't want to accidentally initialize it.
Be careful calling it, because it might result in a instance being processed twice.
"""
if not ConnectorManager.is_initialized():
return
ConnectorManager()._add_work(action_type, *instances)
@staticmethod
def worker(worker_id: int, worker_queue: queue.Queue):
logger = logging.getLogger("recipes.connector.worker")
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
logger.info(f"started ConnectionManager worker {worker_id}")
# When multiple workers are used, please make sure the cache is shared across all threads, otherwise it might lead to un-expected behavior.
_connectors_cache: Dict[int, List[Connector]] = dict()
while True:
try:
item: Optional[Work] = worker_queue.get()
except KeyboardInterrupt:
break
if item is None:
break
logger.debug(f"received {item.instance=} with {item.actionType=}")
# If a Connector was changed/updated, refresh connector from the database for said space
refresh_connector_cache = isinstance(item.instance, ConnectorConfig)
space: Space = item.instance.space
connectors: Optional[List[Connector]] = _connectors_cache.get(space.id)
if connectors is None or refresh_connector_cache:
if connectors is not None:
loop.run_until_complete(_close_connectors(connectors))
with scope(space=space):
connectors: List[Connector] = list()
for config in space.connectorconfig_set.all():
config: ConnectorConfig = config
if not config.enabled:
continue
try:
connector: Optional[Connector] = ConnectorManager.get_connected_for_config(config)
except BaseException:
logger.exception(f"failed to initialize {config.name}")
continue
if connector is not None:
connectors.append(connector)
_connectors_cache[space.id] = connectors
if len(connectors) == 0 or refresh_connector_cache:
worker_queue.task_done()
continue
logger.debug(f"running {len(connectors)} connectors for {item.instance=} with {item.actionType=}")
loop.run_until_complete(run_connectors(connectors, item.instance, item.actionType))
worker_queue.task_done()
logger.info(f"terminating ConnectionManager worker {worker_id}")
from django.db import connection
connection.close()
asyncio.set_event_loop(None)
loop.close()
@staticmethod
def get_connected_for_config(config: ConnectorConfig) -> Optional[Connector]:
match config.type:
case ConnectorConfig.HOMEASSISTANT:
return HomeAssistant(config)
case _:
return None
def _force_load_instance(instance: REGISTERED_CLASSES):
if isinstance(instance, ShoppingListEntry):
_ = instance.food # Force load food
_ = instance.unit # Force load unit
_ = instance.created_by # Force load created_by
async def _close_connectors(connectors: List[Connector]):
tasks: List[Task] = [asyncio.create_task(connector.close()) for connector in connectors]
if len(tasks) == 0:
return
try:
await asyncio.gather(*tasks, return_exceptions=False)
except BaseException:
logging.exception("received an exception while closing one of the connectors")
async def run_connectors(connectors: List[Connector], instance: REGISTERED_CLASSES, action_type: ActionType):
tasks: List[Task] = list()
if isinstance(instance, ShoppingListEntry):
shopping_list_entry = ShoppingListEntryDTO.try_create_from_entry(instance)
if shopping_list_entry is None:
return
match action_type:
case ActionType.CREATED:
for connector in connectors:
tasks.append(asyncio.create_task(connector.on_shopping_list_entry_created(shopping_list_entry)))
case ActionType.UPDATED:
for connector in connectors:
tasks.append(asyncio.create_task(connector.on_shopping_list_entry_updated(shopping_list_entry)))
case ActionType.DELETED:
for connector in connectors:
tasks.append(asyncio.create_task(connector.on_shopping_list_entry_deleted(shopping_list_entry)))
if len(tasks) == 0:
return
try:
# Wait for all async tasks to finish, if one fails, the others still continue.
await asyncio.gather(*tasks, return_exceptions=False)
except BaseException:
logging.exception("received an exception from one of the connectors")
================================================
FILE: cookbook/connectors/homeassistant.py
================================================
import logging
from logging import Logger
from typing import Dict, Tuple
from urllib.parse import urljoin
from aiohttp import request, ClientResponseError
from cookbook.connectors.connector import Connector, ShoppingListEntryDTO
from cookbook.models import ConnectorConfig
class HomeAssistant(Connector):
_config: ConnectorConfig
_logger: Logger
def __init__(self, config: ConnectorConfig):
if not config.token or not config.url or not config.todo_entity:
raise ValueError("config for HomeAssistantConnector in incomplete")
self._logger = logging.getLogger(f"recipes.connector.homeassistant.{config.name}")
if config.url[-1] != "/":
config.url += "/"
self._config = config
async def homeassistant_api_call(self, method: str, path: str, data: Dict) -> str:
headers = {
"Authorization": f"Bearer {self._config.token}",
"Content-Type": "application/json"
}
async with request(method, urljoin(self._config.url, path), headers=headers, json=data) as response:
response.raise_for_status()
return await response.json()
async def on_shopping_list_entry_created(self, shopping_list_entry: ShoppingListEntryDTO) -> None:
if not self._config.on_shopping_list_entry_created_enabled:
return
item, description = _format_shopping_list_entry(shopping_list_entry)
self._logger.debug(f"adding {item=} with {description=} to {self._config.todo_entity}")
data = {
"entity_id": self._config.todo_entity,
"item": item,
}
if self._config.supports_description_field:
data["description"] = description
try:
await self.homeassistant_api_call("POST", "services/todo/add_item", data)
except ClientResponseError as err:
self._logger.warning(f"received an exception from the api: {err.request_info.url=}, {err.request_info.method=}, {err.status=}, {err.message=}, {type(err)=}")
async def on_shopping_list_entry_updated(self, shopping_list_entry: ShoppingListEntryDTO) -> None:
if not self._config.on_shopping_list_entry_updated_enabled:
return
pass
async def on_shopping_list_entry_deleted(self, shopping_list_entry: ShoppingListEntryDTO) -> None:
if not self._config.on_shopping_list_entry_deleted_enabled:
return
item, _ = _format_shopping_list_entry(shopping_list_entry)
self._logger.debug(f"removing {item=} from {self._config.todo_entity}")
data = {
"entity_id": self._config.todo_entity,
"item": item,
}
try:
await self.homeassistant_api_call("POST", "services/todo/remove_item", data)
except ClientResponseError as err:
# This error will always trigger if the item is not present/found
self._logger.debug(f"received an exception from the api: {err.request_info.url=}, {err.request_info.method=}, {err.status=}, {err.message=}, {type(err)=}")
async def close(self) -> None:
pass
def _format_shopping_list_entry(shopping_list_entry: ShoppingListEntryDTO) -> Tuple[str, str]:
item = shopping_list_entry.food_name
if shopping_list_entry.amount:
item += f" ({shopping_list_entry.amount:.2f}".rstrip('0').rstrip('.')
if shopping_list_entry.base_unit:
item += f" {shopping_list_entry.base_unit})"
elif shopping_list_entry.unit_name:
item += f" {shopping_list_entry.unit_name})"
else:
item += ")"
description = "From TandoorRecipes"
if shopping_list_entry.created_by.first_name:
description += f", by {shopping_list_entry.created_by.first_name}"
else:
description += f", by {shopping_list_entry.created_by.username}"
return item, description
================================================
FILE: cookbook/fixtures/german_example_tags.json
================================================
[
{
"model": "cookbook.keyword",
"pk": 1,
"fields": {
"name": "Hauptgang",
"icon": "\ud83c\udf7d",
"description": "",
"created_by": 0,
"created_at": "2019-11-15T14:07:08.957Z",
"updated_at": "2019-11-15T14:07:08.957Z"
}
},
{
"model": "cookbook.keyword",
"pk": 2,
"fields": {
"name": "Vorspeise",
"icon": "\ud83e\udd63",
"description": "",
"created_by": 0,
"created_at": "2019-11-15T14:07:31.863Z",
"updated_at": "2019-11-15T14:07:31.863Z"
}
},
{
"model": "cookbook.keyword",
"pk": 3,
"fields": {
"name": "Nachtisch",
"icon": "\ud83c\udf68",
"description": "",
"created_by": 0,
"created_at": "2019-11-15T14:07:49.873Z",
"updated_at": "2019-11-15T14:07:49.873Z"
}
},
{
"model": "cookbook.keyword",
"pk": 4,
"fields": {
"name": "Salat",
"icon": "\ud83e\udd57",
"description": "",
"created_by": 0,
"created_at": "2019-11-15T14:07:58.033Z",
"updated_at": "2019-11-15T14:07:58.034Z"
}
},
{
"model": "cookbook.keyword",
"pk": 5,
"fields": {
"name": "Rind",
"icon": "\ud83d\udc04",
"description": "",
"created_by": 0,
"created_at": "2019-11-15T14:08:24.881Z",
"updated_at": "2019-11-15T14:08:24.881Z"
}
},
{
"model": "cookbook.keyword",
"pk": 6,
"fields": {
"name": "Schwein",
"icon": "\ud83d\udc16",
"description": "",
"created_by": 0,
"created_at": "2019-11-15T14:08:35.754Z",
"updated_at": "2019-11-15T14:08:35.754Z"
}
},
{
"model": "cookbook.keyword",
"pk": 7,
"fields": {
"name": "Huhn",
"icon": "\ud83d\udc14",
"description": "",
"created_by": 0,
"created_at": "2019-11-15T14:08:43.360Z",
"updated_at": "2019-11-15T14:08:43.360Z"
}
},
{
"model": "cookbook.keyword",
"pk": 8,
"fields": {
"name": "Wild",
"icon": "\ud83e\udd8c",
"description": "",
"created_by": 0,
"created_at": "2019-11-15T14:08:52.137Z",
"updated_at": "2019-11-15T14:08:52.137Z"
}
},
{
"model": "cookbook.keyword",
"pk": 9,
"fields": {
"name": "Kuchen",
"icon": "\ud83c\udf70",
"description": "",
"created_by": 0,
"created_at": "2019-11-15T14:09:00.433Z",
"updated_at": "2019-11-15T14:09:00.433Z"
}
},
{
"model": "cookbook.keyword",
"pk": 10,
"fields": {
"name": "Nudeln",
"icon": "\ud83c\udf5d",
"description": "",
"created_by": 0,
"created_at": "2019-11-15T14:09:25.839Z",
"updated_at": "2019-11-15T14:09:25.839Z"
}
},
{
"model": "cookbook.keyword",
"pk": 11,
"fields": {
"name": "Reis",
"icon": "\ud83c\udf5a",
"description": "",
"created_by": 0,
"created_at": "2019-11-15T14:09:31.879Z",
"updated_at": "2019-11-15T14:09:31.879Z"
}
},
{
"model": "cookbook.keyword",
"pk": 12,
"fields": {
"name": "Kartoffeln",
"icon": "\ud83e\udd54",
"description": "",
"created_by": 0,
"created_at": "2019-11-15T14:09:39.023Z",
"updated_at": "2019-11-15T14:09:39.023Z"
}
},
{
"model": "cookbook.keyword",
"pk": 13,
"fields": {
"name": "Suppe",
"icon": "\ud83c\udf72",
"description": "",
"created_by": 0,
"created_at": "2019-11-15T14:14:54.922Z",
"updated_at": "2019-11-15T14:14:54.922Z"
}
},
{
"model": "cookbook.keyword",
"pk": 14,
"fields": {
"name": "Thermomix",
"icon": "\u2699",
"description": "",
"created_by": 0,
"created_at": "2019-11-15T14:15:35.806Z",
"updated_at": "2019-11-15T14:15:35.806Z"
}
},
{
"model": "cookbook.keyword",
"pk": 15,
"fields": {
"name": "S\u00fc\u00df",
"icon": "\ud83c\udf6f",
"description": "",
"created_by": 0,
"created_at": "2019-11-15T14:19:03.339Z",
"updated_at": "2019-11-15T14:19:03.339Z"
}
},
{
"model": "cookbook.keyword",
"pk": 16,
"fields": {
"name": "Auflauf",
"icon": "\ud83e\udd58",
"description": "",
"created_by": 0,
"created_at": "2019-11-15T14:19:21.874Z",
"updated_at": "2019-11-15T14:19:21.874Z"
}
},
{
"model": "cookbook.keyword",
"pk": 17,
"fields": {
"name": "Backen",
"icon": "\ud83d\udd25",
"description": "",
"created_by": 0,
"created_at": "2019-11-15T14:21:19.399Z",
"updated_at": "2019-11-15T14:21:19.399Z"
}
},
{
"model": "cookbook.keyword",
"pk": 18,
"fields": {
"name": "Fisch",
"icon": "\ud83d\udc1f",
"description": "",
"created_by": 0,
"created_at": "2019-11-15T14:23:32.303Z",
"updated_at": "2019-11-15T14:23:32.303Z"
}
},
{
"model": "cookbook.keyword",
"pk": 19,
"fields": {
"name": "Dip",
"icon": "\ud83e\udd6b",
"description": "",
"created_by": 0,
"created_at": "2019-11-16T13:32:42.625Z",
"updated_at": "2019-11-16T13:43:28.404Z"
}
}
]
================================================
FILE: cookbook/forms.py
================================================
from datetime import datetime
from allauth.account.forms import ResetPasswordForm, SignupForm
from allauth.socialaccount.forms import SignupForm as SocialSignupForm
from django import forms
from django.conf import settings
from django.contrib.auth.models import Group
from django.core.exceptions import ValidationError
from django.forms import widgets
from django.utils.translation import gettext_lazy as _
from django_scopes import scopes_disabled
from django_scopes.forms import SafeModelChoiceField
from hcaptcha.fields import hCaptchaField
from .models import InviteLink, Recipe, Space, User, UserPreference, UserSpace
class SelectWidget(widgets.Select):
class Media:
js = ('custom/js/form_select.js', )
class MultiSelectWidget(widgets.SelectMultiple):
class Media:
js = ('custom/js/form_multiselect.js', )
class ImportExportBase(forms.Form):
DEFAULT = 'DEFAULT'
PAPRIKA = 'PAPRIKA'
NEXTCLOUD = 'NEXTCLOUD'
MEALIE = 'MEALIE'
MEALIE1 = 'MEALIE1'
CHOWDOWN = 'CHOWDOWN'
SAFFRON = 'SAFFRON'
CHEFTAP = 'CHEFTAP'
PEPPERPLATE = 'PEPPERPLATE'
RECIPEKEEPER = 'RECIPEKEEPER'
RECETTETEK = 'RECETTETEK'
RECIPESAGE = 'RECIPESAGE'
DOMESTICA = 'DOMESTICA'
MEALMASTER = 'MEALMASTER'
MELARECIPES = 'MELARECIPES'
REZKONV = 'REZKONV'
OPENEATS = 'OPENEATS'
PLANTOEAT = 'PLANTOEAT'
COOKBOOKAPP = 'COOKBOOKAPP'
COOKLANG = 'COOKLANG'
COPYMETHAT = 'COPYMETHAT'
COOKMATE = 'COOKMATE'
REZEPTSUITEDE = 'REZEPTSUITEDE'
PDF = 'PDF'
GOURMET = 'GOURMET'
type = forms.ChoiceField(
choices=((DEFAULT, _('Default')), (PAPRIKA, 'Paprika'), (NEXTCLOUD, 'Nextcloud Cookbook'), (MEALIE, 'Mealie'), (MEALIE1, 'Mealie1'), (CHOWDOWN, 'Chowdown'),
(SAFFRON, 'Saffron'), (CHEFTAP, 'ChefTap'), (PEPPERPLATE, 'Pepperplate'), (RECETTETEK, 'RecetteTek'), (RECIPESAGE, 'Recipe Sage'), (DOMESTICA, 'Domestica'),
(MEALMASTER, 'MealMaster'), (REZKONV, 'RezKonv'), (OPENEATS, 'Openeats'), (RECIPEKEEPER, 'Recipe Keeper'), (PLANTOEAT, 'Plantoeat'), (COOKBOOKAPP, 'CookBookApp'),
(COOKLANG, 'Cooklang Markdown'), (COPYMETHAT, 'CopyMeThat'), (PDF, 'PDF'), (MELARECIPES, 'Melarecipes'), (COOKMATE, 'Cookmate'),
(REZEPTSUITEDE, 'Recipesuite.de'), (GOURMET, 'Gourmet'))
)
class MultipleFileInput(forms.ClearableFileInput):
allow_multiple_selected = True
class MultipleFileField(forms.FileField):
def __init__(self, *args, **kwargs):
kwargs.setdefault("widget", MultipleFileInput())
super().__init__(*args, **kwargs)
def clean(self, data, initial=None):
single_file_clean = super().clean
if isinstance(data, (list, tuple)):
result = [single_file_clean(d, initial) for d in data]
else:
result = single_file_clean(data, initial)
return result
class ImportForm(ImportExportBase):
files = MultipleFileField(required=True)
duplicates = forms.BooleanField(
help_text=_('To prevent duplicates recipes with the same name as existing ones are ignored. Check this box to import everything.'), required=False
)
meal_plans = forms.BooleanField(required=False)
shopping_lists = forms.BooleanField(required=False)
nutrition_per_serving = forms.BooleanField(required=False) # some managers (e.g. mealie) do not specify what the nutrition's relate to so we let the user choose
class ExportForm(ImportExportBase):
recipes = forms.ModelMultipleChoiceField(widget=MultiSelectWidget, queryset=Recipe.objects.none(), required=False)
all = forms.BooleanField(required=False)
custom_filter = forms.IntegerField(required=False)
def __init__(self, *args, **kwargs):
space = kwargs.pop('space')
super().__init__(*args, **kwargs)
self.fields['recipes'].queryset = Recipe.objects.filter(space=space).all()
class InviteLinkForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
super().__init__(*args, **kwargs)
self.fields['space'].queryset = Space.objects.filter(created_by=user).all()
def clean(self):
space = self.cleaned_data['space']
if space.max_users != 0 and (
UserPreference.objects.filter(space=space).count() + InviteLink.objects.filter(valid_until__gte=datetime.today(), used_by=None, space=space).count()
) >= space.max_users:
raise ValidationError(_('Maximum number of users for this space reached.'))
def clean_email(self):
email = self.cleaned_data['email']
with scopes_disabled():
if email != '' and User.objects.filter(email=email).exists():
raise ValidationError(_('Email address already taken!'))
return email
class Meta:
model = InviteLink
fields = ('email', 'group', 'valid_until', 'space')
help_texts = {
'email': _('An email address is not required but if present the invite link will be sent to the user.'),
}
field_classes = {
'space': SafeModelChoiceField,
}
class SpaceCreateForm(forms.Form):
prefix = 'create'
name = forms.CharField()
def clean_name(self):
name = self.cleaned_data['name']
with scopes_disabled():
if Space.objects.filter(name=name).exists():
raise ValidationError(_('Name already taken.'))
return name
class SpaceJoinForm(forms.Form):
prefix = 'join'
token = forms.CharField()
class AllAuthSignupForm(SignupForm):
captcha = hCaptchaField()
terms = forms.BooleanField(label=_('Accept Terms and Privacy'))
def __init__(self, **kwargs):
super().__init__(**kwargs)
if settings.PRIVACY_URL == '' and settings.TERMS_URL == '':
self.fields.pop('terms')
if settings.HCAPTCHA_SECRET == '':
self.fields.pop('captcha')
def signup(self, request, user):
pass
class AllAuthSocialSignupForm(SocialSignupForm):
terms = forms.BooleanField(label=_('Accept Terms and Privacy'))
def __init__(self, **kwargs):
super().__init__(**kwargs)
if settings.PRIVACY_URL == '' and settings.TERMS_URL == '':
self.fields.pop('terms')
def signup(self, request, user):
if settings.SOCIAL_DEFAULT_ACCESS:
with scopes_disabled():
space = Space.objects.first()
if space:
user_space = UserSpace.objects.create(
space=space, user=user, active=True
)
user_space.groups.add(
Group.objects.filter(name=settings.SOCIAL_DEFAULT_GROUP).get()
)
class CustomPasswordResetForm(ResetPasswordForm):
captcha = hCaptchaField()
def __init__(self, **kwargs):
super(CustomPasswordResetForm, self).__init__(**kwargs)
if settings.HCAPTCHA_SECRET == '':
self.fields.pop('captcha')
class UserCreateForm(forms.Form):
name = forms.CharField(label='Username')
password = forms.CharField(widget=forms.TextInput(attrs={'autocomplete': 'new-password', 'type': 'password'}))
password_confirm = forms.CharField(widget=forms.TextInput(attrs={'autocomplete': 'new-password', 'type': 'password'}))
================================================
FILE: cookbook/helper/AllAuthCustomAdapter.py
================================================
import datetime
import logging
from gettext import gettext as _
from allauth.account.adapter import DefaultAccountAdapter
from django.conf import settings
from django.contrib import messages
from django.core.cache import caches
from django.utils import timezone
from cookbook.models import InviteLink
logger = logging.getLogger(__name__)
class AllAuthCustomAdapter(DefaultAccountAdapter):
def is_open_for_signup(self, request):
"""
Whether to allow sign-ups.
"""
signup_token = False
if 'signup_token' in request.session and InviteLink.objects.filter(
valid_until__gte=timezone.now().date(), used_by=None, uuid=request.session['signup_token']).exists():
signup_token = True
if request.resolver_match.view_name == 'account_signup' and not settings.ENABLE_SIGNUP and not signup_token:
return False
elif request.resolver_match.view_name == 'socialaccount_signup' and len(settings.SOCIAL_PROVIDERS) < 1:
return False
else:
return super(AllAuthCustomAdapter, self).is_open_for_signup(request)
# disable password reset for now
def send_mail(self, template_prefix, email, context):
if settings.EMAIL_HOST != '':
default = timezone.now()
c = caches['default'].get_or_set(email, default, timeout=360)
if c == default:
try:
super(AllAuthCustomAdapter, self).send_mail(template_prefix, email, context)
except Exception as e: # dont fail signup just because confirmation mail could not be send
logger.error(f"Failed to send {template_prefix} email to {email}: {type(e).__name__}: {e}")
else:
messages.add_message(self.request, messages.ERROR, _('In order to prevent spam, the requested email was not send. Please wait a few minutes and try again.'))
else:
logger.debug(f"Email not sent (EMAIL_HOST not configured): {template_prefix} to {email}")
================================================
FILE: cookbook/helper/CustomStorageClass.py
================================================
import hashlib
from django.conf import settings
from django.core.cache import cache
from storages.backends.s3boto3 import S3Boto3Storage
class CachedS3Boto3Storage(S3Boto3Storage):
def url(self, name, **kwargs):
key = hashlib.md5(f'recipes_media_urls_{name}'.encode('utf-8')).hexdigest()
if result := cache.get(key):
return result
result = super(CachedS3Boto3Storage, self).url(name, **kwargs)
timeout = int(settings.AWS_QUERYSTRING_EXPIRE * .95)
cache.set(key, result, timeout)
return result
================================================
FILE: cookbook/helper/HelperFunctions.py
================================================
import socket
import requests
import struct
from ipaddress import ip_address
from urllib.parse import urlparse, quote, urlunparse
from django.core.exceptions import ValidationError
from django.core.validators import URLValidator
from django.db.models import Func
from thefuzz import fuzz
from thefuzz import process as fuzz_process
from requests_hardened import Config, Manager
class Round(Func):
function = 'ROUND'
template = '%(function)s(%(expressions)s, 0)'
def str2bool(v):
if isinstance(v, bool) or v is None:
return v
else:
return v.lower() in ("yes", "true", "1")
def safe_request(method, url, **kwargs):
"""
use requests-hardened to make external requests SSRF safe
"""
http_manager = Manager(
Config(
default_timeout=(2, 10),
never_redirect=False,
# Enable SSRF IP filter
ip_filter_enable=True,
ip_filter_allow_loopback_ips=False,
)
)
return http_manager.send_request(method, url, **kwargs)
def match_or_fuzzymatch(check_string: str, key_dict: dict) -> tuple[str, int]:
"""
takes a string and sees if it matches exactly any of the Dictionary keys
or any of the alternative strings listed in the value of each key.
If there are no matches return the key of the string that returns the best fuzzy match against your check_string.
:param check_string: A string that you want to attempt to match
:param key_dict: key: exact terms you are searching for, value:a list of strings that are alternative terms to check.
:return:
"""
score = (None, 0)
for key in key_dict:
key_dict[key].append(key)
if check_string.lower() in [match.lower() for match in key_dict[key]]:
return (key, 100)
for key in key_dict:
key_score = fuzz_process.extract(check_string, key_dict[key], limit=1, scorer=fuzz.partial_token_sort_ratio)[0]
if key_score[1] > score[1]:
score = (key, key_score[1])
return score
================================================
FILE: cookbook/helper/__init__.py
================================================
from cookbook.helper.AllAuthCustomAdapter import AllAuthCustomAdapter
__all__ = [
]
================================================
FILE: cookbook/helper/ai_helper.py
================================================
from decimal import Decimal
from django.utils import timezone
from django.db.models import Sum
from litellm import CustomLogger
from cookbook.models import AiLog
from recipes import settings
def get_monthly_token_usage(space):
"""
returns the number of credits the space has used in the current month
"""
token_usage = AiLog.objects.filter(space=space, credits_from_balance=False, created_at__month=timezone.now().month).aggregate(Sum('credit_cost'))['credit_cost__sum']
if token_usage is None:
token_usage = 0
return token_usage
def has_monthly_token(space):
"""
checks if the monthly credit limit has been exceeded
"""
return get_monthly_token_usage(space) < space.ai_credits_monthly
def can_perform_ai_request(space):
return (has_monthly_token(space) or space.ai_credits_balance > 0) and space.ai_enabled
class AiCallbackHandler(CustomLogger):
space = None
user = None
ai_provider = None
function = None
def __init__(self, space, user, ai_provider, function):
super().__init__()
self.space = space
self.user = user
self.ai_provider = ai_provider
self.function = function
def log_pre_api_call(self, model, messages, kwargs):
pass
def log_post_api_call(self, kwargs, response_obj, start_time, end_time):
pass
def log_success_event(self, kwargs, response_obj, start_time, end_time):
self.create_ai_log(kwargs, response_obj, start_time, end_time)
def log_failure_event(self, kwargs, response_obj, start_time, end_time):
self.create_ai_log(kwargs, response_obj, start_time, end_time)
def create_ai_log(self, kwargs, response_obj, start_time, end_time):
credit_cost = 0
credits_from_balance = False
if self.ai_provider.log_credit_cost:
credit_cost = kwargs.get("response_cost", 0) * 100
if (not has_monthly_token(self.space)) and self.space.ai_credits_balance > 0:
remaining_balance = self.space.ai_credits_balance - Decimal(str(credit_cost))
if remaining_balance < 0:
remaining_balance = 0
if settings.HOSTED and self.space.ai_credits_monthly == 0:
self.space.ai_enabled = False
self.space.ai_credits_balance = remaining_balance
credits_from_balance = True
self.space.save()
AiLog.objects.create(
created_by=self.user,
space=self.space,
ai_provider=self.ai_provider,
start_time=start_time,
end_time=end_time,
input_tokens=response_obj['usage']['prompt_tokens'],
output_tokens=response_obj['usage']['completion_tokens'],
function=self.function,
credit_cost=credit_cost,
credits_from_balance=credits_from_balance,
)
================================================
FILE: cookbook/helper/automation_helper.py
================================================
import re
from django.core.cache import caches
from django.db.models.functions import Lower
from cookbook.models import Automation
class AutomationEngine:
request = None
source = None
use_cache = None
food_aliases = None
keyword_aliases = None
unit_aliases = None
never_unit = None
transpose_words = None
regex_replace = {
Automation.DESCRIPTION_REPLACE: None,
Automation.INSTRUCTION_REPLACE: None,
Automation.FOOD_REPLACE: None,
Automation.UNIT_REPLACE: None,
Automation.NAME_REPLACE: None,
}
def __init__(self, request, use_cache=True, source=None):
self.request = request
self.use_cache = use_cache
if not source:
self.source = "default_string_to_avoid_false_regex_match"
else:
self.source = source
def apply_keyword_automation(self, keyword):
keyword = keyword.strip()
if self.use_cache and self.keyword_aliases is None:
self.keyword_aliases = {}
KEYWORD_CACHE_KEY = f'automation_keyword_alias_{self.request.space.pk}'
if c := caches['default'].get(KEYWORD_CACHE_KEY, None):
self.keyword_aliases = c
caches['default'].touch(KEYWORD_CACHE_KEY, 30)
else:
for a in Automation.objects.filter(space=self.request.space, disabled=False, type=Automation.KEYWORD_ALIAS).only('param_1', 'param_2').order_by('order').all():
self.keyword_aliases[a.param_1.lower()] = a.param_2
caches['default'].set(KEYWORD_CACHE_KEY, self.keyword_aliases, 30)
else:
self.keyword_aliases = {}
if self.keyword_aliases:
try:
keyword = self.keyword_aliases[keyword.lower()]
except KeyError:
pass
else:
if automation := Automation.objects.filter(space=self.request.space, type=Automation.KEYWORD_ALIAS, param_1__iexact=keyword, disabled=False).order_by('order').first():
return automation.param_2
return keyword
def apply_unit_automation(self, unit):
unit = unit.strip()
if self.use_cache and self.unit_aliases is None:
self.unit_aliases = {}
UNIT_CACHE_KEY = f'automation_unit_alias_{self.request.space.pk}'
if c := caches['default'].get(UNIT_CACHE_KEY, None):
self.unit_aliases = c
caches['default'].touch(UNIT_CACHE_KEY, 30)
else:
for a in Automation.objects.filter(space=self.request.space, disabled=False, type=Automation.UNIT_ALIAS).only('param_1', 'param_2').order_by('order').all():
self.unit_aliases[a.param_1.lower()] = a.param_2
caches['default'].set(UNIT_CACHE_KEY, self.unit_aliases, 30)
else:
self.unit_aliases = {}
if self.unit_aliases:
try:
unit = self.unit_aliases[unit.lower()]
except KeyError:
pass
else:
if automation := Automation.objects.filter(space=self.request.space, type=Automation.UNIT_ALIAS, param_1__iexact=unit, disabled=False).order_by('order').first():
return automation.param_2
return self.apply_regex_replace_automation(unit, Automation.UNIT_REPLACE)
def apply_food_automation(self, food):
food = food.strip()
if self.use_cache and self.food_aliases is None:
self.food_aliases = {}
FOOD_CACHE_KEY = f'automation_food_alias_{self.request.space.pk}'
if c := caches['default'].get(FOOD_CACHE_KEY, None):
self.food_aliases = c
caches['default'].touch(FOOD_CACHE_KEY, 30)
else:
for a in Automation.objects.filter(space=self.request.space, disabled=False, type=Automation.FOOD_ALIAS).only('param_1', 'param_2').order_by('order').all():
self.food_aliases[a.param_1.lower()] = a.param_2
caches['default'].set(FOOD_CACHE_KEY, self.food_aliases, 30)
else:
self.food_aliases = {}
if self.food_aliases:
try:
return self.food_aliases[food.lower()]
except KeyError:
return self.apply_regex_replace_automation(food, Automation.FOOD_REPLACE)
else:
if automation := Automation.objects.filter(space=self.request.space, type=Automation.FOOD_ALIAS, param_1__iexact=food, disabled=False).order_by('order').first():
return automation.param_2
return self.apply_regex_replace_automation(food, Automation.FOOD_REPLACE)
def apply_never_unit_automation(self, tokens):
"""
Moves a string that should never be treated as a unit to next token and optionally replaced with default unit
e.g. NEVER_UNIT: param1: egg, param2: None would modify ['1', 'egg', 'white'] to ['1', '', 'egg', 'white']
or NEVER_UNIT: param1: egg, param2: pcs would modify ['1', 'egg', 'yolk'] to ['1', 'pcs', 'egg', 'yolk']
:param1 tokens: string that should never be considered a unit, will be moved to token[2]
:param2 (optional) unit as string: will insert unit string into token[1]
:return: unit as string (possibly changed by automation)
"""
if self.use_cache and self.never_unit is None:
self.never_unit = {}
NEVER_UNIT_CACHE_KEY = f'automation_never_unit_{self.request.space.pk}'
if c := caches['default'].get(NEVER_UNIT_CACHE_KEY, None):
self.never_unit = c
caches['default'].touch(NEVER_UNIT_CACHE_KEY, 30)
else:
for a in Automation.objects.filter(space=self.request.space, disabled=False, type=Automation.NEVER_UNIT).only('param_1', 'param_2').order_by('order').all():
self.never_unit[a.param_1.lower()] = a.param_2
caches['default'].set(NEVER_UNIT_CACHE_KEY, self.never_unit, 30)
else:
self.never_unit = {}
new_unit = None
alt_unit = self.apply_unit_automation(tokens[1])
never_unit = False
if self.never_unit:
try:
new_unit = self.never_unit[tokens[1].lower()]
never_unit = True
except KeyError:
return tokens, never_unit
else:
if a := Automation.objects.annotate(param_1_lower=Lower('param_1')).filter(space=self.request.space, type=Automation.NEVER_UNIT, param_1_lower__in=[
tokens[1].lower(), alt_unit.lower()], disabled=False).order_by('order').first():
new_unit = a.param_2
never_unit = True
if never_unit:
tokens.insert(1, new_unit)
return tokens, never_unit
def apply_transpose_automation(self, string):
"""
If two words (param_1 & param_2) are detected in sequence, swap their position in the ingredient string
:param 1: first word to detect
:param 2: second word to detect
return: new ingredient string
"""
if self.use_cache and self.transpose_words is None:
self.transpose_words = {}
TRANSPOSE_WORDS_CACHE_KEY = f'automation_transpose_words_{self.request.space.pk}'
if c := caches['default'].get(TRANSPOSE_WORDS_CACHE_KEY, None):
self.transpose_words = c
caches['default'].touch(TRANSPOSE_WORDS_CACHE_KEY, 30)
else:
i = 0
for a in Automation.objects.filter(space=self.request.space, disabled=False, type=Automation.TRANSPOSE_WORDS).only(
'param_1', 'param_2').order_by('order').all()[:512]:
self.transpose_words[i] = [a.param_1.lower(), a.param_2.lower()]
i += 1
caches['default'].set(TRANSPOSE_WORDS_CACHE_KEY, self.transpose_words, 30)
else:
self.transpose_words = {}
tokens = [x.lower() for x in string.replace(',', ' ').split()]
if self.transpose_words:
for key, value in self.transpose_words.items():
if value[0] in tokens and value[1] in tokens:
string = re.sub(rf"\b({value[0]})\W*({value[1]})\b", r"\2 \1", string, flags=re.IGNORECASE)
else:
for rule in Automation.objects.filter(space=self.request.space, type=Automation.TRANSPOSE_WORDS, disabled=False) \
.annotate(param_1_lower=Lower('param_1'), param_2_lower=Lower('param_2')) \
.filter(param_1_lower__in=tokens, param_2_lower__in=tokens).order_by('order')[:512]:
if rule.param_1 in tokens and rule.param_2 in tokens:
string = re.sub(rf"\b({rule.param_1})\W*({rule.param_2})\b", r"\2 \1", string, flags=re.IGNORECASE)
return string
def apply_regex_replace_automation(self, string, automation_type):
# TODO add warning - maybe on SPACE page? when a max of 512 automations of a specific type is exceeded (ALIAS types excluded?)
"""
Replaces strings in a recipe field that are from a matched source
field_type are Automation.type that apply regex replacements
Automation.DESCRIPTION_REPLACE
Automation.INSTRUCTION_REPLACE
Automation.FOOD_REPLACE
Automation.UNIT_REPLACE
Automation.NAME_REPLACE
regex replacment utilized the following fields from the Automation model
:param 1: source that should apply the automation in regex format ('.*' for all)
:param 2: regex pattern to match ()
:param 3: replacement string (leave blank to delete)
return: new string
"""
if self.use_cache and self.regex_replace[automation_type] is None:
self.regex_replace[automation_type] = {}
REGEX_REPLACE_CACHE_KEY = f'automation_regex_replace_{self.request.space.pk}'
if c := caches['default'].get(REGEX_REPLACE_CACHE_KEY, None):
self.regex_replace[automation_type] = c[automation_type]
caches['default'].touch(REGEX_REPLACE_CACHE_KEY, 30)
else:
i = 0
for a in Automation.objects.filter(space=self.request.space, disabled=False, type=automation_type).only(
'param_1', 'param_2', 'param_3').order_by('order').all()[:512]:
self.regex_replace[automation_type][i] = [a.param_1, a.param_2, a.param_3]
i += 1
caches['default'].set(REGEX_REPLACE_CACHE_KEY, self.regex_replace, 30)
else:
self.regex_replace[automation_type] = {}
if self.regex_replace[automation_type]:
for rule in self.regex_replace[automation_type].values():
if re.match(rule[0], (self.source)[:512]):
string = re.sub(rule[1], rule[2], string, flags=re.IGNORECASE)
else:
for rule in Automation.objects.filter(space=self.request.space, disabled=False, type=automation_type).only(
'param_1', 'param_2', 'param_3').order_by('order').all()[:512]:
if re.match(rule.param_1, (self.source)[:512]):
string = re.sub(rule.param_2, rule.param_3, string, flags=re.IGNORECASE)
return string
================================================
FILE: cookbook/helper/batch_edit_helper.py
================================================
def add_to_relation(relation_model, base_field_name, base_ids, related_field_name, related_ids):
"""
given a model, the base and related field and the base and related ids, bulk create relation objects
"""
relation_objects = []
for b in base_ids:
for r in related_ids:
relation_objects.append(relation_model(**{base_field_name: b, related_field_name: r}))
relation_model.objects.bulk_create(relation_objects, ignore_conflicts=True, unique_fields=(base_field_name, related_field_name,))
def remove_from_relation(relation_model, base_field_name, base_ids, related_field_name, related_ids):
relation_model.objects.filter(**{f'{base_field_name}__in': base_ids, f'{related_field_name}__in': related_ids}).delete()
def remove_all_from_relation(relation_model, base_field_name, base_ids):
relation_model.objects.filter(**{f'{base_field_name}__in': base_ids}).delete()
def set_relation(relation_model, base_field_name, base_ids, related_field_name, related_ids):
remove_all_from_relation(relation_model, base_field_name, base_ids)
add_to_relation(relation_model, base_field_name, base_ids, related_field_name, related_ids)
================================================
FILE: cookbook/helper/cache_helper.py
================================================
class CacheHelper:
space = None
BASE_UNITS_CACHE_KEY = None
PROPERTY_TYPE_CACHE_KEY = None
def __init__(self, space):
self.space = space
self.BASE_UNITS_CACHE_KEY = f'SPACE_{space.id}_BASE_UNITS'
self.PROPERTY_TYPE_CACHE_KEY = f'SPACE_{space.id}_PROPERTY_TYPES'
================================================
FILE: cookbook/helper/context_processors.py
================================================
from django.conf import settings
def context_settings(request):
return {
'EMAIL_ENABLED': settings.EMAIL_HOST != '',
'SIGNUP_ENABLED': settings.ENABLE_SIGNUP,
'CAPTCHA_ENABLED': settings.HCAPTCHA_SITEKEY != '',
'HOSTED': settings.HOSTED,
'TERMS_URL': settings.TERMS_URL,
'PRIVACY_URL': settings.PRIVACY_URL,
'IMPRINT_URL': settings.IMPRINT_URL,
'SHOPPING_MIN_AUTOSYNC_INTERVAL': settings.SHOPPING_MIN_AUTOSYNC_INTERVAL,
'DISABLE_EXTERNAL_CONNECTORS': settings.DISABLE_EXTERNAL_CONNECTORS,
'HIDE_LOGIN_FORM': settings.HIDE_LOGIN_FORM,
}
================================================
FILE: cookbook/helper/cooklang_parser.py
================================================
# Cooklang parser forked from on https://github.com/luizribeiro/py-cooklang cooklang.py as of 11/18/25 - MIT License
# Modifications by doylelew
import copy
import re
from dataclasses import dataclass
from typing import Optional, Union
@dataclass
class Quantity:
amount: Union[str, float]
unit: Optional[str] = None
@classmethod
def parse(cls, raw) -> "Quantity":
amount = raw
unit = ""
if len(quantity_tokens := re.split(r'%', amount)) == 2:
amount = quantity_tokens[0]
unit = quantity_tokens[1]
if amount != "":
try:
float(amount)
except ValueError:
whole_num = 0
if len(integer_fraction_tokens := re.split(r' ', amount)) == 2:
whole_num = int(integer_fraction_tokens[0])
amount = integer_fraction_tokens[1]
if len(fraction := re.split(r'/', amount)) == 2:
amount = int(fraction[0]) / int(fraction[1])
amount += whole_num
else:
raise Exception(f"Cooklang:Quantity:parse Syntax Error: measurements must be in float or #/# format, received {amount}")
amount = round(float(amount), 3)
return Quantity(amount=amount, unit=unit)
@classmethod
def add_optional(cls, a: Optional["Quantity"], b: Optional["Quantity"]) -> Optional["Quantity"]:
if a and b:
return a + b
elif a or b:
return a or b
return None
def __add__(self, other: "Quantity") -> "Quantity":
if self.unit != other.unit:
raise ValueError(f"Cannot add unit {self.unit} to {other.unit}")
new_amount = self.amount + other.amount
return Quantity(
amount=new_amount,
unit=self.unit,
)
@dataclass
class Timer:
ingredient_str: str
quantity: float
unit: str
@classmethod
def parse(cls, raw) -> "Timer":
if len(timer_tokens := re.split(r'[{%]', raw)) < 3:
raise Exception(f"Cooklang:Recipe:Timer Syntax Error: timers must be in {{num%unit}} format, received {raw}")
ingredient = timer_tokens[0]
quantity = timer_tokens[1]
unit = timer_tokens[2].replace("}", "")
try:
float(quantity)
except ValueError:
whole_num = 0
if len(integer_fraction_tokens := re.split(r' ', quantity)) == 2:
whole_num = int(integer_fraction_tokens[0])
quantity = integer_fraction_tokens[1]
if len(fraction := re.split(r'/', quantity)) == 2:
quantity = int(fraction[0]) / int(fraction[1])
quantity += whole_num
else:
raise Exception(f"Cooklang:Timer:parse Syntax Error: measurements must be in float or #/# format, received {quantity}")
quantity = round(float(quantity), 3)
return Timer(ingredient_str=ingredient, quantity=quantity, unit=unit)
@dataclass
class Ingredient:
name: str
quantity: Optional[Quantity] = None
@classmethod
def parse(cls, raw: str) -> "Ingredient":
ingredient = raw
quantity = ""
if len(ingredient_tokens := re.split(r'{', raw)) == 2:
ingredient = ingredient_tokens[0]
quantity = ingredient_tokens[1].replace('}', '')
return Ingredient(
name=ingredient,
quantity=Quantity.parse(quantity),
)
def __add__(self, other: "Ingredient") -> "Ingredient":
if self.name != other.name:
raise ValueError(f"Cannot add ingredient {self.name} with {other.name}", )
return Ingredient(
name=self.name,
quantity=Quantity.add_optional(self.quantity, other.quantity),
)
@dataclass
class Block:
type: str
value: Union[str, Ingredient, Timer]
@classmethod
def new(cls):
return Block(type="", value="")
@dataclass
class Step:
blocks: list[Block]
@classmethod
def parse(cls, raw: str) -> "Step":
# split steps into a stream of tokens broken up by delimiters
blocks = []
token_stream = re.split(r'(--|\[-|-\]|[@#}~])', raw)
# verify terminating delimiter and return token or if no delimiter then return only first word
def find_arbitrary_termination_token(current_token, next_token, termination_delimiter) -> tuple[str, list[str]]:
return_tokens = []
if next_token != termination_delimiter:
sub_tokens = re.split(r'(?=[\W\s])([^-])', current_token)
current_token = sub_tokens.pop(0)
if next_token:
return_tokens.insert(0, next_token)
return_tokens.insert(0, "".join(sub_tokens))
else:
current_token += termination_delimiter
return current_token, return_tokens
# ensure that the correct terminating delimiter is used
def find_termination_token(current_token, next_token, termination_delimiter) -> str:
if next_token != termination_delimiter:
raise Exception(f"Cooklang:Recipe:parse Syntax Error: expected terminating delimiter {termination_delimiter}, recieved {next_token}")
else:
return current_token + termination_delimiter
# asses tokens in order to identify and denote datatypes into blocks
while token_stream:
block = Block.new()
token = token_stream.pop(0)
stream_return = []
match token:
case "@":
block.type = "Ingredient"
token_next = token_stream.pop(0)
lookahead = None
if len(token_stream) > 0:
lookahead = token_stream.pop(0)
token, stream_return = find_arbitrary_termination_token(token_next, lookahead, '}')
block.value = Ingredient.parse(token)
case "#":
block.type = "Cookware"
token_next = token_stream.pop(0)
lookahead = None
if len(token_stream) > 0:
lookahead = token_stream.pop(0)
token, stream_return = find_arbitrary_termination_token(token_next, lookahead, '}')
block.value = token.replace("{}", "")
case "~":
block.type = "Timer"
token_next = token_stream.pop(0)
lookahead = None
if len(token_stream) > 0:
lookahead = token_stream.pop(0)
token, stream_return = find_arbitrary_termination_token(token_next, lookahead, '}')
block.value = Timer.parse(token)
case "--":
block.type = "inline comment"
token = token_stream.pop(0)
if len(find_new_line := token.split("\n", 1)) == 2:
block.value = find_new_line[0]
stream_return = ["\n" + find_new_line[1]]
elif len(token_stream) == 0:
block.value = token
else:
raise Exception("Cooklang:Step:parse Syntax Error: inline comments must end with a new line")
case "[-":
block.type = "block comment"
block.value = find_termination_token(token_stream.pop(0), token_stream.pop(0), '-]').replace("-]", "")
case "}":
raise Exception("Cooklang:Step:parse Syntax Error: stray '}' found with no delimiting '@', '#' or '~'")
case "-]":
raise Exception("Cooklang:Step:parse Syntax Error: stray '-]' found with no opening '[-'")
case _:
block.type = "text"
block.value = token
token_stream = stream_return + token_stream
if block.value != '':
blocks.append(block)
return Step(blocks=blocks)
@dataclass
class Recipe:
metadata: dict[str, str]
ingredients: list[Ingredient]
steps: list[Step]
@classmethod
def parse(cls, raw: str) -> "Recipe":
# Remove white space at the end of the document
raw = re.sub(r'\s+$', '', raw)
# Separate the Metadata from the rest of the recipe, "--- metadata ---" format
raw_metadata = ""
raw_no_metadata = raw
if len(raw_meta_split := re.split(r'---([\s\S]*?)---\n', raw)) > 1:
raw_metadata = raw_meta_split[-2]
raw_no_metadata = raw_meta_split[-1]
# Separate the Metadata from the rest of the recipe, ">> metadata" format
if not raw_metadata:
for line in raw.lstrip().splitlines():
if not bool(re.match(r'^>>', line)):
break
raw_no_metadata = raw_no_metadata.replace(line, "")
raw_metadata = raw_metadata + re.sub(r'^>>', "", line).lstrip() + "\n"
raw_metadata.rstrip()
# Parse the metadata
metadata = {}
if raw_metadata:
current_key = None
for line in raw_metadata.splitlines():
if len(key_value_pair := re.split(":", line, maxsplit=1)) > 1:
current_key, value = key_value_pair
metadata[current_key] = value.lstrip()
elif re.match(r'^\s*-\s', line) and current_key is not None:
metadata[current_key] = metadata[current_key] + f"{re.split(r'^\s*-\s', line)[-1]}, "
# Parse the Steps recursively Step -> Ingredient -> Quantity
raw_steps = re.split(r'\n\n', raw_no_metadata.lstrip())
steps = [Step.parse(step.lstrip()) for step in raw_steps]
for step in steps:
if not step.blocks:
steps.remove(step)
# using the blocks in the steps, calculate global ingredients
def add_ingredient_to_global(global_dict, ingredient_object) -> dict[str:Ingredient]:
if ingredient_object.name in global_dict.keys():
try:
global_dict[ingredient_object.name] += ingredient_object
except ValueError:
new_ingredient_object = copy.deepcopy(ingredient_object)
new_ingredient_object.name = f"{ingredient_object.name}({ingredient_object.quantity.unit})"
global_dict = add_ingredient_to_global(global_dict, new_ingredient_object)
else:
global_dict[ingredient_object.name] = copy.deepcopy(ingredient_object)
return global_dict
ingredient_dict = {}
ingredient_blocks = [item for sublist in [[block.value for block in step.blocks if block.type == "Ingredient"] for step in steps] for item in sublist]
for ingredient in ingredient_blocks:
ingredient_dict = add_ingredient_to_global(ingredient_dict, ingredient)
return Recipe(
metadata=metadata,
ingredients=[ingredient_dict[key] for key in ingredient_dict.keys()],
steps=steps,
)
================================================
FILE: cookbook/helper/drf_spectacular_hooks.py
================================================
# custom processing for schema
# reason: DRF writable nested needs ID's to decide if a nested object should be created or updated
# the API schema/client make ID's read only by default and strips them entirely in request objects (with COMPONENT_SPLIT_REQUEST enabled)
# change the schema to make IDs optional but writable so they are included in the request
def custom_postprocessing_hook(result, generator, request, public):
for c in result['components']['schemas'].keys():
# handle schemas used by the client to do requests on the server
if 'properties' in result['components']['schemas'][c] and 'id' in result['components']['schemas'][c]['properties']:
# make ID field not read only so it's not stripped from the request on the client
result['components']['schemas'][c]['properties']['id']['readOnly'] = False
# make ID field not required
if 'required' in result['components']['schemas'][c] and 'id' in result['components']['schemas'][c]['required']:
result['components']['schemas'][c]['required'].remove('id')
return result
# TODO remove below once legacy API has been fully deprecated
from drf_spectacular.openapi import AutoSchema # noqa: E402 isort: skip
import functools # noqa: E402 isort: skip
import re # noqa: E402 isort: skip
class LegacySchema(AutoSchema):
operation_id_base = None
@functools.cached_property
def path(self):
path = re.sub(pattern=self.path_prefix, repl='', string=self.path, flags=re.IGNORECASE)
# remove path variables
return re.sub(pattern=r'\{[\w\-]+\}', repl='', string=path)
def get_operation_id(self):
"""
Compute an operation ID from the view type and get_operation_id_base method.
"""
method_name = getattr(self.view, 'action', self.method.lower())
if self._is_list_view():
action = 'list'
elif method_name not in self.method_mapping:
action = self._to_camel_case(method_name)
else:
action = self.method_mapping[self.method.lower()]
name = self.get_operation_id_base(action)
return action + name
def get_operation_id_base(self, action):
"""
Compute the base part for operation ID from the model, serializer or view name.
"""
model = getattr(getattr(self.view, 'queryset', None), 'model', None)
if self.operation_id_base is not None:
name = self.operation_id_base
# Try to deduce the ID from the view's model
elif model is not None:
name = model.__name__
# Try with the serializer class name
elif self.get_serializer() is not None:
name = self.get_serializer().__class__.__name__
if name.endswith('Serializer'):
name = name[:-10]
# Fallback to the view name
else:
name = self.view.__class__.__name__
if name.endswith('APIView'):
name = name[:-7]
elif name.endswith('View'):
name = name[:-4]
# Due to camel-casing of classes and `action` being lowercase, apply title in order to find if action truly
# comes at the end of the name
if name.endswith(action.title()): # ListView, UpdateAPIView, ThingDelete ...
name = name[:-len(action)]
if action == 'list' and not name.endswith('s'): # listThings instead of listThing
name += 's'
return name
def get_serializer(self):
view = self.view
if not hasattr(view, 'get_serializer'):
return None
try:
return view.get_serializer()
except Exception:
return None
def _to_camel_case(self, snake_str):
components = snake_str.split('_')
# We capitalize the first letter of each component except the first one
# with the 'title' method and join them together.
return components[0] + ''.join(x.title() for x in components[1:])
================================================
FILE: cookbook/helper/fdc_helper.py
================================================
import json
def get_all_nutrient_types():
f = open('') # <--- download the foundation food or any other dataset and retrieve all nutrition ID's from it https://fdc.nal.usda.gov/download-datasets.html
json_data = json.loads(f.read())
nutrients = {}
for food in json_data['FoundationFoods']:
for entry in food['foodNutrients']:
nutrients[entry['nutrient']['id']] = {'name': entry['nutrient']['name'], 'unit': entry['nutrient']['unitName']}
nutrient_ids = list(nutrients.keys())
nutrient_ids.sort()
for nid in nutrient_ids:
print('{', f'value: {nid}, text: "{nutrients[nid]["name"]} [{nutrients[nid]["unit"]}] ({nid})"', '},')
get_all_nutrient_types()
================================================
FILE: cookbook/helper/image_processing.py
================================================
import os
from io import BytesIO
from PIL import Image
def rescale_image_jpeg(image_object, base_width=1020):
img = Image.open(image_object)
icc_profile = img.info.get('icc_profile') # remember color profile to not mess up colors
width_percent = (base_width / float(img.size[0]))
height = int((float(img.size[1]) * float(width_percent)))
img = img.resize((base_width, height), Image.LANCZOS)
img_bytes = BytesIO()
img.save(img_bytes, 'JPEG', quality=90, optimize=True, icc_profile=icc_profile)
return img_bytes
def rescale_image_png(image_object, base_width=1020):
image_object = Image.open(image_object)
wpercent = (base_width / float(image_object.size[0]))
hsize = int((float(image_object.size[1]) * float(wpercent)))
img = image_object.resize((base_width, hsize), Image.LANCZOS)
im_io = BytesIO()
img.save(im_io, 'PNG', quality=90)
return im_io
def get_filetype(name):
try:
return os.path.splitext(name)[1]
except Exception:
return '.jpeg'
def is_file_type_allowed(filename, image_only=False):
is_file_allowed = False
allowed_file_types = ['.pdf', '.docx', '.xlsx', '.css', '.mp4', '.mov']
allowed_image_types = ['.png', '.jpg', '.jpeg', '.gif', '.webp']
check_list = allowed_image_types
if not image_only:
check_list += allowed_file_types
for file_type in check_list:
if filename.lower().endswith(file_type):
is_file_allowed = True
return is_file_allowed
def strip_image_meta(image_object, file_format):
image_object = Image.open(image_object)
data = list(image_object.getdata())
image_without_exif = Image.new(image_object.mode, image_object.size)
image_without_exif.putdata(data)
im_io = BytesIO()
image_without_exif.save(im_io, file_format)
return im_io
# TODO this whole file needs proper documentation, refactoring, and testing
# TODO also add env variable to define which images sizes should be compressed
# filetype argument can not be optional, otherwise this function will treat all images as if they were a jpeg
# Because it's no longer optional, no reason to return it
def handle_image(request, image_object, filetype):
try:
Image.open(image_object).verify()
except Exception:
return None
file_format = None
if filetype == '.jpeg' or filetype == '.jpg':
file_format = 'JPEG'
if filetype == '.png':
file_format = 'PNG'
if filetype == '.webp':
file_format = 'WEBP'
if (image_object.size / 1000) > 500: # if larger than 500 kb compress
if filetype == '.jpeg' or filetype == '.jpg':
return rescale_image_jpeg(image_object)
if filetype == '.png':
return rescale_image_png(image_object)
else:
return strip_image_meta(image_object, file_format)
# TODO webp and gifs bypass the scaling and metadata checks, fix
return image_object
================================================
FILE: cookbook/helper/ingredient_parser.py
================================================
import re
import string
import unicodedata
from django.db.models import Q
from cookbook.helper.automation_helper import AutomationEngine
from cookbook.models import Food, Ingredient, Unit
class IngredientParser:
request = None
ignore_rules = False
automation = None
def __init__(self, request, cache_mode=True, ignore_automations=False):
"""
Initialize ingredient parser
:param request: request context (to control caching, rule ownership, etc.)
:param cache_mode: defines if all rules should be loaded on initialization (good when parser is used many times) or if they should be retrieved every time (good when parser is not used many times in a row)
:param ignore_automations: ignore automation rules, allows to use ingredient parser without database access/request (request can be None)
"""
self.request = request
self.ignore_rules = ignore_automations
if not self.ignore_rules:
self.automation = AutomationEngine(self.request, use_cache=cache_mode)
def get_unit(self, unit_name):
"""
Get or create a unit for given space respecting possible automations
:param unit_name: string unit
:return: None if unit passed is invalid, Unit object otherwise
"""
if not unit_name:
return None
if len(unit_name) > 0:
if not self.ignore_rules:
unit_name = self.automation.apply_unit_automation(unit_name)
if unit_obj := Unit.objects.filter(space=self.request.space).filter(Q(name=unit_name) | Q(plural_name=unit_name)).first():
return unit_obj
else:
return Unit.objects.create(space=self.request.space, name=unit_name)
return None
def get_food(self, food_name):
"""
Get or create a food for given space respecting possible automations
:param food_name: string food
:return: None if food passed is invalid, Food object otherwise
"""
if not food_name:
return None
if len(food_name) > 0:
if not self.ignore_rules:
food_name = self.automation.apply_food_automation(food_name)
if food_obj := Food.objects.filter(space=self.request.space).filter(Q(name=food_name) | Q(plural_name=food_name)).first():
return food_obj
else:
return Food.objects.create(space=self.request.space, name=food_name)
return None
def parse_fraction(self, x):
if len(x) == 1 and 'fraction' in unicodedata.decomposition(x):
frac_split = unicodedata.decomposition(x[-1:]).split()
return (float((frac_split[1]).replace('003', ''))
/ float((frac_split[3]).replace('003', '')))
else:
frac_split = x.split('/')
if not len(frac_split) == 2:
raise ValueError
try:
return int(frac_split[0]) / int(frac_split[1])
except ZeroDivisionError:
raise ValueError
def parse_amount(self, x):
amount = 0
unit = None
note = ''
if x.strip() == '':
return amount, unit, note
did_check_frac = False
end = 0
while (end < len(x) and (x[end] in string.digits
or (
(x[end] == '.' or x[end] == ',' or x[end] == '/')
and end + 1 < len(x)
and x[end + 1] in string.digits
))):
end += 1
if end > 0:
if "/" in x[:end]:
amount = self.parse_fraction(x[:end])
else:
amount = float(x[:end].replace(',', '.'))
else:
amount = self.parse_fraction(x[0])
end += 1
did_check_frac = True
if end < len(x):
if did_check_frac:
unit = x[end:]
else:
try:
amount += self.parse_fraction(x[end])
unit = x[end + 1:]
except ValueError:
unit = x[end:]
if unit is not None and unit.strip() == '':
unit = None
if unit is not None and (unit.startswith('(') or unit.startswith(
'-')): # i dont know any unit that starts with ( or - so its likely an alternative like 1L (500ml) Water or 2-3
unit = None
note = x
return amount, unit, note
def parse_food_with_comma(self, tokens):
food = ''
note = ''
start = 0
# search for first occurrence of an argument ending in a comma
while start < len(tokens) and not tokens[start].endswith((',', ';', ':')):
start += 1
if start == len(tokens):
# no token ending in a comma found -> use everything as food
food = ' '.join(tokens)
else:
food = ' '.join(tokens[:start + 1])[:-1]
note = ' '.join(tokens[start + 1:])
return food, note
def parse_food(self, tokens):
food = ''
note = ''
if tokens[-1].endswith(')'):
# Check if the matching opening bracket is in the same token
if (not tokens[-1].startswith('(')) and ('(' in tokens[-1]):
return self.parse_food_with_comma(tokens)
# last argument ends with closing bracket -> look for opening bracket
start = len(tokens) - 1
while not tokens[start].startswith('(') and not start == 0:
start -= 1
if start == 0:
# the whole list is wrapped in brackets -> assume it is an error (e.g. assumed first argument was the unit) # noqa: E501
raise ValueError
elif start < 0:
# no opening bracket anywhere -> just ignore the last bracket
food, note = self.parse_food_with_comma(tokens)
else:
# opening bracket found -> split in food and note, remove brackets from note # noqa: E501
note = ' '.join(tokens[start:])[1:-1]
food = ' '.join(tokens[:start])
else:
food, note = self.parse_food_with_comma(tokens)
return food, note
def parse(self, ingredient):
"""
Main parsing function, takes an ingredient string (e.g. '1 l Water') and extracts amount, unit, food, ...
:param ingredient: string ingredient
:return: amount, unit (can be None), food, note (can be empty)
"""
# initialize default values
amount = 0
unit = None
food = ''
note = ''
unit_note = ''
ingredient = ingredient.strip()
if len(ingredient) == 0:
raise ValueError('string to parse cannot be empty')
if len(ingredient) > 512:
raise ValueError('cannot parse ingredients with more than 512 characters')
# remove leading commas, dots and other symbols that typically do not occur at the start of an ingredient string
ingredient = re.sub(r"^[,.\-_=+#*|\\/]+", "", ingredient)
# some people/languages put amount and unit at the end of the ingredient string
# if something like this is detected move it to the beginning so the parser can handle it
if len(ingredient) < 1000 and re.search(r'^([^\W\d_])+(.)*[1-9](\d)*\s*([^\W\d_])+', ingredient):
match = re.search(r'[1-9](\d)*\s*([^\W\d_])+', ingredient)
ingredient = ingredient[match.start():match.end()] + ' ' + ingredient.replace(ingredient[match.start():match.end()], '')
# if the string contains parenthesis early on remove it and place it at the end
# because its likely some kind of note
if re.match('(.){1,6}\\s\\((.[^\\(\\)])+\\)\\s', ingredient):
match = re.search('\\((.[^\\(])+\\)', ingredient)
ingredient = ingredient[:match.start()] + ingredient[match.end():] + ' ' + ingredient[match.start():match.end()]
# leading spaces before commas result in extra tokens, clean them out
ingredient = ingredient.replace(' ,', ',')
# handle "(from) - (to)" amounts by using the minimum amount and adding the range to the description
# "10.5 - 200 g XYZ" => "100 g XYZ (10.5 - 200)"
ingredient = re.sub("^(\\d+|\\d+[\\.,]\\d+) - (\\d+|\\d+[\\.,]\\d+) (.*)", "\\1 \\3 (\\1 - \\2)", ingredient)
# if amount and unit are connected add space in between
if re.match('([0-9])+([A-z])+\\s', ingredient):
ingredient = re.sub(r'(?<=([a-z])|\d)(?=(?(1)\d|[a-z]))', ' ', ingredient)
if not self.ignore_rules:
ingredient = self.automation.apply_transpose_automation(ingredient)
tokens = ingredient.split() # split at each space into tokens
if len(tokens) == 1:
# there only is one argument, that must be the food
food = tokens[0]
else:
try:
# try to parse first argument as amount
amount, unit, unit_note = self.parse_amount(tokens[0])
# only try to parse second argument as amount if there are at least
# three arguments if it already has a unit there can't be
# a fraction for the amount
if len(tokens) > 2:
never_unit_applied = False
if not self.ignore_rules:
tokens, never_unit_applied = self.automation.apply_never_unit_automation(tokens)
if never_unit_applied:
unit = tokens[1]
food, note = self.parse_food(tokens[2:])
else:
try:
if unit is not None:
# a unit is already found, no need to try the second argument for a fraction
# probably not the best method to do it, but I didn't want to make an if check and paste the exact same thing in the else as already is in the except
raise ValueError
# try to parse second argument as amount and add that, in case of '2 1/2' or '2 ½'
if tokens[1]:
amount += self.parse_fraction(tokens[1])
# assume that units can't end with a comma
if len(tokens) > 3 and not tokens[2].endswith(','):
# try to use third argument as unit and everything else as food, use everything as food if it fails
try:
food, note = self.parse_food(tokens[3:])
unit = tokens[2]
except ValueError:
food, note = self.parse_food(tokens[2:])
else:
food, note = self.parse_food(tokens[2:])
except ValueError:
# assume that units can't end with a comma
if not tokens[1].endswith(','):
# try to use second argument as unit and everything else as food, use everything as food if it fails
try:
food, note = self.parse_food(tokens[2:])
if unit is None:
unit = tokens[1]
else:
note = tokens[1]
except ValueError:
food, note = self.parse_food(tokens[1:])
else:
food, note = self.parse_food(tokens[1:])
else:
# only two arguments, first one is the amount
# which means this is the food
food = tokens[1]
except ValueError:
try:
# can't parse first argument as amount
# -> no unit -> parse everything as food
food, note = self.parse_food(tokens)
except ValueError:
food = ' '.join(tokens[1:])
if unit_note not in note:
note += ' ' + unit_note
if unit and not self.ignore_rules:
unit = self.automation.apply_unit_automation(unit)
if food and not self.ignore_rules:
food = self.automation.apply_food_automation(food)
if len(food) > Food._meta.get_field('name').max_length: # test if food name is to long
# try splitting it at a space and taking only the first arg
if len(food.split()) > 1 and len(food.split()[0]) < Food._meta.get_field('name').max_length:
note = ' '.join(food.split()[1:]) + ' ' + note
food = food.split()[0]
else:
note = food + ' ' + note
food = food[:Food._meta.get_field('name').max_length]
if len(food.strip()) == 0:
raise ValueError(f'Error parsing string {ingredient}, food cannot be empty')
return amount, unit, food, note[:Ingredient._meta.get_field('note').max_length].strip()
def parse_as_ingredient(self, text):
"""
Parse ingredient string into ingredient object with nested food information
:param text: ingredient string
:return: ingredient object
"""
amount, unit, food, note = self.parse(text)
ingredient = Ingredient(
amount=amount,
unit=None,
food=None,
space=self.request.space,
note=note,
original_text=text
)
if food:
ingredient.food = self.get_food(food)
if unit:
ingredient.unit = self.get_unit(unit)
return ingredient
================================================
FILE: cookbook/helper/mdx_attributes.py
================================================
import markdown
from markdown.treeprocessors import Treeprocessor
class StyleTreeprocessor(Treeprocessor):
def run_processor(self, node):
for child in node:
if child.tag == "table":
child.set("class", "markdown-table")
if child.tag == "th" or child.tag == "td":
child.set("class", "markdown-table-cell")
if child.tag == "img":
child.set("class", "img-fluid")
self.run_processor(child)
return node
def run(self, root):
self.run_processor(root)
return root
class MarkdownFormatExtension(markdown.Extension):
# md_ globals deprecated - see here:
def extendMarkdown(self, md):
md.treeprocessors.register(
StyleTreeprocessor(),
'StyleTreeprocessor',
10
)
================================================
FILE: cookbook/helper/mdx_urlize.py
================================================
"""
A more liberal autolinker
Inspired by Django's urlize function.
Positive examples:
>>> import markdown
>>> md = markdown.Markdown(extensions=['urlize'])
>>> md.convert('http://example.com/')
u''
>>> md.convert('go to http://example.com')
u'
Community
Get support, share best practices, discuss feature ideas, and meet other Tandoor users.
Discord
We have a public Discord server that anyone can join. This is where all our developers and contributors hang out and where we make announcements
', '').replace('
', ''), space=self.request.space, )) for s in file.find("div", {"itemprop": "recipeDirections"}).find_all("p"): if s.text == "": continue step.instruction += s.text + ' \n' step.save() for s in file.find("div", {"itemprop": "recipeNotes"}).find_all("p"): if s.text == "": continue step.instruction += s.text + ' \n' step.save() if file.find("span", {"itemprop": "recipeSource"}).text != '': step.instruction += "\n\n" + _("Imported from") + ": " + file.find("span", {"itemprop": "recipeSource"}).text step.save() recipe.steps.add(step) # import the Primary recipe image that is stored in the Zip try: for f in self.files: if '.zip' in f['name']: import_zip = ZipFile(f['file']) self.import_recipe_image(recipe, BytesIO(import_zip.read(file.find("img", class_="recipe-photo").get("src"))), filetype='.jpeg') except Exception as e: print(recipe.name, ': failed to import image ', str(e)) return recipe def get_file_from_recipe(self, recipe): raise NotImplementedError('Method not implemented in storage integration') ================================================ FILE: cookbook/integration/recipesage.py ================================================ import json import html from io import BytesIO from cookbook.helper.HelperFunctions import safe_request from cookbook.helper.ingredient_parser import IngredientParser from cookbook.helper.recipe_url_import import parse_servings, parse_servings_text, parse_time from cookbook.integration.integration import Integration from cookbook.models import Ingredient, Recipe, Step, Keyword class RecipeSage(Integration): def get_recipe_from_file(self, file): recipe = Recipe.objects.create( name=file['name'].strip(), created_by=self.request.user, internal=True, space=self.request.space) if file['recipeYield'] != '': recipe.servings = parse_servings(file['recipeYield']) recipe.servings_text = parse_servings_text(file['recipeYield']) try: if 'totalTime' in file and file['totalTime'] != '': recipe.working_time = parse_time(file['totalTime']) if 'timePrep' in file and file['prepTime'] != '': recipe.working_time = parse_time(file['timePrep']) recipe.waiting_time = parse_time(file['totalTime']) - parse_time(file['timePrep']) except Exception as e: print('failed to parse time ', str(e)) if 'isBasedOn' in file and file['isBasedOn']!="": recipe.source_url = file['isBasedOn'].strip() if 'description' in file and file['description'].strip()!="" and len(file['description'])<500: recipe.description = html.unescape(file['description'].strip()) recipe.save() ingredient_parser = IngredientParser(self.request, True) ingredients_added = False for s in file['recipeInstructions']: txt=html.unescape(s['text'].strip()) if txt != "": if txt[0]=='[' and txt[-1]==']': step = Step.objects.create( instruction=txt[1:-1], space=self.request.space, show_ingredients_table=self.request.user.userpreference.show_step_ingredients, ) else: step = Step.objects.create( instruction=txt, space=self.request.space, show_ingredients_table=self.request.user.userpreference.show_step_ingredients, ) if not ingredients_added: ingredients_added = True for ingredient in file['recipeIngredient']: ingredient=html.unescape(ingredient.strip()) if ingredient!="": if ingredient[0]=='[' and ingredient[-1]==']': step.ingredients.add(Ingredient.objects.create(is_header=True, original_text=ingredient[1:-1],space=self.request.space,note=ingredient[1:-1],)) else: amount, unit, food, note = ingredient_parser.parse(ingredient.strip()) f = ingredient_parser.get_food(food) u = ingredient_parser.get_unit(unit) step.ingredients.add(Ingredient.objects.create( food=f, unit=u, amount=amount, note=note, original_text=ingredient, space=self.request.space, )) recipe.steps.add(step) if len(file['image']) > 0: try: url = file['image'][0] response = safe_request('GET', url) self.import_recipe_image(recipe, BytesIO(response.content)) except Exception as e: print('failed to import image ', str(e)) if 'recipeCategory' in file and file['recipeCategory']!=[]: try: for k in file['recipeCategory']: recipe.keywords.add(Keyword.objects.get_or_create(space=self.request.space, name=k)[0]) except Exception as e: print("Failed to import keywords", str(e)) return recipe def get_file_from_recipe(self, recipe): data = { '@context': 'http://schema.org', '@type': 'Recipe', 'creditText': '', 'isBasedOn': '', 'name': recipe.name, 'description': recipe.description, 'prepTime': str(recipe.working_time), 'totalTime': str(recipe.waiting_time + recipe.working_time), 'recipeYield': str(recipe.servings), 'image': [], 'recipeCategory': [], 'comment': [], 'recipeIngredient': [], 'recipeInstructions': [], } for s in recipe.steps.all(): data['recipeInstructions'].append({ '@type': 'HowToStep', 'text': s.instruction }) for i in s.ingredients.all(): data['recipeIngredient'].append(f'{float(i.amount)} {i.unit} {i.food}') return data def get_files_from_recipes(self, recipes, el, cookie): json_list = [] for r in recipes: json_list.append(self.get_file_from_recipe(r)) el.exported_recipes += 1 el.msg += self.get_recipe_processed_msg(r) el.save() return [[self.get_export_file_name('json'), json.dumps(json_list)]] def split_recipe_file(self, file): try: data=json.loads(file.read().decode("utf-8")) if 'recipes' in data: return data['recipes'] else: return data except Exception as e: print("Failed to split file ", str(e)) ================================================ FILE: cookbook/integration/rezeptsuitede.py ================================================ import base64 from io import BytesIO from lxml import etree from cookbook.helper.ingredient_parser import IngredientParser from cookbook.helper.recipe_url_import import parse_servings, parse_servings_text from cookbook.integration.integration import Integration from cookbook.models import Ingredient, Keyword, Recipe, Step class Rezeptsuitede(Integration): def split_recipe_file(self, file): return etree.parse(file).getroot().getchildren() def get_recipe_from_file(self, file): recipe_xml = file recipe = Recipe.objects.create( name=recipe_xml.find('head').attrib['title'].strip(), created_by=self.request.user, internal=True, space=self.request.space) try: if recipe_xml.find('head').attrib['servingtype']: recipe.servings = parse_servings(recipe_xml.find('head').attrib['servingtype'].strip()) recipe.servings_text = parse_servings_text(recipe_xml.find('head').attrib['servingtype'].strip()) except KeyError: pass if recipe_xml.find('remark') is not None: # description is a list ofversion.py in your update "
"script to generate version information (done automatically in docker).\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:56
msgid "Plugins"
msgstr ""
#: .\cookbook\templates\system.html:67
msgid "Media Serving"
msgstr ""
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:123 .\cookbook\templates\system.html:134
msgid "Warning"
msgstr ""
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:125 .\cookbook\templates\system.html:134
msgid "Ok"
msgstr ""
#: .\cookbook\templates\system.html:70
msgid ""
"Serving media files directly using gunicorn/python is not recommend!\n"
" Please follow the steps described\n"
" here to update\n"
" your installation.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:76 .\cookbook\templates\system.html:91
#: .\cookbook\templates\system.html:104 .\cookbook\templates\system.html:115
#: .\cookbook\views\views.py:168
msgid "Everything is fine!"
msgstr ""
#: .\cookbook\templates\system.html:80
msgid "Secret Key"
msgstr ""
#: .\cookbook\templates\system.html:84
msgid ""
"\n"
" You do not have a SECRET_KEY configured in your "
".env file. Django defaulted to the\n"
" standard key\n"
" provided with the installation which is publicly know and "
"insecure! Please set\n"
" SECRET_KEY int the .env configuration "
"file.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:94
msgid "Debug Mode"
msgstr ""
#: .\cookbook\templates\system.html:98
msgid ""
"\n"
" This application is still running in debug mode. This is most "
"likely not needed. Turn of debug mode by\n"
" setting\n"
" DEBUG=0 int the .env configuration "
"file.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:107
msgid "Allowed Hosts"
msgstr ""
#: .\cookbook\templates\system.html:111
msgid ""
"\n"
" Your allowed hosts are configured to allow every host. This "
"might be ok in some setups but should be avoided. Please see the docs about "
"this.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:118
msgid "Database"
msgstr ""
#: .\cookbook\templates\system.html:121
msgid "Info"
msgstr ""
#: .\cookbook\templates\system.html:131 .\cookbook\templates\system.html:148
#, fuzzy
#| msgid "Use fractions"
msgid "Migrations"
msgstr "إستخدم الكسور"
#: .\cookbook\templates\system.html:137
msgid ""
"\n"
" Migrations should never fail!\n"
" Failed migrations will likely cause major parts of the app to "
"not function correctly.\n"
" If a migration fails make sure you are on the latest version and "
"if so please post the migration log and the overview below in a GitHub "
"issue.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "False"
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "True"
msgstr ""
#: .\cookbook\templates\system.html:268
msgid "Hide"
msgstr ""
#: .\cookbook\templates\system.html:271
msgid "Show"
msgstr ""
#: .\cookbook\templates\test2.html:6
msgid "Export Recipes"
msgstr ""
#: .\cookbook\templates\test2.html:14 .\cookbook\templates\test2.html:20
msgid "Export"
msgstr ""
#: .\cookbook\views\api.py:198 .\cookbook\views\api.py:326
msgid "Parameter updated_at incorrectly formatted"
msgstr ""
#: .\cookbook\views\api.py:351 .\cookbook\views\api.py:484
#, python-brace-format
msgid "No {self.basename} with id {pk} exists"
msgstr ""
#: .\cookbook\views\api.py:355
msgid "Cannot merge with the same object!"
msgstr ""
#: .\cookbook\views\api.py:362
#, python-brace-format
msgid "No {self.basename} with id {target} exists"
msgstr ""
#: .\cookbook\views\api.py:367
msgid "Cannot merge with child object!"
msgstr ""
#: .\cookbook\views\api.py:405
#, python-brace-format
msgid "{source.name} was merged successfully with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:411
#, python-brace-format
msgid "An error occurred attempting to merge {source.name} with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:493
#, python-brace-format
msgid "{child.name} was moved successfully to the root."
msgstr ""
#: .\cookbook\views\api.py:496 .\cookbook\views\api.py:514
msgid "An error occurred attempting to move "
msgstr ""
#: .\cookbook\views\api.py:499
msgid "Cannot move an object to itself!"
msgstr ""
#: .\cookbook\views\api.py:505
#, python-brace-format
msgid "No {self.basename} with id {parent} exists"
msgstr ""
#: .\cookbook\views\api.py:511
#, python-brace-format
msgid "{child.name} was moved successfully to parent {parent.name}"
msgstr ""
#: .\cookbook\views\api.py:992
#, python-brace-format
msgid "{obj.name} was removed from the shopping list."
msgstr ""
#: .\cookbook\views\api.py:997 .\cookbook\views\api.py:1627
#, python-brace-format
msgid "{obj.name} was added to the shopping list."
msgstr ""
#: .\cookbook\views\api.py:1239
msgid "Filter meal plans from date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1241
msgid "Filter meal plans to date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1244
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1404
msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1406
msgid "Query string matched (fuzzy) against object name."
msgstr ""
#: .\cookbook\views\api.py:1442
msgid ""
"Query string matched (fuzzy) against recipe name. In the future also "
"fulltext search."
msgstr ""
#: .\cookbook\views\api.py:1444
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr ""
#: .\cookbook\views\api.py:1445
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr ""
#: .\cookbook\views\api.py:1446
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1447
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1448
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1450
msgid "ID of food a recipe should have. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1451
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr ""
#: .\cookbook\views\api.py:1452
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1453
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr ""
#: .\cookbook\views\api.py:1454
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1456
msgid "ID of book a recipe should be in. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1457
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr ""
#: .\cookbook\views\api.py:1458
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1459
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr ""
#: .\cookbook\views\api.py:1460
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1462
msgid "ID of unit a recipe should have."
msgstr ""
#: .\cookbook\views\api.py:1464
msgid "Exact rating of recipe"
msgstr ""
#: .\cookbook\views\api.py:1465
msgid "Rating a recipe should have or greater."
msgstr ""
#: .\cookbook\views\api.py:1466
msgid "Rating a recipe should have or smaller."
msgstr ""
#: .\cookbook\views\api.py:1468
msgid "Filter recipes cooked X times."
msgstr ""
#: .\cookbook\views\api.py:1469
msgid "Filter recipes cooked X times or more."
msgstr ""
#: .\cookbook\views\api.py:1470
msgid "Filter recipes cooked X times or less."
msgstr ""
#: .\cookbook\views\api.py:1472
msgid "Filter recipes created on the given date."
msgstr ""
#: .\cookbook\views\api.py:1473
msgid "Filter recipes created on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1474
msgid "Filter recipes created on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1476 .\cookbook\views\api.py:1477
#: .\cookbook\views\api.py:1478
msgid "Filter recipes updated on the given date."
msgstr ""
#: .\cookbook\views\api.py:1480
msgid "Filter recipes last cooked on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1481
msgid "Filter recipes last cooked on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1483 .\cookbook\views\api.py:1484
msgid "Filter recipes lasts viewed on the given date."
msgstr ""
#: .\cookbook\views\api.py:1486
msgid "Filter recipes for ones created by the given user ID"
msgstr ""
#: .\cookbook\views\api.py:1487
msgid "If only internal recipes should be returned. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1488
msgid "Returns the results in randomized order. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1490
msgid ""
"Determines the order of the results. Options are: score,-score,name,-name,"
"lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-"
"created_at,lastviewed,-lastviewed"
msgstr ""
#: .\cookbook\views\api.py:1492
msgid "Returns new results first in search results. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1493
msgid ""
"Returns the given number of recently viewed recipes before search results "
"(if given)"
msgstr ""
#: .\cookbook\views\api.py:1494
msgid "ID of a custom filter. Returns all recipes matched by that filter."
msgstr ""
#: .\cookbook\views\api.py:1495
msgid "Filter recipes that can be made with OnHand food. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1773
msgid ""
"Return the PropertyTypes matching the property category. Repeat for "
"multiple."
msgstr ""
#: .\cookbook\views\api.py:1804 .\cookbook\views\api.py:1860
msgid "Returns only entries associated with the given mealplan id"
msgstr ""
#: .\cookbook\views\api.py:1858
msgid ""
"Returns only elements updated after the given timestamp in ISO 8601 format."
msgstr ""
#: .\cookbook\views\api.py:2031
msgid ""
"Return the Automations matching the automation type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2048
msgid ""
"Text field to store data that gets carried over to the UserSpace created "
"from the InviteLink"
msgstr ""
#: .\cookbook\views\api.py:2049
msgid "Only return InviteLinks that have not been used yet."
msgstr ""
#: .\cookbook\views\api.py:2076
msgid "Return the CustomFilters matching the model type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2176
msgid "Nothing to do."
msgstr ""
#: .\cookbook\views\api.py:2222
msgid "Invalid Url"
msgstr ""
#: .\cookbook\views\api.py:2228
msgid "Connection Refused."
msgstr ""
#: .\cookbook\views\api.py:2232
msgid "Bad URL Schema."
msgstr ""
#: .\cookbook\views\api.py:2257
msgid "No usable data could be found."
msgstr ""
#: .\cookbook\views\api.py:2286 .\cookbook\views\api.py:2434
msgid "You must select an AI provider to perform your request."
msgstr ""
#: .\cookbook\views\api.py:2293 .\cookbook\views\api.py:2441
msgid ""
"You don't have any credits remaining to use AI or AI features are not "
"enabled for your space."
msgstr ""
#: .\cookbook\views\api.py:2499 .\cookbook\views\api.py:2667
msgid "File is above space limit"
msgstr ""
#: .\cookbook\views\api.py:2522 .\cookbook\views\api.py:2684
msgid "Importing is not implemented for this provider"
msgstr ""
#: .\cookbook\views\api.py:2548
msgid ""
"The PDF Exporter is not enabled on this instance as it is still in an "
"experimental state."
msgstr ""
#: .\cookbook\views\api.py:2842
msgid "This feature is not yet available in the hosted version of tandoor!"
msgstr ""
#: .\cookbook\views\api.py:2863
msgid "Sync successful!"
msgstr ""
#: .\cookbook\views\api.py:2866
msgid "Error synchronizing with Storage"
msgstr ""
#: .\cookbook\views\views.py:89
msgid "This feature is not available in the demo version!"
msgstr "هذه الميزة غير موجودة في النسخة التجريبية!"
#: .\cookbook\views\views.py:110
msgid ""
"You have successfully created your own recipe space. Start by adding some "
"recipes or invite other people to join you."
msgstr ""
#: .\cookbook\views\views.py:171
#, python-format
msgid "PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!"
msgstr ""
#: .\cookbook\views\views.py:174
#, python-format
msgid "You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended"
msgstr ""
#: .\cookbook\views\views.py:178
msgid "Unable to determine PostgreSQL version."
msgstr ""
#: .\cookbook\views\views.py:182
msgid ""
"This application is not running with a Postgres database backend. This is ok "
"but not recommended as some features only work with postgres databases."
msgstr ""
#: .\cookbook\views\views.py:296
msgid ""
"The setup page can only be used to create the first "
"user! If you have forgotten your superuser credentials "
"please consult the django documentation on how to reset passwords."
msgstr ""
#: .\cookbook\views\views.py:304
msgid "Passwords dont match!"
msgstr ""
#: .\cookbook\views\views.py:312
msgid "User has been created, please login!"
msgstr ""
#: .\cookbook\views\views.py:352
msgid ""
"Reporting share links is not enabled for this instance. Please notify the "
"page administrator to report problems."
msgstr ""
#: .\cookbook\views\views.py:357
msgid ""
"Recipe sharing link has been disabled! For additional information please "
"contact the page administrator."
msgstr ""
#: .\cookbook\views\views.py:383
msgid "Manage recipes, shopping list, meal plans and more."
msgstr ""
#: .\cookbook\views\views.py:397 .\cookbook\views\views.py:398
msgid "Plan"
msgstr ""
#: .\cookbook\views\views.py:399
msgid "View your meal Plan"
msgstr ""
#: .\cookbook\views\views.py:418
#, fuzzy
#| msgid "Users with whom to share shopping lists."
msgid "View your shopping lists"
msgstr "المستخدمون الذين يمكن مشاركة قوائم التسوق معهم."
#~ msgid ""
#~ "Both fields are optional. If none are given the username will be "
#~ "displayed instead"
#~ msgstr ""
#~ "كلا الحقلين اختيارية. إذا لم يتم كتابة أي منها فسيتم عرض اسم المستخدم "
#~ "بدلاً من ذلك"
#~ msgid "Name"
#~ msgstr "الإسم"
#~ msgid "Keywords"
#~ msgstr "الكلمات الدالة"
#~ msgid "Preparation time in minutes"
#~ msgstr "وقت التحضير بالدقائق"
#~ msgid "Waiting time (cooking/baking) in minutes"
#~ msgstr "مدة الانتظار (الطبخ / الخبز) بالدقائق"
#~ msgid "Path"
#~ msgstr "المسار"
#~ msgid "Storage UID"
#~ msgstr "معرف وحدة التخزين الفريد"
#~ msgid "Add your comment: "
#~ msgstr "أضف تعليقك: "
#~ msgid "Leave empty for dropbox and enter app password for nextcloud."
#~ msgstr "اتركه فارغًا لـ dropbox وأدخل كلمة المرور لـ nextcloud."
#~ msgid "Leave empty for nextcloud and enter api token for dropbox."
#~ msgstr "اتركه فارغًا لـ nextcloud وأدخل الـ token للـ API لـ dropbox."
#~ msgid "Storage"
#~ msgstr "وحدة التخزين"
#~ msgid "Active"
#~ msgstr "فعال"
#~ msgid "Search String"
#~ msgstr "دالة البحث"
#~ msgid "File ID"
#~ msgstr "معرف الملف"
#~ msgid "Search Method"
#~ msgstr "طريقة البحث"
#~ msgid "Partial Match"
#~ msgstr "تطابق جزئي"
#~ msgid "Starts With"
#~ msgstr "إبدأ بـ"
#~ msgid "Fuzzy Search"
#~ msgstr "بحث غير محصور"
#~ msgid "Full Text"
#~ msgstr "نص كامل"
#~ msgid "Delete"
#~ msgstr "حذف"
#~ msgid "Settings"
#~ msgstr "إعدادات"
#~ msgid "Email"
#~ msgstr "البريد الإلكتروني"
#~ msgid "Password"
#~ msgstr "كلمة المرور"
#~ msgid "Units"
#~ msgstr "الوحدات"
#~ msgid "Automations"
#~ msgstr "الأتمتات"
#~ msgid "Files"
#~ msgstr "الملفات"
#~ msgid "Batch Edit"
#~ msgstr "تعديل جماعي"
#~ msgid "Edit"
#~ msgstr "تعديل"
#~ msgid "New"
#~ msgstr "جديد"
#~ msgid "Comments"
#~ msgstr "التعليقات"
#, fuzzy
#~| msgid "This feature is not available in the demo version!"
#~ msgid "This feature is not enabled by the server admin!"
#~ msgstr "هذه الميزة غير موجودة في النسخة التجريبية!"
#~ msgid "Ingredients"
#~ msgstr "المقادير"
#~ msgid "Default unit"
#~ msgstr "الوحدة الإفتراضية"
#~ msgid "Use KJ"
#~ msgstr "إستخدم الكيلو جول"
#~ msgid "Theme"
#~ msgstr "السمة"
#~ msgid "Navbar color"
#~ msgstr "لون شريط التنقل"
#~ msgid "Sticky navbar"
#~ msgstr "شريط تنقل ثابت"
#~ msgid "Default page"
#~ msgstr "الصفحة الإفتراضية"
#~ msgid "Show recent recipes"
#~ msgstr "عرض الوصفات الحديثة"
#~ msgid "Search style"
#~ msgstr "أسلوب البحث"
#~ msgid "Plan sharing"
#~ msgstr "مشاركة الخطة"
#~ msgid "Ingredient decimal places"
#~ msgstr "المنازل العشرية للمقدار"
#~ msgid "Shopping list auto sync period"
#~ msgstr "فترة المزامنة التلقائية لقائمة التسوق"
#~ msgid "Left-handed mode"
#~ msgstr "وضع اليد اليسرى"
#~ msgid ""
#~ "Color of the top navigation bar. Not all colors work with all themes, "
#~ "just try them out!"
#~ msgstr ""
#~ "لون شريط التنقل العلوي. كل الألوان لا تعمل مع جميع السمات ، جربها لتعرف!"
#~ msgid ""
#~ "Default Unit to be used when inserting a new ingredient into a recipe."
#~ msgstr "الوحدة الافتراضية التي ستسخدم عند إدخال مكون جديد في الوصفة."
#~ msgid ""
#~ "Enables support for fractions in ingredient amounts (e.g. convert "
#~ "decimals to fractions automatically)"
#~ msgstr ""
#~ "إتاحة دعم الكسور في كميات المقادير (على سبيل المثال ، تحويل الكسور "
#~ "العشرية إلى كسور تلقائيًا)"
#~ msgid "Display nutritional energy amounts in joules instead of calories"
#~ msgstr "عرض كميات الطاقة الغذائية بالجول بدلاً من السعرات الحرارية"
#~ msgid ""
#~ "Users with whom newly created meal plans should be shared by default."
#~ msgstr ""
#~ "المستخدمون الذين ستشارك خطط وجباتهم التي أنشاؤها حديثًا بشكل افتراضي."
#~ msgid "Show recently viewed recipes on search page."
#~ msgstr "عرض الوصفات التي تمت مشاهدتها مؤخراً على صفحة البحث."
#~ msgid "Number of decimals to round ingredients."
#~ msgstr "عدد الكسور العشرية لتقريب المكونات."
#~ msgid ""
#~ "If you want to be able to create and see comments underneath recipes."
#~ msgstr "إذا كنت تريد إنشاء ورؤية التعليقات أسفل الوصفات."
#~ msgid ""
#~ "Setting to 0 will disable auto sync. When viewing a shopping list the "
#~ "list is updated every set seconds to sync changes someone else might have "
#~ "made. Useful when shopping with multiple people but might use a little "
#~ "bit of mobile data. If lower than instance limit it is reset when saving."
#~ msgstr ""
#~ "سيؤدي الضبط على 0 إلى تعطيل المزامنة التلقائية. عند عرض قائمة التسوق يتم "
#~ "تحديث القائمة كل ثوانٍ محددة لمزامنة التغييرات التي ربما قام شخص آخر بها. "
#~ "هذه الخاصية مفيدة عند التسوق مع عدة أشخاص ولكن قد تستخدم القليل من بيانات "
#~ "الجوال. إذا كانت أقل من حد الوضع الحالي فسيتم إعادة تعيينها عند الحفظ."
#~ msgid "Makes the navbar stick to the top of the page."
#~ msgstr "يجعل شريط التنقل يثبت بأعلى الصفحة."
#~ msgid "Automatically add meal plan ingredients to shopping list."
#~ msgstr "أضف مقادير خطة الوجبة تلقائيًا إلى قائمة التسوق."
#~ msgid "Exclude ingredients that are on hand."
#~ msgstr "استبعاد المقادير الموجودة في متناول اليد."
#~ msgid "Will optimize the UI for use with your left hand."
#~ msgstr "سيعمل على تحسين واجهة المستخدم لاستخدامها بيدك اليسرى."
#~ msgid "You must provide at least a recipe or a title."
#~ msgstr "يجب عليك تقديم وصفة أو عنوان على الأقل."
#~ msgid "Share Shopping List"
#~ msgstr "مشاركة قائمة التسوق"
#~ msgid "Autosync"
#~ msgstr "مزامنة آلية"
#~ msgid "Auto Add Meal Plan"
#~ msgstr "إضافة خطة الوجبة تلقائيًا"
#~ msgid "Recent Days"
#~ msgstr "الأيام الأخيرة"
#~ msgid "A user is required"
#~ msgstr "لابد من وجود مستخدم"
================================================
FILE: cookbook/locale/bg/LC_MESSAGES/django.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR version.py in your update "
"script to generate version information (done automatically in docker).\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:56
msgid "Plugins"
msgstr ""
#: .\cookbook\templates\system.html:67
msgid "Media Serving"
msgstr "Обслужване на медии"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:123 .\cookbook\templates\system.html:134
msgid "Warning"
msgstr "Внимание"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:125 .\cookbook\templates\system.html:134
msgid "Ok"
msgstr "ОК"
#: .\cookbook\templates\system.html:70
msgid ""
"Serving media files directly using gunicorn/python is not recommend!\n"
" Please follow the steps described\n"
" here to update\n"
" your installation.\n"
" "
msgstr ""
"Сервирането на медийни файлове директно чрез gunicorn/python не се "
"препоръчва!\n"
" Моля, следвайте описаните стъпки\n"
" тук, за да актуализирате\n"
" вашата инсталация.\n"
" "
#: .\cookbook\templates\system.html:76 .\cookbook\templates\system.html:91
#: .\cookbook\templates\system.html:104 .\cookbook\templates\system.html:115
#: .\cookbook\views\views.py:168
msgid "Everything is fine!"
msgstr "Всичко е наред!"
#: .\cookbook\templates\system.html:80
msgid "Secret Key"
msgstr "Секретен ключ"
#: .\cookbook\templates\system.html:84
msgid ""
"\n"
" You do not have a SECRET_KEY configured in your "
".env file. Django defaulted to the\n"
" standard key\n"
" provided with the installation which is publicly know and "
"insecure! Please set\n"
" SECRET_KEY int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" Нямате конфигуриран SECRET_KEY във вашия файл "
".env. Django по подразбиране е на\n"
" стандартен ключ\n"
" снабден с инсталацията, която е публично известна и несигурна! "
"Моля, задайте\n"
" SECRET_KEY в конфигурационния файл .env"
"code>.\n"
" "
#: .\cookbook\templates\system.html:94
msgid "Debug Mode"
msgstr "Режим за отстраняване на грешки"
#: .\cookbook\templates\system.html:98
msgid ""
"\n"
" This application is still running in debug mode. This is most "
"likely not needed. Turn of debug mode by\n"
" setting\n"
" DEBUG=0 int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" Това приложение все още работи в режим на отстраняване на "
"грешки. Това най-вероятно не е необходимо. Изключване на режима за "
"отстраняване на грешки от\n"
" настройка\n"
" DEBUG=0 в конфигурационния файл .env"
"code>.\n"
" "
#: .\cookbook\templates\system.html:107
msgid "Allowed Hosts"
msgstr ""
#: .\cookbook\templates\system.html:111
msgid ""
"\n"
" Your allowed hosts are configured to allow every host. This "
"might be ok in some setups but should be avoided. Please see the docs about "
"this.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:118
msgid "Database"
msgstr "База данни"
#: .\cookbook\templates\system.html:121
msgid "Info"
msgstr "Информация"
#: .\cookbook\templates\system.html:131 .\cookbook\templates\system.html:148
#, fuzzy
#| msgid "Use fractions"
msgid "Migrations"
msgstr "Използвайте дроби"
#: .\cookbook\templates\system.html:137
msgid ""
"\n"
" Migrations should never fail!\n"
" Failed migrations will likely cause major parts of the app to "
"not function correctly.\n"
" If a migration fails make sure you are on the latest version and "
"if so please post the migration log and the overview below in a GitHub "
"issue.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "False"
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "True"
msgstr ""
#: .\cookbook\templates\system.html:268
msgid "Hide"
msgstr ""
#: .\cookbook\templates\system.html:271
#, fuzzy
#| msgid "Show Log"
msgid "Show"
msgstr "Покажи дневник"
#: .\cookbook\templates\test2.html:6
msgid "Export Recipes"
msgstr "Експортиране на рецепти"
#: .\cookbook\templates\test2.html:14 .\cookbook\templates\test2.html:20
msgid "Export"
msgstr "Експортиране"
#: .\cookbook\views\api.py:198 .\cookbook\views\api.py:326
msgid "Parameter updated_at incorrectly formatted"
msgstr "Параметърът updated_at е форматиран неправилно"
#: .\cookbook\views\api.py:351 .\cookbook\views\api.py:484
#, python-brace-format
msgid "No {self.basename} with id {pk} exists"
msgstr "Не съществува {self.basename} с идентификатор {pk}"
#: .\cookbook\views\api.py:355
msgid "Cannot merge with the same object!"
msgstr "Не може да се слее със същия обект!"
#: .\cookbook\views\api.py:362
#, python-brace-format
msgid "No {self.basename} with id {target} exists"
msgstr "Не съществува {self.basename} с идентификатор {target}"
#: .\cookbook\views\api.py:367
msgid "Cannot merge with child object!"
msgstr "Не може да се слее с дъщерен обект!"
#: .\cookbook\views\api.py:405
#, python-brace-format
msgid "{source.name} was merged successfully with {target.name}"
msgstr "{source.name} беше обединен успешно с {target.name}"
#: .\cookbook\views\api.py:411
#, python-brace-format
msgid "An error occurred attempting to merge {source.name} with {target.name}"
msgstr "Възникна грешка при опит за сливане на {source.name} с {target.name}"
#: .\cookbook\views\api.py:493
#, python-brace-format
msgid "{child.name} was moved successfully to the root."
msgstr "{child.name} беше преместен успешно в основния."
#: .\cookbook\views\api.py:496 .\cookbook\views\api.py:514
msgid "An error occurred attempting to move "
msgstr "Възникна грешка при опит за преместване "
#: .\cookbook\views\api.py:499
msgid "Cannot move an object to itself!"
msgstr "Не може да премести обект към себе си!"
#: .\cookbook\views\api.py:505
#, python-brace-format
msgid "No {self.basename} with id {parent} exists"
msgstr "Не съществува {self.basename} с идентификатор {parent}"
#: .\cookbook\views\api.py:511
#, python-brace-format
msgid "{child.name} was moved successfully to parent {parent.name}"
msgstr "{child.name} беше преместен успешно в родител {parent.name}"
#: .\cookbook\views\api.py:992
#, python-brace-format
msgid "{obj.name} was removed from the shopping list."
msgstr "{obj.name} беше премахнат от списъка за пазаруване."
#: .\cookbook\views\api.py:997 .\cookbook\views\api.py:1627
#, python-brace-format
msgid "{obj.name} was added to the shopping list."
msgstr "{obj.name} беше добавен към списъка за пазаруване."
#: .\cookbook\views\api.py:1239
msgid "Filter meal plans from date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1241
msgid "Filter meal plans to date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1244
#, fuzzy
#| msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
msgstr ""
"Идентификатор на рецептата, част от която е стъпка. За параметър за "
"многократно повторение."
#: .\cookbook\views\api.py:1404
msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgstr ""
"Идентификатор на рецептата, част от която е стъпка. За параметър за "
"многократно повторение."
#: .\cookbook\views\api.py:1406
msgid "Query string matched (fuzzy) against object name."
msgstr "Низът на заявката съответства (размито) спрямо името на обекта."
#: .\cookbook\views\api.py:1442
msgid ""
"Query string matched (fuzzy) against recipe name. In the future also "
"fulltext search."
msgstr ""
"Низът на заявката съвпада (размито) с името на рецептата. В бъдеще също и "
"пълнотекстово търсене."
#: .\cookbook\views\api.py:1444
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr ""
"Идентификатор на ключовата дума, която рецептата трябва да има. За параметър "
"за многократно повторение. Еквивалентно на keywords_or"
#: .\cookbook\views\api.py:1445
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr ""
"Идентификатори на ключови думи, повторете за няколко. Връща рецепти с някоя "
"от ключовите думи"
#: .\cookbook\views\api.py:1446
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr ""
"Идентификатори на ключови думи, повторете за няколко. Връща рецепти с всички "
"ключови думи."
#: .\cookbook\views\api.py:1447
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr ""
"Идентификатори на ключови думи, повторете за няколко. Изключва рецепти с "
"някоя от ключовите думи."
#: .\cookbook\views\api.py:1448
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr ""
"Идентификатори на ключови думи, повторете за няколко. Изключва рецепти с "
"всички ключови думи."
#: .\cookbook\views\api.py:1450
msgid "ID of food a recipe should have. For multiple repeat parameter."
msgstr ""
"Идентификация на храната, която рецептата трябва да има. За параметър за "
"многократно повторение."
#: .\cookbook\views\api.py:1451
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr ""
"Идентификатори на храни, повторете за няколко. Връща рецепти с някоя от "
"храните"
#: .\cookbook\views\api.py:1452
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr ""
"Идентификатори на храни, повторете за няколко. Връща рецептите с всички "
"храни."
#: .\cookbook\views\api.py:1453
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr ""
"Идентификатори на храни, повторете за няколко. Изключва рецепти с някоя от "
"храните."
#: .\cookbook\views\api.py:1454
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr ""
"Идентификатори на храни, повторете за няколко. Изключва рецепти с всички "
"храни."
#: .\cookbook\views\api.py:1456
msgid "ID of book a recipe should be in. For multiple repeat parameter."
msgstr ""
"Идентификатор на книгата, в която трябва да е рецепта. За параметър за "
"многократно повторение."
#: .\cookbook\views\api.py:1457
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr ""
"Идентификационен № на книги, повторете за няколко. Връща рецепти с някоя от "
"книгите"
#: .\cookbook\views\api.py:1458
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr ""
"Идентификационен № на книги, повторете за няколко. Връща рецептите с всички "
"книги."
#: .\cookbook\views\api.py:1459
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr ""
"Идентификационен № на книги, повторете за няколко. Изключва рецептите с "
"някоя от книгите."
#: .\cookbook\views\api.py:1460
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr ""
"Идентификационен № на книги, повторете за няколко. Изключва рецептите от "
"всички книги."
#: .\cookbook\views\api.py:1462
msgid "ID of unit a recipe should have."
msgstr "Идентификатор на единицата, която рецептата трябва да има."
#: .\cookbook\views\api.py:1464
msgid "Exact rating of recipe"
msgstr ""
#: .\cookbook\views\api.py:1465
#, fuzzy
#| msgid "ID of unit a recipe should have."
msgid "Rating a recipe should have or greater."
msgstr "Идентификатор на единицата, която рецептата трябва да има."
#: .\cookbook\views\api.py:1466
#, fuzzy
#| msgid "ID of unit a recipe should have."
msgid "Rating a recipe should have or smaller."
msgstr "Идентификатор на единицата, която рецептата трябва да има."
#: .\cookbook\views\api.py:1468
msgid "Filter recipes cooked X times."
msgstr ""
#: .\cookbook\views\api.py:1469
#, fuzzy
#| msgid ""
#| "Filter recipes cooked X times or more. Negative values returns cooked "
#| "less than X times"
msgid "Filter recipes cooked X times or more."
msgstr ""
"Филтрирайте рецепти, приготвени X пъти или повече. Отрицателните стойности "
"връщат приготвени по-малко от X пъти"
#: .\cookbook\views\api.py:1470
msgid "Filter recipes cooked X times or less."
msgstr ""
#: .\cookbook\views\api.py:1472
#, fuzzy
#| msgid ""
#| "Filter recipes created on or after YYYY-MM-DD. Prepending - filters on or "
#| "before date."
msgid "Filter recipes created on the given date."
msgstr ""
"Филтрирайте рецептите, създадени на или след ГГГГ-ММ-ДД. Предварително – "
"филтрира на или преди дата."
#: .\cookbook\views\api.py:1473
#, fuzzy
#| msgid ""
#| "Filter recipes created on or after YYYY-MM-DD. Prepending - filters on or "
#| "before date."
msgid "Filter recipes created on the given date or after."
msgstr ""
"Филтрирайте рецептите, създадени на или след ГГГГ-ММ-ДД. Предварително – "
"филтрира на или преди дата."
#: .\cookbook\views\api.py:1474
#, fuzzy
#| msgid ""
#| "Filter recipes created on or after YYYY-MM-DD. Prepending - filters on or "
#| "before date."
msgid "Filter recipes created on the given date or before."
msgstr ""
"Филтрирайте рецептите, създадени на или след ГГГГ-ММ-ДД. Предварително – "
"филтрира на или преди дата."
#: .\cookbook\views\api.py:1476 .\cookbook\views\api.py:1477
#: .\cookbook\views\api.py:1478
#, fuzzy
#| msgid ""
#| "Filter recipes updated on or after YYYY-MM-DD. Prepending - filters on or "
#| "before date."
msgid "Filter recipes updated on the given date."
msgstr ""
"Филтрирайте рецептите, актуализирани на или след ГГГГ-ММ-ДД. Предварително – "
"филтрира на или преди дата."
#: .\cookbook\views\api.py:1480
#, fuzzy
#| msgid ""
#| "Filter recipes last cooked on or after YYYY-MM-DD. Prepending - filters "
#| "on or before date."
msgid "Filter recipes last cooked on the given date or after."
msgstr ""
"Филтрирайте последно приготвените рецепти на или след ГГГГ-ММ-ДД. "
"Предварително – филтрира на или преди дата."
#: .\cookbook\views\api.py:1481
#, fuzzy
#| msgid ""
#| "Filter recipes last cooked on or after YYYY-MM-DD. Prepending - filters "
#| "on or before date."
msgid "Filter recipes last cooked on the given date or before."
msgstr ""
"Филтрирайте последно приготвените рецепти на или след ГГГГ-ММ-ДД. "
"Предварително – филтрира на или преди дата."
#: .\cookbook\views\api.py:1483 .\cookbook\views\api.py:1484
#, fuzzy
#| msgid ""
#| "Filter recipes lasts viewed on or after YYYY-MM-DD. Prepending - filters "
#| "on or before date."
msgid "Filter recipes lasts viewed on the given date."
msgstr ""
"Филтрирането на рецептите последно разглеждани на или след ГГГГ-ММ-ДД. "
"Предварително – филтрира на или преди дата."
#: .\cookbook\views\api.py:1486
msgid "Filter recipes for ones created by the given user ID"
msgstr ""
#: .\cookbook\views\api.py:1487
msgid "If only internal recipes should be returned. [true/false]"
msgstr "Ако трябва да се върнат само вътрешни рецепти. [вярно/невярно]"
#: .\cookbook\views\api.py:1488
msgid "Returns the results in randomized order. [true/false]"
msgstr "Връща резултатите в произволен ред. [вярно/невярно]"
#: .\cookbook\views\api.py:1490
msgid ""
"Determines the order of the results. Options are: score,-score,name,-name,"
"lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-"
"created_at,lastviewed,-lastviewed"
msgstr ""
#: .\cookbook\views\api.py:1492
msgid "Returns new results first in search results. [true/false]"
msgstr ""
"Първо връща нови резултати в резултатите от търсенето. [вярно/невярно]"
#: .\cookbook\views\api.py:1493
msgid ""
"Returns the given number of recently viewed recipes before search results "
"(if given)"
msgstr ""
#: .\cookbook\views\api.py:1494
msgid "ID of a custom filter. Returns all recipes matched by that filter."
msgstr ""
#: .\cookbook\views\api.py:1495
msgid "Filter recipes that can be made with OnHand food. [true/false]"
msgstr ""
"Филтрирайте рецепти, които могат да се приготвят с храна в наличност. [вярно/"
"невярно]"
#: .\cookbook\views\api.py:1773
msgid ""
"Return the PropertyTypes matching the property category. Repeat for "
"multiple."
msgstr ""
#: .\cookbook\views\api.py:1804 .\cookbook\views\api.py:1860
msgid "Returns only entries associated with the given mealplan id"
msgstr ""
#: .\cookbook\views\api.py:1858
msgid ""
"Returns only elements updated after the given timestamp in ISO 8601 format."
msgstr ""
#: .\cookbook\views\api.py:2031
#, fuzzy
#| msgid ""
#| "Returns the shopping list entry with a primary key of id. Multiple "
#| "values allowed."
msgid ""
"Return the Automations matching the automation type. Repeat for multiple."
msgstr ""
"Връща записа в списъка за пазаруване с първичен ключ на идентификатора. "
"Разрешени са множество стойности."
#: .\cookbook\views\api.py:2048
msgid ""
"Text field to store data that gets carried over to the UserSpace created "
"from the InviteLink"
msgstr ""
#: .\cookbook\views\api.py:2049
msgid "Only return InviteLinks that have not been used yet."
msgstr ""
#: .\cookbook\views\api.py:2076
#, fuzzy
#| msgid ""
#| "Returns the shopping list entry with a primary key of id. Multiple "
#| "values allowed."
msgid "Return the CustomFilters matching the model type. Repeat for multiple."
msgstr ""
"Връща записа в списъка за пазаруване с първичен ключ на идентификатора. "
"Разрешени са множество стойности."
#: .\cookbook\views\api.py:2176
msgid "Nothing to do."
msgstr "Няма нищо за правене."
#: .\cookbook\views\api.py:2222
msgid "Invalid Url"
msgstr ""
#: .\cookbook\views\api.py:2228
msgid "Connection Refused."
msgstr "Връзката е отказана."
#: .\cookbook\views\api.py:2232
msgid "Bad URL Schema."
msgstr "Лоша URL схема."
#: .\cookbook\views\api.py:2257
msgid "No usable data could be found."
msgstr "Не бяха намерени полезни данни."
#: .\cookbook\views\api.py:2286 .\cookbook\views\api.py:2434
msgid "You must select an AI provider to perform your request."
msgstr ""
#: .\cookbook\views\api.py:2293 .\cookbook\views\api.py:2441
msgid ""
"You don't have any credits remaining to use AI or AI features are not "
"enabled for your space."
msgstr ""
#: .\cookbook\views\api.py:2499 .\cookbook\views\api.py:2667
msgid "File is above space limit"
msgstr ""
#: .\cookbook\views\api.py:2522 .\cookbook\views\api.py:2684
msgid "Importing is not implemented for this provider"
msgstr "Импортирането не е реализирано за този доставчик"
#: .\cookbook\views\api.py:2548
msgid ""
"The PDF Exporter is not enabled on this instance as it is still in an "
"experimental state."
msgstr ""
"PDF експортирането не е активирано в този вид, тъй като все още е в "
"експериментално състояние."
#: .\cookbook\views\api.py:2842
msgid "This feature is not yet available in the hosted version of tandoor!"
msgstr "Тази функция все още не е налична в хостваната версия на tandoor!"
#: .\cookbook\views\api.py:2863
msgid "Sync successful!"
msgstr "Синхронизирането успешно!"
#: .\cookbook\views\api.py:2866
msgid "Error synchronizing with Storage"
msgstr "Грешка при синхронизирането с хранилището"
#: .\cookbook\views\views.py:89
msgid "This feature is not available in the demo version!"
msgstr "Тази функция не е налична в демо версията!"
#: .\cookbook\views\views.py:110
msgid ""
"You have successfully created your own recipe space. Start by adding some "
"recipes or invite other people to join you."
msgstr ""
"Успешно създадохте свое собствено пространство за рецепти. Започнете, като "
"добавите някои рецепти или поканете други хора да се присъединят към вас."
#: .\cookbook\views\views.py:171
#, python-format
msgid "PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!"
msgstr ""
#: .\cookbook\views\views.py:174
#, python-format
msgid "You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended"
msgstr ""
#: .\cookbook\views\views.py:178
msgid "Unable to determine PostgreSQL version."
msgstr ""
#: .\cookbook\views\views.py:182
#, fuzzy
#| msgid ""
#| "\n"
#| " This application is not running with a Postgres database "
#| "backend. This is ok but not recommended as some\n"
#| " features only work with postgres databases.\n"
#| " "
msgid ""
"This application is not running with a Postgres database backend. This is ok "
"but not recommended as some features only work with postgres databases."
msgstr ""
"\n"
" Това приложение не работи с база данни на Postgres. Това е "
"добре, но не се препоръчва като някои от\n"
" функциите работят само с бази данни Postgres.\n"
" "
#: .\cookbook\views\views.py:296
#, fuzzy
#| msgid ""
#| "The setup page can only be used to create the first user! If you have "
#| "forgotten your superuser credentials please consult the django "
#| "documentation on how to reset passwords."
msgid ""
"The setup page can only be used to create the first "
"user! If you have forgotten your superuser credentials "
"please consult the django documentation on how to reset passwords."
msgstr ""
"Страницата за настройка може да се използва само за създаване на първия "
"потребител! Ако сте забравили идентификационните си данни на супер "
"потребител, моля, вижте документацията на django за това как да нулирате "
"пароли."
#: .\cookbook\views\views.py:304
msgid "Passwords dont match!"
msgstr "Паролите не съвпадат!"
#: .\cookbook\views\views.py:312
msgid "User has been created, please login!"
msgstr "Потребителят е създаден, моля, влезте!"
#: .\cookbook\views\views.py:352
msgid ""
"Reporting share links is not enabled for this instance. Please notify the "
"page administrator to report problems."
msgstr ""
"Отчитането на връзките за споделяне не е активирано за тази инстанция. Моля, "
"уведомете администратора на страницата, за да съобщи за проблеми."
#: .\cookbook\views\views.py:357
msgid ""
"Recipe sharing link has been disabled! For additional information please "
"contact the page administrator."
msgstr ""
"Връзката за споделяне на рецепти е деактивирана! За допълнителна информация, "
"моля свържете се с администратора на страницата."
#: .\cookbook\views\views.py:383
msgid "Manage recipes, shopping list, meal plans and more."
msgstr ""
#: .\cookbook\views\views.py:397 .\cookbook\views\views.py:398
#, fuzzy
#| msgid "Meal-Plan"
msgid "Plan"
msgstr "План на хранене"
#: .\cookbook\views\views.py:399
msgid "View your meal Plan"
msgstr ""
#: .\cookbook\views\views.py:418
#, fuzzy
#| msgid "Users with whom to share shopping lists."
msgid "View your shopping lists"
msgstr "Потребители, с които да споделят списъци за пазаруване."
#~ msgid ""
#~ "Both fields are optional. If none are given the username will be "
#~ "displayed instead"
#~ msgstr ""
#~ "И двете полета са незадължителни. Ако не се даде нито едно, вместо него "
#~ "ще се покаже потребителското име"
#~ msgid "Name"
#~ msgstr "Име"
#~ msgid "Keywords"
#~ msgstr "Ключови думи"
#~ msgid "Preparation time in minutes"
#~ msgstr "Време за приготвяне в минути"
#~ msgid "Waiting time (cooking/baking) in minutes"
#~ msgstr "Време за изчакване (готвене/изпичане) в минути"
#~ msgid "Path"
#~ msgstr "Път"
#~ msgid "Storage UID"
#~ msgstr "Потребителски идентификатор на хранилището"
#~ msgid "Add your comment: "
#~ msgstr "Добавете вашия коментар: "
#~ msgid "Leave empty for dropbox and enter app password for nextcloud."
#~ msgstr ""
#~ "Оставете празно за dropbox и въведете парола за приложението за nextcloud."
#~ msgid "Leave empty for nextcloud and enter api token for dropbox."
#~ msgstr "Оставете празно за nextcloud и въведете api токен за dropbox."
#~ msgid ""
#~ "Leave empty for dropbox and enter only base url for nextcloud (/"
#~ "remote.php/webdav/ is added automatically)"
#~ msgstr ""
#~ "Оставете празно за dropbox и въведете само основен URL адрес за nextcloud "
#~ "(/remote.php/webdav/ се добавя автоматично)"
#~ msgid "Storage"
#~ msgstr "Хранилище"
#~ msgid "Active"
#~ msgstr "Активен"
#~ msgid "Search String"
#~ msgstr "Търсене низ"
#~ msgid "File ID"
#~ msgstr "Идентификатор на файла"
#~ msgid ""
#~ "Determines how fuzzy a search is if it uses trigram similarity matching "
#~ "(e.g. low values mean more typos are ignored)."
#~ msgstr ""
#~ "Определя колко размито е търсенето, ако използва съвпадение на триграми "
#~ "(напр. ниските стойности означават, че повече правописни грешки се "
#~ "игнорират)."
#~ msgid ""
#~ "Select type method of search. Click here "
#~ "for full description of choices."
#~ msgstr ""
#~ "Изберете тип метод за търсене. Кликнете тук "
#~ "за пълно описание на избора."
#~ msgid ""
#~ "Use fuzzy matching on units, keywords and ingredients when editing and "
#~ "importing recipes."
#~ msgstr ""
#~ "Използвайте размито съвпадение за единици, ключови думи и съставки, "
#~ "когато редактирате и импортирате рецепти."
#~ msgid ""
#~ "Fields to search ignoring accents. Selecting this option can improve or "
#~ "degrade search quality depending on language"
#~ msgstr ""
#~ "Полета за търсене без акценти. Избирането на тази опция може да подобри "
#~ "или влоши качеството на търсене в зависимост от езика"
#~ msgid ""
#~ "Fields to search for partial matches. (e.g. searching for 'Pie' will "
#~ "return 'pie' and 'piece' and 'soapie')"
#~ msgstr ""
#~ "Полета за търсене на частични съвпадения. (напр. търсенето на „Па“ ще "
#~ "върне „пай“, „парче“ и „супа“)"
#~ msgid ""
#~ "Fields to search for beginning of word matches. (e.g. searching for 'sa' "
#~ "will return 'salad' and 'sandwich')"
#~ msgstr ""
#~ "Полета за търсене на начало на съвпадения на думи. (напр. търсенето на "
#~ "„са“ ще върне „салата“ и „сандвич“)"
#~ msgid ""
#~ "Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find "
#~ "'recipe'.) Note: this option will conflict with 'web' and 'raw' methods "
#~ "of search."
#~ msgstr ""
#~ "Полета за „размито“ търсене. (напр. търсенето на „recpie“ ще намери "
#~ "„recipe“.) Забележка: тази опция ще е в конфликт с „web“ и „raw“ методи "
#~ "за търсене."
#~ msgid ""
#~ "Fields to full text search. Note: 'web', 'phrase', and 'raw' search "
#~ "methods only function with fulltext fields."
#~ msgstr ""
#~ "Полета за пълно текстово търсене. Забележка: Методите за търсене „web“, "
#~ "„phrase“ и „raw“ функционират само с пълнотекстови полета."
#~ msgid "Search Method"
#~ msgstr "Метод за търсене"
#~ msgid "Fuzzy Lookups"
#~ msgstr "Размити търсения"
#~ msgid "Ignore Accent"
#~ msgstr "Игнорирайте акцента"
#~ msgid "Partial Match"
#~ msgstr "Частично съвпадение"
#~ msgid "Starts With"
#~ msgstr "Започва с"
#~ msgid "Fuzzy Search"
#~ msgstr "Размито търсене"
#~ msgid "Full Text"
#~ msgstr "Пълен текст"
#~ msgid " is part of a recipe step and cannot be deleted"
#~ msgstr " е част от стъпка от рецептата и не може да бъде изтрита"
#~ msgid "Delete"
#~ msgstr "Изтриване"
#~ msgid "Settings"
#~ msgstr "Настройки"
#~ msgid "Email"
#~ msgstr "Електронна поща"
#~ msgid "Password"
#~ msgstr "Парола"
#~ msgid "Foods"
#~ msgstr "Храни"
#~ msgid "Units"
#~ msgstr "Мерни единици"
#~ msgid "Supermarket"
#~ msgstr "Супермаркет"
#~ msgid "Supermarket Category"
#~ msgstr "Категория супермаркет"
#~ msgid "Automations"
#~ msgstr "Автоматики"
#~ msgid "Files"
#~ msgstr "Файлове"
#~ msgid "Batch Edit"
#~ msgstr "Пакетно редактиране"
#~ msgid "History"
#~ msgstr "История"
#~ msgid "Ingredient Editor"
#~ msgstr "Редактор на съставки"
#, fuzzy
#~| msgid "Account Connections"
#~ msgid "Unit Conversions"
#~ msgstr "Връзки с профил"
#~ msgid "Create"
#~ msgstr "Създайте"
#~ msgid "External Recipes"
#~ msgstr "Външни рецепти"
#~ msgid "Space Settings"
#~ msgstr "Настройки на пространството"
#, fuzzy
#~| msgid "External Recipes"
#~ msgid "External Connectors"
#~ msgstr "Външни рецепти"
#~ msgid "Admin"
#~ msgstr "Админ"
#~ msgid "Markdown Guide"
#~ msgstr "Ръководство за намаление"
#~ msgid "GitHub"
#~ msgstr "GitHub"
#~ msgid "Translate Tandoor"
#~ msgstr "Преведете Tandoor"
#~ msgid "API Browser"
#~ msgstr "API браузър"
#~ msgid "Log out"
#~ msgstr "Излез от профила си"
#~ msgid "You are using the free version of Tandor"
#~ msgstr "Вие използвате безплатната версия на Tandor"
#~ msgid "Upgrade Now"
#~ msgstr "Надстройте сега"
#~ msgid "Batch edit Category"
#~ msgstr "Категория за групово редактиране"
#~ msgid "Batch edit Recipes"
#~ msgstr "Рецепти за групово редактиране"
#~ msgid "Add the specified keywords to all recipes containing a word"
#~ msgstr "Добавете посочените ключови думи към всички рецепти, съдържащи дума"
#~ msgid "Sync"
#~ msgstr "Синхронизиране"
#~ msgid "Manage watched Folders"
#~ msgstr "Управление на наблюдаваните папки"
#~ msgid ""
#~ "On this Page you can manage all storage folder locations that should be "
#~ "monitored and synced."
#~ msgstr ""
#~ "На тази страница можете да управлявате всички местоположения на папки за "
#~ "съхранение, които трябва да бъдат наблюдавани и синхронизирани."
#~ msgid "The path must be in the following format"
#~ msgstr "Пътят трябва да бъде в следния формат"
#~ msgid "Save"
#~ msgstr "Запазете"
#~ msgid "Manage External Storage"
#~ msgstr "Управление на външно хранилище"
#~ msgid "Sync Now!"
#~ msgstr "Синхронизирайте сега!"
#~ msgid "Show Recipes"
#~ msgstr "Покажи рецепти"
#~ msgid "Show Log"
#~ msgstr "Покажи дневник"
#~ msgid "Importing Recipes"
#~ msgstr "Импортиране на рецепти"
#~ msgid ""
#~ "This can take a few minutes, depending on the number of recipes in sync, "
#~ "please wait."
#~ msgstr ""
#~ "Това може да отнеме няколко минути, в зависимост от броя на "
#~ "синхронизираните рецепти, моля, изчакайте."
#~ msgid "Recipe Books"
#~ msgstr "Книги с рецепти"
#~ msgid "Import new Recipe"
#~ msgstr "Импортиране на нова рецепта"
#~ msgid "Edit Recipe"
#~ msgstr "Редактиране на рецепта"
#, python-format
#~ msgid "Are you sure you want to delete the %(title)s: %(object)s "
#~ msgstr "Наистина ли искате да изтриете %(title)s: %(object)s "
#~ msgid "Cascade"
#~ msgstr "Каскада"
#~ msgid "Cancel"
#~ msgstr "Отмяна"
#~ msgid "Edit"
#~ msgstr "Редактиране"
#~ msgid "View"
#~ msgstr "Изглед"
#~ msgid "Delete original file"
#~ msgstr "Изтрийте оригиналния файл"
#~ msgid "List"
#~ msgstr "Списък"
#~ msgid "Filter"
#~ msgstr "Филтрирайте"
#~ msgid "Import all"
#~ msgstr "Импортирайте всичко"
#~ msgid "New"
#~ msgstr "Нов"
#~ msgid "previous"
#~ msgstr "предишен"
#~ msgid "next"
#~ msgstr "следващ"
#~ msgid "View Log"
#~ msgstr "Преглед на дневника"
#~ msgid "Cook Log"
#~ msgstr "Дневник на готвене"
#~ msgid "Import"
#~ msgstr "Импортиране"
#~ msgid "Security Warning"
#~ msgstr "Предупреждение за сигурност"
#~ msgid ""
#~ "\n"
#~ " The Password and Token field are stored as plain text"
#~ "b> inside the database.\n"
#~ " This is necessary because they are needed to make API requests, "
#~ "but it also increases the risk of\n"
#~ " someone stealing it.
\n"
#~ " To limit the possible damage tokens or accounts with limited "
#~ "access can be used.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Полето Парола и символ се съхраняват като обикновен "
#~ "текст в базата данни.\n"
#~ " Това е необходимо, защото те са необходими за отправяне на "
#~ "заявки за API, но също така увеличава риска от\n"
#~ " някой да го открадне.
\n"
#~ " За ограничаване на възможните щети могат да се използват символи "
#~ "или профили с ограничен достъп.\n"
#~ " "
#~ msgid "You are currently offline!"
#~ msgstr "В момента сте извън линия!"
#~ msgid ""
#~ "The recipes listed below are available for offline viewing because you "
#~ "have recently viewed them. Keep in mind that data might be outdated."
#~ msgstr ""
#~ "Изброените по-долу рецепти са достъпни за гледане офлайн, тъй като "
#~ "наскоро сте ги гледали. Имайте предвид, че данните може да са остарели."
#, fuzzy
#~| msgid "Ingredient Editor"
#~ msgid "Property Editor"
#~ msgstr "Редактор на съставки"
#~ msgid "Comments"
#~ msgstr "Коментари"
#~ msgid "by"
#~ msgstr "от"
#~ msgid "Comment"
#~ msgstr "Коментар"
#~ msgid ""
#~ "There are many options to configure the search depending on your personal "
#~ "preferences."
#~ msgstr ""
#~ "Има много опции за конфигуриране на търсенето в зависимост от вашите "
#~ "лични предпочитания."
#~ msgid ""
#~ "Usually you do not need to configure any of them and can just "
#~ "stick with either the default or one of the following presets."
#~ msgstr ""
#~ "Обикновено не е необходимо да конфигурирате нито един от тях и "
#~ "можете просто да се придържате към стандартната или една от следните "
#~ "предварително зададени настройки."
#~ msgid ""
#~ "If you do want to configure the search you can read about the different "
#~ "options here."
#~ msgstr ""
#~ "Ако искате да конфигурирате търсенето, можете да прочетете за различните "
#~ "опции тук."
#~ msgid "Fuzzy"
#~ msgstr "Размит"
#~ msgid ""
#~ "Find what you need even if your search or the recipe contains typos. "
#~ "Might return more results than needed to make sure you find what you are "
#~ "looking for."
#~ msgstr ""
#~ "Намерете това, от което се нуждаете, дори ако вашето търсене или "
#~ "рецептата съдържат правописни грешки. Може да върне повече резултати, "
#~ "отколкото е необходимо, за да сте сигурни, че ще намерите това, което "
#~ "търсите."
#~ msgid "This is the default behavior"
#~ msgstr "Това е поведението по подразбиране"
#~ msgid "Apply"
#~ msgstr "Приложи"
#~ msgid "Precise"
#~ msgstr "Прецизно"
#~ msgid ""
#~ "Allows fine control over search results but might not return results if "
#~ "too many spelling mistakes are made."
#~ msgstr ""
#~ "Позволява фин контрол върху резултатите от търсенето, но може да не върне "
#~ "резултати, ако са направени твърде много правописни грешки."
#~ msgid "Perfect for large Databases"
#~ msgstr "Идеален за големи бази данни"
#~ msgid "Social"
#~ msgstr "Социални"
#~ msgid "Space:"
#~ msgstr "Пространство:"
#~ msgid "Manage Subscription"
#~ msgstr "Управление на абонамента"
#, fuzzy
#~| msgid "Create Space"
#~ msgid "Leave Space"
#~ msgstr "Създайте пространство"
#~ msgid "URL Import"
#~ msgstr "Импортиране на URL"
#~ msgid ""
#~ "Rating a recipe should have or greater. [0 - 5] Negative value filters "
#~ "rating less than."
#~ msgstr ""
#~ "Оценка на рецептата трябва да има или по-висока. [0 - 5] Отрицателна "
#~ "стойност филтрира оценка по-малка от."
#~ msgid ""
#~ "Returns the shopping list entry with a primary key of id. Multiple "
#~ "values allowed."
#~ msgstr ""
#~ "Връща записа в списъка за пазаруване с първичен ключ на идентификатора. "
#~ "Разрешени са множество стойности."
#, fuzzy
#~| msgid ""
#~| "Filter shopping list entries on checked. [true, false, both, recent"
#~| "b>]
- recent includes unchecked items and recently completed items."
#~ msgid ""
#~ "Filter shopping list entries on checked. [true, false, both, recent"
#~ "b>]
- recent includes unchecked items and recently "
#~ "completed items."
#~ msgstr ""
#~ "Филтрирайте записите в списъка за пазаруване на отметнато. [вярно, "
#~ "невярно, и двете, скорошни]
- скорошни включва неотметнати "
#~ "елементи и наскоро завършени елементи."
#~ msgid ""
#~ "Returns the shopping list entries sorted by supermarket category order."
#~ msgstr ""
#~ "Връща записите в списъка за пазаруване, сортирани по реда на категории "
#~ "супермаркети."
#, python-format
#~ msgid "Batch edit done. %(count)d recipe was updated."
#~ msgid_plural "Batch edit done. %(count)d Recipes where updated."
#~ msgstr[0] "Редакцията е извършена. %(count)d рецепта бе актуализирана."
#~ msgstr[1] "Редакцията е извършена. %(count)d рецептите бяха актуализирани."
#~ msgid "Monitor"
#~ msgstr "Монитор"
#~ msgid "Storage Backend"
#~ msgstr "Бекенд за съхранение"
#~ msgid ""
#~ "Could not delete this storage backend as it is used in at least one "
#~ "monitor."
#~ msgstr ""
#~ "Не можа да се изтрие този бекенд за съхранение, тъй като се използва в "
#~ "поне един монитор."
#, fuzzy
#~| msgid "Storage Backend"
#~ msgid "Connectors Config Backend"
#~ msgstr "Бекенд за съхранение"
#~ msgid "Invite Link"
#~ msgstr "Връзка за покана"
#, fuzzy
#~| msgid "Members"
#~ msgid "Space Membership"
#~ msgstr "Членове"
#~ msgid "You cannot edit this storage!"
#~ msgstr "Не можете да редактирате това хранилище!"
#~ msgid "Storage saved!"
#~ msgstr "Хранилището е запазено!"
#~ msgid "There was an error updating this storage backend!"
#~ msgstr "Възникна грешка при актуализирането на този бекенд за съхранение!"
#, fuzzy
#~| msgid "Changes saved!"
#~ msgid "Config saved!"
#~ msgstr "Промените са запазени!"
#~ msgid "Changes saved!"
#~ msgstr "Промените са запазени!"
#~ msgid "Error saving changes!"
#~ msgstr "Грешка при запазване на промените!"
#~ msgid "Import Log"
#~ msgstr "Дневник за импортиране"
#~ msgid "Discovery"
#~ msgstr "Откритие"
#~ msgid "Shopping List"
#~ msgstr "Списък за пазаруване"
#, fuzzy
#~| msgid "Storage Backend"
#~ msgid "Connector Config Backend"
#~ msgstr "Бекенд за съхранение"
#~ msgid "Invite Links"
#~ msgstr "Връзки за покани"
#~ msgid "Supermarkets"
#~ msgstr "Супермаркети"
#~ msgid "Shopping Categories"
#~ msgstr "Категории за пазаруване"
#~ msgid "Custom Filters"
#~ msgstr "Персонализирани филтри"
#~ msgid "Steps"
#~ msgstr "Стъпки"
#, fuzzy
#~| msgid "This feature is not available in the demo version!"
#~ msgid "This feature is not enabled by the server admin!"
#~ msgstr "Тази функция не е налична в демо версията!"
#~ msgid "Imported new recipe!"
#~ msgstr "Добавена бе нова рецепта!"
#~ msgid "There was an error importing this recipe!"
#~ msgstr "При импортирането на тази рецепта възникна грешка!"
#~ msgid "You do not have the required permissions to perform this action!"
#~ msgstr "Нямате необходимите разрешения за извършване на това действие!"
#~ msgid "Comment saved!"
#~ msgstr "Коментарът е запазен!"
#~ msgid "You must select at least one field to search!"
#~ msgstr "Трябва да изберете поне едно поле за търсене!"
#~ msgid ""
#~ "To use this search method you must select at least one full text search "
#~ "field!"
#~ msgstr ""
#~ "За да използвате този метод за търсене, трябва да изберете поне едно поле "
#~ "за пълно текстово търсене!"
#~ msgid "Fuzzy search is not compatible with this search method!"
#~ msgstr "Размитото търсене не е съвместимо с този метод за търсене!"
#~ msgid "Malformed Invite Link supplied!"
#~ msgstr "Malformed връзка за покана е предоставена!"
#~ msgid "Successfully joined space."
#~ msgstr "Успешно присъединяване към пространството."
#~ msgid "Invite Link not valid or already used!"
#~ msgstr "Връзката за покана не е валидна или вече е използвана!"
#~ msgid "Ingredients"
#~ msgstr "Съставки"
#~ msgid "Default unit"
#~ msgstr "Единица по подразбиране"
#~ msgid "Use KJ"
#~ msgstr "Използвай джаули"
#~ msgid "Theme"
#~ msgstr "Тема"
#~ msgid "Navbar color"
#~ msgstr "Цвят на навигационната лента"
#~ msgid "Sticky navbar"
#~ msgstr "Залепваща навигационна лента"
#~ msgid "Default page"
#~ msgstr "Страница по подразбиране"
#~ msgid "Show recent recipes"
#~ msgstr "Показване на последните рецепти"
#~ msgid "Search style"
#~ msgstr "Стил на търсене"
#~ msgid "Plan sharing"
#~ msgstr "Споделяне на план"
#~ msgid "Ingredient decimal places"
#~ msgstr "Съставките след десетичната запетая"
#~ msgid "Shopping list auto sync period"
#~ msgstr "Период на автоматично синхронизиране на списъка за пазаруване"
#~ msgid "Left-handed mode"
#~ msgstr "Режим за лява ръка"
#~ msgid ""
#~ "Color of the top navigation bar. Not all colors work with all themes, "
#~ "just try them out!"
#~ msgstr ""
#~ "Цвят на горната лента за навигация. Не всички цветове работят с всички "
#~ "теми, просто ги изпробвайте!"
#~ msgid ""
#~ "Default Unit to be used when inserting a new ingredient into a recipe."
#~ msgstr ""
#~ "Единица по подразбиране, която се използва при вмъкване на нова съставка "
#~ "в рецепта."
#~ msgid ""
#~ "Enables support for fractions in ingredient amounts (e.g. convert "
#~ "decimals to fractions automatically)"
#~ msgstr ""
#~ "Позволява поддръжка на дроби в количества на съставките (например "
#~ "автоматично преобразуване на десетичните дроби в дроби)"
#~ msgid "Display nutritional energy amounts in joules instead of calories"
#~ msgstr ""
#~ "Покажете хранителните енергийни количества в джаули вместо в калории"
#~ msgid ""
#~ "Users with whom newly created meal plans should be shared by default."
#~ msgstr ""
#~ "Потребители, с които новосъздадените планове за хранене трябва да се "
#~ "споделят по подразбиране."
#~ msgid "Show recently viewed recipes on search page."
#~ msgstr "Показване на наскоро гледани рецепти на страницата за търсене."
#~ msgid "Number of decimals to round ingredients."
#~ msgstr "Брой знаци след десетичната запетая до закръгляне на съставката."
#~ msgid ""
#~ "If you want to be able to create and see comments underneath recipes."
#~ msgstr ""
#~ "Ако искате да можете да създавате и виждате коментари под рецептите."
#~ msgid ""
#~ "Setting to 0 will disable auto sync. When viewing a shopping list the "
#~ "list is updated every set seconds to sync changes someone else might have "
#~ "made. Useful when shopping with multiple people but might use a little "
#~ "bit of mobile data. If lower than instance limit it is reset when saving."
#~ msgstr ""
#~ "Задаването на 0 ще деактивира автоматичното синхронизиране. Когато "
#~ "разглеждате списък за пазаруване, списъкът се актуализира на всеки "
#~ "зададени секунди, за да синхронизира промените, които някой друг може да "
#~ "е направил. Полезно при пазаруване с множество хора, но може да използва "
#~ "малко мобилни данни. Ако е по-нисък от лимита на инстанцията, той се "
#~ "нулира при записване."
#~ msgid "Makes the navbar stick to the top of the page."
#~ msgstr ""
#~ "Кара навигационната лента да се придържа към горната част на страницата."
#~ msgid "Automatically add meal plan ingredients to shopping list."
#~ msgstr ""
#~ "Автоматично добавяне на съставки за план за хранене към списъка за "
#~ "пазаруване."
#~ msgid "Exclude ingredients that are on hand."
#~ msgstr "Изключете съставките, които са под ръка."
#~ msgid "Will optimize the UI for use with your left hand."
#~ msgstr ""
#~ "Ще оптимизира потребителския интерфейс за използване с лявата ви ръка."
#~ msgid "You must provide at least a recipe or a title."
#~ msgstr "Трябва да предоставите поне рецепта или заглавие."
#~ msgid "You can list default users to share recipes with in the settings."
#~ msgstr ""
#~ "Можете да посочите потребители по подразбиране, с които да споделяте "
#~ "рецепти в настройките."
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "Можете да използвате markdown, за да форматирате това поле. Вижте документите тук"
#~ msgid ""
#~ "Users will see all items you add to your shopping list. They must add "
#~ "you to see items on their list."
#~ msgstr ""
#~ "Потребителите ще видят всички артикули, които добавите към списъка си за "
#~ "пазаруване. Те трябва да ви добавят, за да видят елементите в техния "
#~ "списък."
#~ msgid ""
#~ "When adding a meal plan to the shopping list (manually or automatically), "
#~ "include all related recipes."
#~ msgstr ""
#~ "Когато добавяте план за хранене към списъка за пазаруване (ръчно или "
#~ "автоматично), включете всички свързани рецепти."
#~ msgid ""
#~ "When adding a meal plan to the shopping list (manually or automatically), "
#~ "exclude ingredients that are on hand."
#~ msgstr ""
#~ "Когато добавяте план за хранене към списъка за пазаруване (ръчно или "
#~ "автоматично), изключете съставките, които са под ръка."
#~ msgid "Default number of hours to delay a shopping list entry."
#~ msgstr ""
#~ "Брой часове по подразбиране за забавяне на записа в списъка за пазаруване."
#~ msgid "Filter shopping list to only include supermarket categories."
#~ msgstr ""
#~ "Филтрирайте списъка за пазаруване, за да включите само категории "
#~ "супермаркети."
#~ msgid "Days of recent shopping list entries to display."
#~ msgstr "Дни на последните записи в списъка за пазаруване за показване."
#~ msgid "Mark food 'On Hand' when checked off shopping list."
#~ msgstr ""
#~ "Маркирайте храната „На ръка“, когато сте отметнати от списъка за "
#~ "пазаруване."
#~ msgid "Delimiter to use for CSV exports."
#~ msgstr "Ограничител за използване за CSV експортиране."
#~ msgid "Prefix to add when copying list to the clipboard."
#~ msgstr "Префикс за добавяне при копиране на списък в клипборда."
#~ msgid "Share Shopping List"
#~ msgstr "Споделете списък за пазаруване"
#~ msgid "Autosync"
#~ msgstr "Автоматично синхронизиране"
#~ msgid "Auto Add Meal Plan"
#~ msgstr "Автоматично добавяне на план за хранене"
#~ msgid "Exclude On Hand"
#~ msgstr "Изключване на ръка"
#~ msgid "Include Related"
#~ msgstr "Включете свързани"
#~ msgid "Default Delay Hours"
#~ msgstr "Часове на забавяне по подразбиране"
#~ msgid "Filter to Supermarket"
#~ msgstr "Филтрирайте до супермаркет"
#~ msgid "Recent Days"
#~ msgstr "Последни дни"
#~ msgid "CSV Delimiter"
#~ msgstr "CSV разделител"
#~ msgid "List Prefix"
#~ msgstr "Префикс за списък"
#~ msgid "Auto On Hand"
#~ msgstr "Автоматично под ръка"
#~ msgid "Reset Food Inheritance"
#~ msgstr "Нулиране на хранителното наследство"
#~ msgid "Reset all food to inherit the fields configured."
#~ msgstr "Нулирайте цялата храна, за да наследите конфигурираните полета."
#~ msgid "Fields on food that should be inherited by default."
#~ msgstr "Полета за храна, които трябва да бъдат наследени по подразбиране."
#~ msgid "Show recipe counts on search filters"
#~ msgstr "Показване на броя рецепти във филтрите за търсене"
#~ msgid "One of queryset or hash_key must be provided"
#~ msgstr "Трябва да се предостави един от queryset или hash_key"
#~ msgid "Small"
#~ msgstr "Малък"
#~ msgid "Large"
#~ msgstr "Голям"
#~ msgid "A user is required"
#~ msgstr "Изисква се потребител"
#~ msgid "Edit Ingredients"
#~ msgstr "Редактиране на съставките"
#~ msgid ""
#~ "\n"
#~ " The following form can be used if, accidentally, two (or more) "
#~ "units or ingredients where created that should be\n"
#~ " the same.\n"
#~ " It merges two units or ingredients and updates all recipes using "
#~ "them.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Следният формуляр може да се използва, ако случайно са създадени "
#~ "две (или повече) единици или съставки, които трябва да бъдат създадени\n"
#~ " същото.\n"
#~ " Той обединява две единици или съставки и актуализира всички "
#~ "рецепти, които ги използват.\n"
#~ " "
#~ msgid "Are you sure that you want to merge these two units?"
#~ msgstr "Сигурни ли сте, че искате да обедините тези две единици?"
#~ msgid "Are you sure that you want to merge these two ingredients?"
#~ msgstr "Сигурни ли сте, че искате да обедините тези две съставки?"
#~ msgid "Import Recipes"
#~ msgstr "Импортиране на рецепти"
#~ msgid "Close"
#~ msgstr "Затвори"
#~ msgid "Open Recipe"
#~ msgstr "Отворете рецепта"
#~ msgid "Meal Plan View"
#~ msgstr "Изглед на план за хранене"
#~ msgid "Created by"
#~ msgstr "Създадено от"
#~ msgid "Shared with"
#~ msgstr "Споделено с"
#~ msgid "Never cooked before."
#~ msgstr "Никога не е готвен преди."
#~ msgid "Other meals on this day"
#~ msgstr "Други хранения на този ден"
#~ msgid "Recipe Image"
#~ msgstr "Изображение на рецептата"
#~ msgid "Preparation time ca."
#~ msgstr "Време за приготвяне."
#~ msgid "Waiting time ca."
#~ msgstr "Време за изчакване."
#~ msgid "External"
#~ msgstr "Външен"
#~ msgid "Log Cooking"
#~ msgstr "Дневник на готвене"
#~ msgid "Account"
#~ msgstr "Профил"
#~ msgid "Preferences"
#~ msgstr "Предпочитания"
#~ msgid "API-Settings"
#~ msgstr "API-Настройки"
#~ msgid "Search-Settings"
#~ msgstr "Търсене-Настройки"
#~ msgid "Shopping-Settings"
#~ msgstr "Пазаруване-Настройки"
#~ msgid "Name Settings"
#~ msgstr "Настройки за име"
#~ msgid "Account Settings"
#~ msgstr "Настройки на профила"
#~ msgid "Emails"
#~ msgstr "Имейли"
#~ msgid "Language"
#~ msgstr "Език"
#~ msgid "Style"
#~ msgstr "Стил"
#~ msgid "API Token"
#~ msgstr "API Символ"
#~ msgid ""
#~ "You can use both basic authentication and token based authentication to "
#~ "access the REST API."
#~ msgstr ""
#~ "Можете да използвате както основно удостоверяване, така и удостоверяване, "
#~ "базирано на символ, за достъп до REST API."
#~ msgid ""
#~ "Use the token as an Authorization header prefixed by the word token as "
#~ "shown in the following examples:"
#~ msgstr ""
#~ "Използвайте символа като заглавка за упълномощаване с префикс на думата "
#~ "символ, както е показано в следните примери:"
#~ msgid "or"
#~ msgstr "или"
#~ msgid "Shopping Settings"
#~ msgstr "Настройки за пазаруване"
#~ msgid "Number of objects"
#~ msgstr "Брой обекти"
#~ msgid "Recipe Imports"
#~ msgstr "Внасяне на рецепти"
#~ msgid "Objects stats"
#~ msgstr "Статистика на обектите"
#~ msgid "Recipes without Keywords"
#~ msgstr "Рецепти без ключови думи"
#~ msgid "Internal Recipes"
#~ msgstr "Вътрешни рецепти"
#~ msgid "Invite User"
#~ msgstr "Поканете потребител"
#~ msgid "User"
#~ msgstr "Потребител"
#~ msgid "Groups"
#~ msgstr "Групи"
#~ msgid "admin"
#~ msgstr "админ"
#~ msgid "user"
#~ msgstr "потребител"
#~ msgid "guest"
#~ msgstr "гост"
#~ msgid "remove"
#~ msgstr "Премахване"
#~ msgid "Update"
#~ msgstr "Актуализация"
#~ msgid "You cannot edit yourself."
#~ msgstr "Не можете да редактирате себе си."
#~ msgid "There are no members in your space yet!"
#~ msgstr "Все още няма членове във вашето пространство!"
#~ msgid "Stats"
#~ msgstr "Статистика"
#~ msgid "Statistics"
#~ msgstr "Статистики"
#~ msgid "Show Links"
#~ msgstr "Показване на връзки"
#~ msgid "Recipe Book"
#~ msgstr "Книга с рецепти"
#~ msgid "Bookmarks"
#~ msgstr "Отметки"
#~ msgid "Invite link successfully send to user."
#~ msgstr "Връзката за покана е изпратена успешно до потребителя."
#~ msgid ""
#~ "You have send to many emails, please share the link manually or wait a "
#~ "few hours."
#~ msgstr ""
#~ "Изпратили сте на много имейли, моля, споделете връзката ръчно или "
#~ "изчакайте няколко часа."
#~ msgid "Email could not be sent to user. Please share the link manually."
#~ msgstr ""
#~ "Имейлът не можа да бъде изпратен до потребителя. Моля, споделете връзката "
#~ "ръчно."
#~ msgid ""
#~ "You are already member of a space and therefore cannot join this one."
#~ msgstr ""
#~ "Вече сте член на пространство и следователно не можете да се присъедините "
#~ "към това."
================================================
FILE: cookbook/locale/ca/LC_MESSAGES/django.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
# Translators:
# Rubens Rodri , 2020
# gimy16 , 2021
# Miguel Canteras , 2021
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-22 20:15+0200\n"
"PO-Revision-Date: 2026-01-18 02:57+0000\n"
"Last-Translator: Oitantksi \n"
"Language-Team: Catalan \n"
"Language: ca\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.13.3\n"
#: .\cookbook\forms.py:50
msgid "Default"
msgstr "Per defecte"
#: .\cookbook\forms.py:77
msgid ""
"To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything."
msgstr ""
"Per evitar duplicats, s'ignoren les receptes amb el mateix nom que les "
"existents. Marqueu aquesta casella per importar-ho tot."
#: .\cookbook\forms.py:108
msgid "Maximum number of users for this space reached."
msgstr "Nombre màxim d'usuaris assolit per a aquest espai."
#: .\cookbook\forms.py:114
msgid "Email address already taken!"
msgstr "Adreça de correu electrònic existent!"
#: .\cookbook\forms.py:121
msgid ""
"An email address is not required but if present the invite link will be sent "
"to the user."
msgstr ""
"No cal una adreça de correu electrònic, però si està present, s'enviarà "
"l'enllaç d'invitació a l'usuari."
#: .\cookbook\forms.py:133
msgid "Name already taken."
msgstr "Nom agafat."
#: .\cookbook\forms.py:144 .\cookbook\forms.py:158
msgid "Accept Terms and Privacy"
msgstr "Accepteu les condicions i la privadesa"
#: .\cookbook\helper\AllAuthCustomAdapter.py:41
msgid ""
"In order to prevent spam, the requested email was not send. Please wait a "
"few minutes and try again."
msgstr ""
"Per evitar spam, no s'ha enviat el correu electrònic sol·licitat. Espereu "
"uns minuts i torneu-ho a provar."
#: .\cookbook\helper\permission_helper.py:165
#: .\cookbook\helper\permission_helper.py:186 .\cookbook\views\views.py:138
msgid "You are not logged in and therefore cannot view this page!"
msgstr "No heu iniciat la sessió, no podeu veure aquesta pàgina!"
#: .\cookbook\helper\permission_helper.py:168
#: .\cookbook\helper\permission_helper.py:173
#: .\cookbook\helper\permission_helper.py:198
#: .\cookbook\helper\permission_helper.py:265
#: .\cookbook\helper\permission_helper.py:279
#: .\cookbook\helper\permission_helper.py:290
#: .\cookbook\helper\permission_helper.py:301
#: .\cookbook\helper\permission_helper.py:317
#: .\cookbook\helper\permission_helper.py:343
#: .\cookbook\helper\permission_helper.py:359
msgid "You do not have the required permissions to view this page!"
msgstr "No teniu els permisos necessaris per veure aquesta pàgina!"
#: .\cookbook\helper\permission_helper.py:191
#: .\cookbook\helper\permission_helper.py:214
#: .\cookbook\helper\permission_helper.py:236
#: .\cookbook\helper\permission_helper.py:251
msgid "You cannot interact with this object as it is not owned by you!"
msgstr ""
"No pots interaccionar amb aquest objecte ja que no és de la teva propietat!"
#: .\cookbook\helper\permission_helper.py:420
msgid "You have reached the maximum number of recipes for your space."
msgstr "Has arribat al nombre màxim de receptes per al vostre espai."
#: .\cookbook\helper\permission_helper.py:432
msgid "You have more users than allowed in your space."
msgstr "Tens més usuaris dels permesos al teu espai."
#: .\cookbook\helper\recipe_url_import.py:319
msgid "reverse rotation"
msgstr "Direcció inversa"
#: .\cookbook\helper\recipe_url_import.py:320
msgid "careful rotation"
msgstr "Rotació amb cura"
#: .\cookbook\helper\recipe_url_import.py:321
msgid "knead"
msgstr "amassar"
#: .\cookbook\helper\recipe_url_import.py:322
msgid "thicken"
msgstr "espessir"
#: .\cookbook\helper\recipe_url_import.py:323
msgid "warm up"
msgstr "preescalfar"
#: .\cookbook\helper\recipe_url_import.py:324
msgid "ferment"
msgstr "fermentar"
#: .\cookbook\helper\recipe_url_import.py:325
msgid "slow cook"
msgstr "cocció lenta"
#: .\cookbook\helper\recipe_url_import.py:326
msgid "egg boiler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:327
msgid "kettle"
msgstr "tetera"
#: .\cookbook\helper\recipe_url_import.py:328
msgid "blend"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:329
msgid "pre-clean"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:330
msgid "high temperature"
msgstr "alta temperatura"
#: .\cookbook\helper\recipe_url_import.py:331
msgid "rice cooker"
msgstr "arrossera"
#: .\cookbook\helper\recipe_url_import.py:332
msgid "caramelize"
msgstr "caramelitzar"
#: .\cookbook\helper\recipe_url_import.py:333
msgid "peeler"
msgstr "pelador"
#: .\cookbook\helper\recipe_url_import.py:334
msgid "slicer"
msgstr "mandolina"
#: .\cookbook\helper\recipe_url_import.py:335
msgid "grater"
msgstr "ratllador"
#: .\cookbook\helper\recipe_url_import.py:336
msgid "spiralizer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:337
msgid "sous-vide"
msgstr "sous-vide"
#: .\cookbook\helper\shopping_helper.py:150
msgid "You must supply a servings size"
msgstr "Heu de proporcionar una mida de porcions"
#: .\cookbook\helper\template_helper.py:97
#: .\cookbook\helper\template_helper.py:99
#: .\cookbook\helper\template_helper.py:101
msgid "Could not parse template code."
msgstr "No s'ha pogut analitzar el codi de la plantilla."
#: .\cookbook\integration\copymethat.py:44
#: .\cookbook\integration\melarecipes.py:37
msgid "Favorite"
msgstr "Preferit"
#: .\cookbook\integration\copymethat.py:50
msgid "I made this"
msgstr "Ho he fet jo"
#: .\cookbook\integration\integration.py:238
msgid ""
"Importer expected a .zip file. Did you choose the correct importer type for "
"your data ?"
msgstr ""
"S'esperava un fitxer .zip. Heu escollit el tipus d'importador correcte per a "
"les vostres dades?"
#: .\cookbook\integration\integration.py:241
msgid ""
"An unexpected error occurred during the import. Please make sure you have "
"uploaded a valid file."
msgstr ""
"S'ha produït un error inesperat durant la importació. Assegureu-vos que heu "
"penjat un fitxer vàlid."
#: .\cookbook\integration\integration.py:246
msgid "The following recipes were ignored because they already existed:"
msgstr "Les receptes següents s'han ignorat perquè ja existien:"
#: .\cookbook\integration\integration.py:250
#, python-format
msgid "Imported %s recipes."
msgstr "%s Receptes Importades."
#: .\cookbook\integration\mealie1.py:210
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "Calories"
msgstr "Calories"
#: .\cookbook\integration\mealie1.py:211
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
msgid "Carbohydrates"
msgstr "Hidrats de carboni"
#: .\cookbook\integration\mealie1.py:212
msgid "Cholesterol"
msgstr "colesterol"
#: .\cookbook\integration\mealie1.py:213
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
msgid "Fat"
msgstr "Greix"
#: .\cookbook\integration\mealie1.py:214
msgid "Fiber"
msgstr "Fibra"
#: .\cookbook\integration\mealie1.py:215
msgid "Protein"
msgstr "Proteïna"
#: .\cookbook\integration\mealie1.py:216
msgid "Saturated Fat"
msgstr "Greixos Saturats"
#: .\cookbook\integration\mealie1.py:217
msgid "Sodium"
msgstr "Sodi"
#: .\cookbook\integration\mealie1.py:218
msgid "Sugar"
msgstr "Sucre"
#: .\cookbook\integration\mealie1.py:219
msgid "Trans Fat"
msgstr "Greixos Trans"
#: .\cookbook\integration\mealie1.py:220
msgid "Unsaturated Fat"
msgstr "Greixos Insaturats"
#: .\cookbook\integration\openeats.py:28
msgid "Recipe source:"
msgstr "Origen de la recepta:"
#: .\cookbook\integration\paprika.py:49
msgid "Notes"
msgstr "Notes"
#: .\cookbook\integration\paprika.py:52
msgid "Nutritional Information"
msgstr "Informació Nutricional"
#: .\cookbook\integration\paprika.py:56
msgid "Source"
msgstr "Font"
#: .\cookbook\integration\recettetek.py:55
#: .\cookbook\integration\recipekeeper.py:70
msgid "Imported from"
msgstr "Importat des de"
#: .\cookbook\integration\saffron.py:23
msgid "Servings"
msgstr "Racions"
#: .\cookbook\integration\saffron.py:25
msgid "Waiting time"
msgstr "Temps d'espera"
#: .\cookbook\integration\saffron.py:27
msgid "Preparation Time"
msgstr "Temps de preparació"
#: .\cookbook\integration\saffron.py:29 .\cookbook\templates\index.html:6
msgid "Cookbook"
msgstr "Llibre de receptes"
#: .\cookbook\integration\saffron.py:31
msgid "Section"
msgstr "Secció"
#: .\cookbook\management\commands\fix_duplicate_properties.py:15
msgid "Fixes foods with "
msgstr "Arregla els aliments amb "
#: .\cookbook\management\commands\rebuildindex.py:14
msgid "Rebuilds full text search index on Recipe"
msgstr "Reconstrueix l'índex de cerca de text complet de la recepta"
#: .\cookbook\management\commands\rebuildindex.py:18
msgid "Only Postgresql databases use full text search, no index to rebuild"
msgstr ""
"Només les bases de dades Postgresql utilitzen la cerca de text complet, "
"sense índex per reconstruir"
#: .\cookbook\management\commands\rebuildindex.py:29
msgid "Recipe index rebuild complete."
msgstr "S'ha completat la reconstrucció de l'índex de receptes."
#: .\cookbook\management\commands\rebuildindex.py:31
msgid "Recipe index rebuild failed."
msgstr "La reconstrucció de l'índex de receptes ha fallat."
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
msgid "Breakfast"
msgstr "Esmorzar"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:19
msgid "Lunch"
msgstr "Dinar"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:24
msgid "Dinner"
msgstr "Sopar"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:29 .\cookbook\models.py:971
msgid "Other"
msgstr "Altres"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "g"
msgstr "g"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "Proteins"
msgstr "Proteïnes"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "kcal"
msgstr "kcal"
#: .\cookbook\models.py:325
msgid ""
"Maximum file storage for space in MB. 0 for unlimited, -1 to disable file "
"upload."
msgstr ""
"Emmagatzematge màxim de fitxers per espai en MB. 0 per il·limitat, -1 per "
"desactivar la càrrega de fitxers."
#: .\cookbook\models.py:513
msgid "Search"
msgstr "Cerca"
#: .\cookbook\models.py:514
msgid "Meal-Plan"
msgstr "Planificació d'àpats"
#: .\cookbook\models.py:515
msgid "Books"
msgstr "Receptes"
#: .\cookbook\models.py:516 .\cookbook\views\views.py:416
#: .\cookbook\views\views.py:417
msgid "Shopping"
msgstr "Compres"
#: .\cookbook\models.py:967
msgid "Nutrition"
msgstr "Informació nutricional"
#: .\cookbook\models.py:968
msgid "Allergen"
msgstr "Al·lèrgens"
#: .\cookbook\models.py:969
msgid "Price"
msgstr "Preu"
#: .\cookbook\models.py:970
msgid "Goal"
msgstr "Fita"
#: .\cookbook\models.py:1467 .\cookbook\templates\search_info.html:28
msgid "Simple"
msgstr "Simple"
#: .\cookbook\models.py:1468 .\cookbook\templates\search_info.html:33
msgid "Phrase"
msgstr "Frase"
#: .\cookbook\models.py:1469 .\cookbook\templates\search_info.html:38
msgid "Web"
msgstr "Web"
#: .\cookbook\models.py:1470 .\cookbook\templates\search_info.html:47
msgid "Raw"
msgstr "Cru"
#: .\cookbook\models.py:1525
msgid "Food Alias"
msgstr "Aliment equivalent"
#: .\cookbook\models.py:1526
msgid "Unit Alias"
msgstr "Unitat equivalent"
#: .\cookbook\models.py:1527
msgid "Keyword Alias"
msgstr "Paraula clau equivalent"
#: .\cookbook\models.py:1528
msgid "Description Replace"
msgstr "Substitució de la descripció"
#: .\cookbook\models.py:1529
msgid "Instruction Replace"
msgstr "Substituir les Instruccions"
#: .\cookbook\models.py:1530
msgid "Never Unit"
msgstr "Mai unitats"
#: .\cookbook\models.py:1531
msgid "Transpose Words"
msgstr "Substituir les paraules"
#: .\cookbook\models.py:1532
msgid "Food Replace"
msgstr "Aliment equivalent"
#: .\cookbook\models.py:1533
msgid "Unit Replace"
msgstr "Reemplaçar la descripció"
#: .\cookbook\models.py:1534
msgid "Name Replace"
msgstr "Substitueix Nom"
#: .\cookbook\models.py:1564
msgid "Recipe"
msgstr "Recepta"
#: .\cookbook\models.py:1565
msgid "Food"
msgstr "Menjar o aliment"
#: .\cookbook\models.py:1566
msgid "Keyword"
msgstr "Paraula Clau"
#: .\cookbook\serializer.py:262
msgid "File uploads are not enabled for this Space."
msgstr "Càrregues de fitxers no habilitades en aquest espai."
#: .\cookbook\serializer.py:273
msgid "You have reached your file upload limit."
msgstr "Límit de càrrega de fitxers Assolit."
#: .\cookbook\serializer.py:281
msgid "The given file type is not allowed."
msgstr "Aquest tipus de fitxer no està permès."
#: .\cookbook\serializer.py:421 .\cookbook\views\views.py:94
msgid ""
"You have the reached the maximum amount of spaces that can be owned by you."
msgstr "Has assolit la quantitat màxima d'espais que pots tenir."
#: .\cookbook\serializer.py:434
msgid "Space Name must be unique."
msgstr "El nom de l'espai ha de ser únic."
#: .\cookbook\serializer.py:469
msgid "Cannot modify Space owner permission."
msgstr "No pots modificar els permisos del propietari de la instància."
#: .\cookbook\serializer.py:1596
msgid "Hello"
msgstr "Hola"
#: .\cookbook\serializer.py:1596
msgid "You have been invited by "
msgstr "Convidat per "
#: .\cookbook\serializer.py:1598
msgid " to join their Tandoor Recipes space "
msgstr " per unir-se al seu espai de Receptes "
#: .\cookbook\serializer.py:1600
msgid "Click the following link to activate your account: "
msgstr "Click per activar el teu compte: "
#: .\cookbook\serializer.py:1602
msgid ""
"If the link does not work use the following code to manually join the space: "
msgstr ""
"Si l'enllaç no funciona, utilitzeu el codi següent per unir-vos a l'espai: "
#: .\cookbook\serializer.py:1604
msgid "The invitation is valid until "
msgstr "Invitació vàlida fins "
#: .\cookbook\serializer.py:1606
msgid ""
"Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub "
msgstr ""
"Tandoor Recipes és un gestor de receptes de codi obert. Comprova a GitHub "
#: .\cookbook\serializer.py:1609
msgid "Tandoor Recipes Invite"
msgstr "Invitació de receptes Tandoor"
#: .\cookbook\serializer.py:1813
msgid "Existing shopping list to update"
msgstr "Llista de la compra existent a actualitzar"
#: .\cookbook\serializer.py:1815
msgid ""
"List of ingredient IDs from the recipe to add, if not provided all "
"ingredients will be added."
msgstr ""
"Llista d'ingredients IDs de la recepta per afegir, si no es proporciona, "
"s'afegiran tots els ingredients."
#: .\cookbook\serializer.py:1817
msgid ""
"Providing a list_recipe ID and servings of 0 will delete that shopping list."
msgstr ""
"Proporcionant un list_recipe ID i porcions de 0, se suprimirà aquesta llista "
"de la compra."
#: .\cookbook\serializer.py:1826
msgid "Amount of food to add to the shopping list"
msgstr "Quantitat de menjar per afegir a la llista de la compra"
#: .\cookbook\serializer.py:1828
msgid "ID of unit to use for the shopping list"
msgstr "ID de la unitat a utilitzar per a la llista de la compra"
#: .\cookbook\serializer.py:1830
msgid "When set to true will delete all food from active shopping lists."
msgstr ""
"Quan s'estableix a true, se suprimirà tots els aliments de les llistes de "
"compra actives."
#: .\cookbook\templates\404.html:5
msgid "404 Error"
msgstr "Error 404"
#: .\cookbook\templates\404.html:18
msgid "The page you are looking for could not be found."
msgstr "No s'ha trobat la pàgina que cerqueu."
#: .\cookbook\templates\404.html:33
msgid "Take me Home"
msgstr "Porta'm a Casa"
#: .\cookbook\templates\404.html:35
msgid "Report a Bug"
msgstr "Reporta Errada"
#: .\cookbook\templates\account\email.html:6
#: .\cookbook\templates\account\email.html:10
msgid "E-mail Addresses"
msgstr "Adreces Email"
#: .\cookbook\templates\account\email.html:12
msgid "The following e-mail addresses are associated with your account:"
msgstr "Adreces de correu electrònic estan associades al vostre compte:"
#: .\cookbook\templates\account\email.html:29
msgid "Verified"
msgstr "Verificat"
#: .\cookbook\templates\account\email.html:31
msgid "Unverified"
msgstr "No Verificat"
#: .\cookbook\templates\account\email.html:33
msgid "Primary"
msgstr "Primari"
#: .\cookbook\templates\account\email.html:40
msgid "Make Primary"
msgstr "Crea Primari"
#: .\cookbook\templates\account\email.html:42
msgid "Re-send Verification"
msgstr "Reenvia Verificació"
#: .\cookbook\templates\account\email.html:43
#: .\cookbook\templates\socialaccount\connections.html:36
msgid "Remove"
msgstr "Eliminar"
#: .\cookbook\templates\account\email.html:51
msgid "Warning:"
msgstr "Advertència:"
#: .\cookbook\templates\account\email.html:51
msgid ""
"You currently do not have any e-mail address set up. You should really add "
"an e-mail address so you can receive notifications, reset your password, etc."
msgstr ""
"Actualment no teniu cap adreça d'email configurada. Hauríes d'afegir una "
"adreça perquè pugueu rebre notificacions, restablir la vostra contrasenya, "
"etc."
#: .\cookbook\templates\account\email.html:57
msgid "Add E-mail Address"
msgstr "Afegir Email"
#: .\cookbook\templates\account\email.html:62
msgid "Add E-mail"
msgstr "Afegir Email"
#: .\cookbook\templates\account\email.html:72
msgid "Do you really want to remove the selected e-mail address?"
msgstr "Eliminar l'adreça de correu electrònic seleccionada?"
#: .\cookbook\templates\account\email_confirm.html:6
#: .\cookbook\templates\account\email_confirm.html:10
msgid "Confirm E-mail Address"
msgstr "Confirma Email"
#: .\cookbook\templates\account\email_confirm.html:16
#, python-format
msgid ""
"Please confirm that\n"
" %(email)s is an e-mail address "
"for user %(user_display)s\n"
" ."
msgstr ""
"Si us plau, confirma\n"
" %(email)s és una adreça "
"d'email per a l'usuari %(user_display)s\n"
" ."
#: .\cookbook\templates\account\email_confirm.html:22
msgid "Confirm"
msgstr "Confirma"
#: .\cookbook\templates\account\email_confirm.html:29
#, python-format
msgid ""
"This e-mail confirmation link expired or is invalid. Please\n"
" issue a new e-mail confirmation "
"request."
msgstr ""
"Enllaç de confirmació d'email ha caducat o no és vàlid. Si us plau\n"
" emet una nova sol·licitud de "
"confirmació d'email."
#: .\cookbook\templates\account\login.html:8
#: .\cookbook\templates\openid\login.html:8
msgid "Login"
msgstr "Iniciar Sessió"
#: .\cookbook\templates\account\login.html:15
#: .\cookbook\templates\account\login.html:31
#: .\cookbook\templates\account\password_reset.html:39
#: .\cookbook\templates\account\password_reset_done.html:31
#: .\cookbook\templates\account\signup.html:68
#: .\cookbook\templates\account\signup_closed.html:15
#: .\cookbook\templates\openid\login.html:15
#: .\cookbook\templates\openid\login.html:26
#: .\cookbook\templates\socialaccount\authentication_error.html:15
msgid "Sign In"
msgstr "Inicia Sessió"
#: .\cookbook\templates\account\login.html:34
#: .\cookbook\templates\account\password_reset.html:41
#: .\cookbook\templates\account\password_reset_done.html:33
#: .\cookbook\templates\socialaccount\signup.html:8
#: .\cookbook\templates\socialaccount\signup.html:56
msgid "Sign Up"
msgstr "Donar Alta"
#: .\cookbook\templates\account\login.html:38
msgid "Lost your password?"
msgstr "Clau Oblidada?"
#: .\cookbook\templates\account\login.html:39
#: .\cookbook\templates\account\password_reset.html:29
msgid "Reset My Password"
msgstr "Restablir Clau"
#: .\cookbook\templates\account\login.html:50
msgid "Social Login"
msgstr "Accés Social"
#: .\cookbook\templates\account\login.html:51
msgid "You can use any of the following providers to sign in."
msgstr "Pots utilitzar els proveïdors següents per iniciar sessió."
#: .\cookbook\templates\account\logout.html:5
#: .\cookbook\templates\account\logout.html:9
#: .\cookbook\templates\account\logout.html:18
msgid "Sign Out"
msgstr "Tanca Sessió"
#: .\cookbook\templates\account\logout.html:11
msgid "Are you sure you want to sign out?"
msgstr "Segur que vols tancar sessió?"
#: .\cookbook\templates\account\password_change.html:6
#: .\cookbook\templates\account\password_change.html:10
#: .\cookbook\templates\account\password_change.html:15
#: .\cookbook\templates\account\password_reset_from_key.html:7
#: .\cookbook\templates\account\password_reset_from_key.html:13
#: .\cookbook\templates\account\password_reset_from_key_done.html:7
#: .\cookbook\templates\account\password_reset_from_key_done.html:13
msgid "Change Password"
msgstr "Canvia clau"
#: .\cookbook\templates\account\password_change.html:16
msgid "Forgot Password?"
msgstr "Clau Oblidada?"
#: .\cookbook\templates\account\password_reset.html:7
#: .\cookbook\templates\account\password_reset.html:13
#: .\cookbook\templates\account\password_reset_done.html:7
#: .\cookbook\templates\account\password_reset_done.html:18
msgid "Password Reset"
msgstr "Restablir Clau"
#: .\cookbook\templates\account\password_reset.html:24
msgid ""
"Forgotten your password? Enter your e-mail address below, and we'll send you "
"an e-mail allowing you to reset it."
msgstr "Clau Oblidada? Introduïu el email i rebràs un correu per restablir-la."
#: .\cookbook\templates\account\password_reset.html:32
msgid "Password reset is disabled on this instance."
msgstr "Deshabilitat el restabliment de Clau."
#: .\cookbook\templates\account\password_reset_done.html:25
msgid ""
"We have sent you an e-mail. Please contact us if you do not receive it "
"within a few minutes."
msgstr "Email enviat."
#: .\cookbook\templates\account\password_reset_from_key.html:13
msgid "Bad Token"
msgstr "Token incorrecte"
#: .\cookbook\templates\account\password_reset_from_key.html:25
#, python-format
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used.\n"
" Please request a new "
"password reset."
msgstr ""
"Enllaç de restabliment invàlid, possiblement perquè ja utilitzat.\n"
" Sol·liciteu un nou "
"restabliment de Clau."
#: .\cookbook\templates\account\password_reset_from_key.html:33
msgid "change password"
msgstr "canvia clau"
#: .\cookbook\templates\account\password_reset_from_key.html:36
#: .\cookbook\templates\account\password_reset_from_key_done.html:19
msgid "Your password is now changed."
msgstr "Clau Canviada."
#: .\cookbook\templates\account\password_set.html:6
#: .\cookbook\templates\account\password_set.html:10
#: .\cookbook\templates\account\password_set.html:15
msgid "Set Password"
msgstr "Establir Clau"
#: .\cookbook\templates\account\signup.html:5
msgid "Register"
msgstr "Registre"
#: .\cookbook\templates\account\signup.html:11
msgid "Create an Account"
msgstr "Crear Compte"
#: .\cookbook\templates\account\signup.html:41
msgid "I accept the follwoing"
msgstr "Accepto el Següent"
#: .\cookbook\templates\account\signup.html:44
#: .\cookbook\templates\socialaccount\signup.html:35
msgid "Terms and Conditions"
msgstr "Termes i Condicions"
#: .\cookbook\templates\account\signup.html:47
#: .\cookbook\templates\socialaccount\signup.html:38
msgid "and"
msgstr "i"
#: .\cookbook\templates\account\signup.html:51
#: .\cookbook\templates\socialaccount\signup.html:42
msgid "Privacy Policy"
msgstr "Política de Privadesa"
#: .\cookbook\templates\account\signup.html:64
msgid "Create User"
msgstr "Crear Usuari"
#: .\cookbook\templates\account\signup.html:68
msgid "Already have an account?"
msgstr "Ja tens un Compte?"
#: .\cookbook\templates\account\signup_closed.html:5
#: .\cookbook\templates\account\signup_closed.html:11
msgid "Sign Up Closed"
msgstr "Inicis Tancats"
#: .\cookbook\templates\account\signup_closed.html:13
msgid "We are sorry, but the sign up is currently closed."
msgstr "Inicis de Sessió tancats temporalment."
#: .\cookbook\templates\frontend\tandoor.html:15
msgid "Tandoor Recipe Manager"
msgstr "Gestor de receptes Tandoor"
#: .\cookbook\templates\index.html:28
msgid "Search recipe ..."
msgstr "Cerca Recepta ..."
#: .\cookbook\templates\index.html:43
msgid "New Recipe"
msgstr "Nova Recepta"
#: .\cookbook\templates\index.html:46
msgid "Import Recipe"
msgstr "Importa recepta"
#: .\cookbook\templates\index.html:52
msgid "Advanced Search"
msgstr "Cerca Avançada"
#: .\cookbook\templates\index.html:56
msgid "Reset Search"
msgstr "Restableix la cerca"
#: .\cookbook\templates\index.html:84
msgid "Last viewed"
msgstr "Darrera visualització"
#: .\cookbook\templates\index.html:86
msgid "Recipes"
msgstr "Receptes"
#: .\cookbook\templates\index.html:93
msgid "Log in to view recipes"
msgstr "Inicia sessió per veure les receptes"
#: .\cookbook\templates\markdown_info.html:5
#: .\cookbook\templates\markdown_info.html:13
msgid "Markdown Info"
msgstr "Informació de Markdown"
#: .\cookbook\templates\markdown_info.html:14
msgid ""
"\n"
" Markdown is lightweight markup language that can be used to format "
"plain text easily.\n"
" This site uses the Python Markdown library to\n"
" convert your text into nice looking HTML. Its full markdown "
"documentation can be found\n"
" here.\n"
" An incomplete but most likely sufficient documentation can be found "
"below.\n"
" "
msgstr ""
"\n"
" Markdown és un llenguatge de marcatge lleuger que es pot utilitzar "
"per donar format a text pla de forma senzilla.\n"
"Aquest lloc utilitza la biblioteca Python Markown \n"
"per convertir el teu text en un bonic format HTML. La documentació completa "
"de Markdown es pot trobar\n"
" aquí.\n"
"Pots trobar informació incompleta, encara que suficient més avall.\n"
" "
#: .\cookbook\templates\markdown_info.html:25
msgid "Headers"
msgstr "Capçaleres"
#: .\cookbook\templates\markdown_info.html:54
msgid "Formatting"
msgstr "Format"
#: .\cookbook\templates\markdown_info.html:56
#: .\cookbook\templates\markdown_info.html:72
msgid "Line breaks are inserted by adding two spaces after the end of a line"
msgstr ""
"Els salts de línia s'insereixen afegint dos espais després del final d'una "
"línia"
#: .\cookbook\templates\markdown_info.html:57
#: .\cookbook\templates\markdown_info.html:73
msgid "or by leaving a blank line in between."
msgstr "o deixant una línia en blanc entremig."
#: .\cookbook\templates\markdown_info.html:59
#: .\cookbook\templates\markdown_info.html:74
msgid "This text is bold"
msgstr "Aquest text està en negreta"
#: .\cookbook\templates\markdown_info.html:60
#: .\cookbook\templates\markdown_info.html:75
msgid "This text is italic"
msgstr "Aquest text és en cursiva"
#: .\cookbook\templates\markdown_info.html:61
#: .\cookbook\templates\markdown_info.html:77
msgid "Blockquotes are also possible"
msgstr "Les marques també són possibles"
#: .\cookbook\templates\markdown_info.html:84
msgid "Lists"
msgstr "Llistes"
#: .\cookbook\templates\markdown_info.html:85
msgid ""
"Lists can ordered or unordered. It is important to leave a blank line "
"before the list!"
msgstr ""
"Les llistes es poden ordenar o desordenar. És important deixar una línia "
"en blanc abans de la llista!"
#: .\cookbook\templates\markdown_info.html:87
#: .\cookbook\templates\markdown_info.html:108
msgid "Ordered List"
msgstr "Llista Ordenada"
#: .\cookbook\templates\markdown_info.html:89
#: .\cookbook\templates\markdown_info.html:90
#: .\cookbook\templates\markdown_info.html:91
#: .\cookbook\templates\markdown_info.html:110
#: .\cookbook\templates\markdown_info.html:111
#: .\cookbook\templates\markdown_info.html:112
msgid "unordered list item"
msgstr "element de llista no ordenat"
#: .\cookbook\templates\markdown_info.html:93
#: .\cookbook\templates\markdown_info.html:114
msgid "Unordered List"
msgstr "Llista no ordenada"
#: .\cookbook\templates\markdown_info.html:95
#: .\cookbook\templates\markdown_info.html:96
#: .\cookbook\templates\markdown_info.html:97
#: .\cookbook\templates\markdown_info.html:116
#: .\cookbook\templates\markdown_info.html:117
#: .\cookbook\templates\markdown_info.html:118
msgid "ordered list item"
msgstr "element de llista ordenat"
#: .\cookbook\templates\markdown_info.html:125
msgid "Images & Links"
msgstr "Imatges i enllaços"
#: .\cookbook\templates\markdown_info.html:126
msgid ""
"Links can be formatted with Markdown. This application also allows to paste "
"links directly into markdown fields without any formatting."
msgstr ""
"Es pot donar format als enllaços amb Markdown. Aquesta aplicació també "
"permet enganxar enllaços directament en camps Markdown sense cap tipus de "
"format."
#: .\cookbook\templates\markdown_info.html:132
#: .\cookbook\templates\markdown_info.html:145
msgid "This will become an image"
msgstr "Això es convertirà en una imatge"
#: .\cookbook\templates\markdown_info.html:152
msgid "Tables"
msgstr "Taules"
#: .\cookbook\templates\markdown_info.html:153
msgid ""
"Markdown tables are hard to create by hand. It is recommended to use a table "
"editor like this one."
msgstr ""
"Les taules de rebaixes són difícils de crear a mà. Es recomana utilitzar un "
"editor de taules com aquest."
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:171
#: .\cookbook\templates\markdown_info.html:177
msgid "Table"
msgstr "Taula"
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:172
msgid "Header"
msgstr "Capçalera"
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:178
msgid "Cell"
msgstr "Cel·la"
#: .\cookbook\templates\no_groups_info.html:5
#: .\cookbook\templates\no_groups_info.html:12
msgid "No Permissions"
msgstr "Sense Permisos"
#: .\cookbook\templates\no_groups_info.html:17
msgid "You do not have any groups and therefor cannot use this application."
msgstr "No teniu cap grup i, per tant, no podeu utilitzar aquesta aplicació."
#: .\cookbook\templates\no_groups_info.html:18
#: .\cookbook\templates\no_perm_info.html:15
msgid "Please contact your administrator."
msgstr "Contacta amb l'administrador."
#: .\cookbook\templates\no_perm_info.html:5
#: .\cookbook\templates\no_perm_info.html:12
msgid "No Permission"
msgstr "Sense Permis"
#: .\cookbook\templates\no_perm_info.html:15
msgid ""
"You do not have the required permissions to view this page or perform this "
"action."
msgstr "No teniu els permisos necessaris per dur a terme aquesta acció."
#: .\cookbook\templates\offline.html:5
msgid "Offline"
msgstr "Desconnectat"
#: .\cookbook\templates\openid\login.html:27
#: .\cookbook\templates\socialaccount\authentication_error.html:27
msgid "Back"
msgstr "Enrere"
#: .\cookbook\templates\rest_framework\api.html:5
msgid "Recipe Home"
msgstr "Pàgina d'inici"
#: .\cookbook\templates\rest_framework\api.html:11
msgid "API Documentation"
msgstr "Documentació API"
#: .\cookbook\templates\search_info.html:5
#: .\cookbook\templates\search_info.html:9
msgid "Search Settings"
msgstr "Cerca Opcions"
#: .\cookbook\templates\search_info.html:10
msgid ""
"\n"
" Creating the best search experience is complicated and weighs "
"heavily on your personal configuration. \n"
" Changing any of the search settings can have significant impact on "
"the speed and quality of the results.\n"
" Search Methods, Trigrams and Full Text Search configurations are "
"only available if you are using Postgres for your database.\n"
" "
msgstr ""
"\n"
" Aconseguir la millor experiència de cerca és complicada i recau en "
"gran mesura en la teva configuració personal. \n"
" Canviar qualsevol configuració de cerca pot tenir un gran impacte en "
"la velocitat i els resultats obtinguts.\n"
" Els mètodes de cerca, els Trigrames i les configuracions de cerca de "
"text complet només estan disponibles si feu servir Postgres per a la vostra "
"base de dades.\n"
" "
#: .\cookbook\templates\search_info.html:19
msgid "Search Methods"
msgstr "Mètodes de Cerca"
#: .\cookbook\templates\search_info.html:23
msgid ""
" \n"
" Full text searches attempt to normalize the words provided to "
"match common variants. For example: 'forked', 'forking', 'forks' will all "
"normalize to 'fork'.\n"
" There are several methods available, described below, that will "
"control how the search behavior should react when multiple words are "
"searched.\n"
" Full technical details on how these operate can be viewed on Postgresql's website.\n"
" "
msgstr ""
" \n"
" Les cerques de text complet intenten normalitzar les paraules "
"proporcionades perquè coincideixin amb les variants habituals. Per exemple: "
"'forquilla', 'forquilles', es normalitzaran tots a 'forquilla'.\n"
" Hi ha diversos mètodes disponibles, que es descriuen a "
"continuació, que controlaran com ha de reaccionar el comportament de cerca "
"quan es cerquen diverses paraules.\n"
" Els detalls tècnics complets sobre com funcionen es poden "
"consultar a Postgresql's website.\n"
" "
#: .\cookbook\templates\search_info.html:29
msgid ""
" \n"
" Simple searches ignore punctuation and common words such as "
"'the', 'a', 'and'. And will treat separate words as required.\n"
" Searching for 'apple or flour' will return any recipe that "
"includes both 'apple' and 'flour' anywhere in the fields that have been "
"selected for a full text search.\n"
" "
msgstr ""
" \n"
" Les cerques simples ignoren la puntuació i les paraules "
"habituals com ara \"el\", \"a\", \"i\". I tractarà paraules separades segons "
"sigui necessari.\n"
" Si cerqueu \"poma o farina\", es tornarà qualsevol recepta que "
"inclogui \"poma\" i \"farina\" a qualsevol part dels camps que s'han "
"seleccionat per a una cerca de text complet.\n"
" "
#: .\cookbook\templates\search_info.html:34
msgid ""
" \n"
" Phrase searches ignore punctuation, but will search for all of "
"the words in the exact order provided.\n"
" Searching for 'apple or flour' will only return a recipe that "
"includes the exact phrase 'apple or flour' in any of the fields that have "
"been selected for a full text search.\n"
" "
msgstr ""
" \n"
" Les cerques de frases ignoren la puntuació, però cercaran totes "
"les paraules en l'ordre exacte que s'indiquen.\n"
" La cerca de \"poma o farina\" només retornarà una recepta que "
"inclogui la frase exacta \"poma o farina\" en qualsevol dels camps que s'han "
"seleccionat per a una cerca de text complet.\n"
" "
#: .\cookbook\templates\search_info.html:39
msgid ""
" \n"
" Web searches simulate functionality found on many web search "
"sites supporting special syntax.\n"
" Placing quotes around several words will convert those words "
"into a phrase.\n"
" 'or' is recognized as searching for the word (or phrase) "
"immediately before 'or' OR the word (or phrase) directly after.\n"
" '-' is recognized as searching for recipes that do not include "
"the word (or phrase) that comes immediately after. \n"
" For example searching for 'apple pie' or cherry -butter will "
"return any recipe that includes the phrase 'apple pie' or the word "
"'cherry' \n"
" in any field included in the full text search but exclude any "
"recipe that has the word 'butter' in any field included.\n"
" "
msgstr ""
" \n"
" Les cerques web simulen la funcionalitat que es troba a molts "
"llocs de cerca web que admeten una sintaxi especial.\n"
" Col·locar cometes al voltant de diverses paraules convertirà "
"aquestes paraules en una frase.\n"
" \"or\" es reconeix com a cercar la paraula (o frase) "
"immediatament abans de \"or\" O la paraula (o frase) directament després.\n"
" '-' es reconeix com la recerca de receptes que no inclouen la "
"paraula (o frase) que ve immediatament després. \n"
" Per exemple, si cerqueu \"pastís de poma\" o mantega de cireres, "
"es retornarà qualsevol recepta que inclogui la frase \"pastís de poma\" o la "
"paraula \"cirera\". \n"
" En qualsevol camp inclòs a la cerca de text complet, però exclou "
"qualsevol recepta que tingui la paraula \"mantega\" en qualsevol camp "
"inclòs.\n"
" "
#: .\cookbook\templates\search_info.html:48
msgid ""
" \n"
" Raw search is similar to Web except will take puncuation "
"operators such as '|', '&' and '()'\n"
" "
msgstr ""
" \n"
" La cerca en brut és similar a la web, excepte que prendrà "
"operadors de puntuació com ara '|', '&' i '()'\n"
" "
#: .\cookbook\templates\search_info.html:59
msgid ""
" \n"
" Another approach to searching that also requires Postgresql is "
"fuzzy search or trigram similarity. A trigram is a group of three "
"consecutive characters.\n"
" For example searching for 'apple' will create x trigrams 'app', "
"'ppl', 'ple' and will create a score of how closely words match the "
"generated trigrams.\n"
" One benefit of searching trigams is that a search for 'sandwich' "
"will find misspelled words such as 'sandwhich' that would be missed by other "
"methods.\n"
" "
msgstr ""
" \n"
" Un altre enfocament de cerca que també requereix Postgresql és "
"la cerca difusa o la semblança de trigrames. Un trigrama és un grup de tres "
"caràcters consecutius.\n"
" Per exemple, cercar \"pastís\" crearà x trigrames \"pas\", \"ast"
"\", \"tís\" i crearà una puntuació de la proximitat de les paraules amb els "
"trigrames generats.\n"
" Un dels avantatges de la cerca de trigrames és que una cerca d "
"\"entrepà\" trobarà paraules mal escrites com \"entepà\" que es perdrien per "
"altres mètodes.\n"
" "
#: .\cookbook\templates\search_info.html:69
msgid "Search Fields"
msgstr "Camps de Cerca"
#: .\cookbook\templates\search_info.html:73
msgid ""
" \n"
" Unaccent is a special case in that it enables searching a field "
"'unaccented' for each search style attempting to ignore accented values. \n"
" For example when you enable unaccent for 'Name' any search "
"(starts with, contains, trigram) will attempt the search ignoring accented "
"characters.\n"
" \n"
" For the other options, you can enable search on any or all "
"fields and they will be combined together with an assumed 'OR'.\n"
" For example enabling 'Name' for Starts With, 'Name' and "
"'Description' for Partial Match and 'Ingredients' and 'Keywords' for Full "
"Search\n"
" and searching for 'apple' will generate a search that will "
"return recipes that have:\n"
" - A recipe name that starts with 'apple'\n"
" - OR a recipe name that contains 'apple'\n"
" - OR a recipe description that contains 'apple'\n"
" - OR a recipe that will have a full text search match ('apple' "
"or 'apples') in ingredients\n"
" - OR a recipe that will have a full text search match in "
"Keywords\n"
"\n"
" Combining too many fields in too many types of search can have a "
"negative impact on performance, create duplicate results or return "
"unexpected results.\n"
" For example, enabling fuzzy search or partial matches will "
"interfere with web search methods. \n"
" Searching for 'apple -pie' with fuzzy search and full text "
"search will return the recipe Apple Pie. Though it is not included in the "
"full text results, it does match the trigram results.\n"
" "
msgstr ""
" \n"
" Unaccent és un cas especial perquè permet cercar un camp \"sense "
"accent\" per a cada estil de cerca intentant ignorar els valors "
"accentuats. \n"
" Per exemple, quan activeu sense accent per a \"poma\", qualsevol "
"cerca (comença per, conté, trigrama) intentarà la cerca ignorant els "
"caràcters accentuats.\n"
" \n"
" Per a la resta d'opcions, podeu habilitar la cerca en qualsevol "
"o tots els camps i es combinaran juntament amb un assumpte 'OR'.\n"
" Per exemple, activar \"Nom\" per a Comença per, \"Nom\" i "
"\"Descripció\" per a la concordança parcial i \"Ingredients\" i \"Paraules "
"clau\" per a la cerca completa\n"
" i cercant \"poma\" generarà una cerca que retornarà les receptes "
"que tenen:\n"
" - El nom de la recepta comença amb \"poma\"\n"
" - O bé una recepta que conté \"poma\"\n"
" - O bé una descripció de la recepta que conté \"poma\"\n"
" - O bé una recepta que contingui una recepta que tindrà una "
"coincidència de cerca de text complet ('poma' o 'pomes') als ingredients.\n"
" - O bé una recepta que coincideix amb el text complet a les "
"paraules clau\n"
"\n"
" Combinar massa camps en massa tipus de cerca pot tenir un "
"impacte negatiu en el rendiment, crear resultats duplicats o retornar "
"resultats inesperats.\n"
" Per exemple, activar la cerca difusa o les coincidències "
"parcials interferirà amb els mètodes de cerca web. \n"
" Si cerqueu \"tarta de poma\" amb la cerca difusa i la cerca de "
"text complet, es tornarà la recepta Tarta de poma. Tot i que no s'inclou als "
"resultats del text complet, coincideix amb els resultats del trigrama.\n"
" "
#: .\cookbook\templates\search_info.html:95
msgid "Search Index"
msgstr "Índex de Cerca"
#: .\cookbook\templates\search_info.html:99
msgid ""
" \n"
" Trigram search and Full Text Search both rely on database "
"indexes to perform effectively. \n"
" You can rebuild the indexes on all fields in the Admin page for "
"Recipes and selecting all recipes and running 'rebuild index for selected "
"recipes'\n"
" You can also rebuild indexes at the command line by executing "
"the management command 'python manage.py rebuildindex'\n"
" "
msgstr ""
" \n"
" La cerca amb trigram i la cerca de text complet es basen en "
"l’índex de bases de dades per operar de manera eficaç. \n"
" Podeu reconstruir els índexs de tots els camps de la pàgina "
"d’administració de receptes, seleccionant totes les receptes i realitzant "
"l’acció “Reconstruir l’índex per a la recepta seleccionada”.\n"
" També podeu reconstruir els índexs de la línia de comandaments "
"executant el la comanda 'Python Manage.py RebuildIndex'\n"
" "
#: .\cookbook\templates\setup.html:6
msgid "Cookbook Setup"
msgstr "Opcions del Cookbook"
#: .\cookbook\templates\setup.html:14
msgid "Setup"
msgstr "Opcions"
#: .\cookbook\templates\setup.html:15
msgid ""
"To start using this application you must first create a superuser account."
msgstr ""
"Per començar a utilitzar aquesta aplicació és necessari crear un compte de "
"superusuari."
#: .\cookbook\templates\setup.html:20
msgid "Create Superuser account"
msgstr "Crear compte de superusuari"
#: .\cookbook\templates\socialaccount\authentication_error.html:7
#: .\cookbook\templates\socialaccount\authentication_error.html:23
msgid "Social Network Login Failure"
msgstr "Error d'inici de sessió mitjançant l'inici de sessió social"
#: .\cookbook\templates\socialaccount\authentication_error.html:25
msgid ""
"An error occurred while attempting to login via your social network account."
msgstr ""
"S'ha produït un error en intentar iniciar sessió mitjançant el teu compte de "
"xarxa social."
#: .\cookbook\templates\socialaccount\connections.html:4
#: .\cookbook\templates\socialaccount\connections.html:7
msgid "Account Connections"
msgstr "Connexions de Compte"
#: .\cookbook\templates\socialaccount\connections.html:10
msgid ""
"You can sign in to your account using any of the following third party\n"
" accounts:"
msgstr ""
"Pots accedir al teu compte mitjançant qualsevol dels \n"
" comptes de tercers següents:"
#: .\cookbook\templates\socialaccount\connections.html:44
msgid ""
"You currently have no social network accounts connected to this account."
msgstr "Sense xarxes socials connectades al compte."
#: .\cookbook\templates\socialaccount\connections.html:47
msgid "Add a 3rd Party Account"
msgstr "Afegir Compte de tercers"
#: .\cookbook\templates\socialaccount\login.html:5
#: .\cookbook\templates\socialaccount\signup.html:5
msgid "Signup"
msgstr "Registrar"
#: .\cookbook\templates\socialaccount\login.html:9
#, python-format
msgid "Connect %(provider)s"
msgstr "Connectar %(provider)s"
#: .\cookbook\templates\socialaccount\login.html:11
#, python-format
msgid "You are about to connect a new third party account from %(provider)s."
msgstr "Estàs a punt de connectar un nou compte de tercers per a %(provider)s."
#: .\cookbook\templates\socialaccount\login.html:13
#, python-format
msgid "Sign In Via %(provider)s"
msgstr "Connectar via %(provider)s"
#: .\cookbook\templates\socialaccount\login.html:15
#, python-format
msgid "You are about to sign in using a third party account from %(provider)s."
msgstr ""
"Estàs a punt de connectar fent servir un compte de tercers de %(provider)s."
#: .\cookbook\templates\socialaccount\login.html:20
msgid "Continue"
msgstr "Continuar"
#: .\cookbook\templates\socialaccount\signup.html:10
#, python-format
msgid ""
"You are about to use your\n"
" %(provider_name)s account to login to\n"
" %(site_name)s. As a final step, please complete the following form:"
msgstr ""
"Estàs a punt d'utilitzar el teu \n"
" %(provider_name)s compte per connectar a \n"
" %(site_name)s. Per a finalitzar, si us plau completa el següent "
"formulari:"
#: .\cookbook\templates\socialaccount\signup.html:32
msgid "I accept the following"
msgstr "Accepto el següent"
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:23
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:31
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:39
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:47
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:55
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:63
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:71
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:79
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:87
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:95
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:103
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:111
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:119
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:127
msgid "Sign in using"
msgstr "Registrar emprant"
#: .\cookbook\templates\space_overview.html:6
msgid "Overview"
msgstr "Vista general"
#: .\cookbook\templates\space_overview.html:13
msgid "Space"
msgstr "Espai"
#: .\cookbook\templates\space_overview.html:17
msgid ""
"Recipes, foods, shopping lists and more are organized in spaces of one or "
"more people."
msgstr ""
"Receptes, aliments, llistes de la compra i més s'organitzen en espais d'una "
"o més persones."
#: .\cookbook\templates\space_overview.html:18
msgid ""
"You can either be invited into an existing space or create your own one."
msgstr "Pots ser convidat a un espai existent o crear el teu propi."
#: .\cookbook\templates\space_overview.html:25
msgid "Your Spaces"
msgstr "El teu espai"
#: .\cookbook\templates\space_overview.html:53
msgid "Owner"
msgstr "Propietari"
#: .\cookbook\templates\space_overview.html:73
#: .\cookbook\templates\space_overview.html:83
msgid "Join Space"
msgstr "uneix-te a l'espai"
#: .\cookbook\templates\space_overview.html:76
msgid "Join an existing space."
msgstr "Unir-se a espai existent."
#: .\cookbook\templates\space_overview.html:78
msgid ""
"To join an existing space either enter your invite token or click on the "
"invite link the space owner send you."
msgstr ""
"Per unir-vos a un espai existent, introduïu el vostre token d'invitació o "
"feu clic a l'enllaç d'invitació."
#: .\cookbook\templates\space_overview.html:91
#: .\cookbook\templates\space_overview.html:100
msgid "Create Space"
msgstr "Crear Espai"
#: .\cookbook\templates\space_overview.html:94
msgid "Create your own recipe space."
msgstr "Crear el propi espai de recepta."
#: .\cookbook\templates\space_overview.html:96
msgid "Start your own recipe space and invite other users to it."
msgstr "Inicieu el vostre propi espai de receptes i convideu altres usuaris."
#: .\cookbook\templates\system.html:23
msgid "System"
msgstr "Sistema"
#: .\cookbook\templates\system.html:24
msgid ""
"\n"
" Tandoor Recipes is an open source free software application. It can "
"be found on\n"
" GitHub.\n"
" Changelogs can be found here.\n"
" "
msgstr ""
"\n"
" Tandoor Recipes és una aplicació de programari lliure de codi obert. "
"Es pot trobar a\n"
" GitHub.\n"
" Els registres de canvis es poden trobar aquí.\n"
" "
#: .\cookbook\templates\system.html:30
msgid "System Information"
msgstr "Informació de Sistema"
#: .\cookbook\templates\system.html:51
msgid ""
"\n"
" You need to execute version.py in your update "
"script to generate version information (done automatically in docker).\n"
" "
msgstr ""
"\n"
" Necessites executar version.py al teu script "
"d'actualització per generar informació de la versió (feta automàticament a "
"Docker).\n"
" "
#: .\cookbook\templates\system.html:56
msgid "Plugins"
msgstr "Connectors"
#: .\cookbook\templates\system.html:67
msgid "Media Serving"
msgstr "Servei Mitjans"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:123 .\cookbook\templates\system.html:134
msgid "Warning"
msgstr "Advertència"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:125 .\cookbook\templates\system.html:134
msgid "Ok"
msgstr "Ok"
#: .\cookbook\templates\system.html:70
msgid ""
"Serving media files directly using gunicorn/python is not recommend!\n"
" Please follow the steps described\n"
" here to update\n"
" your installation.\n"
" "
msgstr ""
"No es recomana publicar fitxers multimèdia directament mitjançant "
"gunicorn / python!\n"
"Seguiu els passos descrits\n"
"aquí"
"a> per actualitzar\n"
"la vostra instal·lació.\n"
" "
#: .\cookbook\templates\system.html:76 .\cookbook\templates\system.html:91
#: .\cookbook\templates\system.html:104 .\cookbook\templates\system.html:115
#: .\cookbook\views\views.py:168
msgid "Everything is fine!"
msgstr "Tot està bé!"
#: .\cookbook\templates\system.html:80
msgid "Secret Key"
msgstr "Paraula Clau"
#: .\cookbook\templates\system.html:84
msgid ""
"\n"
" You do not have a SECRET_KEY configured in your "
".env file. Django defaulted to the\n"
" standard key\n"
" provided with the installation which is publicly know and "
"insecure! Please set\n"
" SECRET_KEY int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" No teniu un SECRET_KEY configurat al fitxer ."
"env. Django per defecte ha estat\n"
"clau estàndard\n"
"subministrat amb la instal·lació que és coneguda i insegura públicament. "
"Estableix-ho\n"
"SECRET_KEY al fitxer de configuració .env.\n"
" "
#: .\cookbook\templates\system.html:94
msgid "Debug Mode"
msgstr "Mode Depuració"
#: .\cookbook\templates\system.html:98
msgid ""
"\n"
" This application is still running in debug mode. This is most "
"likely not needed. Turn of debug mode by\n"
" setting\n"
" DEBUG=0 int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" Aquesta aplicació encara s’executa en mode de depuració. És "
"probable que això no sigui necessari. Activa el mode de depuració\n"
"configuració\n"
"DEBUG = 0 al fitxer de configuració .env.\n"
" "
#: .\cookbook\templates\system.html:107
msgid "Allowed Hosts"
msgstr "Allotjaments permesos"
#: .\cookbook\templates\system.html:111
msgid ""
"\n"
" Your allowed hosts are configured to allow every host. This "
"might be ok in some setups but should be avoided. Please see the docs about "
"this.\n"
" "
msgstr ""
"\n"
" La vostra configuració permet tots els amfitrions, això està bé "
"en algunes instal·lacions, però en general cal evitar-ho. Consulteu la "
"documentació sobre aquest tema.\n"
" "
#: .\cookbook\templates\system.html:118
msgid "Database"
msgstr "Base de Dades"
#: .\cookbook\templates\system.html:121
msgid "Info"
msgstr "Info"
#: .\cookbook\templates\system.html:131 .\cookbook\templates\system.html:148
msgid "Migrations"
msgstr "Migracions"
#: .\cookbook\templates\system.html:137
msgid ""
"\n"
" Migrations should never fail!\n"
" Failed migrations will likely cause major parts of the app to "
"not function correctly.\n"
" If a migration fails make sure you are on the latest version and "
"if so please post the migration log and the overview below in a GitHub "
"issue.\n"
" "
msgstr ""
"\n"
" Les migracions de dades no haurien de fallar mai!\n"
" Els errors de migració podrien provocar problemes operatius "
"importants a l'aplicació.\n"
" Si una migració falla, assegureu-vos que teniu l'última versió "
"i, si és així, publiqueu el registre de migració i la visió general a "
"continuació en un tiquet de GitHub.\n"
" "
#: .\cookbook\templates\system.html:238
msgid "False"
msgstr "Fals"
#: .\cookbook\templates\system.html:238
msgid "True"
msgstr "Cert"
#: .\cookbook\templates\system.html:268
msgid "Hide"
msgstr "Amagar"
#: .\cookbook\templates\system.html:271
msgid "Show"
msgstr "Mostra"
#: .\cookbook\templates\test2.html:6
msgid "Export Recipes"
msgstr "Exporta Receptes"
#: .\cookbook\templates\test2.html:14 .\cookbook\templates\test2.html:20
msgid "Export"
msgstr "Exporta"
#: .\cookbook\views\api.py:198 .\cookbook\views\api.py:326
msgid "Parameter updated_at incorrectly formatted"
msgstr "El paràmetre updated_at té un format incorrecte"
#: .\cookbook\views\api.py:351 .\cookbook\views\api.py:484
#, python-brace-format
msgid "No {self.basename} with id {pk} exists"
msgstr "No {self.basename} amb id {pk} existeix"
#: .\cookbook\views\api.py:355
msgid "Cannot merge with the same object!"
msgstr "No es pot fusionar amb el mateix objecte!"
#: .\cookbook\views\api.py:362
#, python-brace-format
msgid "No {self.basename} with id {target} exists"
msgstr "No {self.basename} amb id {target} existeix"
#: .\cookbook\views\api.py:367
msgid "Cannot merge with child object!"
msgstr "No es pot combinar amb l'objecte fill!"
#: .\cookbook\views\api.py:405
#, python-brace-format
msgid "{source.name} was merged successfully with {target.name}"
msgstr "{source.name} s'ha fusionat amb {target.name}"
#: .\cookbook\views\api.py:411
#, python-brace-format
msgid "An error occurred attempting to merge {source.name} with {target.name}"
msgstr "Error en intentar combinar {source.name} amb {target.name}"
#: .\cookbook\views\api.py:493
#, python-brace-format
msgid "{child.name} was moved successfully to the root."
msgstr "{child.name} s'ha mogut correctament a l'arrel."
#: .\cookbook\views\api.py:496 .\cookbook\views\api.py:514
msgid "An error occurred attempting to move "
msgstr "Error a l'intentar moure "
#: .\cookbook\views\api.py:499
msgid "Cannot move an object to itself!"
msgstr "No es pot moure un objecte cap a si mateix!"
#: .\cookbook\views\api.py:505
#, python-brace-format
msgid "No {self.basename} with id {parent} exists"
msgstr "No existeix {self.basename} amb identificador {parent}"
#: .\cookbook\views\api.py:511
#, python-brace-format
msgid "{child.name} was moved successfully to parent {parent.name}"
msgstr "{child.name} s'ha mogut correctament al pare {parent.name}"
#: .\cookbook\views\api.py:992
#, python-brace-format
msgid "{obj.name} was removed from the shopping list."
msgstr "{obj.name} eliminat de la llista de la compra."
#: .\cookbook\views\api.py:997 .\cookbook\views\api.py:1627
#, python-brace-format
msgid "{obj.name} was added to the shopping list."
msgstr "Afegit {obj.name} a la llista de la compra."
#: .\cookbook\views\api.py:1239
msgid "Filter meal plans from date (inclusive)."
msgstr "Filtrar els plans d'àpats des de la data (inclosa)."
#: .\cookbook\views\api.py:1241
msgid "Filter meal plans to date (inclusive)."
msgstr "Filtreu els plans d'àpats fins la data (inclosa)."
#: .\cookbook\views\api.py:1244
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
msgstr ""
"Filtra els plans d'àpats amb MealType ID. Per a múltiples paràmetres de "
"repetició."
#: .\cookbook\views\api.py:1404
msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgstr "ID de recepta forma part d'un pas. Per a múltiples repeteix paràmetre."
#: .\cookbook\views\api.py:1406
msgid "Query string matched (fuzzy) against object name."
msgstr "La cadena de consulta coincideix (difusa) amb el nom de l'objecte."
#: .\cookbook\views\api.py:1442
msgid ""
"Query string matched (fuzzy) against recipe name. In the future also "
"fulltext search."
msgstr ""
"Cadena de consulta coincideix (difusa) amb el nom de la recepta. En el futur "
"també cerca text complet."
#: .\cookbook\views\api.py:1444
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr ""
"ID de la paraula clau que hauria de tenir una recepta. Per a múltiples "
"repeteix paràmetre. Equivalent a keywords_or"
#: .\cookbook\views\api.py:1445
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr ""
"Identificadors (IDs) de paraules clau, Paraules clau d'identificació, "
"repetiu-ne per a múltiples. Retorna receptes amb qualsevol paraula clau"
#: .\cookbook\views\api.py:1446
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr ""
"Paraules clau d'identificació (IDs), repetiu-ne per a múltiples. Torna "
"receptes que contenen totes les paraules clau."
#: .\cookbook\views\api.py:1447
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr ""
"Identificador (ID) de les paraules clau, repetiu-ne per a diversos. Exclou "
"les receptes que continguin alguna de les paraules clau."
#: .\cookbook\views\api.py:1448
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr ""
"Identificació (ID) de les paraules clau, repetiu-ne per a diversos. Exclou "
"les receptes que continguin alguna de les paraules clau."
#: .\cookbook\views\api.py:1450
msgid "ID of food a recipe should have. For multiple repeat parameter."
msgstr ""
"ID d'aliments que ha de tenir una recepta. Per a múltiples repeteix "
"paràmetres."
#: .\cookbook\views\api.py:1451
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr ""
"ID dels aliments, repeteix-lo per a múltiples. Retorna les receptes que "
"continguin més d'un aliment"
#: .\cookbook\views\api.py:1452
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr ""
"ID d'aliments, repetiu-ho per a múltiples. Retorna receptes amb tots els "
"aliments."
#: .\cookbook\views\api.py:1453
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr ""
"ID d'aliments, repetiu-ho per a múltiples. Exclou receptes que contingui "
"algun dels aliments."
#: .\cookbook\views\api.py:1454
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr ""
"Identificació (ID) dels aliments, repetiu-ne per a diversos. Exclou les "
"receptes que continguin tots els aliments."
#: .\cookbook\views\api.py:1456
msgid "ID of book a recipe should be in. For multiple repeat parameter."
msgstr ""
"ID del llibre hauria d'haver-hi en una recepta. Per al paràmetre de "
"repetició múltiple."
#: .\cookbook\views\api.py:1457
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr ""
"Identificadors de llibre (IDs), repeteix per a diversos. Retorna receptes "
"amb qualsevol dels llibres"
#: .\cookbook\views\api.py:1458
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr ""
"Identificador (IDs) de llibres, repetiu-ho per a diversos. Torna receptes "
"amb tots els llibres."
#: .\cookbook\views\api.py:1459
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr ""
"ID del llibre. Es pot especificar diverses vegades. Exclou les receptes dels "
"llibres amb l'ID especificat."
#: .\cookbook\views\api.py:1460
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr ""
"ID dels llibres, es pot especificar diverses vegades. Exclou les receptes "
"amb tots els llibres amb les ID especificades."
#: .\cookbook\views\api.py:1462
msgid "ID of unit a recipe should have."
msgstr "ID d'unitat que hauria de tenir una recepta."
#: .\cookbook\views\api.py:1464
msgid "Exact rating of recipe"
msgstr ""
#: .\cookbook\views\api.py:1465
#, fuzzy
#| msgid "Rating a recipe should have. [0 - 5]"
msgid "Rating a recipe should have or greater."
msgstr "Valoració que hauria de tenir una recepta. [0 - 5]"
#: .\cookbook\views\api.py:1466
#, fuzzy
#| msgid "Rating a recipe should have. [0 - 5]"
msgid "Rating a recipe should have or smaller."
msgstr "Valoració que hauria de tenir una recepta. [0 - 5]"
#: .\cookbook\views\api.py:1468
msgid "Filter recipes cooked X times."
msgstr "Filtra les receptes cuinades X vegades."
#: .\cookbook\views\api.py:1469
msgid "Filter recipes cooked X times or more."
msgstr "Filtra les receptes cuinades X vegades o més."
#: .\cookbook\views\api.py:1470
msgid "Filter recipes cooked X times or less."
msgstr "Filtra les receptes cuinades X vegades o menys."
#: .\cookbook\views\api.py:1472
msgid "Filter recipes created on the given date."
msgstr "Filtrar les receptes creades en la data especificada."
#: .\cookbook\views\api.py:1473
msgid "Filter recipes created on the given date or after."
msgstr "Filtra les receptes creades en una data determinada o posteriorment."
#: .\cookbook\views\api.py:1474
msgid "Filter recipes created on the given date or before."
msgstr "Filtra les receptes creades en una data determinada o anteriorment."
#: .\cookbook\views\api.py:1476 .\cookbook\views\api.py:1477
#: .\cookbook\views\api.py:1478
msgid "Filter recipes updated on the given date."
msgstr "Filtrar les receptes actualitzades en la data especificada."
#: .\cookbook\views\api.py:1480
msgid "Filter recipes last cooked on the given date or after."
msgstr ""
"Filtra les receptes cuinades per darrera vegada en una data determinada o "
"posteriorment."
#: .\cookbook\views\api.py:1481
msgid "Filter recipes last cooked on the given date or before."
msgstr ""
"Filtra les receptes cuinades per darrera vegada en una data determinada o "
"anteriorment."
#: .\cookbook\views\api.py:1483 .\cookbook\views\api.py:1484
msgid "Filter recipes lasts viewed on the given date."
msgstr ""
"Filtra les receptes que s'han visitat per darrera vegada en la data "
"determinada."
#: .\cookbook\views\api.py:1486
msgid "Filter recipes for ones created by the given user ID"
msgstr "Filtrar les receptes creades per l'usuari amb un ID determinat"
#: .\cookbook\views\api.py:1487
msgid "If only internal recipes should be returned. [true/false]"
msgstr "Només cal retornar les receptes internes. [sí/no]"
#: .\cookbook\views\api.py:1488
msgid "Returns the results in randomized order. [true/false]"
msgstr "Retorna resultats en ordre aleatori. [sí/no]"
#: .\cookbook\views\api.py:1490
msgid ""
"Determines the order of the results. Options are: score,-score,name,-name,"
"lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-"
"created_at,lastviewed,-lastviewed"
msgstr ""
#: .\cookbook\views\api.py:1492
msgid "Returns new results first in search results. [true/false]"
msgstr ""
"En primer lloc, retorna nous resultats als resultats de la cerca. [cert/"
"fals]"
#: .\cookbook\views\api.py:1493
msgid ""
"Returns the given number of recently viewed recipes before search results "
"(if given)"
msgstr ""
#: .\cookbook\views\api.py:1494
msgid "ID of a custom filter. Returns all recipes matched by that filter."
msgstr ""
#: .\cookbook\views\api.py:1495
msgid "Filter recipes that can be made with OnHand food. [true/false]"
msgstr ""
"Filtra receptes que poden elaborar-se amb aliments disponibles. [true/"
"false]"
#: .\cookbook\views\api.py:1773
msgid ""
"Return the PropertyTypes matching the property category. Repeat for "
"multiple."
msgstr ""
#: .\cookbook\views\api.py:1804 .\cookbook\views\api.py:1860
msgid "Returns only entries associated with the given mealplan id"
msgstr "Retorna només entrades asociades amb l'id de pla d'àpats determinat"
#: .\cookbook\views\api.py:1858
msgid ""
"Returns only elements updated after the given timestamp in ISO 8601 format."
msgstr ""
#: .\cookbook\views\api.py:2031
msgid ""
"Return the Automations matching the automation type. Repeat for multiple."
msgstr ""
"Retorna les automatitzacions que coincideixen amb el tipus d'automatització.·"
" ·Repeteix per a múltiples."
#: .\cookbook\views\api.py:2048
msgid ""
"Text field to store data that gets carried over to the UserSpace created "
"from the InviteLink"
msgstr ""
#: .\cookbook\views\api.py:2049
msgid "Only return InviteLinks that have not been used yet."
msgstr ""
#: .\cookbook\views\api.py:2076
msgid "Return the CustomFilters matching the model type. Repeat for multiple."
msgstr ""
"Retorna els filtres personalitzats que coincideixen amb el tipus de model.· ·"
"Repeteix per a múltiples."
#: .\cookbook\views\api.py:2176
msgid "Nothing to do."
msgstr "Res a fer."
#: .\cookbook\views\api.py:2222
msgid "Invalid Url"
msgstr "Url Invàlida"
#: .\cookbook\views\api.py:2228
msgid "Connection Refused."
msgstr "Connexió Refusada."
#: .\cookbook\views\api.py:2232
msgid "Bad URL Schema."
msgstr "Esquema URL erroni."
#: .\cookbook\views\api.py:2257
msgid "No usable data could be found."
msgstr "No s'han trobat dades utilitzables."
#: .\cookbook\views\api.py:2286 .\cookbook\views\api.py:2434
msgid "You must select an AI provider to perform your request."
msgstr ""
#: .\cookbook\views\api.py:2293 .\cookbook\views\api.py:2441
msgid ""
"You don't have any credits remaining to use AI or AI features are not "
"enabled for your space."
msgstr ""
#: .\cookbook\views\api.py:2499 .\cookbook\views\api.py:2667
msgid "File is above space limit"
msgstr "El fitxer supera el límit d'emmagatzematge"
#: .\cookbook\views\api.py:2522 .\cookbook\views\api.py:2684
msgid "Importing is not implemented for this provider"
msgstr "Importació no implementada en aquest proveïdor"
#: .\cookbook\views\api.py:2548
msgid ""
"The PDF Exporter is not enabled on this instance as it is still in an "
"experimental state."
msgstr ""
"L'exportador de PDF no està habilitat en aquesta instància perquè encara es "
"troba en un estat experimental."
#: .\cookbook\views\api.py:2842
msgid "This feature is not yet available in the hosted version of tandoor!"
msgstr ""
"Aquesta funció encara no està disponible a la versió allotjada de tandoor!"
#: .\cookbook\views\api.py:2863
msgid "Sync successful!"
msgstr "Sincronització correcta!"
#: .\cookbook\views\api.py:2866
msgid "Error synchronizing with Storage"
msgstr "Error de sincronització amb emmagatzematge"
#: .\cookbook\views\views.py:89
msgid "This feature is not available in the demo version!"
msgstr "Funció no està disponible a la versió de demostració!"
#: .\cookbook\views\views.py:110
msgid ""
"You have successfully created your own recipe space. Start by adding some "
"recipes or invite other people to join you."
msgstr ""
"Espai de Receptes creat correctament. Comenceu afegint algunes receptes o "
"convida altres persones a unir-se."
#: .\cookbook\views\views.py:171
#, python-format
msgid "PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!"
msgstr ""
"PostgreSQL %(v)s està obsolet. Actualitza a una versió totalment compatible!"
#: .\cookbook\views\views.py:174
#, python-format
msgid "You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended"
msgstr "Estàs fent servir PostgreSQL %(v1)s. Es recomana PostgreSQL %(v2)s"
#: .\cookbook\views\views.py:178
msgid "Unable to determine PostgreSQL version."
msgstr "No es pot determinar la versió de PostgreSQL."
#: .\cookbook\views\views.py:182
msgid ""
"This application is not running with a Postgres database backend. This is ok "
"but not recommended as some features only work with postgres databases."
msgstr ""
"Aquesta aplicació no s'executa amb un backend de base de dades Postgres. "
"Això està bé, però no es recomanable, ja que algunes funcions només "
"funcionen amb bases de dades Postgres."
#: .\cookbook\views\views.py:296
msgid ""
"The setup page can only be used to create the first "
"user! If you have forgotten your superuser credentials "
"please consult the django documentation on how to reset passwords."
msgstr ""
"La pàgina de configuració només es pot utilitzar per crear el primer "
"usuari! Si heu oblidat les vostres credencials de "
"superusuari, consulteu la documentació de django sobre com restablir les "
"contrasenyes."
#: .\cookbook\views\views.py:304
msgid "Passwords dont match!"
msgstr "Les contrasenyes no coincideixen!"
#: .\cookbook\views\views.py:312
msgid "User has been created, please login!"
msgstr "L'usuari s'ha creat, si us plau inicieu la sessió!"
#: .\cookbook\views\views.py:352
msgid ""
"Reporting share links is not enabled for this instance. Please notify the "
"page administrator to report problems."
msgstr ""
"Notificació d'enllaços compartits no activada en aquesta instància. Aviseu "
"l'administrador per informar dels problemes."
#: .\cookbook\views\views.py:357
msgid ""
"Recipe sharing link has been disabled! For additional information please "
"contact the page administrator."
msgstr ""
"L'enllaç per compartir receptes s'ha desactivat! Per obtenir informació "
"addicional, poseu-vos en contacte amb l'administrador."
#: .\cookbook\views\views.py:383
msgid "Manage recipes, shopping list, meal plans and more."
msgstr "Gestiona receptes, llistes de la compra, menús setmanals i molt més."
#: .\cookbook\views\views.py:397 .\cookbook\views\views.py:398
msgid "Plan"
msgstr "Pla"
#: .\cookbook\views\views.py:399
msgid "View your meal Plan"
msgstr "Veure la planificació de menús"
#: .\cookbook\views\views.py:418
msgid "View your shopping lists"
msgstr "Veure la teva llista de la compra"
#~ msgid ""
#~ "Both fields are optional. If none are given the username will be "
#~ "displayed instead"
#~ msgstr ""
#~ "Tots dos camps són opcionals. Si no se'n dóna cap, es mostrarà el nom "
#~ "d'usuari"
#~ msgid "Name"
#~ msgstr "Nom"
#~ msgid "Keywords"
#~ msgstr "Paraules clau"
#~ msgid "Preparation time in minutes"
#~ msgstr "Temps de preparació en minuts"
#~ msgid "Waiting time (cooking/baking) in minutes"
#~ msgstr "Temps d'espera (cocció/fornejat) en minuts"
#~ msgid "Path"
#~ msgstr "Ruta"
#~ msgid "Storage UID"
#~ msgstr "UID Emmagatzematge"
#~ msgid "Add your comment: "
#~ msgstr "Afegir el teu comentari: "
#~ msgid "Leave empty for dropbox and enter app password for nextcloud."
#~ msgstr ""
#~ "Deixeu-lo buit per a Dropbox i introduïu la contrasenya de l'aplicació "
#~ "per a nextcloud."
#~ msgid "Leave empty for nextcloud and enter api token for dropbox."
#~ msgstr ""
#~ "Deixeu-lo buit per a nextcloud i introduïu el token API per a Dropbox."
#~ msgid ""
#~ "Leave empty for dropbox and enter only base url for nextcloud (/"
#~ "remote.php/webdav/ is added automatically)"
#~ msgstr ""
#~ "Deixeu-lo buit per a Dropbox i introduïu només l'URL base per a Nextcloud "
#~ "(/remote.php/webdav/ s'afegeix automàticament)"
#~ msgid ""
#~ "Long Lived Access Token for your HomeAssistant instance"
#~ msgstr ""
#~ "Token d'accés de llarga durada per a la teva instància de "
#~ "HomeAssistant"
#~ msgid "Something like http://homeassistant.local:8123/api"
#~ msgstr "Alguna cosa similar a http://homeassistant.local:8123/api"
#~ msgid "http://homeassistant.local:8123/api for example"
#~ msgstr "Per exemple http://homeassistant.local:8123/api"
#~ msgid "Storage"
#~ msgstr "Emmagatzematge"
#~ msgid "Active"
#~ msgstr "Actiu"
#~ msgid "Search String"
#~ msgstr "Cerca Cadena"
#~ msgid "File ID"
#~ msgstr "ID d'Arxiu"
#~ msgid ""
#~ "Determines how fuzzy a search is if it uses trigram similarity matching "
#~ "(e.g. low values mean more typos are ignored)."
#~ msgstr ""
#~ "Determina com de difusa és una cerca si utilitza la concordança de "
#~ "similitud de trigrama (p. ex., els valors baixos signifiquen que "
#~ "s'ignoren més errors ortogràfics)."
#~ msgid ""
#~ "Select type method of search. Click here "
#~ "for full description of choices."
#~ msgstr ""
#~ "Seleccioneu el tipus de mètode de cerca. Feu clic aquí per obtenir una descripció completa de les opcions."
#~ msgid ""
#~ "Use fuzzy matching on units, keywords and ingredients when editing and "
#~ "importing recipes."
#~ msgstr ""
#~ "Utilitzeu la concordança difusa en unitats, paraules clau i ingredients "
#~ "quan editeu i importeu receptes."
#~ msgid ""
#~ "Fields to search ignoring accents. Selecting this option can improve or "
#~ "degrade search quality depending on language"
#~ msgstr ""
#~ "Camps per cercar ignorant els accents. La selecció d'aquesta opció pot "
#~ "millorar o degradar la qualitat de la cerca en funció de l'idioma"
#~ msgid ""
#~ "Fields to search for partial matches. (e.g. searching for 'Pie' will "
#~ "return 'pie' and 'piece' and 'soapie')"
#~ msgstr ""
#~ "Camps per cercar coincidències parcials. (p. ex., en cercar \"Pastís\" "
#~ "tornarà \"pastís\" i \"peça\" i \"sabó\")"
#~ msgid ""
#~ "Fields to search for beginning of word matches. (e.g. searching for 'sa' "
#~ "will return 'salad' and 'sandwich')"
#~ msgstr ""
#~ "Camps per cercar l'inici de les coincidències de paraula. (p. ex., en "
#~ "cercar \"sa\" es tornarà \"amanida\" i \"entrepà\")"
#~ msgid ""
#~ "Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find "
#~ "'recipe'.) Note: this option will conflict with 'web' and 'raw' methods "
#~ "of search."
#~ msgstr ""
#~ "Camps per a la cerca \"difusa\". (per exemple, si cerqueu \"recpie\" "
#~ "trobareu \"recepta\".) Nota: aquesta opció entrarà en conflicte amb els "
#~ "mètodes de cerca \"web\" i \"cru\"."
#~ msgid ""
#~ "Fields to full text search. Note: 'web', 'phrase', and 'raw' search "
#~ "methods only function with fulltext fields."
#~ msgstr ""
#~ "Camps per a la cerca de text complet. Nota: els mètodes de cerca \"web\", "
#~ "\"frase\" i \"en brut\" només funcionen amb camps de text complet."
#~ msgid "Search Method"
#~ msgstr "Mètode de cerca"
#~ msgid "Fuzzy Lookups"
#~ msgstr "Cerques difuses"
#~ msgid "Ignore Accent"
#~ msgstr "Ignora Accents"
#~ msgid "Partial Match"
#~ msgstr "Cerca Parcial"
#~ msgid "Starts With"
#~ msgstr "Comença amb"
#~ msgid "Fuzzy Search"
#~ msgstr "Cerca Difusa"
#~ msgid "Full Text"
#~ msgstr "Text Sencer"
#~ msgid " is part of a recipe step and cannot be deleted"
#~ msgstr " forma part d'un pas de recepta i no es pot suprimir"
#~ msgid "Delete"
#~ msgstr "Esborra"
#~ msgid "Settings"
#~ msgstr "Opcions"
#~ msgid "Email"
#~ msgstr "Email"
#~ msgid "Password"
#~ msgstr "Clau"
#~ msgid "Foods"
#~ msgstr "Menjars"
#~ msgid "Units"
#~ msgstr "Unitats"
#~ msgid "Supermarket"
#~ msgstr "Supermercat"
#~ msgid "Supermarket Category"
#~ msgstr "Categoria de Supermercat"
#~ msgid "Automations"
#~ msgstr "Automatitzacions"
#~ msgid "Files"
#~ msgstr "Arxius"
#~ msgid "Batch Edit"
#~ msgstr "Edició per lots"
#~ msgid "History"
#~ msgstr "Historial"
#~ msgid "Ingredient Editor"
#~ msgstr "Editor d'ingredients"
#~ msgid "Properties"
#~ msgstr "Propietats"
#~ msgid "Unit Conversions"
#~ msgstr "Conversió d'unitats"
#~ msgid "Create"
#~ msgstr "Crea"
#~ msgid "External Recipes"
#~ msgstr "Receptes Externes"
#~ msgid "Space Settings"
#~ msgstr "Opcions d'espai"
#~ msgid "External Connectors"
#~ msgstr "Connectors Externs"
#~ msgid "Admin"
#~ msgstr "Admin"
#~ msgid "Markdown Guide"
#~ msgstr "Guia Markdown"
#~ msgid "GitHub"
#~ msgstr "GitHub"
#~ msgid "Translate Tandoor"
#~ msgstr "Tradueix Tandoor"
#~ msgid "API Browser"
#~ msgstr "Navegador API"
#~ msgid "Log out"
#~ msgstr "Tanca sessió"
#~ msgid "You are using the free version of Tandor"
#~ msgstr "Estàs fent servir una versió gratuïta de Tandor"
#~ msgid "Upgrade Now"
#~ msgstr "Actualitzar ara"
#~ msgid "Batch edit Category"
#~ msgstr "Edició per lots de Categoria"
#~ msgid "Batch edit Recipes"
#~ msgstr "Edició per lots de Receptes"
#~ msgid "Add the specified keywords to all recipes containing a word"
#~ msgstr ""
#~ "Afegiu les paraules clau especificades a totes les receptes que "
#~ "continguin una paraula"
#~ msgid "Sync"
#~ msgstr "Sincronitzar"
#~ msgid "Manage watched Folders"
#~ msgstr "Gestiona les carpetes de descobriment"
#~ msgid ""
#~ "On this Page you can manage all storage folder locations that should be "
#~ "monitored and synced."
#~ msgstr ""
#~ "En aquesta pàgina pots gestionar totes les ubicacions de les carpetes "
#~ "d'emmagatzematge que s'han de supervisar i sincronitzar."
#~ msgid "The path must be in the following format"
#~ msgstr "El camí ha de tenir el format següent"
#~ msgid "Save"
#~ msgstr "Desa"
#~ msgid "Manage External Storage"
#~ msgstr "Gestiona Emmagatzematge Extern"
#~ msgid "Sync Now!"
#~ msgstr "Sincronitza Ara!"
#~ msgid "Show Recipes"
#~ msgstr "Mostra Receptes"
#~ msgid "Show Log"
#~ msgstr "Mostra Logs"
#~ msgid "Importing Recipes"
#~ msgstr "Important Receptes"
#~ msgid ""
#~ "This can take a few minutes, depending on the number of recipes in sync, "
#~ "please wait."
#~ msgstr ""
#~ "Això pot trigar uns minuts, en funció del nombre de receptes "
#~ "sincronitzades, espereu."
#~ msgid "Recipe Books"
#~ msgstr "Llibres de Receptes"
#~ msgid "Import new Recipe"
#~ msgstr "Importa nova Recepta"
#~ msgid "Edit Recipe"
#~ msgstr "Edita Recepta"
#, python-format
#~ msgid "Are you sure you want to delete the %(title)s: %(object)s "
#~ msgstr "Segur que vols esborrar el %(title)s:%(object)s "
#~ msgid "This cannot be undone!"
#~ msgstr "Aquesta operació no pot desfer-se!"
#~ msgid "Protected"
#~ msgstr "Protegit"
#~ msgid "Cascade"
#~ msgstr "Cascada"
#~ msgid "Cancel"
#~ msgstr "Cancel·la"
#~ msgid "Edit"
#~ msgstr "Edita"
#~ msgid "View"
#~ msgstr "Veure"
#~ msgid "Delete original file"
#~ msgstr "Esborra arxiu original"
#~ msgid "List"
#~ msgstr "Llista"
#~ msgid "Filter"
#~ msgstr "Filtre"
#~ msgid "Import all"
#~ msgstr "Importa tot"
#~ msgid "New"
#~ msgstr "Nova"
#~ msgid "previous"
#~ msgstr "anterior"
#~ msgid "next"
#~ msgstr "següent"
#~ msgid "View Log"
#~ msgstr "Veure Registre"
#~ msgid "Cook Log"
#~ msgstr "Registre de Receptes"
#~ msgid "Import"
#~ msgstr "Importar"
#~ msgid "Security Warning"
#~ msgstr "Advertència de Seguretat"
#~ msgid ""
#~ "\n"
#~ " The Password and Token field are stored as plain text"
#~ "b> inside the database.\n"
#~ " This is necessary because they are needed to make API requests, "
#~ "but it also increases the risk of\n"
#~ " someone stealing it.
\n"
#~ " To limit the possible damage tokens or accounts with limited "
#~ "access can be used.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Es camps contrasenya i testimoni s’emmagatzemen com a "
#~ "text pla a la base de dades.\n"
#~ " Això és necessari perquè són necessaris per fer sol·licituds "
#~ "API, però també augmenta el risc que\n"
#~ " algú el robi
\n"
#~ " Per limitar el possible dany de fitxes o comptes amb accés "
#~ "limitat.\n"
#~ " "
#~ msgid "You are currently offline!"
#~ msgstr "Estàs desconnectat!"
#~ msgid ""
#~ "The recipes listed below are available for offline viewing because you "
#~ "have recently viewed them. Keep in mind that data might be outdated."
#~ msgstr ""
#~ "Les receptes a continuació estan disponibles per a la visualització fora "
#~ "de línia perquè les heu vist recentment. Tingueu en compte que les dades "
#~ "poden estar obsoletes."
#~ msgid "Property Editor"
#~ msgstr "Editor de propietats"
#~ msgid "Comments"
#~ msgstr "Comentaris"
#~ msgid "by"
#~ msgstr "per"
#~ msgid "Comment"
#~ msgstr "Comentari"
#~ msgid ""
#~ "There are many options to configure the search depending on your personal "
#~ "preferences."
#~ msgstr ""
#~ "Hi ha moltes opcions per configurar la cerca en funció de les vostres "
#~ "preferències personals."
#~ msgid ""
#~ "Usually you do not need to configure any of them and can just "
#~ "stick with either the default or one of the following presets."
#~ msgstr ""
#~ "Normalment no cal configurar cap d'ells i només podeu quedar-vos "
#~ "amb el valor predeterminat o amb un dels següents valors predefinits."
#~ msgid ""
#~ "If you do want to configure the search you can read about the different "
#~ "options here."
#~ msgstr ""
#~ "Si vols configurar la cerca, pots llegir les diferents opcions aquí."
#~ msgid "Fuzzy"
#~ msgstr "Difusa"
#~ msgid ""
#~ "Find what you need even if your search or the recipe contains typos. "
#~ "Might return more results than needed to make sure you find what you are "
#~ "looking for."
#~ msgstr ""
#~ "Trobeu el que necessiteu encara que la cerca o la recepta contingui "
#~ "errors d'ortografia. Pot ser que tornin més resultats dels necessaris."
#~ msgid "This is the default behavior"
#~ msgstr "Comportament per Defecte"
#~ msgid "Apply"
#~ msgstr "Aplicar"
#~ msgid "Precise"
#~ msgstr "Precisar"
#~ msgid ""
#~ "Allows fine control over search results but might not return results if "
#~ "too many spelling mistakes are made."
#~ msgstr ""
#~ "Permet un control minuciós sobre els resultats de la cerca, però és "
#~ "possible que no es tornin si hi han errors ortogràfics."
#~ msgid "Perfect for large Databases"
#~ msgstr "Perfecte per BBDD Grans"
#~ msgid "Social"
#~ msgstr "Social"
#~ msgid "Space Management"
#~ msgstr "Gestiona de l'espai"
#~ msgid "Space:"
#~ msgstr "Espai:"
#~ msgid "Manage Subscription"
#~ msgstr "Administra Subscripció"
#~ msgid "Leave Space"
#~ msgstr "Abandonar l'espai"
#~ msgid "URL Import"
#~ msgstr "Importació d’URL"
#~ msgid ""
#~ "Rating a recipe should have or greater. [0 - 5] Negative value filters "
#~ "rating less than."
#~ msgstr ""
#~ "Tingues en compte que una recepta hauria de tenir o ser superior. [0 - 5] "
#~ "Un valor negatiu filtra una puntuació inferior a."
#~ msgid ""
#~ "Filter recipes updated on or after YYYY-MM-DD. Prepending - filters on or "
#~ "before date."
#~ msgstr ""
#~ "Filtra les receptes que s'han actualitzat el AAAA-MM-DD o després. "
#~ "Prefixació: filtra la data exacta o abans de la data."
#~ msgid ""
#~ "Returns the shopping list entry with a primary key of id. Multiple "
#~ "values allowed."
#~ msgstr ""
#~ "Retorna l'entrada de la llista de la compra amb una clau primària "
#~ "d'identificador. Es permeten diversos valors."
#~ msgid ""
#~ "Filter shopping list entries on checked. [true, false, both, recent"
#~ "b>]
- recent includes unchecked items and recently "
#~ "completed items."
#~ msgstr ""
#~ "Filtreu les entrades de la llista de compres per marcades. [cert, fals, "
#~ "ambdues, recent]
-recent inclou elements no "
#~ "marcats i elements completats recentment."
#~ msgid ""
#~ "Returns the shopping list entries sorted by supermarket category order."
#~ msgstr ""
#~ "Retorna les entrades de la llista de la compra ordenades per comanda de "
#~ "categoria de supermercat."
#, python-format
#~ msgid "Batch edit done. %(count)d recipe was updated."
#~ msgid_plural "Batch edit done. %(count)d Recipes where updated."
#~ msgstr[0] ""
#~ "Edició per lots Completada. %(count)d La Recepta s’ha actualitzat."
#~ msgstr[1] ""
#~ "Edició per lots Completada. %(count)d Les receptes s’han actualitzat."
#~ msgid "Monitor"
#~ msgstr "Monitoratge"
#~ msgid "Storage Backend"
#~ msgstr "Backend d'emmagatzematge"
#~ msgid ""
#~ "Could not delete this storage backend as it is used in at least one "
#~ "monitor."
#~ msgstr ""
#~ "No s'ha pogut suprimir aquest fons d'emmagatzematge, ja que s'utilitza en "
#~ "almenys un monitor."
#~ msgid "Connectors Config Backend"
#~ msgstr "Back-end de configuració de connectors"
#~ msgid "Invite Link"
#~ msgstr "Enllaç de invitació"
#~ msgid "Space Membership"
#~ msgstr "Membres de l'espai"
#~ msgid "You cannot edit this storage!"
#~ msgstr "No podeu editar aquest emmagatzematge!"
#~ msgid "Storage saved!"
#~ msgstr "Emmagatzematge desat!"
#~ msgid "There was an error updating this storage backend!"
#~ msgstr ""
#~ "S'ha produït un error en actualitzar aquest backend d'emmagatzematge!"
#~ msgid "Config saved!"
#~ msgstr "Configuració desada!"
#~ msgid "ConnectorConfig"
#~ msgstr "Configuració del connector"
#~ msgid "Changes saved!"
#~ msgstr "Canvis desats!"
#~ msgid "Error saving changes!"
#~ msgstr "Error al desar canvis!"
#~ msgid "Import Log"
#~ msgstr "Importa Registre"
#~ msgid "Discovery"
#~ msgstr "Descobriment"
#~ msgid "Shopping List"
#~ msgstr "Llista de la Compra"
#~ msgid "Connector Config Backend"
#~ msgstr "Configuració del backend per a connectors"
#~ msgid "Invite Links"
#~ msgstr "Enllaços Invitació"
#~ msgid "Supermarkets"
#~ msgstr "Supermercats"
#~ msgid "Shopping Categories"
#~ msgstr "Categories de Compres"
#~ msgid "Custom Filters"
#~ msgstr "Filtres personalitzats"
#~ msgid "Steps"
#~ msgstr "Passos"
#~ msgid "Property Types"
#~ msgstr "Tipus de propietat"
#~ msgid "This feature is not enabled by the server admin!"
#~ msgstr "Aquesta funció no està activada per l'administrador del servidor!"
#~ msgid "Imported new recipe!"
#~ msgstr "Nova Recepta importada!"
#~ msgid "There was an error importing this recipe!"
#~ msgstr "S'ha produït un error en importar la recepta!"
#~ msgid "You do not have the required permissions to perform this action!"
#~ msgstr "No teniu els permisos necessaris per dur a terme aquesta acció!"
#~ msgid "Comment saved!"
#~ msgstr "Comentari Desat!"
#~ msgid "You must select at least one field to search!"
#~ msgstr "Heu de seleccionar almenys un camp per cercar!"
#~ msgid ""
#~ "To use this search method you must select at least one full text search "
#~ "field!"
#~ msgstr ""
#~ "Per utilitzar aquest mètode de cerca, heu de seleccionar almenys un camp "
#~ "de cerca de text complet!"
#~ msgid "Fuzzy search is not compatible with this search method!"
#~ msgstr "Cerca difusa no és compatible amb aquest mètode de cerca!"
#~ msgid "Malformed Invite Link supplied!"
#~ msgstr "S'ha proporcionat un enllaç d'invitació amb un format incorrecte!"
#~ msgid "Successfully joined space."
#~ msgstr "Unit correctament a l'espai."
#~ msgid "Invite Link not valid or already used!"
#~ msgstr "L'enllaç d'invitació no és vàlid o ja s'ha utilitzat!"
#~ msgid "View your cookbooks"
#~ msgstr "Veure els meus llibres de receptes"
#~ msgid "Default unit"
#~ msgstr "Unitat per defecte"
#~ msgid "Use KJ"
#~ msgstr "Usa KJ"
#~ msgid "Theme"
#~ msgstr "Tema"
#~ msgid "Navbar color"
#~ msgstr "Color barra"
#~ msgid "Sticky navbar"
#~ msgstr "Barra Sticky"
#~ msgid "Default page"
#~ msgstr "Pàgina per defecte"
#~ msgid "Plan sharing"
#~ msgstr "Comparteix pla"
#~ msgid "Ingredient decimal places"
#~ msgstr "Decimals Ingredients"
#~ msgid "Shopping list auto sync period"
#~ msgstr "Auto-sincronització Llista compra"
#~ msgid "Left-handed mode"
#~ msgstr "Mode per a esquerrans"
#~ msgid ""
#~ "Color of the top navigation bar. Not all colors work with all themes, "
#~ "just try them out!"
#~ msgstr ""
#~ "Color de la barra de navegació superior. No tots els colors funcionen amb "
#~ "tots els temes, cal provar-los!"
#~ msgid ""
#~ "Default Unit to be used when inserting a new ingredient into a recipe."
#~ msgstr ""
#~ "Unitat per defecte que s'utilitzarà quan s'insereixi un ingredient nou en "
#~ "una recepta."
#~ msgid ""
#~ "Enables support for fractions in ingredient amounts (e.g. convert "
#~ "decimals to fractions automatically)"
#~ msgstr ""
#~ "Permet l'ús de fraccions de quantitats d'ingredients (p.ex.: converteix "
#~ "els decimals a fraccions automàticament)"
#~ msgid "Display nutritional energy amounts in joules instead of calories"
#~ msgstr "Mostra quantitats nutricionals d'energia en joules"
#~ msgid ""
#~ "Users with whom newly created meal plans should be shared by default."
#~ msgstr ""
#~ "Els usuaris que han creat elements d'un pla de menjars s'haurien de "
#~ "compartir per defecte."
#~ msgid "Users with whom to share shopping lists."
#~ msgstr "Usuaris amb qui compartir llistes de la compra."
#~ msgid "Number of decimals to round ingredients."
#~ msgstr "Nombre de decimals dels ingredients."
#~ msgid ""
#~ "If you want to be able to create and see comments underneath recipes."
#~ msgstr "Si vols poder crear i veure comentaris a sota de les receptes."
#~ msgid ""
#~ "Setting to 0 will disable auto sync. When viewing a shopping list the "
#~ "list is updated every set seconds to sync changes someone else might have "
#~ "made. Useful when shopping with multiple people but might use a little "
#~ "bit of mobile data. If lower than instance limit it is reset when saving."
#~ msgstr ""
#~ "Configurat a 0, es desactivarà la sincronització automàtica. Quan es "
#~ "visualitza una llista de la compra, la llista s'actualitza cada segon per "
#~ "sincronitzar els canvis que algú hagi pogut fer. Útil per comprar amb "
#~ "diverses persones, però pot fer servir una mica de dades mòbils. Si és "
#~ "inferior al límit d’instància, es restablirà quan es desa."
#~ msgid "Makes the navbar stick to the top of the page."
#~ msgstr "Barra de navegació s'enganxi a la part superior de la pàgina."
#~ msgid "Automatically add meal plan ingredients to shopping list."
#~ msgstr ""
#~ "Afegeix automàticament els ingredients del pla d'àpats a la llista de la "
#~ "compra."
#~ msgid "Exclude ingredients that are on hand."
#~ msgstr "Exclou els ingredients que hi ha a mà."
#~ msgid "Will optimize the UI for use with your left hand."
#~ msgstr "S'optimitzarà la UI pel seu ús amb la mà esquerra."
#~ msgid "You must provide at least a recipe or a title."
#~ msgstr "Has de proporcionar com a mínim una recepta o un títol."
#~ msgid "You can list default users to share recipes with in the settings."
#~ msgstr ""
#~ "Podeu llistar els usuaris predeterminats amb els quals voleu compartir "
#~ "receptes a la configuració."
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "Podeu utilitzar el marcador per donar format a aquest camp. Consulteu els "
#~ "documents aquí "
#~ msgid ""
#~ "Users will see all items you add to your shopping list. They must add "
#~ "you to see items on their list."
#~ msgstr ""
#~ "Els usuaris veuran tots els articles que afegiu a la vostra llista de la "
#~ "compra. Us han d'afegir per veure els elements de la seva llista."
#~ msgid ""
#~ "When adding a meal plan to the shopping list (manually or automatically), "
#~ "include all related recipes."
#~ msgstr ""
#~ "Quan afegiu un pla d'àpats a la llista de la compra (de manera manual o "
#~ "automàtica), inclou totes les receptes relacionades."
#~ msgid ""
#~ "When adding a meal plan to the shopping list (manually or automatically), "
#~ "exclude ingredients that are on hand."
#~ msgstr ""
#~ "Quan afegiu un pla d'àpats a la llista de la compra (manual o "
#~ "automàticament), excloeu els ingredients que teniu a mà."
#~ msgid "Default number of hours to delay a shopping list entry."
#~ msgstr ""
#~ "Nombre d'hores per defecte per retardar l'entrada d'una llista de la "
#~ "compra."
#~ msgid "Filter shopping list to only include supermarket categories."
#~ msgstr ""
#~ "Filtreu la llista de compres per incloure només categories de "
#~ "supermercats."
#~ msgid "Days of recent shopping list entries to display."
#~ msgstr "Dies de les entrades recents de la llista de la compra per mostrar."
#~ msgid "Mark food 'On Hand' when checked off shopping list."
#~ msgstr "Marca el menjar com a \"A mà\" quan marqueu la llista de la compra."
#~ msgid "Delimiter to use for CSV exports."
#~ msgstr "Delimitador per a les exportacions CSV."
#~ msgid "Prefix to add when copying list to the clipboard."
#~ msgstr "Prefix per afegir en copiar la llista al porta-retalls."
#~ msgid "Share Shopping List"
#~ msgstr "Compartir Llista de la Compra"
#~ msgid "Autosync"
#~ msgstr "Autosinc"
#~ msgid "Auto Add Meal Plan"
#~ msgstr "Afegeix automàticament un pla d'àpats"
#~ msgid "Exclude On Hand"
#~ msgstr "Exclou a mà"
#~ msgid "Include Related"
#~ msgstr "Incloure Relacionats"
#~ msgid "Default Delay Hours"
#~ msgstr "Hores de retard per defecte"
#~ msgid "Filter to Supermarket"
#~ msgstr "Filtrar a supermercat"
#~ msgid "Recent Days"
#~ msgstr "Dies recents"
#~ msgid "CSV Delimiter"
#~ msgstr "Delimitador CSV"
#~ msgid "List Prefix"
#~ msgstr "Prefix de Llista"
#~ msgid "Auto On Hand"
#~ msgstr "Auto a mà"
#~ msgid "Reset Food Inheritance"
#~ msgstr "Restablir Herència Alimentària"
#~ msgid "Reset all food to inherit the fields configured."
#~ msgstr "Restableix tots els aliments per heretar els camps configurats."
#~ msgid "Fields on food that should be inherited by default."
#~ msgstr "Camps dels aliments que s'han d'heretar per defecte."
#~ msgid "Show recipe counts on search filters"
#~ msgstr "Mostra el recompte de receptes als filtres de cerca"
#~ msgid "Use the plural form for units and food inside this space."
#~ msgstr "Empra el plural d'aquestes unitats i menjars dins de l'espai."
#~ msgid "One of queryset or hash_key must be provided"
#~ msgstr "S'ha de proporcionar una de queryset o hash_key"
#~ msgid "Recipe Book"
#~ msgstr "Llibre de Receptes"
#~ msgid "Bookmarks"
#~ msgstr "Marcadors"
#~ msgid "Ingredients"
#~ msgstr "Ingredients"
#~ msgid "Show recent recipes"
#~ msgstr "Mostra Receptes Recents"
#~ msgid "Search style"
#~ msgstr "Estil de Cerca"
#~ msgid "Show recently viewed recipes on search page."
#~ msgstr "Mostra les receptes vistes recentment a la pàgina de cerca."
#~ msgid "Small"
#~ msgstr "Petit"
#~ msgid "Large"
#~ msgstr "Gran"
#~ msgid "Edit Ingredients"
#~ msgstr "Edita Ingredients"
#~ msgid ""
#~ "\n"
#~ " The following form can be used if, accidentally, two (or more) "
#~ "units or ingredients where created that should be\n"
#~ " the same.\n"
#~ " It merges two units or ingredients and updates all recipes using "
#~ "them.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Es pot utilitzar el següent formulari si, de manera accidental "
#~ "dues (o més) unitats o ingredients es van crear haurien\n"
#~ " de ser el mateix.\n"
#~ " Combina dues unitats o ingredients i actualitza totes les "
#~ "receptes amb ells\n"
#~ " "
#~ msgid "Are you sure that you want to merge these two units?"
#~ msgstr "Estàs segur que vols combinar aquestes dues unitats?"
#~ msgid "Are you sure that you want to merge these two ingredients?"
#~ msgstr "Estàs segur que vols combinar aquests dos ingredients?"
#~ msgid "Import Recipes"
#~ msgstr "Importar Receptes"
#~ msgid "Close"
#~ msgstr "Tanca"
#~ msgid "Open Recipe"
#~ msgstr "Obrir Recepta"
#~ msgid "Meal Plan View"
#~ msgstr "Vista Pla de menjars"
#~ msgid "Created by"
#~ msgstr "Creat per"
#~ msgid "Shared with"
#~ msgstr "Compartit per"
#~ msgid "Never cooked before."
#~ msgstr "No cuinat abans."
#~ msgid "Other meals on this day"
#~ msgstr "Altres menjars en aquest dia"
#~ msgid "Recipe Image"
#~ msgstr "Imatge de la Recepta"
#~ msgid "Preparation time ca."
#~ msgstr "Temps de Preparació ca."
#~ msgid "Waiting time ca."
#~ msgstr "Temps d'Espera ca."
#~ msgid "External"
#~ msgstr "Extern"
#~ msgid "Log Cooking"
#~ msgstr "Registre de Cuines"
#~ msgid "Account"
#~ msgstr "Compte"
#~ msgid "Preferences"
#~ msgstr "Preferències"
#~ msgid "API-Settings"
#~ msgstr "Opcions API"
#~ msgid "Search-Settings"
#~ msgstr "Cerca-Opcions"
#~ msgid "Shopping-Settings"
#~ msgstr "Compres-Opcions"
#~ msgid "Name Settings"
#~ msgstr "Noms Opcions"
#~ msgid "Account Settings"
#~ msgstr "Opcions de Compte"
#~ msgid "Emails"
#~ msgstr "Emails"
#~ msgid "Language"
#~ msgstr "Idioma"
#~ msgid "Style"
#~ msgstr "Estil"
#~ msgid "API Token"
#~ msgstr "Token API"
#~ msgid ""
#~ "You can use both basic authentication and token based authentication to "
#~ "access the REST API."
#~ msgstr ""
#~ "Podeu utilitzar tant l’autenticació bàsica com l’autenticació basada en "
#~ "token per accedir a l’API REST."
#~ msgid ""
#~ "Use the token as an Authorization header prefixed by the word token as "
#~ "shown in the following examples:"
#~ msgstr ""
#~ "Utilitzeu el testimoni com a capçalera d'autorització prefixada per la "
#~ "paraula símbol tal com es mostra als exemples següents:"
#~ msgid "or"
#~ msgstr "o"
#~ msgid "Shopping Settings"
#~ msgstr "Opcions de Compra"
#~ msgid "Stats"
#~ msgstr "Estadístiques"
#~ msgid "Statistics"
#~ msgstr "Estadístiques"
#~ msgid "Number of objects"
#~ msgstr "Nombre d'objectes"
#~ msgid "Recipe Imports"
#~ msgstr "Importacions de receptes"
#~ msgid "Objects stats"
#~ msgstr "Estadístiques d'objectes"
#~ msgid "Recipes without Keywords"
#~ msgstr "Receptes sense paraules clau"
#~ msgid "Internal Recipes"
#~ msgstr "Receptes Internes"
#~ msgid "Show Links"
#~ msgstr "Mostra Enllaços"
#~ msgid "A user is required"
#~ msgstr "Usuari requerit"
#~ msgid "Invite User"
#~ msgstr "Convida Usuari"
#~ msgid "User"
#~ msgstr "Usuari"
#~ msgid "Groups"
#~ msgstr "Grups"
#~ msgid "admin"
#~ msgstr "Admin"
#~ msgid "user"
#~ msgstr "usuari"
#~ msgid "guest"
#~ msgstr "convidat"
#~ msgid "remove"
#~ msgstr "elimina"
#~ msgid "Update"
#~ msgstr "Actualitza"
#~ msgid "You cannot edit yourself."
#~ msgstr "No et pot editar a tu mateix."
#~ msgid "There are no members in your space yet!"
#~ msgstr "No hi ha membres en aquest espai!"
#~ msgid "Invite link successfully send to user."
#~ msgstr "Enllaç d'invitació enviat a l'usuari."
#~ msgid ""
#~ "You have send to many emails, please share the link manually or wait a "
#~ "few hours."
#~ msgstr ""
#~ "Masses emails enviats, compartiu l'enllaç manualment o espereu unes hores."
#~ msgid "Email could not be sent to user. Please share the link manually."
#~ msgstr "No es pot enviar email a l'usuari. Comparteix l'enllaç manualment."
#~ msgid ""
#~ "You are already member of a space and therefore cannot join this one."
#~ msgstr "Ja ets membre d'un espai, no pots unir-te a aquest."
#~ msgid "Try the new shopping list"
#~ msgstr "Prova la nova Llista de la Compra"
#~ msgid "Search Recipe"
#~ msgstr "Cerca Recepta"
#~ msgid "Shopping Recipes"
#~ msgstr "Llista de Compra de Receptes"
#~ msgid "No recipes selected"
#~ msgstr "Receptes no seleccionades"
#~ msgid "Entry Mode"
#~ msgstr "Mode entrada"
#~ msgid "Add Entry"
#~ msgstr "Afegir Entrada"
#~ msgid "Amount"
#~ msgstr "Quantitat"
#~ msgid "Select Unit"
#~ msgstr "Selecciona Unitat"
#~ msgid "Select"
#~ msgstr "Selecciona"
#~ msgid "Select Food"
#~ msgstr "Selecciona Menjar"
#~ msgid "Select Supermarket"
#~ msgstr "Selecciona Supermercat"
#~ msgid "Select User"
#~ msgstr "Selecciona Usuari"
#~ msgid "Finished"
#~ msgstr "Acabat"
#~ msgid "You are offline, shopping list might not synchronize."
#~ msgstr ""
#~ "Fora de línia, és possible que la llista de compra no es sincronitzi."
#~ msgid "Copy/Export"
#~ msgstr "Copia/Exporta"
#~ msgid "Drag me to your bookmarks to import recipes from anywhere"
#~ msgstr "Arrossega als marcadors per importar receptes des de qualsevol lloc"
#~ msgid "Bookmark Me!"
#~ msgstr "Marca'm!"
#~ msgid "URL"
#~ msgstr "URL"
#~ msgid "App"
#~ msgstr "App"
#~ msgid "Text"
#~ msgstr "Text"
#~ msgid "File"
#~ msgstr "Arxiu"
#~ msgid "Enter website URL"
#~ msgstr "Introduïu l'URL del lloc web"
#~ msgid "Select recipe files to import or drop them here..."
#~ msgstr ""
#~ "Seleccioneu fitxers de receptes per importar-los o deixeu-los anar aquí..."
#~ msgid "Paste json or html source here to load recipe."
#~ msgstr "Enganxa json o html aquí per carregar la recepta."
#~ msgid "Preview Recipe Data"
#~ msgstr "Previsualitzar dades Recepta"
#~ msgid ""
#~ "Drag recipe attributes from the right into the appropriate box below."
#~ msgstr ""
#~ "Arrossega atributs de la recepta des de la dreta al quadre corresponent."
#~ msgid "Clear Contents"
#~ msgstr "Neteja comentaris"
#~ msgid "Text dragged here will be appended to the name."
#~ msgstr "Text arrossegat aquí s'afegirà al nom."
#~ msgid "Text dragged here will be appended to the description."
#~ msgstr "Text arrossegat aquí s'afegirà a la descripció."
#~ msgid "Keywords dragged here will be appended to current list"
#~ msgstr "Paraules clau arrossegades aquí s'afegiran a la llista actual"
#~ msgid "Image"
#~ msgstr "Imatge"
#~ msgid "Prep Time"
#~ msgstr "Temps preparació"
#~ msgid "Cook Time"
#~ msgstr "Temps Cocció"
#~ msgid "Ingredients dragged here will be appended to current list."
#~ msgstr "Ingredients arrossegats aquí s'afegiran a la llista actual."
#~ msgid ""
#~ "Recipe instructions dragged here will be appended to current instructions."
#~ msgstr ""
#~ "Instruccions de la recepta arrossegades aquí s'afegiran a les "
#~ "instruccions actuals."
#~ msgid "Discovered Attributes"
#~ msgstr "Atributs Descoberts"
#~ msgid ""
#~ "Drag recipe attributes from below into the appropriate box on the left. "
#~ "Click any node to display its full properties."
#~ msgstr ""
#~ "Arrossega atributs de la recepta des de sota al quadre corresponent de "
#~ "l'esquerra. Feu clic a qualsevol node per mostrar les seves propietats."
#~ msgid "Show Blank Field"
#~ msgstr "Mostra Camp en Blanc"
#~ msgid "Blank Field"
#~ msgstr "Camp en Blanc"
#~ msgid "Items dragged to Blank Field will be appended."
#~ msgstr "S'afegiran Elements arrossegats al camp en blanc."
#~ msgid "Delete Text"
#~ msgstr "Esborra Text"
#~ msgid "Delete image"
#~ msgstr "Esborra Imatge"
#~ msgid "Recipe Name"
#~ msgstr "Nom de la Recepta"
#~ msgid "Recipe Description"
#~ msgstr "Descripció de Recepta"
#~ msgid "Select one"
#~ msgstr "Sel·lecciona un"
#~ msgid "Note"
#~ msgstr "Nota"
#~ msgid "Add Keyword"
#~ msgstr "Afegir paraula clau"
#~ msgid "All Keywords"
#~ msgstr "Totes les paraules clau"
#~ msgid "Import all keywords, not only the ones already existing."
#~ msgstr "Importa totes les paraules clau, no només les ja existents."
#~ msgid "Information"
#~ msgstr "Informació"
#~ msgid ""
#~ " Only websites containing ld+json or microdata information can currently\n"
#~ " be imported. Most big recipe pages "
#~ "support this. If you site cannot be imported but\n"
#~ " you think\n"
#~ " it probably has some kind of "
#~ "structured data feel free to post an example in the\n"
#~ " github issues."
#~ msgstr ""
#~ "Actualment, només els llocs web que contenen informació de ld + json o "
#~ "microdades poden fer-ho\n"
#~ "ser importat. La majoria de les pàgines de receptes grans admeten això. "
#~ "Si el lloc no es pot importar però\n"
#~ "tu penses\n"
#~ "probablement tingui algun tipus de dades estructurades. No dubteu a "
#~ "publicar un exemple a\n"
#~ "problemes de github."
#~ msgid "Google ld+json Info"
#~ msgstr "Google ld+json Info"
#~ msgid "GitHub Issues"
#~ msgstr "Problemes de GitHub"
#~ msgid "Recipe Markup Specification"
#~ msgstr "Especificació de marcatge de receptes"
#~ msgid "The requested site provided malformed data and cannot be read."
#~ msgstr ""
#~ "El lloc sol·licitat proporcionava dades malformades i no es pot llegir."
#~ msgid "The requested page could not be found."
#~ msgstr "No s'ha pogut trobar la pàgina sol·licitada."
#~ msgid ""
#~ "The requested site does not provide any recognized data format to import "
#~ "the recipe from."
#~ msgstr ""
#~ "El lloc sol·licitat no proporciona cap format de dades reconegut des d’on "
#~ "importar la recepta."
#~ msgid "I couldn't find anything to do."
#~ msgstr "No es pot trobar res a fer."
#~ msgid "Shopping Lists"
#~ msgstr "Llistes de Compra"
#~ msgid "Time"
#~ msgstr "Temps"
#~ msgid "Log Recipe Cooking"
#~ msgstr "Registre de Receptes de Cuina"
#~ msgid "All fields are optional and can be left empty."
#~ msgstr "Tots els camps són opcionals i es poden deixar buits."
#~ msgid "Rating"
#~ msgstr "Valoració"
#~ msgid "New unit that other gets replaced by."
#~ msgstr "Nova unitat per la qual se substitueix una altra."
#~ msgid "Old Unit"
#~ msgstr "Unitat Antiga"
#~ msgid "Unit that should be replaced."
#~ msgstr "Unitat que s’hauria de substituir."
#~ msgid "New Food"
#~ msgstr "Menjar Nou"
#~ msgid "New food that other gets replaced by."
#~ msgstr "Nou menjar que altres substitueixen."
#~ msgid "Old Food"
#~ msgstr "Antic Menjar"
#~ msgid "New Entry"
#~ msgstr "Nova Entrada"
#~ msgid "Title"
#~ msgstr "Títol"
#~ msgid "Note (optional)"
#~ msgstr "Nota (opcional)"
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "Pots utilitzar marcadors per donar format a aquest camp. Consulteu els documents aquí"
#~ msgid "Create only note"
#~ msgstr "Crear només nota"
#~ msgid "Number of Days"
#~ msgstr "Nombre de dies"
#~ msgid "Weekday offset"
#~ msgstr "Desplaçament entre setmana"
#~ msgid ""
#~ "Number of days starting from the first day of the week to offset the "
#~ "default view."
#~ msgstr ""
#~ "Nombre de dies començant pel primer dia de la setmana per a desplaçar la "
#~ "vista actual"
#~ msgid "Edit plan types"
#~ msgstr "Edita el tipus de pla"
#~ msgid "Show help"
#~ msgstr "Mostra ajuda"
#~ msgid "Week iCal export"
#~ msgstr "Exportació iCal setmanal"
#~ msgid "Add to Shopping"
#~ msgstr "Afegir a la compra"
#~ msgid "New meal type"
#~ msgstr "nou tipus de menú"
#~ msgid "Meal Plan Help"
#~ msgstr "Ajuda del pla de menjars"
#~ msgid "Units merged!"
#~ msgstr "Unitats fusionades!"
#~ msgid "Foods merged!"
#~ msgstr "Menjars Fusionats!"
#~ msgid "Utensils"
#~ msgstr "Estris"
#~ msgid "Storage Data"
#~ msgstr "Emmagatzematge de dades"
#~ msgid "Storage Backends"
#~ msgstr "Backends d'emmagatzematge"
#~ msgid "Configure Sync"
#~ msgstr "Configurar Sync"
#~ msgid "Discovered Recipes"
#~ msgstr "Receptes Descobertes"
#~ msgid "Discovery Log"
#~ msgstr "Registre de descobriment"
#~ msgid "Units & Ingredients"
#~ msgstr "Unitats i ingredients"
#~ msgid "New Book"
#~ msgstr "Nou Llibre"
#~ msgid "Toggle Recipes"
#~ msgstr "Commuta Receptes"
#~ msgid "There are no recipes in this book yet."
#~ msgstr "Encara no hi ha receptes en aquest llibre."
#~ msgid "Waiting Time"
#~ msgstr "Temps d'Espera"
#~ msgid "Select Keywords"
#~ msgstr "Selecciona Paraules clau"
#~ msgid "Delete Step"
#~ msgstr "Esborra Pas"
#~ msgid "Step"
#~ msgstr "Pas"
#~ msgid "Show as header"
#~ msgstr "Mostra com a capçalera"
#~ msgid "Hide as header"
#~ msgstr "Amaga com a capçalera"
#~ msgid "Move Up"
#~ msgstr "Mou Amunt"
#~ msgid "Move Down"
#~ msgstr "Mou Avall"
#~ msgid "Step Name"
#~ msgstr "Nom del Pas"
#~ msgid "Step Type"
#~ msgstr "Tipus de Pas"
#~ msgid "Step time in Minutes"
#~ msgstr "Temps de pas en Minuts"
#, fuzzy
#~| msgid "Select one"
#~ msgid "Select File"
#~ msgstr "Sel·lecciona un"
#, fuzzy
#~| msgid "Delete Recipe"
#~ msgid "Select Recipe"
#~ msgstr "Esborra Recepta"
#~ msgid "Delete Ingredient"
#~ msgstr "Esborra Ingredient"
#~ msgid "Make Header"
#~ msgstr "Crea Capçalera"
#~ msgid "Make Ingredient"
#~ msgstr "Crea Ingredient"
#~ msgid "Disable Amount"
#~ msgstr "Deshabilita Quantitat"
#~ msgid "Enable Amount"
#~ msgstr "Habilita Quantitat"
#~ msgid "Save & View"
#~ msgstr "Desa i Comprova"
#~ msgid "Add Step"
#~ msgstr "Afegir Pas"
#~ msgid "Add Nutrition"
#~ msgstr "Afegeix nutrients"
#~ msgid "Remove Nutrition"
#~ msgstr "Elimina nutrients"
#~ msgid "View Recipe"
#~ msgstr "Veure Recepta"
#~ msgid "Delete Recipe"
#~ msgstr "Esborra Recepta"
#~ msgid ""
#~ "A username is not required, if left blank the new user can choose one."
#~ msgstr ""
#~ "No cal un nom d’usuari, si es deixa en blanc el nou usuari en pot triar "
#~ "un."
#~ msgid "Link"
#~ msgstr "Enllaç"
#~ msgid "Logout"
#~ msgstr "Tancar Sessió"
#~ msgid "Website Import"
#~ msgstr "Importa desde Web"
#~ msgid "There was an error creating a resource!"
#~ msgstr "S'ha produït un error en crear un recurs."
#~ msgid ""
#~ "The requested page refused to provide any information (Status Code 403)."
#~ msgstr ""
#~ "La pàgina sol·licitada refusa a proporcionar cap informació (Codi d’estat "
#~ "403)."
#~ msgid "Number of servings"
#~ msgstr "Nombre de racions"
#~ msgid ""
#~ "Include - [ ] in list for easier usage in markdown based "
#~ "documents."
#~ msgstr ""
#~ "Incloure 1 - [ ] 1 a la llista per a un ús més fàcil en documents basats "
#~ "en la reducció."
#~ msgid "Backup & Restore"
#~ msgstr "Còpia i Restauració"
#~ msgid "Download Backup"
#~ msgstr "Descarregar còpia de seguretat"
#~ msgid "Preference for given user already exists"
#~ msgstr "Ja existeix la preferència per a l'usuari"
================================================
FILE: cookbook/locale/cs/LC_MESSAGES/django.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
# Translators:
# Pavel Solař , 2021
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-22 20:15+0200\n"
"PO-Revision-Date: 2024-01-09 12:07+0000\n"
"Last-Translator: Jan Kubošek \n"
"Language-Team: Czech \n"
"Language: cs\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n "
"<= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n"
"X-Generator: Weblate 4.15\n"
#: .\cookbook\forms.py:50
msgid "Default"
msgstr "Výchozí"
#: .\cookbook\forms.py:77
msgid ""
"To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything."
msgstr ""
#: .\cookbook\forms.py:108
msgid "Maximum number of users for this space reached."
msgstr ""
#: .\cookbook\forms.py:114
msgid "Email address already taken!"
msgstr ""
#: .\cookbook\forms.py:121
msgid ""
"An email address is not required but if present the invite link will be sent "
"to the user."
msgstr ""
#: .\cookbook\forms.py:133
msgid "Name already taken."
msgstr ""
#: .\cookbook\forms.py:144 .\cookbook\forms.py:158
msgid "Accept Terms and Privacy"
msgstr ""
#: .\cookbook\helper\AllAuthCustomAdapter.py:41
msgid ""
"In order to prevent spam, the requested email was not send. Please wait a "
"few minutes and try again."
msgstr ""
#: .\cookbook\helper\permission_helper.py:165
#: .\cookbook\helper\permission_helper.py:186 .\cookbook\views\views.py:138
msgid "You are not logged in and therefore cannot view this page!"
msgstr "Nejste přihlášen(a), proto nelze stránku zobrazit!"
#: .\cookbook\helper\permission_helper.py:168
#: .\cookbook\helper\permission_helper.py:173
#: .\cookbook\helper\permission_helper.py:198
#: .\cookbook\helper\permission_helper.py:265
#: .\cookbook\helper\permission_helper.py:279
#: .\cookbook\helper\permission_helper.py:290
#: .\cookbook\helper\permission_helper.py:301
#: .\cookbook\helper\permission_helper.py:317
#: .\cookbook\helper\permission_helper.py:343
#: .\cookbook\helper\permission_helper.py:359
msgid "You do not have the required permissions to view this page!"
msgstr "Pro zobrazení této stránky nemáte dostatečné oprávnění!"
#: .\cookbook\helper\permission_helper.py:191
#: .\cookbook\helper\permission_helper.py:214
#: .\cookbook\helper\permission_helper.py:236
#: .\cookbook\helper\permission_helper.py:251
msgid "You cannot interact with this object as it is not owned by you!"
msgstr "Nemůžete ovlivnit tento objekt, protože není vlastněn vámi!"
#: .\cookbook\helper\permission_helper.py:420
msgid "You have reached the maximum number of recipes for your space."
msgstr ""
#: .\cookbook\helper\permission_helper.py:432
msgid "You have more users than allowed in your space."
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:319
msgid "reverse rotation"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:320
msgid "careful rotation"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:321
msgid "knead"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:322
msgid "thicken"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:323
msgid "warm up"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:324
msgid "ferment"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:325
#, fuzzy
#| msgid "Last cooked"
msgid "slow cook"
msgstr "Naposled uvařeno"
#: .\cookbook\helper\recipe_url_import.py:326
msgid "egg boiler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:327
msgid "kettle"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:328
msgid "blend"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:329
msgid "pre-clean"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:330
msgid "high temperature"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:331
msgid "rice cooker"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:332
msgid "caramelize"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:333
msgid "peeler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:334
msgid "slicer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:335
msgid "grater"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:336
msgid "spiralizer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:337
msgid "sous-vide"
msgstr ""
#: .\cookbook\helper\shopping_helper.py:150
msgid "You must supply a servings size"
msgstr ""
#: .\cookbook\helper\template_helper.py:97
#: .\cookbook\helper\template_helper.py:99
#: .\cookbook\helper\template_helper.py:101
msgid "Could not parse template code."
msgstr ""
#: .\cookbook\integration\copymethat.py:44
#: .\cookbook\integration\melarecipes.py:37
msgid "Favorite"
msgstr ""
#: .\cookbook\integration\copymethat.py:50
msgid "I made this"
msgstr ""
#: .\cookbook\integration\integration.py:238
msgid ""
"Importer expected a .zip file. Did you choose the correct importer type for "
"your data ?"
msgstr ""
"Importér očekával soubor .zip. Zvolili jste pro svá data správný typ "
"importéru?"
#: .\cookbook\integration\integration.py:241
msgid ""
"An unexpected error occurred during the import. Please make sure you have "
"uploaded a valid file."
msgstr ""
#: .\cookbook\integration\integration.py:246
msgid "The following recipes were ignored because they already existed:"
msgstr ""
#: .\cookbook\integration\integration.py:250
#, fuzzy, python-format
#| msgid "Imported new recipe!"
msgid "Imported %s recipes."
msgstr "Nový recept naimportován!"
#: .\cookbook\integration\mealie1.py:210
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "Calories"
msgstr "Kalorie"
#: .\cookbook\integration\mealie1.py:211
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
msgid "Carbohydrates"
msgstr "Karbohydráty"
#: .\cookbook\integration\mealie1.py:212
msgid "Cholesterol"
msgstr ""
#: .\cookbook\integration\mealie1.py:213
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
#, fuzzy
#| msgid "Fats"
msgid "Fat"
msgstr "Tuky"
#: .\cookbook\integration\mealie1.py:214
msgid "Fiber"
msgstr ""
#: .\cookbook\integration\mealie1.py:215
#, fuzzy
#| msgid "Proteins"
msgid "Protein"
msgstr "Proteiny"
#: .\cookbook\integration\mealie1.py:216
msgid "Saturated Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:217
msgid "Sodium"
msgstr ""
#: .\cookbook\integration\mealie1.py:218
msgid "Sugar"
msgstr ""
#: .\cookbook\integration\mealie1.py:219
msgid "Trans Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:220
msgid "Unsaturated Fat"
msgstr ""
#: .\cookbook\integration\openeats.py:28
#, fuzzy
#| msgid "Recipe Home"
msgid "Recipe source:"
msgstr "Úvod receptů"
#: .\cookbook\integration\paprika.py:49
#, fuzzy
#| msgid "Note"
msgid "Notes"
msgstr "Poznámka"
#: .\cookbook\integration\paprika.py:52
#, fuzzy
#| msgid "Information"
msgid "Nutritional Information"
msgstr "Informace"
#: .\cookbook\integration\paprika.py:56
msgid "Source"
msgstr ""
#: .\cookbook\integration\recettetek.py:55
#: .\cookbook\integration\recipekeeper.py:70
msgid "Imported from"
msgstr "Nahráno z adresy"
#: .\cookbook\integration\saffron.py:23
msgid "Servings"
msgstr "Porce"
#: .\cookbook\integration\saffron.py:25
msgid "Waiting time"
msgstr "Doba čekání"
#: .\cookbook\integration\saffron.py:27
msgid "Preparation Time"
msgstr "Doba přípavy"
#: .\cookbook\integration\saffron.py:29 .\cookbook\templates\index.html:6
msgid "Cookbook"
msgstr "Kuchařka"
#: .\cookbook\integration\saffron.py:31
msgid "Section"
msgstr "Sekce"
#: .\cookbook\management\commands\fix_duplicate_properties.py:15
msgid "Fixes foods with "
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:14
msgid "Rebuilds full text search index on Recipe"
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:18
msgid "Only Postgresql databases use full text search, no index to rebuild"
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:29
msgid "Recipe index rebuild complete."
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:31
msgid "Recipe index rebuild failed."
msgstr ""
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
msgid "Breakfast"
msgstr "Snídaně"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:19
msgid "Lunch"
msgstr "Oběd"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:24
msgid "Dinner"
msgstr "Večeře"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:29 .\cookbook\models.py:971
msgid "Other"
msgstr "Ostatní"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "g"
msgstr ""
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "Proteins"
msgstr "Proteiny"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "kcal"
msgstr ""
#: .\cookbook\models.py:325
msgid ""
"Maximum file storage for space in MB. 0 for unlimited, -1 to disable file "
"upload."
msgstr ""
#: .\cookbook\models.py:513
msgid "Search"
msgstr "Vyhledat"
#: .\cookbook\models.py:514
msgid "Meal-Plan"
msgstr "Jídelníček"
#: .\cookbook\models.py:515
msgid "Books"
msgstr "Kuchařky"
#: .\cookbook\models.py:516 .\cookbook\views\views.py:416
#: .\cookbook\views\views.py:417
msgid "Shopping"
msgstr "Nákupy"
#: .\cookbook\models.py:967
msgid "Nutrition"
msgstr "Výživové hodnoty"
#: .\cookbook\models.py:968
#, fuzzy
#| msgid "Merge"
msgid "Allergen"
msgstr "Sloučit"
#: .\cookbook\models.py:969
msgid "Price"
msgstr ""
#: .\cookbook\models.py:970
msgid "Goal"
msgstr ""
#: .\cookbook\models.py:1467 .\cookbook\templates\search_info.html:28
msgid "Simple"
msgstr ""
#: .\cookbook\models.py:1468 .\cookbook\templates\search_info.html:33
msgid "Phrase"
msgstr ""
#: .\cookbook\models.py:1469 .\cookbook\templates\search_info.html:38
msgid "Web"
msgstr ""
#: .\cookbook\models.py:1470 .\cookbook\templates\search_info.html:47
msgid "Raw"
msgstr ""
#: .\cookbook\models.py:1525
msgid "Food Alias"
msgstr ""
#: .\cookbook\models.py:1526
#, fuzzy
#| msgid "Units"
msgid "Unit Alias"
msgstr "Jednotky"
#: .\cookbook\models.py:1527
#, fuzzy
#| msgid "Keywords"
msgid "Keyword Alias"
msgstr "Štítky"
#: .\cookbook\models.py:1528
#, fuzzy
#| msgid "Description"
msgid "Description Replace"
msgstr "Popis"
#: .\cookbook\models.py:1529
#, fuzzy
#| msgid "Instructions"
msgid "Instruction Replace"
msgstr "Instrukce"
#: .\cookbook\models.py:1530
#, fuzzy
#| msgid "New Unit"
msgid "Never Unit"
msgstr "Nová jednotka"
#: .\cookbook\models.py:1531
msgid "Transpose Words"
msgstr ""
#: .\cookbook\models.py:1532
msgid "Food Replace"
msgstr ""
#: .\cookbook\models.py:1533
#, fuzzy
#| msgid "Edit Recipe"
msgid "Unit Replace"
msgstr "Upravit recept"
#: .\cookbook\models.py:1534
msgid "Name Replace"
msgstr ""
#: .\cookbook\models.py:1564
msgid "Recipe"
msgstr "Recept"
#: .\cookbook\models.py:1565
msgid "Food"
msgstr "Potravina"
#: .\cookbook\models.py:1566
msgid "Keyword"
msgstr "Štítek"
#: .\cookbook\serializer.py:262
msgid "File uploads are not enabled for this Space."
msgstr ""
#: .\cookbook\serializer.py:273
msgid "You have reached your file upload limit."
msgstr ""
#: .\cookbook\serializer.py:281
msgid "The given file type is not allowed."
msgstr ""
#: .\cookbook\serializer.py:421 .\cookbook\views\views.py:94
msgid ""
"You have the reached the maximum amount of spaces that can be owned by you."
msgstr ""
#: .\cookbook\serializer.py:434
msgid "Space Name must be unique."
msgstr ""
#: .\cookbook\serializer.py:469
msgid "Cannot modify Space owner permission."
msgstr ""
#: .\cookbook\serializer.py:1596
msgid "Hello"
msgstr ""
#: .\cookbook\serializer.py:1596
msgid "You have been invited by "
msgstr ""
#: .\cookbook\serializer.py:1598
msgid " to join their Tandoor Recipes space "
msgstr ""
#: .\cookbook\serializer.py:1600
msgid "Click the following link to activate your account: "
msgstr ""
#: .\cookbook\serializer.py:1602
msgid ""
"If the link does not work use the following code to manually join the space: "
msgstr ""
#: .\cookbook\serializer.py:1604
msgid "The invitation is valid until "
msgstr ""
#: .\cookbook\serializer.py:1606
msgid ""
"Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub "
msgstr ""
#: .\cookbook\serializer.py:1609
msgid "Tandoor Recipes Invite"
msgstr ""
#: .\cookbook\serializer.py:1813
msgid "Existing shopping list to update"
msgstr ""
#: .\cookbook\serializer.py:1815
msgid ""
"List of ingredient IDs from the recipe to add, if not provided all "
"ingredients will be added."
msgstr ""
#: .\cookbook\serializer.py:1817
msgid ""
"Providing a list_recipe ID and servings of 0 will delete that shopping list."
msgstr ""
#: .\cookbook\serializer.py:1826
msgid "Amount of food to add to the shopping list"
msgstr ""
#: .\cookbook\serializer.py:1828
msgid "ID of unit to use for the shopping list"
msgstr ""
#: .\cookbook\serializer.py:1830
msgid "When set to true will delete all food from active shopping lists."
msgstr ""
#: .\cookbook\templates\404.html:5
msgid "404 Error"
msgstr "Chyba 404"
#: .\cookbook\templates\404.html:18
msgid "The page you are looking for could not be found."
msgstr "Stránka kterou hledáte nebyla nalezena."
#: .\cookbook\templates\404.html:33
msgid "Take me Home"
msgstr "Přejít domů"
#: .\cookbook\templates\404.html:35
msgid "Report a Bug"
msgstr "Nahlásit chybu"
#: .\cookbook\templates\account\email.html:6
#: .\cookbook\templates\account\email.html:10
msgid "E-mail Addresses"
msgstr ""
#: .\cookbook\templates\account\email.html:12
msgid "The following e-mail addresses are associated with your account:"
msgstr ""
#: .\cookbook\templates\account\email.html:29
msgid "Verified"
msgstr ""
#: .\cookbook\templates\account\email.html:31
msgid "Unverified"
msgstr ""
#: .\cookbook\templates\account\email.html:33
msgid "Primary"
msgstr ""
#: .\cookbook\templates\account\email.html:40
#, fuzzy
#| msgid "Make Header"
msgid "Make Primary"
msgstr "Vytvořit hlavičku"
#: .\cookbook\templates\account\email.html:42
msgid "Re-send Verification"
msgstr ""
#: .\cookbook\templates\account\email.html:43
#: .\cookbook\templates\socialaccount\connections.html:36
msgid "Remove"
msgstr "Odstranit"
#: .\cookbook\templates\account\email.html:51
#, fuzzy
#| msgid "Warning"
msgid "Warning:"
msgstr "Varování"
#: .\cookbook\templates\account\email.html:51
msgid ""
"You currently do not have any e-mail address set up. You should really add "
"an e-mail address so you can receive notifications, reset your password, etc."
msgstr ""
#: .\cookbook\templates\account\email.html:57
msgid "Add E-mail Address"
msgstr ""
#: .\cookbook\templates\account\email.html:62
msgid "Add E-mail"
msgstr ""
#: .\cookbook\templates\account\email.html:72
msgid "Do you really want to remove the selected e-mail address?"
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:6
#: .\cookbook\templates\account\email_confirm.html:10
msgid "Confirm E-mail Address"
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:16
#, python-format
msgid ""
"Please confirm that\n"
" %(email)s is an e-mail address "
"for user %(user_display)s\n"
" ."
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:22
msgid "Confirm"
msgstr "Potvrdit"
#: .\cookbook\templates\account\email_confirm.html:29
#, python-format
msgid ""
"This e-mail confirmation link expired or is invalid. Please\n"
" issue a new e-mail confirmation "
"request."
msgstr ""
#: .\cookbook\templates\account\login.html:8
#: .\cookbook\templates\openid\login.html:8
msgid "Login"
msgstr "Přihlášení"
#: .\cookbook\templates\account\login.html:15
#: .\cookbook\templates\account\login.html:31
#: .\cookbook\templates\account\password_reset.html:39
#: .\cookbook\templates\account\password_reset_done.html:31
#: .\cookbook\templates\account\signup.html:68
#: .\cookbook\templates\account\signup_closed.html:15
#: .\cookbook\templates\openid\login.html:15
#: .\cookbook\templates\openid\login.html:26
#: .\cookbook\templates\socialaccount\authentication_error.html:15
msgid "Sign In"
msgstr "Přihlásit se"
#: .\cookbook\templates\account\login.html:34
#: .\cookbook\templates\account\password_reset.html:41
#: .\cookbook\templates\account\password_reset_done.html:33
#: .\cookbook\templates\socialaccount\signup.html:8
#: .\cookbook\templates\socialaccount\signup.html:56
#, fuzzy
#| msgid "Sign In"
msgid "Sign Up"
msgstr "Přihlásit se"
#: .\cookbook\templates\account\login.html:38
msgid "Lost your password?"
msgstr ""
#: .\cookbook\templates\account\login.html:39
#: .\cookbook\templates\account\password_reset.html:29
msgid "Reset My Password"
msgstr ""
#: .\cookbook\templates\account\login.html:50
msgid "Social Login"
msgstr "Přihlásit přes soc. sítě"
#: .\cookbook\templates\account\login.html:51
msgid "You can use any of the following providers to sign in."
msgstr "K přihlášení můžete využít některého z následujících poskytovatelů."
#: .\cookbook\templates\account\logout.html:5
#: .\cookbook\templates\account\logout.html:9
#: .\cookbook\templates\account\logout.html:18
msgid "Sign Out"
msgstr "Ohlásit se"
#: .\cookbook\templates\account\logout.html:11
msgid "Are you sure you want to sign out?"
msgstr "Opravdu se chcete odhlásit?"
#: .\cookbook\templates\account\password_change.html:6
#: .\cookbook\templates\account\password_change.html:10
#: .\cookbook\templates\account\password_change.html:15
#: .\cookbook\templates\account\password_reset_from_key.html:7
#: .\cookbook\templates\account\password_reset_from_key.html:13
#: .\cookbook\templates\account\password_reset_from_key_done.html:7
#: .\cookbook\templates\account\password_reset_from_key_done.html:13
#, fuzzy
#| msgid "Changes saved!"
msgid "Change Password"
msgstr "Změny uloženy!"
#: .\cookbook\templates\account\password_change.html:16
msgid "Forgot Password?"
msgstr ""
#: .\cookbook\templates\account\password_reset.html:7
#: .\cookbook\templates\account\password_reset.html:13
#: .\cookbook\templates\account\password_reset_done.html:7
#: .\cookbook\templates\account\password_reset_done.html:18
msgid "Password Reset"
msgstr "Resetovat heslo"
#: .\cookbook\templates\account\password_reset.html:24
msgid ""
"Forgotten your password? Enter your e-mail address below, and we'll send you "
"an e-mail allowing you to reset it."
msgstr ""
#: .\cookbook\templates\account\password_reset.html:32
#, fuzzy
#| msgid "Password reset is not implemented for the time being!"
msgid "Password reset is disabled on this instance."
msgstr "Obnovení hesla zatím není implementováno!"
#: .\cookbook\templates\account\password_reset_done.html:25
msgid ""
"We have sent you an e-mail. Please contact us if you do not receive it "
"within a few minutes."
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:13
#, fuzzy
#| msgid "API Token"
msgid "Bad Token"
msgstr "API Token"
#: .\cookbook\templates\account\password_reset_from_key.html:25
#, python-format
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used.\n"
" Please request a new "
"password reset."
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:33
msgid "change password"
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:36
#: .\cookbook\templates\account\password_reset_from_key_done.html:19
msgid "Your password is now changed."
msgstr ""
#: .\cookbook\templates\account\password_set.html:6
#: .\cookbook\templates\account\password_set.html:10
#: .\cookbook\templates\account\password_set.html:15
#, fuzzy
#| msgid "Password Reset"
msgid "Set Password"
msgstr "Resetovat heslo"
#: .\cookbook\templates\account\signup.html:5
msgid "Register"
msgstr "Registrovat"
#: .\cookbook\templates\account\signup.html:11
#, fuzzy
#| msgid "Create your Account"
msgid "Create an Account"
msgstr "Vytvořit účet"
#: .\cookbook\templates\account\signup.html:41
msgid "I accept the follwoing"
msgstr ""
#: .\cookbook\templates\account\signup.html:44
#: .\cookbook\templates\socialaccount\signup.html:35
msgid "Terms and Conditions"
msgstr ""
#: .\cookbook\templates\account\signup.html:47
#: .\cookbook\templates\socialaccount\signup.html:38
msgid "and"
msgstr ""
#: .\cookbook\templates\account\signup.html:51
#: .\cookbook\templates\socialaccount\signup.html:42
msgid "Privacy Policy"
msgstr ""
#: .\cookbook\templates\account\signup.html:64
msgid "Create User"
msgstr "Vytvořit uživatele"
#: .\cookbook\templates\account\signup.html:68
msgid "Already have an account?"
msgstr ""
#: .\cookbook\templates\account\signup_closed.html:5
#: .\cookbook\templates\account\signup_closed.html:11
msgid "Sign Up Closed"
msgstr ""
#: .\cookbook\templates\account\signup_closed.html:13
msgid "We are sorry, but the sign up is currently closed."
msgstr ""
#: .\cookbook\templates\frontend\tandoor.html:15
msgid "Tandoor Recipe Manager"
msgstr ""
#: .\cookbook\templates\index.html:28
msgid "Search recipe ..."
msgstr "Hledat recept ..."
#: .\cookbook\templates\index.html:43
msgid "New Recipe"
msgstr "Nový recept"
#: .\cookbook\templates\index.html:46
msgid "Import Recipe"
msgstr "Import receptu"
#: .\cookbook\templates\index.html:52
msgid "Advanced Search"
msgstr "Pokročilé hledání"
#: .\cookbook\templates\index.html:56
msgid "Reset Search"
msgstr "Obnovit hledání"
#: .\cookbook\templates\index.html:84
msgid "Last viewed"
msgstr "Naposledy zobrazené"
#: .\cookbook\templates\index.html:86
msgid "Recipes"
msgstr "Recepty"
#: .\cookbook\templates\index.html:93
msgid "Log in to view recipes"
msgstr "Pro zobrazení receptů se přihlaste"
#: .\cookbook\templates\markdown_info.html:5
#: .\cookbook\templates\markdown_info.html:13
msgid "Markdown Info"
msgstr "Informace o značkách"
#: .\cookbook\templates\markdown_info.html:14
msgid ""
"\n"
" Markdown is lightweight markup language that can be used to format "
"plain text easily.\n"
" This site uses the Python Markdown library to\n"
" convert your text into nice looking HTML. Its full markdown "
"documentation can be found\n"
" here.\n"
" An incomplete but most likely sufficient documentation can be found "
"below.\n"
" "
msgstr ""
"\n"
" Značky jsou nenáročný jazyk, který lze jednoduše použít k "
"formátování bežného textu.\n"
" Tato aplikace používá pro zkrášlení HTML knihovnu značek Python \n"
"Kompletní dokumentaci ke značkám naleznete\n"
" zde.\n"
" Nekompletní, ale pro většinu případů dostačující dokumentaci, "
"naleznete níže.\n"
" "
#: .\cookbook\templates\markdown_info.html:25
msgid "Headers"
msgstr "Nadpisy"
#: .\cookbook\templates\markdown_info.html:54
msgid "Formatting"
msgstr "Formátování"
#: .\cookbook\templates\markdown_info.html:56
#: .\cookbook\templates\markdown_info.html:72
msgid "Line breaks are inserted by adding two spaces after the end of a line"
msgstr "Zalomení řádku vložíte přidáním dvou mezer na konci řádku"
#: .\cookbook\templates\markdown_info.html:57
#: .\cookbook\templates\markdown_info.html:73
#, fuzzy
#| msgid "or by leaving a blank line inbetween."
msgid "or by leaving a blank line in between."
msgstr "nebo vynecháním prázdné linky mezi nimi."
#: .\cookbook\templates\markdown_info.html:59
#: .\cookbook\templates\markdown_info.html:74
msgid "This text is bold"
msgstr "Tento text je tučně"
#: .\cookbook\templates\markdown_info.html:60
#: .\cookbook\templates\markdown_info.html:75
msgid "This text is italic"
msgstr "Tento text je kurzívou"
#: .\cookbook\templates\markdown_info.html:61
#: .\cookbook\templates\markdown_info.html:77
msgid "Blockquotes are also possible"
msgstr "Lze použít i kvotace"
#: .\cookbook\templates\markdown_info.html:84
msgid "Lists"
msgstr "Seznamy"
#: .\cookbook\templates\markdown_info.html:85
#, fuzzy
#| msgid ""
#| "Lists can ordered or unorderd. It is important to leave a blank line "
#| "before the list!"
msgid ""
"Lists can ordered or unordered. It is important to leave a blank line "
"before the list!"
msgstr ""
"Seznamy mohou být řazené, nebo neřazené. Před seznamem je důležité "
"vynechat řádek!"
#: .\cookbook\templates\markdown_info.html:87
#: .\cookbook\templates\markdown_info.html:108
msgid "Ordered List"
msgstr "Tříděný seznam"
#: .\cookbook\templates\markdown_info.html:89
#: .\cookbook\templates\markdown_info.html:90
#: .\cookbook\templates\markdown_info.html:91
#: .\cookbook\templates\markdown_info.html:110
#: .\cookbook\templates\markdown_info.html:111
#: .\cookbook\templates\markdown_info.html:112
msgid "unordered list item"
msgstr "položka netříděného seznamu"
#: .\cookbook\templates\markdown_info.html:93
#: .\cookbook\templates\markdown_info.html:114
msgid "Unordered List"
msgstr "Netříděný seznam"
#: .\cookbook\templates\markdown_info.html:95
#: .\cookbook\templates\markdown_info.html:96
#: .\cookbook\templates\markdown_info.html:97
#: .\cookbook\templates\markdown_info.html:116
#: .\cookbook\templates\markdown_info.html:117
#: .\cookbook\templates\markdown_info.html:118
msgid "ordered list item"
msgstr "položka tříděného seznamu"
#: .\cookbook\templates\markdown_info.html:125
msgid "Images & Links"
msgstr "Obrázky a odkazy"
#: .\cookbook\templates\markdown_info.html:126
msgid ""
"Links can be formatted with Markdown. This application also allows to paste "
"links directly into markdown fields without any formatting."
msgstr ""
"Odkazy mohou být formátovány pomocí značek. Aplikace také umožňuje vložení "
"přímo do polí značek bez jakéhokoliv formátování."
#: .\cookbook\templates\markdown_info.html:132
#: .\cookbook\templates\markdown_info.html:145
msgid "This will become an image"
msgstr "Toto se stane obrázkem"
#: .\cookbook\templates\markdown_info.html:152
msgid "Tables"
msgstr "Tabulky"
#: .\cookbook\templates\markdown_info.html:153
msgid ""
"Markdown tables are hard to create by hand. It is recommended to use a table "
"editor like this one."
msgstr ""
"Ruční vytváření tabulek pomocí značek je složité. Doporučujeme použít "
"například tento tabulkový editor."
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:171
#: .\cookbook\templates\markdown_info.html:177
msgid "Table"
msgstr "Tabulka"
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:172
msgid "Header"
msgstr "Nadpis"
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:178
msgid "Cell"
msgstr "Buňka"
#: .\cookbook\templates\no_groups_info.html:5
#: .\cookbook\templates\no_groups_info.html:12
msgid "No Permissions"
msgstr "Žádná práva"
#: .\cookbook\templates\no_groups_info.html:17
#, fuzzy
#| msgid ""
#| "You do not have any groups and therefor cannot use this application. "
#| "Please contact your administrator."
msgid "You do not have any groups and therefor cannot use this application."
msgstr ""
"Nemáte žádné skupiny a proto nemůžete používat tuto aplikaci. Prosím "
"kontaktujte administrátora."
#: .\cookbook\templates\no_groups_info.html:18
#: .\cookbook\templates\no_perm_info.html:15
msgid "Please contact your administrator."
msgstr ""
#: .\cookbook\templates\no_perm_info.html:5
#: .\cookbook\templates\no_perm_info.html:12
#, fuzzy
#| msgid "No Permissions"
msgid "No Permission"
msgstr "Žádná práva"
#: .\cookbook\templates\no_perm_info.html:15
#, fuzzy
#| msgid "You do not have the required permissions to perform this action!"
msgid ""
"You do not have the required permissions to view this page or perform this "
"action."
msgstr "Pro provedení této akce nemáte dostatečná práva!"
#: .\cookbook\templates\offline.html:5
msgid "Offline"
msgstr "Offline"
#: .\cookbook\templates\openid\login.html:27
#: .\cookbook\templates\socialaccount\authentication_error.html:27
msgid "Back"
msgstr ""
#: .\cookbook\templates\rest_framework\api.html:5
msgid "Recipe Home"
msgstr "Úvod receptů"
#: .\cookbook\templates\rest_framework\api.html:11
msgid "API Documentation"
msgstr "API dokumentace"
#: .\cookbook\templates\search_info.html:5
#: .\cookbook\templates\search_info.html:9
#, fuzzy
#| msgid "Search String"
msgid "Search Settings"
msgstr "Hledat řetězec"
#: .\cookbook\templates\search_info.html:10
msgid ""
"\n"
" Creating the best search experience is complicated and weighs "
"heavily on your personal configuration. \n"
" Changing any of the search settings can have significant impact on "
"the speed and quality of the results.\n"
" Search Methods, Trigrams and Full Text Search configurations are "
"only available if you are using Postgres for your database.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:19
#, fuzzy
#| msgid "Search"
msgid "Search Methods"
msgstr "Vyhledat"
#: .\cookbook\templates\search_info.html:23
msgid ""
" \n"
" Full text searches attempt to normalize the words provided to "
"match common variants. For example: 'forked', 'forking', 'forks' will all "
"normalize to 'fork'.\n"
" There are several methods available, described below, that will "
"control how the search behavior should react when multiple words are "
"searched.\n"
" Full technical details on how these operate can be viewed on Postgresql's website.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:29
msgid ""
" \n"
" Simple searches ignore punctuation and common words such as "
"'the', 'a', 'and'. And will treat separate words as required.\n"
" Searching for 'apple or flour' will return any recipe that "
"includes both 'apple' and 'flour' anywhere in the fields that have been "
"selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:34
msgid ""
" \n"
" Phrase searches ignore punctuation, but will search for all of "
"the words in the exact order provided.\n"
" Searching for 'apple or flour' will only return a recipe that "
"includes the exact phrase 'apple or flour' in any of the fields that have "
"been selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:39
msgid ""
" \n"
" Web searches simulate functionality found on many web search "
"sites supporting special syntax.\n"
" Placing quotes around several words will convert those words "
"into a phrase.\n"
" 'or' is recognized as searching for the word (or phrase) "
"immediately before 'or' OR the word (or phrase) directly after.\n"
" '-' is recognized as searching for recipes that do not include "
"the word (or phrase) that comes immediately after. \n"
" For example searching for 'apple pie' or cherry -butter will "
"return any recipe that includes the phrase 'apple pie' or the word "
"'cherry' \n"
" in any field included in the full text search but exclude any "
"recipe that has the word 'butter' in any field included.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:48
msgid ""
" \n"
" Raw search is similar to Web except will take puncuation "
"operators such as '|', '&' and '()'\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:59
msgid ""
" \n"
" Another approach to searching that also requires Postgresql is "
"fuzzy search or trigram similarity. A trigram is a group of three "
"consecutive characters.\n"
" For example searching for 'apple' will create x trigrams 'app', "
"'ppl', 'ple' and will create a score of how closely words match the "
"generated trigrams.\n"
" One benefit of searching trigams is that a search for 'sandwich' "
"will find misspelled words such as 'sandwhich' that would be missed by other "
"methods.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:69
#, fuzzy
#| msgid "Search Recipe"
msgid "Search Fields"
msgstr "Hledat recept"
#: .\cookbook\templates\search_info.html:73
msgid ""
" \n"
" Unaccent is a special case in that it enables searching a field "
"'unaccented' for each search style attempting to ignore accented values. \n"
" For example when you enable unaccent for 'Name' any search "
"(starts with, contains, trigram) will attempt the search ignoring accented "
"characters.\n"
" \n"
" For the other options, you can enable search on any or all "
"fields and they will be combined together with an assumed 'OR'.\n"
" For example enabling 'Name' for Starts With, 'Name' and "
"'Description' for Partial Match and 'Ingredients' and 'Keywords' for Full "
"Search\n"
" and searching for 'apple' will generate a search that will "
"return recipes that have:\n"
" - A recipe name that starts with 'apple'\n"
" - OR a recipe name that contains 'apple'\n"
" - OR a recipe description that contains 'apple'\n"
" - OR a recipe that will have a full text search match ('apple' "
"or 'apples') in ingredients\n"
" - OR a recipe that will have a full text search match in "
"Keywords\n"
"\n"
" Combining too many fields in too many types of search can have a "
"negative impact on performance, create duplicate results or return "
"unexpected results.\n"
" For example, enabling fuzzy search or partial matches will "
"interfere with web search methods. \n"
" Searching for 'apple -pie' with fuzzy search and full text "
"search will return the recipe Apple Pie. Though it is not included in the "
"full text results, it does match the trigram results.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:95
#, fuzzy
#| msgid "Search"
msgid "Search Index"
msgstr "Vyhledat"
#: .\cookbook\templates\search_info.html:99
msgid ""
" \n"
" Trigram search and Full Text Search both rely on database "
"indexes to perform effectively. \n"
" You can rebuild the indexes on all fields in the Admin page for "
"Recipes and selecting all recipes and running 'rebuild index for selected "
"recipes'\n"
" You can also rebuild indexes at the command line by executing "
"the management command 'python manage.py rebuildindex'\n"
" "
msgstr ""
#: .\cookbook\templates\setup.html:6
msgid "Cookbook Setup"
msgstr "Nastavení kuchařky"
#: .\cookbook\templates\setup.html:14
msgid "Setup"
msgstr "Nastavení"
#: .\cookbook\templates\setup.html:15
msgid ""
"To start using this application you must first create a superuser account."
msgstr "Před použitím této aplikace musíte vytvořit účet superuživatele."
#: .\cookbook\templates\setup.html:20
msgid "Create Superuser account"
msgstr "Vytvořit účet Superuser"
#: .\cookbook\templates\socialaccount\authentication_error.html:7
#: .\cookbook\templates\socialaccount\authentication_error.html:23
#, fuzzy
#| msgid "Social Login"
msgid "Social Network Login Failure"
msgstr "Přihlásit přes soc. sítě"
#: .\cookbook\templates\socialaccount\authentication_error.html:25
msgid ""
"An error occurred while attempting to login via your social network account."
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:4
#: .\cookbook\templates\socialaccount\connections.html:7
msgid "Account Connections"
msgstr "Připojení účtu"
#: .\cookbook\templates\socialaccount\connections.html:10
msgid ""
"You can sign in to your account using any of the following third party\n"
" accounts:"
msgstr ""
"Můžete se přihlásit ke svému účtu pomocí účtu\n"
" třetích stran:"
#: .\cookbook\templates\socialaccount\connections.html:44
msgid ""
"You currently have no social network accounts connected to this account."
msgstr "V současnou dobu nemáte k účtu připojen žádný účet sociálních sítí."
#: .\cookbook\templates\socialaccount\connections.html:47
msgid "Add a 3rd Party Account"
msgstr "Přidat účet 3. strany"
#: .\cookbook\templates\socialaccount\login.html:5
#: .\cookbook\templates\socialaccount\signup.html:5
#, fuzzy
#| msgid "Sign Out"
msgid "Signup"
msgstr "Ohlásit se"
#: .\cookbook\templates\socialaccount\login.html:9
#, python-format
msgid "Connect %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:11
#, python-format
msgid "You are about to connect a new third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:13
#, python-format
msgid "Sign In Via %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:15
#, python-format
msgid "You are about to sign in using a third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:20
msgid "Continue"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:10
#, python-format
msgid ""
"You are about to use your\n"
" %(provider_name)s account to login to\n"
" %(site_name)s. As a final step, please complete the following form:"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:32
msgid "I accept the following"
msgstr ""
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:23
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:31
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:39
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:47
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:55
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:63
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:71
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:79
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:87
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:95
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:103
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:111
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:119
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:127
#, fuzzy
#| msgid "Sign In"
msgid "Sign in using"
msgstr "Přihlásit se"
#: .\cookbook\templates\space_overview.html:6
msgid "Overview"
msgstr ""
#: .\cookbook\templates\space_overview.html:13
msgid "Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:17
msgid ""
"Recipes, foods, shopping lists and more are organized in spaces of one or "
"more people."
msgstr ""
#: .\cookbook\templates\space_overview.html:18
msgid ""
"You can either be invited into an existing space or create your own one."
msgstr ""
#: .\cookbook\templates\space_overview.html:25
msgid "Your Spaces"
msgstr ""
#: .\cookbook\templates\space_overview.html:53
msgid "Owner"
msgstr ""
#: .\cookbook\templates\space_overview.html:73
#: .\cookbook\templates\space_overview.html:83
msgid "Join Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:76
msgid "Join an existing space."
msgstr ""
#: .\cookbook\templates\space_overview.html:78
msgid ""
"To join an existing space either enter your invite token or click on the "
"invite link the space owner send you."
msgstr ""
#: .\cookbook\templates\space_overview.html:91
#: .\cookbook\templates\space_overview.html:100
#, fuzzy
#| msgid "Create User"
msgid "Create Space"
msgstr "Vytvořit uživatele"
#: .\cookbook\templates\space_overview.html:94
msgid "Create your own recipe space."
msgstr ""
#: .\cookbook\templates\space_overview.html:96
msgid "Start your own recipe space and invite other users to it."
msgstr ""
#: .\cookbook\templates\system.html:23
msgid "System"
msgstr "Systém"
#: .\cookbook\templates\system.html:24
#, fuzzy
#| msgid ""
#| "\n"
#| " Django Recipes is an open source free software application. It "
#| "can be found on\n"
#| " GitHub.\n"
#| " Changelogs can be found here.\n"
#| " "
msgid ""
"\n"
" Tandoor Recipes is an open source free software application. It can "
"be found on\n"
" GitHub.\n"
" Changelogs can be found here.\n"
" "
msgstr ""
"\n"
" Django kuchařka je volně dostupné softwarová aplikace a lze ji "
"nalézt na\n"
" GitHub.\n"
" Changelog naleznete zde.\n"
" "
#: .\cookbook\templates\system.html:30
msgid "System Information"
msgstr "Systémové informace"
#: .\cookbook\templates\system.html:51
msgid ""
"\n"
" You need to execute version.py in your update "
"script to generate version information (done automatically in docker).\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:56
msgid "Plugins"
msgstr ""
#: .\cookbook\templates\system.html:67
msgid "Media Serving"
msgstr "Mediální služby"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:123 .\cookbook\templates\system.html:134
msgid "Warning"
msgstr "Varování"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:125 .\cookbook\templates\system.html:134
msgid "Ok"
msgstr "OK"
#: .\cookbook\templates\system.html:70
msgid ""
"Serving media files directly using gunicorn/python is not recommend!\n"
" Please follow the steps described\n"
" here to update\n"
" your installation.\n"
" "
msgstr ""
"Přímé předávání mediálních služeb pomocí gunicorn/python není doporučeno"
"b>!\n"
" Prosím postupujte podle\n"
" tohoto návodu a aktualizujte\n"
" svoji instalaci.\n"
" "
#: .\cookbook\templates\system.html:76 .\cookbook\templates\system.html:91
#: .\cookbook\templates\system.html:104 .\cookbook\templates\system.html:115
#: .\cookbook\views\views.py:168
msgid "Everything is fine!"
msgstr "Vše je v pořádku!"
#: .\cookbook\templates\system.html:80
msgid "Secret Key"
msgstr "Tajný klíč"
#: .\cookbook\templates\system.html:84
msgid ""
"\n"
" You do not have a SECRET_KEY configured in your "
".env file. Django defaulted to the\n"
" standard key\n"
" provided with the installation which is publicly know and "
"insecure! Please set\n"
" SECRET_KEY int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" Nemáte nakonfigurován SECRET_KEY ve Vašem ."
"env souboru. Django nyní pracuje s\n"
" výchozím klíčem\n"
" poskytnutým během instalace, který je veřejně známý a "
"nezabezpečený! Prosím nastavte\n"
" SECRET_KEY v konfiguračním souboru .env"
"code>.\n"
" "
#: .\cookbook\templates\system.html:94
msgid "Debug Mode"
msgstr "Režim ladění"
#: .\cookbook\templates\system.html:98
msgid ""
"\n"
" This application is still running in debug mode. This is most "
"likely not needed. Turn of debug mode by\n"
" setting\n"
" DEBUG=0 int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" Aplikace běží v režimu ladění, což není pravděpodobně nutné. "
"Režim ladění vypnete\n"
" nastavením\n"
" DEBUG=0 v konfiguračním souboru .env.\n"
" "
#: .\cookbook\templates\system.html:107
msgid "Allowed Hosts"
msgstr ""
#: .\cookbook\templates\system.html:111
msgid ""
"\n"
" Your allowed hosts are configured to allow every host. This "
"might be ok in some setups but should be avoided. Please see the docs about "
"this.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:118
msgid "Database"
msgstr "Databáze"
#: .\cookbook\templates\system.html:121
msgid "Info"
msgstr "Informace"
#: .\cookbook\templates\system.html:131 .\cookbook\templates\system.html:148
msgid "Migrations"
msgstr ""
#: .\cookbook\templates\system.html:137
msgid ""
"\n"
" Migrations should never fail!\n"
" Failed migrations will likely cause major parts of the app to "
"not function correctly.\n"
" If a migration fails make sure you are on the latest version and "
"if so please post the migration log and the overview below in a GitHub "
"issue.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "False"
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "True"
msgstr ""
#: .\cookbook\templates\system.html:268
msgid "Hide"
msgstr ""
#: .\cookbook\templates\system.html:271
#, fuzzy
#| msgid "Show help"
msgid "Show"
msgstr "Zobrazit nápovědu"
#: .\cookbook\templates\test2.html:6
msgid "Export Recipes"
msgstr "Exportovat recepty"
#: .\cookbook\templates\test2.html:14 .\cookbook\templates\test2.html:20
msgid "Export"
msgstr "Export"
#: .\cookbook\views\api.py:198 .\cookbook\views\api.py:326
#, fuzzy
#| msgid "Parameter filter_list incorrectly formatted"
msgid "Parameter updated_at incorrectly formatted"
msgstr "Parametr filter_list v nesprávném formátu"
#: .\cookbook\views\api.py:351 .\cookbook\views\api.py:484
#, python-brace-format
msgid "No {self.basename} with id {pk} exists"
msgstr ""
#: .\cookbook\views\api.py:355
msgid "Cannot merge with the same object!"
msgstr "Nelze sloučit se stejným objektem!"
#: .\cookbook\views\api.py:362
#, python-brace-format
msgid "No {self.basename} with id {target} exists"
msgstr ""
#: .\cookbook\views\api.py:367
#, fuzzy
#| msgid "Cannot merge with the same object!"
msgid "Cannot merge with child object!"
msgstr "Nelze sloučit se stejným objektem!"
#: .\cookbook\views\api.py:405
#, python-brace-format
msgid "{source.name} was merged successfully with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:411
#, python-brace-format
msgid "An error occurred attempting to merge {source.name} with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:493
#, python-brace-format
msgid "{child.name} was moved successfully to the root."
msgstr ""
#: .\cookbook\views\api.py:496 .\cookbook\views\api.py:514
msgid "An error occurred attempting to move "
msgstr ""
#: .\cookbook\views\api.py:499
msgid "Cannot move an object to itself!"
msgstr ""
#: .\cookbook\views\api.py:505
#, python-brace-format
msgid "No {self.basename} with id {parent} exists"
msgstr ""
#: .\cookbook\views\api.py:511
#, python-brace-format
msgid "{child.name} was moved successfully to parent {parent.name}"
msgstr ""
#: .\cookbook\views\api.py:992
#, python-brace-format
msgid "{obj.name} was removed from the shopping list."
msgstr ""
#: .\cookbook\views\api.py:997 .\cookbook\views\api.py:1627
#, python-brace-format
msgid "{obj.name} was added to the shopping list."
msgstr ""
#: .\cookbook\views\api.py:1239
msgid "Filter meal plans from date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1241
msgid "Filter meal plans to date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1244
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1404
msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1406
msgid "Query string matched (fuzzy) against object name."
msgstr ""
#: .\cookbook\views\api.py:1442
msgid ""
"Query string matched (fuzzy) against recipe name. In the future also "
"fulltext search."
msgstr ""
#: .\cookbook\views\api.py:1444
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr ""
#: .\cookbook\views\api.py:1445
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr ""
#: .\cookbook\views\api.py:1446
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1447
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1448
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1450
msgid "ID of food a recipe should have. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1451
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr ""
#: .\cookbook\views\api.py:1452
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1453
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr ""
#: .\cookbook\views\api.py:1454
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1456
msgid "ID of book a recipe should be in. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1457
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr ""
#: .\cookbook\views\api.py:1458
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1459
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr ""
#: .\cookbook\views\api.py:1460
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1462
msgid "ID of unit a recipe should have."
msgstr ""
#: .\cookbook\views\api.py:1464
msgid "Exact rating of recipe"
msgstr ""
#: .\cookbook\views\api.py:1465
msgid "Rating a recipe should have or greater."
msgstr ""
#: .\cookbook\views\api.py:1466
msgid "Rating a recipe should have or smaller."
msgstr ""
#: .\cookbook\views\api.py:1468
msgid "Filter recipes cooked X times."
msgstr ""
#: .\cookbook\views\api.py:1469
msgid "Filter recipes cooked X times or more."
msgstr ""
#: .\cookbook\views\api.py:1470
msgid "Filter recipes cooked X times or less."
msgstr ""
#: .\cookbook\views\api.py:1472
msgid "Filter recipes created on the given date."
msgstr ""
#: .\cookbook\views\api.py:1473
msgid "Filter recipes created on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1474
msgid "Filter recipes created on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1476 .\cookbook\views\api.py:1477
#: .\cookbook\views\api.py:1478
msgid "Filter recipes updated on the given date."
msgstr ""
#: .\cookbook\views\api.py:1480
msgid "Filter recipes last cooked on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1481
msgid "Filter recipes last cooked on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1483 .\cookbook\views\api.py:1484
msgid "Filter recipes lasts viewed on the given date."
msgstr ""
#: .\cookbook\views\api.py:1486
msgid "Filter recipes for ones created by the given user ID"
msgstr ""
#: .\cookbook\views\api.py:1487
msgid "If only internal recipes should be returned. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1488
msgid "Returns the results in randomized order. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1490
msgid ""
"Determines the order of the results. Options are: score,-score,name,-name,"
"lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-"
"created_at,lastviewed,-lastviewed"
msgstr ""
#: .\cookbook\views\api.py:1492
msgid "Returns new results first in search results. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1493
msgid ""
"Returns the given number of recently viewed recipes before search results "
"(if given)"
msgstr ""
#: .\cookbook\views\api.py:1494
msgid "ID of a custom filter. Returns all recipes matched by that filter."
msgstr ""
#: .\cookbook\views\api.py:1495
msgid "Filter recipes that can be made with OnHand food. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1773
msgid ""
"Return the PropertyTypes matching the property category. Repeat for "
"multiple."
msgstr ""
#: .\cookbook\views\api.py:1804 .\cookbook\views\api.py:1860
msgid "Returns only entries associated with the given mealplan id"
msgstr ""
#: .\cookbook\views\api.py:1858
msgid ""
"Returns only elements updated after the given timestamp in ISO 8601 format."
msgstr ""
#: .\cookbook\views\api.py:2031
msgid ""
"Return the Automations matching the automation type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2048
msgid ""
"Text field to store data that gets carried over to the UserSpace created "
"from the InviteLink"
msgstr ""
#: .\cookbook\views\api.py:2049
msgid "Only return InviteLinks that have not been used yet."
msgstr ""
#: .\cookbook\views\api.py:2076
msgid "Return the CustomFilters matching the model type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2176
msgid "Nothing to do."
msgstr ""
#: .\cookbook\views\api.py:2222
msgid "Invalid Url"
msgstr ""
#: .\cookbook\views\api.py:2228
msgid "Connection Refused."
msgstr ""
#: .\cookbook\views\api.py:2232
msgid "Bad URL Schema."
msgstr ""
#: .\cookbook\views\api.py:2257
#, fuzzy
#| msgid "The requested page could not be found."
msgid "No usable data could be found."
msgstr "Požadovaná stránka nebyla nalezena."
#: .\cookbook\views\api.py:2286 .\cookbook\views\api.py:2434
msgid "You must select an AI provider to perform your request."
msgstr ""
#: .\cookbook\views\api.py:2293 .\cookbook\views\api.py:2441
msgid ""
"You don't have any credits remaining to use AI or AI features are not "
"enabled for your space."
msgstr ""
#: .\cookbook\views\api.py:2499 .\cookbook\views\api.py:2667
msgid "File is above space limit"
msgstr ""
#: .\cookbook\views\api.py:2522 .\cookbook\views\api.py:2684
msgid "Importing is not implemented for this provider"
msgstr "Import není pro tohoto poskytovatele implementován!"
#: .\cookbook\views\api.py:2548
msgid ""
"The PDF Exporter is not enabled on this instance as it is still in an "
"experimental state."
msgstr ""
#: .\cookbook\views\api.py:2842
#, fuzzy
#| msgid "This feature is not available in the demo version!"
msgid "This feature is not yet available in the hosted version of tandoor!"
msgstr "Tato funkce není dostupná v demo verzi!"
#: .\cookbook\views\api.py:2863
msgid "Sync successful!"
msgstr "Synchronizace proběhla úspěšně!"
#: .\cookbook\views\api.py:2866
msgid "Error synchronizing with Storage"
msgstr "Chyba synchronizace s úložištěm"
#: .\cookbook\views\views.py:89
msgid "This feature is not available in the demo version!"
msgstr "Tato funkce není dostupná v demo verzi!"
#: .\cookbook\views\views.py:110
msgid ""
"You have successfully created your own recipe space. Start by adding some "
"recipes or invite other people to join you."
msgstr ""
#: .\cookbook\views\views.py:171
#, python-format
msgid "PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!"
msgstr ""
#: .\cookbook\views\views.py:174
#, python-format
msgid "You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended"
msgstr ""
#: .\cookbook\views\views.py:178
msgid "Unable to determine PostgreSQL version."
msgstr ""
#: .\cookbook\views\views.py:182
#, fuzzy
#| msgid ""
#| "\n"
#| " This application is not running with a Postgres database "
#| "backend. This is ok but not recommended as some\n"
#| " features only work with postgres databases.\n"
#| " "
msgid ""
"This application is not running with a Postgres database backend. This is ok "
"but not recommended as some features only work with postgres databases."
msgstr ""
"\n"
" Tato aplikace nepoužívá backend Postgree databáze. To je v "
"pořádku, ale není doporučeno s ohledem na to, že\n"
" funkce pracují pouze s Postgree databází.\n"
" "
#: .\cookbook\views\views.py:296
#, fuzzy
#| msgid ""
#| "The setup page can only be used to create the first user! If you have "
#| "forgotten your superuser credentials please consult the django "
#| "documentation on how to reset passwords."
msgid ""
"The setup page can only be used to create the first "
"user! If you have forgotten your superuser credentials "
"please consult the django documentation on how to reset passwords."
msgstr ""
"Stránka nastavení může být použita pouze pro vytvoření první uživatele! "
"Pokud jste zapoměl(a) heslo superuživatele, prozkoumejte django dokumentaci "
"a postupujte podle návodu pro reset hesla."
#: .\cookbook\views\views.py:304
msgid "Passwords dont match!"
msgstr "Hesla nesouhlasí!"
#: .\cookbook\views\views.py:312
msgid "User has been created, please login!"
msgstr "Uživatel byl vytvořen, prosím přihlaste se!"
#: .\cookbook\views\views.py:352
msgid ""
"Reporting share links is not enabled for this instance. Please notify the "
"page administrator to report problems."
msgstr ""
#: .\cookbook\views\views.py:357
msgid ""
"Recipe sharing link has been disabled! For additional information please "
"contact the page administrator."
msgstr ""
#: .\cookbook\views\views.py:383
msgid "Manage recipes, shopping list, meal plans and more."
msgstr ""
#: .\cookbook\views\views.py:397 .\cookbook\views\views.py:398
msgid "Plan"
msgstr "Plán"
#: .\cookbook\views\views.py:399
msgid "View your meal Plan"
msgstr ""
#: .\cookbook\views\views.py:418
#, fuzzy
#| msgid "Shopping Lists"
msgid "View your shopping lists"
msgstr "Nákupní seznamy"
#~ msgid ""
#~ "Both fields are optional. If none are given the username will be "
#~ "displayed instead"
#~ msgstr ""
#~ "Obě pole jsou nepovinná. Pokud není ani jedno vyplněno, bude zobrazeno "
#~ "uživatelské jméno."
#~ msgid "Name"
#~ msgstr "Název"
#~ msgid "Keywords"
#~ msgstr "Štítky"
#~ msgid "Preparation time in minutes"
#~ msgstr "Doba přípravy v minutách"
#~ msgid "Waiting time (cooking/baking) in minutes"
#~ msgstr "Doba čekání (vaření, pečení) v minutách"
#~ msgid "Path"
#~ msgstr "Cesta"
#~ msgid "Storage UID"
#~ msgstr "UID úložiště"
#~ msgid "Add your comment: "
#~ msgstr "Přidat vlastní komentář: "
#~ msgid "Leave empty for dropbox and enter app password for nextcloud."
#~ msgstr "Pro dropbox ponechejte nevyplňeno. Pro nextcloud vyplňte své heslo."
#~ msgid "Leave empty for nextcloud and enter api token for dropbox."
#~ msgstr "Pro nextcloud ponechejte nevyplněno. Pro dropbox zadejte API klíč."
#~ msgid ""
#~ "Leave empty for dropbox and enter only base url for nextcloud (/"
#~ "remote.php/webdav/ is added automatically)"
#~ msgstr ""
#~ "Pro dropbox ponechejte nevyplněné pole. Pro nextcloud použijte pouze "
#~ "základní url (/remote.php/webdav/ bude přidán automaticky)."
#~ msgid "Storage"
#~ msgstr "Úložiště"
#~ msgid "Search String"
#~ msgstr "Hledat řetězec"
#~ msgid "File ID"
#~ msgstr "ID souboru"
#, fuzzy
#~| msgid "Search"
#~ msgid "Search Method"
#~ msgstr "Vyhledat"
#, fuzzy
#~| msgid "Search"
#~ msgid "Fuzzy Search"
#~ msgstr "Vyhledat"
#, fuzzy
#~| msgid "Text"
#~ msgid "Full Text"
#~ msgstr "Text"
#~ msgid "Delete"
#~ msgstr "Smazat"
#~ msgid "Settings"
#~ msgstr "Nastavení"
#, fuzzy
#~| msgid "Password Reset"
#~ msgid "Password"
#~ msgstr "Resetovat heslo"
#, fuzzy
#~| msgid "Food"
#~ msgid "Foods"
#~ msgstr "Potravina"
#~ msgid "Units"
#~ msgstr "Jednotky"
#~ msgid "Supermarket"
#~ msgstr "Obchod"
#, fuzzy
#~| msgid "Supermarket"
#~ msgid "Supermarket Category"
#~ msgstr "Obchod"
#, fuzzy
#~| msgid "Information"
#~ msgid "Automations"
#~ msgstr "Informace"
#, fuzzy
#~| msgid "File ID"
#~ msgid "Files"
#~ msgstr "ID souboru"
#~ msgid "Batch Edit"
#~ msgstr "Hromadná úprava"
#~ msgid "History"
#~ msgstr "Historie"
#, fuzzy
#~| msgid "Ingredients"
#~ msgid "Ingredient Editor"
#~ msgstr "Ingredience"
#, fuzzy
#~| msgid "Account Connections"
#~ msgid "Unit Conversions"
#~ msgstr "Připojení účtu"
#~ msgid "Create"
#~ msgstr "Vytvořit"
#~ msgid "External Recipes"
#~ msgstr "Externí recepty"
#, fuzzy
#~| msgid "Settings"
#~ msgid "Space Settings"
#~ msgstr "Nastavení"
#, fuzzy
#~| msgid "External Recipes"
#~ msgid "External Connectors"
#~ msgstr "Externí recepty"
#~ msgid "Admin"
#~ msgstr "Administrátor"
#~ msgid "Markdown Guide"
#~ msgstr "Návod na značky"
#~ msgid "GitHub"
#~ msgstr "GitHub"
#~ msgid "API Browser"
#~ msgstr "Průzkumník API"
#~ msgid "Batch edit Category"
#~ msgstr "Hromadná úprava kategorií"
#~ msgid "Batch edit Recipes"
#~ msgstr "Dávková úprava receptu"
#~ msgid "Add the specified keywords to all recipes containing a word"
#~ msgstr "Přidat štítek ke všem receptům, které obsahují specifické slovo"
#~ msgid "Sync"
#~ msgstr "Synchronizace"
#~ msgid "Manage watched Folders"
#~ msgstr "Spravovat hlídané složky"
#~ msgid ""
#~ "On this Page you can manage all storage folder locations that should be "
#~ "monitored and synced."
#~ msgstr ""
#~ "Na této stránce můžete spravovat všechny složky úložiště, které by měly "
#~ "být monitorovány a synchronizovány."
#~ msgid "The path must be in the following format"
#~ msgstr "Cesta musí být v následujícím formátu"
#~ msgid "Save"
#~ msgstr "Uložit"
#~ msgid "Sync Now!"
#~ msgstr "Zahájit synchronizaci!"
#, fuzzy
#~| msgid "Shopping Recipes"
#~ msgid "Show Recipes"
#~ msgstr "Nákup pro recepty"
#, fuzzy
#~| msgid "Show Links"
#~ msgid "Show Log"
#~ msgstr "Zobrazit odkazy"
#~ msgid "Importing Recipes"
#~ msgstr "Importuji recepty"
#~ msgid ""
#~ "This can take a few minutes, depending on the number of recipes in sync, "
#~ "please wait."
#~ msgstr ""
#~ "Prosím čekejte, akce může trvat několik minut v závislosti na počtu "
#~ "synchronizovaných receptů."
#~ msgid "Recipe Books"
#~ msgstr "Kuchařky"
#~ msgid "Import new Recipe"
#~ msgstr "Import nového receptu"
#~ msgid "Edit Recipe"
#~ msgstr "Upravit recept"
#, python-format
#~ msgid "Are you sure you want to delete the %(title)s: %(object)s "
#~ msgstr "Opravdu si přejte smazat %(title)s: %(object)s "
#~ msgid "Edit"
#~ msgstr "Upravit"
#~ msgid "View"
#~ msgstr "Zobrazit"
#~ msgid "Delete original file"
#~ msgstr "Smazat originální soubor"
#~ msgid "List"
#~ msgstr "Seznam"
#~ msgid "Filter"
#~ msgstr "Filtr"
#~ msgid "Import all"
#~ msgstr "Importovat vše"
#~ msgid "New"
#~ msgstr "Nový"
#~ msgid "previous"
#~ msgstr "předchozí"
#~ msgid "next"
#~ msgstr "další"
#~ msgid "View Log"
#~ msgstr "Zobrazit záznamy"
#~ msgid "Cook Log"
#~ msgstr "Záznamy vaření"
#~ msgid "Import"
#~ msgstr "Importovat"
#~ msgid "Security Warning"
#~ msgstr "Bezpečnostní výstraha"
#~ msgid ""
#~ "\n"
#~ " The Password and Token field are stored as plain text"
#~ "b> inside the database.\n"
#~ " This is necessary because they are needed to make API requests, "
#~ "but it also increases the risk of\n"
#~ " someone stealing it.
\n"
#~ " To limit the possible damage tokens or accounts with limited "
#~ "access can be used.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Heslo a tajný klíč jsou v databázi uloženy jako "
#~ "obyčejný text.\n"
#~ " To je nezbytné pro vytvoření API požadavků, ale zvyšuje riziko "
#~ "zneužití\n"
#~ " při krádeži údajů.
\n"
#~ " Jako prevence zneužití doporučujeme omezit přístupová práva "
#~ "ostatním uživatelům.\n"
#~ " "
#~ msgid "You are currently offline!"
#~ msgstr "Právě jste offline!"
#~ msgid ""
#~ "The recipes listed below are available for offline viewing because you "
#~ "have recently viewed them. Keep in mind that data might be outdated."
#~ msgstr ""
#~ "Uvedené recepty jsou dostupné pro offline zobrazení, protože jste je "
#~ "nedávno zobrazil(a). Berte prosím v potaz, že data mohou být neaktuální."
#~ msgid "Comments"
#~ msgstr "Komentáře"
#~ msgid "by"
#~ msgstr "od"
#~ msgid "Comment"
#~ msgstr "Komentář"
#, fuzzy
#~| msgid "Social Login"
#~ msgid "Social"
#~ msgstr "Přihlásit přes soc. sítě"
#, fuzzy
#~| msgid "Description"
#~ msgid "Manage Subscription"
#~ msgstr "Popis"
#~ msgid "URL Import"
#~ msgstr "Import URL"
#, python-format
#~ msgid "Batch edit done. %(count)d recipe was updated."
#~ msgid_plural "Batch edit done. %(count)d Recipes where updated."
#~ msgstr[0] "Hromadná úprava dokončena. %(count)d recept byl aktualizován."
#~ msgstr[1] "Hromadná úprava dokončena. %(count)d receptů bylo aktualizováno."
#~ msgstr[2] "Hromadná úprava dokončena. %(count)d receptů bylo aktualizováno."
#~ msgstr[3] "Hromadná úprava dokončena. %(count)d recepty byly aktualizovány."
#~ msgid "Monitor"
#~ msgstr "Monitor"
#~ msgid "Storage Backend"
#~ msgstr "Backend úložiště"
#~ msgid ""
#~ "Could not delete this storage backend as it is used in at least one "
#~ "monitor."
#~ msgstr ""
#~ "Backend úložiště nelze smazat, protože je používán nejméně jedním "
#~ "monitorem."
#, fuzzy
#~| msgid "Storage Backend"
#~ msgid "Connectors Config Backend"
#~ msgstr "Backend úložiště"
#~ msgid "Invite Link"
#~ msgstr "Odkaz s pozvánkou"
#~ msgid "You cannot edit this storage!"
#~ msgstr "Toto úložiště nemůžete editovat!"
#~ msgid "Storage saved!"
#~ msgstr "Úložiště uloženo!"
#~ msgid "There was an error updating this storage backend!"
#~ msgstr "Vyskytla se chyba při pokusu o aktualizaci backendu úložiště!"
#, fuzzy
#~| msgid "Changes saved!"
#~ msgid "Config saved!"
#~ msgstr "Změny uloženy!"
#~ msgid "Changes saved!"
#~ msgstr "Změny uloženy!"
#~ msgid "Error saving changes!"
#~ msgstr "Chyba při ukládání změn!"
#~ msgid "Import Log"
#~ msgstr "Záznam importu"
#~ msgid "Discovery"
#~ msgstr "Průzkum"
#~ msgid "Shopping List"
#~ msgstr "Nákupní seznam"
#, fuzzy
#~| msgid "Storage Backend"
#~ msgid "Connector Config Backend"
#~ msgstr "Backend úložiště"
#~ msgid "Invite Links"
#~ msgstr "Odkazy s pozvánkou"
#, fuzzy
#~| msgid "Supermarket"
#~ msgid "Supermarkets"
#~ msgstr "Obchod"
#, fuzzy
#~| msgid "Shopping Recipes"
#~ msgid "Shopping Categories"
#~ msgstr "Nákup pro recepty"
#, fuzzy
#~| msgid "Filter"
#~ msgid "Custom Filters"
#~ msgstr "Filtr"
#~ msgid "Steps"
#~ msgstr "Kroky"
#, fuzzy
#~| msgid "This feature is not available in the demo version!"
#~ msgid "This feature is not enabled by the server admin!"
#~ msgstr "Tato funkce není dostupná v demo verzi!"
#~ msgid "Imported new recipe!"
#~ msgstr "Nový recept naimportován!"
#~ msgid "There was an error importing this recipe!"
#~ msgstr "Nastala chyba při importu tohoto receptu!"
#~ msgid "You do not have the required permissions to perform this action!"
#~ msgstr "Pro provedení této akce nemáte dostatečná práva!"
#~ msgid "Comment saved!"
#~ msgstr "Komentář uložen!"
#~ msgid "Malformed Invite Link supplied!"
#~ msgstr "Vložena neplatná URL pozvánky!"
#~ msgid "Invite Link not valid or already used!"
#~ msgstr "Neplatná URL pozvánky, nebo se již používá!"
#~ msgid ""
#~ "Color of the top navigation bar. Not all colors work with all themes, "
#~ "just try them out!"
#~ msgstr ""
#~ "Barva horního navigačního menu. Některé barvy neladí se všemi tématy a je "
#~ "třeba je vyzkoušet!"
#~ msgid ""
#~ "Default Unit to be used when inserting a new ingredient into a recipe."
#~ msgstr ""
#~ "Výchozí jednotka, která bude použita při vkládání nové ingredience k "
#~ "receptu."
#~ msgid ""
#~ "Enables support for fractions in ingredient amounts (e.g. convert "
#~ "decimals to fractions automatically)"
#~ msgstr ""
#~ "Povolit podporu zlomků u množství ingrediencí (desetinná čísla budou "
#~ "automaticky převedena na zlomky)"
#~ msgid ""
#~ "Users with whom newly created meal plan/shopping list entries should be "
#~ "shared by default."
#~ msgstr ""
#~ "Uživatelé, kteří nově vytvořili jídelníček, nebo nákupní seznam budou ve "
#~ "výchozím stavu sdíleny."
#~ msgid "Show recently viewed recipes on search page."
#~ msgstr "Zobrazit naposledy navštívené recepty na domovské stránce."
#~ msgid "Number of decimals to round ingredients."
#~ msgstr "Počet desetinných míst, na které se zaokrouhlí ingredience."
#~ msgid ""
#~ "If you want to be able to create and see comments underneath recipes."
#~ msgstr "Pokud chcete zobrazovat a vytvářet komentáře pod recepty."
#~ msgid ""
#~ "Setting to 0 will disable auto sync. When viewing a shopping list the "
#~ "list is updated every set seconds to sync changes someone else might have "
#~ "made. Useful when shopping with multiple people but might use a little "
#~ "bit of mobile data. If lower than instance limit it is reset when saving."
#~ msgstr ""
#~ "Nastavení na 0 zakáže automatickou synchronizaci. Při zobrazení nákupního "
#~ "seznamu je nákupní seznam aktualizován každou setinu sekundy. To zajistí, "
#~ "aby se v seznamu zobrazily vždy aktuální položky. Funkce je vhodná "
#~ "především při nákupu více lidí, ale může spotřebovávat více mobilních "
#~ "dat. Pokud je hodnota nižší, než nastavený limit, vyresetuje se při "
#~ "uložení."
#~ msgid "Makes the navbar stick to the top of the page."
#~ msgstr "Navigační menu bude přichyceno k hornímu okraji stránky."
#~ msgid "Number of servings"
#~ msgstr "Počet porcí"
#~ msgid ""
#~ "Include - [ ] in list for easier usage in markdown based "
#~ "documents."
#~ msgstr ""
#~ "Pro snažší použití a zobrazení značek v dokumentech uveďte - [ ]"
#~ "code>."
#~ msgid "New unit that other gets replaced by."
#~ msgstr "Nová jednotka, kterou bude jiná jednotka nahrazena."
#~ msgid "Old Unit"
#~ msgstr "Stará jednotka"
#~ msgid "Unit that should be replaced."
#~ msgstr "Jednotka, která by měla být nahrazena."
#~ msgid "New Food"
#~ msgstr "Nová potravina"
#~ msgid "New food that other gets replaced by."
#~ msgstr "Nová potravina, kterou bude jiná potravina nahrazena."
#~ msgid "Old Food"
#~ msgstr "Stará potravina"
#~ msgid "Food that should be replaced."
#~ msgstr "Potravina, která by měla být nahrazena."
#~ msgid "You must provide at least a recipe or a title."
#~ msgstr "Musíte poskytnout alespoň recept, nebo název."
#~ msgid "You can list default users to share recipes with in the settings."
#~ msgstr ""
#~ "Seznam, vychozích uživatelů pro sdílení receptů můžete upravit v "
#~ "nastavení."
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "Pro formátování tohoto pole lze použít značky. Více informací v dokumentaci."
#~ msgid ""
#~ "A username is not required, if left blank the new user can choose one."
#~ msgstr ""
#~ "Uživatelské jméno není vyžadováno. Pokud nebude vyplňeno, uživatel si ho "
#~ "zvolí sám."
#~ msgid "The requested site provided malformed data and cannot be read."
#~ msgstr "Požadovaná stránka poskytla neplatná data a nemůže být přečtena."
#~ msgid ""
#~ "The requested site does not provide any recognized data format to import "
#~ "the recipe from."
#~ msgstr ""
#~ "Požadovaná stránka neposkytuje žádný podporovaný datový formát pro import "
#~ "receptu."
#~ msgid "Small"
#~ msgstr "Malý"
#~ msgid "Large"
#~ msgstr "Velký"
#~ msgid "Time"
#~ msgstr "Čas"
#~ msgid "Link"
#~ msgstr "Odkaz"
#~ msgid "Utensils"
#~ msgstr "Kuchyně"
#~ msgid "Storage Data"
#~ msgstr "Úložiště"
#~ msgid "Storage Backends"
#~ msgstr "Backendy úložiště"
#~ msgid "Configure Sync"
#~ msgstr "Synchronizace"
#~ msgid "Discovered Recipes"
#~ msgstr "Nalezené recepty"
#~ msgid "Discovery Log"
#~ msgstr "Záznam průzkumu"
#~ msgid "Statistics"
#~ msgstr "Statistiky"
#~ msgid "Units & Ingredients"
#~ msgstr "Jednotky a ingredience"
#~ msgid "Logout"
#~ msgstr "Odhlásit"
#~ msgid "New Book"
#~ msgstr "Nová kuchařka"
#~ msgid "Toggle Recipes"
#~ msgstr "Přepnout recepty"
#~ msgid "There are no recipes in this book yet."
#~ msgstr "V této kuchařce zatím nejsou žádné recepty."
#~ msgid "Waiting Time"
#~ msgstr "Doba čekání"
#~ msgid "Servings Text"
#~ msgstr "Text porcí"
#~ msgid "Select Keywords"
#~ msgstr "Vybrat štítky"
#~ msgid "Delete Step"
#~ msgstr "Smazat krok"
#~ msgid "Step"
#~ msgstr "Krok"
#~ msgid "Show as header"
#~ msgstr "Použít jako hlavičku"
#~ msgid "Hide as header"
#~ msgstr "Nepoužívat jako hlavičku"
#~ msgid "Move Up"
#~ msgstr "Posunout nahoru"
#~ msgid "Move Down"
#~ msgstr "Posunout dolů"
#~ msgid "Step Name"
#~ msgstr "Popis kroku"
#~ msgid "Step Type"
#~ msgstr "Typ kroku"
#~ msgid "Step time in Minutes"
#~ msgstr "Délka kroku v minutách"
#~ msgid "Select Unit"
#~ msgstr "Vybrat jednotky"
#~ msgid "Select"
#~ msgstr "Vybrat"
#~ msgid "Select Food"
#~ msgstr "Vybrat jídlo"
#~ msgid "Delete Ingredient"
#~ msgstr "Smazat ingredienci"
#~ msgid "Make Ingredient"
#~ msgstr "Vytvořit ingredienci"
#~ msgid "Disable Amount"
#~ msgstr "Zakázat množství"
#~ msgid "Enable Amount"
#~ msgstr "Povolit množství"
#~ msgid "Copy Template Reference"
#~ msgstr "Kopírovat referenční šablonu"
#~ msgid "Save & View"
#~ msgstr "Uložit a zobrazit"
#~ msgid "Add Step"
#~ msgstr "Přidat krok"
#~ msgid "Add Nutrition"
#~ msgstr "Přidat výživu"
#~ msgid "Remove Nutrition"
#~ msgstr "Odebrat výživu"
#~ msgid "View Recipe"
#~ msgstr "Náhled receptu"
#~ msgid "Delete Recipe"
#~ msgstr "Smazat recept"
#~ msgid "Edit Ingredients"
#~ msgstr "Upravit ingredience"
#~ msgid ""
#~ "\n"
#~ " The following form can be used if, accidentally, two (or more) "
#~ "units or ingredients where created that should be\n"
#~ " the same.\n"
#~ " It merges two units or ingredients and updates all recipes using "
#~ "them.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Následující formulář slouží pro sloučení náhodně vytvořených "
#~ "jednotek, nebo ingrediencí, které by měly mýt \n"
#~ " shodné.\n"
#~ " Sloučí dvě a více položek (jednotek, nebo ingrediencí) a "
#~ "aktualizuje všechny recepty, které je používají.\n"
#~ " "
#~ msgid "Are you sure that you want to merge these two units?"
#~ msgstr "Opravdu chcete sloučit tyto dvě jednotky?"
#~ msgid "Are you sure that you want to merge these two ingredients?"
#~ msgstr "Opravdu si přejete sloučit tyto dvě ingredience?"
#~ msgid "Import Recipes"
#~ msgstr "Importovat recepty"
#~ msgid "Log Recipe Cooking"
#~ msgstr "Zaznamenat vaření receptu"
#~ msgid "All fields are optional and can be left empty."
#~ msgstr "Všechna pole jsou nepovinná a není nutné je vyplňovat."
#~ msgid "Rating"
#~ msgstr "Hodnocení"
#~ msgid "Close"
#~ msgstr "Zavřít"
#~ msgid "Open Recipe"
#~ msgstr "Otevřít recept"
#~ msgid "Website Import"
#~ msgstr "Nový z webu"
#~ msgid "New Entry"
#~ msgstr "Nová položka"
#~ msgid "Title"
#~ msgstr "Nadpis"
#~ msgid "Note (optional)"
#~ msgstr "Poznámka (volitelné)"
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "Pro formátování tohoto pole můžete použít značky. Více zde v "
#~ "dokumentaci."
#~ msgid "Serving Count"
#~ msgstr "Počet porcí"
#~ msgid "Create only note"
#~ msgstr "Vytvořit pouze poznámku"
#~ msgid "Shopping list currently empty"
#~ msgstr "Nákupní seznam je prázdný"
#~ msgid "Open Shopping List"
#~ msgstr "Otevřít nákupní seznam"
#~ msgid "Number of Days"
#~ msgstr "Počet dní"
#~ msgid "Weekday offset"
#~ msgstr "Posun dne v týdnu"
#~ msgid ""
#~ "Number of days starting from the first day of the week to offset the "
#~ "default view."
#~ msgstr ""
#~ "Počet dní, počínaje prvním dnem v týdnu, o který se posune výchozí "
#~ "zobrazení."
#~ msgid "Edit plan types"
#~ msgstr "Upravit typy plánu"
#~ msgid "Week iCal export"
#~ msgstr "Týdenní export do iCal"
#~ msgid "Created by"
#~ msgstr "Vytvořil(a)"
#~ msgid "Shared with"
#~ msgstr "Sdíleno s"
#~ msgid "Add to Shopping"
#~ msgstr "Přidat k nákupu"
#~ msgid "New meal type"
#~ msgstr "Nový typ jídla"
#~ msgid "Meal Plan Help"
#~ msgstr "Nápověda jídelníčku"
#~ msgid ""
#~ "\n"
#~ " The meal plan module allows planning of "
#~ "meals both with recipes and notes.
\n"
#~ " Simply select a recipe from the list of "
#~ "recently viewed recipes or search the one you\n"
#~ " want and drag it to the desired plan "
#~ "position. You can also add a note and a title and\n"
#~ " then drag the recipe to create a plan "
#~ "entry with a custom title and note. Creating only\n"
#~ " Notes is possible by dragging the create "
#~ "note box into the plan.
\n"
#~ " Click on a recipe in order to open the "
#~ "detailed view. There you can also add it to the\n"
#~ " shopping list. You can also add all "
#~ "recipes of a day to the shopping list by\n"
#~ " clicking the shopping cart at the top of "
#~ "the table.
\n"
#~ " Since a common use case is to plan meals "
#~ "together you can define\n"
#~ " users you want to share your plan with in "
#~ "the settings.\n"
#~ "
\n"
#~ " You can also edit the types of meals you "
#~ "want to plan. If you share your plan with\n"
#~ " someone with\n"
#~ " different meals, their meal types will "
#~ "appear in your list as well. To prevent\n"
#~ " duplicates (e.g. Other and Misc.)\n"
#~ " name your meal types the same as the "
#~ "users you share your meals with and they will be\n"
#~ " merged.
\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Modul jídelníčku umožňuje plánovat jídlo "
#~ "pomocí receptů i poznámek.
\n"
#~ " Jednoduše vyberte recept ze seznamu "
#~ "naposledy navštívených receptů, nebo ho vyhledejte\n"
#~ " s přetáhněte na požadovaný den v rozvrhu. "
#~ "Můžete také přidat poznámku s popiskem\n"
#~ " a poté přetáhnout recept pro vytvoření "
#~ "plánu s vlatními popisky. Vytvořením samotné poznámky\n"
#~ " je možné přetažením pole poznámky do "
#~ "rozvrhu.
\n"
#~ " Kliknutím na recept zobrazíte detailní "
#~ "náhled. Odtud lze také přidat položky\n"
#~ " do nákupního seznamu. Do nákupního "
#~ "seznamu můžete také přidat všechny recepty na daný den\n"
#~ " kliknutím na ikonu nákupního košíku na "
#~ "horní straně tabulky.
\n"
#~ " V běžném případě se jídelníček plánuje "
#~ "hromadně, proto můžete v nastavení definovat\n"
#~ " se kterými uživateli si přejete "
#~ "jídelníčky sdílet.\n"
#~ "
\n"
#~ " Můžete také upravovat typy jídel, které si "
#~ "přejete naplánovat. Pokud budete sdílet jídelníček \n"
#~ " s někým, kdo\n"
#~ " má přidána jiná jídla, jeho typy jídel se "
#~ "objeví i ve vašem seznamu. Pro předcházení\n"
#~ " duplicitám (např. Ostatní, Jiná)\n"
#~ " pojmenujte váš typ jídla stejně, jako "
#~ "uživatel se kterým své seznamy sdílíte. Tím budou seznamy\n"
#~ " sloučeny.
\n"
#~ " "
#~ msgid "Meal Plan View"
#~ msgstr "Zobrazení jídelníčku"
#~ msgid "Never cooked before."
#~ msgstr "Ještě nebylo uvařeno."
#~ msgid "Other meals on this day"
#~ msgstr "Ostatní jídla pro tento den"
#~ msgid "Recipe Image"
#~ msgstr "Obrázek receptu"
#~ msgid "Preparation time ca."
#~ msgstr "Doba přípravy cca."
#~ msgid "Waiting time ca."
#~ msgstr "Doba čekání cca."
#~ msgid "External"
#~ msgstr "Externí"
#~ msgid "Log Cooking"
#~ msgstr "Záznam vaření"
#~ msgid "Account"
#~ msgstr "Účet"
#~ msgid "Link social account"
#~ msgstr "Propojit účet soc. sítě"
#~ msgid "Language"
#~ msgstr "Jazyk"
#~ msgid "Style"
#~ msgstr "Styl"
#~ msgid ""
#~ "You can use both basic authentication and token based authentication to "
#~ "access the REST API."
#~ msgstr ""
#~ "Můžete použít jak základní ověření, tak tajný klíč založený na ověření "
#~ "přístupu k REST API."
#~ msgid ""
#~ "Use the token as an Authorization header prefixed by the word token as "
#~ "shown in the following examples:"
#~ msgstr ""
#~ "Použijte tajný klíč jako autorizační hlavičku definovanou slovním klíčem, "
#~ "jak je uvedeno v následujících příkladech:"
#~ msgid "or"
#~ msgstr "nebo"
#~ msgid "No recipes selected"
#~ msgstr "Nejsou vybrány žádné recepty"
#~ msgid "Entry Mode"
#~ msgstr "Režim vkládání"
#~ msgid "Add Entry"
#~ msgstr "Přidat položku"
#~ msgid "Amount"
#~ msgstr "Množství"
#~ msgid "Select Supermarket"
#~ msgstr "Vybrat obchod"
#~ msgid "Select User"
#~ msgstr "Vybrat uživatele"
#~ msgid "Finished"
#~ msgstr "Dokončeno"
#~ msgid "You are offline, shopping list might not syncronize."
#~ msgstr "Jste offline, nákupní seznam se nemusí synchronizovat."
#~ msgid "Copy/Export"
#~ msgstr "Kopírovat/Export"
#~ msgid "List Prefix"
#~ msgstr "Prefix seznamu"
#~ msgid "There was an error creating a resource!"
#~ msgstr "Nastala chyba při vytváření zdroje!"
#~ msgid "Stats"
#~ msgstr "Statistiky"
#~ msgid "Number of objects"
#~ msgstr "Počet objektů"
#~ msgid "Recipe Imports"
#~ msgstr "Importy receptů"
#~ msgid "Objects stats"
#~ msgstr "Statistiky objektů"
#~ msgid "Recipes without Keywords"
#~ msgstr "Recepty bez štítků"
#~ msgid "Internal Recipes"
#~ msgstr "Interní recepty"
#~ msgid "Backup & Restore"
#~ msgstr "Záloha a obnovení"
#~ msgid "Download Backup"
#~ msgstr "Stáhnout zálohu"
#~ msgid "Enter website URL"
#~ msgstr "Zadejte URL stránky"
#~ msgid "Recipe Name"
#~ msgstr "Název recceptu"
#~ msgid "Select one"
#~ msgstr "Vybrat štítek"
#~ msgid "All Keywords"
#~ msgstr "Všechny štítky"
#~ msgid "Import all keywords, not only the ones already existing."
#~ msgstr "Kromě existujících štítků importovat všechny štítky."
#~ msgid ""
#~ " Only websites containing ld+json or microdata information can currently\n"
#~ " be imported. Most big recipe pages "
#~ "support this. If you site cannot be imported but\n"
#~ " you think\n"
#~ " it probably has some kind of "
#~ "structured data feel free to post an example in the\n"
#~ " github issues."
#~ msgstr ""
#~ " Pouze stránky obsahující ld+json nebo mikrodata mohou být importovány.\n"
#~ " Většina hlavních poskytovatelů je "
#~ "podporuje. Pokud vaši stránku nelze importovat, ale máte za to\n"
#~ " že by měla jít,\n"
#~ " jedná se pravděpodobně o chybně "
#~ "strukturovaná data. Zašlete nám příklad webu na\n"
#~ " náš github."
#~ msgid "Google ld+json Info"
#~ msgstr "Google ld+json Info"
#~ msgid "GitHub Issues"
#~ msgstr "GitHub řešení problémů"
#~ msgid "Recipe Markup Specification"
#~ msgstr "Specifikace značek receptu"
#~ msgid "Preference for given user already exists"
#~ msgstr "Možnost pro daného uživatele již existuje"
#~ msgid ""
#~ "The requested page refused to provide any information (Status Code 403)."
#~ msgstr "Požadovaná stránka odmítla poskytnout informace (Kód chyby 403)."
#~ msgid "Recipe Book"
#~ msgstr "Kuchařka"
#~ msgid "Bookmarks"
#~ msgstr "Záložky"
#~ msgid "Units merged!"
#~ msgstr "Jednotky sloučeny!"
#~ msgid "Foods merged!"
#~ msgstr "Potraviny sloučeny!"
#~ msgid "Exporting is not implemented for this provider"
#~ msgstr "Export není pro tohoto poskytovatele implementován!"
#~ msgid "This recipe is already linked to the book!"
#~ msgstr "Tento recept už v kuchařce existuje!"
#~ msgid "Bookmark saved!"
#~ msgstr "Uloženo do kuchařky!"
================================================
FILE: cookbook/locale/da/LC_MESSAGES/django.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-22 20:15+0200\n"
"PO-Revision-Date: 2023-04-12 11:55+0000\n"
"Last-Translator: noxonad \n"
"Language-Team: Danish \n"
"Language: da\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.15\n"
#: .\cookbook\forms.py:50
msgid "Default"
msgstr "Standard"
#: .\cookbook\forms.py:77
msgid ""
"To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything."
msgstr ""
"For at undgå dubletter bliver opskrifter med det samme navn som eksisterende "
"opskrifter ignoreret. Ving den boks af for at tilføje alt."
#: .\cookbook\forms.py:108
msgid "Maximum number of users for this space reached."
msgstr "Maksimalt antal af brugere i dette rum er nået."
#: .\cookbook\forms.py:114
msgid "Email address already taken!"
msgstr "Emailadresse allerede brugt!"
#: .\cookbook\forms.py:121
msgid ""
"An email address is not required but if present the invite link will be sent "
"to the user."
msgstr ""
"En emailadresse er ikke et krav, men hvis den er indtastet vil et "
"invitationslink blive sendt til brugeren."
#: .\cookbook\forms.py:133
msgid "Name already taken."
msgstr "Navn allerede i brug."
#: .\cookbook\forms.py:144 .\cookbook\forms.py:158
msgid "Accept Terms and Privacy"
msgstr "Accepter vilkår og privatlivspolitik"
#: .\cookbook\helper\AllAuthCustomAdapter.py:41
msgid ""
"In order to prevent spam, the requested email was not send. Please wait a "
"few minutes and try again."
msgstr ""
"For at undgå spam blev mailen ikke sendt. Vent venligst et par minutter, og "
"prøv igen."
#: .\cookbook\helper\permission_helper.py:165
#: .\cookbook\helper\permission_helper.py:186 .\cookbook\views\views.py:138
msgid "You are not logged in and therefore cannot view this page!"
msgstr "Du er ikke logget ind, og kan derfor ikke åbne denne side!"
#: .\cookbook\helper\permission_helper.py:168
#: .\cookbook\helper\permission_helper.py:173
#: .\cookbook\helper\permission_helper.py:198
#: .\cookbook\helper\permission_helper.py:265
#: .\cookbook\helper\permission_helper.py:279
#: .\cookbook\helper\permission_helper.py:290
#: .\cookbook\helper\permission_helper.py:301
#: .\cookbook\helper\permission_helper.py:317
#: .\cookbook\helper\permission_helper.py:343
#: .\cookbook\helper\permission_helper.py:359
msgid "You do not have the required permissions to view this page!"
msgstr "Du har ikke rettigheder til at se denne side!"
#: .\cookbook\helper\permission_helper.py:191
#: .\cookbook\helper\permission_helper.py:214
#: .\cookbook\helper\permission_helper.py:236
#: .\cookbook\helper\permission_helper.py:251
msgid "You cannot interact with this object as it is not owned by you!"
msgstr ""
"Du kan ikke interagere med dette objekt, da det ikke er dig der ejer det!"
#: .\cookbook\helper\permission_helper.py:420
msgid "You have reached the maximum number of recipes for your space."
msgstr "Du har nået det maksimale antal opskrifter for dit rum."
#: .\cookbook\helper\permission_helper.py:432
msgid "You have more users than allowed in your space."
msgstr "Du har flere brugere end tilladt i dit rum."
#: .\cookbook\helper\recipe_url_import.py:319
#, fuzzy
#| msgid "Use fractions"
msgid "reverse rotation"
msgstr "Benyt brøker"
#: .\cookbook\helper\recipe_url_import.py:320
msgid "careful rotation"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:321
msgid "knead"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:322
msgid "thicken"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:323
msgid "warm up"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:324
msgid "ferment"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:325
#, fuzzy
#| msgid "Last cooked"
msgid "slow cook"
msgstr "Sidst tilberedt"
#: .\cookbook\helper\recipe_url_import.py:326
msgid "egg boiler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:327
msgid "kettle"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:328
msgid "blend"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:329
msgid "pre-clean"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:330
msgid "high temperature"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:331
msgid "rice cooker"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:332
msgid "caramelize"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:333
msgid "peeler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:334
msgid "slicer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:335
msgid "grater"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:336
msgid "spiralizer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:337
msgid "sous-vide"
msgstr ""
#: .\cookbook\helper\shopping_helper.py:150
msgid "You must supply a servings size"
msgstr "Du skal angive en portionstørrelse"
#: .\cookbook\helper\template_helper.py:97
#: .\cookbook\helper\template_helper.py:99
#: .\cookbook\helper\template_helper.py:101
msgid "Could not parse template code."
msgstr "Kunne ikke indsætte skabelonkode."
#: .\cookbook\integration\copymethat.py:44
#: .\cookbook\integration\melarecipes.py:37
msgid "Favorite"
msgstr "Favorit"
#: .\cookbook\integration\copymethat.py:50
msgid "I made this"
msgstr ""
#: .\cookbook\integration\integration.py:238
msgid ""
"Importer expected a .zip file. Did you choose the correct importer type for "
"your data ?"
msgstr ""
"Importværktøj forventede en .zip fil. Valgte du den korrekte "
"importeringstype til dit data?"
#: .\cookbook\integration\integration.py:241
msgid ""
"An unexpected error occurred during the import. Please make sure you have "
"uploaded a valid file."
msgstr ""
"En uventet fejl opstod under importen. Dobbeltjek at du har uploadet en "
"gyldig fil."
#: .\cookbook\integration\integration.py:246
msgid "The following recipes were ignored because they already existed:"
msgstr "De følgende opskrifter blev ignoreret fordi de allerede eksisterer:"
#: .\cookbook\integration\integration.py:250
#, python-format
msgid "Imported %s recipes."
msgstr "Importerede %s opskrifter."
#: .\cookbook\integration\mealie1.py:210
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "Calories"
msgstr ""
#: .\cookbook\integration\mealie1.py:211
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
msgid "Carbohydrates"
msgstr ""
#: .\cookbook\integration\mealie1.py:212
msgid "Cholesterol"
msgstr ""
#: .\cookbook\integration\mealie1.py:213
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
msgid "Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:214
msgid "Fiber"
msgstr ""
#: .\cookbook\integration\mealie1.py:215
#, fuzzy
#| msgid "Protected"
msgid "Protein"
msgstr "Beskyttet"
#: .\cookbook\integration\mealie1.py:216
msgid "Saturated Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:217
msgid "Sodium"
msgstr ""
#: .\cookbook\integration\mealie1.py:218
msgid "Sugar"
msgstr ""
#: .\cookbook\integration\mealie1.py:219
msgid "Trans Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:220
msgid "Unsaturated Fat"
msgstr ""
#: .\cookbook\integration\openeats.py:28
#, fuzzy
#| msgid "Recipe Home"
msgid "Recipe source:"
msgstr "Opskrift hjem"
#: .\cookbook\integration\paprika.py:49
msgid "Notes"
msgstr "Noter"
#: .\cookbook\integration\paprika.py:52
msgid "Nutritional Information"
msgstr "Næringsinformation"
#: .\cookbook\integration\paprika.py:56
msgid "Source"
msgstr "Kilde"
#: .\cookbook\integration\recettetek.py:55
#: .\cookbook\integration\recipekeeper.py:70
msgid "Imported from"
msgstr "Importeret fra"
#: .\cookbook\integration\saffron.py:23
msgid "Servings"
msgstr "Serveringer"
#: .\cookbook\integration\saffron.py:25
msgid "Waiting time"
msgstr "Ventetid"
#: .\cookbook\integration\saffron.py:27
msgid "Preparation Time"
msgstr "Forberedelsestid"
#: .\cookbook\integration\saffron.py:29 .\cookbook\templates\index.html:6
msgid "Cookbook"
msgstr "Kogebog"
#: .\cookbook\integration\saffron.py:31
msgid "Section"
msgstr "Sektion"
#: .\cookbook\management\commands\fix_duplicate_properties.py:15
msgid "Fixes foods with "
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:14
msgid "Rebuilds full text search index on Recipe"
msgstr "Rekonstruerer heltekst søgningsindeks på opskrift"
#: .\cookbook\management\commands\rebuildindex.py:18
msgid "Only Postgresql databases use full text search, no index to rebuild"
msgstr ""
"Kun Postgresql databaser bruger heltekst søgning, ingen indeks at "
"rekonstruere"
#: .\cookbook\management\commands\rebuildindex.py:29
msgid "Recipe index rebuild complete."
msgstr "Opskriftsindeks rekonstruering komplet."
#: .\cookbook\management\commands\rebuildindex.py:31
msgid "Recipe index rebuild failed."
msgstr "Opskriftsindeks rekonstruering fejlede."
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
msgid "Breakfast"
msgstr "Morgenmad"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:19
msgid "Lunch"
msgstr "Frokost"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:24
msgid "Dinner"
msgstr "Aftensmad"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:29 .\cookbook\models.py:971
msgid "Other"
msgstr "Andet"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "g"
msgstr ""
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "Proteins"
msgstr ""
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "kcal"
msgstr ""
#: .\cookbook\models.py:325
msgid ""
"Maximum file storage for space in MB. 0 for unlimited, -1 to disable file "
"upload."
msgstr ""
"Maksimal fil lager for rum i MB. 0 for ubegrænset, -1 for at slå upload af "
"filer fra."
#: .\cookbook\models.py:513
msgid "Search"
msgstr "Søg"
#: .\cookbook\models.py:514
msgid "Meal-Plan"
msgstr "Madplan"
#: .\cookbook\models.py:515
msgid "Books"
msgstr "Bøger"
#: .\cookbook\models.py:516 .\cookbook\views\views.py:416
#: .\cookbook\views\views.py:417
msgid "Shopping"
msgstr "Indkøb"
#: .\cookbook\models.py:967
#, fuzzy
#| msgid "Automations"
msgid "Nutrition"
msgstr "Automationer"
#: .\cookbook\models.py:968
#, fuzzy
#| msgid "Merge"
msgid "Allergen"
msgstr "Sammenflet"
#: .\cookbook\models.py:969
msgid "Price"
msgstr ""
#: .\cookbook\models.py:970
msgid "Goal"
msgstr ""
#: .\cookbook\models.py:1467 .\cookbook\templates\search_info.html:28
msgid "Simple"
msgstr "Simpel"
#: .\cookbook\models.py:1468 .\cookbook\templates\search_info.html:33
msgid "Phrase"
msgstr "Frase"
#: .\cookbook\models.py:1469 .\cookbook\templates\search_info.html:38
msgid "Web"
msgstr "Internet"
#: .\cookbook\models.py:1470 .\cookbook\templates\search_info.html:47
msgid "Raw"
msgstr "Rå"
#: .\cookbook\models.py:1525
msgid "Food Alias"
msgstr "Alternativt navn til mad"
#: .\cookbook\models.py:1526
msgid "Unit Alias"
msgstr "Alternativt navn til enhed"
#: .\cookbook\models.py:1527
msgid "Keyword Alias"
msgstr "Alternativt navn til nøgleord"
#: .\cookbook\models.py:1528
msgid "Description Replace"
msgstr ""
#: .\cookbook\models.py:1529
msgid "Instruction Replace"
msgstr ""
#: .\cookbook\models.py:1530
msgid "Never Unit"
msgstr ""
#: .\cookbook\models.py:1531
msgid "Transpose Words"
msgstr ""
#: .\cookbook\models.py:1532
#, fuzzy
#| msgid "Food Alias"
msgid "Food Replace"
msgstr "Alternativt navn til mad"
#: .\cookbook\models.py:1533
#, fuzzy
#| msgid "Unit Alias"
msgid "Unit Replace"
msgstr "Alternativt navn til enhed"
#: .\cookbook\models.py:1534
msgid "Name Replace"
msgstr ""
#: .\cookbook\models.py:1564
msgid "Recipe"
msgstr "Opskrift"
#: .\cookbook\models.py:1565
msgid "Food"
msgstr "Mad"
#: .\cookbook\models.py:1566
msgid "Keyword"
msgstr "Nøgleord"
#: .\cookbook\serializer.py:262
msgid "File uploads are not enabled for this Space."
msgstr "Fil upload er ikke slået til i dette rum."
#: .\cookbook\serializer.py:273
msgid "You have reached your file upload limit."
msgstr "Du har nået grænsen for upload af filer."
#: .\cookbook\serializer.py:281
msgid "The given file type is not allowed."
msgstr ""
#: .\cookbook\serializer.py:421 .\cookbook\views\views.py:94
#, fuzzy
#| msgid "You have reached the maximum number of recipes for your space."
msgid ""
"You have the reached the maximum amount of spaces that can be owned by you."
msgstr "Du har nået det maksimale antal opskrifter for dit rum."
#: .\cookbook\serializer.py:434
msgid "Space Name must be unique."
msgstr ""
#: .\cookbook\serializer.py:469
msgid "Cannot modify Space owner permission."
msgstr ""
#: .\cookbook\serializer.py:1596
msgid "Hello"
msgstr "Hej"
#: .\cookbook\serializer.py:1596
msgid "You have been invited by "
msgstr "Der er blevet inviteret af "
#: .\cookbook\serializer.py:1598
msgid " to join their Tandoor Recipes space "
msgstr " til at melde dig til deres Tandoor Opskrift rum "
#: .\cookbook\serializer.py:1600
msgid "Click the following link to activate your account: "
msgstr "Klik det følgende link for at aktivere din konto: "
#: .\cookbook\serializer.py:1602
msgid ""
"If the link does not work use the following code to manually join the space: "
msgstr ""
"Hvis linket ikke virker, kan du i stedet bruge følgende kode til manuelt at "
"melde dig til rummet: "
#: .\cookbook\serializer.py:1604
msgid "The invitation is valid until "
msgstr "Invitation er gyldig indtil "
#: .\cookbook\serializer.py:1606
msgid ""
"Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub "
msgstr ""
"Tandoor Recipes er et open source opskriftshåndteringsværktøj. Tjek det ud "
"på GitHub "
#: .\cookbook\serializer.py:1609
msgid "Tandoor Recipes Invite"
msgstr "Tandoor Recipes invitation"
#: .\cookbook\serializer.py:1813
msgid "Existing shopping list to update"
msgstr "Eksisterende indkøbslister som skal opdateres"
#: .\cookbook\serializer.py:1815
msgid ""
"List of ingredient IDs from the recipe to add, if not provided all "
"ingredients will be added."
msgstr ""
"Liste af ingrediens ID'er fra opskriften som skal tilføjes, hvis ikke "
"angivet vil alle ingredienser blive tilføjet."
#: .\cookbook\serializer.py:1817
msgid ""
"Providing a list_recipe ID and servings of 0 will delete that shopping list."
msgstr ""
"At angive et list_recipe ID og serveringer på 0 vil slette den indkøbsliste."
#: .\cookbook\serializer.py:1826
msgid "Amount of food to add to the shopping list"
msgstr "Mængde af man som skal tilføjes til indkøbslisten"
#: .\cookbook\serializer.py:1828
msgid "ID of unit to use for the shopping list"
msgstr "ID på enhed som skal bruges til indkøbslisten"
#: .\cookbook\serializer.py:1830
msgid "When set to true will delete all food from active shopping lists."
msgstr ""
"Når denne indstilling er slået til, vil alt mad fra den aktive indkøbsliste "
"blive slettet."
#: .\cookbook\templates\404.html:5
msgid "404 Error"
msgstr "404 fejl"
#: .\cookbook\templates\404.html:18
msgid "The page you are looking for could not be found."
msgstr "Siden du leder efter kunne ikke findes."
#: .\cookbook\templates\404.html:33
msgid "Take me Home"
msgstr "Tag mig hjem"
#: .\cookbook\templates\404.html:35
msgid "Report a Bug"
msgstr "Rapporter en fejl"
#: .\cookbook\templates\account\email.html:6
#: .\cookbook\templates\account\email.html:10
msgid "E-mail Addresses"
msgstr "Email adresser"
#: .\cookbook\templates\account\email.html:12
msgid "The following e-mail addresses are associated with your account:"
msgstr "De følgende email adresser er tilknyttet din konto:"
#: .\cookbook\templates\account\email.html:29
msgid "Verified"
msgstr "Bekræftet"
#: .\cookbook\templates\account\email.html:31
msgid "Unverified"
msgstr "Ikke bekræftet"
#: .\cookbook\templates\account\email.html:33
msgid "Primary"
msgstr "Primær"
#: .\cookbook\templates\account\email.html:40
msgid "Make Primary"
msgstr "Brug som primær"
#: .\cookbook\templates\account\email.html:42
msgid "Re-send Verification"
msgstr "Gensend bekræftelsesmail"
#: .\cookbook\templates\account\email.html:43
#: .\cookbook\templates\socialaccount\connections.html:36
msgid "Remove"
msgstr "Fjern"
#: .\cookbook\templates\account\email.html:51
msgid "Warning:"
msgstr "Advarsel:"
#: .\cookbook\templates\account\email.html:51
msgid ""
"You currently do not have any e-mail address set up. You should really add "
"an e-mail address so you can receive notifications, reset your password, etc."
msgstr ""
"Du har i øjeblikket ikke nogen email adresser tilknyttet. Du burde virkelig "
"få tilføjet en email adresse, så du kan modtager notifikationer, nulstiller "
"dit kodeord osv."
#: .\cookbook\templates\account\email.html:57
msgid "Add E-mail Address"
msgstr "Tilføj email adresse"
#: .\cookbook\templates\account\email.html:62
msgid "Add E-mail"
msgstr "Tilføj email"
#: .\cookbook\templates\account\email.html:72
msgid "Do you really want to remove the selected e-mail address?"
msgstr "Er du sikker på at du vil fjerne den valgte email adresse?"
#: .\cookbook\templates\account\email_confirm.html:6
#: .\cookbook\templates\account\email_confirm.html:10
msgid "Confirm E-mail Address"
msgstr "Bekræft email adresse"
#: .\cookbook\templates\account\email_confirm.html:16
#, python-format
msgid ""
"Please confirm that\n"
" %(email)s is an e-mail address "
"for user %(user_display)s\n"
" ."
msgstr ""
"Bekræft venligst at \n"
" %(email)s er en email adresse "
"for bruger %(user_display)s\n"
" ."
#: .\cookbook\templates\account\email_confirm.html:22
msgid "Confirm"
msgstr "Bekræft"
#: .\cookbook\templates\account\email_confirm.html:29
#, python-format
msgid ""
"This e-mail confirmation link expired or is invalid. Please\n"
" issue a new e-mail confirmation "
"request."
msgstr ""
"Dette email bekræftelseslink er udløbet eller ugyldigt. Venligst \n"
" udsend en ny email bekræftelsesmail"
"a>."
#: .\cookbook\templates\account\login.html:8
#: .\cookbook\templates\openid\login.html:8
msgid "Login"
msgstr "Log på"
#: .\cookbook\templates\account\login.html:15
#: .\cookbook\templates\account\login.html:31
#: .\cookbook\templates\account\password_reset.html:39
#: .\cookbook\templates\account\password_reset_done.html:31
#: .\cookbook\templates\account\signup.html:68
#: .\cookbook\templates\account\signup_closed.html:15
#: .\cookbook\templates\openid\login.html:15
#: .\cookbook\templates\openid\login.html:26
#: .\cookbook\templates\socialaccount\authentication_error.html:15
msgid "Sign In"
msgstr "Log ind"
#: .\cookbook\templates\account\login.html:34
#: .\cookbook\templates\account\password_reset.html:41
#: .\cookbook\templates\account\password_reset_done.html:33
#: .\cookbook\templates\socialaccount\signup.html:8
#: .\cookbook\templates\socialaccount\signup.html:56
msgid "Sign Up"
msgstr "Registrer"
#: .\cookbook\templates\account\login.html:38
msgid "Lost your password?"
msgstr "Mistet dit kodeord?"
#: .\cookbook\templates\account\login.html:39
#: .\cookbook\templates\account\password_reset.html:29
msgid "Reset My Password"
msgstr "Nulstil mit kodeord"
#: .\cookbook\templates\account\login.html:50
msgid "Social Login"
msgstr "Social login"
#: .\cookbook\templates\account\login.html:51
msgid "You can use any of the following providers to sign in."
msgstr "Du kan benytte en af de følgende udbydere til at logge ind med."
#: .\cookbook\templates\account\logout.html:5
#: .\cookbook\templates\account\logout.html:9
#: .\cookbook\templates\account\logout.html:18
msgid "Sign Out"
msgstr "Log ud"
#: .\cookbook\templates\account\logout.html:11
msgid "Are you sure you want to sign out?"
msgstr "Er du sikker på at du vil logge ud?"
#: .\cookbook\templates\account\password_change.html:6
#: .\cookbook\templates\account\password_change.html:10
#: .\cookbook\templates\account\password_change.html:15
#: .\cookbook\templates\account\password_reset_from_key.html:7
#: .\cookbook\templates\account\password_reset_from_key.html:13
#: .\cookbook\templates\account\password_reset_from_key_done.html:7
#: .\cookbook\templates\account\password_reset_from_key_done.html:13
msgid "Change Password"
msgstr "Skift kodeord"
#: .\cookbook\templates\account\password_change.html:16
msgid "Forgot Password?"
msgstr "Glemt kodeord?"
#: .\cookbook\templates\account\password_reset.html:7
#: .\cookbook\templates\account\password_reset.html:13
#: .\cookbook\templates\account\password_reset_done.html:7
#: .\cookbook\templates\account\password_reset_done.html:18
msgid "Password Reset"
msgstr "Nulstil kodeord"
#: .\cookbook\templates\account\password_reset.html:24
msgid ""
"Forgotten your password? Enter your e-mail address below, and we'll send you "
"an e-mail allowing you to reset it."
msgstr ""
"Glemt dit kodeord? Indtast din email adresse herunder, og så sender vi en "
"email så du kan nulstille det."
#: .\cookbook\templates\account\password_reset.html:32
msgid "Password reset is disabled on this instance."
msgstr "Nulstilling af kodeord er slået fra på denne instans."
#: .\cookbook\templates\account\password_reset_done.html:25
msgid ""
"We have sent you an e-mail. Please contact us if you do not receive it "
"within a few minutes."
msgstr ""
"Vi har sendt dig en email. Kontakt os venligst hvis du ikke modtager den "
"inden for få minutter."
#: .\cookbook\templates\account\password_reset_from_key.html:13
msgid "Bad Token"
msgstr "Ugyldigt token"
#: .\cookbook\templates\account\password_reset_from_key.html:25
#, python-format
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used.\n"
" Please request a new "
"password reset."
msgstr ""
"Linket til nulstillelse af kodeord var ugyldigt, det kan evt. allerede have "
"været brugt.\n"
" Spørg venligst om et nyt kodeordsnulstillelse."
#: .\cookbook\templates\account\password_reset_from_key.html:33
msgid "change password"
msgstr "skift kodeord"
#: .\cookbook\templates\account\password_reset_from_key.html:36
#: .\cookbook\templates\account\password_reset_from_key_done.html:19
msgid "Your password is now changed."
msgstr "Dit kodeord er nu ændret."
#: .\cookbook\templates\account\password_set.html:6
#: .\cookbook\templates\account\password_set.html:10
#: .\cookbook\templates\account\password_set.html:15
msgid "Set Password"
msgstr "Sæt kodeord"
#: .\cookbook\templates\account\signup.html:5
msgid "Register"
msgstr "Registrer"
#: .\cookbook\templates\account\signup.html:11
msgid "Create an Account"
msgstr "Opret en konto"
#: .\cookbook\templates\account\signup.html:41
msgid "I accept the follwoing"
msgstr "Jeg accepterer følgende"
#: .\cookbook\templates\account\signup.html:44
#: .\cookbook\templates\socialaccount\signup.html:35
msgid "Terms and Conditions"
msgstr "Vilkår og betingelser"
#: .\cookbook\templates\account\signup.html:47
#: .\cookbook\templates\socialaccount\signup.html:38
msgid "and"
msgstr "og"
#: .\cookbook\templates\account\signup.html:51
#: .\cookbook\templates\socialaccount\signup.html:42
msgid "Privacy Policy"
msgstr "Privatlivspolitik"
#: .\cookbook\templates\account\signup.html:64
msgid "Create User"
msgstr "Opret bruger"
#: .\cookbook\templates\account\signup.html:68
msgid "Already have an account?"
msgstr "Har du allerede en bruger?"
#: .\cookbook\templates\account\signup_closed.html:5
#: .\cookbook\templates\account\signup_closed.html:11
msgid "Sign Up Closed"
msgstr "Brugeroprettelse er lukket"
#: .\cookbook\templates\account\signup_closed.html:13
msgid "We are sorry, but the sign up is currently closed."
msgstr "Vi beklager meget, men brugeroprettelse er i øjeblikket lukket."
#: .\cookbook\templates\frontend\tandoor.html:15
#, fuzzy
#| msgid "Tandoor Recipes Invite"
msgid "Tandoor Recipe Manager"
msgstr "Tandoor Recipes invitation"
#: .\cookbook\templates\index.html:28
msgid "Search recipe ..."
msgstr "Søg efter opskrift ..."
#: .\cookbook\templates\index.html:43
msgid "New Recipe"
msgstr "Ny opskrift"
#: .\cookbook\templates\index.html:46
msgid "Import Recipe"
msgstr "Importer opskrift"
#: .\cookbook\templates\index.html:52
msgid "Advanced Search"
msgstr "Avanceret søgning"
#: .\cookbook\templates\index.html:56
msgid "Reset Search"
msgstr "Nulstil søgning"
#: .\cookbook\templates\index.html:84
msgid "Last viewed"
msgstr "Sidst åbnet"
#: .\cookbook\templates\index.html:86
msgid "Recipes"
msgstr "Opskrifter"
#: .\cookbook\templates\index.html:93
msgid "Log in to view recipes"
msgstr "Log ind for at se opskrifter"
#: .\cookbook\templates\markdown_info.html:5
#: .\cookbook\templates\markdown_info.html:13
msgid "Markdown Info"
msgstr "Markdown information"
#: .\cookbook\templates\markdown_info.html:14
msgid ""
"\n"
" Markdown is lightweight markup language that can be used to format "
"plain text easily.\n"
" This site uses the Python Markdown library to\n"
" convert your text into nice looking HTML. Its full markdown "
"documentation can be found\n"
" here.\n"
" An incomplete but most likely sufficient documentation can be found "
"below.\n"
" "
msgstr ""
"\n"
" Markdown er et letvægts sprog der kan blive brugt til at formatere "
"rå tekst nemt og hurtigt.\n"
" Denne side bruger Python Markdown bibliotektet til at\n"
" konvertere din tekst om til pæn HTML. Den fulde Markdown "
"dokumentation kan findes\n"
" her.\n"
" En ufuldstændig men sandsynligvis god nok dokumentation kan findes "
"herunder.\n"
" "
#: .\cookbook\templates\markdown_info.html:25
msgid "Headers"
msgstr "Rubrikker"
#: .\cookbook\templates\markdown_info.html:54
msgid "Formatting"
msgstr "Formatering"
#: .\cookbook\templates\markdown_info.html:56
#: .\cookbook\templates\markdown_info.html:72
msgid "Line breaks are inserted by adding two spaces after the end of a line"
msgstr ""
"Linjeskift indsættes ved at tilføje to mellemrum ved slutningen af en linje"
#: .\cookbook\templates\markdown_info.html:57
#: .\cookbook\templates\markdown_info.html:73
msgid "or by leaving a blank line in between."
msgstr "eller ved at have en blank linje imellem."
#: .\cookbook\templates\markdown_info.html:59
#: .\cookbook\templates\markdown_info.html:74
msgid "This text is bold"
msgstr "Denne tekst er fed"
#: .\cookbook\templates\markdown_info.html:60
#: .\cookbook\templates\markdown_info.html:75
msgid "This text is italic"
msgstr "Denne tekst er kursiv"
#: .\cookbook\templates\markdown_info.html:61
#: .\cookbook\templates\markdown_info.html:77
msgid "Blockquotes are also possible"
msgstr "Blokcitater er også muligt"
#: .\cookbook\templates\markdown_info.html:84
msgid "Lists"
msgstr "Lister"
#: .\cookbook\templates\markdown_info.html:85
msgid ""
"Lists can ordered or unordered. It is important to leave a blank line "
"before the list!"
msgstr ""
"Lister kan være med eller uden rækkefølge. Det er vigtigt at have en "
"blank linje inden listen!"
#: .\cookbook\templates\markdown_info.html:87
#: .\cookbook\templates\markdown_info.html:108
msgid "Ordered List"
msgstr "Liste med rækkefølge"
#: .\cookbook\templates\markdown_info.html:89
#: .\cookbook\templates\markdown_info.html:90
#: .\cookbook\templates\markdown_info.html:91
#: .\cookbook\templates\markdown_info.html:110
#: .\cookbook\templates\markdown_info.html:111
#: .\cookbook\templates\markdown_info.html:112
msgid "unordered list item"
msgstr "punkt på liste uden rækkefølge"
#: .\cookbook\templates\markdown_info.html:93
#: .\cookbook\templates\markdown_info.html:114
msgid "Unordered List"
msgstr "Liste uden rækkefølge"
#: .\cookbook\templates\markdown_info.html:95
#: .\cookbook\templates\markdown_info.html:96
#: .\cookbook\templates\markdown_info.html:97
#: .\cookbook\templates\markdown_info.html:116
#: .\cookbook\templates\markdown_info.html:117
#: .\cookbook\templates\markdown_info.html:118
msgid "ordered list item"
msgstr "punkt på liste med rækkefølge"
#: .\cookbook\templates\markdown_info.html:125
msgid "Images & Links"
msgstr "Billeder & Links"
#: .\cookbook\templates\markdown_info.html:126
msgid ""
"Links can be formatted with Markdown. This application also allows to paste "
"links directly into markdown fields without any formatting."
msgstr ""
"Links kan blive formateret med Markdown. Denne applikation gør det også "
"muligt at indsætte links direkte ind i Markdown felter uden at formatere dem."
#: .\cookbook\templates\markdown_info.html:132
#: .\cookbook\templates\markdown_info.html:145
msgid "This will become an image"
msgstr "Dette vil blive til et billede"
#: .\cookbook\templates\markdown_info.html:152
msgid "Tables"
msgstr "Tabeller"
#: .\cookbook\templates\markdown_info.html:153
msgid ""
"Markdown tables are hard to create by hand. It is recommended to use a table "
"editor like this one."
msgstr ""
"Markdown tabeller er ikke nemme at konstruere manuelt. Det anbefales at "
"bruge et tabelredigeringsværktøj såsom dette."
"a>"
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:171
#: .\cookbook\templates\markdown_info.html:177
msgid "Table"
msgstr "Tabel"
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:172
msgid "Header"
msgstr "Rubrik"
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:178
msgid "Cell"
msgstr "Celle"
#: .\cookbook\templates\no_groups_info.html:5
#: .\cookbook\templates\no_groups_info.html:12
msgid "No Permissions"
msgstr "Ingen rettigheder"
#: .\cookbook\templates\no_groups_info.html:17
msgid "You do not have any groups and therefor cannot use this application."
msgstr "Du har ingen grupper og kan derfor ikke benytte denne applikation."
#: .\cookbook\templates\no_groups_info.html:18
#: .\cookbook\templates\no_perm_info.html:15
msgid "Please contact your administrator."
msgstr "Kontakt venligst din adminstrator."
#: .\cookbook\templates\no_perm_info.html:5
#: .\cookbook\templates\no_perm_info.html:12
msgid "No Permission"
msgstr "Ingen rettighed"
#: .\cookbook\templates\no_perm_info.html:15
msgid ""
"You do not have the required permissions to view this page or perform this "
"action."
msgstr ""
"Du har ikke de påkrævede rettigheder til at vise denne side eller udføre "
"denne handling."
#: .\cookbook\templates\offline.html:5
msgid "Offline"
msgstr "Offline"
#: .\cookbook\templates\openid\login.html:27
#: .\cookbook\templates\socialaccount\authentication_error.html:27
msgid "Back"
msgstr "Tilbage"
#: .\cookbook\templates\rest_framework\api.html:5
msgid "Recipe Home"
msgstr "Opskrift hjem"
#: .\cookbook\templates\rest_framework\api.html:11
msgid "API Documentation"
msgstr "API dokumentation"
#: .\cookbook\templates\search_info.html:5
#: .\cookbook\templates\search_info.html:9
msgid "Search Settings"
msgstr "Søgningsindstillinger"
#: .\cookbook\templates\search_info.html:10
msgid ""
"\n"
" Creating the best search experience is complicated and weighs "
"heavily on your personal configuration. \n"
" Changing any of the search settings can have significant impact on "
"the speed and quality of the results.\n"
" Search Methods, Trigrams and Full Text Search configurations are "
"only available if you are using Postgres for your database.\n"
" "
msgstr ""
"\n"
" At lave den bedste søgeoplevelse er kompliceret og vægter meget på "
"din personlige konfiguration.\n"
" At ændre disse indstillinger kan have stor indflydelse på "
"hastigheden og kvaliteten af resultaterne.\n"
" Søgemetoder, Trigrams og heltekstsøgning konfigurationer er kun "
"tilgængelige hvis du bruger Postgres som database.\n"
" "
#: .\cookbook\templates\search_info.html:19
msgid "Search Methods"
msgstr "Søgemetoder"
#: .\cookbook\templates\search_info.html:23
msgid ""
" \n"
" Full text searches attempt to normalize the words provided to "
"match common variants. For example: 'forked', 'forking', 'forks' will all "
"normalize to 'fork'.\n"
" There are several methods available, described below, that will "
"control how the search behavior should react when multiple words are "
"searched.\n"
" Full technical details on how these operate can be viewed on Postgresql's website.\n"
" "
msgstr ""
" \n"
" Heltekstsøgning forsøger at normalisere de givne ord så de "
"matcher stammevarianter. F.eks: 'skeen', 'skeer' og 'sket' vil alt "
"normaliseres til 'ske'.\n"
" Der er flere metoder tilgængelige, beskrevet herunder, som vil "
"bestemme hvordan søgningen skal opfører sig når flere søgeord er angivet.\n"
" Alle tekniske detaljer om hvordan dette fungere kan ses på Postgresql's hjemmeside.\n"
" "
#: .\cookbook\templates\search_info.html:29
msgid ""
" \n"
" Simple searches ignore punctuation and common words such as "
"'the', 'a', 'and'. And will treat separate words as required.\n"
" Searching for 'apple or flour' will return any recipe that "
"includes both 'apple' and 'flour' anywhere in the fields that have been "
"selected for a full text search.\n"
" "
msgstr ""
" \n"
" Simple søgninger ignorerer tegnsætning og almindelige ord såsom "
"'en' og 'et', og behandler separate ord som nødvendige.\n"
" At søge efter 'æbler eller mel' vil returnere enhver opskrift som "
"indeholder både 'æble' og 'mel' hvor som helst i de felter som blev valgt "
"til heltekstsøgningen.\n"
" "
#: .\cookbook\templates\search_info.html:34
msgid ""
" \n"
" Phrase searches ignore punctuation, but will search for all of "
"the words in the exact order provided.\n"
" Searching for 'apple or flour' will only return a recipe that "
"includes the exact phrase 'apple or flour' in any of the fields that have "
"been selected for a full text search.\n"
" "
msgstr ""
" \n"
" Frasesøgning ignorerer tegnsætning, men søger efter alle orderne "
"i præcis den rækkefølge som de blev angivet i.\n"
" At søge efter 'æble eller mel' vil kun returnere en opskrift som "
"indeholder den præcise frase 'æble eller mel' i et eller flere af de felter "
"som blev valgt til heltekstsøgning.\n"
" "
#: .\cookbook\templates\search_info.html:39
msgid ""
" \n"
" Web searches simulate functionality found on many web search "
"sites supporting special syntax.\n"
" Placing quotes around several words will convert those words "
"into a phrase.\n"
" 'or' is recognized as searching for the word (or phrase) "
"immediately before 'or' OR the word (or phrase) directly after.\n"
" '-' is recognized as searching for recipes that do not include "
"the word (or phrase) that comes immediately after. \n"
" For example searching for 'apple pie' or cherry -butter will "
"return any recipe that includes the phrase 'apple pie' or the word "
"'cherry' \n"
" in any field included in the full text search but exclude any "
"recipe that has the word 'butter' in any field included.\n"
" "
msgstr ""
" \n"
" Internetsøgning simulerer funktionalitet fundet på mange "
"internetsøgningssider som understøtter speciel syntaks.\n"
" At sættet citationstegn rund om flere ord vil konvertere de ord ind "
"til en frase.\n"
" 'eller' genkendes som at søge efter ordet eller frasen inden "
"'eller', ELLER ordet eller frasen lige efter. \n"
" '-' genkdens som at søge efter opskrifter som ikke inkluderer ordet "
"eller frasen som kommer lige efter.\n"
" F.eks. hvis der søges efter 'gammeldags æblekage' eller fløde -skum "
"vil returnere opskrifter som indeholder frasen 'gammeldags æblekage' eller "
"ordet 'fløde' \n"
" i et eller flere af de valgte felter til heltekstsøgning, men "
"sortere de opskrifter som indeholder order 'skum' fra.\n"
" "
#: .\cookbook\templates\search_info.html:48
msgid ""
" \n"
" Raw search is similar to Web except will take puncuation "
"operators such as '|', '&' and '()'\n"
" "
msgstr ""
" \n"
" Rå søgning minder om internetsøgning, udover den tager "
"tegnsætning såsom '|', '&', og '()'\n"
" "
#: .\cookbook\templates\search_info.html:59
msgid ""
" \n"
" Another approach to searching that also requires Postgresql is "
"fuzzy search or trigram similarity. A trigram is a group of three "
"consecutive characters.\n"
" For example searching for 'apple' will create x trigrams 'app', "
"'ppl', 'ple' and will create a score of how closely words match the "
"generated trigrams.\n"
" One benefit of searching trigams is that a search for 'sandwich' "
"will find misspelled words such as 'sandwhich' that would be missed by other "
"methods.\n"
" "
msgstr ""
" \n"
" Endnu en tilgang til søgning som også kræver Postgresql er fuzzy "
"søgning eller trigram lighed. En trigram er en gruppe af tre karakterer "
"efter hinanden.\n"
" F.eks. hvis der søges efter 'æble' vil der oprettes x trigram'er "
"'æbl' og 'ble', og vil lave en score som fortæller om hvor tæt ord matcher "
"de generede trigram'er.\n"
" En fordel ved at søge med trigram er at hvis man f.eks. søger efter "
"'koldskåk', kan den rent faktisk finde ud af at finde 'koldskål' - hvilket "
"er noget andre søgemetoder ofte vil overse.\n"
" "
#: .\cookbook\templates\search_info.html:69
msgid "Search Fields"
msgstr "Søgefelter"
#: .\cookbook\templates\search_info.html:73
msgid ""
" \n"
" Unaccent is a special case in that it enables searching a field "
"'unaccented' for each search style attempting to ignore accented values. \n"
" For example when you enable unaccent for 'Name' any search "
"(starts with, contains, trigram) will attempt the search ignoring accented "
"characters.\n"
" \n"
" For the other options, you can enable search on any or all "
"fields and they will be combined together with an assumed 'OR'.\n"
" For example enabling 'Name' for Starts With, 'Name' and "
"'Description' for Partial Match and 'Ingredients' and 'Keywords' for Full "
"Search\n"
" and searching for 'apple' will generate a search that will "
"return recipes that have:\n"
" - A recipe name that starts with 'apple'\n"
" - OR a recipe name that contains 'apple'\n"
" - OR a recipe description that contains 'apple'\n"
" - OR a recipe that will have a full text search match ('apple' "
"or 'apples') in ingredients\n"
" - OR a recipe that will have a full text search match in "
"Keywords\n"
"\n"
" Combining too many fields in too many types of search can have a "
"negative impact on performance, create duplicate results or return "
"unexpected results.\n"
" For example, enabling fuzzy search or partial matches will "
"interfere with web search methods. \n"
" Searching for 'apple -pie' with fuzzy search and full text "
"search will return the recipe Apple Pie. Though it is not included in the "
"full text results, it does match the trigram results.\n"
" "
msgstr ""
" \n"
" Af-accent er et specieltilfælde, i det den gør det muligt at "
"søge et felt uden accenttegn for hver søgestil som forsøger at ignorere "
"accentegn.\n"
" F.eks. hvis du slår af-accent til for 'Navn' vil søgning (starter "
"med, indeholder, trigram) forsøge at ignorere karakterer med accentegn.\n"
" \n"
" For de andre indstillinger kan du så slå søgning til for ethvert "
"eller alle felter og de vil blive kombineret med et antaget 'ELLER'.\n"
" F.eks. hvis du for 'Navn' slår starter med til, for 'Navn' og "
"'Beskrivelse' slår delvis match til og for 'Ingredienser' og 'Nøgleord' slår "
"heltekstsøgning til\n"
" og søger efter 'æble' vil genere en søgning som vil returnere "
"opskrifter der har følgende:\n"
" - Et opskriftsnavn som starter med 'æble'\n"
" - ELLER et opskriftsnavn som indeholder 'æble'\n"
" - ELLER en opskriftsbeskrivelse som indeholder 'æble'\n"
" - ELLER en opskrift som vil have et heltekst match ('æble' eller "
"'æbler') i ingredienser\n"
" - ELLER en opskrift som vil have et heltekst match i nøgleord\n"
"\n"
" At kombinere for mange felter eller typer af søgning kan have en "
"negativ indflydelse på hastigheden, og kan producere dubletter eller "
"returnere uventede resultater.\n"
" F.eks. hvis fuzzy search søgning og heltekst søgning slås til, vil "
"der opstå interferens med internetsøgning. \n"
" At søge efter 'æble-tærte' med fuzzy søgning og heltekst søgning vil "
"returnere opskriften 'Æble Tærte'. Selvom den ikke er inkluderet i heltekst "
"resultaterne, matcher den stadig med trigram resultaterne.\n"
" "
#: .\cookbook\templates\search_info.html:95
msgid "Search Index"
msgstr "Søgeindeks"
#: .\cookbook\templates\search_info.html:99
msgid ""
" \n"
" Trigram search and Full Text Search both rely on database "
"indexes to perform effectively. \n"
" You can rebuild the indexes on all fields in the Admin page for "
"Recipes and selecting all recipes and running 'rebuild index for selected "
"recipes'\n"
" You can also rebuild indexes at the command line by executing "
"the management command 'python manage.py rebuildindex'\n"
" "
msgstr ""
" \n"
" Trigram søgning og heltekst søgning afhænger begge to på "
"database indeks for at kunne fungere effektivt. \n"
" Du kan rekonstruere indekserne på alle felter på adminstrationssiden "
"for opskrifter ved at vælge alle opskrifter og køre 'rekonstruer indeks for "
"valgte opskrifter'\n"
" Du kan også rekonstruere indekserne på kommandolinjen ved at "
"eksekvere kommandoen 'python mange.py rebuildindex'\n"
" "
#: .\cookbook\templates\setup.html:6
msgid "Cookbook Setup"
msgstr "Kogebog opsætning"
#: .\cookbook\templates\setup.html:14
msgid "Setup"
msgstr "Opsætning"
#: .\cookbook\templates\setup.html:15
msgid ""
"To start using this application you must first create a superuser account."
msgstr ""
"For at starte med at bruge denne applikation skal du først oprette en "
"superbruger konto."
#: .\cookbook\templates\setup.html:20
msgid "Create Superuser account"
msgstr "Opret superbruger konto"
#: .\cookbook\templates\socialaccount\authentication_error.html:7
#: .\cookbook\templates\socialaccount\authentication_error.html:23
msgid "Social Network Login Failure"
msgstr "Social netværk login fejl"
#: .\cookbook\templates\socialaccount\authentication_error.html:25
msgid ""
"An error occurred while attempting to login via your social network account."
msgstr ""
"Der opstod en fejl mens vi forsøgte at logge dig ind via din sociale "
"netværkskonto."
#: .\cookbook\templates\socialaccount\connections.html:4
#: .\cookbook\templates\socialaccount\connections.html:7
msgid "Account Connections"
msgstr "Kontoforbindelser"
#: .\cookbook\templates\socialaccount\connections.html:10
msgid ""
"You can sign in to your account using any of the following third party\n"
" accounts:"
msgstr ""
"Du kan logge ind på din konto med enhver af de følgende tredjepartskontoer:"
#: .\cookbook\templates\socialaccount\connections.html:44
msgid ""
"You currently have no social network accounts connected to this account."
msgstr ""
"Du har i øjeblikket ikke nogen sociale netværkskontoer forbundet med denne "
"konto."
#: .\cookbook\templates\socialaccount\connections.html:47
msgid "Add a 3rd Party Account"
msgstr "Tilføj en tredjepartskonto"
#: .\cookbook\templates\socialaccount\login.html:5
#: .\cookbook\templates\socialaccount\signup.html:5
msgid "Signup"
msgstr "Registrer"
#: .\cookbook\templates\socialaccount\login.html:9
#, python-format
msgid "Connect %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:11
#, python-format
msgid "You are about to connect a new third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:13
#, python-format
msgid "Sign In Via %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:15
#, python-format
msgid "You are about to sign in using a third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:20
msgid "Continue"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:10
#, python-format
msgid ""
"You are about to use your\n"
" %(provider_name)s account to login to\n"
" %(site_name)s. As a final step, please complete the following form:"
msgstr ""
"Du er ved at bruge din\n"
" %(provider_name)s konto til at logge ind på\n"
" %(site_name)s. Som et sidste trin, udfyld den følgende blanket:"
#: .\cookbook\templates\socialaccount\signup.html:32
#, fuzzy
#| msgid "I accept the follwoing"
msgid "I accept the following"
msgstr "Jeg accepterer følgende"
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:23
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:31
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:39
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:47
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:55
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:63
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:71
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:79
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:87
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:95
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:103
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:111
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:119
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:127
msgid "Sign in using"
msgstr "Log ind med"
#: .\cookbook\templates\space_overview.html:6
msgid "Overview"
msgstr ""
#: .\cookbook\templates\space_overview.html:13
#, fuzzy
#| msgid "Space:"
msgid "Space"
msgstr "Rum:"
#: .\cookbook\templates\space_overview.html:17
msgid ""
"Recipes, foods, shopping lists and more are organized in spaces of one or "
"more people."
msgstr ""
"Opskrifter, mad, indkøbslister og mere er organiseret i rum af en eller "
"flere personer."
#: .\cookbook\templates\space_overview.html:18
msgid ""
"You can either be invited into an existing space or create your own one."
msgstr ""
"Du kan enten blive inviteret ind i et eksisterende rum eller lave dit eget."
#: .\cookbook\templates\space_overview.html:25
#, fuzzy
#| msgid "No Space"
msgid "Your Spaces"
msgstr "Ingen plads"
#: .\cookbook\templates\space_overview.html:53
msgid "Owner"
msgstr ""
#: .\cookbook\templates\space_overview.html:73
#: .\cookbook\templates\space_overview.html:83
msgid "Join Space"
msgstr "Bliv medlem af rum"
#: .\cookbook\templates\space_overview.html:76
msgid "Join an existing space."
msgstr "Bliv medlem af et eksisterende rum."
#: .\cookbook\templates\space_overview.html:78
msgid ""
"To join an existing space either enter your invite token or click on the "
"invite link the space owner send you."
msgstr ""
"For at melde dig til et eksisterende rum, skal du enten indtaste dit "
"invitationstoken eller klikke på invitationslinket som ejeren af rummet har "
"sendt til dig."
#: .\cookbook\templates\space_overview.html:91
#: .\cookbook\templates\space_overview.html:100
msgid "Create Space"
msgstr "Opret rum"
#: .\cookbook\templates\space_overview.html:94
msgid "Create your own recipe space."
msgstr "Opret dit eget opskriftsrum."
#: .\cookbook\templates\space_overview.html:96
msgid "Start your own recipe space and invite other users to it."
msgstr "Opret dit eget opskriftsrum og inviter andre brugere til det."
#: .\cookbook\templates\system.html:23
msgid "System"
msgstr "System"
#: .\cookbook\templates\system.html:24
#, fuzzy
#| msgid ""
#| "\n"
#| " Django Recipes is an open source free software application. It "
#| "can be found on\n"
#| " GitHub.\n"
#| " Changelogs can be found here.\n"
#| " "
msgid ""
"\n"
" Tandoor Recipes is an open source free software application. It can "
"be found on\n"
" GitHub.\n"
" Changelogs can be found here.\n"
" "
msgstr ""
"\n"
" Tandoor Recipes er en open source gratis software applikation. Den "
"kan findes på\n"
" GitHub.\n"
" Ændringer kan findes her.\n"
" "
#: .\cookbook\templates\system.html:30
msgid "System Information"
msgstr "Systeminformation"
#: .\cookbook\templates\system.html:51
msgid ""
"\n"
" You need to execute version.py in your update "
"script to generate version information (done automatically in docker).\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:56
msgid "Plugins"
msgstr ""
#: .\cookbook\templates\system.html:67
msgid "Media Serving"
msgstr "Medie servering"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:123 .\cookbook\templates\system.html:134
msgid "Warning"
msgstr "Advarsel"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:125 .\cookbook\templates\system.html:134
msgid "Ok"
msgstr "Ok"
#: .\cookbook\templates\system.html:70
msgid ""
"Serving media files directly using gunicorn/python is not recommend!\n"
" Please follow the steps described\n"
" here to update\n"
" your installation.\n"
" "
msgstr ""
"At servere mediefiler direkte med gunicorn/python er ikke anbefalet!\n"
" Følg venligst trinnene beskrevet\n"
" her for at opdatere\n"
" din installation.\n"
" "
#: .\cookbook\templates\system.html:76 .\cookbook\templates\system.html:91
#: .\cookbook\templates\system.html:104 .\cookbook\templates\system.html:115
#: .\cookbook\views\views.py:168
msgid "Everything is fine!"
msgstr "Alt er fint!"
#: .\cookbook\templates\system.html:80
msgid "Secret Key"
msgstr "Hemmelig nøgle"
#: .\cookbook\templates\system.html:84
msgid ""
"\n"
" You do not have a SECRET_KEY configured in your "
".env file. Django defaulted to the\n"
" standard key\n"
" provided with the installation which is publicly know and "
"insecure! Please set\n"
" SECRET_KEY int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" Du har ikke en SECRET_KEY konfigueret in din ."
"env fil. Django valgte automatisk \n"
" standardnøglen\n"
" som er givet sammen med installationen, som er offentlig kendt og "
"usikker! Sæt venligst\n"
" SECRET_KEY i .env konfigurationsfilen.\n"
" "
#: .\cookbook\templates\system.html:94
msgid "Debug Mode"
msgstr "Fejlsøgningsmode"
#: .\cookbook\templates\system.html:98
msgid ""
"\n"
" This application is still running in debug mode. This is most "
"likely not needed. Turn of debug mode by\n"
" setting\n"
" DEBUG=0 int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" Denne applikation kører i fejlsøgningsmode. Dette er højst "
"sandsynligt ikke nødvendigt. Slå fejlsøgningsmode fra\n"
" ved at sætte\n"
" DEBUG= in the .env konfigurationsfil.\n"
" "
#: .\cookbook\templates\system.html:107
msgid "Allowed Hosts"
msgstr ""
#: .\cookbook\templates\system.html:111
msgid ""
"\n"
" Your allowed hosts are configured to allow every host. This "
"might be ok in some setups but should be avoided. Please see the docs about "
"this.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:118
msgid "Database"
msgstr "Database"
#: .\cookbook\templates\system.html:121
msgid "Info"
msgstr "Information"
#: .\cookbook\templates\system.html:131 .\cookbook\templates\system.html:148
#, fuzzy
#| msgid "Use fractions"
msgid "Migrations"
msgstr "Benyt brøker"
#: .\cookbook\templates\system.html:137
msgid ""
"\n"
" Migrations should never fail!\n"
" Failed migrations will likely cause major parts of the app to "
"not function correctly.\n"
" If a migration fails make sure you are on the latest version and "
"if so please post the migration log and the overview below in a GitHub "
"issue.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "False"
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "True"
msgstr ""
#: .\cookbook\templates\system.html:268
msgid "Hide"
msgstr ""
#: .\cookbook\templates\system.html:271
#, fuzzy
#| msgid "Show Log"
msgid "Show"
msgstr "Vis log"
#: .\cookbook\templates\test2.html:6
msgid "Export Recipes"
msgstr "Eksporter opskrifter"
#: .\cookbook\templates\test2.html:14 .\cookbook\templates\test2.html:20
msgid "Export"
msgstr "Eksporter"
#: .\cookbook\views\api.py:198 .\cookbook\views\api.py:326
msgid "Parameter updated_at incorrectly formatted"
msgstr "Parameter updated_at ikke formateret korrekt"
#: .\cookbook\views\api.py:351 .\cookbook\views\api.py:484
#, python-brace-format
msgid "No {self.basename} with id {pk} exists"
msgstr "Ingen {self.basename} med ID {pk} eksisterer"
#: .\cookbook\views\api.py:355
msgid "Cannot merge with the same object!"
msgstr "Kan ikke sammenflette med det samme objekt!"
#: .\cookbook\views\api.py:362
#, python-brace-format
msgid "No {self.basename} with id {target} exists"
msgstr "Ingen {self.basename} med ID {target} eksisterer"
#: .\cookbook\views\api.py:367
msgid "Cannot merge with child object!"
msgstr "Kan ikke sammenflette med et objekt som er et barn!"
#: .\cookbook\views\api.py:405
#, python-brace-format
msgid "{source.name} was merged successfully with {target.name}"
msgstr "{source.name} blev sammenflettet med {target.name}"
#: .\cookbook\views\api.py:411
#, python-brace-format
msgid "An error occurred attempting to merge {source.name} with {target.name}"
msgstr ""
"Der opstod en fejl under sammenfletningen af {source.name} med {target.name}"
#: .\cookbook\views\api.py:493
#, python-brace-format
msgid "{child.name} was moved successfully to the root."
msgstr "{child.name} blev flyttet til roden."
#: .\cookbook\views\api.py:496 .\cookbook\views\api.py:514
msgid "An error occurred attempting to move "
msgstr "Der skete en fejl under flytningen "
#: .\cookbook\views\api.py:499
msgid "Cannot move an object to itself!"
msgstr "Kan ikke flytte et objekt til sig selv!"
#: .\cookbook\views\api.py:505
#, python-brace-format
msgid "No {self.basename} with id {parent} exists"
msgstr "Ingen {self.basename} med ID {parent} eksisterer"
#: .\cookbook\views\api.py:511
#, python-brace-format
msgid "{child.name} was moved successfully to parent {parent.name}"
msgstr "{child.name} blev flyttet til forælder {parent.name}"
#: .\cookbook\views\api.py:992
#, python-brace-format
msgid "{obj.name} was removed from the shopping list."
msgstr "{obj.name} blev fjernet fra indkøbslisten."
#: .\cookbook\views\api.py:997 .\cookbook\views\api.py:1627
#, python-brace-format
msgid "{obj.name} was added to the shopping list."
msgstr "{obj.name} blev tilføjet til indkøbslisten."
#: .\cookbook\views\api.py:1239
msgid "Filter meal plans from date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1241
msgid "Filter meal plans to date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1244
#, fuzzy
#| msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
msgstr "ID på den opskrift som et trin er del af. For flere, gentag parameter."
#: .\cookbook\views\api.py:1404
msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgstr "ID på den opskrift som et trin er del af. For flere, gentag parameter."
#: .\cookbook\views\api.py:1406
msgid "Query string matched (fuzzy) against object name."
msgstr "Søgningen matchede (fuzzy) mod objektets navn."
#: .\cookbook\views\api.py:1442
msgid ""
"Query string matched (fuzzy) against recipe name. In the future also "
"fulltext search."
msgstr ""
"Søgningen matchede (fuzzy) mod opskriftens navn. I fremtiden også heltekst "
"søgning."
#: .\cookbook\views\api.py:1444
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr ""
"ID på nøgeord som opskriften skal have. For flere, gentag parameter. "
"Sammenligneligt med keywords_or"
#: .\cookbook\views\api.py:1445
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr ""
"Nøgleord ID'er, gentag for flere. Returnerer opskrifter med bare et af "
"nøgleorderne"
#: .\cookbook\views\api.py:1446
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr ""
"Nøgleord ID'er, gentag for flere. Returnerer opskrifter med alle "
"nøgleorderne."
#: .\cookbook\views\api.py:1447
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr ""
"Nøgleord ID'er, gentag for flere. Ekskluderer opskrifter med bare et af "
"nøgleorderne."
#: .\cookbook\views\api.py:1448
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr ""
"Nøgleord ID'er, gentag for flere. Ekskluderer opskrifter med alle "
"nøgleorderne."
#: .\cookbook\views\api.py:1450
msgid "ID of food a recipe should have. For multiple repeat parameter."
msgstr "ID på mad en opskrift skal have. For flere, gentag parameter."
#: .\cookbook\views\api.py:1451
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr ""
"Mad ID'er, gentag for flere. Returnerer mad med bare et af de angivne mad"
#: .\cookbook\views\api.py:1452
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr ""
"Mad ID'er, gentag for flere. Returnerer opskrifter med alt det angivne mad."
#: .\cookbook\views\api.py:1453
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr ""
"Mad ID'er, gentag for flere. Eksluderer opskrifter med bare et af det "
"angivne mad."
#: .\cookbook\views\api.py:1454
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr ""
"Mad ID'er, gentag for flere. Eksluderer opskrifter med alt det angivne mad."
#: .\cookbook\views\api.py:1456
msgid "ID of book a recipe should be in. For multiple repeat parameter."
msgstr "ID på bog en opskrift skal være i. For flere, gentag parameter."
#: .\cookbook\views\api.py:1457
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr ""
"Bog ID'er, gentag for flere. Returnerer opskrifter med bare en af bøgerne"
#: .\cookbook\views\api.py:1458
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr "Bog ID'er, gentag for flere. Returnerer opskrifter med alle bøgerne."
#: .\cookbook\views\api.py:1459
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr ""
"Bog ID'er, gentag for flere. Eksluderer opskrifter med bare en af bøgerne."
#: .\cookbook\views\api.py:1460
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr "Bog ID'er, gentag for flere. Ekskluderer opskrifter med alle bøgerne."
#: .\cookbook\views\api.py:1462
msgid "ID of unit a recipe should have."
msgstr "ID på enhed en opskrift skal have."
#: .\cookbook\views\api.py:1464
msgid "Exact rating of recipe"
msgstr ""
#: .\cookbook\views\api.py:1465
#, fuzzy
#| msgid "ID of unit a recipe should have."
msgid "Rating a recipe should have or greater."
msgstr "ID på enhed en opskrift skal have."
#: .\cookbook\views\api.py:1466
#, fuzzy
#| msgid "ID of unit a recipe should have."
msgid "Rating a recipe should have or smaller."
msgstr "ID på enhed en opskrift skal have."
#: .\cookbook\views\api.py:1468
msgid "Filter recipes cooked X times."
msgstr ""
#: .\cookbook\views\api.py:1469
#, fuzzy
#| msgid ""
#| "Filter recipes cooked X times or more. Negative values returns cooked "
#| "less than X times"
msgid "Filter recipes cooked X times or more."
msgstr ""
"Filtrer opskrifter tilberedt X gange eller flere. Negative værdier "
"returnerer tilberedt mindre end X gange"
#: .\cookbook\views\api.py:1470
msgid "Filter recipes cooked X times or less."
msgstr ""
#: .\cookbook\views\api.py:1472
#, fuzzy
#| msgid ""
#| "Filter recipes created on or after YYYY-MM-DD. Prepending - filters on or "
#| "before date."
msgid "Filter recipes created on the given date."
msgstr ""
"Filtrer opskrifter oprettet på eller efter d. YYYY-MM-DD. Hvis datoen "
"starter med '-', filtrerer den i stedet på eller før dato."
#: .\cookbook\views\api.py:1473
#, fuzzy
#| msgid ""
#| "Filter recipes created on or after YYYY-MM-DD. Prepending - filters on or "
#| "before date."
msgid "Filter recipes created on the given date or after."
msgstr ""
"Filtrer opskrifter oprettet på eller efter d. YYYY-MM-DD. Hvis datoen "
"starter med '-', filtrerer den i stedet på eller før dato."
#: .\cookbook\views\api.py:1474
#, fuzzy
#| msgid ""
#| "Filter recipes created on or after YYYY-MM-DD. Prepending - filters on or "
#| "before date."
msgid "Filter recipes created on the given date or before."
msgstr ""
"Filtrer opskrifter oprettet på eller efter d. YYYY-MM-DD. Hvis datoen "
"starter med '-', filtrerer den i stedet på eller før dato."
#: .\cookbook\views\api.py:1476 .\cookbook\views\api.py:1477
#: .\cookbook\views\api.py:1478
#, fuzzy
#| msgid ""
#| "Filter recipes updated on or after YYYY-MM-DD. Prepending - filters on or "
#| "before date."
msgid "Filter recipes updated on the given date."
msgstr ""
"Filtrer opskrifter opdateret på eller efter d. YYYY-MM-DD. Hvis datoen "
"starter med '-', filtrerer den i stedet på eller før dato."
#: .\cookbook\views\api.py:1480
#, fuzzy
#| msgid ""
#| "Filter recipes last cooked on or after YYYY-MM-DD. Prepending - filters "
#| "on or before date."
msgid "Filter recipes last cooked on the given date or after."
msgstr ""
"Filtrer opskrifter sidst tilberedt på eller efter d. YYYY-MM-DD. Hvis datoen "
"starter med '-', filtrerer den i stedet på eller før dato."
#: .\cookbook\views\api.py:1481
#, fuzzy
#| msgid ""
#| "Filter recipes last cooked on or after YYYY-MM-DD. Prepending - filters "
#| "on or before date."
msgid "Filter recipes last cooked on the given date or before."
msgstr ""
"Filtrer opskrifter sidst tilberedt på eller efter d. YYYY-MM-DD. Hvis datoen "
"starter med '-', filtrerer den i stedet på eller før dato."
#: .\cookbook\views\api.py:1483 .\cookbook\views\api.py:1484
#, fuzzy
#| msgid ""
#| "Filter recipes lasts viewed on or after YYYY-MM-DD. Prepending - filters "
#| "on or before date."
msgid "Filter recipes lasts viewed on the given date."
msgstr ""
"Filtrer opskrifter sidst åbnet på eller efter d. YYYY-MM-DD. Hvis datoen "
"starter med '-', filtrerer den i stedet på eller før dato."
#: .\cookbook\views\api.py:1486
msgid "Filter recipes for ones created by the given user ID"
msgstr ""
#: .\cookbook\views\api.py:1487
msgid "If only internal recipes should be returned. [true/false]"
msgstr "Om kun interne opskrifter skal returneres. [true/false]"
#: .\cookbook\views\api.py:1488
msgid "Returns the results in randomized order. [true/false]"
msgstr "Returnerer resultaterne i tilfældig rækkefølge. [true/false]"
#: .\cookbook\views\api.py:1490
msgid ""
"Determines the order of the results. Options are: score,-score,name,-name,"
"lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-"
"created_at,lastviewed,-lastviewed"
msgstr ""
#: .\cookbook\views\api.py:1492
msgid "Returns new results first in search results. [true/false]"
msgstr ""
"Returnerer nye resultater først i søgeresultaterne. [true/false]"
#: .\cookbook\views\api.py:1493
msgid ""
"Returns the given number of recently viewed recipes before search results "
"(if given)"
msgstr ""
#: .\cookbook\views\api.py:1494
msgid "ID of a custom filter. Returns all recipes matched by that filter."
msgstr ""
#: .\cookbook\views\api.py:1495
msgid "Filter recipes that can be made with OnHand food. [true/false]"
msgstr ""
"Filtrer opskrifter der kan laves med tilgængeligt mad. [true/false]"
#: .\cookbook\views\api.py:1773
msgid ""
"Return the PropertyTypes matching the property category. Repeat for "
"multiple."
msgstr ""
#: .\cookbook\views\api.py:1804 .\cookbook\views\api.py:1860
msgid "Returns only entries associated with the given mealplan id"
msgstr ""
#: .\cookbook\views\api.py:1858
msgid ""
"Returns only elements updated after the given timestamp in ISO 8601 format."
msgstr ""
#: .\cookbook\views\api.py:2031
#, fuzzy
#| msgid ""
#| "Returns the shopping list entry with a primary key of id. Multiple "
#| "values allowed."
msgid ""
"Return the Automations matching the automation type. Repeat for multiple."
msgstr ""
"Returnerer indkøbslistepunktet med primær nøgle på ID. Flere værdier tilladt."
#: .\cookbook\views\api.py:2048
msgid ""
"Text field to store data that gets carried over to the UserSpace created "
"from the InviteLink"
msgstr ""
#: .\cookbook\views\api.py:2049
msgid "Only return InviteLinks that have not been used yet."
msgstr ""
#: .\cookbook\views\api.py:2076
#, fuzzy
#| msgid ""
#| "Returns the shopping list entry with a primary key of id. Multiple "
#| "values allowed."
msgid "Return the CustomFilters matching the model type. Repeat for multiple."
msgstr ""
"Returnerer indkøbslistepunktet med primær nøgle på ID. Flere værdier tilladt."
#: .\cookbook\views\api.py:2176
msgid "Nothing to do."
msgstr "Ikke noget at gøre."
#: .\cookbook\views\api.py:2222
msgid "Invalid Url"
msgstr ""
#: .\cookbook\views\api.py:2228
msgid "Connection Refused."
msgstr "Forbindelse nægtet."
#: .\cookbook\views\api.py:2232
msgid "Bad URL Schema."
msgstr "Ugyldigt link."
#: .\cookbook\views\api.py:2257
msgid "No usable data could be found."
msgstr "Intet brugbart data kunne findes."
#: .\cookbook\views\api.py:2286 .\cookbook\views\api.py:2434
msgid "You must select an AI provider to perform your request."
msgstr ""
#: .\cookbook\views\api.py:2293 .\cookbook\views\api.py:2441
msgid ""
"You don't have any credits remaining to use AI or AI features are not "
"enabled for your space."
msgstr ""
#: .\cookbook\views\api.py:2499 .\cookbook\views\api.py:2667
msgid "File is above space limit"
msgstr ""
#: .\cookbook\views\api.py:2522 .\cookbook\views\api.py:2684
msgid "Importing is not implemented for this provider"
msgstr "Importering er ikke implementeret for denne udbyder"
#: .\cookbook\views\api.py:2548
msgid ""
"The PDF Exporter is not enabled on this instance as it is still in an "
"experimental state."
msgstr ""
"PDF eksporteringsværktøjet er ikke slået til for denne instans, og er stadig "
"i et eksperimentelt stadie."
#: .\cookbook\views\api.py:2842
msgid "This feature is not yet available in the hosted version of tandoor!"
msgstr ""
"Denne funktion er endnu ikke tilgængelig i den hostede version af Tandoor!"
#: .\cookbook\views\api.py:2863
msgid "Sync successful!"
msgstr "Synkronisering var en succes!"
#: .\cookbook\views\api.py:2866
msgid "Error synchronizing with Storage"
msgstr "Der skete en fejl under synkroniseringen med lageret"
#: .\cookbook\views\views.py:89
msgid "This feature is not available in the demo version!"
msgstr "Denne funktion er ikke tilgængelig i demoversionen!"
#: .\cookbook\views\views.py:110
msgid ""
"You have successfully created your own recipe space. Start by adding some "
"recipes or invite other people to join you."
msgstr ""
"Du har nu oprettet dit eget opskriftsrum. Start ved at tilføje nogle "
"opskrifter eller tilføje andre personer til at forbinde sig."
#: .\cookbook\views\views.py:171
#, python-format
msgid "PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!"
msgstr ""
#: .\cookbook\views\views.py:174
#, python-format
msgid "You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended"
msgstr ""
#: .\cookbook\views\views.py:178
msgid "Unable to determine PostgreSQL version."
msgstr ""
#: .\cookbook\views\views.py:182
#, fuzzy
#| msgid ""
#| "\n"
#| " This application is not running with a Postgres database "
#| "backend. This is ok but not recommended as some\n"
#| " features only work with postgres databases.\n"
#| " "
msgid ""
"This application is not running with a Postgres database backend. This is ok "
"but not recommended as some features only work with postgres databases."
msgstr ""
"\n"
" Denne applikation kører ikke med en Postgres database. Dette er "
"ok, men ikke anbefalet da\n"
" nogle funktioner kun virker med en postgres database.\n"
" "
#: .\cookbook\views\views.py:296
#, fuzzy
#| msgid ""
#| "The setup page can only be used to create the first user! If you have "
#| "forgotten your superuser credentials please consult the django "
#| "documentation on how to reset passwords."
msgid ""
"The setup page can only be used to create the first "
"user! If you have forgotten your superuser credentials "
"please consult the django documentation on how to reset passwords."
msgstr ""
"Opsætningssiden kan kun blive brugt til at oprette den første bruger! Hvis "
"du har gemt dine superbruger oplysninger, konsulter venligst Django "
"dokumentationen for at lære at nulstille dit kodeord."
#: .\cookbook\views\views.py:304
msgid "Passwords dont match!"
msgstr "Kodeorderne er ikke ens!"
#: .\cookbook\views\views.py:312
msgid "User has been created, please login!"
msgstr "Brugeren blev oprettet, log venligst ind!"
#: .\cookbook\views\views.py:352
msgid ""
"Reporting share links is not enabled for this instance. Please notify the "
"page administrator to report problems."
msgstr ""
"Rapportering af delingslinks er ikke slået til for denne instans. Oplys "
"venligst din sideadministrator om at rapportere problemer."
#: .\cookbook\views\views.py:357
msgid ""
"Recipe sharing link has been disabled! For additional information please "
"contact the page administrator."
msgstr ""
"Opskriftsdelingslinks er blevet slået fra! For mere information kontakt "
"venligst din sideadministrator."
#: .\cookbook\views\views.py:383
msgid "Manage recipes, shopping list, meal plans and more."
msgstr ""
#: .\cookbook\views\views.py:397 .\cookbook\views\views.py:398
#, fuzzy
#| msgid "Meal-Plan"
msgid "Plan"
msgstr "Madplan"
#: .\cookbook\views\views.py:399
msgid "View your meal Plan"
msgstr ""
#: .\cookbook\views\views.py:418
#, fuzzy
#| msgid "Users with whom to share shopping lists."
msgid "View your shopping lists"
msgstr "Brugere som indkøbslister skal deles med."
#~ msgid ""
#~ "Both fields are optional. If none are given the username will be "
#~ "displayed instead"
#~ msgstr ""
#~ "Begge felter er valgfri. Hvis intet er givet vil brugernavnet blive "
#~ "benyttet i stedet"
#~ msgid "Name"
#~ msgstr "Navn"
#~ msgid "Keywords"
#~ msgstr "Nøgleord"
#~ msgid "Preparation time in minutes"
#~ msgstr "Forberedelsestid i minutter"
#~ msgid "Waiting time (cooking/baking) in minutes"
#~ msgstr "Ventetid (f.eks. bagning) i minutter"
#~ msgid "Path"
#~ msgstr "Sti"
#~ msgid "Storage UID"
#~ msgstr "Lager UID"
#~ msgid "Add your comment: "
#~ msgstr "Tilføj din kommentar: "
#~ msgid "Leave empty for dropbox and enter app password for nextcloud."
#~ msgstr ""
#~ "Efterlad tom for at bruge dropbox, indtast applikationskodeord for at "
#~ "bruge nextcloud."
#~ msgid "Leave empty for nextcloud and enter api token for dropbox."
#~ msgstr ""
#~ "Efterlad tom for at bruge nextcloud, indtast API token for at bruge "
#~ "dropbox."
#~ msgid ""
#~ "Leave empty for dropbox and enter only base url for nextcloud (/"
#~ "remote.php/webdav/ is added automatically)"
#~ msgstr ""
#~ "Efterlad tom for at bruge dropbox, indtast kun base URL for at bruge "
#~ "nextcloud (/remote.php/WebDAV/ bliver tilføjet automatisk)"
#~ msgid "Storage"
#~ msgstr "Lager"
#~ msgid "Active"
#~ msgstr "Aktiv"
#~ msgid "Search String"
#~ msgstr "Søgeterm"
#~ msgid "File ID"
#~ msgstr "Fil ID"
#~ msgid ""
#~ "Determines how fuzzy a search is if it uses trigram similarity matching "
#~ "(e.g. low values mean more typos are ignored)."
#~ msgstr ""
#~ "Bestemmer hvor fuzzy en søgning kan være hvis den bruger trigram "
#~ "similarity matching (lave værdier gør at flere stavefejl bliver "
#~ "ignoreret)."
#~ msgid ""
#~ "Select type method of search. Click here "
#~ "for full description of choices."
#~ msgstr ""
#~ "Vælg metoden til søgning. Klik her for en "
#~ "fuld beskrivelse af valgmulighederne."
#~ msgid ""
#~ "Use fuzzy matching on units, keywords and ingredients when editing and "
#~ "importing recipes."
#~ msgstr ""
#~ "Benyt fuzzy matching på enheder, nøgleord og ingredienser når opskrifter "
#~ "bliver redigeret og importeret."
#~ msgid ""
#~ "Fields to search ignoring accents. Selecting this option can improve or "
#~ "degrade search quality depending on language"
#~ msgstr ""
#~ "Ignorer accenter på feltsøgning. Dette kan gøre søgning bedre eller "
#~ "værre, alt efter hvilket sprog der bliver brugt"
#~ msgid ""
#~ "Fields to search for partial matches. (e.g. searching for 'Pie' will "
#~ "return 'pie' and 'piece' and 'soapie')"
#~ msgstr ""
#~ "Felter hvor del-resultater findes. (F.eks. hvis der søges efter 'ost' vil "
#~ "resultater inkludere'hytteost' og 'revet ost')"
#~ msgid ""
#~ "Fields to search for beginning of word matches. (e.g. searching for 'sa' "
#~ "will return 'salad' and 'sandwich')"
#~ msgstr ""
#~ "Felter hvor begyndene matches findes (f.eks. hvis der søges efter 'pas' "
#~ "vil den kunne finde både 'pasta', 'pastinak', 'pastrami' osv.)"
#~ msgid ""
#~ "Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find "
#~ "'recipe'.) Note: this option will conflict with 'web' and 'raw' methods "
#~ "of search."
#~ msgstr ""
#~ "Felter som skal 'fuzzy' søges efter. (f.eks. søges der efter 'ospkrift' "
#~ "vil den finde 'opskrift'.) Bemærk: Denne indstilling har konklifkt med "
#~ "'internet' og 'rå' metoder af søgning."
#~ msgid ""
#~ "Fields to full text search. Note: 'web', 'phrase', and 'raw' search "
#~ "methods only function with fulltext fields."
#~ msgstr ""
#~ "Felter som skal søges som heltekst. Bemærk: 'internet', 'frase' og 'rå' "
#~ "søgemetoder fungerer kun med heltekst felter."
#~ msgid "Search Method"
#~ msgstr "Søgemetode"
#~ msgid "Fuzzy Lookups"
#~ msgstr "Fuzzy opslag"
#~ msgid "Ignore Accent"
#~ msgstr "Ignorer accenter"
#~ msgid "Partial Match"
#~ msgstr "Delvis match"
#~ msgid "Starts With"
#~ msgstr "Starter med"
#~ msgid "Fuzzy Search"
#~ msgstr "Fuzzy søgning"
#~ msgid "Full Text"
#~ msgstr "Heltekst"
#~ msgid " is part of a recipe step and cannot be deleted"
#~ msgstr " er del af et opskriftstrin og kan ikke slettes"
#~ msgid "Delete"
#~ msgstr "Slet"
#~ msgid "Settings"
#~ msgstr "Indstillinger"
#~ msgid "Email"
#~ msgstr "Email"
#~ msgid "Password"
#~ msgstr "Kodeord"
#~ msgid "Foods"
#~ msgstr "Mad"
#~ msgid "Units"
#~ msgstr "Enheder"
#~ msgid "Supermarket"
#~ msgstr "Supermarked"
#~ msgid "Supermarket Category"
#~ msgstr "Supermarkedskategori"
#~ msgid "Automations"
#~ msgstr "Automationer"
#~ msgid "Files"
#~ msgstr "Filer"
#~ msgid "Batch Edit"
#~ msgstr "Mængderedigering"
#~ msgid "History"
#~ msgstr "Historik"
#~ msgid "Ingredient Editor"
#~ msgstr "Ingrediens redigeringsværktøj"
#, fuzzy
#~| msgid "Account Connections"
#~ msgid "Unit Conversions"
#~ msgstr "Kontoforbindelser"
#~ msgid "Create"
#~ msgstr "Opret"
#~ msgid "External Recipes"
#~ msgstr "Eksterne opskrifter"
#~ msgid "Space Settings"
#~ msgstr "Indstillinger for rum"
#, fuzzy
#~| msgid "External Recipes"
#~ msgid "External Connectors"
#~ msgstr "Eksterne opskrifter"
#~ msgid "Admin"
#~ msgstr "Adminstration"
#~ msgid "Markdown Guide"
#~ msgstr "Markdown guide"
#~ msgid "GitHub"
#~ msgstr "GitHub"
#~ msgid "Translate Tandoor"
#~ msgstr "Oversæt Tandoor"
#~ msgid "API Browser"
#~ msgstr "API browser"
#~ msgid "Log out"
#~ msgstr "Log ud"
#~ msgid "You are using the free version of Tandor"
#~ msgstr "Du bruger den gratis version af Tandoor"
#~ msgid "Upgrade Now"
#~ msgstr "Opgrader nu"
#~ msgid "Batch edit Category"
#~ msgstr "Mængderediger kategori"
#~ msgid "Batch edit Recipes"
#~ msgstr "Mængderediger opskrifter"
#~ msgid "Add the specified keywords to all recipes containing a word"
#~ msgstr "Tilføj det valte nøgleord til alle opskrifter der indeholder et ord"
#~ msgid "Sync"
#~ msgstr "Synkroniser"
#~ msgid "Manage watched Folders"
#~ msgstr "Håndter overvågede mapper"
#~ msgid ""
#~ "On this Page you can manage all storage folder locations that should be "
#~ "monitored and synced."
#~ msgstr ""
#~ "På denne side kan du håndtere alle lagermappe lokationer der skal "
#~ "overvåges og synkroniseres."
#~ msgid "The path must be in the following format"
#~ msgstr "Stien skal være i den følgende format"
#~ msgid "Save"
#~ msgstr "Gem"
#~ msgid "Manage External Storage"
#~ msgstr "Håndter ekstern lager"
#~ msgid "Sync Now!"
#~ msgstr "Synkroniser nu!"
#~ msgid "Show Recipes"
#~ msgstr "Vis opskrifter"
#~ msgid "Show Log"
#~ msgstr "Vis log"
#~ msgid "Importing Recipes"
#~ msgstr "Importerer opskrifter"
#~ msgid ""
#~ "This can take a few minutes, depending on the number of recipes in sync, "
#~ "please wait."
#~ msgstr ""
#~ "Dette kan tage et par minutter, alt efter hvor mange opskrifter der "
#~ "synkroniseres - vent venligst."
#~ msgid "Recipe Books"
#~ msgstr "Opskriftsbøger"
#~ msgid "Import new Recipe"
#~ msgstr "Importer ny opskrift"
#~ msgid "Edit Recipe"
#~ msgstr "Rediger opskrift"
#, python-format
#~ msgid "Are you sure you want to delete the %(title)s: %(object)s "
#~ msgstr "Er du sikker på at du vil slette dette %(title)s%(object)s "
#~ msgid "Cascade"
#~ msgstr "Kaskade"
#~ msgid "Cancel"
#~ msgstr "Annuller"
#~ msgid "Edit"
#~ msgstr "Rediger"
#~ msgid "View"
#~ msgstr "Vis"
#~ msgid "Delete original file"
#~ msgstr "Slet original fil"
#~ msgid "List"
#~ msgstr "Liste"
#~ msgid "Filter"
#~ msgstr "Filter"
#~ msgid "Import all"
#~ msgstr "Importer alle"
#~ msgid "New"
#~ msgstr "Ny"
#~ msgid "previous"
#~ msgstr "foregående"
#~ msgid "next"
#~ msgstr "næste"
#~ msgid "View Log"
#~ msgstr "Vis log"
#~ msgid "Cook Log"
#~ msgstr "Tilberedningslog"
#~ msgid "Import"
#~ msgstr "Importer"
#~ msgid "Security Warning"
#~ msgstr "Sikkerhedsadvarsel"
#~ msgid ""
#~ "\n"
#~ " The Password and Token field are stored as plain text"
#~ "b> inside the database.\n"
#~ " This is necessary because they are needed to make API requests, "
#~ "but it also increases the risk of\n"
#~ " someone stealing it.
\n"
#~ " To limit the possible damage tokens or accounts with limited "
#~ "access can be used.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Kodeord og token feltet bliver opbevaret som rå tekst"
#~ "b> inde i databasen.\n"
#~ " Dette er nødvendigt fordi de skal bruges til at lave API kald, "
#~ "men det øger også risikoen\n"
#~ " for at nogen kan stjæle det.
\n"
#~ " For at begrænse det potentielle skade, kan tokens og brugerer med "
#~ "begrænset adgang blive benyttet.\n"
#~ " "
#~ msgid "You are currently offline!"
#~ msgstr "Du er i øjeblikket offline!"
#~ msgid ""
#~ "The recipes listed below are available for offline viewing because you "
#~ "have recently viewed them. Keep in mind that data might be outdated."
#~ msgstr ""
#~ "Opskrifterne som optræder herunder er tilgængelige for offline visning, "
#~ "fordi du har haft dem åbnet for nyligt. Husk at dataen måske kan være "
#~ "forældet."
#, fuzzy
#~| msgid "Ingredient Editor"
#~ msgid "Property Editor"
#~ msgstr "Ingrediens redigeringsværktøj"
#~ msgid "Comments"
#~ msgstr "Kommentarer"
#~ msgid "by"
#~ msgstr "af"
#~ msgid "Comment"
#~ msgstr "Kommentar"
#~ msgid ""
#~ "There are many options to configure the search depending on your personal "
#~ "preferences."
#~ msgstr ""
#~ "Der er mange indstillinger til at konfigurere søgningen baseret på dine "
#~ "præferencer."
#~ msgid ""
#~ "Usually you do not need to configure any of them and can just "
#~ "stick with either the default or one of the following presets."
#~ msgstr ""
#~ "Normalt har du ikke brug for at konfigurere nogen af dem, og kan "
#~ "bare beholde standardindstillingerne eller en af de følgende "
#~ "forudsindstillinger."
#~ msgid ""
#~ "If you do want to configure the search you can read about the different "
#~ "options here."
#~ msgstr ""
#~ "Hvis du har lyst til at konfigurere søgningen kan du læse mere om de "
#~ "forskellige muligheder her."
#~ msgid "Fuzzy"
#~ msgstr "Fuzzy"
#~ msgid ""
#~ "Find what you need even if your search or the recipe contains typos. "
#~ "Might return more results than needed to make sure you find what you are "
#~ "looking for."
#~ msgstr ""
#~ "Find hvad du har brug for, selvom opskriften har stavefejl. Kan måske "
#~ "returnere flere resultater end du har brug for, for at være sikker på, at "
#~ "du finder, hvad du leder efter."
#~ msgid "This is the default behavior"
#~ msgstr "Dette er standardopførslen"
#~ msgid "Apply"
#~ msgstr "Anvend"
#~ msgid "Precise"
#~ msgstr "Præcis"
#~ msgid ""
#~ "Allows fine control over search results but might not return results if "
#~ "too many spelling mistakes are made."
#~ msgstr ""
#~ "Tillader fin kontrol over søgeresultaterne, men kan måske ikke finde "
#~ "resultater hvis der er for mange stavefejl."
#~ msgid "Perfect for large Databases"
#~ msgstr "Perfekt til store databaser"
#~ msgid "Social"
#~ msgstr "Social"
#~ msgid "Space:"
#~ msgstr "Rum:"
#~ msgid "Manage Subscription"
#~ msgstr "Håndter abonnement"
#, fuzzy
#~| msgid "Create Space"
#~ msgid "Leave Space"
#~ msgstr "Opret rum"
#~ msgid "URL Import"
#~ msgstr "URL import"
#~ msgid ""
#~ "Rating a recipe should have or greater. [0 - 5] Negative value filters "
#~ "rating less than."
#~ msgstr ""
#~ "Bedømmelse en opskrift mindst skal have. [0 - 5] Negative værdier "
#~ "filtrerer opskrifter med mindre end det angivne."
#~ msgid ""
#~ "Returns the shopping list entry with a primary key of id. Multiple "
#~ "values allowed."
#~ msgstr ""
#~ "Returnerer indkøbslistepunktet med primær nøgle på ID. Flere værdier "
#~ "tilladt."
#, fuzzy
#~| msgid ""
#~| "Filter shopping list entries on checked. [true, false, both, recent"
#~| "b>]
- recent includes unchecked items and recently completed items."
#~ msgid ""
#~ "Filter shopping list entries on checked. [true, false, both, recent"
#~ "b>]
- recent includes unchecked items and recently "
#~ "completed items."
#~ msgstr ""
#~ "Filtrer indkøbslistepunkter baseret på om de er krydset af eller ej. "
#~ "[true, false, both, recent]
- 'recent' har både ikke "
#~ "afkrydsede artikler og nyligt færdiggjorte artikler."
#~ msgid ""
#~ "Returns the shopping list entries sorted by supermarket category order."
#~ msgstr ""
#~ "Returnerer indkøbslistepunkterne sorteret efter "
#~ "supermarkedskategorirækkefølge."
#, python-format
#~ msgid "Batch edit done. %(count)d recipe was updated."
#~ msgid_plural "Batch edit done. %(count)d Recipes where updated."
#~ msgstr[0] "Mængderedigering fuldført. %(count)d opskrift blev opdateret."
#~ msgstr[1] "Mængderedigering fuldført. %(count)d opskrifter blev opdateret."
#~ msgid "Monitor"
#~ msgstr "Overvåg"
#~ msgid "Storage Backend"
#~ msgstr "Backend til lager"
#~ msgid ""
#~ "Could not delete this storage backend as it is used in at least one "
#~ "monitor."
#~ msgstr ""
#~ "Kunne ikke slette denne lager backend, da den bliver brugt i mindst en "
#~ "overvågning."
#, fuzzy
#~| msgid "Storage Backend"
#~ msgid "Connectors Config Backend"
#~ msgstr "Backend til lager"
#~ msgid "Invite Link"
#~ msgstr "Invitationslink"
#, fuzzy
#~| msgid "Members"
#~ msgid "Space Membership"
#~ msgstr "Medlemmer"
#~ msgid "You cannot edit this storage!"
#~ msgstr "Du kan ikke redigere dette lager!"
#~ msgid "Storage saved!"
#~ msgstr "Lager gemt!"
#~ msgid "There was an error updating this storage backend!"
#~ msgstr "Der opstod en fejl under opdateringen af dette lagers backend!"
#, fuzzy
#~| msgid "Changes saved!"
#~ msgid "Config saved!"
#~ msgstr "Ændringer gemt!"
#~ msgid "Changes saved!"
#~ msgstr "Ændringer gemt!"
#~ msgid "Error saving changes!"
#~ msgstr "Der skete en fejl, så ændringerne blev ikke gemt!"
#~ msgid "Import Log"
#~ msgstr "Importeringslog"
#~ msgid "Discovery"
#~ msgstr "Opdag"
#~ msgid "Shopping List"
#~ msgstr "Indkøbsliste"
#, fuzzy
#~| msgid "Storage Backend"
#~ msgid "Connector Config Backend"
#~ msgstr "Backend til lager"
#~ msgid "Invite Links"
#~ msgstr "Invitationslinks"
#~ msgid "Supermarkets"
#~ msgstr "Supermarkeder"
#~ msgid "Shopping Categories"
#~ msgstr "Indkøbskategorier"
#~ msgid "Custom Filters"
#~ msgstr "Brugerdefinerede filtre"
#~ msgid "Steps"
#~ msgstr "Trin"
#, fuzzy
#~| msgid "This feature is not available in the demo version!"
#~ msgid "This feature is not enabled by the server admin!"
#~ msgstr "Denne funktion er ikke tilgængelig i demoversionen!"
#~ msgid "Imported new recipe!"
#~ msgstr "Importerede ny opskrift!"
#~ msgid "There was an error importing this recipe!"
#~ msgstr "Der opstod en fejl under importeringen af denne opskrift!"
#~ msgid "You do not have the required permissions to perform this action!"
#~ msgstr "Du har ikke de nødvendige rettigheder til at udføre denne handling!"
#~ msgid "Comment saved!"
#~ msgstr "Kommentar gemt!"
#~ msgid "You must select at least one field to search!"
#~ msgstr "Du skal vælge mindst et felt at søge efter!"
#~ msgid ""
#~ "To use this search method you must select at least one full text search "
#~ "field!"
#~ msgstr ""
#~ "For at bruge denne søgemetode, skal du vælge mindst et heltekst søgefelt!"
#~ msgid "Fuzzy search is not compatible with this search method!"
#~ msgstr "Fuzzy søgning er ikke kompatibelt med denne søgemetode!"
#~ msgid "Malformed Invite Link supplied!"
#~ msgstr "Ugyldigt invitationslink angivet!"
#~ msgid "Successfully joined space."
#~ msgstr "Du er nu medlem af rummet."
#~ msgid "Invite Link not valid or already used!"
#~ msgstr "Invitationslink er ugyldigt eller allerede brugt!"
#~ msgid "Ingredients"
#~ msgstr "Ingredienser"
#~ msgid "Default unit"
#~ msgstr "Standardenhed"
#~ msgid "Use KJ"
#~ msgstr "Benyt KJ"
#~ msgid "Theme"
#~ msgstr "Tema"
#~ msgid "Navbar color"
#~ msgstr "Farve på menubjælke"
#~ msgid "Sticky navbar"
#~ msgstr "Fastgjort menubjælke"
#~ msgid "Default page"
#~ msgstr "Standardside"
#~ msgid "Show recent recipes"
#~ msgstr "Vis nylige opskrifter"
#~ msgid "Search style"
#~ msgstr "Søgningsstil"
#~ msgid "Plan sharing"
#~ msgstr "Madplansdeling"
#~ msgid "Ingredient decimal places"
#~ msgstr "Decimaler til ingredienser"
#~ msgid "Shopping list auto sync period"
#~ msgstr "Indkøbsliste automatisk synkroniseringsperiode"
#~ msgid "Left-handed mode"
#~ msgstr "Venstrehåndstilstand"
#~ msgid ""
#~ "Color of the top navigation bar. Not all colors work with all themes, "
#~ "just try them out!"
#~ msgstr ""
#~ "Farven på den øverste menubjælke. Ikke alle farver ser godt ud med alle "
#~ "temaer, prøv dig frem!"
#~ msgid ""
#~ "Default Unit to be used when inserting a new ingredient into a recipe."
#~ msgstr ""
#~ "Standardenhed som skal benyttes når en ny ingrediens indsættes i en "
#~ "opskrift."
#~ msgid ""
#~ "Enables support for fractions in ingredient amounts (e.g. convert "
#~ "decimals to fractions automatically)"
#~ msgstr ""
#~ "Gør det muligt at have brøker i beløbet til ingredienser (decimaltal "
#~ "bliver automatisk konverteret til brøker)"
#~ msgid "Display nutritional energy amounts in joules instead of calories"
#~ msgstr "Vis energimængder i Joules i stedet for Kalorier"
#~ msgid ""
#~ "Users with whom newly created meal plans should be shared by default."
#~ msgstr "Brugere som nyligt oprettede madplaner automatisk skal deles med."
#~ msgid "Show recently viewed recipes on search page."
#~ msgstr "Vis nyligt besøgte opskrifter på søgesiden."
#~ msgid "Number of decimals to round ingredients."
#~ msgstr "Hvor mange decimaler der skal rundes af til i ingredienser."
#~ msgid ""
#~ "If you want to be able to create and see comments underneath recipes."
#~ msgstr ""
#~ "Om du vil have mulighed for at oprette og se kommentarer under opskrifter."
#~ msgid ""
#~ "Setting to 0 will disable auto sync. When viewing a shopping list the "
#~ "list is updated every set seconds to sync changes someone else might have "
#~ "made. Useful when shopping with multiple people but might use a little "
#~ "bit of mobile data. If lower than instance limit it is reset when saving."
#~ msgstr ""
#~ "Hvis den sættes til 0, bliver automatisk synkronisering slået fra. Når en "
#~ "indkøbsliste vises bliver den opdateret efter den indstillede periode i "
#~ "sekunder, for at synkronisere eventuelle ændringer andre har foretaget. "
#~ "Brugbar når man køber ind sammen med andre, men det bruger mobildata. "
#~ "Hvis sat til mindre end instans-begrænsning, bliver den genindstillet når "
#~ "der gemmes."
#~ msgid "Makes the navbar stick to the top of the page."
#~ msgstr "Får menubjælken til at sidde fast i toppen af siden."
#~ msgid "Automatically add meal plan ingredients to shopping list."
#~ msgstr "Tilføj automatisk madplansingredienser til indkøbsliste."
#~ msgid "Exclude ingredients that are on hand."
#~ msgstr "Ekskluder ingredienser som er til rådighed."
#~ msgid "Will optimize the UI for use with your left hand."
#~ msgstr "Optimerer brugerfladen til benyttelse med din venstre hånd."
#~ msgid "You must provide at least a recipe or a title."
#~ msgstr "Du skal benytte mindst enten en opskrift eller en titel."
#~ msgid "You can list default users to share recipes with in the settings."
#~ msgstr ""
#~ "Du kan vælge de brugere som opskrifter automatisk deles med i "
#~ "indstillingerne."
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "Du kan bruge Markdown til at formatere dette felt. Se dokumentation her"
#~ msgid ""
#~ "Users will see all items you add to your shopping list. They must add "
#~ "you to see items on their list."
#~ msgstr ""
#~ "Brugere vil se alle varer du tilføjer til din indkøbsliste. De skal "
#~ "tilføje dig for at se varer på deres liste."
#~ msgid ""
#~ "When adding a meal plan to the shopping list (manually or automatically), "
#~ "include all related recipes."
#~ msgstr ""
#~ "Når en madplan tilføjes til indkøbslisten (manuelt eller automatisk), "
#~ "inkluder alle relaterede opskrifter."
#~ msgid ""
#~ "When adding a meal plan to the shopping list (manually or automatically), "
#~ "exclude ingredients that are on hand."
#~ msgstr ""
#~ "Når en madplan tilføjes til indkøbslisten (manuelt eller automatisk), "
#~ "ekskluder tilgængelige ingredienser."
#~ msgid "Default number of hours to delay a shopping list entry."
#~ msgstr "Standard antal timer et punkt på indkøbslisten skal udskydes."
#~ msgid "Filter shopping list to only include supermarket categories."
#~ msgstr "Filtrer indkøbsliste så den kun indeholder supermarkedskategorier."
#~ msgid "Days of recent shopping list entries to display."
#~ msgstr "Antal dage seneste varer fra indkøbslister skal vises."
#~ msgid "Mark food 'On Hand' when checked off shopping list."
#~ msgstr ""
#~ "Marker mad som 'Tilgængeligt' når det bliver krydset af på indkøbslisten."
#~ msgid "Delimiter to use for CSV exports."
#~ msgstr "Tegn der skal afgrænse elementer i CSV eksporteringer."
#~ msgid "Prefix to add when copying list to the clipboard."
#~ msgstr "Præfiks som skal tilføjes når der kopieres til udklipsholder."
#~ msgid "Share Shopping List"
#~ msgstr "Del indkøbsliste"
#~ msgid "Autosync"
#~ msgstr "Synkroniser automatisk"
#~ msgid "Auto Add Meal Plan"
#~ msgstr "Tilføj madplan automatisk"
#~ msgid "Exclude On Hand"
#~ msgstr "Ekskluder tilgængelige"
#~ msgid "Include Related"
#~ msgstr "Inkluder relaterede"
#~ msgid "Default Delay Hours"
#~ msgstr "Standard udsættelse i timer"
#~ msgid "Filter to Supermarket"
#~ msgstr "Filtrer til supermarked"
#~ msgid "Recent Days"
#~ msgstr "Seneste dage"
#~ msgid "CSV Delimiter"
#~ msgstr "CSV afgrænsningstegn"
#~ msgid "List Prefix"
#~ msgstr "Liste præfiks"
#~ msgid "Auto On Hand"
#~ msgstr "Automatisk tilgængelig markering"
#~ msgid "Reset Food Inheritance"
#~ msgstr "Nulstil mad nedarvning"
#~ msgid "Reset all food to inherit the fields configured."
#~ msgstr "Nulstil alt mad til at nedarve de konfigurerede felter."
#~ msgid "Fields on food that should be inherited by default."
#~ msgstr "Felter på mad der skal nedarves som standard."
#~ msgid "Show recipe counts on search filters"
#~ msgstr "Vis opskriftsantal ved søgefiltre"
#~ msgid "One of queryset or hash_key must be provided"
#~ msgstr "Du skal angive enten queryset eller hash_key"
#~ msgid "Small"
#~ msgstr "Lille"
#~ msgid "Large"
#~ msgstr "Stor"
#~ msgid "A user is required"
#~ msgstr "En bruger er påkrævet"
#~ msgid "Edit Ingredients"
#~ msgstr "Rediger ingredienser"
#~ msgid ""
#~ "\n"
#~ " The following form can be used if, accidentally, two (or more) "
#~ "units or ingredients where created that should be\n"
#~ " the same.\n"
#~ " It merges two units or ingredients and updates all recipes using "
#~ "them.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Den følgende blanket kan blive brugt hvis, to (eller flere) "
#~ "ingredienser blev unhensigtsmæssigt oprettet, som skulle have været\n"
#~ " den samme.\n"
#~ " Den sammenfletter to enheder eller ingredienser og opdaterer alle "
#~ "opskrifter der bruger dem.\n"
#~ " "
#~ msgid "Are you sure that you want to merge these two units?"
#~ msgstr "Er du sikker på at du vil sammenflette disse to enheder?"
#~ msgid "Are you sure that you want to merge these two ingredients?"
#~ msgstr "Er du sikker på at du vil sammenflette disse to ingredienser?"
#~ msgid "Import Recipes"
#~ msgstr "Importer opskrifter"
#~ msgid "Close"
#~ msgstr "Luk"
#~ msgid "Open Recipe"
#~ msgstr "Åben opskrift"
#~ msgid "Meal Plan View"
#~ msgstr "Madplansvisning"
#~ msgid "Created by"
#~ msgstr "Oprettet af"
#~ msgid "Shared with"
#~ msgstr "Delt med"
#~ msgid "Never cooked before."
#~ msgstr "Aldrig tilberedt."
#~ msgid "Other meals on this day"
#~ msgstr "Andre måltider på denne dag"
#~ msgid "Recipe Image"
#~ msgstr "Opskriftsbillede"
#~ msgid "Preparation time ca."
#~ msgstr "Tilberedelsestid ca."
#~ msgid "Waiting time ca."
#~ msgstr "Ventetid ca."
#~ msgid "External"
#~ msgstr "Ekstern"
#~ msgid "Log Cooking"
#~ msgstr "Noter tilberedning"
#~ msgid "Account"
#~ msgstr "Konto"
#~ msgid "Preferences"
#~ msgstr "Indstillinger"
#~ msgid "API-Settings"
#~ msgstr "API indstillinger"
#~ msgid "Search-Settings"
#~ msgstr "Søgningsindstillinger"
#~ msgid "Shopping-Settings"
#~ msgstr "Indkøbsindstillinger"
#~ msgid "Name Settings"
#~ msgstr "Navneindstillinger"
#~ msgid "Account Settings"
#~ msgstr "Konto indstillinger"
#~ msgid "Emails"
#~ msgstr "Email"
#~ msgid "Language"
#~ msgstr "Sprog"
#~ msgid "Style"
#~ msgstr "Udseende"
#~ msgid "API Token"
#~ msgstr "API token"
#~ msgid ""
#~ "You can use both basic authentication and token based authentication to "
#~ "access the REST API."
#~ msgstr ""
#~ "Du kan bruge nåde basal autentificering og token baseret autentificering "
#~ "til at tilgå REST API'en."
#~ msgid ""
#~ "Use the token as an Authorization header prefixed by the word token as "
#~ "shown in the following examples:"
#~ msgstr ""
#~ "Brug token som en Authorization header præfikset med orde 'token' som "
#~ "vist i de følgende eksempler:"
#~ msgid "or"
#~ msgstr "eller"
#~ msgid "Shopping Settings"
#~ msgstr "Indkøbsindstillinger"
#~ msgid "Number of objects"
#~ msgstr "Antal af objekter"
#~ msgid "Recipe Imports"
#~ msgstr "Opskrift importeringer"
#~ msgid "Objects stats"
#~ msgstr "Statistik for objekter"
#~ msgid "Recipes without Keywords"
#~ msgstr "Opskrifter uden nøgleord"
#~ msgid "Internal Recipes"
#~ msgstr "Interne opskrifter"
#~ msgid "Invite User"
#~ msgstr "Inviter bruger"
#~ msgid "User"
#~ msgstr "Bruger"
#~ msgid "Groups"
#~ msgstr "Grupper"
#~ msgid "admin"
#~ msgstr "adminstrator"
#~ msgid "user"
#~ msgstr "bruger"
#~ msgid "guest"
#~ msgstr "gæst"
#~ msgid "remove"
#~ msgstr "fjern"
#~ msgid "Update"
#~ msgstr "Opdater"
#~ msgid "You cannot edit yourself."
#~ msgstr "Du kan ikke redigere dig selv."
#~ msgid "There are no members in your space yet!"
#~ msgstr "Der er ikke nogen medlemmer i dit rum endnu!"
#~ msgid "Stats"
#~ msgstr "Statistik"
#~ msgid "Statistics"
#~ msgstr "Statistik"
#~ msgid "Show Links"
#~ msgstr "Vis links"
#~ msgid "Recipe Book"
#~ msgstr "Opskriftsbog"
#~ msgid "Bookmarks"
#~ msgstr "Bogmærker"
#~ msgid "Invite link successfully send to user."
#~ msgstr "Invitationslink blev send til brugeren."
#~ msgid ""
#~ "You have send to many emails, please share the link manually or wait a "
#~ "few hours."
#~ msgstr ""
#~ "Du har sendt for mange emails, del venligst linket med dem manuelt eller "
#~ "vent et par timer."
#~ msgid "Email could not be sent to user. Please share the link manually."
#~ msgstr ""
#~ "Email kunne ikke sendes til bruger. Del venligst linket med dem manuelt."
#~ msgid ""
#~ "You are already member of a space and therefore cannot join this one."
#~ msgstr ""
#~ "Du er allerede medlem af et rum, og kan derfor ikke melde dig til dette."
================================================
FILE: cookbook/locale/de/LC_MESSAGES/django.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
# Translators:
# Benjamin Danowski , 2020
# vabene1111 , 2020
# Aaron Dötsch, 2020
# Tobias Lindenberg , 2021
# Maximilian J, 2021
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-22 20:15+0200\n"
"PO-Revision-Date: 2024-09-27 13:58+0000\n"
"Last-Translator: supaeasy \n"
"Language-Team: German \n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.6.2\n"
#: .\cookbook\forms.py:50
msgid "Default"
msgstr "Standard"
#: .\cookbook\forms.py:77
msgid ""
"To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything."
msgstr ""
"Um Duplikate zu vermeiden werden Rezepte mit dem gleichen Namen ignoriert. "
"Aktivieren Sie dieses Kontrollkästchen, um alles zu importieren."
#: .\cookbook\forms.py:108
msgid "Maximum number of users for this space reached."
msgstr "Maximale Nutzer-Anzahl wurde für diesen Space erreicht."
#: .\cookbook\forms.py:114
msgid "Email address already taken!"
msgstr "Email-Adresse ist bereits vergeben!"
#: .\cookbook\forms.py:121
msgid ""
"An email address is not required but if present the invite link will be sent "
"to the user."
msgstr ""
"Eine Email-Adresse wird nicht benötigt, aber falls vorhanden, wird der "
"Einladungslink zum Benutzer geschickt."
#: .\cookbook\forms.py:133
msgid "Name already taken."
msgstr "Name wird bereits verwendet."
#: .\cookbook\forms.py:144 .\cookbook\forms.py:158
msgid "Accept Terms and Privacy"
msgstr "AGB und Datenschutzerklärung akzeptieren"
#: .\cookbook\helper\AllAuthCustomAdapter.py:41
msgid ""
"In order to prevent spam, the requested email was not send. Please wait a "
"few minutes and try again."
msgstr ""
"Um Spam zu vermeiden, wurde die angeforderte Email nicht gesendet. Bitte "
"warte ein paar Minuten und versuche es erneut."
#: .\cookbook\helper\permission_helper.py:165
#: .\cookbook\helper\permission_helper.py:186 .\cookbook\views\views.py:138
msgid "You are not logged in and therefore cannot view this page!"
msgstr "Du bist nicht angemeldet, daher kannst du diese Seite nicht sehen!"
#: .\cookbook\helper\permission_helper.py:168
#: .\cookbook\helper\permission_helper.py:173
#: .\cookbook\helper\permission_helper.py:198
#: .\cookbook\helper\permission_helper.py:265
#: .\cookbook\helper\permission_helper.py:279
#: .\cookbook\helper\permission_helper.py:290
#: .\cookbook\helper\permission_helper.py:301
#: .\cookbook\helper\permission_helper.py:317
#: .\cookbook\helper\permission_helper.py:343
#: .\cookbook\helper\permission_helper.py:359
msgid "You do not have the required permissions to view this page!"
msgstr "Du hast nicht die notwendigen Rechte um diese Seite zu sehen!"
#: .\cookbook\helper\permission_helper.py:191
#: .\cookbook\helper\permission_helper.py:214
#: .\cookbook\helper\permission_helper.py:236
#: .\cookbook\helper\permission_helper.py:251
msgid "You cannot interact with this object as it is not owned by you!"
msgstr ""
"Du kannst mit diesem Objekt nicht interagieren, da es dir nicht gehört!"
#: .\cookbook\helper\permission_helper.py:420
msgid "You have reached the maximum number of recipes for your space."
msgstr "Du hast die maximale Anzahl an Rezepten für Deinen Space erreicht."
#: .\cookbook\helper\permission_helper.py:432
msgid "You have more users than allowed in your space."
msgstr "Du hast mehr Benutzer in Deinem Space als erlaubt."
#: .\cookbook\helper\recipe_url_import.py:319
msgid "reverse rotation"
msgstr "Linkslauf"
#: .\cookbook\helper\recipe_url_import.py:320
msgid "careful rotation"
msgstr "Kochlöffel"
#: .\cookbook\helper\recipe_url_import.py:321
msgid "knead"
msgstr "Kneten"
#: .\cookbook\helper\recipe_url_import.py:322
msgid "thicken"
msgstr "Andicken"
#: .\cookbook\helper\recipe_url_import.py:323
msgid "warm up"
msgstr "Erwärmen"
#: .\cookbook\helper\recipe_url_import.py:324
msgid "ferment"
msgstr "Fermentieren"
#: .\cookbook\helper\recipe_url_import.py:325
#, fuzzy
#| msgid "Last cooked"
msgid "slow cook"
msgstr "Zuletzt gekocht"
#: .\cookbook\helper\recipe_url_import.py:326
msgid "egg boiler"
msgstr "Eierkocher"
#: .\cookbook\helper\recipe_url_import.py:327
msgid "kettle"
msgstr "Kochtopf"
#: .\cookbook\helper\recipe_url_import.py:328
msgid "blend"
msgstr "Mischen"
#: .\cookbook\helper\recipe_url_import.py:329
msgid "pre-clean"
msgstr "Vorspühlen"
#: .\cookbook\helper\recipe_url_import.py:330
msgid "high temperature"
msgstr "Hochtemperatur"
#: .\cookbook\helper\recipe_url_import.py:331
msgid "rice cooker"
msgstr "Reiskocher"
#: .\cookbook\helper\recipe_url_import.py:332
msgid "caramelize"
msgstr "Karamellisieren"
#: .\cookbook\helper\recipe_url_import.py:333
msgid "peeler"
msgstr "Schäler"
#: .\cookbook\helper\recipe_url_import.py:334
msgid "slicer"
msgstr "Schneider"
#: .\cookbook\helper\recipe_url_import.py:335
msgid "grater"
msgstr "Reibe"
#: .\cookbook\helper\recipe_url_import.py:336
msgid "spiralizer"
msgstr "Spiralschneider"
#: .\cookbook\helper\recipe_url_import.py:337
msgid "sous-vide"
msgstr "Sous-vide"
#: .\cookbook\helper\shopping_helper.py:150
msgid "You must supply a servings size"
msgstr "Sie müssen eine Portionsgröße angeben"
#: .\cookbook\helper\template_helper.py:97
#: .\cookbook\helper\template_helper.py:99
#: .\cookbook\helper\template_helper.py:101
msgid "Could not parse template code."
msgstr "Konnte den Template code nicht verarbeiten."
#: .\cookbook\integration\copymethat.py:44
#: .\cookbook\integration\melarecipes.py:37
msgid "Favorite"
msgstr "Favorit"
#: .\cookbook\integration\copymethat.py:50
msgid "I made this"
msgstr "Von mir gekocht"
#: .\cookbook\integration\integration.py:238
msgid ""
"Importer expected a .zip file. Did you choose the correct importer type for "
"your data ?"
msgstr ""
"Importer erwartet eine .zip Datei. Hast du den richtigen Importer-Typ für "
"deine Daten ausgewählt?"
#: .\cookbook\integration\integration.py:241
msgid ""
"An unexpected error occurred during the import. Please make sure you have "
"uploaded a valid file."
msgstr ""
"Ein unerwarteter Fehler trat beim Importieren auf. Bitte stelle sicher, dass "
"die hochgeladene Datei gültig ist."
#: .\cookbook\integration\integration.py:246
msgid "The following recipes were ignored because they already existed:"
msgstr "Die folgenden Rezepte wurden ignoriert da sie bereits existieren:"
#: .\cookbook\integration\integration.py:250
#, python-format
msgid "Imported %s recipes."
msgstr "%s Rezepte importiert."
#: .\cookbook\integration\mealie1.py:210
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "Calories"
msgstr "Kalorien"
#: .\cookbook\integration\mealie1.py:211
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
msgid "Carbohydrates"
msgstr "Kohlenhydrate"
#: .\cookbook\integration\mealie1.py:212
msgid "Cholesterol"
msgstr ""
#: .\cookbook\integration\mealie1.py:213
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
msgid "Fat"
msgstr "Fett"
#: .\cookbook\integration\mealie1.py:214
msgid "Fiber"
msgstr ""
#: .\cookbook\integration\mealie1.py:215
#, fuzzy
#| msgid "Proteins"
msgid "Protein"
msgstr "Proteine"
#: .\cookbook\integration\mealie1.py:216
msgid "Saturated Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:217
msgid "Sodium"
msgstr ""
#: .\cookbook\integration\mealie1.py:218
msgid "Sugar"
msgstr ""
#: .\cookbook\integration\mealie1.py:219
msgid "Trans Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:220
msgid "Unsaturated Fat"
msgstr ""
#: .\cookbook\integration\openeats.py:28
msgid "Recipe source:"
msgstr "Rezept-Quelle:"
#: .\cookbook\integration\paprika.py:49
msgid "Notes"
msgstr "Notizen"
#: .\cookbook\integration\paprika.py:52
msgid "Nutritional Information"
msgstr "Nährwert Informationen"
#: .\cookbook\integration\paprika.py:56
msgid "Source"
msgstr "Quelle"
#: .\cookbook\integration\recettetek.py:55
#: .\cookbook\integration\recipekeeper.py:70
msgid "Imported from"
msgstr "Importiert von"
#: .\cookbook\integration\saffron.py:23
msgid "Servings"
msgstr "Portionen"
#: .\cookbook\integration\saffron.py:25
msgid "Waiting time"
msgstr "Wartezeit"
#: .\cookbook\integration\saffron.py:27
msgid "Preparation Time"
msgstr "Vorbereitungszeit"
#: .\cookbook\integration\saffron.py:29 .\cookbook\templates\index.html:6
msgid "Cookbook"
msgstr "Kochbuch"
#: .\cookbook\integration\saffron.py:31
msgid "Section"
msgstr "Sektion"
#: .\cookbook\management\commands\fix_duplicate_properties.py:15
msgid "Fixes foods with "
msgstr "Behebt Lebensmittel mit "
#: .\cookbook\management\commands\rebuildindex.py:14
msgid "Rebuilds full text search index on Recipe"
msgstr "Generiert den Index für die Rezept-Volltextsuche neu"
#: .\cookbook\management\commands\rebuildindex.py:18
msgid "Only Postgresql databases use full text search, no index to rebuild"
msgstr ""
"Nur PostgreSQL Datenbanken verwenden Volltextsuche, es muss kein Index neu "
"generiert werden"
#: .\cookbook\management\commands\rebuildindex.py:29
msgid "Recipe index rebuild complete."
msgstr "Generierung des Indizes abgeschlossen."
#: .\cookbook\management\commands\rebuildindex.py:31
msgid "Recipe index rebuild failed."
msgstr "Generierung des Indizes fehlgeschlagen."
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
msgid "Breakfast"
msgstr "Frühstück"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:19
msgid "Lunch"
msgstr "Mittagessen"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:24
msgid "Dinner"
msgstr "Abendessen"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:29 .\cookbook\models.py:971
msgid "Other"
msgstr "Andere"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "g"
msgstr "g"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "Proteins"
msgstr "Proteine"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "kcal"
msgstr "kcal"
#: .\cookbook\models.py:325
msgid ""
"Maximum file storage for space in MB. 0 for unlimited, -1 to disable file "
"upload."
msgstr ""
"Maximale Datei-Speichergröße in MB. 0 für unbegrenzt, -1 um den Datei-Upload "
"zu deaktivieren."
#: .\cookbook\models.py:513
msgid "Search"
msgstr "Suchen"
#: .\cookbook\models.py:514
msgid "Meal-Plan"
msgstr "Essensplan"
#: .\cookbook\models.py:515
msgid "Books"
msgstr "Kochbücher"
#: .\cookbook\models.py:516 .\cookbook\views\views.py:416
#: .\cookbook\views\views.py:417
msgid "Shopping"
msgstr "Einkaufsliste"
#: .\cookbook\models.py:967
msgid "Nutrition"
msgstr "Nährwerte"
#: .\cookbook\models.py:968
msgid "Allergen"
msgstr "Allergen"
#: .\cookbook\models.py:969
msgid "Price"
msgstr "Preis"
#: .\cookbook\models.py:970
msgid "Goal"
msgstr "Ziel"
#: .\cookbook\models.py:1467 .\cookbook\templates\search_info.html:28
msgid "Simple"
msgstr "Einfach"
#: .\cookbook\models.py:1468 .\cookbook\templates\search_info.html:33
msgid "Phrase"
msgstr "Satz"
#: .\cookbook\models.py:1469 .\cookbook\templates\search_info.html:38
msgid "Web"
msgstr "Web"
#: .\cookbook\models.py:1470 .\cookbook\templates\search_info.html:47
msgid "Raw"
msgstr "Rohdaten"
#: .\cookbook\models.py:1525
msgid "Food Alias"
msgstr "Lebensmittel Alias"
#: .\cookbook\models.py:1526
msgid "Unit Alias"
msgstr "Einheiten Alias"
#: .\cookbook\models.py:1527
msgid "Keyword Alias"
msgstr "Stichwort Alias"
#: .\cookbook\models.py:1528
msgid "Description Replace"
msgstr "Beschreibung ersetzen"
#: .\cookbook\models.py:1529
msgid "Instruction Replace"
msgstr "Anleitung ersetzen"
#: .\cookbook\models.py:1530
msgid "Never Unit"
msgstr "Nie Einheit"
#: .\cookbook\models.py:1531
msgid "Transpose Words"
msgstr "Wörter austauschen"
#: .\cookbook\models.py:1532
msgid "Food Replace"
msgstr "Lebensmittelersatz"
#: .\cookbook\models.py:1533
msgid "Unit Replace"
msgstr "Einheit Ersetzen"
#: .\cookbook\models.py:1534
msgid "Name Replace"
msgstr "Namensersetzung"
#: .\cookbook\models.py:1564
msgid "Recipe"
msgstr "Rezept"
#: .\cookbook\models.py:1565
msgid "Food"
msgstr "Lebensmittel"
#: .\cookbook\models.py:1566
msgid "Keyword"
msgstr "Schlüsselwort"
#: .\cookbook\serializer.py:262
msgid "File uploads are not enabled for this Space."
msgstr "Datei-Uploads sind in diesem Space nicht aktiviert."
#: .\cookbook\serializer.py:273
msgid "You have reached your file upload limit."
msgstr "Du hast Dein Datei-Uploadlimit erreicht."
#: .\cookbook\serializer.py:281
msgid "The given file type is not allowed."
msgstr ""
#: .\cookbook\serializer.py:421 .\cookbook\views\views.py:94
msgid ""
"You have the reached the maximum amount of spaces that can be owned by you."
msgstr ""
"Du hast die maximale Anzahl an Rezepten erreicht, die Du besitzen kannst."
#: .\cookbook\serializer.py:434
msgid "Space Name must be unique."
msgstr ""
#: .\cookbook\serializer.py:469
msgid "Cannot modify Space owner permission."
msgstr "Die Eigentumsberechtigung am Space kann nicht geändert werden."
#: .\cookbook\serializer.py:1596
msgid "Hello"
msgstr "Hallo"
#: .\cookbook\serializer.py:1596
msgid "You have been invited by "
msgstr "Du wurdest eingeladen von "
#: .\cookbook\serializer.py:1598
msgid " to join their Tandoor Recipes space "
msgstr " um deren Tandoor Recipes Instanz beizutreten "
#: .\cookbook\serializer.py:1600
msgid "Click the following link to activate your account: "
msgstr "Klicke auf den folgenden Link, um deinen Account zu aktivieren: "
#: .\cookbook\serializer.py:1602
msgid ""
"If the link does not work use the following code to manually join the space: "
msgstr ""
"Falls der Link nicht funktioniert, benutze den folgenden Code um dem Space "
"manuell beizutreten: "
#: .\cookbook\serializer.py:1604
msgid "The invitation is valid until "
msgstr "Die Einladung ist gültig bis "
#: .\cookbook\serializer.py:1606
msgid ""
"Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub "
msgstr ""
"Tandoor Recipes ist ein Open-Source Rezept-Manager. Mehr Informationen sind "
"auf GitHub zu finden "
#: .\cookbook\serializer.py:1609
msgid "Tandoor Recipes Invite"
msgstr "Tandoor Recipes Einladung"
#: .\cookbook\serializer.py:1813
msgid "Existing shopping list to update"
msgstr "Bestehende Einkaufliste, die aktualisiert werden soll"
#: .\cookbook\serializer.py:1815
msgid ""
"List of ingredient IDs from the recipe to add, if not provided all "
"ingredients will be added."
msgstr ""
"Liste der Zutaten-IDs aus dem Rezept, wenn keine Angabe erfolgt, werden alle "
"Zutaten hinzugefügt."
#: .\cookbook\serializer.py:1817
msgid ""
"Providing a list_recipe ID and servings of 0 will delete that shopping list."
msgstr ""
"Wenn Sie eine list_recipe ID und Portion mit dem Wert 0 angeben, wird diese "
"Einkaufsliste gelöscht."
#: .\cookbook\serializer.py:1826
msgid "Amount of food to add to the shopping list"
msgstr ""
"Menge des Lebensmittels, welches der Einkaufsliste hinzugefügt werden soll"
#: .\cookbook\serializer.py:1828
msgid "ID of unit to use for the shopping list"
msgstr "ID der Einheit, die für die Einkaufsliste verwendet werden soll"
#: .\cookbook\serializer.py:1830
msgid "When set to true will delete all food from active shopping lists."
msgstr ""
"Wenn diese Option aktiviert ist, werden alle Lebensmittel aus den aktiven "
"Einkaufslisten gelöscht."
#: .\cookbook\templates\404.html:5
msgid "404 Error"
msgstr "404 Fehler"
#: .\cookbook\templates\404.html:18
msgid "The page you are looking for could not be found."
msgstr "Die angeforderte Seite konnte nicht gefunden werden."
#: .\cookbook\templates\404.html:33
msgid "Take me Home"
msgstr "Zur Hauptseite"
#: .\cookbook\templates\404.html:35
msgid "Report a Bug"
msgstr "Fehler melden"
#: .\cookbook\templates\account\email.html:6
#: .\cookbook\templates\account\email.html:10
msgid "E-mail Addresses"
msgstr "Email-Adressen"
#: .\cookbook\templates\account\email.html:12
msgid "The following e-mail addresses are associated with your account:"
msgstr "Die folgenden Email-Adressen sind mit deinem Account verknüpft:"
#: .\cookbook\templates\account\email.html:29
msgid "Verified"
msgstr "Verfiziert"
#: .\cookbook\templates\account\email.html:31
msgid "Unverified"
msgstr "Unverfiziert"
#: .\cookbook\templates\account\email.html:33
msgid "Primary"
msgstr "Primär"
#: .\cookbook\templates\account\email.html:40
msgid "Make Primary"
msgstr "Als Hauptadresse verwenden"
#: .\cookbook\templates\account\email.html:42
msgid "Re-send Verification"
msgstr "Verifikation erneut senden"
#: .\cookbook\templates\account\email.html:43
#: .\cookbook\templates\socialaccount\connections.html:36
msgid "Remove"
msgstr "Entfernen"
#: .\cookbook\templates\account\email.html:51
msgid "Warning:"
msgstr "Warnung:"
#: .\cookbook\templates\account\email.html:51
msgid ""
"You currently do not have any e-mail address set up. You should really add "
"an e-mail address so you can receive notifications, reset your password, etc."
msgstr ""
"Du hast aktuell keine Email-Adressen eingerichtet. Du solltest eine Email-"
"Adresse hinzufügen, um Benachrichtigungen, Passwort-Rücksetzungen, etc. "
"empfangen zu können."
#: .\cookbook\templates\account\email.html:57
msgid "Add E-mail Address"
msgstr "Email-Adresse hinzufügen"
#: .\cookbook\templates\account\email.html:62
msgid "Add E-mail"
msgstr "Email hinzufügen"
#: .\cookbook\templates\account\email.html:72
msgid "Do you really want to remove the selected e-mail address?"
msgstr "Möchtest Du wirklich die ausgewählte Email-Adresse entfernen?"
#: .\cookbook\templates\account\email_confirm.html:6
#: .\cookbook\templates\account\email_confirm.html:10
msgid "Confirm E-mail Address"
msgstr "Email-Adresse bestätigen"
#: .\cookbook\templates\account\email_confirm.html:16
#, python-format
msgid ""
"Please confirm that\n"
" %(email)s is an e-mail address "
"for user %(user_display)s\n"
" ."
msgstr ""
"Bitte bestätige, dass\n"
" %(email)s für den Benutzer "
"%(user_display)s\n"
" ist."
#: .\cookbook\templates\account\email_confirm.html:22
msgid "Confirm"
msgstr "Bestätigen"
#: .\cookbook\templates\account\email_confirm.html:29
#, python-format
msgid ""
"This e-mail confirmation link expired or is invalid. Please\n"
" issue a new e-mail confirmation "
"request."
msgstr ""
"Dieser Email-Bestätigungslink ist abgelaufen oder ungültig. Bitte\n"
" beantrage einen neuen Email-"
"Bestätigungslink."
#: .\cookbook\templates\account\login.html:8
#: .\cookbook\templates\openid\login.html:8
msgid "Login"
msgstr "Anmelden"
#: .\cookbook\templates\account\login.html:15
#: .\cookbook\templates\account\login.html:31
#: .\cookbook\templates\account\password_reset.html:39
#: .\cookbook\templates\account\password_reset_done.html:31
#: .\cookbook\templates\account\signup.html:68
#: .\cookbook\templates\account\signup_closed.html:15
#: .\cookbook\templates\openid\login.html:15
#: .\cookbook\templates\openid\login.html:26
#: .\cookbook\templates\socialaccount\authentication_error.html:15
msgid "Sign In"
msgstr "Einloggen"
#: .\cookbook\templates\account\login.html:34
#: .\cookbook\templates\account\password_reset.html:41
#: .\cookbook\templates\account\password_reset_done.html:33
#: .\cookbook\templates\socialaccount\signup.html:8
#: .\cookbook\templates\socialaccount\signup.html:56
msgid "Sign Up"
msgstr "Registrieren"
#: .\cookbook\templates\account\login.html:38
msgid "Lost your password?"
msgstr "Passwort vergessen?"
#: .\cookbook\templates\account\login.html:39
#: .\cookbook\templates\account\password_reset.html:29
msgid "Reset My Password"
msgstr "Passwort zurücksetzen"
#: .\cookbook\templates\account\login.html:50
msgid "Social Login"
msgstr "Social Login"
#: .\cookbook\templates\account\login.html:51
msgid "You can use any of the following providers to sign in."
msgstr "Du kannst jeden der folgenden Anbieter zum Einloggen verwenden."
#: .\cookbook\templates\account\logout.html:5
#: .\cookbook\templates\account\logout.html:9
#: .\cookbook\templates\account\logout.html:18
msgid "Sign Out"
msgstr "Ausloggen"
#: .\cookbook\templates\account\logout.html:11
msgid "Are you sure you want to sign out?"
msgstr "Willst du dich wirklich ausloggen?"
#: .\cookbook\templates\account\password_change.html:6
#: .\cookbook\templates\account\password_change.html:10
#: .\cookbook\templates\account\password_change.html:15
#: .\cookbook\templates\account\password_reset_from_key.html:7
#: .\cookbook\templates\account\password_reset_from_key.html:13
#: .\cookbook\templates\account\password_reset_from_key_done.html:7
#: .\cookbook\templates\account\password_reset_from_key_done.html:13
msgid "Change Password"
msgstr "Passwort ändern"
#: .\cookbook\templates\account\password_change.html:16
msgid "Forgot Password?"
msgstr "Passwort vergessen?"
#: .\cookbook\templates\account\password_reset.html:7
#: .\cookbook\templates\account\password_reset.html:13
#: .\cookbook\templates\account\password_reset_done.html:7
#: .\cookbook\templates\account\password_reset_done.html:18
msgid "Password Reset"
msgstr "Passwort Reset"
#: .\cookbook\templates\account\password_reset.html:24
msgid ""
"Forgotten your password? Enter your e-mail address below, and we'll send you "
"an e-mail allowing you to reset it."
msgstr ""
"Passwort vergessen? Gebe Deine Email-Adresse unten ein und wir senden Dir "
"eine Email zur Passwort-Rücksetzung."
#: .\cookbook\templates\account\password_reset.html:32
msgid "Password reset is disabled on this instance."
msgstr "Passwort-Rücksetzung ist in dieser Instanz deaktiviert."
#: .\cookbook\templates\account\password_reset_done.html:25
msgid ""
"We have sent you an e-mail. Please contact us if you do not receive it "
"within a few minutes."
msgstr ""
"Wir haben Dir eine Email gesendet. Bitte kontaktiere uns, falls du sie nicht "
"innerhalb der nächsten Minuten erhältst."
#: .\cookbook\templates\account\password_reset_from_key.html:13
msgid "Bad Token"
msgstr "Ungültiger Token"
#: .\cookbook\templates\account\password_reset_from_key.html:25
#, python-format
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used.\n"
" Please request a new "
"password reset."
msgstr ""
"Der Link wurde bereits benutzt oder deaktiviert.\n"
" Bitte fordern Sie hier"
"a> einen neuen Link an ."
#: .\cookbook\templates\account\password_reset_from_key.html:33
msgid "change password"
msgstr "passwort ändern"
#: .\cookbook\templates\account\password_reset_from_key.html:36
#: .\cookbook\templates\account\password_reset_from_key_done.html:19
msgid "Your password is now changed."
msgstr "Passwort wurde geändert."
#: .\cookbook\templates\account\password_set.html:6
#: .\cookbook\templates\account\password_set.html:10
#: .\cookbook\templates\account\password_set.html:15
msgid "Set Password"
msgstr "Passwort setzen"
#: .\cookbook\templates\account\signup.html:5
msgid "Register"
msgstr "Registrieren"
#: .\cookbook\templates\account\signup.html:11
msgid "Create an Account"
msgstr "Account erstellen"
#: .\cookbook\templates\account\signup.html:41
msgid "I accept the follwoing"
msgstr "Ich akzeptiere folgendes"
#: .\cookbook\templates\account\signup.html:44
#: .\cookbook\templates\socialaccount\signup.html:35
msgid "Terms and Conditions"
msgstr "Bedingungen und Bestimmungen"
#: .\cookbook\templates\account\signup.html:47
#: .\cookbook\templates\socialaccount\signup.html:38
msgid "and"
msgstr "und"
#: .\cookbook\templates\account\signup.html:51
#: .\cookbook\templates\socialaccount\signup.html:42
msgid "Privacy Policy"
msgstr "Datenschutzbelehrung"
#: .\cookbook\templates\account\signup.html:64
msgid "Create User"
msgstr "Nutzer erstellen"
#: .\cookbook\templates\account\signup.html:68
msgid "Already have an account?"
msgstr "Hast Du bereits einen Account?"
#: .\cookbook\templates\account\signup_closed.html:5
#: .\cookbook\templates\account\signup_closed.html:11
msgid "Sign Up Closed"
msgstr "Registrierung geschlossen"
#: .\cookbook\templates\account\signup_closed.html:13
msgid "We are sorry, but the sign up is currently closed."
msgstr "Es tut uns Leid, aber die Registrierung ist derzeit geschlossen."
#: .\cookbook\templates\frontend\tandoor.html:15
#, fuzzy
#| msgid "Tandoor Recipes Invite"
msgid "Tandoor Recipe Manager"
msgstr "Tandoor Recipes Einladung"
#: .\cookbook\templates\index.html:28
msgid "Search recipe ..."
msgstr "Rezept suchen ..."
#: .\cookbook\templates\index.html:43
msgid "New Recipe"
msgstr "Neues Rezept"
#: .\cookbook\templates\index.html:46
msgid "Import Recipe"
msgstr "Rezept importieren"
#: .\cookbook\templates\index.html:52
msgid "Advanced Search"
msgstr "Erweiterte Suche"
#: .\cookbook\templates\index.html:56
msgid "Reset Search"
msgstr "Suche zurücksetzen"
#: .\cookbook\templates\index.html:84
msgid "Last viewed"
msgstr "Zuletzt angesehen"
#: .\cookbook\templates\index.html:86
msgid "Recipes"
msgstr "Rezepte"
#: .\cookbook\templates\index.html:93
msgid "Log in to view recipes"
msgstr "Einloggen, um Rezepte anzusehen"
#: .\cookbook\templates\markdown_info.html:5
#: .\cookbook\templates\markdown_info.html:13
msgid "Markdown Info"
msgstr "Markdown-Übersicht"
#: .\cookbook\templates\markdown_info.html:14
msgid ""
"\n"
" Markdown is lightweight markup language that can be used to format "
"plain text easily.\n"
" This site uses the Python Markdown library to\n"
" convert your text into nice looking HTML. Its full markdown "
"documentation can be found\n"
" here.\n"
" An incomplete but most likely sufficient documentation can be found "
"below.\n"
" "
msgstr ""
"\n"
" Markdown ist eine Schreibweise mit der Text einfach formatiert "
"werden kann. Diese Seite benutzt Python Markdown, eine Bibliothek, die reinen Text "
"in schönes HTML umwandelt. Die komplette Dokumentation befindet sich hier. Die wichtigsten Formatierungszeichen befinden sich hier auf "
"dieser Seite.\n"
" "
#: .\cookbook\templates\markdown_info.html:25
msgid "Headers"
msgstr "Überschriften"
#: .\cookbook\templates\markdown_info.html:54
msgid "Formatting"
msgstr "Formatierung"
#: .\cookbook\templates\markdown_info.html:56
#: .\cookbook\templates\markdown_info.html:72
msgid "Line breaks are inserted by adding two spaces after the end of a line"
msgstr "Zeilenumbrüche entstehen durch zwei Leerzeichen am ende einer Zeile"
#: .\cookbook\templates\markdown_info.html:57
#: .\cookbook\templates\markdown_info.html:73
msgid "or by leaving a blank line in between."
msgstr "oder durch eine Leerzeile dazwischen."
#: .\cookbook\templates\markdown_info.html:59
#: .\cookbook\templates\markdown_info.html:74
msgid "This text is bold"
msgstr "Dieser Text ist fett"
#: .\cookbook\templates\markdown_info.html:60
#: .\cookbook\templates\markdown_info.html:75
msgid "This text is italic"
msgstr "Dieser Text ist kursiv"
#: .\cookbook\templates\markdown_info.html:61
#: .\cookbook\templates\markdown_info.html:77
msgid "Blockquotes are also possible"
msgstr "Zitate sind auch möglich"
#: .\cookbook\templates\markdown_info.html:84
msgid "Lists"
msgstr "Listen"
#: .\cookbook\templates\markdown_info.html:85
msgid ""
"Lists can ordered or unordered. It is important to leave a blank line "
"before the list!"
msgstr ""
"Listen können sortiert oder unsortiert sein. Es ist wichtig, dass vor der "
"Liste eine Zeile frei gelassen wird!"
#: .\cookbook\templates\markdown_info.html:87
#: .\cookbook\templates\markdown_info.html:108
msgid "Ordered List"
msgstr "Geordnete Liste"
#: .\cookbook\templates\markdown_info.html:89
#: .\cookbook\templates\markdown_info.html:90
#: .\cookbook\templates\markdown_info.html:91
#: .\cookbook\templates\markdown_info.html:110
#: .\cookbook\templates\markdown_info.html:111
#: .\cookbook\templates\markdown_info.html:112
msgid "unordered list item"
msgstr "Ungeordneter Listeneintrag"
#: .\cookbook\templates\markdown_info.html:93
#: .\cookbook\templates\markdown_info.html:114
msgid "Unordered List"
msgstr "Ungeordnete Liste"
#: .\cookbook\templates\markdown_info.html:95
#: .\cookbook\templates\markdown_info.html:96
#: .\cookbook\templates\markdown_info.html:97
#: .\cookbook\templates\markdown_info.html:116
#: .\cookbook\templates\markdown_info.html:117
#: .\cookbook\templates\markdown_info.html:118
msgid "ordered list item"
msgstr "Geordneter Listeneintrag"
#: .\cookbook\templates\markdown_info.html:125
msgid "Images & Links"
msgstr "Bilder & Links"
#: .\cookbook\templates\markdown_info.html:126
msgid ""
"Links can be formatted with Markdown. This application also allows to paste "
"links directly into markdown fields without any formatting."
msgstr ""
"Links können mit Markdown formatiert werden, aber es ist auch möglich, Links "
"vollständig ohne Formatierung einzufügen."
#: .\cookbook\templates\markdown_info.html:132
#: .\cookbook\templates\markdown_info.html:145
msgid "This will become an image"
msgstr "Dies wird ein Bild werden"
#: .\cookbook\templates\markdown_info.html:152
msgid "Tables"
msgstr "Tabellen"
#: .\cookbook\templates\markdown_info.html:153
msgid ""
"Markdown tables are hard to create by hand. It is recommended to use a table "
"editor like this one."
msgstr ""
"Es ist schwierig, Markdown-Tabellen von Hand zu erstellen. Daher bietet es "
"sich an, Werkzeuge wie dieses hier"
"a> zu verwenden."
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:171
#: .\cookbook\templates\markdown_info.html:177
msgid "Table"
msgstr "Tabelle"
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:172
msgid "Header"
msgstr "Überschrift"
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:178
msgid "Cell"
msgstr "Zelle"
#: .\cookbook\templates\no_groups_info.html:5
#: .\cookbook\templates\no_groups_info.html:12
msgid "No Permissions"
msgstr "Keine Berechtigungen"
#: .\cookbook\templates\no_groups_info.html:17
msgid "You do not have any groups and therefor cannot use this application."
msgstr "Du hast keine Gruppe und kannst daher diese Anwendung nicht nutzen."
#: .\cookbook\templates\no_groups_info.html:18
#: .\cookbook\templates\no_perm_info.html:15
msgid "Please contact your administrator."
msgstr "Bitte kontaktiere einen Administrator."
#: .\cookbook\templates\no_perm_info.html:5
#: .\cookbook\templates\no_perm_info.html:12
msgid "No Permission"
msgstr "Keine Berechtigung"
#: .\cookbook\templates\no_perm_info.html:15
msgid ""
"You do not have the required permissions to view this page or perform this "
"action."
msgstr ""
"Du hast nicht die notwendige Berechtigung, um diese Seite anzusehen oder "
"diese Aktion durchzuführen."
#: .\cookbook\templates\offline.html:5
msgid "Offline"
msgstr "Offline"
#: .\cookbook\templates\openid\login.html:27
#: .\cookbook\templates\socialaccount\authentication_error.html:27
msgid "Back"
msgstr "Zurück"
#: .\cookbook\templates\rest_framework\api.html:5
msgid "Recipe Home"
msgstr "Rezept-Hauptseite"
#: .\cookbook\templates\rest_framework\api.html:11
msgid "API Documentation"
msgstr "API-Dokumentation"
#: .\cookbook\templates\search_info.html:5
#: .\cookbook\templates\search_info.html:9
msgid "Search Settings"
msgstr "Sucheinstellungen"
#: .\cookbook\templates\search_info.html:10
msgid ""
"\n"
" Creating the best search experience is complicated and weighs "
"heavily on your personal configuration. \n"
" Changing any of the search settings can have significant impact on "
"the speed and quality of the results.\n"
" Search Methods, Trigrams and Full Text Search configurations are "
"only available if you are using Postgres for your database.\n"
" "
msgstr ""
"\n"
" Das optimieren der Suchergebnisse ist kompliziert und stark abhängig "
"von deiner persönlichen Konfiguration. \n"
" Auch einzelne Einstellungen können die Geschwindigkeit und Qualität "
"der Suchergebnisse maßgeblich beinflussen..\n"
" Die Suchtypen \"Trigram\" und Volltext sind nur verfügbar falls eine "
"PostgreSQL Datenbank verwendet wird..\n"
" "
#: .\cookbook\templates\search_info.html:19
msgid "Search Methods"
msgstr "Suchtypen"
#: .\cookbook\templates\search_info.html:23
msgid ""
" \n"
" Full text searches attempt to normalize the words provided to "
"match common variants. For example: 'forked', 'forking', 'forks' will all "
"normalize to 'fork'.\n"
" There are several methods available, described below, that will "
"control how the search behavior should react when multiple words are "
"searched.\n"
" Full technical details on how these operate can be viewed on Postgresql's website.\n"
" "
msgstr ""
" \n"
" Die Volltextsuche versucht Wörter in übliche Varianten zu "
"normalisieren. z.B.: 'Kürbis', 'Kürbissuppe', 'Kürbiskuchen' werden alle zu "
"'Kürbis' normalisiert..\n"
" Es sind verschiedene Methoden verfügbar, welche weiter unten "
"genau beschrieben werden. Diese beeinflussen das Suchergebnis bei einer "
"Suche mit mehreren Wörtern.\n"
" Technische Details können auf der Website von PostgreSQL. eingesehen werden.\n"
" "
#: .\cookbook\templates\search_info.html:29
msgid ""
" \n"
" Simple searches ignore punctuation and common words such as "
"'the', 'a', 'and'. And will treat separate words as required.\n"
" Searching for 'apple or flour' will return any recipe that "
"includes both 'apple' and 'flour' anywhere in the fields that have been "
"selected for a full text search.\n"
" "
msgstr ""
" \n"
" Einfache Suchen ignorieren Satzzeichen und Füllwörter wie \"und"
"\", \"der\", \"ein\". Alle anderen Wörter werden als erforderlich gewertet.\n"
" Eine Suche nach \"Der Apfel und Mehl\" wird alle Rezepte liefern "
"die \"Apfel\" und \"Mehl\" in einem der ausgewählten Suchfeldern enthalten.\n"
" "
#: .\cookbook\templates\search_info.html:34
msgid ""
" \n"
" Phrase searches ignore punctuation, but will search for all of "
"the words in the exact order provided.\n"
" Searching for 'apple or flour' will only return a recipe that "
"includes the exact phrase 'apple or flour' in any of the fields that have "
"been selected for a full text search.\n"
" "
msgstr ""
" \n"
" Die Suche nach Phrasen ignoriert Satzzeichen, beachtet aber die "
"Reihenfolge der Suchworte.\n"
" Eine Suche nach \"Apfel und Mehl\" wird nur Rezepte liefern die "
"\"Apfel und Mehl\" in einem der ausgewählten Suchfeldern enthalten.\n"
" "
#: .\cookbook\templates\search_info.html:39
msgid ""
" \n"
" Web searches simulate functionality found on many web search "
"sites supporting special syntax.\n"
" Placing quotes around several words will convert those words "
"into a phrase.\n"
" 'or' is recognized as searching for the word (or phrase) "
"immediately before 'or' OR the word (or phrase) directly after.\n"
" '-' is recognized as searching for recipes that do not include "
"the word (or phrase) that comes immediately after. \n"
" For example searching for 'apple pie' or cherry -butter will "
"return any recipe that includes the phrase 'apple pie' or the word "
"'cherry' \n"
" in any field included in the full text search but exclude any "
"recipe that has the word 'butter' in any field included.\n"
" "
msgstr ""
" \n"
" Der Suchtyp \"Web\" simuliert die Funktion vieler "
"Internetsuchmaschinen und unterstützt eine ähnliche Syntax.\n"
" Einfache Anführungszeichen (') um mehrere Wörter verwandeln "
"diese in einen zusammenhängenden Suchbegriff.\n"
" \"or\" (oder) verknüpft zwei Suchbegriffe. Mindestens einer der "
"beiden Begriffe (oder beide) muss enthalten sein.\n"
" \"-\" kann verwendet werden, um Begriffe auszuschließen. Es "
"werden nur Rezepte gefunden die nicht den darauf folgenden Begriff "
"enthalten.\n"
" Beispiel: Eine Suche nach \"'Apfelkuchen mit Sahne' or Torte -"
"Butter\" liefert alle Suchergebnisse die entweder \"Apfelkuchen mit Sahne"
"\" \n"
" oder Torte (oder beides) enthalten, schließt aber Ergebnisse "
"welche Butter enthalten aus.\n"
" "
#: .\cookbook\templates\search_info.html:48
msgid ""
" \n"
" Raw search is similar to Web except will take puncuation "
"operators such as '|', '&' and '()'\n"
" "
msgstr ""
" \n"
" Der Suchtyp Rohdaten ist ähnlich zu Web, erlaubt aber "
"Satzzeichen und spezielle Operatoren wie '|', '&' und '()'\n"
" "
#: .\cookbook\templates\search_info.html:59
msgid ""
" \n"
" Another approach to searching that also requires Postgresql is "
"fuzzy search or trigram similarity. A trigram is a group of three "
"consecutive characters.\n"
" For example searching for 'apple' will create x trigrams 'app', "
"'ppl', 'ple' and will create a score of how closely words match the "
"generated trigrams.\n"
" One benefit of searching trigams is that a search for 'sandwich' "
"will find misspelled words such as 'sandwhich' that would be missed by other "
"methods.\n"
" "
msgstr ""
" \n"
" Eine weitere Suchmethode (welche ebenfalls PostgreSQL erfordert) "
"ist die unscharfe Suche oder Trigramm-Suche. Ein Trigramm sind 3 "
"aufeinanderfolgende Zeichen.\n"
" Beispiel: Die Suche nach \"Apfel\" erzeugt die Trigramme \"Apf"
"\", \"pfl\" und \"fel\". Die Suchergebnisse erhalten dann eine Wertung "
"abhängig davon wie gut sie mit den Trigrammen übereinstimmen.\n"
" Ein Vorteil der Trigramm-Suche ist das korrekte Suchwörter wie "
"\"Apfel\", Tippfehler in Suchfeldern (wie z.B. \"Afpel\") finden.\n"
" "
#: .\cookbook\templates\search_info.html:69
msgid "Search Fields"
msgstr "Suchfelder"
#: .\cookbook\templates\search_info.html:73
msgid ""
" \n"
" Unaccent is a special case in that it enables searching a field "
"'unaccented' for each search style attempting to ignore accented values. \n"
" For example when you enable unaccent for 'Name' any search "
"(starts with, contains, trigram) will attempt the search ignoring accented "
"characters.\n"
" \n"
" For the other options, you can enable search on any or all "
"fields and they will be combined together with an assumed 'OR'.\n"
" For example enabling 'Name' for Starts With, 'Name' and "
"'Description' for Partial Match and 'Ingredients' and 'Keywords' for Full "
"Search\n"
" and searching for 'apple' will generate a search that will "
"return recipes that have:\n"
" - A recipe name that starts with 'apple'\n"
" - OR a recipe name that contains 'apple'\n"
" - OR a recipe description that contains 'apple'\n"
" - OR a recipe that will have a full text search match ('apple' "
"or 'apples') in ingredients\n"
" - OR a recipe that will have a full text search match in "
"Keywords\n"
"\n"
" Combining too many fields in too many types of search can have a "
"negative impact on performance, create duplicate results or return "
"unexpected results.\n"
" For example, enabling fuzzy search or partial matches will "
"interfere with web search methods. \n"
" Searching for 'apple -pie' with fuzzy search and full text "
"search will return the recipe Apple Pie. Though it is not included in the "
"full text results, it does match the trigram results.\n"
" "
msgstr ""
" \n"
" Unbetonte Suche ist ein spezieller Typ welcher erlaubt ein Feld "
"\"unbetont\" für jeden Suchtyp zu durchsuchen. \n"
" Wenn die unbetonte Suche Beispielsweise für das Feld \"Name\" "
"aktiviert werden, wird jeder Suchtyp auf diesem Feld versuchen unbetont zu "
"suchen.\n"
" \n"
" Wen die Suche für weitere Felder aktiviert wird, versteht die "
"Suche dies als \"oder\".\n"
" Beispielsweise könnte man \"Name\" als Beginnt mit , \"Name\" "
"und \"Description\" als Partielle Treffer und \"Inhaltstoffe\" sowie "
"\"Keyword\" im Volltext durchsuchen \n"
" Eine Suche nach \"Apfel\" wird dann zu folgenden Ergebnissen "
"führen::\n"
" - Rezeptname beginnt mit \"Apfel\"\n"
" - ODER Rezeptname enthält \"Apfel\"\n"
" - ODER Rezeptbeschreibung enthält \"Apfel'\n"
" - ODER das Rezept hat einen Volltext-Treffer in den "
"Inhaltstoffen\n"
" - ODER das Rezept hat einen Volltext-Treffer in den Keywords\n"
"\n"
" Das Kombinieren von zu vielen Suchtypen kann negative Einflüsse "
"auf die Geschwindigkeit der Suche haben, doppelte Ergebnisse verursachen "
"oder unerwartete Ergebnisse liefern.\n"
" Beispielsweise führt das aktivieren der Ungenauen Suche zu "
"Problemen in Kombination mit dem Suchtyp Web.\n"
" "
#: .\cookbook\templates\search_info.html:95
msgid "Search Index"
msgstr "Suchindex"
#: .\cookbook\templates\search_info.html:99
msgid ""
" \n"
" Trigram search and Full Text Search both rely on database "
"indexes to perform effectively. \n"
" You can rebuild the indexes on all fields in the Admin page for "
"Recipes and selecting all recipes and running 'rebuild index for selected "
"recipes'\n"
" You can also rebuild indexes at the command line by executing "
"the management command 'python manage.py rebuildindex'\n"
" "
msgstr ""
" \n"
" Die Trigramm und Volltext-Suche benötigen Indizes auf der "
"Datenbank um schnell Ergebnisse zu liefern. \n"
" Die Indizes für alle Felder können auf der Admin Seite neu "
"erstellt werden.'\n"
" Ansonsten können die Indizes auch mit dem management command "
"'python manage.py rebuildindex' neu erstellt werden.\n"
" "
#: .\cookbook\templates\setup.html:6
msgid "Cookbook Setup"
msgstr "Kochbuch-Setup"
#: .\cookbook\templates\setup.html:14
msgid "Setup"
msgstr "Setup"
#: .\cookbook\templates\setup.html:15
msgid ""
"To start using this application you must first create a superuser account."
msgstr ""
"Um diese Anwendung zu benutzen, muss zunächst ein Administrator-Account "
"erstellt werden."
#: .\cookbook\templates\setup.html:20
msgid "Create Superuser account"
msgstr "Administrator-Account Erstellen"
#: .\cookbook\templates\socialaccount\authentication_error.html:7
#: .\cookbook\templates\socialaccount\authentication_error.html:23
msgid "Social Network Login Failure"
msgstr "Fehler beim Anmelden via sozialem Netzwerk"
#: .\cookbook\templates\socialaccount\authentication_error.html:25
msgid ""
"An error occurred while attempting to login via your social network account."
msgstr ""
"Es ist ein Fehler aufgetreten bei der Anmeldung über dein soziales Netzwerk."
#: .\cookbook\templates\socialaccount\connections.html:4
#: .\cookbook\templates\socialaccount\connections.html:7
msgid "Account Connections"
msgstr "Account-Verbindungen"
#: .\cookbook\templates\socialaccount\connections.html:10
msgid ""
"You can sign in to your account using any of the following third party\n"
" accounts:"
msgstr ""
"Du kannst dich mit den folgenden Drittanbieter-Accounts\n"
" anmelden:"
#: .\cookbook\templates\socialaccount\connections.html:44
msgid ""
"You currently have no social network accounts connected to this account."
msgstr ""
"Du hast momentan keine Social-Media Accounts mit diesem Account verbunden."
#: .\cookbook\templates\socialaccount\connections.html:47
msgid "Add a 3rd Party Account"
msgstr "Fremden Account hinzufügen"
#: .\cookbook\templates\socialaccount\login.html:5
#: .\cookbook\templates\socialaccount\signup.html:5
msgid "Signup"
msgstr "Registrierung"
#: .\cookbook\templates\socialaccount\login.html:9
#, python-format
msgid "Connect %(provider)s"
msgstr "Verbinde zu %(provider)s"
#: .\cookbook\templates\socialaccount\login.html:11
#, python-format
msgid "You are about to connect a new third party account from %(provider)s."
msgstr "Die Anmeldung über %(provider)s wird eingerichtet."
#: .\cookbook\templates\socialaccount\login.html:13
#, python-format
msgid "Sign In Via %(provider)s"
msgstr "Über %(provider)s anmelden"
#: .\cookbook\templates\socialaccount\login.html:15
#, python-format
msgid "You are about to sign in using a third party account from %(provider)s."
msgstr "Die Anmeldung erfolgt über %(provider)s."
#: .\cookbook\templates\socialaccount\login.html:20
msgid "Continue"
msgstr "Weiter"
#: .\cookbook\templates\socialaccount\signup.html:10
#, python-format
msgid ""
"You are about to use your\n"
" %(provider_name)s account to login to\n"
" %(site_name)s. As a final step, please complete the following form:"
msgstr ""
"Du wirst via\n"
" %(provider_name)s eingeloggt.\n"
" %(site_name)s. Fülle bitte vorher noch diese Formular aus:"
#: .\cookbook\templates\socialaccount\signup.html:32
#, fuzzy
#| msgid "I accept the follwoing"
msgid "I accept the following"
msgstr "Ich akzeptiere folgendes"
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:23
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:31
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:39
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:47
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:55
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:63
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:71
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:79
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:87
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:95
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:103
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:111
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:119
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:127
msgid "Sign in using"
msgstr "Einloggen mit"
#: .\cookbook\templates\space_overview.html:6
msgid "Overview"
msgstr "Übersicht"
#: .\cookbook\templates\space_overview.html:13
msgid "Space"
msgstr "Space"
#: .\cookbook\templates\space_overview.html:17
msgid ""
"Recipes, foods, shopping lists and more are organized in spaces of one or "
"more people."
msgstr ""
"Rezepte, Lebensmittel, Einkaufslisten und weiteres werden Instanzen mit "
"einem oder mehreren Mitgliedern zugeordnet."
#: .\cookbook\templates\space_overview.html:18
msgid ""
"You can either be invited into an existing space or create your own one."
msgstr ""
"Du kannst entweder in einen existierenden Space eingeladen werden oder "
"Deinen eigenen erstellen."
#: .\cookbook\templates\space_overview.html:25
msgid "Your Spaces"
msgstr "Deine Spaces"
#: .\cookbook\templates\space_overview.html:53
msgid "Owner"
msgstr "Eigentümer"
#: .\cookbook\templates\space_overview.html:73
#: .\cookbook\templates\space_overview.html:83
msgid "Join Space"
msgstr "Space beitreten"
#: .\cookbook\templates\space_overview.html:76
msgid "Join an existing space."
msgstr "Existierenden Space beitreten."
#: .\cookbook\templates\space_overview.html:78
msgid ""
"To join an existing space either enter your invite token or click on the "
"invite link the space owner send you."
msgstr ""
"Um einem existierenden Space beizutreten, kannst Du entweder den "
"Einladungstoken eingeben oder auf den Einladungslink des Space-Eigentümers "
"klicken."
#: .\cookbook\templates\space_overview.html:91
#: .\cookbook\templates\space_overview.html:100
msgid "Create Space"
msgstr "Space erstellen"
#: .\cookbook\templates\space_overview.html:94
msgid "Create your own recipe space."
msgstr "Erstelle Deinen eigenen Rezept-Space."
#: .\cookbook\templates\space_overview.html:96
msgid "Start your own recipe space and invite other users to it."
msgstr "Starte deinen eigenen Rezept-Space und lade andere Benutzer ein."
#: .\cookbook\templates\system.html:23
msgid "System"
msgstr "System"
#: .\cookbook\templates\system.html:24
#, fuzzy
#| msgid ""
#| "\n"
#| " Django Recipes is an open source free software application. It "
#| "can be found on\n"
#| " GitHub.\n"
#| " Changelogs can be found here.\n"
#| " "
msgid ""
"\n"
" Tandoor Recipes is an open source free software application. It can "
"be found on\n"
" GitHub.\n"
" Changelogs can be found here.\n"
" "
msgstr ""
"\n"
" Django Recipes ist eine kostenlose OpenSource-Anwendung. Der "
"Quellcode befindet sich auf\n"
" GitHub.\n"
" Eine Übersicht über alle Änderungen findet sich hier.\n"
" "
#: .\cookbook\templates\system.html:30
msgid "System Information"
msgstr "Systeminformation"
#: .\cookbook\templates\system.html:51
msgid ""
"\n"
" You need to execute version.py in your update "
"script to generate version information (done automatically in docker).\n"
" "
msgstr ""
"\n"
" Führe version.py im update script durch, um "
"Informationen zur Version anzuzeigen (wird automatisch von docker "
"durchgeführt).\n"
" "
#: .\cookbook\templates\system.html:56
msgid "Plugins"
msgstr ""
#: .\cookbook\templates\system.html:67
msgid "Media Serving"
msgstr "Medien ausliefern"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:123 .\cookbook\templates\system.html:134
msgid "Warning"
msgstr "Warnung"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:125 .\cookbook\templates\system.html:134
msgid "Ok"
msgstr "Ok"
#: .\cookbook\templates\system.html:70
msgid ""
"Serving media files directly using gunicorn/python is not recommend!\n"
" Please follow the steps described\n"
" here to update\n"
" your installation.\n"
" "
msgstr ""
"Das direkte ausliefern von Mediendateien mit gunicorn/python ist nicht "
"empfehlenswert! Bitte folge den beschriebenen Schritten hier, um "
"Ihre Installation zu aktualisieren.\n"
" "
#: .\cookbook\templates\system.html:76 .\cookbook\templates\system.html:91
#: .\cookbook\templates\system.html:104 .\cookbook\templates\system.html:115
#: .\cookbook\views\views.py:168
msgid "Everything is fine!"
msgstr "Alles in Ordnung!"
#: .\cookbook\templates\system.html:80
msgid "Secret Key"
msgstr "Geheimer Schlüssel"
#: .\cookbook\templates\system.html:84
msgid ""
"\n"
" You do not have a SECRET_KEY configured in your "
".env file. Django defaulted to the\n"
" standard key\n"
" provided with the installation which is publicly know and "
"insecure! Please set\n"
" SECRET_KEY int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" Du hast keinen SECRET_KEY in deiner .env"
"code>-Datei konfiguriert. Django verwendet standardmäßig den mit der "
"Installation gelieferten Standardschlüssel, der öffentlich bekannt und "
"unsicher ist! Bitte setze den SECRET_KEY in der "
"Konfigurationsdatei .env.\n"
" "
#: .\cookbook\templates\system.html:94
msgid "Debug Mode"
msgstr "Debug-Modus"
#: .\cookbook\templates\system.html:98
msgid ""
"\n"
" This application is still running in debug mode. This is most "
"likely not needed. Turn of debug mode by\n"
" setting\n"
" DEBUG=0 int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" Diese Anwendung läuft noch im Debug-Modus. Dieser wird "
"höchstwahrscheinlich nicht benötigt.\n"
"Schalte den Debug-Modus aus, indem du DEBUG=0 in der "
"Konfigurationsdatei .env einstellst.\n"
" "
#: .\cookbook\templates\system.html:107
msgid "Allowed Hosts"
msgstr "Erlaubte Hosts"
#: .\cookbook\templates\system.html:111
msgid ""
"\n"
" Your allowed hosts are configured to allow every host. This "
"might be ok in some setups but should be avoided. Please see the docs about "
"this.\n"
" "
msgstr ""
"\n"
" Die erlaubten Hosts sind so konfiguriert, dass sie jeden Host "
"erlauben. Das mag in einigen Fällen in Ordnung sein, sollte aber im "
"Regelfall vermieden werden. Bitte lies in der Dokumentation dazu nach.\n"
" "
#: .\cookbook\templates\system.html:118
msgid "Database"
msgstr "Datenbank"
#: .\cookbook\templates\system.html:121
msgid "Info"
msgstr "Info"
#: .\cookbook\templates\system.html:131 .\cookbook\templates\system.html:148
msgid "Migrations"
msgstr "Migrationen"
#: .\cookbook\templates\system.html:137
msgid ""
"\n"
" Migrations should never fail!\n"
" Failed migrations will likely cause major parts of the app to "
"not function correctly.\n"
" If a migration fails make sure you are on the latest version and "
"if so please post the migration log and the overview below in a GitHub "
"issue.\n"
" "
msgstr ""
"\n"
" Migrationen sollten niemals fehlschlagen!\n"
" Fehlgeschlagene Migrationen werden wahrscheinlich dazu führen, "
"dass wichtige Teile der App nicht mehr korrekt funktionieren.\n"
" Wenn eine Migration fehlschlägt, vergewissern Sie sich, dass Sie "
"die neueste Version verwenden. Wenn ja, posten Sie bitte das "
"Migrationsprotokoll und die Übersicht unten in einem GitHub-Problem.\n"
" "
#: .\cookbook\templates\system.html:238
msgid "False"
msgstr "Falsch"
#: .\cookbook\templates\system.html:238
msgid "True"
msgstr "Wahr"
#: .\cookbook\templates\system.html:268
msgid "Hide"
msgstr "Verbergen"
#: .\cookbook\templates\system.html:271
msgid "Show"
msgstr "Anzeigen"
#: .\cookbook\templates\test2.html:6
msgid "Export Recipes"
msgstr "Rezepte exportieren"
#: .\cookbook\templates\test2.html:14 .\cookbook\templates\test2.html:20
msgid "Export"
msgstr "Exportieren"
#: .\cookbook\views\api.py:198 .\cookbook\views\api.py:326
msgid "Parameter updated_at incorrectly formatted"
msgstr "Der Parameter updated_at ist falsch formatiert"
#: .\cookbook\views\api.py:351 .\cookbook\views\api.py:484
#, python-brace-format
msgid "No {self.basename} with id {pk} exists"
msgstr "Kein {self.basename} mit der ID {pk} existiert"
#: .\cookbook\views\api.py:355
msgid "Cannot merge with the same object!"
msgstr "Zusammenführen mit selben Objekt nicht möglich!"
#: .\cookbook\views\api.py:362
#, python-brace-format
msgid "No {self.basename} with id {target} exists"
msgstr "Kein {self.basename} mit der ID {target} existiert"
#: .\cookbook\views\api.py:367
msgid "Cannot merge with child object!"
msgstr "Zusammenführen mit untergeordnetem Objekt nicht möglich!"
#: .\cookbook\views\api.py:405
#, python-brace-format
msgid "{source.name} was merged successfully with {target.name}"
msgstr "{source.name} wurde erfolgreich mit {target.name} zusammengeführt"
#: .\cookbook\views\api.py:411
#, python-brace-format
msgid "An error occurred attempting to merge {source.name} with {target.name}"
msgstr ""
"Beim zusammenführen von {source.name} mit {target.name} ist ein Fehler "
"aufgetreten"
#: .\cookbook\views\api.py:493
#, python-brace-format
msgid "{child.name} was moved successfully to the root."
msgstr "{child.name} wurde erfolgreich zur Wurzel verschoben."
#: .\cookbook\views\api.py:496 .\cookbook\views\api.py:514
msgid "An error occurred attempting to move "
msgstr "Fehler aufgetreten beim verschieben von "
#: .\cookbook\views\api.py:499
msgid "Cannot move an object to itself!"
msgstr "Ein Element kann nicht in sich selbst verschoben werden!"
#: .\cookbook\views\api.py:505
#, python-brace-format
msgid "No {self.basename} with id {parent} exists"
msgstr "Kein {self.basename} mit ID {parent} existiert"
#: .\cookbook\views\api.py:511
#, python-brace-format
msgid "{child.name} was moved successfully to parent {parent.name}"
msgstr ""
"{child.name} wurde erfolgreich zum Überelement {parent.name} verschoben"
#: .\cookbook\views\api.py:992
#, python-brace-format
msgid "{obj.name} was removed from the shopping list."
msgstr "{obj.name} wurde von der Einkaufsliste entfernt."
#: .\cookbook\views\api.py:997 .\cookbook\views\api.py:1627
#, python-brace-format
msgid "{obj.name} was added to the shopping list."
msgstr "{obj.name} wurde der Einkaufsliste hinzugefügt."
#: .\cookbook\views\api.py:1239
#, fuzzy
#| msgid "Filter meal plans from date (inclusive) in the format of YYYY-MM-DD."
msgid "Filter meal plans from date (inclusive)."
msgstr ""
"Filtern Sie Essenspläne ab Datum (einschließlich) im Format JJJJ-MM-TT."
#: .\cookbook\views\api.py:1241
#, fuzzy
#| msgid "Filter meal plans to date (inclusive) in the format of YYYY-MM-DD."
msgid "Filter meal plans to date (inclusive)."
msgstr ""
"Filtern Sie die Essenspläne nach Datum (einschließlich) im Format JJJJ-MM-TT."
#: .\cookbook\views\api.py:1244
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
msgstr ""
"Filtern Sie Mahlzeitenpläne nach der MealType ID. Für mehrere "
"Wiederholungsparameter."
#: .\cookbook\views\api.py:1404
msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgstr ""
"ID des Rezeptes zu dem ein Schritt gehört. Kann mehrfach angegeben werden."
#: .\cookbook\views\api.py:1406
msgid "Query string matched (fuzzy) against object name."
msgstr "Abfragezeichenfolge, die mit dem Objektnamen übereinstimmt (ungenau)."
#: .\cookbook\views\api.py:1442
msgid ""
"Query string matched (fuzzy) against recipe name. In the future also "
"fulltext search."
msgstr ""
"Suchbegriff wird mit dem Rezeptnamen abgeglichen. In Zukunft auch "
"Volltextsuche."
#: .\cookbook\views\api.py:1444
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr ""
"ID des Stichwortes, das ein Rezept haben muss. Kann mehrfach angegeben "
"werden. Äquivalent zu keywords_or"
#: .\cookbook\views\api.py:1445
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr ""
"Stichwort IDs. Kann mehrfach angegeben werden. Listet Rezepte zu jedem der "
"angegebenen Stichwörter"
#: .\cookbook\views\api.py:1446
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr ""
"Stichwort IDs. Kann mehrfach angegeben werden. Listet Rezepte mit allen "
"angegebenen Stichwörtern."
#: .\cookbook\views\api.py:1447
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr ""
"Stichwort ID. Kann mehrfach angegeben werden. Schließt Rezepte einem der "
"angegebenen Stichwörtern aus."
#: .\cookbook\views\api.py:1448
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr ""
"Stichwort IDs. Kann mehrfach angegeben werden. Schließt Rezepte mit allen "
"angegebenen Stichwörtern aus."
#: .\cookbook\views\api.py:1450
msgid "ID of food a recipe should have. For multiple repeat parameter."
msgstr ""
"ID einer Zutat, zu der Rezepte gelistet werden sollen. Kann mehrfach "
"angegeben werden."
#: .\cookbook\views\api.py:1451
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr ""
"Zutat ID. Kann mehrfach angegeben werden. Listet Rezepte mindestens einer "
"der Zutaten"
#: .\cookbook\views\api.py:1452
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr ""
"Zutat ID. Kann mehrfach angegeben werden. Listet Rezepte mit allen "
"angegebenen Zutaten."
#: .\cookbook\views\api.py:1453
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr ""
"Zutat ID. Kann mehrfach angegeben werden. Schließt Rezepte aus, die eine der "
"angegebenen Zutaten enthalten."
#: .\cookbook\views\api.py:1454
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr ""
"Zutat ID. Kann mehrfach angegeben werden. Schließt Rezepte aus, die alle "
"angegebenen Zutaten enthalten."
#: .\cookbook\views\api.py:1456
msgid "ID of book a recipe should be in. For multiple repeat parameter."
msgstr "Buch ID, in dem das Rezept ist. Kann mehrfach angegeben werden."
#: .\cookbook\views\api.py:1457
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr ""
"Buch ID. Kann mehrfach angegeben werden. Listet alle Rezepte aus den "
"angegebenen Büchern"
#: .\cookbook\views\api.py:1458
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr ""
"Buch ID. Kann mehrfach angegeben werden. Listet die Rezepte, die in allen "
"Büchern enthalten sind."
#: .\cookbook\views\api.py:1459
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr ""
"Buch IDs. Kann mehrfach angegeben werden. Schließt Rezepte aus den "
"angegebenen Büchern aus."
#: .\cookbook\views\api.py:1460
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr ""
"Buch IDs. Kann mehrfach angegeben werden. Schließt Rezepte aus, die in allen "
"angegebenen Büchern enthalten sind."
#: .\cookbook\views\api.py:1462
msgid "ID of unit a recipe should have."
msgstr "ID der Einheit, die ein Rezept haben sollte."
#: .\cookbook\views\api.py:1464
msgid "Exact rating of recipe"
msgstr ""
#: .\cookbook\views\api.py:1465
#, fuzzy
#| msgid "Rating a recipe should have. [0 - 5]"
msgid "Rating a recipe should have or greater."
msgstr "Bewertung, die ein Rezept haben sollte. [ 0 - 5]"
#: .\cookbook\views\api.py:1466
#, fuzzy
#| msgid "Rating a recipe should have. [0 - 5]"
msgid "Rating a recipe should have or smaller."
msgstr "Bewertung, die ein Rezept haben sollte. [ 0 - 5]"
#: .\cookbook\views\api.py:1468
msgid "Filter recipes cooked X times."
msgstr ""
#: .\cookbook\views\api.py:1469
#, fuzzy
#| msgid ""
#| "Filter recipes cooked X times or more. Negative values returns cooked "
#| "less than X times"
msgid "Filter recipes cooked X times or more."
msgstr ""
"Rezepte listen, die mindestens x-mal gekocht wurden. Eine negative Zahl "
"listet Rezepte, die weniger als x-mal gekocht wurden"
#: .\cookbook\views\api.py:1470
msgid "Filter recipes cooked X times or less."
msgstr ""
#: .\cookbook\views\api.py:1472
#, fuzzy
#| msgid "Filter for entries with the given recipe"
msgid "Filter recipes created on the given date."
msgstr "Filter für Einträge mit dem angegebenen Rezept"
#: .\cookbook\views\api.py:1473
#, fuzzy
#| msgid ""
#| "Filter recipes created on or after YYYY-MM-DD. Prepending - filters on or "
#| "before date."
msgid "Filter recipes created on the given date or after."
msgstr ""
"Rezepte listen, die am angegebenen Datum oder später erstellt wurden. Wenn - "
"vorangestellt wird, wird am oder vor dem Datum gelistet."
#: .\cookbook\views\api.py:1474
#, fuzzy
#| msgid ""
#| "Filter recipes created on or after YYYY-MM-DD. Prepending - filters on or "
#| "before date."
msgid "Filter recipes created on the given date or before."
msgstr ""
"Rezepte listen, die am angegebenen Datum oder später erstellt wurden. Wenn - "
"vorangestellt wird, wird am oder vor dem Datum gelistet."
#: .\cookbook\views\api.py:1476 .\cookbook\views\api.py:1477
#: .\cookbook\views\api.py:1478
#, fuzzy
#| msgid "Filter for entries with the given recipe"
msgid "Filter recipes updated on the given date."
msgstr "Filter für Einträge mit dem angegebenen Rezept"
#: .\cookbook\views\api.py:1480
#, fuzzy
#| msgid ""
#| "Filter recipes last cooked on or after YYYY-MM-DD. Prepending - filters "
#| "on or before date."
msgid "Filter recipes last cooked on the given date or after."
msgstr ""
"Rezepte anzeigen, die zuletzt am angegebenen Datum oder später gekocht "
"wurden. Mit vorangestelltem - , werden Rezepte am oder vor dem Datum "
"gelistet."
#: .\cookbook\views\api.py:1481
#, fuzzy
#| msgid ""
#| "Filter recipes last cooked on or after YYYY-MM-DD. Prepending - filters "
#| "on or before date."
msgid "Filter recipes last cooked on the given date or before."
msgstr ""
"Rezepte anzeigen, die zuletzt am angegebenen Datum oder später gekocht "
"wurden. Mit vorangestelltem - , werden Rezepte am oder vor dem Datum "
"gelistet."
#: .\cookbook\views\api.py:1483 .\cookbook\views\api.py:1484
#, fuzzy
#| msgid ""
#| "Filter recipes lasts viewed on or after YYYY-MM-DD. Prepending - filters "
#| "on or before date."
msgid "Filter recipes lasts viewed on the given date."
msgstr ""
"Rezepte listen, die am angegebenen Datum oder später zuletzt angesehen "
"wurden. Wenn - vorangestellt wird, wird am oder vor dem Datum gelistet."
#: .\cookbook\views\api.py:1486
#, fuzzy
#| msgid "Filter for entries with the given recipe"
msgid "Filter recipes for ones created by the given user ID"
msgstr "Filter für Einträge mit dem angegebenen Rezept"
#: .\cookbook\views\api.py:1487
msgid "If only internal recipes should be returned. [true/false]"
msgstr "Nur interne Rezepte sollen gelistet werden. [ja/nein]"
#: .\cookbook\views\api.py:1488
msgid "Returns the results in randomized order. [true/false]"
msgstr ""
"Die Suchergebnisse sollen in zufälliger Reihenfolge gelistet werden. [ja/"
"nein]"
#: .\cookbook\views\api.py:1490
msgid ""
"Determines the order of the results. Options are: score,-score,name,-name,"
"lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-"
"created_at,lastviewed,-lastviewed"
msgstr ""
#: .\cookbook\views\api.py:1492
msgid "Returns new results first in search results. [true/false]"
msgstr ""
"Die neuesten Suchergebnisse sollen zuerst angezeigt werden. [ja/nein]"
#: .\cookbook\views\api.py:1493
msgid ""
"Returns the given number of recently viewed recipes before search results "
"(if given)"
msgstr ""
#: .\cookbook\views\api.py:1494
msgid "ID of a custom filter. Returns all recipes matched by that filter."
msgstr ""
#: .\cookbook\views\api.py:1495
msgid "Filter recipes that can be made with OnHand food. [true/false]"
msgstr ""
"Rezepte listen, die mit vorhandenen Zutaten gekocht werden können. [ja/"
"nein]"
#: .\cookbook\views\api.py:1773
msgid ""
"Return the PropertyTypes matching the property category. Repeat for "
"multiple."
msgstr ""
#: .\cookbook\views\api.py:1804 .\cookbook\views\api.py:1860
#, fuzzy
#| msgid "Filter for entries with the given recipe"
msgid "Returns only entries associated with the given mealplan id"
msgstr "Filter für Einträge mit dem angegebenen Rezept"
#: .\cookbook\views\api.py:1858
msgid ""
"Returns only elements updated after the given timestamp in ISO 8601 format."
msgstr ""
#: .\cookbook\views\api.py:2031
#, fuzzy
#| msgid ""
#| "Return the Automations matching the automation type. Multiple values "
#| "allowed."
msgid ""
"Return the Automations matching the automation type. Repeat for multiple."
msgstr ""
"Zeigt Automationen, die dem Automationstyp entsprechen. Kann mehrfach "
"angegeben werden."
#: .\cookbook\views\api.py:2048
msgid ""
"Text field to store data that gets carried over to the UserSpace created "
"from the InviteLink"
msgstr ""
#: .\cookbook\views\api.py:2049
msgid "Only return InviteLinks that have not been used yet."
msgstr ""
#: .\cookbook\views\api.py:2076
#, fuzzy
#| msgid ""
#| "Return the Automations matching the automation type. Multiple values "
#| "allowed."
msgid "Return the CustomFilters matching the model type. Repeat for multiple."
msgstr ""
"Zeigt Automationen, die dem Automationstyp entsprechen. Kann mehrfach "
"angegeben werden."
#: .\cookbook\views\api.py:2176
msgid "Nothing to do."
msgstr "Nichts zu tun."
#: .\cookbook\views\api.py:2222
msgid "Invalid Url"
msgstr "Ungültige URL"
#: .\cookbook\views\api.py:2228
msgid "Connection Refused."
msgstr "Verbindung fehlgeschlagen."
#: .\cookbook\views\api.py:2232
msgid "Bad URL Schema."
msgstr "Ungültiges URL Schema."
#: .\cookbook\views\api.py:2257
msgid "No usable data could be found."
msgstr "Es konnten keine passenden Daten gefunden werden."
#: .\cookbook\views\api.py:2286 .\cookbook\views\api.py:2434
msgid "You must select an AI provider to perform your request."
msgstr ""
#: .\cookbook\views\api.py:2293 .\cookbook\views\api.py:2441
msgid ""
"You don't have any credits remaining to use AI or AI features are not "
"enabled for your space."
msgstr ""
#: .\cookbook\views\api.py:2499 .\cookbook\views\api.py:2667
msgid "File is above space limit"
msgstr "Datei überschreitet das Speicherplatzlimit"
#: .\cookbook\views\api.py:2522 .\cookbook\views\api.py:2684
msgid "Importing is not implemented for this provider"
msgstr "Importieren ist für diesen Anbieter noch nicht implementiert"
#: .\cookbook\views\api.py:2548
msgid ""
"The PDF Exporter is not enabled on this instance as it is still in an "
"experimental state."
msgstr ""
"Der PDF-Exporter ist in dieser Instanz nicht aktiviert, da er sich noch in "
"einem experimentellen Zustand befindet."
#: .\cookbook\views\api.py:2842
msgid "This feature is not yet available in the hosted version of tandoor!"
msgstr "Diese Funktion ist in dieser Version von Tandoor noch nicht verfügbar!"
#: .\cookbook\views\api.py:2863
msgid "Sync successful!"
msgstr "Synchronisation erfolgreich!"
#: .\cookbook\views\api.py:2866
msgid "Error synchronizing with Storage"
msgstr "Fehler beim Synchronisieren"
#: .\cookbook\views\views.py:89
msgid "This feature is not available in the demo version!"
msgstr "Diese Funktion ist in der Demo-Version nicht verfügbar!"
#: .\cookbook\views\views.py:110
msgid ""
"You have successfully created your own recipe space. Start by adding some "
"recipes or invite other people to join you."
msgstr ""
"Du hast erfolgreich deinen eigenen Rezept-Space erstellt. Beginne, indem Du "
"ein paar Rezepte hinzufügst oder weitere Leute einlädst."
#: .\cookbook\views\views.py:171
#, python-format
msgid "PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!"
msgstr ""
"PostgreSQL %(v)s ist veraltet. Aktualisieren Sie auf eine vollständig "
"unterstützte Version!"
#: .\cookbook\views\views.py:174
#, python-format
msgid "You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended"
msgstr "Sie verwenden PostgreSQL %(v1)s. PostgreSQL %(v2)s wird empfohlen"
#: .\cookbook\views\views.py:178
msgid "Unable to determine PostgreSQL version."
msgstr "PostgreSQL version konnte nicht erkannt werden."
#: .\cookbook\views\views.py:182
msgid ""
"This application is not running with a Postgres database backend. This is ok "
"but not recommended as some features only work with postgres databases."
msgstr ""
"Diese Anwendung läuft nicht mit einer PostgreSQL Datenbank. Dies ist in "
"Ordnung, wird aber nicht empfohlen, da einige Funktionen nur mit einer "
"PostgreSQL-Datenbanken funktionieren."
#: .\cookbook\views\views.py:296
msgid ""
"The setup page can only be used to create the first "
"user! If you have forgotten your superuser credentials "
"please consult the django documentation on how to reset passwords."
msgstr ""
"Die Setup-Seite kann nur für den ersten Nutzer verwendet "
"werden. Falls du die Superuser Logindaten vergessen "
"hast, folge bitte der Django-Dokumentation um Passwörter zurückzusetzen."
#: .\cookbook\views\views.py:304
msgid "Passwords dont match!"
msgstr "Passwörter stimmen nicht überein!"
#: .\cookbook\views\views.py:312
msgid "User has been created, please login!"
msgstr "Benutzer wurde erstellt, bitte einloggen!"
#: .\cookbook\views\views.py:352
msgid ""
"Reporting share links is not enabled for this instance. Please notify the "
"page administrator to report problems."
msgstr ""
"Das melden von Links ist in dieser Instanz nicht aktiviert. Bitte "
"kontaktieren sie den Seitenadministrator um Probleme zu melden."
#: .\cookbook\views\views.py:357
msgid ""
"Recipe sharing link has been disabled! For additional information please "
"contact the page administrator."
msgstr ""
"Dieser Link wurde deaktiviert! Bitte kontaktieren sie den "
"Seitenadministrator für weitere Informationen."
#: .\cookbook\views\views.py:383
msgid "Manage recipes, shopping list, meal plans and more."
msgstr "Manage Rezepte, Einkaufslisten Essenspläne und mehr."
#: .\cookbook\views\views.py:397 .\cookbook\views\views.py:398
msgid "Plan"
msgstr "Plan"
#: .\cookbook\views\views.py:399
msgid "View your meal Plan"
msgstr "Betrachte deinen Essensplan"
#: .\cookbook\views\views.py:418
msgid "View your shopping lists"
msgstr "Zeige deine Einkaufslisten"
#~ msgid ""
#~ "Both fields are optional. If none are given the username will be "
#~ "displayed instead"
#~ msgstr ""
#~ "Beide Felder sind optional. Wenn keins von beiden gegeben ist, wird der "
#~ "Nutzername angezeigt"
#~ msgid "Name"
#~ msgstr "Name"
#~ msgid "Keywords"
#~ msgstr "Schlagwörter"
#~ msgid "Preparation time in minutes"
#~ msgstr "Zubereitungszeit in Minuten"
#~ msgid "Waiting time (cooking/baking) in minutes"
#~ msgstr "Wartezeit (kochen/backen) in Minuten"
#~ msgid "Path"
#~ msgstr "Pfad"
#~ msgid "Storage UID"
#~ msgstr "Speicher-UID"
#~ msgid "Add your comment: "
#~ msgstr "Schreibe einen Kommentar: "
#~ msgid "Leave empty for dropbox and enter app password for nextcloud."
#~ msgstr "Für Dropbox leer lassen, bei Nextcloud App-Passwort eingeben."
#~ msgid "Leave empty for nextcloud and enter api token for dropbox."
#~ msgstr "Für Nextcloud leer lassen, für Dropbox API-Token eingeben."
#~ msgid ""
#~ "Leave empty for dropbox and enter only base url for nextcloud (/"
#~ "remote.php/webdav/ is added automatically)"
#~ msgstr ""
#~ "Für Dropbox leer lassen, für Nextcloud Server-URL angeben (/remote."
#~ "php/webdav/ wird automatisch hinzugefügt)"
#~ msgid ""
#~ "Long Lived Access Token for your HomeAssistant instance"
#~ msgstr ""
#~ "Lang gültiger Access Token für deine HomeAssistant Instanz"
#~ msgid "Something like http://homeassistant.local:8123/api"
#~ msgstr "Zum Beispiel http://homeassistant.local:8123/api"
#~ msgid "http://homeassistant.local:8123/api for example"
#~ msgstr "http://homeassistant.local:8123/api zum Beispiel"
#~ msgid "Storage"
#~ msgstr "Speicher"
#~ msgid "Active"
#~ msgstr "Aktiv"
#~ msgid "Search String"
#~ msgstr "Suchwort"
#~ msgid "File ID"
#~ msgstr "Datei-ID"
#~ msgid ""
#~ "Determines how fuzzy a search is if it uses trigram similarity matching "
#~ "(e.g. low values mean more typos are ignored)."
#~ msgstr ""
#~ "Legt fest wie unscharf eine Suche ist, falls Trigramme verwendet werden "
#~ "(i.A. führen niedrigere Werte zum ignorieren von mehr Tippfehlern)."
#~ msgid ""
#~ "Select type method of search. Click here "
#~ "for full description of choices."
#~ msgstr ""
#~ "Suchmethode auswählen. Klicke hier für eine "
#~ "vollständige Erklärung der Optionen."
#~ msgid ""
#~ "Use fuzzy matching on units, keywords and ingredients when editing and "
#~ "importing recipes."
#~ msgstr ""
#~ "Benutze die unscharfe Suche für Einheiten, Schlüsselwörter und Zutaten "
#~ "beim ändern und importieren von Rezepten."
#~ msgid ""
#~ "Fields to search ignoring accents. Selecting this option can improve or "
#~ "degrade search quality depending on language"
#~ msgstr ""
#~ "Felder bei welchen Akzente ignoriert werden. Das aktivieren dieser "
#~ "Option kann die Suchqualität je nach Sprache verbessern oder "
#~ "verschlechtern"
#~ msgid ""
#~ "Fields to search for partial matches. (e.g. searching for 'Pie' will "
#~ "return 'pie' and 'piece' and 'soapie')"
#~ msgstr ""
#~ "Felder welche auf partielle Treffer durchsucht werden. (z.B. eine Suche "
#~ "nach 'Spa' wird 'Spaghetti', 'Spargel' und 'Grünspargel' liefern.)"
#~ msgid ""
#~ "Fields to search for beginning of word matches. (e.g. searching for 'sa' "
#~ "will return 'salad' and 'sandwich')"
#~ msgstr ""
#~ "Felder welche auf übereinstimmenden Wortbeginn durchsucht werden. (z.B. "
#~ "eine Suche nach \"Spa\" wird \"Spaghetti\" und \"Spargel\" liefern.)"
#~ msgid ""
#~ "Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find "
#~ "'recipe'.) Note: this option will conflict with 'web' and 'raw' methods "
#~ "of search."
#~ msgstr ""
#~ "Felder welche \"ungenau\" durchsucht werden sollen. (z.B. eine Suche nach "
#~ "\"Kuhcen\" wird \"Kuchen\" liefern.) Tipp: Diese Option konfligiert mit "
#~ "den \"web\" und \"raw\" Suchtypen."
#~ msgid ""
#~ "Fields to full text search. Note: 'web', 'phrase', and 'raw' search "
#~ "methods only function with fulltext fields."
#~ msgstr ""
#~ "Felder welche im Volltext durchsucht werden sollen. Tipp: Die Suchtypen "
#~ "\"web\", \"raw\" und \"phrase\" funktionieren nur mit Volltext-Feldern."
#~ msgid "Search Method"
#~ msgstr "Suchmethode"
#~ msgid "Fuzzy Lookups"
#~ msgstr "Unscharfe Suche"
#~ msgid "Ignore Accent"
#~ msgstr "Akzente ignorieren"
#~ msgid "Partial Match"
#~ msgstr "Teilweise Übereinstimmung"
#~ msgid "Starts With"
#~ msgstr "Beginnt mit"
#~ msgid "Fuzzy Search"
#~ msgstr "Unpräzise Suche"
#~ msgid "Full Text"
#~ msgstr "Volltext"
#~ msgid " is part of a recipe step and cannot be deleted"
#~ msgstr " ist Teil eines Rezepts und kann nicht gelöscht werden"
#~ msgid "Delete"
#~ msgstr "Löschen"
#~ msgid "Settings"
#~ msgstr "Einstellungen"
#~ msgid "Email"
#~ msgstr "E-Mail"
#~ msgid "Password"
#~ msgstr "Passwort"
#~ msgid "Foods"
#~ msgstr "Lebensmittel"
#~ msgid "Units"
#~ msgstr "Einheiten"
#~ msgid "Supermarket"
#~ msgstr "Supermarkt"
#~ msgid "Supermarket Category"
#~ msgstr "Supermarkt-Kategorie"
#~ msgid "Automations"
#~ msgstr "Automatisierungen"
#~ msgid "Files"
#~ msgstr "Dateien"
#~ msgid "Batch Edit"
#~ msgstr "Massenbearbeitung"
#~ msgid "History"
#~ msgstr "Verlauf"
#~ msgid "Ingredient Editor"
#~ msgstr "Zutateneditor"
#~ msgid "Properties"
#~ msgstr "Eigenschaften"
#~ msgid "Unit Conversions"
#~ msgstr "Einheitenumwandlungen"
#~ msgid "Create"
#~ msgstr "Erstellen"
#~ msgid "External Recipes"
#~ msgstr "Externe Rezepte"
#~ msgid "Space Settings"
#~ msgstr "Space Einstellungen"
#~ msgid "External Connectors"
#~ msgstr "Externe Konnektoren"
#~ msgid "Admin"
#~ msgstr "Admin"
#~ msgid "Markdown Guide"
#~ msgstr "Markdown-Anleitung"
#~ msgid "GitHub"
#~ msgstr "GitHub"
#~ msgid "Translate Tandoor"
#~ msgstr "Tandoor übersetzen"
#~ msgid "API Browser"
#~ msgstr "API Browser"
#~ msgid "Log out"
#~ msgstr "Ausloggen"
#~ msgid "You are using the free version of Tandor"
#~ msgstr "Du benutzt die Gratis-Version von Tandoor"
#~ msgid "Upgrade Now"
#~ msgstr "Jetzt upgraden"
#~ msgid "Batch edit Category"
#~ msgstr "Kategorie-Massenbearbeitung"
#~ msgid "Batch edit Recipes"
#~ msgstr "Rezept-Massenbearbeitung"
#~ msgid "Add the specified keywords to all recipes containing a word"
#~ msgstr ""
#~ "Ausgewählte Schlagwörter zu allen Rezepten, die das Suchwort enthalten, "
#~ "hinzufügen"
#~ msgid "Sync"
#~ msgstr "Synchronisieren"
#~ msgid "Manage watched Folders"
#~ msgstr "Überwachte Ordner verwalten"
#~ msgid ""
#~ "On this Page you can manage all storage folder locations that should be "
#~ "monitored and synced."
#~ msgstr ""
#~ "Auf dieser Seite kannst du alle Ordner verwalten, die überwacht und "
#~ "synchronisiert werden sollen."
#~ msgid "The path must be in the following format"
#~ msgstr "Der Pfad muss folgendes Format haben"
#~ msgid "Save"
#~ msgstr "Speichern"
#~ msgid "Manage External Storage"
#~ msgstr "Externe Speicherquellen verwalten"
#~ msgid "Sync Now!"
#~ msgstr "Jetzt Synchronisieren!"
#~ msgid "Show Recipes"
#~ msgstr "Rezepte anzeigen"
#~ msgid "Show Log"
#~ msgstr "Log anzeigen"
#~ msgid "Importing Recipes"
#~ msgstr "Rezepte werden importiert"
#~ msgid ""
#~ "This can take a few minutes, depending on the number of recipes in sync, "
#~ "please wait."
#~ msgstr ""
#~ "Abhängig von der Anzahl der Rezepte kann dieser Vorgang einige Minuten "
#~ "dauern, bitte gedulde dich ein wenig."
#~ msgid "Recipe Books"
#~ msgstr "Rezeptbuch"
#~ msgid "Import new Recipe"
#~ msgstr "Rezept importieren"
#~ msgid "Edit Recipe"
#~ msgstr "Rezept bearbeiten"
#, python-format
#~ msgid "Are you sure you want to delete the %(title)s: %(object)s "
#~ msgstr ""
#~ "Bist du sicher, dass %(title)s: %(object)s gelöscht werden soll? "
#~ msgid "This cannot be undone!"
#~ msgstr "Dies kann nicht rückgängig gemacht werden!"
#~ msgid "Protected"
#~ msgstr "Geschützt"
#~ msgid "Cascade"
#~ msgstr "Kaskadierung"
#~ msgid "Cancel"
#~ msgstr "Abbrechen"
#~ msgid "Edit"
#~ msgstr "Bearbeiten"
#~ msgid "View"
#~ msgstr "Ansicht"
#~ msgid "Delete original file"
#~ msgstr "Originaldatei löschen"
#~ msgid "List"
#~ msgstr "Liste"
#~ msgid "Filter"
#~ msgstr "Filter"
#~ msgid "Import all"
#~ msgstr "Alle importieren"
#~ msgid "New"
#~ msgstr "Neu"
#~ msgid "previous"
#~ msgstr "vorherige"
#~ msgid "next"
#~ msgstr "nächste"
#~ msgid "View Log"
#~ msgstr "Anschau-Verlauf"
#~ msgid "Cook Log"
#~ msgstr "Kochverlauf"
#~ msgid "Import"
#~ msgstr "Importieren"
#~ msgid "Security Warning"
#~ msgstr "Sicherheitswarnung"
#~ msgid ""
#~ "\n"
#~ " The Password and Token field are stored as plain text"
#~ "b> inside the database.\n"
#~ " This is necessary because they are needed to make API requests, "
#~ "but it also increases the risk of\n"
#~ " someone stealing it.
\n"
#~ " To limit the possible damage tokens or accounts with limited "
#~ "access can be used.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Kennwort und Token werden im Klartext in der "
#~ "Datenbank gespeichert.\n"
#~ " Dies ist notwendig da Kennwort oder Token benötigt werden, um API-"
#~ "Anfragen zu stellen, bringt jedoch auch ein Sicherheitsrisiko mit sich. "
#~ "
\n"
#~ " Um das Risiko zu minimieren sollten, wenn möglich, Tokens oder "
#~ "Accounts mit limitiertem Zugriff verwendet werden.\n"
#~ " "
#~ msgid "You are currently offline!"
#~ msgstr "Du bist gerade offline!"
#~ msgid ""
#~ "The recipes listed below are available for offline viewing because you "
#~ "have recently viewed them. Keep in mind that data might be outdated."
#~ msgstr ""
#~ "Die unterhalb aufgelisteten Rezepte sind offline verfügbar, da du sie vor "
#~ "kurzem angesehen hast. Beachte, dass die Daten veraltetet sein könnten."
#~ msgid "Property Editor"
#~ msgstr "Eigenschaften-Editor"
#~ msgid "Comments"
#~ msgstr "Kommentare"
#~ msgid "by"
#~ msgstr "von"
#~ msgid "Comment"
#~ msgstr "Kommentar"
#~ msgid ""
#~ "There are many options to configure the search depending on your personal "
#~ "preferences."
#~ msgstr ""
#~ "Die Suche kann je nach Präferenz vielfältig Individualisiert werden."
#~ msgid ""
#~ "Usually you do not need to configure any of them and can just "
#~ "stick with either the default or one of the following presets."
#~ msgstr ""
#~ "Im Normalfall ist es nicht notwendig die Einstellung zu verändern. "
#~ "Meist erreichen die Standardeinstellungen oder eine der folgenden "
#~ "Suchprofile sehr gute Suchergebnisse."
#~ msgid ""
#~ "If you do want to configure the search you can read about the different "
#~ "options here."
#~ msgstr ""
#~ "Weitere Informationen zu den einzelnen Optionen sind hier zu finden."
#~ msgid "Fuzzy"
#~ msgstr "Unscharf"
#~ msgid ""
#~ "Find what you need even if your search or the recipe contains typos. "
#~ "Might return more results than needed to make sure you find what you are "
#~ "looking for."
#~ msgstr ""
#~ "Liefert alle erwartbaren Suchergebnisse auch wenn Tippfehler im "
#~ "Suchbegriff sind. Kann jedoch mehr Ergebnisse als notwendig liefern."
#~ msgid "This is the default behavior"
#~ msgstr "Dies ist die Standardeinstellung"
#~ msgid "Apply"
#~ msgstr "Anwenden"
#~ msgid "Precise"
#~ msgstr "Präzise"
#~ msgid ""
#~ "Allows fine control over search results but might not return results if "
#~ "too many spelling mistakes are made."
#~ msgstr ""
#~ "Erlaubt eine feine Steuerung der Suchergebnisse, aber es könnten keine "
#~ "Ergebnisse geliefert werden, wenn zu viele Tippfehler gemacht wurden."
#~ msgid "Perfect for large Databases"
#~ msgstr "Ideal für große Datenbanken"
#~ msgid "Social"
#~ msgstr "Social"
#~ msgid "Space Management"
#~ msgstr "Space-Management"
#~ msgid "Space:"
#~ msgstr "Instanz:"
#~ msgid "Manage Subscription"
#~ msgstr "Tarif verwalten"
#~ msgid "Leave Space"
#~ msgstr "Space verlassen"
#~ msgid "URL Import"
#~ msgstr "URL-Import"
#~ msgid ""
#~ "Rating a recipe should have or greater. [0 - 5] Negative value filters "
#~ "rating less than."
#~ msgstr ""
#~ "Mindestbewertung eines Rezeptes (0-5). Negative Werte filtern nach "
#~ "Maximalbewertung."
#~ msgid ""
#~ "Filter recipes updated on or after YYYY-MM-DD. Prepending - filters on or "
#~ "before date."
#~ msgstr ""
#~ "Rezepte listen, die am angegebenen Datum oder später aktualisiert wurden. "
#~ "Wenn - vorangestellt wird, wird am oder vor dem Datum gelistet."
#~ msgid ""
#~ "Returns the shopping list entry with a primary key of id. Multiple "
#~ "values allowed."
#~ msgstr ""
#~ "Zeigt denjenigen Eintrag auf der Einkaufliste mit der angegebenen ID. "
#~ "Kann mehrfach angegeben werden."
#~ msgid ""
#~ "Filter shopping list entries on checked. [true, false, both, recent"
#~ "b>]
- recent includes unchecked items and recently "
#~ "completed items."
#~ msgstr ""
#~ "Einkaufslisteneinträge nach Häkchen filtern. [ja, nein, beides, "
#~ "kürzlich]
- kürzlich enthält nicht abgehakte "
#~ "Einträge und kürzlich abgeschlossene Einträge."
#~ msgid ""
#~ "Returns the shopping list entries sorted by supermarket category order."
#~ msgstr ""
#~ "Listet die Einträge der Einkaufsliste sortiert nach Supermarktkategorie."
#, python-format
#~ msgid "Batch edit done. %(count)d recipe was updated."
#~ msgid_plural "Batch edit done. %(count)d Recipes where updated."
#~ msgstr[0] ""
#~ "Massenbearbeitung erfolgreich. %(count)d Rezept wurde aktualisiert."
#~ msgstr[1] ""
#~ "Massenbearbeitung erfolgreich. %(count)d Rezepte wurden aktualisiert."
#~ msgid "Monitor"
#~ msgstr "Überwachen"
#~ msgid "Storage Backend"
#~ msgstr "Speicherquelle"
#~ msgid ""
#~ "Could not delete this storage backend as it is used in at least one "
#~ "monitor."
#~ msgstr ""
#~ "Speicherquelle konnte nicht gelöscht werden, da sie in mindestens einem "
#~ "Monitor verwendet wird."
#~ msgid "Connectors Config Backend"
#~ msgstr "Backendkonfiguration für Konnektoren"
#~ msgid "Invite Link"
#~ msgstr "Einladungslink"
#~ msgid "Space Membership"
#~ msgstr "Space-Mitgliedschaft"
#~ msgid "You cannot edit this storage!"
#~ msgstr "Du kannst diese Speicherquelle nicht bearbeiten!"
#~ msgid "Storage saved!"
#~ msgstr "Speicherquelle gespeichert!"
#~ msgid "There was an error updating this storage backend!"
#~ msgstr "Es gab einen Fehler beim Aktualisieren dieser Speicherquelle!"
#~ msgid "Config saved!"
#~ msgstr "Einstellung gespeichert!"
#~ msgid "ConnectorConfig"
#~ msgstr "Konnektor-Config"
#~ msgid "Changes saved!"
#~ msgstr "Änderungen gespeichert!"
#~ msgid "Error saving changes!"
#~ msgstr "Fehler beim Speichern der Daten!"
#~ msgid "Import Log"
#~ msgstr "Importverlauf"
#~ msgid "Discovery"
#~ msgstr "Entdecken"
#~ msgid "Shopping List"
#~ msgstr "Einkaufsliste"
#~ msgid "Connector Config Backend"
#~ msgstr "Backendkonfiguration für Konnektoren"
#~ msgid "Invite Links"
#~ msgstr "Einladungslinks"
#~ msgid "Supermarkets"
#~ msgstr "Supermärkte"
#~ msgid "Shopping Categories"
#~ msgstr "Einkaufskategorien"
#~ msgid "Custom Filters"
#~ msgstr "Benutzerdefinierte Filter"
#~ msgid "Steps"
#~ msgstr "Schritte"
#~ msgid "Property Types"
#~ msgstr "Eigenschaftstypen"
#~ msgid "This feature is not enabled by the server admin!"
#~ msgstr "Diese Funktion wurde vom Administrator nicht freigeschaltet!"
#~ msgid "Imported new recipe!"
#~ msgstr "Neues Rezept importiert!"
#~ msgid "There was an error importing this recipe!"
#~ msgstr "Beim Importieren des Rezeptes ist ein Fehler aufgetreten!"
#~ msgid "You do not have the required permissions to perform this action!"
#~ msgstr ""
#~ "Du hast nicht die notwendige Berechtigung, um diese Aktion durchzuführen!"
#~ msgid "Comment saved!"
#~ msgstr "Kommentar gespeichert!"
#~ msgid "You must select at least one field to search!"
#~ msgstr "Es muss mindestens ein Feld ausgewählt sein!"
#~ msgid ""
#~ "To use this search method you must select at least one full text search "
#~ "field!"
#~ msgstr ""
#~ "Um diese Suchmethode zu verwenden muss mindestens ein Feld für die "
#~ "Volltextsuche ausgewählt sein!"
#~ msgid "Fuzzy search is not compatible with this search method!"
#~ msgstr "Die \"Ungenaue\" Suche ist mit diesem Suchtyp nicht kompatibel!"
#~ msgid "Malformed Invite Link supplied!"
#~ msgstr "Fehlerhafter Einladungslink angegeben!"
#~ msgid "Successfully joined space."
#~ msgstr "Space erfolgreich beigetreten."
#~ msgid "Invite Link not valid or already used!"
#~ msgstr "Einladungslink ungültig oder bereits genutzt!"
#~ msgid "View your cookbooks"
#~ msgstr "Betrachte deine Kochbücher"
#~ msgid "Default unit"
#~ msgstr "Standardeinheit"
#~ msgid "Use KJ"
#~ msgstr "Kilojoule verwenden"
#~ msgid "Theme"
#~ msgstr "Theme"
#~ msgid "Navbar color"
#~ msgstr "Farbe der Navigationsleiste"
#~ msgid "Sticky navbar"
#~ msgstr "Navigationsleiste anheften"
#~ msgid "Default page"
#~ msgstr "Standardseite"
#~ msgid "Plan sharing"
#~ msgstr "Essensplan teilen"
#~ msgid "Ingredient decimal places"
#~ msgstr "Nachkommastellen für Zutaten"
#~ msgid "Shopping list auto sync period"
#~ msgstr "Synchronisierungshäufigkeit der Einkaufsliste"
#~ msgid "Left-handed mode"
#~ msgstr "Linkshänder-Modus"
#~ msgid ""
#~ "Color of the top navigation bar. Not all colors work with all themes, "
#~ "just try them out!"
#~ msgstr ""
#~ "Farbe der oberen Navigationsleiste. Nicht alle Farben passen, daher "
#~ "einfach mal ausprobieren!"
#~ msgid ""
#~ "Default Unit to be used when inserting a new ingredient into a recipe."
#~ msgstr ""
#~ "Standardeinheit, die beim Einfügen einer neuen Zutat in ein Rezept zu "
#~ "verwenden ist."
#~ msgid ""
#~ "Enables support for fractions in ingredient amounts (e.g. convert "
#~ "decimals to fractions automatically)"
#~ msgstr ""
#~ "Unterstützung für Brüche in Zutaten aktivieren (dadurch werden "
#~ "Dezimalzahlen automatisch mit Brüchen ersetzt)"
#~ msgid "Display nutritional energy amounts in joules instead of calories"
#~ msgstr "Nährwerte in Joule statt Kalorien anzeigen"
#~ msgid ""
#~ "Users with whom newly created meal plans should be shared by default."
#~ msgstr ""
#~ "Nutzer, mit denen neue Essenspläne standardmäßig geteilt werden sollen."
#~ msgid "Users with whom to share shopping lists."
#~ msgstr "Benutzer, mit denen Einkaufslisten geteilt werden sollen."
#~ msgid "Number of decimals to round ingredients."
#~ msgstr "Anzahl an Dezimalstellen, auf die gerundet werden soll."
#~ msgid ""
#~ "If you want to be able to create and see comments underneath recipes."
#~ msgstr ""
#~ "Wenn du in der Lage sein willst, Kommentare unter Rezepten zu erstellen "
#~ "und zu sehen."
#~ msgid ""
#~ "Setting to 0 will disable auto sync. When viewing a shopping list the "
#~ "list is updated every set seconds to sync changes someone else might have "
#~ "made. Useful when shopping with multiple people but might use a little "
#~ "bit of mobile data. If lower than instance limit it is reset when saving."
#~ msgstr ""
#~ "0 deaktiviert automatische Synchronisation. Wird eine Einkaufsliste "
#~ "betrachtet, dann wird sie gemäß der Einstellung alle paar Sekunden "
#~ "aktualisiert. Dies ist nützlich, wenn mehrere Personen eine Liste beim "
#~ "Einkaufen verwenden, benötigt jedoch etwas Datenvolumen."
#~ msgid "Makes the navbar stick to the top of the page."
#~ msgstr "Navigationsleiste wird oben angeheftet."
#~ msgid "Automatically add meal plan ingredients to shopping list."
#~ msgstr ""
#~ "Fügt die Zutaten des Speiseplans automatisch zur Einkaufsliste hinzu."
#~ msgid "Exclude ingredients that are on hand."
#~ msgstr "Zutaten, die vorrätig sind, ausschließen."
#~ msgid "Will optimize the UI for use with your left hand."
#~ msgstr "Optimiert die Darstellung für die Benutzung mit der linken Hand."
#~ msgid "You must provide at least a recipe or a title."
#~ msgstr "Mindestens ein Rezept oder ein Titel müssen angegeben werden."
#~ msgid "You can list default users to share recipes with in the settings."
#~ msgstr ""
#~ "Sie können in den Einstellungen Standardbenutzer auflisten, für die Sie "
#~ "Rezepte freigeben möchten."
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "Markdown kann genutzt werden, um dieses Feld zu formatieren. Siehe hier für weitere Information"
#~ msgid ""
#~ "Users will see all items you add to your shopping list. They must add "
#~ "you to see items on their list."
#~ msgstr ""
#~ "Die Benutzer sehen alle Artikel, die Sie auf Ihre Einkaufsliste setzen. "
#~ "Die Benutzer müssen Sie hinzufügen, damit Sie Artikel auf der Liste der "
#~ "Benutzer sehen können."
#~ msgid ""
#~ "When adding a meal plan to the shopping list (manually or automatically), "
#~ "include all related recipes."
#~ msgstr ""
#~ "Wenn Sie einen Essensplan zur Einkaufsliste hinzufügen (manuell oder "
#~ "automatisch), fügen Sie alle zugehörigen Rezepte hinzu."
#~ msgid ""
#~ "When adding a meal plan to the shopping list (manually or automatically), "
#~ "exclude ingredients that are on hand."
#~ msgstr ""
#~ "Wenn Sie einen Essensplan zur Einkaufsliste hinzufügen (manuell oder "
#~ "automatisch), schließen Sie Zutaten aus, die Sie gerade zur Hand haben."
#~ msgid "Default number of hours to delay a shopping list entry."
#~ msgstr ""
#~ "Voreingestellte Anzahl von Stunden für die Verzögerung eines "
#~ "Einkaufslisteneintrags."
#~ msgid "Filter shopping list to only include supermarket categories."
#~ msgstr ""
#~ "Nur für den Supermarkt konfigurierte Kategorien in Einkaufsliste anzeigen."
#~ msgid "Days of recent shopping list entries to display."
#~ msgstr ""
#~ "Tage der letzten Einträge in der Einkaufsliste, die angezeigt werden "
#~ "sollen."
#~ msgid "Mark food 'On Hand' when checked off shopping list."
#~ msgstr ""
#~ "Lebensmittel als vorrätig markieren, wenn es in der Einkaufliste abgehakt "
#~ "wurde."
#~ msgid "Delimiter to use for CSV exports."
#~ msgstr "Separator für CSV-Export."
#~ msgid "Prefix to add when copying list to the clipboard."
#~ msgstr ""
#~ "Zusatz wird der in die Zwischenablage kopierten Liste vorangestellt."
#~ msgid "Share Shopping List"
#~ msgstr "Einkaufsliste teilen"
#~ msgid "Autosync"
#~ msgstr "Automatischer Abgleich"
#~ msgid "Auto Add Meal Plan"
#~ msgstr "automatisch dem Menüplan hinzufügen"
#~ msgid "Exclude On Hand"
#~ msgstr "Ausgenommen Vorrätiges"
#~ msgid "Include Related"
#~ msgstr "dazugehörend"
#~ msgid "Default Delay Hours"
#~ msgstr "Standardmäßige Verzögerung in Stunden"
#~ msgid "Filter to Supermarket"
#~ msgstr "Supermarkt filtern"
#~ msgid "Recent Days"
#~ msgstr "Vergangene Tage"
#~ msgid "CSV Delimiter"
#~ msgstr "CSV Trennzeichen"
#~ msgid "List Prefix"
#~ msgstr "Listenpräfix"
#~ msgid "Auto On Hand"
#~ msgstr "Automatisch als vorrätig markieren"
#~ msgid "Reset Food Inheritance"
#~ msgstr "Lebensmittelvererbung zurücksetzen"
#~ msgid "Reset all food to inherit the fields configured."
#~ msgstr ""
#~ "Alle Lebensmittel zurücksetzen, um die konfigurierten Felder zu "
#~ "übernehmen."
#~ msgid "Fields on food that should be inherited by default."
#~ msgstr "Zutaten, die standardmäßig übernommen werden sollen."
#~ msgid "Show recipe counts on search filters"
#~ msgstr "Rezeptanzahl im Suchfiltern anzeigen"
#~ msgid "Use the plural form for units and food inside this space."
#~ msgstr "Pluralform für Einheiten und Essen in diesem Space verwenden."
#~ msgid "One of queryset or hash_key must be provided"
#~ msgstr "Es muss die Abfrage oder der Hash_Key angeben werden"
#~ msgid "Profile"
#~ msgstr "Profil"
#~ msgid "Recipe Book"
#~ msgstr "Rezeptbuch"
#~ msgid "Bookmarks"
#~ msgstr "Lesezeichen"
#~ msgid "Ingredients"
#~ msgstr "Zutaten"
#~ msgid "Show recent recipes"
#~ msgstr "Zuletzt betrachtete Rezepte anzeigen"
#~ msgid "Search style"
#~ msgstr "Suchmethode"
#~ msgid "Show recently viewed recipes on search page."
#~ msgstr "Zuletzt angeschaute Rezepte bei der Suche anzeigen."
#~ msgid "Small"
#~ msgstr "Klein"
#~ msgid "Large"
#~ msgstr "Groß"
#~ msgid "Edit Ingredients"
#~ msgstr "Zutaten bearbeiten"
#~ msgid ""
#~ "\n"
#~ " The following form can be used if, accidentally, two (or more) "
#~ "units or ingredients where created that should be\n"
#~ " the same.\n"
#~ " It merges two units or ingredients and updates all recipes using "
#~ "them.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Dieses Formular kann genutzt werden, wenn versehentlich zwei "
#~ "(oder mehr) Einheiten oder Zutaten erstellt wurden, die eigentlich "
#~ "identisch\n"
#~ " sein sollen.\n"
#~ " Es vereint zwei Zutaten oder Einheiten und aktualisiert alle "
#~ "entsprechenden Rezepte.\n"
#~ " "
#~ msgid "Are you sure that you want to merge these two units?"
#~ msgstr ""
#~ "Bist du dir sicher, dass du diese beiden Einheiten zusammenführen "
#~ "möchtest?"
#~ msgid "Are you sure that you want to merge these two ingredients?"
#~ msgstr ""
#~ "Bist du dir sicher, dass du diese beiden Zutaten zusammenführen möchtest?"
#~ msgid "Import Recipes"
#~ msgstr "Rezepte importieren"
#~ msgid "Close"
#~ msgstr "Schließen"
#~ msgid "Open Recipe"
#~ msgstr "Rezept öffnen"
#~ msgid "Meal Plan View"
#~ msgstr "Plan-Ansicht"
#~ msgid "Created by"
#~ msgstr "Erstellt von"
#~ msgid "Shared with"
#~ msgstr "Geteilt mit"
#~ msgid "Never cooked before."
#~ msgstr "Noch nie gekocht."
#~ msgid "Other meals on this day"
#~ msgstr "Andere Mahlzeiten an diesem Tag"
#~ msgid "Recipe Image"
#~ msgstr "Rezeptbild"
#~ msgid "Preparation time ca."
#~ msgstr "Zubereitungszeit ca."
#~ msgid "Waiting time ca."
#~ msgstr "Wartezeit ca."
#~ msgid "External"
#~ msgstr "Extern"
#~ msgid "Log Cooking"
#~ msgstr "Kochen protokollieren"
#~ msgid "Account"
#~ msgstr "Account"
#~ msgid "Preferences"
#~ msgstr "Präferenzen"
#~ msgid "API-Settings"
#~ msgstr "API-Einstellungen"
#~ msgid "Search-Settings"
#~ msgstr "Sucheinstellungen"
#~ msgid "Shopping-Settings"
#~ msgstr "Einstellungen Einkaufsliste"
#~ msgid "Name Settings"
#~ msgstr "Namen-Einstellungen"
#~ msgid "Account Settings"
#~ msgstr "Account-Einstellungen"
#~ msgid "Emails"
#~ msgstr "E-Mail Adressen"
#~ msgid "Language"
#~ msgstr "Sprache"
#~ msgid "Style"
#~ msgstr "Stil"
#~ msgid "API Token"
#~ msgstr "API-Token"
#~ msgid ""
#~ "You can use both basic authentication and token based authentication to "
#~ "access the REST API."
#~ msgstr ""
#~ "Sowohl Basic Authentication als auch tokenbasierte Authentifizierung "
#~ "können für die REST-API verwendet werden."
#~ msgid ""
#~ "Use the token as an Authorization header prefixed by the word token as "
#~ "shown in the following examples:"
#~ msgstr ""
#~ "Nutz den Token als Authorization-Header mit der Präfix \"Token\" wie in "
#~ "folgendem Beispiel:"
#~ msgid "or"
#~ msgstr "oder"
#~ msgid "Shopping Settings"
#~ msgstr "Einstellungen Einkaufsliste"
#~ msgid "Stats"
#~ msgstr "Statistiken"
#~ msgid "Statistics"
#~ msgstr "Statistiken"
#~ msgid "Number of objects"
#~ msgstr "Anzahl an Objekten"
#~ msgid "Recipe Imports"
#~ msgstr "Importierte Rezepte"
#~ msgid "Objects stats"
#~ msgstr "Objekt-Statistiken"
#~ msgid "Recipes without Keywords"
#~ msgstr "Rezepte ohne Schlagwort"
#~ msgid "Internal Recipes"
#~ msgstr "Interne Rezepte"
#~ msgid "Show Links"
#~ msgstr "Links anzeigen"
#~ msgid "A user is required"
#~ msgstr "Ein Benutzername ist notwendig"
#~ msgid "Invite User"
#~ msgstr "Benutzer einladen"
#~ msgid "User"
#~ msgstr "Benutzer"
#~ msgid "Groups"
#~ msgstr "Gruppen"
#~ msgid "admin"
#~ msgstr "Admin"
#~ msgid "user"
#~ msgstr "Benutzer"
#~ msgid "guest"
#~ msgstr "Gast"
#~ msgid "remove"
#~ msgstr "Entfernen"
#~ msgid "Update"
#~ msgstr "Aktualisierung"
#~ msgid "You cannot edit yourself."
#~ msgstr "Du kannst dies nicht selbst bearbeiten."
#~ msgid "There are no members in your space yet!"
#~ msgstr "In diesem Space sind bisher noch keine Mitglieder!"
#~ msgid "Invite link successfully send to user."
#~ msgstr "Einladungslink erfolgreich an Benutzer gesendet."
#~ msgid ""
#~ "You have send to many emails, please share the link manually or wait a "
#~ "few hours."
#~ msgstr ""
#~ "Du hast zu viele Email gesendet. Bitte teile den Link manuell oder warte "
#~ "ein paar Stunden."
#~ msgid "Email could not be sent to user. Please share the link manually."
#~ msgstr ""
#~ "Email konnte an den Benutzer nicht gesendet werden. Bitte teile den Link "
#~ "manuell."
#~ msgid ""
#~ "You are already member of a space and therefore cannot join this one."
#~ msgstr ""
#~ "Du bist bereits Mitglied eines Space, daher kannst du diesem Space nicht "
#~ "beitreten."
#~ msgid "Try the new shopping list"
#~ msgstr "Neue Einkaufsliste ausprobieren"
#~ msgid "Search Recipe"
#~ msgstr "Rezept suchen"
#~ msgid "Shopping Recipes"
#~ msgstr "Einkaufs-Rezepte"
#~ msgid "No recipes selected"
#~ msgstr "Keine Rezepte ausgewählt"
#~ msgid "Entry Mode"
#~ msgstr "Eintrags-Modus"
#~ msgid "Add Entry"
#~ msgstr "Eintrag hinzufügen"
#~ msgid "Amount"
#~ msgstr "Menge"
#~ msgid "Select Unit"
#~ msgstr "Einheit wählen"
#~ msgid "Select"
#~ msgstr "Auswählen"
#~ msgid "Select Food"
#~ msgstr "Zutat auswählen"
#~ msgid "Select Supermarket"
#~ msgstr "Supermarkt auswählen"
#~ msgid "Select User"
#~ msgstr "Nutzer auswählen"
#~ msgid "Finished"
#~ msgstr "Erledigt"
#, fuzzy
#~ msgid "You are offline, shopping list might not synchronize."
#~ msgstr "Du bist offline, die Einkaufsliste wird ggf. nicht synchronisiert."
#~ msgid "Copy/Export"
#~ msgstr "Kopieren/Exportieren"
#~ msgid "Drag me to your bookmarks to import recipes from anywhere"
#~ msgstr ""
#~ "Ziehe mich in deine Lesezeichen, um Rezepte von überall zu importieren"
#~ msgid "Bookmark Me!"
#~ msgstr "Lesezeichen speichern!"
#~ msgid "URL"
#~ msgstr "URL"
#~ msgid "App"
#~ msgstr "Anwendung"
#~ msgid "Text"
#~ msgstr "Text"
#~ msgid "File"
#~ msgstr "Datei"
#~ msgid "Enter website URL"
#~ msgstr "Webseite-URL eingeben"
#~ msgid "Select recipe files to import or drop them here..."
#~ msgstr "Wähle Rezept-Dateien zum Importieren oder platziere sie hier..."
#~ msgid "Paste json or html source here to load recipe."
#~ msgstr "Füge JSON- oder HTML-Daten hier ein um das Rezept zu laden."
#~ msgid "Preview Recipe Data"
#~ msgstr "Rezept-Daten ansehen"
#~ msgid ""
#~ "Drag recipe attributes from the right into the appropriate box below."
#~ msgstr ""
#~ "Ziehe Rezepteigenschaften von Rechts in die entsprechende Box unten."
#~ msgid "Clear Contents"
#~ msgstr "Inhalte leeren"
#~ msgid "Text dragged here will be appended to the name."
#~ msgstr "Text welcher hierhin gezogen wird, wird an den Namen angehängt."
#~ msgid "Text dragged here will be appended to the description."
#~ msgstr ""
#~ "Text welcher hierhin gezogen wird, wird an die Beschreibung angehängt."
#~ msgid "Keywords dragged here will be appended to current list"
#~ msgstr ""
#~ "Stichworte welche hierhin gezogen werden, werden zur aktuellen Liste "
#~ "hinzugefügt"
#~ msgid "Image"
#~ msgstr "Bild"
#~ msgid "Prep Time"
#~ msgstr "Vorbereitungszeit"
#~ msgid "Cook Time"
#~ msgstr "Kochzeit"
#~ msgid "Ingredients dragged here will be appended to current list."
#~ msgstr ""
#~ "Zutaten welche hierhin gezogen werden, werden zur aktuellen Liste "
#~ "hinzugefügt."
#~ msgid ""
#~ "Recipe instructions dragged here will be appended to current instructions."
#~ msgstr ""
#~ "Rezeptanweisungen welche hierhin gezogen werden, werden zu den aktuellen "
#~ "Anweisungen hinzugefügt."
#~ msgid "Discovered Attributes"
#~ msgstr "Entdeckte Attribute"
#~ msgid ""
#~ "Drag recipe attributes from below into the appropriate box on the left. "
#~ "Click any node to display its full properties."
#~ msgstr ""
#~ "Ziehe Rezepteigenschaften von unten in das jeweilige Feld links. Klicke "
#~ "auf beliebige Elemente um dessen Eigenschaften anzuzeigen."
#~ msgid "Show Blank Field"
#~ msgstr "Leeres Feld anzeigen"
#~ msgid "Blank Field"
#~ msgstr "Leeres Feld"
#~ msgid "Items dragged to Blank Field will be appended."
#~ msgstr "Elemente die in ein leeres Feld gezogen werden, werden angefügt."
#~ msgid "Delete Text"
#~ msgstr "Text löschen"
#~ msgid "Delete image"
#~ msgstr "Bild löschen"
#~ msgid "Recipe Name"
#~ msgstr "Rezeptname"
#~ msgid "Recipe Description"
#~ msgstr "Rezept Beschreibung"
#~ msgid "Select one"
#~ msgstr "Auswählen"
#~ msgid "Note"
#~ msgstr "Notiz"
#~ msgid "Add Keyword"
#~ msgstr "Schlagwort hinzufügen"
#~ msgid "All Keywords"
#~ msgstr "Alle Schlagwörter"
#~ msgid "Import all keywords, not only the ones already existing."
#~ msgstr "Alle Schlagwörter importieren, nicht nur die bereits bestehenden."
#~ msgid "Information"
#~ msgstr "Information"
#~ msgid ""
#~ " Only websites containing ld+json or microdata information can currently\n"
#~ " be imported. Most big recipe pages "
#~ "support this. If you site cannot be imported but\n"
#~ " you think\n"
#~ " it probably has some kind of "
#~ "structured data feel free to post an example in the\n"
#~ " github issues."
#~ msgstr ""
#~ " Nur Webseiten mit ld+json oder microdata können importiert werden. Die "
#~ "meisten großen Seiten unterstützen diese Formate. Wenn eine Seite nicht "
#~ "importiert werden kann, sie aber strukturierte Daten aufweist, kann ein "
#~ "GitHub-Issue geöffnet werden."
#~ msgid "Google ld+json Info"
#~ msgstr "Google ld+json Informationen"
#~ msgid "GitHub Issues"
#~ msgstr "GitHub-Issues"
#~ msgid "Recipe Markup Specification"
#~ msgstr "Rezept-Markup-Spezifikation"
#~ msgid "The requested site provided malformed data and cannot be read."
#~ msgstr ""
#~ "Die angefragte Seite hat ungültige Daten zurückgegeben oder die Daten "
#~ "konnten nicht verarbeitet werden."
#~ msgid "The requested page could not be found."
#~ msgstr "Die angefragte Seite konnte nicht gefunden werden."
#~ msgid ""
#~ "The requested site does not provide any recognized data format to import "
#~ "the recipe from."
#~ msgstr ""
#~ "Die angefragte Seite stellt keine bekannten Datenformate zur Verfügung."
#~ msgid "I couldn't find anything to do."
#~ msgstr "Ich konnte nichts zu tun finden."
#~ msgid "Shopping Lists"
#~ msgstr "Einkaufslisten"
#~ msgid "You must supply a recipe or mealplan"
#~ msgstr "Mindestens ein Rezept oder ein Essensplan müssen angegeben werden"
#~ msgid "Time"
#~ msgstr "Zeit"
#~ msgid "Log Recipe Cooking"
#~ msgstr "Kochen protokollieren"
#~ msgid "All fields are optional and can be left empty."
#~ msgstr "Alle Felder sind optional und können leer gelassen werden."
#~ msgid "Rating"
#~ msgstr "Bewertung"
#~ msgid "Exporting is not implemented for this provider"
#~ msgstr "Exportieren ist für diesen Anbieter noch nicht implementiert"
#~ msgid "No {self.basename} with id {child} exists"
#~ msgstr "Kein {self.basename} mit ID {child} existiert"
#~ msgid "New unit that other gets replaced by."
#~ msgstr "Neue Einheit, welche die alte ersetzt."
#~ msgid "Old Unit"
#~ msgstr "Alte Einheit"
#~ msgid "Unit that should be replaced."
#~ msgstr "Einheit, die ersetzt werden soll."
#~ msgid "New Food"
#~ msgstr "Neue Zutat"
#~ msgid "New food that other gets replaced by."
#~ msgstr "Neue Zutat, welche die alte ersetzt."
#~ msgid "Old Food"
#~ msgstr "Alte Zutat"
#~ msgid "New Entry"
#~ msgstr "Neuer Eintrag"
#~ msgid "Title"
#~ msgstr "Titel"
#~ msgid "Note (optional)"
#~ msgstr "Notiz (optional)"
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "Dieses Feld Unterstützt Markdown Formatierung. Siehe Dokumentation"
#~ "a>"
#~ msgid "Serving Count"
#~ msgstr "Anzahl Portionen"
#~ msgid "Create only note"
#~ msgstr "Nur Notiz erstellen"
#~ msgid "Number of Days"
#~ msgstr "Anzahl an Tagen"
#~ msgid "Weekday offset"
#~ msgstr "Wochentage verschieben"
#~ msgid ""
#~ "Number of days starting from the first day of the week to offset the "
#~ "default view."
#~ msgstr ""
#~ "Anzahl der Tage von ersten Tag der Woche, die der Plan standardmäßig "
#~ "verschoben sein soll."
#~ msgid "Edit plan types"
#~ msgstr "Plantypen editieren"
#~ msgid "Show help"
#~ msgstr "Hilfe anzeigen"
#~ msgid "Week iCal export"
#~ msgstr "Woche als iCal exportieren"
#~ msgid "Add to Shopping"
#~ msgstr "Zur Einkaufsliste hinzufügen"
#~ msgid "New meal type"
#~ msgstr "Neue Mahlzeit"
#~ msgid "Meal Plan Help"
#~ msgstr "Plan-Hilfe"
#~ msgid ""
#~ "\n"
#~ " The meal plan module allows planning of "
#~ "meals both with recipes and notes.
\n"
#~ " Simply select a recipe from the list of "
#~ "recently viewed recipes or search the one you\n"
#~ " want and drag it to the desired plan "
#~ "position. You can also add a note and a title and\n"
#~ " then drag the recipe to create a plan "
#~ "entry with a custom title and note. Creating only\n"
#~ " Notes is possible by dragging the create "
#~ "note box into the plan.
\n"
#~ " Click on a recipe in order to open the "
#~ "detailed view. There you can also add it to the\n"
#~ " shopping list. You can also add all "
#~ "recipes of a day to the shopping list by\n"
#~ " clicking the shopping cart at the top of "
#~ "the table.
\n"
#~ " Since a common use case is to plan meals "
#~ "together you can define\n"
#~ " users you want to share your plan with in "
#~ "the settings.\n"
#~ "
\n"
#~ " You can also edit the types of meals you "
#~ "want to plan. If you share your plan with\n"
#~ " someone with\n"
#~ " different meals, their meal types will "
#~ "appear in your list as well. To prevent\n"
#~ " duplicates (e.g. Other and Misc.)\n"
#~ " name your meal types the same as the "
#~ "users you share your meals with and they will be\n"
#~ " merged.
\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Das Planmodul erlaubt das Planen mithilfe "
#~ "von Rezepten und Notizen.
\n"
#~ " Einfach ein Rezept aussuchen und an die "
#~ "Stelle im Plan ziehen, an der es gekocht werden soll. Es kann außerdem "
#~ "eine Notiz und ein Titel hinzugefügt werden. Einen Eintrag nur als Notiz "
#~ "zu erstellen ist durch Eingabe einer Notiz und Schieben des Notiz-Blocks "
#~ "in den Plan möglich.
\n"
#~ " Durch Klicken auf ein Rezept öffnet sich "
#~ "die Detailansicht. Da kann das Rezept auch auf die Einkaufsliste "
#~ "hinzugefügt werden. Es können auch alle Rezepte eines Tages auf die "
#~ "Einkaufsliste gesetzt werden, indem der Einkaufswagen im Tabellenkopf "
#~ "angeklickt wird.
\n"
#~ " Da Pläne häufig für mehrere Nutzer "
#~ "erstellt werden, können Nutzer in den Einstellungen angegeben werden, mit "
#~ "denen neue Pläne automatisch geteilt werden sollen.\n"
#~ "
\n"
#~ " Die Mahlzeiten, die geplant werden sollen, "
#~ "können bearbeitet werden. Wenn Pläne zwischen Nutzern mit "
#~ "unterschiedlichen Mahlzeiten geteilt werden, erscheinen alle Mahlzeiten. "
#~ "Um Duplikate zu vermeiden (z.B. Mittagessen und Mittag) sollten "
#~ "Mahlzeiten teilender Nutzer gleich benannt werden, dadurch kann das "
#~ "System sie zusammenfassen.
\n"
#~ " "
#~ msgid "Units merged!"
#~ msgstr "Einheiten zusammengeführt!"
#~ msgid "Foods merged!"
#~ msgstr "Zutaten zusammengeführt!"
#~ msgid "Utensils"
#~ msgstr "Utensilien"
#~ msgid "Storage Data"
#~ msgstr "Datenquellen"
#~ msgid "Storage Backends"
#~ msgstr "Speicherquellen"
#~ msgid "Configure Sync"
#~ msgstr "Synchronisation einstellen"
#~ msgid "Discovered Recipes"
#~ msgstr "Entdeckte Rezepte"
#~ msgid "Discovery Log"
#~ msgstr "Entdeckungsverlauf"
#~ msgid "Units & Ingredients"
#~ msgstr "Einheiten & Zutaten"
#~ msgid "New Book"
#~ msgstr "Neues Buch"
#~ msgid "Toggle Recipes"
#~ msgstr "Rezepte umschalten"
#~ msgid "There are no recipes in this book yet."
#~ msgstr "In diesem Buch sind bisher noch keine Rezepte."
#~ msgid "Waiting Time"
#~ msgstr "Wartezeit"
#~ msgid "Servings Text"
#~ msgstr "Portionen-Text"
#~ msgid "Select Keywords"
#~ msgstr "Schlagwörter wählen"
#~ msgid "Delete Step"
#~ msgstr "Schritt löschen"
#~ msgid "Step"
#~ msgstr "Schritt"
#~ msgid "Show as header"
#~ msgstr "Als Überschrift anzeigen"
#~ msgid "Hide as header"
#~ msgstr "Nicht als Überschrift anzeigen"
#~ msgid "Move Up"
#~ msgstr "Nach oben"
#~ msgid "Move Down"
#~ msgstr "Nach unten"
#~ msgid "Step Name"
#~ msgstr "Name des Schritts"
#~ msgid "Step Type"
#~ msgstr "Art des Schritts"
#~ msgid "Step time in Minutes"
#~ msgstr "Zeit in Minuten"
#~ msgid "Select File"
#~ msgstr "Datei auswählen"
#, fuzzy
#~ msgid "Select Recipe"
#~ msgstr "Rezept löschen"
#~ msgid "Delete Ingredient"
#~ msgstr "Zutat löschen"
#~ msgid "Make Header"
#~ msgstr "Überschrift erstellen"
#~ msgid "Make Ingredient"
#~ msgstr "Zutat erstellen"
#~ msgid "Disable Amount"
#~ msgstr "Menge deaktivieren"
#~ msgid "Enable Amount"
#~ msgstr "Menge aktivieren"
#~ msgid "Copy Template Reference"
#~ msgstr "Kopiere Vorlagen-Referenz"
#~ msgid "Save & View"
#~ msgstr "Speichern & Ansehen"
#~ msgid "Add Step"
#~ msgstr "Schritt hinzufügen"
#~ msgid "Add Nutrition"
#~ msgstr "Nährwerte hinzufügen"
#~ msgid "Remove Nutrition"
#~ msgstr "Nährwerte entfernen"
#~ msgid "View Recipe"
#~ msgstr "Rezept ansehen"
#~ msgid "Delete Recipe"
#~ msgstr "Rezept löschen"
#~ msgid "Password Settings"
#~ msgstr "Passwort-Einstellungen"
#~ msgid "Email Settings"
#~ msgstr "Email-Einstellungen"
#~ msgid "Manage Social Accounts"
#~ msgstr "Social Accounts verwalten"
#~ msgid ""
#~ "A username is not required, if left blank the new user can choose one."
#~ msgstr ""
#~ "Kein Benutzername benötigt. Wenn leer gelassen, kann der neue Benutzer "
#~ "einen wählen."
#~ msgid "Link"
#~ msgstr "Link"
#~ msgid "Logout"
#~ msgstr "Abmelden"
#~ msgid "Website Import"
#~ msgstr "Webseiten-Import"
#~ msgid "You are not a member of any space."
#~ msgstr "Du bist kein Mitglied von einem Space."
#~ msgid "There was an error creating a resource!"
#~ msgstr "Es gab einen Fehler beim Erstellen einer Ressource!"
#~ msgid "Enter json directly"
#~ msgstr "JSON direkt eingeben"
#~ msgid ""
#~ "The requested page refused to provide any information (Status Code 403)."
#~ msgstr "Die angefragte Seite hat die Anfrage abgelehnt (Status-Code 403)."
#~ msgid "Could not parse correctly..."
#~ msgstr "Konnte Inhalt nicht korrekt parsen..."
#~ msgid "Number of servings"
#~ msgstr "Anzahl der Portionen"
#~ msgid ""
#~ "Include - [ ] in list for easier usage in markdown based "
#~ "documents."
#~ msgstr ""
#~ "Füge - [ ] vor den Zutaten ein, um sie besser in einem "
#~ "Markdown-Dokument zu verwenden."
#~ msgid "Backup & Restore"
#~ msgstr "Backup & Wiederherstellung"
#~ msgid "Download Backup"
#~ msgstr "Backup herunterladen"
#~ msgid "Preference for given user already exists"
#~ msgstr "Präferenz für den Benutzer existiert bereits"
#~ msgid "This recipe is already linked to the book!"
#~ msgstr "Dieses Rezept ist bereits mit dem Buch verlinkt!"
================================================
FILE: cookbook/locale/el/LC_MESSAGES/django.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-22 20:15+0200\n"
"PO-Revision-Date: 2024-09-23 21:58+0000\n"
"Last-Translator: Emmker \n"
"Language-Team: Greek \n"
"Language: el\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.6.2\n"
#: .\cookbook\forms.py:50
msgid "Default"
msgstr "Προεπιλογή"
#: .\cookbook\forms.py:77
msgid ""
"To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything."
msgstr ""
"Για την αποτροπή της εισαγωγής διπλών συνταγών, συνταγές με το ίδιο όνομα με "
"υπάρχουσες, αγνοούνται. Επιλέξτε αυτό το πλαίσιο για να εισαγάγετε τα πάντα."
#: .\cookbook\forms.py:108
msgid "Maximum number of users for this space reached."
msgstr "Έχει επιτευχθεί ο μέγιστος αριθμός χρηστών για αυτόν τον χώρο."
#: .\cookbook\forms.py:114
msgid "Email address already taken!"
msgstr "Αυτή η διεύθυνση email δεν είναι διαθέσιμη!"
#: .\cookbook\forms.py:121
msgid ""
"An email address is not required but if present the invite link will be sent "
"to the user."
msgstr ""
"Δεν απαιτείται η διεύθυνση email, αλλά αν υπάρχει, ο σύνδεσμος πρόσκλησης θα "
"αποσταλεί στον χρήστη."
#: .\cookbook\forms.py:133
msgid "Name already taken."
msgstr "Το όνομα αυτό είναι ήδη πιασμένο."
#: .\cookbook\forms.py:144 .\cookbook\forms.py:158
msgid "Accept Terms and Privacy"
msgstr "Αποδοχή των όρων και της πολιτικής απορρήτου"
#: .\cookbook\helper\AllAuthCustomAdapter.py:41
msgid ""
"In order to prevent spam, the requested email was not send. Please wait a "
"few minutes and try again."
msgstr ""
"Για να αποκλείσουμε πιθανά spam, το email που ζητήθηκε δεν στάλθηκε. "
"Παρακαλώ περιμένετε λίγα λεπτά και δοκιμάστε ξανά."
#: .\cookbook\helper\permission_helper.py:165
#: .\cookbook\helper\permission_helper.py:186 .\cookbook\views\views.py:138
msgid "You are not logged in and therefore cannot view this page!"
msgstr "Δεν μπορείτε να δείτε αυτή τη σελίδα γιατί δεν είστε συνδεδεμένος!"
#: .\cookbook\helper\permission_helper.py:168
#: .\cookbook\helper\permission_helper.py:173
#: .\cookbook\helper\permission_helper.py:198
#: .\cookbook\helper\permission_helper.py:265
#: .\cookbook\helper\permission_helper.py:279
#: .\cookbook\helper\permission_helper.py:290
#: .\cookbook\helper\permission_helper.py:301
#: .\cookbook\helper\permission_helper.py:317
#: .\cookbook\helper\permission_helper.py:343
#: .\cookbook\helper\permission_helper.py:359
msgid "You do not have the required permissions to view this page!"
msgstr "Δεν έχετε τα απαιτούμενα δικαιώματα να δείτε αυτή τη σελίδα!"
#: .\cookbook\helper\permission_helper.py:191
#: .\cookbook\helper\permission_helper.py:214
#: .\cookbook\helper\permission_helper.py:236
#: .\cookbook\helper\permission_helper.py:251
msgid "You cannot interact with this object as it is not owned by you!"
msgstr ""
"Δεν μπορείτε να αλληλεπιδράστε με αυτό το αντικείμενο γιατί δεν σας ανήκει!"
#: .\cookbook\helper\permission_helper.py:420
msgid "You have reached the maximum number of recipes for your space."
msgstr "Έχετε υπερβεί τον μέγιστο αριθμό συνταγών για τον χώρο σας."
#: .\cookbook\helper\permission_helper.py:432
msgid "You have more users than allowed in your space."
msgstr "Έχετε περισσότερους χρήστες από το επιτρεπόμενο στον χώρο σας."
#: .\cookbook\helper\recipe_url_import.py:319
#, fuzzy
#| msgid "Use fractions"
msgid "reverse rotation"
msgstr "Χρήση κλασμάτων"
#: .\cookbook\helper\recipe_url_import.py:320
msgid "careful rotation"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:321
msgid "knead"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:322
msgid "thicken"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:323
msgid "warm up"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:324
msgid "ferment"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:325
msgid "slow cook"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:326
msgid "egg boiler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:327
msgid "kettle"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:328
msgid "blend"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:329
msgid "pre-clean"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:330
msgid "high temperature"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:331
msgid "rice cooker"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:332
msgid "caramelize"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:333
msgid "peeler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:334
msgid "slicer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:335
msgid "grater"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:336
msgid "spiralizer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:337
msgid "sous-vide"
msgstr ""
#: .\cookbook\helper\shopping_helper.py:150
msgid "You must supply a servings size"
msgstr "Θα πρέπει να προσθέσετε το μέγεθος της μερίδας"
#: .\cookbook\helper\template_helper.py:97
#: .\cookbook\helper\template_helper.py:99
#: .\cookbook\helper\template_helper.py:101
msgid "Could not parse template code."
msgstr ""
#: .\cookbook\integration\copymethat.py:44
#: .\cookbook\integration\melarecipes.py:37
msgid "Favorite"
msgstr "Αγαπημένα"
#: .\cookbook\integration\copymethat.py:50
msgid "I made this"
msgstr ""
#: .\cookbook\integration\integration.py:238
msgid ""
"Importer expected a .zip file. Did you choose the correct importer type for "
"your data ?"
msgstr ""
"Ο εισαγωγέας περίμενε ένα αρχείο .zip. Έχετε σίγουρα διαλέξει τον σωστό τύπο "
"εισαγωγέα για τα δεδομένα θέλετε να εισάγετε;"
#: .\cookbook\integration\integration.py:241
msgid ""
"An unexpected error occurred during the import. Please make sure you have "
"uploaded a valid file."
msgstr ""
"Παρουσιάστηκε ένα απρόβλεπτο σφάλμα κατά την εισαγωγή. Βεβαιωθείτε ότι έχετε "
"μεταφορτώσει ένα έγκυρο αρχείο."
#: .\cookbook\integration\integration.py:246
msgid "The following recipes were ignored because they already existed:"
msgstr "Οι παρακάτω συνταγές αγνοήθηκαν επειδή υπήρχαν ήδη:"
#: .\cookbook\integration\integration.py:250
#, python-format
msgid "Imported %s recipes."
msgstr "Εισήχθησαν %s συνταγές."
#: .\cookbook\integration\mealie1.py:210
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "Calories"
msgstr ""
#: .\cookbook\integration\mealie1.py:211
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
msgid "Carbohydrates"
msgstr ""
#: .\cookbook\integration\mealie1.py:212
msgid "Cholesterol"
msgstr ""
#: .\cookbook\integration\mealie1.py:213
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
msgid "Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:214
msgid "Fiber"
msgstr ""
#: .\cookbook\integration\mealie1.py:215
#, fuzzy
#| msgid "Protected"
msgid "Protein"
msgstr "Προστατευμένο"
#: .\cookbook\integration\mealie1.py:216
msgid "Saturated Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:217
msgid "Sodium"
msgstr ""
#: .\cookbook\integration\mealie1.py:218
msgid "Sugar"
msgstr ""
#: .\cookbook\integration\mealie1.py:219
msgid "Trans Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:220
msgid "Unsaturated Fat"
msgstr ""
#: .\cookbook\integration\openeats.py:28
#, fuzzy
#| msgid "Recipes"
msgid "Recipe source:"
msgstr "Συνταγές"
#: .\cookbook\integration\paprika.py:49
msgid "Notes"
msgstr "Σημειώσεις"
#: .\cookbook\integration\paprika.py:52
msgid "Nutritional Information"
msgstr "Διατροφικές πληροφορίες"
#: .\cookbook\integration\paprika.py:56
msgid "Source"
msgstr "Πηγή"
#: .\cookbook\integration\recettetek.py:55
#: .\cookbook\integration\recipekeeper.py:70
msgid "Imported from"
msgstr "Εισήχθη από"
#: .\cookbook\integration\saffron.py:23
msgid "Servings"
msgstr "Μερίδες"
#: .\cookbook\integration\saffron.py:25
msgid "Waiting time"
msgstr "Χρόνος αναμονής"
#: .\cookbook\integration\saffron.py:27
msgid "Preparation Time"
msgstr "Χρόνος προετοιμασίας"
#: .\cookbook\integration\saffron.py:29 .\cookbook\templates\index.html:6
msgid "Cookbook"
msgstr "Βιβλίο συνταγών"
#: .\cookbook\integration\saffron.py:31
msgid "Section"
msgstr "Τομέας"
#: .\cookbook\management\commands\fix_duplicate_properties.py:15
msgid "Fixes foods with "
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:14
msgid "Rebuilds full text search index on Recipe"
msgstr "Αναδόμηση πλήρους ευρετηρίου αναζήτησης κειμένου για τις συνταγές"
#: .\cookbook\management\commands\rebuildindex.py:18
msgid "Only Postgresql databases use full text search, no index to rebuild"
msgstr ""
"Μόνο οι βάσεις δεδομένων Postgresql χρησιμοποιούν αναζήτηση πλήρους "
"κειμένου, δεν υπάρχει ανάγκη ανασύνθεσης των ευρετηρίων"
#: .\cookbook\management\commands\rebuildindex.py:29
msgid "Recipe index rebuild complete."
msgstr "Η αναδόμηση του ευρετηρίου των συνταγών ολοκληρώθηκε."
#: .\cookbook\management\commands\rebuildindex.py:31
msgid "Recipe index rebuild failed."
msgstr "Η αναδόμηση του ευρετηρίου των συνταγών απέτυχε."
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
msgid "Breakfast"
msgstr "Πρωινό"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:19
msgid "Lunch"
msgstr "Μεσημεριανό"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:24
msgid "Dinner"
msgstr "Βραδινό"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:29 .\cookbook\models.py:971
msgid "Other"
msgstr "Άλλο"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "g"
msgstr ""
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "Proteins"
msgstr ""
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "kcal"
msgstr ""
#: .\cookbook\models.py:325
msgid ""
"Maximum file storage for space in MB. 0 for unlimited, -1 to disable file "
"upload."
msgstr ""
"Μέγιστος χώρος αποθήκευσης αρχείων σε MB. Ορίστε το σε 0 για απεριόριστο "
"χώρο, σε -1 για να απενεργοποιήσετε τη μεταφόρτωση αρχείων."
#: .\cookbook\models.py:513
msgid "Search"
msgstr "Αναζήτηση"
#: .\cookbook\models.py:514
msgid "Meal-Plan"
msgstr "Πραγματισμός γευμάτων"
#: .\cookbook\models.py:515
msgid "Books"
msgstr "Βιβλία"
#: .\cookbook\models.py:516 .\cookbook\views\views.py:416
#: .\cookbook\views\views.py:417
msgid "Shopping"
msgstr "Αγορές"
#: .\cookbook\models.py:967
#, fuzzy
#| msgid "Automations"
msgid "Nutrition"
msgstr "Αυτοματισμοί"
#: .\cookbook\models.py:968
#, fuzzy
#| msgid "Merge"
msgid "Allergen"
msgstr "Συγχώνευση"
#: .\cookbook\models.py:969
msgid "Price"
msgstr ""
#: .\cookbook\models.py:970
msgid "Goal"
msgstr ""
#: .\cookbook\models.py:1467 .\cookbook\templates\search_info.html:28
msgid "Simple"
msgstr "Απλό"
#: .\cookbook\models.py:1468 .\cookbook\templates\search_info.html:33
msgid "Phrase"
msgstr "Φράση"
#: .\cookbook\models.py:1469 .\cookbook\templates\search_info.html:38
msgid "Web"
msgstr "Δίκτυο"
#: .\cookbook\models.py:1470 .\cookbook\templates\search_info.html:47
msgid "Raw"
msgstr ""
#: .\cookbook\models.py:1525
msgid "Food Alias"
msgstr ""
#: .\cookbook\models.py:1526
msgid "Unit Alias"
msgstr ""
#: .\cookbook\models.py:1527
msgid "Keyword Alias"
msgstr ""
#: .\cookbook\models.py:1528
msgid "Description Replace"
msgstr ""
#: .\cookbook\models.py:1529
msgid "Instruction Replace"
msgstr ""
#: .\cookbook\models.py:1530
msgid "Never Unit"
msgstr ""
#: .\cookbook\models.py:1531
msgid "Transpose Words"
msgstr ""
#: .\cookbook\models.py:1532
msgid "Food Replace"
msgstr ""
#: .\cookbook\models.py:1533
#, fuzzy
#| msgid "Edit Recipe"
msgid "Unit Replace"
msgstr "Τροποποίηση συνταγής"
#: .\cookbook\models.py:1534
msgid "Name Replace"
msgstr ""
#: .\cookbook\models.py:1564
msgid "Recipe"
msgstr "Συνταγή"
#: .\cookbook\models.py:1565
msgid "Food"
msgstr "Φαγητό"
#: .\cookbook\models.py:1566
msgid "Keyword"
msgstr "Λέξη κλειδί"
#: .\cookbook\serializer.py:262
msgid "File uploads are not enabled for this Space."
msgstr "Οι μεταφορτώσεις αρχείων δεν είναι ενεργοποιημένες για αυτόν τον χώρο."
#: .\cookbook\serializer.py:273
msgid "You have reached your file upload limit."
msgstr "Έχετε φτάσει το όριο μεταφόρτωσης αρχείων."
#: .\cookbook\serializer.py:281
msgid "The given file type is not allowed."
msgstr ""
#: .\cookbook\serializer.py:421 .\cookbook\views\views.py:94
#, fuzzy
#| msgid "You have reached the maximum number of recipes for your space."
msgid ""
"You have the reached the maximum amount of spaces that can be owned by you."
msgstr "Έχετε υπερβεί τον μέγιστο αριθμό συνταγών για τον χώρο σας."
#: .\cookbook\serializer.py:434
msgid "Space Name must be unique."
msgstr ""
#: .\cookbook\serializer.py:469
msgid "Cannot modify Space owner permission."
msgstr ""
"Δεν είναι δυνατή η τροποποίηση των δικαιωμάτων του ιδιοκτήτη του χώρου."
#: .\cookbook\serializer.py:1596
msgid "Hello"
msgstr "Γεια"
#: .\cookbook\serializer.py:1596
msgid "You have been invited by "
msgstr "Έχετε προσκληθεί από "
#: .\cookbook\serializer.py:1598
msgid " to join their Tandoor Recipes space "
msgstr " για να συνδεθείτε στό χώρο συνταγών του Tandoor "
#: .\cookbook\serializer.py:1600
msgid "Click the following link to activate your account: "
msgstr ""
"Κάντε κλικ στον παρακάτω σύνδεσμο για να ενεργοποιήσετε τον λογαριασμό σας: "
#: .\cookbook\serializer.py:1602
msgid ""
"If the link does not work use the following code to manually join the space: "
msgstr ""
"Εάν ο σύνδεσμος δεν λειτουργεί, χρησιμοποιήστε τον παρακάτω κωδικό για να "
"εγγραφείτε χειροκίνητα στον χώρο: "
#: .\cookbook\serializer.py:1604
msgid "The invitation is valid until "
msgstr "Η πρόσκληση είναι σε ισχύ μέχρι "
#: .\cookbook\serializer.py:1606
msgid ""
"Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub "
msgstr ""
"Το Tandoor Recipes είναι ένας διαχειριστής συνταγών ανοιχτού κώδικα. Ρίξτε "
"μια ματιά στο GitHub "
#: .\cookbook\serializer.py:1609
msgid "Tandoor Recipes Invite"
msgstr "Πρόσκληση στο Tandoor Recipes"
#: .\cookbook\serializer.py:1813
msgid "Existing shopping list to update"
msgstr "Υπάρχουσα λίστα αγορών για ενημέρωση"
#: .\cookbook\serializer.py:1815
msgid ""
"List of ingredient IDs from the recipe to add, if not provided all "
"ingredients will be added."
msgstr ""
"Λίστα αναγνωριστικών συστατικών (ID) από τη συνταγή προς προσθήκη. Εάν δεν "
"παρέχονται όλα τα συστατικά θα προστεθούν."
#: .\cookbook\serializer.py:1817
msgid ""
"Providing a list_recipe ID and servings of 0 will delete that shopping list."
msgstr ""
#: .\cookbook\serializer.py:1826
msgid "Amount of food to add to the shopping list"
msgstr "Ποσότητα του φαγητού που θα προστεθεί στη λίστα αγορών"
#: .\cookbook\serializer.py:1828
msgid "ID of unit to use for the shopping list"
msgstr "Το ID της μονάδας μέτρησης που θα χρησιμοποιείται στη λίστα αγορών"
#: .\cookbook\serializer.py:1830
msgid "When set to true will delete all food from active shopping lists."
msgstr ""
"Όταν οριστεί σε true, θα διαγραφούν όλα τα τρόφιμα από τις ενεργές λίστες "
"αγορών."
#: .\cookbook\templates\404.html:5
msgid "404 Error"
msgstr "404 Error"
#: .\cookbook\templates\404.html:18
msgid "The page you are looking for could not be found."
msgstr "Η σελίδα που αναζητάτε δεν μπορεί να βρεθεί."
#: .\cookbook\templates\404.html:33
msgid "Take me Home"
msgstr "Πήγαινε με στη αρχική σελίδα"
#: .\cookbook\templates\404.html:35
msgid "Report a Bug"
msgstr "Αναφορά σφάλματος"
#: .\cookbook\templates\account\email.html:6
#: .\cookbook\templates\account\email.html:10
msgid "E-mail Addresses"
msgstr "Διευθύνσεις e-mail"
#: .\cookbook\templates\account\email.html:12
msgid "The following e-mail addresses are associated with your account:"
msgstr "Οι παρακάτω διευθύνσεις e-mail συνδέονται με τον λογαριασμό σας:"
#: .\cookbook\templates\account\email.html:29
msgid "Verified"
msgstr "Πιστοποιημένο"
#: .\cookbook\templates\account\email.html:31
msgid "Unverified"
msgstr "Μη πιστοποιημένο"
#: .\cookbook\templates\account\email.html:33
msgid "Primary"
msgstr "Κύριο"
#: .\cookbook\templates\account\email.html:40
msgid "Make Primary"
msgstr "Μετατροπή σε κύριο"
#: .\cookbook\templates\account\email.html:42
msgid "Re-send Verification"
msgstr "Επαναποστολή της επαλήθευσης"
#: .\cookbook\templates\account\email.html:43
#: .\cookbook\templates\socialaccount\connections.html:36
msgid "Remove"
msgstr "Αφαίρεση"
#: .\cookbook\templates\account\email.html:51
msgid "Warning:"
msgstr "Προειδοποίηση:"
#: .\cookbook\templates\account\email.html:51
msgid ""
"You currently do not have any e-mail address set up. You should really add "
"an e-mail address so you can receive notifications, reset your password, etc."
msgstr ""
"Προς το παρόν, δεν έχετε καμία διεύθυνση e-mail καταχωρημένη. Θα πρέπει να "
"προσθέσετε μια διεύθυνση ηλεκτρονικού ταχυδρομείου, ώστε να μπορείτε να "
"λαμβάνετε ειδοποιήσεις, να επαναφέρετε τον κωδικό πρόσβασης, κ.λπ."
#: .\cookbook\templates\account\email.html:57
msgid "Add E-mail Address"
msgstr "Προσθήκη διεύθυνσης e-mail"
#: .\cookbook\templates\account\email.html:62
msgid "Add E-mail"
msgstr "Προσθήκη e-mail"
#: .\cookbook\templates\account\email.html:72
msgid "Do you really want to remove the selected e-mail address?"
msgstr "Θέλετε πραγματικά να αφαιρέσετε την επιλεγμένη διεύθυνση e-mail;"
#: .\cookbook\templates\account\email_confirm.html:6
#: .\cookbook\templates\account\email_confirm.html:10
msgid "Confirm E-mail Address"
msgstr "Επιβεβαίωση διεύθυνσης e-mail"
#: .\cookbook\templates\account\email_confirm.html:16
#, python-format
msgid ""
"Please confirm that\n"
" %(email)s is an e-mail address "
"for user %(user_display)s\n"
" ."
msgstr ""
"Παρακαλώ επιβεβαιώστε ότι το\n"
" %(email)s είναι μια διεύθυνση "
"e-mail για τον χρήστη %(user_display)s\n"
" ."
#: .\cookbook\templates\account\email_confirm.html:22
msgid "Confirm"
msgstr "Επιβεβαίωση"
#: .\cookbook\templates\account\email_confirm.html:29
#, python-format
msgid ""
"This e-mail confirmation link expired or is invalid. Please\n"
" issue a new e-mail confirmation "
"request."
msgstr ""
"Αυτός ο σύνδεσμος επιβεβαίωσης έχει λήξει είναι δεν είναι έγκυρος. "
"Παρακαλώ \n"
" κάντε ένα νέο αίτημα για επιβεβαιωτικό "
"e-mail."
#: .\cookbook\templates\account\login.html:8
#: .\cookbook\templates\openid\login.html:8
msgid "Login"
msgstr "Σύνδεση"
#: .\cookbook\templates\account\login.html:15
#: .\cookbook\templates\account\login.html:31
#: .\cookbook\templates\account\password_reset.html:39
#: .\cookbook\templates\account\password_reset_done.html:31
#: .\cookbook\templates\account\signup.html:68
#: .\cookbook\templates\account\signup_closed.html:15
#: .\cookbook\templates\openid\login.html:15
#: .\cookbook\templates\openid\login.html:26
#: .\cookbook\templates\socialaccount\authentication_error.html:15
msgid "Sign In"
msgstr "Σύνδεση"
#: .\cookbook\templates\account\login.html:34
#: .\cookbook\templates\account\password_reset.html:41
#: .\cookbook\templates\account\password_reset_done.html:33
#: .\cookbook\templates\socialaccount\signup.html:8
#: .\cookbook\templates\socialaccount\signup.html:56
msgid "Sign Up"
msgstr "Εγγραφή"
#: .\cookbook\templates\account\login.html:38
msgid "Lost your password?"
msgstr "Χασάτε τον κωδικό πρόσβασης;"
#: .\cookbook\templates\account\login.html:39
#: .\cookbook\templates\account\password_reset.html:29
msgid "Reset My Password"
msgstr "Επαναφορά κωδικού πρόσβασης"
#: .\cookbook\templates\account\login.html:50
msgid "Social Login"
msgstr "Σύνδεση με social media"
#: .\cookbook\templates\account\login.html:51
msgid "You can use any of the following providers to sign in."
msgstr ""
"Μπορείτε να χρησιμοποιήσετε οποιονδήποτε από τους παρακάτω παρόχους για να "
"συνδεθείτε."
#: .\cookbook\templates\account\logout.html:5
#: .\cookbook\templates\account\logout.html:9
#: .\cookbook\templates\account\logout.html:18
msgid "Sign Out"
msgstr "Αποσύνδεση"
#: .\cookbook\templates\account\logout.html:11
msgid "Are you sure you want to sign out?"
msgstr "Είστε σίγουροι ότι θέλετε να αποσυνδεθείτε;"
#: .\cookbook\templates\account\password_change.html:6
#: .\cookbook\templates\account\password_change.html:10
#: .\cookbook\templates\account\password_change.html:15
#: .\cookbook\templates\account\password_reset_from_key.html:7
#: .\cookbook\templates\account\password_reset_from_key.html:13
#: .\cookbook\templates\account\password_reset_from_key_done.html:7
#: .\cookbook\templates\account\password_reset_from_key_done.html:13
msgid "Change Password"
msgstr "Αλλαγή κωδικού πρόσβασης"
#: .\cookbook\templates\account\password_change.html:16
msgid "Forgot Password?"
msgstr "Ξεχάσατε τον κωδικό πρόσβασης;"
#: .\cookbook\templates\account\password_reset.html:7
#: .\cookbook\templates\account\password_reset.html:13
#: .\cookbook\templates\account\password_reset_done.html:7
#: .\cookbook\templates\account\password_reset_done.html:18
msgid "Password Reset"
msgstr "Επαναφορά κωδικού πρόσβασης"
#: .\cookbook\templates\account\password_reset.html:24
msgid ""
"Forgotten your password? Enter your e-mail address below, and we'll send you "
"an e-mail allowing you to reset it."
msgstr ""
"Ξεχάσατε τον κωδικό πρόσβασης σας; Εισάγετε τη διεύθυνση ηλεκτρονικού "
"ταχυδρομείου σας παρακάτω και θα σας στείλουμε ένα email που θα σας "
"επιτρέψει να τον επαναφέρετε."
#: .\cookbook\templates\account\password_reset.html:32
msgid "Password reset is disabled on this instance."
msgstr ""
"Η επαναφορά κωδικού πρόσβασης είναι απενεργοποιημένη σε αυτήν την πλατφόρμα."
#: .\cookbook\templates\account\password_reset_done.html:25
msgid ""
"We have sent you an e-mail. Please contact us if you do not receive it "
"within a few minutes."
msgstr ""
"Σας έχουμε στείλει ένα email. Παρακαλούμε επικοινωνήστε μαζί μας αν δεν το "
"λάβετε εντός λίγων λεπτών."
#: .\cookbook\templates\account\password_reset_from_key.html:13
msgid "Bad Token"
msgstr "Μη έγκυρο token"
#: .\cookbook\templates\account\password_reset_from_key.html:25
#, python-format
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used.\n"
" Please request a new "
"password reset."
msgstr ""
"Ο σύνδεσμος επαναφοράς κωδικού πρόσβασης ήταν άκυρος, πιθανώς επειδή έχει "
"ήδη χρησιμοποιηθεί.\n"
" Παρακαλώ ζητήστε έναν νέο σύνδεσμο επαναφοράς κωδικού πρόσβασης."
#: .\cookbook\templates\account\password_reset_from_key.html:33
msgid "change password"
msgstr "Αλλαγή κωδικού πρόσβασης"
#: .\cookbook\templates\account\password_reset_from_key.html:36
#: .\cookbook\templates\account\password_reset_from_key_done.html:19
msgid "Your password is now changed."
msgstr "Ο κωδικός πρόσβασης σας έχει αλλάξει."
#: .\cookbook\templates\account\password_set.html:6
#: .\cookbook\templates\account\password_set.html:10
#: .\cookbook\templates\account\password_set.html:15
msgid "Set Password"
msgstr "Ορισμός Κωδικού Πρόσβασης"
#: .\cookbook\templates\account\signup.html:5
msgid "Register"
msgstr "Εγγραφή"
#: .\cookbook\templates\account\signup.html:11
msgid "Create an Account"
msgstr "Δημιουργία λογαριασμού"
#: .\cookbook\templates\account\signup.html:41
msgid "I accept the follwoing"
msgstr "Αποδέχομαι τα παρακάτω"
#: .\cookbook\templates\account\signup.html:44
#: .\cookbook\templates\socialaccount\signup.html:35
msgid "Terms and Conditions"
msgstr "Όροι και προϋποθέσεις"
#: .\cookbook\templates\account\signup.html:47
#: .\cookbook\templates\socialaccount\signup.html:38
msgid "and"
msgstr "και"
#: .\cookbook\templates\account\signup.html:51
#: .\cookbook\templates\socialaccount\signup.html:42
msgid "Privacy Policy"
msgstr "Πολιτική απορρήτου"
#: .\cookbook\templates\account\signup.html:64
msgid "Create User"
msgstr "Δημιουργία χρήστη"
#: .\cookbook\templates\account\signup.html:68
msgid "Already have an account?"
msgstr "Έχετε ήδη λογαριασμό;"
#: .\cookbook\templates\account\signup_closed.html:5
#: .\cookbook\templates\account\signup_closed.html:11
msgid "Sign Up Closed"
msgstr "Οι εγγραφές έκλεισαν"
#: .\cookbook\templates\account\signup_closed.html:13
msgid "We are sorry, but the sign up is currently closed."
msgstr "Λυπούμαστε, αλλά οι εγγραφές έχουν ήδη κλείσει."
#: .\cookbook\templates\frontend\tandoor.html:15
#, fuzzy
#| msgid "Tandoor Recipes Invite"
msgid "Tandoor Recipe Manager"
msgstr "Πρόσκληση στο Tandoor Recipes"
#: .\cookbook\templates\index.html:28
msgid "Search recipe ..."
msgstr "Αναζήτηση συνταγής ..."
#: .\cookbook\templates\index.html:43
msgid "New Recipe"
msgstr "Νέα συνταγή"
#: .\cookbook\templates\index.html:46
msgid "Import Recipe"
msgstr "Εισαγωγή συνταγής"
#: .\cookbook\templates\index.html:52
msgid "Advanced Search"
msgstr "Αναζήτηση για προχωρημένους"
#: .\cookbook\templates\index.html:56
msgid "Reset Search"
msgstr "Επαναφορά αναζήτησης"
#: .\cookbook\templates\index.html:84
msgid "Last viewed"
msgstr "Τελευταίες προβολές"
#: .\cookbook\templates\index.html:86
msgid "Recipes"
msgstr "Συνταγές"
#: .\cookbook\templates\index.html:93
msgid "Log in to view recipes"
msgstr "Συνδεθείτε για να δείτε τις συνταγές"
#: .\cookbook\templates\markdown_info.html:5
#: .\cookbook\templates\markdown_info.html:13
msgid "Markdown Info"
msgstr "Πληροφορίες για το Markdown"
#: .\cookbook\templates\markdown_info.html:14
msgid ""
"\n"
" Markdown is lightweight markup language that can be used to format "
"plain text easily.\n"
" This site uses the Python Markdown library to\n"
" convert your text into nice looking HTML. Its full markdown "
"documentation can be found\n"
" here.\n"
" An incomplete but most likely sufficient documentation can be found "
"below.\n"
" "
msgstr ""
#: .\cookbook\templates\markdown_info.html:25
msgid "Headers"
msgstr "Επικεφαλίδες"
#: .\cookbook\templates\markdown_info.html:54
msgid "Formatting"
msgstr "Μορφοποίηση"
#: .\cookbook\templates\markdown_info.html:56
#: .\cookbook\templates\markdown_info.html:72
msgid "Line breaks are inserted by adding two spaces after the end of a line"
msgstr ""
"Οι αλλαγές γραμμής εισάγονται προσθέτοντας δύο κενά μετά το τέλος μιας "
"γραμμής"
#: .\cookbook\templates\markdown_info.html:57
#: .\cookbook\templates\markdown_info.html:73
msgid "or by leaving a blank line in between."
msgstr "ή αφήνοντας μια κενή γραμμή μεταξύ τους."
#: .\cookbook\templates\markdown_info.html:59
#: .\cookbook\templates\markdown_info.html:74
msgid "This text is bold"
msgstr "Το κείμενο είναι έντονο (bold)"
#: .\cookbook\templates\markdown_info.html:60
#: .\cookbook\templates\markdown_info.html:75
msgid "This text is italic"
msgstr "Αυτό το κείμενο είναι πλάγιο (italic)"
#: .\cookbook\templates\markdown_info.html:61
#: .\cookbook\templates\markdown_info.html:77
msgid "Blockquotes are also possible"
msgstr ""
#: .\cookbook\templates\markdown_info.html:84
msgid "Lists"
msgstr "Λίστες"
#: .\cookbook\templates\markdown_info.html:85
msgid ""
"Lists can ordered or unordered. It is important to leave a blank line "
"before the list!"
msgstr ""
#: .\cookbook\templates\markdown_info.html:87
#: .\cookbook\templates\markdown_info.html:108
msgid "Ordered List"
msgstr ""
#: .\cookbook\templates\markdown_info.html:89
#: .\cookbook\templates\markdown_info.html:90
#: .\cookbook\templates\markdown_info.html:91
#: .\cookbook\templates\markdown_info.html:110
#: .\cookbook\templates\markdown_info.html:111
#: .\cookbook\templates\markdown_info.html:112
msgid "unordered list item"
msgstr ""
#: .\cookbook\templates\markdown_info.html:93
#: .\cookbook\templates\markdown_info.html:114
msgid "Unordered List"
msgstr ""
#: .\cookbook\templates\markdown_info.html:95
#: .\cookbook\templates\markdown_info.html:96
#: .\cookbook\templates\markdown_info.html:97
#: .\cookbook\templates\markdown_info.html:116
#: .\cookbook\templates\markdown_info.html:117
#: .\cookbook\templates\markdown_info.html:118
msgid "ordered list item"
msgstr ""
#: .\cookbook\templates\markdown_info.html:125
msgid "Images & Links"
msgstr "Φωτογραφίες και σύνδεσμοι"
#: .\cookbook\templates\markdown_info.html:126
msgid ""
"Links can be formatted with Markdown. This application also allows to paste "
"links directly into markdown fields without any formatting."
msgstr ""
#: .\cookbook\templates\markdown_info.html:132
#: .\cookbook\templates\markdown_info.html:145
msgid "This will become an image"
msgstr ""
#: .\cookbook\templates\markdown_info.html:152
msgid "Tables"
msgstr ""
#: .\cookbook\templates\markdown_info.html:153
msgid ""
"Markdown tables are hard to create by hand. It is recommended to use a table "
"editor like this one."
msgstr ""
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:171
#: .\cookbook\templates\markdown_info.html:177
msgid "Table"
msgstr ""
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:172
msgid "Header"
msgstr ""
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:178
msgid "Cell"
msgstr ""
#: .\cookbook\templates\no_groups_info.html:5
#: .\cookbook\templates\no_groups_info.html:12
msgid "No Permissions"
msgstr ""
#: .\cookbook\templates\no_groups_info.html:17
msgid "You do not have any groups and therefor cannot use this application."
msgstr ""
#: .\cookbook\templates\no_groups_info.html:18
#: .\cookbook\templates\no_perm_info.html:15
msgid "Please contact your administrator."
msgstr ""
#: .\cookbook\templates\no_perm_info.html:5
#: .\cookbook\templates\no_perm_info.html:12
msgid "No Permission"
msgstr ""
#: .\cookbook\templates\no_perm_info.html:15
msgid ""
"You do not have the required permissions to view this page or perform this "
"action."
msgstr ""
#: .\cookbook\templates\offline.html:5
msgid "Offline"
msgstr ""
#: .\cookbook\templates\openid\login.html:27
#: .\cookbook\templates\socialaccount\authentication_error.html:27
msgid "Back"
msgstr ""
#: .\cookbook\templates\rest_framework\api.html:5
msgid "Recipe Home"
msgstr ""
#: .\cookbook\templates\rest_framework\api.html:11
msgid "API Documentation"
msgstr "Τεκμηρίωση API"
#: .\cookbook\templates\search_info.html:5
#: .\cookbook\templates\search_info.html:9
msgid "Search Settings"
msgstr ""
#: .\cookbook\templates\search_info.html:10
msgid ""
"\n"
" Creating the best search experience is complicated and weighs "
"heavily on your personal configuration. \n"
" Changing any of the search settings can have significant impact on "
"the speed and quality of the results.\n"
" Search Methods, Trigrams and Full Text Search configurations are "
"only available if you are using Postgres for your database.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:19
msgid "Search Methods"
msgstr ""
#: .\cookbook\templates\search_info.html:23
msgid ""
" \n"
" Full text searches attempt to normalize the words provided to "
"match common variants. For example: 'forked', 'forking', 'forks' will all "
"normalize to 'fork'.\n"
" There are several methods available, described below, that will "
"control how the search behavior should react when multiple words are "
"searched.\n"
" Full technical details on how these operate can be viewed on Postgresql's website.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:29
msgid ""
" \n"
" Simple searches ignore punctuation and common words such as "
"'the', 'a', 'and'. And will treat separate words as required.\n"
" Searching for 'apple or flour' will return any recipe that "
"includes both 'apple' and 'flour' anywhere in the fields that have been "
"selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:34
msgid ""
" \n"
" Phrase searches ignore punctuation, but will search for all of "
"the words in the exact order provided.\n"
" Searching for 'apple or flour' will only return a recipe that "
"includes the exact phrase 'apple or flour' in any of the fields that have "
"been selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:39
msgid ""
" \n"
" Web searches simulate functionality found on many web search "
"sites supporting special syntax.\n"
" Placing quotes around several words will convert those words "
"into a phrase.\n"
" 'or' is recognized as searching for the word (or phrase) "
"immediately before 'or' OR the word (or phrase) directly after.\n"
" '-' is recognized as searching for recipes that do not include "
"the word (or phrase) that comes immediately after. \n"
" For example searching for 'apple pie' or cherry -butter will "
"return any recipe that includes the phrase 'apple pie' or the word "
"'cherry' \n"
" in any field included in the full text search but exclude any "
"recipe that has the word 'butter' in any field included.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:48
msgid ""
" \n"
" Raw search is similar to Web except will take puncuation "
"operators such as '|', '&' and '()'\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:59
msgid ""
" \n"
" Another approach to searching that also requires Postgresql is "
"fuzzy search or trigram similarity. A trigram is a group of three "
"consecutive characters.\n"
" For example searching for 'apple' will create x trigrams 'app', "
"'ppl', 'ple' and will create a score of how closely words match the "
"generated trigrams.\n"
" One benefit of searching trigams is that a search for 'sandwich' "
"will find misspelled words such as 'sandwhich' that would be missed by other "
"methods.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:69
msgid "Search Fields"
msgstr ""
#: .\cookbook\templates\search_info.html:73
msgid ""
" \n"
" Unaccent is a special case in that it enables searching a field "
"'unaccented' for each search style attempting to ignore accented values. \n"
" For example when you enable unaccent for 'Name' any search "
"(starts with, contains, trigram) will attempt the search ignoring accented "
"characters.\n"
" \n"
" For the other options, you can enable search on any or all "
"fields and they will be combined together with an assumed 'OR'.\n"
" For example enabling 'Name' for Starts With, 'Name' and "
"'Description' for Partial Match and 'Ingredients' and 'Keywords' for Full "
"Search\n"
" and searching for 'apple' will generate a search that will "
"return recipes that have:\n"
" - A recipe name that starts with 'apple'\n"
" - OR a recipe name that contains 'apple'\n"
" - OR a recipe description that contains 'apple'\n"
" - OR a recipe that will have a full text search match ('apple' "
"or 'apples') in ingredients\n"
" - OR a recipe that will have a full text search match in "
"Keywords\n"
"\n"
" Combining too many fields in too many types of search can have a "
"negative impact on performance, create duplicate results or return "
"unexpected results.\n"
" For example, enabling fuzzy search or partial matches will "
"interfere with web search methods. \n"
" Searching for 'apple -pie' with fuzzy search and full text "
"search will return the recipe Apple Pie. Though it is not included in the "
"full text results, it does match the trigram results.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:95
msgid "Search Index"
msgstr ""
#: .\cookbook\templates\search_info.html:99
msgid ""
" \n"
" Trigram search and Full Text Search both rely on database "
"indexes to perform effectively. \n"
" You can rebuild the indexes on all fields in the Admin page for "
"Recipes and selecting all recipes and running 'rebuild index for selected "
"recipes'\n"
" You can also rebuild indexes at the command line by executing "
"the management command 'python manage.py rebuildindex'\n"
" "
msgstr ""
#: .\cookbook\templates\setup.html:6
msgid "Cookbook Setup"
msgstr ""
#: .\cookbook\templates\setup.html:14
msgid "Setup"
msgstr ""
#: .\cookbook\templates\setup.html:15
msgid ""
"To start using this application you must first create a superuser account."
msgstr ""
#: .\cookbook\templates\setup.html:20
msgid "Create Superuser account"
msgstr ""
#: .\cookbook\templates\socialaccount\authentication_error.html:7
#: .\cookbook\templates\socialaccount\authentication_error.html:23
msgid "Social Network Login Failure"
msgstr ""
#: .\cookbook\templates\socialaccount\authentication_error.html:25
msgid ""
"An error occurred while attempting to login via your social network account."
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:4
#: .\cookbook\templates\socialaccount\connections.html:7
msgid "Account Connections"
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:10
msgid ""
"You can sign in to your account using any of the following third party\n"
" accounts:"
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:44
msgid ""
"You currently have no social network accounts connected to this account."
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:47
msgid "Add a 3rd Party Account"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:5
#: .\cookbook\templates\socialaccount\signup.html:5
msgid "Signup"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:9
#, python-format
msgid "Connect %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:11
#, python-format
msgid "You are about to connect a new third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:13
#, python-format
msgid "Sign In Via %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:15
#, python-format
msgid "You are about to sign in using a third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:20
msgid "Continue"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:10
#, python-format
msgid ""
"You are about to use your\n"
" %(provider_name)s account to login to\n"
" %(site_name)s. As a final step, please complete the following form:"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:32
#, fuzzy
#| msgid "I accept the follwoing"
msgid "I accept the following"
msgstr "Αποδέχομαι τα παρακάτω"
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:23
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:31
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:39
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:47
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:55
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:63
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:71
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:79
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:87
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:95
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:103
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:111
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:119
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:127
msgid "Sign in using"
msgstr ""
#: .\cookbook\templates\space_overview.html:6
msgid "Overview"
msgstr "Σύνοψη"
#: .\cookbook\templates\space_overview.html:13
msgid "Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:17
msgid ""
"Recipes, foods, shopping lists and more are organized in spaces of one or "
"more people."
msgstr ""
#: .\cookbook\templates\space_overview.html:18
msgid ""
"You can either be invited into an existing space or create your own one."
msgstr ""
#: .\cookbook\templates\space_overview.html:25
msgid "Your Spaces"
msgstr "Οι χώροι σας"
#: .\cookbook\templates\space_overview.html:53
msgid "Owner"
msgstr ""
#: .\cookbook\templates\space_overview.html:73
#: .\cookbook\templates\space_overview.html:83
msgid "Join Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:76
msgid "Join an existing space."
msgstr ""
#: .\cookbook\templates\space_overview.html:78
msgid ""
"To join an existing space either enter your invite token or click on the "
"invite link the space owner send you."
msgstr ""
#: .\cookbook\templates\space_overview.html:91
#: .\cookbook\templates\space_overview.html:100
msgid "Create Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:94
msgid "Create your own recipe space."
msgstr ""
#: .\cookbook\templates\space_overview.html:96
msgid "Start your own recipe space and invite other users to it."
msgstr ""
#: .\cookbook\templates\system.html:23
msgid "System"
msgstr "Σύστημα"
#: .\cookbook\templates\system.html:24
msgid ""
"\n"
" Tandoor Recipes is an open source free software application. It can "
"be found on\n"
" GitHub.\n"
" Changelogs can be found here.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:30
msgid "System Information"
msgstr ""
#: .\cookbook\templates\system.html:51
msgid ""
"\n"
" You need to execute version.py in your update "
"script to generate version information (done automatically in docker).\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:56
msgid "Plugins"
msgstr ""
#: .\cookbook\templates\system.html:67
msgid "Media Serving"
msgstr ""
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:123 .\cookbook\templates\system.html:134
msgid "Warning"
msgstr ""
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:125 .\cookbook\templates\system.html:134
msgid "Ok"
msgstr ""
#: .\cookbook\templates\system.html:70
msgid ""
"Serving media files directly using gunicorn/python is not recommend!\n"
" Please follow the steps described\n"
" here to update\n"
" your installation.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:76 .\cookbook\templates\system.html:91
#: .\cookbook\templates\system.html:104 .\cookbook\templates\system.html:115
#: .\cookbook\views\views.py:168
msgid "Everything is fine!"
msgstr ""
#: .\cookbook\templates\system.html:80
msgid "Secret Key"
msgstr ""
#: .\cookbook\templates\system.html:84
msgid ""
"\n"
" You do not have a SECRET_KEY configured in your "
".env file. Django defaulted to the\n"
" standard key\n"
" provided with the installation which is publicly know and "
"insecure! Please set\n"
" SECRET_KEY int the .env configuration "
"file.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:94
msgid "Debug Mode"
msgstr ""
#: .\cookbook\templates\system.html:98
msgid ""
"\n"
" This application is still running in debug mode. This is most "
"likely not needed. Turn of debug mode by\n"
" setting\n"
" DEBUG=0 int the .env configuration "
"file.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:107
msgid "Allowed Hosts"
msgstr ""
#: .\cookbook\templates\system.html:111
msgid ""
"\n"
" Your allowed hosts are configured to allow every host. This "
"might be ok in some setups but should be avoided. Please see the docs about "
"this.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:118
msgid "Database"
msgstr ""
#: .\cookbook\templates\system.html:121
msgid "Info"
msgstr ""
#: .\cookbook\templates\system.html:131 .\cookbook\templates\system.html:148
#, fuzzy
#| msgid "Use fractions"
msgid "Migrations"
msgstr "Χρήση κλασμάτων"
#: .\cookbook\templates\system.html:137
msgid ""
"\n"
" Migrations should never fail!\n"
" Failed migrations will likely cause major parts of the app to "
"not function correctly.\n"
" If a migration fails make sure you are on the latest version and "
"if so please post the migration log and the overview below in a GitHub "
"issue.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "False"
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "True"
msgstr ""
#: .\cookbook\templates\system.html:268
msgid "Hide"
msgstr ""
#: .\cookbook\templates\system.html:271
#, fuzzy
#| msgid "Show Log"
msgid "Show"
msgstr "Προβολή αρχείων καταγραφής"
#: .\cookbook\templates\test2.html:6
msgid "Export Recipes"
msgstr "Εξαγωγή Συνταγών"
#: .\cookbook\templates\test2.html:14 .\cookbook\templates\test2.html:20
msgid "Export"
msgstr "Εξαγωγή"
#: .\cookbook\views\api.py:198 .\cookbook\views\api.py:326
msgid "Parameter updated_at incorrectly formatted"
msgstr ""
#: .\cookbook\views\api.py:351 .\cookbook\views\api.py:484
#, python-brace-format
msgid "No {self.basename} with id {pk} exists"
msgstr ""
#: .\cookbook\views\api.py:355
msgid "Cannot merge with the same object!"
msgstr ""
#: .\cookbook\views\api.py:362
#, python-brace-format
msgid "No {self.basename} with id {target} exists"
msgstr ""
#: .\cookbook\views\api.py:367
msgid "Cannot merge with child object!"
msgstr ""
#: .\cookbook\views\api.py:405
#, python-brace-format
msgid "{source.name} was merged successfully with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:411
#, python-brace-format
msgid "An error occurred attempting to merge {source.name} with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:493
#, python-brace-format
msgid "{child.name} was moved successfully to the root."
msgstr ""
#: .\cookbook\views\api.py:496 .\cookbook\views\api.py:514
msgid "An error occurred attempting to move "
msgstr ""
#: .\cookbook\views\api.py:499
msgid "Cannot move an object to itself!"
msgstr ""
#: .\cookbook\views\api.py:505
#, python-brace-format
msgid "No {self.basename} with id {parent} exists"
msgstr ""
#: .\cookbook\views\api.py:511
#, python-brace-format
msgid "{child.name} was moved successfully to parent {parent.name}"
msgstr ""
#: .\cookbook\views\api.py:992
#, python-brace-format
msgid "{obj.name} was removed from the shopping list."
msgstr ""
#: .\cookbook\views\api.py:997 .\cookbook\views\api.py:1627
#, python-brace-format
msgid "{obj.name} was added to the shopping list."
msgstr ""
#: .\cookbook\views\api.py:1239
msgid "Filter meal plans from date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1241
msgid "Filter meal plans to date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1244
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1404
msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1406
msgid "Query string matched (fuzzy) against object name."
msgstr ""
#: .\cookbook\views\api.py:1442
msgid ""
"Query string matched (fuzzy) against recipe name. In the future also "
"fulltext search."
msgstr ""
#: .\cookbook\views\api.py:1444
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr ""
#: .\cookbook\views\api.py:1445
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr ""
#: .\cookbook\views\api.py:1446
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1447
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1448
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1450
msgid "ID of food a recipe should have. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1451
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr ""
#: .\cookbook\views\api.py:1452
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1453
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr ""
#: .\cookbook\views\api.py:1454
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1456
msgid "ID of book a recipe should be in. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1457
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr ""
#: .\cookbook\views\api.py:1458
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1459
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr ""
#: .\cookbook\views\api.py:1460
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1462
msgid "ID of unit a recipe should have."
msgstr ""
#: .\cookbook\views\api.py:1464
msgid "Exact rating of recipe"
msgstr ""
#: .\cookbook\views\api.py:1465
msgid "Rating a recipe should have or greater."
msgstr ""
#: .\cookbook\views\api.py:1466
msgid "Rating a recipe should have or smaller."
msgstr ""
#: .\cookbook\views\api.py:1468
msgid "Filter recipes cooked X times."
msgstr ""
#: .\cookbook\views\api.py:1469
msgid "Filter recipes cooked X times or more."
msgstr ""
#: .\cookbook\views\api.py:1470
msgid "Filter recipes cooked X times or less."
msgstr ""
#: .\cookbook\views\api.py:1472
msgid "Filter recipes created on the given date."
msgstr ""
#: .\cookbook\views\api.py:1473
msgid "Filter recipes created on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1474
msgid "Filter recipes created on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1476 .\cookbook\views\api.py:1477
#: .\cookbook\views\api.py:1478
msgid "Filter recipes updated on the given date."
msgstr ""
#: .\cookbook\views\api.py:1480
msgid "Filter recipes last cooked on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1481
msgid "Filter recipes last cooked on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1483 .\cookbook\views\api.py:1484
msgid "Filter recipes lasts viewed on the given date."
msgstr ""
#: .\cookbook\views\api.py:1486
msgid "Filter recipes for ones created by the given user ID"
msgstr ""
#: .\cookbook\views\api.py:1487
msgid "If only internal recipes should be returned. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1488
msgid "Returns the results in randomized order. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1490
msgid ""
"Determines the order of the results. Options are: score,-score,name,-name,"
"lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-"
"created_at,lastviewed,-lastviewed"
msgstr ""
#: .\cookbook\views\api.py:1492
msgid "Returns new results first in search results. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1493
msgid ""
"Returns the given number of recently viewed recipes before search results "
"(if given)"
msgstr ""
#: .\cookbook\views\api.py:1494
msgid "ID of a custom filter. Returns all recipes matched by that filter."
msgstr ""
#: .\cookbook\views\api.py:1495
msgid "Filter recipes that can be made with OnHand food. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1773
msgid ""
"Return the PropertyTypes matching the property category. Repeat for "
"multiple."
msgstr ""
#: .\cookbook\views\api.py:1804 .\cookbook\views\api.py:1860
msgid "Returns only entries associated with the given mealplan id"
msgstr ""
#: .\cookbook\views\api.py:1858
msgid ""
"Returns only elements updated after the given timestamp in ISO 8601 format."
msgstr ""
#: .\cookbook\views\api.py:2031
msgid ""
"Return the Automations matching the automation type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2048
msgid ""
"Text field to store data that gets carried over to the UserSpace created "
"from the InviteLink"
msgstr ""
#: .\cookbook\views\api.py:2049
msgid "Only return InviteLinks that have not been used yet."
msgstr ""
#: .\cookbook\views\api.py:2076
msgid "Return the CustomFilters matching the model type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2176
msgid "Nothing to do."
msgstr ""
#: .\cookbook\views\api.py:2222
msgid "Invalid Url"
msgstr ""
#: .\cookbook\views\api.py:2228
msgid "Connection Refused."
msgstr ""
#: .\cookbook\views\api.py:2232
msgid "Bad URL Schema."
msgstr ""
#: .\cookbook\views\api.py:2257
msgid "No usable data could be found."
msgstr ""
#: .\cookbook\views\api.py:2286 .\cookbook\views\api.py:2434
msgid "You must select an AI provider to perform your request."
msgstr ""
#: .\cookbook\views\api.py:2293 .\cookbook\views\api.py:2441
msgid ""
"You don't have any credits remaining to use AI or AI features are not "
"enabled for your space."
msgstr ""
#: .\cookbook\views\api.py:2499 .\cookbook\views\api.py:2667
msgid "File is above space limit"
msgstr ""
#: .\cookbook\views\api.py:2522 .\cookbook\views\api.py:2684
msgid "Importing is not implemented for this provider"
msgstr ""
#: .\cookbook\views\api.py:2548
msgid ""
"The PDF Exporter is not enabled on this instance as it is still in an "
"experimental state."
msgstr ""
#: .\cookbook\views\api.py:2842
msgid "This feature is not yet available in the hosted version of tandoor!"
msgstr ""
#: .\cookbook\views\api.py:2863
msgid "Sync successful!"
msgstr ""
#: .\cookbook\views\api.py:2866
msgid "Error synchronizing with Storage"
msgstr ""
#: .\cookbook\views\views.py:89
msgid "This feature is not available in the demo version!"
msgstr ""
#: .\cookbook\views\views.py:110
msgid ""
"You have successfully created your own recipe space. Start by adding some "
"recipes or invite other people to join you."
msgstr ""
#: .\cookbook\views\views.py:171
#, python-format
msgid "PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!"
msgstr ""
#: .\cookbook\views\views.py:174
#, python-format
msgid "You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended"
msgstr ""
#: .\cookbook\views\views.py:178
msgid "Unable to determine PostgreSQL version."
msgstr ""
#: .\cookbook\views\views.py:182
msgid ""
"This application is not running with a Postgres database backend. This is ok "
"but not recommended as some features only work with postgres databases."
msgstr ""
#: .\cookbook\views\views.py:296
msgid ""
"The setup page can only be used to create the first "
"user! If you have forgotten your superuser credentials "
"please consult the django documentation on how to reset passwords."
msgstr ""
#: .\cookbook\views\views.py:304
msgid "Passwords dont match!"
msgstr ""
#: .\cookbook\views\views.py:312
msgid "User has been created, please login!"
msgstr ""
#: .\cookbook\views\views.py:352
msgid ""
"Reporting share links is not enabled for this instance. Please notify the "
"page administrator to report problems."
msgstr ""
#: .\cookbook\views\views.py:357
msgid ""
"Recipe sharing link has been disabled! For additional information please "
"contact the page administrator."
msgstr ""
"Ο σύνδεσμος κοινοποίησης συνταγής έχει απενεργοποιηθεί! Για περαιτέρω "
"πληροφορίες, παρακαλώ επικοινωνήστε με τον διαχειριστή της σελίδας."
#: .\cookbook\views\views.py:383
msgid "Manage recipes, shopping list, meal plans and more."
msgstr ""
#: .\cookbook\views\views.py:397 .\cookbook\views\views.py:398
#, fuzzy
#| msgid "Meal-Plan"
msgid "Plan"
msgstr "Πραγματισμός γευμάτων"
#: .\cookbook\views\views.py:399
msgid "View your meal Plan"
msgstr ""
#: .\cookbook\views\views.py:418
#, fuzzy
#| msgid "Users with whom to share shopping lists."
msgid "View your shopping lists"
msgstr "Χρήστες με του οποίους θα γίνει κοινοποίηση των λιστών αγορών."
#~ msgid ""
#~ "Both fields are optional. If none are given the username will be "
#~ "displayed instead"
#~ msgstr ""
#~ "Και τα δύο πεδία είναι προαιρετικά. Αν κανένα δεν συμπληρωθεί, α "
#~ "εμφανιστεί αντί αυτών το όνομα χρήστη"
#~ msgid "Name"
#~ msgstr "Όνομα"
#~ msgid "Keywords"
#~ msgstr "Λέξεις Κλειδιά"
#~ msgid "Preparation time in minutes"
#~ msgstr "Χρόνος προετοιμασίας σε λεπτά"
#~ msgid "Waiting time (cooking/baking) in minutes"
#~ msgstr "Χρόνος αναμονής (μαγείρεμα/ ψήσιμο) σε λεπτά"
#~ msgid "Path"
#~ msgstr "Διαδρομή"
#~ msgid "Storage UID"
#~ msgstr "Αναγνωριστικό αποθήκευσης (Storage UID)"
#~ msgid "Add your comment: "
#~ msgstr "Προσθήκη σχολίου: "
#~ msgid "Leave empty for dropbox and enter app password for nextcloud."
#~ msgstr ""
#~ "Για dropbox παρακαλώ αφήστε το κενό και πληκτρολογήστε το password για "
#~ "nextcloud."
#~ msgid "Leave empty for nextcloud and enter api token for dropbox."
#~ msgstr ""
#~ "Για nextcloud αφήστε το κενό και για dropbox πληκτρολογήστε το api token."
#~ msgid ""
#~ "Leave empty for dropbox and enter only base url for nextcloud (/"
#~ "remote.php/webdav/ is added automatically)"
#~ msgstr ""
#~ "Αφήστε το κενό για το Dropbox και εισάγετε μόνο τη βασική διεύθυνση URL "
#~ "για το Nextcloud (το /remote.php/webdav/ προστίθεται "
#~ "αυτόματα)"
#~ msgid "http://homeassistant.local:8123/api for example"
#~ msgstr "http://homeassistant.local:8123/api για παράδειγμα"
#~ msgid "Storage"
#~ msgstr "Χώρος αποθήκευσης"
#~ msgid "Active"
#~ msgstr "Ενεργό"
#~ msgid "Search String"
#~ msgstr "Κείμενο αναζήτησης"
#~ msgid "File ID"
#~ msgstr "ID αρχείου"
#~ msgid ""
#~ "Determines how fuzzy a search is if it uses trigram similarity matching "
#~ "(e.g. low values mean more typos are ignored)."
#~ msgstr ""
#~ "Καθορίζει πόσο ασαφής είναι η αναζήτηση εάν χρησιμοποιείται η "
#~ "αντιστοίχιση ομοιότητας τριγώνων (trigram similarity matching) (π.χ. "
#~ "χαμηλές τιμές σημαίνουν ότι αγνοούνται περισσότερα λάθη πληκτρολόγησης)."
#~ msgid ""
#~ "Select type method of search. Click here "
#~ "for full description of choices."
#~ msgstr ""
#~ "Επιλέξτε τη μέθοδο αναζήτησης. Κάντε κλικ εδώ"
#~ "a> για πλήρη περιγραφή των επιλογών."
#~ msgid ""
#~ "Use fuzzy matching on units, keywords and ingredients when editing and "
#~ "importing recipes."
#~ msgstr ""
#~ "Χρησιμοποιήστε ασαφείς (fuzzy) αντιστοιχίες σε μονάδες μέτρησης, λέξεις-"
#~ "κλειδιά και συστατικά κατά την επεξεργασία και εισαγωγή συνταγών."
#~ msgid ""
#~ "Fields to search ignoring accents. Selecting this option can improve or "
#~ "degrade search quality depending on language"
#~ msgstr ""
#~ "Πεδία αναζήτησης αγνοώντας τις τόνους. Η επιλογή αυτή μπορεί να "
#~ "βελτιώσει ή να επιδεινώσει την ποιότητα της αναζήτησης, ανάλογα με τη "
#~ "γλώσσα"
#~ msgid ""
#~ "Fields to search for partial matches. (e.g. searching for 'Pie' will "
#~ "return 'pie' and 'piece' and 'soapie')"
#~ msgstr ""
#~ "Πεδία για αναζήτηση μερικών αντιστοιχιών. (π.χ. αναζήτηση για 'πίτα' τα "
#~ "'τυρόπιτα' και 'απιτα' θα βρίσκονται στα αποτελέσματα)"
#~ msgid ""
#~ "Fields to search for beginning of word matches. (e.g. searching for 'sa' "
#~ "will return 'salad' and 'sandwich')"
#~ msgstr ""
#~ "Πεδία για αναζήτηση αρχής λέξεων. (π.χ. η αναζήτηση για το γράμμα 'σα' θα "
#~ "επιστρέψει τις λέξεις 'σαλάτα' και 'σάντουιτς')"
#~ msgid "Search Method"
#~ msgstr "Μέθοδος αναζήτησης"
#~ msgid "Ignore Accent"
#~ msgstr "Αγνόηση τόνων"
#~ msgid "Partial Match"
#~ msgstr "Μερική ταύτιση"
#~ msgid "Starts With"
#~ msgstr "Ξεκινάει με"
#~ msgid "Fuzzy Search"
#~ msgstr "Ασαφής αναζήτηση(fuzzy)"
#~ msgid "Full Text"
#~ msgstr "Πλήρες κείμενο"
#~ msgid " is part of a recipe step and cannot be deleted"
#~ msgstr " είναι μέρος ενός βήματος συνταγής και δεν μπορεί να διαγράφει"
#~ msgid "Delete"
#~ msgstr "Διαγραφή"
#~ msgid "Settings"
#~ msgstr "Ρυθμίσεις"
#~ msgid "Email"
#~ msgstr "Email"
#~ msgid "Password"
#~ msgstr "Κωδικός πρόσβασης"
#~ msgid "Foods"
#~ msgstr "Φαγητά"
#~ msgid "Units"
#~ msgstr "Μονάδες μέτρησης"
#~ msgid "Supermarket"
#~ msgstr "Supermarket"
#~ msgid "Supermarket Category"
#~ msgstr "Κατηγορία Supermarket"
#~ msgid "Automations"
#~ msgstr "Αυτοματισμοί"
#~ msgid "Files"
#~ msgstr "Αρχεία"
#~ msgid "Batch Edit"
#~ msgstr "Μαζική Επεξεργασία"
#~ msgid "History"
#~ msgstr "Ιστορικό"
#~ msgid "Ingredient Editor"
#~ msgstr "Επεξεργαστής Συστατικών"
#~ msgid "Create"
#~ msgstr "Δημιουργία"
#~ msgid "External Recipes"
#~ msgstr "Εξωτερικές Συνταγές"
#~ msgid "Space Settings"
#~ msgstr "Ρυθμίσεις χώρου"
#, fuzzy
#~| msgid "External Recipes"
#~ msgid "External Connectors"
#~ msgstr "Εξωτερικές Συνταγές"
#~ msgid "Admin"
#~ msgstr "Διαχειριστής"
#~ msgid "Markdown Guide"
#~ msgstr "Οδηγός χρήσης του Markdown"
#~ msgid "GitHub"
#~ msgstr "GitHub"
#~ msgid "Translate Tandoor"
#~ msgstr "Μεταφράστε το Tandoor"
#~ msgid "API Browser"
#~ msgstr "Περιηγητής API"
#~ msgid "Log out"
#~ msgstr "Αποσύνδεση"
#~ msgid "You are using the free version of Tandor"
#~ msgstr "Χρησιμοποιείται την δωρεάν έκδοση του Tandoor"
#~ msgid "Upgrade Now"
#~ msgstr "Αναβαθμιστείτε τώρα"
#~ msgid "Batch edit Category"
#~ msgstr "Μαζική τροποποίηση κατηγοριών"
#~ msgid "Batch edit Recipes"
#~ msgstr "Μαζική τροποποίηση Συνταγών"
#~ msgid "Add the specified keywords to all recipes containing a word"
#~ msgstr ""
#~ "Προσθέστε τις καθορισμένες λέξεις-κλειδιά σε όλες τις συνταγές που "
#~ "περιέχουν μια λέξη"
#~ msgid "Sync"
#~ msgstr "Συγχρονισμός"
#~ msgid "Manage watched Folders"
#~ msgstr "Διαχείριση φακέλων που έχουν προβληθεί"
#~ msgid ""
#~ "On this Page you can manage all storage folder locations that should be "
#~ "monitored and synced."
#~ msgstr ""
#~ "Σε αυτήν τη σελίδα μπορείτε να διαχειριστείτε όλες τις τοποθεσίες "
#~ "αποθήκευσης φακέλων που πρέπει να παρακολουθούνται και να συγχρονίζονται."
#~ msgid "The path must be in the following format"
#~ msgstr "Η διαδρομή (path) πρέπει να είναι στην ακόλουθη μορφή"
#~ msgid "Save"
#~ msgstr "Αποθήκευση"
#~ msgid "Manage External Storage"
#~ msgstr "Διαχείριση εξωτερικού χώρου αποθήκευσης"
#~ msgid "Sync Now!"
#~ msgstr "Συγχρονισμός τώρα!"
#~ msgid "Show Recipes"
#~ msgstr "Προβολή Συνταγών"
#~ msgid "Show Log"
#~ msgstr "Προβολή αρχείων καταγραφής"
#~ msgid "Importing Recipes"
#~ msgstr "Οι συνταγές εισάγονται"
#~ msgid ""
#~ "This can take a few minutes, depending on the number of recipes in sync, "
#~ "please wait."
#~ msgstr ""
#~ "Αυτή η διαδικασία μπορεί να πάρει μερικά λεπτά, ανάλογα με τον αριθμό των "
#~ "συνταγών που πρέπει να συγχρονιστούν, παρακαλώ περιμένετε."
#~ msgid "Recipe Books"
#~ msgstr "Βιβλία Συνταγών"
#~ msgid "Import new Recipe"
#~ msgstr "Εισαγωγή μια νέας συνταγή"
#~ msgid "Edit Recipe"
#~ msgstr "Τροποποίηση συνταγής"
#, python-format
#~ msgid "Are you sure you want to delete the %(title)s: %(object)s "
#~ msgstr ""
#~ "Είστε σίγουροι ότι θέλετε να διαγράψετε τα %(title)s: %(object)s "
#~ msgid "This cannot be undone!"
#~ msgstr "Αυτό δεν μπορεί να αναιρεθεί!"
#~ msgid "Cancel"
#~ msgstr "Ακύρωση"
#~ msgid "Edit"
#~ msgstr "Τροποποίηση"
#~ msgid "View"
#~ msgstr "Προβολή"
#~ msgid "Delete original file"
#~ msgstr "Διαγραφή πρωτότυπου αρχείου"
#~ msgid "List"
#~ msgstr "Λίστα"
#~ msgid "Filter"
#~ msgstr "Φίλτρο"
#~ msgid "Import all"
#~ msgstr "Εισαγωγή όλων"
#~ msgid "New"
#~ msgstr "Νέο"
#~ msgid "previous"
#~ msgstr "προηγούμενο"
#~ msgid "next"
#~ msgstr "επόμενο"
#~ msgid "View Log"
#~ msgstr "Προβολή αρχείων καταγραφής"
#~ msgid "Cook Log"
#~ msgstr "Αρχείο καταγραφής μαγειρέματος"
#~ msgid "Import"
#~ msgstr "Εισαγωγή"
#~ msgid "Security Warning"
#~ msgstr "Προειδοποίηση ασφαλείας"
#, fuzzy
#~| msgid "Ingredient Editor"
#~ msgid "Property Editor"
#~ msgstr "Επεξεργαστής Συστατικών"
#~ msgid "Comments"
#~ msgstr "Σχόλια"
#~ msgid "Ingredients"
#~ msgstr "Υλικά"
#~ msgid "Default unit"
#~ msgstr "Προεπιλεγμένη μονάδα μέτρησης"
#~ msgid "Use KJ"
#~ msgstr "Χρήση KiloJoule(KJ)"
#~ msgid "Theme"
#~ msgstr "Θέμα"
#~ msgid "Navbar color"
#~ msgstr "Χρώμα μπάρας πλοήγησης"
#~ msgid "Sticky navbar"
#~ msgstr "Σταθερή μπάρα πλοήγησης"
#~ msgid "Default page"
#~ msgstr "Προεπιλεγμένη σελίδα"
#~ msgid "Show recent recipes"
#~ msgstr "Προβολή πρόσφατων συνταγών"
#~ msgid "Search style"
#~ msgstr "Τρόπος αναζήτησης"
#~ msgid "Plan sharing"
#~ msgstr "Κοινοποίηση προγράμματος"
#~ msgid "Ingredient decimal places"
#~ msgstr "Δεκαδικά ψηφία υλικών"
#~ msgid "Shopping list auto sync period"
#~ msgstr "Χρονική περίοδος αυτόματου συγχρονισμού λίστας αγορών"
#~ msgid "Left-handed mode"
#~ msgstr "Έκδοση για αριστερόχειρες"
#~ msgid ""
#~ "Color of the top navigation bar. Not all colors work with all themes, "
#~ "just try them out!"
#~ msgstr ""
#~ "Χρώμα της πάνω μπάρας πλοήγησης. Δεν δουλεύουν όλα τα χρώματα με όλα τα "
#~ "θέματα, απλά δοκιμάστε τα!"
#~ msgid ""
#~ "Default Unit to be used when inserting a new ingredient into a recipe."
#~ msgstr ""
#~ "Προεπιλεγμένη μονάδα μέτρησης που θα χρησιμοποιείται όταν προστίθεται ένα "
#~ "υλικό σε μια συνταγή."
#~ msgid ""
#~ "Enables support for fractions in ingredient amounts (e.g. convert "
#~ "decimals to fractions automatically)"
#~ msgstr ""
#~ "Ενεργοποιεί τη υποστήριξη για κλάσματα στις ποσότητες των υλικών (π.χ. "
#~ "μετατρέπει τα δεκαδικά σε κλάσματα αυτόματα)"
#~ msgid "Display nutritional energy amounts in joules instead of calories"
#~ msgstr ""
#~ "Εμφάνιση της διατροφικής ενεργειακής αξίας σε joules αντί για θερμίδες"
#~ msgid ""
#~ "Users with whom newly created meal plans should be shared by default."
#~ msgstr ""
#~ "Χρήστες με του οποίους η κοινοποίηση του προγραμματισμού των γευμάτων θα "
#~ "γίνεται από προεπιλογή."
#~ msgid "Show recently viewed recipes on search page."
#~ msgstr "Προβολή των προσφάτως προβεβλημένων συνταγών στη σελίδα αναζήτησης."
#~ msgid "Number of decimals to round ingredients."
#~ msgstr "Αριθμός των δεκαδικών στα οποία θα γίνεται στρογγυλοποίηση."
#~ msgid ""
#~ "If you want to be able to create and see comments underneath recipes."
#~ msgstr ""
#~ "Εάν θέλετε να μπορείτε να δημιουργείτε και να βλέπετε σχόλια κάτω από τις "
#~ "συνταγές."
#~ msgid ""
#~ "Setting to 0 will disable auto sync. When viewing a shopping list the "
#~ "list is updated every set seconds to sync changes someone else might have "
#~ "made. Useful when shopping with multiple people but might use a little "
#~ "bit of mobile data. If lower than instance limit it is reset when saving."
#~ msgstr ""
#~ "Η ρύθμιση στο 0 θα απενεργοποιήσει τον αυτόματο συγχρονισμό. Όταν "
#~ "προβάλλετε μια λίστα αγορών, η λίστα ενημερώνεται κάθε καθορισμένα "
#~ "δευτερόλεπτα για να συγχρονίσει τις αλλαγές που μπορεί να έχει κάνει "
#~ "κάποιος άλλος. Χρήσιμο όταν ψωνίζετε με πολλούς ανθρώπους, αλλά μπορεί να "
#~ "χρησιμοποιήσει λίγα δεδομένα κινητής τηλεφωνίας. Εάν είναι μικρότερο από "
#~ "το όριο του στιγμιότυπου, επαναφέρεται όταν γίνεται αποθήκευση."
#~ msgid "Makes the navbar stick to the top of the page."
#~ msgstr "Καθιστά τη γραμμή πλοήγησης κολλημένη στην κορυφή της σελίδας."
#~ msgid "Automatically add meal plan ingredients to shopping list."
#~ msgstr ""
#~ "Αυτόματη προσθήκη των υλικών του γεύματος που έχει προγραμματιστεί στη "
#~ "λίστα αγορών."
#~ msgid "Exclude ingredients that are on hand."
#~ msgstr "Αποκλεισμός υλικών που είναι διαθέσιμα."
#~ msgid "Will optimize the UI for use with your left hand."
#~ msgstr ""
#~ "Θα βελτιστοποιήσει το περιβάλλον χρήστη για χρήση με το αριστερό χέρι."
#~ msgid "You must provide at least a recipe or a title."
#~ msgstr "Πρέπει να παρέχετε τουλάχιστον μια συνταγή ή έναν τίτλο."
#~ msgid "You can list default users to share recipes with in the settings."
#~ msgstr ""
#~ "Μπορείτε να καταχωρίσετε τους προεπιλεγμένους χρήστες με τους οποίους "
#~ "θέλετε να μοιράζεστε συνταγές στις ρυθμίσεις."
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "Μπορείτε να χρησιμοποιήσετε τη μορφοποίηση Markdown για να διαμορφώσετε "
#~ "αυτό το πεδίο. Δείτε τα έγγραφα εδώ"
#~ msgid ""
#~ "Users will see all items you add to your shopping list. They must add "
#~ "you to see items on their list."
#~ msgstr ""
#~ "Οι χρήστες θα μπορούν να δουν όλα τα αντικείμενα που προστίθενται στην "
#~ "λίστα αγορών σας. Για να δείτε τα αντικείμενα στις λίστα αυτών θα πρέπει "
#~ "να σας προστέσουν."
#~ msgid ""
#~ "When adding a meal plan to the shopping list (manually or automatically), "
#~ "include all related recipes."
#~ msgstr ""
#~ "Όταν προστίθεται ένα πρόγραμμα γευμάτων στη λίστα αγορών (χειροκίνητα ή "
#~ "αυτόματα), να συμπεριλαμβάνονται όλες οι σχετικές συνταγές."
#~ msgid ""
#~ "When adding a meal plan to the shopping list (manually or automatically), "
#~ "exclude ingredients that are on hand."
#~ msgstr ""
#~ "Όταν προσθέτετε ένα προγραμματισμό γεύματος στη λίστα αγορών (χειροκίνητα "
#~ "ή αυτόματα), αποκλείστε τα συστατικά που έχετε ήδη στη διάθεσή σας."
#~ msgid "Default number of hours to delay a shopping list entry."
#~ msgstr ""
#~ "Προεπιλεγμένος αριθμός ωρών για την καθυστέρηση μιας εγγραφής στη λίστα "
#~ "αγορών."
#~ msgid "Filter shopping list to only include supermarket categories."
#~ msgstr ""
#~ "Φιλτράρισμα λίστας αγορών ώστε να περιλαμβάνει μόνο τις κατηγορίες του "
#~ "supermarker."
#~ msgid "Days of recent shopping list entries to display."
#~ msgstr ""
#~ "Αριθμός ημερών για τη προβολή των πρόσφατων εγγραφών της λίστας αγορών."
#~ msgid "Mark food 'On Hand' when checked off shopping list."
#~ msgstr ""
#~ "Χαρακτηρισμός ενός τροφίμου ως 'Διαθέσιμο' όταν τσεκαριστεί στη λίστα "
#~ "αγορών."
#~ msgid "Delimiter to use for CSV exports."
#~ msgstr ""
#~ "Το σημείο στίξης διαχωρισμού δεκαδικών για τις εξαγωγές σε αρχεία CSV."
#~ msgid "Prefix to add when copying list to the clipboard."
#~ msgstr ""
#~ "Πρόθεμα που προστίθεται κατά την αντιγραφή της λίστας στο πρόχειρο "
#~ "(clipboard)."
#~ msgid "Share Shopping List"
#~ msgstr "Κοινοποίηση λίστας αγορών"
#~ msgid "Autosync"
#~ msgstr "Αυτόματος συγχρονισμός"
#~ msgid "Auto Add Meal Plan"
#~ msgstr "Αυτόματη προσθήκη προγραμματισμού γευμάτων"
#~ msgid "Exclude On Hand"
#~ msgstr "Αποκλεισμός διαθέσιμων"
#~ msgid "Include Related"
#~ msgstr "Συμπερίληψη σχετικών"
#~ msgid "Default Delay Hours"
#~ msgstr "Προεπιλεγμένες ώρες καθυστέρησης"
#~ msgid "Filter to Supermarket"
#~ msgstr "Ταξινόμηση ανά Supermarket"
#~ msgid "Recent Days"
#~ msgstr "Πρόσφατες ημέρες"
#~ msgid "CSV Delimiter"
#~ msgstr "CSV σημείο στίξης διαχωρισμού δεκαδικών"
#~ msgid "Auto On Hand"
#~ msgstr "Αυτόματα διαθέσιμο"
#~ msgid "Reset Food Inheritance"
#~ msgstr "Επαναφορά κληρονομιάς φαγητών"
#~ msgid "Fields on food that should be inherited by default."
#~ msgstr "Πεδία στα φαγητά που πρέπει να κληρονομούνται από προεπιλογή."
#~ msgid "One of queryset or hash_key must be provided"
#~ msgstr "Πρέπει να παρέχετε είτε το queryset είτε το hash_key"
#~ msgid "Small"
#~ msgstr "Μικρό"
#~ msgid "Large"
#~ msgstr "Μεγάλο"
#~ msgid "Edit Ingredients"
#~ msgstr "Τροποποίηση υλικών"
#~ msgid ""
#~ "\n"
#~ " The following form can be used if, accidentally, two (or more) "
#~ "units or ingredients where created that should be\n"
#~ " the same.\n"
#~ " It merges two units or ingredients and updates all recipes using "
#~ "them.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Την παρακάτω φόρμα μπορεί να χρησιμοποιηθεί στην περίπτωση που, "
#~ "κατά λάθος, δημιουργήθηκαν δύο (ή περισσότερες) μονάδες μέτρησης ή "
#~ "συστατικά που θα έπρεπε να είναι\n"
#~ " τα ίδια.\n"
#~ " Αυτή η φόρμα συγχωνεύει δύο μονάδες ή συστατικά και ενημερώνει "
#~ "όλες τις συνταγές που τα χρησιμοποιούν.\n"
#~ " "
#~ msgid "Are you sure that you want to merge these two units?"
#~ msgstr "Είστε βέβαιος ότι θέλετε να συγχωνεύσετε αυτές τις δύο μονάδες;"
#~ msgid "Are you sure that you want to merge these two ingredients?"
#~ msgstr "Είστε βέβαιος ότι θέλετε να συγχωνεύσετε αυτά τα δύο υλικά;"
#~ msgid "Import Recipes"
#~ msgstr "Εισαγωγή Συνταγών"
#~ msgid "Close"
#~ msgstr "Κλείσιμο"
#~ msgid "Open Recipe"
#~ msgstr "Άνοιγμα Συνταγής"
================================================
FILE: cookbook/locale/en/LC_MESSAGES/django.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-22 20:15+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: .\cookbook\forms.py:50
msgid "Default"
msgstr ""
#: .\cookbook\forms.py:77
msgid ""
"To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything."
msgstr ""
#: .\cookbook\forms.py:108
msgid "Maximum number of users for this space reached."
msgstr ""
#: .\cookbook\forms.py:114
msgid "Email address already taken!"
msgstr ""
#: .\cookbook\forms.py:121
msgid ""
"An email address is not required but if present the invite link will be sent "
"to the user."
msgstr ""
#: .\cookbook\forms.py:133
msgid "Name already taken."
msgstr ""
#: .\cookbook\forms.py:144 .\cookbook\forms.py:158
msgid "Accept Terms and Privacy"
msgstr ""
#: .\cookbook\helper\AllAuthCustomAdapter.py:41
msgid ""
"In order to prevent spam, the requested email was not send. Please wait a "
"few minutes and try again."
msgstr ""
#: .\cookbook\helper\permission_helper.py:165
#: .\cookbook\helper\permission_helper.py:186 .\cookbook\views\views.py:138
msgid "You are not logged in and therefore cannot view this page!"
msgstr ""
#: .\cookbook\helper\permission_helper.py:168
#: .\cookbook\helper\permission_helper.py:173
#: .\cookbook\helper\permission_helper.py:198
#: .\cookbook\helper\permission_helper.py:265
#: .\cookbook\helper\permission_helper.py:279
#: .\cookbook\helper\permission_helper.py:290
#: .\cookbook\helper\permission_helper.py:301
#: .\cookbook\helper\permission_helper.py:317
#: .\cookbook\helper\permission_helper.py:343
#: .\cookbook\helper\permission_helper.py:359
msgid "You do not have the required permissions to view this page!"
msgstr ""
#: .\cookbook\helper\permission_helper.py:191
#: .\cookbook\helper\permission_helper.py:214
#: .\cookbook\helper\permission_helper.py:236
#: .\cookbook\helper\permission_helper.py:251
msgid "You cannot interact with this object as it is not owned by you!"
msgstr ""
#: .\cookbook\helper\permission_helper.py:420
msgid "You have reached the maximum number of recipes for your space."
msgstr ""
#: .\cookbook\helper\permission_helper.py:432
msgid "You have more users than allowed in your space."
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:319
msgid "reverse rotation"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:320
msgid "careful rotation"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:321
msgid "knead"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:322
msgid "thicken"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:323
msgid "warm up"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:324
msgid "ferment"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:325
msgid "slow cook"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:326
msgid "egg boiler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:327
msgid "kettle"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:328
msgid "blend"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:329
msgid "pre-clean"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:330
msgid "high temperature"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:331
msgid "rice cooker"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:332
msgid "caramelize"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:333
msgid "peeler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:334
msgid "slicer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:335
msgid "grater"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:336
msgid "spiralizer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:337
msgid "sous-vide"
msgstr ""
#: .\cookbook\helper\shopping_helper.py:150
msgid "You must supply a servings size"
msgstr ""
#: .\cookbook\helper\template_helper.py:97
#: .\cookbook\helper\template_helper.py:99
#: .\cookbook\helper\template_helper.py:101
msgid "Could not parse template code."
msgstr ""
#: .\cookbook\integration\copymethat.py:44
#: .\cookbook\integration\melarecipes.py:37
msgid "Favorite"
msgstr ""
#: .\cookbook\integration\copymethat.py:50
msgid "I made this"
msgstr ""
#: .\cookbook\integration\integration.py:238
msgid ""
"Importer expected a .zip file. Did you choose the correct importer type for "
"your data ?"
msgstr ""
#: .\cookbook\integration\integration.py:241
msgid ""
"An unexpected error occurred during the import. Please make sure you have "
"uploaded a valid file."
msgstr ""
#: .\cookbook\integration\integration.py:246
msgid "The following recipes were ignored because they already existed:"
msgstr ""
#: .\cookbook\integration\integration.py:250
#, python-format
msgid "Imported %s recipes."
msgstr ""
#: .\cookbook\integration\mealie1.py:210
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "Calories"
msgstr ""
#: .\cookbook\integration\mealie1.py:211
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
msgid "Carbohydrates"
msgstr ""
#: .\cookbook\integration\mealie1.py:212
msgid "Cholesterol"
msgstr ""
#: .\cookbook\integration\mealie1.py:213
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
msgid "Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:214
msgid "Fiber"
msgstr ""
#: .\cookbook\integration\mealie1.py:215
msgid "Protein"
msgstr ""
#: .\cookbook\integration\mealie1.py:216
msgid "Saturated Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:217
msgid "Sodium"
msgstr ""
#: .\cookbook\integration\mealie1.py:218
msgid "Sugar"
msgstr ""
#: .\cookbook\integration\mealie1.py:219
msgid "Trans Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:220
msgid "Unsaturated Fat"
msgstr ""
#: .\cookbook\integration\openeats.py:28
msgid "Recipe source:"
msgstr ""
#: .\cookbook\integration\paprika.py:49
msgid "Notes"
msgstr ""
#: .\cookbook\integration\paprika.py:52
msgid "Nutritional Information"
msgstr ""
#: .\cookbook\integration\paprika.py:56
msgid "Source"
msgstr ""
#: .\cookbook\integration\recettetek.py:55
#: .\cookbook\integration\recipekeeper.py:70
msgid "Imported from"
msgstr ""
#: .\cookbook\integration\saffron.py:23
msgid "Servings"
msgstr ""
#: .\cookbook\integration\saffron.py:25
msgid "Waiting time"
msgstr ""
#: .\cookbook\integration\saffron.py:27
msgid "Preparation Time"
msgstr ""
#: .\cookbook\integration\saffron.py:29 .\cookbook\templates\index.html:6
msgid "Cookbook"
msgstr ""
#: .\cookbook\integration\saffron.py:31
msgid "Section"
msgstr ""
#: .\cookbook\management\commands\fix_duplicate_properties.py:15
msgid "Fixes foods with "
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:14
msgid "Rebuilds full text search index on Recipe"
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:18
msgid "Only Postgresql databases use full text search, no index to rebuild"
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:29
msgid "Recipe index rebuild complete."
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:31
msgid "Recipe index rebuild failed."
msgstr ""
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
msgid "Breakfast"
msgstr ""
#: .\cookbook\migrations\0047_auto_20200602_1133.py:19
msgid "Lunch"
msgstr ""
#: .\cookbook\migrations\0047_auto_20200602_1133.py:24
msgid "Dinner"
msgstr ""
#: .\cookbook\migrations\0047_auto_20200602_1133.py:29 .\cookbook\models.py:971
msgid "Other"
msgstr ""
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "g"
msgstr ""
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "Proteins"
msgstr ""
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "kcal"
msgstr ""
#: .\cookbook\models.py:325
msgid ""
"Maximum file storage for space in MB. 0 for unlimited, -1 to disable file "
"upload."
msgstr ""
#: .\cookbook\models.py:513
msgid "Search"
msgstr ""
#: .\cookbook\models.py:514
msgid "Meal-Plan"
msgstr ""
#: .\cookbook\models.py:515
msgid "Books"
msgstr ""
#: .\cookbook\models.py:516 .\cookbook\views\views.py:416
#: .\cookbook\views\views.py:417
msgid "Shopping"
msgstr ""
#: .\cookbook\models.py:967
msgid "Nutrition"
msgstr ""
#: .\cookbook\models.py:968
msgid "Allergen"
msgstr ""
#: .\cookbook\models.py:969
msgid "Price"
msgstr ""
#: .\cookbook\models.py:970
msgid "Goal"
msgstr ""
#: .\cookbook\models.py:1467 .\cookbook\templates\search_info.html:28
msgid "Simple"
msgstr ""
#: .\cookbook\models.py:1468 .\cookbook\templates\search_info.html:33
msgid "Phrase"
msgstr ""
#: .\cookbook\models.py:1469 .\cookbook\templates\search_info.html:38
msgid "Web"
msgstr ""
#: .\cookbook\models.py:1470 .\cookbook\templates\search_info.html:47
msgid "Raw"
msgstr ""
#: .\cookbook\models.py:1525
msgid "Food Alias"
msgstr ""
#: .\cookbook\models.py:1526
msgid "Unit Alias"
msgstr ""
#: .\cookbook\models.py:1527
msgid "Keyword Alias"
msgstr ""
#: .\cookbook\models.py:1528
msgid "Description Replace"
msgstr ""
#: .\cookbook\models.py:1529
msgid "Instruction Replace"
msgstr ""
#: .\cookbook\models.py:1530
msgid "Never Unit"
msgstr ""
#: .\cookbook\models.py:1531
msgid "Transpose Words"
msgstr ""
#: .\cookbook\models.py:1532
msgid "Food Replace"
msgstr ""
#: .\cookbook\models.py:1533
msgid "Unit Replace"
msgstr ""
#: .\cookbook\models.py:1534
msgid "Name Replace"
msgstr ""
#: .\cookbook\models.py:1564
msgid "Recipe"
msgstr ""
#: .\cookbook\models.py:1565
msgid "Food"
msgstr ""
#: .\cookbook\models.py:1566
msgid "Keyword"
msgstr ""
#: .\cookbook\serializer.py:262
msgid "File uploads are not enabled for this Space."
msgstr ""
#: .\cookbook\serializer.py:273
msgid "You have reached your file upload limit."
msgstr ""
#: .\cookbook\serializer.py:281
msgid "The given file type is not allowed."
msgstr ""
#: .\cookbook\serializer.py:421 .\cookbook\views\views.py:94
msgid ""
"You have the reached the maximum amount of spaces that can be owned by you."
msgstr ""
#: .\cookbook\serializer.py:434
msgid "Space Name must be unique."
msgstr ""
#: .\cookbook\serializer.py:469
msgid "Cannot modify Space owner permission."
msgstr ""
#: .\cookbook\serializer.py:1596
msgid "Hello"
msgstr ""
#: .\cookbook\serializer.py:1596
msgid "You have been invited by "
msgstr ""
#: .\cookbook\serializer.py:1598
msgid " to join their Tandoor Recipes space "
msgstr ""
#: .\cookbook\serializer.py:1600
msgid "Click the following link to activate your account: "
msgstr ""
#: .\cookbook\serializer.py:1602
msgid ""
"If the link does not work use the following code to manually join the space: "
msgstr ""
#: .\cookbook\serializer.py:1604
msgid "The invitation is valid until "
msgstr ""
#: .\cookbook\serializer.py:1606
msgid ""
"Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub "
msgstr ""
#: .\cookbook\serializer.py:1609
msgid "Tandoor Recipes Invite"
msgstr ""
#: .\cookbook\serializer.py:1813
msgid "Existing shopping list to update"
msgstr ""
#: .\cookbook\serializer.py:1815
msgid ""
"List of ingredient IDs from the recipe to add, if not provided all "
"ingredients will be added."
msgstr ""
#: .\cookbook\serializer.py:1817
msgid ""
"Providing a list_recipe ID and servings of 0 will delete that shopping list."
msgstr ""
#: .\cookbook\serializer.py:1826
msgid "Amount of food to add to the shopping list"
msgstr ""
#: .\cookbook\serializer.py:1828
msgid "ID of unit to use for the shopping list"
msgstr ""
#: .\cookbook\serializer.py:1830
msgid "When set to true will delete all food from active shopping lists."
msgstr ""
#: .\cookbook\templates\404.html:5
msgid "404 Error"
msgstr ""
#: .\cookbook\templates\404.html:18
msgid "The page you are looking for could not be found."
msgstr ""
#: .\cookbook\templates\404.html:33
msgid "Take me Home"
msgstr ""
#: .\cookbook\templates\404.html:35
msgid "Report a Bug"
msgstr ""
#: .\cookbook\templates\account\email.html:6
#: .\cookbook\templates\account\email.html:10
msgid "E-mail Addresses"
msgstr ""
#: .\cookbook\templates\account\email.html:12
msgid "The following e-mail addresses are associated with your account:"
msgstr ""
#: .\cookbook\templates\account\email.html:29
msgid "Verified"
msgstr ""
#: .\cookbook\templates\account\email.html:31
msgid "Unverified"
msgstr ""
#: .\cookbook\templates\account\email.html:33
msgid "Primary"
msgstr ""
#: .\cookbook\templates\account\email.html:40
msgid "Make Primary"
msgstr ""
#: .\cookbook\templates\account\email.html:42
msgid "Re-send Verification"
msgstr ""
#: .\cookbook\templates\account\email.html:43
#: .\cookbook\templates\socialaccount\connections.html:36
msgid "Remove"
msgstr ""
#: .\cookbook\templates\account\email.html:51
msgid "Warning:"
msgstr ""
#: .\cookbook\templates\account\email.html:51
msgid ""
"You currently do not have any e-mail address set up. You should really add "
"an e-mail address so you can receive notifications, reset your password, etc."
msgstr ""
#: .\cookbook\templates\account\email.html:57
msgid "Add E-mail Address"
msgstr ""
#: .\cookbook\templates\account\email.html:62
msgid "Add E-mail"
msgstr ""
#: .\cookbook\templates\account\email.html:72
msgid "Do you really want to remove the selected e-mail address?"
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:6
#: .\cookbook\templates\account\email_confirm.html:10
msgid "Confirm E-mail Address"
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:16
#, python-format
msgid ""
"Please confirm that\n"
" %(email)s is an e-mail address "
"for user %(user_display)s\n"
" ."
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:22
msgid "Confirm"
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:29
#, python-format
msgid ""
"This e-mail confirmation link expired or is invalid. Please\n"
" issue a new e-mail confirmation "
"request."
msgstr ""
#: .\cookbook\templates\account\login.html:8
#: .\cookbook\templates\openid\login.html:8
msgid "Login"
msgstr ""
#: .\cookbook\templates\account\login.html:15
#: .\cookbook\templates\account\login.html:31
#: .\cookbook\templates\account\password_reset.html:39
#: .\cookbook\templates\account\password_reset_done.html:31
#: .\cookbook\templates\account\signup.html:68
#: .\cookbook\templates\account\signup_closed.html:15
#: .\cookbook\templates\openid\login.html:15
#: .\cookbook\templates\openid\login.html:26
#: .\cookbook\templates\socialaccount\authentication_error.html:15
msgid "Sign In"
msgstr ""
#: .\cookbook\templates\account\login.html:34
#: .\cookbook\templates\account\password_reset.html:41
#: .\cookbook\templates\account\password_reset_done.html:33
#: .\cookbook\templates\socialaccount\signup.html:8
#: .\cookbook\templates\socialaccount\signup.html:56
msgid "Sign Up"
msgstr ""
#: .\cookbook\templates\account\login.html:38
msgid "Lost your password?"
msgstr ""
#: .\cookbook\templates\account\login.html:39
#: .\cookbook\templates\account\password_reset.html:29
msgid "Reset My Password"
msgstr ""
#: .\cookbook\templates\account\login.html:50
msgid "Social Login"
msgstr ""
#: .\cookbook\templates\account\login.html:51
msgid "You can use any of the following providers to sign in."
msgstr ""
#: .\cookbook\templates\account\logout.html:5
#: .\cookbook\templates\account\logout.html:9
#: .\cookbook\templates\account\logout.html:18
msgid "Sign Out"
msgstr ""
#: .\cookbook\templates\account\logout.html:11
msgid "Are you sure you want to sign out?"
msgstr ""
#: .\cookbook\templates\account\password_change.html:6
#: .\cookbook\templates\account\password_change.html:10
#: .\cookbook\templates\account\password_change.html:15
#: .\cookbook\templates\account\password_reset_from_key.html:7
#: .\cookbook\templates\account\password_reset_from_key.html:13
#: .\cookbook\templates\account\password_reset_from_key_done.html:7
#: .\cookbook\templates\account\password_reset_from_key_done.html:13
msgid "Change Password"
msgstr ""
#: .\cookbook\templates\account\password_change.html:16
msgid "Forgot Password?"
msgstr ""
#: .\cookbook\templates\account\password_reset.html:7
#: .\cookbook\templates\account\password_reset.html:13
#: .\cookbook\templates\account\password_reset_done.html:7
#: .\cookbook\templates\account\password_reset_done.html:18
msgid "Password Reset"
msgstr ""
#: .\cookbook\templates\account\password_reset.html:24
msgid ""
"Forgotten your password? Enter your e-mail address below, and we'll send you "
"an e-mail allowing you to reset it."
msgstr ""
#: .\cookbook\templates\account\password_reset.html:32
msgid "Password reset is disabled on this instance."
msgstr ""
#: .\cookbook\templates\account\password_reset_done.html:25
msgid ""
"We have sent you an e-mail. Please contact us if you do not receive it "
"within a few minutes."
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:13
msgid "Bad Token"
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:25
#, python-format
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used.\n"
" Please request a new "
"password reset."
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:33
msgid "change password"
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:36
#: .\cookbook\templates\account\password_reset_from_key_done.html:19
msgid "Your password is now changed."
msgstr ""
#: .\cookbook\templates\account\password_set.html:6
#: .\cookbook\templates\account\password_set.html:10
#: .\cookbook\templates\account\password_set.html:15
msgid "Set Password"
msgstr ""
#: .\cookbook\templates\account\signup.html:5
msgid "Register"
msgstr ""
#: .\cookbook\templates\account\signup.html:11
msgid "Create an Account"
msgstr ""
#: .\cookbook\templates\account\signup.html:41
msgid "I accept the follwoing"
msgstr ""
#: .\cookbook\templates\account\signup.html:44
#: .\cookbook\templates\socialaccount\signup.html:35
msgid "Terms and Conditions"
msgstr ""
#: .\cookbook\templates\account\signup.html:47
#: .\cookbook\templates\socialaccount\signup.html:38
msgid "and"
msgstr ""
#: .\cookbook\templates\account\signup.html:51
#: .\cookbook\templates\socialaccount\signup.html:42
msgid "Privacy Policy"
msgstr ""
#: .\cookbook\templates\account\signup.html:64
msgid "Create User"
msgstr ""
#: .\cookbook\templates\account\signup.html:68
msgid "Already have an account?"
msgstr ""
#: .\cookbook\templates\account\signup_closed.html:5
#: .\cookbook\templates\account\signup_closed.html:11
msgid "Sign Up Closed"
msgstr ""
#: .\cookbook\templates\account\signup_closed.html:13
msgid "We are sorry, but the sign up is currently closed."
msgstr ""
#: .\cookbook\templates\frontend\tandoor.html:15
msgid "Tandoor Recipe Manager"
msgstr ""
#: .\cookbook\templates\index.html:28
msgid "Search recipe ..."
msgstr ""
#: .\cookbook\templates\index.html:43
msgid "New Recipe"
msgstr ""
#: .\cookbook\templates\index.html:46
msgid "Import Recipe"
msgstr ""
#: .\cookbook\templates\index.html:52
msgid "Advanced Search"
msgstr ""
#: .\cookbook\templates\index.html:56
msgid "Reset Search"
msgstr ""
#: .\cookbook\templates\index.html:84
msgid "Last viewed"
msgstr ""
#: .\cookbook\templates\index.html:86
msgid "Recipes"
msgstr ""
#: .\cookbook\templates\index.html:93
msgid "Log in to view recipes"
msgstr ""
#: .\cookbook\templates\markdown_info.html:5
#: .\cookbook\templates\markdown_info.html:13
msgid "Markdown Info"
msgstr ""
#: .\cookbook\templates\markdown_info.html:14
msgid ""
"\n"
" Markdown is lightweight markup language that can be used to format "
"plain text easily.\n"
" This site uses the Python Markdown library to\n"
" convert your text into nice looking HTML. Its full markdown "
"documentation can be found\n"
" here.\n"
" An incomplete but most likely sufficient documentation can be found "
"below.\n"
" "
msgstr ""
#: .\cookbook\templates\markdown_info.html:25
msgid "Headers"
msgstr ""
#: .\cookbook\templates\markdown_info.html:54
msgid "Formatting"
msgstr ""
#: .\cookbook\templates\markdown_info.html:56
#: .\cookbook\templates\markdown_info.html:72
msgid "Line breaks are inserted by adding two spaces after the end of a line"
msgstr ""
#: .\cookbook\templates\markdown_info.html:57
#: .\cookbook\templates\markdown_info.html:73
msgid "or by leaving a blank line in between."
msgstr ""
#: .\cookbook\templates\markdown_info.html:59
#: .\cookbook\templates\markdown_info.html:74
msgid "This text is bold"
msgstr ""
#: .\cookbook\templates\markdown_info.html:60
#: .\cookbook\templates\markdown_info.html:75
msgid "This text is italic"
msgstr ""
#: .\cookbook\templates\markdown_info.html:61
#: .\cookbook\templates\markdown_info.html:77
msgid "Blockquotes are also possible"
msgstr ""
#: .\cookbook\templates\markdown_info.html:84
msgid "Lists"
msgstr ""
#: .\cookbook\templates\markdown_info.html:85
msgid ""
"Lists can ordered or unordered. It is important to leave a blank line "
"before the list!"
msgstr ""
#: .\cookbook\templates\markdown_info.html:87
#: .\cookbook\templates\markdown_info.html:108
msgid "Ordered List"
msgstr ""
#: .\cookbook\templates\markdown_info.html:89
#: .\cookbook\templates\markdown_info.html:90
#: .\cookbook\templates\markdown_info.html:91
#: .\cookbook\templates\markdown_info.html:110
#: .\cookbook\templates\markdown_info.html:111
#: .\cookbook\templates\markdown_info.html:112
msgid "unordered list item"
msgstr ""
#: .\cookbook\templates\markdown_info.html:93
#: .\cookbook\templates\markdown_info.html:114
msgid "Unordered List"
msgstr ""
#: .\cookbook\templates\markdown_info.html:95
#: .\cookbook\templates\markdown_info.html:96
#: .\cookbook\templates\markdown_info.html:97
#: .\cookbook\templates\markdown_info.html:116
#: .\cookbook\templates\markdown_info.html:117
#: .\cookbook\templates\markdown_info.html:118
msgid "ordered list item"
msgstr ""
#: .\cookbook\templates\markdown_info.html:125
msgid "Images & Links"
msgstr ""
#: .\cookbook\templates\markdown_info.html:126
msgid ""
"Links can be formatted with Markdown. This application also allows to paste "
"links directly into markdown fields without any formatting."
msgstr ""
#: .\cookbook\templates\markdown_info.html:132
#: .\cookbook\templates\markdown_info.html:145
msgid "This will become an image"
msgstr ""
#: .\cookbook\templates\markdown_info.html:152
msgid "Tables"
msgstr ""
#: .\cookbook\templates\markdown_info.html:153
msgid ""
"Markdown tables are hard to create by hand. It is recommended to use a table "
"editor like this one."
msgstr ""
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:171
#: .\cookbook\templates\markdown_info.html:177
msgid "Table"
msgstr ""
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:172
msgid "Header"
msgstr ""
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:178
msgid "Cell"
msgstr ""
#: .\cookbook\templates\no_groups_info.html:5
#: .\cookbook\templates\no_groups_info.html:12
msgid "No Permissions"
msgstr ""
#: .\cookbook\templates\no_groups_info.html:17
msgid "You do not have any groups and therefor cannot use this application."
msgstr ""
#: .\cookbook\templates\no_groups_info.html:18
#: .\cookbook\templates\no_perm_info.html:15
msgid "Please contact your administrator."
msgstr ""
#: .\cookbook\templates\no_perm_info.html:5
#: .\cookbook\templates\no_perm_info.html:12
msgid "No Permission"
msgstr ""
#: .\cookbook\templates\no_perm_info.html:15
msgid ""
"You do not have the required permissions to view this page or perform this "
"action."
msgstr ""
#: .\cookbook\templates\offline.html:5
msgid "Offline"
msgstr ""
#: .\cookbook\templates\openid\login.html:27
#: .\cookbook\templates\socialaccount\authentication_error.html:27
msgid "Back"
msgstr ""
#: .\cookbook\templates\rest_framework\api.html:5
msgid "Recipe Home"
msgstr ""
#: .\cookbook\templates\rest_framework\api.html:11
msgid "API Documentation"
msgstr ""
#: .\cookbook\templates\search_info.html:5
#: .\cookbook\templates\search_info.html:9
msgid "Search Settings"
msgstr ""
#: .\cookbook\templates\search_info.html:10
msgid ""
"\n"
" Creating the best search experience is complicated and weighs "
"heavily on your personal configuration. \n"
" Changing any of the search settings can have significant impact on "
"the speed and quality of the results.\n"
" Search Methods, Trigrams and Full Text Search configurations are "
"only available if you are using Postgres for your database.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:19
msgid "Search Methods"
msgstr ""
#: .\cookbook\templates\search_info.html:23
msgid ""
" \n"
" Full text searches attempt to normalize the words provided to "
"match common variants. For example: 'forked', 'forking', 'forks' will all "
"normalize to 'fork'.\n"
" There are several methods available, described below, that will "
"control how the search behavior should react when multiple words are "
"searched.\n"
" Full technical details on how these operate can be viewed on Postgresql's website.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:29
msgid ""
" \n"
" Simple searches ignore punctuation and common words such as "
"'the', 'a', 'and'. And will treat separate words as required.\n"
" Searching for 'apple or flour' will return any recipe that "
"includes both 'apple' and 'flour' anywhere in the fields that have been "
"selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:34
msgid ""
" \n"
" Phrase searches ignore punctuation, but will search for all of "
"the words in the exact order provided.\n"
" Searching for 'apple or flour' will only return a recipe that "
"includes the exact phrase 'apple or flour' in any of the fields that have "
"been selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:39
msgid ""
" \n"
" Web searches simulate functionality found on many web search "
"sites supporting special syntax.\n"
" Placing quotes around several words will convert those words "
"into a phrase.\n"
" 'or' is recognized as searching for the word (or phrase) "
"immediately before 'or' OR the word (or phrase) directly after.\n"
" '-' is recognized as searching for recipes that do not include "
"the word (or phrase) that comes immediately after. \n"
" For example searching for 'apple pie' or cherry -butter will "
"return any recipe that includes the phrase 'apple pie' or the word "
"'cherry' \n"
" in any field included in the full text search but exclude any "
"recipe that has the word 'butter' in any field included.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:48
msgid ""
" \n"
" Raw search is similar to Web except will take puncuation "
"operators such as '|', '&' and '()'\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:59
msgid ""
" \n"
" Another approach to searching that also requires Postgresql is "
"fuzzy search or trigram similarity. A trigram is a group of three "
"consecutive characters.\n"
" For example searching for 'apple' will create x trigrams 'app', "
"'ppl', 'ple' and will create a score of how closely words match the "
"generated trigrams.\n"
" One benefit of searching trigams is that a search for 'sandwich' "
"will find misspelled words such as 'sandwhich' that would be missed by other "
"methods.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:69
msgid "Search Fields"
msgstr ""
#: .\cookbook\templates\search_info.html:73
msgid ""
" \n"
" Unaccent is a special case in that it enables searching a field "
"'unaccented' for each search style attempting to ignore accented values. \n"
" For example when you enable unaccent for 'Name' any search "
"(starts with, contains, trigram) will attempt the search ignoring accented "
"characters.\n"
" \n"
" For the other options, you can enable search on any or all "
"fields and they will be combined together with an assumed 'OR'.\n"
" For example enabling 'Name' for Starts With, 'Name' and "
"'Description' for Partial Match and 'Ingredients' and 'Keywords' for Full "
"Search\n"
" and searching for 'apple' will generate a search that will "
"return recipes that have:\n"
" - A recipe name that starts with 'apple'\n"
" - OR a recipe name that contains 'apple'\n"
" - OR a recipe description that contains 'apple'\n"
" - OR a recipe that will have a full text search match ('apple' "
"or 'apples') in ingredients\n"
" - OR a recipe that will have a full text search match in "
"Keywords\n"
"\n"
" Combining too many fields in too many types of search can have a "
"negative impact on performance, create duplicate results or return "
"unexpected results.\n"
" For example, enabling fuzzy search or partial matches will "
"interfere with web search methods. \n"
" Searching for 'apple -pie' with fuzzy search and full text "
"search will return the recipe Apple Pie. Though it is not included in the "
"full text results, it does match the trigram results.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:95
msgid "Search Index"
msgstr ""
#: .\cookbook\templates\search_info.html:99
msgid ""
" \n"
" Trigram search and Full Text Search both rely on database "
"indexes to perform effectively. \n"
" You can rebuild the indexes on all fields in the Admin page for "
"Recipes and selecting all recipes and running 'rebuild index for selected "
"recipes'\n"
" You can also rebuild indexes at the command line by executing "
"the management command 'python manage.py rebuildindex'\n"
" "
msgstr ""
#: .\cookbook\templates\setup.html:6
msgid "Cookbook Setup"
msgstr ""
#: .\cookbook\templates\setup.html:14
msgid "Setup"
msgstr ""
#: .\cookbook\templates\setup.html:15
msgid ""
"To start using this application you must first create a superuser account."
msgstr ""
#: .\cookbook\templates\setup.html:20
msgid "Create Superuser account"
msgstr ""
#: .\cookbook\templates\socialaccount\authentication_error.html:7
#: .\cookbook\templates\socialaccount\authentication_error.html:23
msgid "Social Network Login Failure"
msgstr ""
#: .\cookbook\templates\socialaccount\authentication_error.html:25
msgid ""
"An error occurred while attempting to login via your social network account."
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:4
#: .\cookbook\templates\socialaccount\connections.html:7
msgid "Account Connections"
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:10
msgid ""
"You can sign in to your account using any of the following third party\n"
" accounts:"
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:44
msgid ""
"You currently have no social network accounts connected to this account."
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:47
msgid "Add a 3rd Party Account"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:5
#: .\cookbook\templates\socialaccount\signup.html:5
msgid "Signup"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:9
#, python-format
msgid "Connect %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:11
#, python-format
msgid "You are about to connect a new third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:13
#, python-format
msgid "Sign In Via %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:15
#, python-format
msgid "You are about to sign in using a third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:20
msgid "Continue"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:10
#, python-format
msgid ""
"You are about to use your\n"
" %(provider_name)s account to login to\n"
" %(site_name)s. As a final step, please complete the following form:"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:32
msgid "I accept the following"
msgstr ""
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:23
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:31
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:39
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:47
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:55
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:63
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:71
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:79
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:87
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:95
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:103
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:111
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:119
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:127
msgid "Sign in using"
msgstr ""
#: .\cookbook\templates\space_overview.html:6
msgid "Overview"
msgstr ""
#: .\cookbook\templates\space_overview.html:13
msgid "Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:17
msgid ""
"Recipes, foods, shopping lists and more are organized in spaces of one or "
"more people."
msgstr ""
#: .\cookbook\templates\space_overview.html:18
msgid ""
"You can either be invited into an existing space or create your own one."
msgstr ""
#: .\cookbook\templates\space_overview.html:25
msgid "Your Spaces"
msgstr ""
#: .\cookbook\templates\space_overview.html:53
msgid "Owner"
msgstr ""
#: .\cookbook\templates\space_overview.html:73
#: .\cookbook\templates\space_overview.html:83
msgid "Join Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:76
msgid "Join an existing space."
msgstr ""
#: .\cookbook\templates\space_overview.html:78
msgid ""
"To join an existing space either enter your invite token or click on the "
"invite link the space owner send you."
msgstr ""
#: .\cookbook\templates\space_overview.html:91
#: .\cookbook\templates\space_overview.html:100
msgid "Create Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:94
msgid "Create your own recipe space."
msgstr ""
#: .\cookbook\templates\space_overview.html:96
msgid "Start your own recipe space and invite other users to it."
msgstr ""
#: .\cookbook\templates\system.html:23
msgid "System"
msgstr ""
#: .\cookbook\templates\system.html:24
msgid ""
"\n"
" Tandoor Recipes is an open source free software application. It can "
"be found on\n"
" GitHub.\n"
" Changelogs can be found here.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:30
msgid "System Information"
msgstr ""
#: .\cookbook\templates\system.html:51
msgid ""
"\n"
" You need to execute version.py in your update "
"script to generate version information (done automatically in docker).\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:56
msgid "Plugins"
msgstr ""
#: .\cookbook\templates\system.html:67
msgid "Media Serving"
msgstr ""
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:123 .\cookbook\templates\system.html:134
msgid "Warning"
msgstr ""
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:125 .\cookbook\templates\system.html:134
msgid "Ok"
msgstr ""
#: .\cookbook\templates\system.html:70
msgid ""
"Serving media files directly using gunicorn/python is not recommend!\n"
" Please follow the steps described\n"
" here to update\n"
" your installation.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:76 .\cookbook\templates\system.html:91
#: .\cookbook\templates\system.html:104 .\cookbook\templates\system.html:115
#: .\cookbook\views\views.py:168
msgid "Everything is fine!"
msgstr ""
#: .\cookbook\templates\system.html:80
msgid "Secret Key"
msgstr ""
#: .\cookbook\templates\system.html:84
msgid ""
"\n"
" You do not have a SECRET_KEY configured in your "
".env file. Django defaulted to the\n"
" standard key\n"
" provided with the installation which is publicly know and "
"insecure! Please set\n"
" SECRET_KEY int the .env configuration "
"file.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:94
msgid "Debug Mode"
msgstr ""
#: .\cookbook\templates\system.html:98
msgid ""
"\n"
" This application is still running in debug mode. This is most "
"likely not needed. Turn of debug mode by\n"
" setting\n"
" DEBUG=0 int the .env configuration "
"file.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:107
msgid "Allowed Hosts"
msgstr ""
#: .\cookbook\templates\system.html:111
msgid ""
"\n"
" Your allowed hosts are configured to allow every host. This "
"might be ok in some setups but should be avoided. Please see the docs about "
"this.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:118
msgid "Database"
msgstr ""
#: .\cookbook\templates\system.html:121
msgid "Info"
msgstr ""
#: .\cookbook\templates\system.html:131 .\cookbook\templates\system.html:148
msgid "Migrations"
msgstr ""
#: .\cookbook\templates\system.html:137
msgid ""
"\n"
" Migrations should never fail!\n"
" Failed migrations will likely cause major parts of the app to "
"not function correctly.\n"
" If a migration fails make sure you are on the latest version and "
"if so please post the migration log and the overview below in a GitHub "
"issue.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "False"
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "True"
msgstr ""
#: .\cookbook\templates\system.html:268
msgid "Hide"
msgstr ""
#: .\cookbook\templates\system.html:271
msgid "Show"
msgstr ""
#: .\cookbook\templates\test2.html:6
msgid "Export Recipes"
msgstr ""
#: .\cookbook\templates\test2.html:14 .\cookbook\templates\test2.html:20
msgid "Export"
msgstr ""
#: .\cookbook\views\api.py:198 .\cookbook\views\api.py:326
msgid "Parameter updated_at incorrectly formatted"
msgstr ""
#: .\cookbook\views\api.py:351 .\cookbook\views\api.py:484
#, python-brace-format
msgid "No {self.basename} with id {pk} exists"
msgstr ""
#: .\cookbook\views\api.py:355
msgid "Cannot merge with the same object!"
msgstr ""
#: .\cookbook\views\api.py:362
#, python-brace-format
msgid "No {self.basename} with id {target} exists"
msgstr ""
#: .\cookbook\views\api.py:367
msgid "Cannot merge with child object!"
msgstr ""
#: .\cookbook\views\api.py:405
#, python-brace-format
msgid "{source.name} was merged successfully with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:411
#, python-brace-format
msgid "An error occurred attempting to merge {source.name} with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:493
#, python-brace-format
msgid "{child.name} was moved successfully to the root."
msgstr ""
#: .\cookbook\views\api.py:496 .\cookbook\views\api.py:514
msgid "An error occurred attempting to move "
msgstr ""
#: .\cookbook\views\api.py:499
msgid "Cannot move an object to itself!"
msgstr ""
#: .\cookbook\views\api.py:505
#, python-brace-format
msgid "No {self.basename} with id {parent} exists"
msgstr ""
#: .\cookbook\views\api.py:511
#, python-brace-format
msgid "{child.name} was moved successfully to parent {parent.name}"
msgstr ""
#: .\cookbook\views\api.py:992
#, python-brace-format
msgid "{obj.name} was removed from the shopping list."
msgstr ""
#: .\cookbook\views\api.py:997 .\cookbook\views\api.py:1627
#, python-brace-format
msgid "{obj.name} was added to the shopping list."
msgstr ""
#: .\cookbook\views\api.py:1239
msgid "Filter meal plans from date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1241
msgid "Filter meal plans to date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1244
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1404
msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1406
msgid "Query string matched (fuzzy) against object name."
msgstr ""
#: .\cookbook\views\api.py:1442
msgid ""
"Query string matched (fuzzy) against recipe name. In the future also "
"fulltext search."
msgstr ""
#: .\cookbook\views\api.py:1444
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr ""
#: .\cookbook\views\api.py:1445
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr ""
#: .\cookbook\views\api.py:1446
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1447
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1448
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1450
msgid "ID of food a recipe should have. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1451
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr ""
#: .\cookbook\views\api.py:1452
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1453
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr ""
#: .\cookbook\views\api.py:1454
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1456
msgid "ID of book a recipe should be in. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1457
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr ""
#: .\cookbook\views\api.py:1458
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1459
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr ""
#: .\cookbook\views\api.py:1460
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1462
msgid "ID of unit a recipe should have."
msgstr ""
#: .\cookbook\views\api.py:1464
msgid "Exact rating of recipe"
msgstr ""
#: .\cookbook\views\api.py:1465
msgid "Rating a recipe should have or greater."
msgstr ""
#: .\cookbook\views\api.py:1466
msgid "Rating a recipe should have or smaller."
msgstr ""
#: .\cookbook\views\api.py:1468
msgid "Filter recipes cooked X times."
msgstr ""
#: .\cookbook\views\api.py:1469
msgid "Filter recipes cooked X times or more."
msgstr ""
#: .\cookbook\views\api.py:1470
msgid "Filter recipes cooked X times or less."
msgstr ""
#: .\cookbook\views\api.py:1472
msgid "Filter recipes created on the given date."
msgstr ""
#: .\cookbook\views\api.py:1473
msgid "Filter recipes created on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1474
msgid "Filter recipes created on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1476 .\cookbook\views\api.py:1477
#: .\cookbook\views\api.py:1478
msgid "Filter recipes updated on the given date."
msgstr ""
#: .\cookbook\views\api.py:1480
msgid "Filter recipes last cooked on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1481
msgid "Filter recipes last cooked on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1483 .\cookbook\views\api.py:1484
msgid "Filter recipes lasts viewed on the given date."
msgstr ""
#: .\cookbook\views\api.py:1486
msgid "Filter recipes for ones created by the given user ID"
msgstr ""
#: .\cookbook\views\api.py:1487
msgid "If only internal recipes should be returned. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1488
msgid "Returns the results in randomized order. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1490
msgid ""
"Determines the order of the results. Options are: score,-score,name,-name,"
"lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-"
"created_at,lastviewed,-lastviewed"
msgstr ""
#: .\cookbook\views\api.py:1492
msgid "Returns new results first in search results. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1493
msgid ""
"Returns the given number of recently viewed recipes before search results "
"(if given)"
msgstr ""
#: .\cookbook\views\api.py:1494
msgid "ID of a custom filter. Returns all recipes matched by that filter."
msgstr ""
#: .\cookbook\views\api.py:1495
msgid "Filter recipes that can be made with OnHand food. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1773
msgid ""
"Return the PropertyTypes matching the property category. Repeat for "
"multiple."
msgstr ""
#: .\cookbook\views\api.py:1804 .\cookbook\views\api.py:1860
msgid "Returns only entries associated with the given mealplan id"
msgstr ""
#: .\cookbook\views\api.py:1858
msgid ""
"Returns only elements updated after the given timestamp in ISO 8601 format."
msgstr ""
#: .\cookbook\views\api.py:2031
msgid ""
"Return the Automations matching the automation type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2048
msgid ""
"Text field to store data that gets carried over to the UserSpace created "
"from the InviteLink"
msgstr ""
#: .\cookbook\views\api.py:2049
msgid "Only return InviteLinks that have not been used yet."
msgstr ""
#: .\cookbook\views\api.py:2076
msgid "Return the CustomFilters matching the model type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2176
msgid "Nothing to do."
msgstr ""
#: .\cookbook\views\api.py:2222
msgid "Invalid Url"
msgstr ""
#: .\cookbook\views\api.py:2228
msgid "Connection Refused."
msgstr ""
#: .\cookbook\views\api.py:2232
msgid "Bad URL Schema."
msgstr ""
#: .\cookbook\views\api.py:2257
msgid "No usable data could be found."
msgstr ""
#: .\cookbook\views\api.py:2286 .\cookbook\views\api.py:2434
msgid "You must select an AI provider to perform your request."
msgstr ""
#: .\cookbook\views\api.py:2293 .\cookbook\views\api.py:2441
msgid ""
"You don't have any credits remaining to use AI or AI features are not "
"enabled for your space."
msgstr ""
#: .\cookbook\views\api.py:2499 .\cookbook\views\api.py:2667
msgid "File is above space limit"
msgstr ""
#: .\cookbook\views\api.py:2522 .\cookbook\views\api.py:2684
msgid "Importing is not implemented for this provider"
msgstr ""
#: .\cookbook\views\api.py:2548
msgid ""
"The PDF Exporter is not enabled on this instance as it is still in an "
"experimental state."
msgstr ""
#: .\cookbook\views\api.py:2842
msgid "This feature is not yet available in the hosted version of tandoor!"
msgstr ""
#: .\cookbook\views\api.py:2863
msgid "Sync successful!"
msgstr ""
#: .\cookbook\views\api.py:2866
msgid "Error synchronizing with Storage"
msgstr ""
#: .\cookbook\views\views.py:89
msgid "This feature is not available in the demo version!"
msgstr ""
#: .\cookbook\views\views.py:110
msgid ""
"You have successfully created your own recipe space. Start by adding some "
"recipes or invite other people to join you."
msgstr ""
#: .\cookbook\views\views.py:171
#, python-format
msgid "PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!"
msgstr ""
#: .\cookbook\views\views.py:174
#, python-format
msgid "You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended"
msgstr ""
#: .\cookbook\views\views.py:178
msgid "Unable to determine PostgreSQL version."
msgstr ""
#: .\cookbook\views\views.py:182
msgid ""
"This application is not running with a Postgres database backend. This is ok "
"but not recommended as some features only work with postgres databases."
msgstr ""
#: .\cookbook\views\views.py:296
msgid ""
"The setup page can only be used to create the first "
"user! If you have forgotten your superuser credentials "
"please consult the django documentation on how to reset passwords."
msgstr ""
#: .\cookbook\views\views.py:304
msgid "Passwords dont match!"
msgstr ""
#: .\cookbook\views\views.py:312
msgid "User has been created, please login!"
msgstr ""
#: .\cookbook\views\views.py:352
msgid ""
"Reporting share links is not enabled for this instance. Please notify the "
"page administrator to report problems."
msgstr ""
#: .\cookbook\views\views.py:357
msgid ""
"Recipe sharing link has been disabled! For additional information please "
"contact the page administrator."
msgstr ""
#: .\cookbook\views\views.py:383
msgid "Manage recipes, shopping list, meal plans and more."
msgstr ""
#: .\cookbook\views\views.py:397 .\cookbook\views\views.py:398
msgid "Plan"
msgstr ""
#: .\cookbook\views\views.py:399
msgid "View your meal Plan"
msgstr ""
#: .\cookbook\views\views.py:418
msgid "View your shopping lists"
msgstr ""
================================================
FILE: cookbook/locale/es/LC_MESSAGES/django.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
# Translators:
# Alberto , 2020
# alfa5 , 2020
# miguel angel , 2020
# Miguel Canteras , 2021
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-22 20:15+0200\n"
"PO-Revision-Date: 2025-06-23 08:28+0000\n"
"Last-Translator: Ángel <1024mb@users.noreply.translate.tandoor.dev>\n"
"Language-Team: Spanish \n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.8.4\n"
#: .\cookbook\forms.py:50
msgid "Default"
msgstr "Por defecto"
#: .\cookbook\forms.py:77
msgid ""
"To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything."
msgstr ""
"Para evitar duplicados, las recetas con el mismo nombre que las ya "
"existentes serán ignoradas. Marca esta casilla para importar todo."
#: .\cookbook\forms.py:108
msgid "Maximum number of users for this space reached."
msgstr "Se ha alcanzado el número máximo de usuarios en este espacio."
#: .\cookbook\forms.py:114
msgid "Email address already taken!"
msgstr "¡El correo electrónico ya existe!"
#: .\cookbook\forms.py:121
msgid ""
"An email address is not required but if present the invite link will be sent "
"to the user."
msgstr ""
"El correo electrónico es opcional. Si se añade uno se mandará un link de "
"invitación."
#: .\cookbook\forms.py:133
msgid "Name already taken."
msgstr "El nombre ya existe."
#: .\cookbook\forms.py:144 .\cookbook\forms.py:158
msgid "Accept Terms and Privacy"
msgstr "Aceptar términos y condiciones"
#: .\cookbook\helper\AllAuthCustomAdapter.py:41
msgid ""
"In order to prevent spam, the requested email was not send. Please wait a "
"few minutes and try again."
msgstr ""
"Para prevenir el spam, el correo electrónico solicitado no se envió. Por "
"favor, espere unos minutos e inténtelo de nuevo."
#: .\cookbook\helper\permission_helper.py:165
#: .\cookbook\helper\permission_helper.py:186 .\cookbook\views\views.py:138
msgid "You are not logged in and therefore cannot view this page!"
msgstr "¡No ha iniciado sesión y por lo tanto no puede ver esta página!"
#: .\cookbook\helper\permission_helper.py:168
#: .\cookbook\helper\permission_helper.py:173
#: .\cookbook\helper\permission_helper.py:198
#: .\cookbook\helper\permission_helper.py:265
#: .\cookbook\helper\permission_helper.py:279
#: .\cookbook\helper\permission_helper.py:290
#: .\cookbook\helper\permission_helper.py:301
#: .\cookbook\helper\permission_helper.py:317
#: .\cookbook\helper\permission_helper.py:343
#: .\cookbook\helper\permission_helper.py:359
msgid "You do not have the required permissions to view this page!"
msgstr "¡No tienes los permisos necesarios para ver esta página!"
#: .\cookbook\helper\permission_helper.py:191
#: .\cookbook\helper\permission_helper.py:214
#: .\cookbook\helper\permission_helper.py:236
#: .\cookbook\helper\permission_helper.py:251
msgid "You cannot interact with this object as it is not owned by you!"
msgstr "¡No puede interactuar con este objeto ya que no es de tu propiedad!"
#: .\cookbook\helper\permission_helper.py:420
msgid "You have reached the maximum number of recipes for your space."
msgstr "Ha alcanzado el número máximo de recetas para su espacio."
#: .\cookbook\helper\permission_helper.py:432
msgid "You have more users than allowed in your space."
msgstr "Tenés mas usuarios que los permitidos en tu espacio"
#: .\cookbook\helper\recipe_url_import.py:319
msgid "reverse rotation"
msgstr "rotación inversa"
#: .\cookbook\helper\recipe_url_import.py:320
msgid "careful rotation"
msgstr "rotación cuidadosa"
#: .\cookbook\helper\recipe_url_import.py:321
msgid "knead"
msgstr "amasar"
#: .\cookbook\helper\recipe_url_import.py:322
msgid "thicken"
msgstr "espesar"
#: .\cookbook\helper\recipe_url_import.py:323
msgid "warm up"
msgstr "precalentar"
#: .\cookbook\helper\recipe_url_import.py:324
msgid "ferment"
msgstr "fermentar"
#: .\cookbook\helper\recipe_url_import.py:325
#, fuzzy
#| msgid "Last cooked"
msgid "slow cook"
msgstr "Cocinado por última vez"
#: .\cookbook\helper\recipe_url_import.py:326
msgid "egg boiler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:327
msgid "kettle"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:328
msgid "blend"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:329
msgid "pre-clean"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:330
msgid "high temperature"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:331
msgid "rice cooker"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:332
msgid "caramelize"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:333
msgid "peeler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:334
msgid "slicer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:335
msgid "grater"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:336
msgid "spiralizer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:337
msgid "sous-vide"
msgstr "sous-vide"
#: .\cookbook\helper\shopping_helper.py:150
msgid "You must supply a servings size"
msgstr "Debe proporcionar un tamaño de porción"
#: .\cookbook\helper\template_helper.py:97
#: .\cookbook\helper\template_helper.py:99
#: .\cookbook\helper\template_helper.py:101
msgid "Could not parse template code."
msgstr "No se pudo parsear el código de la planitlla."
#: .\cookbook\integration\copymethat.py:44
#: .\cookbook\integration\melarecipes.py:37
msgid "Favorite"
msgstr "Favorito"
#: .\cookbook\integration\copymethat.py:50
msgid "I made this"
msgstr "Lo he preparado"
#: .\cookbook\integration\integration.py:238
msgid ""
"Importer expected a .zip file. Did you choose the correct importer type for "
"your data ?"
msgstr ""
"El importador esperaba un fichero.zip. ¿Has escogido el tipo de importador "
"correcto para tus datos?"
#: .\cookbook\integration\integration.py:241
msgid ""
"An unexpected error occurred during the import. Please make sure you have "
"uploaded a valid file."
msgstr ""
"Ocurrió un error inesperado al importar. Por favor asegurate de haber subido "
"un archivo válido."
#: .\cookbook\integration\integration.py:246
msgid "The following recipes were ignored because they already existed:"
msgstr "Las siguentes recetas han sido ignordas por que ya existen:"
#: .\cookbook\integration\integration.py:250
#, python-format
msgid "Imported %s recipes."
msgstr "Se importaron %s recetas."
#: .\cookbook\integration\mealie1.py:210
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "Calories"
msgstr "Calorías"
#: .\cookbook\integration\mealie1.py:211
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
msgid "Carbohydrates"
msgstr "Carbohidratos"
#: .\cookbook\integration\mealie1.py:212
msgid "Cholesterol"
msgstr ""
#: .\cookbook\integration\mealie1.py:213
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
msgid "Fat"
msgstr "Grasa"
#: .\cookbook\integration\mealie1.py:214
msgid "Fiber"
msgstr ""
#: .\cookbook\integration\mealie1.py:215
#, fuzzy
#| msgid "Proteins"
msgid "Protein"
msgstr "Proteinas"
#: .\cookbook\integration\mealie1.py:216
msgid "Saturated Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:217
msgid "Sodium"
msgstr ""
#: .\cookbook\integration\mealie1.py:218
msgid "Sugar"
msgstr ""
#: .\cookbook\integration\mealie1.py:219
msgid "Trans Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:220
msgid "Unsaturated Fat"
msgstr ""
#: .\cookbook\integration\openeats.py:28
msgid "Recipe source:"
msgstr "Fuente de la receta:"
#: .\cookbook\integration\paprika.py:49
msgid "Notes"
msgstr "Notas"
#: .\cookbook\integration\paprika.py:52
msgid "Nutritional Information"
msgstr "Información Nutricional"
#: .\cookbook\integration\paprika.py:56
msgid "Source"
msgstr "Fuente"
#: .\cookbook\integration\recettetek.py:55
#: .\cookbook\integration\recipekeeper.py:70
msgid "Imported from"
msgstr "Importado de"
#: .\cookbook\integration\saffron.py:23
msgid "Servings"
msgstr "Raciones"
#: .\cookbook\integration\saffron.py:25
msgid "Waiting time"
msgstr "Tiempo de espera"
#: .\cookbook\integration\saffron.py:27
msgid "Preparation Time"
msgstr "Tiempo de Preparación"
#: .\cookbook\integration\saffron.py:29 .\cookbook\templates\index.html:6
msgid "Cookbook"
msgstr "Libro de cocina"
#: .\cookbook\integration\saffron.py:31
msgid "Section"
msgstr "Sección"
#: .\cookbook\management\commands\fix_duplicate_properties.py:15
#, fuzzy
msgid "Fixes foods with "
msgstr "Corrige alimentos con "
#: .\cookbook\management\commands\rebuildindex.py:14
msgid "Rebuilds full text search index on Recipe"
msgstr "Reconstruye el índice de búsqueda por texto completo de la receta"
#: .\cookbook\management\commands\rebuildindex.py:18
msgid "Only Postgresql databases use full text search, no index to rebuild"
msgstr ""
"Solo las bases de datos Postgresql utilizan la búsqueda por texto completo, "
"no hay índice para reconstruir"
#: .\cookbook\management\commands\rebuildindex.py:29
msgid "Recipe index rebuild complete."
msgstr "Se reconstruyó el índice de la receta."
#: .\cookbook\management\commands\rebuildindex.py:31
msgid "Recipe index rebuild failed."
msgstr "No fue posible reconstruir el índice de la receta."
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
msgid "Breakfast"
msgstr "Desayuno"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:19
msgid "Lunch"
msgstr "Almuerzo"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:24
msgid "Dinner"
msgstr "Cena"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:29 .\cookbook\models.py:971
msgid "Other"
msgstr "Otro"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "g"
msgstr "gr."
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "Proteins"
msgstr "Proteinas"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "kcal"
msgstr "kcal"
#: .\cookbook\models.py:325
msgid ""
"Maximum file storage for space in MB. 0 for unlimited, -1 to disable file "
"upload."
msgstr ""
"Almacenamiento máximo de archivos para el espacio en MB. 0 para ilimitado, "
"-1 para desactivar la carga de archivos."
#: .\cookbook\models.py:513
msgid "Search"
msgstr "Buscar"
#: .\cookbook\models.py:514
msgid "Meal-Plan"
msgstr "Régimen de comidas"
#: .\cookbook\models.py:515
msgid "Books"
msgstr "Libros"
#: .\cookbook\models.py:516 .\cookbook\views\views.py:416
#: .\cookbook\views\views.py:417
msgid "Shopping"
msgstr "Compras"
#: .\cookbook\models.py:967
msgid "Nutrition"
msgstr "Información Nutricional"
#: .\cookbook\models.py:968
msgid "Allergen"
msgstr "Alérgeno"
#: .\cookbook\models.py:969
msgid "Price"
msgstr "Precio"
#: .\cookbook\models.py:970
msgid "Goal"
msgstr "Objetivo"
#: .\cookbook\models.py:1467 .\cookbook\templates\search_info.html:28
msgid "Simple"
msgstr "Simple"
#: .\cookbook\models.py:1468 .\cookbook\templates\search_info.html:33
msgid "Phrase"
msgstr "Frase"
#: .\cookbook\models.py:1469 .\cookbook\templates\search_info.html:38
msgid "Web"
msgstr "Web"
#: .\cookbook\models.py:1470 .\cookbook\templates\search_info.html:47
msgid "Raw"
msgstr "Crudo"
#: .\cookbook\models.py:1525
msgid "Food Alias"
msgstr "Alias de la Comida"
#: .\cookbook\models.py:1526
msgid "Unit Alias"
msgstr "Alias de unidad"
#: .\cookbook\models.py:1527
msgid "Keyword Alias"
msgstr "Alias de palabra clave"
#: .\cookbook\models.py:1528
msgid "Description Replace"
msgstr "Reemplazo de descripción"
#: .\cookbook\models.py:1529
msgid "Instruction Replace"
msgstr "Reemplazo de instrucciones"
#: .\cookbook\models.py:1530
msgid "Never Unit"
msgstr "Unidad prohibida"
#: .\cookbook\models.py:1531
msgid "Transpose Words"
msgstr "Transponer palabras"
#: .\cookbook\models.py:1532
msgid "Food Replace"
msgstr "Reemplazo de alimento"
#: .\cookbook\models.py:1533
msgid "Unit Replace"
msgstr "Reemplazo de unidad"
#: .\cookbook\models.py:1534
msgid "Name Replace"
msgstr "Reemplazo de nombre"
#: .\cookbook\models.py:1564
msgid "Recipe"
msgstr "Receta"
#: .\cookbook\models.py:1565
msgid "Food"
msgstr "Alimento"
#: .\cookbook\models.py:1566
msgid "Keyword"
msgstr "Palabra clave"
#: .\cookbook\serializer.py:262
msgid "File uploads are not enabled for this Space."
msgstr "Las cargas de archivo no están habilitadas para esta Instancia."
#: .\cookbook\serializer.py:273
msgid "You have reached your file upload limit."
msgstr "Has alcanzado el límite de cargas de archivo."
#: .\cookbook\serializer.py:281
msgid "The given file type is not allowed."
msgstr ""
#: .\cookbook\serializer.py:421 .\cookbook\views\views.py:94
#, fuzzy
#| msgid "You have reached the maximum number of recipes for your space."
msgid ""
"You have the reached the maximum amount of spaces that can be owned by you."
msgstr "Ha alcanzado el número máximo de recetas para su espacio."
#: .\cookbook\serializer.py:434
msgid "Space Name must be unique."
msgstr ""
#: .\cookbook\serializer.py:469
msgid "Cannot modify Space owner permission."
msgstr "No puedes modificar los permisos del propietario de la Instancia."
#: .\cookbook\serializer.py:1596
msgid "Hello"
msgstr "Hola"
#: .\cookbook\serializer.py:1596
msgid "You have been invited by "
msgstr "Has sido invitado por: "
#: .\cookbook\serializer.py:1598
msgid " to join their Tandoor Recipes space "
msgstr " para unirte a su instancia de Tandoor Recipes "
#: .\cookbook\serializer.py:1600
msgid "Click the following link to activate your account: "
msgstr "Haz click en el siguiente enlace para activar tu cuenta: "
#: .\cookbook\serializer.py:1602
msgid ""
"If the link does not work use the following code to manually join the space: "
msgstr ""
"Si el enlace no funciona, utiliza el siguiente código para unirte "
"manualmente a la instancia: "
#: .\cookbook\serializer.py:1604
msgid "The invitation is valid until "
msgstr "La invitación es válida hasta "
#: .\cookbook\serializer.py:1606
msgid ""
"Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub "
msgstr ""
"Tandoor Recipes es un administrador de recetas Open Source. Dale una ojeada "
"en GitHub "
#: .\cookbook\serializer.py:1609
msgid "Tandoor Recipes Invite"
msgstr "Invitación para Tandoor Recipes"
#: .\cookbook\serializer.py:1813
msgid "Existing shopping list to update"
msgstr "Lista de compras existente para actualizar"
#: .\cookbook\serializer.py:1815
msgid ""
"List of ingredient IDs from the recipe to add, if not provided all "
"ingredients will be added."
msgstr ""
"Lista de IDs de ingredientes de la receta para agregar; si no se "
"proporciona, se agregarán todos los ingredientes."
#: .\cookbook\serializer.py:1817
msgid ""
"Providing a list_recipe ID and servings of 0 will delete that shopping list."
msgstr ""
"Proporcionar un ID list_recipe y porciones igual a 0 eliminará esa lista de "
"compras."
#: .\cookbook\serializer.py:1826
msgid "Amount of food to add to the shopping list"
msgstr "Cantidad de alimento a agregar a la lista de compras"
#: .\cookbook\serializer.py:1828
msgid "ID of unit to use for the shopping list"
msgstr ""
#: .\cookbook\serializer.py:1830
msgid "When set to true will delete all food from active shopping lists."
msgstr ""
#: .\cookbook\templates\404.html:5
msgid "404 Error"
msgstr "Error 404"
#: .\cookbook\templates\404.html:18
msgid "The page you are looking for could not be found."
msgstr "No se pudo encontrar la página que busca."
#: .\cookbook\templates\404.html:33
msgid "Take me Home"
msgstr "Llévame a Inicio"
#: .\cookbook\templates\404.html:35
msgid "Report a Bug"
msgstr "Reportar un error"
#: .\cookbook\templates\account\email.html:6
#: .\cookbook\templates\account\email.html:10
msgid "E-mail Addresses"
msgstr ""
#: .\cookbook\templates\account\email.html:12
msgid "The following e-mail addresses are associated with your account:"
msgstr ""
#: .\cookbook\templates\account\email.html:29
msgid "Verified"
msgstr ""
#: .\cookbook\templates\account\email.html:31
msgid "Unverified"
msgstr ""
#: .\cookbook\templates\account\email.html:33
msgid "Primary"
msgstr ""
#: .\cookbook\templates\account\email.html:40
#, fuzzy
#| msgid "Make Header"
msgid "Make Primary"
msgstr "Crear encabezado"
#: .\cookbook\templates\account\email.html:42
msgid "Re-send Verification"
msgstr ""
#: .\cookbook\templates\account\email.html:43
#: .\cookbook\templates\socialaccount\connections.html:36
msgid "Remove"
msgstr "Eliminar"
#: .\cookbook\templates\account\email.html:51
msgid "Warning:"
msgstr "Advertencia:"
#: .\cookbook\templates\account\email.html:51
msgid ""
"You currently do not have any e-mail address set up. You should really add "
"an e-mail address so you can receive notifications, reset your password, etc."
msgstr ""
#: .\cookbook\templates\account\email.html:57
msgid "Add E-mail Address"
msgstr ""
#: .\cookbook\templates\account\email.html:62
msgid "Add E-mail"
msgstr ""
#: .\cookbook\templates\account\email.html:72
msgid "Do you really want to remove the selected e-mail address?"
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:6
#: .\cookbook\templates\account\email_confirm.html:10
msgid "Confirm E-mail Address"
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:16
#, python-format
msgid ""
"Please confirm that\n"
" %(email)s is an e-mail address "
"for user %(user_display)s\n"
" ."
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:22
msgid "Confirm"
msgstr "Confirmar"
#: .\cookbook\templates\account\email_confirm.html:29
#, python-format
msgid ""
"This e-mail confirmation link expired or is invalid. Please\n"
" issue a new e-mail confirmation "
"request."
msgstr ""
#: .\cookbook\templates\account\login.html:8
#: .\cookbook\templates\openid\login.html:8
msgid "Login"
msgstr "Iniciar sesión"
#: .\cookbook\templates\account\login.html:15
#: .\cookbook\templates\account\login.html:31
#: .\cookbook\templates\account\password_reset.html:39
#: .\cookbook\templates\account\password_reset_done.html:31
#: .\cookbook\templates\account\signup.html:68
#: .\cookbook\templates\account\signup_closed.html:15
#: .\cookbook\templates\openid\login.html:15
#: .\cookbook\templates\openid\login.html:26
#: .\cookbook\templates\socialaccount\authentication_error.html:15
msgid "Sign In"
msgstr "Iniciar sesión"
#: .\cookbook\templates\account\login.html:34
#: .\cookbook\templates\account\password_reset.html:41
#: .\cookbook\templates\account\password_reset_done.html:33
#: .\cookbook\templates\socialaccount\signup.html:8
#: .\cookbook\templates\socialaccount\signup.html:56
#, fuzzy
#| msgid "Sign In"
msgid "Sign Up"
msgstr "Iniciar sesión"
#: .\cookbook\templates\account\login.html:38
msgid "Lost your password?"
msgstr ""
#: .\cookbook\templates\account\login.html:39
#: .\cookbook\templates\account\password_reset.html:29
msgid "Reset My Password"
msgstr ""
#: .\cookbook\templates\account\login.html:50
msgid "Social Login"
msgstr "Inicio de sesión social"
#: .\cookbook\templates\account\login.html:51
msgid "You can use any of the following providers to sign in."
msgstr ""
"Puedes usar cualquiera de los siguientes proveedores de inicio de sesión."
#: .\cookbook\templates\account\logout.html:5
#: .\cookbook\templates\account\logout.html:9
#: .\cookbook\templates\account\logout.html:18
msgid "Sign Out"
msgstr "Salir"
#: .\cookbook\templates\account\logout.html:11
msgid "Are you sure you want to sign out?"
msgstr "¿Seguro que quieres salir?"
#: .\cookbook\templates\account\password_change.html:6
#: .\cookbook\templates\account\password_change.html:10
#: .\cookbook\templates\account\password_change.html:15
#: .\cookbook\templates\account\password_reset_from_key.html:7
#: .\cookbook\templates\account\password_reset_from_key.html:13
#: .\cookbook\templates\account\password_reset_from_key_done.html:7
#: .\cookbook\templates\account\password_reset_from_key_done.html:13
msgid "Change Password"
msgstr "Cambiar contraseña"
#: .\cookbook\templates\account\password_change.html:16
msgid "Forgot Password?"
msgstr ""
#: .\cookbook\templates\account\password_reset.html:7
#: .\cookbook\templates\account\password_reset.html:13
#: .\cookbook\templates\account\password_reset_done.html:7
#: .\cookbook\templates\account\password_reset_done.html:18
msgid "Password Reset"
msgstr "Restablecer contraseña"
#: .\cookbook\templates\account\password_reset.html:24
msgid ""
"Forgotten your password? Enter your e-mail address below, and we'll send you "
"an e-mail allowing you to reset it."
msgstr ""
#: .\cookbook\templates\account\password_reset.html:32
msgid "Password reset is disabled on this instance."
msgstr "Restablecimiento de contraseña no está implementado de momento."
#: .\cookbook\templates\account\password_reset_done.html:25
msgid ""
"We have sent you an e-mail. Please contact us if you do not receive it "
"within a few minutes."
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:13
#, fuzzy
#| msgid "API Token"
msgid "Bad Token"
msgstr "Token API"
#: .\cookbook\templates\account\password_reset_from_key.html:25
#, python-format
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used.\n"
" Please request a new "
"password reset."
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:33
msgid "change password"
msgstr "Cambiar contraseña"
#: .\cookbook\templates\account\password_reset_from_key.html:36
#: .\cookbook\templates\account\password_reset_from_key_done.html:19
msgid "Your password is now changed."
msgstr ""
#: .\cookbook\templates\account\password_set.html:6
#: .\cookbook\templates\account\password_set.html:10
#: .\cookbook\templates\account\password_set.html:15
#, fuzzy
#| msgid "Password Reset"
msgid "Set Password"
msgstr "Restablecer contraseña"
#: .\cookbook\templates\account\signup.html:5
msgid "Register"
msgstr "Registrar"
#: .\cookbook\templates\account\signup.html:11
#, fuzzy
#| msgid "Create your Account"
msgid "Create an Account"
msgstr "Crea tu Cuenta"
#: .\cookbook\templates\account\signup.html:41
msgid "I accept the follwoing"
msgstr ""
#: .\cookbook\templates\account\signup.html:44
#: .\cookbook\templates\socialaccount\signup.html:35
msgid "Terms and Conditions"
msgstr ""
#: .\cookbook\templates\account\signup.html:47
#: .\cookbook\templates\socialaccount\signup.html:38
msgid "and"
msgstr ""
#: .\cookbook\templates\account\signup.html:51
#: .\cookbook\templates\socialaccount\signup.html:42
msgid "Privacy Policy"
msgstr ""
#: .\cookbook\templates\account\signup.html:64
msgid "Create User"
msgstr "Crear Usuario"
#: .\cookbook\templates\account\signup.html:68
msgid "Already have an account?"
msgstr ""
#: .\cookbook\templates\account\signup_closed.html:5
#: .\cookbook\templates\account\signup_closed.html:11
msgid "Sign Up Closed"
msgstr ""
#: .\cookbook\templates\account\signup_closed.html:13
msgid "We are sorry, but the sign up is currently closed."
msgstr ""
#: .\cookbook\templates\frontend\tandoor.html:15
#, fuzzy
#| msgid "Tandoor Recipes Invite"
msgid "Tandoor Recipe Manager"
msgstr "Invitación para Tandoor Recipes"
#: .\cookbook\templates\index.html:28
msgid "Search recipe ..."
msgstr "Buscar receta ..."
#: .\cookbook\templates\index.html:43
msgid "New Recipe"
msgstr "Nueva receta"
#: .\cookbook\templates\index.html:46
msgid "Import Recipe"
msgstr "Importar receta"
#: .\cookbook\templates\index.html:52
msgid "Advanced Search"
msgstr "Búsqueda Avanzada"
#: .\cookbook\templates\index.html:56
msgid "Reset Search"
msgstr "Restablecer búsqueda"
#: .\cookbook\templates\index.html:84
msgid "Last viewed"
msgstr "Visto por última vez"
#: .\cookbook\templates\index.html:86
msgid "Recipes"
msgstr "Recetas"
#: .\cookbook\templates\index.html:93
msgid "Log in to view recipes"
msgstr "Inicia sesión para ver recetas"
#: .\cookbook\templates\markdown_info.html:5
#: .\cookbook\templates\markdown_info.html:13
msgid "Markdown Info"
msgstr "Información de Markdown"
#: .\cookbook\templates\markdown_info.html:14
msgid ""
"\n"
" Markdown is lightweight markup language that can be used to format "
"plain text easily.\n"
" This site uses the Python Markdown library to\n"
" convert your text into nice looking HTML. Its full markdown "
"documentation can be found\n"
" here.\n"
" An incomplete but most likely sufficient documentation can be found "
"below.\n"
" "
msgstr ""
"\n"
"Markdown es un lenguaje de marcado ligero que puede usarse para formatear "
"texto plano fácilmente. Este sitio usa la librería Python Markdownpara convertir tu "
"texto en HTML atractivo. Su documentación completa puede ser encontrada aquí.\n"
"Una documentación incompleta pero suficiente puede encontrarse a "
"continuación."
#: .\cookbook\templates\markdown_info.html:25
msgid "Headers"
msgstr "Cabeceras"
#: .\cookbook\templates\markdown_info.html:54
msgid "Formatting"
msgstr "Formato"
#: .\cookbook\templates\markdown_info.html:56
#: .\cookbook\templates\markdown_info.html:72
msgid "Line breaks are inserted by adding two spaces after the end of a line"
msgstr ""
"Los saltos de línea se insertan añadiendo dos espacios después del final de "
"una línea"
#: .\cookbook\templates\markdown_info.html:57
#: .\cookbook\templates\markdown_info.html:73
#, fuzzy
#| msgid "or by leaving a blank line inbetween."
msgid "or by leaving a blank line in between."
msgstr "o dejando una línea en blanco entre ellos."
#: .\cookbook\templates\markdown_info.html:59
#: .\cookbook\templates\markdown_info.html:74
msgid "This text is bold"
msgstr "Este texto está en negrita"
#: .\cookbook\templates\markdown_info.html:60
#: .\cookbook\templates\markdown_info.html:75
msgid "This text is italic"
msgstr "Este texto está en itálica."
#: .\cookbook\templates\markdown_info.html:61
#: .\cookbook\templates\markdown_info.html:77
msgid "Blockquotes are also possible"
msgstr "Las citas también son posibles"
#: .\cookbook\templates\markdown_info.html:84
msgid "Lists"
msgstr "Listas"
#: .\cookbook\templates\markdown_info.html:85
#, fuzzy
#| msgid ""
#| "Lists can ordered or unorderd. It is important to leave a blank line "
#| "before the list!"
msgid ""
"Lists can ordered or unordered. It is important to leave a blank line "
"before the list!"
msgstr ""
"Las listas pueden ser ordenadas o no ordenadas. ¡Es importante dejar una "
"línea en blanco antes de la lista!"
#: .\cookbook\templates\markdown_info.html:87
#: .\cookbook\templates\markdown_info.html:108
msgid "Ordered List"
msgstr "Lista ordenada"
#: .\cookbook\templates\markdown_info.html:89
#: .\cookbook\templates\markdown_info.html:90
#: .\cookbook\templates\markdown_info.html:91
#: .\cookbook\templates\markdown_info.html:110
#: .\cookbook\templates\markdown_info.html:111
#: .\cookbook\templates\markdown_info.html:112
msgid "unordered list item"
msgstr "elemento de lista desordenado"
#: .\cookbook\templates\markdown_info.html:93
#: .\cookbook\templates\markdown_info.html:114
msgid "Unordered List"
msgstr "Lista desordenada"
#: .\cookbook\templates\markdown_info.html:95
#: .\cookbook\templates\markdown_info.html:96
#: .\cookbook\templates\markdown_info.html:97
#: .\cookbook\templates\markdown_info.html:116
#: .\cookbook\templates\markdown_info.html:117
#: .\cookbook\templates\markdown_info.html:118
msgid "ordered list item"
msgstr "elemento de lista ordenada"
#: .\cookbook\templates\markdown_info.html:125
msgid "Images & Links"
msgstr "Imágenes y enlaces"
#: .\cookbook\templates\markdown_info.html:126
msgid ""
"Links can be formatted with Markdown. This application also allows to paste "
"links directly into markdown fields without any formatting."
msgstr ""
"Los enlaces pueden ser formateados con Markdown. Esta aplicación también "
"permite pegar enlaces directamente en campos markdown sin formato."
#: .\cookbook\templates\markdown_info.html:132
#: .\cookbook\templates\markdown_info.html:145
msgid "This will become an image"
msgstr "Esto se convertirá en una imagen"
#: .\cookbook\templates\markdown_info.html:152
msgid "Tables"
msgstr "Tablas"
#: .\cookbook\templates\markdown_info.html:153
msgid ""
"Markdown tables are hard to create by hand. It is recommended to use a table "
"editor like this one."
msgstr ""
"Las tablas Mardown son difíciles de crear a mano. Se recomienda usar un "
"editor de tablas como este."
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:171
#: .\cookbook\templates\markdown_info.html:177
msgid "Table"
msgstr "Tabla"
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:172
msgid "Header"
msgstr "Cabecera"
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:178
msgid "Cell"
msgstr "Celda"
#: .\cookbook\templates\no_groups_info.html:5
#: .\cookbook\templates\no_groups_info.html:12
msgid "No Permissions"
msgstr "Sin permisos"
#: .\cookbook\templates\no_groups_info.html:17
#, fuzzy
#| msgid ""
#| "You do not have any groups and therefor cannot use this application. "
#| "Please contact your administrator."
msgid "You do not have any groups and therefor cannot use this application."
msgstr ""
"No tienes ningún grupo y por eso no puedes usar esta aplicación. Por favor, "
"contacta con tu administrador."
#: .\cookbook\templates\no_groups_info.html:18
#: .\cookbook\templates\no_perm_info.html:15
msgid "Please contact your administrator."
msgstr ""
#: .\cookbook\templates\no_perm_info.html:5
#: .\cookbook\templates\no_perm_info.html:12
#, fuzzy
#| msgid "No Permissions"
msgid "No Permission"
msgstr "Sin permisos"
#: .\cookbook\templates\no_perm_info.html:15
msgid ""
"You do not have the required permissions to view this page or perform this "
"action."
msgstr "No tienes los permisos necesarios para realizar esta acción."
#: .\cookbook\templates\offline.html:5
msgid "Offline"
msgstr "Desconectado"
#: .\cookbook\templates\openid\login.html:27
#: .\cookbook\templates\socialaccount\authentication_error.html:27
msgid "Back"
msgstr ""
#: .\cookbook\templates\rest_framework\api.html:5
msgid "Recipe Home"
msgstr "Página de inicio"
#: .\cookbook\templates\rest_framework\api.html:11
msgid "API Documentation"
msgstr "Documentación de API"
#: .\cookbook\templates\search_info.html:5
#: .\cookbook\templates\search_info.html:9
#, fuzzy
#| msgid "Search String"
msgid "Search Settings"
msgstr "Cadena de búsqueda"
#: .\cookbook\templates\search_info.html:10
msgid ""
"\n"
" Creating the best search experience is complicated and weighs "
"heavily on your personal configuration. \n"
" Changing any of the search settings can have significant impact on "
"the speed and quality of the results.\n"
" Search Methods, Trigrams and Full Text Search configurations are "
"only available if you are using Postgres for your database.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:19
#, fuzzy
#| msgid "Search"
msgid "Search Methods"
msgstr "Buscar"
#: .\cookbook\templates\search_info.html:23
msgid ""
" \n"
" Full text searches attempt to normalize the words provided to "
"match common variants. For example: 'forked', 'forking', 'forks' will all "
"normalize to 'fork'.\n"
" There are several methods available, described below, that will "
"control how the search behavior should react when multiple words are "
"searched.\n"
" Full technical details on how these operate can be viewed on Postgresql's website.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:29
msgid ""
" \n"
" Simple searches ignore punctuation and common words such as "
"'the', 'a', 'and'. And will treat separate words as required.\n"
" Searching for 'apple or flour' will return any recipe that "
"includes both 'apple' and 'flour' anywhere in the fields that have been "
"selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:34
msgid ""
" \n"
" Phrase searches ignore punctuation, but will search for all of "
"the words in the exact order provided.\n"
" Searching for 'apple or flour' will only return a recipe that "
"includes the exact phrase 'apple or flour' in any of the fields that have "
"been selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:39
msgid ""
" \n"
" Web searches simulate functionality found on many web search "
"sites supporting special syntax.\n"
" Placing quotes around several words will convert those words "
"into a phrase.\n"
" 'or' is recognized as searching for the word (or phrase) "
"immediately before 'or' OR the word (or phrase) directly after.\n"
" '-' is recognized as searching for recipes that do not include "
"the word (or phrase) that comes immediately after. \n"
" For example searching for 'apple pie' or cherry -butter will "
"return any recipe that includes the phrase 'apple pie' or the word "
"'cherry' \n"
" in any field included in the full text search but exclude any "
"recipe that has the word 'butter' in any field included.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:48
msgid ""
" \n"
" Raw search is similar to Web except will take puncuation "
"operators such as '|', '&' and '()'\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:59
msgid ""
" \n"
" Another approach to searching that also requires Postgresql is "
"fuzzy search or trigram similarity. A trigram is a group of three "
"consecutive characters.\n"
" For example searching for 'apple' will create x trigrams 'app', "
"'ppl', 'ple' and will create a score of how closely words match the "
"generated trigrams.\n"
" One benefit of searching trigams is that a search for 'sandwich' "
"will find misspelled words such as 'sandwhich' that would be missed by other "
"methods.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:69
#, fuzzy
#| msgid "Search Recipe"
msgid "Search Fields"
msgstr "Buscar Receta"
#: .\cookbook\templates\search_info.html:73
msgid ""
" \n"
" Unaccent is a special case in that it enables searching a field "
"'unaccented' for each search style attempting to ignore accented values. \n"
" For example when you enable unaccent for 'Name' any search "
"(starts with, contains, trigram) will attempt the search ignoring accented "
"characters.\n"
" \n"
" For the other options, you can enable search on any or all "
"fields and they will be combined together with an assumed 'OR'.\n"
" For example enabling 'Name' for Starts With, 'Name' and "
"'Description' for Partial Match and 'Ingredients' and 'Keywords' for Full "
"Search\n"
" and searching for 'apple' will generate a search that will "
"return recipes that have:\n"
" - A recipe name that starts with 'apple'\n"
" - OR a recipe name that contains 'apple'\n"
" - OR a recipe description that contains 'apple'\n"
" - OR a recipe that will have a full text search match ('apple' "
"or 'apples') in ingredients\n"
" - OR a recipe that will have a full text search match in "
"Keywords\n"
"\n"
" Combining too many fields in too many types of search can have a "
"negative impact on performance, create duplicate results or return "
"unexpected results.\n"
" For example, enabling fuzzy search or partial matches will "
"interfere with web search methods. \n"
" Searching for 'apple -pie' with fuzzy search and full text "
"search will return the recipe Apple Pie. Though it is not included in the "
"full text results, it does match the trigram results.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:95
#, fuzzy
#| msgid "Search"
msgid "Search Index"
msgstr "Buscar"
#: .\cookbook\templates\search_info.html:99
msgid ""
" \n"
" Trigram search and Full Text Search both rely on database "
"indexes to perform effectively. \n"
" You can rebuild the indexes on all fields in the Admin page for "
"Recipes and selecting all recipes and running 'rebuild index for selected "
"recipes'\n"
" You can also rebuild indexes at the command line by executing "
"the management command 'python manage.py rebuildindex'\n"
" "
msgstr ""
#: .\cookbook\templates\setup.html:6
msgid "Cookbook Setup"
msgstr "Configuración del libro de recetas"
#: .\cookbook\templates\setup.html:14
msgid "Setup"
msgstr "Configuración"
#: .\cookbook\templates\setup.html:15
msgid ""
"To start using this application you must first create a superuser account."
msgstr ""
"Para empezar a usar esta aplicación primero tienes que crear una cuenta de "
"superusuario."
#: .\cookbook\templates\setup.html:20
msgid "Create Superuser account"
msgstr "Crear cuenta de Superusuario"
#: .\cookbook\templates\socialaccount\authentication_error.html:7
#: .\cookbook\templates\socialaccount\authentication_error.html:23
#, fuzzy
#| msgid "Social Login"
msgid "Social Network Login Failure"
msgstr "Inicio de sesión social"
#: .\cookbook\templates\socialaccount\authentication_error.html:25
msgid ""
"An error occurred while attempting to login via your social network account."
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:4
#: .\cookbook\templates\socialaccount\connections.html:7
msgid "Account Connections"
msgstr "Conexiones de la cuenta"
#: .\cookbook\templates\socialaccount\connections.html:10
msgid ""
"You can sign in to your account using any of the following third party\n"
" accounts:"
msgstr ""
"Puedes entrar en tu cuenta usando cualquiera de las siguientes cuentas de "
"terceros:"
#: .\cookbook\templates\socialaccount\connections.html:44
msgid ""
"You currently have no social network accounts connected to this account."
msgstr "Actualmente no tienes una cuenta social conectada a esta cuenta."
#: .\cookbook\templates\socialaccount\connections.html:47
msgid "Add a 3rd Party Account"
msgstr "Añadir una cuenta de terceros"
#: .\cookbook\templates\socialaccount\login.html:5
#: .\cookbook\templates\socialaccount\signup.html:5
#, fuzzy
#| msgid "Sign In"
msgid "Signup"
msgstr "Iniciar sesión"
#: .\cookbook\templates\socialaccount\login.html:9
#, python-format
msgid "Connect %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:11
#, python-format
msgid "You are about to connect a new third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:13
#, python-format
msgid "Sign In Via %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:15
#, python-format
msgid "You are about to sign in using a third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:20
msgid "Continue"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:10
#, python-format
msgid ""
"You are about to use your\n"
" %(provider_name)s account to login to\n"
" %(site_name)s. As a final step, please complete the following form:"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:32
msgid "I accept the following"
msgstr ""
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:23
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:31
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:39
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:47
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:55
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:63
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:71
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:79
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:87
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:95
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:103
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:111
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:119
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:127
#, fuzzy
#| msgid "Sign In"
msgid "Sign in using"
msgstr "Iniciar sesión"
#: .\cookbook\templates\space_overview.html:6
msgid "Overview"
msgstr ""
#: .\cookbook\templates\space_overview.html:13
msgid "Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:17
msgid ""
"Recipes, foods, shopping lists and more are organized in spaces of one or "
"more people."
msgstr ""
#: .\cookbook\templates\space_overview.html:18
msgid ""
"You can either be invited into an existing space or create your own one."
msgstr ""
#: .\cookbook\templates\space_overview.html:25
#, fuzzy
#| msgid "Create User"
msgid "Your Spaces"
msgstr "Crear Usuario"
#: .\cookbook\templates\space_overview.html:53
msgid "Owner"
msgstr ""
#: .\cookbook\templates\space_overview.html:73
#: .\cookbook\templates\space_overview.html:83
msgid "Join Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:76
msgid "Join an existing space."
msgstr ""
#: .\cookbook\templates\space_overview.html:78
msgid ""
"To join an existing space either enter your invite token or click on the "
"invite link the space owner send you."
msgstr ""
#: .\cookbook\templates\space_overview.html:91
#: .\cookbook\templates\space_overview.html:100
#, fuzzy
#| msgid "Create User"
msgid "Create Space"
msgstr "Crear Usuario"
#: .\cookbook\templates\space_overview.html:94
msgid "Create your own recipe space."
msgstr ""
#: .\cookbook\templates\space_overview.html:96
msgid "Start your own recipe space and invite other users to it."
msgstr ""
#: .\cookbook\templates\system.html:23
msgid "System"
msgstr "Sistema"
#: .\cookbook\templates\system.html:24
#, fuzzy
#| msgid ""
#| "\n"
#| " Django Recipes is an open source free software application. It "
#| "can be found on\n"
#| " GitHub.\n"
#| " Changelogs can be found here.\n"
#| " "
msgid ""
"\n"
" Tandoor Recipes is an open source free software application. It can "
"be found on\n"
" GitHub.\n"
" Changelogs can be found here.\n"
" "
msgstr ""
"\n"
" Django Recipes es una aplicación de software libre de código "
"abierto. Se puede encontrar en\n"
" GitHub.\n"
" Los registros de cambios se pueden encontrar aquí.\n"
" "
#: .\cookbook\templates\system.html:30
msgid "System Information"
msgstr "Información del Sistema"
#: .\cookbook\templates\system.html:51
msgid ""
"\n"
" You need to execute version.py in your update "
"script to generate version information (done automatically in docker).\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:56
msgid "Plugins"
msgstr ""
#: .\cookbook\templates\system.html:67
msgid "Media Serving"
msgstr "Servidor multimedia"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:123 .\cookbook\templates\system.html:134
msgid "Warning"
msgstr "Advertencia"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:125 .\cookbook\templates\system.html:134
msgid "Ok"
msgstr "Ok"
#: .\cookbook\templates\system.html:70
msgid ""
"Serving media files directly using gunicorn/python is not recommend!\n"
" Please follow the steps described\n"
" here to update\n"
" your installation.\n"
" "
msgstr ""
"Servir archivos multimedia utilizando directamente gunicorn/python no "
"está recomendado!\n"
" Por favor, sigue los pasos descritos\n"
" aquí para actualizar\n"
" tu instalación.\n"
" "
#: .\cookbook\templates\system.html:76 .\cookbook\templates\system.html:91
#: .\cookbook\templates\system.html:104 .\cookbook\templates\system.html:115
#: .\cookbook\views\views.py:168
msgid "Everything is fine!"
msgstr "¡Todo va bien!"
#: .\cookbook\templates\system.html:80
msgid "Secret Key"
msgstr "Clave Secreta"
#: .\cookbook\templates\system.html:84
msgid ""
"\n"
" You do not have a SECRET_KEY configured in your "
".env file. Django defaulted to the\n"
" standard key\n"
" provided with the installation which is publicly know and "
"insecure! Please set\n"
" SECRET_KEY int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" No has configurado la variable SECRET_KEY en el "
"fichero .env. Django está utilizando la\n"
" clave estándar\n"
" proporcionada con la instalación, esta clave es pública e "
"insegura. Por favor, configura\n"
" SECRET_KEY en el fichero de configuración ."
"env.\n"
" "
#: .\cookbook\templates\system.html:94
msgid "Debug Mode"
msgstr "Modo Depuración"
#: .\cookbook\templates\system.html:98
msgid ""
"\n"
" This application is still running in debug mode. This is most "
"likely not needed. Turn of debug mode by\n"
" setting\n"
" DEBUG=0 int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" Esta aplicación está funcionando en modo de depuración. Lo más "
"probable es que no sea necesario. Para desactivar el modo de depuración\n"
" configura\n"
" DEBUG=0 en el fichero de configuración .env"
"code>.\n"
" "
#: .\cookbook\templates\system.html:107
msgid "Allowed Hosts"
msgstr ""
#: .\cookbook\templates\system.html:111
msgid ""
"\n"
" Your allowed hosts are configured to allow every host. This "
"might be ok in some setups but should be avoided. Please see the docs about "
"this.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:118
msgid "Database"
msgstr "Base de Datos"
#: .\cookbook\templates\system.html:121
msgid "Info"
msgstr "Información"
#: .\cookbook\templates\system.html:131 .\cookbook\templates\system.html:148
#, fuzzy
#| msgid "Use fractions"
msgid "Migrations"
msgstr "Usar fracciones"
#: .\cookbook\templates\system.html:137
msgid ""
"\n"
" Migrations should never fail!\n"
" Failed migrations will likely cause major parts of the app to "
"not function correctly.\n"
" If a migration fails make sure you are on the latest version and "
"if so please post the migration log and the overview below in a GitHub "
"issue.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "False"
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "True"
msgstr ""
#: .\cookbook\templates\system.html:268
msgid "Hide"
msgstr ""
#: .\cookbook\templates\system.html:271
#, fuzzy
#| msgid "Show Links"
msgid "Show"
msgstr "Mostrar Enlaces"
#: .\cookbook\templates\test2.html:6
msgid "Export Recipes"
msgstr "Exportar recetas"
#: .\cookbook\templates\test2.html:14 .\cookbook\templates\test2.html:20
msgid "Export"
msgstr "Exportar"
#: .\cookbook\views\api.py:198 .\cookbook\views\api.py:326
#, fuzzy
#| msgid "Parameter filter_list incorrectly formatted"
msgid "Parameter updated_at incorrectly formatted"
msgstr "Parámetro filter_list formateado incorrectamente"
#: .\cookbook\views\api.py:351 .\cookbook\views\api.py:484
#, python-brace-format
msgid "No {self.basename} with id {pk} exists"
msgstr ""
#: .\cookbook\views\api.py:355
msgid "Cannot merge with the same object!"
msgstr "¡No se puede unir con el mismo objeto!"
#: .\cookbook\views\api.py:362
#, python-brace-format
msgid "No {self.basename} with id {target} exists"
msgstr ""
#: .\cookbook\views\api.py:367
#, fuzzy
#| msgid "Cannot merge with the same object!"
msgid "Cannot merge with child object!"
msgstr "¡No se puede unir con el mismo objeto!"
#: .\cookbook\views\api.py:405
#, python-brace-format
msgid "{source.name} was merged successfully with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:411
#, python-brace-format
msgid "An error occurred attempting to merge {source.name} with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:493
#, python-brace-format
msgid "{child.name} was moved successfully to the root."
msgstr ""
#: .\cookbook\views\api.py:496 .\cookbook\views\api.py:514
msgid "An error occurred attempting to move "
msgstr ""
#: .\cookbook\views\api.py:499
msgid "Cannot move an object to itself!"
msgstr ""
#: .\cookbook\views\api.py:505
#, python-brace-format
msgid "No {self.basename} with id {parent} exists"
msgstr ""
#: .\cookbook\views\api.py:511
#, python-brace-format
msgid "{child.name} was moved successfully to parent {parent.name}"
msgstr ""
#: .\cookbook\views\api.py:992
#, python-brace-format
msgid "{obj.name} was removed from the shopping list."
msgstr ""
#: .\cookbook\views\api.py:997 .\cookbook\views\api.py:1627
#, python-brace-format
msgid "{obj.name} was added to the shopping list."
msgstr ""
#: .\cookbook\views\api.py:1239
msgid "Filter meal plans from date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1241
msgid "Filter meal plans to date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1244
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1404
msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1406
msgid "Query string matched (fuzzy) against object name."
msgstr ""
#: .\cookbook\views\api.py:1442
msgid ""
"Query string matched (fuzzy) against recipe name. In the future also "
"fulltext search."
msgstr ""
#: .\cookbook\views\api.py:1444
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr ""
#: .\cookbook\views\api.py:1445
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr ""
#: .\cookbook\views\api.py:1446
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1447
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1448
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1450
msgid "ID of food a recipe should have. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1451
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr ""
#: .\cookbook\views\api.py:1452
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1453
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr ""
#: .\cookbook\views\api.py:1454
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1456
msgid "ID of book a recipe should be in. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1457
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr ""
#: .\cookbook\views\api.py:1458
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1459
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr ""
#: .\cookbook\views\api.py:1460
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1462
msgid "ID of unit a recipe should have."
msgstr ""
#: .\cookbook\views\api.py:1464
msgid "Exact rating of recipe"
msgstr ""
#: .\cookbook\views\api.py:1465
msgid "Rating a recipe should have or greater."
msgstr ""
#: .\cookbook\views\api.py:1466
msgid "Rating a recipe should have or smaller."
msgstr ""
#: .\cookbook\views\api.py:1468
msgid "Filter recipes cooked X times."
msgstr ""
#: .\cookbook\views\api.py:1469
msgid "Filter recipes cooked X times or more."
msgstr ""
#: .\cookbook\views\api.py:1470
msgid "Filter recipes cooked X times or less."
msgstr ""
#: .\cookbook\views\api.py:1472
msgid "Filter recipes created on the given date."
msgstr ""
#: .\cookbook\views\api.py:1473
msgid "Filter recipes created on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1474
msgid "Filter recipes created on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1476 .\cookbook\views\api.py:1477
#: .\cookbook\views\api.py:1478
msgid "Filter recipes updated on the given date."
msgstr ""
#: .\cookbook\views\api.py:1480
msgid "Filter recipes last cooked on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1481
msgid "Filter recipes last cooked on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1483 .\cookbook\views\api.py:1484
msgid "Filter recipes lasts viewed on the given date."
msgstr ""
#: .\cookbook\views\api.py:1486
msgid "Filter recipes for ones created by the given user ID"
msgstr ""
#: .\cookbook\views\api.py:1487
msgid "If only internal recipes should be returned. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1488
msgid "Returns the results in randomized order. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1490
msgid ""
"Determines the order of the results. Options are: score,-score,name,-name,"
"lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-"
"created_at,lastviewed,-lastviewed"
msgstr ""
#: .\cookbook\views\api.py:1492
msgid "Returns new results first in search results. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1493
msgid ""
"Returns the given number of recently viewed recipes before search results "
"(if given)"
msgstr ""
#: .\cookbook\views\api.py:1494
msgid "ID of a custom filter. Returns all recipes matched by that filter."
msgstr ""
#: .\cookbook\views\api.py:1495
msgid "Filter recipes that can be made with OnHand food. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1773
msgid ""
"Return the PropertyTypes matching the property category. Repeat for "
"multiple."
msgstr ""
#: .\cookbook\views\api.py:1804 .\cookbook\views\api.py:1860
msgid "Returns only entries associated with the given mealplan id"
msgstr ""
#: .\cookbook\views\api.py:1858
msgid ""
"Returns only elements updated after the given timestamp in ISO 8601 format."
msgstr ""
#: .\cookbook\views\api.py:2031
msgid ""
"Return the Automations matching the automation type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2048
msgid ""
"Text field to store data that gets carried over to the UserSpace created "
"from the InviteLink"
msgstr ""
#: .\cookbook\views\api.py:2049
msgid "Only return InviteLinks that have not been used yet."
msgstr ""
#: .\cookbook\views\api.py:2076
msgid "Return the CustomFilters matching the model type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2176
msgid "Nothing to do."
msgstr ""
#: .\cookbook\views\api.py:2222
msgid "Invalid Url"
msgstr ""
#: .\cookbook\views\api.py:2228
msgid "Connection Refused."
msgstr ""
#: .\cookbook\views\api.py:2232
msgid "Bad URL Schema."
msgstr ""
#: .\cookbook\views\api.py:2257
#, fuzzy
#| msgid "The requested page could not be found."
msgid "No usable data could be found."
msgstr "La página solicitada no pudo ser encontrada."
#: .\cookbook\views\api.py:2286 .\cookbook\views\api.py:2434
msgid "You must select an AI provider to perform your request."
msgstr ""
#: .\cookbook\views\api.py:2293 .\cookbook\views\api.py:2441
msgid ""
"You don't have any credits remaining to use AI or AI features are not "
"enabled for your space."
msgstr ""
#: .\cookbook\views\api.py:2499 .\cookbook\views\api.py:2667
msgid "File is above space limit"
msgstr ""
#: .\cookbook\views\api.py:2522 .\cookbook\views\api.py:2684
msgid "Importing is not implemented for this provider"
msgstr "La importación no está implementada para este proveedor"
#: .\cookbook\views\api.py:2548
msgid ""
"The PDF Exporter is not enabled on this instance as it is still in an "
"experimental state."
msgstr ""
#: .\cookbook\views\api.py:2842
#, fuzzy
#| msgid "This feature is not available in the demo version!"
msgid "This feature is not yet available in the hosted version of tandoor!"
msgstr "¡Esta funcionalidad no está disponible en la versión demo!"
#: .\cookbook\views\api.py:2863
msgid "Sync successful!"
msgstr "¡Sincronización exitosa!"
#: .\cookbook\views\api.py:2866
msgid "Error synchronizing with Storage"
msgstr "Error de sincronización con el almacenamiento"
#: .\cookbook\views\views.py:89
msgid "This feature is not available in the demo version!"
msgstr "¡Esta funcionalidad no está disponible en la versión demo!"
#: .\cookbook\views\views.py:110
msgid ""
"You have successfully created your own recipe space. Start by adding some "
"recipes or invite other people to join you."
msgstr ""
#: .\cookbook\views\views.py:171
#, python-format
msgid "PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!"
msgstr ""
#: .\cookbook\views\views.py:174
#, python-format
msgid "You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended"
msgstr ""
#: .\cookbook\views\views.py:178
msgid "Unable to determine PostgreSQL version."
msgstr ""
#: .\cookbook\views\views.py:182
#, fuzzy
#| msgid ""
#| "\n"
#| " This application is not running with a Postgres database "
#| "backend. This is ok but not recommended as some\n"
#| " features only work with postgres databases.\n"
#| " "
msgid ""
"This application is not running with a Postgres database backend. This is ok "
"but not recommended as some features only work with postgres databases."
msgstr ""
"\n"
" Esta aplicación no se ejecuta con un backend de base de datos "
"Postgres. Esto es válido pero no es recomendado ya que algunas\n"
" características sólo funcionan con bases de datos Postgres.\n"
" "
#: .\cookbook\views\views.py:296
#, fuzzy
#| msgid ""
#| "The setup page can only be used to create the first user! If you have "
#| "forgotten your superuser credentials please consult the django "
#| "documentation on how to reset passwords."
msgid ""
"The setup page can only be used to create the first "
"user! If you have forgotten your superuser credentials "
"please consult the django documentation on how to reset passwords."
msgstr ""
"La página de configuración sólo puede ser utilizada para crear el primer "
"usuario. Si has olvidado tus credenciales de superusuario, por favor "
"consulta la documentación de django sobre cómo restablecer las contraseñas."
#: .\cookbook\views\views.py:304
msgid "Passwords dont match!"
msgstr "¡Las contraseñas no coinciden!"
#: .\cookbook\views\views.py:312
msgid "User has been created, please login!"
msgstr "El usuario ha sido creado, ¡inicie sesión!"
#: .\cookbook\views\views.py:352
msgid ""
"Reporting share links is not enabled for this instance. Please notify the "
"page administrator to report problems."
msgstr ""
#: .\cookbook\views\views.py:357
msgid ""
"Recipe sharing link has been disabled! For additional information please "
"contact the page administrator."
msgstr ""
#: .\cookbook\views\views.py:383
msgid "Manage recipes, shopping list, meal plans and more."
msgstr ""
#: .\cookbook\views\views.py:397 .\cookbook\views\views.py:398
msgid "Plan"
msgstr "Menú"
#: .\cookbook\views\views.py:399
msgid "View your meal Plan"
msgstr ""
#: .\cookbook\views\views.py:418
#, fuzzy
#| msgid "Shopping List"
msgid "View your shopping lists"
msgstr "Lista de la Compra"
#~ msgid ""
#~ "Both fields are optional. If none are given the username will be "
#~ "displayed instead"
#~ msgstr ""
#~ "Ambos campos son opcionales. Si no se proporciona ninguno, se mostrará el "
#~ "nombre de usuario en su lugar"
#~ msgid "Name"
#~ msgstr "Nombre"
#~ msgid "Keywords"
#~ msgstr "Palabras clave"
#~ msgid "Preparation time in minutes"
#~ msgstr "Tiempo de preparación en minutos"
#~ msgid "Waiting time (cooking/baking) in minutes"
#~ msgstr "Tiempo de espera (cocinar/hornear) en minutos"
#~ msgid "Path"
#~ msgstr "Ruta"
#~ msgid "Storage UID"
#~ msgstr "UID de almacenamiento"
#~ msgid "Add your comment: "
#~ msgstr "Añade tu comentario: "
#~ msgid "Leave empty for dropbox and enter app password for nextcloud."
#~ msgstr ""
#~ "Déjalo vacío para Dropbox o ingresa la contraseña de la aplicación para "
#~ "Nextcloud."
#~ msgid "Leave empty for nextcloud and enter api token for dropbox."
#~ msgstr "Déjalo vacío para Nextcloud o ingresa el token API para Dropbox."
#~ msgid ""
#~ "Leave empty for dropbox and enter only base url for nextcloud (/"
#~ "remote.php/webdav/ is added automatically)"
#~ msgstr ""
#~ "Déjalo vacío para Dropbox o ingresa solo la URL base para Nextcloud "
#~ "(/remote.php/webdav/ es añadido automáticamente)"
#~ msgid ""
#~ "Long Lived Access Token for your HomeAssistant instance"
#~ msgstr ""
#~ "Token de acceso de larga duración para tu instancia de "
#~ "HomeAssistant"
#~ msgid "Something like http://homeassistant.local:8123/api"
#~ msgstr "Algo como http://homeassistant.local:8123/api"
#~ msgid "http://homeassistant.local:8123/api for example"
#~ msgstr "Por ejemplo http://homeassistant.local:8123/api"
#~ msgid "Storage"
#~ msgstr "Almacenamiento"
#~ msgid "Active"
#~ msgstr "Activo"
#~ msgid "Search String"
#~ msgstr "Cadena de búsqueda"
#~ msgid "File ID"
#~ msgstr "ID de Fichero"
#~ msgid ""
#~ "Determines how fuzzy a search is if it uses trigram similarity matching "
#~ "(e.g. low values mean more typos are ignored)."
#~ msgstr ""
#~ "Determina como de 'perezosa' es la búsqueda si utiliza la coincidencia de "
#~ "similitud de trigramas(Ej. Valores más pequeños indican que más fallos se "
#~ "van a ignorar)."
#~ msgid ""
#~ "Select type method of search. Click here "
#~ "for full description of choices."
#~ msgstr ""
#~ "Selecciona el tipo de búsqueda. Haz click aquí para una descripción completa de las opciones."
#~ msgid ""
#~ "Use fuzzy matching on units, keywords and ingredients when editing and "
#~ "importing recipes."
#~ msgstr ""
#~ "Utilizar comparación difusa en unidades, palabras clave e ingredientes al "
#~ "editar e importar recetas."
#~ msgid ""
#~ "Fields to search ignoring accents. Selecting this option can improve or "
#~ "degrade search quality depending on language"
#~ msgstr ""
#~ "Campos de búsqueda ignorando acentos. La selección de esta opción puede "
#~ "mejorar o degradar la calidad de la búsqueda dependiendo del idioma"
#~ msgid ""
#~ "Fields to search for partial matches. (e.g. searching for 'Pie' will "
#~ "return 'pie' and 'piece' and 'soapie')"
#~ msgstr ""
#~ "Campos de búsqueda para coincidencias parciales. (por ejemplo, buscar "
#~ "'Pie' devolverá 'pie' y 'piece' y 'soapie')"
#~ msgid ""
#~ "Fields to search for beginning of word matches. (e.g. searching for 'sa' "
#~ "will return 'salad' and 'sandwich')"
#~ msgstr ""
#~ "Campos de búsqueda para coincidencias al principio de la palabra. (por "
#~ "ejemplo, buscar 'sa' devolverá 'ensalada' y 'sándwich')"
#~ msgid ""
#~ "Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find "
#~ "'recipe'.) Note: this option will conflict with 'web' and 'raw' methods "
#~ "of search."
#~ msgstr ""
#~ "Campos para búsqueda \"difusa\". (por ejemplo, buscar 'recpie' encontrará "
#~ "'receta'). Nota: esta opción entrará en conflicto con los métodos de "
#~ "búsqueda 'web' y 'raw'."
#~ msgid ""
#~ "Fields to full text search. Note: 'web', 'phrase', and 'raw' search "
#~ "methods only function with fulltext fields."
#~ msgstr ""
#~ "Campos para búsqueda de texto completo. Nota: los métodos de búsqueda "
#~ "'web', 'phrase' y 'raw' solo funcionan con campos de texto completo."
#~ msgid "Search Method"
#~ msgstr "Método de Búsqueda"
#~ msgid "Fuzzy Lookups"
#~ msgstr "Búsquedas difusas"
#~ msgid "Ignore Accent"
#~ msgstr "Ignorar Acento"
#~ msgid "Partial Match"
#~ msgstr "Coincidencia Parcial"
#~ msgid "Starts With"
#~ msgstr "Comienza Con"
#~ msgid "Fuzzy Search"
#~ msgstr "Búsqueda Difusa"
#~ msgid "Full Text"
#~ msgstr "Texto Completo"
#~ msgid " is part of a recipe step and cannot be deleted"
#~ msgstr " es parte del paso de una receta y no puede ser eliminado"
#~ msgid "Delete"
#~ msgstr "Eliminar"
#~ msgid "Settings"
#~ msgstr "Opciones"
#, fuzzy
#~| msgid "Password Reset"
#~ msgid "Password"
#~ msgstr "Restablecer contraseña"
#, fuzzy
#~| msgid "Food"
#~ msgid "Foods"
#~ msgstr "Comida"
#~ msgid "Units"
#~ msgstr "Unidades"
#~ msgid "Supermarket"
#~ msgstr "Supermercado"
#, fuzzy
#~| msgid "Supermarket"
#~ msgid "Supermarket Category"
#~ msgstr "Supermercado"
#, fuzzy
#~| msgid "Information"
#~ msgid "Automations"
#~ msgstr "Información"
#, fuzzy
#~| msgid "File ID"
#~ msgid "Files"
#~ msgstr "ID de Fichero"
#~ msgid "Batch Edit"
#~ msgstr "Edición Masiva"
#~ msgid "History"
#~ msgstr "Historial"
#, fuzzy
#~| msgid "Ingredients"
#~ msgid "Ingredient Editor"
#~ msgstr "Ingredientes"
#, fuzzy
#~| msgid "Account Connections"
#~ msgid "Unit Conversions"
#~ msgstr "Conexiones de la cuenta"
#~ msgid "Create"
#~ msgstr "Crear"
#~ msgid "External Recipes"
#~ msgstr "Recetas Externas"
#, fuzzy
#~| msgid "Settings"
#~ msgid "Space Settings"
#~ msgstr "Opciones"
#, fuzzy
#~| msgid "External Recipes"
#~ msgid "External Connectors"
#~ msgstr "Recetas Externas"
#~ msgid "Admin"
#~ msgstr "Administrador"
#~ msgid "Markdown Guide"
#~ msgstr "Guia Markdown"
#~ msgid "GitHub"
#~ msgstr "GitHub"
#~ msgid "API Browser"
#~ msgstr "Explorador de API"
#~ msgid "Batch edit Category"
#~ msgstr "Edición masiva de Categorías"
#~ msgid "Batch edit Recipes"
#~ msgstr "Edición masiva de Recetas"
#~ msgid "Add the specified keywords to all recipes containing a word"
#~ msgstr ""
#~ "Agregue las palabras clave especificadas a todas las recetas que "
#~ "contengan una palabra"
#~ msgid "Sync"
#~ msgstr "Sincronizar"
#~ msgid "Manage watched Folders"
#~ msgstr "Administrar carpetas observadas"
#~ msgid ""
#~ "On this Page you can manage all storage folder locations that should be "
#~ "monitored and synced."
#~ msgstr ""
#~ "En esta página puede administrar todas las ubicaciones de las carpetas de "
#~ "almacenamiento que deben monitorearse y sincronizarse."
#~ msgid "The path must be in the following format"
#~ msgstr "La ruta debe tener el siguiente formato"
#~ msgid "Save"
#~ msgstr "Guardar"
#~ msgid "Sync Now!"
#~ msgstr "¡Sincronizar ahora!"
#, fuzzy
#~| msgid "Shopping Recipes"
#~ msgid "Show Recipes"
#~ msgstr "Recetas en el carro de la compra"
#, fuzzy
#~| msgid "Show Links"
#~ msgid "Show Log"
#~ msgstr "Mostrar Enlaces"
#~ msgid "Importing Recipes"
#~ msgstr "Importando Recetas"
#~ msgid ""
#~ "This can take a few minutes, depending on the number of recipes in sync, "
#~ "please wait."
#~ msgstr ""
#~ "Esto puede tardar unos minutos, dependiendo de la cantidad de recetas "
#~ "sincronizadas, espere."
#~ msgid "Recipe Books"
#~ msgstr "Libros de recetas"
#~ msgid "Import new Recipe"
#~ msgstr "Importar nueva receta"
#~ msgid "Edit Recipe"
#~ msgstr "Editar receta"
#, python-format
#~ msgid "Are you sure you want to delete the %(title)s: %(object)s "
#~ msgstr ""
#~ "¿Estás seguro de que quieres borrar el %(title)s: %(object)s? "
#~ msgid "Edit"
#~ msgstr "Editar"
#~ msgid "View"
#~ msgstr "Ver"
#~ msgid "Delete original file"
#~ msgstr "Eliminar archivo original"
#~ msgid "List"
#~ msgstr "Lista"
#~ msgid "Filter"
#~ msgstr "Filtro"
#~ msgid "Import all"
#~ msgstr "Importar todo"
#~ msgid "New"
#~ msgstr "Nuevo"
#~ msgid "previous"
#~ msgstr "anterior"
#~ msgid "next"
#~ msgstr "siguiente"
#~ msgid "View Log"
#~ msgstr "Ver registro"
#~ msgid "Cook Log"
#~ msgstr "Registro de cocina"
#~ msgid "Import"
#~ msgstr "Importar"
#~ msgid "Security Warning"
#~ msgstr "Advertencia de seguridad"
#~ msgid ""
#~ "\n"
#~ " The Password and Token field are stored as plain text"
#~ "b> inside the database.\n"
#~ " This is necessary because they are needed to make API requests, "
#~ "but it also increases the risk of\n"
#~ " someone stealing it.
\n"
#~ " To limit the possible damage tokens or accounts with limited "
#~ "access can be used.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Los camposContraseña y Tokenson almacenados en texto "
#~ "plano dentro de la base de datos.\n"
#~ " Esto es necesario porque son requeridos para hacer peticiones de "
#~ "la API, pero esto incrementa el riesgo de\n"
#~ " que alguien lo robe.
\n"
#~ " Para limitar los posibles daños se pueden utilizar tokens o "
#~ "cuentas con acceso limitado.\n"
#~ " "
#~ msgid "You are currently offline!"
#~ msgstr "¡Estás desconectado!"
#~ msgid ""
#~ "The recipes listed below are available for offline viewing because you "
#~ "have recently viewed them. Keep in mind that data might be outdated."
#~ msgstr ""
#~ "Las recetas listadas a continuación están disponibles para ver sin "
#~ "conexión porque las has visto recientemente. Ten en cuenta que los datos "
#~ "pueden estar desactualizados."
#, fuzzy
#~| msgid "Ingredients"
#~ msgid "Property Editor"
#~ msgstr "Ingredientes"
#~ msgid "Comments"
#~ msgstr "Comentarios"
#~ msgid "by"
#~ msgstr "por"
#~ msgid "Comment"
#~ msgstr "Comentario"
#, fuzzy
#~| msgid "Social Login"
#~ msgid "Social"
#~ msgstr "Inicio de sesión social"
#, fuzzy
#~| msgid "Description"
#~ msgid "Manage Subscription"
#~ msgstr "Descripción"
#, fuzzy
#~| msgid "Create User"
#~ msgid "Leave Space"
#~ msgstr "Crear Usuario"
#~ msgid "URL Import"
#~ msgstr "Importar URL"
#, python-format
#~ msgid "Batch edit done. %(count)d recipe was updated."
#~ msgid_plural "Batch edit done. %(count)d Recipes where updated."
#~ msgstr[0] "Edición por lotes realizada. %(count)d Receta fue actualizada."
#~ msgstr[1] "Edición masiva realizada. %(count)d Recetas fueron actualizadas."
#~ msgid "Monitor"
#~ msgstr "Monitor"
#~ msgid "Storage Backend"
#~ msgstr "Backend de Almacenamiento"
#~ msgid ""
#~ "Could not delete this storage backend as it is used in at least one "
#~ "monitor."
#~ msgstr ""
#~ "No se pudo borrar este backend de almacenamiento ya que se utiliza en al "
#~ "menos un monitor."
#, fuzzy
#~| msgid "Storage Backend"
#~ msgid "Connectors Config Backend"
#~ msgstr "Backend de Almacenamiento"
#~ msgid "Invite Link"
#~ msgstr "Enlace de invitación"
#~ msgid "You cannot edit this storage!"
#~ msgstr "¡No puede editar este almacenamiento!"
#~ msgid "Storage saved!"
#~ msgstr "¡Almacenamiento guardado!"
#~ msgid "There was an error updating this storage backend!"
#~ msgstr "¡Hubo un error al actualizar este backend de almacenamiento!"
#, fuzzy
#~| msgid "Changes saved!"
#~ msgid "Config saved!"
#~ msgstr "¡Cambios guardados!"
#~ msgid "Changes saved!"
#~ msgstr "¡Cambios guardados!"
#~ msgid "Error saving changes!"
#~ msgstr "¡Error al guardar los cambios!"
#~ msgid "Import Log"
#~ msgstr "Importar registro"
#~ msgid "Discovery"
#~ msgstr "Descubrimiento"
#~ msgid "Shopping List"
#~ msgstr "Lista de la Compra"
#, fuzzy
#~| msgid "Storage Backend"
#~ msgid "Connector Config Backend"
#~ msgstr "Backend de Almacenamiento"
#~ msgid "Invite Links"
#~ msgstr "Enlaces de Invitación"
#, fuzzy
#~| msgid "Supermarket"
#~ msgid "Supermarkets"
#~ msgstr "Supermercado"
#, fuzzy
#~| msgid "Shopping Recipes"
#~ msgid "Shopping Categories"
#~ msgstr "Recetas en el carro de la compra"
#, fuzzy
#~| msgid "Filter"
#~ msgid "Custom Filters"
#~ msgstr "Filtro"
#~ msgid "Steps"
#~ msgstr "Pasos"
#, fuzzy
#~| msgid "This feature is not available in the demo version!"
#~ msgid "This feature is not enabled by the server admin!"
#~ msgstr "¡Esta funcionalidad no está disponible en la versión demo!"
#~ msgid "Imported new recipe!"
#~ msgstr "¡Nueva receta importada!"
#~ msgid "There was an error importing this recipe!"
#~ msgstr "¡Hubo un error al importar esta receta!"
#~ msgid "You do not have the required permissions to perform this action!"
#~ msgstr "¡No tienes los permisos necesarios para realizar esta acción!"
#~ msgid "Comment saved!"
#~ msgstr "¡Comentario guardado!"
#~ msgid "Malformed Invite Link supplied!"
#~ msgstr "¡Se proporcionó un enlace de invitación con formato incorrecto!"
#~ msgid "Invite Link not valid or already used!"
#~ msgstr "¡El enlace de invitación no es válido o ya se ha utilizado!"
#~ msgid "Default unit"
#~ msgstr "Unidad por defecto"
#~ msgid "Use KJ"
#~ msgstr "Usar KJ"
#~ msgid "Theme"
#~ msgstr "Tema"
#~ msgid "Navbar color"
#~ msgstr "Color de la barra de navegación"
#~ msgid "Sticky navbar"
#~ msgstr "Barra de navegación pegajosa"
#~ msgid "Default page"
#~ msgstr "Página por defecto"
#~ msgid "Plan sharing"
#~ msgstr "Compartir régimen"
#~ msgid "Ingredient decimal places"
#~ msgstr "Número de decimales del ingrediente"
#~ msgid "Shopping list auto sync period"
#~ msgstr "Período de sincronización automática de la lista de compras"
#~ msgid "Left-handed mode"
#~ msgstr "Modo para zurdos"
#~ msgid ""
#~ "Color of the top navigation bar. Not all colors work with all themes, "
#~ "just try them out!"
#~ msgstr ""
#~ "Color de la barra de navegación superior. No todos los colores funcionan "
#~ "con todos los temas, ¡pruébalos!"
#~ msgid ""
#~ "Default Unit to be used when inserting a new ingredient into a recipe."
#~ msgstr ""
#~ "Unidad predeterminada que se utilizará al insertar un nuevo ingrediente "
#~ "en una receta."
#~ msgid ""
#~ "Enables support for fractions in ingredient amounts (e.g. convert "
#~ "decimals to fractions automatically)"
#~ msgstr ""
#~ "Permite utilizar fracciones en cantidades de ingredientes (e.g. convierte "
#~ "los decimales en fracciones automáticamente)"
#~ msgid "Display nutritional energy amounts in joules instead of calories"
#~ msgstr "Mostrar los valores nutricionales en Julios en vez de calorías"
#~ msgid ""
#~ "Users with whom newly created meal plans should be shared by default."
#~ msgstr ""
#~ "Usuarios con los que las entradas recién creadas del plan de comida deben "
#~ "compartirse de forma predeterminada."
#~ msgid "Users with whom to share shopping lists."
#~ msgstr "Usuarios con quienes compartir listas de compra."
#~ msgid "Number of decimals to round ingredients."
#~ msgstr "Número de decimales para redondear los ingredientes."
#~ msgid ""
#~ "If you want to be able to create and see comments underneath recipes."
#~ msgstr "Si desea poder crear y ver comentarios debajo de las recetas."
#~ msgid ""
#~ "Setting to 0 will disable auto sync. When viewing a shopping list the "
#~ "list is updated every set seconds to sync changes someone else might have "
#~ "made. Useful when shopping with multiple people but might use a little "
#~ "bit of mobile data. If lower than instance limit it is reset when saving."
#~ msgstr ""
#~ "Fijar en 0 deshabilita la sincronización automática. Al ver una lista de "
#~ "la compra, la lista se actualiza cada periodo establecido de segundos "
#~ "para sincronizar los cambios que otra persona pueda haber hecho. Es útil "
#~ "cuando se compra con varias personas, pero puede consumir datos móviles. "
#~ "Si el valor establecido es inferior al límite de la instancia, este se "
#~ "restablecerá al guardar."
#~ msgid "Makes the navbar stick to the top of the page."
#~ msgstr "Hace la barra de navegación fija en la parte superior de la página."
#~ msgid "Automatically add meal plan ingredients to shopping list."
#~ msgstr ""
#~ "Añadir de manera automática los ingredientes del plan a la lista de la "
#~ "compra."
#~ msgid "Exclude ingredients that are on hand."
#~ msgstr "Excluir ingredientes que están disponibles."
#~ msgid "Will optimize the UI for use with your left hand."
#~ msgstr "Se optimizará la UI para su uso con la mano izquierda."
#~ msgid "You must provide at least a recipe or a title."
#~ msgstr "Debe proporcionar al menos una receta o un título."
#~ msgid "You can list default users to share recipes with in the settings."
#~ msgstr ""
#~ "Puede enumerar los usuarios predeterminados con los que compartir recetas "
#~ "en la configuración."
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "Puede utilizar Markdown para formatear este campo. Vea la documentación aqui"
#~ msgid ""
#~ "Users will see all items you add to your shopping list. They must add "
#~ "you to see items on their list."
#~ msgstr ""
#~ "Los usuarios verán todos los elementos que agregues a tu lista de "
#~ "compras. Deben agregarte para ver los elementos en su lista."
#~ msgid ""
#~ "When adding a meal plan to the shopping list (manually or automatically), "
#~ "include all related recipes."
#~ msgstr ""
#~ "Al agregar un plan de comidas a la lista de compras (manualmente o "
#~ "automáticamente), incluir todas las recetas relacionadas."
#~ msgid ""
#~ "When adding a meal plan to the shopping list (manually or automatically), "
#~ "exclude ingredients that are on hand."
#~ msgstr ""
#~ "Al agregar un plan de comidas a la lista de compras (manualmente o "
#~ "automáticamente), excluir los ingredientes que están disponibles."
#~ msgid "Default number of hours to delay a shopping list entry."
#~ msgstr ""
#~ "Número predeterminado de horas para retrasar una entrada en la lista de "
#~ "compras."
#~ msgid "Filter shopping list to only include supermarket categories."
#~ msgstr ""
#~ "Filtrar la lista de compras para incluir solo categorías de supermercados."
#~ msgid "Days of recent shopping list entries to display."
#~ msgstr "Días de entradas recientes en la lista de compras a mostrar."
#~ msgid "Mark food 'On Hand' when checked off shopping list."
#~ msgstr ""
#~ "Marcar los alimentos como 'Disponible' cuando se marca en la lista de "
#~ "compras."
#~ msgid "Delimiter to use for CSV exports."
#~ msgstr "Delimitador a utilizar para exportaciones CSV."
#~ msgid "Prefix to add when copying list to the clipboard."
#~ msgstr "Prefijo a agregar al copiar la lista al portapapeles."
#~ msgid "Share Shopping List"
#~ msgstr "Compartir Lista de la Compra"
#~ msgid "Autosync"
#~ msgstr "Autosincronización"
#~ msgid "Auto Add Meal Plan"
#~ msgstr "Agregar Plan de Comidas automáticamente"
#~ msgid "Exclude On Hand"
#~ msgstr "Excluir Disponible"
#~ msgid "Include Related"
#~ msgstr "Incluir Relacionados"
#~ msgid "Default Delay Hours"
#~ msgstr "Horas de Retraso Predeterminadas"
#~ msgid "Filter to Supermarket"
#~ msgstr "Filtrar según Supermercado"
#~ msgid "Recent Days"
#~ msgstr "Días Recientes"
#~ msgid "CSV Delimiter"
#~ msgstr "Delimitador CSV"
#~ msgid "List Prefix"
#~ msgstr "Prefijo de la lista"
#~ msgid "Auto On Hand"
#~ msgstr "Auto en existencia"
#~ msgid "Reset Food Inheritance"
#~ msgstr "Restablecer la herencia de alimentos"
#~ msgid "Reset all food to inherit the fields configured."
#~ msgstr "Reiniciar todos los alimentos para heredar los campos configurados."
#~ msgid "Fields on food that should be inherited by default."
#~ msgstr "Campos en los alimentos que deben ser heredados por defecto."
#~ msgid "Show recipe counts on search filters"
#~ msgstr "Mostrar cantidad de recetas en los filtros de búsquedas"
#~ msgid "Use the plural form for units and food inside this space."
#~ msgstr ""
#~ "Utilice la forma plural para las unidades y alimentos dentro de este "
#~ "espacio."
#~ msgid "Recipe Book"
#~ msgstr "Libro de recetas"
#~ msgid "Bookmarks"
#~ msgstr "Marcadores"
#~ msgid "Ingredients"
#~ msgstr "Ingredientes"
#~ msgid "Show recent recipes"
#~ msgstr "Mostrar recetas recientes"
#~ msgid "Search style"
#~ msgstr "Estilo de búsqueda"
#~ msgid "Show recently viewed recipes on search page."
#~ msgstr "Muestra recetas vistas recientemente en la página de búsqueda."
#~ msgid "Small"
#~ msgstr "Pequeño"
#~ msgid "Large"
#~ msgstr "Grande"
#~ msgid "Edit Ingredients"
#~ msgstr "Editar ingredientes"
#~ msgid ""
#~ "\n"
#~ " The following form can be used if, accidentally, two (or more) "
#~ "units or ingredients where created that should be\n"
#~ " the same.\n"
#~ " It merges two units or ingredients and updates all recipes using "
#~ "them.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " La siguiente forma puede utilizarse si, accidentalmente, se crean "
#~ "dos (o más) unidades o ingredientes que deberían ser\n"
#~ " iguales.\n"
#~ " Fusiona dos unidades o ingredientes y actualiza todas las recetas "
#~ "que los usan.\n"
#~ " "
#~ msgid "Are you sure that you want to merge these two units?"
#~ msgstr "¿Estás seguro de que quieres combinar estas dos unidades?"
#~ msgid "Are you sure that you want to merge these two ingredients?"
#~ msgstr "¿Estás seguro de que quieres combinar estos dos ingredientes?"
#~ msgid "Import Recipes"
#~ msgstr "Importar recetas"
#~ msgid "Close"
#~ msgstr "Cerrar"
#~ msgid "Open Recipe"
#~ msgstr "Abrir Receta"
#~ msgid "Meal Plan View"
#~ msgstr "Vista de menú"
#~ msgid "Created by"
#~ msgstr "Creado por"
#~ msgid "Shared with"
#~ msgstr "Compartido con"
#~ msgid "Never cooked before."
#~ msgstr "Nunca antes cocinado."
#~ msgid "Other meals on this day"
#~ msgstr "Otras comidas en este día"
#~ msgid "Recipe Image"
#~ msgstr "Imagen de la receta"
#~ msgid "Preparation time ca."
#~ msgstr "Tiempo de preparación ca."
#~ msgid "Waiting time ca."
#~ msgstr "Tiempo de espera ca."
#~ msgid "External"
#~ msgstr "Externo"
#~ msgid "Log Cooking"
#~ msgstr "Registrar receta cocinada"
#~ msgid "Account"
#~ msgstr "Cuenta"
#, fuzzy
#~| msgid "Settings"
#~ msgid "API-Settings"
#~ msgstr "Opciones"
#, fuzzy
#~| msgid "Search String"
#~ msgid "Search-Settings"
#~ msgstr "Cadena de búsqueda"
#, fuzzy
#~| msgid "Search String"
#~ msgid "Shopping-Settings"
#~ msgstr "Cadena de búsqueda"
#, fuzzy
#~| msgid "Settings"
#~ msgid "Name Settings"
#~ msgstr "Opciones"
#, fuzzy
#~| msgid "Account Connections"
#~ msgid "Account Settings"
#~ msgstr "Conexiones de la cuenta"
#, fuzzy
#~| msgid "Settings"
#~ msgid "Emails"
#~ msgstr "Opciones"
#~ msgid "Language"
#~ msgstr "Idioma"
#~ msgid "Style"
#~ msgstr "Estilo"
#~ msgid "API Token"
#~ msgstr "Token API"
#~ msgid ""
#~ "You can use both basic authentication and token based authentication to "
#~ "access the REST API."
#~ msgstr ""
#~ "Puedes utilizar tanto la autenticación básica como la autenticación "
#~ "basada en tokens para acceder a la API REST."
#~ msgid ""
#~ "Use the token as an Authorization header prefixed by the word token as "
#~ "shown in the following examples:"
#~ msgstr ""
#~ "Utilice el token como cabecera de autorización usando como prefijo la "
#~ "palabra token, tal y como se muestra en los siguientes ejemplos:"
#~ msgid "or"
#~ msgstr "o"
#, fuzzy
#~| msgid "Shopping List"
#~ msgid "Shopping Settings"
#~ msgstr "Lista de la Compra"
#~ msgid "Stats"
#~ msgstr "Estadísticas"
#~ msgid "Statistics"
#~ msgstr "Estadísticas"
#~ msgid "Number of objects"
#~ msgstr "Número de objetos"
#~ msgid "Recipe Imports"
#~ msgstr "Recetas importadas"
#~ msgid "Objects stats"
#~ msgstr "Estadísticas de objetos"
#~ msgid "Recipes without Keywords"
#~ msgstr "Recetas sin palabras clave"
#~ msgid "Internal Recipes"
#~ msgstr "Recetas Internas"
#~ msgid "Show Links"
#~ msgstr "Mostrar Enlaces"
#, fuzzy
#~| msgid "Invite Links"
#~ msgid "Invite User"
#~ msgstr "Enlaces de Invitación"
#, fuzzy
#~| msgid "Admin"
#~ msgid "admin"
#~ msgstr "Administrador"
#~ msgid "user"
#~ msgstr "usuario"
#~ msgid "guest"
#~ msgstr "invitado"
#~ msgid "remove"
#~ msgstr "eliminar"
#~ msgid "Update"
#~ msgstr "Actualizar"
#, fuzzy
#~| msgid "You cannot edit this storage!"
#~ msgid "You cannot edit yourself."
#~ msgstr "¡No puede editar este almacenamiento!"
#, fuzzy
#~| msgid "There are no recipes in this book yet."
#~ msgid "There are no members in your space yet!"
#~ msgstr "Todavía no hay recetas en este libro."
#, fuzzy
#~| msgid "You are not logged in and therefore cannot view this page!"
#~ msgid ""
#~ "You are already member of a space and therefore cannot join this one."
#~ msgstr "¡No ha iniciado sesión y por lo tanto no puede ver esta página!"
#, fuzzy
#~| msgid "Open Shopping List"
#~ msgid "Try the new shopping list"
#~ msgstr "Abrir Lista de la Compra"
#~ msgid "Search Recipe"
#~ msgstr "Buscar Receta"
#~ msgid "Shopping Recipes"
#~ msgstr "Recetas en el carro de la compra"
#~ msgid "No recipes selected"
#~ msgstr "No hay recetas seleccionadas"
#~ msgid "Entry Mode"
#~ msgstr "Modo de entrada"
#~ msgid "Add Entry"
#~ msgstr "Añadir entrada"
#~ msgid "Amount"
#~ msgstr "Cantidad"
#~ msgid "Select Unit"
#~ msgstr "Seleccionar unidad"
#~ msgid "Select"
#~ msgstr "Seleccionar"
#~ msgid "Select Food"
#~ msgstr "Seleccionar Alimento"
#~ msgid "Select Supermarket"
#~ msgstr "Seleccionar supermercado"
#~ msgid "Select User"
#~ msgstr "Seleccionar Usuario"
#~ msgid "Finished"
#~ msgstr "Completada"
#, fuzzy
#~| msgid "You are offline, shopping list might not syncronize."
#~ msgid "You are offline, shopping list might not synchronize."
#~ msgstr "Estás desconectado, la lista de la compra no se sincronizará."
#~ msgid "Copy/Export"
#~ msgstr "Copiar/Exportar"
#, fuzzy
#~| msgid "Bookmark saved!"
#~ msgid "Bookmark Me!"
#~ msgstr "¡Marcador guardado!"
#~ msgid "Text"
#~ msgstr "Texto"
#, fuzzy
#~| msgid "File ID"
#~ msgid "File"
#~ msgstr "ID de Fichero"
#~ msgid "Enter website URL"
#~ msgstr "Introduce la URL del sitio web"
#, fuzzy
#~| msgid "View Recipe"
#~ msgid "Preview Recipe Data"
#~ msgstr "Ver la receta"
#, fuzzy
#~| msgid "Preparation Time"
#~ msgid "Prep Time"
#~ msgstr "Tiempo de Preparación"
#, fuzzy
#~| msgid "Time"
#~ msgid "Cook Time"
#~ msgstr "Tiempo"
#, fuzzy
#~| msgid "Discovered Recipes"
#~ msgid "Discovered Attributes"
#~ msgstr "Recetas Descubiertas"
#, fuzzy
#~| msgid "Show as header"
#~ msgid "Show Blank Field"
#~ msgstr "Mostrar como encabezado"
#, fuzzy
#~| msgid "Delete Step"
#~ msgid "Delete Text"
#~ msgstr "Eliminar paso"
#, fuzzy
#~| msgid "Delete Recipe"
#~ msgid "Delete image"
#~ msgstr "Eliminar receta"
#~ msgid "Recipe Name"
#~ msgstr "Nombre de la Receta"
#, fuzzy
#~| msgid "Description"
#~ msgid "Recipe Description"
#~ msgstr "Descripción"
#~ msgid "Select one"
#~ msgstr "Seleccione uno"
#~ msgid "Note"
#~ msgstr "Nota"
#, fuzzy
#~| msgid "All Keywords"
#~ msgid "Add Keyword"
#~ msgstr "Todas las palabras clave."
#~ msgid "All Keywords"
#~ msgstr "Todas las palabras clave."
#~ msgid "Import all keywords, not only the ones already existing."
#~ msgstr "Importar todas las palabras clave, no solo las ya existentes."
#~ msgid "Information"
#~ msgstr "Información"
#~ msgid ""
#~ " Only websites containing ld+json or microdata information can currently\n"
#~ " be imported. Most big recipe pages "
#~ "support this. If you site cannot be imported but\n"
#~ " you think\n"
#~ " it probably has some kind of "
#~ "structured data feel free to post an example in the\n"
#~ " github issues."
#~ msgstr ""
#~ "Actualmente sólo se pueden importar sitios web que contengan información "
#~ "en\n"
#~ " ld+json o microdatos. La mayoría de "
#~ "las grandes páginas de recetas soportan esto. Si tu sitio no puede ser "
#~ "importado pero \n"
#~ " crees que\n"
#~ " tiene algún tipo de datos "
#~ "estructurados, no dudes en poner un ejemplo en las\n"
#~ " propuestas de GitHub."
#~ msgid "Google ld+json Info"
#~ msgstr "Información de Google ld+json"
#~ msgid "GitHub Issues"
#~ msgstr "Propuestas de GitHub"
#~ msgid "Recipe Markup Specification"
#~ msgstr "Especificación de anotaciones de la receta"
#~ msgid "The requested site provided malformed data and cannot be read."
#~ msgstr ""
#~ "El sitio solicitado proporcionó datos con formato incorrecto y no se "
#~ "puede leer."
#~ msgid "The requested page could not be found."
#~ msgstr "La página solicitada no pudo ser encontrada."
#~ msgid ""
#~ "The requested site does not provide any recognized data format to import "
#~ "the recipe from."
#~ msgstr ""
#~ "El sitio solicitado no proporciona ningún formato de datos reconocido "
#~ "para importar la receta."
#~ msgid "Shopping Lists"
#~ msgstr "Listas de la compra"
#~ msgid "Time"
#~ msgstr "Tiempo"
#~ msgid "Log Recipe Cooking"
#~ msgstr "Registrar receta cocinada"
#~ msgid "All fields are optional and can be left empty."
#~ msgstr "Todos los campos son opcionales y pueden dejarse vacíos."
#~ msgid "Rating"
#~ msgstr "Calificación"
#~ msgid "Exporting is not implemented for this provider"
#~ msgstr "La exportación no está implementada para este proveedor"
#~ msgid "New unit that other gets replaced by."
#~ msgstr "Nueva unidad que reemplaza a la anterior."
#~ msgid "Old Unit"
#~ msgstr "Antigua unidad"
#~ msgid "Unit that should be replaced."
#~ msgstr "Unidad que se va a reemplazar."
#~ msgid "New Food"
#~ msgstr "Nuevo Alimento"
#~ msgid "New food that other gets replaced by."
#~ msgstr "Nuevo alimento que remplaza al anterior."
#~ msgid "Old Food"
#~ msgstr "Antiguo alimento"
#~ msgid "New Entry"
#~ msgstr "Nueva entrada"
#~ msgid "Title"
#~ msgstr "Titulo"
#~ msgid "Note (optional)"
#~ msgstr "Nota (opcional)"
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "Puedes utilizar Markdown para dar formato a este campo. Consulta la documentación aquí"
#~ msgid "Serving Count"
#~ msgstr "Número de raciones"
#~ msgid "Create only note"
#~ msgstr "Crear sólo una nota"
#~ msgid "Number of Days"
#~ msgstr "Número de Días"
#~ msgid "Weekday offset"
#~ msgstr "Compensar día inicial"
#~ msgid ""
#~ "Number of days starting from the first day of the week to offset the "
#~ "default view."
#~ msgstr ""
#~ "Número de días a partir del primer día de la semana para compensar la "
#~ "vista por defecto."
#~ msgid "Edit plan types"
#~ msgstr "Modificar el tipo de menú"
#~ msgid "Show help"
#~ msgstr "Mostrar Ayuda"
#~ msgid "Week iCal export"
#~ msgstr "Exportar a iCal"
#~ msgid "Add to Shopping"
#~ msgstr "Añadir para comprar"
#~ msgid "New meal type"
#~ msgstr "Nuevo tipo de comida"
#~ msgid "Meal Plan Help"
#~ msgstr "Ayuda del menú"
#~ msgid ""
#~ "\n"
#~ " The meal plan module allows planning of "
#~ "meals both with recipes and notes.
\n"
#~ " Simply select a recipe from the list of "
#~ "recently viewed recipes or search the one you\n"
#~ " want and drag it to the desired plan "
#~ "position. You can also add a note and a title and\n"
#~ " then drag the recipe to create a plan "
#~ "entry with a custom title and note. Creating only\n"
#~ " Notes is possible by dragging the create "
#~ "note box into the plan.
\n"
#~ " Click on a recipe in order to open the "
#~ "detailed view. There you can also add it to the\n"
#~ " shopping list. You can also add all "
#~ "recipes of a day to the shopping list by\n"
#~ " clicking the shopping cart at the top of "
#~ "the table.
\n"
#~ " Since a common use case is to plan meals "
#~ "together you can define\n"
#~ " users you want to share your plan with in "
#~ "the settings.\n"
#~ "
\n"
#~ " You can also edit the types of meals you "
#~ "want to plan. If you share your plan with\n"
#~ " someone with\n"
#~ " different meals, their meal types will "
#~ "appear in your list as well. To prevent\n"
#~ " duplicates (e.g. Other and Misc.)\n"
#~ " name your meal types the same as the "
#~ "users you share your meals with and they will be\n"
#~ " merged.
\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " El módulo de menú permite planificar las "
#~ "comidas con recetas o con notas.
\n"
#~ " Simplemente selecciona una receta de la "
#~ "lista de recetas vistas recientemente o busca la que\n"
#~ " quieras y arrastrala a la posición "
#~ "deseada del menú. También puede añadir una nota y un título y\n"
#~ " luego arrastrar la receta para crear una "
#~ "entrada del plan con un título y una nota personalizados. Es posible "
#~ "crear\n"
#~ " solamente notas arrastrando el cuadro de "
#~ "creación de notas al menú.
\n"
#~ " Haga clic en una receta para abrir la "
#~ "vista detallada. Desde aquí también puedes añadirla a la\n"
#~ " lista de la compra. También puedes añadir "
#~ "todas las recetas de un día a la lista de la compra\n"
#~ " haciendo clic en el carrito de la compra "
#~ "en la parte superior de la tabla.
\n"
#~ " Ya que un caso de uso común es planificar "
#~ "las comidas juntos, en los ajustes\n"
#~ " puedes definir los usuarios con los que "
#~ "quieres compartir el menú.\n"
#~ "
\n"
#~ " También puedes editar los tipos de comidas "
#~ "del menú. Si compartes tu menú con\n"
#~ " alguien con\n"
#~ " diferentes tipos de comidas, sus tipos de "
#~ "comida aparecerán también en tu listado. Para prevenir\n"
#~ " duplicados (p. ej. Otros y Misc.)\n"
#~ " nombra los tipos de comida igual que el "
#~ "resto de usuarios con los que compartes tus comidas y serán\n"
#~ " combinados.
\n"
#~ " "
#~ msgid "Units merged!"
#~ msgstr "¡Unidades fusionadas!"
#~ msgid "Foods merged!"
#~ msgstr "¡Alimentos fusionados!"
#~ msgid "Utensils"
#~ msgstr "Utensilios"
#~ msgid "Storage Data"
#~ msgstr "Almacenamiento de Datos"
#~ msgid "Storage Backends"
#~ msgstr "Backends de Almacenamiento"
#~ msgid "Configure Sync"
#~ msgstr "Configurar Sincronización"
#~ msgid "Discovered Recipes"
#~ msgstr "Recetas Descubiertas"
#~ msgid "Discovery Log"
#~ msgstr "Registro de descubrimiento"
#~ msgid "Units & Ingredients"
#~ msgstr "Unidades e ingredientes"
#~ msgid "New Book"
#~ msgstr "Nuevo Libro"
#~ msgid "Toggle Recipes"
#~ msgstr "Alternar recetas"
#~ msgid "There are no recipes in this book yet."
#~ msgstr "Todavía no hay recetas en este libro."
#~ msgid "Waiting Time"
#~ msgstr "Tiempo de espera"
#~ msgid "Servings Text"
#~ msgstr "Texto de raciones"
#~ msgid "Select Keywords"
#~ msgstr "Seleccionar palabras clave"
#~ msgid "Delete Step"
#~ msgstr "Eliminar paso"
#~ msgid "Step"
#~ msgstr "Paso"
#~ msgid "Show as header"
#~ msgstr "Mostrar como encabezado"
#~ msgid "Hide as header"
#~ msgstr "Ocultar como encabezado"
#~ msgid "Move Up"
#~ msgstr "Mover Arriba"
#~ msgid "Move Down"
#~ msgstr "Mover Abajo"
#~ msgid "Step Name"
#~ msgstr "Nombre del paso"
#~ msgid "Step Type"
#~ msgstr "Tipo de paso"
#~ msgid "Step time in Minutes"
#~ msgstr "Tiempo de paso en minutos"
#, fuzzy
#~| msgid "Select one"
#~ msgid "Select File"
#~ msgstr "Seleccione uno"
#, fuzzy
#~| msgid "Delete Recipe"
#~ msgid "Select Recipe"
#~ msgstr "Eliminar receta"
#~ msgid "Delete Ingredient"
#~ msgstr "Eliminar ingrediente"
#~ msgid "Make Header"
#~ msgstr "Crear encabezado"
#~ msgid "Make Ingredient"
#~ msgstr "Crear ingrediente"
#~ msgid "Disable Amount"
#~ msgstr "Deshabilitar cantidad"
#~ msgid "Enable Amount"
#~ msgstr "Habilitar cantidad"
#~ msgid "Copy Template Reference"
#~ msgstr "Copiar Referencia de Plantilla"
#~ msgid "Save & View"
#~ msgstr "Guardar y ver"
#~ msgid "Add Step"
#~ msgstr "Agregar paso"
#~ msgid "Add Nutrition"
#~ msgstr "Añadir Información Nutricional"
#~ msgid "Remove Nutrition"
#~ msgstr "Eliminar Información Nutricional"
#~ msgid "View Recipe"
#~ msgstr "Ver la receta"
#~ msgid "Delete Recipe"
#~ msgstr "Eliminar receta"
#, fuzzy
#~| msgid "Password Reset"
#~ msgid "Password Settings"
#~ msgstr "Restablecer contraseña"
#, fuzzy
#~| msgid "Link social account"
#~ msgid "Manage Social Accounts"
#~ msgstr "Enlazar cuenta social"
#~ msgid ""
#~ "A username is not required, if left blank the new user can choose one."
#~ msgstr ""
#~ "No se requiere un nombre de usuario, si se deja en blanco, el nuevo "
#~ "usuario puede elegir uno."
#~ msgid "Link"
#~ msgstr "Enlace"
#~ msgid "Logout"
#~ msgstr "Cerrar sesión"
#~ msgid "Website Import"
#~ msgstr "Importación de sitios web"
#~ msgid "There was an error creating a resource!"
#~ msgstr "¡Hubo un error al crear un recurso!"
#~ msgid ""
#~ "The requested page refused to provide any information (Status Code 403)."
#~ msgstr ""
#~ "La página solicitada se negó a proporcionar información (Código de estado "
#~ "403)."
#~ msgid "Number of servings"
#~ msgstr "Número de raciones"
#~ msgid ""
#~ "Include - [ ] in list for easier usage in markdown based "
#~ "documents."
#~ msgstr ""
#~ "Incluir - [ ] en la lista para facilitar el uso en los "
#~ "documentos basados en Markdown."
#~ msgid "Backup & Restore"
#~ msgstr "Copiar y Restaurar"
#~ msgid "Download Backup"
#~ msgstr "Descargar Copia de Seguridad"
#~ msgid "Preference for given user already exists"
#~ msgstr "Las preferencias para este usuario ya existen"
#~ msgid "This recipe is already linked to the book!"
#~ msgstr "¡Esta receta ya está enlazada al libro!"
================================================
FILE: cookbook/locale/fi/LC_MESSAGES/django.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-22 20:15+0200\n"
"PO-Revision-Date: 2025-02-14 23:58+0000\n"
"Last-Translator: Juha Antikainen \n"
"Language-Team: Finnish \n"
"Language: fi\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.8.4\n"
#: .\cookbook\forms.py:50
msgid "Default"
msgstr "Oletus"
#: .\cookbook\forms.py:77
msgid ""
"To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything."
msgstr ""
"Päällekkäisyyksien estämiseksi reseptit, joilla on sama nimi kuin olemassa "
"olevat, ohitetaan. Valitse tämä ruutu tuodaksesi kaiken."
#: .\cookbook\forms.py:108
msgid "Maximum number of users for this space reached."
msgstr "Tämän tilan käyttäjien enimmäismäärä saavutettu."
#: .\cookbook\forms.py:114
msgid "Email address already taken!"
msgstr "Sähköpostiosoite on jo varattu!"
#: .\cookbook\forms.py:121
msgid ""
"An email address is not required but if present the invite link will be sent "
"to the user."
msgstr ""
"Sähköpostiosoitetta ei vaadita, mutta kutsulinkki lähetetään käyttäjälle , "
"jos se on olemassa ."
#: .\cookbook\forms.py:133
msgid "Name already taken."
msgstr "Nimi on jo käytössä."
#: .\cookbook\forms.py:144 .\cookbook\forms.py:158
msgid "Accept Terms and Privacy"
msgstr "Hyväksy Ehdot ja Tietosuoja"
#: .\cookbook\helper\AllAuthCustomAdapter.py:41
msgid ""
"In order to prevent spam, the requested email was not send. Please wait a "
"few minutes and try again."
msgstr ""
"Roskapostin estämiseksi pyydettyä sähköpostia ei lähetetty. Odota muutama "
"minuutti ja yritä uudelleen."
#: .\cookbook\helper\permission_helper.py:165
#: .\cookbook\helper\permission_helper.py:186 .\cookbook\views\views.py:138
msgid "You are not logged in and therefore cannot view this page!"
msgstr "Et ole kirjautunut sisään ja siksi et voi tarkastella tätä sivua!"
#: .\cookbook\helper\permission_helper.py:168
#: .\cookbook\helper\permission_helper.py:173
#: .\cookbook\helper\permission_helper.py:198
#: .\cookbook\helper\permission_helper.py:265
#: .\cookbook\helper\permission_helper.py:279
#: .\cookbook\helper\permission_helper.py:290
#: .\cookbook\helper\permission_helper.py:301
#: .\cookbook\helper\permission_helper.py:317
#: .\cookbook\helper\permission_helper.py:343
#: .\cookbook\helper\permission_helper.py:359
msgid "You do not have the required permissions to view this page!"
msgstr "Sinulla ei ole tämän sivun katseluoikeuksia!"
#: .\cookbook\helper\permission_helper.py:191
#: .\cookbook\helper\permission_helper.py:214
#: .\cookbook\helper\permission_helper.py:236
#: .\cookbook\helper\permission_helper.py:251
msgid "You cannot interact with this object as it is not owned by you!"
msgstr ""
"Et voi olla vuorovaikutuksessa tämän kohteen kanssa, koska et omista sitä!"
#: .\cookbook\helper\permission_helper.py:420
msgid "You have reached the maximum number of recipes for your space."
msgstr "Olet saavuttanut tilasi enimmäismäärän reseptejä."
#: .\cookbook\helper\permission_helper.py:432
msgid "You have more users than allowed in your space."
msgstr "Tilassasi on enemmän käyttäjiä kuin sallitaan."
#: .\cookbook\helper\recipe_url_import.py:319
msgid "reverse rotation"
msgstr "käänteinen kierto"
#: .\cookbook\helper\recipe_url_import.py:320
msgid "careful rotation"
msgstr "huolellinen kierto"
#: .\cookbook\helper\recipe_url_import.py:321
msgid "knead"
msgstr "vaivata"
#: .\cookbook\helper\recipe_url_import.py:322
msgid "thicken"
msgstr "paksuuntua"
#: .\cookbook\helper\recipe_url_import.py:323
msgid "warm up"
msgstr "lämmetä"
#: .\cookbook\helper\recipe_url_import.py:324
msgid "ferment"
msgstr "käydä"
#: .\cookbook\helper\recipe_url_import.py:325
msgid "slow cook"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:326
msgid "egg boiler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:327
msgid "kettle"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:328
msgid "blend"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:329
msgid "pre-clean"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:330
msgid "high temperature"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:331
msgid "rice cooker"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:332
msgid "caramelize"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:333
msgid "peeler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:334
msgid "slicer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:335
msgid "grater"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:336
msgid "spiralizer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:337
msgid "sous-vide"
msgstr "sous-vide"
#: .\cookbook\helper\shopping_helper.py:150
msgid "You must supply a servings size"
msgstr "Sinun on annettava annoksen koko"
#: .\cookbook\helper\template_helper.py:97
#: .\cookbook\helper\template_helper.py:99
#: .\cookbook\helper\template_helper.py:101
msgid "Could not parse template code."
msgstr "Mallin koodia ei voitu jäsentää."
#: .\cookbook\integration\copymethat.py:44
#: .\cookbook\integration\melarecipes.py:37
msgid "Favorite"
msgstr "Suosikki"
#: .\cookbook\integration\copymethat.py:50
msgid "I made this"
msgstr "Tein tämän"
#: .\cookbook\integration\integration.py:238
#, fuzzy
msgid ""
"Importer expected a .zip file. Did you choose the correct importer type for "
"your data ?"
msgstr "Tuoja odotti .zip-tiedostoa. Valitsitko tiedostolle oikean tyypin?"
#: .\cookbook\integration\integration.py:241
msgid ""
"An unexpected error occurred during the import. Please make sure you have "
"uploaded a valid file."
msgstr ""
"Tuonnin aikana tapahtui odottamaton virhe. Varmista, että olet ladannut "
"kelvollisen tiedoston."
#: .\cookbook\integration\integration.py:246
msgid "The following recipes were ignored because they already existed:"
msgstr ""
"Seuraavat reseptit jätettiin huomioimatta, koska ne olivat jo olemassa:"
#: .\cookbook\integration\integration.py:250
#, python-format
msgid "Imported %s recipes."
msgstr "Tuotiin %s reseptiä."
#: .\cookbook\integration\mealie1.py:210
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "Calories"
msgstr "Kalorit"
#: .\cookbook\integration\mealie1.py:211
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
msgid "Carbohydrates"
msgstr "Hiilihydraatit"
#: .\cookbook\integration\mealie1.py:212
msgid "Cholesterol"
msgstr ""
#: .\cookbook\integration\mealie1.py:213
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
msgid "Fat"
msgstr "Rasva"
#: .\cookbook\integration\mealie1.py:214
msgid "Fiber"
msgstr ""
#: .\cookbook\integration\mealie1.py:215
#, fuzzy
#| msgid "Proteins"
msgid "Protein"
msgstr "Proteiinit"
#: .\cookbook\integration\mealie1.py:216
msgid "Saturated Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:217
msgid "Sodium"
msgstr ""
#: .\cookbook\integration\mealie1.py:218
msgid "Sugar"
msgstr ""
#: .\cookbook\integration\mealie1.py:219
msgid "Trans Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:220
msgid "Unsaturated Fat"
msgstr ""
#: .\cookbook\integration\openeats.py:28
msgid "Recipe source:"
msgstr "Reseptin lähde:"
#: .\cookbook\integration\paprika.py:49
msgid "Notes"
msgstr "Muistio"
#: .\cookbook\integration\paprika.py:52
msgid "Nutritional Information"
msgstr "Ravitsemustiedot"
#: .\cookbook\integration\paprika.py:56
msgid "Source"
msgstr "Lähde"
#: .\cookbook\integration\recettetek.py:55
#: .\cookbook\integration\recipekeeper.py:70
msgid "Imported from"
msgstr "Tuotu osoitteesta"
#: .\cookbook\integration\saffron.py:23
msgid "Servings"
msgstr "Annokset"
#: .\cookbook\integration\saffron.py:25
msgid "Waiting time"
msgstr "Odotusaika"
#: .\cookbook\integration\saffron.py:27
msgid "Preparation Time"
msgstr "Esivalmistelu aika"
#: .\cookbook\integration\saffron.py:29 .\cookbook\templates\index.html:6
msgid "Cookbook"
msgstr "Keittokirja"
#: .\cookbook\integration\saffron.py:31
msgid "Section"
msgstr "Osio"
#: .\cookbook\management\commands\fix_duplicate_properties.py:15
msgid "Fixes foods with "
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:14
msgid "Rebuilds full text search index on Recipe"
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:18
msgid "Only Postgresql databases use full text search, no index to rebuild"
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:29
msgid "Recipe index rebuild complete."
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:31
msgid "Recipe index rebuild failed."
msgstr ""
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
msgid "Breakfast"
msgstr "Aamupala"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:19
msgid "Lunch"
msgstr "Lounas"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:24
msgid "Dinner"
msgstr "Päivällinen"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:29 .\cookbook\models.py:971
msgid "Other"
msgstr "Muu"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "g"
msgstr "g"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "Proteins"
msgstr "Proteiinit"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "kcal"
msgstr "kcal"
#: .\cookbook\models.py:325
msgid ""
"Maximum file storage for space in MB. 0 for unlimited, -1 to disable file "
"upload."
msgstr ""
#: .\cookbook\models.py:513
msgid "Search"
msgstr "Haku"
#: .\cookbook\models.py:514
msgid "Meal-Plan"
msgstr "Ateriasuunnitelma"
#: .\cookbook\models.py:515
msgid "Books"
msgstr "Kirjat"
#: .\cookbook\models.py:516 .\cookbook\views\views.py:416
#: .\cookbook\views\views.py:417
msgid "Shopping"
msgstr "Ostokset"
#: .\cookbook\models.py:967
msgid "Nutrition"
msgstr "Ravitsemus"
#: .\cookbook\models.py:968
msgid "Allergen"
msgstr "Allergeeni"
#: .\cookbook\models.py:969
msgid "Price"
msgstr "Hinta"
#: .\cookbook\models.py:970
msgid "Goal"
msgstr ""
#: .\cookbook\models.py:1467 .\cookbook\templates\search_info.html:28
msgid "Simple"
msgstr ""
#: .\cookbook\models.py:1468 .\cookbook\templates\search_info.html:33
msgid "Phrase"
msgstr ""
#: .\cookbook\models.py:1469 .\cookbook\templates\search_info.html:38
msgid "Web"
msgstr ""
#: .\cookbook\models.py:1470 .\cookbook\templates\search_info.html:47
msgid "Raw"
msgstr ""
#: .\cookbook\models.py:1525
msgid "Food Alias"
msgstr ""
#: .\cookbook\models.py:1526
msgid "Unit Alias"
msgstr ""
#: .\cookbook\models.py:1527
msgid "Keyword Alias"
msgstr ""
#: .\cookbook\models.py:1528
msgid "Description Replace"
msgstr ""
#: .\cookbook\models.py:1529
msgid "Instruction Replace"
msgstr ""
#: .\cookbook\models.py:1530
msgid "Never Unit"
msgstr ""
#: .\cookbook\models.py:1531
msgid "Transpose Words"
msgstr ""
#: .\cookbook\models.py:1532
msgid "Food Replace"
msgstr ""
#: .\cookbook\models.py:1533
msgid "Unit Replace"
msgstr ""
#: .\cookbook\models.py:1534
msgid "Name Replace"
msgstr ""
#: .\cookbook\models.py:1564
msgid "Recipe"
msgstr "Resepti"
#: .\cookbook\models.py:1565
msgid "Food"
msgstr "Ruoka"
#: .\cookbook\models.py:1566
msgid "Keyword"
msgstr "Avainsana"
#: .\cookbook\serializer.py:262
msgid "File uploads are not enabled for this Space."
msgstr "Tiedostojen lataaminen ei ole käytössä tässä tilassa."
#: .\cookbook\serializer.py:273
msgid "You have reached your file upload limit."
msgstr "Olet saavuttanut tiedostojen lataus rajan."
#: .\cookbook\serializer.py:281
msgid "The given file type is not allowed."
msgstr ""
#: .\cookbook\serializer.py:421 .\cookbook\views\views.py:94
msgid ""
"You have the reached the maximum amount of spaces that can be owned by you."
msgstr ""
#: .\cookbook\serializer.py:434
msgid "Space Name must be unique."
msgstr ""
#: .\cookbook\serializer.py:469
msgid "Cannot modify Space owner permission."
msgstr ""
#: .\cookbook\serializer.py:1596
msgid "Hello"
msgstr "Hei"
#: .\cookbook\serializer.py:1596
msgid "You have been invited by "
msgstr "Sinut on kutsunut "
#: .\cookbook\serializer.py:1598
msgid " to join their Tandoor Recipes space "
msgstr " liittymään mukaan Tandoor Resepti Tilaan "
#: .\cookbook\serializer.py:1600
msgid "Click the following link to activate your account: "
msgstr "Napsauta seuraavaa linkkiä aktivoidaksesi tilisi: "
#: .\cookbook\serializer.py:1602
msgid ""
"If the link does not work use the following code to manually join the space: "
msgstr ""
#: .\cookbook\serializer.py:1604
msgid "The invitation is valid until "
msgstr ""
#: .\cookbook\serializer.py:1606
msgid ""
"Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub "
msgstr ""
#: .\cookbook\serializer.py:1609
msgid "Tandoor Recipes Invite"
msgstr ""
#: .\cookbook\serializer.py:1813
msgid "Existing shopping list to update"
msgstr ""
#: .\cookbook\serializer.py:1815
msgid ""
"List of ingredient IDs from the recipe to add, if not provided all "
"ingredients will be added."
msgstr ""
#: .\cookbook\serializer.py:1817
msgid ""
"Providing a list_recipe ID and servings of 0 will delete that shopping list."
msgstr ""
#: .\cookbook\serializer.py:1826
msgid "Amount of food to add to the shopping list"
msgstr ""
#: .\cookbook\serializer.py:1828
msgid "ID of unit to use for the shopping list"
msgstr ""
#: .\cookbook\serializer.py:1830
msgid "When set to true will delete all food from active shopping lists."
msgstr ""
#: .\cookbook\templates\404.html:5
msgid "404 Error"
msgstr "404 Virhe"
#: .\cookbook\templates\404.html:18
msgid "The page you are looking for could not be found."
msgstr "Etsimääsi sivua ei löydy."
#: .\cookbook\templates\404.html:33
msgid "Take me Home"
msgstr ""
#: .\cookbook\templates\404.html:35
msgid "Report a Bug"
msgstr ""
#: .\cookbook\templates\account\email.html:6
#: .\cookbook\templates\account\email.html:10
msgid "E-mail Addresses"
msgstr ""
#: .\cookbook\templates\account\email.html:12
msgid "The following e-mail addresses are associated with your account:"
msgstr ""
#: .\cookbook\templates\account\email.html:29
msgid "Verified"
msgstr "Vahvistettu"
#: .\cookbook\templates\account\email.html:31
msgid "Unverified"
msgstr "Vahvistamaton"
#: .\cookbook\templates\account\email.html:33
msgid "Primary"
msgstr "Ensisijainen"
#: .\cookbook\templates\account\email.html:40
msgid "Make Primary"
msgstr "Tee Ensijaiseksi"
#: .\cookbook\templates\account\email.html:42
msgid "Re-send Verification"
msgstr "Lähetä vahvistus uudelleen"
#: .\cookbook\templates\account\email.html:43
#: .\cookbook\templates\socialaccount\connections.html:36
msgid "Remove"
msgstr "Siirrä"
#: .\cookbook\templates\account\email.html:51
msgid "Warning:"
msgstr "Varoitus:"
#: .\cookbook\templates\account\email.html:51
msgid ""
"You currently do not have any e-mail address set up. You should really add "
"an e-mail address so you can receive notifications, reset your password, etc."
msgstr ""
#: .\cookbook\templates\account\email.html:57
msgid "Add E-mail Address"
msgstr "Lisää sähköpostiosoite"
#: .\cookbook\templates\account\email.html:62
msgid "Add E-mail"
msgstr "Lisää sähköposti"
#: .\cookbook\templates\account\email.html:72
msgid "Do you really want to remove the selected e-mail address?"
msgstr "Haluatko todella poistaa valitun sähköpostiosoitteen?"
#: .\cookbook\templates\account\email_confirm.html:6
#: .\cookbook\templates\account\email_confirm.html:10
msgid "Confirm E-mail Address"
msgstr "Vahvista sähköpostiosoite"
#: .\cookbook\templates\account\email_confirm.html:16
#, python-format
msgid ""
"Please confirm that\n"
" %(email)s is an e-mail address "
"for user %(user_display)s\n"
" ."
msgstr ""
"Ole hyvä ja vahvista, että\n"
" %(email)s on sähköpostiosoite "
"käyttäjälle %(user_display)s\n"
" ."
#: .\cookbook\templates\account\email_confirm.html:22
msgid "Confirm"
msgstr "Vahvista"
#: .\cookbook\templates\account\email_confirm.html:29
#, python-format
msgid ""
"This e-mail confirmation link expired or is invalid. Please\n"
" issue a new e-mail confirmation "
"request."
msgstr ""
#: .\cookbook\templates\account\login.html:8
#: .\cookbook\templates\openid\login.html:8
msgid "Login"
msgstr "Kirjaudu sisään"
#: .\cookbook\templates\account\login.html:15
#: .\cookbook\templates\account\login.html:31
#: .\cookbook\templates\account\password_reset.html:39
#: .\cookbook\templates\account\password_reset_done.html:31
#: .\cookbook\templates\account\signup.html:68
#: .\cookbook\templates\account\signup_closed.html:15
#: .\cookbook\templates\openid\login.html:15
#: .\cookbook\templates\openid\login.html:26
#: .\cookbook\templates\socialaccount\authentication_error.html:15
msgid "Sign In"
msgstr ""
#: .\cookbook\templates\account\login.html:34
#: .\cookbook\templates\account\password_reset.html:41
#: .\cookbook\templates\account\password_reset_done.html:33
#: .\cookbook\templates\socialaccount\signup.html:8
#: .\cookbook\templates\socialaccount\signup.html:56
msgid "Sign Up"
msgstr ""
#: .\cookbook\templates\account\login.html:38
msgid "Lost your password?"
msgstr ""
#: .\cookbook\templates\account\login.html:39
#: .\cookbook\templates\account\password_reset.html:29
msgid "Reset My Password"
msgstr ""
#: .\cookbook\templates\account\login.html:50
msgid "Social Login"
msgstr ""
#: .\cookbook\templates\account\login.html:51
msgid "You can use any of the following providers to sign in."
msgstr ""
#: .\cookbook\templates\account\logout.html:5
#: .\cookbook\templates\account\logout.html:9
#: .\cookbook\templates\account\logout.html:18
msgid "Sign Out"
msgstr "Kirjaudu ulos"
#: .\cookbook\templates\account\logout.html:11
msgid "Are you sure you want to sign out?"
msgstr "Haluatko varmasti kirjautua ulos?"
#: .\cookbook\templates\account\password_change.html:6
#: .\cookbook\templates\account\password_change.html:10
#: .\cookbook\templates\account\password_change.html:15
#: .\cookbook\templates\account\password_reset_from_key.html:7
#: .\cookbook\templates\account\password_reset_from_key.html:13
#: .\cookbook\templates\account\password_reset_from_key_done.html:7
#: .\cookbook\templates\account\password_reset_from_key_done.html:13
msgid "Change Password"
msgstr "Vaihda Salasana"
#: .\cookbook\templates\account\password_change.html:16
msgid "Forgot Password?"
msgstr "Unohditko Salasanan?"
#: .\cookbook\templates\account\password_reset.html:7
#: .\cookbook\templates\account\password_reset.html:13
#: .\cookbook\templates\account\password_reset_done.html:7
#: .\cookbook\templates\account\password_reset_done.html:18
msgid "Password Reset"
msgstr "Salasanan palautus"
#: .\cookbook\templates\account\password_reset.html:24
msgid ""
"Forgotten your password? Enter your e-mail address below, and we'll send you "
"an e-mail allowing you to reset it."
msgstr ""
#: .\cookbook\templates\account\password_reset.html:32
msgid "Password reset is disabled on this instance."
msgstr ""
#: .\cookbook\templates\account\password_reset_done.html:25
msgid ""
"We have sent you an e-mail. Please contact us if you do not receive it "
"within a few minutes."
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:13
msgid "Bad Token"
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:25
#, python-format
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used.\n"
" Please request a new "
"password reset."
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:33
msgid "change password"
msgstr "vaihda salasana"
#: .\cookbook\templates\account\password_reset_from_key.html:36
#: .\cookbook\templates\account\password_reset_from_key_done.html:19
msgid "Your password is now changed."
msgstr "Salasanasi on nyt vaihdettu."
#: .\cookbook\templates\account\password_set.html:6
#: .\cookbook\templates\account\password_set.html:10
#: .\cookbook\templates\account\password_set.html:15
msgid "Set Password"
msgstr "Aseta Salasana"
#: .\cookbook\templates\account\signup.html:5
msgid "Register"
msgstr ""
#: .\cookbook\templates\account\signup.html:11
msgid "Create an Account"
msgstr ""
#: .\cookbook\templates\account\signup.html:41
msgid "I accept the follwoing"
msgstr "Hyväksyn seuraavat"
#: .\cookbook\templates\account\signup.html:44
#: .\cookbook\templates\socialaccount\signup.html:35
msgid "Terms and Conditions"
msgstr "Käyttöehdot"
#: .\cookbook\templates\account\signup.html:47
#: .\cookbook\templates\socialaccount\signup.html:38
msgid "and"
msgstr "ja"
#: .\cookbook\templates\account\signup.html:51
#: .\cookbook\templates\socialaccount\signup.html:42
msgid "Privacy Policy"
msgstr "Tietosuojakäytännöt"
#: .\cookbook\templates\account\signup.html:64
msgid "Create User"
msgstr "Luo Käyttäjä"
#: .\cookbook\templates\account\signup.html:68
msgid "Already have an account?"
msgstr ""
#: .\cookbook\templates\account\signup_closed.html:5
#: .\cookbook\templates\account\signup_closed.html:11
msgid "Sign Up Closed"
msgstr ""
#: .\cookbook\templates\account\signup_closed.html:13
msgid "We are sorry, but the sign up is currently closed."
msgstr ""
#: .\cookbook\templates\frontend\tandoor.html:15
msgid "Tandoor Recipe Manager"
msgstr ""
#: .\cookbook\templates\index.html:28
msgid "Search recipe ..."
msgstr "Hae reseptiä ..."
#: .\cookbook\templates\index.html:43
msgid "New Recipe"
msgstr "Uusi Resepti"
#: .\cookbook\templates\index.html:46
msgid "Import Recipe"
msgstr "Tuo Resepti"
#: .\cookbook\templates\index.html:52
msgid "Advanced Search"
msgstr "Tarkennettu Haku"
#: .\cookbook\templates\index.html:56
msgid "Reset Search"
msgstr "Haun Nollaus"
#: .\cookbook\templates\index.html:84
msgid "Last viewed"
msgstr "Viimeksi katsottu"
#: .\cookbook\templates\index.html:86
msgid "Recipes"
msgstr "Reseptit"
#: .\cookbook\templates\index.html:93
msgid "Log in to view recipes"
msgstr "Kirjaudu sisään nähdäksesi reseptit"
#: .\cookbook\templates\markdown_info.html:5
#: .\cookbook\templates\markdown_info.html:13
msgid "Markdown Info"
msgstr ""
#: .\cookbook\templates\markdown_info.html:14
msgid ""
"\n"
" Markdown is lightweight markup language that can be used to format "
"plain text easily.\n"
" This site uses the Python Markdown library to\n"
" convert your text into nice looking HTML. Its full markdown "
"documentation can be found\n"
" here.\n"
" An incomplete but most likely sufficient documentation can be found "
"below.\n"
" "
msgstr ""
#: .\cookbook\templates\markdown_info.html:25
msgid "Headers"
msgstr ""
#: .\cookbook\templates\markdown_info.html:54
msgid "Formatting"
msgstr ""
#: .\cookbook\templates\markdown_info.html:56
#: .\cookbook\templates\markdown_info.html:72
msgid "Line breaks are inserted by adding two spaces after the end of a line"
msgstr ""
#: .\cookbook\templates\markdown_info.html:57
#: .\cookbook\templates\markdown_info.html:73
msgid "or by leaving a blank line in between."
msgstr ""
#: .\cookbook\templates\markdown_info.html:59
#: .\cookbook\templates\markdown_info.html:74
msgid "This text is bold"
msgstr "Tämä teksti on lihavoitu"
#: .\cookbook\templates\markdown_info.html:60
#: .\cookbook\templates\markdown_info.html:75
#, fuzzy
msgid "This text is italic"
msgstr "Tämä teksti on kursivoitu"
#: .\cookbook\templates\markdown_info.html:61
#: .\cookbook\templates\markdown_info.html:77
msgid "Blockquotes are also possible"
msgstr "Myös lohkolainaukset ovat mahdollisia"
#: .\cookbook\templates\markdown_info.html:84
msgid "Lists"
msgstr "Listat"
#: .\cookbook\templates\markdown_info.html:85
msgid ""
"Lists can ordered or unordered. It is important to leave a blank line "
"before the list!"
msgstr ""
#: .\cookbook\templates\markdown_info.html:87
#: .\cookbook\templates\markdown_info.html:108
msgid "Ordered List"
msgstr ""
#: .\cookbook\templates\markdown_info.html:89
#: .\cookbook\templates\markdown_info.html:90
#: .\cookbook\templates\markdown_info.html:91
#: .\cookbook\templates\markdown_info.html:110
#: .\cookbook\templates\markdown_info.html:111
#: .\cookbook\templates\markdown_info.html:112
msgid "unordered list item"
msgstr ""
#: .\cookbook\templates\markdown_info.html:93
#: .\cookbook\templates\markdown_info.html:114
msgid "Unordered List"
msgstr ""
#: .\cookbook\templates\markdown_info.html:95
#: .\cookbook\templates\markdown_info.html:96
#: .\cookbook\templates\markdown_info.html:97
#: .\cookbook\templates\markdown_info.html:116
#: .\cookbook\templates\markdown_info.html:117
#: .\cookbook\templates\markdown_info.html:118
msgid "ordered list item"
msgstr ""
#: .\cookbook\templates\markdown_info.html:125
msgid "Images & Links"
msgstr ""
#: .\cookbook\templates\markdown_info.html:126
msgid ""
"Links can be formatted with Markdown. This application also allows to paste "
"links directly into markdown fields without any formatting."
msgstr ""
#: .\cookbook\templates\markdown_info.html:132
#: .\cookbook\templates\markdown_info.html:145
msgid "This will become an image"
msgstr ""
#: .\cookbook\templates\markdown_info.html:152
msgid "Tables"
msgstr ""
#: .\cookbook\templates\markdown_info.html:153
msgid ""
"Markdown tables are hard to create by hand. It is recommended to use a table "
"editor like this one."
msgstr ""
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:171
#: .\cookbook\templates\markdown_info.html:177
msgid "Table"
msgstr ""
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:172
msgid "Header"
msgstr ""
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:178
msgid "Cell"
msgstr ""
#: .\cookbook\templates\no_groups_info.html:5
#: .\cookbook\templates\no_groups_info.html:12
msgid "No Permissions"
msgstr ""
#: .\cookbook\templates\no_groups_info.html:17
msgid "You do not have any groups and therefor cannot use this application."
msgstr ""
#: .\cookbook\templates\no_groups_info.html:18
#: .\cookbook\templates\no_perm_info.html:15
msgid "Please contact your administrator."
msgstr ""
#: .\cookbook\templates\no_perm_info.html:5
#: .\cookbook\templates\no_perm_info.html:12
msgid "No Permission"
msgstr ""
#: .\cookbook\templates\no_perm_info.html:15
msgid ""
"You do not have the required permissions to view this page or perform this "
"action."
msgstr ""
#: .\cookbook\templates\offline.html:5
msgid "Offline"
msgstr ""
#: .\cookbook\templates\openid\login.html:27
#: .\cookbook\templates\socialaccount\authentication_error.html:27
msgid "Back"
msgstr ""
#: .\cookbook\templates\rest_framework\api.html:5
msgid "Recipe Home"
msgstr ""
#: .\cookbook\templates\rest_framework\api.html:11
msgid "API Documentation"
msgstr "API Dokumentaatio"
#: .\cookbook\templates\search_info.html:5
#: .\cookbook\templates\search_info.html:9
msgid "Search Settings"
msgstr ""
#: .\cookbook\templates\search_info.html:10
msgid ""
"\n"
" Creating the best search experience is complicated and weighs "
"heavily on your personal configuration. \n"
" Changing any of the search settings can have significant impact on "
"the speed and quality of the results.\n"
" Search Methods, Trigrams and Full Text Search configurations are "
"only available if you are using Postgres for your database.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:19
msgid "Search Methods"
msgstr ""
#: .\cookbook\templates\search_info.html:23
msgid ""
" \n"
" Full text searches attempt to normalize the words provided to "
"match common variants. For example: 'forked', 'forking', 'forks' will all "
"normalize to 'fork'.\n"
" There are several methods available, described below, that will "
"control how the search behavior should react when multiple words are "
"searched.\n"
" Full technical details on how these operate can be viewed on Postgresql's website.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:29
msgid ""
" \n"
" Simple searches ignore punctuation and common words such as "
"'the', 'a', 'and'. And will treat separate words as required.\n"
" Searching for 'apple or flour' will return any recipe that "
"includes both 'apple' and 'flour' anywhere in the fields that have been "
"selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:34
msgid ""
" \n"
" Phrase searches ignore punctuation, but will search for all of "
"the words in the exact order provided.\n"
" Searching for 'apple or flour' will only return a recipe that "
"includes the exact phrase 'apple or flour' in any of the fields that have "
"been selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:39
msgid ""
" \n"
" Web searches simulate functionality found on many web search "
"sites supporting special syntax.\n"
" Placing quotes around several words will convert those words "
"into a phrase.\n"
" 'or' is recognized as searching for the word (or phrase) "
"immediately before 'or' OR the word (or phrase) directly after.\n"
" '-' is recognized as searching for recipes that do not include "
"the word (or phrase) that comes immediately after. \n"
" For example searching for 'apple pie' or cherry -butter will "
"return any recipe that includes the phrase 'apple pie' or the word "
"'cherry' \n"
" in any field included in the full text search but exclude any "
"recipe that has the word 'butter' in any field included.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:48
msgid ""
" \n"
" Raw search is similar to Web except will take puncuation "
"operators such as '|', '&' and '()'\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:59
msgid ""
" \n"
" Another approach to searching that also requires Postgresql is "
"fuzzy search or trigram similarity. A trigram is a group of three "
"consecutive characters.\n"
" For example searching for 'apple' will create x trigrams 'app', "
"'ppl', 'ple' and will create a score of how closely words match the "
"generated trigrams.\n"
" One benefit of searching trigams is that a search for 'sandwich' "
"will find misspelled words such as 'sandwhich' that would be missed by other "
"methods.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:69
msgid "Search Fields"
msgstr ""
#: .\cookbook\templates\search_info.html:73
msgid ""
" \n"
" Unaccent is a special case in that it enables searching a field "
"'unaccented' for each search style attempting to ignore accented values. \n"
" For example when you enable unaccent for 'Name' any search "
"(starts with, contains, trigram) will attempt the search ignoring accented "
"characters.\n"
" \n"
" For the other options, you can enable search on any or all "
"fields and they will be combined together with an assumed 'OR'.\n"
" For example enabling 'Name' for Starts With, 'Name' and "
"'Description' for Partial Match and 'Ingredients' and 'Keywords' for Full "
"Search\n"
" and searching for 'apple' will generate a search that will "
"return recipes that have:\n"
" - A recipe name that starts with 'apple'\n"
" - OR a recipe name that contains 'apple'\n"
" - OR a recipe description that contains 'apple'\n"
" - OR a recipe that will have a full text search match ('apple' "
"or 'apples') in ingredients\n"
" - OR a recipe that will have a full text search match in "
"Keywords\n"
"\n"
" Combining too many fields in too many types of search can have a "
"negative impact on performance, create duplicate results or return "
"unexpected results.\n"
" For example, enabling fuzzy search or partial matches will "
"interfere with web search methods. \n"
" Searching for 'apple -pie' with fuzzy search and full text "
"search will return the recipe Apple Pie. Though it is not included in the "
"full text results, it does match the trigram results.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:95
msgid "Search Index"
msgstr ""
#: .\cookbook\templates\search_info.html:99
msgid ""
" \n"
" Trigram search and Full Text Search both rely on database "
"indexes to perform effectively. \n"
" You can rebuild the indexes on all fields in the Admin page for "
"Recipes and selecting all recipes and running 'rebuild index for selected "
"recipes'\n"
" You can also rebuild indexes at the command line by executing "
"the management command 'python manage.py rebuildindex'\n"
" "
msgstr ""
#: .\cookbook\templates\setup.html:6
msgid "Cookbook Setup"
msgstr ""
#: .\cookbook\templates\setup.html:14
msgid "Setup"
msgstr ""
#: .\cookbook\templates\setup.html:15
msgid ""
"To start using this application you must first create a superuser account."
msgstr ""
#: .\cookbook\templates\setup.html:20
msgid "Create Superuser account"
msgstr ""
#: .\cookbook\templates\socialaccount\authentication_error.html:7
#: .\cookbook\templates\socialaccount\authentication_error.html:23
msgid "Social Network Login Failure"
msgstr ""
#: .\cookbook\templates\socialaccount\authentication_error.html:25
msgid ""
"An error occurred while attempting to login via your social network account."
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:4
#: .\cookbook\templates\socialaccount\connections.html:7
msgid "Account Connections"
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:10
msgid ""
"You can sign in to your account using any of the following third party\n"
" accounts:"
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:44
msgid ""
"You currently have no social network accounts connected to this account."
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:47
msgid "Add a 3rd Party Account"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:5
#: .\cookbook\templates\socialaccount\signup.html:5
msgid "Signup"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:9
#, python-format
msgid "Connect %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:11
#, python-format
msgid "You are about to connect a new third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:13
#, python-format
msgid "Sign In Via %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:15
#, python-format
msgid "You are about to sign in using a third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:20
msgid "Continue"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:10
#, python-format
msgid ""
"You are about to use your\n"
" %(provider_name)s account to login to\n"
" %(site_name)s. As a final step, please complete the following form:"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:32
#, fuzzy
#| msgid "I accept the follwoing"
msgid "I accept the following"
msgstr "Hyväksyn seuraavat"
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:23
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:31
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:39
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:47
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:55
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:63
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:71
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:79
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:87
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:95
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:103
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:111
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:119
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:127
msgid "Sign in using"
msgstr ""
#: .\cookbook\templates\space_overview.html:6
msgid "Overview"
msgstr ""
#: .\cookbook\templates\space_overview.html:13
msgid "Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:17
msgid ""
"Recipes, foods, shopping lists and more are organized in spaces of one or "
"more people."
msgstr ""
#: .\cookbook\templates\space_overview.html:18
msgid ""
"You can either be invited into an existing space or create your own one."
msgstr ""
#: .\cookbook\templates\space_overview.html:25
msgid "Your Spaces"
msgstr ""
#: .\cookbook\templates\space_overview.html:53
msgid "Owner"
msgstr ""
#: .\cookbook\templates\space_overview.html:73
#: .\cookbook\templates\space_overview.html:83
msgid "Join Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:76
msgid "Join an existing space."
msgstr ""
#: .\cookbook\templates\space_overview.html:78
msgid ""
"To join an existing space either enter your invite token or click on the "
"invite link the space owner send you."
msgstr ""
#: .\cookbook\templates\space_overview.html:91
#: .\cookbook\templates\space_overview.html:100
msgid "Create Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:94
msgid "Create your own recipe space."
msgstr ""
#: .\cookbook\templates\space_overview.html:96
msgid "Start your own recipe space and invite other users to it."
msgstr ""
#: .\cookbook\templates\system.html:23
msgid "System"
msgstr ""
#: .\cookbook\templates\system.html:24
msgid ""
"\n"
" Tandoor Recipes is an open source free software application. It can "
"be found on\n"
" GitHub.\n"
" Changelogs can be found here.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:30
msgid "System Information"
msgstr ""
#: .\cookbook\templates\system.html:51
msgid ""
"\n"
" You need to execute version.py in your update "
"script to generate version information (done automatically in docker).\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:56
msgid "Plugins"
msgstr ""
#: .\cookbook\templates\system.html:67
msgid "Media Serving"
msgstr ""
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:123 .\cookbook\templates\system.html:134
msgid "Warning"
msgstr "Varoitus"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:125 .\cookbook\templates\system.html:134
msgid "Ok"
msgstr ""
#: .\cookbook\templates\system.html:70
msgid ""
"Serving media files directly using gunicorn/python is not recommend!\n"
" Please follow the steps described\n"
" here to update\n"
" your installation.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:76 .\cookbook\templates\system.html:91
#: .\cookbook\templates\system.html:104 .\cookbook\templates\system.html:115
#: .\cookbook\views\views.py:168
msgid "Everything is fine!"
msgstr ""
#: .\cookbook\templates\system.html:80
msgid "Secret Key"
msgstr ""
#: .\cookbook\templates\system.html:84
msgid ""
"\n"
" You do not have a SECRET_KEY configured in your "
".env file. Django defaulted to the\n"
" standard key\n"
" provided with the installation which is publicly know and "
"insecure! Please set\n"
" SECRET_KEY int the .env configuration "
"file.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:94
msgid "Debug Mode"
msgstr ""
#: .\cookbook\templates\system.html:98
msgid ""
"\n"
" This application is still running in debug mode. This is most "
"likely not needed. Turn of debug mode by\n"
" setting\n"
" DEBUG=0 int the .env configuration "
"file.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:107
msgid "Allowed Hosts"
msgstr ""
#: .\cookbook\templates\system.html:111
msgid ""
"\n"
" Your allowed hosts are configured to allow every host. This "
"might be ok in some setups but should be avoided. Please see the docs about "
"this.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:118
msgid "Database"
msgstr ""
#: .\cookbook\templates\system.html:121
msgid "Info"
msgstr ""
#: .\cookbook\templates\system.html:131 .\cookbook\templates\system.html:148
msgid "Migrations"
msgstr ""
#: .\cookbook\templates\system.html:137
msgid ""
"\n"
" Migrations should never fail!\n"
" Failed migrations will likely cause major parts of the app to "
"not function correctly.\n"
" If a migration fails make sure you are on the latest version and "
"if so please post the migration log and the overview below in a GitHub "
"issue.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "False"
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "True"
msgstr ""
#: .\cookbook\templates\system.html:268
msgid "Hide"
msgstr ""
#: .\cookbook\templates\system.html:271
msgid "Show"
msgstr ""
#: .\cookbook\templates\test2.html:6
msgid "Export Recipes"
msgstr "Tuo Reseptejä"
#: .\cookbook\templates\test2.html:14 .\cookbook\templates\test2.html:20
msgid "Export"
msgstr "Vienti"
#: .\cookbook\views\api.py:198 .\cookbook\views\api.py:326
msgid "Parameter updated_at incorrectly formatted"
msgstr ""
#: .\cookbook\views\api.py:351 .\cookbook\views\api.py:484
#, python-brace-format
msgid "No {self.basename} with id {pk} exists"
msgstr ""
#: .\cookbook\views\api.py:355
msgid "Cannot merge with the same object!"
msgstr ""
#: .\cookbook\views\api.py:362
#, python-brace-format
msgid "No {self.basename} with id {target} exists"
msgstr ""
#: .\cookbook\views\api.py:367
msgid "Cannot merge with child object!"
msgstr ""
#: .\cookbook\views\api.py:405
#, python-brace-format
msgid "{source.name} was merged successfully with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:411
#, python-brace-format
msgid "An error occurred attempting to merge {source.name} with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:493
#, python-brace-format
msgid "{child.name} was moved successfully to the root."
msgstr ""
#: .\cookbook\views\api.py:496 .\cookbook\views\api.py:514
msgid "An error occurred attempting to move "
msgstr ""
#: .\cookbook\views\api.py:499
msgid "Cannot move an object to itself!"
msgstr ""
#: .\cookbook\views\api.py:505
#, python-brace-format
msgid "No {self.basename} with id {parent} exists"
msgstr ""
#: .\cookbook\views\api.py:511
#, python-brace-format
msgid "{child.name} was moved successfully to parent {parent.name}"
msgstr ""
#: .\cookbook\views\api.py:992
#, python-brace-format
msgid "{obj.name} was removed from the shopping list."
msgstr ""
#: .\cookbook\views\api.py:997 .\cookbook\views\api.py:1627
#, python-brace-format
msgid "{obj.name} was added to the shopping list."
msgstr ""
#: .\cookbook\views\api.py:1239
msgid "Filter meal plans from date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1241
msgid "Filter meal plans to date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1244
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1404
msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1406
msgid "Query string matched (fuzzy) against object name."
msgstr ""
#: .\cookbook\views\api.py:1442
msgid ""
"Query string matched (fuzzy) against recipe name. In the future also "
"fulltext search."
msgstr ""
#: .\cookbook\views\api.py:1444
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr ""
#: .\cookbook\views\api.py:1445
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr ""
#: .\cookbook\views\api.py:1446
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1447
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1448
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1450
msgid "ID of food a recipe should have. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1451
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr ""
#: .\cookbook\views\api.py:1452
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1453
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr ""
#: .\cookbook\views\api.py:1454
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1456
msgid "ID of book a recipe should be in. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1457
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr ""
#: .\cookbook\views\api.py:1458
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1459
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr ""
#: .\cookbook\views\api.py:1460
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1462
msgid "ID of unit a recipe should have."
msgstr ""
#: .\cookbook\views\api.py:1464
msgid "Exact rating of recipe"
msgstr ""
#: .\cookbook\views\api.py:1465
msgid "Rating a recipe should have or greater."
msgstr ""
#: .\cookbook\views\api.py:1466
msgid "Rating a recipe should have or smaller."
msgstr ""
#: .\cookbook\views\api.py:1468
msgid "Filter recipes cooked X times."
msgstr ""
#: .\cookbook\views\api.py:1469
msgid "Filter recipes cooked X times or more."
msgstr ""
#: .\cookbook\views\api.py:1470
msgid "Filter recipes cooked X times or less."
msgstr ""
#: .\cookbook\views\api.py:1472
msgid "Filter recipes created on the given date."
msgstr ""
#: .\cookbook\views\api.py:1473
msgid "Filter recipes created on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1474
msgid "Filter recipes created on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1476 .\cookbook\views\api.py:1477
#: .\cookbook\views\api.py:1478
msgid "Filter recipes updated on the given date."
msgstr ""
#: .\cookbook\views\api.py:1480
msgid "Filter recipes last cooked on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1481
msgid "Filter recipes last cooked on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1483 .\cookbook\views\api.py:1484
msgid "Filter recipes lasts viewed on the given date."
msgstr ""
#: .\cookbook\views\api.py:1486
msgid "Filter recipes for ones created by the given user ID"
msgstr ""
#: .\cookbook\views\api.py:1487
msgid "If only internal recipes should be returned. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1488
msgid "Returns the results in randomized order. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1490
msgid ""
"Determines the order of the results. Options are: score,-score,name,-name,"
"lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-"
"created_at,lastviewed,-lastviewed"
msgstr ""
#: .\cookbook\views\api.py:1492
msgid "Returns new results first in search results. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1493
msgid ""
"Returns the given number of recently viewed recipes before search results "
"(if given)"
msgstr ""
#: .\cookbook\views\api.py:1494
msgid "ID of a custom filter. Returns all recipes matched by that filter."
msgstr ""
#: .\cookbook\views\api.py:1495
msgid "Filter recipes that can be made with OnHand food. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1773
msgid ""
"Return the PropertyTypes matching the property category. Repeat for "
"multiple."
msgstr ""
#: .\cookbook\views\api.py:1804 .\cookbook\views\api.py:1860
msgid "Returns only entries associated with the given mealplan id"
msgstr ""
#: .\cookbook\views\api.py:1858
msgid ""
"Returns only elements updated after the given timestamp in ISO 8601 format."
msgstr ""
#: .\cookbook\views\api.py:2031
msgid ""
"Return the Automations matching the automation type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2048
msgid ""
"Text field to store data that gets carried over to the UserSpace created "
"from the InviteLink"
msgstr ""
#: .\cookbook\views\api.py:2049
msgid "Only return InviteLinks that have not been used yet."
msgstr ""
#: .\cookbook\views\api.py:2076
msgid "Return the CustomFilters matching the model type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2176
msgid "Nothing to do."
msgstr ""
#: .\cookbook\views\api.py:2222
msgid "Invalid Url"
msgstr ""
#: .\cookbook\views\api.py:2228
msgid "Connection Refused."
msgstr ""
#: .\cookbook\views\api.py:2232
msgid "Bad URL Schema."
msgstr ""
#: .\cookbook\views\api.py:2257
msgid "No usable data could be found."
msgstr ""
#: .\cookbook\views\api.py:2286 .\cookbook\views\api.py:2434
msgid "You must select an AI provider to perform your request."
msgstr ""
#: .\cookbook\views\api.py:2293 .\cookbook\views\api.py:2441
msgid ""
"You don't have any credits remaining to use AI or AI features are not "
"enabled for your space."
msgstr ""
#: .\cookbook\views\api.py:2499 .\cookbook\views\api.py:2667
msgid "File is above space limit"
msgstr ""
#: .\cookbook\views\api.py:2522 .\cookbook\views\api.py:2684
msgid "Importing is not implemented for this provider"
msgstr ""
#: .\cookbook\views\api.py:2548
msgid ""
"The PDF Exporter is not enabled on this instance as it is still in an "
"experimental state."
msgstr ""
#: .\cookbook\views\api.py:2842
msgid "This feature is not yet available in the hosted version of tandoor!"
msgstr ""
#: .\cookbook\views\api.py:2863
msgid "Sync successful!"
msgstr ""
#: .\cookbook\views\api.py:2866
msgid "Error synchronizing with Storage"
msgstr ""
#: .\cookbook\views\views.py:89
msgid "This feature is not available in the demo version!"
msgstr ""
#: .\cookbook\views\views.py:110
msgid ""
"You have successfully created your own recipe space. Start by adding some "
"recipes or invite other people to join you."
msgstr ""
#: .\cookbook\views\views.py:171
#, python-format
msgid "PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!"
msgstr ""
#: .\cookbook\views\views.py:174
#, python-format
msgid "You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended"
msgstr ""
#: .\cookbook\views\views.py:178
msgid "Unable to determine PostgreSQL version."
msgstr ""
#: .\cookbook\views\views.py:182
msgid ""
"This application is not running with a Postgres database backend. This is ok "
"but not recommended as some features only work with postgres databases."
msgstr ""
#: .\cookbook\views\views.py:296
msgid ""
"The setup page can only be used to create the first "
"user! If you have forgotten your superuser credentials "
"please consult the django documentation on how to reset passwords."
msgstr ""
#: .\cookbook\views\views.py:304
msgid "Passwords dont match!"
msgstr ""
#: .\cookbook\views\views.py:312
msgid "User has been created, please login!"
msgstr ""
#: .\cookbook\views\views.py:352
msgid ""
"Reporting share links is not enabled for this instance. Please notify the "
"page administrator to report problems."
msgstr ""
#: .\cookbook\views\views.py:357
msgid ""
"Recipe sharing link has been disabled! For additional information please "
"contact the page administrator."
msgstr ""
#: .\cookbook\views\views.py:383
msgid "Manage recipes, shopping list, meal plans and more."
msgstr ""
#: .\cookbook\views\views.py:397 .\cookbook\views\views.py:398
msgid "Plan"
msgstr ""
#: .\cookbook\views\views.py:399
msgid "View your meal Plan"
msgstr ""
#: .\cookbook\views\views.py:418
msgid "View your shopping lists"
msgstr "Katso ostoslistaasi"
#~ msgid ""
#~ "Both fields are optional. If none are given the username will be "
#~ "displayed instead"
#~ msgstr ""
#~ "Molemmat kentät ovat valinnaisia. Jos niitä ei anneta, käyttäjänimi "
#~ "näytetään sen sijaan"
#~ msgid "Name"
#~ msgstr "Nimi"
#~ msgid "Keywords"
#~ msgstr "Avainsanat"
#~ msgid "Preparation time in minutes"
#~ msgstr "Esivalmistelu aika minuuteissa"
#~ msgid "Waiting time (cooking/baking) in minutes"
#~ msgstr "Odotusaika (paisto / keitto) minuuteissa"
#~ msgid "Path"
#~ msgstr "Polku"
#~ msgid "Storage UID"
#~ msgstr "Varasto UID"
#~ msgid "Add your comment: "
#~ msgstr "Lisää kommenttisi: "
#~ msgid "Leave empty for dropbox and enter app password for nextcloud."
#~ msgstr "Jätä tyhjäksi dropboxia varten ja anna salasana nextcloudia varten."
#~ msgid "Leave empty for nextcloud and enter api token for dropbox."
#~ msgstr "Jätä tyhjäksi nextcloudia varten ja anna dropbox api-avain tähän."
#~ msgid ""
#~ "Leave empty for dropbox and enter only base url for nextcloud (/"
#~ "remote.php/webdav/ is added automatically)"
#~ msgstr ""
#~ "Jätä tyhjäksi dropboxia varten ja anna nextcloudin osoite tähän (/"
#~ "remote.php/webdav/ lisätään automaattisesti)"
#~ msgid ""
#~ "Long Lived Access Token for your HomeAssistant instance"
#~ msgstr ""
#~ "Pitkäikäinen Käyttöoikeustunnus \"Long-Lived Access Token\" "
#~ "sinun HomeAssistant ohjelmistoon"
#~ msgid "Something like http://homeassistant.local:8123/api"
#~ msgstr "Jotain kuin http://homeassistant.local:8123/api"
#~ msgid "http://homeassistant.local:8123/api for example"
#~ msgstr "http://homeassistant.local:8123/api esimerkiksi"
#~ msgid "Storage"
#~ msgstr "Varasto"
#~ msgid "Active"
#~ msgstr "Aktiivinen"
#~ msgid "Search String"
#~ msgstr "Hakusana"
#~ msgid "File ID"
#~ msgstr "Tiedosto ID"
#~ msgid ""
#~ "Determines how fuzzy a search is if it uses trigram similarity matching "
#~ "(e.g. low values mean more typos are ignored)."
#~ msgstr ""
#~ "Määrittää, kuinka sumea haku on, jos se käyttää trigrammien "
#~ "samankaltaisuussovitusta (esim. pienet arvot tarkoittavat, että enemmän "
#~ "kirjoitusvirheitä jätetään huomiotta)."
#~ msgid ""
#~ "Select type method of search. Click here "
#~ "for full description of choices."
#~ msgstr ""
#~ "Valitse haku tavan menetelmä. Paina tästä "
#~ "saadaksesi täydellisen kuvauksen valinnoista."
#~ msgid ""
#~ "Use fuzzy matching on units, keywords and ingredients when editing and "
#~ "importing recipes."
#~ msgstr ""
#~ "Käytä sumeaa hakua yksiköissä, avainsanoissa ja ainesosissa , kun "
#~ "muokkaat ja tuot reseptejä."
#~ msgid ""
#~ "Fields to search ignoring accents. Selecting this option can improve or "
#~ "degrade search quality depending on language"
#~ msgstr ""
#~ "Hakukentät aksenttimerkit huomioimatta. Tämän vaihtoehdon valitseminen "
#~ "voi parantaa tai huonontaa haun laatua kielestä riippuen"
#~ msgid ""
#~ "Fields to search for partial matches. (e.g. searching for 'Pie' will "
#~ "return 'pie' and 'piece' and 'soapie')"
#~ msgstr ""
#~ "Kentät osittaisten osumien etsimiseen. (esim. haku \"pii\" palauttaa "
#~ "sanat \"piirakka\" ja \"omenapiiras\" ja \"nakkipiilo\")"
#~ msgid ""
#~ "Fields to search for beginning of word matches. (e.g. searching for 'sa' "
#~ "will return 'salad' and 'sandwich')"
#~ msgstr ""
#~ "Kentät, joilla etsitään sana osumien alkua. (esim. haku sanalla \"sa\" "
#~ "palauttaa sanat \"salaatti\" ja \"savuporopasta\")"
#~ msgid ""
#~ "Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find "
#~ "'recipe'.) Note: this option will conflict with 'web' and 'raw' methods "
#~ "of search."
#~ msgstr ""
#~ "\"Sumean\" haun kentät. (esim. hakusanalla \"respti\" löytyy \"resepti "
#~ "\".) Huomautus: tämä vaihtoehto on ristiriidassa \"web\"- ja \"raw\"-"
#~ "hakumenetelmien kanssa."
#~ msgid ""
#~ "Fields to full text search. Note: 'web', 'phrase', and 'raw' search "
#~ "methods only function with fulltext fields."
#~ msgstr ""
#~ "Kentät koko tekstihakuun. Huomautus: \"Web\", \"phrase\" ja \"raw\" "
#~ "hakumenetelmät toimivat vain koko tekstikentissä."
#~ msgid "Search Method"
#~ msgstr "Hakumenetelmä"
#~ msgid "Fuzzy Lookups"
#~ msgstr "Sumeat Haut"
#, fuzzy
#~ msgid "Ignore Accent"
#~ msgstr "Ohita aksentti"
#~ msgid "Partial Match"
#~ msgstr "Osittainen Vastaavuus"
#~ msgid "Starts With"
#~ msgstr "Alkaa"
#~ msgid "Fuzzy Search"
#~ msgstr "Sumea Haku"
#~ msgid "Full Text"
#~ msgstr "Koko Teksti"
#~ msgid "Delete"
#~ msgstr "Poista"
#~ msgid "Settings"
#~ msgstr "Asetukset"
#~ msgid "Email"
#~ msgstr "Sähköposti"
#~ msgid "Password"
#~ msgstr "Salasana"
#~ msgid "Foods"
#~ msgstr "Ruuat"
#~ msgid "Units"
#~ msgstr "Yksiköt"
#~ msgid "Supermarket"
#~ msgstr "Kauppa"
#~ msgid "Files"
#~ msgstr "Tiedostot"
#~ msgid "History"
#~ msgstr "Historia"
#~ msgid "Ingredient Editor"
#~ msgstr "Ainesosien muokkaus"
#~ msgid "Properties"
#~ msgstr "Ominaisuudet"
#~ msgid "Unit Conversions"
#~ msgstr "Yksikkömuunnokset"
#~ msgid "Create"
#~ msgstr "Luo"
#~ msgid "External Recipes"
#~ msgstr "Ulkoiset Reseptit"
#~ msgid "Space Settings"
#~ msgstr "Tila Asetukset"
#~ msgid "Admin"
#~ msgstr "Ylläpitäjä"
#~ msgid "GitHub"
#~ msgstr "GitHub"
#~ msgid "Translate Tandoor"
#~ msgstr "Käännä Tandoor"
#~ msgid "API Browser"
#~ msgstr "API -selain"
#~ msgid "Log out"
#~ msgstr "Kirjaudu ulos"
#~ msgid "Upgrade Now"
#~ msgstr "Päivitä Nyt"
#~ msgid "Save"
#~ msgstr "Tallenna"
#~ msgid "Show Recipes"
#~ msgstr "Näytä Reseptit"
#~ msgid "Importing Recipes"
#~ msgstr "Tuodaan Reseptejä"
#~ msgid ""
#~ "This can take a few minutes, depending on the number of recipes in sync, "
#~ "please wait."
#~ msgstr ""
#~ "Tämä voi kestää muutaman minuutin, riippuen synkronoitujen reseptien "
#~ "määrästä. Ole hyvä ja odota."
#~ msgid "Import new Recipe"
#~ msgstr "Tuo uusi Resepti"
#~ msgid "Edit Recipe"
#~ msgstr "Muokkaa Reseptiä"
#~ msgid "Protected"
#~ msgstr "Suojattu"
#~ msgid "Edit"
#~ msgstr "Muokkaa"
#~ msgid "View"
#~ msgstr "Katso"
#~ msgid "Delete original file"
#~ msgstr "Poista alkuperäinen tiedosto"
#~ msgid "List"
#~ msgstr "Lista"
#~ msgid "Filter"
#~ msgstr "Suodatin"
#~ msgid "Import all"
#~ msgstr "Tuo kaikki"
#~ msgid "New"
#~ msgstr "Uusi"
#~ msgid "previous"
#~ msgstr "edellinen"
#~ msgid "next"
#~ msgstr "seuraava"
#~ msgid "View Log"
#~ msgstr "Näytä loki"
#~ msgid "Cook Log"
#~ msgstr "Keittoloki"
#~ msgid "Import"
#~ msgstr "Tuo"
#~ msgid "Security Warning"
#~ msgstr "Turvallisuusvaroitus"
#~ msgid ""
#~ "Color of the top navigation bar. Not all colors work with all themes, "
#~ "just try them out!"
#~ msgstr ""
#~ "Ylänavigointipalkin väri. Ei kaikki värit toimi kaikkien teemojen kanssa; "
#~ "kokeile!"
#~ msgid ""
#~ "Default Unit to be used when inserting a new ingredient into a recipe."
#~ msgstr "Oletusmittayksikkö uuden aineksen lisäämisessä."
#~ msgid ""
#~ "Enables support for fractions in ingredient amounts (e.g. convert "
#~ "decimals to fractions automatically)"
#~ msgstr ""
#~ "Mahdollistaa ainesosien määrien murto-osien tuen (esim. muuntaa "
#~ "desimaalit murtoluvuiksi automaattisesti)"
#~ msgid "Display nutritional energy amounts in joules instead of calories"
#~ msgstr "Näytä ravitsemukselliset energiamäärä jouleina kalorien sijaan"
#~ msgid "Show recently viewed recipes on search page."
#~ msgstr "Näytä äskettäin katsotut reseptit hakusivulla."
#~ msgid "Number of decimals to round ingredients."
#~ msgstr "Ainesosien pyöristettävä desimaalien määrä."
#~ msgid ""
#~ "If you want to be able to create and see comments underneath recipes."
#~ msgstr "Jos haluat luoda ja nähdä kommentteja reseptien alla."
#~ msgid "You must provide at least a recipe or a title."
#~ msgstr "Sinun on annettava vähintään resepti tai otsikko."
#~ msgid "Rating"
#~ msgstr "Luokitus"
#~ msgid "Close"
#~ msgstr "Sulje"
#~ msgid "user"
#~ msgstr "käyttäjä"
================================================
FILE: cookbook/locale/fr/LC_MESSAGES/django.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
# Translators:
# vabene1111 , 2020
# nerdinator , 2020
# A G , 2020
# Grégoire Menuel , 2021
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-22 20:15+0200\n"
"PO-Revision-Date: 2026-01-21 17:46+0000\n"
"Last-Translator: Fymyte \n"
"Language-Team: French \n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 5.13.3\n"
#: .\cookbook\forms.py:50
msgid "Default"
msgstr "Par défaut"
#: .\cookbook\forms.py:77
msgid ""
"To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything."
msgstr ""
"Pour éviter les doublons, les recettes de même nom seront ignorées. Cocher "
"cette case pour tout importer."
#: .\cookbook\forms.py:108
msgid "Maximum number of users for this space reached."
msgstr "Nombre maximum d’utilisateurs atteint pour ce groupe."
#: .\cookbook\forms.py:114
msgid "Email address already taken!"
msgstr "Adresse mail déjà utilisée !"
#: .\cookbook\forms.py:121
msgid ""
"An email address is not required but if present the invite link will be sent "
"to the user."
msgstr ""
"Une adresse mail n’est pas requise, mais si elle est renseignée, le lien "
"d’invitation sera envoyé à l’utilisateur."
#: .\cookbook\forms.py:133
msgid "Name already taken."
msgstr "Nom déjà utilisé."
#: .\cookbook\forms.py:144 .\cookbook\forms.py:158
msgid "Accept Terms and Privacy"
msgstr "Accepter les conditions d’utilisation"
#: .\cookbook\helper\AllAuthCustomAdapter.py:41
msgid ""
"In order to prevent spam, the requested email was not send. Please wait a "
"few minutes and try again."
msgstr ""
"Pour éviter les courriers indésirables, l’email demandé n’a pas été envoyé. "
"Veuillez patienter quelques minutes et réessayer."
#: .\cookbook\helper\permission_helper.py:165
#: .\cookbook\helper\permission_helper.py:186 .\cookbook\views\views.py:138
msgid "You are not logged in and therefore cannot view this page!"
msgstr ""
"Vous n’êtes pas connecté(e) et ne pouvez donc pas afficher cette page !"
#: .\cookbook\helper\permission_helper.py:168
#: .\cookbook\helper\permission_helper.py:173
#: .\cookbook\helper\permission_helper.py:198
#: .\cookbook\helper\permission_helper.py:265
#: .\cookbook\helper\permission_helper.py:279
#: .\cookbook\helper\permission_helper.py:290
#: .\cookbook\helper\permission_helper.py:301
#: .\cookbook\helper\permission_helper.py:317
#: .\cookbook\helper\permission_helper.py:343
#: .\cookbook\helper\permission_helper.py:359
msgid "You do not have the required permissions to view this page!"
msgstr "Vous ne disposez pas de droits suffisants pour afficher cette page !"
#: .\cookbook\helper\permission_helper.py:191
#: .\cookbook\helper\permission_helper.py:214
#: .\cookbook\helper\permission_helper.py:236
#: .\cookbook\helper\permission_helper.py:251
msgid "You cannot interact with this object as it is not owned by you!"
msgstr ""
"Vous ne pouvez pas interagir avec cet objet car il appartient à un autre "
"utilisateur !"
#: .\cookbook\helper\permission_helper.py:420
msgid "You have reached the maximum number of recipes for your space."
msgstr "Vous avez atteint le nombre maximum de recettes pour votre groupe."
#: .\cookbook\helper\permission_helper.py:432
msgid "You have more users than allowed in your space."
msgstr ""
"Le nombre d’utilisateurs dans votre groupe dépasse le nombre d’utilisateurs "
"autorisé."
#: .\cookbook\helper\recipe_url_import.py:319
msgid "reverse rotation"
msgstr "sens inverse"
#: .\cookbook\helper\recipe_url_import.py:320
msgid "careful rotation"
msgstr "sens horloger"
#: .\cookbook\helper\recipe_url_import.py:321
msgid "knead"
msgstr "pétrir"
#: .\cookbook\helper\recipe_url_import.py:322
msgid "thicken"
msgstr "épaissir"
#: .\cookbook\helper\recipe_url_import.py:323
msgid "warm up"
msgstr "réchauffer"
#: .\cookbook\helper\recipe_url_import.py:324
msgid "ferment"
msgstr "fermenter"
#: .\cookbook\helper\recipe_url_import.py:325
msgid "slow cook"
msgstr "Cuisson lente"
#: .\cookbook\helper\recipe_url_import.py:326
msgid "egg boiler"
msgstr "Cuiseur à oeuf"
#: .\cookbook\helper\recipe_url_import.py:327
msgid "kettle"
msgstr "Bouilloire"
#: .\cookbook\helper\recipe_url_import.py:328
msgid "blend"
msgstr "mixe"
#: .\cookbook\helper\recipe_url_import.py:329
#, fuzzy
msgid "pre-clean"
msgstr "pré-nettoyé"
#: .\cookbook\helper\recipe_url_import.py:330
msgid "high temperature"
msgstr "haute température"
#: .\cookbook\helper\recipe_url_import.py:331
msgid "rice cooker"
msgstr "cuiseur à riz"
#: .\cookbook\helper\recipe_url_import.py:332
msgid "caramelize"
msgstr "caramélize"
#: .\cookbook\helper\recipe_url_import.py:333
msgid "peeler"
msgstr "éplucheur"
#: .\cookbook\helper\recipe_url_import.py:334
msgid "slicer"
msgstr "mandoline"
#: .\cookbook\helper\recipe_url_import.py:335
msgid "grater"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:336
msgid "spiralizer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:337
msgid "sous-vide"
msgstr "sous-vide"
#: .\cookbook\helper\shopping_helper.py:150
msgid "You must supply a servings size"
msgstr "Vous devez fournir un nombre de portions"
#: .\cookbook\helper\template_helper.py:97
#: .\cookbook\helper\template_helper.py:99
#: .\cookbook\helper\template_helper.py:101
msgid "Could not parse template code."
msgstr "Impossible d’analyser le code du modèle."
#: .\cookbook\integration\copymethat.py:44
#: .\cookbook\integration\melarecipes.py:37
msgid "Favorite"
msgstr "Favori"
#: .\cookbook\integration\copymethat.py:50
msgid "I made this"
msgstr "J'ai fait ça"
#: .\cookbook\integration\integration.py:238
msgid ""
"Importer expected a .zip file. Did you choose the correct importer type for "
"your data ?"
msgstr ""
"Un fichier .zip était attendu à l’importation. Avez-vous choisi le bon "
"format pour vos données ?"
#: .\cookbook\integration\integration.py:241
msgid ""
"An unexpected error occurred during the import. Please make sure you have "
"uploaded a valid file."
msgstr ""
"Une erreur imprévue est survenue durant l’importation. Vérifiez que vous "
"avez téléversé un fichier valide."
#: .\cookbook\integration\integration.py:246
msgid "The following recipes were ignored because they already existed:"
msgstr "Les recettes suivantes ont été ignorées car elles existaient déjà :"
#: .\cookbook\integration\integration.py:250
#, python-format
msgid "Imported %s recipes."
msgstr "%s recettes importées."
#: .\cookbook\integration\mealie1.py:210
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "Calories"
msgstr "Calories"
#: .\cookbook\integration\mealie1.py:211
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
msgid "Carbohydrates"
msgstr "Glucides"
#: .\cookbook\integration\mealie1.py:212
msgid "Cholesterol"
msgstr ""
#: .\cookbook\integration\mealie1.py:213
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
msgid "Fat"
msgstr "Matières grasses"
#: .\cookbook\integration\mealie1.py:214
msgid "Fiber"
msgstr ""
#: .\cookbook\integration\mealie1.py:215
#, fuzzy
#| msgid "Proteins"
msgid "Protein"
msgstr "Protéines"
#: .\cookbook\integration\mealie1.py:216
msgid "Saturated Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:217
msgid "Sodium"
msgstr ""
#: .\cookbook\integration\mealie1.py:218
msgid "Sugar"
msgstr ""
#: .\cookbook\integration\mealie1.py:219
msgid "Trans Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:220
msgid "Unsaturated Fat"
msgstr ""
#: .\cookbook\integration\openeats.py:28
msgid "Recipe source:"
msgstr "Source de la recette :"
#: .\cookbook\integration\paprika.py:49
msgid "Notes"
msgstr "Notes"
#: .\cookbook\integration\paprika.py:52
msgid "Nutritional Information"
msgstr "Informations nutritionnelles"
#: .\cookbook\integration\paprika.py:56
msgid "Source"
msgstr "Source"
#: .\cookbook\integration\recettetek.py:55
#: .\cookbook\integration\recipekeeper.py:70
msgid "Imported from"
msgstr "Importé depuis"
#: .\cookbook\integration\saffron.py:23
msgid "Servings"
msgstr "Portions"
#: .\cookbook\integration\saffron.py:25
msgid "Waiting time"
msgstr "Temps d’attente"
#: .\cookbook\integration\saffron.py:27
msgid "Preparation Time"
msgstr "Temps de préparation"
#: .\cookbook\integration\saffron.py:29 .\cookbook\templates\index.html:6
msgid "Cookbook"
msgstr "Livre de recettes"
#: .\cookbook\integration\saffron.py:31
msgid "Section"
msgstr "Rubrique"
#: .\cookbook\management\commands\fix_duplicate_properties.py:15
msgid "Fixes foods with "
msgstr "Corriger les aliments avec "
#: .\cookbook\management\commands\rebuildindex.py:14
msgid "Rebuilds full text search index on Recipe"
msgstr "Reconstruction de l’index de recherche en texte intégral de Tandoor"
#: .\cookbook\management\commands\rebuildindex.py:18
msgid "Only Postgresql databases use full text search, no index to rebuild"
msgstr ""
"Seules les bases de données Postgresql utilisent la recherche en texte "
"intégral, sans index à reconstruire"
#: .\cookbook\management\commands\rebuildindex.py:29
msgid "Recipe index rebuild complete."
msgstr "La reconstruction de l’index des recettes est terminée."
#: .\cookbook\management\commands\rebuildindex.py:31
msgid "Recipe index rebuild failed."
msgstr "La reconstruction de l’index des recettes a échoué."
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
msgid "Breakfast"
msgstr "Petit déjeuner"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:19
msgid "Lunch"
msgstr "Déjeuner"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:24
msgid "Dinner"
msgstr "Dîner"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:29 .\cookbook\models.py:971
msgid "Other"
msgstr "Autre"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "g"
msgstr "g"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "Proteins"
msgstr "Protéines"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "kcal"
msgstr "kcal"
#: .\cookbook\models.py:325
msgid ""
"Maximum file storage for space in MB. 0 for unlimited, -1 to disable file "
"upload."
msgstr ""
"Le stockage maximal de fichiers pour ce groupe en Mo. Mettre 0 pour ne pas "
"avoir de limite et -1 pour empêcher le téléversement de fichiers."
#: .\cookbook\models.py:513
msgid "Search"
msgstr "Rechercher"
#: .\cookbook\models.py:514
msgid "Meal-Plan"
msgstr "Menu de la semaine"
#: .\cookbook\models.py:515
msgid "Books"
msgstr "Livres"
#: .\cookbook\models.py:516 .\cookbook\views\views.py:416
#: .\cookbook\views\views.py:417
msgid "Shopping"
msgstr "Courses"
#: .\cookbook\models.py:967
msgid "Nutrition"
msgstr "Informations nutritionnelles"
#: .\cookbook\models.py:968
msgid "Allergen"
msgstr "Allergène"
#: .\cookbook\models.py:969
msgid "Price"
msgstr "Prix"
#: .\cookbook\models.py:970
msgid "Goal"
msgstr "Objectif"
#: .\cookbook\models.py:1467 .\cookbook\templates\search_info.html:28
msgid "Simple"
msgstr "Simple"
#: .\cookbook\models.py:1468 .\cookbook\templates\search_info.html:33
msgid "Phrase"
msgstr "Phrase"
#: .\cookbook\models.py:1469 .\cookbook\templates\search_info.html:38
msgid "Web"
msgstr "Internet"
#: .\cookbook\models.py:1470 .\cookbook\templates\search_info.html:47
msgid "Raw"
msgstr "Brut"
#: .\cookbook\models.py:1525
msgid "Food Alias"
msgstr "Aliment équivalent"
#: .\cookbook\models.py:1526
msgid "Unit Alias"
msgstr "Unité équivalente"
#: .\cookbook\models.py:1527
msgid "Keyword Alias"
msgstr "Mot-clé équivalent"
#: .\cookbook\models.py:1528
msgid "Description Replace"
msgstr "Remplacer la Description"
#: .\cookbook\models.py:1529
msgid "Instruction Replace"
msgstr "Remplacer l'instruction"
#: .\cookbook\models.py:1530
msgid "Never Unit"
msgstr "Aucune unité"
#: .\cookbook\models.py:1531
msgid "Transpose Words"
msgstr "Transposer les mots"
#: .\cookbook\models.py:1532
msgid "Food Replace"
msgstr "Aliment alternatif"
#: .\cookbook\models.py:1533
msgid "Unit Replace"
msgstr "Remplacer l'unité"
#: .\cookbook\models.py:1534
msgid "Name Replace"
msgstr "Remplacer le nom"
#: .\cookbook\models.py:1564
msgid "Recipe"
msgstr "Recette"
#: .\cookbook\models.py:1565
msgid "Food"
msgstr "Aliment"
#: .\cookbook\models.py:1566
msgid "Keyword"
msgstr "Mot-clé"
#: .\cookbook\serializer.py:262
msgid "File uploads are not enabled for this Space."
msgstr "Le téléversement de fichiers n’est pas autorisé pour ce groupe."
#: .\cookbook\serializer.py:273
msgid "You have reached your file upload limit."
msgstr "Vous avez atteint votre limite de téléversement de fichiers."
#: .\cookbook\serializer.py:281
msgid "The given file type is not allowed."
msgstr ""
#: .\cookbook\serializer.py:421 .\cookbook\views\views.py:94
#, fuzzy
#| msgid "You have reached the maximum number of recipes for your space."
msgid ""
"You have the reached the maximum amount of spaces that can be owned by you."
msgstr "Vous avez atteint le nombre maximum de recettes pour votre groupe."
#: .\cookbook\serializer.py:434
msgid "Space Name must be unique."
msgstr ""
#: .\cookbook\serializer.py:469
msgid "Cannot modify Space owner permission."
msgstr "Impossible de modifier les permissions du propriétaire de groupe."
#: .\cookbook\serializer.py:1596
msgid "Hello"
msgstr "Bonjour"
#: .\cookbook\serializer.py:1596
msgid "You have been invited by "
msgstr "Vous avez été invité par "
#: .\cookbook\serializer.py:1598
msgid " to join their Tandoor Recipes space "
msgstr " pour rejoindre leur groupe Tandoor Recipes "
#: .\cookbook\serializer.py:1600
msgid "Click the following link to activate your account: "
msgstr "Cliquez le lien suivant pour activer votre compte : "
#: .\cookbook\serializer.py:1602
msgid ""
"If the link does not work use the following code to manually join the space: "
msgstr ""
"Si le lien ne fonctionne pas, utilisez le code suivant pour rejoindre le "
"groupe manuellement : "
#: .\cookbook\serializer.py:1604
msgid "The invitation is valid until "
msgstr "L’invitation est valide jusqu’au "
#: .\cookbook\serializer.py:1606
msgid ""
"Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub "
msgstr ""
"Tandoor Recipes est un gestionnaire de recettes open source. Venez-voir "
"notre Github "
#: .\cookbook\serializer.py:1609
msgid "Tandoor Recipes Invite"
msgstr "Invitation Tandoor Recipes"
#: .\cookbook\serializer.py:1813
msgid "Existing shopping list to update"
msgstr "Liste de courses existante à mettre à jour"
#: .\cookbook\serializer.py:1815
msgid ""
"List of ingredient IDs from the recipe to add, if not provided all "
"ingredients will be added."
msgstr ""
"Liste d’identifiants d’ingrédient de la recette à ajouter, si non renseigné, "
"tous les ingrédients seront ajoutés."
#: .\cookbook\serializer.py:1817
msgid ""
"Providing a list_recipe ID and servings of 0 will delete that shopping list."
msgstr ""
"Fournir un identifiant de liste de courses et un nombre de portions de 0 "
"supprimera cette liste de courses."
#: .\cookbook\serializer.py:1826
msgid "Amount of food to add to the shopping list"
msgstr "Quantité d’aliments à ajouter à la liste de courses"
#: .\cookbook\serializer.py:1828
msgid "ID of unit to use for the shopping list"
msgstr "ID de l’unité à utiliser pour la liste de courses"
#: .\cookbook\serializer.py:1830
msgid "When set to true will delete all food from active shopping lists."
msgstr ""
"Lorsqu'il est défini sur \"true\", tous les aliments des listes de courses "
"actives seront supprimés."
#: .\cookbook\templates\404.html:5
msgid "404 Error"
msgstr "Erreur 404"
#: .\cookbook\templates\404.html:18
msgid "The page you are looking for could not be found."
msgstr "La page que vous recherchez est introuvable."
#: .\cookbook\templates\404.html:33
msgid "Take me Home"
msgstr "Page d’accueil"
#: .\cookbook\templates\404.html:35
msgid "Report a Bug"
msgstr "Signaler un bogue"
#: .\cookbook\templates\account\email.html:6
#: .\cookbook\templates\account\email.html:10
msgid "E-mail Addresses"
msgstr "Adresses mail"
#: .\cookbook\templates\account\email.html:12
msgid "The following e-mail addresses are associated with your account:"
msgstr "Les adresses mail suivantes sont associées à votre compte :"
#: .\cookbook\templates\account\email.html:29
msgid "Verified"
msgstr "Vérifiée"
#: .\cookbook\templates\account\email.html:31
msgid "Unverified"
msgstr "Non vérifiée"
#: .\cookbook\templates\account\email.html:33
msgid "Primary"
msgstr "Principale"
#: .\cookbook\templates\account\email.html:40
msgid "Make Primary"
msgstr "Mettre en principale"
#: .\cookbook\templates\account\email.html:42
msgid "Re-send Verification"
msgstr "Renvoyer le mail de vérification"
#: .\cookbook\templates\account\email.html:43
#: .\cookbook\templates\socialaccount\connections.html:36
msgid "Remove"
msgstr "Supprimer"
#: .\cookbook\templates\account\email.html:51
msgid "Warning:"
msgstr "Avertissement :"
#: .\cookbook\templates\account\email.html:51
msgid ""
"You currently do not have any e-mail address set up. You should really add "
"an e-mail address so you can receive notifications, reset your password, etc."
msgstr ""
"Vous n’avez pas encore d’adresse mail associée. Vous devriez en ajouter une "
"afin de recevoir les notifications, réinitialiser votre mot de passe, etc."
#: .\cookbook\templates\account\email.html:57
msgid "Add E-mail Address"
msgstr "Ajouter une adresse mail"
#: .\cookbook\templates\account\email.html:62
msgid "Add E-mail"
msgstr "Ajouter une adresse mail"
#: .\cookbook\templates\account\email.html:72
msgid "Do you really want to remove the selected e-mail address?"
msgstr "Voulez-vous vraiment supprimer l’adresse mail sélectionnée ?"
#: .\cookbook\templates\account\email_confirm.html:6
#: .\cookbook\templates\account\email_confirm.html:10
msgid "Confirm E-mail Address"
msgstr "Confirmer l’adresse mail"
#: .\cookbook\templates\account\email_confirm.html:16
#, python-format
msgid ""
"Please confirm that\n"
" %(email)s is an e-mail address "
"for user %(user_display)s\n"
" ."
msgstr ""
"Confirmez SVP que\n"
" est une adresse mail de "
"l’utilisateur %(user_display)s\n"
" ."
#: .\cookbook\templates\account\email_confirm.html:22
msgid "Confirm"
msgstr "Confirmer"
#: .\cookbook\templates\account\email_confirm.html:29
#, python-format
msgid ""
"This e-mail confirmation link expired or is invalid. Please\n"
" issue a new e-mail confirmation "
"request."
msgstr ""
"Ce lien de confirmation reçu par mail est expiré ou non valide. Veuillez\n"
" demander une nouvelle vérification "
"par mail."
#: .\cookbook\templates\account\login.html:8
#: .\cookbook\templates\openid\login.html:8
msgid "Login"
msgstr "Connexion"
#: .\cookbook\templates\account\login.html:15
#: .\cookbook\templates\account\login.html:31
#: .\cookbook\templates\account\password_reset.html:39
#: .\cookbook\templates\account\password_reset_done.html:31
#: .\cookbook\templates\account\signup.html:68
#: .\cookbook\templates\account\signup_closed.html:15
#: .\cookbook\templates\openid\login.html:15
#: .\cookbook\templates\openid\login.html:26
#: .\cookbook\templates\socialaccount\authentication_error.html:15
msgid "Sign In"
msgstr "Connexion"
#: .\cookbook\templates\account\login.html:34
#: .\cookbook\templates\account\password_reset.html:41
#: .\cookbook\templates\account\password_reset_done.html:33
#: .\cookbook\templates\socialaccount\signup.html:8
#: .\cookbook\templates\socialaccount\signup.html:56
msgid "Sign Up"
msgstr "S’inscrire"
#: .\cookbook\templates\account\login.html:38
msgid "Lost your password?"
msgstr "Mot de passe perdu ?"
#: .\cookbook\templates\account\login.html:39
#: .\cookbook\templates\account\password_reset.html:29
msgid "Reset My Password"
msgstr "Réinitialiser le mot de passe"
#: .\cookbook\templates\account\login.html:50
msgid "Social Login"
msgstr "Connexion par réseau social"
#: .\cookbook\templates\account\login.html:51
msgid "You can use any of the following providers to sign in."
msgstr "Vous pouvez utiliser les comptes suivants pour vous connecter."
#: .\cookbook\templates\account\logout.html:5
#: .\cookbook\templates\account\logout.html:9
#: .\cookbook\templates\account\logout.html:18
msgid "Sign Out"
msgstr "Déconnexion"
#: .\cookbook\templates\account\logout.html:11
msgid "Are you sure you want to sign out?"
msgstr "Êtes-vous sûr(e) de vouloir vous déconnecter ?"
#: .\cookbook\templates\account\password_change.html:6
#: .\cookbook\templates\account\password_change.html:10
#: .\cookbook\templates\account\password_change.html:15
#: .\cookbook\templates\account\password_reset_from_key.html:7
#: .\cookbook\templates\account\password_reset_from_key.html:13
#: .\cookbook\templates\account\password_reset_from_key_done.html:7
#: .\cookbook\templates\account\password_reset_from_key_done.html:13
msgid "Change Password"
msgstr "Modifier le mot de passe"
#: .\cookbook\templates\account\password_change.html:16
msgid "Forgot Password?"
msgstr "Mot de passe oublié ?"
#: .\cookbook\templates\account\password_reset.html:7
#: .\cookbook\templates\account\password_reset.html:13
#: .\cookbook\templates\account\password_reset_done.html:7
#: .\cookbook\templates\account\password_reset_done.html:18
msgid "Password Reset"
msgstr "Réinitialiser le mot de passe"
#: .\cookbook\templates\account\password_reset.html:24
msgid ""
"Forgotten your password? Enter your e-mail address below, and we'll send you "
"an e-mail allowing you to reset it."
msgstr ""
"Mot de passe oublié ? Saisissez votre adresse mail ci-dessous et vous "
"recevrez un mail permettant de le réinitialiser."
#: .\cookbook\templates\account\password_reset.html:32
msgid "Password reset is disabled on this instance."
msgstr ""
"La réinitialisation de mot de passe est désactivée pour cette installation."
#: .\cookbook\templates\account\password_reset_done.html:25
msgid ""
"We have sent you an e-mail. Please contact us if you do not receive it "
"within a few minutes."
msgstr ""
"Un email a été envoyé. Contactez-nous si vous ne le recevez pas dans les "
"minutes à suivre."
#: .\cookbook\templates\account\password_reset_from_key.html:13
msgid "Bad Token"
msgstr "Mauvais jeton"
#: .\cookbook\templates\account\password_reset_from_key.html:25
#, python-format
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used.\n"
" Please request a new "
"password reset."
msgstr ""
"Le lien de changement du mot de passe n’est pas valide, probablement parce "
"qu’il a déjà été utilisé.\n"
" Merci de demander un nouveau changement de mot de passe."
#: .\cookbook\templates\account\password_reset_from_key.html:33
msgid "change password"
msgstr "modifier le mot de passe"
#: .\cookbook\templates\account\password_reset_from_key.html:36
#: .\cookbook\templates\account\password_reset_from_key_done.html:19
msgid "Your password is now changed."
msgstr "Votre mot de passe a été modifié."
#: .\cookbook\templates\account\password_set.html:6
#: .\cookbook\templates\account\password_set.html:10
#: .\cookbook\templates\account\password_set.html:15
msgid "Set Password"
msgstr "Définir un mot de passe"
#: .\cookbook\templates\account\signup.html:5
msgid "Register"
msgstr "S’inscrire"
#: .\cookbook\templates\account\signup.html:11
msgid "Create an Account"
msgstr "Créer un compte"
#: .\cookbook\templates\account\signup.html:41
msgid "I accept the follwoing"
msgstr "J’accepte les"
#: .\cookbook\templates\account\signup.html:44
#: .\cookbook\templates\socialaccount\signup.html:35
msgid "Terms and Conditions"
msgstr "conditions d’utilisation"
#: .\cookbook\templates\account\signup.html:47
#: .\cookbook\templates\socialaccount\signup.html:38
msgid "and"
msgstr "et"
#: .\cookbook\templates\account\signup.html:51
#: .\cookbook\templates\socialaccount\signup.html:42
msgid "Privacy Policy"
msgstr "la politique de confidentialité"
#: .\cookbook\templates\account\signup.html:64
msgid "Create User"
msgstr "Créer un utilisateur"
#: .\cookbook\templates\account\signup.html:68
msgid "Already have an account?"
msgstr "Vous avez déjà un compte ?"
#: .\cookbook\templates\account\signup_closed.html:5
#: .\cookbook\templates\account\signup_closed.html:11
msgid "Sign Up Closed"
msgstr "Inscriptions closes"
#: .\cookbook\templates\account\signup_closed.html:13
msgid "We are sorry, but the sign up is currently closed."
msgstr "Nous sommes désolés, mais les inscriptions sont closes pour le moment."
#: .\cookbook\templates\frontend\tandoor.html:15
#, fuzzy
#| msgid "Tandoor Recipes Invite"
msgid "Tandoor Recipe Manager"
msgstr "Invitation Tandoor Recipes"
#: .\cookbook\templates\index.html:28
msgid "Search recipe ..."
msgstr "Rechercher une recette ..."
#: .\cookbook\templates\index.html:43
msgid "New Recipe"
msgstr "Nouvelle recette"
#: .\cookbook\templates\index.html:46
msgid "Import Recipe"
msgstr "Importer une recette"
#: .\cookbook\templates\index.html:52
msgid "Advanced Search"
msgstr "Recherche avancée"
#: .\cookbook\templates\index.html:56
msgid "Reset Search"
msgstr "Réinitialiser la recherche"
#: .\cookbook\templates\index.html:84
msgid "Last viewed"
msgstr "Dernières recettes vues"
#: .\cookbook\templates\index.html:86
msgid "Recipes"
msgstr "Recettes"
#: .\cookbook\templates\index.html:93
msgid "Log in to view recipes"
msgstr "Connectez-vous pour voir les recettes"
#: .\cookbook\templates\markdown_info.html:5
#: .\cookbook\templates\markdown_info.html:13
msgid "Markdown Info"
msgstr "Infos Markdown"
#: .\cookbook\templates\markdown_info.html:14
msgid ""
"\n"
" Markdown is lightweight markup language that can be used to format "
"plain text easily.\n"
" This site uses the Python Markdown library to\n"
" convert your text into nice looking HTML. Its full markdown "
"documentation can be found\n"
" here.\n"
" An incomplete but most likely sufficient documentation can be found "
"below.\n"
" "
msgstr ""
"\n"
" Markdown est un langage de balisage léger utilisé pour formatter du "
"texte facilement.\n"
" Ce site utilise la bibliothèque Python Markdown \n"
" pour convertir votre texte en un joli format HTML. Sa documentation "
"complète est consultable\n"
" ici.\n"
" Une documentation incomplète mais probablement suffisante se trouve "
"plus bas.\n"
" "
#: .\cookbook\templates\markdown_info.html:25
msgid "Headers"
msgstr "Titres"
#: .\cookbook\templates\markdown_info.html:54
msgid "Formatting"
msgstr "Mise en forme"
#: .\cookbook\templates\markdown_info.html:56
#: .\cookbook\templates\markdown_info.html:72
msgid "Line breaks are inserted by adding two spaces after the end of a line"
msgstr ""
"Vous insérez des sauts de ligne en ajoutant deux espaces après la fin d’une "
"ligne"
#: .\cookbook\templates\markdown_info.html:57
#: .\cookbook\templates\markdown_info.html:73
msgid "or by leaving a blank line in between."
msgstr "ou en laissant une ligne vide entre deux."
#: .\cookbook\templates\markdown_info.html:59
#: .\cookbook\templates\markdown_info.html:74
msgid "This text is bold"
msgstr "Ce texte est en gras"
#: .\cookbook\templates\markdown_info.html:60
#: .\cookbook\templates\markdown_info.html:75
msgid "This text is italic"
msgstr "Ce texte est en italique"
#: .\cookbook\templates\markdown_info.html:61
#: .\cookbook\templates\markdown_info.html:77
msgid "Blockquotes are also possible"
msgstr "Les citations groupées sont également possibles"
#: .\cookbook\templates\markdown_info.html:84
msgid "Lists"
msgstr "Listes"
#: .\cookbook\templates\markdown_info.html:85
msgid ""
"Lists can ordered or unordered. It is important to leave a blank line "
"before the list!"
msgstr ""
"Les listes peuvent être ordonnées ou non. Il est important de laisser une "
"ligne vide avant la liste!"
#: .\cookbook\templates\markdown_info.html:87
#: .\cookbook\templates\markdown_info.html:108
msgid "Ordered List"
msgstr "Liste ordonnée"
#: .\cookbook\templates\markdown_info.html:89
#: .\cookbook\templates\markdown_info.html:90
#: .\cookbook\templates\markdown_info.html:91
#: .\cookbook\templates\markdown_info.html:110
#: .\cookbook\templates\markdown_info.html:111
#: .\cookbook\templates\markdown_info.html:112
msgid "unordered list item"
msgstr "élément d’une liste non ordonnée"
#: .\cookbook\templates\markdown_info.html:93
#: .\cookbook\templates\markdown_info.html:114
msgid "Unordered List"
msgstr "Liste non ordonnée"
#: .\cookbook\templates\markdown_info.html:95
#: .\cookbook\templates\markdown_info.html:96
#: .\cookbook\templates\markdown_info.html:97
#: .\cookbook\templates\markdown_info.html:116
#: .\cookbook\templates\markdown_info.html:117
#: .\cookbook\templates\markdown_info.html:118
msgid "ordered list item"
msgstr "élément d’une liste ordonnée"
#: .\cookbook\templates\markdown_info.html:125
msgid "Images & Links"
msgstr "Images & Liens"
#: .\cookbook\templates\markdown_info.html:126
msgid ""
"Links can be formatted with Markdown. This application also allows to paste "
"links directly into markdown fields without any formatting."
msgstr ""
"Les liens peuvent être formattés avec Markdown. Cette application permet "
"également de coller des liens directement en Markdown sans formattage."
#: .\cookbook\templates\markdown_info.html:132
#: .\cookbook\templates\markdown_info.html:145
msgid "This will become an image"
msgstr "Ceci deviendra une image"
#: .\cookbook\templates\markdown_info.html:152
msgid "Tables"
msgstr "Tableaux"
#: .\cookbook\templates\markdown_info.html:153
msgid ""
"Markdown tables are hard to create by hand. It is recommended to use a table "
"editor like this one."
msgstr ""
"Les tableaux Markdown sont difficiles à créer à la main. Il est recommandé "
"d’utiliser un éditeur de tableau comme celui-ci."
""
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:171
#: .\cookbook\templates\markdown_info.html:177
msgid "Table"
msgstr "Tableau"
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:172
msgid "Header"
msgstr "En-tête"
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:178
msgid "Cell"
msgstr "Cellule"
#: .\cookbook\templates\no_groups_info.html:5
#: .\cookbook\templates\no_groups_info.html:12
msgid "No Permissions"
msgstr "Pas d’autorisations"
#: .\cookbook\templates\no_groups_info.html:17
msgid "You do not have any groups and therefor cannot use this application."
msgstr ""
"Vous ne faites pas partie d’un groupe et ne pouvez donc pas utiliser cette "
"application."
#: .\cookbook\templates\no_groups_info.html:18
#: .\cookbook\templates\no_perm_info.html:15
msgid "Please contact your administrator."
msgstr "Veuillez contacter l’administrateur."
#: .\cookbook\templates\no_perm_info.html:5
#: .\cookbook\templates\no_perm_info.html:12
msgid "No Permission"
msgstr "Pas d’autorisation"
#: .\cookbook\templates\no_perm_info.html:15
msgid ""
"You do not have the required permissions to view this page or perform this "
"action."
msgstr ""
"Vous n’avez pas la permission de voir cette page ou d’effectuer cette action."
#: .\cookbook\templates\offline.html:5
msgid "Offline"
msgstr "Hors ligne"
#: .\cookbook\templates\openid\login.html:27
#: .\cookbook\templates\socialaccount\authentication_error.html:27
msgid "Back"
msgstr "Retour"
#: .\cookbook\templates\rest_framework\api.html:5
msgid "Recipe Home"
msgstr "Page d’accueil"
#: .\cookbook\templates\rest_framework\api.html:11
msgid "API Documentation"
msgstr "Documentation API"
#: .\cookbook\templates\search_info.html:5
#: .\cookbook\templates\search_info.html:9
msgid "Search Settings"
msgstr "Paramètres de recherche"
#: .\cookbook\templates\search_info.html:10
msgid ""
"\n"
" Creating the best search experience is complicated and weighs "
"heavily on your personal configuration. \n"
" Changing any of the search settings can have significant impact on "
"the speed and quality of the results.\n"
" Search Methods, Trigrams and Full Text Search configurations are "
"only available if you are using Postgres for your database.\n"
" "
msgstr ""
"\n"
" La création d'une expérience de recherche optimale est complexe et "
"dépend fortement de votre configuration personnelle. \n"
" La modification de l'un des paramètres de recherche peut avoir un "
"impact significatif sur la vitesse et la qualité des résultats.\n"
" Les configurations Méthodes de recherche, Trigrammes et Recherche "
"texte intégral ne sont disponibles que si vous utilisez Postgres comme base "
"de données.\n"
" "
#: .\cookbook\templates\search_info.html:19
msgid "Search Methods"
msgstr "Méthodes de recherche"
#: .\cookbook\templates\search_info.html:23
msgid ""
" \n"
" Full text searches attempt to normalize the words provided to "
"match common variants. For example: 'forked', 'forking', 'forks' will all "
"normalize to 'fork'.\n"
" There are several methods available, described below, that will "
"control how the search behavior should react when multiple words are "
"searched.\n"
" Full technical details on how these operate can be viewed on Postgresql's website.\n"
" "
msgstr ""
" \n"
" Les recherches en texte intégral tentent de normaliser les mots "
"fournis pour qu'ils correspondent aux variantes courantes. Par exemple : "
"'forked', 'forking', 'forks' seront tous normalisés en 'fork'.\n"
" Il existe plusieurs méthodes, décrites ci-dessous, qui "
"permettent de contrôler la façon dont la recherche doit réagir lorsque "
"plusieurs mots sont recherchés.\n"
" Des détails techniques complets sur leur fonctionnement peuvent "
"être consultés sur le site Postgresql's website."
"a>\n"
" "
#: .\cookbook\templates\search_info.html:29
msgid ""
" \n"
" Simple searches ignore punctuation and common words such as "
"'the', 'a', 'and'. And will treat separate words as required.\n"
" Searching for 'apple or flour' will return any recipe that "
"includes both 'apple' and 'flour' anywhere in the fields that have been "
"selected for a full text search.\n"
" "
msgstr ""
" \n"
" Les recherches simples ignorent la ponctuation et les mots "
"courants tels que \"le\", \"et\", \"a\", et traiteront les mots séparés "
"comme il se doit.\n"
" Si vous recherchez \"pomme ou farine\", vous obtiendrez toutes "
"les recettes qui contiennent à la fois \"pomme\" et \"farine\" dans les "
"champs sélectionnés pour la recherche en texte intégral.\n"
" "
#: .\cookbook\templates\search_info.html:34
msgid ""
" \n"
" Phrase searches ignore punctuation, but will search for all of "
"the words in the exact order provided.\n"
" Searching for 'apple or flour' will only return a recipe that "
"includes the exact phrase 'apple or flour' in any of the fields that have "
"been selected for a full text search.\n"
" "
msgstr ""
" \n"
" Les recherches de phrases ignorent la ponctuation, mais "
"recherchent tous les mots dans l'ordre exact indiqué.\n"
" La recherche de \"pomme ou farine\" ne donnera que les recettes "
"qui contiennent l'expression exacte \"pomme ou farine\" dans l'un des champs "
"sélectionnés pour la recherche en texte intégral.\n"
" "
#: .\cookbook\templates\search_info.html:39
msgid ""
" \n"
" Web searches simulate functionality found on many web search "
"sites supporting special syntax.\n"
" Placing quotes around several words will convert those words "
"into a phrase.\n"
" 'or' is recognized as searching for the word (or phrase) "
"immediately before 'or' OR the word (or phrase) directly after.\n"
" '-' is recognized as searching for recipes that do not include "
"the word (or phrase) that comes immediately after. \n"
" For example searching for 'apple pie' or cherry -butter will "
"return any recipe that includes the phrase 'apple pie' or the word "
"'cherry' \n"
" in any field included in the full text search but exclude any "
"recipe that has the word 'butter' in any field included.\n"
" "
msgstr ""
" \n"
" Les recherches sur le Web simulent la fonctionnalité que l'on "
"trouve sur de nombreux sites de recherche sur le Web qui prennent en charge "
"une syntaxe spéciale.\n"
" En plaçant des guillemets autour de plusieurs mots, ces derniers "
"seront convertis en une phrase.\n"
" Le terme \"ou\" signifie que l'on recherche le mot (ou "
"l'expression) qui précède immédiatement \"ou\" OU le mot (ou l'expression) "
"qui suit immédiatement.\n"
" Le signe \"-\" indique que la recherche porte sur des recettes "
"qui ne comprennent pas le mot (ou la phrase) qui suit immédiatement. \n"
" Par exemple, si vous recherchez \"tarte aux pommes\" ou cerise -"
"beurre, vous obtiendrez toutes les recettes contenant l'expression \"tarte "
"aux pommes\" ou le mot \"cerise\". \n"
" dans tous les champs inclus dans la recherche en texte intégral, "
"mais exclure toute recette comportant le mot \"beurre\" dans tous les champs "
"inclus.\n"
" "
#: .\cookbook\templates\search_info.html:48
msgid ""
" \n"
" Raw search is similar to Web except will take puncuation "
"operators such as '|', '&' and '()'\n"
" "
msgstr ""
" \n"
" La recherche brute est similaire à la recherche sur le Web, mais "
"elle prend en compte les opérateurs de ponctuation tels que \"|\", \"&\" et "
"\"()\".\n"
" "
#: .\cookbook\templates\search_info.html:59
msgid ""
" \n"
" Another approach to searching that also requires Postgresql is "
"fuzzy search or trigram similarity. A trigram is a group of three "
"consecutive characters.\n"
" For example searching for 'apple' will create x trigrams 'app', "
"'ppl', 'ple' and will create a score of how closely words match the "
"generated trigrams.\n"
" One benefit of searching trigams is that a search for 'sandwich' "
"will find misspelled words such as 'sandwhich' that would be missed by other "
"methods.\n"
" "
msgstr ""
" \n"
" Une autre approche de la recherche qui nécessite également "
"Postgresql est la recherche floue ou la similarité des trigrammes. Un "
"trigramme est un groupe de trois caractères consécutifs.\n"
" Par exemple, la recherche de \"apple\" créera x trigrammes \"app"
"\", \"ppl\", \"ple\" et créera un score de la proximité des mots avec les "
"trigrammes générés.\n"
" L'un des avantages de la recherche par trigamme est qu'une "
"recherche sur \"sandwich\" permet de trouver des mots mal orthographiés tels "
"que \"sandwhich\", qui ne seraient pas détectés par d'autres méthodes.\n"
" "
#: .\cookbook\templates\search_info.html:69
msgid "Search Fields"
msgstr "Champs de recherche"
#: .\cookbook\templates\search_info.html:73
msgid ""
" \n"
" Unaccent is a special case in that it enables searching a field "
"'unaccented' for each search style attempting to ignore accented values. \n"
" For example when you enable unaccent for 'Name' any search "
"(starts with, contains, trigram) will attempt the search ignoring accented "
"characters.\n"
" \n"
" For the other options, you can enable search on any or all "
"fields and they will be combined together with an assumed 'OR'.\n"
" For example enabling 'Name' for Starts With, 'Name' and "
"'Description' for Partial Match and 'Ingredients' and 'Keywords' for Full "
"Search\n"
" and searching for 'apple' will generate a search that will "
"return recipes that have:\n"
" - A recipe name that starts with 'apple'\n"
" - OR a recipe name that contains 'apple'\n"
" - OR a recipe description that contains 'apple'\n"
" - OR a recipe that will have a full text search match ('apple' "
"or 'apples') in ingredients\n"
" - OR a recipe that will have a full text search match in "
"Keywords\n"
"\n"
" Combining too many fields in too many types of search can have a "
"negative impact on performance, create duplicate results or return "
"unexpected results.\n"
" For example, enabling fuzzy search or partial matches will "
"interfere with web search methods. \n"
" Searching for 'apple -pie' with fuzzy search and full text "
"search will return the recipe Apple Pie. Though it is not included in the "
"full text results, it does match the trigram results.\n"
" "
msgstr ""
" \n"
" Unaccent est un cas particulier car il permet de rechercher un "
"champ \"non accentué\" pour chaque style de recherche qui tente d'ignorer "
"les valeurs accentuées. \n"
" Par exemple, si vous activez l'option \"non accentué\" pour \"Nom"
"\", toute recherche (commence par, contient, trigramme) tentera d'ignorer "
"les caractères accentués.\n"
" \n"
" Pour les autres options, vous pouvez activer la recherche sur un "
"ou tous les champs et ils seront combinés ensemble avec un 'OR' présumé.\n"
" Par exemple, si vous activez l'option \"Nom\" pour l'option "
"\"Commence par\", \"Nom\" et \"Description\" pour l'option \"Correspondance "
"partielle\" et \"Ingrédients\" et \"Mots-clés\" pour l'option \"Recherche "
"complète\".\n"
" et que vous recherchez \"pomme\", vous obtiendrez les recettes "
"qui ont.. :\n"
" - un nom de recette qui commence par \"pomme\".\n"
" - OU un nom de recette qui contient 'pomme'.\n"
" - OU une description de recette qui contient 'pomme'.\n"
" - OU une recette qui aura une correspondance de recherche en "
"texte intégral ('pomme' ou 'pommes') dans les ingrédients\n"
" - OU une recette qui aura une correspondance de recherche en "
"texte intégral dans les mots-clés.\n"
"\n"
" La combinaison d'un trop grand nombre de champs dans un trop "
"grand nombre de types de recherche peut avoir un impact négatif sur les "
"performances, créer des résultats en double ou renvoyer des résultats "
"inattendus.\n"
" Par exemple, l'activation de la recherche floue ou des "
"correspondances partielles interfère avec les méthodes de recherche sur le "
"Web. \n"
" La recherche de \"apple -pie\" à l'aide d'une recherche floue et "
"d'une recherche en texte intégral donnera la recette de la tarte aux "
"pommes. Bien qu'elle ne soit pas incluse dans les résultats du texte "
"intégral, elle correspond aux résultats de la recherche par trigramme.\n"
" "
#: .\cookbook\templates\search_info.html:95
msgid "Search Index"
msgstr "Index de recherche"
#: .\cookbook\templates\search_info.html:99
msgid ""
" \n"
" Trigram search and Full Text Search both rely on database "
"indexes to perform effectively. \n"
" You can rebuild the indexes on all fields in the Admin page for "
"Recipes and selecting all recipes and running 'rebuild index for selected "
"recipes'\n"
" You can also rebuild indexes at the command line by executing "
"the management command 'python manage.py rebuildindex'\n"
" "
msgstr ""
" \n"
" La recherche par trigramme et la recherche en texte intégral "
"reposent toutes deux sur les index de la base de données pour fonctionner "
"efficacement. \n"
" Vous pouvez reconstruire les index de tous les champs dans la "
"page d'administration des recettes, en sélectionnant toutes les recettes et "
"en exécutant la commande 'rebuild index for selected recipes'.\n"
" Vous pouvez également reconstruire les index en ligne de "
"commande en exécutant la commande de gestion 'python manage.py "
"rebuildindex'.\n"
" "
#: .\cookbook\templates\setup.html:6
msgid "Cookbook Setup"
msgstr "Paramètres du livre de recettes"
#: .\cookbook\templates\setup.html:14
msgid "Setup"
msgstr "Paramètres"
#: .\cookbook\templates\setup.html:15
msgid ""
"To start using this application you must first create a superuser account."
msgstr ""
"Pour commencer à utiliser cette application, vous devez d’abord créer un "
"compte superutilisateur."
#: .\cookbook\templates\setup.html:20
msgid "Create Superuser account"
msgstr "Créer un compte superutilisateur"
#: .\cookbook\templates\socialaccount\authentication_error.html:7
#: .\cookbook\templates\socialaccount\authentication_error.html:23
msgid "Social Network Login Failure"
msgstr "Échec de la connexion au réseau social"
#: .\cookbook\templates\socialaccount\authentication_error.html:25
msgid ""
"An error occurred while attempting to login via your social network account."
msgstr ""
"Une erreur est survenue en essayant de vous connecter avec votre compte de "
"réseau social."
#: .\cookbook\templates\socialaccount\connections.html:4
#: .\cookbook\templates\socialaccount\connections.html:7
msgid "Account Connections"
msgstr "Comptes connectés"
#: .\cookbook\templates\socialaccount\connections.html:10
msgid ""
"You can sign in to your account using any of the following third party\n"
" accounts:"
msgstr ""
"Vous pouvez vous connecter à votre compte en utilisant un des \n"
" comptes tiers suivants :"
#: .\cookbook\templates\socialaccount\connections.html:44
msgid ""
"You currently have no social network accounts connected to this account."
msgstr ""
"Vous n’avez actuellement aucun compte de réseaux sociaux connecté à votre "
"compte."
#: .\cookbook\templates\socialaccount\connections.html:47
msgid "Add a 3rd Party Account"
msgstr "Ajouter un compte tiers"
#: .\cookbook\templates\socialaccount\login.html:5
#: .\cookbook\templates\socialaccount\signup.html:5
msgid "Signup"
msgstr "S’inscrire"
#: .\cookbook\templates\socialaccount\login.html:9
#, python-format
msgid "Connect %(provider)s"
msgstr "Connecter %(provider)s"
#: .\cookbook\templates\socialaccount\login.html:11
#, python-format
msgid "You are about to connect a new third party account from %(provider)s."
msgstr ""
"Vous êtes sur le point de connecter un nouveau compte tiers depuis "
"%(provider)s."
#: .\cookbook\templates\socialaccount\login.html:13
#, python-format
msgid "Sign In Via %(provider)s"
msgstr "Se connecter via %(provider)s"
#: .\cookbook\templates\socialaccount\login.html:15
#, python-format
msgid "You are about to sign in using a third party account from %(provider)s."
msgstr ""
"Vous êtes sur le point de vous connecter en utilisant un compte tiers depuis "
"%(provider)s."
#: .\cookbook\templates\socialaccount\login.html:20
msgid "Continue"
msgstr "Continuer"
#: .\cookbook\templates\socialaccount\signup.html:10
#, python-format
msgid ""
"You are about to use your\n"
" %(provider_name)s account to login to\n"
" %(site_name)s. As a final step, please complete the following form:"
msgstr ""
"Vous êtes sur le point d’utiliser\n"
" votre compte %(provider_name)s pour vous connecter à\n"
" %(site_name)s. Pour finaliser la requête, veuillez compléter le "
"formulaire suivant :"
#: .\cookbook\templates\socialaccount\signup.html:32
#, fuzzy
#| msgid "I accept the follwoing"
msgid "I accept the following"
msgstr "J’accepte les"
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:23
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:31
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:39
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:47
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:55
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:63
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:71
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:79
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:87
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:95
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:103
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:111
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:119
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:127
msgid "Sign in using"
msgstr "Se connecter avec"
#: .\cookbook\templates\space_overview.html:6
msgid "Overview"
msgstr "Aperçu"
#: .\cookbook\templates\space_overview.html:13
msgid "Space"
msgstr "Groupe"
#: .\cookbook\templates\space_overview.html:17
msgid ""
"Recipes, foods, shopping lists and more are organized in spaces of one or "
"more people."
msgstr ""
"Recettes, aliments, listes de courses et plus encore sont organisés en "
"groupes d’une ou plusieurs personnes."
#: .\cookbook\templates\space_overview.html:18
msgid ""
"You can either be invited into an existing space or create your own one."
msgstr "Vous pouvez être invité dans un groupe existant ou en créer un."
#: .\cookbook\templates\space_overview.html:25
msgid "Your Spaces"
msgstr "Vos groupes"
#: .\cookbook\templates\space_overview.html:53
msgid "Owner"
msgstr "Propriétaire"
#: .\cookbook\templates\space_overview.html:73
#: .\cookbook\templates\space_overview.html:83
msgid "Join Space"
msgstr "Rejoindre un groupe"
#: .\cookbook\templates\space_overview.html:76
msgid "Join an existing space."
msgstr "Rejoindre un groupe déjà existant."
#: .\cookbook\templates\space_overview.html:78
msgid ""
"To join an existing space either enter your invite token or click on the "
"invite link the space owner send you."
msgstr ""
"Pour rejoindre un groupe déjà existant, saisissez le jeton d’invitation ou "
"cliquez sur le lien d’invitation que le créateur du groupe vous a envoyé."
#: .\cookbook\templates\space_overview.html:91
#: .\cookbook\templates\space_overview.html:100
msgid "Create Space"
msgstr "Créer un groupe"
#: .\cookbook\templates\space_overview.html:94
msgid "Create your own recipe space."
msgstr "Créer votre propre groupe de partage de recettes."
#: .\cookbook\templates\space_overview.html:96
msgid "Start your own recipe space and invite other users to it."
msgstr ""
"Créez votre propre groupe de partage de recettes et invitez d’autres "
"utilisateurs à l’utiliser."
#: .\cookbook\templates\system.html:23
msgid "System"
msgstr "Système"
#: .\cookbook\templates\system.html:24
#, fuzzy
#| msgid ""
#| "\n"
#| " Django Recipes is an open source free software application. It "
#| "can be found on\n"
#| " GitHub.\n"
#| " Changelogs can be found here.\n"
#| " "
msgid ""
"\n"
" Tandoor Recipes is an open source free software application. It can "
"be found on\n"
" GitHub.\n"
" Changelogs can be found here.\n"
" "
msgstr ""
"\n"
" Django Recipes est un logiciel libre et open source. Retrouvez-le "
"sur \n"
" GitHub. \n"
" L'historique des mises à jour est accessible ici.\n"
" "
#: .\cookbook\templates\system.html:30
msgid "System Information"
msgstr "Informations système"
#: .\cookbook\templates\system.html:51
msgid ""
"\n"
" You need to execute version.py in your update "
"script to generate version information (done automatically in docker).\n"
" "
msgstr ""
"\n"
" Vous devez exécuter version.py dans votre script de "
"mise à jour pour générer les informations de version (automatique avec "
"docker).\n"
" "
#: .\cookbook\templates\system.html:56
msgid "Plugins"
msgstr ""
#: .\cookbook\templates\system.html:67
msgid "Media Serving"
msgstr "Publication des médias"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:123 .\cookbook\templates\system.html:134
msgid "Warning"
msgstr "Avertissement"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:125 .\cookbook\templates\system.html:134
msgid "Ok"
msgstr "OK"
#: .\cookbook\templates\system.html:70
msgid ""
"Serving media files directly using gunicorn/python is not recommend!\n"
" Please follow the steps described\n"
" here to update\n"
" your installation.\n"
" "
msgstr ""
"Publier les médias directement avec gunicorn/python n'est pas recommandé"
"b> !\n"
" Veuillez suivre les étapes décrites ici \n"
" pour mettre à jour votre installation.\n"
" "
#: .\cookbook\templates\system.html:76 .\cookbook\templates\system.html:91
#: .\cookbook\templates\system.html:104 .\cookbook\templates\system.html:115
#: .\cookbook\views\views.py:168
msgid "Everything is fine!"
msgstr "Tout est en ordre !"
#: .\cookbook\templates\system.html:80
msgid "Secret Key"
msgstr "Clé secrète"
#: .\cookbook\templates\system.html:84
msgid ""
"\n"
" You do not have a SECRET_KEY configured in your "
".env file. Django defaulted to the\n"
" standard key\n"
" provided with the installation which is publicly know and "
"insecure! Please set\n"
" SECRET_KEY int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" Vous n'avez pas de SECRET_KEY configuré dans votre "
"fichier.env. Django utilise par défaut\n"
" la clé standard fournie avec l'application qui est connue "
"publiquement et non sécurisée ! \n"
" Veuillez définir SECRET_KEY dans le fichier."
"env\n"
" "
#: .\cookbook\templates\system.html:94
msgid "Debug Mode"
msgstr "Mode debug"
#: .\cookbook\templates\system.html:98
msgid ""
"\n"
" This application is still running in debug mode. This is most "
"likely not needed. Turn of debug mode by\n"
" setting\n"
" DEBUG=0 int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" Cette application est toujours en mode debug. Ce n'est sûrement "
"pas nécessaire. Désactivez le mode debug\n"
" en définissant DEBUG=0 dans le fichier .env"
"code>.\n"
" "
#: .\cookbook\templates\system.html:107
msgid "Allowed Hosts"
msgstr "Hôtes autorisés"
#: .\cookbook\templates\system.html:111
msgid ""
"\n"
" Your allowed hosts are configured to allow every host. This "
"might be ok in some setups but should be avoided. Please see the docs about "
"this.\n"
" "
msgstr ""
"\n"
" Votre configuration autorise tous les hôtes, cela ok dans "
"certaines installations mais évité. Veuillez consulter les documentations à "
"ce sujet.\n"
" "
#: .\cookbook\templates\system.html:118
msgid "Database"
msgstr "Base de données"
#: .\cookbook\templates\system.html:121
msgid "Info"
msgstr "Info"
#: .\cookbook\templates\system.html:131 .\cookbook\templates\system.html:148
msgid "Migrations"
msgstr "Migrations"
#: .\cookbook\templates\system.html:137
msgid ""
"\n"
" Migrations should never fail!\n"
" Failed migrations will likely cause major parts of the app to "
"not function correctly.\n"
" If a migration fails make sure you are on the latest version and "
"if so please post the migration log and the overview below in a GitHub "
"issue.\n"
" "
msgstr ""
"\n"
" Les migrations de données ne devraient jamais échouer!\n"
" Les échecs de migrations vont causer des problèmes de "
"fonctionnement majeurs dans l’application..\n"
" Si une migration échoue, vérifiez que vous êtes sur la dernière "
"version et si c'est le cas veuillez créer un ticket sur GitHub avec le "
"contenu du journal de migration.\n"
" "
#: .\cookbook\templates\system.html:238
msgid "False"
msgstr "Faux"
#: .\cookbook\templates\system.html:238
msgid "True"
msgstr "Vrai"
#: .\cookbook\templates\system.html:268
msgid "Hide"
msgstr "Cacher"
#: .\cookbook\templates\system.html:271
msgid "Show"
msgstr "Afficher"
#: .\cookbook\templates\test2.html:6
msgid "Export Recipes"
msgstr "Exporter des recettes"
#: .\cookbook\templates\test2.html:14 .\cookbook\templates\test2.html:20
msgid "Export"
msgstr "Exporter"
#: .\cookbook\views\api.py:198 .\cookbook\views\api.py:326
msgid "Parameter updated_at incorrectly formatted"
msgstr "Le paramètre « update_at » n'est pas correctement formaté"
#: .\cookbook\views\api.py:351 .\cookbook\views\api.py:484
#, python-brace-format
msgid "No {self.basename} with id {pk} exists"
msgstr "Il n’existe aucun(e) {self.basename} avec l’identifiant {pk}"
#: .\cookbook\views\api.py:355
msgid "Cannot merge with the same object!"
msgstr "Impossible de fusionner un objet avec lui-même !"
#: .\cookbook\views\api.py:362
#, python-brace-format
msgid "No {self.basename} with id {target} exists"
msgstr "Il n’existe aucun(e) {self.basename} avec l’id {target}"
#: .\cookbook\views\api.py:367
msgid "Cannot merge with child object!"
msgstr "Impossible de fusionner avec l’objet enfant !"
#: .\cookbook\views\api.py:405
#, python-brace-format
msgid "{source.name} was merged successfully with {target.name}"
msgstr "{source.name} a été fusionné avec succès avec {target.name}"
#: .\cookbook\views\api.py:411
#, python-brace-format
msgid "An error occurred attempting to merge {source.name} with {target.name}"
msgstr ""
"Une erreur est survenue lors de la tentative de fusion de {source.name} avec "
"{target.name}"
#: .\cookbook\views\api.py:493
#, python-brace-format
msgid "{child.name} was moved successfully to the root."
msgstr "{child.name} a été déplacé avec succès vers la racine."
#: .\cookbook\views\api.py:496 .\cookbook\views\api.py:514
msgid "An error occurred attempting to move "
msgstr "Une erreur est survenue en essayant de déplacer "
#: .\cookbook\views\api.py:499
msgid "Cannot move an object to itself!"
msgstr "Impossible de déplacer un objet vers lui-même !"
#: .\cookbook\views\api.py:505
#, python-brace-format
msgid "No {self.basename} with id {parent} exists"
msgstr "Il n’existe aucun(e) {self.basename} avec l’id {parent}"
#: .\cookbook\views\api.py:511
#, python-brace-format
msgid "{child.name} was moved successfully to parent {parent.name}"
msgstr "{child.name} a été déplacé avec succès vers le parent {parent.name}"
#: .\cookbook\views\api.py:992
#, python-brace-format
msgid "{obj.name} was removed from the shopping list."
msgstr "{obj.name} a été supprimé(e) de la liste de courses."
#: .\cookbook\views\api.py:997 .\cookbook\views\api.py:1627
#, python-brace-format
msgid "{obj.name} was added to the shopping list."
msgstr "{obj.name} a été ajouté(e) à la liste de courses."
#: .\cookbook\views\api.py:1239
#, fuzzy
#| msgid "Filter meal plans from date (inclusive) in the format of YYYY-MM-DD."
msgid "Filter meal plans from date (inclusive)."
msgstr "Filtrer les repas depuis la date (incluse) avec le format YYYY-MM-DD."
#: .\cookbook\views\api.py:1241
#, fuzzy
#| msgid "Filter meal plans to date (inclusive) in the format of YYYY-MM-DD."
msgid "Filter meal plans to date (inclusive)."
msgstr ""
"Filtrer les plannings de repas depuis la date (incluse) avec le format YYYY-"
"MM-DD."
#: .\cookbook\views\api.py:1244
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
msgstr ""
"Filtrer le planning des repas avec l'identifiant MealType. Pour plusieurs "
"paramètres de répétition."
#: .\cookbook\views\api.py:1404
msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgstr ""
"Identifiant de la recette dont fait partie une étape. Pour plusieurs "
"paramètres de répétition."
#: .\cookbook\views\api.py:1406
msgid "Query string matched (fuzzy) against object name."
msgstr ""
"Correspondance (floue) entre la chaîne de requête et le nom de l'objet."
#: .\cookbook\views\api.py:1442
msgid ""
"Query string matched (fuzzy) against recipe name. In the future also "
"fulltext search."
msgstr ""
"La chaîne d'interrogation correspond (de manière floue) au nom de la "
"recette. À l'avenir, la recherche en texte intégral sera également possible."
#: .\cookbook\views\api.py:1444
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr ""
"ID du mot-clé qu'une recette doit avoir. Pour les paramètres à répétition "
"multiple. Equivalent à keywords_or"
#: .\cookbook\views\api.py:1445
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr ""
"ID des mots-clés, répéter pour plusieurs. Retourner les recettes avec "
"n'importe quel mot-clé"
#: .\cookbook\views\api.py:1446
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr ""
"ID des mots-clés, répéter pour plusieurs. Retourner les recettes contenant "
"tous les mots-clés."
#: .\cookbook\views\api.py:1447
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr ""
"ID des mots-clés, répéter pour plusieurs. Exclure les recettes contenant "
"l'un des mots-clés."
#: .\cookbook\views\api.py:1448
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr ""
"ID des mots-clés, répéter pour plusieurs. Exclure les recettes contenant "
"l'un des mots-clés."
#: .\cookbook\views\api.py:1450
msgid "ID of food a recipe should have. For multiple repeat parameter."
msgstr ""
"ID de l'aliment qu'une recette doit contenir. Pour les paramètres de "
"répétition multiples."
#: .\cookbook\views\api.py:1451
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr ""
"ID des aliments, répéter pour plusieurs. Retourner les recettes contenant "
"l'un des aliments"
#: .\cookbook\views\api.py:1452
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr ""
"ID des aliments, répéter pour plusieurs. Retourner les recettes avec tous "
"les aliments."
#: .\cookbook\views\api.py:1453
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr ""
"ID des aliments, répéter pour plusieurs. Exclure les recettes contenant l'un "
"des aliments."
#: .\cookbook\views\api.py:1454
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr ""
"ID des aliments, répéter pour plusieurs. Exclure les recettes contenant tous "
"les aliments."
#: .\cookbook\views\api.py:1456
msgid "ID of book a recipe should be in. For multiple repeat parameter."
msgstr ""
"ID du livre dans lequel une recette doit se trouver. Pour plusieurs "
"paramètres de répétition."
#: .\cookbook\views\api.py:1457
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr ""
"IDs de livre, répéter pour plusieurs livres. Renvoie les recettes dans "
"n'importe quel livre."
#: .\cookbook\views\api.py:1458
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr ""
"IDs de livre, répéter pour plusieurs livres. Renvoie les recettes dans tous "
"les livre."
#: .\cookbook\views\api.py:1459
#, fuzzy
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr ""
"Identifiants de livres : répéter pour plusieurs. Exclure les recettes de "
"l'un des livres."
#: .\cookbook\views\api.py:1460
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1462
msgid "ID of unit a recipe should have."
msgstr "ID de l'unité qu'une recette doit avoir."
#: .\cookbook\views\api.py:1464
msgid "Exact rating of recipe"
msgstr ""
#: .\cookbook\views\api.py:1465
#, fuzzy
#| msgid "ID of unit a recipe should have."
msgid "Rating a recipe should have or greater."
msgstr "ID de l'unité qu'une recette doit avoir."
#: .\cookbook\views\api.py:1466
#, fuzzy
#| msgid "ID of unit a recipe should have."
msgid "Rating a recipe should have or smaller."
msgstr "ID de l'unité qu'une recette doit avoir."
#: .\cookbook\views\api.py:1468
msgid "Filter recipes cooked X times."
msgstr ""
#: .\cookbook\views\api.py:1469
msgid "Filter recipes cooked X times or more."
msgstr ""
#: .\cookbook\views\api.py:1470
msgid "Filter recipes cooked X times or less."
msgstr ""
#: .\cookbook\views\api.py:1472
msgid "Filter recipes created on the given date."
msgstr ""
#: .\cookbook\views\api.py:1473
msgid "Filter recipes created on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1474
msgid "Filter recipes created on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1476 .\cookbook\views\api.py:1477
#: .\cookbook\views\api.py:1478
msgid "Filter recipes updated on the given date."
msgstr ""
#: .\cookbook\views\api.py:1480
msgid "Filter recipes last cooked on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1481
msgid "Filter recipes last cooked on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1483 .\cookbook\views\api.py:1484
msgid "Filter recipes lasts viewed on the given date."
msgstr ""
#: .\cookbook\views\api.py:1486
msgid "Filter recipes for ones created by the given user ID"
msgstr ""
#: .\cookbook\views\api.py:1487
msgid "If only internal recipes should be returned. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1488
msgid "Returns the results in randomized order. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1490
msgid ""
"Determines the order of the results. Options are: score,-score,name,-name,"
"lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-"
"created_at,lastviewed,-lastviewed"
msgstr ""
#: .\cookbook\views\api.py:1492
msgid "Returns new results first in search results. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1493
msgid ""
"Returns the given number of recently viewed recipes before search results "
"(if given)"
msgstr ""
#: .\cookbook\views\api.py:1494
msgid "ID of a custom filter. Returns all recipes matched by that filter."
msgstr ""
#: .\cookbook\views\api.py:1495
msgid "Filter recipes that can be made with OnHand food. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1773
msgid ""
"Return the PropertyTypes matching the property category. Repeat for "
"multiple."
msgstr ""
#: .\cookbook\views\api.py:1804 .\cookbook\views\api.py:1860
msgid "Returns only entries associated with the given mealplan id"
msgstr ""
#: .\cookbook\views\api.py:1858
msgid ""
"Returns only elements updated after the given timestamp in ISO 8601 format."
msgstr ""
#: .\cookbook\views\api.py:2031
msgid ""
"Return the Automations matching the automation type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2048
msgid ""
"Text field to store data that gets carried over to the UserSpace created "
"from the InviteLink"
msgstr ""
#: .\cookbook\views\api.py:2049
msgid "Only return InviteLinks that have not been used yet."
msgstr ""
#: .\cookbook\views\api.py:2076
msgid "Return the CustomFilters matching the model type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2176
msgid "Nothing to do."
msgstr "Rien à faire."
#: .\cookbook\views\api.py:2222
msgid "Invalid Url"
msgstr "Url non valide"
#: .\cookbook\views\api.py:2228
msgid "Connection Refused."
msgstr "Connexion refusée."
#: .\cookbook\views\api.py:2232
msgid "Bad URL Schema."
msgstr "Mauvais schéma d’URL."
#: .\cookbook\views\api.py:2257
msgid "No usable data could be found."
msgstr "Aucune information utilisable n'a été trouvée."
#: .\cookbook\views\api.py:2286 .\cookbook\views\api.py:2434
msgid "You must select an AI provider to perform your request."
msgstr ""
#: .\cookbook\views\api.py:2293 .\cookbook\views\api.py:2441
msgid ""
"You don't have any credits remaining to use AI or AI features are not "
"enabled for your space."
msgstr ""
#: .\cookbook\views\api.py:2499 .\cookbook\views\api.py:2667
msgid "File is above space limit"
msgstr ""
#: .\cookbook\views\api.py:2522 .\cookbook\views\api.py:2684
msgid "Importing is not implemented for this provider"
msgstr "L’importation n’est pas implémentée pour ce fournisseur"
#: .\cookbook\views\api.py:2548
msgid ""
"The PDF Exporter is not enabled on this instance as it is still in an "
"experimental state."
msgstr ""
"L'export PDF n'est pas activé sur cette instance car il est toujours au "
"statut expérimental."
#: .\cookbook\views\api.py:2842
msgid "This feature is not yet available in the hosted version of tandoor!"
msgstr ""
"Cette fonctionnalité n’est pas encore disponible dans la version hébergée de "
"Tandoor !"
#: .\cookbook\views\api.py:2863
msgid "Sync successful!"
msgstr "Synchronisation réussie !"
#: .\cookbook\views\api.py:2866
msgid "Error synchronizing with Storage"
msgstr "Erreur lors de la synchronisation avec le stockage"
#: .\cookbook\views\views.py:89
msgid "This feature is not available in the demo version!"
msgstr "Cette fonctionnalité n’est pas disponible dans la version d’essai !"
#: .\cookbook\views\views.py:110
msgid ""
"You have successfully created your own recipe space. Start by adding some "
"recipes or invite other people to join you."
msgstr ""
"Vous avez réussi à créer votre propre groupe de partage de recettes. "
"Commencez à ajoutez des recettes ou invitez d’autres personnes à vous "
"rejoindre."
#: .\cookbook\views\views.py:171
#, python-format
msgid "PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!"
msgstr ""
#: .\cookbook\views\views.py:174
#, python-format
msgid "You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended"
msgstr ""
#: .\cookbook\views\views.py:178
msgid "Unable to determine PostgreSQL version."
msgstr ""
#: .\cookbook\views\views.py:182
#, fuzzy
#| msgid ""
#| "\n"
#| " This application is not running with a Postgres database "
#| "backend. This is ok but not recommended as some\n"
#| " features only work with postgres databases.\n"
#| " "
msgid ""
"This application is not running with a Postgres database backend. This is ok "
"but not recommended as some features only work with postgres databases."
msgstr ""
"\n"
" Cette application ne tourne pas sur une base de données "
"Postgres. Ce n'est pas grave mais déconseillé\n"
" car certaines fonctionnalités ne fonctionnent qu'avec une base "
"de données Postgres.\n"
" "
#: .\cookbook\views\views.py:296
#, fuzzy
#| msgid ""
#| "The setup page can only be used to create the first user! If you have "
#| "forgotten your superuser credentials please consult the django "
#| "documentation on how to reset passwords."
msgid ""
"The setup page can only be used to create the first "
"user! If you have forgotten your superuser credentials "
"please consult the django documentation on how to reset passwords."
msgstr ""
"Cette page d’installation peut uniquement être utilisée pour créer le "
"premier utilisateur ! Si vous avez oublié vos identifiants de super-"
"utilisateur, counsultez la documentation Django pour savoir comment "
"réinitialiser le mot de passe."
#: .\cookbook\views\views.py:304
msgid "Passwords dont match!"
msgstr "Les mots de passe ne correspondent pas !"
#: .\cookbook\views\views.py:312
msgid "User has been created, please login!"
msgstr "L’utilisateur a été créé, veuillez vous connecter !"
#: .\cookbook\views\views.py:352
msgid ""
"Reporting share links is not enabled for this instance. Please notify the "
"page administrator to report problems."
msgstr ""
"Le signalement de liens partagés n’est pas autorisé pour cette installation. "
"Veuillez contacter l’administrateur de la page pour signaler le problème."
#: .\cookbook\views\views.py:357
msgid ""
"Recipe sharing link has been disabled! For additional information please "
"contact the page administrator."
msgstr ""
"Le lien de partage de la recette a été désactivé ! Pour plus d’informations, "
"veuillez contacter l’administrateur de la page."
#: .\cookbook\views\views.py:383
msgid "Manage recipes, shopping list, meal plans and more."
msgstr ""
#: .\cookbook\views\views.py:397 .\cookbook\views\views.py:398
msgid "Plan"
msgstr "Menu"
#: .\cookbook\views\views.py:399
msgid "View your meal Plan"
msgstr ""
#: .\cookbook\views\views.py:418
msgid "View your shopping lists"
msgstr "Voire vos listes de course"
#~ msgid ""
#~ "Both fields are optional. If none are given the username will be "
#~ "displayed instead"
#~ msgstr ""
#~ "Les deux champs sont facultatifs. Si aucun n’est rempli, le nom "
#~ "d’utilisateur sera affiché à la place"
#~ msgid "Name"
#~ msgstr "Nom"
#~ msgid "Keywords"
#~ msgstr "Mots-clés"
#~ msgid "Preparation time in minutes"
#~ msgstr "Temps de préparation en minutes"
#~ msgid "Waiting time (cooking/baking) in minutes"
#~ msgstr "Temps d’attente (cuisson) en minutes"
#~ msgid "Path"
#~ msgstr "Chemin"
#~ msgid "Storage UID"
#~ msgstr "UID de stockage"
#~ msgid "Add your comment: "
#~ msgstr "Ajoutez votre commentaire : "
#~ msgid "Leave empty for dropbox and enter app password for nextcloud."
#~ msgstr ""
#~ "Laissez vide pour Dropbox et renseignez votre mot de passe pour Nextcloud."
#~ msgid "Leave empty for nextcloud and enter api token for dropbox."
#~ msgstr ""
#~ "Laissez vide pour Nextcloud et renseignez votre jeton d’API pour Dropbox."
#~ msgid ""
#~ "Leave empty for dropbox and enter only base url for nextcloud (/"
#~ "remote.php/webdav/ is added automatically)"
#~ msgstr ""
#~ "Laisser vide pour Dropbox et saisissez seulement l’URL de base pour "
#~ "Nextcloud (/remote.php/webdav/ est ajouté automatiquement)"
#~ msgid ""
#~ "Long Lived Access Token for your HomeAssistant instance"
#~ msgstr ""
#~ "Jeton d'accès longue durée pour votre instance de "
#~ "HomeAssistant"
#~ msgid "Something like http://homeassistant.local:8123/api"
#~ msgstr "Par exemple http://homeassistant.local:8123/api"
#~ msgid "http://homeassistant.local:8123/api for example"
#~ msgstr "http://homeassistant.local:8123/api par exemple"
#~ msgid "Storage"
#~ msgstr "Stockage"
#~ msgid "Active"
#~ msgstr "Actif"
#~ msgid "Search String"
#~ msgstr "Texte recherché"
#~ msgid "File ID"
#~ msgstr "ID du fichier"
#~ msgid ""
#~ "Determines how fuzzy a search is if it uses trigram similarity matching "
#~ "(e.g. low values mean more typos are ignored)."
#~ msgstr ""
#~ "Détermine le degré de flou d’une recherche si elle utilise la "
#~ "correspondance par similarité de trigrammes (par exemple, des valeurs "
#~ "faibles signifient que davantage de fautes de frappe sont ignorées)."
#~ msgid ""
#~ "Select type method of search. Click here "
#~ "for full description of choices."
#~ msgstr ""
#~ "Sélectionner la méthode de recherche. Cliquer ici pour une description complète des choix."
#~ msgid ""
#~ "Use fuzzy matching on units, keywords and ingredients when editing and "
#~ "importing recipes."
#~ msgstr ""
#~ "Employez la correspondance approximative pour les unités, les mots-clés "
#~ "et les ingrédients pendant l'édition et l'importation des recettes."
#~ msgid ""
#~ "Fields to search ignoring accents. Selecting this option can improve or "
#~ "degrade search quality depending on language"
#~ msgstr ""
#~ "La recherche de champs sans tenir compte des accents peut soit améliorer "
#~ "soit détériorer la qualité des résultats de recherche, cela dépend de la "
#~ "langue"
#~ msgid ""
#~ "Fields to search for partial matches. (e.g. searching for 'Pie' will "
#~ "return 'pie' and 'piece' and 'soapie')"
#~ msgstr ""
#~ "Cherchez des champs pour des correspondances partielles. Par exemple : "
#~ "rechercher \"Tarte\" pourrait retourner \"tarte\", \"tartelette\", et "
#~ "\"tartes\""
#~ msgid ""
#~ "Fields to search for beginning of word matches. (e.g. searching for 'sa' "
#~ "will return 'salad' and 'sandwich')"
#~ msgstr ""
#~ "Les champs de recherche conçus pour trouver des correspondances en début "
#~ "de mot vous permettront, par exemple, de saisir « sa » et d'obtenir des "
#~ "résultats tels que « salade » et « sandwich »"
#~ msgid ""
#~ "Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find "
#~ "'recipe'.) Note: this option will conflict with 'web' and 'raw' methods "
#~ "of search."
#~ msgstr ""
#~ "Champs pour la recherche « floue » (par exemple, si vous recherchez "
#~ "« rectte», vous trouverez « recette ».) Remarque : cette option est "
#~ "incompatible avec les méthodes de recherche « web » et « brute »."
#~ msgid ""
#~ "Fields to full text search. Note: 'web', 'phrase', and 'raw' search "
#~ "methods only function with fulltext fields."
#~ msgstr ""
#~ "Champs de recherche en texte intégral. Remarque : les méthodes de "
#~ "recherche « web », « phrase » et « brute » ne fonctionnent qu’avec des "
#~ "champs en texte intégral."
#~ msgid "Search Method"
#~ msgstr "Méthode de recherche"
#~ msgid "Fuzzy Lookups"
#~ msgstr "Recherches floues"
#~ msgid "Ignore Accent"
#~ msgstr "Ignorer les accents"
#~ msgid "Partial Match"
#~ msgstr "correspondance partielle"
#~ msgid "Starts With"
#~ msgstr "Commence par"
#~ msgid "Fuzzy Search"
#~ msgstr "Recherche floue"
#~ msgid "Full Text"
#~ msgstr "Texte intégral"
#~ msgid " is part of a recipe step and cannot be deleted"
#~ msgstr " fait partie d’une étape de la recette et ne peut être supprimé(e)"
#~ msgid "Delete"
#~ msgstr "Supprimer"
#~ msgid "Settings"
#~ msgstr "Paramètres"
#~ msgid "Email"
#~ msgstr "Adresse mail"
#~ msgid "Password"
#~ msgstr "Mot de passe"
#~ msgid "Foods"
#~ msgstr "Aliments"
#~ msgid "Units"
#~ msgstr "Unités"
#~ msgid "Supermarket"
#~ msgstr "Supermarché"
#~ msgid "Supermarket Category"
#~ msgstr "Catégorie de supermarché"
#~ msgid "Automations"
#~ msgstr "Automatisations"
#~ msgid "Files"
#~ msgstr "Fichiers"
#~ msgid "Batch Edit"
#~ msgstr "Édition par lot"
#~ msgid "History"
#~ msgstr "Historique"
#~ msgid "Ingredient Editor"
#~ msgstr "Éditeur d’ingrédients"
#~ msgid "Properties"
#~ msgstr "Propriétés"
#~ msgid "Unit Conversions"
#~ msgstr "Conversions d'unités"
#~ msgid "Create"
#~ msgstr "Créer"
#~ msgid "External Recipes"
#~ msgstr "Recettes externes"
#~ msgid "Space Settings"
#~ msgstr "Paramètres de groupe"
#~ msgid "External Connectors"
#~ msgstr "Connecteurs externes"
#~ msgid "Admin"
#~ msgstr "Admin"
#~ msgid "Markdown Guide"
#~ msgstr "Guide Markdown"
#~ msgid "GitHub"
#~ msgstr "GitHub"
#~ msgid "Translate Tandoor"
#~ msgstr "Traduire Tandoor"
#~ msgid "API Browser"
#~ msgstr "Navigateur API"
#~ msgid "Log out"
#~ msgstr "Déconnexion"
#~ msgid "You are using the free version of Tandor"
#~ msgstr "Vous utilisez la version gratuite de Tandoor"
#~ msgid "Upgrade Now"
#~ msgstr "Mettez à jour maintenant"
#~ msgid "Batch edit Category"
#~ msgstr "Édition par lot des catégories"
#~ msgid "Batch edit Recipes"
#~ msgstr "Édition par lot des recettes"
#~ msgid "Add the specified keywords to all recipes containing a word"
#~ msgstr ""
#~ "Ajouter les mots-clés spécifiés à toutes les recettes contenant un mot"
#~ msgid "Sync"
#~ msgstr "Synchro"
#~ msgid "Manage watched Folders"
#~ msgstr "Gérer les dossiers surveillés"
#~ msgid ""
#~ "On this Page you can manage all storage folder locations that should be "
#~ "monitored and synced."
#~ msgstr ""
#~ "Sur cette page, vous pouvez gérer tous les emplacements de stockage à "
#~ "surveiller et synchroniser."
#~ msgid "The path must be in the following format"
#~ msgstr "Le chemin doit être au format suivant"
#~ msgid "Save"
#~ msgstr "Sauvegarder"
#~ msgid "Manage External Storage"
#~ msgstr "Gérer le stockage externe"
#~ msgid "Sync Now!"
#~ msgstr "Lancer la synchro !"
#~ msgid "Show Recipes"
#~ msgstr "Afficher les recettes"
#~ msgid "Show Log"
#~ msgstr "Afficher le journal"
#~ msgid "Importing Recipes"
#~ msgstr "Importer des recettes"
#~ msgid ""
#~ "This can take a few minutes, depending on the number of recipes in sync, "
#~ "please wait."
#~ msgstr ""
#~ "Cela peut prendre quelques minutes, selon le nombre de recettes à "
#~ "synchroniser. Veuillez patienter."
#~ msgid "Recipe Books"
#~ msgstr "Livres de recettes"
#~ msgid "Import new Recipe"
#~ msgstr "Importer une nouvelle recette"
#~ msgid "Edit Recipe"
#~ msgstr "Modifier une recette"
#, python-format
#~ msgid "Are you sure you want to delete the %(title)s: %(object)s "
#~ msgstr ""
#~ "Êtes-vous sûr(e) de vouloir supprimer %(title)s : %(object)s "
#~ msgid "This cannot be undone!"
#~ msgstr "L'opération ne peut pas être annulée !"
#~ msgid "Protected"
#~ msgstr "Protégé"
#~ msgid "Cascade"
#~ msgstr "Cascade"
#~ msgid "Cancel"
#~ msgstr "Annuler"
#~ msgid "Edit"
#~ msgstr "Modifier"
#~ msgid "View"
#~ msgstr "Voir"
#~ msgid "Delete original file"
#~ msgstr "Supprimer le fichier original"
#~ msgid "List"
#~ msgstr "Liste"
#~ msgid "Filter"
#~ msgstr "Filtre"
#~ msgid "Import all"
#~ msgstr "Tout importer"
#~ msgid "New"
#~ msgstr "Nouveau"
#~ msgid "previous"
#~ msgstr "précédent"
#~ msgid "next"
#~ msgstr "suivant"
#~ msgid "View Log"
#~ msgstr "Voir l’historique"
#~ msgid "Cook Log"
#~ msgstr "Historique de cuisine"
#~ msgid "Import"
#~ msgstr "Importer"
#~ msgid "Security Warning"
#~ msgstr "Avertissement de sécurité"
#~ msgid ""
#~ "\n"
#~ " The Password and Token field are stored as plain text"
#~ "b> inside the database.\n"
#~ " This is necessary because they are needed to make API requests, "
#~ "but it also increases the risk of\n"
#~ " someone stealing it.
\n"
#~ " To limit the possible damage tokens or accounts with limited "
#~ "access can be used.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Les champs Mot de passe et Token sont stockés en clair"
#~ "b>dans la base de données.\n"
#~ " C'est nécessaire car ils sont utilisés pour faire des requêtes "
#~ "API, mais cela accroît le risque que quelqu'un les vole.
\n"
#~ " Pour limiter les risques, il est possible d'utiliser des tokens "
#~ "ou des comptes avec un accès limité.\n"
#~ " "
#~ msgid "You are currently offline!"
#~ msgstr "Vous êtes actuellement hors ligne !"
#~ msgid ""
#~ "The recipes listed below are available for offline viewing because you "
#~ "have recently viewed them. Keep in mind that data might be outdated."
#~ msgstr ""
#~ "Les recettes listées ci-dessous sont accessible hors connexion car vous "
#~ "les avez récemment regardées. Veuillez tenir compte du fait que les "
#~ "données peuvent avoir été modifiées depuis."
#~ msgid "Property Editor"
#~ msgstr "Éditeur de propriété"
#~ msgid "Comments"
#~ msgstr "Commentaires"
#~ msgid "by"
#~ msgstr "par"
#~ msgid "Comment"
#~ msgstr "Commentaire"
#~ msgid ""
#~ "There are many options to configure the search depending on your personal "
#~ "preferences."
#~ msgstr ""
#~ "Il existe de nombreuses options pour configurer la recherche en fonction "
#~ "de vos préférences personnelles."
#~ msgid ""
#~ "Usually you do not need to configure any of them and can just "
#~ "stick with either the default or one of the following presets."
#~ msgstr ""
#~ "En général, vous n’avez pas besoin de configurer l’un d’entre eux "
#~ "et pouvez simplement vous en tenir à la valeur par défaut ou à l’un des "
#~ "préréglages suivants."
#~ msgid ""
#~ "If you do want to configure the search you can read about the different "
#~ "options here."
#~ msgstr ""
#~ "Si vous souhaitez configurer la recherche, vous pouvez consulter les "
#~ "différentes options ici."
#~ msgid "Fuzzy"
#~ msgstr "Flou"
#~ msgid ""
#~ "Find what you need even if your search or the recipe contains typos. "
#~ "Might return more results than needed to make sure you find what you are "
#~ "looking for."
#~ msgstr ""
#~ "Trouvez ce dont vous avez besoin même si votre recherche ou la recette "
#~ "contient des fautes de frappe. Il se peut que vous obteniez plus de "
#~ "résultats que nécessaire pour être sûr(e) de trouver ce que vous cherchez."
#~ msgid "This is the default behavior"
#~ msgstr "Il s’agit du comportement par défaut"
#~ msgid "Apply"
#~ msgstr "Appliquer"
#~ msgid "Precise"
#~ msgstr "Préciser"
#~ msgid ""
#~ "Allows fine control over search results but might not return results if "
#~ "too many spelling mistakes are made."
#~ msgstr ""
#~ "Permet un contrôle fin des résultats de la recherche mais peut ne pas "
#~ "donner de résultats si le texte saisi contient trop de fautes "
#~ "d’orthographe."
#~ msgid "Perfect for large Databases"
#~ msgstr "Parfait pour les grandes bases de données"
#~ msgid "Social"
#~ msgstr "Réseaux sociaux"
#~ msgid "Space Management"
#~ msgstr "Gestion de l'espace"
#~ msgid "Space:"
#~ msgstr "Groupe :"
#~ msgid "Manage Subscription"
#~ msgstr "Gérer l’abonnement"
#~ msgid "Leave Space"
#~ msgstr "Quitter le groupe"
#~ msgid "URL Import"
#~ msgstr "Import URL"
#~ msgid ""
#~ "Rating a recipe should have or greater. [0 - 5] Negative value filters "
#~ "rating less than."
#~ msgstr ""
#~ "Note qu'une recette devrait avoir ou être supérieure. [0 - 5] Une valeur "
#~ "négative filtre une note inférieure à."
#, python-format
#~ msgid "Batch edit done. %(count)d recipe was updated."
#~ msgid_plural "Batch edit done. %(count)d Recipes where updated."
#~ msgstr[0] "Édition par lot effectuée. %(count)d recette a été mise à jour."
#~ msgstr[1] ""
#~ "Édition par lot effectuée. %(count)d recettes ont été mises à jour."
#~ msgid "Monitor"
#~ msgstr "Surveiller"
#~ msgid "Storage Backend"
#~ msgstr "Espace de stockage"
#~ msgid ""
#~ "Could not delete this storage backend as it is used in at least one "
#~ "monitor."
#~ msgstr ""
#~ "Impossible de supprimer cet espace de stockage car il est utilisé dans au "
#~ "moins un dossier surveillé."
#, fuzzy
#~| msgid "Storage Backend"
#~ msgid "Connectors Config Backend"
#~ msgstr "Espace de stockage"
#~ msgid "Invite Link"
#~ msgstr "Lien d’invitation"
#~ msgid "Space Membership"
#~ msgstr "Adhésion à l'espace"
#~ msgid "You cannot edit this storage!"
#~ msgstr "Vous ne pouvez pas modifier ce stockage !"
#~ msgid "Storage saved!"
#~ msgstr "Stockage sauvegardé !"
#~ msgid "There was an error updating this storage backend!"
#~ msgstr ""
#~ "Une erreur est survenue lors de la mise à jour de cet espace de stockage !"
#, fuzzy
#~| msgid "Changes saved!"
#~ msgid "Config saved!"
#~ msgstr "Modifications sauvegardées !"
#~ msgid "Changes saved!"
#~ msgstr "Modifications sauvegardées !"
#~ msgid "Error saving changes!"
#~ msgstr "Erreur lors de la sauvegarde des modifications !"
#~ msgid "Import Log"
#~ msgstr "Historique d’importation"
#~ msgid "Discovery"
#~ msgstr "Découverte"
#~ msgid "Shopping List"
#~ msgstr "Liste de courses"
#, fuzzy
#~| msgid "Storage Backend"
#~ msgid "Connector Config Backend"
#~ msgstr "Espace de stockage"
#~ msgid "Invite Links"
#~ msgstr "Liens d’invitation"
#~ msgid "Supermarkets"
#~ msgstr "Supermarchés"
#~ msgid "Shopping Categories"
#~ msgstr "Catégories de courses"
#~ msgid "Custom Filters"
#~ msgstr "Filtre personnalisé"
#~ msgid "Steps"
#~ msgstr "Étapes"
#, fuzzy
#~| msgid "This feature is not available in the demo version!"
#~ msgid "This feature is not enabled by the server admin!"
#~ msgstr "Cette fonctionnalité n’est pas disponible dans la version d’essai !"
#~ msgid "Imported new recipe!"
#~ msgstr "Nouvelle recette importée !"
#~ msgid "There was an error importing this recipe!"
#~ msgstr "Une erreur est survenue lors de l’importation de cette recette !"
#~ msgid "You do not have the required permissions to perform this action!"
#~ msgstr "Vous n’êtes pas autorisé(e) à effectuer cette action !"
#~ msgid "Comment saved!"
#~ msgstr "Commentaire sauvegardé !"
#~ msgid "You must select at least one field to search!"
#~ msgstr ""
#~ "Vous devez sélectionner au moins un champ pour effectuer une recherche !"
#~ msgid ""
#~ "To use this search method you must select at least one full text search "
#~ "field!"
#~ msgstr ""
#~ "Pour utiliser cette méthode de recherche, vous devez sélectionner au "
#~ "moins un champ de recherche en texte intégral !"
#~ msgid "Fuzzy search is not compatible with this search method!"
#~ msgstr ""
#~ "La recherche floue n’est pas compatible avec cette méthode de recherche !"
#~ msgid "Malformed Invite Link supplied!"
#~ msgstr "Le lien d’invitation fourni est mal formé !"
#~ msgid "Successfully joined space."
#~ msgstr "Vous avez bien rejoint le groupe."
#~ msgid "Invite Link not valid or already used!"
#~ msgstr "Le lien d’invitation est invalide ou déjà utilisé !"
#~ msgid "Default unit"
#~ msgstr "Unité par défaut"
#~ msgid "Use KJ"
#~ msgstr "Utiliser les kJ"
#~ msgid "Theme"
#~ msgstr "Thème"
#~ msgid "Navbar color"
#~ msgstr "Couleur de la barre de navigation"
#~ msgid "Sticky navbar"
#~ msgstr "Barre de navigation permanente"
#~ msgid "Default page"
#~ msgstr "Page par défaut"
#~ msgid "Plan sharing"
#~ msgstr "Partage du planificateur"
#~ msgid "Ingredient decimal places"
#~ msgstr "Nombre de décimales pour les ingrédients"
#~ msgid "Shopping list auto sync period"
#~ msgstr "Période de synchro automatique de la liste de courses"
#~ msgid "Left-handed mode"
#~ msgstr "Mode gaucher"
#~ msgid ""
#~ "Color of the top navigation bar. Not all colors work with all themes, "
#~ "just try them out!"
#~ msgstr ""
#~ "Couleur de la barre de navigation du haut. Les couleurs ne fonctionnent "
#~ "pas avec tous les thèmes, faites des essais !"
#~ msgid ""
#~ "Default Unit to be used when inserting a new ingredient into a recipe."
#~ msgstr ""
#~ "Unité par défaut utilisée lors de l’ajout d’un nouvel ingrédient dans une "
#~ "recette."
#~ msgid ""
#~ "Enables support for fractions in ingredient amounts (e.g. convert "
#~ "decimals to fractions automatically)"
#~ msgstr ""
#~ "Permet la prise en charge des fractions dans les quantités d’ingrédients "
#~ "(convertit les décimales en fractions automatiquement)"
#~ msgid "Display nutritional energy amounts in joules instead of calories"
#~ msgstr ""
#~ "Afficher les quantités d’énergie nutritionnelle en joules plutôt qu’en "
#~ "calories"
#~ msgid ""
#~ "Users with whom newly created meal plans should be shared by default."
#~ msgstr ""
#~ "Utilisateurs avec lesquels partager par défaut les menus de la semaines "
#~ "nouvellement créés."
#~ msgid "Users with whom to share shopping lists."
#~ msgstr "Utilisateurs avec lesquels partager des listes de courses."
#~ msgid "Number of decimals to round ingredients."
#~ msgstr "Nombre de décimales pour arrondir les ingrédients."
#~ msgid ""
#~ "If you want to be able to create and see comments underneath recipes."
#~ msgstr ""
#~ "Si vous souhaitez pouvoir créer et consulter des commentaires en dessous "
#~ "des recettes."
#~ msgid ""
#~ "Setting to 0 will disable auto sync. When viewing a shopping list the "
#~ "list is updated every set seconds to sync changes someone else might have "
#~ "made. Useful when shopping with multiple people but might use a little "
#~ "bit of mobile data. If lower than instance limit it is reset when saving."
#~ msgstr ""
#~ "« 0 » désactivera la synchronisation automatique. Lorsque vous consultez "
#~ "une liste de courses, la liste sera mise à jour toutes les x secondes "
#~ "pour synchroniser les modifications apportées par quelqu’un d'autre. "
#~ "Utile lorsque vous faites vos courses à plusieurs mais peut consommer "
#~ "davantage de données mobiles. Si la valeur est plus petite que les "
#~ "limites de l’instance, le paramètre sera réinitialisé."
#~ msgid "Makes the navbar stick to the top of the page."
#~ msgstr "Épingler la barre de navigation en haut de la page."
#~ msgid "Automatically add meal plan ingredients to shopping list."
#~ msgstr ""
#~ "Ajouter les ingrédients du menu de la semaine à la liste de courses "
#~ "automatiquement."
#~ msgid "Exclude ingredients that are on hand."
#~ msgstr "Exclure les ingrédients disponibles."
#~ msgid "Will optimize the UI for use with your left hand."
#~ msgstr "Optimisation de l'interface pour l'utilisation avec la main gauche."
#~ msgid "You must provide at least a recipe or a title."
#~ msgstr "Vous devez au moins fournir une recette ou un titre."
#~ msgid "You can list default users to share recipes with in the settings."
#~ msgstr ""
#~ "Vous pouvez lister les utilisateurs par défaut avec qui partager des "
#~ "recettes dans les paramètres."
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "Vous pouvez utiliser du markdown pour mettre en forme ce champ. Voir la "
#~ "documentation ici"
#~ msgid ""
#~ "Users will see all items you add to your shopping list. They must add "
#~ "you to see items on their list."
#~ msgstr ""
#~ "Les utilisateurs verront tous les éléments que vous ajoutez à votre liste "
#~ "de courses. Ils doivent vous ajouter pour que vous puissiez voir les "
#~ "éléments de leur liste."
#~ msgid ""
#~ "When adding a meal plan to the shopping list (manually or automatically), "
#~ "include all related recipes."
#~ msgstr ""
#~ "Lors de l’ajout d’un menu de la semaine à la liste de courses (manuel ou "
#~ "automatique), inclure toutes les recettes connexes."
#~ msgid ""
#~ "When adding a meal plan to the shopping list (manually or automatically), "
#~ "exclude ingredients that are on hand."
#~ msgstr ""
#~ "Lors de l’ajout d’un menu de la semaine à la liste de courses (manuel ou "
#~ "automatique), exclure les ingrédients disponibles."
#~ msgid "Default number of hours to delay a shopping list entry."
#~ msgstr ""
#~ "Nombre d'heures par défaut pour retarder l'ajoût d'un article à la liste "
#~ "de courses."
#~ msgid "Filter shopping list to only include supermarket categories."
#~ msgstr ""
#~ "Filtrer la liste de courses pour n’inclure que des catégories de "
#~ "supermarchés."
#~ msgid "Days of recent shopping list entries to display."
#~ msgstr "Jours des entrées récentes de la liste de courses à afficher."
#~ msgid "Mark food 'On Hand' when checked off shopping list."
#~ msgstr ""
#~ "Marquer l’aliment comme disponible lorsqu’il est rayé de la liste de "
#~ "courses."
#~ msgid "Delimiter to use for CSV exports."
#~ msgstr "Caractère de séparation à utiliser pour les exportations CSV."
#~ msgid "Prefix to add when copying list to the clipboard."
#~ msgstr ""
#~ "Préfixe à ajouter lors de la copie de la liste dans le presse-papiers."
#~ msgid "Share Shopping List"
#~ msgstr "Partager la liste de courses"
#~ msgid "Autosync"
#~ msgstr "Synchronisation automatique"
#~ msgid "Auto Add Meal Plan"
#~ msgstr "Ajouter le menu de la semaine automatiquement"
#~ msgid "Exclude On Hand"
#~ msgstr "Exclure ingrédients disponibles"
#~ msgid "Include Related"
#~ msgstr "Inclure recettes connexes"
#~ msgid "Default Delay Hours"
#~ msgstr "Heures de retard par défaut"
#~ msgid "Filter to Supermarket"
#~ msgstr "Filtrer par supermarché"
#~ msgid "Recent Days"
#~ msgstr "Jours récents"
#~ msgid "CSV Delimiter"
#~ msgstr "Caractère de séparation CSV"
#~ msgid "List Prefix"
#~ msgstr "Préfixe de la liste"
#~ msgid "Auto On Hand"
#~ msgstr "Disponible automatique"
#~ msgid "Reset Food Inheritance"
#~ msgstr "Réinitialiser l'héritage alimentaire"
#~ msgid "Reset all food to inherit the fields configured."
#~ msgstr "Réinitialiser tous les aliments pour hériter les champs configurés."
#~ msgid "Fields on food that should be inherited by default."
#~ msgstr "Champs sur les aliments à hériter par défaut."
#~ msgid "Show recipe counts on search filters"
#~ msgstr ""
#~ "Afficher le nombre de consultations par recette sur les filtres de "
#~ "recherche"
#~ msgid "Use the plural form for units and food inside this space."
#~ msgstr ""
#~ "Utiliser la forme plurielle pour les unités et les aliments dans ce "
#~ "groupe."
#~ msgid "One of queryset or hash_key must be provided"
#~ msgstr ""
#~ "Il est nécessaire de fournir soit le queryset, soit la clé de hachage"
#~ msgid "Profile"
#~ msgstr "Profil"
#~ msgid "Recipe Book"
#~ msgstr "Livre de recettes"
#~ msgid "Bookmarks"
#~ msgstr "Favoris"
#~ msgid "Ingredients"
#~ msgstr "Ingrédients"
#~ msgid "Show recent recipes"
#~ msgstr "Afficher les recettes récentes"
#~ msgid "Search style"
#~ msgstr "Rechercher"
#~ msgid "Show recently viewed recipes on search page."
#~ msgstr ""
#~ "Afficher les recettes récemment consultées sur la page de recherche."
#~ msgid "Small"
#~ msgstr "Petit"
#~ msgid "Large"
#~ msgstr "Grand"
#~ msgid "Edit Ingredients"
#~ msgstr "Modifier les ingrédients"
#~ msgid ""
#~ "\n"
#~ " The following form can be used if, accidentally, two (or more) "
#~ "units or ingredients where created that should be\n"
#~ " the same.\n"
#~ " It merges two units or ingredients and updates all recipes using "
#~ "them.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Le formulaire suivant est utile lorsqu'il y a des doublons dans "
#~ "les unités ou les ingrédients.\n"
#~ " Il fusionne deux unités ou ingrédients et met à jour toutes les "
#~ "recettes les utilisant.\n"
#~ " "
#~ msgid "Are you sure that you want to merge these two units?"
#~ msgstr "Êtes-vous sûr(e) de vouloir fusionner ces deux unités ?"
#~ msgid "Are you sure that you want to merge these two ingredients?"
#~ msgstr "Êtes-vous sûr(e) de vouloir fusionner ces deux ingrédients ?"
#~ msgid "Import Recipes"
#~ msgstr "Importer des recettes"
#~ msgid "Close"
#~ msgstr "Fermer"
#~ msgid "Open Recipe"
#~ msgstr "Ouvrir la recette"
#~ msgid "Meal Plan View"
#~ msgstr "Vue des menus"
#~ msgid "Created by"
#~ msgstr "Créé par"
#~ msgid "Shared with"
#~ msgstr "Partagé avec"
#~ msgid "Never cooked before."
#~ msgstr "Pas encore cuisiné."
#~ msgid "Other meals on this day"
#~ msgstr "Autres repas ce jour"
#~ msgid "Recipe Image"
#~ msgstr "Image de la recette"
#~ msgid "Preparation time ca."
#~ msgstr "Temps moyen de préparation"
#~ msgid "Waiting time ca."
#~ msgstr "Temps moyen d'attente"
#~ msgid "External"
#~ msgstr "Externe"
#~ msgid "Log Cooking"
#~ msgstr "Marquer comme cuisiné"
#~ msgid "Account"
#~ msgstr "Compte"
#~ msgid "Preferences"
#~ msgstr "Préférences"
#~ msgid "API-Settings"
#~ msgstr "Paramètres d’API"
#~ msgid "Search-Settings"
#~ msgstr "Paramètres de recherche"
#, fuzzy
#~| msgid "Search-Settings"
#~ msgid "Shopping-Settings"
#~ msgstr "Paramètres de recherche"
#~ msgid "Name Settings"
#~ msgstr "Paramètres de noms"
#~ msgid "Account Settings"
#~ msgstr "Paramètres de compte"
#~ msgid "Emails"
#~ msgstr "Adresses mail"
#~ msgid "Language"
#~ msgstr "Langue"
#~ msgid "Style"
#~ msgstr "Style"
#~ msgid "API Token"
#~ msgstr "Jeton API"
#~ msgid ""
#~ "You can use both basic authentication and token based authentication to "
#~ "access the REST API."
#~ msgstr ""
#~ "Vous pouvez utiliser à la fois l’authentification classique et "
#~ "l’authentification par jeton pour accéder à l’API REST."
#~ msgid ""
#~ "Use the token as an Authorization header prefixed by the word token as "
#~ "shown in the following examples:"
#~ msgstr ""
#~ "Utilisez le jeton dans l’en-tête d’autorisation précédé du mot « token » "
#~ "comme indiqué dans les exemples suivants :"
#~ msgid "or"
#~ msgstr "ou"
#, fuzzy
#~| msgid "Shopping List"
#~ msgid "Shopping Settings"
#~ msgstr "Liste de courses"
#~ msgid "Stats"
#~ msgstr "Stats"
#~ msgid "Statistics"
#~ msgstr "Statistiques"
#~ msgid "Number of objects"
#~ msgstr "Nombre d’objets"
#~ msgid "Recipe Imports"
#~ msgstr "Recettes importées"
#~ msgid "Objects stats"
#~ msgstr "Statistiques d’objets"
#~ msgid "Recipes without Keywords"
#~ msgstr "Recettes sans mots-clés"
#~ msgid "Internal Recipes"
#~ msgstr "Recettes internes"
#~ msgid "Show Links"
#~ msgstr "Afficher les liens"
#~ msgid "A user is required"
#~ msgstr "Un utilisateur est requis"
#~ msgid "Invite User"
#~ msgstr "Inviter un utilisateur"
#~ msgid "User"
#~ msgstr "Utilisateurs"
#~ msgid "Groups"
#~ msgstr "Groupes"
#~ msgid "admin"
#~ msgstr "administrateur"
#~ msgid "user"
#~ msgstr "utilisateur"
#~ msgid "guest"
#~ msgstr "invité"
#~ msgid "remove"
#~ msgstr "supprimer"
#~ msgid "Update"
#~ msgstr "Mettre à jour"
#~ msgid "You cannot edit yourself."
#~ msgstr "Vous ne pouvez pas modifier cela vous-même."
#~ msgid "There are no members in your space yet!"
#~ msgstr "Votre groupe ne contient pas encore de membres !"
#~ msgid "Invite link successfully send to user."
#~ msgstr "Le lien d’invitation a été envoyé à l’utilisateur avec succès."
#~ msgid ""
#~ "You have send to many emails, please share the link manually or wait a "
#~ "few hours."
#~ msgstr ""
#~ "Vous avez envoyé trop de mails, partagez le lien manuellement ou "
#~ "patientez quelques heures."
#~ msgid "Email could not be sent to user. Please share the link manually."
#~ msgstr ""
#~ "Impossible d’envoyer le mail à l’utilisateur, veuillez partager le lien "
#~ "manuellement."
#~ msgid ""
#~ "You are already member of a space and therefore cannot join this one."
#~ msgstr ""
#~ "Vous êtes déjà membre d’un groupe et ne pouvez donc pas rejoindre celui-"
#~ "ci."
#~ msgid "Try the new shopping list"
#~ msgstr "Essayer la nouvelle liste de courses"
#~ msgid "Search Recipe"
#~ msgstr "Rechercher une recette"
#~ msgid "Shopping Recipes"
#~ msgstr "Recettes dans le panier"
#~ msgid "No recipes selected"
#~ msgstr "Aucune recette sélectionnée"
#~ msgid "Entry Mode"
#~ msgstr "Mode d’ajout"
#~ msgid "Add Entry"
#~ msgstr "Ajouter une entrée"
#~ msgid "Amount"
#~ msgstr "Quantité"
#~ msgid "Select Unit"
#~ msgstr "Sélectionnez l’unité"
#~ msgid "Select"
#~ msgstr "Sélectionner"
#~ msgid "Select Food"
#~ msgstr "Sélectionner l’aliment"
#~ msgid "Select Supermarket"
#~ msgstr "Sélectionner un supermarché"
#~ msgid "Select User"
#~ msgstr "Sélectionnez un utilisateur"
#~ msgid "Finished"
#~ msgstr "Terminé"
#, fuzzy
#~| msgid "You are offline, shopping list might not syncronize."
#~ msgid "You are offline, shopping list might not synchronize."
#~ msgstr "Vous êtes hors ligne, la liste de courses n’est pas synchronisée."
#~ msgid "Copy/Export"
#~ msgstr "Copier/exporter"
#~ msgid "Drag me to your bookmarks to import recipes from anywhere"
#~ msgstr ""
#~ "Glissez-moi vers vos favoris pour importer des recettes de n’importe où"
#~ msgid "Bookmark Me!"
#~ msgstr "Ajoutez-moi aux favoris !"
#~ msgid "URL"
#~ msgstr "URL"
#~ msgid "App"
#~ msgstr "App"
#~ msgid "Text"
#~ msgstr "Texte"
#~ msgid "File"
#~ msgstr "Fichier"
#~ msgid "Enter website URL"
#~ msgstr "Saisissez l’URL du site web"
#~ msgid "Select recipe files to import or drop them here..."
#~ msgstr ""
#~ "Sélectionnez des fichiers de recettes à importer ou glissez-les ici…"
#~ msgid "Paste json or html source here to load recipe."
#~ msgstr "Collez une source json ou html pour charger la recette."
#~ msgid "Preview Recipe Data"
#~ msgstr "Prévisualiser les informations de la recette"
#~ msgid ""
#~ "Drag recipe attributes from the right into the appropriate box below."
#~ msgstr ""
#~ "Glissez les attributs de la recette depuis la droite dans la boîte "
#~ "appropriée ci-dessous."
#~ msgid "Clear Contents"
#~ msgstr "Effacer le contenu"
#~ msgid "Text dragged here will be appended to the name."
#~ msgstr "Le texte glissé ici sera ajouté au nom."
#~ msgid "Text dragged here will be appended to the description."
#~ msgstr "Le texte glissé ici sera ajouté à la description."
#~ msgid "Keywords dragged here will be appended to current list"
#~ msgstr "Les mots-clés ajoutés ici seront ajoutés à la liste actuelle"
#~ msgid "Image"
#~ msgstr "Image"
#~ msgid "Prep Time"
#~ msgstr "Temps de préparation"
#~ msgid "Cook Time"
#~ msgstr "Temps de cuisson"
#~ msgid "Ingredients dragged here will be appended to current list."
#~ msgstr "Les ingrédients glissés ici seront ajoutés à la liste actuelle."
#~ msgid ""
#~ "Recipe instructions dragged here will be appended to current instructions."
#~ msgstr ""
#~ "Les instructions de recette glissés ici seront ajoutés aux instructions "
#~ "actuelles."
#~ msgid "Discovered Attributes"
#~ msgstr "Attributs découverts"
#~ msgid ""
#~ "Drag recipe attributes from below into the appropriate box on the left. "
#~ "Click any node to display its full properties."
#~ msgstr ""
#~ "Glissez les attributs de recettes d’en dessous vers la boîte appropriée à "
#~ "gauche. Cliquez sur un nœud pour voir toutes ses propriétés."
#~ msgid "Show Blank Field"
#~ msgstr "Afficher un champ vierge"
#~ msgid "Blank Field"
#~ msgstr "Champ vierge"
#~ msgid "Items dragged to Blank Field will be appended."
#~ msgstr "Les objets glissés dans le champ vierge seront ajoutés."
#~ msgid "Delete Text"
#~ msgstr "Supprimer le texte"
#~ msgid "Delete image"
#~ msgstr "Supprimer l’image"
#~ msgid "Recipe Name"
#~ msgstr "Nom de la recette"
#~ msgid "Recipe Description"
#~ msgstr "Description de la recette"
#~ msgid "Select one"
#~ msgstr "Faites votre choix"
#~ msgid "Note"
#~ msgstr "Notes"
#~ msgid "Add Keyword"
#~ msgstr "Ajouter un mot-clé"
#~ msgid "All Keywords"
#~ msgstr "Tous les mots-clés"
#~ msgid "Import all keywords, not only the ones already existing."
#~ msgstr "Importer tous les mots-clés, pas uniquement ceux déjà existant."
#~ msgid "Information"
#~ msgstr "Information"
#~ msgid ""
#~ " Only websites containing ld+json or microdata information can currently\n"
#~ " be imported. Most big recipe pages "
#~ "support this. If you site cannot be imported but\n"
#~ " you think\n"
#~ " it probably has some kind of "
#~ "structured data feel free to post an example in the\n"
#~ " github issues."
#~ msgstr ""
#~ " Seuls les sites webs contenant des données ld+json ou microdatas "
#~ "peuvent\n"
#~ " actuellement être "
#~ "importés.\n"
#~ " C'est le cas de la plupart "
#~ "des grands sites web. Si votre site ne peut pas être importé\n"
#~ " alors qu'il est censé "
#~ "disposer de données correctement structurées,\n"
#~ " n'hésitez pas à publier un "
#~ "exemple dans un ticket sur GitHub."
#~ msgid "Google ld+json Info"
#~ msgstr "Informations Google ld+json"
#~ msgid "GitHub Issues"
#~ msgstr "Ticket GitHub"
#~ msgid "Recipe Markup Specification"
#~ msgstr "Spécification Markup de recette"
#~ msgid "The requested site provided malformed data and cannot be read."
#~ msgstr "Le site web a renvoyé des données malformées et ne peut être lu."
#~ msgid "The requested page could not be found."
#~ msgstr "La page souhaitée est introuvable."
#~ msgid ""
#~ "The requested site does not provide any recognized data format to import "
#~ "the recipe from."
#~ msgstr ""
#~ "Le site web est dans un format qui ne permet pas d’importer "
#~ "automatiquement la recette."
#~ msgid "I couldn't find anything to do."
#~ msgstr "Je n’ai rien trouvé à faire."
#~ msgid "Shopping Lists"
#~ msgstr "Listes de course"
#~ msgid "You must supply a recipe or mealplan"
#~ msgstr "Vous devez fournir une recette ou un menu de la semaine"
#~ msgid "Time"
#~ msgstr "Temps"
#~ msgid "Log Recipe Cooking"
#~ msgstr "Marquer la recette comme cuisinée"
#~ msgid "All fields are optional and can be left empty."
#~ msgstr "Tous les champs sont facultatifs et peuvent être laissés vides."
#~ msgid "Rating"
#~ msgstr "Note"
#~ msgid "Exporting is not implemented for this provider"
#~ msgstr "L’exportation n’est pas implémentée pour ce fournisseur"
#~ msgid "No {self.basename} with id {child} exists"
#~ msgstr "Il n'existe pas de {self.basename} avec l'id {child}"
#~ msgid "New unit that other gets replaced by."
#~ msgstr "La nouvelle unité qui remplacera l'autre."
#~ msgid "Old Unit"
#~ msgstr "Ancienne unité"
#~ msgid "Unit that should be replaced."
#~ msgstr "L'unité qui doit être remplacée."
#~ msgid "New Food"
#~ msgstr "Nouvel aliment"
#~ msgid "New food that other gets replaced by."
#~ msgstr "Nouvel aliment qui remplace les autres."
#~ msgid "Old Food"
#~ msgstr "Ancien aliment"
#~ msgid "New Entry"
#~ msgstr "Nouvelle ligne"
#~ msgid "Title"
#~ msgstr "Titre"
#~ msgid "Note (optional)"
#~ msgstr "Note (facultatif)"
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "Vous pouvez utiliser du markdown pour mettre en forme ce champ. Consultez "
#~ "la documentation ici"
#~ msgid "Serving Count"
#~ msgstr "Nombre de parts"
#~ msgid "Create only note"
#~ msgstr "Créer uniquement une note"
#~ msgid "Number of Days"
#~ msgstr "Nombre de jours"
#~ msgid "Weekday offset"
#~ msgstr "Décalage du jour"
#~ msgid ""
#~ "Number of days starting from the first day of the week to offset the "
#~ "default view."
#~ msgstr ""
#~ "Permet de décaler le premier jour de la semaine dans la vue par défaut."
#~ msgid "Edit plan types"
#~ msgstr "Modifier l'organisation du menu"
#~ msgid "Show help"
#~ msgstr "Afficher l'aide"
#~ msgid "Week iCal export"
#~ msgstr "Export iCal"
#~ msgid "Add to Shopping"
#~ msgstr "Ajouter aux courses"
#~ msgid "New meal type"
#~ msgstr "Nouveau type de repas"
#~ msgid "Meal Plan Help"
#~ msgstr "Aide sur le menu de la semaine"
#~ msgid ""
#~ "\n"
#~ " The meal plan module allows planning of "
#~ "meals both with recipes and notes.
\n"
#~ " Simply select a recipe from the list of "
#~ "recently viewed recipes or search the one you\n"
#~ " want and drag it to the desired plan "
#~ "position. You can also add a note and a title and\n"
#~ " then drag the recipe to create a plan "
#~ "entry with a custom title and note. Creating only\n"
#~ " Notes is possible by dragging the create "
#~ "note box into the plan.
\n"
#~ " Click on a recipe in order to open the "
#~ "detailed view. There you can also add it to the\n"
#~ " shopping list. You can also add all "
#~ "recipes of a day to the shopping list by\n"
#~ " clicking the shopping cart at the top of "
#~ "the table.
\n"
#~ " Since a common use case is to plan meals "
#~ "together you can define\n"
#~ " users you want to share your plan with in "
#~ "the settings.\n"
#~ "
\n"
#~ " You can also edit the types of meals you "
#~ "want to plan. If you share your plan with\n"
#~ " someone with\n"
#~ " different meals, their meal types will "
#~ "appear in your list as well. To prevent\n"
#~ " duplicates (e.g. Other and Misc.)\n"
#~ " name your meal types the same as the "
#~ "users you share your meals with and they will be\n"
#~ " merged.
\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Le module menu de la semaine permet de "
#~ "planifier les repas grâce à des recettes et des notes.
\n"
#~ "Choisissez simplement une recette dans la liste des recettes récemment "
#~ "vues ou cherchez celle de votre choix et glissez-la à l'emplacement "
#~ "désiré. Vous pouvez aussi ajouter une notre et un titre puis glisser la "
#~ "recette pour créer une entrée personnalisée. Il est possible de "
#~ "n'utiliser que des notes en glissant la boîte de création de notes dans "
#~ "le menu.
\n"
#~ "Cliquez sur une recette pour ouvrir la vue détaillée. Là, vous pouvez "
#~ "aussi l'ajouter à la liste de courses.Vous pouvez aussi ajouter toutes "
#~ "les recettes d'une journée à la liste de courses en cliquant sur le caddy "
#~ "en haut du tableau.
\n"
#~ "Étant donné que les menus de la semaine sont souvent préparés "
#~ "ensemble, vous pouvez définir des utilisateurs avec qui partager votre "
#~ "menu dans les paramètres.
\n"
#~ "Vous pouvez aussi modifier le type de repas que vous voulez planifier. "
#~ "Si vous partagez le menu avec quelqu'un qui a d'autres types de repas, "
#~ "leurs repas apparaîtront aussi dans votre liste. Pour éviter les "
#~ "doublons, nommez vos types de repas de la même façon que les utilisateurs "
#~ "avec qui vous l'avez partager afin qu'ils soient fusionnés.
\n"
#~ " "
#~ msgid "Units merged!"
#~ msgstr "Unités fusionnées !"
#~ msgid "Foods merged!"
#~ msgstr "Aliments fusionnés !"
#~ msgid "Utensils"
#~ msgstr "Ustensiles"
#~ msgid "Storage Data"
#~ msgstr "Données de stockage"
#~ msgid "Storage Backends"
#~ msgstr "Espaces de stockage"
#~ msgid "Configure Sync"
#~ msgstr "Configurer synchro"
#~ msgid "Discovered Recipes"
#~ msgstr "Recettes découvertes"
#~ msgid "Discovery Log"
#~ msgstr "Historique des découvertes"
#~ msgid "Units & Ingredients"
#~ msgstr "Unités et ingrédients"
#~ msgid "New Book"
#~ msgstr "Nouveau livre"
#~ msgid "Toggle Recipes"
#~ msgstr "Afficher les recettes"
#~ msgid "There are no recipes in this book yet."
#~ msgstr "Il n'y a pas encore de recettes dans ce livre."
#~ msgid "Waiting Time"
#~ msgstr "Temps d'attente"
#~ msgid "Servings Text"
#~ msgstr "Service"
#~ msgid "Select Keywords"
#~ msgstr "Sélectionner des mots-clés"
#~ msgid "Delete Step"
#~ msgstr "Supprimer l'étape"
#~ msgid "Step"
#~ msgstr "Étape"
#~ msgid "Show as header"
#~ msgstr "Afficher en entête"
#~ msgid "Hide as header"
#~ msgstr "Masquer en entête"
#~ msgid "Move Up"
#~ msgstr "Remonter"
#~ msgid "Move Down"
#~ msgstr "Descendre"
#~ msgid "Step Name"
#~ msgstr "Nom de l'étape"
#~ msgid "Step Type"
#~ msgstr "Type de l'étape"
#~ msgid "Step time in Minutes"
#~ msgstr "Durée de l'étape en minutes"
#~ msgid "Select File"
#~ msgstr "Sélectionner un fichier"
#~ msgid "Select Recipe"
#~ msgstr "Sélectionner la recette"
#~ msgid "Delete Ingredient"
#~ msgstr "Supprimer l'ingrédient"
#~ msgid "Make Header"
#~ msgstr "Transformer en texte"
#~ msgid "Make Ingredient"
#~ msgstr "Transformer en ingrédient"
#~ msgid "Disable Amount"
#~ msgstr "Sans quantité"
#~ msgid "Enable Amount"
#~ msgstr "Avec quantité"
#~ msgid "Copy Template Reference"
#~ msgstr "Copier le modèle de référence"
#~ msgid "Save & View"
#~ msgstr "Sauvegarder et afficher"
#~ msgid "Add Step"
#~ msgstr "Ajouter une étape"
#~ msgid "Add Nutrition"
#~ msgstr "Ajouter les informations nutritionnelles"
#~ msgid "Remove Nutrition"
#~ msgstr "Supprimer les informations nutritionnelles"
#~ msgid "View Recipe"
#~ msgstr "Afficher la recette"
#~ msgid "Delete Recipe"
#~ msgstr "Supprimer la recette"
#~ msgid "Password Settings"
#~ msgstr "Paramètres de mots de passe"
#~ msgid "Email Settings"
#~ msgstr "Paramètres d'email"
#~ msgid "Manage Social Accounts"
#~ msgstr "Gérer les comptes de réseaux sociaux"
#~ msgid ""
#~ "A username is not required, if left blank the new user can choose one."
#~ msgstr ""
#~ "Il n'est pas obligatoire de renseigner un nom d'utilisateur. S'il est "
#~ "laissé vide, le nouvel utilisateur pourra le choisir."
#~ msgid "Link"
#~ msgstr "Lien"
#~ msgid "Logout"
#~ msgstr "Déconnexion"
#~ msgid "Website Import"
#~ msgstr "Importer depuis un site web"
#~ msgid "There was an error creating a resource!"
#~ msgstr ""
#~ "Une erreur s\\\\'est produite lors de la création d\\\\'une ressource !"
#~ msgid ""
#~ "The requested page refused to provide any information (Status Code 403)."
#~ msgstr "La page souhaitée refuse de fournir des informations (erreur 403)."
#~ msgid ""
#~ "Include - [ ] in list for easier usage in markdown based "
#~ "documents."
#~ msgstr ""
#~ "Inclure - [ ] dans les listes pour une utilisation plus "
#~ "facile dans les documents Markdown."
#~ msgid "Backup & Restore"
#~ msgstr "Sauvegarde & récupération"
#~ msgid "Download Backup"
#~ msgstr "Télécharger une sauvegarde"
#~ msgid "Preference for given user already exists"
#~ msgstr "Les préférences pour cet utilisateur existent déjà"
================================================
FILE: cookbook/locale/he/LC_MESSAGES/django.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-22 20:15+0200\n"
"PO-Revision-Date: 2026-02-01 16:03+0000\n"
"Last-Translator: timycool \n"
"Language-Team: Hebrew \n"
"Language: he\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=4; plural=(n == 1) ? 0 : ((n == 2) ? 1 : "
"((n > 10 && n % 10 == 0) ? 2 : 3));\n"
"X-Generator: Weblate 5.13.3\n"
#: .\cookbook\forms.py:50
msgid "Default"
msgstr "ברירת מחדל"
#: .\cookbook\forms.py:77
msgid ""
"To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything."
msgstr ""
"בשביל למנוע כפילויות, מתוכנים בעלי שם זהה למתכון קיים לא יעובדו. סמן כאן כדי "
"לייבא בכל זאת."
#: .\cookbook\forms.py:108
msgid "Maximum number of users for this space reached."
msgstr "המספר המקסימלי של משתמשים עבור מרחב זה נוצל."
#: .\cookbook\forms.py:114
msgid "Email address already taken!"
msgstr "כתובת האימייל כבר בשימוש!"
#: .\cookbook\forms.py:121
msgid ""
"An email address is not required but if present the invite link will be sent "
"to the user."
msgstr "כתובת אימייל לא נדרשת אבל אם קיימת, קישור השיתוף ישלח למשתמש."
#: .\cookbook\forms.py:133
msgid "Name already taken."
msgstr "שם כבר בשימוש."
#: .\cookbook\forms.py:144 .\cookbook\forms.py:158
msgid "Accept Terms and Privacy"
msgstr "הסכם לתנאים ולפרטיות"
#: .\cookbook\helper\AllAuthCustomAdapter.py:41
msgid ""
"In order to prevent spam, the requested email was not send. Please wait a "
"few minutes and try again."
msgstr "למניעת ספאם, המייל לא נשלח. נסה שוב עוד מספר דקות."
#: .\cookbook\helper\permission_helper.py:165
#: .\cookbook\helper\permission_helper.py:186 .\cookbook\views\views.py:138
msgid "You are not logged in and therefore cannot view this page!"
msgstr "אין באפשרותך לראות עמוד זה, כי אינך מחובר!"
#: .\cookbook\helper\permission_helper.py:168
#: .\cookbook\helper\permission_helper.py:173
#: .\cookbook\helper\permission_helper.py:198
#: .\cookbook\helper\permission_helper.py:265
#: .\cookbook\helper\permission_helper.py:279
#: .\cookbook\helper\permission_helper.py:290
#: .\cookbook\helper\permission_helper.py:301
#: .\cookbook\helper\permission_helper.py:317
#: .\cookbook\helper\permission_helper.py:343
#: .\cookbook\helper\permission_helper.py:359
msgid "You do not have the required permissions to view this page!"
msgstr "אין לך הרשאות לראות דף זה!"
#: .\cookbook\helper\permission_helper.py:191
#: .\cookbook\helper\permission_helper.py:214
#: .\cookbook\helper\permission_helper.py:236
#: .\cookbook\helper\permission_helper.py:251
msgid "You cannot interact with this object as it is not owned by you!"
msgstr "אינך יכול/ה לפעול על אובייקט זה כיוון שהוא לא בבעלותך!"
#: .\cookbook\helper\permission_helper.py:420
msgid "You have reached the maximum number of recipes for your space."
msgstr "הגעת למכסת המתכונים במרחב זה."
#: .\cookbook\helper\permission_helper.py:432
msgid "You have more users than allowed in your space."
msgstr "יש לך יותר משתמשים מהמותר במרחב שלך."
#: .\cookbook\helper\recipe_url_import.py:319
msgid "reverse rotation"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:320
msgid "careful rotation"
msgstr "סיבוב זהיר"
#: .\cookbook\helper\recipe_url_import.py:321
msgid "knead"
msgstr "ללוש"
#: .\cookbook\helper\recipe_url_import.py:322
msgid "thicken"
msgstr "לעבות"
#: .\cookbook\helper\recipe_url_import.py:323
msgid "warm up"
msgstr "לחמם"
#: .\cookbook\helper\recipe_url_import.py:324
msgid "ferment"
msgstr "תסיסה"
#: .\cookbook\helper\recipe_url_import.py:325
msgid "slow cook"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:326
msgid "egg boiler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:327
msgid "kettle"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:328
msgid "blend"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:329
msgid "pre-clean"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:330
msgid "high temperature"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:331
msgid "rice cooker"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:332
msgid "caramelize"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:333
msgid "peeler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:334
msgid "slicer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:335
msgid "grater"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:336
msgid "spiralizer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:337
msgid "sous-vide"
msgstr "תחת ווקום"
#: .\cookbook\helper\shopping_helper.py:150
msgid "You must supply a servings size"
msgstr "עליך לספק גודל מנות"
#: .\cookbook\helper\template_helper.py:97
#: .\cookbook\helper\template_helper.py:99
#: .\cookbook\helper\template_helper.py:101
msgid "Could not parse template code."
msgstr "לא ניתן לנתח קוד מהתבנית."
#: .\cookbook\integration\copymethat.py:44
#: .\cookbook\integration\melarecipes.py:37
msgid "Favorite"
msgstr "אהובים"
#: .\cookbook\integration\copymethat.py:50
msgid "I made this"
msgstr "הכנתי את זה"
#: .\cookbook\integration\integration.py:238
msgid ""
"Importer expected a .zip file. Did you choose the correct importer type for "
"your data ?"
msgstr "המייבא ציפה לקובץ zip. האם בחרת את המייבא הנכון עבור המידע שלך?"
#: .\cookbook\integration\integration.py:241
msgid ""
"An unexpected error occurred during the import. Please make sure you have "
"uploaded a valid file."
msgstr "שגיאה לא צפויה קרתה בזמן הייבוא. אנא ודא שהעלת קובץ תקף."
#: .\cookbook\integration\integration.py:246
msgid "The following recipes were ignored because they already existed:"
msgstr "התעלמו מהמתכונים הנ\"ל כיוון שהם כבר קיימים:"
#: .\cookbook\integration\integration.py:250
#, python-format
msgid "Imported %s recipes."
msgstr "יובאו %s מתכונים."
#: .\cookbook\integration\mealie1.py:210
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "Calories"
msgstr "קלוריות"
#: .\cookbook\integration\mealie1.py:211
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
msgid "Carbohydrates"
msgstr "פחמימות"
#: .\cookbook\integration\mealie1.py:212
msgid "Cholesterol"
msgstr ""
#: .\cookbook\integration\mealie1.py:213
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
msgid "Fat"
msgstr "שומן"
#: .\cookbook\integration\mealie1.py:214
msgid "Fiber"
msgstr ""
#: .\cookbook\integration\mealie1.py:215
msgid "Protein"
msgstr ""
#: .\cookbook\integration\mealie1.py:216
msgid "Saturated Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:217
msgid "Sodium"
msgstr ""
#: .\cookbook\integration\mealie1.py:218
msgid "Sugar"
msgstr ""
#: .\cookbook\integration\mealie1.py:219
msgid "Trans Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:220
msgid "Unsaturated Fat"
msgstr ""
#: .\cookbook\integration\openeats.py:28
msgid "Recipe source:"
msgstr "מקור המתכון:"
#: .\cookbook\integration\paprika.py:49
msgid "Notes"
msgstr "הערות"
#: .\cookbook\integration\paprika.py:52
msgid "Nutritional Information"
msgstr "מידע תזונתי"
#: .\cookbook\integration\paprika.py:56
msgid "Source"
msgstr "מקור"
#: .\cookbook\integration\recettetek.py:55
#: .\cookbook\integration\recipekeeper.py:70
msgid "Imported from"
msgstr "יובא מ"
#: .\cookbook\integration\saffron.py:23
msgid "Servings"
msgstr "מנות"
#: .\cookbook\integration\saffron.py:25
msgid "Waiting time"
msgstr "זמן המתנה"
#: .\cookbook\integration\saffron.py:27
msgid "Preparation Time"
msgstr "זמן הכנה"
#: .\cookbook\integration\saffron.py:29 .\cookbook\templates\index.html:6
msgid "Cookbook"
msgstr "ספר מתכונים"
#: .\cookbook\integration\saffron.py:31
msgid "Section"
msgstr "חלק"
#: .\cookbook\management\commands\fix_duplicate_properties.py:15
msgid "Fixes foods with "
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:14
msgid "Rebuilds full text search index on Recipe"
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:18
msgid "Only Postgresql databases use full text search, no index to rebuild"
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:29
msgid "Recipe index rebuild complete."
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:31
msgid "Recipe index rebuild failed."
msgstr ""
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
msgid "Breakfast"
msgstr "ארוחת בוקר"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:19
msgid "Lunch"
msgstr "אורחת צהריים"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:24
msgid "Dinner"
msgstr "ארוחת ערב"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:29 .\cookbook\models.py:971
msgid "Other"
msgstr "אחר"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "g"
msgstr "ג"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "Proteins"
msgstr "חלבונים"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "kcal"
msgstr "קק\"ל"
#: .\cookbook\models.py:325
msgid ""
"Maximum file storage for space in MB. 0 for unlimited, -1 to disable file "
"upload."
msgstr ""
#: .\cookbook\models.py:513
msgid "Search"
msgstr "חיפוש"
#: .\cookbook\models.py:514
msgid "Meal-Plan"
msgstr "תכנון מנה"
#: .\cookbook\models.py:515
msgid "Books"
msgstr "ספרים"
#: .\cookbook\models.py:516 .\cookbook\views\views.py:416
#: .\cookbook\views\views.py:417
msgid "Shopping"
msgstr "קניות"
#: .\cookbook\models.py:967
msgid "Nutrition"
msgstr "תזונה"
#: .\cookbook\models.py:968
msgid "Allergen"
msgstr "אלרגנים"
#: .\cookbook\models.py:969
msgid "Price"
msgstr "מחיר"
#: .\cookbook\models.py:970
msgid "Goal"
msgstr "מטרה"
#: .\cookbook\models.py:1467 .\cookbook\templates\search_info.html:28
msgid "Simple"
msgstr "פשוט"
#: .\cookbook\models.py:1468 .\cookbook\templates\search_info.html:33
msgid "Phrase"
msgstr "משפט"
#: .\cookbook\models.py:1469 .\cookbook\templates\search_info.html:38
msgid "Web"
msgstr ""
#: .\cookbook\models.py:1470 .\cookbook\templates\search_info.html:47
msgid "Raw"
msgstr "נא"
#: .\cookbook\models.py:1525
msgid "Food Alias"
msgstr ""
#: .\cookbook\models.py:1526
msgid "Unit Alias"
msgstr ""
#: .\cookbook\models.py:1527
msgid "Keyword Alias"
msgstr ""
#: .\cookbook\models.py:1528
msgid "Description Replace"
msgstr "החלפת תיאור"
#: .\cookbook\models.py:1529
msgid "Instruction Replace"
msgstr "החלפת הוראות"
#: .\cookbook\models.py:1530
msgid "Never Unit"
msgstr ""
#: .\cookbook\models.py:1531
msgid "Transpose Words"
msgstr ""
#: .\cookbook\models.py:1532
msgid "Food Replace"
msgstr ""
#: .\cookbook\models.py:1533
msgid "Unit Replace"
msgstr ""
#: .\cookbook\models.py:1534
msgid "Name Replace"
msgstr ""
#: .\cookbook\models.py:1564
msgid "Recipe"
msgstr "מתכון"
#: .\cookbook\models.py:1565
msgid "Food"
msgstr "אוכל"
#: .\cookbook\models.py:1566
msgid "Keyword"
msgstr "מילת מפתח"
#: .\cookbook\serializer.py:262
msgid "File uploads are not enabled for this Space."
msgstr ""
#: .\cookbook\serializer.py:273
msgid "You have reached your file upload limit."
msgstr "הגעת למכסת הקבצים."
#: .\cookbook\serializer.py:281
msgid "The given file type is not allowed."
msgstr ""
#: .\cookbook\serializer.py:421 .\cookbook\views\views.py:94
msgid ""
"You have the reached the maximum amount of spaces that can be owned by you."
msgstr ""
#: .\cookbook\serializer.py:434
msgid "Space Name must be unique."
msgstr ""
#: .\cookbook\serializer.py:469
msgid "Cannot modify Space owner permission."
msgstr ""
#: .\cookbook\serializer.py:1596
msgid "Hello"
msgstr "שלום"
#: .\cookbook\serializer.py:1596
msgid "You have been invited by "
msgstr ""
#: .\cookbook\serializer.py:1598
msgid " to join their Tandoor Recipes space "
msgstr ""
#: .\cookbook\serializer.py:1600
msgid "Click the following link to activate your account: "
msgstr ""
#: .\cookbook\serializer.py:1602
msgid ""
"If the link does not work use the following code to manually join the space: "
msgstr ""
#: .\cookbook\serializer.py:1604
msgid "The invitation is valid until "
msgstr "ההזמנה תקפה עד "
#: .\cookbook\serializer.py:1606
msgid ""
"Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub "
msgstr ""
"Tandoor Recipes הוא מנהל מתכונים בקוד פתוח. ניתן למצוא אותנו ב-GitHub. "
#: .\cookbook\serializer.py:1609
msgid "Tandoor Recipes Invite"
msgstr ""
#: .\cookbook\serializer.py:1813
msgid "Existing shopping list to update"
msgstr ""
#: .\cookbook\serializer.py:1815
msgid ""
"List of ingredient IDs from the recipe to add, if not provided all "
"ingredients will be added."
msgstr ""
#: .\cookbook\serializer.py:1817
msgid ""
"Providing a list_recipe ID and servings of 0 will delete that shopping list."
msgstr ""
#: .\cookbook\serializer.py:1826
msgid "Amount of food to add to the shopping list"
msgstr ""
#: .\cookbook\serializer.py:1828
msgid "ID of unit to use for the shopping list"
msgstr ""
#: .\cookbook\serializer.py:1830
msgid "When set to true will delete all food from active shopping lists."
msgstr ""
#: .\cookbook\templates\404.html:5
msgid "404 Error"
msgstr "שגיאה 404"
#: .\cookbook\templates\404.html:18
msgid "The page you are looking for could not be found."
msgstr ""
#: .\cookbook\templates\404.html:33
msgid "Take me Home"
msgstr ""
#: .\cookbook\templates\404.html:35
msgid "Report a Bug"
msgstr "דווח על באג"
#: .\cookbook\templates\account\email.html:6
#: .\cookbook\templates\account\email.html:10
msgid "E-mail Addresses"
msgstr "כתובות אימייל"
#: .\cookbook\templates\account\email.html:12
msgid "The following e-mail addresses are associated with your account:"
msgstr ""
#: .\cookbook\templates\account\email.html:29
msgid "Verified"
msgstr "מאומת"
#: .\cookbook\templates\account\email.html:31
msgid "Unverified"
msgstr "לא מאומת"
#: .\cookbook\templates\account\email.html:33
msgid "Primary"
msgstr "ראשי"
#: .\cookbook\templates\account\email.html:40
msgid "Make Primary"
msgstr "הגדר כראשי"
#: .\cookbook\templates\account\email.html:42
msgid "Re-send Verification"
msgstr "שלח אימות מחדש"
#: .\cookbook\templates\account\email.html:43
#: .\cookbook\templates\socialaccount\connections.html:36
msgid "Remove"
msgstr "הסרה"
#: .\cookbook\templates\account\email.html:51
msgid "Warning:"
msgstr "אזהרה:"
#: .\cookbook\templates\account\email.html:51
msgid ""
"You currently do not have any e-mail address set up. You should really add "
"an e-mail address so you can receive notifications, reset your password, etc."
msgstr ""
#: .\cookbook\templates\account\email.html:57
msgid "Add E-mail Address"
msgstr "הוספת כתובת מייל"
#: .\cookbook\templates\account\email.html:62
msgid "Add E-mail"
msgstr "הוספת מייל"
#: .\cookbook\templates\account\email.html:72
msgid "Do you really want to remove the selected e-mail address?"
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:6
#: .\cookbook\templates\account\email_confirm.html:10
msgid "Confirm E-mail Address"
msgstr "אשר כתובת מייל"
#: .\cookbook\templates\account\email_confirm.html:16
#, python-format
msgid ""
"Please confirm that\n"
" %(email)s is an e-mail address "
"for user %(user_display)s\n"
" ."
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:22
msgid "Confirm"
msgstr "אשר"
#: .\cookbook\templates\account\email_confirm.html:29
#, python-format
msgid ""
"This e-mail confirmation link expired or is invalid. Please\n"
" issue a new e-mail confirmation "
"request."
msgstr ""
#: .\cookbook\templates\account\login.html:8
#: .\cookbook\templates\openid\login.html:8
msgid "Login"
msgstr "התחבר"
#: .\cookbook\templates\account\login.html:15
#: .\cookbook\templates\account\login.html:31
#: .\cookbook\templates\account\password_reset.html:39
#: .\cookbook\templates\account\password_reset_done.html:31
#: .\cookbook\templates\account\signup.html:68
#: .\cookbook\templates\account\signup_closed.html:15
#: .\cookbook\templates\openid\login.html:15
#: .\cookbook\templates\openid\login.html:26
#: .\cookbook\templates\socialaccount\authentication_error.html:15
msgid "Sign In"
msgstr "התחבר"
#: .\cookbook\templates\account\login.html:34
#: .\cookbook\templates\account\password_reset.html:41
#: .\cookbook\templates\account\password_reset_done.html:33
#: .\cookbook\templates\socialaccount\signup.html:8
#: .\cookbook\templates\socialaccount\signup.html:56
msgid "Sign Up"
msgstr "הירשם"
#: .\cookbook\templates\account\login.html:38
msgid "Lost your password?"
msgstr ""
#: .\cookbook\templates\account\login.html:39
#: .\cookbook\templates\account\password_reset.html:29
msgid "Reset My Password"
msgstr "איפוס סיסמא"
#: .\cookbook\templates\account\login.html:50
msgid "Social Login"
msgstr ""
#: .\cookbook\templates\account\login.html:51
msgid "You can use any of the following providers to sign in."
msgstr ""
#: .\cookbook\templates\account\logout.html:5
#: .\cookbook\templates\account\logout.html:9
#: .\cookbook\templates\account\logout.html:18
msgid "Sign Out"
msgstr "התנתק"
#: .\cookbook\templates\account\logout.html:11
msgid "Are you sure you want to sign out?"
msgstr ""
#: .\cookbook\templates\account\password_change.html:6
#: .\cookbook\templates\account\password_change.html:10
#: .\cookbook\templates\account\password_change.html:15
#: .\cookbook\templates\account\password_reset_from_key.html:7
#: .\cookbook\templates\account\password_reset_from_key.html:13
#: .\cookbook\templates\account\password_reset_from_key_done.html:7
#: .\cookbook\templates\account\password_reset_from_key_done.html:13
msgid "Change Password"
msgstr ""
#: .\cookbook\templates\account\password_change.html:16
msgid "Forgot Password?"
msgstr ""
#: .\cookbook\templates\account\password_reset.html:7
#: .\cookbook\templates\account\password_reset.html:13
#: .\cookbook\templates\account\password_reset_done.html:7
#: .\cookbook\templates\account\password_reset_done.html:18
msgid "Password Reset"
msgstr ""
#: .\cookbook\templates\account\password_reset.html:24
msgid ""
"Forgotten your password? Enter your e-mail address below, and we'll send you "
"an e-mail allowing you to reset it."
msgstr ""
#: .\cookbook\templates\account\password_reset.html:32
msgid "Password reset is disabled on this instance."
msgstr ""
#: .\cookbook\templates\account\password_reset_done.html:25
msgid ""
"We have sent you an e-mail. Please contact us if you do not receive it "
"within a few minutes."
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:13
msgid "Bad Token"
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:25
#, python-format
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used.\n"
" Please request a new "
"password reset."
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:33
msgid "change password"
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:36
#: .\cookbook\templates\account\password_reset_from_key_done.html:19
msgid "Your password is now changed."
msgstr ""
#: .\cookbook\templates\account\password_set.html:6
#: .\cookbook\templates\account\password_set.html:10
#: .\cookbook\templates\account\password_set.html:15
msgid "Set Password"
msgstr "קבע סיסמא"
#: .\cookbook\templates\account\signup.html:5
msgid "Register"
msgstr "הירשם"
#: .\cookbook\templates\account\signup.html:11
msgid "Create an Account"
msgstr "צור משתמש"
#: .\cookbook\templates\account\signup.html:41
msgid "I accept the follwoing"
msgstr "אני מקבל את התנאים הבאים"
#: .\cookbook\templates\account\signup.html:44
#: .\cookbook\templates\socialaccount\signup.html:35
msgid "Terms and Conditions"
msgstr "תנאים והגבלות"
#: .\cookbook\templates\account\signup.html:47
#: .\cookbook\templates\socialaccount\signup.html:38
msgid "and"
msgstr "ו"
#: .\cookbook\templates\account\signup.html:51
#: .\cookbook\templates\socialaccount\signup.html:42
msgid "Privacy Policy"
msgstr "מדיניות פרטיות"
#: .\cookbook\templates\account\signup.html:64
msgid "Create User"
msgstr "צור משתמש"
#: .\cookbook\templates\account\signup.html:68
msgid "Already have an account?"
msgstr "יש לך חשבון?"
#: .\cookbook\templates\account\signup_closed.html:5
#: .\cookbook\templates\account\signup_closed.html:11
msgid "Sign Up Closed"
msgstr "ההרשמה סגורה"
#: .\cookbook\templates\account\signup_closed.html:13
msgid "We are sorry, but the sign up is currently closed."
msgstr "אנו מצטערים, אך ההרשמה כרגע סגורה."
#: .\cookbook\templates\frontend\tandoor.html:15
msgid "Tandoor Recipe Manager"
msgstr ""
#: .\cookbook\templates\index.html:28
msgid "Search recipe ..."
msgstr ""
#: .\cookbook\templates\index.html:43
msgid "New Recipe"
msgstr ""
#: .\cookbook\templates\index.html:46
msgid "Import Recipe"
msgstr ""
#: .\cookbook\templates\index.html:52
msgid "Advanced Search"
msgstr ""
#: .\cookbook\templates\index.html:56
msgid "Reset Search"
msgstr ""
#: .\cookbook\templates\index.html:84
msgid "Last viewed"
msgstr ""
#: .\cookbook\templates\index.html:86
msgid "Recipes"
msgstr "מתכונים"
#: .\cookbook\templates\index.html:93
msgid "Log in to view recipes"
msgstr ""
#: .\cookbook\templates\markdown_info.html:5
#: .\cookbook\templates\markdown_info.html:13
msgid "Markdown Info"
msgstr ""
#: .\cookbook\templates\markdown_info.html:14
msgid ""
"\n"
" Markdown is lightweight markup language that can be used to format "
"plain text easily.\n"
" This site uses the Python Markdown library to\n"
" convert your text into nice looking HTML. Its full markdown "
"documentation can be found\n"
" here.\n"
" An incomplete but most likely sufficient documentation can be found "
"below.\n"
" "
msgstr ""
#: .\cookbook\templates\markdown_info.html:25
msgid "Headers"
msgstr ""
#: .\cookbook\templates\markdown_info.html:54
msgid "Formatting"
msgstr ""
#: .\cookbook\templates\markdown_info.html:56
#: .\cookbook\templates\markdown_info.html:72
msgid "Line breaks are inserted by adding two spaces after the end of a line"
msgstr ""
#: .\cookbook\templates\markdown_info.html:57
#: .\cookbook\templates\markdown_info.html:73
msgid "or by leaving a blank line in between."
msgstr ""
#: .\cookbook\templates\markdown_info.html:59
#: .\cookbook\templates\markdown_info.html:74
msgid "This text is bold"
msgstr ""
#: .\cookbook\templates\markdown_info.html:60
#: .\cookbook\templates\markdown_info.html:75
msgid "This text is italic"
msgstr ""
#: .\cookbook\templates\markdown_info.html:61
#: .\cookbook\templates\markdown_info.html:77
msgid "Blockquotes are also possible"
msgstr ""
#: .\cookbook\templates\markdown_info.html:84
msgid "Lists"
msgstr ""
#: .\cookbook\templates\markdown_info.html:85
msgid ""
"Lists can ordered or unordered. It is important to leave a blank line "
"before the list!"
msgstr ""
#: .\cookbook\templates\markdown_info.html:87
#: .\cookbook\templates\markdown_info.html:108
msgid "Ordered List"
msgstr ""
#: .\cookbook\templates\markdown_info.html:89
#: .\cookbook\templates\markdown_info.html:90
#: .\cookbook\templates\markdown_info.html:91
#: .\cookbook\templates\markdown_info.html:110
#: .\cookbook\templates\markdown_info.html:111
#: .\cookbook\templates\markdown_info.html:112
msgid "unordered list item"
msgstr ""
#: .\cookbook\templates\markdown_info.html:93
#: .\cookbook\templates\markdown_info.html:114
msgid "Unordered List"
msgstr ""
#: .\cookbook\templates\markdown_info.html:95
#: .\cookbook\templates\markdown_info.html:96
#: .\cookbook\templates\markdown_info.html:97
#: .\cookbook\templates\markdown_info.html:116
#: .\cookbook\templates\markdown_info.html:117
#: .\cookbook\templates\markdown_info.html:118
msgid "ordered list item"
msgstr ""
#: .\cookbook\templates\markdown_info.html:125
msgid "Images & Links"
msgstr ""
#: .\cookbook\templates\markdown_info.html:126
msgid ""
"Links can be formatted with Markdown. This application also allows to paste "
"links directly into markdown fields without any formatting."
msgstr ""
#: .\cookbook\templates\markdown_info.html:132
#: .\cookbook\templates\markdown_info.html:145
msgid "This will become an image"
msgstr ""
#: .\cookbook\templates\markdown_info.html:152
msgid "Tables"
msgstr ""
#: .\cookbook\templates\markdown_info.html:153
msgid ""
"Markdown tables are hard to create by hand. It is recommended to use a table "
"editor like this one."
msgstr ""
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:171
#: .\cookbook\templates\markdown_info.html:177
msgid "Table"
msgstr ""
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:172
msgid "Header"
msgstr ""
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:178
msgid "Cell"
msgstr ""
#: .\cookbook\templates\no_groups_info.html:5
#: .\cookbook\templates\no_groups_info.html:12
msgid "No Permissions"
msgstr "אין הרשאות"
#: .\cookbook\templates\no_groups_info.html:17
msgid "You do not have any groups and therefor cannot use this application."
msgstr ""
#: .\cookbook\templates\no_groups_info.html:18
#: .\cookbook\templates\no_perm_info.html:15
msgid "Please contact your administrator."
msgstr "אנא פנה למנהל המערכת."
#: .\cookbook\templates\no_perm_info.html:5
#: .\cookbook\templates\no_perm_info.html:12
msgid "No Permission"
msgstr "אין הרשאות"
#: .\cookbook\templates\no_perm_info.html:15
msgid ""
"You do not have the required permissions to view this page or perform this "
"action."
msgstr ""
#: .\cookbook\templates\offline.html:5
msgid "Offline"
msgstr "לא מקוון"
#: .\cookbook\templates\openid\login.html:27
#: .\cookbook\templates\socialaccount\authentication_error.html:27
msgid "Back"
msgstr "אחורה"
#: .\cookbook\templates\rest_framework\api.html:5
msgid "Recipe Home"
msgstr ""
#: .\cookbook\templates\rest_framework\api.html:11
msgid "API Documentation"
msgstr ""
#: .\cookbook\templates\search_info.html:5
#: .\cookbook\templates\search_info.html:9
msgid "Search Settings"
msgstr "הגדרות חיפוש"
#: .\cookbook\templates\search_info.html:10
msgid ""
"\n"
" Creating the best search experience is complicated and weighs "
"heavily on your personal configuration. \n"
" Changing any of the search settings can have significant impact on "
"the speed and quality of the results.\n"
" Search Methods, Trigrams and Full Text Search configurations are "
"only available if you are using Postgres for your database.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:19
msgid "Search Methods"
msgstr "שיטות חיפוש"
#: .\cookbook\templates\search_info.html:23
msgid ""
" \n"
" Full text searches attempt to normalize the words provided to "
"match common variants. For example: 'forked', 'forking', 'forks' will all "
"normalize to 'fork'.\n"
" There are several methods available, described below, that will "
"control how the search behavior should react when multiple words are "
"searched.\n"
" Full technical details on how these operate can be viewed on Postgresql's website.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:29
msgid ""
" \n"
" Simple searches ignore punctuation and common words such as "
"'the', 'a', 'and'. And will treat separate words as required.\n"
" Searching for 'apple or flour' will return any recipe that "
"includes both 'apple' and 'flour' anywhere in the fields that have been "
"selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:34
msgid ""
" \n"
" Phrase searches ignore punctuation, but will search for all of "
"the words in the exact order provided.\n"
" Searching for 'apple or flour' will only return a recipe that "
"includes the exact phrase 'apple or flour' in any of the fields that have "
"been selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:39
msgid ""
" \n"
" Web searches simulate functionality found on many web search "
"sites supporting special syntax.\n"
" Placing quotes around several words will convert those words "
"into a phrase.\n"
" 'or' is recognized as searching for the word (or phrase) "
"immediately before 'or' OR the word (or phrase) directly after.\n"
" '-' is recognized as searching for recipes that do not include "
"the word (or phrase) that comes immediately after. \n"
" For example searching for 'apple pie' or cherry -butter will "
"return any recipe that includes the phrase 'apple pie' or the word "
"'cherry' \n"
" in any field included in the full text search but exclude any "
"recipe that has the word 'butter' in any field included.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:48
msgid ""
" \n"
" Raw search is similar to Web except will take puncuation "
"operators such as '|', '&' and '()'\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:59
msgid ""
" \n"
" Another approach to searching that also requires Postgresql is "
"fuzzy search or trigram similarity. A trigram is a group of three "
"consecutive characters.\n"
" For example searching for 'apple' will create x trigrams 'app', "
"'ppl', 'ple' and will create a score of how closely words match the "
"generated trigrams.\n"
" One benefit of searching trigams is that a search for 'sandwich' "
"will find misspelled words such as 'sandwhich' that would be missed by other "
"methods.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:69
msgid "Search Fields"
msgstr "שדות חיפוש"
#: .\cookbook\templates\search_info.html:73
msgid ""
" \n"
" Unaccent is a special case in that it enables searching a field "
"'unaccented' for each search style attempting to ignore accented values. \n"
" For example when you enable unaccent for 'Name' any search "
"(starts with, contains, trigram) will attempt the search ignoring accented "
"characters.\n"
" \n"
" For the other options, you can enable search on any or all "
"fields and they will be combined together with an assumed 'OR'.\n"
" For example enabling 'Name' for Starts With, 'Name' and "
"'Description' for Partial Match and 'Ingredients' and 'Keywords' for Full "
"Search\n"
" and searching for 'apple' will generate a search that will "
"return recipes that have:\n"
" - A recipe name that starts with 'apple'\n"
" - OR a recipe name that contains 'apple'\n"
" - OR a recipe description that contains 'apple'\n"
" - OR a recipe that will have a full text search match ('apple' "
"or 'apples') in ingredients\n"
" - OR a recipe that will have a full text search match in "
"Keywords\n"
"\n"
" Combining too many fields in too many types of search can have a "
"negative impact on performance, create duplicate results or return "
"unexpected results.\n"
" For example, enabling fuzzy search or partial matches will "
"interfere with web search methods. \n"
" Searching for 'apple -pie' with fuzzy search and full text "
"search will return the recipe Apple Pie. Though it is not included in the "
"full text results, it does match the trigram results.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:95
msgid "Search Index"
msgstr ""
#: .\cookbook\templates\search_info.html:99
msgid ""
" \n"
" Trigram search and Full Text Search both rely on database "
"indexes to perform effectively. \n"
" You can rebuild the indexes on all fields in the Admin page for "
"Recipes and selecting all recipes and running 'rebuild index for selected "
"recipes'\n"
" You can also rebuild indexes at the command line by executing "
"the management command 'python manage.py rebuildindex'\n"
" "
msgstr ""
#: .\cookbook\templates\setup.html:6
msgid "Cookbook Setup"
msgstr ""
#: .\cookbook\templates\setup.html:14
msgid "Setup"
msgstr ""
#: .\cookbook\templates\setup.html:15
msgid ""
"To start using this application you must first create a superuser account."
msgstr ""
#: .\cookbook\templates\setup.html:20
msgid "Create Superuser account"
msgstr ""
#: .\cookbook\templates\socialaccount\authentication_error.html:7
#: .\cookbook\templates\socialaccount\authentication_error.html:23
msgid "Social Network Login Failure"
msgstr ""
#: .\cookbook\templates\socialaccount\authentication_error.html:25
msgid ""
"An error occurred while attempting to login via your social network account."
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:4
#: .\cookbook\templates\socialaccount\connections.html:7
msgid "Account Connections"
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:10
msgid ""
"You can sign in to your account using any of the following third party\n"
" accounts:"
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:44
msgid ""
"You currently have no social network accounts connected to this account."
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:47
msgid "Add a 3rd Party Account"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:5
#: .\cookbook\templates\socialaccount\signup.html:5
msgid "Signup"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:9
#, python-format
msgid "Connect %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:11
#, python-format
msgid "You are about to connect a new third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:13
#, python-format
msgid "Sign In Via %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:15
#, python-format
msgid "You are about to sign in using a third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:20
msgid "Continue"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:10
#, python-format
msgid ""
"You are about to use your\n"
" %(provider_name)s account to login to\n"
" %(site_name)s. As a final step, please complete the following form:"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:32
#, fuzzy
#| msgid "I accept the follwoing"
msgid "I accept the following"
msgstr "אני מקבל את התנאים הבאים"
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:23
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:31
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:39
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:47
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:55
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:63
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:71
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:79
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:87
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:95
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:103
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:111
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:119
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:127
msgid "Sign in using"
msgstr ""
#: .\cookbook\templates\space_overview.html:6
msgid "Overview"
msgstr "סקירה כללית"
#: .\cookbook\templates\space_overview.html:13
msgid "Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:17
msgid ""
"Recipes, foods, shopping lists and more are organized in spaces of one or "
"more people."
msgstr ""
#: .\cookbook\templates\space_overview.html:18
msgid ""
"You can either be invited into an existing space or create your own one."
msgstr ""
#: .\cookbook\templates\space_overview.html:25
msgid "Your Spaces"
msgstr "המרחבים שלך"
#: .\cookbook\templates\space_overview.html:53
msgid "Owner"
msgstr ""
#: .\cookbook\templates\space_overview.html:73
#: .\cookbook\templates\space_overview.html:83
msgid "Join Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:76
msgid "Join an existing space."
msgstr ""
#: .\cookbook\templates\space_overview.html:78
msgid ""
"To join an existing space either enter your invite token or click on the "
"invite link the space owner send you."
msgstr ""
#: .\cookbook\templates\space_overview.html:91
#: .\cookbook\templates\space_overview.html:100
msgid "Create Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:94
msgid "Create your own recipe space."
msgstr ""
#: .\cookbook\templates\space_overview.html:96
msgid "Start your own recipe space and invite other users to it."
msgstr ""
#: .\cookbook\templates\system.html:23
msgid "System"
msgstr "מערכת"
#: .\cookbook\templates\system.html:24
msgid ""
"\n"
" Tandoor Recipes is an open source free software application. It can "
"be found on\n"
" GitHub.\n"
" Changelogs can be found here.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:30
msgid "System Information"
msgstr ""
#: .\cookbook\templates\system.html:51
msgid ""
"\n"
" You need to execute version.py in your update "
"script to generate version information (done automatically in docker).\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:56
msgid "Plugins"
msgstr ""
#: .\cookbook\templates\system.html:67
msgid "Media Serving"
msgstr ""
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:123 .\cookbook\templates\system.html:134
msgid "Warning"
msgstr ""
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:125 .\cookbook\templates\system.html:134
msgid "Ok"
msgstr ""
#: .\cookbook\templates\system.html:70
msgid ""
"Serving media files directly using gunicorn/python is not recommend!\n"
" Please follow the steps described\n"
" here to update\n"
" your installation.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:76 .\cookbook\templates\system.html:91
#: .\cookbook\templates\system.html:104 .\cookbook\templates\system.html:115
#: .\cookbook\views\views.py:168
msgid "Everything is fine!"
msgstr ""
#: .\cookbook\templates\system.html:80
msgid "Secret Key"
msgstr ""
#: .\cookbook\templates\system.html:84
msgid ""
"\n"
" You do not have a SECRET_KEY configured in your "
".env file. Django defaulted to the\n"
" standard key\n"
" provided with the installation which is publicly know and "
"insecure! Please set\n"
" SECRET_KEY int the .env configuration "
"file.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:94
msgid "Debug Mode"
msgstr ""
#: .\cookbook\templates\system.html:98
msgid ""
"\n"
" This application is still running in debug mode. This is most "
"likely not needed. Turn of debug mode by\n"
" setting\n"
" DEBUG=0 int the .env configuration "
"file.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:107
msgid "Allowed Hosts"
msgstr ""
#: .\cookbook\templates\system.html:111
msgid ""
"\n"
" Your allowed hosts are configured to allow every host. This "
"might be ok in some setups but should be avoided. Please see the docs about "
"this.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:118
msgid "Database"
msgstr ""
#: .\cookbook\templates\system.html:121
msgid "Info"
msgstr ""
#: .\cookbook\templates\system.html:131 .\cookbook\templates\system.html:148
#, fuzzy
#| msgid "Use fractions"
msgid "Migrations"
msgstr "שימוש בשברים"
#: .\cookbook\templates\system.html:137
msgid ""
"\n"
" Migrations should never fail!\n"
" Failed migrations will likely cause major parts of the app to "
"not function correctly.\n"
" If a migration fails make sure you are on the latest version and "
"if so please post the migration log and the overview below in a GitHub "
"issue.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "False"
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "True"
msgstr ""
#: .\cookbook\templates\system.html:268
msgid "Hide"
msgstr ""
#: .\cookbook\templates\system.html:271
msgid "Show"
msgstr ""
#: .\cookbook\templates\test2.html:6
msgid "Export Recipes"
msgstr ""
#: .\cookbook\templates\test2.html:14 .\cookbook\templates\test2.html:20
msgid "Export"
msgstr "יצא"
#: .\cookbook\views\api.py:198 .\cookbook\views\api.py:326
msgid "Parameter updated_at incorrectly formatted"
msgstr ""
#: .\cookbook\views\api.py:351 .\cookbook\views\api.py:484
#, python-brace-format
msgid "No {self.basename} with id {pk} exists"
msgstr ""
#: .\cookbook\views\api.py:355
msgid "Cannot merge with the same object!"
msgstr ""
#: .\cookbook\views\api.py:362
#, python-brace-format
msgid "No {self.basename} with id {target} exists"
msgstr ""
#: .\cookbook\views\api.py:367
msgid "Cannot merge with child object!"
msgstr ""
#: .\cookbook\views\api.py:405
#, python-brace-format
msgid "{source.name} was merged successfully with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:411
#, python-brace-format
msgid "An error occurred attempting to merge {source.name} with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:493
#, python-brace-format
msgid "{child.name} was moved successfully to the root."
msgstr ""
#: .\cookbook\views\api.py:496 .\cookbook\views\api.py:514
msgid "An error occurred attempting to move "
msgstr ""
#: .\cookbook\views\api.py:499
msgid "Cannot move an object to itself!"
msgstr ""
#: .\cookbook\views\api.py:505
#, python-brace-format
msgid "No {self.basename} with id {parent} exists"
msgstr ""
#: .\cookbook\views\api.py:511
#, python-brace-format
msgid "{child.name} was moved successfully to parent {parent.name}"
msgstr ""
#: .\cookbook\views\api.py:992
#, python-brace-format
msgid "{obj.name} was removed from the shopping list."
msgstr ""
#: .\cookbook\views\api.py:997 .\cookbook\views\api.py:1627
#, python-brace-format
msgid "{obj.name} was added to the shopping list."
msgstr ""
#: .\cookbook\views\api.py:1239
msgid "Filter meal plans from date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1241
msgid "Filter meal plans to date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1244
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1404
msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1406
msgid "Query string matched (fuzzy) against object name."
msgstr ""
#: .\cookbook\views\api.py:1442
msgid ""
"Query string matched (fuzzy) against recipe name. In the future also "
"fulltext search."
msgstr ""
#: .\cookbook\views\api.py:1444
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr ""
#: .\cookbook\views\api.py:1445
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr ""
#: .\cookbook\views\api.py:1446
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1447
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1448
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1450
msgid "ID of food a recipe should have. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1451
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr ""
#: .\cookbook\views\api.py:1452
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1453
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr ""
#: .\cookbook\views\api.py:1454
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1456
msgid "ID of book a recipe should be in. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1457
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr ""
#: .\cookbook\views\api.py:1458
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1459
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr ""
#: .\cookbook\views\api.py:1460
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1462
msgid "ID of unit a recipe should have."
msgstr ""
#: .\cookbook\views\api.py:1464
msgid "Exact rating of recipe"
msgstr ""
#: .\cookbook\views\api.py:1465
msgid "Rating a recipe should have or greater."
msgstr ""
#: .\cookbook\views\api.py:1466
msgid "Rating a recipe should have or smaller."
msgstr ""
#: .\cookbook\views\api.py:1468
msgid "Filter recipes cooked X times."
msgstr ""
#: .\cookbook\views\api.py:1469
msgid "Filter recipes cooked X times or more."
msgstr ""
#: .\cookbook\views\api.py:1470
msgid "Filter recipes cooked X times or less."
msgstr ""
#: .\cookbook\views\api.py:1472
msgid "Filter recipes created on the given date."
msgstr ""
#: .\cookbook\views\api.py:1473
msgid "Filter recipes created on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1474
msgid "Filter recipes created on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1476 .\cookbook\views\api.py:1477
#: .\cookbook\views\api.py:1478
msgid "Filter recipes updated on the given date."
msgstr ""
#: .\cookbook\views\api.py:1480
msgid "Filter recipes last cooked on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1481
msgid "Filter recipes last cooked on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1483 .\cookbook\views\api.py:1484
msgid "Filter recipes lasts viewed on the given date."
msgstr ""
#: .\cookbook\views\api.py:1486
msgid "Filter recipes for ones created by the given user ID"
msgstr ""
#: .\cookbook\views\api.py:1487
msgid "If only internal recipes should be returned. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1488
msgid "Returns the results in randomized order. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1490
msgid ""
"Determines the order of the results. Options are: score,-score,name,-name,"
"lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-"
"created_at,lastviewed,-lastviewed"
msgstr ""
#: .\cookbook\views\api.py:1492
msgid "Returns new results first in search results. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1493
msgid ""
"Returns the given number of recently viewed recipes before search results "
"(if given)"
msgstr ""
#: .\cookbook\views\api.py:1494
msgid "ID of a custom filter. Returns all recipes matched by that filter."
msgstr ""
#: .\cookbook\views\api.py:1495
msgid "Filter recipes that can be made with OnHand food. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1773
msgid ""
"Return the PropertyTypes matching the property category. Repeat for "
"multiple."
msgstr ""
#: .\cookbook\views\api.py:1804 .\cookbook\views\api.py:1860
msgid "Returns only entries associated with the given mealplan id"
msgstr ""
#: .\cookbook\views\api.py:1858
msgid ""
"Returns only elements updated after the given timestamp in ISO 8601 format."
msgstr ""
#: .\cookbook\views\api.py:2031
msgid ""
"Return the Automations matching the automation type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2048
msgid ""
"Text field to store data that gets carried over to the UserSpace created "
"from the InviteLink"
msgstr ""
#: .\cookbook\views\api.py:2049
msgid "Only return InviteLinks that have not been used yet."
msgstr ""
#: .\cookbook\views\api.py:2076
msgid "Return the CustomFilters matching the model type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2176
msgid "Nothing to do."
msgstr ""
#: .\cookbook\views\api.py:2222
msgid "Invalid Url"
msgstr ""
#: .\cookbook\views\api.py:2228
msgid "Connection Refused."
msgstr ""
#: .\cookbook\views\api.py:2232
msgid "Bad URL Schema."
msgstr ""
#: .\cookbook\views\api.py:2257
msgid "No usable data could be found."
msgstr ""
#: .\cookbook\views\api.py:2286 .\cookbook\views\api.py:2434
msgid "You must select an AI provider to perform your request."
msgstr ""
#: .\cookbook\views\api.py:2293 .\cookbook\views\api.py:2441
msgid ""
"You don't have any credits remaining to use AI or AI features are not "
"enabled for your space."
msgstr ""
#: .\cookbook\views\api.py:2499 .\cookbook\views\api.py:2667
msgid "File is above space limit"
msgstr ""
#: .\cookbook\views\api.py:2522 .\cookbook\views\api.py:2684
msgid "Importing is not implemented for this provider"
msgstr ""
#: .\cookbook\views\api.py:2548
msgid ""
"The PDF Exporter is not enabled on this instance as it is still in an "
"experimental state."
msgstr ""
#: .\cookbook\views\api.py:2842
msgid "This feature is not yet available in the hosted version of tandoor!"
msgstr ""
#: .\cookbook\views\api.py:2863
msgid "Sync successful!"
msgstr ""
#: .\cookbook\views\api.py:2866
msgid "Error synchronizing with Storage"
msgstr ""
#: .\cookbook\views\views.py:89
msgid "This feature is not available in the demo version!"
msgstr ""
#: .\cookbook\views\views.py:110
msgid ""
"You have successfully created your own recipe space. Start by adding some "
"recipes or invite other people to join you."
msgstr ""
#: .\cookbook\views\views.py:171
#, python-format
msgid "PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!"
msgstr ""
#: .\cookbook\views\views.py:174
#, python-format
msgid "You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended"
msgstr ""
#: .\cookbook\views\views.py:178
msgid "Unable to determine PostgreSQL version."
msgstr ""
#: .\cookbook\views\views.py:182
msgid ""
"This application is not running with a Postgres database backend. This is ok "
"but not recommended as some features only work with postgres databases."
msgstr ""
#: .\cookbook\views\views.py:296
msgid ""
"The setup page can only be used to create the first "
"user! If you have forgotten your superuser credentials "
"please consult the django documentation on how to reset passwords."
msgstr ""
#: .\cookbook\views\views.py:304
msgid "Passwords dont match!"
msgstr ""
#: .\cookbook\views\views.py:312
msgid "User has been created, please login!"
msgstr ""
#: .\cookbook\views\views.py:352
msgid ""
"Reporting share links is not enabled for this instance. Please notify the "
"page administrator to report problems."
msgstr ""
#: .\cookbook\views\views.py:357
msgid ""
"Recipe sharing link has been disabled! For additional information please "
"contact the page administrator."
msgstr ""
#: .\cookbook\views\views.py:383
msgid "Manage recipes, shopping list, meal plans and more."
msgstr ""
#: .\cookbook\views\views.py:397 .\cookbook\views\views.py:398
msgid "Plan"
msgstr ""
#: .\cookbook\views\views.py:399
msgid "View your meal Plan"
msgstr ""
#: .\cookbook\views\views.py:418
msgid "View your shopping lists"
msgstr ""
#~ msgid ""
#~ "Both fields are optional. If none are given the username will be "
#~ "displayed instead"
#~ msgstr "שני השדות אופציונלים. אם שני השדות ריקים, שם המשתמש יוצג במקום."
#~ msgid "Name"
#~ msgstr "שם"
#~ msgid "Keywords"
#~ msgstr "מילות מפתח"
#~ msgid "Preparation time in minutes"
#~ msgstr "זמן הכנה בדקות"
#~ msgid "Waiting time (cooking/baking) in minutes"
#~ msgstr "זמן המתנה (בישול/אפייה) בדקות"
#~ msgid "Path"
#~ msgstr "נתיב"
#~ msgid "Storage UID"
#~ msgstr "אחסון UID"
#~ msgid "Add your comment: "
#~ msgstr "הוסף את ההערה שלך::- "
#~ msgid "Leave empty for dropbox and enter app password for nextcloud."
#~ msgstr "השאר ריק עבור Dropbox והכנס סיסמא עבור NextCloud."
#~ msgid "Leave empty for nextcloud and enter api token for dropbox."
#~ msgstr "השאר ריק עבור NextCloud והכנס טוקן API עבור Dropbox."
#~ msgid ""
#~ "Leave empty for dropbox and enter only base url for nextcloud (/"
#~ "remote.php/webdav/ is added automatically)"
#~ msgstr ""
#~ "השאר ריק עבור dropbox וכנס רק URL בסיסי עבור nextcloud (/remote.php/"
#~ "webdav/ נוסף אוטומטי)"
#~ msgid "Something like http://homeassistant.local:8123/api"
#~ msgstr "משהו דומה לhttp://homeassistant.local:8123/api"
#~ msgid "http://homeassistant.local:8123/api for example"
#~ msgstr "לדוגמא http://homeassistant.local:8123/api"
#~ msgid "Storage"
#~ msgstr "אחסון"
#~ msgid "Active"
#~ msgstr "פעיל"
#~ msgid "Search String"
#~ msgstr "מחרוזת חיפוש"
#~ msgid "File ID"
#~ msgstr "ID של הקובץ"
#~ msgid ""
#~ "Use fuzzy matching on units, keywords and ingredients when editing and "
#~ "importing recipes."
#~ msgstr ""
#~ "השתמש בהתאמה גמישה ליחידות, מילות מפתח ורכיבים בעת עריכה וייבוא מתכונים."
#~ msgid "Delete"
#~ msgstr "מחיקה"
#~ msgid "Settings"
#~ msgstr "הגדרות"
#~ msgid "Email"
#~ msgstr "מייל"
#~ msgid "Password"
#~ msgstr "סיסמא"
#~ msgid "Foods"
#~ msgstr "מאכלים"
#~ msgid "Automations"
#~ msgstr "אוטומציות"
#~ msgid "Files"
#~ msgstr "קבצים"
#~ msgid "Space Settings"
#~ msgstr "הגדרות מרחב"
#~ msgid "Admin"
#~ msgstr "מנהל"
#~ msgid "Log out"
#~ msgstr "התנתק"
#~ msgid "Show Recipes"
#~ msgstr "הראה מתכונים"
#~ msgid "This cannot be undone!"
#~ msgstr "לא ניתן לבטל פעולה זו!"
#~ msgid "Edit"
#~ msgstr "ערוך"
#~ msgid "next"
#~ msgstr "הבא"
#~ msgid "Import"
#~ msgstr "יבא"
#~ msgid "You are currently offline!"
#~ msgstr "אתה כרגע במצב לא מקוון!"
#~ msgid "Default unit"
#~ msgstr "יחידות ברירת מחדל"
#~ msgid "Use KJ"
#~ msgstr "שימוש ב KJ"
#~ msgid "Theme"
#~ msgstr "נושא"
================================================
FILE: cookbook/locale/hr/LC_MESSAGES/django.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-22 20:15+0200\n"
"PO-Revision-Date: 2026-02-20 22:13+0000\n"
"Last-Translator: Bruno \n"
"Language-Team: Croatian \n"
"Language: hr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
"X-Generator: Weblate 5.13.3\n"
#: .\cookbook\forms.py:50
msgid "Default"
msgstr "Zadano"
#: .\cookbook\forms.py:77
msgid ""
"To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything."
msgstr ""
"Kako bi spriječili duplikate, recepti s istim nazivom kao i postojeći "
"zanemaruju se. Označite ovaj okvir za uvoz svega."
#: .\cookbook\forms.py:108
msgid "Maximum number of users for this space reached."
msgstr "Dosegnut je maksimalan broj korisnika za ovaj prostor."
#: .\cookbook\forms.py:114
msgid "Email address already taken!"
msgstr "Email adresa je već zauzeta!"
#: .\cookbook\forms.py:121
msgid ""
"An email address is not required but if present the invite link will be sent "
"to the user."
msgstr ""
"Adresa e-pošte nije potrebna, ali ako postoji, korisniku će biti poslana "
"poveznica s pozivnicom."
#: .\cookbook\forms.py:133
msgid "Name already taken."
msgstr "Ime je već zauzeto."
#: .\cookbook\forms.py:144 .\cookbook\forms.py:158
msgid "Accept Terms and Privacy"
msgstr "Prihvatite uvjete i privatnost"
#: .\cookbook\helper\AllAuthCustomAdapter.py:41
msgid ""
"In order to prevent spam, the requested email was not send. Please wait a "
"few minutes and try again."
msgstr ""
"Kako bismo spriječili neželjenu poštu, tražena e-pošta nije poslana. "
"Pričekajte nekoliko minuta i pokušajte ponovno."
#: .\cookbook\helper\permission_helper.py:165
#: .\cookbook\helper\permission_helper.py:186 .\cookbook\views\views.py:138
msgid "You are not logged in and therefore cannot view this page!"
msgstr "Niste prijavljeni i stoga ne možete vidjeti ovu stranicu!"
#: .\cookbook\helper\permission_helper.py:168
#: .\cookbook\helper\permission_helper.py:173
#: .\cookbook\helper\permission_helper.py:198
#: .\cookbook\helper\permission_helper.py:265
#: .\cookbook\helper\permission_helper.py:279
#: .\cookbook\helper\permission_helper.py:290
#: .\cookbook\helper\permission_helper.py:301
#: .\cookbook\helper\permission_helper.py:317
#: .\cookbook\helper\permission_helper.py:343
#: .\cookbook\helper\permission_helper.py:359
msgid "You do not have the required permissions to view this page!"
msgstr "Nemate potrebna dopuštenja za pregled ove stranice!"
#: .\cookbook\helper\permission_helper.py:191
#: .\cookbook\helper\permission_helper.py:214
#: .\cookbook\helper\permission_helper.py:236
#: .\cookbook\helper\permission_helper.py:251
msgid "You cannot interact with this object as it is not owned by you!"
msgstr "Ne možete komunicirati s ovim objektom jer nije u vašem vlasništvu!"
#: .\cookbook\helper\permission_helper.py:420
msgid "You have reached the maximum number of recipes for your space."
msgstr "Dosegli ste najveći broj recepata za svoj prostor."
#: .\cookbook\helper\permission_helper.py:432
msgid "You have more users than allowed in your space."
msgstr "Imate više korisnika nego što je dopušteno u vašem prostoru."
#: .\cookbook\helper\recipe_url_import.py:319
msgid "reverse rotation"
msgstr "obrnuto okretanje"
#: .\cookbook\helper\recipe_url_import.py:320
msgid "careful rotation"
msgstr "pažljivo okretanje"
#: .\cookbook\helper\recipe_url_import.py:321
msgid "knead"
msgstr "mijesiti"
#: .\cookbook\helper\recipe_url_import.py:322
msgid "thicken"
msgstr "zgusnuti"
#: .\cookbook\helper\recipe_url_import.py:323
msgid "warm up"
msgstr "zagrijati"
#: .\cookbook\helper\recipe_url_import.py:324
msgid "ferment"
msgstr "kvasac"
#: .\cookbook\helper\recipe_url_import.py:325
msgid "slow cook"
msgstr "sporo kuhanje"
#: .\cookbook\helper\recipe_url_import.py:326
msgid "egg boiler"
msgstr "kuhalo za jaja"
#: .\cookbook\helper\recipe_url_import.py:327
msgid "kettle"
msgstr "kuhalo za vodu"
#: .\cookbook\helper\recipe_url_import.py:328
msgid "blend"
msgstr "smjesa"
#: .\cookbook\helper\recipe_url_import.py:329
msgid "pre-clean"
msgstr "prethodno čišćenje"
#: .\cookbook\helper\recipe_url_import.py:330
msgid "high temperature"
msgstr "visoka temperatura"
#: .\cookbook\helper\recipe_url_import.py:331
msgid "rice cooker"
msgstr "kuhalo za rižu"
#: .\cookbook\helper\recipe_url_import.py:332
msgid "caramelize"
msgstr "karamelizirati"
#: .\cookbook\helper\recipe_url_import.py:333
msgid "peeler"
msgstr "guljač"
#: .\cookbook\helper\recipe_url_import.py:334
msgid "slicer"
msgstr "rezač"
#: .\cookbook\helper\recipe_url_import.py:335
msgid "grater"
msgstr "ribež"
#: .\cookbook\helper\recipe_url_import.py:336
msgid "spiralizer"
msgstr "spiralizator"
#: .\cookbook\helper\recipe_url_import.py:337
msgid "sous-vide"
msgstr "sous-vide"
#: .\cookbook\helper\shopping_helper.py:150
msgid "You must supply a servings size"
msgstr "Morate navesti veličinu porcija"
#: .\cookbook\helper\template_helper.py:97
#: .\cookbook\helper\template_helper.py:99
#: .\cookbook\helper\template_helper.py:101
msgid "Could not parse template code."
msgstr "Nije moguće analizirati kôd predloška."
#: .\cookbook\integration\copymethat.py:44
#: .\cookbook\integration\melarecipes.py:37
msgid "Favorite"
msgstr "Omiljeni"
#: .\cookbook\integration\copymethat.py:50
msgid "I made this"
msgstr "Ja sam ovo napravio"
#: .\cookbook\integration\integration.py:238
msgid ""
"Importer expected a .zip file. Did you choose the correct importer type for "
"your data ?"
msgstr ""
"Uvoznik očekuje .zip datoteku. Jeste li odabrali ispravnu vrstu uvoznika za "
"svoje podatke?"
#: .\cookbook\integration\integration.py:241
msgid ""
"An unexpected error occurred during the import. Please make sure you have "
"uploaded a valid file."
msgstr ""
"Došlo je do neočekivane pogreške tijekom uvoza. Provjerite jeste li "
"prenijeli valjanu datoteku."
#: .\cookbook\integration\integration.py:246
msgid "The following recipes were ignored because they already existed:"
msgstr "Sljedeći recepti su zanemareni jer su već postojali:"
#: .\cookbook\integration\integration.py:250
#, python-format
msgid "Imported %s recipes."
msgstr "Uvezeno %s recepata."
#: .\cookbook\integration\mealie1.py:210
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "Calories"
msgstr "Kalorije"
#: .\cookbook\integration\mealie1.py:211
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
msgid "Carbohydrates"
msgstr "Ugljikohidrati"
#: .\cookbook\integration\mealie1.py:212
msgid "Cholesterol"
msgstr "Kolesterol"
#: .\cookbook\integration\mealie1.py:213
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
msgid "Fat"
msgstr "Masnoća"
#: .\cookbook\integration\mealie1.py:214
msgid "Fiber"
msgstr "Vlakna"
#: .\cookbook\integration\mealie1.py:215
#, fuzzy
#| msgid "Proteins"
msgid "Protein"
msgstr "Proteini"
#: .\cookbook\integration\mealie1.py:216
msgid "Saturated Fat"
msgstr "Zasićene masti"
#: .\cookbook\integration\mealie1.py:217
msgid "Sodium"
msgstr "Natrij"
#: .\cookbook\integration\mealie1.py:218
msgid "Sugar"
msgstr "Šećer"
#: .\cookbook\integration\mealie1.py:219
msgid "Trans Fat"
msgstr "Trans masti"
#: .\cookbook\integration\mealie1.py:220
msgid "Unsaturated Fat"
msgstr "Nezasićene masti"
#: .\cookbook\integration\openeats.py:28
msgid "Recipe source:"
msgstr "Izvor recepta:"
#: .\cookbook\integration\paprika.py:49
msgid "Notes"
msgstr "Bilješke"
#: .\cookbook\integration\paprika.py:52
msgid "Nutritional Information"
msgstr "Nutritivne vrijednosti"
#: .\cookbook\integration\paprika.py:56
msgid "Source"
msgstr "Izvor"
#: .\cookbook\integration\recettetek.py:55
#: .\cookbook\integration\recipekeeper.py:70
msgid "Imported from"
msgstr "Uvezeno iz"
#: .\cookbook\integration\saffron.py:23
msgid "Servings"
msgstr "Porcije"
#: .\cookbook\integration\saffron.py:25
msgid "Waiting time"
msgstr "Vrijeme čekanja"
#: .\cookbook\integration\saffron.py:27
msgid "Preparation Time"
msgstr "Vrijeme pripreme"
#: .\cookbook\integration\saffron.py:29 .\cookbook\templates\index.html:6
msgid "Cookbook"
msgstr "Kuharica"
#: .\cookbook\integration\saffron.py:31
msgid "Section"
msgstr "Odjeljak"
#: .\cookbook\management\commands\fix_duplicate_properties.py:15
msgid "Fixes foods with "
msgstr "Popravlja namirnice sa "
#: .\cookbook\management\commands\rebuildindex.py:14
msgid "Rebuilds full text search index on Recipe"
msgstr "Ponovno gradi indeks pretraživanja cijelog teksta na receptu"
#: .\cookbook\management\commands\rebuildindex.py:18
msgid "Only Postgresql databases use full text search, no index to rebuild"
msgstr ""
"Samo Postgresql baze podataka koriste pretraživanje cijelog teksta, bez "
"indeksa za ponovnu izgradnju"
#: .\cookbook\management\commands\rebuildindex.py:29
msgid "Recipe index rebuild complete."
msgstr "Dovršena ponovna izgradnja indeksa recepata."
#: .\cookbook\management\commands\rebuildindex.py:31
msgid "Recipe index rebuild failed."
msgstr "Ponovna izgradnja indeksa recepata nije uspjela."
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
msgid "Breakfast"
msgstr "Doručak"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:19
msgid "Lunch"
msgstr "Ručak"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:24
msgid "Dinner"
msgstr "Večera"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:29 .\cookbook\models.py:971
msgid "Other"
msgstr "Ostalo"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "g"
msgstr "g"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "Proteins"
msgstr "Proteini"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "kcal"
msgstr "kcal"
#: .\cookbook\models.py:325
msgid ""
"Maximum file storage for space in MB. 0 for unlimited, -1 to disable file "
"upload."
msgstr ""
"Maksimalna pohrana datoteka za prostor u MB. 0 za neograničeno, -1 za "
"onemogućavanje prijenosa datoteka."
#: .\cookbook\models.py:513
msgid "Search"
msgstr "Pretraga"
#: .\cookbook\models.py:514
msgid "Meal-Plan"
msgstr "Plan obroka"
#: .\cookbook\models.py:515
msgid "Books"
msgstr "Knjige"
#: .\cookbook\models.py:516 .\cookbook\views\views.py:416
#: .\cookbook\views\views.py:417
msgid "Shopping"
msgstr "Kupovina"
#: .\cookbook\models.py:967
msgid "Nutrition"
msgstr "Nutritivna vrijednost"
#: .\cookbook\models.py:968
msgid "Allergen"
msgstr "Alergeni"
#: .\cookbook\models.py:969
msgid "Price"
msgstr "Cijena"
#: .\cookbook\models.py:970
msgid "Goal"
msgstr "Cilj"
#: .\cookbook\models.py:1467 .\cookbook\templates\search_info.html:28
msgid "Simple"
msgstr "Jednostavno"
#: .\cookbook\models.py:1468 .\cookbook\templates\search_info.html:33
msgid "Phrase"
msgstr "Fraza"
#: .\cookbook\models.py:1469 .\cookbook\templates\search_info.html:38
msgid "Web"
msgstr "Web"
#: .\cookbook\models.py:1470 .\cookbook\templates\search_info.html:47
msgid "Raw"
msgstr "Raw"
#: .\cookbook\models.py:1525
msgid "Food Alias"
msgstr "Nadimak namirnice"
#: .\cookbook\models.py:1526
msgid "Unit Alias"
msgstr "Jedinica nadimka"
#: .\cookbook\models.py:1527
msgid "Keyword Alias"
msgstr "Nadimci ključnih riječi"
#: .\cookbook\models.py:1528
msgid "Description Replace"
msgstr "Zamjena opisa"
#: .\cookbook\models.py:1529
msgid "Instruction Replace"
msgstr "Zamjena uputa"
#: .\cookbook\models.py:1530
msgid "Never Unit"
msgstr "Nikada jedinica"
#: .\cookbook\models.py:1531
msgid "Transpose Words"
msgstr "Transponirajte riječi"
#: .\cookbook\models.py:1532
msgid "Food Replace"
msgstr "Zamjena namirnice"
#: .\cookbook\models.py:1533
msgid "Unit Replace"
msgstr "Zamjena jedinice"
#: .\cookbook\models.py:1534
msgid "Name Replace"
msgstr "Zamjena imena"
#: .\cookbook\models.py:1564
msgid "Recipe"
msgstr "Recept"
#: .\cookbook\models.py:1565
msgid "Food"
msgstr "Namirnica"
#: .\cookbook\models.py:1566
msgid "Keyword"
msgstr "Ključna riječ"
#: .\cookbook\serializer.py:262
msgid "File uploads are not enabled for this Space."
msgstr "Učitavanje datoteka nije omogućeno za ovaj prostor."
#: .\cookbook\serializer.py:273
msgid "You have reached your file upload limit."
msgstr "Dosegli ste ograničenje učitavanja datoteke."
#: .\cookbook\serializer.py:281
msgid "The given file type is not allowed."
msgstr ""
#: .\cookbook\serializer.py:421 .\cookbook\views\views.py:94
msgid ""
"You have the reached the maximum amount of spaces that can be owned by you."
msgstr ""
#: .\cookbook\serializer.py:434
msgid "Space Name must be unique."
msgstr ""
#: .\cookbook\serializer.py:469
msgid "Cannot modify Space owner permission."
msgstr "Nije moguće promijeniti dozvolu vlasnika prostora."
#: .\cookbook\serializer.py:1596
msgid "Hello"
msgstr "Pozdrav"
#: .\cookbook\serializer.py:1596
msgid "You have been invited by "
msgstr "Pozvao vas je "
#: .\cookbook\serializer.py:1598
msgid " to join their Tandoor Recipes space "
msgstr " pridružiti se njihovom prostoru Tandoor Recipes "
#: .\cookbook\serializer.py:1600
msgid "Click the following link to activate your account: "
msgstr "Kliknite na sljedeću poveznicu kako biste aktivirali svoj račun: "
#: .\cookbook\serializer.py:1602
msgid ""
"If the link does not work use the following code to manually join the space: "
msgstr ""
"Ako poveznica ne radi, upotrijebite sljedeći kod za ručno pridruživanje "
"prostoru: "
#: .\cookbook\serializer.py:1604
msgid "The invitation is valid until "
msgstr "Poziv vrijedi do "
#: .\cookbook\serializer.py:1606
msgid ""
"Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub "
msgstr ""
"Tandoor Recipes je upravitelj recepata otvorenog koda. Provjerite na GitHubu "
#: .\cookbook\serializer.py:1609
msgid "Tandoor Recipes Invite"
msgstr "Tandoor Recipes pozivnica"
#: .\cookbook\serializer.py:1813
msgid "Existing shopping list to update"
msgstr "Postojeći popis za kupovinu koji treba ažurirati"
#: .\cookbook\serializer.py:1815
msgid ""
"List of ingredient IDs from the recipe to add, if not provided all "
"ingredients will be added."
msgstr ""
"Popis ID-ova sastojaka iz recepta koje treba dodati, ako se ne navede, svi "
"će sastojci biti dodani."
#: .\cookbook\serializer.py:1817
msgid ""
"Providing a list_recipe ID and servings of 0 will delete that shopping list."
msgstr ""
"Navođenje ID-a list_recipe i 0 porcija izbrisat će taj popis za kupnju."
#: .\cookbook\serializer.py:1826
msgid "Amount of food to add to the shopping list"
msgstr "Količina namirnica za dodavanje na popis za kupovinu"
#: .\cookbook\serializer.py:1828
msgid "ID of unit to use for the shopping list"
msgstr "ID jedinice koja se koristi za popis za kupnju"
#: .\cookbook\serializer.py:1830
msgid "When set to true will delete all food from active shopping lists."
msgstr ""
"Kada je postavljeno na true, izbrisat će sve namirnice s aktivnih popisa za "
"kupnju."
#: .\cookbook\templates\404.html:5
msgid "404 Error"
msgstr "404 Greška"
#: .\cookbook\templates\404.html:18
msgid "The page you are looking for could not be found."
msgstr "Stranica koju tražite nije pronađena."
#: .\cookbook\templates\404.html:33
msgid "Take me Home"
msgstr "Odvedi me doma"
#: .\cookbook\templates\404.html:35
msgid "Report a Bug"
msgstr "Prijevi grešku"
#: .\cookbook\templates\account\email.html:6
#: .\cookbook\templates\account\email.html:10
msgid "E-mail Addresses"
msgstr "E-maiil adrese"
#: .\cookbook\templates\account\email.html:12
msgid "The following e-mail addresses are associated with your account:"
msgstr "Sljedeće adrese e-pošte povezane su s vašim računom:"
#: .\cookbook\templates\account\email.html:29
msgid "Verified"
msgstr "Provjereno"
#: .\cookbook\templates\account\email.html:31
msgid "Unverified"
msgstr "Neprovjereno"
#: .\cookbook\templates\account\email.html:33
msgid "Primary"
msgstr "Primarno"
#: .\cookbook\templates\account\email.html:40
msgid "Make Primary"
msgstr "Postavi kao Primarno"
#: .\cookbook\templates\account\email.html:42
msgid "Re-send Verification"
msgstr "Ponovno slanje Provjere"
#: .\cookbook\templates\account\email.html:43
#: .\cookbook\templates\socialaccount\connections.html:36
msgid "Remove"
msgstr "Ukloni"
#: .\cookbook\templates\account\email.html:51
msgid "Warning:"
msgstr "Upozorenje:"
#: .\cookbook\templates\account\email.html:51
msgid ""
"You currently do not have any e-mail address set up. You should really add "
"an e-mail address so you can receive notifications, reset your password, etc."
msgstr ""
"Trenutno nemate postavljenu adresu e-pošte. Zaista biste trebali dodati "
"adresu e-pošte kako biste mogli primati obavijesti, poništiti lozinku itd."
#: .\cookbook\templates\account\email.html:57
msgid "Add E-mail Address"
msgstr "Dodajte adresu e-pošte"
#: .\cookbook\templates\account\email.html:62
msgid "Add E-mail"
msgstr "Dodajte e-mail"
#: .\cookbook\templates\account\email.html:72
msgid "Do you really want to remove the selected e-mail address?"
msgstr "Želite li stvarno ukloniti odabranu adresu e-pošte?"
#: .\cookbook\templates\account\email_confirm.html:6
#: .\cookbook\templates\account\email_confirm.html:10
msgid "Confirm E-mail Address"
msgstr "Potvrdite adresu e-pošte"
#: .\cookbook\templates\account\email_confirm.html:16
#, python-format
msgid ""
"Please confirm that\n"
" %(email)s is an e-mail address "
"for user %(user_display)s\n"
" ."
msgstr ""
"Molimo potvrdite da je\n"
" %(email)s e-mail adresa "
"korisnika %(user_display)s\n"
" ."
#: .\cookbook\templates\account\email_confirm.html:22
msgid "Confirm"
msgstr "Potvrdi"
#: .\cookbook\templates\account\email_confirm.html:29
#, python-format
msgid ""
"This e-mail confirmation link expired or is invalid. Please\n"
" issue a new e-mail confirmation "
"request."
msgstr ""
"Poveznica za potvrdu e-maila je istekla ili nevažeća. Molimo\n"
" zatražite slanje nove poveznice za "
"potvrdu e-maila.."
#: .\cookbook\templates\account\login.html:8
#: .\cookbook\templates\openid\login.html:8
msgid "Login"
msgstr "Prijava"
#: .\cookbook\templates\account\login.html:15
#: .\cookbook\templates\account\login.html:31
#: .\cookbook\templates\account\password_reset.html:39
#: .\cookbook\templates\account\password_reset_done.html:31
#: .\cookbook\templates\account\signup.html:68
#: .\cookbook\templates\account\signup_closed.html:15
#: .\cookbook\templates\openid\login.html:15
#: .\cookbook\templates\openid\login.html:26
#: .\cookbook\templates\socialaccount\authentication_error.html:15
msgid "Sign In"
msgstr "Prijava"
#: .\cookbook\templates\account\login.html:34
#: .\cookbook\templates\account\password_reset.html:41
#: .\cookbook\templates\account\password_reset_done.html:33
#: .\cookbook\templates\socialaccount\signup.html:8
#: .\cookbook\templates\socialaccount\signup.html:56
msgid "Sign Up"
msgstr "Registracija"
#: .\cookbook\templates\account\login.html:38
msgid "Lost your password?"
msgstr "Zaboravljena lozinka?"
#: .\cookbook\templates\account\login.html:39
#: .\cookbook\templates\account\password_reset.html:29
msgid "Reset My Password"
msgstr "Resetiraj moju lozinku"
#: .\cookbook\templates\account\login.html:50
msgid "Social Login"
msgstr "Prijava putem društvenih mreža"
#: .\cookbook\templates\account\login.html:51
msgid "You can use any of the following providers to sign in."
msgstr "Možete koristiti bilo kojeg od sljedećih pružatelja usluga za prijavu."
#: .\cookbook\templates\account\logout.html:5
#: .\cookbook\templates\account\logout.html:9
#: .\cookbook\templates\account\logout.html:18
msgid "Sign Out"
msgstr "Odjava"
#: .\cookbook\templates\account\logout.html:11
msgid "Are you sure you want to sign out?"
msgstr "Jeste li sigurni da se želite odjaviti?"
#: .\cookbook\templates\account\password_change.html:6
#: .\cookbook\templates\account\password_change.html:10
#: .\cookbook\templates\account\password_change.html:15
#: .\cookbook\templates\account\password_reset_from_key.html:7
#: .\cookbook\templates\account\password_reset_from_key.html:13
#: .\cookbook\templates\account\password_reset_from_key_done.html:7
#: .\cookbook\templates\account\password_reset_from_key_done.html:13
msgid "Change Password"
msgstr "Promijeni lozinku"
#: .\cookbook\templates\account\password_change.html:16
msgid "Forgot Password?"
msgstr "Zaboravljena lozinka?"
#: .\cookbook\templates\account\password_reset.html:7
#: .\cookbook\templates\account\password_reset.html:13
#: .\cookbook\templates\account\password_reset_done.html:7
#: .\cookbook\templates\account\password_reset_done.html:18
msgid "Password Reset"
msgstr "Ponovno postavljanje lozinke"
#: .\cookbook\templates\account\password_reset.html:24
msgid ""
"Forgotten your password? Enter your e-mail address below, and we'll send you "
"an e-mail allowing you to reset it."
msgstr ""
"Zaboravili ste lozinku? Unesite svoju e-mail adresu u nastavku i poslat ćemo "
"vam e-mail s poveznicom za postavljanje nove lozinke."
#: .\cookbook\templates\account\password_reset.html:32
msgid "Password reset is disabled on this instance."
msgstr "Reset lozinke nije omogućen na ovoj instanci."
#: .\cookbook\templates\account\password_reset_done.html:25
msgid ""
"We have sent you an e-mail. Please contact us if you do not receive it "
"within a few minutes."
msgstr ""
"Poslali smo vam e-mail. Molimo vas da nas kontaktirate ako ga ne primite u "
"roku od nekoliko minuta."
#: .\cookbook\templates\account\password_reset_from_key.html:13
msgid "Bad Token"
msgstr "Nevažeći token"
#: .\cookbook\templates\account\password_reset_from_key.html:25
#, python-format
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used.\n"
" Please request a new "
"password reset."
msgstr ""
"Poveznica za reset lozinke je nevažeća, vjerojatno jer je već iskorištena.\n"
" Molimo zatražite novu "
"poveznicu za reset lozinke."
#: .\cookbook\templates\account\password_reset_from_key.html:33
msgid "change password"
msgstr "promijeni lozinku"
#: .\cookbook\templates\account\password_reset_from_key.html:36
#: .\cookbook\templates\account\password_reset_from_key_done.html:19
msgid "Your password is now changed."
msgstr "Tvoja lozinka je promijenjena."
#: .\cookbook\templates\account\password_set.html:6
#: .\cookbook\templates\account\password_set.html:10
#: .\cookbook\templates\account\password_set.html:15
msgid "Set Password"
msgstr "Postavi lozinku"
#: .\cookbook\templates\account\signup.html:5
msgid "Register"
msgstr "Registracija"
#: .\cookbook\templates\account\signup.html:11
msgid "Create an Account"
msgstr "Kreiraj račun"
#: .\cookbook\templates\account\signup.html:41
msgid "I accept the follwoing"
msgstr "Prihvaćam sljedeće"
#: .\cookbook\templates\account\signup.html:44
#: .\cookbook\templates\socialaccount\signup.html:35
msgid "Terms and Conditions"
msgstr "Uvjeti i odredbe"
#: .\cookbook\templates\account\signup.html:47
#: .\cookbook\templates\socialaccount\signup.html:38
msgid "and"
msgstr "i"
#: .\cookbook\templates\account\signup.html:51
#: .\cookbook\templates\socialaccount\signup.html:42
msgid "Privacy Policy"
msgstr "Politika privatnosti"
#: .\cookbook\templates\account\signup.html:64
msgid "Create User"
msgstr "Kreiraj korisnika"
#: .\cookbook\templates\account\signup.html:68
msgid "Already have an account?"
msgstr "Već imate račun?"
#: .\cookbook\templates\account\signup_closed.html:5
#: .\cookbook\templates\account\signup_closed.html:11
msgid "Sign Up Closed"
msgstr "Registracija je zatvorena"
#: .\cookbook\templates\account\signup_closed.html:13
msgid "We are sorry, but the sign up is currently closed."
msgstr "Žao nam je, ali registracija je trenutno zatvorena."
#: .\cookbook\templates\frontend\tandoor.html:15
#, fuzzy
#| msgid "Tandoor Recipes Invite"
msgid "Tandoor Recipe Manager"
msgstr "Tandoor Recipes pozivnica"
#: .\cookbook\templates\index.html:28
msgid "Search recipe ..."
msgstr "Pretraži recept..."
#: .\cookbook\templates\index.html:43
msgid "New Recipe"
msgstr "Novi Recept"
#: .\cookbook\templates\index.html:46
msgid "Import Recipe"
msgstr "Uvezi Recept"
#: .\cookbook\templates\index.html:52
msgid "Advanced Search"
msgstr "Napredno Pretraživanje"
#: .\cookbook\templates\index.html:56
msgid "Reset Search"
msgstr "Resetiraj Pretraživanje"
#: .\cookbook\templates\index.html:84
msgid "Last viewed"
msgstr "Zadnji put pregledano"
#: .\cookbook\templates\index.html:86
msgid "Recipes"
msgstr "Recepti"
#: .\cookbook\templates\index.html:93
msgid "Log in to view recipes"
msgstr "Prijavi se kako bi vidio recepte"
#: .\cookbook\templates\markdown_info.html:5
#: .\cookbook\templates\markdown_info.html:13
msgid "Markdown Info"
msgstr "Informacije o Markdown-u"
#: .\cookbook\templates\markdown_info.html:14
msgid ""
"\n"
" Markdown is lightweight markup language that can be used to format "
"plain text easily.\n"
" This site uses the Python Markdown library to\n"
" convert your text into nice looking HTML. Its full markdown "
"documentation can be found\n"
" here.\n"
" An incomplete but most likely sufficient documentation can be found "
"below.\n"
" "
msgstr ""
"\n"
" Markdown je lagani jezik za označavanje koji se može koristiti za "
"jednostavno formatiranje običnog teksta.\n"
"Ova stranica koristi Python Markdown biblioteku za\n"
"pretvaranje vašeg teksta u lijepi HTML. Potpunu dokumentaciju za Markdown "
"možete pronaći\n"
"ovdje.\n"
"Nepotpuna, ali vjerojatno dovoljna dokumentacija može se pronaći u "
"nastavku.\n"
" "
#: .\cookbook\templates\markdown_info.html:25
msgid "Headers"
msgstr "Naslovi"
#: .\cookbook\templates\markdown_info.html:54
msgid "Formatting"
msgstr "Formatiranje"
#: .\cookbook\templates\markdown_info.html:56
#: .\cookbook\templates\markdown_info.html:72
msgid "Line breaks are inserted by adding two spaces after the end of a line"
msgstr ""
"Prekid linije se može umetnuti dodavanjem dva razmaka nakon kraja retka"
#: .\cookbook\templates\markdown_info.html:57
#: .\cookbook\templates\markdown_info.html:73
msgid "or by leaving a blank line in between."
msgstr "ili ostavljanjem prazne linije između."
#: .\cookbook\templates\markdown_info.html:59
#: .\cookbook\templates\markdown_info.html:74
msgid "This text is bold"
msgstr "Ovaj tekst je boldan"
#: .\cookbook\templates\markdown_info.html:60
#: .\cookbook\templates\markdown_info.html:75
msgid "This text is italic"
msgstr "Ovaj tekst je italic"
#: .\cookbook\templates\markdown_info.html:61
#: .\cookbook\templates\markdown_info.html:77
msgid "Blockquotes are also possible"
msgstr ""
#: .\cookbook\templates\markdown_info.html:84
msgid "Lists"
msgstr ""
#: .\cookbook\templates\markdown_info.html:85
msgid ""
"Lists can ordered or unordered. It is important to leave a blank line "
"before the list!"
msgstr ""
#: .\cookbook\templates\markdown_info.html:87
#: .\cookbook\templates\markdown_info.html:108
msgid "Ordered List"
msgstr ""
#: .\cookbook\templates\markdown_info.html:89
#: .\cookbook\templates\markdown_info.html:90
#: .\cookbook\templates\markdown_info.html:91
#: .\cookbook\templates\markdown_info.html:110
#: .\cookbook\templates\markdown_info.html:111
#: .\cookbook\templates\markdown_info.html:112
msgid "unordered list item"
msgstr ""
#: .\cookbook\templates\markdown_info.html:93
#: .\cookbook\templates\markdown_info.html:114
msgid "Unordered List"
msgstr ""
#: .\cookbook\templates\markdown_info.html:95
#: .\cookbook\templates\markdown_info.html:96
#: .\cookbook\templates\markdown_info.html:97
#: .\cookbook\templates\markdown_info.html:116
#: .\cookbook\templates\markdown_info.html:117
#: .\cookbook\templates\markdown_info.html:118
msgid "ordered list item"
msgstr ""
#: .\cookbook\templates\markdown_info.html:125
msgid "Images & Links"
msgstr ""
#: .\cookbook\templates\markdown_info.html:126
msgid ""
"Links can be formatted with Markdown. This application also allows to paste "
"links directly into markdown fields without any formatting."
msgstr ""
#: .\cookbook\templates\markdown_info.html:132
#: .\cookbook\templates\markdown_info.html:145
msgid "This will become an image"
msgstr ""
#: .\cookbook\templates\markdown_info.html:152
msgid "Tables"
msgstr ""
#: .\cookbook\templates\markdown_info.html:153
msgid ""
"Markdown tables are hard to create by hand. It is recommended to use a table "
"editor like this one."
msgstr ""
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:171
#: .\cookbook\templates\markdown_info.html:177
msgid "Table"
msgstr ""
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:172
msgid "Header"
msgstr ""
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:178
msgid "Cell"
msgstr ""
#: .\cookbook\templates\no_groups_info.html:5
#: .\cookbook\templates\no_groups_info.html:12
msgid "No Permissions"
msgstr ""
#: .\cookbook\templates\no_groups_info.html:17
msgid "You do not have any groups and therefor cannot use this application."
msgstr ""
#: .\cookbook\templates\no_groups_info.html:18
#: .\cookbook\templates\no_perm_info.html:15
msgid "Please contact your administrator."
msgstr ""
#: .\cookbook\templates\no_perm_info.html:5
#: .\cookbook\templates\no_perm_info.html:12
msgid "No Permission"
msgstr ""
#: .\cookbook\templates\no_perm_info.html:15
msgid ""
"You do not have the required permissions to view this page or perform this "
"action."
msgstr ""
#: .\cookbook\templates\offline.html:5
msgid "Offline"
msgstr ""
#: .\cookbook\templates\openid\login.html:27
#: .\cookbook\templates\socialaccount\authentication_error.html:27
msgid "Back"
msgstr ""
#: .\cookbook\templates\rest_framework\api.html:5
msgid "Recipe Home"
msgstr ""
#: .\cookbook\templates\rest_framework\api.html:11
msgid "API Documentation"
msgstr "API Dokumentacija"
#: .\cookbook\templates\search_info.html:5
#: .\cookbook\templates\search_info.html:9
msgid "Search Settings"
msgstr ""
#: .\cookbook\templates\search_info.html:10
msgid ""
"\n"
" Creating the best search experience is complicated and weighs "
"heavily on your personal configuration. \n"
" Changing any of the search settings can have significant impact on "
"the speed and quality of the results.\n"
" Search Methods, Trigrams and Full Text Search configurations are "
"only available if you are using Postgres for your database.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:19
msgid "Search Methods"
msgstr ""
#: .\cookbook\templates\search_info.html:23
msgid ""
" \n"
" Full text searches attempt to normalize the words provided to "
"match common variants. For example: 'forked', 'forking', 'forks' will all "
"normalize to 'fork'.\n"
" There are several methods available, described below, that will "
"control how the search behavior should react when multiple words are "
"searched.\n"
" Full technical details on how these operate can be viewed on Postgresql's website.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:29
msgid ""
" \n"
" Simple searches ignore punctuation and common words such as "
"'the', 'a', 'and'. And will treat separate words as required.\n"
" Searching for 'apple or flour' will return any recipe that "
"includes both 'apple' and 'flour' anywhere in the fields that have been "
"selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:34
msgid ""
" \n"
" Phrase searches ignore punctuation, but will search for all of "
"the words in the exact order provided.\n"
" Searching for 'apple or flour' will only return a recipe that "
"includes the exact phrase 'apple or flour' in any of the fields that have "
"been selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:39
msgid ""
" \n"
" Web searches simulate functionality found on many web search "
"sites supporting special syntax.\n"
" Placing quotes around several words will convert those words "
"into a phrase.\n"
" 'or' is recognized as searching for the word (or phrase) "
"immediately before 'or' OR the word (or phrase) directly after.\n"
" '-' is recognized as searching for recipes that do not include "
"the word (or phrase) that comes immediately after. \n"
" For example searching for 'apple pie' or cherry -butter will "
"return any recipe that includes the phrase 'apple pie' or the word "
"'cherry' \n"
" in any field included in the full text search but exclude any "
"recipe that has the word 'butter' in any field included.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:48
msgid ""
" \n"
" Raw search is similar to Web except will take puncuation "
"operators such as '|', '&' and '()'\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:59
msgid ""
" \n"
" Another approach to searching that also requires Postgresql is "
"fuzzy search or trigram similarity. A trigram is a group of three "
"consecutive characters.\n"
" For example searching for 'apple' will create x trigrams 'app', "
"'ppl', 'ple' and will create a score of how closely words match the "
"generated trigrams.\n"
" One benefit of searching trigams is that a search for 'sandwich' "
"will find misspelled words such as 'sandwhich' that would be missed by other "
"methods.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:69
msgid "Search Fields"
msgstr ""
#: .\cookbook\templates\search_info.html:73
msgid ""
" \n"
" Unaccent is a special case in that it enables searching a field "
"'unaccented' for each search style attempting to ignore accented values. \n"
" For example when you enable unaccent for 'Name' any search "
"(starts with, contains, trigram) will attempt the search ignoring accented "
"characters.\n"
" \n"
" For the other options, you can enable search on any or all "
"fields and they will be combined together with an assumed 'OR'.\n"
" For example enabling 'Name' for Starts With, 'Name' and "
"'Description' for Partial Match and 'Ingredients' and 'Keywords' for Full "
"Search\n"
" and searching for 'apple' will generate a search that will "
"return recipes that have:\n"
" - A recipe name that starts with 'apple'\n"
" - OR a recipe name that contains 'apple'\n"
" - OR a recipe description that contains 'apple'\n"
" - OR a recipe that will have a full text search match ('apple' "
"or 'apples') in ingredients\n"
" - OR a recipe that will have a full text search match in "
"Keywords\n"
"\n"
" Combining too many fields in too many types of search can have a "
"negative impact on performance, create duplicate results or return "
"unexpected results.\n"
" For example, enabling fuzzy search or partial matches will "
"interfere with web search methods. \n"
" Searching for 'apple -pie' with fuzzy search and full text "
"search will return the recipe Apple Pie. Though it is not included in the "
"full text results, it does match the trigram results.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:95
msgid "Search Index"
msgstr ""
#: .\cookbook\templates\search_info.html:99
msgid ""
" \n"
" Trigram search and Full Text Search both rely on database "
"indexes to perform effectively. \n"
" You can rebuild the indexes on all fields in the Admin page for "
"Recipes and selecting all recipes and running 'rebuild index for selected "
"recipes'\n"
" You can also rebuild indexes at the command line by executing "
"the management command 'python manage.py rebuildindex'\n"
" "
msgstr ""
#: .\cookbook\templates\setup.html:6
msgid "Cookbook Setup"
msgstr ""
#: .\cookbook\templates\setup.html:14
msgid "Setup"
msgstr ""
#: .\cookbook\templates\setup.html:15
msgid ""
"To start using this application you must first create a superuser account."
msgstr ""
#: .\cookbook\templates\setup.html:20
msgid "Create Superuser account"
msgstr ""
#: .\cookbook\templates\socialaccount\authentication_error.html:7
#: .\cookbook\templates\socialaccount\authentication_error.html:23
msgid "Social Network Login Failure"
msgstr ""
#: .\cookbook\templates\socialaccount\authentication_error.html:25
msgid ""
"An error occurred while attempting to login via your social network account."
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:4
#: .\cookbook\templates\socialaccount\connections.html:7
msgid "Account Connections"
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:10
msgid ""
"You can sign in to your account using any of the following third party\n"
" accounts:"
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:44
msgid ""
"You currently have no social network accounts connected to this account."
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:47
msgid "Add a 3rd Party Account"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:5
#: .\cookbook\templates\socialaccount\signup.html:5
msgid "Signup"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:9
#, python-format
msgid "Connect %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:11
#, python-format
msgid "You are about to connect a new third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:13
#, python-format
msgid "Sign In Via %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:15
#, python-format
msgid "You are about to sign in using a third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:20
msgid "Continue"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:10
#, python-format
msgid ""
"You are about to use your\n"
" %(provider_name)s account to login to\n"
" %(site_name)s. As a final step, please complete the following form:"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:32
#, fuzzy
#| msgid "I accept the follwoing"
msgid "I accept the following"
msgstr "Prihvaćam sljedeće"
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:23
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:31
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:39
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:47
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:55
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:63
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:71
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:79
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:87
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:95
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:103
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:111
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:119
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:127
msgid "Sign in using"
msgstr ""
#: .\cookbook\templates\space_overview.html:6
msgid "Overview"
msgstr "Pregled"
#: .\cookbook\templates\space_overview.html:13
msgid "Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:17
msgid ""
"Recipes, foods, shopping lists and more are organized in spaces of one or "
"more people."
msgstr ""
#: .\cookbook\templates\space_overview.html:18
msgid ""
"You can either be invited into an existing space or create your own one."
msgstr ""
#: .\cookbook\templates\space_overview.html:25
msgid "Your Spaces"
msgstr "Tvoji Prostori"
#: .\cookbook\templates\space_overview.html:53
msgid "Owner"
msgstr ""
#: .\cookbook\templates\space_overview.html:73
#: .\cookbook\templates\space_overview.html:83
msgid "Join Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:76
msgid "Join an existing space."
msgstr ""
#: .\cookbook\templates\space_overview.html:78
msgid ""
"To join an existing space either enter your invite token or click on the "
"invite link the space owner send you."
msgstr ""
#: .\cookbook\templates\space_overview.html:91
#: .\cookbook\templates\space_overview.html:100
msgid "Create Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:94
msgid "Create your own recipe space."
msgstr ""
#: .\cookbook\templates\space_overview.html:96
msgid "Start your own recipe space and invite other users to it."
msgstr ""
#: .\cookbook\templates\system.html:23
msgid "System"
msgstr "Sustav"
#: .\cookbook\templates\system.html:24
msgid ""
"\n"
" Tandoor Recipes is an open source free software application. It can "
"be found on\n"
" GitHub.\n"
" Changelogs can be found here.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:30
msgid "System Information"
msgstr ""
#: .\cookbook\templates\system.html:51
msgid ""
"\n"
" You need to execute version.py in your update "
"script to generate version information (done automatically in docker).\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:56
msgid "Plugins"
msgstr ""
#: .\cookbook\templates\system.html:67
msgid "Media Serving"
msgstr ""
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:123 .\cookbook\templates\system.html:134
msgid "Warning"
msgstr ""
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:125 .\cookbook\templates\system.html:134
msgid "Ok"
msgstr ""
#: .\cookbook\templates\system.html:70
msgid ""
"Serving media files directly using gunicorn/python is not recommend!\n"
" Please follow the steps described\n"
" here to update\n"
" your installation.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:76 .\cookbook\templates\system.html:91
#: .\cookbook\templates\system.html:104 .\cookbook\templates\system.html:115
#: .\cookbook\views\views.py:168
msgid "Everything is fine!"
msgstr ""
#: .\cookbook\templates\system.html:80
msgid "Secret Key"
msgstr ""
#: .\cookbook\templates\system.html:84
msgid ""
"\n"
" You do not have a SECRET_KEY configured in your "
".env file. Django defaulted to the\n"
" standard key\n"
" provided with the installation which is publicly know and "
"insecure! Please set\n"
" SECRET_KEY int the .env configuration "
"file.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:94
msgid "Debug Mode"
msgstr ""
#: .\cookbook\templates\system.html:98
msgid ""
"\n"
" This application is still running in debug mode. This is most "
"likely not needed. Turn of debug mode by\n"
" setting\n"
" DEBUG=0 int the .env configuration "
"file.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:107
msgid "Allowed Hosts"
msgstr ""
#: .\cookbook\templates\system.html:111
msgid ""
"\n"
" Your allowed hosts are configured to allow every host. This "
"might be ok in some setups but should be avoided. Please see the docs about "
"this.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:118
msgid "Database"
msgstr ""
#: .\cookbook\templates\system.html:121
msgid "Info"
msgstr ""
#: .\cookbook\templates\system.html:131 .\cookbook\templates\system.html:148
msgid "Migrations"
msgstr ""
#: .\cookbook\templates\system.html:137
msgid ""
"\n"
" Migrations should never fail!\n"
" Failed migrations will likely cause major parts of the app to "
"not function correctly.\n"
" If a migration fails make sure you are on the latest version and "
"if so please post the migration log and the overview below in a GitHub "
"issue.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "False"
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "True"
msgstr ""
#: .\cookbook\templates\system.html:268
msgid "Hide"
msgstr ""
#: .\cookbook\templates\system.html:271
msgid "Show"
msgstr ""
#: .\cookbook\templates\test2.html:6
msgid "Export Recipes"
msgstr "Izvoz Recepata"
#: .\cookbook\templates\test2.html:14 .\cookbook\templates\test2.html:20
msgid "Export"
msgstr "Izvoz"
#: .\cookbook\views\api.py:198 .\cookbook\views\api.py:326
msgid "Parameter updated_at incorrectly formatted"
msgstr ""
#: .\cookbook\views\api.py:351 .\cookbook\views\api.py:484
#, python-brace-format
msgid "No {self.basename} with id {pk} exists"
msgstr ""
#: .\cookbook\views\api.py:355
msgid "Cannot merge with the same object!"
msgstr ""
#: .\cookbook\views\api.py:362
#, python-brace-format
msgid "No {self.basename} with id {target} exists"
msgstr ""
#: .\cookbook\views\api.py:367
msgid "Cannot merge with child object!"
msgstr ""
#: .\cookbook\views\api.py:405
#, python-brace-format
msgid "{source.name} was merged successfully with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:411
#, python-brace-format
msgid "An error occurred attempting to merge {source.name} with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:493
#, python-brace-format
msgid "{child.name} was moved successfully to the root."
msgstr ""
#: .\cookbook\views\api.py:496 .\cookbook\views\api.py:514
msgid "An error occurred attempting to move "
msgstr ""
#: .\cookbook\views\api.py:499
msgid "Cannot move an object to itself!"
msgstr ""
#: .\cookbook\views\api.py:505
#, python-brace-format
msgid "No {self.basename} with id {parent} exists"
msgstr ""
#: .\cookbook\views\api.py:511
#, python-brace-format
msgid "{child.name} was moved successfully to parent {parent.name}"
msgstr ""
#: .\cookbook\views\api.py:992
#, python-brace-format
msgid "{obj.name} was removed from the shopping list."
msgstr ""
#: .\cookbook\views\api.py:997 .\cookbook\views\api.py:1627
#, python-brace-format
msgid "{obj.name} was added to the shopping list."
msgstr ""
#: .\cookbook\views\api.py:1239
msgid "Filter meal plans from date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1241
msgid "Filter meal plans to date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1244
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1404
msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1406
msgid "Query string matched (fuzzy) against object name."
msgstr ""
#: .\cookbook\views\api.py:1442
msgid ""
"Query string matched (fuzzy) against recipe name. In the future also "
"fulltext search."
msgstr ""
#: .\cookbook\views\api.py:1444
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr ""
#: .\cookbook\views\api.py:1445
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr ""
#: .\cookbook\views\api.py:1446
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1447
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1448
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1450
msgid "ID of food a recipe should have. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1451
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr ""
#: .\cookbook\views\api.py:1452
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1453
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr ""
#: .\cookbook\views\api.py:1454
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1456
msgid "ID of book a recipe should be in. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1457
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr ""
#: .\cookbook\views\api.py:1458
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1459
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr ""
#: .\cookbook\views\api.py:1460
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1462
msgid "ID of unit a recipe should have."
msgstr ""
#: .\cookbook\views\api.py:1464
msgid "Exact rating of recipe"
msgstr ""
#: .\cookbook\views\api.py:1465
msgid "Rating a recipe should have or greater."
msgstr ""
#: .\cookbook\views\api.py:1466
msgid "Rating a recipe should have or smaller."
msgstr ""
#: .\cookbook\views\api.py:1468
msgid "Filter recipes cooked X times."
msgstr ""
#: .\cookbook\views\api.py:1469
msgid "Filter recipes cooked X times or more."
msgstr ""
#: .\cookbook\views\api.py:1470
msgid "Filter recipes cooked X times or less."
msgstr ""
#: .\cookbook\views\api.py:1472
msgid "Filter recipes created on the given date."
msgstr ""
#: .\cookbook\views\api.py:1473
msgid "Filter recipes created on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1474
msgid "Filter recipes created on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1476 .\cookbook\views\api.py:1477
#: .\cookbook\views\api.py:1478
msgid "Filter recipes updated on the given date."
msgstr ""
#: .\cookbook\views\api.py:1480
msgid "Filter recipes last cooked on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1481
msgid "Filter recipes last cooked on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1483 .\cookbook\views\api.py:1484
msgid "Filter recipes lasts viewed on the given date."
msgstr ""
#: .\cookbook\views\api.py:1486
msgid "Filter recipes for ones created by the given user ID"
msgstr ""
#: .\cookbook\views\api.py:1487
msgid "If only internal recipes should be returned. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1488
msgid "Returns the results in randomized order. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1490
msgid ""
"Determines the order of the results. Options are: score,-score,name,-name,"
"lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-"
"created_at,lastviewed,-lastviewed"
msgstr ""
#: .\cookbook\views\api.py:1492
msgid "Returns new results first in search results. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1493
msgid ""
"Returns the given number of recently viewed recipes before search results "
"(if given)"
msgstr ""
#: .\cookbook\views\api.py:1494
msgid "ID of a custom filter. Returns all recipes matched by that filter."
msgstr ""
#: .\cookbook\views\api.py:1495
msgid "Filter recipes that can be made with OnHand food. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1773
msgid ""
"Return the PropertyTypes matching the property category. Repeat for "
"multiple."
msgstr ""
#: .\cookbook\views\api.py:1804 .\cookbook\views\api.py:1860
msgid "Returns only entries associated with the given mealplan id"
msgstr ""
#: .\cookbook\views\api.py:1858
msgid ""
"Returns only elements updated after the given timestamp in ISO 8601 format."
msgstr ""
#: .\cookbook\views\api.py:2031
msgid ""
"Return the Automations matching the automation type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2048
msgid ""
"Text field to store data that gets carried over to the UserSpace created "
"from the InviteLink"
msgstr ""
#: .\cookbook\views\api.py:2049
msgid "Only return InviteLinks that have not been used yet."
msgstr ""
#: .\cookbook\views\api.py:2076
msgid "Return the CustomFilters matching the model type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2176
msgid "Nothing to do."
msgstr ""
#: .\cookbook\views\api.py:2222
msgid "Invalid Url"
msgstr ""
#: .\cookbook\views\api.py:2228
msgid "Connection Refused."
msgstr ""
#: .\cookbook\views\api.py:2232
msgid "Bad URL Schema."
msgstr ""
#: .\cookbook\views\api.py:2257
msgid "No usable data could be found."
msgstr ""
#: .\cookbook\views\api.py:2286 .\cookbook\views\api.py:2434
msgid "You must select an AI provider to perform your request."
msgstr ""
#: .\cookbook\views\api.py:2293 .\cookbook\views\api.py:2441
msgid ""
"You don't have any credits remaining to use AI or AI features are not "
"enabled for your space."
msgstr ""
#: .\cookbook\views\api.py:2499 .\cookbook\views\api.py:2667
msgid "File is above space limit"
msgstr ""
#: .\cookbook\views\api.py:2522 .\cookbook\views\api.py:2684
msgid "Importing is not implemented for this provider"
msgstr ""
#: .\cookbook\views\api.py:2548
msgid ""
"The PDF Exporter is not enabled on this instance as it is still in an "
"experimental state."
msgstr ""
#: .\cookbook\views\api.py:2842
msgid "This feature is not yet available in the hosted version of tandoor!"
msgstr ""
#: .\cookbook\views\api.py:2863
msgid "Sync successful!"
msgstr ""
#: .\cookbook\views\api.py:2866
msgid "Error synchronizing with Storage"
msgstr ""
#: .\cookbook\views\views.py:89
msgid "This feature is not available in the demo version!"
msgstr ""
#: .\cookbook\views\views.py:110
msgid ""
"You have successfully created your own recipe space. Start by adding some "
"recipes or invite other people to join you."
msgstr ""
#: .\cookbook\views\views.py:171
#, python-format
msgid "PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!"
msgstr ""
#: .\cookbook\views\views.py:174
#, python-format
msgid "You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended"
msgstr ""
#: .\cookbook\views\views.py:178
msgid "Unable to determine PostgreSQL version."
msgstr ""
#: .\cookbook\views\views.py:182
msgid ""
"This application is not running with a Postgres database backend. This is ok "
"but not recommended as some features only work with postgres databases."
msgstr ""
#: .\cookbook\views\views.py:296
msgid ""
"The setup page can only be used to create the first "
"user! If you have forgotten your superuser credentials "
"please consult the django documentation on how to reset passwords."
msgstr ""
#: .\cookbook\views\views.py:304
msgid "Passwords dont match!"
msgstr ""
#: .\cookbook\views\views.py:312
msgid "User has been created, please login!"
msgstr ""
#: .\cookbook\views\views.py:352
msgid ""
"Reporting share links is not enabled for this instance. Please notify the "
"page administrator to report problems."
msgstr ""
#: .\cookbook\views\views.py:357
msgid ""
"Recipe sharing link has been disabled! For additional information please "
"contact the page administrator."
msgstr ""
#: .\cookbook\views\views.py:383
msgid "Manage recipes, shopping list, meal plans and more."
msgstr ""
#: .\cookbook\views\views.py:397 .\cookbook\views\views.py:398
msgid "Plan"
msgstr ""
#: .\cookbook\views\views.py:399
msgid "View your meal Plan"
msgstr ""
#: .\cookbook\views\views.py:418
msgid "View your shopping lists"
msgstr ""
#~ msgid ""
#~ "Both fields are optional. If none are given the username will be "
#~ "displayed instead"
#~ msgstr ""
#~ "Oba polja nisu obavezna. Ako nema nijednog, umjesto njega će se prikazati "
#~ "korisničko ime"
#~ msgid "Name"
#~ msgstr "Ime"
#~ msgid "Keywords"
#~ msgstr "Ključne riječi"
#~ msgid "Preparation time in minutes"
#~ msgstr "Vrijeme pripreme u minutama"
#~ msgid "Waiting time (cooking/baking) in minutes"
#~ msgstr "Vrijeme čekanja (kuhanje/pečenje) u minutama"
#~ msgid "Path"
#~ msgstr "Putanja"
#~ msgid "Storage UID"
#~ msgstr "UID pohrane"
#~ msgid "Add your comment: "
#~ msgstr "Dodaj svoj komentar: "
#~ msgid "Leave empty for dropbox and enter app password for nextcloud."
#~ msgstr ""
#~ "Ostavite prazno za dropbox i unesite zaporku aplikacije za nextcloud."
#~ msgid "Leave empty for nextcloud and enter api token for dropbox."
#~ msgstr "Ostavite prazno za nextcloud i unesite api token za dropbox."
#~ msgid ""
#~ "Leave empty for dropbox and enter only base url for nextcloud (/"
#~ "remote.php/webdav/ is added automatically)"
#~ msgstr ""
#~ "Ostavite prazno za dropbox i unesite samo osnovni url za nextcloud "
#~ "(/remote.php/webdav/ dodaje se automatski)"
#~ msgid ""
#~ "Long Lived Access Token for your HomeAssistant instance"
#~ msgstr ""
#~ "Dugotrajni pristupni token za vašu HomeAssistant instancu"
#~ msgid "Something like http://homeassistant.local:8123/api"
#~ msgstr "Nešto poput http://homeassistant.local:8123/api"
#~ msgid "http://homeassistant.local:8123/api for example"
#~ msgstr "http://homeassistant.local:8123/api na primjer"
#~ msgid "Storage"
#~ msgstr "Pohrana"
#~ msgid "Active"
#~ msgstr "Aktivan"
#~ msgid "Search String"
#~ msgstr "Niz za pretraživanje"
#~ msgid "File ID"
#~ msgstr "ID datoteke"
#~ msgid ""
#~ "Determines how fuzzy a search is if it uses trigram similarity matching "
#~ "(e.g. low values mean more typos are ignored)."
#~ msgstr ""
#~ "Određuje koliko je neizrazito pretraživanje ako koristi podudaranje "
#~ "sličnosti trigrama (npr. niske vrijednosti znače da se više pogrešaka pri "
#~ "upisu zanemaruje)."
#~ msgid ""
#~ "Select type method of search. Click here "
#~ "for full description of choices."
#~ msgstr ""
#~ "Odaberite vrstu metode pretraživanja. Kliknite ovdje za potpuni opis izbora."
#~ msgid ""
#~ "Use fuzzy matching on units, keywords and ingredients when editing and "
#~ "importing recipes."
#~ msgstr ""
#~ "Koristite neizrazito podudaranje jedinica, ključnih riječi i sastojaka "
#~ "prilikom uređivanja i uvoza recepata."
#~ msgid ""
#~ "Fields to search ignoring accents. Selecting this option can improve or "
#~ "degrade search quality depending on language"
#~ msgstr ""
#~ "Polja za pretraživanje zanemarujući naglaske. Odabir ove opcije može "
#~ "poboljšati ili pogoršati kvalitetu pretraživanja ovisno o jeziku"
#~ msgid ""
#~ "Fields to search for partial matches. (e.g. searching for 'Pie' will "
#~ "return 'pie' and 'piece' and 'soapie')"
#~ msgstr ""
#~ "Polja za traženje djelomičnih podudaranja. (npr. traženje \"pita\" vratit "
#~ "će \"pita\" i \"krempita\")"
#~ msgid ""
#~ "Fields to search for beginning of word matches. (e.g. searching for 'sa' "
#~ "will return 'salad' and 'sandwich')"
#~ msgstr ""
#~ "Polja za traženje početaka riječi. (npr. traženje 'sa' vratit će 'salata' "
#~ "i 'salama')"
#~ msgid ""
#~ "Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find "
#~ "'recipe'.) Note: this option will conflict with 'web' and 'raw' methods "
#~ "of search."
#~ msgstr ""
#~ "Polja za 'neizrazito ' pretraživanje. (npr. traženje 'recpet' će pronaći "
#~ "'recept'.) Napomena: ova opcija će biti u sukobu s 'web' i 'raw' metodama "
#~ "pretraživanja."
#~ msgid ""
#~ "Fields to full text search. Note: 'web', 'phrase', and 'raw' search "
#~ "methods only function with fulltext fields."
#~ msgstr ""
#~ "Polja za pretraživanje cijelog teksta. Napomena: metode pretraživanja "
#~ "'web', 'phrase' i 'raw' funkcioniraju samo s poljima punog teksta."
#~ msgid "Search Method"
#~ msgstr "Metoda pretrage"
#~ msgid "Fuzzy Lookups"
#~ msgstr "Neizrazite pretrage"
#~ msgid "Ignore Accent"
#~ msgstr "Ignorirajte naglasak"
#~ msgid "Partial Match"
#~ msgstr "Djelomično podudaranje"
#~ msgid "Starts With"
#~ msgstr "Počinje s"
#~ msgid "Fuzzy Search"
#~ msgstr "Neizrazito pretraživanje"
#~ msgid "Full Text"
#~ msgstr "Cijeli tekst"
#~ msgid " is part of a recipe step and cannot be deleted"
#~ msgstr " je dio koraka recepta i ne može se izbrisati"
#~ msgid "Delete"
#~ msgstr "Izbriši"
#~ msgid "Settings"
#~ msgstr "Postavke"
#~ msgid "Email"
#~ msgstr "E-mail"
#~ msgid "Password"
#~ msgstr "Lozinka"
#~ msgid "Foods"
#~ msgstr "Hrana"
#~ msgid "Units"
#~ msgstr "Jedinice"
#~ msgid "Supermarket"
#~ msgstr "Supermarket"
#~ msgid "Supermarket Category"
#~ msgstr "Kategorija Supermarketa"
#~ msgid "Automations"
#~ msgstr "Automatizacije"
#~ msgid "Files"
#~ msgstr "Datoteke"
#~ msgid "Batch Edit"
#~ msgstr "Masovno Uređivanje"
#~ msgid "History"
#~ msgstr "Povijest"
#~ msgid "Ingredient Editor"
#~ msgstr "Uređivač Sastojaka"
#~ msgid "Properties"
#~ msgstr "Svojstva"
#~ msgid "Unit Conversions"
#~ msgstr "Konverzija Jedinica"
#~ msgid "Create"
#~ msgstr "Kreiraj"
#~ msgid "External Recipes"
#~ msgstr "Vanjski Recepti"
#~ msgid "Space Settings"
#~ msgstr "Postavke Prostora"
#~ msgid "External Connectors"
#~ msgstr "Vanjski Konektori"
#~ msgid "Admin"
#~ msgstr "Administrator"
#~ msgid "Markdown Guide"
#~ msgstr "Vodič za Markdown"
#~ msgid "GitHub"
#~ msgstr "GitHub"
#~ msgid "Translate Tandoor"
#~ msgstr "Prevedi Tandoor"
#~ msgid "API Browser"
#~ msgstr "API Preglednik"
#~ msgid "Log out"
#~ msgstr "Odjava"
#~ msgid "You are using the free version of Tandor"
#~ msgstr "Koristite besplatnu verziju Tandor aplikacije"
#~ msgid "Upgrade Now"
#~ msgstr "Nadogradi"
#~ msgid "Batch edit Category"
#~ msgstr "Masovno uređivanje Kategorija"
#~ msgid "Batch edit Recipes"
#~ msgstr "Masovno uređivanje Recepata"
#~ msgid "Add the specified keywords to all recipes containing a word"
#~ msgstr ""
#~ "Dodajte navedene ključne riječi svim receptima koji sadrže određenu riječ"
#~ msgid "Sync"
#~ msgstr "Sinkroniziraj"
#~ msgid "Manage watched Folders"
#~ msgstr "Upravljaj praćenim Mapama"
#~ msgid ""
#~ "On this Page you can manage all storage folder locations that should be "
#~ "monitored and synced."
#~ msgstr ""
#~ "Na ovoj stranici možete upravljati svim lokacijama spremišnih mapa koje "
#~ "trebaju biti praćene i sinkronizirane."
#~ msgid "The path must be in the following format"
#~ msgstr "Putanja mora biti u sljedećem formatu"
#~ msgid "Save"
#~ msgstr "Spremi"
#~ msgid "Manage External Storage"
#~ msgstr "Upravljaj vanjskim Spremištem"
#~ msgid "Sync Now!"
#~ msgstr "Sinkroniziraj Sada!"
#~ msgid "Show Recipes"
#~ msgstr "Prikaži Recepte"
#~ msgid "Show Log"
#~ msgstr "Prikaži Log"
#~ msgid "Importing Recipes"
#~ msgstr "Uvoz Recepata"
#~ msgid ""
#~ "This can take a few minutes, depending on the number of recipes in sync, "
#~ "please wait."
#~ msgstr ""
#~ "Ovo može potrajati nekoliko minuta, ovisno o broju recepata koji se "
#~ "sinkroniziraju, molimo pričekajte."
#~ msgid "Recipe Books"
#~ msgstr "Knjiga Recepata"
#~ msgid "Import new Recipe"
#~ msgstr "Uvezi novi Recept"
#~ msgid "Edit Recipe"
#~ msgstr "Uredi Recept"
#, python-format
#~ msgid "Are you sure you want to delete the %(title)s: %(object)s "
#~ msgstr "Jeste li sigurni da želite obrisati %(title)s: %(object)s "
#~ msgid "This cannot be undone!"
#~ msgstr "Ovo se ne može poništiti!"
#~ msgid "Protected"
#~ msgstr "Zaštićeno"
#~ msgid "Cascade"
#~ msgstr "Kaskada"
#~ msgid "Cancel"
#~ msgstr "Otkaži"
#~ msgid "Edit"
#~ msgstr "Uredi"
#~ msgid "View"
#~ msgstr "Pogledaj"
#~ msgid "Delete original file"
#~ msgstr "Izbriši izvornu datoteku"
#~ msgid "List"
#~ msgstr "Lista"
#~ msgid "Filter"
#~ msgstr "Filter"
#~ msgid "Import all"
#~ msgstr "Uvezi sve"
#~ msgid "New"
#~ msgstr "Novo"
#~ msgid "previous"
#~ msgstr "prethodno"
#~ msgid "next"
#~ msgstr "sljedeće"
#~ msgid "View Log"
#~ msgstr "Pogledaj Log"
#~ msgid "Cook Log"
#~ msgstr "Log Kuhanja"
#~ msgid "Import"
#~ msgstr "Uvezi"
#~ msgid "Security Warning"
#~ msgstr "Sigurnosno Upozorenje"
#~ msgid ""
#~ "\n"
#~ " The Password and Token field are stored as plain text"
#~ "b> inside the database.\n"
#~ " This is necessary because they are needed to make API requests, "
#~ "but it also increases the risk of\n"
#~ " someone stealing it.
\n"
#~ " To limit the possible damage tokens or accounts with limited "
#~ "access can be used.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Lozinka i token polja su spremljena kao obični tekst"
#~ "b> unutar baze podataka.\n"
#~ " To je neophodno s obzirom na to da su potrebni za izvršavanje API "
#~ "zahtjeva, ali isto tako povećava \n"
#~ " rizik od krađe.
\n"
#~ " Kako bi limitirali potencijalnu štetu u tom slučaju, predlažemo "
#~ "korištenje tokena ili računa s limitiranim pristupom.\n"
#~ " "
================================================
FILE: cookbook/locale/hu_HU/LC_MESSAGES/django.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
# Translators:
# igazka , 2020
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-22 20:15+0200\n"
"PO-Revision-Date: 2023-12-05 09:15+0000\n"
"Last-Translator: Ferenc \n"
"Language-Team: Hungarian \n"
"Language: hu_HU\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.15\n"
#: .\cookbook\forms.py:50
msgid "Default"
msgstr "Alapértelmezett"
#: .\cookbook\forms.py:77
msgid ""
"To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything."
msgstr ""
"A duplikációk elkerülése érdekében a meglévő receptekkel azonos nevű "
"recepteket a rendszer figyelmen kívül hagyja. Jelölje be ezt a négyzetet, ha "
"mindent importálni szeretne."
#: .\cookbook\forms.py:108
msgid "Maximum number of users for this space reached."
msgstr "Elérte a felhasználók maximális számát ezen a területen."
#: .\cookbook\forms.py:114
msgid "Email address already taken!"
msgstr "Az e-mail cím már foglalt!"
#: .\cookbook\forms.py:121
msgid ""
"An email address is not required but if present the invite link will be sent "
"to the user."
msgstr ""
"Az e-mail cím megadása nem kötelező, de ha van, a meghívó linket elküldi a "
"felhasználónak."
#: .\cookbook\forms.py:133
msgid "Name already taken."
msgstr "A név már foglalt."
#: .\cookbook\forms.py:144 .\cookbook\forms.py:158
msgid "Accept Terms and Privacy"
msgstr "Feltételek és adatvédelem elfogadása"
#: .\cookbook\helper\AllAuthCustomAdapter.py:41
msgid ""
"In order to prevent spam, the requested email was not send. Please wait a "
"few minutes and try again."
msgstr ""
"A spamek elkerülése érdekében a kért e-mailt nem küldtük el. Kérjük, várjon "
"néhány percet, és próbálja meg újra."
#: .\cookbook\helper\permission_helper.py:165
#: .\cookbook\helper\permission_helper.py:186 .\cookbook\views\views.py:138
msgid "You are not logged in and therefore cannot view this page!"
msgstr "Ön nincs bejelentkezve, ezért nem tudja megtekinteni ezt az oldalt!"
#: .\cookbook\helper\permission_helper.py:168
#: .\cookbook\helper\permission_helper.py:173
#: .\cookbook\helper\permission_helper.py:198
#: .\cookbook\helper\permission_helper.py:265
#: .\cookbook\helper\permission_helper.py:279
#: .\cookbook\helper\permission_helper.py:290
#: .\cookbook\helper\permission_helper.py:301
#: .\cookbook\helper\permission_helper.py:317
#: .\cookbook\helper\permission_helper.py:343
#: .\cookbook\helper\permission_helper.py:359
msgid "You do not have the required permissions to view this page!"
msgstr "Nem rendelkezik a szükséges jogosultságokkal az oldal megtekintéséhez!"
#: .\cookbook\helper\permission_helper.py:191
#: .\cookbook\helper\permission_helper.py:214
#: .\cookbook\helper\permission_helper.py:236
#: .\cookbook\helper\permission_helper.py:251
msgid "You cannot interact with this object as it is not owned by you!"
msgstr ""
"Nem léphetsz kapcsolatba ezzel az objektummal, mivel nem a te tulajdonod!"
#: .\cookbook\helper\permission_helper.py:420
msgid "You have reached the maximum number of recipes for your space."
msgstr "Elérte a maximális számú receptet a helyén."
#: .\cookbook\helper\permission_helper.py:432
msgid "You have more users than allowed in your space."
msgstr "Több felhasználója van, mint amennyit engedélyeztek a térben."
#: .\cookbook\helper\recipe_url_import.py:319
msgid "reverse rotation"
msgstr "Ellentétes irány"
#: .\cookbook\helper\recipe_url_import.py:320
msgid "careful rotation"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:321
msgid "knead"
msgstr "dagasztás"
#: .\cookbook\helper\recipe_url_import.py:322
msgid "thicken"
msgstr "sűrítés"
#: .\cookbook\helper\recipe_url_import.py:323
msgid "warm up"
msgstr "melegítés"
#: .\cookbook\helper\recipe_url_import.py:324
msgid "ferment"
msgstr "fermentálás"
#: .\cookbook\helper\recipe_url_import.py:325
#, fuzzy
#| msgid "Last cooked"
msgid "slow cook"
msgstr "Utoljára főzve"
#: .\cookbook\helper\recipe_url_import.py:326
msgid "egg boiler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:327
msgid "kettle"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:328
msgid "blend"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:329
msgid "pre-clean"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:330
msgid "high temperature"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:331
msgid "rice cooker"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:332
msgid "caramelize"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:333
msgid "peeler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:334
msgid "slicer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:335
msgid "grater"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:336
msgid "spiralizer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:337
msgid "sous-vide"
msgstr "sous-vide"
#: .\cookbook\helper\shopping_helper.py:150
msgid "You must supply a servings size"
msgstr "Meg kell adnia az adagok nagyságát"
#: .\cookbook\helper\template_helper.py:97
#: .\cookbook\helper\template_helper.py:99
#: .\cookbook\helper\template_helper.py:101
msgid "Could not parse template code."
msgstr "Nem sikerült elemezni a sablon kódját."
#: .\cookbook\integration\copymethat.py:44
#: .\cookbook\integration\melarecipes.py:37
msgid "Favorite"
msgstr "Kedvenc"
#: .\cookbook\integration\copymethat.py:50
msgid "I made this"
msgstr "Elkészítettem"
#: .\cookbook\integration\integration.py:238
msgid ""
"Importer expected a .zip file. Did you choose the correct importer type for "
"your data ?"
msgstr ""
"Az importáló egy .zip fájlt várt. A megfelelő importálótípust választotta az "
"adataihoz?"
#: .\cookbook\integration\integration.py:241
msgid ""
"An unexpected error occurred during the import. Please make sure you have "
"uploaded a valid file."
msgstr ""
"Az importálás során váratlan hiba történt. Kérjük, ellenőrizze, hogy "
"érvényes fájlt töltött-e fel."
#: .\cookbook\integration\integration.py:246
msgid "The following recipes were ignored because they already existed:"
msgstr "A következő recepteket figyelmen kívül hagytuk, mert már léteztek:"
#: .\cookbook\integration\integration.py:250
#, python-format
msgid "Imported %s recipes."
msgstr "Importálva %s recept."
#: .\cookbook\integration\mealie1.py:210
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "Calories"
msgstr ""
#: .\cookbook\integration\mealie1.py:211
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
msgid "Carbohydrates"
msgstr ""
#: .\cookbook\integration\mealie1.py:212
msgid "Cholesterol"
msgstr ""
#: .\cookbook\integration\mealie1.py:213
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
msgid "Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:214
msgid "Fiber"
msgstr ""
#: .\cookbook\integration\mealie1.py:215
#, fuzzy
#| msgid "Protected"
msgid "Protein"
msgstr "Védett"
#: .\cookbook\integration\mealie1.py:216
msgid "Saturated Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:217
msgid "Sodium"
msgstr ""
#: .\cookbook\integration\mealie1.py:218
msgid "Sugar"
msgstr ""
#: .\cookbook\integration\mealie1.py:219
msgid "Trans Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:220
msgid "Unsaturated Fat"
msgstr ""
#: .\cookbook\integration\openeats.py:28
msgid "Recipe source:"
msgstr "Recept forrása:"
#: .\cookbook\integration\paprika.py:49
msgid "Notes"
msgstr "Jegyzetek"
#: .\cookbook\integration\paprika.py:52
msgid "Nutritional Information"
msgstr "Táplálkozási információk"
#: .\cookbook\integration\paprika.py:56
msgid "Source"
msgstr "Forrás"
#: .\cookbook\integration\recettetek.py:55
#: .\cookbook\integration\recipekeeper.py:70
msgid "Imported from"
msgstr "Importálva a"
#: .\cookbook\integration\saffron.py:23
msgid "Servings"
msgstr "Adagok"
#: .\cookbook\integration\saffron.py:25
msgid "Waiting time"
msgstr "Várakozási idő"
#: .\cookbook\integration\saffron.py:27
msgid "Preparation Time"
msgstr "Előkészítési idő"
#: .\cookbook\integration\saffron.py:29 .\cookbook\templates\index.html:6
msgid "Cookbook"
msgstr "Szakácskönyv"
#: .\cookbook\integration\saffron.py:31
msgid "Section"
msgstr "Szekció"
#: .\cookbook\management\commands\fix_duplicate_properties.py:15
msgid "Fixes foods with "
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:14
msgid "Rebuilds full text search index on Recipe"
msgstr "Újraépíti a teljes szöveges keresési indexet a Recept oldalon"
#: .\cookbook\management\commands\rebuildindex.py:18
msgid "Only Postgresql databases use full text search, no index to rebuild"
msgstr ""
"Csak a Postgresql adatbázisok használják a teljes szöveges keresést, nem "
"kell indexet újjáépíteni"
#: .\cookbook\management\commands\rebuildindex.py:29
msgid "Recipe index rebuild complete."
msgstr "A receptindex újjáépítése befejeződött."
#: .\cookbook\management\commands\rebuildindex.py:31
msgid "Recipe index rebuild failed."
msgstr "A receptindex újjáépítése sikertelen."
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
msgid "Breakfast"
msgstr "Reggeli"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:19
msgid "Lunch"
msgstr "Ebéd"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:24
msgid "Dinner"
msgstr "Vacsora"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:29 .\cookbook\models.py:971
msgid "Other"
msgstr "Egyéb"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "g"
msgstr ""
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "Proteins"
msgstr ""
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "kcal"
msgstr ""
#: .\cookbook\models.py:325
msgid ""
"Maximum file storage for space in MB. 0 for unlimited, -1 to disable file "
"upload."
msgstr ""
"Maximális tárhely a fájloknak MB-ban. 0 a korlátlan, -1 a fájlfeltöltés "
"letiltásához."
#: .\cookbook\models.py:513
msgid "Search"
msgstr "Keresés"
#: .\cookbook\models.py:514
msgid "Meal-Plan"
msgstr "Menüterv"
#: .\cookbook\models.py:515
msgid "Books"
msgstr "Könyvek"
#: .\cookbook\models.py:516 .\cookbook\views\views.py:416
#: .\cookbook\views\views.py:417
msgid "Shopping"
msgstr "Bevásárlás"
#: .\cookbook\models.py:967
#, fuzzy
#| msgid "Automations"
msgid "Nutrition"
msgstr "Automatizációk"
#: .\cookbook\models.py:968
#, fuzzy
#| msgid "Merge"
msgid "Allergen"
msgstr "Egyesítés"
#: .\cookbook\models.py:969
msgid "Price"
msgstr ""
#: .\cookbook\models.py:970
msgid "Goal"
msgstr ""
#: .\cookbook\models.py:1467 .\cookbook\templates\search_info.html:28
msgid "Simple"
msgstr "Egyszerű"
#: .\cookbook\models.py:1468 .\cookbook\templates\search_info.html:33
msgid "Phrase"
msgstr "Kifejezés"
#: .\cookbook\models.py:1469 .\cookbook\templates\search_info.html:38
msgid "Web"
msgstr "Web"
#: .\cookbook\models.py:1470 .\cookbook\templates\search_info.html:47
msgid "Raw"
msgstr "Nyers"
#: .\cookbook\models.py:1525
msgid "Food Alias"
msgstr "Élelmiszer álneve"
#: .\cookbook\models.py:1526
msgid "Unit Alias"
msgstr "Egység álneve"
#: .\cookbook\models.py:1527
msgid "Keyword Alias"
msgstr "Kulcsszó álneve"
#: .\cookbook\models.py:1528
msgid "Description Replace"
msgstr "Leírás csere"
#: .\cookbook\models.py:1529
msgid "Instruction Replace"
msgstr "Leírás cseréje"
#: .\cookbook\models.py:1530
#, fuzzy
#| msgid "New Unit"
msgid "Never Unit"
msgstr "Új Mértékegység"
#: .\cookbook\models.py:1531
msgid "Transpose Words"
msgstr ""
#: .\cookbook\models.py:1532
#, fuzzy
#| msgid "Food Alias"
msgid "Food Replace"
msgstr "Élelmiszer álneve"
#: .\cookbook\models.py:1533
#, fuzzy
#| msgid "Description Replace"
msgid "Unit Replace"
msgstr "Leírás csere"
#: .\cookbook\models.py:1534
msgid "Name Replace"
msgstr ""
#: .\cookbook\models.py:1564
msgid "Recipe"
msgstr "Recept"
#: .\cookbook\models.py:1565
msgid "Food"
msgstr "Étel"
#: .\cookbook\models.py:1566
msgid "Keyword"
msgstr "Kulcsszó"
#: .\cookbook\serializer.py:262
msgid "File uploads are not enabled for this Space."
msgstr "A fájlok feltöltése nem engedélyezett ezen a tárhelyen."
#: .\cookbook\serializer.py:273
msgid "You have reached your file upload limit."
msgstr "Elérte a fájlfeltöltési limitet."
#: .\cookbook\serializer.py:281
msgid "The given file type is not allowed."
msgstr ""
#: .\cookbook\serializer.py:421 .\cookbook\views\views.py:94
#, fuzzy
#| msgid "You have reached the maximum number of recipes for your space."
msgid ""
"You have the reached the maximum amount of spaces that can be owned by you."
msgstr "Elérte a maximális számú receptet a helyén."
#: .\cookbook\serializer.py:434
msgid "Space Name must be unique."
msgstr ""
#: .\cookbook\serializer.py:469
msgid "Cannot modify Space owner permission."
msgstr "A Hely tulajdonosi engedélye nem módosítható."
#: .\cookbook\serializer.py:1596
msgid "Hello"
msgstr "Helló"
#: .\cookbook\serializer.py:1596
msgid "You have been invited by "
msgstr "Önt meghívta "
#: .\cookbook\serializer.py:1598
msgid " to join their Tandoor Recipes space "
msgstr " hogy csatlakozzon a Tandoor Receptek helyhez "
#: .\cookbook\serializer.py:1600
msgid "Click the following link to activate your account: "
msgstr "Kattintson az alábbi linkre fiókja aktiválásához: "
#: .\cookbook\serializer.py:1602
msgid ""
"If the link does not work use the following code to manually join the space: "
msgstr ""
"Ha a link nem működik, használja a következő kódot, hogy manuálisan "
"csatlakozzon a térhez: "
#: .\cookbook\serializer.py:1604
msgid "The invitation is valid until "
msgstr "A meghívó a következő időpontig érvényes "
#: .\cookbook\serializer.py:1606
msgid ""
"Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub "
msgstr ""
"A Tandoor Receptek egy nyílt forráskódú receptkezelő. Nézze meg a GitHubon "
#: .\cookbook\serializer.py:1609
msgid "Tandoor Recipes Invite"
msgstr "Tandoor receptek meghívó"
#: .\cookbook\serializer.py:1813
msgid "Existing shopping list to update"
msgstr "Meglévő bevásárlólista frissítése"
#: .\cookbook\serializer.py:1815
msgid ""
"List of ingredient IDs from the recipe to add, if not provided all "
"ingredients will be added."
msgstr ""
"A hozzáadandó összetevők azonosítóinak listája a receptből, ha nincs "
"megadva, az összes összetevő hozzáadásra kerül."
#: .\cookbook\serializer.py:1817
msgid ""
"Providing a list_recipe ID and servings of 0 will delete that shopping list."
msgstr "A list_recipe azonosító és a 0 adag megadása törli a bevásárlólistát."
#: .\cookbook\serializer.py:1826
msgid "Amount of food to add to the shopping list"
msgstr "A bevásárlólistához hozzáadandó élelmiszerek mennyisége"
#: .\cookbook\serializer.py:1828
msgid "ID of unit to use for the shopping list"
msgstr "A bevásárlólistához használandó egység azonosítója"
#: .\cookbook\serializer.py:1830
msgid "When set to true will delete all food from active shopping lists."
msgstr ""
"Ha igazra van állítva, akkor minden élelmiszert töröl az aktív "
"bevásárlólistákról."
#: .\cookbook\templates\404.html:5
msgid "404 Error"
msgstr "404-es hiba"
#: .\cookbook\templates\404.html:18
msgid "The page you are looking for could not be found."
msgstr "A keresett oldal nem található."
#: .\cookbook\templates\404.html:33
msgid "Take me Home"
msgstr "Vissza a főoldalra"
#: .\cookbook\templates\404.html:35
msgid "Report a Bug"
msgstr "Hiba jelentése"
#: .\cookbook\templates\account\email.html:6
#: .\cookbook\templates\account\email.html:10
msgid "E-mail Addresses"
msgstr "E-mail címek"
#: .\cookbook\templates\account\email.html:12
msgid "The following e-mail addresses are associated with your account:"
msgstr "A te fiókodhoz a következő e-mail címek tartoznak:"
#: .\cookbook\templates\account\email.html:29
msgid "Verified"
msgstr "Ellenőrizve"
#: .\cookbook\templates\account\email.html:31
msgid "Unverified"
msgstr "Nem ellenőrzött"
#: .\cookbook\templates\account\email.html:33
msgid "Primary"
msgstr "Elsődleges"
#: .\cookbook\templates\account\email.html:40
msgid "Make Primary"
msgstr "Legyen elsődleges"
#: .\cookbook\templates\account\email.html:42
msgid "Re-send Verification"
msgstr "Ellenőrzés újraküldése"
#: .\cookbook\templates\account\email.html:43
#: .\cookbook\templates\socialaccount\connections.html:36
msgid "Remove"
msgstr "Eltávolítás"
#: .\cookbook\templates\account\email.html:51
msgid "Warning:"
msgstr "Figyelmeztetés:"
#: .\cookbook\templates\account\email.html:51
msgid ""
"You currently do not have any e-mail address set up. You should really add "
"an e-mail address so you can receive notifications, reset your password, etc."
msgstr ""
"Jelenleg nincs e-mail címe beállítva. Tényleg adjon hozzá egy e-mail címet, "
"hogy értesítéseket kaphasson, visszaállíthassa a jelszavát stb."
#: .\cookbook\templates\account\email.html:57
msgid "Add E-mail Address"
msgstr "E-mail cím hozzáadása"
#: .\cookbook\templates\account\email.html:62
msgid "Add E-mail"
msgstr "E-mail hozzáadása"
#: .\cookbook\templates\account\email.html:72
msgid "Do you really want to remove the selected e-mail address?"
msgstr "Tényleg el akarja távolítani a kiválasztott e-mail címet?"
#: .\cookbook\templates\account\email_confirm.html:6
#: .\cookbook\templates\account\email_confirm.html:10
msgid "Confirm E-mail Address"
msgstr "E-mail cím megerősítése"
#: .\cookbook\templates\account\email_confirm.html:16
#, python-format
msgid ""
"Please confirm that\n"
" %(email)s is an e-mail address "
"for user %(user_display)s\n"
" ."
msgstr ""
"Kérjük, erősítse meg, hogy az\n"
" %(email)s e-mail cím ehhez a "
"felhasználóhoz tartozik: %(user_display)s\n"
" ."
#: .\cookbook\templates\account\email_confirm.html:22
msgid "Confirm"
msgstr "Megerősítés"
#: .\cookbook\templates\account\email_confirm.html:29
#, python-format
msgid ""
"This e-mail confirmation link expired or is invalid. Please\n"
" issue a new e-mail confirmation "
"request."
msgstr ""
"Ez az e-mail megerősítő link lejárt vagy érvénytelen. Kérlek,\n"
" adj ki egy új e-mail megerősítési "
"kérelmet."
#: .\cookbook\templates\account\login.html:8
#: .\cookbook\templates\openid\login.html:8
msgid "Login"
msgstr "Bejelentkezés"
#: .\cookbook\templates\account\login.html:15
#: .\cookbook\templates\account\login.html:31
#: .\cookbook\templates\account\password_reset.html:39
#: .\cookbook\templates\account\password_reset_done.html:31
#: .\cookbook\templates\account\signup.html:68
#: .\cookbook\templates\account\signup_closed.html:15
#: .\cookbook\templates\openid\login.html:15
#: .\cookbook\templates\openid\login.html:26
#: .\cookbook\templates\socialaccount\authentication_error.html:15
msgid "Sign In"
msgstr "Bejelentkezés"
#: .\cookbook\templates\account\login.html:34
#: .\cookbook\templates\account\password_reset.html:41
#: .\cookbook\templates\account\password_reset_done.html:33
#: .\cookbook\templates\socialaccount\signup.html:8
#: .\cookbook\templates\socialaccount\signup.html:56
msgid "Sign Up"
msgstr "Regisztráció"
#: .\cookbook\templates\account\login.html:38
msgid "Lost your password?"
msgstr "Elfelejtette a jelszavát?"
#: .\cookbook\templates\account\login.html:39
#: .\cookbook\templates\account\password_reset.html:29
msgid "Reset My Password"
msgstr "Jelszó visszaállítása"
#: .\cookbook\templates\account\login.html:50
msgid "Social Login"
msgstr "Közösségi bejelentkezés"
#: .\cookbook\templates\account\login.html:51
msgid "You can use any of the following providers to sign in."
msgstr "A bejelentkezéshez a következő szolgáltatók bármelyikét használhatja."
#: .\cookbook\templates\account\logout.html:5
#: .\cookbook\templates\account\logout.html:9
#: .\cookbook\templates\account\logout.html:18
msgid "Sign Out"
msgstr "Kijelentkezés"
#: .\cookbook\templates\account\logout.html:11
msgid "Are you sure you want to sign out?"
msgstr "Biztos, hogy ki akarsz jelentkezni?"
#: .\cookbook\templates\account\password_change.html:6
#: .\cookbook\templates\account\password_change.html:10
#: .\cookbook\templates\account\password_change.html:15
#: .\cookbook\templates\account\password_reset_from_key.html:7
#: .\cookbook\templates\account\password_reset_from_key.html:13
#: .\cookbook\templates\account\password_reset_from_key_done.html:7
#: .\cookbook\templates\account\password_reset_from_key_done.html:13
msgid "Change Password"
msgstr "Jelszó módosítása"
#: .\cookbook\templates\account\password_change.html:16
msgid "Forgot Password?"
msgstr "Elfelejtette jelszavát?"
#: .\cookbook\templates\account\password_reset.html:7
#: .\cookbook\templates\account\password_reset.html:13
#: .\cookbook\templates\account\password_reset_done.html:7
#: .\cookbook\templates\account\password_reset_done.html:18
msgid "Password Reset"
msgstr "Jelszó visszaállítása"
#: .\cookbook\templates\account\password_reset.html:24
msgid ""
"Forgotten your password? Enter your e-mail address below, and we'll send you "
"an e-mail allowing you to reset it."
msgstr ""
"Elfelejtette jelszavát? Adja meg az alábbiakban az e-mail címét, és mi "
"küldünk egy e-mailt, amellyel visszaállíthatja a jelszót."
#: .\cookbook\templates\account\password_reset.html:32
msgid "Password reset is disabled on this instance."
msgstr "A jelszó visszaállítása le van tiltva ezen a példányon."
#: .\cookbook\templates\account\password_reset_done.html:25
msgid ""
"We have sent you an e-mail. Please contact us if you do not receive it "
"within a few minutes."
msgstr ""
"Küldtünk neked egy e-mailt. Kérjük, lépjen kapcsolatba velünk, ha néhány "
"percen belül nem kapja meg."
#: .\cookbook\templates\account\password_reset_from_key.html:13
msgid "Bad Token"
msgstr "Rossz Token"
#: .\cookbook\templates\account\password_reset_from_key.html:25
#, python-format
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used.\n"
" Please request a new "
"password reset."
msgstr ""
"A jelszó-visszaállítási link érvénytelen, valószínűleg azért, mert már "
"használták.\n"
" Kérlek, igényelj egy új "
"jelszó emlékeztetőt."
#: .\cookbook\templates\account\password_reset_from_key.html:33
msgid "change password"
msgstr "jelszó módosítása"
#: .\cookbook\templates\account\password_reset_from_key.html:36
#: .\cookbook\templates\account\password_reset_from_key_done.html:19
msgid "Your password is now changed."
msgstr "A jelszava megváltozott."
#: .\cookbook\templates\account\password_set.html:6
#: .\cookbook\templates\account\password_set.html:10
#: .\cookbook\templates\account\password_set.html:15
msgid "Set Password"
msgstr "Jelszó beállítása"
#: .\cookbook\templates\account\signup.html:5
msgid "Register"
msgstr "Regisztráció"
#: .\cookbook\templates\account\signup.html:11
msgid "Create an Account"
msgstr "Fiók létrehozása"
#: .\cookbook\templates\account\signup.html:41
msgid "I accept the follwoing"
msgstr "Elfogadom a következőket"
#: .\cookbook\templates\account\signup.html:44
#: .\cookbook\templates\socialaccount\signup.html:35
msgid "Terms and Conditions"
msgstr "Feltételek és szabályok"
#: .\cookbook\templates\account\signup.html:47
#: .\cookbook\templates\socialaccount\signup.html:38
msgid "and"
msgstr "és"
#: .\cookbook\templates\account\signup.html:51
#: .\cookbook\templates\socialaccount\signup.html:42
msgid "Privacy Policy"
msgstr "Adatvédelmi szabályzat"
#: .\cookbook\templates\account\signup.html:64
msgid "Create User"
msgstr "Felhasználó létrehozása"
#: .\cookbook\templates\account\signup.html:68
msgid "Already have an account?"
msgstr "Már van fiókja?"
#: .\cookbook\templates\account\signup_closed.html:5
#: .\cookbook\templates\account\signup_closed.html:11
msgid "Sign Up Closed"
msgstr "Regisztráció lezárva"
#: .\cookbook\templates\account\signup_closed.html:13
msgid "We are sorry, but the sign up is currently closed."
msgstr "Sajnáljuk, de a regisztráció jelenleg zárva van."
#: .\cookbook\templates\frontend\tandoor.html:15
#, fuzzy
#| msgid "Tandoor Recipes Invite"
msgid "Tandoor Recipe Manager"
msgstr "Tandoor receptek meghívó"
#: .\cookbook\templates\index.html:28
msgid "Search recipe ..."
msgstr "Recept keresése ..."
#: .\cookbook\templates\index.html:43
msgid "New Recipe"
msgstr "Új recept"
#: .\cookbook\templates\index.html:46
msgid "Import Recipe"
msgstr "Recept importálása"
#: .\cookbook\templates\index.html:52
msgid "Advanced Search"
msgstr "Speciális keresés"
#: .\cookbook\templates\index.html:56
msgid "Reset Search"
msgstr "Keresés visszaállítása"
#: .\cookbook\templates\index.html:84
msgid "Last viewed"
msgstr "Utoljára megtekintett"
#: .\cookbook\templates\index.html:86
msgid "Recipes"
msgstr "Receptek"
#: .\cookbook\templates\index.html:93
msgid "Log in to view recipes"
msgstr "Bejelentkezés a receptek megtekintéséhez"
#: .\cookbook\templates\markdown_info.html:5
#: .\cookbook\templates\markdown_info.html:13
msgid "Markdown Info"
msgstr "Markdown információ"
#: .\cookbook\templates\markdown_info.html:14
msgid ""
"\n"
" Markdown is lightweight markup language that can be used to format "
"plain text easily.\n"
" This site uses the Python Markdown library to\n"
" convert your text into nice looking HTML. Its full markdown "
"documentation can be found\n"
" here.\n"
" An incomplete but most likely sufficient documentation can be found "
"below.\n"
" "
msgstr ""
"\n"
" A Markdown egy könnyű jelölőnyelv, amely egyszerű szövegek egyszerű "
"formázására használható.\n"
" Ez a webhely Python Markdown -t\n"
" használja a szöveg HTML-be konvertálásához. A teljes markdown "
"dokumentáció elérhető\n"
" itt.\n"
" Az alábbiakban egy hiányos, de valószínűleg elegendő dokumentáció "
"található.\n"
" "
#: .\cookbook\templates\markdown_info.html:25
msgid "Headers"
msgstr "Fejlécek"
#: .\cookbook\templates\markdown_info.html:54
msgid "Formatting"
msgstr "Formázás"
#: .\cookbook\templates\markdown_info.html:56
#: .\cookbook\templates\markdown_info.html:72
msgid "Line breaks are inserted by adding two spaces after the end of a line"
msgstr "A sortörés a sor vége után két szóköz hozzáadásával történik"
#: .\cookbook\templates\markdown_info.html:57
#: .\cookbook\templates\markdown_info.html:73
msgid "or by leaving a blank line in between."
msgstr "vagy egy üres sort hagyva közöttük."
#: .\cookbook\templates\markdown_info.html:59
#: .\cookbook\templates\markdown_info.html:74
msgid "This text is bold"
msgstr "Ez a szöveg félkövér"
#: .\cookbook\templates\markdown_info.html:60
#: .\cookbook\templates\markdown_info.html:75
msgid "This text is italic"
msgstr "Ez a szöveg dőlt betűs"
#: .\cookbook\templates\markdown_info.html:61
#: .\cookbook\templates\markdown_info.html:77
msgid "Blockquotes are also possible"
msgstr "A blokkidézetek is lehetségesek"
#: .\cookbook\templates\markdown_info.html:84
msgid "Lists"
msgstr "Listák"
#: .\cookbook\templates\markdown_info.html:85
msgid ""
"Lists can ordered or unordered. It is important to leave a blank line "
"before the list!"
msgstr ""
"A listák lehetnek rendezettek vagy rendezetlenek. Fontos, hogy hagyjon "
"üres sort a lista előtt!"
#: .\cookbook\templates\markdown_info.html:87
#: .\cookbook\templates\markdown_info.html:108
msgid "Ordered List"
msgstr "Rendezett lista"
#: .\cookbook\templates\markdown_info.html:89
#: .\cookbook\templates\markdown_info.html:90
#: .\cookbook\templates\markdown_info.html:91
#: .\cookbook\templates\markdown_info.html:110
#: .\cookbook\templates\markdown_info.html:111
#: .\cookbook\templates\markdown_info.html:112
msgid "unordered list item"
msgstr "rendezetlen listaelem"
#: .\cookbook\templates\markdown_info.html:93
#: .\cookbook\templates\markdown_info.html:114
msgid "Unordered List"
msgstr "Rendezetlen lista"
#: .\cookbook\templates\markdown_info.html:95
#: .\cookbook\templates\markdown_info.html:96
#: .\cookbook\templates\markdown_info.html:97
#: .\cookbook\templates\markdown_info.html:116
#: .\cookbook\templates\markdown_info.html:117
#: .\cookbook\templates\markdown_info.html:118
msgid "ordered list item"
msgstr "rendezett listaelem"
#: .\cookbook\templates\markdown_info.html:125
msgid "Images & Links"
msgstr "Képek és linkek"
#: .\cookbook\templates\markdown_info.html:126
msgid ""
"Links can be formatted with Markdown. This application also allows to paste "
"links directly into markdown fields without any formatting."
msgstr ""
"A hivatkozásokat Markdown segítségével lehet formázni. Ez az alkalmazás azt "
"is lehetővé teszi, hogy a linkeket közvetlenül a Markdown mezőkbe illessze "
"be, mindenféle formázás nélkül."
#: .\cookbook\templates\markdown_info.html:132
#: .\cookbook\templates\markdown_info.html:145
msgid "This will become an image"
msgstr "Ez egy kép lesz"
#: .\cookbook\templates\markdown_info.html:152
msgid "Tables"
msgstr "Táblázatok"
#: .\cookbook\templates\markdown_info.html:153
msgid ""
"Markdown tables are hard to create by hand. It is recommended to use a table "
"editor like this one."
msgstr ""
"Markdown táblázatokat nehéz kézzel létrehozni. Javasoljuk, hogy használjon "
"egy táblázatszerkesztőt, mint ez."
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:171
#: .\cookbook\templates\markdown_info.html:177
msgid "Table"
msgstr "Táblázat"
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:172
msgid "Header"
msgstr "Fejléc"
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:178
msgid "Cell"
msgstr "Cella"
#: .\cookbook\templates\no_groups_info.html:5
#: .\cookbook\templates\no_groups_info.html:12
msgid "No Permissions"
msgstr "Nincs jogosultság"
#: .\cookbook\templates\no_groups_info.html:17
msgid "You do not have any groups and therefor cannot use this application."
msgstr ""
"Önnek nincsenek csoportjai, ezért nem tudja használni ezt az alkalmazást."
#: .\cookbook\templates\no_groups_info.html:18
#: .\cookbook\templates\no_perm_info.html:15
msgid "Please contact your administrator."
msgstr "Kérjük, forduljon a rendszergazdához."
#: .\cookbook\templates\no_perm_info.html:5
#: .\cookbook\templates\no_perm_info.html:12
msgid "No Permission"
msgstr "Nincs engedély"
#: .\cookbook\templates\no_perm_info.html:15
msgid ""
"You do not have the required permissions to view this page or perform this "
"action."
msgstr ""
"Ön nem rendelkezik a szükséges jogosultságokkal az oldal megtekintéséhez "
"vagy a művelet végrehajtásához."
#: .\cookbook\templates\offline.html:5
msgid "Offline"
msgstr "Offline"
#: .\cookbook\templates\openid\login.html:27
#: .\cookbook\templates\socialaccount\authentication_error.html:27
msgid "Back"
msgstr "Vissza"
#: .\cookbook\templates\rest_framework\api.html:5
msgid "Recipe Home"
msgstr "Recept főoldal"
#: .\cookbook\templates\rest_framework\api.html:11
msgid "API Documentation"
msgstr "API dokumentáció"
#: .\cookbook\templates\search_info.html:5
#: .\cookbook\templates\search_info.html:9
msgid "Search Settings"
msgstr "Keresési beállítások"
#: .\cookbook\templates\search_info.html:10
msgid ""
"\n"
" Creating the best search experience is complicated and weighs "
"heavily on your personal configuration. \n"
" Changing any of the search settings can have significant impact on "
"the speed and quality of the results.\n"
" Search Methods, Trigrams and Full Text Search configurations are "
"only available if you are using Postgres for your database.\n"
" "
msgstr ""
"\n"
" A legjobb keresési élmény megteremtése bonyolult, és nagyban függ az "
"Ön személyes konfigurációjától. \n"
" A keresési beállítások bármelyikének megváltoztatása jelentős "
"hatással lehet a találatok sebességére és minőségére.\n"
" A Keresési módszerek, a Trigramok és a Teljes szöveges keresés "
"konfigurációk csak akkor érhetők el, ha Postgres-t használ az "
"adatbázisához.\n"
" "
#: .\cookbook\templates\search_info.html:19
msgid "Search Methods"
msgstr "Keresési módszerek"
#: .\cookbook\templates\search_info.html:23
msgid ""
" \n"
" Full text searches attempt to normalize the words provided to "
"match common variants. For example: 'forked', 'forking', 'forks' will all "
"normalize to 'fork'.\n"
" There are several methods available, described below, that will "
"control how the search behavior should react when multiple words are "
"searched.\n"
" Full technical details on how these operate can be viewed on Postgresql's website.\n"
" "
msgstr ""
" \n"
" A teljes szöveges keresések megpróbálják normalizálni a megadott "
"szavakat, hogy megfeleljenek a gyakori változatoknak. Például: 'forked', "
"'forking', 'forks', 'forks' mind a 'fork' szóra normalizálódik.\n"
" Több, alább ismertetett módszer áll rendelkezésre, amelyekkel "
"szabályozható, hogy a keresési viselkedés hogyan reagáljon több szó keresése "
"esetén.\n"
" Ezek működésének teljes műszaki részletei a megtekinthetőek a Postgresql weboldalán.\n"
" "
#: .\cookbook\templates\search_info.html:29
#, fuzzy
#| msgid ""
#| " \n"
#| " Simple searches ignore punctuation and common words such as "
#| "'the', 'a', 'and'. And will treat seperate words as required.\n"
#| " Searching for 'apple or flour' will return any recipe that "
#| "includes both 'apple' and 'flour' anywhere in the fields that have been "
#| "selected for a full text search.\n"
#| " "
msgid ""
" \n"
" Simple searches ignore punctuation and common words such as "
"'the', 'a', 'and'. And will treat separate words as required.\n"
" Searching for 'apple or flour' will return any recipe that "
"includes both 'apple' and 'flour' anywhere in the fields that have been "
"selected for a full text search.\n"
" "
msgstr ""
" \n"
" Az egyszerű keresések figyelmen kívül hagyják az írásjeleket és "
"az olyan gyakori szavakat, mint az \"a\", \"a\", \"és\". A különálló "
"szavakat pedig szükség szerint kezeli.\n"
" Az \"alma vagy liszt\" keresés minden olyan receptet visszaad, "
"amely tartalmazza az \"alma\" és a \"liszt\" szót a teljes szöveges "
"kereséshez kiválasztott mezőkben.\n"
" "
#: .\cookbook\templates\search_info.html:34
msgid ""
" \n"
" Phrase searches ignore punctuation, but will search for all of "
"the words in the exact order provided.\n"
" Searching for 'apple or flour' will only return a recipe that "
"includes the exact phrase 'apple or flour' in any of the fields that have "
"been selected for a full text search.\n"
" "
msgstr ""
" \n"
" A kifejezéskeresés figyelmen kívül hagyja az írásjeleket, de az "
"összes szót pontosan a megadott sorrendben keresi.\n"
" Az \"alma vagy liszt\" keresés csak olyan receptet ad vissza, "
"amely tartalmazza az \"alma vagy liszt\" kifejezést a teljes szöveges "
"kereséshez kiválasztott mezők bármelyikében.\n"
" "
#: .\cookbook\templates\search_info.html:39
#, fuzzy
#| msgid ""
#| " \n"
#| " Web searches simulate functionality found on many web search "
#| "sites supporting special syntax.\n"
#| " Placing quotes around several words will convert those words "
#| "into a phrase.\n"
#| " 'or' is recongized as searching for the word (or phrase) "
#| "immediately before 'or' OR the word (or phrase) directly after.\n"
#| " '-' is recognized as searching for recipes that do not "
#| "include the word (or phrase) that comes immediately after. \n"
#| " For example searching for 'apple pie' or cherry -butter will "
#| "return any recipe that includes the phrase 'apple pie' or the word "
#| "'cherry' \n"
#| " in any field included in the full text search but exclude any "
#| "recipe that has the word 'butter' in any field included.\n"
#| " "
msgid ""
" \n"
" Web searches simulate functionality found on many web search "
"sites supporting special syntax.\n"
" Placing quotes around several words will convert those words "
"into a phrase.\n"
" 'or' is recognized as searching for the word (or phrase) "
"immediately before 'or' OR the word (or phrase) directly after.\n"
" '-' is recognized as searching for recipes that do not include "
"the word (or phrase) that comes immediately after. \n"
" For example searching for 'apple pie' or cherry -butter will "
"return any recipe that includes the phrase 'apple pie' or the word "
"'cherry' \n"
" in any field included in the full text search but exclude any "
"recipe that has the word 'butter' in any field included.\n"
" "
msgstr ""
" \n"
" A webes keresések szimulálják a speciális szintaxist támogató "
"számos webes keresőoldalon megtalálható funkciókat.\n"
" Ha több szó köré idézőjelet teszel, akkor ezeket a szavakat egy "
"kifejezéssé alakítod.\n"
" Az 'or' kifejezés a közvetlenül az 'or' előtt álló szót (vagy "
"kifejezést) VAGY a közvetlenül utána álló szót (vagy kifejezést) keresi.\n"
" '-' olyan recepteket keres, amelyek nem tartalmazzák a "
"közvetlenül utána következő szót (vagy kifejezést). \n"
" Például az 'apple pie' or cherry -butter keresése minden olyan "
"receptet visszaad, amely tartalmazza az 'apple pie' kifejezést vagy a "
"'cherry' szót \n"
" a teljes szöveges keresésben szereplő bármely mezőben, de kizár "
"minden olyan receptet, amelynek bármely mezőjében szerepel a 'butter' szó.\n"
" "
#: .\cookbook\templates\search_info.html:48
msgid ""
" \n"
" Raw search is similar to Web except will take puncuation "
"operators such as '|', '&' and '()'\n"
" "
msgstr ""
" \n"
" A nyers keresés hasonló a webeshez, kivéve, hogy fogad olyan "
"írásjel-operátorokat, mint a '|', '&' és '()'\n"
" "
#: .\cookbook\templates\search_info.html:59
#, fuzzy
#| msgid ""
#| " \n"
#| " Another approach to searching that also requires Postgresql "
#| "is fuzzy search or trigram similarity. A trigram is a group of three "
#| "consecutive characters.\n"
#| " For example searching for 'apple' will create x trigrams "
#| "'app', 'ppl', 'ple' and will create a score of how closely words match "
#| "the generated trigrams.\n"
#| " One benefit of searching trigams is that a search for "
#| "'sandwich' will find mispelled words such as 'sandwhich' that would be "
#| "missed by other methods.\n"
#| " "
msgid ""
" \n"
" Another approach to searching that also requires Postgresql is "
"fuzzy search or trigram similarity. A trigram is a group of three "
"consecutive characters.\n"
" For example searching for 'apple' will create x trigrams 'app', "
"'ppl', 'ple' and will create a score of how closely words match the "
"generated trigrams.\n"
" One benefit of searching trigams is that a search for 'sandwich' "
"will find misspelled words such as 'sandwhich' that would be missed by other "
"methods.\n"
" "
msgstr ""
" \n"
" A keresés egy másik megközelítése, amely szintén Postgresql-t "
"igényel, a bizonytalan (fuzzy) keresés vagy trigram hasonlóság. A trigram "
"három egymást követő karakterből álló csoport.\n"
" Például az 'apple' keresése x trigramot fog létrehozni: 'app', "
"'ppl', 'ple', és egy pontszámot fog létrehozni arról, hogy a szavak mennyire "
"egyeznek a generált trigramokkal.\n"
" A trigram keresés egyik előnye, hogy a 'sandwich' keresés "
"megtalálja az olyan helytelenül írt szavakat, mint például a 'sandwhich', "
"amelyek más módszerekkel kimaradnának.\n"
" "
#: .\cookbook\templates\search_info.html:69
msgid "Search Fields"
msgstr "Keresési mezők"
#: .\cookbook\templates\search_info.html:73
msgid ""
" \n"
" Unaccent is a special case in that it enables searching a field "
"'unaccented' for each search style attempting to ignore accented values. \n"
" For example when you enable unaccent for 'Name' any search "
"(starts with, contains, trigram) will attempt the search ignoring accented "
"characters.\n"
" \n"
" For the other options, you can enable search on any or all "
"fields and they will be combined together with an assumed 'OR'.\n"
" For example enabling 'Name' for Starts With, 'Name' and "
"'Description' for Partial Match and 'Ingredients' and 'Keywords' for Full "
"Search\n"
" and searching for 'apple' will generate a search that will "
"return recipes that have:\n"
" - A recipe name that starts with 'apple'\n"
" - OR a recipe name that contains 'apple'\n"
" - OR a recipe description that contains 'apple'\n"
" - OR a recipe that will have a full text search match ('apple' "
"or 'apples') in ingredients\n"
" - OR a recipe that will have a full text search match in "
"Keywords\n"
"\n"
" Combining too many fields in too many types of search can have a "
"negative impact on performance, create duplicate results or return "
"unexpected results.\n"
" For example, enabling fuzzy search or partial matches will "
"interfere with web search methods. \n"
" Searching for 'apple -pie' with fuzzy search and full text "
"search will return the recipe Apple Pie. Though it is not included in the "
"full text results, it does match the trigram results.\n"
" "
msgstr ""
" \n"
" Unaccent is a special case in that it enables searching a field "
"'unaccented' for each search style attempting to ignore accented values. \n"
" For example when you enable unaccent for 'Name' any search "
"(starts with, contains, trigram) will attempt the search ignoring accented "
"characters.\n"
" \n"
" For the other options, you can enable search on any or all "
"fields and they will be combined together with an assumed 'OR'.\n"
" For example enabling 'Name' for Starts With, 'Name' and "
"'Description' for Partial Match and 'Ingredients' and 'Keywords' for Full "
"Search\n"
" and searching for 'apple' will generate a search that will "
"return recipes that have:\n"
" - A recipe name that starts with 'apple'\n"
" - OR a recipe name that contains 'apple'\n"
" - OR a recipe description that contains 'apple'\n"
" - OR a recipe that will have a full text search match ('apple' "
"or 'apples') in ingredients\n"
" - OR a recipe that will have a full text search match in "
"Keywords\n"
"\n"
" Combining too many fields in too many types of search can have a "
"negative impact on performance, create duplicate results or return "
"unexpected results.\n"
" For example, enabling fuzzy search or partial matches will "
"interfere with web search methods. \n"
" Searching for 'apple -pie' with fuzzy search and full text "
"search will return the recipe Apple Pie. Though it is not included in the "
"full text results, it does match the trigram results.\n"
" "
#: .\cookbook\templates\search_info.html:95
msgid "Search Index"
msgstr "Keresési index"
#: .\cookbook\templates\search_info.html:99
msgid ""
" \n"
" Trigram search and Full Text Search both rely on database "
"indexes to perform effectively. \n"
" You can rebuild the indexes on all fields in the Admin page for "
"Recipes and selecting all recipes and running 'rebuild index for selected "
"recipes'\n"
" You can also rebuild indexes at the command line by executing "
"the management command 'python manage.py rebuildindex'\n"
" "
msgstr ""
" \n"
" A trigramma keresés és a teljes szöveges keresés egyaránt az "
"adatbázis-indexekre támaszkodik a hatékony működéshez. \n"
" Újraépítheti az indexeket az összes mezőn a Receptek "
"adminisztrációs oldalán, és az összes recept kiválasztásával és a "
"\"kiválasztott receptek indexének újjáépítése\" futtatásával.\n"
" Az indexeket a parancssorból is újjáépítheti a 'python manage.py "
"rebuildindex' parancs végrehajtásával.\n"
" "
#: .\cookbook\templates\setup.html:6
msgid "Cookbook Setup"
msgstr "Receptkönyv beállítása"
#: .\cookbook\templates\setup.html:14
msgid "Setup"
msgstr "Beállítás"
#: .\cookbook\templates\setup.html:15
msgid ""
"To start using this application you must first create a superuser account."
msgstr ""
"Az alkalmazás használatának megkezdéséhez először létre kell hoznia egy "
"szuperfelhasználói fiókot."
#: .\cookbook\templates\setup.html:20
msgid "Create Superuser account"
msgstr "Szuperfelhasználói fiók létrehozása"
#: .\cookbook\templates\socialaccount\authentication_error.html:7
#: .\cookbook\templates\socialaccount\authentication_error.html:23
msgid "Social Network Login Failure"
msgstr "Közösségi hálózat bejelentkezési hiba"
#: .\cookbook\templates\socialaccount\authentication_error.html:25
msgid ""
"An error occurred while attempting to login via your social network account."
msgstr ""
"Hiba történt, miközben megpróbált bejelentkezni a közösségi hálózati fiókján "
"keresztül."
#: .\cookbook\templates\socialaccount\connections.html:4
#: .\cookbook\templates\socialaccount\connections.html:7
msgid "Account Connections"
msgstr "Fiókkapcsolatok"
#: .\cookbook\templates\socialaccount\connections.html:10
msgid ""
"You can sign in to your account using any of the following third party\n"
" accounts:"
msgstr ""
"Bejelentkezhet fiókjába a következő harmadik fél által használt bármelyik "
"használatával\n"
" fiókok használatával:"
#: .\cookbook\templates\socialaccount\connections.html:44
msgid ""
"You currently have no social network accounts connected to this account."
msgstr ""
"Jelenleg nincsenek ehhez a fiókhoz kapcsolódó közösségi hálózati fiókok."
#: .\cookbook\templates\socialaccount\connections.html:47
msgid "Add a 3rd Party Account"
msgstr "Harmadik fél fiók hozzáadása"
#: .\cookbook\templates\socialaccount\login.html:5
#: .\cookbook\templates\socialaccount\signup.html:5
msgid "Signup"
msgstr "Regisztráció"
#: .\cookbook\templates\socialaccount\login.html:9
#, python-format
msgid "Connect %(provider)s"
msgstr "Csatlakozás %(provider)s"
#: .\cookbook\templates\socialaccount\login.html:11
#, python-format
msgid "You are about to connect a new third party account from %(provider)s."
msgstr ""
"Ön egy új, harmadik féltől származó fiókot készül csatlakoztatni a"
"%(provider)-tól/től."
#: .\cookbook\templates\socialaccount\login.html:13
#, python-format
msgid "Sign In Via %(provider)s"
msgstr "Bejelentkezve %(provider)s keresztül"
#: .\cookbook\templates\socialaccount\login.html:15
#, python-format
msgid "You are about to sign in using a third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:20
msgid "Continue"
msgstr "Folytatás"
#: .\cookbook\templates\socialaccount\signup.html:10
#, python-format
msgid ""
"You are about to use your\n"
" %(provider_name)s account to login to\n"
" %(site_name)s. As a final step, please complete the following form:"
msgstr ""
"A következőt fogod használni:\n"
" %(provider_name)s fiókot a bejelentkezéshez ide\n"
" %(site_name)s. Utolsó lépésként töltsd ki az alábbi űrlapot:"
#: .\cookbook\templates\socialaccount\signup.html:32
#, fuzzy
#| msgid "I accept the follwoing"
msgid "I accept the following"
msgstr "Elfogadom a következőket"
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:23
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:31
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:39
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:47
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:55
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:63
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:71
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:79
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:87
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:95
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:103
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:111
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:119
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:127
msgid "Sign in using"
msgstr "Bejelentkezés a következővel"
#: .\cookbook\templates\space_overview.html:6
msgid "Overview"
msgstr "Áttekintés"
#: .\cookbook\templates\space_overview.html:13
msgid "Space"
msgstr "Tér"
#: .\cookbook\templates\space_overview.html:17
msgid ""
"Recipes, foods, shopping lists and more are organized in spaces of one or "
"more people."
msgstr ""
"A receptek, ételek, bevásárlólisták és egyebek egy vagy több személyre szóló "
"terekbe szerveződnek."
#: .\cookbook\templates\space_overview.html:18
msgid ""
"You can either be invited into an existing space or create your own one."
msgstr "Meghívást kaphatsz egy meglévő térbe, vagy létrehozhatod a sajátodat."
#: .\cookbook\templates\space_overview.html:25
msgid "Your Spaces"
msgstr "Ön Helye"
#: .\cookbook\templates\space_overview.html:53
msgid "Owner"
msgstr "Tulajdonos"
#: .\cookbook\templates\space_overview.html:73
#: .\cookbook\templates\space_overview.html:83
msgid "Join Space"
msgstr "Csatlakozz a térhez"
#: .\cookbook\templates\space_overview.html:76
msgid "Join an existing space."
msgstr "Csatlakozz egy meglévő térhez."
#: .\cookbook\templates\space_overview.html:78
msgid ""
"To join an existing space either enter your invite token or click on the "
"invite link the space owner send you."
msgstr ""
"Egy meglévő térhez való csatlakozáshoz add meg a meghívó tokenedet, vagy "
"kattints a meghívó linkre, amelyet a tér tulajdonosa küldött neked."
#: .\cookbook\templates\space_overview.html:91
#: .\cookbook\templates\space_overview.html:100
msgid "Create Space"
msgstr "Tér létrehozása"
#: .\cookbook\templates\space_overview.html:94
msgid "Create your own recipe space."
msgstr "Hozzon létre saját receptteret."
#: .\cookbook\templates\space_overview.html:96
msgid "Start your own recipe space and invite other users to it."
msgstr "Indítsd el a saját receptteredet, és hívj meg oda más felhasználókat."
#: .\cookbook\templates\system.html:23
msgid "System"
msgstr "Rendszer"
#: .\cookbook\templates\system.html:24
#, fuzzy
#| msgid ""
#| "\n"
#| " Django Recipes is an open source free software application. It "
#| "can be found on\n"
#| " GitHub.\n"
#| " Changelogs can be found here.\n"
#| " "
msgid ""
"\n"
" Tandoor Recipes is an open source free software application. It can "
"be found on\n"
" GitHub.\n"
" Changelogs can be found here.\n"
" "
msgstr ""
"\n"
" Django Recipes egy nyílt forráskódú, ingyenes szoftveralkalmazás. "
"Megtalálható a\n"
" GitHubon.\n"
" Változtatások listája elérhető itt.\n"
" "
#: .\cookbook\templates\system.html:30
msgid "System Information"
msgstr "Rendszerinformáció"
#: .\cookbook\templates\system.html:51
msgid ""
"\n"
" You need to execute version.py in your update "
"script to generate version information (done automatically in docker).\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:56
msgid "Plugins"
msgstr ""
#: .\cookbook\templates\system.html:67
msgid "Media Serving"
msgstr "Média kiszolgáló"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:123 .\cookbook\templates\system.html:134
msgid "Warning"
msgstr "Figyelmeztetés"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:125 .\cookbook\templates\system.html:134
msgid "Ok"
msgstr "Rendben"
#: .\cookbook\templates\system.html:70
msgid ""
"Serving media files directly using gunicorn/python is not recommend!\n"
" Please follow the steps described\n"
" here to update\n"
" your installation.\n"
" "
msgstr ""
"A médiafájlok közvetlen kiszolgálása a gunicorn/python használatával nem "
"javasolt!\n"
" Kérlek, kövesd a leírt lépéseket\n"
" itt a\n"
" frissítéshez.\n"
" "
#: .\cookbook\templates\system.html:76 .\cookbook\templates\system.html:91
#: .\cookbook\templates\system.html:104 .\cookbook\templates\system.html:115
#: .\cookbook\views\views.py:168
msgid "Everything is fine!"
msgstr "Minden rendben van!"
#: .\cookbook\templates\system.html:80
msgid "Secret Key"
msgstr "Titkos kulcs"
#: .\cookbook\templates\system.html:84
msgid ""
"\n"
" You do not have a SECRET_KEY configured in your "
".env file. Django defaulted to the\n"
" standard key\n"
" provided with the installation which is publicly know and "
"insecure! Please set\n"
" SECRET_KEY int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" Nincs beállítva SECRET_KEY az .env "
"fájlban. A Django alapértelmezett\n"
" standard kulcs a\n"
" telepítéshez mellékelt, nyilvánosan ismert és nem biztonságos! "
"Kérlek állíts be\n"
" SECRET_KEY -t a .env konfigurációs "
"fájlban.\n"
" "
#: .\cookbook\templates\system.html:94
msgid "Debug Mode"
msgstr "Hibakeresési mód"
#: .\cookbook\templates\system.html:98
msgid ""
"\n"
" This application is still running in debug mode. This is most "
"likely not needed. Turn of debug mode by\n"
" setting\n"
" DEBUG=0 int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" Ez az alkalmazás még mindig hibakeresési módban fut. Erre "
"valószínűleg nincs szükség. Kapcsold ki a hibakeresési módot\n"
" a\n"
" DEBUG=0 -ra állításával a .env "
"konfigurációs fájlban.\n"
" "
#: .\cookbook\templates\system.html:107
msgid "Allowed Hosts"
msgstr ""
#: .\cookbook\templates\system.html:111
msgid ""
"\n"
" Your allowed hosts are configured to allow every host. This "
"might be ok in some setups but should be avoided. Please see the docs about "
"this.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:118
msgid "Database"
msgstr "Adatbázis"
#: .\cookbook\templates\system.html:121
msgid "Info"
msgstr "Információ"
#: .\cookbook\templates\system.html:131 .\cookbook\templates\system.html:148
#, fuzzy
#| msgid "Use fractions"
msgid "Migrations"
msgstr "Törtek használata"
#: .\cookbook\templates\system.html:137
msgid ""
"\n"
" Migrations should never fail!\n"
" Failed migrations will likely cause major parts of the app to "
"not function correctly.\n"
" If a migration fails make sure you are on the latest version and "
"if so please post the migration log and the overview below in a GitHub "
"issue.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "False"
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "True"
msgstr ""
#: .\cookbook\templates\system.html:268
msgid "Hide"
msgstr ""
#: .\cookbook\templates\system.html:271
#, fuzzy
#| msgid "Show Log"
msgid "Show"
msgstr "Napló megjelenítése"
#: .\cookbook\templates\test2.html:6
msgid "Export Recipes"
msgstr "Receptek exportálása"
#: .\cookbook\templates\test2.html:14 .\cookbook\templates\test2.html:20
msgid "Export"
msgstr "Export"
#: .\cookbook\views\api.py:198 .\cookbook\views\api.py:326
msgid "Parameter updated_at incorrectly formatted"
msgstr "Az updated_at paraméter helytelenül van formázva"
#: .\cookbook\views\api.py:351 .\cookbook\views\api.py:484
#, python-brace-format
msgid "No {self.basename} with id {pk} exists"
msgstr "Nem létezik {self.basename} azonosítóval {pk}"
#: .\cookbook\views\api.py:355
msgid "Cannot merge with the same object!"
msgstr "Nem egyesíthető ugyanazzal az objektummal!"
#: .\cookbook\views\api.py:362
#, python-brace-format
msgid "No {self.basename} with id {target} exists"
msgstr "Nem létezik {self.basename} azonosítóval {target}"
#: .\cookbook\views\api.py:367
msgid "Cannot merge with child object!"
msgstr "Nem lehet egyesíteni a gyermekobjektummal!"
#: .\cookbook\views\api.py:405
#, python-brace-format
msgid "{source.name} was merged successfully with {target.name}"
msgstr "{source.name} sikeresen egyesült a {target.name} -vel"
#: .\cookbook\views\api.py:411
#, python-brace-format
msgid "An error occurred attempting to merge {source.name} with {target.name}"
msgstr "Hiba történt a {source.name} és a {target.name} egyesítése során"
#: .\cookbook\views\api.py:493
#, python-brace-format
msgid "{child.name} was moved successfully to the root."
msgstr "{child.name} sikeresen átkerült a gyökérbe."
#: .\cookbook\views\api.py:496 .\cookbook\views\api.py:514
msgid "An error occurred attempting to move "
msgstr "Hiba történt az áthelyezés közben "
#: .\cookbook\views\api.py:499
msgid "Cannot move an object to itself!"
msgstr "Nem lehet egy objektumot önmagába mozgatni!"
#: .\cookbook\views\api.py:505
#, python-brace-format
msgid "No {self.basename} with id {parent} exists"
msgstr "Nem létezik {self.basename} azonosítóval {parent}"
#: .\cookbook\views\api.py:511
#, python-brace-format
msgid "{child.name} was moved successfully to parent {parent.name}"
msgstr "{child.name} sikeresen átkerült a {parent.name} szülőhöz"
#: .\cookbook\views\api.py:992
#, python-brace-format
msgid "{obj.name} was removed from the shopping list."
msgstr "{obj.name} lekerült a bevásárlólistáról."
#: .\cookbook\views\api.py:997 .\cookbook\views\api.py:1627
#, python-brace-format
msgid "{obj.name} was added to the shopping list."
msgstr "{obj.name} hozzá lett adva a bevásárlólistához."
#: .\cookbook\views\api.py:1239
msgid "Filter meal plans from date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1241
msgid "Filter meal plans to date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1244
#, fuzzy
#| msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
msgstr ""
"A recept azonosítója, amelynek egy lépés része. Többszörös ismétlés esetén "
"paraméter."
#: .\cookbook\views\api.py:1404
msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgstr ""
"A recept azonosítója, amelynek egy lépés része. Többszörös ismétlés esetén "
"paraméter."
#: .\cookbook\views\api.py:1406
msgid "Query string matched (fuzzy) against object name."
msgstr "A lekérdezés karakterlánca az objektum nevével összevetve (fuzzy)."
#: .\cookbook\views\api.py:1442
msgid ""
"Query string matched (fuzzy) against recipe name. In the future also "
"fulltext search."
msgstr ""
"A lekérdezési karakterláncot a recept nevével összevetve (fuzzy). A jövőben "
"teljes szöveges keresés is."
#: .\cookbook\views\api.py:1444
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr ""
"A recept kulcsszavának azonosítója. Többszörös ismétlődő paraméter esetén. "
"Egyenértékű a keywords_or kulcsszavakkal"
#: .\cookbook\views\api.py:1445
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr ""
"Kulcsszó azonosítók. Többször is megadható. A megadott kulcsszavak "
"mindegyikéhez tartozó receptek listázza"
#: .\cookbook\views\api.py:1446
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr ""
"Kulcsszó azonosítók. Többször is megadható. Az összes megadott kulcsszót "
"tartalmazó receptek listázása."
#: .\cookbook\views\api.py:1447
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr ""
"Kulcsszó azonosító. Többször is megadható. Kizárja a recepteket a megadott "
"kulcsszavak egyikéből."
#: .\cookbook\views\api.py:1448
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr ""
"Kulcsszó azonosítók. Többször is megadható. Kizárja az összes megadott "
"kulcsszóval rendelkező receptet."
#: .\cookbook\views\api.py:1450
msgid "ID of food a recipe should have. For multiple repeat parameter."
msgstr ""
"Annak az összetevőnek az azonosítója, amelynek receptjeit fel kell sorolni. "
"Többször is megadható."
#: .\cookbook\views\api.py:1451
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr ""
"Összetevő azonosító. Többször is megadható. Legalább egy összetevő "
"receptjeinek listája"
#: .\cookbook\views\api.py:1452
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr ""
"Összetevő azonosító. Többször is megadható. Az összes megadott összetevőt "
"tartalmazó receptek listája."
#: .\cookbook\views\api.py:1453
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr ""
"Összetevő azonosító. Többször is megadható. Kizárja azokat a recepteket, "
"amelyek a megadott összetevők bármelyikét tartalmazzák."
#: .\cookbook\views\api.py:1454
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr ""
"Összetevő azonosító. Többször is megadható. Kizárja az összes megadott "
"összetevőt tartalmazó recepteket."
#: .\cookbook\views\api.py:1456
msgid "ID of book a recipe should be in. For multiple repeat parameter."
msgstr ""
"A könyv azonosítója, amelyben a recept található. Többször is megadható."
#: .\cookbook\views\api.py:1457
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr ""
"A könyv azonosítója. Többször is megadható. A megadott könyvek összes "
"receptjének listája"
#: .\cookbook\views\api.py:1458
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr ""
"A könyv azonosítója. Többször is megadható. Az összes könyvben szereplő "
"recept listája."
#: .\cookbook\views\api.py:1459
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr ""
"A könyv azonosítói. Többször is megadható. Kizárja a megadott könyvek "
"receptjeit."
#: .\cookbook\views\api.py:1460
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr ""
"A könyv azonosítói. Többször is megadható. Kizárja az összes megadott "
"könyvben szereplő receptet."
#: .\cookbook\views\api.py:1462
msgid "ID of unit a recipe should have."
msgstr "A recepthez tartozó mértékegység azonosítója."
#: .\cookbook\views\api.py:1464
msgid "Exact rating of recipe"
msgstr ""
#: .\cookbook\views\api.py:1465
#, fuzzy
#| msgid "Rating a recipe should have. [0 - 5]"
msgid "Rating a recipe should have or greater."
msgstr "Értékelés amely egy receptnek kell legyen. [0 - 5]"
#: .\cookbook\views\api.py:1466
#, fuzzy
#| msgid "Rating a recipe should have. [0 - 5]"
msgid "Rating a recipe should have or smaller."
msgstr "Értékelés amely egy receptnek kell legyen. [0 - 5]"
#: .\cookbook\views\api.py:1468
msgid "Filter recipes cooked X times."
msgstr ""
#: .\cookbook\views\api.py:1469
#, fuzzy
#| msgid ""
#| "Filter recipes cooked X times or more. Negative values returns cooked "
#| "less than X times"
msgid "Filter recipes cooked X times or more."
msgstr ""
"X-szer vagy többször főzött receptek szűrése. A negatív értékek X "
"alkalomnál kevesebbet főzött recepteket jelenítik meg"
#: .\cookbook\views\api.py:1470
msgid "Filter recipes cooked X times or less."
msgstr ""
#: .\cookbook\views\api.py:1472
#, fuzzy
#| msgid ""
#| "Filter recipes created on or after YYYY-MM-DD. Prepending - filters on or "
#| "before date."
msgid "Filter recipes created on the given date."
msgstr ""
"Megjeleníti azokat a recepteket, amelyeket a megadott napon (ÉÉÉÉ-HH-NN) "
"vagy később hoztak létre. A - jelölve az adott dátumon vagy azt megelőzően "
"hozták létre."
#: .\cookbook\views\api.py:1473
#, fuzzy
#| msgid ""
#| "Filter recipes created on or after YYYY-MM-DD. Prepending - filters on or "
#| "before date."
msgid "Filter recipes created on the given date or after."
msgstr ""
"Megjeleníti azokat a recepteket, amelyeket a megadott napon (ÉÉÉÉ-HH-NN) "
"vagy később hoztak létre. A - jelölve az adott dátumon vagy azt megelőzően "
"hozták létre."
#: .\cookbook\views\api.py:1474
#, fuzzy
#| msgid ""
#| "Filter recipes created on or after YYYY-MM-DD. Prepending - filters on or "
#| "before date."
msgid "Filter recipes created on the given date or before."
msgstr ""
"Megjeleníti azokat a recepteket, amelyeket a megadott napon (ÉÉÉÉ-HH-NN) "
"vagy később hoztak létre. A - jelölve az adott dátumon vagy azt megelőzően "
"hozták létre."
#: .\cookbook\views\api.py:1476 .\cookbook\views\api.py:1477
#: .\cookbook\views\api.py:1478
#, fuzzy
#| msgid ""
#| "Filter recipes updated on or after YYYY-MM-DD. Prepending - filters on or "
#| "before date."
msgid "Filter recipes updated on the given date."
msgstr ""
"Megjeleníti azokat a recepteket, amelyeket a megadott napon (ÉÉÉÉ-HH-NN) "
"vagy később frissültek. A - jelölve az adott dátumon vagy azt megelőzően "
"frissültek."
#: .\cookbook\views\api.py:1480
#, fuzzy
#| msgid ""
#| "Filter recipes last cooked on or after YYYY-MM-DD. Prepending - filters "
#| "on or before date."
msgid "Filter recipes last cooked on the given date or after."
msgstr ""
"Megjeleníti azokat a recepteket, amelyeket a megadott napon (ÉÉÉÉ-HH-NN) "
"vagy később főztek meg utoljára. A - jelölve az adott dátumon vagy azt "
"megelőzően elkészítettek kerülnek be a receptek listájába."
#: .\cookbook\views\api.py:1481
#, fuzzy
#| msgid ""
#| "Filter recipes last cooked on or after YYYY-MM-DD. Prepending - filters "
#| "on or before date."
msgid "Filter recipes last cooked on the given date or before."
msgstr ""
"Megjeleníti azokat a recepteket, amelyeket a megadott napon (ÉÉÉÉ-HH-NN) "
"vagy később főztek meg utoljára. A - jelölve az adott dátumon vagy azt "
"megelőzően elkészítettek kerülnek be a receptek listájába."
#: .\cookbook\views\api.py:1483 .\cookbook\views\api.py:1484
#, fuzzy
#| msgid ""
#| "Filter recipes lasts viewed on or after YYYY-MM-DD. Prepending - filters "
#| "on or before date."
msgid "Filter recipes lasts viewed on the given date."
msgstr ""
"Megjeleníti azokat a recepteket, amelyeket a megadott napon (ÉÉÉÉ-HH-NN) "
"vagy később néztek meg utoljára. A - jelölve az adott dátumon vagy azt "
"megelőzően néztek meg utoljára."
#: .\cookbook\views\api.py:1486
msgid "Filter recipes for ones created by the given user ID"
msgstr ""
#: .\cookbook\views\api.py:1487
msgid "If only internal recipes should be returned. [true/false]"
msgstr "Ha csak a belső recepteket kell visszaadni. [true/false]"
#: .\cookbook\views\api.py:1488
msgid "Returns the results in randomized order. [true/false]"
msgstr ""
"Az eredményeket véletlenszerű sorrendben adja vissza. [true/false]"
#: .\cookbook\views\api.py:1490
msgid ""
"Determines the order of the results. Options are: score,-score,name,-name,"
"lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-"
"created_at,lastviewed,-lastviewed"
msgstr ""
#: .\cookbook\views\api.py:1492
msgid "Returns new results first in search results. [true/false]"
msgstr ""
"Az új találatokat adja vissza először a keresési eredmények között. [true/"
"false]"
#: .\cookbook\views\api.py:1493
msgid ""
"Returns the given number of recently viewed recipes before search results "
"(if given)"
msgstr ""
#: .\cookbook\views\api.py:1494
msgid "ID of a custom filter. Returns all recipes matched by that filter."
msgstr ""
#: .\cookbook\views\api.py:1495
msgid "Filter recipes that can be made with OnHand food. [true/false]"
msgstr ""
"Felsorolja azokat a recepteket, amelyeket a rendelkezésre álló összetevőkből "
"el lehet készíteni. [true/false]"
#: .\cookbook\views\api.py:1773
msgid ""
"Return the PropertyTypes matching the property category. Repeat for "
"multiple."
msgstr ""
#: .\cookbook\views\api.py:1804 .\cookbook\views\api.py:1860
msgid "Returns only entries associated with the given mealplan id"
msgstr ""
#: .\cookbook\views\api.py:1858
msgid ""
"Returns only elements updated after the given timestamp in ISO 8601 format."
msgstr ""
#: .\cookbook\views\api.py:2031
#, fuzzy
#| msgid ""
#| "Returns the shopping list entry with a primary key of id. Multiple "
#| "values allowed."
msgid ""
"Return the Automations matching the automation type. Repeat for multiple."
msgstr ""
"Visszaadja az id elsődleges kulccsal rendelkező bevásárlólista-bejegyzést. "
"Több érték megengedett."
#: .\cookbook\views\api.py:2048
msgid ""
"Text field to store data that gets carried over to the UserSpace created "
"from the InviteLink"
msgstr ""
#: .\cookbook\views\api.py:2049
msgid "Only return InviteLinks that have not been used yet."
msgstr ""
#: .\cookbook\views\api.py:2076
#, fuzzy
#| msgid ""
#| "Returns the shopping list entry with a primary key of id. Multiple "
#| "values allowed."
msgid "Return the CustomFilters matching the model type. Repeat for multiple."
msgstr ""
"Visszaadja az id elsődleges kulccsal rendelkező bevásárlólista-bejegyzést. "
"Több érték megengedett."
#: .\cookbook\views\api.py:2176
msgid "Nothing to do."
msgstr "Semmi feladat."
#: .\cookbook\views\api.py:2222
msgid "Invalid Url"
msgstr "Érvénytelen URL"
#: .\cookbook\views\api.py:2228
msgid "Connection Refused."
msgstr "Kapcsolat megtagadva."
#: .\cookbook\views\api.py:2232
msgid "Bad URL Schema."
msgstr "Rossz URL séma."
#: .\cookbook\views\api.py:2257
msgid "No usable data could be found."
msgstr "Nem sikerült használható adatokat találni."
#: .\cookbook\views\api.py:2286 .\cookbook\views\api.py:2434
msgid "You must select an AI provider to perform your request."
msgstr ""
#: .\cookbook\views\api.py:2293 .\cookbook\views\api.py:2441
msgid ""
"You don't have any credits remaining to use AI or AI features are not "
"enabled for your space."
msgstr ""
#: .\cookbook\views\api.py:2499 .\cookbook\views\api.py:2667
msgid "File is above space limit"
msgstr ""
#: .\cookbook\views\api.py:2522 .\cookbook\views\api.py:2684
msgid "Importing is not implemented for this provider"
msgstr "Az importálás nincs implementálva ennél a szolgáltatónál"
#: .\cookbook\views\api.py:2548
msgid ""
"The PDF Exporter is not enabled on this instance as it is still in an "
"experimental state."
msgstr ""
"A PDF-exportáló ezen a példányon nincs engedélyezve, mivel még kísérleti "
"állapotban van."
#: .\cookbook\views\api.py:2842
msgid "This feature is not yet available in the hosted version of tandoor!"
msgstr "Ez a funkció még nem érhető el a tandoor hosztolt verziójában!"
#: .\cookbook\views\api.py:2863
msgid "Sync successful!"
msgstr "Szinkronizálás sikeres!"
#: .\cookbook\views\api.py:2866
msgid "Error synchronizing with Storage"
msgstr "Hiba szinkronizálás közben a tárolóval"
#: .\cookbook\views\views.py:89
msgid "This feature is not available in the demo version!"
msgstr "Ez a funkció nem érhető el a demó verzióban!"
#: .\cookbook\views\views.py:110
msgid ""
"You have successfully created your own recipe space. Start by adding some "
"recipes or invite other people to join you."
msgstr ""
"Sikeresen létrehozta saját receptterét. Kezdje el néhány recept "
"hozzáadásával, vagy hívjon meg másokat is, hogy csatlakozzanak Önhöz."
#: .\cookbook\views\views.py:171
#, python-format
msgid "PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!"
msgstr ""
#: .\cookbook\views\views.py:174
#, python-format
msgid "You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended"
msgstr ""
#: .\cookbook\views\views.py:178
msgid "Unable to determine PostgreSQL version."
msgstr ""
#: .\cookbook\views\views.py:182
#, fuzzy
#| msgid ""
#| "\n"
#| " This application is not running with a Postgres database "
#| "backend. This is ok but not recommended as some\n"
#| " features only work with postgres databases.\n"
#| " "
msgid ""
"This application is not running with a Postgres database backend. This is ok "
"but not recommended as some features only work with postgres databases."
msgstr ""
"\n"
" Ez az alkalmazás nem Postgres adatbázis háttérrendszerrel fut. "
"Ez rendben van, de nem ajánlott, mivel egyes\n"
" funkciók csak Postgres adatbázisokkal működnek.\n"
" "
#: .\cookbook\views\views.py:296
#, fuzzy
#| msgid ""
#| "The setup page can only be used to create the first user! If you have "
#| "forgotten your superuser credentials please consult the django "
#| "documentation on how to reset passwords."
msgid ""
"The setup page can only be used to create the first "
"user! If you have forgotten your superuser credentials "
"please consult the django documentation on how to reset passwords."
msgstr ""
"A beállítási oldal csak az első felhasználó létrehozására használható! Ha "
"elfelejtette a szuperfelhasználói hitelesítő adatait, kérjük, olvassa el a "
"django dokumentációját a jelszavak visszaállításáról."
#: .\cookbook\views\views.py:304
msgid "Passwords dont match!"
msgstr "A jelszavak nem egyeznek!"
#: .\cookbook\views\views.py:312
msgid "User has been created, please login!"
msgstr "A felhasználó létre lett hozva, kérjük, jelentkezzen be!"
#: .\cookbook\views\views.py:352
msgid ""
"Reporting share links is not enabled for this instance. Please notify the "
"page administrator to report problems."
msgstr ""
"A megosztási hivatkozások jelentése nem engedélyezett ezen a példányon. "
"Kérjük, a problémák jelentéséhez értesítse az oldal adminisztrátorát."
#: .\cookbook\views\views.py:357
msgid ""
"Recipe sharing link has been disabled! For additional information please "
"contact the page administrator."
msgstr ""
"A receptmegosztó linket letiltották! További információkért kérjük, "
"forduljon az oldal adminisztrátorához."
#: .\cookbook\views\views.py:383
msgid "Manage recipes, shopping list, meal plans and more."
msgstr ""
#: .\cookbook\views\views.py:397 .\cookbook\views\views.py:398
#, fuzzy
#| msgid "Meal-Plan"
msgid "Plan"
msgstr "Menüterv"
#: .\cookbook\views\views.py:399
msgid "View your meal Plan"
msgstr ""
#: .\cookbook\views\views.py:418
#, fuzzy
#| msgid "New Shopping List"
msgid "View your shopping lists"
msgstr "Új bevásárló lista"
#~ msgid ""
#~ "Both fields are optional. If none are given the username will be "
#~ "displayed instead"
#~ msgstr ""
#~ "Mindkét mező opcionális. Ha egyiket sem adjuk meg, akkor a felhasználónév "
#~ "jelenik meg helyette"
#~ msgid "Name"
#~ msgstr "Név"
#~ msgid "Keywords"
#~ msgstr "Kulcsszavak"
#~ msgid "Preparation time in minutes"
#~ msgstr "Előkészítési idő percben"
#~ msgid "Waiting time (cooking/baking) in minutes"
#~ msgstr "Várakozási idő (sütés/főzés) percben"
#~ msgid "Path"
#~ msgstr "Elérési útvonal"
#~ msgid "Storage UID"
#~ msgstr "Tárhely UID"
#~ msgid "Add your comment: "
#~ msgstr "Add hozzá a kommented: "
#~ msgid "Leave empty for dropbox and enter app password for nextcloud."
#~ msgstr ""
#~ "A dropbox esetében hagyja üresen, a nextcloud esetében pedig adja meg az "
#~ "alkalmazás jelszavát."
#~ msgid "Leave empty for nextcloud and enter api token for dropbox."
#~ msgstr ""
#~ "A nextcloud esetében hagyja üresen, a dropbox esetében pedig adja meg az "
#~ "api tokent."
#~ msgid ""
#~ "Leave empty for dropbox and enter only base url for nextcloud (/"
#~ "remote.php/webdav/ is added automatically)"
#~ msgstr ""
#~ "Hagyja üresen a dropbox esetén, és csak a nextcloud alap url-jét adja meg "
#~ "(/remote.php/webdav/ automatikusan hozzáadódik)"
#~ msgid "Storage"
#~ msgstr "Tárhely"
#~ msgid "Active"
#~ msgstr "Aktív"
#~ msgid "Search String"
#~ msgstr "Keresési kifejezés"
#~ msgid "File ID"
#~ msgstr "Fájl ID"
#~ msgid ""
#~ "Determines how fuzzy a search is if it uses trigram similarity matching "
#~ "(e.g. low values mean more typos are ignored)."
#~ msgstr ""
#~ "Meghatározza, hogy a keresés mennyire bizonytalan, ha trigram-hasonlósági "
#~ "párosítást használ (pl. az alacsony értékek azt jelentik, hogy több "
#~ "gépelési hibát figyelmen kívül hagynak)."
#~ msgid ""
#~ "Select type method of search. Click here "
#~ "for full description of choices."
#~ msgstr ""
#~ "Válassza ki a keresés típusát. Kattintson ide a lehetőségek teljes leírásáért."
#~ msgid ""
#~ "Use fuzzy matching on units, keywords and ingredients when editing and "
#~ "importing recipes."
#~ msgstr ""
#~ "A receptek szerkesztése és importálása során az egységek, kulcsszavak és "
#~ "összetevők bizonytalan megfeleltetése."
#~ msgid ""
#~ "Fields to search ignoring accents. Selecting this option can improve or "
#~ "degrade search quality depending on language"
#~ msgstr ""
#~ "Az ékezetek figyelmen kívül hagyásával keresendő mezők. Ennek az "
#~ "opciónak a kiválasztása javíthatja vagy ronthatja a keresés minőségét a "
#~ "nyelvtől függően"
#~ msgid ""
#~ "Fields to search for partial matches. (e.g. searching for 'Pie' will "
#~ "return 'pie' and 'piece' and 'soapie')"
#~ msgstr ""
#~ "Részleges egyezések keresésére szolgáló mezők. (pl. a 'Pie' keresése a "
#~ "'pie' és a 'piece' és a 'soapie' kifejezéseket adja vissza.)"
#~ msgid ""
#~ "Fields to search for beginning of word matches. (e.g. searching for 'sa' "
#~ "will return 'salad' and 'sandwich')"
#~ msgstr ""
#~ "Mezők a szó eleji egyezések kereséséhez. (pl. a 'sa' keresés a 'salad' és "
#~ "a 'sandwich' kifejezéseket adja vissza)"
#~ msgid ""
#~ "Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find "
#~ "'recipe'.) Note: this option will conflict with 'web' and 'raw' methods "
#~ "of search."
#~ msgstr ""
#~ "Mezők a \" bizonytalan\" kereséshez. (pl. a 'recpie' keresés megtalálja a "
#~ "'recipe' szót.) Megjegyzés: ez az opció ütközik a 'web' és a 'raw' "
#~ "keresési módszerekkel."
#~ msgid ""
#~ "Fields to full text search. Note: 'web', 'phrase', and 'raw' search "
#~ "methods only function with fulltext fields."
#~ msgstr ""
#~ "Mezők a teljes szöveges kereséshez. Megjegyzés: A 'web', 'phrase' és "
#~ "'raw' keresési módszerek csak teljes szöveges mezőkkel működnek."
#~ msgid "Search Method"
#~ msgstr "Keresési módszer"
#~ msgid "Fuzzy Lookups"
#~ msgstr "Bizonytalan keresések"
#~ msgid "Ignore Accent"
#~ msgstr "Ékezetek ignorálása"
#~ msgid "Partial Match"
#~ msgstr "Részleges találat"
#~ msgid "Starts With"
#~ msgstr "Ezzel kezdődik"
#~ msgid "Fuzzy Search"
#~ msgstr "Bizonytalan keresés"
#~ msgid "Full Text"
#~ msgstr "Teljes szöveg"
#~ msgid " is part of a recipe step and cannot be deleted"
#~ msgstr " egy recept része, ezért nem törölhető"
#~ msgid "Delete"
#~ msgstr "Törlés"
#~ msgid "Settings"
#~ msgstr "Beállítások"
#~ msgid "Email"
#~ msgstr "Email"
#~ msgid "Password"
#~ msgstr "Jelszó"
#~ msgid "Foods"
#~ msgstr "Ételek"
#~ msgid "Units"
#~ msgstr "Mértékegységek"
#~ msgid "Supermarket"
#~ msgstr "Szupermarket"
#~ msgid "Supermarket Category"
#~ msgstr "Szupermarket kategória"
#~ msgid "Automations"
#~ msgstr "Automatizációk"
#~ msgid "Files"
#~ msgstr "Fájlok"
#~ msgid "Batch Edit"
#~ msgstr "Csoportos szerkesztés"
#~ msgid "History"
#~ msgstr "Előzmények"
#~ msgid "Ingredient Editor"
#~ msgstr "Hozzávaló szerkesztő"
#, fuzzy
#~| msgid "Account Connections"
#~ msgid "Unit Conversions"
#~ msgstr "Fiókkapcsolatok"
#~ msgid "Create"
#~ msgstr "Létrehozás"
#~ msgid "External Recipes"
#~ msgstr "Külső receptek"
#~ msgid "Space Settings"
#~ msgstr "Tér beállítások"
#, fuzzy
#~| msgid "External Recipes"
#~ msgid "External Connectors"
#~ msgstr "Külső receptek"
#~ msgid "Admin"
#~ msgstr "Admin"
#~ msgid "Markdown Guide"
#~ msgstr "Markdown útmutató"
#~ msgid "GitHub"
#~ msgstr "GitHub"
#~ msgid "Translate Tandoor"
#~ msgstr "Tandoor fordítása"
#~ msgid "API Browser"
#~ msgstr "API böngésző"
#~ msgid "Log out"
#~ msgstr "Kijelentkezés"
#~ msgid "You are using the free version of Tandor"
#~ msgstr "Ön a Tandoor ingyenes verzióját használja"
#~ msgid "Upgrade Now"
#~ msgstr "Frissítés most"
#~ msgid "Batch edit Category"
#~ msgstr "Kategória csoportos szerkesztése"
#~ msgid "Batch edit Recipes"
#~ msgstr "Receptek csoportos szerkesztése"
#~ msgid "Add the specified keywords to all recipes containing a word"
#~ msgstr ""
#~ "A megadott kulcsszavak hozzáadása az összes olyan recepthez, amely "
#~ "tartalmaz egy szót"
#~ msgid "Sync"
#~ msgstr "Szinkronizálás"
#~ msgid "Manage watched Folders"
#~ msgstr "Megfigyelt mappák kezelése"
#~ msgid ""
#~ "On this Page you can manage all storage folder locations that should be "
#~ "monitored and synced."
#~ msgstr ""
#~ "Ezen az oldalon kezelheti az összes olyan tárhely mappát, amelyek "
#~ "figyelve és szinkronizálva lesznek."
#~ msgid "The path must be in the following format"
#~ msgstr "Az elérési útnak a következő formátumúnak kell lennie"
#~ msgid "Save"
#~ msgstr "Mentés"
#~ msgid "Manage External Storage"
#~ msgstr "Külső tárhely kezelése"
#~ msgid "Sync Now!"
#~ msgstr "Szinkronizálj most!"
#~ msgid "Show Recipes"
#~ msgstr "Receptek mutatása"
#~ msgid "Show Log"
#~ msgstr "Napló megjelenítése"
#~ msgid "Importing Recipes"
#~ msgstr "Receptek importálása"
#~ msgid ""
#~ "This can take a few minutes, depending on the number of recipes in sync, "
#~ "please wait."
#~ msgstr ""
#~ "Ez a szinkronizált receptek számától függően néhány percet vehet igénybe, "
#~ "kérjük, várjon."
#~ msgid "Recipe Books"
#~ msgstr "Receptkönyvek"
#~ msgid "Import new Recipe"
#~ msgstr "Új recept importálása"
#~ msgid "Edit Recipe"
#~ msgstr "Recept szerkesztése"
#, python-format
#~ msgid "Are you sure you want to delete the %(title)s: %(object)s "
#~ msgstr "Biztos, hogy törölni akarod a %(title)s: %(object)s "
#~ msgid "This cannot be undone!"
#~ msgstr "Ezt nem lehet visszafordítani!"
#~ msgid "Cascade"
#~ msgstr "Kaszkád"
#~ msgid "Cancel"
#~ msgstr "Mégsem"
#~ msgid "Edit"
#~ msgstr "Szerkesztés"
#~ msgid "View"
#~ msgstr "Megtekintés"
#~ msgid "Delete original file"
#~ msgstr "Eredeti fájl törlése"
#~ msgid "List"
#~ msgstr "Lista"
#~ msgid "Filter"
#~ msgstr "Szűrő"
#~ msgid "Import all"
#~ msgstr "Importáljon mindent"
#~ msgid "New"
#~ msgstr "Új"
#~ msgid "previous"
#~ msgstr "előző"
#~ msgid "next"
#~ msgstr "következő"
#~ msgid "View Log"
#~ msgstr "Napló megtekintése"
#~ msgid "Cook Log"
#~ msgstr "Főzési napló"
#~ msgid "Import"
#~ msgstr "Importálás"
#~ msgid "Security Warning"
#~ msgstr "Biztonsági figyelmeztetés"
#~ msgid ""
#~ "\n"
#~ " The Password and Token field are stored as plain text"
#~ "b> inside the database.\n"
#~ " This is necessary because they are needed to make API requests, "
#~ "but it also increases the risk of\n"
#~ " someone stealing it.
\n"
#~ " To limit the possible damage tokens or accounts with limited "
#~ "access can be used.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " A jelszó és token mező sima szövegként tárolódik az "
#~ "adatbázisban.\n"
#~ " Erre azért van szükség, mert az API-kérésekhez szükségesek, de "
#~ "növeli annak kockázatát is\n"
#~ " hogy valaki ellopja őket.
\n"
#~ " A lehetséges károk korlátozására tokenek vagy korlátozott "
#~ "hozzáféréssel rendelkező fiókok használhatók.\n"
#~ " "
#~ msgid "You are currently offline!"
#~ msgstr "Jelenleg offline vagy!"
#~ msgid ""
#~ "The recipes listed below are available for offline viewing because you "
#~ "have recently viewed them. Keep in mind that data might be outdated."
#~ msgstr ""
#~ "Az alább felsorolt receptek offline megtekinthetők, mivel Ön nemrégiben "
#~ "már megtekintette őket. Ne feledje, hogy az adatok elavultak lehetnek."
#, fuzzy
#~| msgid "Ingredient Editor"
#~ msgid "Property Editor"
#~ msgstr "Hozzávaló szerkesztő"
#~ msgid "Comments"
#~ msgstr "Megjegyzések"
#~ msgid "by"
#~ msgstr "által"
#~ msgid "Comment"
#~ msgstr "Megjegyzés"
#~ msgid ""
#~ "There are many options to configure the search depending on your personal "
#~ "preferences."
#~ msgstr ""
#~ "Számos lehetőség van a keresés konfigurálására a te személyes "
#~ "preferenciáidtól függően."
#~ msgid ""
#~ "Usually you do not need to configure any of them and can just "
#~ "stick with either the default or one of the following presets."
#~ msgstr ""
#~ "Általában nem kell egyiket sem konfigurálnod, és maradhatsz az "
#~ "alapértelmezett vagy az alábbi beállítások valamelyikénél."
#~ msgid ""
#~ "If you do want to configure the search you can read about the different "
#~ "options here."
#~ msgstr ""
#~ "Ha mégis konfigurálni szeretné a keresést, a különböző lehetőségekről itt olvashat."
#~ msgid "Fuzzy"
#~ msgstr "Bizonytalan"
#~ msgid ""
#~ "Find what you need even if your search or the recipe contains typos. "
#~ "Might return more results than needed to make sure you find what you are "
#~ "looking for."
#~ msgstr ""
#~ "Akkor is megtalálja, amire szüksége van, ha a keresés vagy a recept "
#~ "elírásokat tartalmaz. Lehet, hogy a szükségesnél több találatot ad "
#~ "vissza, hogy biztosan megtalálja, amit keres."
#~ msgid "This is the default behavior"
#~ msgstr "Ez az alapértelmezett viselkedés"
#~ msgid "Apply"
#~ msgstr "Alkalmazás"
#~ msgid "Precise"
#~ msgstr "Pontos"
#~ msgid ""
#~ "Allows fine control over search results but might not return results if "
#~ "too many spelling mistakes are made."
#~ msgstr ""
#~ "Lehetővé teszi a keresési eredmények finom ellenőrzését, de előfordulhat, "
#~ "hogy nem ad vissza eredményeket, ha túl sok helyesírási hiba van."
#~ msgid "Perfect for large Databases"
#~ msgstr "Tökéletes nagy adatbázisokhoz"
#~ msgid "Social"
#~ msgstr "Social"
#, fuzzy
#~| msgid "Members"
#~ msgid "Space Management"
#~ msgstr "Tagok"
#~ msgid "Space:"
#~ msgstr "Tér:"
#~ msgid "Manage Subscription"
#~ msgstr "Feliratkozás kezelése"
#~ msgid "Leave Space"
#~ msgstr "Kilépés a Térből"
#~ msgid "URL Import"
#~ msgstr "URL importálása"
#~ msgid ""
#~ "Rating a recipe should have or greater. [0 - 5] Negative value filters "
#~ "rating less than."
#~ msgstr ""
#~ "Egy recept minimális értékelése (0-5). A negatív értékek a maximális "
#~ "értékelés szerint szűrnek."
#~ msgid ""
#~ "Returns the shopping list entry with a primary key of id. Multiple "
#~ "values allowed."
#~ msgstr ""
#~ "Visszaadja az id elsődleges kulccsal rendelkező bevásárlólista-"
#~ "bejegyzést. Több érték megengedett."
#, fuzzy
#~| msgid ""
#~| "Filter shopping list entries on checked. [true, false, both, recent"
#~| "b>]
- recent includes unchecked items and recently completed items."
#~ msgid ""
#~ "Filter shopping list entries on checked. [true, false, both, recent"
#~ "b>]
- recent includes unchecked items and recently "
#~ "completed items."
#~ msgstr ""
#~ "A bevásárlólista bejegyzéseinek szűrése a bejelölt oldalon. [true, false, "
#~ "mindkettő, legutóbbi]
– a legutóbbi a nem bejelölt és a nemrég "
#~ "befejezett elemeket tartalmazza."
#~ msgid ""
#~ "Returns the shopping list entries sorted by supermarket category order."
#~ msgstr ""
#~ "Visszaadja a bevásárlólista bejegyzéseit szupermarket kategóriák szerinti "
#~ "sorrendben."
#, python-format
#~ msgid "Batch edit done. %(count)d recipe was updated."
#~ msgid_plural "Batch edit done. %(count)d Recipes where updated."
#~ msgstr[0] "Batch szerkesztés kész. A %(count)d recept frissült."
#~ msgstr[1] "Batch szerkesztés kész. A %(count)d recept frissült."
#~ msgid "Monitor"
#~ msgstr "Figyelő"
#~ msgid "Storage Backend"
#~ msgstr "Tárolási háttértár"
#~ msgid ""
#~ "Could not delete this storage backend as it is used in at least one "
#~ "monitor."
#~ msgstr ""
#~ "Nem sikerült törölni ezt a tároló háttértárat, mivel legalább egy monitor "
#~ "használja."
#, fuzzy
#~| msgid "Storage Backend"
#~ msgid "Connectors Config Backend"
#~ msgstr "Tárolási háttértár"
#~ msgid "Invite Link"
#~ msgstr "Meghívó link"
#, fuzzy
#~| msgid "Members"
#~ msgid "Space Membership"
#~ msgstr "Tagok"
#~ msgid "You cannot edit this storage!"
#~ msgstr "Ezt a tárolót nem lehet szerkeszteni!"
#~ msgid "Storage saved!"
#~ msgstr "Tároló mentve!"
#~ msgid "There was an error updating this storage backend!"
#~ msgstr "Hiba történt a tárolási háttértár frissítésénél!"
#, fuzzy
#~| msgid "Changes saved!"
#~ msgid "Config saved!"
#~ msgstr "Változások mentve!"
#~ msgid "Changes saved!"
#~ msgstr "Változások mentve!"
#~ msgid "Error saving changes!"
#~ msgstr "Hiba a módosítások mentése közben!"
#~ msgid "Import Log"
#~ msgstr "Import napló"
#~ msgid "Discovery"
#~ msgstr "Felfedezés"
#~ msgid "Shopping List"
#~ msgstr "Bevásárlólista"
#, fuzzy
#~| msgid "Storage Backend"
#~ msgid "Connector Config Backend"
#~ msgstr "Tárolási háttértár"
#~ msgid "Invite Links"
#~ msgstr "Meghívó linkek"
#~ msgid "Supermarkets"
#~ msgstr "Szupermarketek"
#~ msgid "Shopping Categories"
#~ msgstr "Bevásárlási kategóriák"
#~ msgid "Custom Filters"
#~ msgstr "Egyedi szűrők"
#~ msgid "Steps"
#~ msgstr "Lépések"
#, fuzzy
#~| msgid "This feature is not available in the demo version!"
#~ msgid "This feature is not enabled by the server admin!"
#~ msgstr "Ez a funkció nem érhető el a demó verzióban!"
#~ msgid "Imported new recipe!"
#~ msgstr "Új recept importálva!"
#~ msgid "There was an error importing this recipe!"
#~ msgstr "Hiba történt a recept importálásakor!"
#~ msgid "You do not have the required permissions to perform this action!"
#~ msgstr ""
#~ "Nem rendelkezik a művelet végrehajtásához szükséges jogosultságokkal!"
#~ msgid "Comment saved!"
#~ msgstr "Megjegyzés mentve!"
#~ msgid "You must select at least one field to search!"
#~ msgstr "Legalább egy mezőt ki kell választania a kereséshez!"
#~ msgid ""
#~ "To use this search method you must select at least one full text search "
#~ "field!"
#~ msgstr ""
#~ "Ennek a keresési módszernek a használatához legalább egy teljes szöveges "
#~ "keresési mezőt ki kell választania!"
#~ msgid "Fuzzy search is not compatible with this search method!"
#~ msgstr "A bizonytalan keresés nem kompatibilis ezzel a keresési módszerrel!"
#~ msgid "Malformed Invite Link supplied!"
#~ msgstr "Hibás meghívó linket küldtek!"
#~ msgid "Successfully joined space."
#~ msgstr "Sikeresen csatlakozott az térhez."
#~ msgid "Invite Link not valid or already used!"
#~ msgstr "A meghívó link nem érvényes vagy már felhasználásra került!"
#~ msgid "Default unit"
#~ msgstr "Alapértelmezett mértékegység"
#~ msgid "Use KJ"
#~ msgstr "KJ használata"
#~ msgid "Theme"
#~ msgstr "Kinézet"
#~ msgid "Navbar color"
#~ msgstr "Navigációs sáv színe"
#~ msgid "Sticky navbar"
#~ msgstr "Ragadós navigációs sáv"
#~ msgid "Default page"
#~ msgstr "Alapértelmezett oldal"
#~ msgid "Plan sharing"
#~ msgstr "Terv megosztása"
#~ msgid "Ingredient decimal places"
#~ msgstr "Összetevők tizedesjegyei"
#~ msgid "Shopping list auto sync period"
#~ msgstr "Bevásárlólista automatikus szinkronizálásának periódusa"
#~ msgid "Left-handed mode"
#~ msgstr "Balkezes üzemmód"
#~ msgid ""
#~ "Color of the top navigation bar. Not all colors work with all themes, "
#~ "just try them out!"
#~ msgstr ""
#~ "A felső navigációs sáv színe. Nem minden szín működik minden témával. "
#~ "Próbáld ki őket!"
#~ msgid ""
#~ "Default Unit to be used when inserting a new ingredient into a recipe."
#~ msgstr ""
#~ "Az alapértelmezett mértékegység, új hozzávaló receptbe való "
#~ "beillesztésekor."
#~ msgid ""
#~ "Enables support for fractions in ingredient amounts (e.g. convert "
#~ "decimals to fractions automatically)"
#~ msgstr ""
#~ "Lehetővé teszi az összetevők mennyiségében a törtrészek használatát (pl. "
#~ "A tizedesjegyek automatikus törtrészekké alakítása)"
#~ msgid "Display nutritional energy amounts in joules instead of calories"
#~ msgstr ""
#~ "A tápanyag energiamennyiségek kalória helyett joule-ban történő "
#~ "megjelenítése"
#~ msgid ""
#~ "Users with whom newly created meal plans should be shared by default."
#~ msgstr ""
#~ "Azok a felhasználók, akikkel az újonnan létrehozott menüterveket "
#~ "alapértelmezés szerint meg kell osztani."
#~ msgid "Users with whom to share shopping lists."
#~ msgstr "Felhasználók, akikkel megosztja a bevásárlólistákat."
#~ msgid "Number of decimals to round ingredients."
#~ msgstr "A kerekítendő összetevők tizedesjegyeinek száma."
#~ msgid ""
#~ "If you want to be able to create and see comments underneath recipes."
#~ msgstr ""
#~ "Ha azt szeretné, hogy hozzászólásokat tudjon létrehozni és látni a "
#~ "receptek alatt."
#~ msgid ""
#~ "Setting to 0 will disable auto sync. When viewing a shopping list the "
#~ "list is updated every set seconds to sync changes someone else might have "
#~ "made. Useful when shopping with multiple people but might use a little "
#~ "bit of mobile data. If lower than instance limit it is reset when saving."
#~ msgstr ""
#~ "A 0-ra állítás kikapcsolja az automatikus szinkronizálást. A "
#~ "bevásárlólista megtekintésekor a lista minden beállított másodpercben "
#~ "frissül, hogy szinkronizálja a más által esetleg elvégzett módosításokat. "
#~ "Hasznos, ha több emberrel együtt vásárol, de egy kicsit több mobiladatot "
#~ "használhat. Ha alacsonyabb, mint a lehetséges határérték, akkor a "
#~ "mentéskor visszaáll."
#~ msgid "Makes the navbar stick to the top of the page."
#~ msgstr "A navigációs sávot az oldal tetejére rögzíti."
#~ msgid "Automatically add meal plan ingredients to shopping list."
#~ msgstr "Automatikusan hozzáadja a menüterv hozzávalóit a bevásárlólistához."
#~ msgid "Exclude ingredients that are on hand."
#~ msgstr "Mellőzze a kéznél lévő összetevőket."
#~ msgid "Will optimize the UI for use with your left hand."
#~ msgstr "Optimalizálja a felületet, bal kézzel történő használatra."
#~ msgid "You must provide at least a recipe or a title."
#~ msgstr "Legalább egy receptet vagy címet kell megadnia."
#~ msgid "You can list default users to share recipes with in the settings."
#~ msgstr ""
#~ "A beállításokban megadhatja a receptek megosztására szolgáló "
#~ "alapértelmezett felhasználókat."
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "A mező formázásához használhatja a markdown formátumot. Lásd a dokumentációt itt"
#~ msgid ""
#~ "Users will see all items you add to your shopping list. They must add "
#~ "you to see items on their list."
#~ msgstr ""
#~ "A felhasználók látni fogják a bevásárlólistára felvett összes terméket. "
#~ "Ahhoz, hogy láthassák a saját listájukon szereplő tételeket, hozzá kell "
#~ "adniuk téged."
#~ msgid ""
#~ "When adding a meal plan to the shopping list (manually or automatically), "
#~ "include all related recipes."
#~ msgstr ""
#~ "Amikor menütervet ad hozzá a bevásárlólistához (kézzel vagy "
#~ "automatikusan), vegye fel az összes kapcsolódó receptet."
#~ msgid ""
#~ "When adding a meal plan to the shopping list (manually or automatically), "
#~ "exclude ingredients that are on hand."
#~ msgstr ""
#~ "Amikor menütervet ad hozzá a bevásárlólistához (kézzel vagy "
#~ "automatikusan), zárja ki a kéznél lévő összetevőket."
#~ msgid "Default number of hours to delay a shopping list entry."
#~ msgstr "A bevásárlólista bejegyzés késleltetésének alapértelmezett ideje."
#~ msgid "Filter shopping list to only include supermarket categories."
#~ msgstr ""
#~ "Szűrje a bevásárlólistát úgy, hogy csak a szupermarket kategóriákat "
#~ "tartalmazza."
#~ msgid "Days of recent shopping list entries to display."
#~ msgstr "A legutóbbi bevásárlólista bejegyzések megjelenítendő napjai."
#~ msgid "Mark food 'On Hand' when checked off shopping list."
#~ msgstr ""
#~ "Jelölje meg a \" Kéznél van\" jelölést, ha a bevásárlólistáról kipipálta "
#~ "az élelmiszert."
#~ msgid "Delimiter to use for CSV exports."
#~ msgstr "A CSV exportáláshoz használandó elválasztójel."
#~ msgid "Prefix to add when copying list to the clipboard."
#~ msgstr "A lista vágólapra másolásakor hozzáadandó előtag."
#~ msgid "Share Shopping List"
#~ msgstr "Bevásárlólista megosztása"
#~ msgid "Autosync"
#~ msgstr "Automatikus szinkronizálás"
#~ msgid "Auto Add Meal Plan"
#~ msgstr "Menüterv automatikus hozzáadása"
#~ msgid "Exclude On Hand"
#~ msgstr "Kéznél levő kihagyása"
#~ msgid "Include Related"
#~ msgstr "Tartalmazza a kapcsolódókat"
#~ msgid "Default Delay Hours"
#~ msgstr "Alapértelmezett késleltetési órák"
#~ msgid "Filter to Supermarket"
#~ msgstr "Szűrő a szupermarkethez"
#~ msgid "Recent Days"
#~ msgstr "Legutóbbi napok"
#~ msgid "CSV Delimiter"
#~ msgstr "CSV elválasztó"
#~ msgid "List Prefix"
#~ msgstr "Lista előtagja"
#~ msgid "Auto On Hand"
#~ msgstr "Automatikus Kéznél lévő"
#~ msgid "Reset Food Inheritance"
#~ msgstr "Élelmiszer-öröklés visszaállítása"
#~ msgid "Reset all food to inherit the fields configured."
#~ msgstr ""
#~ "Állítsa vissza az összes ételt, hogy örökölje a konfigurált mezőket."
#~ msgid "Fields on food that should be inherited by default."
#~ msgstr ""
#~ "Az élelmiszerek azon mezői, amelyeket alapértelmezés szerint örökölni "
#~ "kell."
#~ msgid "Show recipe counts on search filters"
#~ msgstr "A receptek számának megjelenítése a keresési szűrőkön"
#~ msgid "Use the plural form for units and food inside this space."
#~ msgstr ""
#~ "Használja a többes számot az egységek és az ételek esetében ezen a helyen."
#~ msgid "One of queryset or hash_key must be provided"
#~ msgstr "A queryset vagy a hash_key valamelyikét meg kell adni"
#~ msgid "Profile"
#~ msgstr "Profil"
#~ msgid "Recipe Book"
#~ msgstr "Receptkönyv"
#~ msgid "Bookmarks"
#~ msgstr "Könyvjelzők"
#~ msgid "Ingredients"
#~ msgstr "Hozzávalók"
#~ msgid "Show recent recipes"
#~ msgstr "Legutóbbi receptek megjelenítése"
#~ msgid "Search style"
#~ msgstr "Keresés stílusa"
#~ msgid "Show recently viewed recipes on search page."
#~ msgstr "Nemrég megtekintett receptek megjelenítése a keresési oldalon."
#~ msgid "Small"
#~ msgstr "Kicsi"
#~ msgid "Large"
#~ msgstr "Nagy"
#~ msgid "Edit Ingredients"
#~ msgstr "Összetevők szerkesztése"
#~ msgid ""
#~ "\n"
#~ " The following form can be used if, accidentally, two (or more) "
#~ "units or ingredients where created that should be\n"
#~ " the same.\n"
#~ " It merges two units or ingredients and updates all recipes using "
#~ "them.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " A következő űrlapot akkor lehet használni, ha véletlenül két "
#~ "(vagy több) olyan egység vagy összetevő jött létre,\n"
#~ " amelyeknek azonosnak kellene lennie.\n"
#~ " Összevon két egységet vagy összetevőt, és frissíti az ezeket "
#~ "használó összes receptet.\n"
#~ " "
#~ msgid "Are you sure that you want to merge these two units?"
#~ msgstr "Biztos, hogy össze akarja vonni ezt a két egységet?"
#~ msgid "Are you sure that you want to merge these two ingredients?"
#~ msgstr "Biztos vagy benne, hogy ezt a két összetevőt szeretnéd egyesíteni?"
#~ msgid "Import Recipes"
#~ msgstr "Receptek importálása"
#~ msgid "Close"
#~ msgstr "Bezár"
#~ msgid "Open Recipe"
#~ msgstr "Recept megnyitása"
#~ msgid "Meal Plan View"
#~ msgstr "Étkezési terv nézet"
#~ msgid "Created by"
#~ msgstr "Létrehozta"
#~ msgid "Shared with"
#~ msgstr "Megosztva"
#~ msgid "Never cooked before."
#~ msgstr "Soha nem főzött még."
#~ msgid "Other meals on this day"
#~ msgstr "Egyéb ételek ezen a napon"
#~ msgid "Recipe Image"
#~ msgstr "Recept kép"
#~ msgid "Preparation time ca."
#~ msgstr "Elkészítési idő kb."
#~ msgid "Waiting time ca."
#~ msgstr "Várakozási idő kb."
#~ msgid "External"
#~ msgstr "Külső"
#~ msgid "Log Cooking"
#~ msgstr "Főzés naplózása"
#~ msgid "Account"
#~ msgstr "Fiók"
#~ msgid "Preferences"
#~ msgstr "Beállítások"
#~ msgid "API-Settings"
#~ msgstr "API beállítások"
#~ msgid "Search-Settings"
#~ msgstr "Keresési beállítások"
#~ msgid "Shopping-Settings"
#~ msgstr "Bevásárlási beállítások"
#~ msgid "Name Settings"
#~ msgstr "Név beállításai"
#~ msgid "Account Settings"
#~ msgstr "Fiók beállítások"
#~ msgid "Emails"
#~ msgstr "E-mailek"
#~ msgid "Language"
#~ msgstr "Nyelv"
#~ msgid "Style"
#~ msgstr "Kinézet"
#~ msgid "API Token"
#~ msgstr "API Token"
#~ msgid ""
#~ "You can use both basic authentication and token based authentication to "
#~ "access the REST API."
#~ msgstr ""
#~ "A REST API-hoz való hozzáféréshez alapszintű és tokenalapú hitelesítést "
#~ "is használhat."
#~ msgid ""
#~ "Use the token as an Authorization header prefixed by the word token as "
#~ "shown in the following examples:"
#~ msgstr ""
#~ "Használja a tokent Engedélyezés fejlécként a token szó előtaggal, ahogy a "
#~ "következő példákban látható:"
#~ msgid "or"
#~ msgstr "vagy"
#~ msgid "Shopping Settings"
#~ msgstr "Bevásárlási beállítások"
#~ msgid "Stats"
#~ msgstr "Statisztikák"
#~ msgid "Statistics"
#~ msgstr "Statisztikák"
#~ msgid "Number of objects"
#~ msgstr "Objektumok száma"
#~ msgid "Recipe Imports"
#~ msgstr "Recept importálás"
#~ msgid "Objects stats"
#~ msgstr "Objektumok statisztikái"
#~ msgid "Recipes without Keywords"
#~ msgstr "Receptek kulcsszavak nélkül"
#~ msgid "Internal Recipes"
#~ msgstr "Belső receptek"
#~ msgid "Show Links"
#~ msgstr "Linkek megjelenítése"
#~ msgid "A user is required"
#~ msgstr "Egy felhasználó szükséges"
#~ msgid "Invite User"
#~ msgstr "Felhasználó meghívása"
#~ msgid "User"
#~ msgstr "Felhasználó"
#~ msgid "Groups"
#~ msgstr "Csoportok"
#~ msgid "admin"
#~ msgstr "admin"
#~ msgid "user"
#~ msgstr "felhasználó"
#~ msgid "guest"
#~ msgstr "vendég"
#~ msgid "remove"
#~ msgstr "eltávolítás"
#~ msgid "Update"
#~ msgstr "Frissítés"
#~ msgid "You cannot edit yourself."
#~ msgstr "Nem szerkesztheted magad."
#~ msgid "There are no members in your space yet!"
#~ msgstr "Még nincsenek tagok a térben!"
#~ msgid "Invite link successfully send to user."
#~ msgstr "A meghívó linket sikeresen elküldtük a felhasználónak."
#~ msgid ""
#~ "You have send to many emails, please share the link manually or wait a "
#~ "few hours."
#~ msgstr ""
#~ "Túl sok e-mailt küldött, kérjük, ossza meg a linket kézzel, vagy várjon "
#~ "néhány órát."
#~ msgid "Email could not be sent to user. Please share the link manually."
#~ msgstr ""
#~ "Az e-mailt nem sikerült elküldeni a felhasználónak. Kérjük, ossza meg a "
#~ "linket kézzel."
#~ msgid ""
#~ "You are already member of a space and therefore cannot join this one."
#~ msgstr "Ön már tagja egy térnek, ezért nem csatlakozhat ehhez a térhez."
#~ msgid "You must supply a recipe or mealplan"
#~ msgstr "Receptet vagy étkezési tervet kell megadnia"
#~ msgid "Text"
#~ msgstr "Szöveg"
#~ msgid "Time"
#~ msgstr "Idő"
#~ msgid "File"
#~ msgstr "Fájl"
#~ msgid "Try the new shopping list"
#~ msgstr "Próbálja ki az új bevásárlólistát"
#~ msgid "Log Recipe Cooking"
#~ msgstr "Log Recipe Cooking"
#~ msgid "All fields are optional and can be left empty."
#~ msgstr "Minden mező opcionális és üresen hagyható."
#~ msgid "Rating"
#~ msgstr "Értékelés"
#~ msgid "Search Recipe"
#~ msgstr "Recept keresése"
#~ msgid "Shopping Recipes"
#~ msgstr "Bevásárlás receptek"
#~ msgid "No recipes selected"
#~ msgstr "Nincs kiválasztott recept"
#~ msgid "Entry Mode"
#~ msgstr "Beviteli mód"
#~ msgid "Add Entry"
#~ msgstr "Bejegyzés hozzáadása"
#~ msgid "Amount"
#~ msgstr "Összeg"
#~ msgid "Select Unit"
#~ msgstr "Egység kiválasztása"
#~ msgid "Select"
#~ msgstr "Válassz"
#~ msgid "Select Food"
#~ msgstr "Válasszon ételt"
#~ msgid "Select Supermarket"
#~ msgstr "Válasszon szupermarketet"
#~ msgid "Select User"
#~ msgstr "Válasszon felhasználót"
#~ msgid "Finished"
#~ msgstr "Kész"
#~ msgid "You are offline, shopping list might not syncronize."
#~ msgstr "Offline vagy, a bevásárlólista nem szinkronizálódik."
#~ msgid "Copy/Export"
#~ msgstr "Másolás/Export"
#~ msgid "Drag me to your bookmarks to import recipes from anywhere"
#~ msgstr "Húzzon a könyvjelzők közé, hogy bárhonnan importálhasson recepteket"
#~ msgid "Bookmark Me!"
#~ msgstr "Könyvjelzőbe!"
#~ msgid "URL"
#~ msgstr "URL"
#~ msgid "App"
#~ msgstr "Applikáció"
#~ msgid "Enter website URL"
#~ msgstr "Adja meg a weboldal URL címét"
#~ msgid "Select recipe files to import or drop them here..."
#~ msgstr "Válassza ki az importálandó receptfájlokat, vagy húzza őket ide..."
#~ msgid "Paste json or html source here to load recipe."
#~ msgstr "A recept betöltéséhez illessze be ide a json vagy html forrást."
#~ msgid "Preview Recipe Data"
#~ msgstr "Receptadatok előnézete"
#~ msgid ""
#~ "Drag recipe attributes from the right into the appropriate box below."
#~ msgstr ""
#~ "Húzza a recept attribútumait a jobb oldalról az alábbi megfelelő mezőbe."
#~ msgid "Clear Contents"
#~ msgstr "Tartalom törlése"
#~ msgid "Text dragged here will be appended to the name."
#~ msgstr "Az ide húzott szöveg a névhez lesz csatolva."
#~ msgid "Text dragged here will be appended to the description."
#~ msgstr "Az ide húzott szöveg hozzá lesz csatolva a leíráshoz."
#~ msgid "Keywords dragged here will be appended to current list"
#~ msgstr ""
#~ "Az ide húzott kulcsszavak hozzá lesznek csatolva az aktuális listához"
#~ msgid "Image"
#~ msgstr "Kép"
#~ msgid "Prep Time"
#~ msgstr "Előkészítési idő"
#~ msgid "Cook Time"
#~ msgstr "Főzési idő"
#~ msgid "Ingredients dragged here will be appended to current list."
#~ msgstr ""
#~ "Az ide húzott összetevők hozzá lesznek csatolva az aktuális listához."
#~ msgid ""
#~ "Recipe instructions dragged here will be appended to current instructions."
#~ msgstr ""
#~ "Az ide húzott receptutasítások a jelenlegi utasításokhoz lesznek csatolva."
#~ msgid "Discovered Attributes"
#~ msgstr "Felfedezett attribútumok"
#~ msgid ""
#~ "Drag recipe attributes from below into the appropriate box on the left. "
#~ "Click any node to display its full properties."
#~ msgstr ""
#~ "Húzza a recept attribútumait alulról a bal oldali megfelelő mezőbe. "
#~ "Kattintson bármelyik csomópontra a teljes tulajdonságainak "
#~ "megjelenítéséhez."
#~ msgid "Show Blank Field"
#~ msgstr "Üres mező megjelenítése"
#~ msgid "Blank Field"
#~ msgstr "Üres mező"
#~ msgid "Items dragged to Blank Field will be appended."
#~ msgstr "Az üres mezőre húzott elemek hozzá lesznek csatolva."
#~ msgid "Delete Text"
#~ msgstr "Szöveg törlése"
#~ msgid "Delete image"
#~ msgstr "Kép törlése"
#~ msgid "Recipe Name"
#~ msgstr "Recept neve"
#~ msgid "Recipe Description"
#~ msgstr "Recept leírása"
#~ msgid "Select one"
#~ msgstr "Válassz egyet"
#~ msgid "Note"
#~ msgstr "Megjegyzés"
#~ msgid "Add Keyword"
#~ msgstr "Kulcsszavak hozzáadása"
#~ msgid "All Keywords"
#~ msgstr "Összes kulcsszó"
#~ msgid "Import all keywords, not only the ones already existing."
#~ msgstr "Importálja az összes kulcsszót, nem csak a már meglévőket."
#~ msgid "Information"
#~ msgstr "Információ"
#~ msgid ""
#~ " Only websites containing ld+json or microdata information can currently\n"
#~ " be imported. Most big recipe pages "
#~ "support this. If you site cannot be imported but\n"
#~ " you think\n"
#~ " it probably has some kind of "
#~ "structured data feel free to post an example in the\n"
#~ " github issues."
#~ msgstr ""
#~ " Jelenleg csak az ld+json vagy microdata információkat tartalmazó "
#~ "weboldalakat lehet\n"
#~ " importálni. A legtöbb nagy "
#~ "receptoldal támogatja ezt. Ha az oldal nem importálható, de\n"
#~ " úgy gondolod, hogy\n"
#~ " valószínűleg valamilyen strukturált "
#~ "adatot tartalmaz, bátran küldj egy példát a\n"
#~ " github issues-re."
#~ msgid "Google ld+json Info"
#~ msgstr "Google ld+json információ"
#~ msgid "GitHub Issues"
#~ msgstr "GitHub Issues"
#~ msgid "Recipe Markup Specification"
#~ msgstr "Recept markup specifikáció"
#~ msgid ""
#~ "If recipe should have all (AND=false) or any (OR=true) of the "
#~ "provided keywords."
#~ msgstr ""
#~ "Ha a receptnek a megadott kulcsszavak mindegyikét (AND=false) vagy "
#~ "bármelyikét (OR=true) tartalmaznia kell."
#~ msgid ""
#~ "If recipe should have all (AND=false) or any (OR=true) of the "
#~ "provided foods."
#~ msgstr ""
#~ "Ha a receptnek tartalmaznia kell az összes (AND=false) vagy bármelyik "
#~ "(OR=true) megadott élelmiszert."
#~ msgid ""
#~ "If recipe should be in all (AND=false) or any (OR=true) of the "
#~ "provided books."
#~ msgstr ""
#~ "Ha a recept az összes (AND=false) vagy bármelyik (OR=true) "
#~ "megadott könyvben szerepel."
#~ msgid "The requested site provided malformed data and cannot be read."
#~ msgstr ""
#~ "A kért webhely rosszul formázott adatokat szolgáltatott, és nem lehet "
#~ "beolvasni."
#~ msgid "The requested page could not be found."
#~ msgstr "A kért oldal nem található."
#~ msgid ""
#~ "The requested site does not provide any recognized data format to import "
#~ "the recipe from."
#~ msgstr ""
#~ "A kért webhely nem biztosít semmilyen elismert adatformátumot a recept "
#~ "importálásához."
#~ msgid "I couldn't find anything to do."
#~ msgstr "Nem találtam semmi tennivalót."
#~ msgid "Exporting is not implemented for this provider"
#~ msgstr "Az exportálás nincs megvalósítva ennél a szolgáltatónál"
#~ msgid "Shopping Lists"
#~ msgstr "Bevásárlólisták"
#~ msgid "Old Unit"
#~ msgstr "Régi Mértékegység"
#~ msgid "New Food"
#~ msgstr "Új Étel"
#~ msgid "Old Food"
#~ msgstr "Régi Étel"
================================================
FILE: cookbook/locale/hy/LC_MESSAGES/django.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
# Translators:
# H K , 2021
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-22 20:15+0200\n"
"PO-Revision-Date: 2023-01-08 17:55+0000\n"
"Last-Translator: Joachim Weber \n"
"Language-Team: Armenian \n"
"Language: hy\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Weblate 4.15\n"
#: .\cookbook\forms.py:50
msgid "Default"
msgstr "Լռելյայն"
#: .\cookbook\forms.py:77
msgid ""
"To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything."
msgstr ""
#: .\cookbook\forms.py:108
msgid "Maximum number of users for this space reached."
msgstr ""
#: .\cookbook\forms.py:114
msgid "Email address already taken!"
msgstr ""
#: .\cookbook\forms.py:121
msgid ""
"An email address is not required but if present the invite link will be sent "
"to the user."
msgstr ""
#: .\cookbook\forms.py:133
msgid "Name already taken."
msgstr ""
#: .\cookbook\forms.py:144 .\cookbook\forms.py:158
msgid "Accept Terms and Privacy"
msgstr ""
#: .\cookbook\helper\AllAuthCustomAdapter.py:41
msgid ""
"In order to prevent spam, the requested email was not send. Please wait a "
"few minutes and try again."
msgstr ""
#: .\cookbook\helper\permission_helper.py:165
#: .\cookbook\helper\permission_helper.py:186 .\cookbook\views\views.py:138
msgid "You are not logged in and therefore cannot view this page!"
msgstr "Դուք մուտք չեք գործել, հետևաբար չեք կարող տեսնել այս էջը։"
#: .\cookbook\helper\permission_helper.py:168
#: .\cookbook\helper\permission_helper.py:173
#: .\cookbook\helper\permission_helper.py:198
#: .\cookbook\helper\permission_helper.py:265
#: .\cookbook\helper\permission_helper.py:279
#: .\cookbook\helper\permission_helper.py:290
#: .\cookbook\helper\permission_helper.py:301
#: .\cookbook\helper\permission_helper.py:317
#: .\cookbook\helper\permission_helper.py:343
#: .\cookbook\helper\permission_helper.py:359
msgid "You do not have the required permissions to view this page!"
msgstr "Դուք չունեք անհրաժեշտ թույլտվություն այս էջը դիտելու համար։"
#: .\cookbook\helper\permission_helper.py:191
#: .\cookbook\helper\permission_helper.py:214
#: .\cookbook\helper\permission_helper.py:236
#: .\cookbook\helper\permission_helper.py:251
msgid "You cannot interact with this object as it is not owned by you!"
msgstr "Դուք չեք կարող փոփոխել այս օբյեկտը, որովհետև այն չի պատկանում ձեզ։"
#: .\cookbook\helper\permission_helper.py:420
msgid "You have reached the maximum number of recipes for your space."
msgstr ""
#: .\cookbook\helper\permission_helper.py:432
msgid "You have more users than allowed in your space."
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:319
msgid "reverse rotation"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:320
msgid "careful rotation"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:321
msgid "knead"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:322
msgid "thicken"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:323
msgid "warm up"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:324
msgid "ferment"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:325
#, fuzzy
#| msgid "Last cooked"
msgid "slow cook"
msgstr "Վերջին պատրաստումը"
#: .\cookbook\helper\recipe_url_import.py:326
msgid "egg boiler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:327
msgid "kettle"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:328
msgid "blend"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:329
msgid "pre-clean"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:330
msgid "high temperature"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:331
msgid "rice cooker"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:332
msgid "caramelize"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:333
msgid "peeler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:334
msgid "slicer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:335
msgid "grater"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:336
msgid "spiralizer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:337
msgid "sous-vide"
msgstr ""
#: .\cookbook\helper\shopping_helper.py:150
msgid "You must supply a servings size"
msgstr ""
#: .\cookbook\helper\template_helper.py:97
#: .\cookbook\helper\template_helper.py:99
#: .\cookbook\helper\template_helper.py:101
msgid "Could not parse template code."
msgstr ""
#: .\cookbook\integration\copymethat.py:44
#: .\cookbook\integration\melarecipes.py:37
msgid "Favorite"
msgstr ""
#: .\cookbook\integration\copymethat.py:50
msgid "I made this"
msgstr ""
#: .\cookbook\integration\integration.py:238
msgid ""
"Importer expected a .zip file. Did you choose the correct importer type for "
"your data ?"
msgstr ""
"Ներմուծողն ակնկալում էր .zip ֆայլ։ Արդյո՞ք ձեր ֆայլին համապատասխանող ճիշտ "
"տեսակի ներմուծող եք ընտրել։"
#: .\cookbook\integration\integration.py:241
msgid ""
"An unexpected error occurred during the import. Please make sure you have "
"uploaded a valid file."
msgstr ""
#: .\cookbook\integration\integration.py:246
msgid "The following recipes were ignored because they already existed:"
msgstr ""
#: .\cookbook\integration\integration.py:250
#, fuzzy, python-format
#| msgid "Imported new recipe!"
msgid "Imported %s recipes."
msgstr "Բաղադրատոմսը ներմուծված է:"
#: .\cookbook\integration\mealie1.py:210
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "Calories"
msgstr "Կալորիաներ"
#: .\cookbook\integration\mealie1.py:211
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
msgid "Carbohydrates"
msgstr "Ածխաջրեր"
#: .\cookbook\integration\mealie1.py:212
msgid "Cholesterol"
msgstr ""
#: .\cookbook\integration\mealie1.py:213
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
#, fuzzy
#| msgid "Fats"
msgid "Fat"
msgstr "Ճարպեր"
#: .\cookbook\integration\mealie1.py:214
msgid "Fiber"
msgstr ""
#: .\cookbook\integration\mealie1.py:215
#, fuzzy
#| msgid "Proteins"
msgid "Protein"
msgstr "Սպիտակուցներ"
#: .\cookbook\integration\mealie1.py:216
msgid "Saturated Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:217
msgid "Sodium"
msgstr ""
#: .\cookbook\integration\mealie1.py:218
msgid "Sugar"
msgstr ""
#: .\cookbook\integration\mealie1.py:219
msgid "Trans Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:220
msgid "Unsaturated Fat"
msgstr ""
#: .\cookbook\integration\openeats.py:28
#, fuzzy
#| msgid "Recipe Home"
msgid "Recipe source:"
msgstr "Բաղադրատոմսի տուն"
#: .\cookbook\integration\paprika.py:49
#, fuzzy
#| msgid "Note"
msgid "Notes"
msgstr "Նոթեր"
#: .\cookbook\integration\paprika.py:52
#, fuzzy
#| msgid "Information"
msgid "Nutritional Information"
msgstr "Տեղեկություն"
#: .\cookbook\integration\paprika.py:56
msgid "Source"
msgstr ""
#: .\cookbook\integration\recettetek.py:55
#: .\cookbook\integration\recipekeeper.py:70
msgid "Imported from"
msgstr "Ներմուծվել է՝"
#: .\cookbook\integration\saffron.py:23
msgid "Servings"
msgstr "Չափաբաժիններ"
#: .\cookbook\integration\saffron.py:25
msgid "Waiting time"
msgstr "Սպասման տևողություն"
#: .\cookbook\integration\saffron.py:27
msgid "Preparation Time"
msgstr "Պատրաստման տևողություն"
#: .\cookbook\integration\saffron.py:29 .\cookbook\templates\index.html:6
msgid "Cookbook"
msgstr "Խոհարարական գիրք"
#: .\cookbook\integration\saffron.py:31
msgid "Section"
msgstr "Բաժին"
#: .\cookbook\management\commands\fix_duplicate_properties.py:15
msgid "Fixes foods with "
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:14
msgid "Rebuilds full text search index on Recipe"
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:18
msgid "Only Postgresql databases use full text search, no index to rebuild"
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:29
msgid "Recipe index rebuild complete."
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:31
msgid "Recipe index rebuild failed."
msgstr ""
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
msgid "Breakfast"
msgstr "Նախաճաշ"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:19
msgid "Lunch"
msgstr "Ճաշ"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:24
msgid "Dinner"
msgstr "Ընթրիք"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:29 .\cookbook\models.py:971
msgid "Other"
msgstr "Այլ"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "g"
msgstr ""
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "Proteins"
msgstr "Սպիտակուցներ"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "kcal"
msgstr ""
#: .\cookbook\models.py:325
msgid ""
"Maximum file storage for space in MB. 0 for unlimited, -1 to disable file "
"upload."
msgstr ""
#: .\cookbook\models.py:513
msgid "Search"
msgstr "Փնտրել"
#: .\cookbook\models.py:514
msgid "Meal-Plan"
msgstr "Ճաշացուցակ"
#: .\cookbook\models.py:515
msgid "Books"
msgstr "Գրքեր"
#: .\cookbook\models.py:516 .\cookbook\views\views.py:416
#: .\cookbook\views\views.py:417
msgid "Shopping"
msgstr "Գնումներ"
#: .\cookbook\models.py:967
msgid "Nutrition"
msgstr "Սննդայնություն"
#: .\cookbook\models.py:968
#, fuzzy
#| msgid "Merge"
msgid "Allergen"
msgstr "Միավորել"
#: .\cookbook\models.py:969
msgid "Price"
msgstr ""
#: .\cookbook\models.py:970
msgid "Goal"
msgstr ""
#: .\cookbook\models.py:1467 .\cookbook\templates\search_info.html:28
msgid "Simple"
msgstr ""
#: .\cookbook\models.py:1468 .\cookbook\templates\search_info.html:33
msgid "Phrase"
msgstr ""
#: .\cookbook\models.py:1469 .\cookbook\templates\search_info.html:38
msgid "Web"
msgstr ""
#: .\cookbook\models.py:1470 .\cookbook\templates\search_info.html:47
msgid "Raw"
msgstr ""
#: .\cookbook\models.py:1525
msgid "Food Alias"
msgstr ""
#: .\cookbook\models.py:1526
#, fuzzy
#| msgid "Units"
msgid "Unit Alias"
msgstr "Միավորներ"
#: .\cookbook\models.py:1527
#, fuzzy
#| msgid "Keywords"
msgid "Keyword Alias"
msgstr "Բանալի բառեր"
#: .\cookbook\models.py:1528
#, fuzzy
#| msgid "Description"
msgid "Description Replace"
msgstr "Նկարագրություն"
#: .\cookbook\models.py:1529
#, fuzzy
#| msgid "Instructions"
msgid "Instruction Replace"
msgstr "Հրահանգներ"
#: .\cookbook\models.py:1530
#, fuzzy
#| msgid "New Unit"
msgid "Never Unit"
msgstr "Նոր միավոր"
#: .\cookbook\models.py:1531
msgid "Transpose Words"
msgstr ""
#: .\cookbook\models.py:1532
msgid "Food Replace"
msgstr ""
#: .\cookbook\models.py:1533
#, fuzzy
#| msgid "Edit Recipe"
msgid "Unit Replace"
msgstr "Խմբագրել բաղադրատոմսը"
#: .\cookbook\models.py:1534
msgid "Name Replace"
msgstr ""
#: .\cookbook\models.py:1564
msgid "Recipe"
msgstr "Բաղադրատոմս"
#: .\cookbook\models.py:1565
msgid "Food"
msgstr "Սննդամթերք"
#: .\cookbook\models.py:1566
msgid "Keyword"
msgstr "Բանալի բառ"
#: .\cookbook\serializer.py:262
msgid "File uploads are not enabled for this Space."
msgstr ""
#: .\cookbook\serializer.py:273
msgid "You have reached your file upload limit."
msgstr ""
#: .\cookbook\serializer.py:281
msgid "The given file type is not allowed."
msgstr ""
#: .\cookbook\serializer.py:421 .\cookbook\views\views.py:94
msgid ""
"You have the reached the maximum amount of spaces that can be owned by you."
msgstr ""
#: .\cookbook\serializer.py:434
msgid "Space Name must be unique."
msgstr ""
#: .\cookbook\serializer.py:469
msgid "Cannot modify Space owner permission."
msgstr ""
#: .\cookbook\serializer.py:1596
msgid "Hello"
msgstr ""
#: .\cookbook\serializer.py:1596
msgid "You have been invited by "
msgstr ""
#: .\cookbook\serializer.py:1598
msgid " to join their Tandoor Recipes space "
msgstr ""
#: .\cookbook\serializer.py:1600
msgid "Click the following link to activate your account: "
msgstr ""
#: .\cookbook\serializer.py:1602
msgid ""
"If the link does not work use the following code to manually join the space: "
msgstr ""
#: .\cookbook\serializer.py:1604
msgid "The invitation is valid until "
msgstr ""
#: .\cookbook\serializer.py:1606
msgid ""
"Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub "
msgstr ""
#: .\cookbook\serializer.py:1609
msgid "Tandoor Recipes Invite"
msgstr ""
#: .\cookbook\serializer.py:1813
msgid "Existing shopping list to update"
msgstr ""
#: .\cookbook\serializer.py:1815
msgid ""
"List of ingredient IDs from the recipe to add, if not provided all "
"ingredients will be added."
msgstr ""
#: .\cookbook\serializer.py:1817
msgid ""
"Providing a list_recipe ID and servings of 0 will delete that shopping list."
msgstr ""
#: .\cookbook\serializer.py:1826
msgid "Amount of food to add to the shopping list"
msgstr ""
#: .\cookbook\serializer.py:1828
msgid "ID of unit to use for the shopping list"
msgstr ""
#: .\cookbook\serializer.py:1830
msgid "When set to true will delete all food from active shopping lists."
msgstr ""
#: .\cookbook\templates\404.html:5
msgid "404 Error"
msgstr "Սխալ 404"
#: .\cookbook\templates\404.html:18
msgid "The page you are looking for could not be found."
msgstr "Ձեր փնտրած էջը հնարավոր չէ գտնել։"
#: .\cookbook\templates\404.html:33
msgid "Take me Home"
msgstr "Գնալ տուն"
#: .\cookbook\templates\404.html:35
msgid "Report a Bug"
msgstr "Զեկուցել սխալի մասին"
#: .\cookbook\templates\account\email.html:6
#: .\cookbook\templates\account\email.html:10
msgid "E-mail Addresses"
msgstr ""
#: .\cookbook\templates\account\email.html:12
msgid "The following e-mail addresses are associated with your account:"
msgstr ""
#: .\cookbook\templates\account\email.html:29
msgid "Verified"
msgstr ""
#: .\cookbook\templates\account\email.html:31
msgid "Unverified"
msgstr ""
#: .\cookbook\templates\account\email.html:33
msgid "Primary"
msgstr ""
#: .\cookbook\templates\account\email.html:40
#, fuzzy
#| msgid "Make Header"
msgid "Make Primary"
msgstr "Ստեղծել Խորագիր"
#: .\cookbook\templates\account\email.html:42
msgid "Re-send Verification"
msgstr ""
#: .\cookbook\templates\account\email.html:43
#: .\cookbook\templates\socialaccount\connections.html:36
msgid "Remove"
msgstr "Հեռացնել"
#: .\cookbook\templates\account\email.html:51
#, fuzzy
#| msgid "Warning"
msgid "Warning:"
msgstr "Զգուշացում"
#: .\cookbook\templates\account\email.html:51
msgid ""
"You currently do not have any e-mail address set up. You should really add "
"an e-mail address so you can receive notifications, reset your password, etc."
msgstr ""
#: .\cookbook\templates\account\email.html:57
msgid "Add E-mail Address"
msgstr ""
#: .\cookbook\templates\account\email.html:62
msgid "Add E-mail"
msgstr ""
#: .\cookbook\templates\account\email.html:72
msgid "Do you really want to remove the selected e-mail address?"
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:6
#: .\cookbook\templates\account\email_confirm.html:10
msgid "Confirm E-mail Address"
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:16
#, python-format
msgid ""
"Please confirm that\n"
" %(email)s is an e-mail address "
"for user %(user_display)s\n"
" ."
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:22
msgid "Confirm"
msgstr "Հաստատել"
#: .\cookbook\templates\account\email_confirm.html:29
#, python-format
msgid ""
"This e-mail confirmation link expired or is invalid. Please\n"
" issue a new e-mail confirmation "
"request."
msgstr ""
#: .\cookbook\templates\account\login.html:8
#: .\cookbook\templates\openid\login.html:8
msgid "Login"
msgstr "Մուտք"
#: .\cookbook\templates\account\login.html:15
#: .\cookbook\templates\account\login.html:31
#: .\cookbook\templates\account\password_reset.html:39
#: .\cookbook\templates\account\password_reset_done.html:31
#: .\cookbook\templates\account\signup.html:68
#: .\cookbook\templates\account\signup_closed.html:15
#: .\cookbook\templates\openid\login.html:15
#: .\cookbook\templates\openid\login.html:26
#: .\cookbook\templates\socialaccount\authentication_error.html:15
msgid "Sign In"
msgstr "Մուտք գործել"
#: .\cookbook\templates\account\login.html:34
#: .\cookbook\templates\account\password_reset.html:41
#: .\cookbook\templates\account\password_reset_done.html:33
#: .\cookbook\templates\socialaccount\signup.html:8
#: .\cookbook\templates\socialaccount\signup.html:56
#, fuzzy
#| msgid "Sign In"
msgid "Sign Up"
msgstr "Մուտք գործել"
#: .\cookbook\templates\account\login.html:38
msgid "Lost your password?"
msgstr ""
#: .\cookbook\templates\account\login.html:39
#: .\cookbook\templates\account\password_reset.html:29
msgid "Reset My Password"
msgstr ""
#: .\cookbook\templates\account\login.html:50
msgid "Social Login"
msgstr "Մուտք Սոցիալական էջով"
#: .\cookbook\templates\account\login.html:51
msgid "You can use any of the following providers to sign in."
msgstr ""
"Դուք կարող եք օգտագործել հետևյալ պրովայդերներից ցանկացածը մուտք գործելու "
"համար։"
#: .\cookbook\templates\account\logout.html:5
#: .\cookbook\templates\account\logout.html:9
#: .\cookbook\templates\account\logout.html:18
msgid "Sign Out"
msgstr "Դուրս գալ"
#: .\cookbook\templates\account\logout.html:11
msgid "Are you sure you want to sign out?"
msgstr "Համոզվա՞ծ եք, որ ցանկանում եք դուրս գալ՞"
#: .\cookbook\templates\account\password_change.html:6
#: .\cookbook\templates\account\password_change.html:10
#: .\cookbook\templates\account\password_change.html:15
#: .\cookbook\templates\account\password_reset_from_key.html:7
#: .\cookbook\templates\account\password_reset_from_key.html:13
#: .\cookbook\templates\account\password_reset_from_key_done.html:7
#: .\cookbook\templates\account\password_reset_from_key_done.html:13
#, fuzzy
#| msgid "Changes saved!"
msgid "Change Password"
msgstr "Փոփոխությունները պահպանված են:"
#: .\cookbook\templates\account\password_change.html:16
msgid "Forgot Password?"
msgstr ""
#: .\cookbook\templates\account\password_reset.html:7
#: .\cookbook\templates\account\password_reset.html:13
#: .\cookbook\templates\account\password_reset_done.html:7
#: .\cookbook\templates\account\password_reset_done.html:18
msgid "Password Reset"
msgstr "Գաղտնաբառի վերականգնում"
#: .\cookbook\templates\account\password_reset.html:24
msgid ""
"Forgotten your password? Enter your e-mail address below, and we'll send you "
"an e-mail allowing you to reset it."
msgstr ""
#: .\cookbook\templates\account\password_reset.html:32
#, fuzzy
#| msgid "Password reset is not implemented for the time being!"
msgid "Password reset is disabled on this instance."
msgstr "Գաղտնաբառի վերականգնում առայժմ իրականացված չէ:"
#: .\cookbook\templates\account\password_reset_done.html:25
msgid ""
"We have sent you an e-mail. Please contact us if you do not receive it "
"within a few minutes."
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:13
#, fuzzy
#| msgid "API Token"
msgid "Bad Token"
msgstr "API ժետոն"
#: .\cookbook\templates\account\password_reset_from_key.html:25
#, python-format
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used.\n"
" Please request a new "
"password reset."
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:33
msgid "change password"
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:36
#: .\cookbook\templates\account\password_reset_from_key_done.html:19
msgid "Your password is now changed."
msgstr ""
#: .\cookbook\templates\account\password_set.html:6
#: .\cookbook\templates\account\password_set.html:10
#: .\cookbook\templates\account\password_set.html:15
#, fuzzy
#| msgid "Password Reset"
msgid "Set Password"
msgstr "Գաղտնաբառի վերականգնում"
#: .\cookbook\templates\account\signup.html:5
msgid "Register"
msgstr "Գրանցվել"
#: .\cookbook\templates\account\signup.html:11
#, fuzzy
#| msgid "Create your Account"
msgid "Create an Account"
msgstr "Ստեղծեք ձեր հաշիվը"
#: .\cookbook\templates\account\signup.html:41
msgid "I accept the follwoing"
msgstr ""
#: .\cookbook\templates\account\signup.html:44
#: .\cookbook\templates\socialaccount\signup.html:35
msgid "Terms and Conditions"
msgstr ""
#: .\cookbook\templates\account\signup.html:47
#: .\cookbook\templates\socialaccount\signup.html:38
msgid "and"
msgstr ""
#: .\cookbook\templates\account\signup.html:51
#: .\cookbook\templates\socialaccount\signup.html:42
msgid "Privacy Policy"
msgstr ""
#: .\cookbook\templates\account\signup.html:64
msgid "Create User"
msgstr "Ստեղծել օգտատեր"
#: .\cookbook\templates\account\signup.html:68
msgid "Already have an account?"
msgstr ""
#: .\cookbook\templates\account\signup_closed.html:5
#: .\cookbook\templates\account\signup_closed.html:11
msgid "Sign Up Closed"
msgstr ""
#: .\cookbook\templates\account\signup_closed.html:13
msgid "We are sorry, but the sign up is currently closed."
msgstr ""
#: .\cookbook\templates\frontend\tandoor.html:15
msgid "Tandoor Recipe Manager"
msgstr ""
#: .\cookbook\templates\index.html:28
msgid "Search recipe ..."
msgstr "Փնտրել բաղադրատոմս"
#: .\cookbook\templates\index.html:43
msgid "New Recipe"
msgstr "Նոր Բաղադրատոմս"
#: .\cookbook\templates\index.html:46
msgid "Import Recipe"
msgstr "Ներմուծել բաղադրատոմս"
#: .\cookbook\templates\index.html:52
msgid "Advanced Search"
msgstr "Հավելյալ որոնում"
#: .\cookbook\templates\index.html:56
msgid "Reset Search"
msgstr "Զրոյացնել որոնումը"
#: .\cookbook\templates\index.html:84
msgid "Last viewed"
msgstr "Վերջին դիտածը"
#: .\cookbook\templates\index.html:86
msgid "Recipes"
msgstr "Բաղադրատոմսեր"
#: .\cookbook\templates\index.html:93
msgid "Log in to view recipes"
msgstr "Մուտք գործեք բաղադրատոմսերը դիտելու համար"
#: .\cookbook\templates\markdown_info.html:5
#: .\cookbook\templates\markdown_info.html:13
msgid "Markdown Info"
msgstr "Markdown-ի մասին տեղեկություն"
#: .\cookbook\templates\markdown_info.html:14
msgid ""
"\n"
" Markdown is lightweight markup language that can be used to format "
"plain text easily.\n"
" This site uses the Python Markdown library to\n"
" convert your text into nice looking HTML. Its full markdown "
"documentation can be found\n"
" here.\n"
" An incomplete but most likely sufficient documentation can be found "
"below.\n"
" "
msgstr ""
"\n"
" Markdown-ը թեթև markup լեզու է, որը կարող է օգտագործվել պարզ տեքստը "
"ձևավորելու համար։\n"
" Այս կայքն օգտագործում էPython Markdown գրադարանը\n"
" ձեր տեքստը գեղեցիկ HTML-ի ձևափոխելու համար։ Markdown-ի ամբողջական "
"ուղեցույցերը կարող եք գտնել\n"
" այստեղ։\n"
" Ոչ լրիվ, բայց հավանաբար բավարար ուղեցույցեր կարող եք գտնել "
"ներքևում։\n"
" "
#: .\cookbook\templates\markdown_info.html:25
msgid "Headers"
msgstr "Խորագրեր"
#: .\cookbook\templates\markdown_info.html:54
msgid "Formatting"
msgstr "Ձևավորում"
#: .\cookbook\templates\markdown_info.html:56
#: .\cookbook\templates\markdown_info.html:72
msgid "Line breaks are inserted by adding two spaces after the end of a line"
msgstr "Տողերի տրոհում կարելի է տեղադրել տողի վերջում ավելացնելող երկու բացատ"
#: .\cookbook\templates\markdown_info.html:57
#: .\cookbook\templates\markdown_info.html:73
#, fuzzy
#| msgid "or by leaving a blank line inbetween."
msgid "or by leaving a blank line in between."
msgstr "կամ դատարկ տող թողնելով։"
#: .\cookbook\templates\markdown_info.html:59
#: .\cookbook\templates\markdown_info.html:74
msgid "This text is bold"
msgstr "Այս տեքստը թավ տառատեսակով է"
#: .\cookbook\templates\markdown_info.html:60
#: .\cookbook\templates\markdown_info.html:75
msgid "This text is italic"
msgstr "Այս տեքստը շեղատառ է"
#: .\cookbook\templates\markdown_info.html:61
#: .\cookbook\templates\markdown_info.html:77
msgid "Blockquotes are also possible"
msgstr "Blockquote-ներ նույնպես հնարավոր են"
#: .\cookbook\templates\markdown_info.html:84
msgid "Lists"
msgstr "Ցուցակներ"
#: .\cookbook\templates\markdown_info.html:85
#, fuzzy
#| msgid ""
#| "Lists can ordered or unorderd. It is important to leave a blank line "
#| "before the list!"
msgid ""
"Lists can ordered or unordered. It is important to leave a blank line "
"before the list!"
msgstr ""
"Ցուցակները կարող են լինել կարգավորված կամ անկարգավորված։ Շատկարևոր է "
"թողնել դատարկ տող ցուցաից առաջ։"
#: .\cookbook\templates\markdown_info.html:87
#: .\cookbook\templates\markdown_info.html:108
msgid "Ordered List"
msgstr "Կարգավորված ցուցակ"
#: .\cookbook\templates\markdown_info.html:89
#: .\cookbook\templates\markdown_info.html:90
#: .\cookbook\templates\markdown_info.html:91
#: .\cookbook\templates\markdown_info.html:110
#: .\cookbook\templates\markdown_info.html:111
#: .\cookbook\templates\markdown_info.html:112
msgid "unordered list item"
msgstr "չկարգավորված ցուցակի իր"
#: .\cookbook\templates\markdown_info.html:93
#: .\cookbook\templates\markdown_info.html:114
msgid "Unordered List"
msgstr "Չկարգավորված ցուցակ"
#: .\cookbook\templates\markdown_info.html:95
#: .\cookbook\templates\markdown_info.html:96
#: .\cookbook\templates\markdown_info.html:97
#: .\cookbook\templates\markdown_info.html:116
#: .\cookbook\templates\markdown_info.html:117
#: .\cookbook\templates\markdown_info.html:118
msgid "ordered list item"
msgstr "կարգավորված ցուցակի իր"
#: .\cookbook\templates\markdown_info.html:125
msgid "Images & Links"
msgstr "Նկարներ և հղումներ"
#: .\cookbook\templates\markdown_info.html:126
msgid ""
"Links can be formatted with Markdown. This application also allows to paste "
"links directly into markdown fields without any formatting."
msgstr ""
"Հղումները կարող են խմբագրվել Markdown-ի օգնությամբ։ Այս ծրագրում հնարավոր է "
"անմիջապես տեղադրել հղումներ markdown դաշտում առանց որևէ խմբագրման։"
#: .\cookbook\templates\markdown_info.html:132
#: .\cookbook\templates\markdown_info.html:145
msgid "This will become an image"
msgstr "Սա կդառնա նկար"
#: .\cookbook\templates\markdown_info.html:152
msgid "Tables"
msgstr "Աղյուսակներ"
#: .\cookbook\templates\markdown_info.html:153
msgid ""
"Markdown tables are hard to create by hand. It is recommended to use a table "
"editor like this one."
msgstr ""
"Markdown աղյուսակները դժվար է ստեղծել ձեռքով։ Խորհուրդ է տրվում օգտագործել "
"աղյուսակների խմբագիր, օրինակ այս մեկը։"
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:171
#: .\cookbook\templates\markdown_info.html:177
msgid "Table"
msgstr "Աղյուսակ"
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:172
msgid "Header"
msgstr "Խորագիր"
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:178
msgid "Cell"
msgstr "Բջիջ"
#: .\cookbook\templates\no_groups_info.html:5
#: .\cookbook\templates\no_groups_info.html:12
msgid "No Permissions"
msgstr "Թույլտվություն չկա"
#: .\cookbook\templates\no_groups_info.html:17
#, fuzzy
#| msgid ""
#| "You do not have any groups and therefor cannot use this application. "
#| "Please contact your administrator."
msgid "You do not have any groups and therefor cannot use this application."
msgstr ""
"Դուք չունեք որևէ խումբ և չեք կարող օգտագործել այս ծրագիրը։ Կապվեք ձեր "
"ադմինիստրատորի հետ։"
#: .\cookbook\templates\no_groups_info.html:18
#: .\cookbook\templates\no_perm_info.html:15
msgid "Please contact your administrator."
msgstr ""
#: .\cookbook\templates\no_perm_info.html:5
#: .\cookbook\templates\no_perm_info.html:12
#, fuzzy
#| msgid "No Permissions"
msgid "No Permission"
msgstr "Թույլտվություն չկա"
#: .\cookbook\templates\no_perm_info.html:15
#, fuzzy
#| msgid "You do not have the required permissions to perform this action!"
msgid ""
"You do not have the required permissions to view this page or perform this "
"action."
msgstr "Դուք չունեք բավարար թույլտվություն այս գործողության համար։"
#: .\cookbook\templates\offline.html:5
msgid "Offline"
msgstr "Ցանցից դուրս"
#: .\cookbook\templates\openid\login.html:27
#: .\cookbook\templates\socialaccount\authentication_error.html:27
msgid "Back"
msgstr ""
#: .\cookbook\templates\rest_framework\api.html:5
msgid "Recipe Home"
msgstr "Բաղադրատոմսի տուն"
#: .\cookbook\templates\rest_framework\api.html:11
msgid "API Documentation"
msgstr "API-ի փաստաթղթեր"
#: .\cookbook\templates\search_info.html:5
#: .\cookbook\templates\search_info.html:9
#, fuzzy
#| msgid "Search String"
msgid "Search Settings"
msgstr "Փնտրել շարքը"
#: .\cookbook\templates\search_info.html:10
msgid ""
"\n"
" Creating the best search experience is complicated and weighs "
"heavily on your personal configuration. \n"
" Changing any of the search settings can have significant impact on "
"the speed and quality of the results.\n"
" Search Methods, Trigrams and Full Text Search configurations are "
"only available if you are using Postgres for your database.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:19
#, fuzzy
#| msgid "Search"
msgid "Search Methods"
msgstr "Փնտրել"
#: .\cookbook\templates\search_info.html:23
msgid ""
" \n"
" Full text searches attempt to normalize the words provided to "
"match common variants. For example: 'forked', 'forking', 'forks' will all "
"normalize to 'fork'.\n"
" There are several methods available, described below, that will "
"control how the search behavior should react when multiple words are "
"searched.\n"
" Full technical details on how these operate can be viewed on Postgresql's website.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:29
msgid ""
" \n"
" Simple searches ignore punctuation and common words such as "
"'the', 'a', 'and'. And will treat separate words as required.\n"
" Searching for 'apple or flour' will return any recipe that "
"includes both 'apple' and 'flour' anywhere in the fields that have been "
"selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:34
msgid ""
" \n"
" Phrase searches ignore punctuation, but will search for all of "
"the words in the exact order provided.\n"
" Searching for 'apple or flour' will only return a recipe that "
"includes the exact phrase 'apple or flour' in any of the fields that have "
"been selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:39
msgid ""
" \n"
" Web searches simulate functionality found on many web search "
"sites supporting special syntax.\n"
" Placing quotes around several words will convert those words "
"into a phrase.\n"
" 'or' is recognized as searching for the word (or phrase) "
"immediately before 'or' OR the word (or phrase) directly after.\n"
" '-' is recognized as searching for recipes that do not include "
"the word (or phrase) that comes immediately after. \n"
" For example searching for 'apple pie' or cherry -butter will "
"return any recipe that includes the phrase 'apple pie' or the word "
"'cherry' \n"
" in any field included in the full text search but exclude any "
"recipe that has the word 'butter' in any field included.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:48
msgid ""
" \n"
" Raw search is similar to Web except will take puncuation "
"operators such as '|', '&' and '()'\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:59
msgid ""
" \n"
" Another approach to searching that also requires Postgresql is "
"fuzzy search or trigram similarity. A trigram is a group of three "
"consecutive characters.\n"
" For example searching for 'apple' will create x trigrams 'app', "
"'ppl', 'ple' and will create a score of how closely words match the "
"generated trigrams.\n"
" One benefit of searching trigams is that a search for 'sandwich' "
"will find misspelled words such as 'sandwhich' that would be missed by other "
"methods.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:69
#, fuzzy
#| msgid "Search Recipe"
msgid "Search Fields"
msgstr "Փնտրել բաղադրատոմս"
#: .\cookbook\templates\search_info.html:73
msgid ""
" \n"
" Unaccent is a special case in that it enables searching a field "
"'unaccented' for each search style attempting to ignore accented values. \n"
" For example when you enable unaccent for 'Name' any search "
"(starts with, contains, trigram) will attempt the search ignoring accented "
"characters.\n"
" \n"
" For the other options, you can enable search on any or all "
"fields and they will be combined together with an assumed 'OR'.\n"
" For example enabling 'Name' for Starts With, 'Name' and "
"'Description' for Partial Match and 'Ingredients' and 'Keywords' for Full "
"Search\n"
" and searching for 'apple' will generate a search that will "
"return recipes that have:\n"
" - A recipe name that starts with 'apple'\n"
" - OR a recipe name that contains 'apple'\n"
" - OR a recipe description that contains 'apple'\n"
" - OR a recipe that will have a full text search match ('apple' "
"or 'apples') in ingredients\n"
" - OR a recipe that will have a full text search match in "
"Keywords\n"
"\n"
" Combining too many fields in too many types of search can have a "
"negative impact on performance, create duplicate results or return "
"unexpected results.\n"
" For example, enabling fuzzy search or partial matches will "
"interfere with web search methods. \n"
" Searching for 'apple -pie' with fuzzy search and full text "
"search will return the recipe Apple Pie. Though it is not included in the "
"full text results, it does match the trigram results.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:95
#, fuzzy
#| msgid "Search"
msgid "Search Index"
msgstr "Փնտրել"
#: .\cookbook\templates\search_info.html:99
msgid ""
" \n"
" Trigram search and Full Text Search both rely on database "
"indexes to perform effectively. \n"
" You can rebuild the indexes on all fields in the Admin page for "
"Recipes and selecting all recipes and running 'rebuild index for selected "
"recipes'\n"
" You can also rebuild indexes at the command line by executing "
"the management command 'python manage.py rebuildindex'\n"
" "
msgstr ""
#: .\cookbook\templates\setup.html:6
msgid "Cookbook Setup"
msgstr "Խոհարարական գրքի կարգավորում"
#: .\cookbook\templates\setup.html:14
msgid "Setup"
msgstr "Կարգավորում"
#: .\cookbook\templates\setup.html:15
msgid ""
"To start using this application you must first create a superuser account."
msgstr ""
"Այս ծրագիրն օգտագործելու համար նախ պետք է ստեղծեք սուպեր-օգտատերի հաշիվ:"
#: .\cookbook\templates\setup.html:20
msgid "Create Superuser account"
msgstr "Ստեղծել սուպեր-օգտատերի հաշիվ"
#: .\cookbook\templates\socialaccount\authentication_error.html:7
#: .\cookbook\templates\socialaccount\authentication_error.html:23
#, fuzzy
#| msgid "Social Login"
msgid "Social Network Login Failure"
msgstr "Մուտք Սոցիալական էջով"
#: .\cookbook\templates\socialaccount\authentication_error.html:25
msgid ""
"An error occurred while attempting to login via your social network account."
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:4
#: .\cookbook\templates\socialaccount\connections.html:7
msgid "Account Connections"
msgstr "Հաշվի կապեր"
#: .\cookbook\templates\socialaccount\connections.html:10
msgid ""
"You can sign in to your account using any of the following third party\n"
" accounts:"
msgstr ""
"Դուք կարող եք մուտք գործել ձեր հաշիվ օգտագործելով հետևյալ երրորդ կողմի\n"
" հաշիվները․"
#: .\cookbook\templates\socialaccount\connections.html:44
msgid ""
"You currently have no social network accounts connected to this account."
msgstr "Դուք այս հաշվին կապված սոցիալական հաշիվներ չունեք:"
#: .\cookbook\templates\socialaccount\connections.html:47
msgid "Add a 3rd Party Account"
msgstr "Ավելացնել 3րդ կողմի հաշիվ"
#: .\cookbook\templates\socialaccount\login.html:5
#: .\cookbook\templates\socialaccount\signup.html:5
#, fuzzy
#| msgid "Sign Out"
msgid "Signup"
msgstr "Դուրս գալ"
#: .\cookbook\templates\socialaccount\login.html:9
#, python-format
msgid "Connect %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:11
#, python-format
msgid "You are about to connect a new third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:13
#, python-format
msgid "Sign In Via %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:15
#, python-format
msgid "You are about to sign in using a third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:20
msgid "Continue"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:10
#, python-format
msgid ""
"You are about to use your\n"
" %(provider_name)s account to login to\n"
" %(site_name)s. As a final step, please complete the following form:"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:32
msgid "I accept the following"
msgstr ""
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:23
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:31
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:39
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:47
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:55
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:63
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:71
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:79
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:87
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:95
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:103
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:111
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:119
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:127
#, fuzzy
#| msgid "Sign In"
msgid "Sign in using"
msgstr "Մուտք գործել"
#: .\cookbook\templates\space_overview.html:6
msgid "Overview"
msgstr ""
#: .\cookbook\templates\space_overview.html:13
msgid "Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:17
msgid ""
"Recipes, foods, shopping lists and more are organized in spaces of one or "
"more people."
msgstr ""
#: .\cookbook\templates\space_overview.html:18
msgid ""
"You can either be invited into an existing space or create your own one."
msgstr ""
#: .\cookbook\templates\space_overview.html:25
msgid "Your Spaces"
msgstr ""
#: .\cookbook\templates\space_overview.html:53
msgid "Owner"
msgstr ""
#: .\cookbook\templates\space_overview.html:73
#: .\cookbook\templates\space_overview.html:83
msgid "Join Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:76
msgid "Join an existing space."
msgstr ""
#: .\cookbook\templates\space_overview.html:78
msgid ""
"To join an existing space either enter your invite token or click on the "
"invite link the space owner send you."
msgstr ""
#: .\cookbook\templates\space_overview.html:91
#: .\cookbook\templates\space_overview.html:100
#, fuzzy
#| msgid "Create User"
msgid "Create Space"
msgstr "Ստեղծել օգտատեր"
#: .\cookbook\templates\space_overview.html:94
msgid "Create your own recipe space."
msgstr ""
#: .\cookbook\templates\space_overview.html:96
msgid "Start your own recipe space and invite other users to it."
msgstr ""
#: .\cookbook\templates\system.html:23
msgid "System"
msgstr "Համակարգ"
#: .\cookbook\templates\system.html:24
#, fuzzy
#| msgid ""
#| "\n"
#| " Django Recipes is an open source free software application. It "
#| "can be found on\n"
#| " GitHub.\n"
#| " Changelogs can be found here.\n"
#| " "
msgid ""
"\n"
" Tandoor Recipes is an open source free software application. It can "
"be found on\n"
" GitHub.\n"
" Changelogs can be found here.\n"
" "
msgstr ""
"\n"
" Django Բաղադրատոմսերը բաց աղբյուրով անվճար ծրագիր է։ Այն կարող եք "
"գտնել \n"
" GitHub-ում։\n"
" Փոփոխությունների մատյանը կարող եք գտնել այստեղ։\n"
" "
#: .\cookbook\templates\system.html:30
msgid "System Information"
msgstr "Համակարգի տեղեկություն"
#: .\cookbook\templates\system.html:51
msgid ""
"\n"
" You need to execute version.py in your update "
"script to generate version information (done automatically in docker).\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:56
msgid "Plugins"
msgstr ""
#: .\cookbook\templates\system.html:67
msgid "Media Serving"
msgstr "Մեդիայի մատուցում"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:123 .\cookbook\templates\system.html:134
msgid "Warning"
msgstr "Զգուշացում"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:125 .\cookbook\templates\system.html:134
msgid "Ok"
msgstr "Լավ է"
#: .\cookbook\templates\system.html:70
msgid ""
"Serving media files directly using gunicorn/python is not recommend!\n"
" Please follow the steps described\n"
" here to update\n"
" your installation.\n"
" "
msgstr ""
"Մեդիա ֆայլերի մատուցումը gunicorn/python-ի կիրառմամբ խորհուրդ չէ տրվում"
"b>։\n"
" Խնդրում ենք օգտագործել քայլերը նկարագրված\n"
" այստեղ ձեր տեղադրումը\n"
" թարմացնելու համար։\n"
" "
#: .\cookbook\templates\system.html:76 .\cookbook\templates\system.html:91
#: .\cookbook\templates\system.html:104 .\cookbook\templates\system.html:115
#: .\cookbook\views\views.py:168
msgid "Everything is fine!"
msgstr "Ամեն բան նորմալ է։"
#: .\cookbook\templates\system.html:80
msgid "Secret Key"
msgstr "Գաղտնի բանալի"
#: .\cookbook\templates\system.html:84
msgid ""
"\n"
" You do not have a SECRET_KEY configured in your "
".env file. Django defaulted to the\n"
" standard key\n"
" provided with the installation which is publicly know and "
"insecure! Please set\n"
" SECRET_KEY int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" Դուք չունեք SECRET_KEY մատնանշված ձեր .env"
"code> ֆայլում. Django-ն օգտագործում է\n"
" ստանդարտ բանալի\n"
" տրված ծրագրի ներդրման ժամանակ, ինչը հանրային է և ոչ անվտանգ։ "
"Խնդրում ենք մատնանշել\n"
" SECRET_KEY բանալի .env Ֆայլում։\n"
" "
#: .\cookbook\templates\system.html:94
msgid "Debug Mode"
msgstr "Վրիպակների վերացման ռեժիմ"
#: .\cookbook\templates\system.html:98
msgid ""
"\n"
" This application is still running in debug mode. This is most "
"likely not needed. Turn of debug mode by\n"
" setting\n"
" DEBUG=0 int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" Այս ծրագիրը դեռ աշխատում է վրիպակների վերացման ռեժիմում։ Սա "
"հավանաբար անհրաժեշտ չէ։ Անժատեք այս ռեժիմը\n"
" սահմանելով\n"
" DEBUG=0 .env կազմաձևումների ֆայլում։\n"
" "
#: .\cookbook\templates\system.html:107
msgid "Allowed Hosts"
msgstr ""
#: .\cookbook\templates\system.html:111
msgid ""
"\n"
" Your allowed hosts are configured to allow every host. This "
"might be ok in some setups but should be avoided. Please see the docs about "
"this.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:118
msgid "Database"
msgstr "Շտեմարան"
#: .\cookbook\templates\system.html:121
msgid "Info"
msgstr "Տեղեկություն"
#: .\cookbook\templates\system.html:131 .\cookbook\templates\system.html:148
msgid "Migrations"
msgstr ""
#: .\cookbook\templates\system.html:137
msgid ""
"\n"
" Migrations should never fail!\n"
" Failed migrations will likely cause major parts of the app to "
"not function correctly.\n"
" If a migration fails make sure you are on the latest version and "
"if so please post the migration log and the overview below in a GitHub "
"issue.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "False"
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "True"
msgstr ""
#: .\cookbook\templates\system.html:268
msgid "Hide"
msgstr ""
#: .\cookbook\templates\system.html:271
#, fuzzy
#| msgid "Show help"
msgid "Show"
msgstr "Ցուցադրել օգնություն"
#: .\cookbook\templates\test2.html:6
msgid "Export Recipes"
msgstr "Արտահանել բաղադրատոմսերը"
#: .\cookbook\templates\test2.html:14 .\cookbook\templates\test2.html:20
msgid "Export"
msgstr "Արտահանել"
#: .\cookbook\views\api.py:198 .\cookbook\views\api.py:326
#, fuzzy
#| msgid "Parameter filter_list incorrectly formatted"
msgid "Parameter updated_at incorrectly formatted"
msgstr "filter_list պարամետրը սխալ է ձևավորված"
#: .\cookbook\views\api.py:351 .\cookbook\views\api.py:484
#, python-brace-format
msgid "No {self.basename} with id {pk} exists"
msgstr ""
#: .\cookbook\views\api.py:355
msgid "Cannot merge with the same object!"
msgstr "Հնարավոր չէ միավորել նույն օբյեկտի հետ:"
#: .\cookbook\views\api.py:362
#, python-brace-format
msgid "No {self.basename} with id {target} exists"
msgstr ""
#: .\cookbook\views\api.py:367
#, fuzzy
#| msgid "Cannot merge with the same object!"
msgid "Cannot merge with child object!"
msgstr "Հնարավոր չէ միավորել նույն օբյեկտի հետ:"
#: .\cookbook\views\api.py:405
#, python-brace-format
msgid "{source.name} was merged successfully with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:411
#, python-brace-format
msgid "An error occurred attempting to merge {source.name} with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:493
#, python-brace-format
msgid "{child.name} was moved successfully to the root."
msgstr ""
#: .\cookbook\views\api.py:496 .\cookbook\views\api.py:514
msgid "An error occurred attempting to move "
msgstr ""
#: .\cookbook\views\api.py:499
msgid "Cannot move an object to itself!"
msgstr ""
#: .\cookbook\views\api.py:505
#, python-brace-format
msgid "No {self.basename} with id {parent} exists"
msgstr ""
#: .\cookbook\views\api.py:511
#, python-brace-format
msgid "{child.name} was moved successfully to parent {parent.name}"
msgstr ""
#: .\cookbook\views\api.py:992
#, python-brace-format
msgid "{obj.name} was removed from the shopping list."
msgstr ""
#: .\cookbook\views\api.py:997 .\cookbook\views\api.py:1627
#, python-brace-format
msgid "{obj.name} was added to the shopping list."
msgstr ""
#: .\cookbook\views\api.py:1239
msgid "Filter meal plans from date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1241
msgid "Filter meal plans to date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1244
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1404
msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1406
msgid "Query string matched (fuzzy) against object name."
msgstr ""
#: .\cookbook\views\api.py:1442
msgid ""
"Query string matched (fuzzy) against recipe name. In the future also "
"fulltext search."
msgstr ""
#: .\cookbook\views\api.py:1444
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr ""
#: .\cookbook\views\api.py:1445
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr ""
#: .\cookbook\views\api.py:1446
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1447
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1448
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1450
msgid "ID of food a recipe should have. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1451
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr ""
#: .\cookbook\views\api.py:1452
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1453
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr ""
#: .\cookbook\views\api.py:1454
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1456
msgid "ID of book a recipe should be in. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1457
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr ""
#: .\cookbook\views\api.py:1458
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1459
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr ""
#: .\cookbook\views\api.py:1460
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1462
msgid "ID of unit a recipe should have."
msgstr ""
#: .\cookbook\views\api.py:1464
msgid "Exact rating of recipe"
msgstr ""
#: .\cookbook\views\api.py:1465
msgid "Rating a recipe should have or greater."
msgstr ""
#: .\cookbook\views\api.py:1466
msgid "Rating a recipe should have or smaller."
msgstr ""
#: .\cookbook\views\api.py:1468
msgid "Filter recipes cooked X times."
msgstr ""
#: .\cookbook\views\api.py:1469
msgid "Filter recipes cooked X times or more."
msgstr ""
#: .\cookbook\views\api.py:1470
msgid "Filter recipes cooked X times or less."
msgstr ""
#: .\cookbook\views\api.py:1472
msgid "Filter recipes created on the given date."
msgstr ""
#: .\cookbook\views\api.py:1473
msgid "Filter recipes created on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1474
msgid "Filter recipes created on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1476 .\cookbook\views\api.py:1477
#: .\cookbook\views\api.py:1478
msgid "Filter recipes updated on the given date."
msgstr ""
#: .\cookbook\views\api.py:1480
msgid "Filter recipes last cooked on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1481
msgid "Filter recipes last cooked on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1483 .\cookbook\views\api.py:1484
msgid "Filter recipes lasts viewed on the given date."
msgstr ""
#: .\cookbook\views\api.py:1486
msgid "Filter recipes for ones created by the given user ID"
msgstr ""
#: .\cookbook\views\api.py:1487
msgid "If only internal recipes should be returned. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1488
msgid "Returns the results in randomized order. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1490
msgid ""
"Determines the order of the results. Options are: score,-score,name,-name,"
"lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-"
"created_at,lastviewed,-lastviewed"
msgstr ""
#: .\cookbook\views\api.py:1492
msgid "Returns new results first in search results. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1493
msgid ""
"Returns the given number of recently viewed recipes before search results "
"(if given)"
msgstr ""
#: .\cookbook\views\api.py:1494
msgid "ID of a custom filter. Returns all recipes matched by that filter."
msgstr ""
#: .\cookbook\views\api.py:1495
msgid "Filter recipes that can be made with OnHand food. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1773
msgid ""
"Return the PropertyTypes matching the property category. Repeat for "
"multiple."
msgstr ""
#: .\cookbook\views\api.py:1804 .\cookbook\views\api.py:1860
msgid "Returns only entries associated with the given mealplan id"
msgstr ""
#: .\cookbook\views\api.py:1858
msgid ""
"Returns only elements updated after the given timestamp in ISO 8601 format."
msgstr ""
#: .\cookbook\views\api.py:2031
msgid ""
"Return the Automations matching the automation type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2048
msgid ""
"Text field to store data that gets carried over to the UserSpace created "
"from the InviteLink"
msgstr ""
#: .\cookbook\views\api.py:2049
msgid "Only return InviteLinks that have not been used yet."
msgstr ""
#: .\cookbook\views\api.py:2076
msgid "Return the CustomFilters matching the model type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2176
msgid "Nothing to do."
msgstr ""
#: .\cookbook\views\api.py:2222
msgid "Invalid Url"
msgstr ""
#: .\cookbook\views\api.py:2228
msgid "Connection Refused."
msgstr ""
#: .\cookbook\views\api.py:2232
msgid "Bad URL Schema."
msgstr ""
#: .\cookbook\views\api.py:2257
#, fuzzy
#| msgid "The requested page could not be found."
msgid "No usable data could be found."
msgstr "Պահանջվող էջը չի գտնվել:"
#: .\cookbook\views\api.py:2286 .\cookbook\views\api.py:2434
msgid "You must select an AI provider to perform your request."
msgstr ""
#: .\cookbook\views\api.py:2293 .\cookbook\views\api.py:2441
msgid ""
"You don't have any credits remaining to use AI or AI features are not "
"enabled for your space."
msgstr ""
#: .\cookbook\views\api.py:2499 .\cookbook\views\api.py:2667
msgid "File is above space limit"
msgstr ""
#: .\cookbook\views\api.py:2522 .\cookbook\views\api.py:2684
msgid "Importing is not implemented for this provider"
msgstr "Ներմուծումն այս պրովայդերի համար իրականացված չէ"
#: .\cookbook\views\api.py:2548
msgid ""
"The PDF Exporter is not enabled on this instance as it is still in an "
"experimental state."
msgstr ""
#: .\cookbook\views\api.py:2842
#, fuzzy
#| msgid "This feature is not available in the demo version!"
msgid "This feature is not yet available in the hosted version of tandoor!"
msgstr "Այս հատկությունը հասանելի չէ փորձնական տարբերակում։"
#: .\cookbook\views\api.py:2863
msgid "Sync successful!"
msgstr "Սինքրոնիզացիան հաջողված է:"
#: .\cookbook\views\api.py:2866
msgid "Error synchronizing with Storage"
msgstr "Պահոցի հետ սինքրոնիզացիայի սխալ"
#: .\cookbook\views\views.py:89
msgid "This feature is not available in the demo version!"
msgstr "Այս հատկությունը հասանելի չէ փորձնական տարբերակում։"
#: .\cookbook\views\views.py:110
msgid ""
"You have successfully created your own recipe space. Start by adding some "
"recipes or invite other people to join you."
msgstr ""
#: .\cookbook\views\views.py:171
#, python-format
msgid "PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!"
msgstr ""
#: .\cookbook\views\views.py:174
#, python-format
msgid "You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended"
msgstr ""
#: .\cookbook\views\views.py:178
msgid "Unable to determine PostgreSQL version."
msgstr ""
#: .\cookbook\views\views.py:182
#, fuzzy
#| msgid ""
#| "\n"
#| " This application is not running with a Postgres database "
#| "backend. This is ok but not recommended as some\n"
#| " features only work with postgres databases.\n"
#| " "
msgid ""
"This application is not running with a Postgres database backend. This is ok "
"but not recommended as some features only work with postgres databases."
msgstr ""
"\n"
" Այս ծրագիրը չի աշխատում Postgres շտեմարան բեքենդով։ Դա նորմալ է, "
"բայց խորհուրդ չի տրվում որովհետև որոշ\n"
" հատկություններ աշխատում են միայն postgres շտեմարանների հետ։\n"
" "
#: .\cookbook\views\views.py:296
#, fuzzy
#| msgid ""
#| "The setup page can only be used to create the first user! If you have "
#| "forgotten your superuser credentials please consult the django "
#| "documentation on how to reset passwords."
msgid ""
"The setup page can only be used to create the first "
"user! If you have forgotten your superuser credentials "
"please consult the django documentation on how to reset passwords."
msgstr ""
"Կարգավորման էջը կարող է օգտագործվել միայն առաջին օգտագործողին ստեղծելու "
"համար։ Եթե մոռացել եք ձեր սուպեր-օգտատերի գաղտնաբառը, խնդրում ենք ստուգել "
"django-ի փաստաթղթերը գաղտնաբառը վերականգնելու ցուցումների համար։"
#: .\cookbook\views\views.py:304
msgid "Passwords dont match!"
msgstr "Գաղտնաբառերը չեն համընկնում:"
#: .\cookbook\views\views.py:312
msgid "User has been created, please login!"
msgstr "Օգտատերը ստեղծված է, խնդրում ենք մուտք գործել։"
#: .\cookbook\views\views.py:352
msgid ""
"Reporting share links is not enabled for this instance. Please notify the "
"page administrator to report problems."
msgstr ""
#: .\cookbook\views\views.py:357
msgid ""
"Recipe sharing link has been disabled! For additional information please "
"contact the page administrator."
msgstr ""
#: .\cookbook\views\views.py:383
msgid "Manage recipes, shopping list, meal plans and more."
msgstr ""
#: .\cookbook\views\views.py:397 .\cookbook\views\views.py:398
msgid "Plan"
msgstr "Պլան"
#: .\cookbook\views\views.py:399
msgid "View your meal Plan"
msgstr ""
#: .\cookbook\views\views.py:418
#, fuzzy
#| msgid "Shopping Lists"
msgid "View your shopping lists"
msgstr "Գնումների ցուցակներ"
#~ msgid ""
#~ "Both fields are optional. If none are given the username will be "
#~ "displayed instead"
#~ msgstr ""
#~ "Երկու դաշտն էլ կամավոր են։ Դատարկ լինելու դեպքում օգտվողի անունը "
#~ "կցուցադրվի փոխարենը"
#~ msgid "Name"
#~ msgstr "Անվանում"
#~ msgid "Keywords"
#~ msgstr "Բանալի բառեր"
#~ msgid "Preparation time in minutes"
#~ msgstr "Պատրաստման տևողությունը րոպեներով"
#~ msgid "Waiting time (cooking/baking) in minutes"
#~ msgstr "Սպասման տևողությունը (եփել/թխել) րոպեներով"
#~ msgid "Path"
#~ msgstr "Ուղի"
#~ msgid "Storage UID"
#~ msgstr "Պահոցի UID"
#~ msgid "Add your comment: "
#~ msgstr "Ավելացրեք ձեր մեկնաբանությունը՝ "
#~ msgid "Leave empty for dropbox and enter app password for nextcloud."
#~ msgstr ""
#~ "Թողնել դատարկ dropbox-ի համար և մուտքագրել ծրագրի գաղտնաբառը nextcloud-ի "
#~ "համար։"
#~ msgid "Leave empty for nextcloud and enter api token for dropbox."
#~ msgstr ""
#~ "Թողնել դատարկ nextcloud-ի համար և մուտքագրել ծրագրի ժետոնը dropbox-ի "
#~ "համար։"
#~ msgid ""
#~ "Leave empty for dropbox and enter only base url for nextcloud (/"
#~ "remote.php/webdav/ is added automatically)"
#~ msgstr ""
#~ "Թողնել դատարկ dropbox-ի համար և մուտքագրել միայն հիմքային հղումը "
#~ "nextcloud-ի համար (/remote.php/webdav/ ինքնաբերաբար "
#~ "ավելացվում է)"
#~ msgid "Storage"
#~ msgstr "Պահոց"
#~ msgid "Search String"
#~ msgstr "Փնտրել շարքը"
#~ msgid "File ID"
#~ msgstr "Ֆայլի ID"
#, fuzzy
#~| msgid "Search"
#~ msgid "Search Method"
#~ msgstr "Փնտրել"
#, fuzzy
#~| msgid "Search"
#~ msgid "Fuzzy Search"
#~ msgstr "Փնտրել"
#, fuzzy
#~| msgid "Text"
#~ msgid "Full Text"
#~ msgstr "Տեքստ"
#~ msgid "Delete"
#~ msgstr "Ջնջել"
#~ msgid "Settings"
#~ msgstr "Կարգավորումներ"
#, fuzzy
#~| msgid "Password Reset"
#~ msgid "Password"
#~ msgstr "Գաղտնաբառի վերականգնում"
#, fuzzy
#~| msgid "Food"
#~ msgid "Foods"
#~ msgstr "Սննդամթերք"
#~ msgid "Units"
#~ msgstr "Միավորներ"
#~ msgid "Supermarket"
#~ msgstr "Սուպերմարկետ"
#, fuzzy
#~| msgid "Supermarket"
#~ msgid "Supermarket Category"
#~ msgstr "Սուպերմարկետ"
#, fuzzy
#~| msgid "Information"
#~ msgid "Automations"
#~ msgstr "Տեղեկություն"
#, fuzzy
#~| msgid "File ID"
#~ msgid "Files"
#~ msgstr "Ֆայլի ID"
#~ msgid "Batch Edit"
#~ msgstr "Խմբային խմբագրում"
#~ msgid "History"
#~ msgstr "Պատմություն"
#, fuzzy
#~| msgid "Ingredients"
#~ msgid "Ingredient Editor"
#~ msgstr "Բաղադրիչներ"
#, fuzzy
#~| msgid "Account Connections"
#~ msgid "Unit Conversions"
#~ msgstr "Հաշվի կապեր"
#~ msgid "Create"
#~ msgstr "Ստեղծել"
#~ msgid "External Recipes"
#~ msgstr "Արտաքին բաղադրատոմսեր"
#, fuzzy
#~| msgid "Settings"
#~ msgid "Space Settings"
#~ msgstr "Կարգավորումներ"
#, fuzzy
#~| msgid "External Recipes"
#~ msgid "External Connectors"
#~ msgstr "Արտաքին բաղադրատոմսեր"
#~ msgid "Admin"
#~ msgstr "Ադմինիստրատոր"
#~ msgid "Markdown Guide"
#~ msgstr "Markdown-ի ուղեցույց"
#~ msgid "GitHub"
#~ msgstr "GitHub"
#~ msgid "API Browser"
#~ msgstr "API բրաուզեր"
#~ msgid "Batch edit Category"
#~ msgstr "Կատեգորիաների խմբային խմբագրում"
#~ msgid "Batch edit Recipes"
#~ msgstr "Բաղադրատոմսերի խմբային խմբագրում"
#~ msgid "Add the specified keywords to all recipes containing a word"
#~ msgstr ""
#~ "Ավելացնել նշված բանալի բառերը բոլոր բաղադրատոմսերին, որոնք պարունակում են "
#~ "բառ"
#~ msgid "Sync"
#~ msgstr "Սինքրոնիզացնել"
#~ msgid "Manage watched Folders"
#~ msgstr "Կարգավորել դիտվող թղթապանակները"
#~ msgid ""
#~ "On this Page you can manage all storage folder locations that should be "
#~ "monitored and synced."
#~ msgstr ""
#~ "Այս էջում կարող եք կարգավորել այն պահպաման թղթապանակները, որոնք պետք է "
#~ "վերահսկվեն և սինքրոնիզացվեն։"
#~ msgid "The path must be in the following format"
#~ msgstr "Ուղին պետք է լինի հետևյալ ձևաչափով՝"
#~ msgid "Save"
#~ msgstr "Պահպանել"
#~ msgid "Sync Now!"
#~ msgstr "Սինքրոնիզացնել հիմա։"
#, fuzzy
#~| msgid "Shopping Recipes"
#~ msgid "Show Recipes"
#~ msgstr "Գնումների բաղադրատոմսեր"
#, fuzzy
#~| msgid "Show Links"
#~ msgid "Show Log"
#~ msgstr "Ցուցադրել հղումները"
#~ msgid "Importing Recipes"
#~ msgstr "Բաղադրատոմսերի ներմուծում"
#~ msgid ""
#~ "This can take a few minutes, depending on the number of recipes in sync, "
#~ "please wait."
#~ msgstr ""
#~ "Կախված սինքրոնիզացվող բաղադրատոմսերի քանակից, պրոցեսը կարող է տևել մի "
#~ "քանի րոպե, խնդրում ենք սպասել։"
#~ msgid "Recipe Books"
#~ msgstr "Բաղադրատոմսերի գիրք"
#~ msgid "Import new Recipe"
#~ msgstr "Ներմուծել նոր բաղադրատոմս"
#~ msgid "Edit Recipe"
#~ msgstr "Խմբագրել բաղադրատոմսը"
#, python-format
#~ msgid "Are you sure you want to delete the %(title)s: %(object)s "
#~ msgstr "Համոզվա՞ծ եք, որ ուզում եք ջնջել %(title)s: %(object)s "
#~ msgid "Edit"
#~ msgstr "Խմբագրել"
#~ msgid "View"
#~ msgstr "Դիտել"
#~ msgid "Delete original file"
#~ msgstr "Ջնջել բնօրինակ ֆայլը"
#~ msgid "List"
#~ msgstr "Ցուցակ"
#~ msgid "Filter"
#~ msgstr "Ֆիլտր"
#~ msgid "Import all"
#~ msgstr "Ներմուծել բոլորը"
#~ msgid "New"
#~ msgstr "Նոր"
#~ msgid "previous"
#~ msgstr "նախորդ"
#~ msgid "next"
#~ msgstr "հաջորդ"
#~ msgid "View Log"
#~ msgstr "Դիտումների մատյան"
#~ msgid "Cook Log"
#~ msgstr "Եփելու մատյան"
#~ msgid "Import"
#~ msgstr "Ներմուծել"
#~ msgid "Security Warning"
#~ msgstr "Անվտանգության զգուշացում"
#~ msgid ""
#~ "\n"
#~ " The Password and Token field are stored as plain text"
#~ "b> inside the database.\n"
#~ " This is necessary because they are needed to make API requests, "
#~ "but it also increases the risk of\n"
#~ " someone stealing it.
\n"
#~ " To limit the possible damage tokens or accounts with limited "
#~ "access can be used.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Գաղտնաբառ և ժետոն դաշտերը պահպանվում են որպես հասարակ "
#~ "տեքստ շտեմարանի մեջ։\n"
#~ " Սա անհրաժեշտ է, որովհետև դրանք օգտագործվում են API հարցումների "
#~ "համար, բայց դա նաև ավելացնում է ռիսկը, որ \n"
#~ " ինչ-որ մեկը կգօողանա դրանք։
\n"
#~ " Վնասը սահմանափակելու համար կարող են կիրառվել սահմանափակ "
#~ "թույլտվությամբ ժետոններ կամ հաշիվներ։\n"
#~ " "
#~ msgid "You are currently offline!"
#~ msgstr "Դուք ցանցից դուրս եք։"
#~ msgid ""
#~ "The recipes listed below are available for offline viewing because you "
#~ "have recently viewed them. Keep in mind that data might be outdated."
#~ msgstr ""
#~ "Ներքևում նշված բաղադրատոմսերը հասանելի են ցանցից դուրս դիտման համար, "
#~ "որովհետև դուք դիտել եք դրանք վերջերս։ Հիշեք, որ տվյալները կարող են հնացած "
#~ "լինել։"
#~ msgid "Comments"
#~ msgstr "Մեկնաբանություններ"
#~ msgid "by"
#~ msgstr "Հեղինակ"
#~ msgid "Comment"
#~ msgstr "Մեկնաբանել"
#, fuzzy
#~| msgid "Social Login"
#~ msgid "Social"
#~ msgstr "Մուտք Սոցիալական էջով"
#, fuzzy
#~| msgid "Description"
#~ msgid "Manage Subscription"
#~ msgstr "Նկարագրություն"
#~ msgid "URL Import"
#~ msgstr "URL ներմուծում"
#, python-format
#~ msgid "Batch edit done. %(count)d recipe was updated."
#~ msgid_plural "Batch edit done. %(count)d Recipes where updated."
#~ msgstr[0] ""
#~ "Խմբային խմբագրումն ավարտված է։ %(count)d բաղադրատոմս թարմացված է:"
#~ msgstr[1] ""
#~ "Խմբային խմբագրումն ավարտված է։ %(count)d բաղադրատոմսեր թարմացված են։"
#~ msgid "Monitor"
#~ msgstr "Վերահսկել"
#~ msgid "Storage Backend"
#~ msgstr "Պահոցի բեքենդ"
#~ msgid ""
#~ "Could not delete this storage backend as it is used in at least one "
#~ "monitor."
#~ msgstr "Չենք կարող ջնջել պահոցի այս բեքենդը, քանի որ այն օգտագործվում է։"
#, fuzzy
#~| msgid "Storage Backend"
#~ msgid "Connectors Config Backend"
#~ msgstr "Պահոցի բեքենդ"
#~ msgid "Invite Link"
#~ msgstr "Հրավերի հղում"
#~ msgid "You cannot edit this storage!"
#~ msgstr "Դուք կարող եք խմբագրել այս պահոցը։"
#~ msgid "Storage saved!"
#~ msgstr "Պահոցը պահպանված է։"
#~ msgid "There was an error updating this storage backend!"
#~ msgstr "Պահոցի այս բեքեբդի թարմացման ժամանակ սխալ է գրանցվել։"
#, fuzzy
#~| msgid "Changes saved!"
#~ msgid "Config saved!"
#~ msgstr "Փոփոխությունները պահպանված են:"
#~ msgid "Changes saved!"
#~ msgstr "Փոփոխությունները պահպանված են:"
#~ msgid "Error saving changes!"
#~ msgstr "Փոփոխությունների պահպանման սխալ:"
#~ msgid "Import Log"
#~ msgstr "Ներմուծման մատյան"
#~ msgid "Discovery"
#~ msgstr "Բացահայտել"
#~ msgid "Shopping List"
#~ msgstr "Գնումների ցուցակ"
#, fuzzy
#~| msgid "Storage Backend"
#~ msgid "Connector Config Backend"
#~ msgstr "Պահոցի բեքենդ"
#~ msgid "Invite Links"
#~ msgstr "Հրավերի հղումներ"
#, fuzzy
#~| msgid "Supermarket"
#~ msgid "Supermarkets"
#~ msgstr "Սուպերմարկետ"
#, fuzzy
#~| msgid "Shopping Recipes"
#~ msgid "Shopping Categories"
#~ msgstr "Գնումների բաղադրատոմսեր"
#, fuzzy
#~| msgid "Filter"
#~ msgid "Custom Filters"
#~ msgstr "Ֆիլտր"
#~ msgid "Steps"
#~ msgstr "Քայլեր"
#, fuzzy
#~| msgid "This feature is not available in the demo version!"
#~ msgid "This feature is not enabled by the server admin!"
#~ msgstr "Այս հատկությունը հասանելի չէ փորձնական տարբերակում։"
#~ msgid "Imported new recipe!"
#~ msgstr "Բաղադրատոմսը ներմուծված է:"
#~ msgid "There was an error importing this recipe!"
#~ msgstr "Այս բաղադրատոմսի ներմուծման ժամանակ սխալ է գրանցվել։"
#~ msgid "You do not have the required permissions to perform this action!"
#~ msgstr "Դուք չունեք բավարար թույլտվություն այս գործողության համար։"
#~ msgid "Comment saved!"
#~ msgstr "Մեկնաբանությունը պահպանված է:"
#~ msgid "Malformed Invite Link supplied!"
#~ msgstr "Հրավերի արատավոր հղում է տրամադրվել։"
#~ msgid "Invite Link not valid or already used!"
#~ msgstr "Հրավերի հղումը վավեր չէ, կամ արդեն օգտագործվել է:"
#~ msgid ""
#~ "Color of the top navigation bar. Not all colors work with all themes, "
#~ "just try them out!"
#~ msgstr ""
#~ "Վերին վահանակի գույնը։ Ոչ բոլոր գույներն են աշխատում բոլոր թեմաների հետ, "
#~ "պարզապես փորձեք։"
#~ msgid ""
#~ "Default Unit to be used when inserting a new ingredient into a recipe."
#~ msgstr "Նոր բաղադրիչ ավելացնելիս չափի լռելյայն միավորը։"
#~ msgid ""
#~ "Enables support for fractions in ingredient amounts (e.g. convert "
#~ "decimals to fractions automatically)"
#~ msgstr ""
#~ "Ակտիվացնել բաղադրիչների քանակի համար կոտորակների աջակցությունը "
#~ "(փոխակերպել տասնորդականները կոտորակների)"
#~ msgid ""
#~ "Users with whom newly created meal plan/shopping list entries should be "
#~ "shared by default."
#~ msgstr ""
#~ "Օգտատերեր, ում հետ նոր ստեղծված ճաշացուցակները/գնումների ցուցակները պետք "
#~ "է կիսվեն լռելյայն:"
#~ msgid "Show recently viewed recipes on search page."
#~ msgstr "Ցույց տալ վերջերս դիտած բաղադրատոմսերը փնտրման էջում։"
#~ msgid "Number of decimals to round ingredients."
#~ msgstr "Բաղադրիչների կլորացման համար տասնորդականների քանակը:"
#~ msgid ""
#~ "If you want to be able to create and see comments underneath recipes."
#~ msgstr ""
#~ "Եթե ցանկանում եք կարողանալ ավելացնել և տեսնել մեկնաբանություններ "
#~ "բաղադրատոմսերի ներքևում։"
#~ msgid ""
#~ "Setting to 0 will disable auto sync. When viewing a shopping list the "
#~ "list is updated every set seconds to sync changes someone else might have "
#~ "made. Useful when shopping with multiple people but might use a little "
#~ "bit of mobile data. If lower than instance limit it is reset when saving."
#~ msgstr ""
#~ "0-ն կանջատի ավտոմատ սինքրոնացումը։ Գնումների ցուցակը թարմացվում է "
#~ "յուրաքանչյուր սահմանված վարկյանը մեկ, ուրիշի կատարած փոփոխությունները "
#~ "սինքրոնացնելու համար։ Հարմար է, երբ մեկից ավել մարդ է կատարում գնումները, "
#~ "բայց կարող է օգտագործել բջջային ինտերնետ։"
#~ msgid "Makes the navbar stick to the top of the page."
#~ msgstr "Կցել նավիգացիոն տողը էջի վերևում:"
#~ msgid "Number of servings"
#~ msgstr "Չափաբաժինների քանակը"
#~ msgid ""
#~ "Include - [ ] in list for easier usage in markdown based "
#~ "documents."
#~ msgstr ""
#~ "Ներառել - [ ] ցուցակում markdown փաստաթղթերում հեշտ կիրառման "
#~ "համար։"
#~ msgid "New unit that other gets replaced by."
#~ msgstr "Նոր միավոր հները փոխարինելու համար։"
#~ msgid "Old Unit"
#~ msgstr "Հին միավոր"
#~ msgid "Unit that should be replaced."
#~ msgstr "Փոխարինման ենթակա միավոր:"
#~ msgid "New Food"
#~ msgstr "Նոր սննդամթերք"
#~ msgid "New food that other gets replaced by."
#~ msgstr "Նոր սննդամթերք, որով փոխարինվում է հինը։"
#~ msgid "Old Food"
#~ msgstr "Հին սննդամթերք"
#~ msgid "Food that should be replaced."
#~ msgstr "Փոխարինման ենթակա սննդամթերք։"
#~ msgid "You must provide at least a recipe or a title."
#~ msgstr "Դուք պետք է տրամադրեք առնվազն բաղադրատոմս կամ վերնագիր:"
#~ msgid "You can list default users to share recipes with in the settings."
#~ msgstr ""
#~ "Դուք կարող եք կարգավորումներում ավելացնել այն օգտատերերին, ում հետ "
#~ "բաղադրատոմսերը պետք է կիսվեն լռելյայն:"
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "Դուք կարող եք օգտագործել markdown-ն այս դաշտը ձևավորելու համար. Տեսեք փաստաթղթերն այստեղ"
#~ msgid ""
#~ "A username is not required, if left blank the new user can choose one."
#~ msgstr ""
#~ "Օգտատերի անուն պարտադիր չէ, դատարկ թողնելու դեպքում նոր օգտատերը կարող է "
#~ "անձամբ ընտրել։"
#~ msgid "The requested site provided malformed data and cannot be read."
#~ msgstr ""
#~ "Հարցված կայքը տրամադրեց վատ ձևավորված տվյալներ և չի կարող կարդացվել։"
#~ msgid ""
#~ "The requested site does not provide any recognized data format to import "
#~ "the recipe from."
#~ msgstr ""
#~ "Հարցված կայքը չի տրամադրում որևէ ճանաչելի տվյալ բաղադրատոմսը ներմուծելու "
#~ "համար։"
#~ msgid "Small"
#~ msgstr "Փոքր"
#~ msgid "Large"
#~ msgstr "Մեծ"
#~ msgid "Time"
#~ msgstr "Ժամանակ"
#~ msgid "Link"
#~ msgstr "Հղում"
#~ msgid "Utensils"
#~ msgstr "Գործիքակազմ"
#~ msgid "Storage Data"
#~ msgstr "Պահոցի տվյալներ"
#~ msgid "Storage Backends"
#~ msgstr "Պահոցի բեքենդեր"
#~ msgid "Configure Sync"
#~ msgstr "Կարգավորել սինքրոնիզացիան"
#~ msgid "Discovered Recipes"
#~ msgstr "Հայտնաբերված բաղադրատոմսեր"
#~ msgid "Discovery Log"
#~ msgstr "Բացահայտումների մատյան"
#~ msgid "Statistics"
#~ msgstr "Վիճակագրություն"
#~ msgid "Units & Ingredients"
#~ msgstr "Միավորներ և բաղադրիչներ"
#~ msgid "Logout"
#~ msgstr "Դուրս գալ"
#~ msgid "New Book"
#~ msgstr "Նոր գիրք"
#~ msgid "Toggle Recipes"
#~ msgstr "Փոխանջատել Բաղադրատոմսերը"
#~ msgid "There are no recipes in this book yet."
#~ msgstr "Այս գրքում բաղադրատոմսեր դեռ չկան։"
#~ msgid "Waiting Time"
#~ msgstr "Սպասման տևողություն"
#~ msgid "Servings Text"
#~ msgstr "Չափաբաժինների տեքստ"
#~ msgid "Select Keywords"
#~ msgstr "Ընտրել բանալի բառեր"
#~ msgid "Delete Step"
#~ msgstr "Ջնջել քայլը"
#~ msgid "Step"
#~ msgstr "Քայլ"
#~ msgid "Show as header"
#~ msgstr "Ցույց տալ որպես խորագիր"
#~ msgid "Hide as header"
#~ msgstr "Թաքցնել որպես խորագիր"
#~ msgid "Move Up"
#~ msgstr "Բարձրացնել"
#~ msgid "Move Down"
#~ msgstr "Իջեցնել"
#~ msgid "Step Name"
#~ msgstr "Քայլի անվանում"
#~ msgid "Step Type"
#~ msgstr "Քայլի տեսակ"
#~ msgid "Step time in Minutes"
#~ msgstr "Քայլի տևողությունը րոպեներով"
#~ msgid "Select Unit"
#~ msgstr "Ընտրել միավորը"
#~ msgid "Select"
#~ msgstr "Ընտրել"
#~ msgid "Select Food"
#~ msgstr "Ընտրել սննդամթերք"
#~ msgid "Delete Ingredient"
#~ msgstr "Ջնջել բաղադրիչը"
#~ msgid "Make Ingredient"
#~ msgstr "Ստեղծել բաղդրիչ"
#~ msgid "Disable Amount"
#~ msgstr "Անջատել քանակը"
#~ msgid "Enable Amount"
#~ msgstr "Միացնել քանակը"
#~ msgid "Copy Template Reference"
#~ msgstr "Պատճենել ձևանմուշի հղումը"
#~ msgid "Save & View"
#~ msgstr "Պահպանել և Դիտել"
#~ msgid "Add Step"
#~ msgstr "Ավելացնել քայլ"
#~ msgid "Add Nutrition"
#~ msgstr "Ավելացնել Սննդայնություն"
#~ msgid "Remove Nutrition"
#~ msgstr "Հեռացնել Սննդայնություն"
#~ msgid "View Recipe"
#~ msgstr "Դիտել բաղադրատոմսը"
#~ msgid "Delete Recipe"
#~ msgstr "Ջնջել բաղադրատոմսը"
#~ msgid "Edit Ingredients"
#~ msgstr "Խմբագրել բաղադրիչները"
#~ msgid ""
#~ "\n"
#~ " The following form can be used if, accidentally, two (or more) "
#~ "units or ingredients where created that should be\n"
#~ " the same.\n"
#~ " It merges two units or ingredients and updates all recipes using "
#~ "them.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Հետևյալ բլանկը կարող է օգտագործվել, եթե երկու (կամ ավելի) "
#~ "միավորներ կամ բաղադրիչներ ստեղծվել են սխալմամբ, սակայն պետք է լինեն \n"
#~ " նույնը։\n"
#~ " Սա միավորում է երկու միավորները կամ բաղադրիչները և թարմացնում "
#~ "դրանք օգտագործող բոլոր բաղադրատոմսերը։ \n"
#~ " "
#~ msgid "Are you sure that you want to merge these two units?"
#~ msgstr "Համոզվա՞ծ եք, որ ցանկանում եք միավորել այս երկու միավորները։"
#~ msgid "Are you sure that you want to merge these two ingredients?"
#~ msgstr "Համոզվա՞ծ եք, որ ցանկանում եք միավորել այս երկու բաղադրիչները։"
#~ msgid "Import Recipes"
#~ msgstr "Ներմուծել բաղադրատոմսերը"
#~ msgid "Log Recipe Cooking"
#~ msgstr "Գրանցել բաղադրատոմսի օգտագործում"
#~ msgid "All fields are optional and can be left empty."
#~ msgstr "Բոլոր դաշտերը կամավոր են և կարող են դատարկ թողնվել:"
#~ msgid "Rating"
#~ msgstr "Վարկանիշ"
#~ msgid "Close"
#~ msgstr "Փակել"
#~ msgid "Open Recipe"
#~ msgstr "Բացել բաղադրատոմսը"
#~ msgid "Website Import"
#~ msgstr "Ներմուծում վեբկայքից"
#~ msgid "New Entry"
#~ msgstr "Նոր գրառում"
#~ msgid "Title"
#~ msgstr "Վերնագիր"
#~ msgid "Note (optional)"
#~ msgstr "Նոթեր (կամավոր)"
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "Դուք կարող եք օգտագործել markdown-ն այս դաշտը ձևավորելու համար. Տեսեք փաստաթղթերն այստեղ"
#~ msgid "Serving Count"
#~ msgstr "Չափաբաժինների քանակ"
#~ msgid "Create only note"
#~ msgstr "Ստեղծել միայն նոթեր"
#~ msgid "Shopping list currently empty"
#~ msgstr "Գնումների ցուցակը դատարկ է"
#~ msgid "Open Shopping List"
#~ msgstr "Բացել գնումների ցուցակը"
#~ msgid "Number of Days"
#~ msgstr "Օրերի քանակ"
#~ msgid "Weekday offset"
#~ msgstr "Աշխատանքային օրերի փոխհատուցում"
#~ msgid ""
#~ "Number of days starting from the first day of the week to offset the "
#~ "default view."
#~ msgstr ""
#~ "Շաբաթվա առաջին օրվանից հաշված օրերի քանակը, որը պետք է փոխհատուցել "
#~ "լռելյայն էջում:"
#~ msgid "Edit plan types"
#~ msgstr "Խմբագրել ճաշացուցակների տեսակները"
#~ msgid "Week iCal export"
#~ msgstr "Շաբաթվա արտահանում iCal ձևաչափով"
#~ msgid "Created by"
#~ msgstr "Ստեղծող"
#~ msgid "Shared with"
#~ msgstr "Ու՞մ հետ է կիսվել"
#~ msgid "Add to Shopping"
#~ msgstr "Ավելացնել գնումներին"
#~ msgid "New meal type"
#~ msgstr "Կերակրի նոր տեսակ"
#~ msgid "Meal Plan Help"
#~ msgstr "Ճաշացուցակի Օգնություն"
#~ msgid ""
#~ "\n"
#~ " The meal plan module allows planning of "
#~ "meals both with recipes and notes.
\n"
#~ " Simply select a recipe from the list of "
#~ "recently viewed recipes or search the one you\n"
#~ " want and drag it to the desired plan "
#~ "position. You can also add a note and a title and\n"
#~ " then drag the recipe to create a plan "
#~ "entry with a custom title and note. Creating only\n"
#~ " Notes is possible by dragging the create "
#~ "note box into the plan.
\n"
#~ " Click on a recipe in order to open the "
#~ "detailed view. There you can also add it to the\n"
#~ " shopping list. You can also add all "
#~ "recipes of a day to the shopping list by\n"
#~ " clicking the shopping cart at the top of "
#~ "the table.
\n"
#~ " Since a common use case is to plan meals "
#~ "together you can define\n"
#~ " users you want to share your plan with in "
#~ "the settings.\n"
#~ "
\n"
#~ " You can also edit the types of meals you "
#~ "want to plan. If you share your plan with\n"
#~ " someone with\n"
#~ " different meals, their meal types will "
#~ "appear in your list as well. To prevent\n"
#~ " duplicates (e.g. Other and Misc.)\n"
#~ " name your meal types the same as the "
#~ "users you share your meals with and they will be\n"
#~ " merged.
\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Ճաշացուցակի մոդուլը թույլ է տալիս "
#~ "պլանավորել ճաշերը բաղադրատոմսերով և նոթերով։
\n"
#~ " Պարզապես ընտրեք բաղադրատոմս վերջերս դիտած "
#~ "բաղադրատոմսերի ցուցակից կամ փնտրեք այն բաղադրատոմսը,\n"
#~ " որն ուզում եք և քաշեք ընտրված ճաշացուցակի "
#~ "դիրք։ Դուք կարող եք նաև ավելացնել նոթեր և վերնագիր, ապա\n"
#~ " քաշեք բաղադրատոմսը ձեր ընտրած վերնագրով և "
#~ "նոթերով ցուցակ ստեղծելու համար։ Միայն\n"
#~ " նոթեր ստեղծելը նույնպես հնարավոր է, եթե "
#~ "քաշեք «ստեղծել միայն նոթեր» պատուհանը դեպի ցուցակ։
\n"
#~ " Սեղմեք որևէ բաղադրատոմսի վրա մանրամասներով "
#~ "պատուհանը բացելու համար։ Այդտեղից կարող եք այն նաև ավելացնել\n"
#~ " գնումների ցուցակ։ Դուք կարող եք նաև "
#~ "ավելացնել օրվա բոլոր բաղադրատոմսերը գնումների ցուցակ\n"
#~ " սեղմելով գնումների սայլակի նշանի վրա։"
#~ "p>\n"
#~ "
Քանի որ ընդունված է ճաշեր պլանավորել "
#~ "միասին, դուք կարող եք կարգավորումներում հստակեցնել\n"
#~ " օգտատերերին ում հետ ցանկանում եք կիսվել "
#~ "ձեր ցուցակով։\n"
#~ "
\n"
#~ " Դուք կարող եք նաև խմբագրել ճաշերի տեսակը, "
#~ "որը ցանկանում եք պլանավորել։ Եթե կիսվեք ձեր պլանով\n"
#~ " որևէ մեկի հետ, ով\n"
#~ " ունի ուրիշ ճաշեր, նրանց ճաշերի տեսակները "
#~ "նույնպես կհայտնվեն ձեր ցուցակում։ Կրկնօրինակներից (օրինակ ընթրիք և "
#~ "ընթրիքներ)\n"
#~ " խուսաբելու համար\n"
#~ " օգտագործեք միևնույն ճաշի տեսակների անունն "
#~ "այն մարդկանց պես, ում հետ ցանկանում եք կիսվել և դրանք\n"
#~ " կմիավորվեն։
\n"
#~ " "
#~ msgid "Meal Plan View"
#~ msgstr "Ճաշացուցակի Դիտման էջ"
#~ msgid "Never cooked before."
#~ msgstr "Երբեք պատրաստված չէ:"
#~ msgid "Other meals on this day"
#~ msgstr "Նույն օրվա այլ ճաշեր"
#~ msgid "Recipe Image"
#~ msgstr "Բաղադրատոմսի նկար"
#~ msgid "Preparation time ca."
#~ msgstr "Պատրաստման տևողություն"
#~ msgid "Waiting time ca."
#~ msgstr "Սպասման տևողություն"
#~ msgid "External"
#~ msgstr "Արտաքին"
#~ msgid "Log Cooking"
#~ msgstr "Գրանցել եփել"
#~ msgid "Account"
#~ msgstr "Հաշիվ"
#~ msgid "Link social account"
#~ msgstr "Կցել սոցիալական հաշիվ"
#~ msgid "Language"
#~ msgstr "Լեզու"
#~ msgid "Style"
#~ msgstr "Ոճ"
#~ msgid ""
#~ "You can use both basic authentication and token based authentication to "
#~ "access the REST API."
#~ msgstr ""
#~ "Դուք կարող եք օգտագործել ինչպես հասարակ այնպես էլ ժետոնով նույնականացում "
#~ "REST API-ին հասանելիության համար։"
#~ msgid ""
#~ "Use the token as an Authorization header prefixed by the word token as "
#~ "shown in the following examples:"
#~ msgstr ""
#~ "Օգտագործեք ժետոնը որպես Թույլտվության խորագիր, նախածանցված բառ ժետոնով, "
#~ "ինչպես ցույց է տրված հետևյալ օրինակում․"
#~ msgid "or"
#~ msgstr "կամ"
#~ msgid "No recipes selected"
#~ msgstr "Բաղադրատոմս ընտրված չէ"
#~ msgid "Entry Mode"
#~ msgstr "Գրառման ռեժիմ"
#~ msgid "Add Entry"
#~ msgstr "Ավելացնել գրառում"
#~ msgid "Amount"
#~ msgstr "Քանակ"
#~ msgid "Select Supermarket"
#~ msgstr "Ընտրել Սուպերմարկետ"
#~ msgid "Select User"
#~ msgstr "Ընտրել օգտատեր"
#~ msgid "Finished"
#~ msgstr "Ավարտված է"
#~ msgid "You are offline, shopping list might not syncronize."
#~ msgstr "Դուք ցանցից դուրս եք, գնումների ցուցակը կարող է չսինքրոնիզացվել։"
#~ msgid "Copy/Export"
#~ msgstr "Պատճենել/Արտահանել"
#~ msgid "List Prefix"
#~ msgstr "Ցուցակի նախածանց"
#~ msgid "There was an error creating a resource!"
#~ msgstr "Ռեսուրսը ստեղծելիս սխալ է գրանցվել:"
#~ msgid "Stats"
#~ msgstr "Վիճակագրություն"
#~ msgid "Number of objects"
#~ msgstr "Օբյեկտների քանակը"
#~ msgid "Recipe Imports"
#~ msgstr "Բաղադրատոմսի ներմուծումներ"
#~ msgid "Objects stats"
#~ msgstr "Օբյեկտների վիճակագրություն"
#~ msgid "Recipes without Keywords"
#~ msgstr "Առանց բանալի բառերի բաղադրատոմսեր"
#~ msgid "Internal Recipes"
#~ msgstr "Ներքին բաղադրատոմսեր"
#~ msgid "Backup & Restore"
#~ msgstr "Կրկնօրինակում և վերականգնում"
#~ msgid "Download Backup"
#~ msgstr "Ներբեռնել կրկնօրինակը"
#~ msgid "Enter website URL"
#~ msgstr "Մուտքագրեք վեբկայքի URL-ը"
#~ msgid "Recipe Name"
#~ msgstr "Բաղադրատոմսի անուն"
#~ msgid "Select one"
#~ msgstr "Ընտրել մեկը"
#~ msgid "All Keywords"
#~ msgstr "Բոլոր բանալի բառերը"
#~ msgid "Import all keywords, not only the ones already existing."
#~ msgstr ""
#~ "Ներմուծել բոլոր բանալի բառերը, ոչ միայն արդեն գոյություն ունեցողները:"
#~ msgid ""
#~ " Only websites containing ld+json or microdata information can currently\n"
#~ " be imported. Most big recipe pages "
#~ "support this. If you site cannot be imported but\n"
#~ " you think\n"
#~ " it probably has some kind of "
#~ "structured data feel free to post an example in the\n"
#~ " github issues."
#~ msgstr ""
#~ " Ներկայումս միայն ld+json կամ microdata տեղեկություն պարունակող կայքերից "
#~ "կարելի է\n"
#~ " ներմուծել։ Բաղադրատոմսերի մեծ կայքերը "
#~ "հիմնականում պարունակում են դա։ Եթե ձեր կայքը հնարավոր չէ ներմուծել, բայց\n"
#~ " կարծում եք\n"
#~ " այն հավանաբար ունի ինչ-որ տեսակի "
#~ "կառուցվածքավորված տվյալ, կարող եք տեղեկացնել մեզ ստեղծելով\n"
#~ " github-ի խնդիր։"
#~ msgid "Google ld+json Info"
#~ msgstr "Google ld+json-ի տեղեկություն"
#~ msgid "GitHub Issues"
#~ msgstr "GitHub-ի խնդիրներ"
#~ msgid "Recipe Markup Specification"
#~ msgstr "Բաղադրատոմսի Markup բնութագրեր"
#~ msgid "Preference for given user already exists"
#~ msgstr "Այս օգտատերի նախապատվությունն արդեն գոյություն ունի"
#~ msgid ""
#~ "The requested page refused to provide any information (Status Code 403)."
#~ msgstr ""
#~ "Պահանջվող էջը մերժեց տրամադրել որևէ տեղեկություն (Կարգավիճակի ծածկագիր "
#~ "403):"
#~ msgid "Recipe Book"
#~ msgstr "Բաղադրատոմսի գիրք"
#~ msgid "Bookmarks"
#~ msgstr "Էջանիշեր"
#~ msgid "Units merged!"
#~ msgstr "Միավորները միավորված են:"
#~ msgid "Foods merged!"
#~ msgstr "Սննդամթերքները միավորված են:"
#~ msgid "Exporting is not implemented for this provider"
#~ msgstr "Արտահանումն այս պրովայդերի համար իրականացված չէ"
#~ msgid "This recipe is already linked to the book!"
#~ msgstr "Բաղադրատոմսն արդեն կապված է գրքին:"
#~ msgid "Bookmark saved!"
#~ msgstr "Էջանիշը պահպանված է:"
================================================
FILE: cookbook/locale/id/LC_MESSAGES/django.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-22 20:15+0200\n"
"PO-Revision-Date: 2022-10-12 08:33+0000\n"
"Last-Translator: wella \n"
"Language-Team: Indonesian \n"
"Language: id\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 4.10.1\n"
#: .\cookbook\forms.py:50
msgid "Default"
msgstr "Bawaan"
#: .\cookbook\forms.py:77
msgid ""
"To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything."
msgstr ""
"Untuk mencegah duplikat resep dengan nama yang sama dengan yang sudah ada "
"diabaikan. Centang kotak ini untuk mengimpor semuanya."
#: .\cookbook\forms.py:108
msgid "Maximum number of users for this space reached."
msgstr "Jumlah maksimum pengguna untuk ruang ini tercapai."
#: .\cookbook\forms.py:114
msgid "Email address already taken!"
msgstr "Alamat email sudah terpakai!"
#: .\cookbook\forms.py:121
msgid ""
"An email address is not required but if present the invite link will be sent "
"to the user."
msgstr ""
"Alamat email tidak diperlukan tetapi jika ada, tautan undangan akan dikirim "
"ke pengguna."
#: .\cookbook\forms.py:133
msgid "Name already taken."
msgstr "Nama sudah terpakai."
#: .\cookbook\forms.py:144 .\cookbook\forms.py:158
msgid "Accept Terms and Privacy"
msgstr "Terima Persyaratan dan Privasi"
#: .\cookbook\helper\AllAuthCustomAdapter.py:41
msgid ""
"In order to prevent spam, the requested email was not send. Please wait a "
"few minutes and try again."
msgstr ""
#: .\cookbook\helper\permission_helper.py:165
#: .\cookbook\helper\permission_helper.py:186 .\cookbook\views\views.py:138
msgid "You are not logged in and therefore cannot view this page!"
msgstr ""
#: .\cookbook\helper\permission_helper.py:168
#: .\cookbook\helper\permission_helper.py:173
#: .\cookbook\helper\permission_helper.py:198
#: .\cookbook\helper\permission_helper.py:265
#: .\cookbook\helper\permission_helper.py:279
#: .\cookbook\helper\permission_helper.py:290
#: .\cookbook\helper\permission_helper.py:301
#: .\cookbook\helper\permission_helper.py:317
#: .\cookbook\helper\permission_helper.py:343
#: .\cookbook\helper\permission_helper.py:359
msgid "You do not have the required permissions to view this page!"
msgstr ""
#: .\cookbook\helper\permission_helper.py:191
#: .\cookbook\helper\permission_helper.py:214
#: .\cookbook\helper\permission_helper.py:236
#: .\cookbook\helper\permission_helper.py:251
msgid "You cannot interact with this object as it is not owned by you!"
msgstr ""
#: .\cookbook\helper\permission_helper.py:420
msgid "You have reached the maximum number of recipes for your space."
msgstr ""
#: .\cookbook\helper\permission_helper.py:432
msgid "You have more users than allowed in your space."
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:319
#, fuzzy
#| msgid "Use fractions"
msgid "reverse rotation"
msgstr "Gunakan pecahan"
#: .\cookbook\helper\recipe_url_import.py:320
msgid "careful rotation"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:321
msgid "knead"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:322
msgid "thicken"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:323
msgid "warm up"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:324
msgid "ferment"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:325
msgid "slow cook"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:326
msgid "egg boiler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:327
msgid "kettle"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:328
msgid "blend"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:329
msgid "pre-clean"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:330
msgid "high temperature"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:331
msgid "rice cooker"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:332
msgid "caramelize"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:333
msgid "peeler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:334
msgid "slicer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:335
msgid "grater"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:336
msgid "spiralizer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:337
msgid "sous-vide"
msgstr ""
#: .\cookbook\helper\shopping_helper.py:150
msgid "You must supply a servings size"
msgstr ""
#: .\cookbook\helper\template_helper.py:97
#: .\cookbook\helper\template_helper.py:99
#: .\cookbook\helper\template_helper.py:101
msgid "Could not parse template code."
msgstr ""
#: .\cookbook\integration\copymethat.py:44
#: .\cookbook\integration\melarecipes.py:37
msgid "Favorite"
msgstr ""
#: .\cookbook\integration\copymethat.py:50
msgid "I made this"
msgstr ""
#: .\cookbook\integration\integration.py:238
msgid ""
"Importer expected a .zip file. Did you choose the correct importer type for "
"your data ?"
msgstr ""
#: .\cookbook\integration\integration.py:241
msgid ""
"An unexpected error occurred during the import. Please make sure you have "
"uploaded a valid file."
msgstr ""
#: .\cookbook\integration\integration.py:246
msgid "The following recipes were ignored because they already existed:"
msgstr ""
#: .\cookbook\integration\integration.py:250
#, python-format
msgid "Imported %s recipes."
msgstr ""
#: .\cookbook\integration\mealie1.py:210
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "Calories"
msgstr ""
#: .\cookbook\integration\mealie1.py:211
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
msgid "Carbohydrates"
msgstr ""
#: .\cookbook\integration\mealie1.py:212
msgid "Cholesterol"
msgstr ""
#: .\cookbook\integration\mealie1.py:213
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
msgid "Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:214
msgid "Fiber"
msgstr ""
#: .\cookbook\integration\mealie1.py:215
msgid "Protein"
msgstr ""
#: .\cookbook\integration\mealie1.py:216
msgid "Saturated Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:217
msgid "Sodium"
msgstr ""
#: .\cookbook\integration\mealie1.py:218
msgid "Sugar"
msgstr ""
#: .\cookbook\integration\mealie1.py:219
msgid "Trans Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:220
msgid "Unsaturated Fat"
msgstr ""
#: .\cookbook\integration\openeats.py:28
msgid "Recipe source:"
msgstr ""
#: .\cookbook\integration\paprika.py:49
msgid "Notes"
msgstr ""
#: .\cookbook\integration\paprika.py:52
msgid "Nutritional Information"
msgstr ""
#: .\cookbook\integration\paprika.py:56
msgid "Source"
msgstr ""
#: .\cookbook\integration\recettetek.py:55
#: .\cookbook\integration\recipekeeper.py:70
msgid "Imported from"
msgstr ""
#: .\cookbook\integration\saffron.py:23
msgid "Servings"
msgstr ""
#: .\cookbook\integration\saffron.py:25
msgid "Waiting time"
msgstr ""
#: .\cookbook\integration\saffron.py:27
msgid "Preparation Time"
msgstr ""
#: .\cookbook\integration\saffron.py:29 .\cookbook\templates\index.html:6
msgid "Cookbook"
msgstr ""
#: .\cookbook\integration\saffron.py:31
msgid "Section"
msgstr ""
#: .\cookbook\management\commands\fix_duplicate_properties.py:15
msgid "Fixes foods with "
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:14
msgid "Rebuilds full text search index on Recipe"
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:18
msgid "Only Postgresql databases use full text search, no index to rebuild"
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:29
msgid "Recipe index rebuild complete."
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:31
msgid "Recipe index rebuild failed."
msgstr ""
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
msgid "Breakfast"
msgstr ""
#: .\cookbook\migrations\0047_auto_20200602_1133.py:19
msgid "Lunch"
msgstr ""
#: .\cookbook\migrations\0047_auto_20200602_1133.py:24
msgid "Dinner"
msgstr ""
#: .\cookbook\migrations\0047_auto_20200602_1133.py:29 .\cookbook\models.py:971
msgid "Other"
msgstr ""
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "g"
msgstr ""
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "Proteins"
msgstr ""
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "kcal"
msgstr ""
#: .\cookbook\models.py:325
msgid ""
"Maximum file storage for space in MB. 0 for unlimited, -1 to disable file "
"upload."
msgstr ""
#: .\cookbook\models.py:513
msgid "Search"
msgstr ""
#: .\cookbook\models.py:514
msgid "Meal-Plan"
msgstr ""
#: .\cookbook\models.py:515
msgid "Books"
msgstr ""
#: .\cookbook\models.py:516 .\cookbook\views\views.py:416
#: .\cookbook\views\views.py:417
msgid "Shopping"
msgstr ""
#: .\cookbook\models.py:967
msgid "Nutrition"
msgstr ""
#: .\cookbook\models.py:968
msgid "Allergen"
msgstr ""
#: .\cookbook\models.py:969
msgid "Price"
msgstr ""
#: .\cookbook\models.py:970
msgid "Goal"
msgstr ""
#: .\cookbook\models.py:1467 .\cookbook\templates\search_info.html:28
msgid "Simple"
msgstr ""
#: .\cookbook\models.py:1468 .\cookbook\templates\search_info.html:33
msgid "Phrase"
msgstr ""
#: .\cookbook\models.py:1469 .\cookbook\templates\search_info.html:38
msgid "Web"
msgstr ""
#: .\cookbook\models.py:1470 .\cookbook\templates\search_info.html:47
msgid "Raw"
msgstr ""
#: .\cookbook\models.py:1525
msgid "Food Alias"
msgstr ""
#: .\cookbook\models.py:1526
msgid "Unit Alias"
msgstr ""
#: .\cookbook\models.py:1527
msgid "Keyword Alias"
msgstr ""
#: .\cookbook\models.py:1528
msgid "Description Replace"
msgstr ""
#: .\cookbook\models.py:1529
msgid "Instruction Replace"
msgstr ""
#: .\cookbook\models.py:1530
msgid "Never Unit"
msgstr ""
#: .\cookbook\models.py:1531
msgid "Transpose Words"
msgstr ""
#: .\cookbook\models.py:1532
msgid "Food Replace"
msgstr ""
#: .\cookbook\models.py:1533
msgid "Unit Replace"
msgstr ""
#: .\cookbook\models.py:1534
msgid "Name Replace"
msgstr ""
#: .\cookbook\models.py:1564
msgid "Recipe"
msgstr ""
#: .\cookbook\models.py:1565
msgid "Food"
msgstr ""
#: .\cookbook\models.py:1566
msgid "Keyword"
msgstr ""
#: .\cookbook\serializer.py:262
msgid "File uploads are not enabled for this Space."
msgstr ""
#: .\cookbook\serializer.py:273
msgid "You have reached your file upload limit."
msgstr ""
#: .\cookbook\serializer.py:281
msgid "The given file type is not allowed."
msgstr ""
#: .\cookbook\serializer.py:421 .\cookbook\views\views.py:94
msgid ""
"You have the reached the maximum amount of spaces that can be owned by you."
msgstr ""
#: .\cookbook\serializer.py:434
msgid "Space Name must be unique."
msgstr ""
#: .\cookbook\serializer.py:469
msgid "Cannot modify Space owner permission."
msgstr ""
#: .\cookbook\serializer.py:1596
msgid "Hello"
msgstr ""
#: .\cookbook\serializer.py:1596
msgid "You have been invited by "
msgstr ""
#: .\cookbook\serializer.py:1598
msgid " to join their Tandoor Recipes space "
msgstr ""
#: .\cookbook\serializer.py:1600
msgid "Click the following link to activate your account: "
msgstr ""
#: .\cookbook\serializer.py:1602
msgid ""
"If the link does not work use the following code to manually join the space: "
msgstr ""
#: .\cookbook\serializer.py:1604
msgid "The invitation is valid until "
msgstr ""
#: .\cookbook\serializer.py:1606
msgid ""
"Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub "
msgstr ""
#: .\cookbook\serializer.py:1609
msgid "Tandoor Recipes Invite"
msgstr ""
#: .\cookbook\serializer.py:1813
msgid "Existing shopping list to update"
msgstr ""
#: .\cookbook\serializer.py:1815
msgid ""
"List of ingredient IDs from the recipe to add, if not provided all "
"ingredients will be added."
msgstr ""
#: .\cookbook\serializer.py:1817
msgid ""
"Providing a list_recipe ID and servings of 0 will delete that shopping list."
msgstr ""
#: .\cookbook\serializer.py:1826
msgid "Amount of food to add to the shopping list"
msgstr ""
#: .\cookbook\serializer.py:1828
msgid "ID of unit to use for the shopping list"
msgstr ""
#: .\cookbook\serializer.py:1830
msgid "When set to true will delete all food from active shopping lists."
msgstr ""
#: .\cookbook\templates\404.html:5
msgid "404 Error"
msgstr ""
#: .\cookbook\templates\404.html:18
msgid "The page you are looking for could not be found."
msgstr ""
#: .\cookbook\templates\404.html:33
msgid "Take me Home"
msgstr ""
#: .\cookbook\templates\404.html:35
msgid "Report a Bug"
msgstr ""
#: .\cookbook\templates\account\email.html:6
#: .\cookbook\templates\account\email.html:10
msgid "E-mail Addresses"
msgstr ""
#: .\cookbook\templates\account\email.html:12
msgid "The following e-mail addresses are associated with your account:"
msgstr ""
#: .\cookbook\templates\account\email.html:29
msgid "Verified"
msgstr ""
#: .\cookbook\templates\account\email.html:31
msgid "Unverified"
msgstr ""
#: .\cookbook\templates\account\email.html:33
msgid "Primary"
msgstr ""
#: .\cookbook\templates\account\email.html:40
msgid "Make Primary"
msgstr ""
#: .\cookbook\templates\account\email.html:42
msgid "Re-send Verification"
msgstr ""
#: .\cookbook\templates\account\email.html:43
#: .\cookbook\templates\socialaccount\connections.html:36
msgid "Remove"
msgstr ""
#: .\cookbook\templates\account\email.html:51
msgid "Warning:"
msgstr ""
#: .\cookbook\templates\account\email.html:51
msgid ""
"You currently do not have any e-mail address set up. You should really add "
"an e-mail address so you can receive notifications, reset your password, etc."
msgstr ""
#: .\cookbook\templates\account\email.html:57
msgid "Add E-mail Address"
msgstr ""
#: .\cookbook\templates\account\email.html:62
msgid "Add E-mail"
msgstr ""
#: .\cookbook\templates\account\email.html:72
msgid "Do you really want to remove the selected e-mail address?"
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:6
#: .\cookbook\templates\account\email_confirm.html:10
msgid "Confirm E-mail Address"
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:16
#, python-format
msgid ""
"Please confirm that\n"
" %(email)s is an e-mail address "
"for user %(user_display)s\n"
" ."
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:22
msgid "Confirm"
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:29
#, python-format
msgid ""
"This e-mail confirmation link expired or is invalid. Please\n"
" issue a new e-mail confirmation "
"request."
msgstr ""
#: .\cookbook\templates\account\login.html:8
#: .\cookbook\templates\openid\login.html:8
msgid "Login"
msgstr ""
#: .\cookbook\templates\account\login.html:15
#: .\cookbook\templates\account\login.html:31
#: .\cookbook\templates\account\password_reset.html:39
#: .\cookbook\templates\account\password_reset_done.html:31
#: .\cookbook\templates\account\signup.html:68
#: .\cookbook\templates\account\signup_closed.html:15
#: .\cookbook\templates\openid\login.html:15
#: .\cookbook\templates\openid\login.html:26
#: .\cookbook\templates\socialaccount\authentication_error.html:15
msgid "Sign In"
msgstr ""
#: .\cookbook\templates\account\login.html:34
#: .\cookbook\templates\account\password_reset.html:41
#: .\cookbook\templates\account\password_reset_done.html:33
#: .\cookbook\templates\socialaccount\signup.html:8
#: .\cookbook\templates\socialaccount\signup.html:56
msgid "Sign Up"
msgstr ""
#: .\cookbook\templates\account\login.html:38
msgid "Lost your password?"
msgstr ""
#: .\cookbook\templates\account\login.html:39
#: .\cookbook\templates\account\password_reset.html:29
msgid "Reset My Password"
msgstr ""
#: .\cookbook\templates\account\login.html:50
msgid "Social Login"
msgstr ""
#: .\cookbook\templates\account\login.html:51
msgid "You can use any of the following providers to sign in."
msgstr ""
#: .\cookbook\templates\account\logout.html:5
#: .\cookbook\templates\account\logout.html:9
#: .\cookbook\templates\account\logout.html:18
msgid "Sign Out"
msgstr ""
#: .\cookbook\templates\account\logout.html:11
msgid "Are you sure you want to sign out?"
msgstr ""
#: .\cookbook\templates\account\password_change.html:6
#: .\cookbook\templates\account\password_change.html:10
#: .\cookbook\templates\account\password_change.html:15
#: .\cookbook\templates\account\password_reset_from_key.html:7
#: .\cookbook\templates\account\password_reset_from_key.html:13
#: .\cookbook\templates\account\password_reset_from_key_done.html:7
#: .\cookbook\templates\account\password_reset_from_key_done.html:13
msgid "Change Password"
msgstr ""
#: .\cookbook\templates\account\password_change.html:16
msgid "Forgot Password?"
msgstr ""
#: .\cookbook\templates\account\password_reset.html:7
#: .\cookbook\templates\account\password_reset.html:13
#: .\cookbook\templates\account\password_reset_done.html:7
#: .\cookbook\templates\account\password_reset_done.html:18
msgid "Password Reset"
msgstr ""
#: .\cookbook\templates\account\password_reset.html:24
msgid ""
"Forgotten your password? Enter your e-mail address below, and we'll send you "
"an e-mail allowing you to reset it."
msgstr ""
#: .\cookbook\templates\account\password_reset.html:32
msgid "Password reset is disabled on this instance."
msgstr ""
#: .\cookbook\templates\account\password_reset_done.html:25
msgid ""
"We have sent you an e-mail. Please contact us if you do not receive it "
"within a few minutes."
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:13
msgid "Bad Token"
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:25
#, python-format
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used.\n"
" Please request a new "
"password reset."
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:33
msgid "change password"
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:36
#: .\cookbook\templates\account\password_reset_from_key_done.html:19
msgid "Your password is now changed."
msgstr ""
#: .\cookbook\templates\account\password_set.html:6
#: .\cookbook\templates\account\password_set.html:10
#: .\cookbook\templates\account\password_set.html:15
msgid "Set Password"
msgstr ""
#: .\cookbook\templates\account\signup.html:5
msgid "Register"
msgstr ""
#: .\cookbook\templates\account\signup.html:11
msgid "Create an Account"
msgstr ""
#: .\cookbook\templates\account\signup.html:41
msgid "I accept the follwoing"
msgstr ""
#: .\cookbook\templates\account\signup.html:44
#: .\cookbook\templates\socialaccount\signup.html:35
msgid "Terms and Conditions"
msgstr ""
#: .\cookbook\templates\account\signup.html:47
#: .\cookbook\templates\socialaccount\signup.html:38
msgid "and"
msgstr ""
#: .\cookbook\templates\account\signup.html:51
#: .\cookbook\templates\socialaccount\signup.html:42
msgid "Privacy Policy"
msgstr ""
#: .\cookbook\templates\account\signup.html:64
msgid "Create User"
msgstr ""
#: .\cookbook\templates\account\signup.html:68
msgid "Already have an account?"
msgstr ""
#: .\cookbook\templates\account\signup_closed.html:5
#: .\cookbook\templates\account\signup_closed.html:11
msgid "Sign Up Closed"
msgstr ""
#: .\cookbook\templates\account\signup_closed.html:13
msgid "We are sorry, but the sign up is currently closed."
msgstr ""
#: .\cookbook\templates\frontend\tandoor.html:15
msgid "Tandoor Recipe Manager"
msgstr ""
#: .\cookbook\templates\index.html:28
msgid "Search recipe ..."
msgstr ""
#: .\cookbook\templates\index.html:43
msgid "New Recipe"
msgstr ""
#: .\cookbook\templates\index.html:46
msgid "Import Recipe"
msgstr ""
#: .\cookbook\templates\index.html:52
msgid "Advanced Search"
msgstr ""
#: .\cookbook\templates\index.html:56
msgid "Reset Search"
msgstr ""
#: .\cookbook\templates\index.html:84
msgid "Last viewed"
msgstr ""
#: .\cookbook\templates\index.html:86
msgid "Recipes"
msgstr ""
#: .\cookbook\templates\index.html:93
msgid "Log in to view recipes"
msgstr ""
#: .\cookbook\templates\markdown_info.html:5
#: .\cookbook\templates\markdown_info.html:13
msgid "Markdown Info"
msgstr ""
#: .\cookbook\templates\markdown_info.html:14
msgid ""
"\n"
" Markdown is lightweight markup language that can be used to format "
"plain text easily.\n"
" This site uses the Python Markdown library to\n"
" convert your text into nice looking HTML. Its full markdown "
"documentation can be found\n"
" here.\n"
" An incomplete but most likely sufficient documentation can be found "
"below.\n"
" "
msgstr ""
#: .\cookbook\templates\markdown_info.html:25
msgid "Headers"
msgstr ""
#: .\cookbook\templates\markdown_info.html:54
msgid "Formatting"
msgstr ""
#: .\cookbook\templates\markdown_info.html:56
#: .\cookbook\templates\markdown_info.html:72
msgid "Line breaks are inserted by adding two spaces after the end of a line"
msgstr ""
#: .\cookbook\templates\markdown_info.html:57
#: .\cookbook\templates\markdown_info.html:73
msgid "or by leaving a blank line in between."
msgstr ""
#: .\cookbook\templates\markdown_info.html:59
#: .\cookbook\templates\markdown_info.html:74
msgid "This text is bold"
msgstr ""
#: .\cookbook\templates\markdown_info.html:60
#: .\cookbook\templates\markdown_info.html:75
msgid "This text is italic"
msgstr ""
#: .\cookbook\templates\markdown_info.html:61
#: .\cookbook\templates\markdown_info.html:77
msgid "Blockquotes are also possible"
msgstr ""
#: .\cookbook\templates\markdown_info.html:84
msgid "Lists"
msgstr ""
#: .\cookbook\templates\markdown_info.html:85
msgid ""
"Lists can ordered or unordered. It is important to leave a blank line "
"before the list!"
msgstr ""
#: .\cookbook\templates\markdown_info.html:87
#: .\cookbook\templates\markdown_info.html:108
msgid "Ordered List"
msgstr ""
#: .\cookbook\templates\markdown_info.html:89
#: .\cookbook\templates\markdown_info.html:90
#: .\cookbook\templates\markdown_info.html:91
#: .\cookbook\templates\markdown_info.html:110
#: .\cookbook\templates\markdown_info.html:111
#: .\cookbook\templates\markdown_info.html:112
msgid "unordered list item"
msgstr ""
#: .\cookbook\templates\markdown_info.html:93
#: .\cookbook\templates\markdown_info.html:114
msgid "Unordered List"
msgstr ""
#: .\cookbook\templates\markdown_info.html:95
#: .\cookbook\templates\markdown_info.html:96
#: .\cookbook\templates\markdown_info.html:97
#: .\cookbook\templates\markdown_info.html:116
#: .\cookbook\templates\markdown_info.html:117
#: .\cookbook\templates\markdown_info.html:118
msgid "ordered list item"
msgstr ""
#: .\cookbook\templates\markdown_info.html:125
msgid "Images & Links"
msgstr ""
#: .\cookbook\templates\markdown_info.html:126
msgid ""
"Links can be formatted with Markdown. This application also allows to paste "
"links directly into markdown fields without any formatting."
msgstr ""
#: .\cookbook\templates\markdown_info.html:132
#: .\cookbook\templates\markdown_info.html:145
msgid "This will become an image"
msgstr ""
#: .\cookbook\templates\markdown_info.html:152
msgid "Tables"
msgstr ""
#: .\cookbook\templates\markdown_info.html:153
msgid ""
"Markdown tables are hard to create by hand. It is recommended to use a table "
"editor like this one."
msgstr ""
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:171
#: .\cookbook\templates\markdown_info.html:177
msgid "Table"
msgstr ""
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:172
msgid "Header"
msgstr ""
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:178
msgid "Cell"
msgstr ""
#: .\cookbook\templates\no_groups_info.html:5
#: .\cookbook\templates\no_groups_info.html:12
msgid "No Permissions"
msgstr ""
#: .\cookbook\templates\no_groups_info.html:17
msgid "You do not have any groups and therefor cannot use this application."
msgstr ""
#: .\cookbook\templates\no_groups_info.html:18
#: .\cookbook\templates\no_perm_info.html:15
msgid "Please contact your administrator."
msgstr ""
#: .\cookbook\templates\no_perm_info.html:5
#: .\cookbook\templates\no_perm_info.html:12
msgid "No Permission"
msgstr ""
#: .\cookbook\templates\no_perm_info.html:15
msgid ""
"You do not have the required permissions to view this page or perform this "
"action."
msgstr ""
#: .\cookbook\templates\offline.html:5
msgid "Offline"
msgstr ""
#: .\cookbook\templates\openid\login.html:27
#: .\cookbook\templates\socialaccount\authentication_error.html:27
msgid "Back"
msgstr ""
#: .\cookbook\templates\rest_framework\api.html:5
msgid "Recipe Home"
msgstr ""
#: .\cookbook\templates\rest_framework\api.html:11
msgid "API Documentation"
msgstr ""
#: .\cookbook\templates\search_info.html:5
#: .\cookbook\templates\search_info.html:9
msgid "Search Settings"
msgstr ""
#: .\cookbook\templates\search_info.html:10
msgid ""
"\n"
" Creating the best search experience is complicated and weighs "
"heavily on your personal configuration. \n"
" Changing any of the search settings can have significant impact on "
"the speed and quality of the results.\n"
" Search Methods, Trigrams and Full Text Search configurations are "
"only available if you are using Postgres for your database.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:19
msgid "Search Methods"
msgstr ""
#: .\cookbook\templates\search_info.html:23
msgid ""
" \n"
" Full text searches attempt to normalize the words provided to "
"match common variants. For example: 'forked', 'forking', 'forks' will all "
"normalize to 'fork'.\n"
" There are several methods available, described below, that will "
"control how the search behavior should react when multiple words are "
"searched.\n"
" Full technical details on how these operate can be viewed on Postgresql's website.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:29
msgid ""
" \n"
" Simple searches ignore punctuation and common words such as "
"'the', 'a', 'and'. And will treat separate words as required.\n"
" Searching for 'apple or flour' will return any recipe that "
"includes both 'apple' and 'flour' anywhere in the fields that have been "
"selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:34
msgid ""
" \n"
" Phrase searches ignore punctuation, but will search for all of "
"the words in the exact order provided.\n"
" Searching for 'apple or flour' will only return a recipe that "
"includes the exact phrase 'apple or flour' in any of the fields that have "
"been selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:39
msgid ""
" \n"
" Web searches simulate functionality found on many web search "
"sites supporting special syntax.\n"
" Placing quotes around several words will convert those words "
"into a phrase.\n"
" 'or' is recognized as searching for the word (or phrase) "
"immediately before 'or' OR the word (or phrase) directly after.\n"
" '-' is recognized as searching for recipes that do not include "
"the word (or phrase) that comes immediately after. \n"
" For example searching for 'apple pie' or cherry -butter will "
"return any recipe that includes the phrase 'apple pie' or the word "
"'cherry' \n"
" in any field included in the full text search but exclude any "
"recipe that has the word 'butter' in any field included.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:48
msgid ""
" \n"
" Raw search is similar to Web except will take puncuation "
"operators such as '|', '&' and '()'\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:59
msgid ""
" \n"
" Another approach to searching that also requires Postgresql is "
"fuzzy search or trigram similarity. A trigram is a group of three "
"consecutive characters.\n"
" For example searching for 'apple' will create x trigrams 'app', "
"'ppl', 'ple' and will create a score of how closely words match the "
"generated trigrams.\n"
" One benefit of searching trigams is that a search for 'sandwich' "
"will find misspelled words such as 'sandwhich' that would be missed by other "
"methods.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:69
msgid "Search Fields"
msgstr ""
#: .\cookbook\templates\search_info.html:73
msgid ""
" \n"
" Unaccent is a special case in that it enables searching a field "
"'unaccented' for each search style attempting to ignore accented values. \n"
" For example when you enable unaccent for 'Name' any search "
"(starts with, contains, trigram) will attempt the search ignoring accented "
"characters.\n"
" \n"
" For the other options, you can enable search on any or all "
"fields and they will be combined together with an assumed 'OR'.\n"
" For example enabling 'Name' for Starts With, 'Name' and "
"'Description' for Partial Match and 'Ingredients' and 'Keywords' for Full "
"Search\n"
" and searching for 'apple' will generate a search that will "
"return recipes that have:\n"
" - A recipe name that starts with 'apple'\n"
" - OR a recipe name that contains 'apple'\n"
" - OR a recipe description that contains 'apple'\n"
" - OR a recipe that will have a full text search match ('apple' "
"or 'apples') in ingredients\n"
" - OR a recipe that will have a full text search match in "
"Keywords\n"
"\n"
" Combining too many fields in too many types of search can have a "
"negative impact on performance, create duplicate results or return "
"unexpected results.\n"
" For example, enabling fuzzy search or partial matches will "
"interfere with web search methods. \n"
" Searching for 'apple -pie' with fuzzy search and full text "
"search will return the recipe Apple Pie. Though it is not included in the "
"full text results, it does match the trigram results.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:95
msgid "Search Index"
msgstr ""
#: .\cookbook\templates\search_info.html:99
msgid ""
" \n"
" Trigram search and Full Text Search both rely on database "
"indexes to perform effectively. \n"
" You can rebuild the indexes on all fields in the Admin page for "
"Recipes and selecting all recipes and running 'rebuild index for selected "
"recipes'\n"
" You can also rebuild indexes at the command line by executing "
"the management command 'python manage.py rebuildindex'\n"
" "
msgstr ""
#: .\cookbook\templates\setup.html:6
msgid "Cookbook Setup"
msgstr ""
#: .\cookbook\templates\setup.html:14
msgid "Setup"
msgstr ""
#: .\cookbook\templates\setup.html:15
msgid ""
"To start using this application you must first create a superuser account."
msgstr ""
#: .\cookbook\templates\setup.html:20
msgid "Create Superuser account"
msgstr ""
#: .\cookbook\templates\socialaccount\authentication_error.html:7
#: .\cookbook\templates\socialaccount\authentication_error.html:23
msgid "Social Network Login Failure"
msgstr ""
#: .\cookbook\templates\socialaccount\authentication_error.html:25
msgid ""
"An error occurred while attempting to login via your social network account."
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:4
#: .\cookbook\templates\socialaccount\connections.html:7
msgid "Account Connections"
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:10
msgid ""
"You can sign in to your account using any of the following third party\n"
" accounts:"
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:44
msgid ""
"You currently have no social network accounts connected to this account."
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:47
msgid "Add a 3rd Party Account"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:5
#: .\cookbook\templates\socialaccount\signup.html:5
msgid "Signup"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:9
#, python-format
msgid "Connect %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:11
#, python-format
msgid "You are about to connect a new third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:13
#, python-format
msgid "Sign In Via %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:15
#, python-format
msgid "You are about to sign in using a third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:20
msgid "Continue"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:10
#, python-format
msgid ""
"You are about to use your\n"
" %(provider_name)s account to login to\n"
" %(site_name)s. As a final step, please complete the following form:"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:32
msgid "I accept the following"
msgstr ""
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:23
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:31
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:39
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:47
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:55
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:63
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:71
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:79
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:87
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:95
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:103
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:111
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:119
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:127
msgid "Sign in using"
msgstr ""
#: .\cookbook\templates\space_overview.html:6
msgid "Overview"
msgstr ""
#: .\cookbook\templates\space_overview.html:13
msgid "Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:17
msgid ""
"Recipes, foods, shopping lists and more are organized in spaces of one or "
"more people."
msgstr ""
#: .\cookbook\templates\space_overview.html:18
msgid ""
"You can either be invited into an existing space or create your own one."
msgstr ""
#: .\cookbook\templates\space_overview.html:25
msgid "Your Spaces"
msgstr ""
#: .\cookbook\templates\space_overview.html:53
msgid "Owner"
msgstr ""
#: .\cookbook\templates\space_overview.html:73
#: .\cookbook\templates\space_overview.html:83
msgid "Join Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:76
msgid "Join an existing space."
msgstr ""
#: .\cookbook\templates\space_overview.html:78
msgid ""
"To join an existing space either enter your invite token or click on the "
"invite link the space owner send you."
msgstr ""
#: .\cookbook\templates\space_overview.html:91
#: .\cookbook\templates\space_overview.html:100
msgid "Create Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:94
msgid "Create your own recipe space."
msgstr ""
#: .\cookbook\templates\space_overview.html:96
msgid "Start your own recipe space and invite other users to it."
msgstr ""
#: .\cookbook\templates\system.html:23
msgid "System"
msgstr ""
#: .\cookbook\templates\system.html:24
msgid ""
"\n"
" Tandoor Recipes is an open source free software application. It can "
"be found on\n"
" GitHub.\n"
" Changelogs can be found here.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:30
msgid "System Information"
msgstr ""
#: .\cookbook\templates\system.html:51
msgid ""
"\n"
" You need to execute version.py in your update "
"script to generate version information (done automatically in docker).\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:56
msgid "Plugins"
msgstr ""
#: .\cookbook\templates\system.html:67
msgid "Media Serving"
msgstr ""
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:123 .\cookbook\templates\system.html:134
msgid "Warning"
msgstr ""
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:125 .\cookbook\templates\system.html:134
msgid "Ok"
msgstr ""
#: .\cookbook\templates\system.html:70
msgid ""
"Serving media files directly using gunicorn/python is not recommend!\n"
" Please follow the steps described\n"
" here to update\n"
" your installation.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:76 .\cookbook\templates\system.html:91
#: .\cookbook\templates\system.html:104 .\cookbook\templates\system.html:115
#: .\cookbook\views\views.py:168
msgid "Everything is fine!"
msgstr ""
#: .\cookbook\templates\system.html:80
msgid "Secret Key"
msgstr ""
#: .\cookbook\templates\system.html:84
msgid ""
"\n"
" You do not have a SECRET_KEY configured in your "
".env file. Django defaulted to the\n"
" standard key\n"
" provided with the installation which is publicly know and "
"insecure! Please set\n"
" SECRET_KEY int the .env configuration "
"file.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:94
msgid "Debug Mode"
msgstr ""
#: .\cookbook\templates\system.html:98
msgid ""
"\n"
" This application is still running in debug mode. This is most "
"likely not needed. Turn of debug mode by\n"
" setting\n"
" DEBUG=0 int the .env configuration "
"file.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:107
msgid "Allowed Hosts"
msgstr ""
#: .\cookbook\templates\system.html:111
msgid ""
"\n"
" Your allowed hosts are configured to allow every host. This "
"might be ok in some setups but should be avoided. Please see the docs about "
"this.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:118
msgid "Database"
msgstr ""
#: .\cookbook\templates\system.html:121
msgid "Info"
msgstr ""
#: .\cookbook\templates\system.html:131 .\cookbook\templates\system.html:148
#, fuzzy
#| msgid "Use fractions"
msgid "Migrations"
msgstr "Gunakan pecahan"
#: .\cookbook\templates\system.html:137
msgid ""
"\n"
" Migrations should never fail!\n"
" Failed migrations will likely cause major parts of the app to "
"not function correctly.\n"
" If a migration fails make sure you are on the latest version and "
"if so please post the migration log and the overview below in a GitHub "
"issue.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "False"
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "True"
msgstr ""
#: .\cookbook\templates\system.html:268
msgid "Hide"
msgstr ""
#: .\cookbook\templates\system.html:271
msgid "Show"
msgstr ""
#: .\cookbook\templates\test2.html:6
msgid "Export Recipes"
msgstr ""
#: .\cookbook\templates\test2.html:14 .\cookbook\templates\test2.html:20
msgid "Export"
msgstr ""
#: .\cookbook\views\api.py:198 .\cookbook\views\api.py:326
msgid "Parameter updated_at incorrectly formatted"
msgstr ""
#: .\cookbook\views\api.py:351 .\cookbook\views\api.py:484
#, python-brace-format
msgid "No {self.basename} with id {pk} exists"
msgstr ""
#: .\cookbook\views\api.py:355
msgid "Cannot merge with the same object!"
msgstr ""
#: .\cookbook\views\api.py:362
#, python-brace-format
msgid "No {self.basename} with id {target} exists"
msgstr ""
#: .\cookbook\views\api.py:367
msgid "Cannot merge with child object!"
msgstr ""
#: .\cookbook\views\api.py:405
#, python-brace-format
msgid "{source.name} was merged successfully with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:411
#, python-brace-format
msgid "An error occurred attempting to merge {source.name} with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:493
#, python-brace-format
msgid "{child.name} was moved successfully to the root."
msgstr ""
#: .\cookbook\views\api.py:496 .\cookbook\views\api.py:514
msgid "An error occurred attempting to move "
msgstr ""
#: .\cookbook\views\api.py:499
msgid "Cannot move an object to itself!"
msgstr ""
#: .\cookbook\views\api.py:505
#, python-brace-format
msgid "No {self.basename} with id {parent} exists"
msgstr ""
#: .\cookbook\views\api.py:511
#, python-brace-format
msgid "{child.name} was moved successfully to parent {parent.name}"
msgstr ""
#: .\cookbook\views\api.py:992
#, python-brace-format
msgid "{obj.name} was removed from the shopping list."
msgstr ""
#: .\cookbook\views\api.py:997 .\cookbook\views\api.py:1627
#, python-brace-format
msgid "{obj.name} was added to the shopping list."
msgstr ""
#: .\cookbook\views\api.py:1239
msgid "Filter meal plans from date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1241
msgid "Filter meal plans to date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1244
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1404
msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1406
msgid "Query string matched (fuzzy) against object name."
msgstr ""
#: .\cookbook\views\api.py:1442
msgid ""
"Query string matched (fuzzy) against recipe name. In the future also "
"fulltext search."
msgstr ""
#: .\cookbook\views\api.py:1444
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr ""
#: .\cookbook\views\api.py:1445
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr ""
#: .\cookbook\views\api.py:1446
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1447
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1448
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1450
msgid "ID of food a recipe should have. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1451
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr ""
#: .\cookbook\views\api.py:1452
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1453
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr ""
#: .\cookbook\views\api.py:1454
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1456
msgid "ID of book a recipe should be in. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1457
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr ""
#: .\cookbook\views\api.py:1458
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1459
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr ""
#: .\cookbook\views\api.py:1460
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1462
msgid "ID of unit a recipe should have."
msgstr ""
#: .\cookbook\views\api.py:1464
msgid "Exact rating of recipe"
msgstr ""
#: .\cookbook\views\api.py:1465
msgid "Rating a recipe should have or greater."
msgstr ""
#: .\cookbook\views\api.py:1466
msgid "Rating a recipe should have or smaller."
msgstr ""
#: .\cookbook\views\api.py:1468
msgid "Filter recipes cooked X times."
msgstr ""
#: .\cookbook\views\api.py:1469
msgid "Filter recipes cooked X times or more."
msgstr ""
#: .\cookbook\views\api.py:1470
msgid "Filter recipes cooked X times or less."
msgstr ""
#: .\cookbook\views\api.py:1472
msgid "Filter recipes created on the given date."
msgstr ""
#: .\cookbook\views\api.py:1473
msgid "Filter recipes created on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1474
msgid "Filter recipes created on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1476 .\cookbook\views\api.py:1477
#: .\cookbook\views\api.py:1478
msgid "Filter recipes updated on the given date."
msgstr ""
#: .\cookbook\views\api.py:1480
msgid "Filter recipes last cooked on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1481
msgid "Filter recipes last cooked on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1483 .\cookbook\views\api.py:1484
msgid "Filter recipes lasts viewed on the given date."
msgstr ""
#: .\cookbook\views\api.py:1486
msgid "Filter recipes for ones created by the given user ID"
msgstr ""
#: .\cookbook\views\api.py:1487
msgid "If only internal recipes should be returned. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1488
msgid "Returns the results in randomized order. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1490
msgid ""
"Determines the order of the results. Options are: score,-score,name,-name,"
"lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-"
"created_at,lastviewed,-lastviewed"
msgstr ""
#: .\cookbook\views\api.py:1492
msgid "Returns new results first in search results. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1493
msgid ""
"Returns the given number of recently viewed recipes before search results "
"(if given)"
msgstr ""
#: .\cookbook\views\api.py:1494
msgid "ID of a custom filter. Returns all recipes matched by that filter."
msgstr ""
#: .\cookbook\views\api.py:1495
msgid "Filter recipes that can be made with OnHand food. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1773
msgid ""
"Return the PropertyTypes matching the property category. Repeat for "
"multiple."
msgstr ""
#: .\cookbook\views\api.py:1804 .\cookbook\views\api.py:1860
msgid "Returns only entries associated with the given mealplan id"
msgstr ""
#: .\cookbook\views\api.py:1858
msgid ""
"Returns only elements updated after the given timestamp in ISO 8601 format."
msgstr ""
#: .\cookbook\views\api.py:2031
msgid ""
"Return the Automations matching the automation type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2048
msgid ""
"Text field to store data that gets carried over to the UserSpace created "
"from the InviteLink"
msgstr ""
#: .\cookbook\views\api.py:2049
msgid "Only return InviteLinks that have not been used yet."
msgstr ""
#: .\cookbook\views\api.py:2076
msgid "Return the CustomFilters matching the model type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2176
msgid "Nothing to do."
msgstr ""
#: .\cookbook\views\api.py:2222
msgid "Invalid Url"
msgstr ""
#: .\cookbook\views\api.py:2228
msgid "Connection Refused."
msgstr ""
#: .\cookbook\views\api.py:2232
msgid "Bad URL Schema."
msgstr ""
#: .\cookbook\views\api.py:2257
msgid "No usable data could be found."
msgstr ""
#: .\cookbook\views\api.py:2286 .\cookbook\views\api.py:2434
msgid "You must select an AI provider to perform your request."
msgstr ""
#: .\cookbook\views\api.py:2293 .\cookbook\views\api.py:2441
msgid ""
"You don't have any credits remaining to use AI or AI features are not "
"enabled for your space."
msgstr ""
#: .\cookbook\views\api.py:2499 .\cookbook\views\api.py:2667
msgid "File is above space limit"
msgstr ""
#: .\cookbook\views\api.py:2522 .\cookbook\views\api.py:2684
msgid "Importing is not implemented for this provider"
msgstr ""
#: .\cookbook\views\api.py:2548
msgid ""
"The PDF Exporter is not enabled on this instance as it is still in an "
"experimental state."
msgstr ""
#: .\cookbook\views\api.py:2842
msgid "This feature is not yet available in the hosted version of tandoor!"
msgstr ""
#: .\cookbook\views\api.py:2863
msgid "Sync successful!"
msgstr ""
#: .\cookbook\views\api.py:2866
msgid "Error synchronizing with Storage"
msgstr ""
#: .\cookbook\views\views.py:89
msgid "This feature is not available in the demo version!"
msgstr ""
#: .\cookbook\views\views.py:110
msgid ""
"You have successfully created your own recipe space. Start by adding some "
"recipes or invite other people to join you."
msgstr ""
#: .\cookbook\views\views.py:171
#, python-format
msgid "PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!"
msgstr ""
#: .\cookbook\views\views.py:174
#, python-format
msgid "You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended"
msgstr ""
#: .\cookbook\views\views.py:178
msgid "Unable to determine PostgreSQL version."
msgstr ""
#: .\cookbook\views\views.py:182
msgid ""
"This application is not running with a Postgres database backend. This is ok "
"but not recommended as some features only work with postgres databases."
msgstr ""
#: .\cookbook\views\views.py:296
msgid ""
"The setup page can only be used to create the first "
"user! If you have forgotten your superuser credentials "
"please consult the django documentation on how to reset passwords."
msgstr ""
#: .\cookbook\views\views.py:304
msgid "Passwords dont match!"
msgstr ""
#: .\cookbook\views\views.py:312
msgid "User has been created, please login!"
msgstr ""
#: .\cookbook\views\views.py:352
msgid ""
"Reporting share links is not enabled for this instance. Please notify the "
"page administrator to report problems."
msgstr ""
#: .\cookbook\views\views.py:357
msgid ""
"Recipe sharing link has been disabled! For additional information please "
"contact the page administrator."
msgstr ""
#: .\cookbook\views\views.py:383
msgid "Manage recipes, shopping list, meal plans and more."
msgstr ""
#: .\cookbook\views\views.py:397 .\cookbook\views\views.py:398
msgid "Plan"
msgstr ""
#: .\cookbook\views\views.py:399
msgid "View your meal Plan"
msgstr ""
#: .\cookbook\views\views.py:418
#, fuzzy
#| msgid "Users with whom to share shopping lists."
msgid "View your shopping lists"
msgstr "Pengguna yang ingin berbagi daftar belanja."
#~ msgid ""
#~ "Both fields are optional. If none are given the username will be "
#~ "displayed instead"
#~ msgstr ""
#~ "Kedua bidang ini opsional. Jika tidak ada yang diberikan nama pengguna "
#~ "akan ditampilkan sebagai gantinya"
#~ msgid "Name"
#~ msgstr "Nama"
#~ msgid "Keywords"
#~ msgstr "Kata Kunci"
#~ msgid "Preparation time in minutes"
#~ msgstr "Waktu persiapan dalam hitungan menit"
#~ msgid "Waiting time (cooking/baking) in minutes"
#~ msgstr "Waktu tunggu (memasak/memanggang) dalam hitungan menit"
#~ msgid "Path"
#~ msgstr "Jalur"
#~ msgid "Storage UID"
#~ msgstr "UID penyimpanan"
#~ msgid "Add your comment: "
#~ msgstr "Tambahkan komentar Anda: "
#~ msgid "Leave empty for dropbox and enter app password for nextcloud."
#~ msgstr ""
#~ "Biarkan kosong untuk dropbox dan masukkan kata sandi aplikasi untuk "
#~ "nextcloud."
#~ msgid "Leave empty for nextcloud and enter api token for dropbox."
#~ msgstr ""
#~ "Biarkan kosong untuk nextcloud dan masukkan token api untuk dropbox."
#~ msgid ""
#~ "Leave empty for dropbox and enter only base url for nextcloud (/"
#~ "remote.php/webdav/ is added automatically)"
#~ msgstr ""
#~ "Biarkan kosong untuk dropbox dan masukkan hanya url dasar untuk cloud "
#~ "berikutnya (/remote.php/webdav/ ditambahkan secara otomatis)"
#~ msgid "Storage"
#~ msgstr "Penyimpanan"
#~ msgid "Active"
#~ msgstr "Aktif"
#~ msgid "Search String"
#~ msgstr "Cari String"
#~ msgid "File ID"
#~ msgstr "ID Berkas"
#~ msgid ""
#~ "Determines how fuzzy a search is if it uses trigram similarity matching "
#~ "(e.g. low values mean more typos are ignored)."
#~ msgstr ""
#~ "Menentukan seberapa kabur pencarian jika menggunakan pencocokan kesamaan "
#~ "trigram (misalnya nilai rendah berarti lebih banyak kesalahan ketik yang "
#~ "diabaikan)."
#~ msgid ""
#~ "Select type method of search. Click here "
#~ "for full description of choices."
#~ msgstr ""
#~ "Pilih jenis metode pencarian. Klik di sini "
#~ "untuk deskripsi lengkap pilihan."
#~ msgid ""
#~ "Use fuzzy matching on units, keywords and ingredients when editing and "
#~ "importing recipes."
#~ msgstr ""
#~ "Gunakan fuzzy pencocokan pada unit, kata kunci, dan bahan saat mengedit "
#~ "dan mengimpor resep."
#~ msgid ""
#~ "Fields to search ignoring accents. Selecting this option can improve or "
#~ "degrade search quality depending on language"
#~ msgstr ""
#~ "Bidang untuk mencari mengabaikan aksen. Memilih opsi ini dapat "
#~ "meningkatkan atau menurunkan kualitas pencarian tergantung pada bahasa"
#~ msgid ""
#~ "Fields to search for partial matches. (e.g. searching for 'Pie' will "
#~ "return 'pie' and 'piece' and 'soapie')"
#~ msgstr ""
#~ "Bidang untuk mencari kecocokan sebagian. (mis. mencari 'Pie' akan "
#~ "mengembalikan 'pie' dan 'piece' dan 'soapie')"
#~ msgid ""
#~ "Fields to search for beginning of word matches. (e.g. searching for 'sa' "
#~ "will return 'salad' and 'sandwich')"
#~ msgstr ""
#~ "Bidang untuk mencari awal kata yang cocok. (misalnya mencari 'sa' akan "
#~ "mengembalikan 'salad' dan 'sandwich')"
#~ msgid "Comments"
#~ msgstr "Komen"
#~ msgid "Ingredients"
#~ msgstr "bahan-bahan"
#~ msgid "Default unit"
#~ msgstr "Unit bawaan"
#~ msgid "Use KJ"
#~ msgstr "Gunakan KJ"
#~ msgid "Theme"
#~ msgstr "Tema"
#~ msgid "Navbar color"
#~ msgstr "Warna Navigasi"
#~ msgid "Sticky navbar"
#~ msgstr "Sticky navbar"
#~ msgid "Default page"
#~ msgstr "Halaman default"
#~ msgid "Show recent recipes"
#~ msgstr "Tampilkan resep terbaru"
#~ msgid "Search style"
#~ msgstr "Cari style"
#~ msgid "Plan sharing"
#~ msgstr "Berbagi rencana"
#~ msgid "Ingredient decimal places"
#~ msgstr "Tempat desimal bahan"
#~ msgid "Shopping list auto sync period"
#~ msgstr "Periode sinkronisasi otomatis daftar belanja"
#~ msgid "Left-handed mode"
#~ msgstr "Mode tangan kiri"
#~ msgid ""
#~ "Color of the top navigation bar. Not all colors work with all themes, "
#~ "just try them out!"
#~ msgstr ""
#~ "Warna bilah navigasi atas. Tidak semua warna bekerja dengan semua tema, "
#~ "coba saja!"
#~ msgid ""
#~ "Default Unit to be used when inserting a new ingredient into a recipe."
#~ msgstr ""
#~ "Default Unit yang akan digunakan saat memasukkan bahan baru ke dalam "
#~ "resep."
#~ msgid ""
#~ "Enables support for fractions in ingredient amounts (e.g. convert "
#~ "decimals to fractions automatically)"
#~ msgstr ""
#~ "Mengaktifkan dukungan untuk pecahan dalam jumlah bahan (misalnya, "
#~ "mengubah desimal menjadi pecahan secara otomatis)"
#~ msgid "Display nutritional energy amounts in joules instead of calories"
#~ msgstr "Tampilkan jumlah energi nutrisi dalam joule, bukan kalori"
#~ msgid ""
#~ "Users with whom newly created meal plans should be shared by default."
#~ msgstr ""
#~ "Pengguna dengan siapa rencana makan yang baru dibuat harus dibagikan "
#~ "secara default."
#~ msgid "Show recently viewed recipes on search page."
#~ msgstr "Tampilkan resep yang baru dilihat di halaman pencarian."
#~ msgid "Number of decimals to round ingredients."
#~ msgstr "Jumlah desimal untuk bahan."
#~ msgid ""
#~ "If you want to be able to create and see comments underneath recipes."
#~ msgstr "Jika Anda ingin dapat membuat dan melihat komentar di bawah resep."
#~ msgid ""
#~ "Setting to 0 will disable auto sync. When viewing a shopping list the "
#~ "list is updated every set seconds to sync changes someone else might have "
#~ "made. Useful when shopping with multiple people but might use a little "
#~ "bit of mobile data. If lower than instance limit it is reset when saving."
#~ msgstr ""
#~ "Menyetel ke 0 akan menonaktifkan sinkronisasi otomatis. Saat melihat "
#~ "daftar belanja, daftar diperbarui setiap detik untuk menyinkronkan "
#~ "perubahan yang mungkin dibuat orang lain. Berguna saat berbelanja dengan "
#~ "banyak orang tetapi mungkin menggunakan sedikit data seluler. Jika lebih "
#~ "rendah dari batas instance, reset saat menyimpan."
#~ msgid "Makes the navbar stick to the top of the page."
#~ msgstr "Membuat navbar menempel di bagian atas halaman."
#~ msgid "Automatically add meal plan ingredients to shopping list."
#~ msgstr "Secara otomatis menambahkan bahan rencana makan ke daftar belanja."
#~ msgid "Exclude ingredients that are on hand."
#~ msgstr "Kecualikan bahan-bahan yang ada."
#~ msgid "Will optimize the UI for use with your left hand."
#~ msgstr "Akan mengoptimalkan UI untuk digunakan dengan tangan kiri Anda."
#~ msgid "You must provide at least a recipe or a title."
#~ msgstr "Anda harus memberikan setidaknya resep atau judul."
#~ msgid "You can list default users to share recipes with in the settings."
#~ msgstr ""
#~ "Anda dapat membuat daftar pengguna default untuk berbagi resep di "
#~ "pengaturan."
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "Anda dapat menggunakan penurunan harga untuk memformat bidang ini. Lihat "
#~ "dokumen di sini"
================================================
FILE: cookbook/locale/it/LC_MESSAGES/django.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
# Translators:
# Alessandro Spallina , 2020
# Oliver Thomas Cervera , 2021
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-22 20:15+0200\n"
"PO-Revision-Date: 2026-02-02 11:45+0000\n"
"Last-Translator: Vincenzo Reale \n"
"Language-Team: Italian \n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.13.3\n"
#: .\cookbook\forms.py:50
msgid "Default"
msgstr "Predefinito"
#: .\cookbook\forms.py:77
msgid ""
"To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything."
msgstr ""
"Per prevenire duplicati, vengono ignorate le ricette che hanno lo stesso "
"nome di quelle esistenti. Seleziona questa casella per importare tutto."
#: .\cookbook\forms.py:108
msgid "Maximum number of users for this space reached."
msgstr "È stato raggiunto il numero massimo di utenti per questo spazio."
#: .\cookbook\forms.py:114
msgid "Email address already taken!"
msgstr "Questo indirizzo email è già in uso!"
#: .\cookbook\forms.py:121
msgid ""
"An email address is not required but if present the invite link will be sent "
"to the user."
msgstr ""
"Non è obbligatorio specificare l'indirizzo email, ma se presente sarà "
"utilizzato per inviare all'utente un collegamento di invito."
#: .\cookbook\forms.py:133
msgid "Name already taken."
msgstr "Nome già in uso."
#: .\cookbook\forms.py:144 .\cookbook\forms.py:158
msgid "Accept Terms and Privacy"
msgstr "Accetta i termini d'uso e privacy"
#: .\cookbook\helper\AllAuthCustomAdapter.py:41
msgid ""
"In order to prevent spam, the requested email was not send. Please wait a "
"few minutes and try again."
msgstr ""
"Per evitare spam, il messaggio non è stato inviato. Aspetta qualche minuto e "
"riprova."
#: .\cookbook\helper\permission_helper.py:165
#: .\cookbook\helper\permission_helper.py:186 .\cookbook\views\views.py:138
msgid "You are not logged in and therefore cannot view this page!"
msgstr ""
"Non hai effettuato l'accesso e quindi non puoi visualizzare questa pagina!"
#: .\cookbook\helper\permission_helper.py:168
#: .\cookbook\helper\permission_helper.py:173
#: .\cookbook\helper\permission_helper.py:198
#: .\cookbook\helper\permission_helper.py:265
#: .\cookbook\helper\permission_helper.py:279
#: .\cookbook\helper\permission_helper.py:290
#: .\cookbook\helper\permission_helper.py:301
#: .\cookbook\helper\permission_helper.py:317
#: .\cookbook\helper\permission_helper.py:343
#: .\cookbook\helper\permission_helper.py:359
msgid "You do not have the required permissions to view this page!"
msgstr "Non hai i permessi necessari per visualizzare questa pagina!"
#: .\cookbook\helper\permission_helper.py:191
#: .\cookbook\helper\permission_helper.py:214
#: .\cookbook\helper\permission_helper.py:236
#: .\cookbook\helper\permission_helper.py:251
msgid "You cannot interact with this object as it is not owned by you!"
msgstr "Non puoi interagire con questo oggetto perché non sei il proprietario!"
#: .\cookbook\helper\permission_helper.py:420
msgid "You have reached the maximum number of recipes for your space."
msgstr "Hai raggiunto il numero massimo di ricette nella tua istanza."
#: .\cookbook\helper\permission_helper.py:432
msgid "You have more users than allowed in your space."
msgstr "Hai più utenti di quanto consentito nella tua istanza."
#: .\cookbook\helper\recipe_url_import.py:319
msgid "reverse rotation"
msgstr "rotazione inversa"
#: .\cookbook\helper\recipe_url_import.py:320
msgid "careful rotation"
msgstr "rotazione con cura"
#: .\cookbook\helper\recipe_url_import.py:321
msgid "knead"
msgstr "impastare"
#: .\cookbook\helper\recipe_url_import.py:322
msgid "thicken"
msgstr "addensare"
#: .\cookbook\helper\recipe_url_import.py:323
msgid "warm up"
msgstr "riscaldare"
#: .\cookbook\helper\recipe_url_import.py:324
msgid "ferment"
msgstr "fermentare"
#: .\cookbook\helper\recipe_url_import.py:325
msgid "slow cook"
msgstr "cottura lenta"
#: .\cookbook\helper\recipe_url_import.py:326
msgid "egg boiler"
msgstr "bollitore per uova"
#: .\cookbook\helper\recipe_url_import.py:327
msgid "kettle"
msgstr "teiera"
#: .\cookbook\helper\recipe_url_import.py:328
msgid "blend"
msgstr "miscela"
#: .\cookbook\helper\recipe_url_import.py:329
msgid "pre-clean"
msgstr "pre-pulizia"
#: .\cookbook\helper\recipe_url_import.py:330
msgid "high temperature"
msgstr "alta temperatura"
#: .\cookbook\helper\recipe_url_import.py:331
msgid "rice cooker"
msgstr "cuociriso"
#: .\cookbook\helper\recipe_url_import.py:332
msgid "caramelize"
msgstr "caramellare"
#: .\cookbook\helper\recipe_url_import.py:333
msgid "peeler"
msgstr "pelapatate"
#: .\cookbook\helper\recipe_url_import.py:334
msgid "slicer"
msgstr "affettatrice"
#: .\cookbook\helper\recipe_url_import.py:335
msgid "grater"
msgstr "grattugia"
#: .\cookbook\helper\recipe_url_import.py:336
msgid "spiralizer"
msgstr "tagliaverdure a spirale"
#: .\cookbook\helper\recipe_url_import.py:337
msgid "sous-vide"
msgstr "sottovuoto"
#: .\cookbook\helper\shopping_helper.py:150
msgid "You must supply a servings size"
msgstr "Devi fornire le dimensione delle porzioni"
#: .\cookbook\helper\template_helper.py:97
#: .\cookbook\helper\template_helper.py:99
#: .\cookbook\helper\template_helper.py:101
msgid "Could not parse template code."
msgstr "Impossibile elaborare il codice del modello."
#: .\cookbook\integration\copymethat.py:44
#: .\cookbook\integration\melarecipes.py:37
msgid "Favorite"
msgstr "Preferito"
#: .\cookbook\integration\copymethat.py:50
msgid "I made this"
msgstr "L'ho preparato"
#: .\cookbook\integration\integration.py:238
msgid ""
"Importer expected a .zip file. Did you choose the correct importer type for "
"your data ?"
msgstr ""
"La procedura di importazione necessita di un file .zip. Hai scelto il tipo "
"di importazione corretta per i tuoi dati?"
#: .\cookbook\integration\integration.py:241
msgid ""
"An unexpected error occurred during the import. Please make sure you have "
"uploaded a valid file."
msgstr ""
"Un errore imprevisto si è verificato durante l'importazione. Assicurati di "
"aver caricato un file valido."
#: .\cookbook\integration\integration.py:246
msgid "The following recipes were ignored because they already existed:"
msgstr "Le seguenti ricette sono state ignorate perché già esistenti:"
#: .\cookbook\integration\integration.py:250
#, python-format
msgid "Imported %s recipes."
msgstr "Importate %s ricette."
#: .\cookbook\integration\mealie1.py:210
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "Calories"
msgstr "Calorie"
#: .\cookbook\integration\mealie1.py:211
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
msgid "Carbohydrates"
msgstr "Carboidrati"
#: .\cookbook\integration\mealie1.py:212
msgid "Cholesterol"
msgstr "Colesterolo"
#: .\cookbook\integration\mealie1.py:213
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
msgid "Fat"
msgstr "Grassi"
#: .\cookbook\integration\mealie1.py:214
msgid "Fiber"
msgstr "Fibra"
#: .\cookbook\integration\mealie1.py:215
msgid "Protein"
msgstr "Proteina"
#: .\cookbook\integration\mealie1.py:216
msgid "Saturated Fat"
msgstr "Grasso saturo"
#: .\cookbook\integration\mealie1.py:217
msgid "Sodium"
msgstr "Sodio"
#: .\cookbook\integration\mealie1.py:218
msgid "Sugar"
msgstr "Zucchero"
#: .\cookbook\integration\mealie1.py:219
msgid "Trans Fat"
msgstr "Grasso trans"
#: .\cookbook\integration\mealie1.py:220
msgid "Unsaturated Fat"
msgstr "Grasso insaturo"
#: .\cookbook\integration\openeats.py:28
msgid "Recipe source:"
msgstr "Fonte ricetta:"
#: .\cookbook\integration\paprika.py:49
msgid "Notes"
msgstr "Note"
#: .\cookbook\integration\paprika.py:52
msgid "Nutritional Information"
msgstr "Informazioni nutrizionali"
#: .\cookbook\integration\paprika.py:56
msgid "Source"
msgstr "Fonte"
#: .\cookbook\integration\recettetek.py:55
#: .\cookbook\integration\recipekeeper.py:70
msgid "Imported from"
msgstr "Importato da"
#: .\cookbook\integration\saffron.py:23
msgid "Servings"
msgstr "Porzioni"
#: .\cookbook\integration\saffron.py:25
msgid "Waiting time"
msgstr "Tempo di cottura"
#: .\cookbook\integration\saffron.py:27
msgid "Preparation Time"
msgstr "Tempo di preparazione"
#: .\cookbook\integration\saffron.py:29 .\cookbook\templates\index.html:6
msgid "Cookbook"
msgstr "Ricettario"
#: .\cookbook\integration\saffron.py:31
msgid "Section"
msgstr "Sezione"
#: .\cookbook\management\commands\fix_duplicate_properties.py:15
msgid "Fixes foods with "
msgstr "Corregge alimenti con "
#: .\cookbook\management\commands\rebuildindex.py:14
msgid "Rebuilds full text search index on Recipe"
msgstr "Ricostruisce l'indice di ricerca full text per la ricetta"
#: .\cookbook\management\commands\rebuildindex.py:18
msgid "Only Postgresql databases use full text search, no index to rebuild"
msgstr ""
"Solo i database Postgresql usano l'indice di ricerca full text, non ci sono "
"indici da ricostruire"
#: .\cookbook\management\commands\rebuildindex.py:29
msgid "Recipe index rebuild complete."
msgstr "È stato ricostruito l'indice della ricetta."
#: .\cookbook\management\commands\rebuildindex.py:31
msgid "Recipe index rebuild failed."
msgstr "Non è stato possibile ricostruire l'indice della ricetta."
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
msgid "Breakfast"
msgstr "Colazione"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:19
msgid "Lunch"
msgstr "Pranzo"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:24
msgid "Dinner"
msgstr "Cena"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:29 .\cookbook\models.py:971
msgid "Other"
msgstr "Altro"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "g"
msgstr "g"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "Proteins"
msgstr "Proteine"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "kcal"
msgstr "kcal"
#: .\cookbook\models.py:325
msgid ""
"Maximum file storage for space in MB. 0 for unlimited, -1 to disable file "
"upload."
msgstr ""
"Archiviazione massima in MB. 0 per illimitata, -1 per disabilitare il "
"caricamento dei file."
#: .\cookbook\models.py:513
msgid "Search"
msgstr "Cerca"
#: .\cookbook\models.py:514
msgid "Meal-Plan"
msgstr "Piano alimentare"
#: .\cookbook\models.py:515
msgid "Books"
msgstr "Libri"
#: .\cookbook\models.py:516 .\cookbook\views\views.py:416
#: .\cookbook\views\views.py:417
msgid "Shopping"
msgstr "Spesa"
#: .\cookbook\models.py:967
msgid "Nutrition"
msgstr "Nutrienti"
#: .\cookbook\models.py:968
msgid "Allergen"
msgstr "Allergene"
#: .\cookbook\models.py:969
msgid "Price"
msgstr "Prezzo"
#: .\cookbook\models.py:970
msgid "Goal"
msgstr "Obiettivo"
#: .\cookbook\models.py:1467 .\cookbook\templates\search_info.html:28
msgid "Simple"
msgstr "Semplice"
#: .\cookbook\models.py:1468 .\cookbook\templates\search_info.html:33
msgid "Phrase"
msgstr "Frase"
#: .\cookbook\models.py:1469 .\cookbook\templates\search_info.html:38
msgid "Web"
msgstr "Web"
#: .\cookbook\models.py:1470 .\cookbook\templates\search_info.html:47
msgid "Raw"
msgstr "Crudo"
#: .\cookbook\models.py:1525
msgid "Food Alias"
msgstr "Alias alimento"
#: .\cookbook\models.py:1526
msgid "Unit Alias"
msgstr "Alias unità"
#: .\cookbook\models.py:1527
msgid "Keyword Alias"
msgstr "Alias parola chiave"
#: .\cookbook\models.py:1528
msgid "Description Replace"
msgstr "Sostituisci descrizione"
#: .\cookbook\models.py:1529
msgid "Instruction Replace"
msgstr "Sostituisci istruzioni"
#: .\cookbook\models.py:1530
msgid "Never Unit"
msgstr "Mai unità"
#: .\cookbook\models.py:1531
msgid "Transpose Words"
msgstr "Trasponi parole"
#: .\cookbook\models.py:1532
msgid "Food Replace"
msgstr "Sostituisci alimento"
#: .\cookbook\models.py:1533
msgid "Unit Replace"
msgstr "Sostituisci unità"
#: .\cookbook\models.py:1534
msgid "Name Replace"
msgstr "Sostituisci nome"
#: .\cookbook\models.py:1564
msgid "Recipe"
msgstr "Ricetta"
#: .\cookbook\models.py:1565
msgid "Food"
msgstr "Alimento"
#: .\cookbook\models.py:1566
msgid "Keyword"
msgstr "Parola chiave"
#: .\cookbook\serializer.py:262
msgid "File uploads are not enabled for this Space."
msgstr "Il caricamento dei file non è abilitato in questa istanza."
#: .\cookbook\serializer.py:273
msgid "You have reached your file upload limit."
msgstr "Hai raggiungo il limite per il caricamento dei file."
#: .\cookbook\serializer.py:281
msgid "The given file type is not allowed."
msgstr "Il tipo di filo specificato non è consentito."
#: .\cookbook\serializer.py:421 .\cookbook\views\views.py:94
msgid ""
"You have the reached the maximum amount of spaces that can be owned by you."
msgstr "Hai raggiunto il numero massimo di istanze di tua proprietà."
#: .\cookbook\serializer.py:434
msgid "Space Name must be unique."
msgstr "Il nome dello spazio deve essere univoco."
#: .\cookbook\serializer.py:469
msgid "Cannot modify Space owner permission."
msgstr "Impossibile modificare i permessi del proprietario dell'istanza."
#: .\cookbook\serializer.py:1596
msgid "Hello"
msgstr "Ciao"
#: .\cookbook\serializer.py:1596
msgid "You have been invited by "
msgstr "Sei stato invitato da "
#: .\cookbook\serializer.py:1598
msgid " to join their Tandoor Recipes space "
msgstr " a entrare nella sua istanza di Tandoor Recipes "
#: .\cookbook\serializer.py:1600
msgid "Click the following link to activate your account: "
msgstr "Fai clic sul collegamento seguente per attivare il tuo account: "
#: .\cookbook\serializer.py:1602
msgid ""
"If the link does not work use the following code to manually join the space: "
msgstr ""
"Se il collegamento non funziona, usa il seguente codice per entrare "
"manualmente nell'istanza: "
#: .\cookbook\serializer.py:1604
msgid "The invitation is valid until "
msgstr "L'invito è valido fino al "
#: .\cookbook\serializer.py:1606
msgid ""
"Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub "
msgstr ""
"Tandoor Recipes è un gestore di ricette Open Source. Dagli un'occhiata su "
"GitHub "
#: .\cookbook\serializer.py:1609
msgid "Tandoor Recipes Invite"
msgstr "Invito per Tandoor Recipes"
#: .\cookbook\serializer.py:1813
msgid "Existing shopping list to update"
msgstr "Lista della spesa esistente da aggiornare"
#: .\cookbook\serializer.py:1815
msgid ""
"List of ingredient IDs from the recipe to add, if not provided all "
"ingredients will be added."
msgstr ""
"Lista degli ID degli ingredienti dalla ricetta da aggiungere, se non è "
"fornita saranno aggiunti tutti gli ingredienti."
#: .\cookbook\serializer.py:1817
msgid ""
"Providing a list_recipe ID and servings of 0 will delete that shopping list."
msgstr ""
"Fornendo un ID list_recipe e impostando le porzioni a 0, la lista della "
"spesa verrà eliminata."
#: .\cookbook\serializer.py:1826
msgid "Amount of food to add to the shopping list"
msgstr "Quantità di alimenti da aggiungere alla lista della spesa"
#: .\cookbook\serializer.py:1828
msgid "ID of unit to use for the shopping list"
msgstr "ID dell'unità da usare per la lista della spesa"
#: .\cookbook\serializer.py:1830
msgid "When set to true will delete all food from active shopping lists."
msgstr ""
"Quando impostato su vero, eliminerà tutti gli alimenti dalle liste della "
"spesa attive."
#: .\cookbook\templates\404.html:5
msgid "404 Error"
msgstr "Errore 404"
#: .\cookbook\templates\404.html:18
msgid "The page you are looking for could not be found."
msgstr "La pagina che stai cercando non è stata trovata."
#: .\cookbook\templates\404.html:33
msgid "Take me Home"
msgstr "Portami nella Home"
#: .\cookbook\templates\404.html:35
msgid "Report a Bug"
msgstr "Segnala un bug"
#: .\cookbook\templates\account\email.html:6
#: .\cookbook\templates\account\email.html:10
msgid "E-mail Addresses"
msgstr "Indirizzi email"
#: .\cookbook\templates\account\email.html:12
msgid "The following e-mail addresses are associated with your account:"
msgstr "I seguenti indirizzi email sono associati al tuo account:"
#: .\cookbook\templates\account\email.html:29
msgid "Verified"
msgstr "Verificato"
#: .\cookbook\templates\account\email.html:31
msgid "Unverified"
msgstr "Non verificato"
#: .\cookbook\templates\account\email.html:33
msgid "Primary"
msgstr "Principale"
#: .\cookbook\templates\account\email.html:40
msgid "Make Primary"
msgstr "Rendi principale"
#: .\cookbook\templates\account\email.html:42
msgid "Re-send Verification"
msgstr "Invia verifica di nuovo"
#: .\cookbook\templates\account\email.html:43
#: .\cookbook\templates\socialaccount\connections.html:36
msgid "Remove"
msgstr "Rimuovi"
#: .\cookbook\templates\account\email.html:51
msgid "Warning:"
msgstr "Attenzione:"
#: .\cookbook\templates\account\email.html:51
msgid ""
"You currently do not have any e-mail address set up. You should really add "
"an e-mail address so you can receive notifications, reset your password, etc."
msgstr ""
"Non hai configurato un indirizzo email. Se lo facessi, potresti ricevere "
"notifiche, ripristinare la password e altro."
#: .\cookbook\templates\account\email.html:57
msgid "Add E-mail Address"
msgstr "Aggiungi indirizzo email"
#: .\cookbook\templates\account\email.html:62
msgid "Add E-mail"
msgstr "Aggiungi email"
#: .\cookbook\templates\account\email.html:72
msgid "Do you really want to remove the selected e-mail address?"
msgstr "Sei sicuro di voler rimuovere l'indirizzo email selezionato?"
#: .\cookbook\templates\account\email_confirm.html:6
#: .\cookbook\templates\account\email_confirm.html:10
msgid "Confirm E-mail Address"
msgstr "Conferma indirizzo email"
#: .\cookbook\templates\account\email_confirm.html:16
#, python-format
msgid ""
"Please confirm that\n"
" %(email)s is an e-mail address "
"for user %(user_display)s\n"
" ."
msgstr ""
"Conferma che\n"
" %(email)s è un indirizzo email "
"per l'utente %(user_display)s\n"
" ."
#: .\cookbook\templates\account\email_confirm.html:22
msgid "Confirm"
msgstr "Conferma"
#: .\cookbook\templates\account\email_confirm.html:29
#, python-format
msgid ""
"This e-mail confirmation link expired or is invalid. Please\n"
" issue a new e-mail confirmation "
"request."
msgstr ""
"Questo collegamento di conferma è scaduto o non è valido. Puoi\n"
" richiedere un nuovo collegamento di "
"conferma."
#: .\cookbook\templates\account\login.html:8
#: .\cookbook\templates\openid\login.html:8
msgid "Login"
msgstr "Login"
#: .\cookbook\templates\account\login.html:15
#: .\cookbook\templates\account\login.html:31
#: .\cookbook\templates\account\password_reset.html:39
#: .\cookbook\templates\account\password_reset_done.html:31
#: .\cookbook\templates\account\signup.html:68
#: .\cookbook\templates\account\signup_closed.html:15
#: .\cookbook\templates\openid\login.html:15
#: .\cookbook\templates\openid\login.html:26
#: .\cookbook\templates\socialaccount\authentication_error.html:15
msgid "Sign In"
msgstr "Accedi"
#: .\cookbook\templates\account\login.html:34
#: .\cookbook\templates\account\password_reset.html:41
#: .\cookbook\templates\account\password_reset_done.html:33
#: .\cookbook\templates\socialaccount\signup.html:8
#: .\cookbook\templates\socialaccount\signup.html:56
msgid "Sign Up"
msgstr "Iscriviti"
#: .\cookbook\templates\account\login.html:38
msgid "Lost your password?"
msgstr "Hai dimenticato la password?"
#: .\cookbook\templates\account\login.html:39
#: .\cookbook\templates\account\password_reset.html:29
msgid "Reset My Password"
msgstr "Reimposta password"
#: .\cookbook\templates\account\login.html:50
msgid "Social Login"
msgstr "Accesso con social network"
#: .\cookbook\templates\account\login.html:51
msgid "You can use any of the following providers to sign in."
msgstr "Puoi usare uno dei seguenti provider per accedere."
#: .\cookbook\templates\account\logout.html:5
#: .\cookbook\templates\account\logout.html:9
#: .\cookbook\templates\account\logout.html:18
msgid "Sign Out"
msgstr "Esci"
#: .\cookbook\templates\account\logout.html:11
msgid "Are you sure you want to sign out?"
msgstr "Sei sicuro di voler uscire?"
#: .\cookbook\templates\account\password_change.html:6
#: .\cookbook\templates\account\password_change.html:10
#: .\cookbook\templates\account\password_change.html:15
#: .\cookbook\templates\account\password_reset_from_key.html:7
#: .\cookbook\templates\account\password_reset_from_key.html:13
#: .\cookbook\templates\account\password_reset_from_key_done.html:7
#: .\cookbook\templates\account\password_reset_from_key_done.html:13
msgid "Change Password"
msgstr "Cambia password"
#: .\cookbook\templates\account\password_change.html:16
msgid "Forgot Password?"
msgstr "Hai dimenticato la password?"
#: .\cookbook\templates\account\password_reset.html:7
#: .\cookbook\templates\account\password_reset.html:13
#: .\cookbook\templates\account\password_reset_done.html:7
#: .\cookbook\templates\account\password_reset_done.html:18
msgid "Password Reset"
msgstr "Recupero password"
#: .\cookbook\templates\account\password_reset.html:24
msgid ""
"Forgotten your password? Enter your e-mail address below, and we'll send you "
"an e-mail allowing you to reset it."
msgstr ""
"Hai dimenticato la password? Digita il tuo indirizzo email e riceverai una "
"email con le istruzioni per il ripristino."
#: .\cookbook\templates\account\password_reset.html:32
msgid "Password reset is disabled on this instance."
msgstr "Il recupero della password è disabilitato in questa istanza."
#: .\cookbook\templates\account\password_reset_done.html:25
msgid ""
"We have sent you an e-mail. Please contact us if you do not receive it "
"within a few minutes."
msgstr ""
"Ti abbiamo inviato un messaggio di posta. Contattaci se non lo ricevi entro "
"qualche minuto."
#: .\cookbook\templates\account\password_reset_from_key.html:13
msgid "Bad Token"
msgstr "Token non valido"
#: .\cookbook\templates\account\password_reset_from_key.html:25
#, python-format
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used.\n"
" Please request a new "
"password reset."
msgstr ""
"Il collegamento per il ripristino della password non è corretto, "
"probabilmente perché è stato già utilizzato.\n"
" Puoi richiedere un nuovo ripristino della password."
#: .\cookbook\templates\account\password_reset_from_key.html:33
msgid "change password"
msgstr "cambia password"
#: .\cookbook\templates\account\password_reset_from_key.html:36
#: .\cookbook\templates\account\password_reset_from_key_done.html:19
msgid "Your password is now changed."
msgstr "La tua password è stata aggiornata."
#: .\cookbook\templates\account\password_set.html:6
#: .\cookbook\templates\account\password_set.html:10
#: .\cookbook\templates\account\password_set.html:15
msgid "Set Password"
msgstr "Imposta password"
#: .\cookbook\templates\account\signup.html:5
msgid "Register"
msgstr "Registrati"
#: .\cookbook\templates\account\signup.html:11
msgid "Create an Account"
msgstr "Crea un account"
#: .\cookbook\templates\account\signup.html:41
msgid "I accept the follwoing"
msgstr "Accetto i seguenti"
#: .\cookbook\templates\account\signup.html:44
#: .\cookbook\templates\socialaccount\signup.html:35
msgid "Terms and Conditions"
msgstr "Termini e condizioni"
#: .\cookbook\templates\account\signup.html:47
#: .\cookbook\templates\socialaccount\signup.html:38
msgid "and"
msgstr "e"
#: .\cookbook\templates\account\signup.html:51
#: .\cookbook\templates\socialaccount\signup.html:42
msgid "Privacy Policy"
msgstr "Policy di riservatezza"
#: .\cookbook\templates\account\signup.html:64
msgid "Create User"
msgstr "Crea utente"
#: .\cookbook\templates\account\signup.html:68
msgid "Already have an account?"
msgstr "Hai già un account?"
#: .\cookbook\templates\account\signup_closed.html:5
#: .\cookbook\templates\account\signup_closed.html:11
msgid "Sign Up Closed"
msgstr "Iscrizioni chiuse"
#: .\cookbook\templates\account\signup_closed.html:13
msgid "We are sorry, but the sign up is currently closed."
msgstr "Spiacenti, al momento le iscrizioni sono chiuse."
#: .\cookbook\templates\frontend\tandoor.html:15
msgid "Tandoor Recipe Manager"
msgstr "Gestore delle ricette Tandoor"
#: .\cookbook\templates\index.html:28
msgid "Search recipe ..."
msgstr "Cerca ricetta ..."
#: .\cookbook\templates\index.html:43
msgid "New Recipe"
msgstr "Nuova Ricetta"
#: .\cookbook\templates\index.html:46
msgid "Import Recipe"
msgstr "Importa ricetta"
#: .\cookbook\templates\index.html:52
msgid "Advanced Search"
msgstr "Ricerca Avanzata"
#: .\cookbook\templates\index.html:56
msgid "Reset Search"
msgstr "Ripristina Ricerca"
#: .\cookbook\templates\index.html:84
msgid "Last viewed"
msgstr "Recenti"
#: .\cookbook\templates\index.html:86
msgid "Recipes"
msgstr "Ricette"
#: .\cookbook\templates\index.html:93
msgid "Log in to view recipes"
msgstr "Effettua l'accesso per vedere le ricette"
#: .\cookbook\templates\markdown_info.html:5
#: .\cookbook\templates\markdown_info.html:13
msgid "Markdown Info"
msgstr "Informazioni su Markdown"
#: .\cookbook\templates\markdown_info.html:14
msgid ""
"\n"
" Markdown is lightweight markup language that can be used to format "
"plain text easily.\n"
" This site uses the Python Markdown library to\n"
" convert your text into nice looking HTML. Its full markdown "
"documentation can be found\n"
" here.\n"
" An incomplete but most likely sufficient documentation can be found "
"below.\n"
" "
msgstr ""
"\n"
" Markdown è un linguaggio di markup molto leggero che può essere "
"utilizzato per formattare facilmente del testo.\n"
" Questo sito utilizza la libreria Python Markdown per\n"
" convertire il tuo testo in HTML formattato. È possibile trovare la "
"documentazione completa del markdown\n"
" qui.\n"
" Di seguito è possibile trovare una documentazione incompleta ma "
"probabilmente sufficiente.\n"
" "
#: .\cookbook\templates\markdown_info.html:25
msgid "Headers"
msgstr "Intestazioni"
#: .\cookbook\templates\markdown_info.html:54
msgid "Formatting"
msgstr "Formattazione"
#: .\cookbook\templates\markdown_info.html:56
#: .\cookbook\templates\markdown_info.html:72
msgid "Line breaks are inserted by adding two spaces after the end of a line"
msgstr ""
"Le interruzioni di riga vengono inserite aggiungendo due spazi dopo la fine "
"di una riga"
#: .\cookbook\templates\markdown_info.html:57
#: .\cookbook\templates\markdown_info.html:73
msgid "or by leaving a blank line in between."
msgstr "oppure lasciando una riga vuota tra di loro."
#: .\cookbook\templates\markdown_info.html:59
#: .\cookbook\templates\markdown_info.html:74
msgid "This text is bold"
msgstr "Questo testo è in grassetto"
#: .\cookbook\templates\markdown_info.html:60
#: .\cookbook\templates\markdown_info.html:75
msgid "This text is italic"
msgstr "Questo testo è in corsivo"
#: .\cookbook\templates\markdown_info.html:61
#: .\cookbook\templates\markdown_info.html:77
msgid "Blockquotes are also possible"
msgstr "Sono possibili anche blockquote"
#: .\cookbook\templates\markdown_info.html:84
msgid "Lists"
msgstr "Liste"
#: .\cookbook\templates\markdown_info.html:85
msgid ""
"Lists can ordered or unordered. It is important to leave a blank line "
"before the list!"
msgstr ""
"Le liste possono essere ordinate o no. È importante lasciare una riga "
"vuota prima della lista!"
#: .\cookbook\templates\markdown_info.html:87
#: .\cookbook\templates\markdown_info.html:108
msgid "Ordered List"
msgstr "Lista ordinata"
#: .\cookbook\templates\markdown_info.html:89
#: .\cookbook\templates\markdown_info.html:90
#: .\cookbook\templates\markdown_info.html:91
#: .\cookbook\templates\markdown_info.html:110
#: .\cookbook\templates\markdown_info.html:111
#: .\cookbook\templates\markdown_info.html:112
msgid "unordered list item"
msgstr "elemento di lista non ordinata"
#: .\cookbook\templates\markdown_info.html:93
#: .\cookbook\templates\markdown_info.html:114
msgid "Unordered List"
msgstr "Lista non ordinata"
#: .\cookbook\templates\markdown_info.html:95
#: .\cookbook\templates\markdown_info.html:96
#: .\cookbook\templates\markdown_info.html:97
#: .\cookbook\templates\markdown_info.html:116
#: .\cookbook\templates\markdown_info.html:117
#: .\cookbook\templates\markdown_info.html:118
msgid "ordered list item"
msgstr "elemento di lista ordinata"
#: .\cookbook\templates\markdown_info.html:125
msgid "Images & Links"
msgstr "Immagini e collegamenti"
#: .\cookbook\templates\markdown_info.html:126
msgid ""
"Links can be formatted with Markdown. This application also allows to paste "
"links directly into markdown fields without any formatting."
msgstr ""
"I collegamenti possono essere formattati con Markdown. Questa applicazione "
"consente anche di incollare i collegamenti direttamente nei campi markdown "
"senza alcuna formattazione."
#: .\cookbook\templates\markdown_info.html:132
#: .\cookbook\templates\markdown_info.html:145
msgid "This will become an image"
msgstr "Questo oggetto diventerà un'immagine"
#: .\cookbook\templates\markdown_info.html:152
msgid "Tables"
msgstr "Tabelle"
#: .\cookbook\templates\markdown_info.html:153
msgid ""
"Markdown tables are hard to create by hand. It is recommended to use a table "
"editor like this one."
msgstr ""
"Le tabelle in markdown sono difficili da creare a mano. Si consiglia "
"l'utilizzo di un editor di tabelle come questo."
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:171
#: .\cookbook\templates\markdown_info.html:177
msgid "Table"
msgstr "Tabella"
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:172
msgid "Header"
msgstr "Intestazione"
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:178
msgid "Cell"
msgstr "Cella"
#: .\cookbook\templates\no_groups_info.html:5
#: .\cookbook\templates\no_groups_info.html:12
msgid "No Permissions"
msgstr "Nessun permesso"
#: .\cookbook\templates\no_groups_info.html:17
msgid "You do not have any groups and therefor cannot use this application."
msgstr ""
"Non fai parte di un gruppo e questo non ti consente di usare l'applicazione."
#: .\cookbook\templates\no_groups_info.html:18
#: .\cookbook\templates\no_perm_info.html:15
msgid "Please contact your administrator."
msgstr "Contatta il tuo amministratore."
#: .\cookbook\templates\no_perm_info.html:5
#: .\cookbook\templates\no_perm_info.html:12
msgid "No Permission"
msgstr "Nessun permesso"
#: .\cookbook\templates\no_perm_info.html:15
msgid ""
"You do not have the required permissions to view this page or perform this "
"action."
msgstr ""
"Non hai i permessi necessari per visualizzare questa pagina o completare "
"l'operazione."
#: .\cookbook\templates\offline.html:5
msgid "Offline"
msgstr "Non in linea"
#: .\cookbook\templates\openid\login.html:27
#: .\cookbook\templates\socialaccount\authentication_error.html:27
msgid "Back"
msgstr "Indietro"
#: .\cookbook\templates\rest_framework\api.html:5
msgid "Recipe Home"
msgstr "Pagina iniziale ricette"
#: .\cookbook\templates\rest_framework\api.html:11
msgid "API Documentation"
msgstr "Documentazione API"
#: .\cookbook\templates\search_info.html:5
#: .\cookbook\templates\search_info.html:9
msgid "Search Settings"
msgstr "Impostazioni di ricerca"
#: .\cookbook\templates\search_info.html:10
msgid ""
"\n"
" Creating the best search experience is complicated and weighs "
"heavily on your personal configuration. \n"
" Changing any of the search settings can have significant impact on "
"the speed and quality of the results.\n"
" Search Methods, Trigrams and Full Text Search configurations are "
"only available if you are using Postgres for your database.\n"
" "
msgstr ""
"\n"
" Creare la migliore esperienza di ricerca è complicato e pesa molto "
"sulla tua configurazione. \n"
" Cambiare una delle opzioni di ricerca può avere impatto "
"significativo sulla velocità e qualità dei risultati.\n"
" Metodi di ricerca, trigrammi e ricerca Full Text sono disponibili "
"solo se stati usando un database Postgres.\n"
" "
#: .\cookbook\templates\search_info.html:19
msgid "Search Methods"
msgstr "Metodi di ricerca"
#: .\cookbook\templates\search_info.html:23
msgid ""
" \n"
" Full text searches attempt to normalize the words provided to "
"match common variants. For example: 'forked', 'forking', 'forks' will all "
"normalize to 'fork'.\n"
" There are several methods available, described below, that will "
"control how the search behavior should react when multiple words are "
"searched.\n"
" Full technical details on how these operate can be viewed on Postgresql's website.\n"
" "
msgstr ""
" \n"
" Le ricerche full-text cercano di normalizzare le parole fornite "
"per abbinare varianti comuni. Ad esempio, 'separato', 'separando', 'separa' "
"verranno tutti normalizzati in 'separare'.\n"
" Ci sono diversi metodi disponibili, descritti di seguito, che "
"controlleranno il comportamento della ricerca in caso di ricerca con più "
"parole.\n"
" I dettagli tecnici completi su come questi funzionano possono "
"essere visualizzati sul sito web di Postgresql."
"a>\n"
" "
#: .\cookbook\templates\search_info.html:29
msgid ""
" \n"
" Simple searches ignore punctuation and common words such as "
"'the', 'a', 'and'. And will treat separate words as required.\n"
" Searching for 'apple or flour' will return any recipe that "
"includes both 'apple' and 'flour' anywhere in the fields that have been "
"selected for a full text search.\n"
" "
msgstr ""
" \n"
" Ricerche semplici ignorano la punteggiatura e parole comuni come "
"\"il\", \"un\", \"e\". E tratterà separatamente le parole come necessario.\n"
" Cercare \"mela o farina\" restituisce ogni ricetta che contiene "
"sia \"mele\" che \"farina\" ovunque nei campi che sono stati selezionati per "
"una ricerca completa di testo.\n"
" "
#: .\cookbook\templates\search_info.html:34
msgid ""
" \n"
" Phrase searches ignore punctuation, but will search for all of "
"the words in the exact order provided.\n"
" Searching for 'apple or flour' will only return a recipe that "
"includes the exact phrase 'apple or flour' in any of the fields that have "
"been selected for a full text search.\n"
" "
msgstr ""
" \n"
" Ricerche di frase ignorano la punteggiatura, ma cercano tutte "
"parole nell'esatto ordine indicato.\n"
" Cercare \"mele o farina\" restituisce una ricetta che contiene "
"l'esatta frase \"mele o farina\" in qualsiasi campo selezionato per una "
"ricerca completa di testo.\n"
" "
#: .\cookbook\templates\search_info.html:39
msgid ""
" \n"
" Web searches simulate functionality found on many web search "
"sites supporting special syntax.\n"
" Placing quotes around several words will convert those words "
"into a phrase.\n"
" 'or' is recognized as searching for the word (or phrase) "
"immediately before 'or' OR the word (or phrase) directly after.\n"
" '-' is recognized as searching for recipes that do not include "
"the word (or phrase) that comes immediately after. \n"
" For example searching for 'apple pie' or cherry -butter will "
"return any recipe that includes the phrase 'apple pie' or the word "
"'cherry' \n"
" in any field included in the full text search but exclude any "
"recipe that has the word 'butter' in any field included.\n"
" "
msgstr ""
" \n"
" Le ricerche web simulano le funzionalità presenti in molti siti "
"di ricerca web che supportano una sintassi speciale.\n"
" Inserire virgolette attorno a più parole convertirà tali parole "
"in una frase.\n"
" \"o\" è riconosciuto come ricerca della parola (o frase) "
"immediatamente prima di \"o\" OPPURE la parola (o frase) direttamente dopo.\n"
" \"-\" è riconosciuto come ricerca di ricette che non includono "
"la parola (o frase) che viene immediatamente dopo.\n"
" Ad esempio, la ricerca di \"apple pie\" o cherry -butter "
"restituirà qualsiasi ricetta che includa la frase \"apple pie\" o la parola "
"\"cherry\" in qualsiasi campo incluso nella ricerca full text, ma escluderà\n"
" qualsiasi ricetta che abbia la parola \"butter\" in qualsiasi "
"campo incluso.\n"
" "
#: .\cookbook\templates\search_info.html:48
msgid ""
" \n"
" Raw search is similar to Web except will take puncuation "
"operators such as '|', '&' and '()'\n"
" "
msgstr ""
" \n"
" La ricerca raw è simile a quella web, ma accetta operatori di "
"punteggiatura come '|', '&' e '()'\n"
" "
#: .\cookbook\templates\search_info.html:59
msgid ""
" \n"
" Another approach to searching that also requires Postgresql is "
"fuzzy search or trigram similarity. A trigram is a group of three "
"consecutive characters.\n"
" For example searching for 'apple' will create x trigrams 'app', "
"'ppl', 'ple' and will create a score of how closely words match the "
"generated trigrams.\n"
" One benefit of searching trigams is that a search for 'sandwich' "
"will find misspelled words such as 'sandwhich' that would be missed by other "
"methods.\n"
" "
msgstr ""
" \n"
" Un altro approccio alla ricerca che richiede anche Postgresql è "
"la ricerca vaga o similarità di trigrammi. Un trigramma è un gruppo di tre "
"caratteri consecutivi.\n"
" Ad esempio, la ricerca di \"apple\" creerà x trigrammi \"app\", "
"\"ppl\", \"ple\" e creerà un punteggio di quanto le parole corrispondono ai "
"trigrammi generati.\n"
" Un vantaggio della ricerca di trigrammi è che una ricerca di "
"\"sandwich\" troverà parole con errori di ortografia come \"sandwhich\" che "
"verrebbero perse con altri metodi.\n"
" "
#: .\cookbook\templates\search_info.html:69
msgid "Search Fields"
msgstr "Campi di ricerca"
#: .\cookbook\templates\search_info.html:73
msgid ""
" \n"
" Unaccent is a special case in that it enables searching a field "
"'unaccented' for each search style attempting to ignore accented values. \n"
" For example when you enable unaccent for 'Name' any search "
"(starts with, contains, trigram) will attempt the search ignoring accented "
"characters.\n"
" \n"
" For the other options, you can enable search on any or all "
"fields and they will be combined together with an assumed 'OR'.\n"
" For example enabling 'Name' for Starts With, 'Name' and "
"'Description' for Partial Match and 'Ingredients' and 'Keywords' for Full "
"Search\n"
" and searching for 'apple' will generate a search that will "
"return recipes that have:\n"
" - A recipe name that starts with 'apple'\n"
" - OR a recipe name that contains 'apple'\n"
" - OR a recipe description that contains 'apple'\n"
" - OR a recipe that will have a full text search match ('apple' "
"or 'apples') in ingredients\n"
" - OR a recipe that will have a full text search match in "
"Keywords\n"
"\n"
" Combining too many fields in too many types of search can have a "
"negative impact on performance, create duplicate results or return "
"unexpected results.\n"
" For example, enabling fuzzy search or partial matches will "
"interfere with web search methods. \n"
" Searching for 'apple -pie' with fuzzy search and full text "
"search will return the recipe Apple Pie. Though it is not included in the "
"full text results, it does match the trigram results.\n"
" "
msgstr ""
" \n"
" 'Senza accento' è un caso speciale in quanto consente di cercare "
"un campo 'non accentato' per ogni stile di ricerca che tenta di ignorare i "
"valori accentati.\n"
" Ad esempio, quando si abilita 'Senza accento' per 'Nome', "
"qualsiasi ricerca (inizia con, contiene, trigramma) tenterà di ignorare i "
"caratteri accentati.\n"
" \n"
" Per le altre opzioni, è possibile abilitare la ricerca su uno o "
"tutti i campi e saranno combinati insieme con un presunto 'OR'.\n"
" Ad esempio, abilitando 'Nome' per Inizia con, 'Nome' e "
"'Descrizione' per Corrispondenza parziale e 'Ingredienti' e 'Parole chiave' "
"per Ricerca completa\n"
" e cercando 'mela' sarà generata una ricerca che restituirà "
"ricette che hanno:\n"
" - Un nome di ricetta che inizia con 'mela'\n"
" - OPPURE un nome di ricetta che contiene 'mela'\n"
" - OPPURE una descrizione di ricetta che contiene 'mela'\n"
" - OPPURE una ricetta che avrà una corrispondenza di ricerca di "
"testo completo ('mela' o 'mele') negli ingredienti\n"
" - OPPURE una ricetta che avrà una corrispondenza di ricerca di "
"testo completo in Parole chiave\n"
"\n"
" Combinare troppi campi in troppi tipi di ricerca può avere un "
"impatto negativo sulle prestazioni, creare risultati duplicati o restituire "
"risultati inaspettati.\n"
" Ad esempio, abilitare la ricerca vaga o le corrispondenze "
"parziali interferirà con i metodi di ricerca web.nelle\n"
" Cercando 'apple -pie' con la ricerca vaga e la ricerca di testo "
"completo sarà restituita la ricetta Apple Pie. Sebbene non sia inclusa nei "
"risultati di testo completo, corrisponde ai risultati del trigramma.\n"
" "
#: .\cookbook\templates\search_info.html:95
msgid "Search Index"
msgstr "Indice di ricerca"
#: .\cookbook\templates\search_info.html:99
msgid ""
" \n"
" Trigram search and Full Text Search both rely on database "
"indexes to perform effectively. \n"
" You can rebuild the indexes on all fields in the Admin page for "
"Recipes and selecting all recipes and running 'rebuild index for selected "
"recipes'\n"
" You can also rebuild indexes at the command line by executing "
"the management command 'python manage.py rebuildindex'\n"
" "
msgstr ""
" \n"
" La ricerca di trigrammi e la ricerca di testo completo si basano "
"entrambe sugli indici del database per funzionare in modo efficace.\n"
" Puoi ricostruire gli indici su tutti i campi nella pagina di "
"amministrazione per le ricette, selezionando tutte le ricette ed eseguendo "
"'ricostruisci indice delle ricette selezionate'\n"
" Puoi anche ricostruire gli indici dalla riga di comando "
"eseguendo il comando di gestione 'python manage.py rebuildindex'\n"
" "
#: .\cookbook\templates\setup.html:6
msgid "Cookbook Setup"
msgstr "Configurazione ricettario"
#: .\cookbook\templates\setup.html:14
msgid "Setup"
msgstr "Configurazione"
#: .\cookbook\templates\setup.html:15
msgid ""
"To start using this application you must first create a superuser account."
msgstr ""
"Per iniziare a usare questa applicazione devi prima creare un super utente."
#: .\cookbook\templates\setup.html:20
msgid "Create Superuser account"
msgstr "Crea super utente"
#: .\cookbook\templates\socialaccount\authentication_error.html:7
#: .\cookbook\templates\socialaccount\authentication_error.html:23
msgid "Social Network Login Failure"
msgstr "Errore di accesso con social network"
#: .\cookbook\templates\socialaccount\authentication_error.html:25
msgid ""
"An error occurred while attempting to login via your social network account."
msgstr ""
"Si è verificato un errore durante il tentativo di accesso con account di "
"social network."
#: .\cookbook\templates\socialaccount\connections.html:4
#: .\cookbook\templates\socialaccount\connections.html:7
msgid "Account Connections"
msgstr "Collegamenti dell'account"
#: .\cookbook\templates\socialaccount\connections.html:10
msgid ""
"You can sign in to your account using any of the following third party\n"
" accounts:"
msgstr ""
"Puoi accedere al tuo account usando uno di questi \n"
" account di terze parti:"
#: .\cookbook\templates\socialaccount\connections.html:44
msgid ""
"You currently have no social network accounts connected to this account."
msgstr "Non hai account di social network collegati a questo account."
#: .\cookbook\templates\socialaccount\connections.html:47
msgid "Add a 3rd Party Account"
msgstr "Aggiungi un account di terze parti"
#: .\cookbook\templates\socialaccount\login.html:5
#: .\cookbook\templates\socialaccount\signup.html:5
msgid "Signup"
msgstr "Iscriviti"
#: .\cookbook\templates\socialaccount\login.html:9
#, python-format
msgid "Connect %(provider)s"
msgstr "Collega %(provider)s"
#: .\cookbook\templates\socialaccount\login.html:11
#, python-format
msgid "You are about to connect a new third party account from %(provider)s."
msgstr "Stai per collegare un nuovo account di terze parti da %(provider)s."
#: .\cookbook\templates\socialaccount\login.html:13
#, python-format
msgid "Sign In Via %(provider)s"
msgstr "Accedi tramite %(provider)s"
#: .\cookbook\templates\socialaccount\login.html:15
#, python-format
msgid "You are about to sign in using a third party account from %(provider)s."
msgstr ""
"Stai per fare l'accesso usando un account di terze parti da %(provider)s."
#: .\cookbook\templates\socialaccount\login.html:20
msgid "Continue"
msgstr "Continua"
#: .\cookbook\templates\socialaccount\signup.html:10
#, python-format
msgid ""
"You are about to use your\n"
" %(provider_name)s account to login to\n"
" %(site_name)s. As a final step, please complete the following form:"
msgstr ""
"Stai per usare il tuo:\n"
" account %(provider_name)s per fare l'accesso a\n"
" %(site_name)s. Per finire, completa il modulo seguente:"
#: .\cookbook\templates\socialaccount\signup.html:32
msgid "I accept the following"
msgstr "Accetto i seguenti"
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:23
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:31
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:39
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:47
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:55
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:63
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:71
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:79
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:87
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:95
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:103
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:111
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:119
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:127
msgid "Sign in using"
msgstr "Accedi usando"
#: .\cookbook\templates\space_overview.html:6
msgid "Overview"
msgstr "Panoramica"
#: .\cookbook\templates\space_overview.html:13
msgid "Space"
msgstr "Istanza"
#: .\cookbook\templates\space_overview.html:17
msgid ""
"Recipes, foods, shopping lists and more are organized in spaces of one or "
"more people."
msgstr ""
"Ricette, alimenti, liste della spesa e altro sono organizzati in istanze per "
"una o più persone."
#: .\cookbook\templates\space_overview.html:18
msgid ""
"You can either be invited into an existing space or create your own one."
msgstr "Puoi essere invitato in una istanza già esistente o crearne una nuova."
#: .\cookbook\templates\space_overview.html:25
msgid "Your Spaces"
msgstr "Le tue istanze"
#: .\cookbook\templates\space_overview.html:53
msgid "Owner"
msgstr "Proprietario"
#: .\cookbook\templates\space_overview.html:73
#: .\cookbook\templates\space_overview.html:83
msgid "Join Space"
msgstr "Partecipa all'istanza"
#: .\cookbook\templates\space_overview.html:76
msgid "Join an existing space."
msgstr "Entra in una istanza già esistente."
#: .\cookbook\templates\space_overview.html:78
msgid ""
"To join an existing space either enter your invite token or click on the "
"invite link the space owner send you."
msgstr ""
"Per entrare in una istanza già esistente, inserisci il token di invito o fai "
"clic sul collegamento di invito che l'amministratore ti ha mandato."
#: .\cookbook\templates\space_overview.html:91
#: .\cookbook\templates\space_overview.html:100
msgid "Create Space"
msgstr "Crea istanza"
#: .\cookbook\templates\space_overview.html:94
msgid "Create your own recipe space."
msgstr "Crea un'istanza per le tue ricette."
#: .\cookbook\templates\space_overview.html:96
msgid "Start your own recipe space and invite other users to it."
msgstr ""
"Apri la tua istanza personale di ricette e invita altri utenti a usarlo."
#: .\cookbook\templates\system.html:23
msgid "System"
msgstr "Sistema"
#: .\cookbook\templates\system.html:24
msgid ""
"\n"
" Tandoor Recipes is an open source free software application. It can "
"be found on\n"
" GitHub.\n"
" Changelogs can be found here.\n"
" "
msgstr ""
"\n"
" Tandoor Recipes è una applicazione gratuita e open source. È "
"disponibile su\n"
" GitHub.\n"
" Puoi consultare le ultime novità qui.\n"
" "
#: .\cookbook\templates\system.html:30
msgid "System Information"
msgstr "Informazioni di sistema"
#: .\cookbook\templates\system.html:51
msgid ""
"\n"
" You need to execute version.py in your update "
"script to generate version information (done automatically in docker).\n"
" "
msgstr ""
"\n"
" Devi eseguire version.py nello script di "
"aggiornamento per generare informazioni sulla versione (operazione eseguita "
"automaticamente in Docker).\n"
" "
#: .\cookbook\templates\system.html:56
msgid "Plugins"
msgstr "Estensioni"
#: .\cookbook\templates\system.html:67
msgid "Media Serving"
msgstr "File multimediali"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:123 .\cookbook\templates\system.html:134
msgid "Warning"
msgstr "Attenzione"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:125 .\cookbook\templates\system.html:134
msgid "Ok"
msgstr "Ok"
#: .\cookbook\templates\system.html:70
msgid ""
"Serving media files directly using gunicorn/python is not recommend!\n"
" Please follow the steps described\n"
" here to update\n"
" your installation.\n"
" "
msgstr ""
"Non è consigliato erogare i file multimediali con gunicorn/python!\n"
" Segui i passi descritti\n"
" qui per aggiornare\n"
" la tua installazione.\n"
" "
#: .\cookbook\templates\system.html:76 .\cookbook\templates\system.html:91
#: .\cookbook\templates\system.html:104 .\cookbook\templates\system.html:115
#: .\cookbook\views\views.py:168
msgid "Everything is fine!"
msgstr "È tutto ok!"
#: .\cookbook\templates\system.html:80
msgid "Secret Key"
msgstr "Chiave segreta"
#: .\cookbook\templates\system.html:84
msgid ""
"\n"
" You do not have a SECRET_KEY configured in your "
".env file. Django defaulted to the\n"
" standard key\n"
" provided with the installation which is publicly know and "
"insecure! Please set\n"
" SECRET_KEY int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" Non hai inserito una SECRET_KEY nel file ."
"env. Django ha dovuto usare la\n"
" chiave standard\n"
" dell'installazione che è pubblica e insicura! Sei pregato di "
"aggiungere una\n"
"\t\t\tSECRET_KEY nel file di configurazione .env.\n"
" "
#: .\cookbook\templates\system.html:94
msgid "Debug Mode"
msgstr "Modalità di debug"
#: .\cookbook\templates\system.html:98
msgid ""
"\n"
" This application is still running in debug mode. This is most "
"likely not needed. Turn of debug mode by\n"
" setting\n"
" DEBUG=0 int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" Questa applicazione è in esecuzione in modalità di debug. "
"Probabilmente non è necessario, spegni la modalità di debug\n"
" configurando\n"
" DEBUG=0 nel file di configurazione.env"
"code>.\n"
" "
#: .\cookbook\templates\system.html:107
msgid "Allowed Hosts"
msgstr "Host consentiti"
#: .\cookbook\templates\system.html:111
msgid ""
"\n"
" Your allowed hosts are configured to allow every host. This "
"might be ok in some setups but should be avoided. Please see the docs about "
"this.\n"
" "
msgstr ""
"\n"
" I tuoi host consentiti sono configurati per consentire ogni "
"host. Questo potrebbe essere accettabile in alcune configurazioni, ma "
"andrebbe evitato. Consulta la documentazione relativa.\n"
" "
#: .\cookbook\templates\system.html:118
msgid "Database"
msgstr "Database"
#: .\cookbook\templates\system.html:121
msgid "Info"
msgstr "Info"
#: .\cookbook\templates\system.html:131 .\cookbook\templates\system.html:148
msgid "Migrations"
msgstr "Migrazioni"
#: .\cookbook\templates\system.html:137
msgid ""
"\n"
" Migrations should never fail!\n"
" Failed migrations will likely cause major parts of the app to "
"not function correctly.\n"
" If a migration fails make sure you are on the latest version and "
"if so please post the migration log and the overview below in a GitHub "
"issue.\n"
" "
msgstr ""
"\n"
" Le migrazioni non dovrebbero mai fallire!\n"
" Le migrazioni non andate a buon fine probabilmente causeranno il "
"malfunzionamento di parti importanti dell'applicazione.\n"
" Se una migrazione non riesce, assicurati di avere la versione "
"più recente e, in tal caso, pubblica il registro della migrazione e il "
"riepilogo che segue in una segnalazione di problema su GitHub.\n"
" "
#: .\cookbook\templates\system.html:238
msgid "False"
msgstr "Falso"
#: .\cookbook\templates\system.html:238
msgid "True"
msgstr "Vero"
#: .\cookbook\templates\system.html:268
msgid "Hide"
msgstr "Nascondi"
#: .\cookbook\templates\system.html:271
msgid "Show"
msgstr "Mostra"
#: .\cookbook\templates\test2.html:6
msgid "Export Recipes"
msgstr "Esporta Ricette"
#: .\cookbook\templates\test2.html:14 .\cookbook\templates\test2.html:20
msgid "Export"
msgstr "Esporta"
#: .\cookbook\views\api.py:198 .\cookbook\views\api.py:326
msgid "Parameter updated_at incorrectly formatted"
msgstr "Il parametro updated_at non è formattato correttamente"
#: .\cookbook\views\api.py:351 .\cookbook\views\api.py:484
#, python-brace-format
msgid "No {self.basename} with id {pk} exists"
msgstr "Non esiste nessun {self.basename} con id {pk}"
#: .\cookbook\views\api.py:355
msgid "Cannot merge with the same object!"
msgstr "Non è possibile unirlo con lo stesso oggetto!"
#: .\cookbook\views\api.py:362
#, python-brace-format
msgid "No {self.basename} with id {target} exists"
msgstr "Non esiste nessun {self.basename} con id {target}"
#: .\cookbook\views\api.py:367
msgid "Cannot merge with child object!"
msgstr "Non è possibile unirlo con un oggetto secondario!"
#: .\cookbook\views\api.py:405
#, python-brace-format
msgid "{source.name} was merged successfully with {target.name}"
msgstr "{source.name} è stato unito con successo a {target.name}"
#: .\cookbook\views\api.py:411
#, python-brace-format
msgid "An error occurred attempting to merge {source.name} with {target.name}"
msgstr ""
"Si è verificato un errore durante l'unione di {source.name} con {target.name}"
#: .\cookbook\views\api.py:493
#, python-brace-format
msgid "{child.name} was moved successfully to the root."
msgstr "{child.name} è stato spostato con successo alla radice."
#: .\cookbook\views\api.py:496 .\cookbook\views\api.py:514
msgid "An error occurred attempting to move "
msgstr "Si è verificato un errore durante lo spostamento "
#: .\cookbook\views\api.py:499
msgid "Cannot move an object to itself!"
msgstr "Non è possibile muovere un oggetto a sé stesso!"
#: .\cookbook\views\api.py:505
#, python-brace-format
msgid "No {self.basename} with id {parent} exists"
msgstr "Non esiste alcun {self.basename} con id {parent}"
#: .\cookbook\views\api.py:511
#, python-brace-format
msgid "{child.name} was moved successfully to parent {parent.name}"
msgstr "{child.name} è stato spostato con successo al primario {parent.name}"
#: .\cookbook\views\api.py:992
#, python-brace-format
msgid "{obj.name} was removed from the shopping list."
msgstr "{obj.name} è stato rimosso dalla lista della spesa."
#: .\cookbook\views\api.py:997 .\cookbook\views\api.py:1627
#, python-brace-format
msgid "{obj.name} was added to the shopping list."
msgstr "{obj.name} è stato aggiunto alla lista della spesa."
#: .\cookbook\views\api.py:1239
msgid "Filter meal plans from date (inclusive)."
msgstr "Filtra i piani alimentari dalla data (inclusa)."
#: .\cookbook\views\api.py:1241
msgid "Filter meal plans to date (inclusive)."
msgstr "Filtra i piani alimentari fino alla data (inclusa)."
#: .\cookbook\views\api.py:1244
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
msgstr ""
"Filtra i piani alimentari con ID MealType. Per più di uno, ripeti il "
"parametro."
#: .\cookbook\views\api.py:1404
msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgstr ""
"ID di una ricetta di cui uno step ne fa parte. Usato per parametri di "
"ripetizione multipla."
#: .\cookbook\views\api.py:1406
msgid "Query string matched (fuzzy) against object name."
msgstr "Stringa di ricerca abbinata (vaga) al nome dell'oggetto."
#: .\cookbook\views\api.py:1442
msgid ""
"Query string matched (fuzzy) against recipe name. In the future also "
"fulltext search."
msgstr ""
"Stringa di ricerca corrispondente (vaga) al nome della ricetta. In futuro "
"anche ricerca fulltext."
#: .\cookbook\views\api.py:1444
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr ""
"ID della parola chiave che una ricetta dovrebbe avere. Per più di uno, "
"ripeti il parametro. Equivalente a keywords_or"
#: .\cookbook\views\api.py:1445
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr ""
"ID delle parole chiave, ripeti per più di uno. Restituisci ricette con una "
"qualsiasi delle parole chiave"
#: .\cookbook\views\api.py:1446
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr ""
"ID delle parole chiave, ripeti per più di uno. Restituisci le ricette con "
"tutte le parole chiave."
#: .\cookbook\views\api.py:1447
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr ""
"ID delle parole chiave, ripeti per più di uno. Escludi le ricette con una "
"qualsiasi delle parole chiave."
#: .\cookbook\views\api.py:1448
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr ""
"ID delle parole chiave, ripeti per più di uno. Escludi le ricette con tutte "
"le parole chiave."
#: .\cookbook\views\api.py:1450
msgid "ID of food a recipe should have. For multiple repeat parameter."
msgstr ""
"ID dell'alimento che una ricetta dovrebbe avere. Per più di uno, ripeti il "
"parametro."
#: .\cookbook\views\api.py:1451
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr ""
"ID degli alimenti, ripeti per più di uno. Restituisci le ricette con uno "
"qualsiasi degli alimenti"
#: .\cookbook\views\api.py:1452
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr ""
"ID degli alimenti, ripeti per più di uno. Restituisci le ricette con tutti "
"gli alimenti."
#: .\cookbook\views\api.py:1453
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr ""
"ID degli alimenti, ripeti per più di uno. Escludi le ricette con uno "
"qualsiasi degli alimenti."
#: .\cookbook\views\api.py:1454
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr ""
"ID degli alimenti, ripeti per più di uno. Escludi le ricette con tutti gli "
"alimenti."
#: .\cookbook\views\api.py:1456
msgid "ID of book a recipe should be in. For multiple repeat parameter."
msgstr ""
"ID del libro in cui dovrebbe trovarsi una ricetta. Per più di uno, ripeti il "
"parametro."
#: .\cookbook\views\api.py:1457
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr ""
"ID dei libri, ripetere per più di uno. Restituisci le ricette con uno "
"qualsiasi dei libri"
#: .\cookbook\views\api.py:1458
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr ""
"ID dei libri, ripetere per più di uno. Restituisci le ricette con tutti i "
"libri."
#: .\cookbook\views\api.py:1459
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr ""
"ID dei libri, ripeti per più di uno. Escludi le ricette con uno qualsiasi "
"dei libri."
#: .\cookbook\views\api.py:1460
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr ""
"ID dei libri, ripeti per più di uno. Escludi le ricette con tutti i libri."
#: .\cookbook\views\api.py:1462
msgid "ID of unit a recipe should have."
msgstr "ID dell'unità che una ricetta dovrebbe avere."
#: .\cookbook\views\api.py:1464
msgid "Exact rating of recipe"
msgstr "Valutazione precisa della ricetta"
#: .\cookbook\views\api.py:1465
msgid "Rating a recipe should have or greater."
msgstr "La valutazione che una ricetta dovrebbe avere o superiore."
#: .\cookbook\views\api.py:1466
msgid "Rating a recipe should have or smaller."
msgstr "La valutazione che una ricetta dovrebbe avere o inferiore."
#: .\cookbook\views\api.py:1468
msgid "Filter recipes cooked X times."
msgstr "Filtra le ricette cucinate N volte."
#: .\cookbook\views\api.py:1469
msgid "Filter recipes cooked X times or more."
msgstr "Filtra le ricette cucinate N volte o più."
#: .\cookbook\views\api.py:1470
msgid "Filter recipes cooked X times or less."
msgstr "Filtra le ricette cucinate N volte o meno."
#: .\cookbook\views\api.py:1472
msgid "Filter recipes created on the given date."
msgstr "Filtra create alla data specificata."
#: .\cookbook\views\api.py:1473
msgid "Filter recipes created on the given date or after."
msgstr "Filtra le ricette create alla data specificata o dopo."
#: .\cookbook\views\api.py:1474
msgid "Filter recipes created on the given date or before."
msgstr "Filtra le ricette create alla data specificata o prima."
#: .\cookbook\views\api.py:1476 .\cookbook\views\api.py:1477
#: .\cookbook\views\api.py:1478
msgid "Filter recipes updated on the given date."
msgstr "Filtra le ricette aggiornate alla data specificata."
#: .\cookbook\views\api.py:1480
msgid "Filter recipes last cooked on the given date or after."
msgstr "Filtra le ricette cucinate l'ultima volta alla data specificata o dopo."
#: .\cookbook\views\api.py:1481
msgid "Filter recipes last cooked on the given date or before."
msgstr ""
"Filtra le ricette cucinate l'ultima volta alla data specificata o prima."
#: .\cookbook\views\api.py:1483 .\cookbook\views\api.py:1484
msgid "Filter recipes lasts viewed on the given date."
msgstr "Filtra le ricette visualizzate per ultime alla data specificata."
#: .\cookbook\views\api.py:1486
msgid "Filter recipes for ones created by the given user ID"
msgstr "Filtra le ricette create dall'ID utente specificato"
#: .\cookbook\views\api.py:1487
msgid "If only internal recipes should be returned. [true/false]"
msgstr ""
"Se devono essere restituite solo le ricette interne. [true/false]"
#: .\cookbook\views\api.py:1488
msgid "Returns the results in randomized order. [true/false]"
msgstr "Restituisce i risultati in ordine casuale. [true/false]"
#: .\cookbook\views\api.py:1490
msgid ""
"Determines the order of the results. Options are: score,-score,name,-name,"
"lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-"
"created_at,lastviewed,-lastviewed"
msgstr ""
"Determina l'ordine dei risultati. Le opzioni sono: "
"score,-score,name,-name,lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-created_at,lastviewed,-lastviewed"
""
#: .\cookbook\views\api.py:1492
msgid "Returns new results first in search results. [true/false]"
msgstr ""
"Restituisce i nuovi risultati per primi nei risultati di ricerca. [vero/"
"falso]"
#: .\cookbook\views\api.py:1493
msgid ""
"Returns the given number of recently viewed recipes before search results "
"(if given)"
msgstr ""
"Restituisce il numero specificato di ricette visualizzate di recente prima "
"dei risultati di ricerca (se indicati)"
#: .\cookbook\views\api.py:1494
msgid "ID of a custom filter. Returns all recipes matched by that filter."
msgstr ""
"ID di un filtro personalizzato. Restituisce tutte le ricette verificate da "
"quel filtro."
#: .\cookbook\views\api.py:1495
msgid "Filter recipes that can be made with OnHand food. [true/false]"
msgstr ""
"Filtra le ricette che possono essere preparate con alimenti già disponibili. "
"[true/false]"
#: .\cookbook\views\api.py:1773
msgid ""
"Return the PropertyTypes matching the property category. Repeat for "
"multiple."
msgstr ""
"Restituisci i PropertyTypes corrispondenti alla categoria di proprietà. "
"Ripeti per più di uno."
#: .\cookbook\views\api.py:1804 .\cookbook\views\api.py:1860
msgid "Returns only entries associated with the given mealplan id"
msgstr ""
"Restituisce solo le voci associate all'ID del piano alimentare specificato"
#: .\cookbook\views\api.py:1858
msgid ""
"Returns only elements updated after the given timestamp in ISO 8601 format."
msgstr ""
"Restituisce solo gli elementi aggiornati dopo la marca temporale specificata "
"nel formato ISO 8601."
#: .\cookbook\views\api.py:2031
msgid ""
"Return the Automations matching the automation type. Repeat for multiple."
msgstr ""
"Restituisci le automazioni corrispondenti al tipo di automazione. Ripeti per "
"più automazioni."
#: .\cookbook\views\api.py:2048
msgid ""
"Text field to store data that gets carried over to the UserSpace created "
"from the InviteLink"
msgstr ""
"Campo di testo per memorizzare i dati che vengono trasferiti allo spazio "
"utente creato dal collegamento di invito"
#: .\cookbook\views\api.py:2049
msgid "Only return InviteLinks that have not been used yet."
msgstr ""
"Restituisci solo i collegamenti di invito che non sono ancora stati "
"utilizzati."
#: .\cookbook\views\api.py:2076
msgid "Return the CustomFilters matching the model type. Repeat for multiple."
msgstr ""
"Restituisci i CustomFilters corrispondenti al tipo di modello. Ripeti per "
"più di un modello."
#: .\cookbook\views\api.py:2176
msgid "Nothing to do."
msgstr "Nulla da fare."
#: .\cookbook\views\api.py:2222
msgid "Invalid Url"
msgstr "URL non valido"
#: .\cookbook\views\api.py:2228
msgid "Connection Refused."
msgstr "Connessione rifiutata."
#: .\cookbook\views\api.py:2232
msgid "Bad URL Schema."
msgstr "Schema URL invalido."
#: .\cookbook\views\api.py:2257
msgid "No usable data could be found."
msgstr "Nessuna informazione utilizzabile è stata trovata."
#: .\cookbook\views\api.py:2286 .\cookbook\views\api.py:2434
msgid "You must select an AI provider to perform your request."
msgstr "Devi selezionare un fornitore AI per eseguire la tua richiesta."
#: .\cookbook\views\api.py:2293 .\cookbook\views\api.py:2441
msgid ""
"You don't have any credits remaining to use AI or AI features are not "
"enabled for your space."
msgstr ""
"Non hai credito rimanente per utilizzare l'AI o le funzionalità di AI "
"abilitate per il tuo spazio."
#: .\cookbook\views\api.py:2499 .\cookbook\views\api.py:2667
msgid "File is above space limit"
msgstr "Il file eccede il limite di spazio"
#: .\cookbook\views\api.py:2522 .\cookbook\views\api.py:2684
msgid "Importing is not implemented for this provider"
msgstr "Questo provider non permette l'importazione"
#: .\cookbook\views\api.py:2548
msgid ""
"The PDF Exporter is not enabled on this instance as it is still in an "
"experimental state."
msgstr ""
"L'esportatore PDF non è abilitato in questa istanza, perché è ancora in "
"stato sperimentale."
#: .\cookbook\views\api.py:2842
msgid "This feature is not yet available in the hosted version of tandoor!"
msgstr ""
"Questa funzione non è ancora disponibile nella versione ospitata di Tandoor!"
#: .\cookbook\views\api.py:2863
msgid "Sync successful!"
msgstr "Sincronizzazione completata con successo!"
#: .\cookbook\views\api.py:2866
msgid "Error synchronizing with Storage"
msgstr "Errore di sincronizzazione con questo backend"
#: .\cookbook\views\views.py:89
msgid "This feature is not available in the demo version!"
msgstr "Questa funzione non è disponibile nella versione demo!"
#: .\cookbook\views\views.py:110
msgid ""
"You have successfully created your own recipe space. Start by adding some "
"recipes or invite other people to join you."
msgstr ""
"Hai creato la tua istanza personale per le ricette. Inizia aggiungendo "
"qualche ricetta o invita altre persone a unirsi a te."
#: .\cookbook\views\views.py:171
#, python-format
msgid "PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!"
msgstr ""
"PostgreSQL %(v)s è deprecato. Esegui l'aggiornamento a una versione "
"completamente supportata!"
#: .\cookbook\views\views.py:174
#, python-format
msgid "You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended"
msgstr "Stai eseguendo PostgreSQL %(v1)s. PostgreSQL %(v2)s è consigliato"
#: .\cookbook\views\views.py:178
msgid "Unable to determine PostgreSQL version."
msgstr "Impossibile determinare la versione di PostgreSQL."
#: .\cookbook\views\views.py:182
msgid ""
"This application is not running with a Postgres database backend. This is ok "
"but not recommended as some features only work with postgres databases."
msgstr ""
"Questa applicazione non è in esecuzione con un database Postgres. Va bene, "
"ma non è consigliato perché alcune funzionalità sono disponibili solo con "
"database Postgres."
#: .\cookbook\views\views.py:296
msgid ""
"The setup page can only be used to create the first "
"user! If you have forgotten your superuser credentials "
"please consult the django documentation on how to reset passwords."
msgstr ""
"La pagina di configurazione può essere usata solo per creare il primo "
"utente! Se hai dimenticato le credenziali del tuo super utente controlla la "
"documentazione di Django per ripristinare le password."
#: .\cookbook\views\views.py:304
msgid "Passwords dont match!"
msgstr "Le password non combaciano!"
#: .\cookbook\views\views.py:312
msgid "User has been created, please login!"
msgstr "L'utente è stato creato e ora può essere usato per il login!"
#: .\cookbook\views\views.py:352
msgid ""
"Reporting share links is not enabled for this instance. Please notify the "
"page administrator to report problems."
msgstr ""
"La segnalazione dei collegamento di condivisione non è abilitata per questa "
"istanza. Notifica l'amministratore per segnalare i problemi."
#: .\cookbook\views\views.py:357
msgid ""
"Recipe sharing link has been disabled! For additional information please "
"contact the page administrator."
msgstr ""
"Il collegamento per la condivisione delle ricette è stato disabilitato! Per "
"maggiori informazioni contatta l'amministratore."
#: .\cookbook\views\views.py:383
msgid "Manage recipes, shopping list, meal plans and more."
msgstr "Gestisci ricette, liste della spesa, piani alimentari e altro ancora."
#: .\cookbook\views\views.py:397 .\cookbook\views\views.py:398
msgid "Plan"
msgstr "Piano"
#: .\cookbook\views\views.py:399
msgid "View your meal Plan"
msgstr "Visualizza il tuo piano alimentare"
#: .\cookbook\views\views.py:418
msgid "View your shopping lists"
msgstr "Visualizza le tue liste della spesa"
#~ msgid ""
#~ "Both fields are optional. If none are given the username will be "
#~ "displayed instead"
#~ msgstr ""
#~ "Entrambi i campi sono facoltativi. Se non viene fornito, sarà "
#~ "visualizzato il nome utente"
#~ msgid "Name"
#~ msgstr "Nome"
#~ msgid "Keywords"
#~ msgstr "Parole chiave"
#~ msgid "Preparation time in minutes"
#~ msgstr "Tempo di preparazione in minuti"
#~ msgid "Waiting time (cooking/baking) in minutes"
#~ msgstr "Tempo di attesa (cottura) in minuti"
#~ msgid "Path"
#~ msgstr "Percorso"
#~ msgid "Storage UID"
#~ msgstr "UID di archiviazione"
#~ msgid "Add your comment: "
#~ msgstr "Aggiungi il tuo commento: "
#~ msgid "Leave empty for dropbox and enter app password for nextcloud."
#~ msgstr ""
#~ "Lascia vuoto per dropbox e digita la password dell'applicazione per "
#~ "nextcloud."
#~ msgid "Leave empty for nextcloud and enter api token for dropbox."
#~ msgstr "Lascia vuoto per nextcloud e digita il token api per dropbox."
#~ msgid ""
#~ "Leave empty for dropbox and enter only base url for nextcloud (/"
#~ "remote.php/webdav/ is added automatically)"
#~ msgstr ""
#~ "Lascia vuoto per dropbox e digita solo l'url base per nextcloud (/"
#~ "remote.php/webdav/ è aggiunto automaticamente)"
#~ msgid ""
#~ "Long Lived Access Token for your HomeAssistant instance"
#~ msgstr ""
#~ "Token di accesso di lunga durata per la tua istanza di "
#~ "HomeAssistant"
#~ msgid "Something like http://homeassistant.local:8123/api"
#~ msgstr "Qualcosa del tipo http://homeassistant.local:8123/api"
#~ msgid "http://homeassistant.local:8123/api for example"
#~ msgstr "http://homeassistant.local:8123/api ad esempio"
#~ msgid "Storage"
#~ msgstr "Archiviazione"
#~ msgid "Active"
#~ msgstr "Attivo"
#~ msgid "Search String"
#~ msgstr "Stringa di ricerca"
#~ msgid "File ID"
#~ msgstr "ID del file"
#~ msgid ""
#~ "Determines how fuzzy a search is if it uses trigram similarity matching "
#~ "(e.g. low values mean more typos are ignored)."
#~ msgstr ""
#~ "Determina quanto una ricerca è vaga se utilizza la corrispondenza dei "
#~ "trigrammi (ad esempio, valori bassi significano che vengono ignorati più "
#~ "errori di battitura)."
#~ msgid ""
#~ "Select type method of search. Click here "
#~ "for full description of choices."
#~ msgstr ""
#~ "Seleziona il metodo di ricerca. Fai clic qui"
#~ "a> per avere maggiori informazioni."
#~ msgid ""
#~ "Use fuzzy matching on units, keywords and ingredients when editing and "
#~ "importing recipes."
#~ msgstr ""
#~ "Usa la corrispondenza vaga per unità, parole chiave e ingredienti durante "
#~ "la modifica e l'importazione di ricette."
#~ msgid ""
#~ "Fields to search ignoring accents. Selecting this option can improve or "
#~ "degrade search quality depending on language"
#~ msgstr ""
#~ "Campi da cercare ignorando gli accenti. A seconda alla lingua "
#~ "utilizzata, questa opzione può migliorare o peggiorare la ricerca"
#~ msgid ""
#~ "Fields to search for partial matches. (e.g. searching for 'Pie' will "
#~ "return 'pie' and 'piece' and 'soapie')"
#~ msgstr ""
#~ "Campi da cercare con corrispondenza parziale. (ad esempio, cercando "
#~ "'Torta' verranno mostrati 'torta', 'tortino' e 'contorta')"
#~ msgid ""
#~ "Fields to search for beginning of word matches. (e.g. searching for 'sa' "
#~ "will return 'salad' and 'sandwich')"
#~ msgstr ""
#~ "Campi da cercare all'inizio di parole corrispondenti (es. cercando per "
#~ "'ins' mostrerà 'insalata' e 'insaccati')"
#~ msgid ""
#~ "Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find "
#~ "'recipe'.) Note: this option will conflict with 'web' and 'raw' methods "
#~ "of search."
#~ msgstr ""
#~ "Campi in cui usare la ricerca 'vaga'. (ad esempio cercando per 'riceta' "
#~ "verrà mostrato 'ricetta'). Nota: questa opzione non è compatibile con la "
#~ "ricerca 'web' o 'raw'."
#~ msgid ""
#~ "Fields to full text search. Note: 'web', 'phrase', and 'raw' search "
#~ "methods only function with fulltext fields."
#~ msgstr ""
#~ "Campi per la ricerca full-text. Nota: i metodi di ricerca 'web', 'frase' "
#~ "e 'raw' funzionano solo con i campi full-text."
#~ msgid "Search Method"
#~ msgstr "Metodo di ricerca"
#~ msgid "Fuzzy Lookups"
#~ msgstr "Ricerche vaghe"
#~ msgid "Ignore Accent"
#~ msgstr "Ignora accento"
#~ msgid "Partial Match"
#~ msgstr "Corrispondenza parziale"
#~ msgid "Starts With"
#~ msgstr "Inizia con"
#~ msgid "Fuzzy Search"
#~ msgstr "Ricerca vaga"
#~ msgid "Full Text"
#~ msgstr "Full Text"
#~ msgid " is part of a recipe step and cannot be deleted"
#~ msgstr " è parte dello step di una ricetta e non può essere eliminato"
#~ msgid "Delete"
#~ msgstr "Elimina"
#~ msgid "Settings"
#~ msgstr "Impostazioni"
#~ msgid "Email"
#~ msgstr "Email"
#~ msgid "Password"
#~ msgstr "Password"
#~ msgid "Foods"
#~ msgstr "Alimenti"
#~ msgid "Units"
#~ msgstr "Unità"
#~ msgid "Supermarket"
#~ msgstr "Supermercato"
#~ msgid "Supermarket Category"
#~ msgstr "Categoria supermercato"
#~ msgid "Automations"
#~ msgstr "Automazioni"
#~ msgid "Files"
#~ msgstr "File"
#~ msgid "Batch Edit"
#~ msgstr "Modifica in blocco"
#~ msgid "History"
#~ msgstr "Cronologia"
#~ msgid "Ingredient Editor"
#~ msgstr "Editor degli ingredienti"
#~ msgid "Properties"
#~ msgstr "Proprietà"
#~ msgid "Unit Conversions"
#~ msgstr "Conversioni di unità"
#~ msgid "Create"
#~ msgstr "Crea"
#~ msgid "External Recipes"
#~ msgstr "Ricette esterne"
#~ msgid "Space Settings"
#~ msgstr "Impostazioni istanza"
#~ msgid "External Connectors"
#~ msgstr "Connettori esterni"
#~ msgid "Admin"
#~ msgstr "Amministratore"
#~ msgid "Markdown Guide"
#~ msgstr "Guida su Markdown"
#~ msgid "GitHub"
#~ msgstr "GitHub"
#~ msgid "Translate Tandoor"
#~ msgstr "Traduci Tandoor"
#~ msgid "API Browser"
#~ msgstr "Browser API"
#~ msgid "Log out"
#~ msgstr "Esci"
#~ msgid "You are using the free version of Tandor"
#~ msgstr "Stai usando la versione gratuita di Tandoor"
#~ msgid "Upgrade Now"
#~ msgstr "Aggiorna ora"
#~ msgid "Batch edit Category"
#~ msgstr "Modifica in blocco per categoria"
#~ msgid "Batch edit Recipes"
#~ msgstr "Modifica in blocco per ricette"
#~ msgid "Add the specified keywords to all recipes containing a word"
#~ msgstr ""
#~ "Aggiungi le parole chiave che desideri a tutte le ricette che contengono "
#~ "una determinata stringa"
#~ msgid "Sync"
#~ msgstr "Sincronizza"
#~ msgid "Manage watched Folders"
#~ msgstr "Gestisci cartelle monitorate"
#~ msgid ""
#~ "On this Page you can manage all storage folder locations that should be "
#~ "monitored and synced."
#~ msgstr ""
#~ "In questa pagina puoi gestire i percorsi delle cartelle di archiviazione "
#~ "che devono essere monitorate e sincronizzate."
#~ msgid "The path must be in the following format"
#~ msgstr "Il percorso deve essere nel formato seguente"
#~ msgid "Save"
#~ msgstr "Salva"
#~ msgid "Manage External Storage"
#~ msgstr "Gestisci archiviazione esterna"
#~ msgid "Sync Now!"
#~ msgstr "Sincronizza ora!"
#~ msgid "Show Recipes"
#~ msgstr "Mostra ricette"
#~ msgid "Show Log"
#~ msgstr "Mostra registro"
#~ msgid "Importing Recipes"
#~ msgstr "Importando ricette"
#~ msgid ""
#~ "This can take a few minutes, depending on the number of recipes in sync, "
#~ "please wait."
#~ msgstr ""
#~ "Questa operazione può richiedere alcuni minuti, a seconda del numero di "
#~ "ricette sincronizzate, attendere."
#~ msgid "Recipe Books"
#~ msgstr "Libri di Ricette"
#~ msgid "Import new Recipe"
#~ msgstr "Importa nuova ricetta"
#~ msgid "Edit Recipe"
#~ msgstr "Modifica Ricetta"
#, python-format
#~ msgid "Are you sure you want to delete the %(title)s: %(object)s "
#~ msgstr "Sei sicuro di volere eliminare %(title)s: %(object)s "
#~ msgid "This cannot be undone!"
#~ msgstr "Questa azione non può essere annullata!"
#~ msgid "Protected"
#~ msgstr "Protetto"
#~ msgid "Cascade"
#~ msgstr "Cascata"
#~ msgid "Cancel"
#~ msgstr "Annulla"
#~ msgid "Edit"
#~ msgstr "Modifica"
#~ msgid "View"
#~ msgstr "Visualizza"
#~ msgid "Delete original file"
#~ msgstr "Elimina il file originale"
#~ msgid "List"
#~ msgstr "Elenco"
#~ msgid "Filter"
#~ msgstr "Filtro"
#~ msgid "Import all"
#~ msgstr "Importa tutto"
#~ msgid "New"
#~ msgstr "Nuovo"
#~ msgid "previous"
#~ msgstr "precedente"
#~ msgid "next"
#~ msgstr "prossimo"
#~ msgid "View Log"
#~ msgstr "Visualizza registro"
#~ msgid "Cook Log"
#~ msgstr "Registro di cottura"
#~ msgid "Import"
#~ msgstr "Importa"
#~ msgid "Security Warning"
#~ msgstr "Avviso di Sicurezza"
#~ msgid ""
#~ "\n"
#~ " The Password and Token field are stored as plain text"
#~ "b> inside the database.\n"
#~ " This is necessary because they are needed to make API requests, "
#~ "but it also increases the risk of\n"
#~ " someone stealing it.
\n"
#~ " To limit the possible damage tokens or accounts with limited "
#~ "access can be used.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " I campi Password e Token sono salvati in chiaro nel "
#~ "database.\n"
#~ " È necessario perché sono usati per fare richieste API, ma ciò "
#~ "aumenta il rischio che\n"
#~ " qualcuno possa impossessarsene.
\n"
#~ " Per liminare i possibili danni puoi usare account con accesso "
#~ "limitato o i token.\n"
#~ " "
#~ msgid "You are currently offline!"
#~ msgstr "Al momento sei offline!"
#~ msgid ""
#~ "The recipes listed below are available for offline viewing because you "
#~ "have recently viewed them. Keep in mind that data might be outdated."
#~ msgstr ""
#~ "Le ricette qui sotto sono disponibili per essere consultate quando sei "
#~ "offline perché le hai aperte di recente. Ricorda che queste informazioni "
#~ "potrebbero non essere aggiornate."
#~ msgid "Property Editor"
#~ msgstr "Editor delle proprietà"
#~ msgid "Comments"
#~ msgstr "Commenti"
#~ msgid "by"
#~ msgstr "di"
#~ msgid "Comment"
#~ msgstr "Commento"
#~ msgid ""
#~ "There are many options to configure the search depending on your personal "
#~ "preferences."
#~ msgstr ""
#~ "Ci sono molte opzioni per configurare la ricerca in base alle tue "
#~ "preferenze."
#~ msgid ""
#~ "Usually you do not need to configure any of them and can just "
#~ "stick with either the default or one of the following presets."
#~ msgstr ""
#~ "Normalmente non c'è bisogno di configurare queste voci e puoi "
#~ "continuare a usare le impostazioni predefinite oppure scegliere una delle "
#~ "seguenti modalità."
#~ msgid ""
#~ "If you do want to configure the search you can read about the different "
#~ "options here."
#~ msgstr ""
#~ "Se vuoi comunque configurare la ricerca, puoi informarti riguardo le "
#~ "opzioni disponibili qui."
#~ msgid "Fuzzy"
#~ msgstr "Vago"
#~ msgid ""
#~ "Find what you need even if your search or the recipe contains typos. "
#~ "Might return more results than needed to make sure you find what you are "
#~ "looking for."
#~ msgstr ""
#~ "Cerca quello che ti serve anche se la ricerca o la ricetta contengono "
#~ "errori. Potrebbe mostrare più risultati di quelli necessari per mostrarti "
#~ "quello che stai cercando."
#~ msgid "This is the default behavior"
#~ msgstr "È il comportamento predefinito"
#~ msgid "Apply"
#~ msgstr "Applica"
#~ msgid "Precise"
#~ msgstr "Preciso"
#~ msgid ""
#~ "Allows fine control over search results but might not return results if "
#~ "too many spelling mistakes are made."
#~ msgstr ""
#~ "Consente un controllo preciso sui risultati della ricerca, ma potrebbe "
#~ "non mostrare risultati se vengono commessi troppi errori."
#~ msgid "Perfect for large Databases"
#~ msgstr "Ideale per database grandi"
#~ msgid "Social"
#~ msgstr "Social"
#~ msgid "Space Management"
#~ msgstr "Gestione istanze"
#~ msgid "Space:"
#~ msgstr "Istanza:"
#~ msgid "Manage Subscription"
#~ msgstr "Gestisci iscrizione"
#~ msgid "Leave Space"
#~ msgstr "Abbandona istanza"
#~ msgid "URL Import"
#~ msgstr "Importa da URL"
#~ msgid ""
#~ "Rating a recipe should have or greater. [0 - 5] Negative value filters "
#~ "rating less than."
#~ msgstr ""
#~ "La valutazione di una ricetta dovrebbe avere o superiore. [0 - 5] I "
#~ "filtri con valore negativo filtrano le valutazioni inferiori a quanto "
#~ "specificato."
#~ msgid ""
#~ "Filter recipes updated on or after YYYY-MM-DD. Prepending - filters on or "
#~ "before date."
#~ msgstr ""
#~ "Filtra le ricette aggiornate il o dopo AAAA-MM-GG. Anteponendo - filtra "
#~ "alla data o prima della data."
#~ msgid ""
#~ "Returns the shopping list entry with a primary key of id. Multiple "
#~ "values allowed."
#~ msgstr ""
#~ "Restituisce la voce della lista della spesa con una chiave primaria di "
#~ "id. Sono consentiti più valori."
#~ msgid ""
#~ "Filter shopping list entries on checked. [true, false, both, recent"
#~ "b>]
- recent includes unchecked items and recently "
#~ "completed items."
#~ msgstr ""
#~ "Filtra le voci della lista della spesa selezionate. [vero, falso, "
#~ "entrambi, recenti]
- recenti include gli "
#~ "elementi non selezionati e quelli completati di recente."
#~ msgid ""
#~ "Returns the shopping list entries sorted by supermarket category order."
#~ msgstr ""
#~ "Restituisce le voci della lista della spesa ordinate per categoria di "
#~ "supermercato."
#, python-format
#~ msgid "Batch edit done. %(count)d recipe was updated."
#~ msgid_plural "Batch edit done. %(count)d Recipes where updated."
#~ msgstr[0] ""
#~ "Modifica di massa completata. %(count)d ricetta è stata aggiornata."
#~ msgstr[1] ""
#~ "Modifica in blocco completata. %(count)d ricette sono state aggiornate."
#~ msgid "Monitor"
#~ msgstr "Monitoraggio"
#~ msgid "Storage Backend"
#~ msgstr "Backend di archiviazione"
#~ msgid ""
#~ "Could not delete this storage backend as it is used in at least one "
#~ "monitor."
#~ msgstr ""
#~ "Non è possibile eliminare questo backend di archiviazione perché è usato "
#~ "in almeno un monitoraggio."
#~ msgid "Connectors Config Backend"
#~ msgstr "Configurazione connettori backend"
#~ msgid "Invite Link"
#~ msgstr "Collegamento di invito"
#~ msgid "Space Membership"
#~ msgstr "Appartenenza istanza"
#~ msgid "You cannot edit this storage!"
#~ msgstr "Non puoi modificare questo backend!"
#~ msgid "Storage saved!"
#~ msgstr "Backend salvato!"
#~ msgid "There was an error updating this storage backend!"
#~ msgstr ""
#~ "Si è verificato un errore durante l'aggiornamento di questo backend di "
#~ "archiviazione!"
#~ msgid "Config saved!"
#~ msgstr "Configurazione salvata!"
#~ msgid "ConnectorConfig"
#~ msgstr "Configurazione connettore"
#~ msgid "Changes saved!"
#~ msgstr "Modifiche salvate!"
#~ msgid "Error saving changes!"
#~ msgstr "Si è verificato un errore durante il salvataggio delle modifiche!"
#~ msgid "Import Log"
#~ msgstr "Registro importazioni"
#~ msgid "Discovery"
#~ msgstr "Trovate"
#~ msgid "Shopping List"
#~ msgstr "Lista della spesa"
#~ msgid "Connector Config Backend"
#~ msgstr "Configurazione connettore backend"
#~ msgid "Invite Links"
#~ msgstr "Collegamenti di invito"
#~ msgid "Supermarkets"
#~ msgstr "Supermercati"
#~ msgid "Shopping Categories"
#~ msgstr "Categorie della spesa"
#~ msgid "Custom Filters"
#~ msgstr "Filtri personalizzati"
#~ msgid "Steps"
#~ msgstr "Step"
#~ msgid "Property Types"
#~ msgstr "Tipi di proprietà"
#~ msgid "This feature is not enabled by the server admin!"
#~ msgstr "Questa funzionalità non è abilitata dall'amministratore del server!"
#~ msgid "Imported new recipe!"
#~ msgstr "La nuova ricetta è stata importata!"
#~ msgid "There was an error importing this recipe!"
#~ msgstr "Si è verificato un errore durante l'importazione di questa ricetta!"
#~ msgid "You do not have the required permissions to perform this action!"
#~ msgstr "Non hai i permessi necessari per completare questa operazione!"
#~ msgid "Comment saved!"
#~ msgstr "Commento salvato!"
#~ msgid "You must select at least one field to search!"
#~ msgstr "Devi selezionare almeno un campo da cercare!"
#~ msgid ""
#~ "To use this search method you must select at least one full text search "
#~ "field!"
#~ msgstr ""
#~ "Per utilizzare questo metodo di ricerca devi selezionare almeno un campo "
#~ "di ricerca full text!"
#~ msgid "Fuzzy search is not compatible with this search method!"
#~ msgstr "La ricerca vaga non è compatibile con questo metodo di ricerca!"
#~ msgid "Malformed Invite Link supplied!"
#~ msgstr "È stato fornito un collegamento di invito non valido!"
#~ msgid "Successfully joined space."
#~ msgstr "Sei entrato a far parte di questa istanza."
#~ msgid "Invite Link not valid or already used!"
#~ msgstr "Il collegamento di invito non è valido o è stato già usato!"
#~ msgid "View your cookbooks"
#~ msgstr "Visualizza i tuoi ricettari"
#~ msgid "Default unit"
#~ msgstr "Unità predefinita"
#~ msgid "Use KJ"
#~ msgstr "Usa KJ"
#~ msgid "Theme"
#~ msgstr "Tema"
#~ msgid "Navbar color"
#~ msgstr "Colore barra di navigazione"
#~ msgid "Sticky navbar"
#~ msgstr "Barra di navigazione persistente"
#~ msgid "Default page"
#~ msgstr "Pagina predefinita"
#~ msgid "Plan sharing"
#~ msgstr "Condivisione piano"
#~ msgid "Ingredient decimal places"
#~ msgstr "Posizioni decimali degli ingredienti"
#~ msgid "Shopping list auto sync period"
#~ msgstr "Frequenza di sincronizzazione automatica della lista della spesa"
#~ msgid "Left-handed mode"
#~ msgstr "Modalità per mancini"
#~ msgid ""
#~ "Color of the top navigation bar. Not all colors work with all themes, "
#~ "just try them out!"
#~ msgstr ""
#~ "Colore della barra di navigazione in alto. Non tutti i colori funzionano "
#~ "con tutti i temi, provali e basta!"
#~ msgid ""
#~ "Default Unit to be used when inserting a new ingredient into a recipe."
#~ msgstr ""
#~ "Unità di misura predefinita da utilizzare quando si inserisce un nuovo "
#~ "ingrediente in una ricetta."
#~ msgid ""
#~ "Enables support for fractions in ingredient amounts (e.g. convert "
#~ "decimals to fractions automatically)"
#~ msgstr ""
#~ "Abilita il supporto alle frazioni per le quantità degli ingredienti (ad "
#~ "esempio converte i decimali in frazioni automaticamente)"
#~ msgid "Display nutritional energy amounts in joules instead of calories"
#~ msgstr "Mostra le informazioni nutrizionali in Joule invece che in calorie"
#~ msgid ""
#~ "Users with whom newly created meal plans should be shared by default."
#~ msgstr ""
#~ "Gli utenti con i quali le nuove voci del piano alimentare devono essere "
#~ "condivise per impostazione predefinita."
#~ msgid "Users with whom to share shopping lists."
#~ msgstr "Utenti con i quali condividere le liste della spesa."
#~ msgid "Number of decimals to round ingredients."
#~ msgstr "Numero di decimali per approssimare gli ingredienti."
#~ msgid ""
#~ "If you want to be able to create and see comments underneath recipes."
#~ msgstr ""
#~ "Se vuoi essere in grado di creare e vedere i commenti sotto le ricette."
#~ msgid ""
#~ "Setting to 0 will disable auto sync. When viewing a shopping list the "
#~ "list is updated every set seconds to sync changes someone else might have "
#~ "made. Useful when shopping with multiple people but might use a little "
#~ "bit of mobile data. If lower than instance limit it is reset when saving."
#~ msgstr ""
#~ "La sincronizzazione automatica verrà disabilitata se impostato a 0. "
#~ "Quando si visualizza una lista della spesa, la lista viene aggiornata "
#~ "ogni tot secondi impostati per sincronizzare le modifiche che qualcun "
#~ "altro potrebbe aver fatto. Utile per gli acquisti condivisi con più "
#~ "persone, ma potrebbe utilizzare un po' di dati mobili. Se inferiore al "
#~ "limite della istanza, viene ripristinato durante il salvataggio."
#~ msgid "Makes the navbar stick to the top of the page."
#~ msgstr "Fissa la barra di navigazione nella parte superiore della pagina."
#~ msgid "Automatically add meal plan ingredients to shopping list."
#~ msgstr ""
#~ "Aggiungi automaticamente gli ingredienti del piano alimentare alla lista "
#~ "della spesa."
#~ msgid "Exclude ingredients that are on hand."
#~ msgstr "Escludi gli ingredienti che sono già disponibili."
#~ msgid "Will optimize the UI for use with your left hand."
#~ msgstr "L'interfaccia verrà ottimizzata per l'uso con la mano sinistra."
#~ msgid "You must provide at least a recipe or a title."
#~ msgstr "Devi fornire almeno una ricetta o un titolo."
#~ msgid "You can list default users to share recipes with in the settings."
#~ msgstr ""
#~ "È possibile visualizzare l'elenco degli utenti predefiniti con cui "
#~ "condividere le ricette nelle impostazioni."
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "Puoi usare markdown per formattare questo campo. Guarda la documentazione qui"
#~ msgid ""
#~ "Users will see all items you add to your shopping list. They must add "
#~ "you to see items on their list."
#~ msgstr ""
#~ "Gli utenti potranno vedere tutti gli elementi che aggiungi alla tua lista "
#~ "della spesa. Devono aggiungerti per vedere gli elementi nella loro lista."
#~ msgid ""
#~ "When adding a meal plan to the shopping list (manually or automatically), "
#~ "include all related recipes."
#~ msgstr ""
#~ "Quando si aggiunge un piano alimentare alla lista della spesa "
#~ "(manualmente o automaticamente), includi tutte le ricette correlate."
#~ msgid ""
#~ "When adding a meal plan to the shopping list (manually or automatically), "
#~ "exclude ingredients that are on hand."
#~ msgstr ""
#~ "Quando si aggiunge un piano alimentare alla lista della spesa "
#~ "(manualmente o automaticamente), escludi gli ingredienti già disponibili."
#~ msgid "Default number of hours to delay a shopping list entry."
#~ msgstr ""
#~ "Il numero predefinito di ore per ritardare l'inserimento di una lista "
#~ "della spesa."
#~ msgid "Filter shopping list to only include supermarket categories."
#~ msgstr ""
#~ "Filtra la lista della spesa per includere solo categorie dei supermercati."
#~ msgid "Days of recent shopping list entries to display."
#~ msgstr "Giorni di visualizzazione di voci recenti della lista della spesa."
#~ msgid "Mark food 'On Hand' when checked off shopping list."
#~ msgstr ""
#~ "Contrassegna gli alimenti come 'Disponibili' quando spuntati dalla lista "
#~ "della spesa."
#~ msgid "Delimiter to use for CSV exports."
#~ msgstr "Delimitatore usato per le esportazioni CSV."
#~ msgid "Prefix to add when copying list to the clipboard."
#~ msgstr "Prefisso da aggiungere quando si copia una lista negli appunti."
#~ msgid "Share Shopping List"
#~ msgstr "Condividi lista della spesa"
#~ msgid "Autosync"
#~ msgstr "Sincronizzazione automatica"
#~ msgid "Auto Add Meal Plan"
#~ msgstr "Aggiungi automaticamente al piano alimentare"
#~ msgid "Exclude On Hand"
#~ msgstr "Escludi Disponibile"
#~ msgid "Include Related"
#~ msgstr "Includi correlati"
#~ msgid "Default Delay Hours"
#~ msgstr "Ore di ritardo predefinite"
#~ msgid "Filter to Supermarket"
#~ msgstr "Filtra per supermercato"
#~ msgid "Recent Days"
#~ msgstr "Giorni recenti"
#~ msgid "CSV Delimiter"
#~ msgstr "Delimitatore CSV"
#~ msgid "List Prefix"
#~ msgstr "Prefisso lista"
#~ msgid "Auto On Hand"
#~ msgstr "Disponibilità automatica"
#~ msgid "Reset Food Inheritance"
#~ msgstr "Ripristina Eredità Alimenti"
#~ msgid "Reset all food to inherit the fields configured."
#~ msgstr "Ripristina tutti gli alimenti per ereditare i campi configurati."
#~ msgid "Fields on food that should be inherited by default."
#~ msgstr ""
#~ "Campi su alimenti che devono essere ereditati per impostazione "
#~ "predefinita."
#~ msgid "Show recipe counts on search filters"
#~ msgstr "Mostra il conteggio delle ricette nei filtri di ricerca"
#~ msgid "Use the plural form for units and food inside this space."
#~ msgstr ""
#~ "Usare la forma plurale per le unità e gli alimenti all'interno di questo "
#~ "spazio."
#~ msgid "One of queryset or hash_key must be provided"
#~ msgstr "Uno tra queryset o has_key deve essere fornito"
#~ msgid "Profile"
#~ msgstr "Profilo"
#~ msgid "Recipe Book"
#~ msgstr "Libro delle ricette"
#~ msgid "Bookmarks"
#~ msgstr "Preferiti"
#~ msgid "Ingredients"
#~ msgstr "Ingredienti"
#~ msgid "Show recent recipes"
#~ msgstr "Mostra ricette recenti"
#~ msgid "Search style"
#~ msgstr "Cerca stile"
#~ msgid "Show recently viewed recipes on search page."
#~ msgstr "Mostra le ricette visualizzate di recente nella pagina di ricerca."
#~ msgid "Small"
#~ msgstr "Piccolo"
#~ msgid "Large"
#~ msgstr "Grande"
#~ msgid "Edit Ingredients"
#~ msgstr "Modifica Ingredienti"
#~ msgid ""
#~ "\n"
#~ " The following form can be used if, accidentally, two (or more) "
#~ "units or ingredients where created that should be\n"
#~ " the same.\n"
#~ " It merges two units or ingredients and updates all recipes using "
#~ "them.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Questo modulo può essere utilizzato se, accidentalmente, sono "
#~ "stati creati due (o più) unità di misura o ingredienti che\n"
#~ " dovrebbero essere lo stesso.\n"
#~ " Unisce due unità di misura o ingredienti e aggiorna tutte le "
#~ "ricette che li utilizzano.\n"
#~ " "
#~ msgid "Are you sure that you want to merge these two units?"
#~ msgstr "Sei sicuro di volere unire queste due unità di misura?"
#~ msgid "Are you sure that you want to merge these two ingredients?"
#~ msgstr "Sei sicuro di volere unire questi due ingredienti?"
#~ msgid "Import Recipes"
#~ msgstr "Importa Ricette"
#~ msgid "Close"
#~ msgstr "Chiudi"
#~ msgid "Open Recipe"
#~ msgstr "Apri Ricetta"
#~ msgid "Meal Plan View"
#~ msgstr "Mostra il piano alimentare"
#~ msgid "Created by"
#~ msgstr "Creato da"
#~ msgid "Shared with"
#~ msgstr "Condiviso con"
#~ msgid "Never cooked before."
#~ msgstr "Mai cucinato."
#~ msgid "Other meals on this day"
#~ msgstr "Altri pasti di questo giorno"
#~ msgid "Recipe Image"
#~ msgstr "Immagine ricetta"
#~ msgid "Preparation time ca."
#~ msgstr "Tempo di preparazione circa"
#~ msgid "Waiting time ca."
#~ msgstr "Tempo di cottura circa"
#~ msgid "External"
#~ msgstr "Esterna"
#~ msgid "Log Cooking"
#~ msgstr "Registro ricette cucinate"
#~ msgid "Account"
#~ msgstr "Account"
#~ msgid "Preferences"
#~ msgstr "Preferenze"
#~ msgid "API-Settings"
#~ msgstr "Impostazioni API"
#~ msgid "Search-Settings"
#~ msgstr "Cerca-Impostazioni"
#~ msgid "Shopping-Settings"
#~ msgstr "Spesa-Impostazioni"
#~ msgid "Name Settings"
#~ msgstr "Impostazioni Nome"
#~ msgid "Account Settings"
#~ msgstr "Impostazioni Account"
#~ msgid "Emails"
#~ msgstr "Email"
#~ msgid "Language"
#~ msgstr "Lingua"
#~ msgid "Style"
#~ msgstr "Stile"
#~ msgid "API Token"
#~ msgstr "Token API"
#~ msgid ""
#~ "You can use both basic authentication and token based authentication to "
#~ "access the REST API."
#~ msgstr ""
#~ "Per accedere alle API REST puoi usare sia l'autenticazione base sia "
#~ "quella tramite token."
#~ msgid ""
#~ "Use the token as an Authorization header prefixed by the word token as "
#~ "shown in the following examples:"
#~ msgstr ""
#~ "Usa il token come header Authorization preceduto dalla parola Token come "
#~ "negli esempi seguenti:"
#~ msgid "or"
#~ msgstr "o"
#, fuzzy
#~| msgid "Shopping List"
#~ msgid "Shopping Settings"
#~ msgstr "Lista della spesa"
#~ msgid "Stats"
#~ msgstr "Statistiche"
#~ msgid "Statistics"
#~ msgstr "Statistiche"
#~ msgid "Number of objects"
#~ msgstr "Numero di oggetti"
#~ msgid "Recipe Imports"
#~ msgstr "Ricette importate"
#~ msgid "Objects stats"
#~ msgstr "Statistiche degli oggetti"
#~ msgid "Recipes without Keywords"
#~ msgstr "Ricette senza parole chiave"
#~ msgid "Internal Recipes"
#~ msgstr "Ricette interne"
#~ msgid "Show Links"
#~ msgstr "Mostra link"
#~ msgid "Invite User"
#~ msgstr "Invita utente"
#~ msgid "User"
#~ msgstr "Utente"
#~ msgid "Groups"
#~ msgstr "Gruppi"
#~ msgid "admin"
#~ msgstr "admin"
#~ msgid "user"
#~ msgstr "utente"
#~ msgid "guest"
#~ msgstr "ospite"
#~ msgid "remove"
#~ msgstr "rimuovi"
#~ msgid "Update"
#~ msgstr "Aggiorna"
#~ msgid "You cannot edit yourself."
#~ msgstr "Non puoi modificare te stesso."
#~ msgid "There are no members in your space yet!"
#~ msgstr "Non ci sono ancora ricette in questa istanza!"
#~ msgid "Invite link successfully send to user."
#~ msgstr "Link di invito inviato con successo all'utente."
#~ msgid ""
#~ "You have send to many emails, please share the link manually or wait a "
#~ "few hours."
#~ msgstr ""
#~ "Hai mandato troppe email, condividi il link manualmente o aspetta qualche "
#~ "ora."
#~ msgid "Email could not be sent to user. Please share the link manually."
#~ msgstr ""
#~ "Non è stato possibile inviare l'email all'utente, condividi il link "
#~ "manualmente."
#~ msgid ""
#~ "You are already member of a space and therefore cannot join this one."
#~ msgstr ""
#~ "Sei già membro di una istanza e quindi non puoi entrare in quest'altra."
#~ msgid "Try the new shopping list"
#~ msgstr "Prova la nuova lista della spesa"
#~ msgid "Search Recipe"
#~ msgstr "Cerca Ricetta"
#~ msgid "Shopping Recipes"
#~ msgstr "Ricette per la spesa"
#~ msgid "No recipes selected"
#~ msgstr "Nessuna ricetta selezionata"
#~ msgid "Entry Mode"
#~ msgstr "Modalità di inserimento"
#~ msgid "Add Entry"
#~ msgstr "Aggiungi voce"
#~ msgid "Amount"
#~ msgstr "Quantità"
#~ msgid "Select Unit"
#~ msgstr "Seleziona unità di misura"
#~ msgid "Select"
#~ msgstr "Seleziona"
#~ msgid "Select Food"
#~ msgstr "Seleziona alimento"
#~ msgid "Select Supermarket"
#~ msgstr "Seleziona supermercato"
#~ msgid "Select User"
#~ msgstr "Seleziona utente"
#~ msgid "Finished"
#~ msgstr "Completato"
#, fuzzy
#~| msgid "You are offline, shopping list might not syncronize."
#~ msgid "You are offline, shopping list might not synchronize."
#~ msgstr "Sei offline: la lista della spesa potrebbe non sincronizzarsi."
#~ msgid "Copy/Export"
#~ msgstr "Copia/Esporta"
#~ msgid "Drag me to your bookmarks to import recipes from anywhere"
#~ msgstr "Spostami nei tuoi segnalibri per importare facilmente le ricette"
#~ msgid "Bookmark Me!"
#~ msgstr "Salvami nei preferiti!"
#~ msgid "URL"
#~ msgstr "URL"
#~ msgid "App"
#~ msgstr "App"
#~ msgid "Text"
#~ msgstr "Testo"
#~ msgid "File"
#~ msgstr "File"
#~ msgid "Enter website URL"
#~ msgstr "Inserisci l'indirizzo del sito web"
#~ msgid "Select recipe files to import or drop them here..."
#~ msgstr "Seleziona i file delle ricette da importare o spostarli qui..."
#~ msgid "Paste json or html source here to load recipe."
#~ msgstr "Incolla qui il codice html o json per caricare una ricetta."
#~ msgid "Preview Recipe Data"
#~ msgstr "Anteprima dati della ricetta"
#~ msgid ""
#~ "Drag recipe attributes from the right into the appropriate box below."
#~ msgstr ""
#~ "Trascina gli attributi della ricetta da destra nella casella in basso."
#~ msgid "Clear Contents"
#~ msgstr "Cancella il contenuto"
#~ msgid "Text dragged here will be appended to the name."
#~ msgstr "Il testo trascinato qui sarà aggiunto al nome."
#~ msgid "Text dragged here will be appended to the description."
#~ msgstr "Il testo trascinato qui sarà aggiunto alla descrizione."
#~ msgid "Keywords dragged here will be appended to current list"
#~ msgstr ""
#~ "Le parole chiave trascinate qui saranno aggiunte alla lista corrente"
#~ msgid "Image"
#~ msgstr "Immagine"
#~ msgid "Prep Time"
#~ msgstr "Tempo di preparazione"
#~ msgid "Cook Time"
#~ msgstr "Tempo di cottura"
#~ msgid "Ingredients dragged here will be appended to current list."
#~ msgstr ""
#~ "Gli ingredienti trascinati qui saranno aggiunti alla lista corrente."
#~ msgid ""
#~ "Recipe instructions dragged here will be appended to current instructions."
#~ msgstr ""
#~ "Le istruzioni per la ricetta trascinate qui saranno aggiunte alle "
#~ "istruzioni correnti."
#~ msgid "Discovered Attributes"
#~ msgstr "Attributi trovati"
#~ msgid ""
#~ "Drag recipe attributes from below into the appropriate box on the left. "
#~ "Click any node to display its full properties."
#~ msgstr ""
#~ "Trascina gli attributi delle ricette dal basso nella casella sulla "
#~ "sinistra. Clicca su qualsiasi nodo per mostrare le sue proprietà complete."
#~ msgid "Show Blank Field"
#~ msgstr "Mostra campo vuoto"
#~ msgid "Blank Field"
#~ msgstr "Campo vuoto"
#~ msgid "Items dragged to Blank Field will be appended."
#~ msgstr "Gli elementi trascinati nel campo vuoto saranno ignorati."
#~ msgid "Delete Text"
#~ msgstr "Elimina testo"
#~ msgid "Delete image"
#~ msgstr "Elimina immagine"
#~ msgid "Recipe Name"
#~ msgstr "Nome Ricetta"
#~ msgid "Recipe Description"
#~ msgstr "Descrizione ricetta"
#~ msgid "Select one"
#~ msgstr "Seleziona un elemento"
#~ msgid "Note"
#~ msgstr "Nota"
#~ msgid "Add Keyword"
#~ msgstr "Aggiungi parole chiave"
#~ msgid "All Keywords"
#~ msgstr "Tutte le parole chiave"
#~ msgid "Import all keywords, not only the ones already existing."
#~ msgstr "Importa tutte le parole chiave, non solo quelle che già esistono."
#~ msgid "Information"
#~ msgstr "Informazioni"
#~ msgid ""
#~ " Only websites containing ld+json or microdata information can currently\n"
#~ " be imported. Most big recipe pages "
#~ "support this. If you site cannot be imported but\n"
#~ " you think\n"
#~ " it probably has some kind of "
#~ "structured data feel free to post an example in the\n"
#~ " github issues."
#~ msgstr ""
#~ " Possono essere importati solo i siti che contengono informazioni Id+json "
#~ "o microdata.\n"
#~ " I maggiori siti di ricette di solito "
#~ "sono supportati. Se questo sito non può essere importato ma \n"
#~ " credi che abbia una qualche tipo di "
#~ "struttura dati, puoi inviare un esempio nella sezione Issues \n"
#~ " su GitHub."
#~ msgid "Google ld+json Info"
#~ msgstr "Info Google Id+json"
#~ msgid "GitHub Issues"
#~ msgstr "Issues (Problemi aperti) su GitHub"
#~ msgid "Recipe Markup Specification"
#~ msgstr "Specifica di Markup della ricetta"
#~ msgid "The requested site provided malformed data and cannot be read."
#~ msgstr ""
#~ "Il sito richiesto ha fornito dati in formato non corretto e non può "
#~ "essere letto."
#~ msgid "The requested page could not be found."
#~ msgstr "La pagina richiesta non è stata trovata."
#~ msgid ""
#~ "The requested site does not provide any recognized data format to import "
#~ "the recipe from."
#~ msgstr ""
#~ "Il sito richiesto non fornisce un formato di dati riconosciuto da cui "
#~ "importare la ricetta."
#~ msgid "I couldn't find anything to do."
#~ msgstr "Non è stato trovato nulla da fare."
#~ msgid "Shopping Lists"
#~ msgstr "Liste della spesa"
#~ msgid "Time"
#~ msgstr "Tempo"
#~ msgid "Log Recipe Cooking"
#~ msgstr "Aggiungi al registro delle ricette cucinate"
#~ msgid "All fields are optional and can be left empty."
#~ msgstr "Tutti i campi sono opzionali e possono essere lasciati vuoti."
#~ msgid "Rating"
#~ msgstr "Valutazione"
#~ msgid "Exporting is not implemented for this provider"
#~ msgstr "Questo provider non permette l'esportazione"
#~ msgid "No {self.basename} with id {child} exists"
#~ msgstr "Non esiste nessun {self.basename} con id {child}"
#~ msgid "New unit that other gets replaced by."
#~ msgstr "Nuova unità di misura che sostituisce le altre."
#~ msgid "Old Unit"
#~ msgstr "Vecchia unità di misura"
#~ msgid "Unit that should be replaced."
#~ msgstr "Unità di misura che dovrebbe essere rimpiazzata."
#~ msgid "New Food"
#~ msgstr "Nuovo alimento"
#~ msgid "New food that other gets replaced by."
#~ msgstr "Nuovo alimento che sostituisce gli altri."
#~ msgid "Old Food"
#~ msgstr "Vecchio alimento"
#~ msgid "New Entry"
#~ msgstr "Nuovo Campo"
#~ msgid "Title"
#~ msgstr "Titolo"
#~ msgid "Note (optional)"
#~ msgstr "Nota (opzionale)"
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "Puoi usare markdown per formattare questo campo. Guarda la documentazione qui"
#~ msgid "Serving Count"
#~ msgstr "Numero di porzioni"
#~ msgid "Create only note"
#~ msgstr "Crea solo una nota"
#~ msgid "Number of Days"
#~ msgstr "Numero di giorni"
#~ msgid "Weekday offset"
#~ msgstr "Correzione giorni feriali"
#~ msgid ""
#~ "Number of days starting from the first day of the week to offset the "
#~ "default view."
#~ msgstr ""
#~ "Numero di giorni a partire dal primo giorno della settimana per "
#~ "correggere la visualizzazione predefinita."
#~ msgid "Edit plan types"
#~ msgstr "Modifica i tipi di piano"
#~ msgid "Show help"
#~ msgstr "Mostra aiuto"
#~ msgid "Week iCal export"
#~ msgstr "Esporta iCall settimanale"
#~ msgid "Add to Shopping"
#~ msgstr "Aggiunti a lista della spesa"
#~ msgid "New meal type"
#~ msgstr "Nuovo tipo di pasto"
#~ msgid "Meal Plan Help"
#~ msgstr "Aiuto per il piano alimentare"
#~ msgid ""
#~ "\n"
#~ " The meal plan module allows planning of "
#~ "meals both with recipes and notes.
\n"
#~ " Simply select a recipe from the list of "
#~ "recently viewed recipes or search the one you\n"
#~ " want and drag it to the desired plan "
#~ "position. You can also add a note and a title and\n"
#~ " then drag the recipe to create a plan "
#~ "entry with a custom title and note. Creating only\n"
#~ " Notes is possible by dragging the create "
#~ "note box into the plan.
\n"
#~ " Click on a recipe in order to open the "
#~ "detailed view. There you can also add it to the\n"
#~ " shopping list. You can also add all "
#~ "recipes of a day to the shopping list by\n"
#~ " clicking the shopping cart at the top of "
#~ "the table.
\n"
#~ " Since a common use case is to plan meals "
#~ "together you can define\n"
#~ " users you want to share your plan with in "
#~ "the settings.\n"
#~ "
\n"
#~ " You can also edit the types of meals you "
#~ "want to plan. If you share your plan with\n"
#~ " someone with\n"
#~ " different meals, their meal types will "
#~ "appear in your list as well. To prevent\n"
#~ " duplicates (e.g. Other and Misc.)\n"
#~ " name your meal types the same as the "
#~ "users you share your meals with and they will be\n"
#~ " merged.
\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ "Il modulo del piano alimentare consente di pianificare i pasti sia con "
#~ "ricette che con semplici note.
\n"
#~ "Seleziona una ricetta dalla lista delle ricette recenti o cercane "
#~ "una,\n"
#~ "quindi spostala sulla posizione desiderata. Puoi anche aggiungere una "
#~ "nota e un titolo e\n"
#~ "poi trascinare la ricetta per creare una voce nel piano con un titolo e "
#~ "una nota personalizzata. Si possono anche creare\n"
#~ "delle note trascinando la casella della nota nel piano.
\n"
#~ "Clicca su una ricetta per aprire la vista dettagliata. Qui potrai "
#~ "anche aggiungerla alla lista della spesa. Puoi anche aggiungere tutte le "
#~ "ricette di un giorno alla lista della spesa, basterà cliccare sul "
#~ "carrello sopra la tabella.
\n"
#~ "Dato che è comune pianificare i pasti con altre persone, nelle "
#~ "impostazioni puoi scegliere gli utenti con i quali condividere il tuo "
#~ "piano.
\n"
#~ "Puoi anche modificare i tipi di pasto che vuoi pianificare. Se "
#~ "condividi il piano con\n"
#~ "qualcuno\n"
#~ "con pasti differenti, i loro tipi di pasto appariranno anche nella tua "
#~ "lista. Per evitare\n"
#~ "duplicati (es. Altri e Varie)\n"
#~ "dai nomi ai tuoi tipi di pasto uguali ai tuoi utenti in modo che verranno "
#~ "uniti.
"
#~ msgid "Units merged!"
#~ msgstr "Le unità sono state unite!"
#~ msgid "Foods merged!"
#~ msgstr "Gli alimenti sono stati uniti!"
#~ msgid "Utensils"
#~ msgstr "Strumenti"
#~ msgid "Storage Data"
#~ msgstr "Dati e Archiviazione"
#~ msgid "Storage Backends"
#~ msgstr "Backend Archiviazione"
#~ msgid "Configure Sync"
#~ msgstr "Configura Sincronizzazione"
#~ msgid "Discovered Recipes"
#~ msgstr "Ricette trovate"
#~ msgid "Discovery Log"
#~ msgstr "Registro ricette trovate"
#~ msgid "Units & Ingredients"
#~ msgstr "Unità di misura & Ingredienti"
#~ msgid "New Book"
#~ msgstr "Nuovo Libro"
#~ msgid "Toggle Recipes"
#~ msgstr "Attiva/Disattiva Ricette"
#~ msgid "There are no recipes in this book yet."
#~ msgstr "Non ci sono ancora ricette in questo libro."
#~ msgid "Waiting Time"
#~ msgstr "Tempo di cottura"
#~ msgid "Servings Text"
#~ msgstr "Nome delle porzioni"
#~ msgid "Select Keywords"
#~ msgstr "Seleziona parole chiave"
#~ msgid "Delete Step"
#~ msgstr "Elimina Step"
#~ msgid "Step"
#~ msgstr "Step"
#~ msgid "Show as header"
#~ msgstr "Mostra come intestazione"
#~ msgid "Hide as header"
#~ msgstr "Nascondi come intestazione"
#~ msgid "Move Up"
#~ msgstr "Sposta Sopra"
#~ msgid "Move Down"
#~ msgstr "Sposta Sotto"
#~ msgid "Step Name"
#~ msgstr "Nome dello Step"
#~ msgid "Step Type"
#~ msgstr "Tipo dello Step"
#~ msgid "Step time in Minutes"
#~ msgstr "Tempo dello step in minuti"
#~ msgid "Select File"
#~ msgstr "Seleziona file"
#~ msgid "Select Recipe"
#~ msgstr "Seleziona ricetta"
#~ msgid "Delete Ingredient"
#~ msgstr "Elimina Ingredienti"
#~ msgid "Make Header"
#~ msgstr "Crea Intestazione"
#~ msgid "Make Ingredient"
#~ msgstr "Crea Ingrediente"
#~ msgid "Disable Amount"
#~ msgstr "Disabilita Quantità"
#~ msgid "Enable Amount"
#~ msgstr "Abilita Quantità"
#~ msgid "Copy Template Reference"
#~ msgstr "Copia riferimento template"
#~ msgid "Save & View"
#~ msgstr "Salva & Mostra"
#~ msgid "Add Step"
#~ msgstr "Aggiungi Step"
#~ msgid "Add Nutrition"
#~ msgstr "Aggiungi nutrienti"
#~ msgid "Remove Nutrition"
#~ msgstr "Rimuovi nutrienti"
#~ msgid "View Recipe"
#~ msgstr "Mostra ricetta"
#~ msgid "Delete Recipe"
#~ msgstr "Elimina Ricetta"
#, fuzzy
#~| msgid "Password Reset"
#~ msgid "Password Settings"
#~ msgstr "Recupero password"
#, fuzzy
#~| msgid "Link social account"
#~ msgid "Manage Social Accounts"
#~ msgstr "Collega account social"
#~ msgid ""
#~ "A username is not required, if left blank the new user can choose one."
#~ msgstr ""
#~ "Non è richiesto un nome utente, se lasciato vuoto il nuovo utente ne può "
#~ "sceglierne uno."
#~ msgid "Link"
#~ msgstr "Link"
#~ msgid "Logout"
#~ msgstr "Logout"
#~ msgid "Website Import"
#~ msgstr "Importa dal web"
#~ msgid "You are not a member of any space."
#~ msgstr "Non sei membro di uno spazio."
#~ msgid "There was an error creating a resource!"
#~ msgstr "Si è verificato un errore durante la creazione di una risorsa!"
#~ msgid "Enter json directly"
#~ msgstr "Inserisci direttamente il json"
#~ msgid ""
#~ "The requested page refused to provide any information (Status Code 403)."
#~ msgstr ""
#~ "La pagina richiesta si è rifiutata di fornire informazioni (Errore 403)."
#~ msgid "Could not parse correctly..."
#~ msgstr "Impossibile elaborare correttamente..."
#~ msgid "Number of servings"
#~ msgstr "Porzioni"
#~ msgid ""
#~ "Include - [ ] in list for easier usage in markdown based "
#~ "documents."
#~ msgstr ""
#~ "Includi - [ ] nella lista per un utilizzo facilitato nei "
#~ "documenti markdown."
#~ msgid "Backup & Restore"
#~ msgstr "Backup & Ripristino"
#~ msgid "Download Backup"
#~ msgstr "Scarica backup"
#~ msgid "Preference for given user already exists"
#~ msgstr "La preferenza per l'utente fornito esiste già"
#~ msgid "This recipe is already linked to the book!"
#~ msgstr "Questa ricetta è già collegata al libro!"
================================================
FILE: cookbook/locale/ko/LC_MESSAGES/django.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-08-01 15:04+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: ko\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: .\cookbook\forms.py:45
msgid ""
"Both fields are optional. If none are given the username will be displayed "
"instead"
msgstr ""
#: .\cookbook\forms.py:62 .\cookbook\forms.py:246
msgid "Name"
msgstr ""
#: .\cookbook\forms.py:62 .\cookbook\forms.py:246 .\cookbook\views\lists.py:103
msgid "Keywords"
msgstr ""
#: .\cookbook\forms.py:62
msgid "Preparation time in minutes"
msgstr ""
#: .\cookbook\forms.py:62
msgid "Waiting time (cooking/baking) in minutes"
msgstr ""
#: .\cookbook\forms.py:63 .\cookbook\forms.py:222 .\cookbook\forms.py:246
msgid "Path"
msgstr ""
#: .\cookbook\forms.py:63
msgid "Storage UID"
msgstr ""
#: .\cookbook\forms.py:93
msgid "Default"
msgstr ""
#: .\cookbook\forms.py:121
msgid ""
"To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything."
msgstr ""
#: .\cookbook\forms.py:143
msgid "Add your comment: "
msgstr ""
#: .\cookbook\forms.py:151
msgid "Leave empty for dropbox and enter app password for nextcloud."
msgstr ""
#: .\cookbook\forms.py:154
msgid "Leave empty for nextcloud and enter api token for dropbox."
msgstr ""
#: .\cookbook\forms.py:160
msgid ""
"Leave empty for dropbox and enter only base url for nextcloud (/remote."
"php/webdav/ is added automatically)"
msgstr ""
#: .\cookbook\forms.py:188
msgid ""
"Long Lived Access Token for your HomeAssistant instance"
msgstr ""
#: .\cookbook\forms.py:193
msgid "Something like http://homeassistant.local:8123/api"
msgstr ""
#: .\cookbook\forms.py:205
msgid "http://homeassistant.local:8123/api for example"
msgstr ""
#: .\cookbook\forms.py:222 .\cookbook\views\edit.py:117
msgid "Storage"
msgstr ""
#: .\cookbook\forms.py:222
msgid "Active"
msgstr ""
#: .\cookbook\forms.py:226
msgid "Search String"
msgstr ""
#: .\cookbook\forms.py:246
msgid "File ID"
msgstr ""
#: .\cookbook\forms.py:262
msgid "Maximum number of users for this space reached."
msgstr ""
#: .\cookbook\forms.py:268
msgid "Email address already taken!"
msgstr ""
#: .\cookbook\forms.py:275
msgid ""
"An email address is not required but if present the invite link will be sent "
"to the user."
msgstr ""
#: .\cookbook\forms.py:287
msgid "Name already taken."
msgstr ""
#: .\cookbook\forms.py:298
msgid "Accept Terms and Privacy"
msgstr ""
#: .\cookbook\forms.py:332
msgid ""
"Determines how fuzzy a search is if it uses trigram similarity matching (e."
"g. low values mean more typos are ignored)."
msgstr ""
#: .\cookbook\forms.py:340
msgid ""
"Select type method of search. Click here for "
"full description of choices."
msgstr ""
#: .\cookbook\forms.py:341
msgid ""
"Use fuzzy matching on units, keywords and ingredients when editing and "
"importing recipes."
msgstr ""
#: .\cookbook\forms.py:342
msgid ""
"Fields to search ignoring accents. Selecting this option can improve or "
"degrade search quality depending on language"
msgstr ""
#: .\cookbook\forms.py:343
msgid ""
"Fields to search for partial matches. (e.g. searching for 'Pie' will return "
"'pie' and 'piece' and 'soapie')"
msgstr ""
#: .\cookbook\forms.py:344
msgid ""
"Fields to search for beginning of word matches. (e.g. searching for 'sa' "
"will return 'salad' and 'sandwich')"
msgstr ""
#: .\cookbook\forms.py:345
msgid ""
"Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) "
"Note: this option will conflict with 'web' and 'raw' methods of search."
msgstr ""
#: .\cookbook\forms.py:346
msgid ""
"Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods "
"only function with fulltext fields."
msgstr ""
#: .\cookbook\forms.py:350
msgid "Search Method"
msgstr ""
#: .\cookbook\forms.py:350
msgid "Fuzzy Lookups"
msgstr ""
#: .\cookbook\forms.py:350
msgid "Ignore Accent"
msgstr ""
#: .\cookbook\forms.py:350
msgid "Partial Match"
msgstr ""
#: .\cookbook\forms.py:350
msgid "Starts With"
msgstr ""
#: .\cookbook\forms.py:351
msgid "Fuzzy Search"
msgstr ""
#: .\cookbook\forms.py:351
msgid "Full Text"
msgstr ""
#: .\cookbook\helper\AllAuthCustomAdapter.py:41
msgid ""
"In order to prevent spam, the requested email was not send. Please wait a "
"few minutes and try again."
msgstr ""
#: .\cookbook\helper\permission_helper.py:164
#: .\cookbook\helper\permission_helper.py:187 .\cookbook\views\views.py:117
msgid "You are not logged in and therefore cannot view this page!"
msgstr ""
#: .\cookbook\helper\permission_helper.py:168
#: .\cookbook\helper\permission_helper.py:174
#: .\cookbook\helper\permission_helper.py:199
#: .\cookbook\helper\permission_helper.py:266
#: .\cookbook\helper\permission_helper.py:280
#: .\cookbook\helper\permission_helper.py:291
#: .\cookbook\helper\permission_helper.py:302
#: .\cookbook\helper\permission_helper.py:318
#: .\cookbook\helper\permission_helper.py:341 .\cookbook\views\data.py:35
#: .\cookbook\views\views.py:127 .\cookbook\views\views.py:131
msgid "You do not have the required permissions to view this page!"
msgstr ""
#: .\cookbook\helper\permission_helper.py:192
#: .\cookbook\helper\permission_helper.py:215
#: .\cookbook\helper\permission_helper.py:237
#: .\cookbook\helper\permission_helper.py:252
msgid "You cannot interact with this object as it is not owned by you!"
msgstr ""
#: .\cookbook\helper\permission_helper.py:402
msgid "You have reached the maximum number of recipes for your space."
msgstr ""
#: .\cookbook\helper\permission_helper.py:414
msgid "You have more users than allowed in your space."
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:310
msgid "reverse rotation"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:311
msgid "careful rotation"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:312
msgid "knead"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:313
msgid "thicken"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:314
msgid "warm up"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:315
msgid "ferment"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:316
msgid "sous-vide"
msgstr ""
#: .\cookbook\helper\shopping_helper.py:150
msgid "You must supply a servings size"
msgstr ""
#: .\cookbook\helper\template_helper.py:95
#: .\cookbook\helper\template_helper.py:97
msgid "Could not parse template code."
msgstr ""
#: .\cookbook\integration\copymethat.py:44
#: .\cookbook\integration\melarecipes.py:37
msgid "Favorite"
msgstr ""
#: .\cookbook\integration\copymethat.py:50
msgid "I made this"
msgstr ""
#: .\cookbook\integration\integration.py:209
msgid ""
"Importer expected a .zip file. Did you choose the correct importer type for "
"your data ?"
msgstr ""
#: .\cookbook\integration\integration.py:212
msgid ""
"An unexpected error occurred during the import. Please make sure you have "
"uploaded a valid file."
msgstr ""
#: .\cookbook\integration\integration.py:217
msgid "The following recipes were ignored because they already existed:"
msgstr ""
#: .\cookbook\integration\integration.py:221
#, python-format
msgid "Imported %s recipes."
msgstr ""
#: .\cookbook\integration\openeats.py:28
msgid "Recipe source:"
msgstr ""
#: .\cookbook\integration\paprika.py:49
msgid "Notes"
msgstr ""
#: .\cookbook\integration\paprika.py:52
msgid "Nutritional Information"
msgstr ""
#: .\cookbook\integration\paprika.py:56
msgid "Source"
msgstr ""
#: .\cookbook\integration\recettetek.py:54
#: .\cookbook\integration\recipekeeper.py:70
msgid "Imported from"
msgstr ""
#: .\cookbook\integration\saffron.py:23
msgid "Servings"
msgstr ""
#: .\cookbook\integration\saffron.py:25
msgid "Waiting time"
msgstr ""
#: .\cookbook\integration\saffron.py:27
msgid "Preparation Time"
msgstr ""
#: .\cookbook\integration\saffron.py:29 .\cookbook\templates\index.html:7
msgid "Cookbook"
msgstr ""
#: .\cookbook\integration\saffron.py:31
msgid "Section"
msgstr ""
#: .\cookbook\management\commands\fix_duplicate_properties.py:15
msgid "Fixes foods with "
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:14
msgid "Rebuilds full text search index on Recipe"
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:18
msgid "Only Postgresql databases use full text search, no index to rebuild"
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:29
msgid "Recipe index rebuild complete."
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:31
msgid "Recipe index rebuild failed."
msgstr ""
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
msgid "Breakfast"
msgstr ""
#: .\cookbook\migrations\0047_auto_20200602_1133.py:19
msgid "Lunch"
msgstr ""
#: .\cookbook\migrations\0047_auto_20200602_1133.py:24
msgid "Dinner"
msgstr ""
#: .\cookbook\migrations\0047_auto_20200602_1133.py:29 .\cookbook\models.py:919
msgid "Other"
msgstr ""
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
msgid "Fat"
msgstr ""
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "g"
msgstr ""
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
msgid "Carbohydrates"
msgstr ""
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "Proteins"
msgstr ""
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "Calories"
msgstr ""
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "kcal"
msgstr ""
#: .\cookbook\models.py:325
msgid ""
"Maximum file storage for space in MB. 0 for unlimited, -1 to disable file "
"upload."
msgstr ""
#: .\cookbook\models.py:454 .\cookbook\templates\search.html:7
#: .\cookbook\templates\settings.html:18
msgid "Search"
msgstr ""
#: .\cookbook\models.py:455 .\cookbook\templates\base.html:114
#: .\cookbook\templates\meal_plan.html:7
msgid "Meal-Plan"
msgstr ""
#: .\cookbook\models.py:456 .\cookbook\templates\base.html:122
#: .\cookbook\views\views.py:459
msgid "Books"
msgstr ""
#: .\cookbook\models.py:457 .\cookbook\templates\base.html:118
#: .\cookbook\views\views.py:460
msgid "Shopping"
msgstr ""
#: .\cookbook\models.py:752
msgid " is part of a recipe step and cannot be deleted"
msgstr ""
#: .\cookbook\models.py:918
msgid "Nutrition"
msgstr ""
#: .\cookbook\models.py:918
msgid "Allergen"
msgstr ""
#: .\cookbook\models.py:919
msgid "Price"
msgstr ""
#: .\cookbook\models.py:919
msgid "Goal"
msgstr ""
#: .\cookbook\models.py:1408 .\cookbook\templates\search_info.html:28
msgid "Simple"
msgstr ""
#: .\cookbook\models.py:1409 .\cookbook\templates\search_info.html:33
msgid "Phrase"
msgstr ""
#: .\cookbook\models.py:1410 .\cookbook\templates\search_info.html:38
msgid "Web"
msgstr ""
#: .\cookbook\models.py:1411 .\cookbook\templates\search_info.html:47
msgid "Raw"
msgstr ""
#: .\cookbook\models.py:1467
msgid "Food Alias"
msgstr ""
#: .\cookbook\models.py:1468
msgid "Unit Alias"
msgstr ""
#: .\cookbook\models.py:1469
msgid "Keyword Alias"
msgstr ""
#: .\cookbook\models.py:1470
msgid "Description Replace"
msgstr ""
#: .\cookbook\models.py:1471
msgid "Instruction Replace"
msgstr ""
#: .\cookbook\models.py:1472
msgid "Never Unit"
msgstr ""
#: .\cookbook\models.py:1473
msgid "Transpose Words"
msgstr ""
#: .\cookbook\models.py:1474
msgid "Food Replace"
msgstr ""
#: .\cookbook\models.py:1475
msgid "Unit Replace"
msgstr ""
#: .\cookbook\models.py:1476
msgid "Name Replace"
msgstr ""
#: .\cookbook\models.py:1503 .\cookbook\views\delete.py:40
#: .\cookbook\views\edit.py:210 .\cookbook\views\new.py:39
msgid "Recipe"
msgstr ""
#: .\cookbook\models.py:1504
msgid "Food"
msgstr ""
#: .\cookbook\models.py:1505 .\cookbook\templates\base.html:149
msgid "Keyword"
msgstr ""
#: .\cookbook\serializer.py:222
msgid "File uploads are not enabled for this Space."
msgstr ""
#: .\cookbook\serializer.py:233
msgid "You have reached your file upload limit."
msgstr ""
#: .\cookbook\serializer.py:328
msgid "Cannot modify Space owner permission."
msgstr ""
#: .\cookbook\serializer.py:1270
msgid "Hello"
msgstr ""
#: .\cookbook\serializer.py:1270
msgid "You have been invited by "
msgstr ""
#: .\cookbook\serializer.py:1272
msgid " to join their Tandoor Recipes space "
msgstr ""
#: .\cookbook\serializer.py:1274
msgid "Click the following link to activate your account: "
msgstr ""
#: .\cookbook\serializer.py:1276
msgid ""
"If the link does not work use the following code to manually join the space: "
msgstr ""
#: .\cookbook\serializer.py:1278
msgid "The invitation is valid until "
msgstr ""
#: .\cookbook\serializer.py:1280
msgid ""
"Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub "
msgstr ""
#: .\cookbook\serializer.py:1283
msgid "Tandoor Recipes Invite"
msgstr ""
#: .\cookbook\serializer.py:1426
msgid "Existing shopping list to update"
msgstr ""
#: .\cookbook\serializer.py:1428
msgid ""
"List of ingredient IDs from the recipe to add, if not provided all "
"ingredients will be added."
msgstr ""
#: .\cookbook\serializer.py:1430
msgid ""
"Providing a list_recipe ID and servings of 0 will delete that shopping list."
msgstr ""
#: .\cookbook\serializer.py:1439
msgid "Amount of food to add to the shopping list"
msgstr ""
#: .\cookbook\serializer.py:1441
msgid "ID of unit to use for the shopping list"
msgstr ""
#: .\cookbook\serializer.py:1443
msgid "When set to true will delete all food from active shopping lists."
msgstr ""
#: .\cookbook\tables.py:69 .\cookbook\tables.py:83
#: .\cookbook\templates\generic\delete_template.html:7
#: .\cookbook\templates\generic\delete_template.html:15
#: .\cookbook\templates\generic\edit_template.html:28
msgid "Delete"
msgstr ""
#: .\cookbook\templates\404.html:5
msgid "404 Error"
msgstr ""
#: .\cookbook\templates\404.html:18
msgid "The page you are looking for could not be found."
msgstr ""
#: .\cookbook\templates\404.html:33
msgid "Take me Home"
msgstr ""
#: .\cookbook\templates\404.html:35
msgid "Report a Bug"
msgstr ""
#: .\cookbook\templates\account\email.html:6
#: .\cookbook\templates\account\email.html:17
msgid "E-mail Addresses"
msgstr ""
#: .\cookbook\templates\account\email.html:12
#: .\cookbook\templates\account\password_change.html:11
#: .\cookbook\templates\account\password_set.html:11
#: .\cookbook\templates\base.html:331 .\cookbook\templates\settings.html:6
#: .\cookbook\templates\settings.html:17
#: .\cookbook\templates\socialaccount\connections.html:10
#: .\cookbook\templates\user_settings.html:8
msgid "Settings"
msgstr ""
#: .\cookbook\templates\account\email.html:13
msgid "Email"
msgstr ""
#: .\cookbook\templates\account\email.html:19
msgid "The following e-mail addresses are associated with your account:"
msgstr ""
#: .\cookbook\templates\account\email.html:36
msgid "Verified"
msgstr ""
#: .\cookbook\templates\account\email.html:38
msgid "Unverified"
msgstr ""
#: .\cookbook\templates\account\email.html:40
msgid "Primary"
msgstr ""
#: .\cookbook\templates\account\email.html:47
msgid "Make Primary"
msgstr ""
#: .\cookbook\templates\account\email.html:49
msgid "Re-send Verification"
msgstr ""
#: .\cookbook\templates\account\email.html:50
#: .\cookbook\templates\generic\delete_template.html:57
#: .\cookbook\templates\socialaccount\connections.html:44
msgid "Remove"
msgstr ""
#: .\cookbook\templates\account\email.html:58
msgid "Warning:"
msgstr ""
#: .\cookbook\templates\account\email.html:58
msgid ""
"You currently do not have any e-mail address set up. You should really add "
"an e-mail address so you can receive notifications, reset your password, etc."
msgstr ""
#: .\cookbook\templates\account\email.html:64
msgid "Add E-mail Address"
msgstr ""
#: .\cookbook\templates\account\email.html:69
msgid "Add E-mail"
msgstr ""
#: .\cookbook\templates\account\email.html:79
msgid "Do you really want to remove the selected e-mail address?"
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:6
#: .\cookbook\templates\account\email_confirm.html:10
msgid "Confirm E-mail Address"
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:16
#, python-format
msgid ""
"Please confirm that\n"
" %(email)s is an e-mail address "
"for user %(user_display)s\n"
" ."
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:22
#: .\cookbook\templates\generic\delete_template.html:72
msgid "Confirm"
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:29
#, python-format
msgid ""
"This e-mail confirmation link expired or is invalid. Please\n"
" issue a new e-mail confirmation "
"request."
msgstr ""
#: .\cookbook\templates\account\login.html:8 .\cookbook\templates\base.html:388
#: .\cookbook\templates\openid\login.html:8
msgid "Login"
msgstr ""
#: .\cookbook\templates\account\login.html:15
#: .\cookbook\templates\account\login.html:31
#: .\cookbook\templates\account\password_reset.html:39
#: .\cookbook\templates\account\password_reset_done.html:31
#: .\cookbook\templates\account\signup.html:69
#: .\cookbook\templates\account\signup_closed.html:15
#: .\cookbook\templates\openid\login.html:15
#: .\cookbook\templates\openid\login.html:26
#: .\cookbook\templates\socialaccount\authentication_error.html:15
msgid "Sign In"
msgstr ""
#: .\cookbook\templates\account\login.html:34
#: .\cookbook\templates\account\password_reset.html:41
#: .\cookbook\templates\account\password_reset_done.html:33
#: .\cookbook\templates\socialaccount\signup.html:8
#: .\cookbook\templates\socialaccount\signup.html:57
msgid "Sign Up"
msgstr ""
#: .\cookbook\templates\account\login.html:38
msgid "Lost your password?"
msgstr ""
#: .\cookbook\templates\account\login.html:39
#: .\cookbook\templates\account\password_reset.html:29
msgid "Reset My Password"
msgstr ""
#: .\cookbook\templates\account\login.html:50
msgid "Social Login"
msgstr ""
#: .\cookbook\templates\account\login.html:51
msgid "You can use any of the following providers to sign in."
msgstr ""
#: .\cookbook\templates\account\logout.html:5
#: .\cookbook\templates\account\logout.html:9
#: .\cookbook\templates\account\logout.html:18
msgid "Sign Out"
msgstr ""
#: .\cookbook\templates\account\logout.html:11
msgid "Are you sure you want to sign out?"
msgstr ""
#: .\cookbook\templates\account\password_change.html:6
#: .\cookbook\templates\account\password_change.html:16
#: .\cookbook\templates\account\password_change.html:21
#: .\cookbook\templates\account\password_reset_from_key.html:7
#: .\cookbook\templates\account\password_reset_from_key.html:13
#: .\cookbook\templates\account\password_reset_from_key_done.html:7
#: .\cookbook\templates\account\password_reset_from_key_done.html:13
msgid "Change Password"
msgstr ""
#: .\cookbook\templates\account\password_change.html:12
#: .\cookbook\templates\account\password_set.html:12
msgid "Password"
msgstr ""
#: .\cookbook\templates\account\password_change.html:22
msgid "Forgot Password?"
msgstr ""
#: .\cookbook\templates\account\password_reset.html:7
#: .\cookbook\templates\account\password_reset.html:13
#: .\cookbook\templates\account\password_reset_done.html:7
#: .\cookbook\templates\account\password_reset_done.html:18
msgid "Password Reset"
msgstr ""
#: .\cookbook\templates\account\password_reset.html:24
msgid ""
"Forgotten your password? Enter your e-mail address below, and we'll send you "
"an e-mail allowing you to reset it."
msgstr ""
#: .\cookbook\templates\account\password_reset.html:32
msgid "Password reset is disabled on this instance."
msgstr ""
#: .\cookbook\templates\account\password_reset_done.html:25
msgid ""
"We have sent you an e-mail. Please contact us if you do not receive it "
"within a few minutes."
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:13
msgid "Bad Token"
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:25
#, python-format
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used.\n"
" Please request a new "
"password reset."
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:33
msgid "change password"
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:36
#: .\cookbook\templates\account\password_reset_from_key_done.html:19
msgid "Your password is now changed."
msgstr ""
#: .\cookbook\templates\account\password_set.html:6
#: .\cookbook\templates\account\password_set.html:16
#: .\cookbook\templates\account\password_set.html:21
msgid "Set Password"
msgstr ""
#: .\cookbook\templates\account\signup.html:6
msgid "Register"
msgstr ""
#: .\cookbook\templates\account\signup.html:12
msgid "Create an Account"
msgstr ""
#: .\cookbook\templates\account\signup.html:42
#: .\cookbook\templates\socialaccount\signup.html:33
msgid "I accept the follwoing"
msgstr ""
#: .\cookbook\templates\account\signup.html:45
#: .\cookbook\templates\socialaccount\signup.html:36
msgid "Terms and Conditions"
msgstr ""
#: .\cookbook\templates\account\signup.html:48
#: .\cookbook\templates\socialaccount\signup.html:39
msgid "and"
msgstr ""
#: .\cookbook\templates\account\signup.html:52
#: .\cookbook\templates\socialaccount\signup.html:43
msgid "Privacy Policy"
msgstr ""
#: .\cookbook\templates\account\signup.html:65
msgid "Create User"
msgstr ""
#: .\cookbook\templates\account\signup.html:69
msgid "Already have an account?"
msgstr ""
#: .\cookbook\templates\account\signup_closed.html:5
#: .\cookbook\templates\account\signup_closed.html:11
msgid "Sign Up Closed"
msgstr ""
#: .\cookbook\templates\account\signup_closed.html:13
msgid "We are sorry, but the sign up is currently closed."
msgstr ""
#: .\cookbook\templates\api_info.html:5 .\cookbook\templates\base.html:378
#: .\cookbook\templates\rest_framework\api.html:11
msgid "API Documentation"
msgstr ""
#: .\cookbook\templates\base.html:110 .\cookbook\templates\index.html:87
msgid "Recipes"
msgstr ""
#: .\cookbook\templates\base.html:161 .\cookbook\views\lists.py:120
msgid "Foods"
msgstr ""
#: .\cookbook\templates\base.html:173 .\cookbook\views\lists.py:137
msgid "Units"
msgstr ""
#: .\cookbook\templates\base.html:187
msgid "Supermarket"
msgstr ""
#: .\cookbook\templates\base.html:199
msgid "Supermarket Category"
msgstr ""
#: .\cookbook\templates\base.html:211 .\cookbook\views\lists.py:186
msgid "Automations"
msgstr ""
#: .\cookbook\templates\base.html:225 .\cookbook\views\lists.py:222
msgid "Files"
msgstr ""
#: .\cookbook\templates\base.html:237
msgid "Batch Edit"
msgstr ""
#: .\cookbook\templates\base.html:249 .\cookbook\templates\history.html:6
#: .\cookbook\templates\history.html:14
msgid "History"
msgstr ""
#: .\cookbook\templates\base.html:263
#: .\cookbook\templates\ingredient_editor.html:7
#: .\cookbook\templates\ingredient_editor.html:13
msgid "Ingredient Editor"
msgstr ""
#: .\cookbook\templates\base.html:275
#: .\cookbook\templates\export_response.html:7
#: .\cookbook\templates\test2.html:14 .\cookbook\templates\test2.html:20
msgid "Export"
msgstr ""
#: .\cookbook\templates\base.html:287
msgid "Properties"
msgstr ""
#: .\cookbook\templates\base.html:301 .\cookbook\views\lists.py:255
msgid "Unit Conversions"
msgstr ""
#: .\cookbook\templates\base.html:318 .\cookbook\templates\index.html:47
msgid "Import Recipe"
msgstr ""
#: .\cookbook\templates\base.html:320
msgid "Create"
msgstr ""
#: .\cookbook\templates\base.html:333
#: .\cookbook\templates\generic\list_template.html:14
msgid "External Recipes"
msgstr ""
#: .\cookbook\templates\base.html:336 .\cookbook\templates\space_manage.html:15
msgid "Space Settings"
msgstr ""
#: .\cookbook\templates\base.html:340
msgid "External Connectors"
msgstr ""
#: .\cookbook\templates\base.html:345 .\cookbook\templates\system.html:13
msgid "System"
msgstr ""
#: .\cookbook\templates\base.html:347
msgid "Admin"
msgstr ""
#: .\cookbook\templates\base.html:351
#: .\cookbook\templates\space_overview.html:25
msgid "Your Spaces"
msgstr ""
#: .\cookbook\templates\base.html:362
#: .\cookbook\templates\space_overview.html:6
msgid "Overview"
msgstr ""
#: .\cookbook\templates\base.html:372
msgid "Markdown Guide"
msgstr ""
#: .\cookbook\templates\base.html:374
msgid "GitHub"
msgstr ""
#: .\cookbook\templates\base.html:376
msgid "Translate Tandoor"
msgstr ""
#: .\cookbook\templates\base.html:380
msgid "API Browser"
msgstr ""
#: .\cookbook\templates\base.html:383
msgid "Log out"
msgstr ""
#: .\cookbook\templates\base.html:406
msgid "You are using the free version of Tandor"
msgstr ""
#: .\cookbook\templates\base.html:407
msgid "Upgrade Now"
msgstr ""
#: .\cookbook\templates\batch\edit.html:6
msgid "Batch edit Category"
msgstr ""
#: .\cookbook\templates\batch\edit.html:15
msgid "Batch edit Recipes"
msgstr ""
#: .\cookbook\templates\batch\edit.html:20
msgid "Add the specified keywords to all recipes containing a word"
msgstr ""
#: .\cookbook\templates\batch\monitor.html:6 .\cookbook\views\edit.py:75
msgid "Sync"
msgstr ""
#: .\cookbook\templates\batch\monitor.html:10
msgid "Manage watched Folders"
msgstr ""
#: .\cookbook\templates\batch\monitor.html:14
msgid ""
"On this Page you can manage all storage folder locations that should be "
"monitored and synced."
msgstr ""
#: .\cookbook\templates\batch\monitor.html:16
msgid "The path must be in the following format"
msgstr ""
#: .\cookbook\templates\batch\monitor.html:20
#: .\cookbook\templates\forms\edit_import_recipe.html:14
#: .\cookbook\templates\generic\edit_template.html:23
#: .\cookbook\templates\generic\new_template.html:23
#: .\cookbook\templates\settings.html:57
msgid "Save"
msgstr ""
#: .\cookbook\templates\batch\monitor.html:21
msgid "Manage External Storage"
msgstr ""
#: .\cookbook\templates\batch\monitor.html:28
msgid "Sync Now!"
msgstr ""
#: .\cookbook\templates\batch\monitor.html:29
msgid "Show Recipes"
msgstr ""
#: .\cookbook\templates\batch\monitor.html:30
msgid "Show Log"
msgstr ""
#: .\cookbook\templates\batch\waiting.html:4
#: .\cookbook\templates\batch\waiting.html:10
msgid "Importing Recipes"
msgstr ""
#: .\cookbook\templates\batch\waiting.html:28
msgid ""
"This can take a few minutes, depending on the number of recipes in sync, "
"please wait."
msgstr ""
#: .\cookbook\templates\books.html:7
msgid "Recipe Books"
msgstr ""
#: .\cookbook\templates\export.html:7 .\cookbook\templates\test2.html:6
msgid "Export Recipes"
msgstr ""
#: .\cookbook\templates\forms\edit_import_recipe.html:5
#: .\cookbook\templates\forms\edit_import_recipe.html:9
msgid "Import new Recipe"
msgstr ""
#: .\cookbook\templates\forms\edit_internal_recipe.html:7
msgid "Edit Recipe"
msgstr ""
#: .\cookbook\templates\generic\delete_template.html:21
#, python-format
msgid "Are you sure you want to delete the %(title)s: %(object)s "
msgstr ""
#: .\cookbook\templates\generic\delete_template.html:22
msgid "This cannot be undone!"
msgstr ""
#: .\cookbook\templates\generic\delete_template.html:27
msgid "Protected"
msgstr ""
#: .\cookbook\templates\generic\delete_template.html:42
msgid "Cascade"
msgstr ""
#: .\cookbook\templates\generic\delete_template.html:73
msgid "Cancel"
msgstr ""
#: .\cookbook\templates\generic\edit_template.html:6
#: .\cookbook\templates\generic\edit_template.html:14
msgid "Edit"
msgstr ""
#: .\cookbook\templates\generic\edit_template.html:32
msgid "View"
msgstr ""
#: .\cookbook\templates\generic\edit_template.html:36
msgid "Delete original file"
msgstr ""
#: .\cookbook\templates\generic\list_template.html:6
#: .\cookbook\templates\generic\list_template.html:22
msgid "List"
msgstr ""
#: .\cookbook\templates\generic\list_template.html:36
msgid "Filter"
msgstr ""
#: .\cookbook\templates\generic\list_template.html:41
msgid "Import all"
msgstr ""
#: .\cookbook\templates\generic\new_template.html:6
#: .\cookbook\templates\generic\new_template.html:14
msgid "New"
msgstr ""
#: .\cookbook\templates\generic\table_template.html:76
msgid "previous"
msgstr ""
#: .\cookbook\templates\generic\table_template.html:98
msgid "next"
msgstr ""
#: .\cookbook\templates\history.html:20
msgid "View Log"
msgstr ""
#: .\cookbook\templates\history.html:24
msgid "Cook Log"
msgstr ""
#: .\cookbook\templates\import_response.html:7 .\cookbook\views\delete.py:90
#: .\cookbook\views\edit.py:174
msgid "Import"
msgstr ""
#: .\cookbook\templates\include\storage_backend_warning.html:4
msgid "Security Warning"
msgstr ""
#: .\cookbook\templates\include\storage_backend_warning.html:5
msgid ""
"\n"
" The Password and Token field are stored as plain text "
"inside the database.\n"
" This is necessary because they are needed to make API requests, but "
"it also increases the risk of\n"
" someone stealing it.
\n"
" To limit the possible damage tokens or accounts with limited access "
"can be used.\n"
" "
msgstr ""
#: .\cookbook\templates\index.html:29
msgid "Search recipe ..."
msgstr ""
#: .\cookbook\templates\index.html:44
msgid "New Recipe"
msgstr ""
#: .\cookbook\templates\index.html:53
msgid "Advanced Search"
msgstr ""
#: .\cookbook\templates\index.html:57
msgid "Reset Search"
msgstr ""
#: .\cookbook\templates\index.html:85
msgid "Last viewed"
msgstr ""
#: .\cookbook\templates\index.html:94
msgid "Log in to view recipes"
msgstr ""
#: .\cookbook\templates\markdown_info.html:5
#: .\cookbook\templates\markdown_info.html:13
msgid "Markdown Info"
msgstr ""
#: .\cookbook\templates\markdown_info.html:14
msgid ""
"\n"
" Markdown is lightweight markup language that can be used to format "
"plain text easily.\n"
" This site uses the Python Markdown library to\n"
" convert your text into nice looking HTML. Its full markdown "
"documentation can be found\n"
" here.\n"
" An incomplete but most likely sufficient documentation can be found "
"below.\n"
" "
msgstr ""
#: .\cookbook\templates\markdown_info.html:25
msgid "Headers"
msgstr ""
#: .\cookbook\templates\markdown_info.html:54
msgid "Formatting"
msgstr ""
#: .\cookbook\templates\markdown_info.html:56
#: .\cookbook\templates\markdown_info.html:72
msgid "Line breaks are inserted by adding two spaces after the end of a line"
msgstr ""
#: .\cookbook\templates\markdown_info.html:57
#: .\cookbook\templates\markdown_info.html:73
msgid "or by leaving a blank line in between."
msgstr ""
#: .\cookbook\templates\markdown_info.html:59
#: .\cookbook\templates\markdown_info.html:74
msgid "This text is bold"
msgstr ""
#: .\cookbook\templates\markdown_info.html:60
#: .\cookbook\templates\markdown_info.html:75
msgid "This text is italic"
msgstr ""
#: .\cookbook\templates\markdown_info.html:61
#: .\cookbook\templates\markdown_info.html:77
msgid "Blockquotes are also possible"
msgstr ""
#: .\cookbook\templates\markdown_info.html:84
msgid "Lists"
msgstr ""
#: .\cookbook\templates\markdown_info.html:85
msgid ""
"Lists can ordered or unordered. It is important to leave a blank line "
"before the list!"
msgstr ""
#: .\cookbook\templates\markdown_info.html:87
#: .\cookbook\templates\markdown_info.html:108
msgid "Ordered List"
msgstr ""
#: .\cookbook\templates\markdown_info.html:89
#: .\cookbook\templates\markdown_info.html:90
#: .\cookbook\templates\markdown_info.html:91
#: .\cookbook\templates\markdown_info.html:110
#: .\cookbook\templates\markdown_info.html:111
#: .\cookbook\templates\markdown_info.html:112
msgid "unordered list item"
msgstr ""
#: .\cookbook\templates\markdown_info.html:93
#: .\cookbook\templates\markdown_info.html:114
msgid "Unordered List"
msgstr ""
#: .\cookbook\templates\markdown_info.html:95
#: .\cookbook\templates\markdown_info.html:96
#: .\cookbook\templates\markdown_info.html:97
#: .\cookbook\templates\markdown_info.html:116
#: .\cookbook\templates\markdown_info.html:117
#: .\cookbook\templates\markdown_info.html:118
msgid "ordered list item"
msgstr ""
#: .\cookbook\templates\markdown_info.html:125
msgid "Images & Links"
msgstr ""
#: .\cookbook\templates\markdown_info.html:126
msgid ""
"Links can be formatted with Markdown. This application also allows to paste "
"links directly into markdown fields without any formatting."
msgstr ""
#: .\cookbook\templates\markdown_info.html:132
#: .\cookbook\templates\markdown_info.html:145
msgid "This will become an image"
msgstr ""
#: .\cookbook\templates\markdown_info.html:152
msgid "Tables"
msgstr ""
#: .\cookbook\templates\markdown_info.html:153
msgid ""
"Markdown tables are hard to create by hand. It is recommended to use a table "
"editor like this one."
msgstr ""
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:171
#: .\cookbook\templates\markdown_info.html:177
msgid "Table"
msgstr ""
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:172
msgid "Header"
msgstr ""
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:178
msgid "Cell"
msgstr ""
#: .\cookbook\templates\no_groups_info.html:5
#: .\cookbook\templates\no_groups_info.html:12
msgid "No Permissions"
msgstr ""
#: .\cookbook\templates\no_groups_info.html:17
msgid "You do not have any groups and therefor cannot use this application."
msgstr ""
#: .\cookbook\templates\no_groups_info.html:18
#: .\cookbook\templates\no_perm_info.html:15
msgid "Please contact your administrator."
msgstr ""
#: .\cookbook\templates\no_perm_info.html:5
#: .\cookbook\templates\no_perm_info.html:12
msgid "No Permission"
msgstr ""
#: .\cookbook\templates\no_perm_info.html:15
msgid ""
"You do not have the required permissions to view this page or perform this "
"action."
msgstr ""
#: .\cookbook\templates\offline.html:6
msgid "Offline"
msgstr ""
#: .\cookbook\templates\offline.html:19
msgid "You are currently offline!"
msgstr ""
#: .\cookbook\templates\offline.html:20
msgid ""
"The recipes listed below are available for offline viewing because you have "
"recently viewed them. Keep in mind that data might be outdated."
msgstr ""
#: .\cookbook\templates\openid\login.html:27
#: .\cookbook\templates\socialaccount\authentication_error.html:27
msgid "Back"
msgstr ""
#: .\cookbook\templates\property_editor.html:7
msgid "Property Editor"
msgstr ""
#: .\cookbook\templates\recipe_view.html:36
msgid "Comments"
msgstr ""
#: .\cookbook\templates\recipe_view.html:41
msgid "by"
msgstr ""
#: .\cookbook\templates\recipe_view.html:59 .\cookbook\views\delete.py:146
#: .\cookbook\views\edit.py:156
msgid "Comment"
msgstr ""
#: .\cookbook\templates\rest_framework\api.html:5
msgid "Recipe Home"
msgstr ""
#: .\cookbook\templates\search_info.html:5
#: .\cookbook\templates\search_info.html:9
#: .\cookbook\templates\settings.html:24
msgid "Search Settings"
msgstr ""
#: .\cookbook\templates\search_info.html:10
msgid ""
"\n"
" Creating the best search experience is complicated and weighs "
"heavily on your personal configuration. \n"
" Changing any of the search settings can have significant impact on "
"the speed and quality of the results.\n"
" Search Methods, Trigrams and Full Text Search configurations are "
"only available if you are using Postgres for your database.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:19
msgid "Search Methods"
msgstr ""
#: .\cookbook\templates\search_info.html:23
msgid ""
" \n"
" Full text searches attempt to normalize the words provided to "
"match common variants. For example: 'forked', 'forking', 'forks' will all "
"normalize to 'fork'.\n"
" There are several methods available, described below, that will "
"control how the search behavior should react when multiple words are "
"searched.\n"
" Full technical details on how these operate can be viewed on Postgresql's website.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:29
msgid ""
" \n"
" Simple searches ignore punctuation and common words such as "
"'the', 'a', 'and'. And will treat separate words as required.\n"
" Searching for 'apple or flour' will return any recipe that "
"includes both 'apple' and 'flour' anywhere in the fields that have been "
"selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:34
msgid ""
" \n"
" Phrase searches ignore punctuation, but will search for all of "
"the words in the exact order provided.\n"
" Searching for 'apple or flour' will only return a recipe that "
"includes the exact phrase 'apple or flour' in any of the fields that have "
"been selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:39
msgid ""
" \n"
" Web searches simulate functionality found on many web search "
"sites supporting special syntax.\n"
" Placing quotes around several words will convert those words "
"into a phrase.\n"
" 'or' is recognized as searching for the word (or phrase) "
"immediately before 'or' OR the word (or phrase) directly after.\n"
" '-' is recognized as searching for recipes that do not include "
"the word (or phrase) that comes immediately after. \n"
" For example searching for 'apple pie' or cherry -butter will "
"return any recipe that includes the phrase 'apple pie' or the word "
"'cherry' \n"
" in any field included in the full text search but exclude any "
"recipe that has the word 'butter' in any field included.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:48
msgid ""
" \n"
" Raw search is similar to Web except will take puncuation "
"operators such as '|', '&' and '()'\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:59
msgid ""
" \n"
" Another approach to searching that also requires Postgresql is "
"fuzzy search or trigram similarity. A trigram is a group of three "
"consecutive characters.\n"
" For example searching for 'apple' will create x trigrams 'app', "
"'ppl', 'ple' and will create a score of how closely words match the "
"generated trigrams.\n"
" One benefit of searching trigams is that a search for 'sandwich' "
"will find misspelled words such as 'sandwhich' that would be missed by other "
"methods.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:69
msgid "Search Fields"
msgstr ""
#: .\cookbook\templates\search_info.html:73
msgid ""
" \n"
" Unaccent is a special case in that it enables searching a field "
"'unaccented' for each search style attempting to ignore accented values. \n"
" For example when you enable unaccent for 'Name' any search "
"(starts with, contains, trigram) will attempt the search ignoring accented "
"characters.\n"
" \n"
" For the other options, you can enable search on any or all "
"fields and they will be combined together with an assumed 'OR'.\n"
" For example enabling 'Name' for Starts With, 'Name' and "
"'Description' for Partial Match and 'Ingredients' and 'Keywords' for Full "
"Search\n"
" and searching for 'apple' will generate a search that will "
"return recipes that have:\n"
" - A recipe name that starts with 'apple'\n"
" - OR a recipe name that contains 'apple'\n"
" - OR a recipe description that contains 'apple'\n"
" - OR a recipe that will have a full text search match ('apple' "
"or 'apples') in ingredients\n"
" - OR a recipe that will have a full text search match in "
"Keywords\n"
"\n"
" Combining too many fields in too many types of search can have a "
"negative impact on performance, create duplicate results or return "
"unexpected results.\n"
" For example, enabling fuzzy search or partial matches will "
"interfere with web search methods. \n"
" Searching for 'apple -pie' with fuzzy search and full text "
"search will return the recipe Apple Pie. Though it is not included in the "
"full text results, it does match the trigram results.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:95
msgid "Search Index"
msgstr ""
#: .\cookbook\templates\search_info.html:99
msgid ""
" \n"
" Trigram search and Full Text Search both rely on database "
"indexes to perform effectively. \n"
" You can rebuild the indexes on all fields in the Admin page for "
"Recipes and selecting all recipes and running 'rebuild index for selected "
"recipes'\n"
" You can also rebuild indexes at the command line by executing "
"the management command 'python manage.py rebuildindex'\n"
" "
msgstr ""
#: .\cookbook\templates\settings.html:25
msgid ""
"There are many options to configure the search depending on your personal "
"preferences."
msgstr ""
#: .\cookbook\templates\settings.html:26
msgid ""
"Usually you do not need to configure any of them and can just stick "
"with either the default or one of the following presets."
msgstr ""
#: .\cookbook\templates\settings.html:27
msgid ""
"If you do want to configure the search you can read about the different "
"options here."
msgstr ""
#: .\cookbook\templates\settings.html:32
msgid "Fuzzy"
msgstr ""
#: .\cookbook\templates\settings.html:33
msgid ""
"Find what you need even if your search or the recipe contains typos. Might "
"return more results than needed to make sure you find what you are looking "
"for."
msgstr ""
#: .\cookbook\templates\settings.html:34
msgid "This is the default behavior"
msgstr ""
#: .\cookbook\templates\settings.html:37 .\cookbook\templates\settings.html:46
msgid "Apply"
msgstr ""
#: .\cookbook\templates\settings.html:42
msgid "Precise"
msgstr ""
#: .\cookbook\templates\settings.html:43
msgid ""
"Allows fine control over search results but might not return results if too "
"many spelling mistakes are made."
msgstr ""
#: .\cookbook\templates\settings.html:44
msgid "Perfect for large Databases"
msgstr ""
#: .\cookbook\templates\setup.html:6 .\cookbook\templates\system.html:5
msgid "Cookbook Setup"
msgstr ""
#: .\cookbook\templates\setup.html:14
msgid "Setup"
msgstr ""
#: .\cookbook\templates\setup.html:15
msgid ""
"To start using this application you must first create a superuser account."
msgstr ""
#: .\cookbook\templates\setup.html:20
msgid "Create Superuser account"
msgstr ""
#: .\cookbook\templates\socialaccount\authentication_error.html:7
#: .\cookbook\templates\socialaccount\authentication_error.html:23
msgid "Social Network Login Failure"
msgstr ""
#: .\cookbook\templates\socialaccount\authentication_error.html:25
msgid ""
"An error occurred while attempting to login via your social network account."
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:4
#: .\cookbook\templates\socialaccount\connections.html:15
msgid "Account Connections"
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:11
msgid "Social"
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:18
msgid ""
"You can sign in to your account using any of the following third party\n"
" accounts:"
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:52
msgid ""
"You currently have no social network accounts connected to this account."
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:55
msgid "Add a 3rd Party Account"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:5
#: .\cookbook\templates\socialaccount\signup.html:5
msgid "Signup"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:9
#, python-format
msgid "Connect %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:11
#, python-format
msgid "You are about to connect a new third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:13
#, python-format
msgid "Sign In Via %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:15
#, python-format
msgid "You are about to sign in using a third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:20
msgid "Continue"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:10
#, python-format
msgid ""
"You are about to use your\n"
" %(provider_name)s account to login to\n"
" %(site_name)s. As a final step, please complete the following form:"
msgstr ""
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:23
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:31
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:39
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:47
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:55
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:63
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:71
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:79
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:87
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:95
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:103
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:111
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:119
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:127
msgid "Sign in using"
msgstr ""
#: .\cookbook\templates\space_manage.html:7
msgid "Space Management"
msgstr ""
#: .\cookbook\templates\space_manage.html:26
msgid "Space:"
msgstr ""
#: .\cookbook\templates\space_manage.html:27
msgid "Manage Subscription"
msgstr ""
#: .\cookbook\templates\space_overview.html:13 .\cookbook\views\delete.py:184
msgid "Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:17
msgid ""
"Recipes, foods, shopping lists and more are organized in spaces of one or "
"more people."
msgstr ""
#: .\cookbook\templates\space_overview.html:18
msgid ""
"You can either be invited into an existing space or create your own one."
msgstr ""
#: .\cookbook\templates\space_overview.html:53
msgid "Owner"
msgstr ""
#: .\cookbook\templates\space_overview.html:57
msgid "Leave Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:78
#: .\cookbook\templates\space_overview.html:88
msgid "Join Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:81
msgid "Join an existing space."
msgstr ""
#: .\cookbook\templates\space_overview.html:83
msgid ""
"To join an existing space either enter your invite token or click on the "
"invite link the space owner send you."
msgstr ""
#: .\cookbook\templates\space_overview.html:96
#: .\cookbook\templates\space_overview.html:105
msgid "Create Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:99
msgid "Create your own recipe space."
msgstr ""
#: .\cookbook\templates\space_overview.html:101
msgid "Start your own recipe space and invite other users to it."
msgstr ""
#: .\cookbook\templates\system.html:14
msgid ""
"\n"
" Django Recipes is an open source free software application. It can "
"be found on\n"
" GitHub.\n"
" Changelogs can be found here.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:20
msgid "System Information"
msgstr ""
#: .\cookbook\templates\system.html:41
msgid ""
"\n"
" You need to execute version.py in your update "
"script to generate version information (done automatically in docker).\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:46
msgid "Media Serving"
msgstr ""
#: .\cookbook\templates\system.html:47 .\cookbook\templates\system.html:61
#: .\cookbook\templates\system.html:75 .\cookbook\templates\system.html:88
#: .\cookbook\templates\system.html:102 .\cookbook\templates\system.html:113
msgid "Warning"
msgstr ""
#: .\cookbook\templates\system.html:47 .\cookbook\templates\system.html:61
#: .\cookbook\templates\system.html:75 .\cookbook\templates\system.html:88
#: .\cookbook\templates\system.html:104 .\cookbook\templates\system.html:113
msgid "Ok"
msgstr ""
#: .\cookbook\templates\system.html:49
msgid ""
"Serving media files directly using gunicorn/python is not recommend!\n"
" Please follow the steps described\n"
" here to update\n"
" your installation.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:55 .\cookbook\templates\system.html:70
#: .\cookbook\templates\system.html:83 .\cookbook\templates\system.html:94
#: .\cookbook\views\views.py:303
msgid "Everything is fine!"
msgstr ""
#: .\cookbook\templates\system.html:59
msgid "Secret Key"
msgstr ""
#: .\cookbook\templates\system.html:63
msgid ""
"\n"
" You do not have a SECRET_KEY configured in your "
".env file. Django defaulted to the\n"
" standard key\n"
" provided with the installation which is publicly know and "
"insecure! Please set\n"
" SECRET_KEY int the .env configuration "
"file.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:73
msgid "Debug Mode"
msgstr ""
#: .\cookbook\templates\system.html:77
msgid ""
"\n"
" This application is still running in debug mode. This is most "
"likely not needed. Turn of debug mode by\n"
" setting\n"
" DEBUG=0 int the .env configuration "
"file.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:86
msgid "Allowed Hosts"
msgstr ""
#: .\cookbook\templates\system.html:90
msgid ""
"\n"
" Your allowed hosts are configured to allow every host. This "
"might be ok in some setups but should be avoided. Please see the docs about "
"this.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:97
msgid "Database"
msgstr ""
#: .\cookbook\templates\system.html:100
msgid "Info"
msgstr ""
#: .\cookbook\templates\system.html:110 .\cookbook\templates\system.html:127
msgid "Migrations"
msgstr ""
#: .\cookbook\templates\system.html:116
msgid ""
"\n"
" Migrations should never fail!\n"
" Failed migrations will likely cause major parts of the app to "
"not function correctly.\n"
" If a migration fails make sure you are on the latest version and "
"if so please post the migration log and the overview below in a GitHub "
"issue.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:182
msgid "False"
msgstr ""
#: .\cookbook\templates\system.html:182
msgid "True"
msgstr ""
#: .\cookbook\templates\system.html:207
msgid "Hide"
msgstr ""
#: .\cookbook\templates\system.html:210
msgid "Show"
msgstr ""
#: .\cookbook\templates\url_import.html:8
msgid "URL Import"
msgstr ""
#: .\cookbook\views\api.py:120 .\cookbook\views\api.py:213
msgid "Parameter updated_at incorrectly formatted"
msgstr ""
#: .\cookbook\views\api.py:234 .\cookbook\views\api.py:340
#, python-brace-format
msgid "No {self.basename} with id {pk} exists"
msgstr ""
#: .\cookbook\views\api.py:238
msgid "Cannot merge with the same object!"
msgstr ""
#: .\cookbook\views\api.py:245
#, python-brace-format
msgid "No {self.basename} with id {target} exists"
msgstr ""
#: .\cookbook\views\api.py:250
msgid "Cannot merge with child object!"
msgstr ""
#: .\cookbook\views\api.py:288
#, python-brace-format
msgid "{source.name} was merged successfully with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:293
#, python-brace-format
msgid "An error occurred attempting to merge {source.name} with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:349
#, python-brace-format
msgid "{child.name} was moved successfully to the root."
msgstr ""
#: .\cookbook\views\api.py:352 .\cookbook\views\api.py:370
msgid "An error occurred attempting to move "
msgstr ""
#: .\cookbook\views\api.py:355
msgid "Cannot move an object to itself!"
msgstr ""
#: .\cookbook\views\api.py:361
#, python-brace-format
msgid "No {self.basename} with id {parent} exists"
msgstr ""
#: .\cookbook\views\api.py:367
#, python-brace-format
msgid "{child.name} was moved successfully to parent {parent.name}"
msgstr ""
#: .\cookbook\views\api.py:589
#, python-brace-format
msgid "{obj.name} was removed from the shopping list."
msgstr ""
#: .\cookbook\views\api.py:594 .\cookbook\views\api.py:1037
#: .\cookbook\views\api.py:1050
#, python-brace-format
msgid "{obj.name} was added to the shopping list."
msgstr ""
#: .\cookbook\views\api.py:742
msgid "Filter meal plans from date (inclusive) in the format of YYYY-MM-DD."
msgstr ""
#: .\cookbook\views\api.py:743
msgid "Filter meal plans to date (inclusive) in the format of YYYY-MM-DD."
msgstr ""
#: .\cookbook\views\api.py:744
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:872
msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:873
msgid "Query string matched (fuzzy) against object name."
msgstr ""
#: .\cookbook\views\api.py:909
msgid ""
"Query string matched (fuzzy) against recipe name. In the future also "
"fulltext search."
msgstr ""
#: .\cookbook\views\api.py:910
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr ""
#: .\cookbook\views\api.py:911
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr ""
#: .\cookbook\views\api.py:912
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:913
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr ""
#: .\cookbook\views\api.py:914
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:915
msgid "ID of food a recipe should have. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:916
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr ""
#: .\cookbook\views\api.py:917
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:918
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr ""
#: .\cookbook\views\api.py:919
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:920
msgid "ID of unit a recipe should have."
msgstr ""
#: .\cookbook\views\api.py:921
msgid ""
"Rating a recipe should have or greater. [0 - 5] Negative value filters "
"rating less than."
msgstr ""
#: .\cookbook\views\api.py:922
msgid "ID of book a recipe should be in. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:923
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr ""
#: .\cookbook\views\api.py:924
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:925
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr ""
#: .\cookbook\views\api.py:926
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:927
msgid "If only internal recipes should be returned. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:928
msgid "Returns the results in randomized order. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:929
msgid "Returns new results first in search results. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:930
msgid ""
"Filter recipes cooked X times or more. Negative values returns cooked less "
"than X times"
msgstr ""
#: .\cookbook\views\api.py:931
msgid ""
"Filter recipes last cooked on or after YYYY-MM-DD. Prepending - filters on "
"or before date."
msgstr ""
#: .\cookbook\views\api.py:932
msgid ""
"Filter recipes created on or after YYYY-MM-DD. Prepending - filters on or "
"before date."
msgstr ""
#: .\cookbook\views\api.py:933
msgid ""
"Filter recipes updated on or after YYYY-MM-DD. Prepending - filters on or "
"before date."
msgstr ""
#: .\cookbook\views\api.py:934
msgid ""
"Filter recipes lasts viewed on or after YYYY-MM-DD. Prepending - filters on "
"or before date."
msgstr ""
#: .\cookbook\views\api.py:935
msgid "Filter recipes that can be made with OnHand food. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1122
msgid ""
"Returns the shopping list entry with a primary key of id. Multiple values "
"allowed."
msgstr ""
#: .\cookbook\views\api.py:1125
msgid ""
"Filter shopping list entries on checked. [true, false, both, recent"
"b>]
- recent includes unchecked items and recently "
"completed items."
msgstr ""
#: .\cookbook\views\api.py:1128
msgid "Returns the shopping list entries sorted by supermarket category order."
msgstr ""
#: .\cookbook\views\api.py:1210
msgid "Filter for entries with the given recipe"
msgstr ""
#: .\cookbook\views\api.py:1292
msgid ""
"Return the Automations matching the automation type. Multiple values "
"allowed."
msgstr ""
#: .\cookbook\views\api.py:1415
msgid "Nothing to do."
msgstr ""
#: .\cookbook\views\api.py:1445
msgid "Invalid Url"
msgstr ""
#: .\cookbook\views\api.py:1449
msgid "Connection Refused."
msgstr ""
#: .\cookbook\views\api.py:1451
msgid "Bad URL Schema."
msgstr ""
#: .\cookbook\views\api.py:1474
msgid "No usable data could be found."
msgstr ""
#: .\cookbook\views\api.py:1549
msgid "File is above space limit"
msgstr ""
#: .\cookbook\views\api.py:1566 .\cookbook\views\import_export.py:114
msgid "Importing is not implemented for this provider"
msgstr ""
#: .\cookbook\views\api.py:1650 .\cookbook\views\data.py:30
#: .\cookbook\views\edit.py:88 .\cookbook\views\new.py:63
#: .\cookbook\views\new.py:82
msgid "This feature is not yet available in the hosted version of tandoor!"
msgstr ""
#: .\cookbook\views\api.py:1671
msgid "Sync successful!"
msgstr ""
#: .\cookbook\views\api.py:1674
msgid "Error synchronizing with Storage"
msgstr ""
#: .\cookbook\views\data.py:99
#, python-format
msgid "Batch edit done. %(count)d recipe was updated."
msgid_plural "Batch edit done. %(count)d Recipes where updated."
msgstr[0] ""
msgstr[1] ""
#: .\cookbook\views\delete.py:102
msgid "Monitor"
msgstr ""
#: .\cookbook\views\delete.py:114 .\cookbook\views\lists.py:61
#: .\cookbook\views\new.py:69
msgid "Storage Backend"
msgstr ""
#: .\cookbook\views\delete.py:122
msgid ""
"Could not delete this storage backend as it is used in at least one monitor."
msgstr ""
#: .\cookbook\views\delete.py:135
msgid "Connectors Config Backend"
msgstr ""
#: .\cookbook\views\delete.py:157
msgid "Invite Link"
msgstr ""
#: .\cookbook\views\delete.py:168
msgid "Space Membership"
msgstr ""
#: .\cookbook\views\edit.py:84
msgid "You cannot edit this storage!"
msgstr ""
#: .\cookbook\views\edit.py:108
msgid "Storage saved!"
msgstr ""
#: .\cookbook\views\edit.py:110
msgid "There was an error updating this storage backend!"
msgstr ""
#: .\cookbook\views\edit.py:134
msgid "Config saved!"
msgstr ""
#: .\cookbook\views\edit.py:142
msgid "ConnectorConfig"
msgstr ""
#: .\cookbook\views\edit.py:198
msgid "Changes saved!"
msgstr ""
#: .\cookbook\views\edit.py:202
msgid "Error saving changes!"
msgstr ""
#: .\cookbook\views\import_export.py:101
msgid ""
"The PDF Exporter is not enabled on this instance as it is still in an "
"experimental state."
msgstr ""
#: .\cookbook\views\lists.py:23
msgid "Import Log"
msgstr ""
#: .\cookbook\views\lists.py:36
msgid "Discovery"
msgstr ""
#: .\cookbook\views\lists.py:46
msgid "Shopping List"
msgstr ""
#: .\cookbook\views\lists.py:77 .\cookbook\views\new.py:98
msgid "Connector Config Backend"
msgstr ""
#: .\cookbook\views\lists.py:91
msgid "Invite Links"
msgstr ""
#: .\cookbook\views\lists.py:154
msgid "Supermarkets"
msgstr ""
#: .\cookbook\views\lists.py:170
msgid "Shopping Categories"
msgstr ""
#: .\cookbook\views\lists.py:202
msgid "Custom Filters"
msgstr ""
#: .\cookbook\views\lists.py:239
msgid "Steps"
msgstr ""
#: .\cookbook\views\lists.py:270
msgid "Property Types"
msgstr ""
#: .\cookbook\views\new.py:86
msgid "This feature is not enabled by the server admin!"
msgstr ""
#: .\cookbook\views\new.py:123
msgid "Imported new recipe!"
msgstr ""
#: .\cookbook\views\new.py:126
msgid "There was an error importing this recipe!"
msgstr ""
#: .\cookbook\views\views.py:69 .\cookbook\views\views.py:177
#: .\cookbook\views\views.py:204 .\cookbook\views\views.py:423
msgid "This feature is not available in the demo version!"
msgstr ""
#: .\cookbook\views\views.py:74
msgid ""
"You have the reached the maximum amount of spaces that can be owned by you."
msgstr ""
#: .\cookbook\views\views.py:89
msgid ""
"You have successfully created your own recipe space. Start by adding some "
"recipes or invite other people to join you."
msgstr ""
#: .\cookbook\views\views.py:138
msgid "You do not have the required permissions to perform this action!"
msgstr ""
#: .\cookbook\views\views.py:149
msgid "Comment saved!"
msgstr ""
#: .\cookbook\views\views.py:240
msgid "You must select at least one field to search!"
msgstr ""
#: .\cookbook\views\views.py:243
msgid ""
"To use this search method you must select at least one full text search "
"field!"
msgstr ""
#: .\cookbook\views\views.py:246
msgid "Fuzzy search is not compatible with this search method!"
msgstr ""
#: .\cookbook\views\views.py:306
#, python-format
msgid "PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!"
msgstr ""
#: .\cookbook\views\views.py:309
#, python-format
msgid "You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended"
msgstr ""
#: .\cookbook\views\views.py:313
msgid "Unable to determine PostgreSQL version."
msgstr ""
#: .\cookbook\views\views.py:317
msgid ""
"This application is not running with a Postgres database backend. This is ok "
"but not recommended as some features only work with postgres databases."
msgstr ""
#: .\cookbook\views\views.py:360
msgid ""
"The setup page can only be used to create the first "
"user! If you have forgotten your superuser credentials "
"please consult the django documentation on how to reset passwords."
msgstr ""
#: .\cookbook\views\views.py:369
msgid "Passwords dont match!"
msgstr ""
#: .\cookbook\views\views.py:377
msgid "User has been created, please login!"
msgstr ""
#: .\cookbook\views\views.py:393
msgid "Malformed Invite Link supplied!"
msgstr ""
#: .\cookbook\views\views.py:410
msgid "Successfully joined space."
msgstr ""
#: .\cookbook\views\views.py:416
msgid "Invite Link not valid or already used!"
msgstr ""
#: .\cookbook\views\views.py:432
msgid ""
"Reporting share links is not enabled for this instance. Please notify the "
"page administrator to report problems."
msgstr ""
#: .\cookbook\views\views.py:437
msgid ""
"Recipe sharing link has been disabled! For additional information please "
"contact the page administrator."
msgstr ""
#: .\cookbook\views\views.py:451
msgid "Manage recipes, shopping list, meal plans and more."
msgstr ""
#: .\cookbook\views\views.py:458
msgid "Plan"
msgstr ""
#: .\cookbook\views\views.py:458
msgid "View your meal Plan"
msgstr ""
#: .\cookbook\views\views.py:459
msgid "View your cookbooks"
msgstr ""
#: .\cookbook\views\views.py:460
msgid "View your shopping lists"
msgstr ""
================================================
FILE: cookbook/locale/lv/LC_MESSAGES/django.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
# Translators:
# vabene1111 , 2021
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-22 20:15+0200\n"
"PO-Revision-Date: 2025-07-21 09:43+0000\n"
"Last-Translator: Aija Kozlovska \n"
"Language-Team: Latvian \n"
"Language: lv\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2;\n"
"X-Generator: Weblate 5.8.4\n"
#: .\cookbook\forms.py:50
msgid "Default"
msgstr "Noklusējuma"
#: .\cookbook\forms.py:77
msgid ""
"To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything."
msgstr ""
"Lai novērstu dublikātus, receptes ar vienādu nosaukumu kā jau eksistējošās "
"tiek ignorētas. Ieķeksējiet šo izvēles rūtiņu lai importētu visu."
#: .\cookbook\forms.py:108
msgid "Maximum number of users for this space reached."
msgstr "Ir sasniegts maksimālais šīs vietnes lietotāju skaits."
#: .\cookbook\forms.py:114
msgid "Email address already taken!"
msgstr "Šāds epasts jau ir aizņemts!"
#: .\cookbook\forms.py:121
msgid ""
"An email address is not required but if present the invite link will be sent "
"to the user."
msgstr ""
"Epasts nav obligāts, bet, ja tas ir norādīts, ielūguma saite tiks nosūtīta "
"lietotājam."
#: .\cookbook\forms.py:133
msgid "Name already taken."
msgstr "Vārds jau ir aizņemts."
#: .\cookbook\forms.py:144 .\cookbook\forms.py:158
msgid "Accept Terms and Privacy"
msgstr "Pieņemts lietošanas un privātuma noteikumus"
#: .\cookbook\helper\AllAuthCustomAdapter.py:41
msgid ""
"In order to prevent spam, the requested email was not send. Please wait a "
"few minutes and try again."
msgstr ""
"Lai novērstu surogātziņas, pieprasītais epasts netika nosūtīts. Lūdzu "
"mēģiniet vēlreiz pēc pāris minūtēm."
#: .\cookbook\helper\permission_helper.py:165
#: .\cookbook\helper\permission_helper.py:186 .\cookbook\views\views.py:138
msgid "You are not logged in and therefore cannot view this page!"
msgstr "Jūs neesat pieteicies un tāpēc nevarat skatīt šo lapu!"
#: .\cookbook\helper\permission_helper.py:168
#: .\cookbook\helper\permission_helper.py:173
#: .\cookbook\helper\permission_helper.py:198
#: .\cookbook\helper\permission_helper.py:265
#: .\cookbook\helper\permission_helper.py:279
#: .\cookbook\helper\permission_helper.py:290
#: .\cookbook\helper\permission_helper.py:301
#: .\cookbook\helper\permission_helper.py:317
#: .\cookbook\helper\permission_helper.py:343
#: .\cookbook\helper\permission_helper.py:359
msgid "You do not have the required permissions to view this page!"
msgstr "Jums nav nepieciešamo atļauju, lai apskatītu šo lapu!"
#: .\cookbook\helper\permission_helper.py:191
#: .\cookbook\helper\permission_helper.py:214
#: .\cookbook\helper\permission_helper.py:236
#: .\cookbook\helper\permission_helper.py:251
msgid "You cannot interact with this object as it is not owned by you!"
msgstr "Jūs nevarat mainīt šo objektu, jo tas nepieder jums!"
#: .\cookbook\helper\permission_helper.py:420
msgid "You have reached the maximum number of recipes for your space."
msgstr "Jūst esat sasnieguši maksimālo Jūtu vietnes recepšu skaitu."
#: .\cookbook\helper\permission_helper.py:432
msgid "You have more users than allowed in your space."
msgstr "Jums ir vairāk lietotāju nekā atļauts Jūsu vietnē."
#: .\cookbook\helper\recipe_url_import.py:319
msgid "reverse rotation"
msgstr "pretējā rotācija"
#: .\cookbook\helper\recipe_url_import.py:320
msgid "careful rotation"
msgstr "piesardzīgā rotācija"
#: .\cookbook\helper\recipe_url_import.py:321
msgid "knead"
msgstr "mīcīt"
#: .\cookbook\helper\recipe_url_import.py:322
msgid "thicken"
msgstr "iebiezināt"
#: .\cookbook\helper\recipe_url_import.py:323
msgid "warm up"
msgstr "uzsildīt"
#: .\cookbook\helper\recipe_url_import.py:324
msgid "ferment"
msgstr "fermentēt"
#: .\cookbook\helper\recipe_url_import.py:325
#, fuzzy
#| msgid "Last cooked"
msgid "slow cook"
msgstr "Pēdējoreiz gatavots"
#: .\cookbook\helper\recipe_url_import.py:326
msgid "egg boiler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:327
msgid "kettle"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:328
msgid "blend"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:329
msgid "pre-clean"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:330
msgid "high temperature"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:331
msgid "rice cooker"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:332
msgid "caramelize"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:333
msgid "peeler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:334
msgid "slicer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:335
msgid "grater"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:336
msgid "spiralizer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:337
msgid "sous-vide"
msgstr "sous-vide"
#: .\cookbook\helper\shopping_helper.py:150
msgid "You must supply a servings size"
msgstr "Jums jānorāda porcijas izmērs"
#: .\cookbook\helper\template_helper.py:97
#: .\cookbook\helper\template_helper.py:99
#: .\cookbook\helper\template_helper.py:101
msgid "Could not parse template code."
msgstr "Nevarēja iekopēt sagataves kodu."
#: .\cookbook\integration\copymethat.py:44
#: .\cookbook\integration\melarecipes.py:37
msgid "Favorite"
msgstr "Iecienītākais"
#: .\cookbook\integration\copymethat.py:50
msgid "I made this"
msgstr "Es to pagatavoju"
#: .\cookbook\integration\integration.py:238
msgid ""
"Importer expected a .zip file. Did you choose the correct importer type for "
"your data ?"
msgstr ""
"Importētājs sagaida .zip failu. Vai izvēlējāties korektu importētāja tipu "
"priekš Jūsu datiem?"
#: .\cookbook\integration\integration.py:241
msgid ""
"An unexpected error occurred during the import. Please make sure you have "
"uploaded a valid file."
msgstr ""
"Importēšanas laikā notika negaidīta kļūda. Lūdzu pārliecinieties, ka "
"ielādētais fails ir korekts."
#: .\cookbook\integration\integration.py:246
msgid "The following recipes were ignored because they already existed:"
msgstr "Sekojošas receptes tika ignorētas, jo tādas jau eksistē:"
#: .\cookbook\integration\integration.py:250
#, python-format
msgid "Imported %s recipes."
msgstr "Importētas %s receptes."
#: .\cookbook\integration\mealie1.py:210
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "Calories"
msgstr "Kalorijas"
#: .\cookbook\integration\mealie1.py:211
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
msgid "Carbohydrates"
msgstr "Ogļhidrāti"
#: .\cookbook\integration\mealie1.py:212
msgid "Cholesterol"
msgstr ""
#: .\cookbook\integration\mealie1.py:213
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
msgid "Fat"
msgstr "Tauki"
#: .\cookbook\integration\mealie1.py:214
msgid "Fiber"
msgstr ""
#: .\cookbook\integration\mealie1.py:215
#, fuzzy
#| msgid "Proteins"
msgid "Protein"
msgstr "Olbaltumvielas"
#: .\cookbook\integration\mealie1.py:216
msgid "Saturated Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:217
msgid "Sodium"
msgstr ""
#: .\cookbook\integration\mealie1.py:218
msgid "Sugar"
msgstr ""
#: .\cookbook\integration\mealie1.py:219
msgid "Trans Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:220
msgid "Unsaturated Fat"
msgstr ""
#: .\cookbook\integration\openeats.py:28
msgid "Recipe source:"
msgstr "Receptes avots:"
#: .\cookbook\integration\paprika.py:49
msgid "Notes"
msgstr "Piezīmes"
#: .\cookbook\integration\paprika.py:52
msgid "Nutritional Information"
msgstr "Uzturvērtības Informācija"
#: .\cookbook\integration\paprika.py:56
msgid "Source"
msgstr "Avots"
#: .\cookbook\integration\recettetek.py:55
#: .\cookbook\integration\recipekeeper.py:70
msgid "Imported from"
msgstr "Importēts no"
#: .\cookbook\integration\saffron.py:23
msgid "Servings"
msgstr "Porciju skaits"
#: .\cookbook\integration\saffron.py:25
msgid "Waiting time"
msgstr "Gaidīšanas laiks"
#: .\cookbook\integration\saffron.py:27
msgid "Preparation Time"
msgstr "Pagatavošanas laiks"
#: .\cookbook\integration\saffron.py:29 .\cookbook\templates\index.html:6
msgid "Cookbook"
msgstr "Pavārgrāmata"
#: .\cookbook\integration\saffron.py:31
msgid "Section"
msgstr "Sadaļa"
#: .\cookbook\management\commands\fix_duplicate_properties.py:15
msgid "Fixes foods with "
msgstr "Salabo ēdienus ar "
#: .\cookbook\management\commands\rebuildindex.py:14
msgid "Rebuilds full text search index on Recipe"
msgstr "Pārbūvē receptes pilnā teksta meklēšanas indeksu"
#: .\cookbook\management\commands\rebuildindex.py:18
msgid "Only Postgresql databases use full text search, no index to rebuild"
msgstr ""
"Pilnā teksta meklēšanu izmanto tikai Postgresql datubāze, indeksu pārbūvēt "
"nav nepieciešams"
#: .\cookbook\management\commands\rebuildindex.py:29
msgid "Recipe index rebuild complete."
msgstr "Recepšu indeksa pārbūvēšana pabeigta."
#: .\cookbook\management\commands\rebuildindex.py:31
msgid "Recipe index rebuild failed."
msgstr "Recepšu indeksa pārbūvēšana neveiksmīga."
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
msgid "Breakfast"
msgstr "Brokastis"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:19
msgid "Lunch"
msgstr "Pusdienas"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:24
msgid "Dinner"
msgstr "Vakariņas"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:29 .\cookbook\models.py:971
msgid "Other"
msgstr "Cits"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "g"
msgstr "g"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "Proteins"
msgstr "Olbaltumvielas"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "kcal"
msgstr "kcal"
#: .\cookbook\models.py:325
msgid ""
"Maximum file storage for space in MB. 0 for unlimited, -1 to disable file "
"upload."
msgstr ""
"Maksimālā failu glabāšanas vieta MB. 0 priekš neierobežotas, -1 lai izslēgtu "
"failu ielādi."
#: .\cookbook\models.py:513
msgid "Search"
msgstr "Meklēt"
#: .\cookbook\models.py:514
msgid "Meal-Plan"
msgstr "Maltīšu plāns"
#: .\cookbook\models.py:515
msgid "Books"
msgstr "Grāmatas"
#: .\cookbook\models.py:516 .\cookbook\views\views.py:416
#: .\cookbook\views\views.py:417
msgid "Shopping"
msgstr "Iepirkšanās"
#: .\cookbook\models.py:967
msgid "Nutrition"
msgstr "Uzturs"
#: .\cookbook\models.py:968
msgid "Allergen"
msgstr "Alergēns"
#: .\cookbook\models.py:969
msgid "Price"
msgstr "Cena"
#: .\cookbook\models.py:970
msgid "Goal"
msgstr "Mērķis"
#: .\cookbook\models.py:1467 .\cookbook\templates\search_info.html:28
msgid "Simple"
msgstr "Vienkāršs"
#: .\cookbook\models.py:1468 .\cookbook\templates\search_info.html:33
msgid "Phrase"
msgstr "Frāze"
#: .\cookbook\models.py:1469 .\cookbook\templates\search_info.html:38
msgid "Web"
msgstr "Web"
#: .\cookbook\models.py:1470 .\cookbook\templates\search_info.html:47
msgid "Raw"
msgstr "Neapstrādāti dati"
#: .\cookbook\models.py:1525
msgid "Food Alias"
msgstr "Ēdiena Aizstājvārds"
#: .\cookbook\models.py:1526
msgid "Unit Alias"
msgstr "Vienības Aizstājvārds"
#: .\cookbook\models.py:1527
msgid "Keyword Alias"
msgstr "Atslēgvārda Aizstājvārds"
#: .\cookbook\models.py:1528
msgid "Description Replace"
msgstr "Aizvietot Aprakstu"
#: .\cookbook\models.py:1529
msgid "Instruction Replace"
msgstr "Aizvietot Instrukciju"
#: .\cookbook\models.py:1530
msgid "Never Unit"
msgstr "Nekā Vienība"
#: .\cookbook\models.py:1531
msgid "Transpose Words"
msgstr "Samainīt Vārdus"
#: .\cookbook\models.py:1532
msgid "Food Replace"
msgstr "Ēdiena Aizvietošana"
#: .\cookbook\models.py:1533
msgid "Unit Replace"
msgstr "Vienības Aizvietošana"
#: .\cookbook\models.py:1534
msgid "Name Replace"
msgstr "Vārda Aizvietošana"
#: .\cookbook\models.py:1564
msgid "Recipe"
msgstr "Recepte"
#: .\cookbook\models.py:1565
#, fuzzy
#| msgid "Food"
msgid "Food"
msgstr "Ēdiens"
#: .\cookbook\models.py:1566
msgid "Keyword"
msgstr "Atslēgvārds"
#: .\cookbook\serializer.py:262
msgid "File uploads are not enabled for this Space."
msgstr "Failu ielāde šajā vietnē nav ieslēgta."
#: .\cookbook\serializer.py:273
msgid "You have reached your file upload limit."
msgstr "Jūs esat sasniedzis failu ielādes limitu."
#: .\cookbook\serializer.py:281
msgid "The given file type is not allowed."
msgstr ""
#: .\cookbook\serializer.py:421 .\cookbook\views\views.py:94
msgid ""
"You have the reached the maximum amount of spaces that can be owned by you."
msgstr ""
#: .\cookbook\serializer.py:434
msgid "Space Name must be unique."
msgstr ""
#: .\cookbook\serializer.py:469
msgid "Cannot modify Space owner permission."
msgstr "Nevar mainīt vietnes īpašnieka tiesības."
#: .\cookbook\serializer.py:1596
msgid "Hello"
msgstr "Sveiki"
#: .\cookbook\serializer.py:1596
msgid "You have been invited by "
msgstr "Jūs ielūdza "
#: .\cookbook\serializer.py:1598
msgid " to join their Tandoor Recipes space "
msgstr " lai pievienotos viņu Tandoor Recipšu vietnei "
#: .\cookbook\serializer.py:1600
msgid "Click the following link to activate your account: "
msgstr "Lai aktivizētu Jūsu kontu nospiediet sekojošu saiti: "
#: .\cookbook\serializer.py:1602
msgid ""
"If the link does not work use the following code to manually join the space: "
msgstr ""
"Ja saite nedarbojās, izmantojiet sekojošu kodu lai pievienotos vietnei: "
#: .\cookbook\serializer.py:1604
msgid "The invitation is valid until "
msgstr "Ielūgums ir aktīvs līdz "
#: .\cookbook\serializer.py:1606
msgid ""
"Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub "
msgstr ""
"Tandoor Receptes ir Atvērtā Koda recepšu pārvaldnieks. Aplūkojiet to GitHub "
#: .\cookbook\serializer.py:1609
msgid "Tandoor Recipes Invite"
msgstr "Tandoor Recepšu Ielūgums"
#: .\cookbook\serializer.py:1813
msgid "Existing shopping list to update"
msgstr "Esošais iepirkumu saraksts atjaunošanai"
#: .\cookbook\serializer.py:1815
msgid ""
"List of ingredient IDs from the recipe to add, if not provided all "
"ingredients will be added."
msgstr ""
"Pievienojamais receptes sastāvdaļu ID saraksts. Ja nav norādīts, tad tiks "
"pievienotas visas sastāvdaļas."
#: .\cookbook\serializer.py:1817
msgid ""
"Providing a list_recipe ID and servings of 0 will delete that shopping list."
msgstr ""
"Norādot list_recipe ID un porciju lielumu 0, iepirkumu saraksts tiks dzēsts."
#: .\cookbook\serializer.py:1826
msgid "Amount of food to add to the shopping list"
msgstr "Iepirkumu sarakstam pievienojamais ēdiena daudzums"
#: .\cookbook\serializer.py:1828
msgid "ID of unit to use for the shopping list"
msgstr "Vienības ID, ko izmantot iepirkumu sarakstā"
#: .\cookbook\serializer.py:1830
msgid "When set to true will delete all food from active shopping lists."
msgstr ""
"Norādot šo vērtību, viss ēdiens no aktīvā iepirkumu saraksta tiks dzēsts."
#: .\cookbook\templates\404.html:5
msgid "404 Error"
msgstr "Kļūda 404"
#: .\cookbook\templates\404.html:18
msgid "The page you are looking for could not be found."
msgstr "Lapa, kuru jūs meklējat nav atrodama."
#: .\cookbook\templates\404.html:33
msgid "Take me Home"
msgstr "Doties uz Sākumu"
#: .\cookbook\templates\404.html:35
msgid "Report a Bug"
msgstr "Ziņot par kļūdu"
#: .\cookbook\templates\account\email.html:6
#: .\cookbook\templates\account\email.html:10
msgid "E-mail Addresses"
msgstr "E-pasta Adreses"
#: .\cookbook\templates\account\email.html:12
msgid "The following e-mail addresses are associated with your account:"
msgstr "Ar Jūsu kontu ir saistītas sekojošas e-pasta adreses:"
#: .\cookbook\templates\account\email.html:29
msgid "Verified"
msgstr "Pārbaudīts"
#: .\cookbook\templates\account\email.html:31
msgid "Unverified"
msgstr "Nepārbaudīts"
#: .\cookbook\templates\account\email.html:33
msgid "Primary"
msgstr "Galvenais"
#: .\cookbook\templates\account\email.html:40
msgid "Make Primary"
msgstr "Izmantot kā Galveno"
#: .\cookbook\templates\account\email.html:42
msgid "Re-send Verification"
msgstr "Atkārtoti nosūtīt Pārbaudīšanu"
#: .\cookbook\templates\account\email.html:43
#: .\cookbook\templates\socialaccount\connections.html:36
msgid "Remove"
msgstr "Noņemt"
#: .\cookbook\templates\account\email.html:51
msgid "Warning:"
msgstr "Brīdinājums:"
#: .\cookbook\templates\account\email.html:51
msgid ""
"You currently do not have any e-mail address set up. You should really add "
"an e-mail address so you can receive notifications, reset your password, etc."
msgstr ""
"Šobrīd Jums nav uzstādīta neviena e-pasta adrese. Vēlams to pievienot, lai "
"varat saņemt atgādinājumus, atjaunot paroli utt."
#: .\cookbook\templates\account\email.html:57
msgid "Add E-mail Address"
msgstr "Pievienot E-pasta Adresi"
#: .\cookbook\templates\account\email.html:62
msgid "Add E-mail"
msgstr "Pievienot E-pastu"
#: .\cookbook\templates\account\email.html:72
msgid "Do you really want to remove the selected e-mail address?"
msgstr "Vai Jūs tiešām vēlaties dzēst izvēlēto e-pasta adresi?"
#: .\cookbook\templates\account\email_confirm.html:6
#: .\cookbook\templates\account\email_confirm.html:10
msgid "Confirm E-mail Address"
msgstr "Apstiprināt E-pasta Adresi"
#: .\cookbook\templates\account\email_confirm.html:16
#, python-format
msgid ""
"Please confirm that\n"
" %(email)s is an e-mail address "
"for user %(user_display)s\n"
" ."
msgstr ""
"Lūdzu apstipriniet, ka\n"
" %(email)s ir e-pasta adrese "
"lietotājam %(user_display)\n"
" ."
#: .\cookbook\templates\account\email_confirm.html:22
msgid "Confirm"
msgstr "Apstiprināt"
#: .\cookbook\templates\account\email_confirm.html:29
#, python-format
msgid ""
"This e-mail confirmation link expired or is invalid. Please\n"
" issue a new e-mail confirmation "
"request."
msgstr ""
"Šī e-pasta apstiprinājuma termiņš ir beidzies vai arī saite nav korekta. "
"Lūdzu\n"
" izveidojiet jaunu e-pasta "
"apstiprināšanas pieprasījumu."
#: .\cookbook\templates\account\login.html:8
#: .\cookbook\templates\openid\login.html:8
msgid "Login"
msgstr "Pieslēgties"
#: .\cookbook\templates\account\login.html:15
#: .\cookbook\templates\account\login.html:31
#: .\cookbook\templates\account\password_reset.html:39
#: .\cookbook\templates\account\password_reset_done.html:31
#: .\cookbook\templates\account\signup.html:68
#: .\cookbook\templates\account\signup_closed.html:15
#: .\cookbook\templates\openid\login.html:15
#: .\cookbook\templates\openid\login.html:26
#: .\cookbook\templates\socialaccount\authentication_error.html:15
msgid "Sign In"
msgstr "Ielogoties"
#: .\cookbook\templates\account\login.html:34
#: .\cookbook\templates\account\password_reset.html:41
#: .\cookbook\templates\account\password_reset_done.html:33
#: .\cookbook\templates\socialaccount\signup.html:8
#: .\cookbook\templates\socialaccount\signup.html:56
msgid "Sign Up"
msgstr "Reģistrēties"
#: .\cookbook\templates\account\login.html:38
msgid "Lost your password?"
msgstr "Aizmirsusies parole?"
#: .\cookbook\templates\account\login.html:39
#: .\cookbook\templates\account\password_reset.html:29
msgid "Reset My Password"
msgstr "Atjaunot Paroli"
#: .\cookbook\templates\account\login.html:50
msgid "Social Login"
msgstr "Soctīklu Ielogošanās"
#: .\cookbook\templates\account\login.html:51
msgid "You can use any of the following providers to sign in."
msgstr "Jūs varat izmantot jebko no sekojošā lai ielogotos."
#: .\cookbook\templates\account\logout.html:5
#: .\cookbook\templates\account\logout.html:9
#: .\cookbook\templates\account\logout.html:18
msgid "Sign Out"
msgstr "Izlogoties"
#: .\cookbook\templates\account\logout.html:11
msgid "Are you sure you want to sign out?"
msgstr "Vai esat droši, ka vēlaties izlogoties?"
#: .\cookbook\templates\account\password_change.html:6
#: .\cookbook\templates\account\password_change.html:10
#: .\cookbook\templates\account\password_change.html:15
#: .\cookbook\templates\account\password_reset_from_key.html:7
#: .\cookbook\templates\account\password_reset_from_key.html:13
#: .\cookbook\templates\account\password_reset_from_key_done.html:7
#: .\cookbook\templates\account\password_reset_from_key_done.html:13
msgid "Change Password"
msgstr "Mainīt Paroli"
#: .\cookbook\templates\account\password_change.html:16
msgid "Forgot Password?"
msgstr "Aizmirsāt Paroli?"
#: .\cookbook\templates\account\password_reset.html:7
#: .\cookbook\templates\account\password_reset.html:13
#: .\cookbook\templates\account\password_reset_done.html:7
#: .\cookbook\templates\account\password_reset_done.html:18
msgid "Password Reset"
msgstr "Paroles Atjaunošana"
#: .\cookbook\templates\account\password_reset.html:24
msgid ""
"Forgotten your password? Enter your e-mail address below, and we'll send you "
"an e-mail allowing you to reset it."
msgstr ""
"Aizmirsāt paroli? Zemāk ievadiet savu e-pasta adresi, un mēs nosūtīsim "
"informāciju tās atjaunošanai."
#: .\cookbook\templates\account\password_reset.html:32
msgid "Password reset is disabled on this instance."
msgstr "Paroles atjaunošana šajā vietnē ir izslēgta."
#: .\cookbook\templates\account\password_reset_done.html:25
msgid ""
"We have sent you an e-mail. Please contact us if you do not receive it "
"within a few minutes."
msgstr ""
"Mēs nosūtījām Jums e-pastu. Lūdzu sazinieties ar mums, ja to neesat saņēmuši "
"pāris minūšu laikā."
#: .\cookbook\templates\account\password_reset_from_key.html:13
msgid "Bad Token"
msgstr "Nederīgs Apliecinājums"
#: .\cookbook\templates\account\password_reset_from_key.html:25
#, python-format
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used.\n"
" Please request a new "
"password reset."
msgstr ""
"Paroles atjaunošanas saite nav derīga, iespējams tādēļ, ka tā jau ir tikusi "
"izmantota.\n"
" Lūdzu pieprasiet a jaunu paroles atjaunošanu."
#: .\cookbook\templates\account\password_reset_from_key.html:33
msgid "change password"
msgstr "mainīt paroli"
#: .\cookbook\templates\account\password_reset_from_key.html:36
#: .\cookbook\templates\account\password_reset_from_key_done.html:19
msgid "Your password is now changed."
msgstr "Jūsu parole ir nomainīta."
#: .\cookbook\templates\account\password_set.html:6
#: .\cookbook\templates\account\password_set.html:10
#: .\cookbook\templates\account\password_set.html:15
msgid "Set Password"
msgstr "Uzstādīt Paroli"
#: .\cookbook\templates\account\signup.html:5
msgid "Register"
msgstr "Reģistrēties"
#: .\cookbook\templates\account\signup.html:11
msgid "Create an Account"
msgstr "Izveidot Kontu"
#: .\cookbook\templates\account\signup.html:41
msgid "I accept the follwoing"
msgstr "Es apstiprinu sekojošo"
#: .\cookbook\templates\account\signup.html:44
#: .\cookbook\templates\socialaccount\signup.html:35
msgid "Terms and Conditions"
msgstr "Noteikumi un Nosacījumi"
#: .\cookbook\templates\account\signup.html:47
#: .\cookbook\templates\socialaccount\signup.html:38
msgid "and"
msgstr "un"
#: .\cookbook\templates\account\signup.html:51
#: .\cookbook\templates\socialaccount\signup.html:42
msgid "Privacy Policy"
msgstr "Privātuma Politika"
#: .\cookbook\templates\account\signup.html:64
msgid "Create User"
msgstr "Izveidot lietotāju"
#: .\cookbook\templates\account\signup.html:68
msgid "Already have an account?"
msgstr "Vai Jums jau ir konts?"
#: .\cookbook\templates\account\signup_closed.html:5
#: .\cookbook\templates\account\signup_closed.html:11
msgid "Sign Up Closed"
msgstr "Reģistrēšanās slēgta"
#: .\cookbook\templates\account\signup_closed.html:13
msgid "We are sorry, but the sign up is currently closed."
msgstr "Atvainojiet, bet reģistrēšanās šobrīd nav atļauta."
#: .\cookbook\templates\frontend\tandoor.html:15
#, fuzzy
#| msgid "Tandoor Recipes Invite"
msgid "Tandoor Recipe Manager"
msgstr "Tandoor Recepšu Ielūgums"
#: .\cookbook\templates\index.html:28
msgid "Search recipe ..."
msgstr "Meklēt recepti ..."
#: .\cookbook\templates\index.html:43
msgid "New Recipe"
msgstr "Jauna recepte"
#: .\cookbook\templates\index.html:46
msgid "Import Recipe"
msgstr "Importēt recepti"
#: .\cookbook\templates\index.html:52
msgid "Advanced Search"
msgstr "Izvērsta meklēšana"
#: .\cookbook\templates\index.html:56
msgid "Reset Search"
msgstr "Atiestatīt meklēšanu"
#: .\cookbook\templates\index.html:84
msgid "Last viewed"
msgstr "Pēdējoreiz skatīts"
#: .\cookbook\templates\index.html:86
msgid "Recipes"
msgstr "Receptes"
#: .\cookbook\templates\index.html:93
msgid "Log in to view recipes"
msgstr "Lai apskatītu receptes, piesakieties"
#: .\cookbook\templates\markdown_info.html:5
#: .\cookbook\templates\markdown_info.html:13
msgid "Markdown Info"
msgstr "Markdown informācija"
#: .\cookbook\templates\markdown_info.html:14
msgid ""
"\n"
" Markdown is lightweight markup language that can be used to format "
"plain text easily.\n"
" This site uses the Python Markdown library to\n"
" convert your text into nice looking HTML. Its full markdown "
"documentation can be found\n"
" here.\n"
" An incomplete but most likely sufficient documentation can be found "
"below.\n"
" "
msgstr ""
"\n"
" Markdown ir iezīmēšanas valoda, kuru var izmantot, lai viegli "
"formatētu tekstu.\n"
" Šajā vietnē tiek izmantota Python Markdown bibliotēka, lai\n"
" pārveidotu savu tekstu HTML formātā. Tās pilno dokumentāciju var "
"atrast\n"
" šeit.\n"
" Nepilnīga, bet, visticamāk, pietiekama dokumentācija ir atrodama "
"zemāk.\n"
" "
#: .\cookbook\templates\markdown_info.html:25
msgid "Headers"
msgstr "Galvenes"
#: .\cookbook\templates\markdown_info.html:54
msgid "Formatting"
msgstr "Formatēšana"
#: .\cookbook\templates\markdown_info.html:56
#: .\cookbook\templates\markdown_info.html:72
msgid "Line breaks are inserted by adding two spaces after the end of a line"
msgstr ""
"Lai teskts pārietu uz nākamo rindu jāievieto divas atstarpes pēc līnijas "
"beigām"
#: .\cookbook\templates\markdown_info.html:57
#: .\cookbook\templates\markdown_info.html:73
msgid "or by leaving a blank line in between."
msgstr "vai atstājot tukšu rindu starp."
#: .\cookbook\templates\markdown_info.html:59
#: .\cookbook\templates\markdown_info.html:74
msgid "This text is bold"
msgstr "Šis teksts ir treknrakstā"
#: .\cookbook\templates\markdown_info.html:60
#: .\cookbook\templates\markdown_info.html:75
msgid "This text is italic"
msgstr "Šis teksts ir kursīvā"
#: .\cookbook\templates\markdown_info.html:61
#: .\cookbook\templates\markdown_info.html:77
msgid "Blockquotes are also possible"
msgstr "Blokzīmes arī ir pieejamas"
#: .\cookbook\templates\markdown_info.html:84
msgid "Lists"
msgstr "Saraksti"
#: .\cookbook\templates\markdown_info.html:85
msgid ""
"Lists can ordered or unordered. It is important to leave a blank line "
"before the list!"
msgstr ""
"Saraksti var tikt numurēti vai nenumurēti. Ir svarīgi atstāt tukšu rindu "
"pirms saraksta!"
#: .\cookbook\templates\markdown_info.html:87
#: .\cookbook\templates\markdown_info.html:108
msgid "Ordered List"
msgstr "Numurēts saraksts"
#: .\cookbook\templates\markdown_info.html:89
#: .\cookbook\templates\markdown_info.html:90
#: .\cookbook\templates\markdown_info.html:91
#: .\cookbook\templates\markdown_info.html:110
#: .\cookbook\templates\markdown_info.html:111
#: .\cookbook\templates\markdown_info.html:112
msgid "unordered list item"
msgstr "nenumurēta saraksta punkts"
#: .\cookbook\templates\markdown_info.html:93
#: .\cookbook\templates\markdown_info.html:114
msgid "Unordered List"
msgstr "Nenumurēts saraksts"
#: .\cookbook\templates\markdown_info.html:95
#: .\cookbook\templates\markdown_info.html:96
#: .\cookbook\templates\markdown_info.html:97
#: .\cookbook\templates\markdown_info.html:116
#: .\cookbook\templates\markdown_info.html:117
#: .\cookbook\templates\markdown_info.html:118
msgid "ordered list item"
msgstr "numurēta saraksta punkts"
#: .\cookbook\templates\markdown_info.html:125
msgid "Images & Links"
msgstr "Attēli un saites"
#: .\cookbook\templates\markdown_info.html:126
msgid ""
"Links can be formatted with Markdown. This application also allows to paste "
"links directly into markdown fields without any formatting."
msgstr ""
"Saites var formatēt, izmantojot Markdown. Šī lietojumprogramma arī ļauj "
"ielīmēt saites tieši Markdown laukos bez jebkāda formatējuma."
#: .\cookbook\templates\markdown_info.html:132
#: .\cookbook\templates\markdown_info.html:145
msgid "This will become an image"
msgstr "Šis kļūs par attēlu"
#: .\cookbook\templates\markdown_info.html:152
msgid "Tables"
msgstr "Tabulas"
#: .\cookbook\templates\markdown_info.html:153
msgid ""
"Markdown tables are hard to create by hand. It is recommended to use a table "
"editor like this one."
msgstr ""
"Novērtēšanas tabulas ir grūti izveidot. Iesakām izmantot šādu tabulu redaktoru."
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:171
#: .\cookbook\templates\markdown_info.html:177
msgid "Table"
msgstr "Tabula"
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:172
msgid "Header"
msgstr "Galvene"
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:178
msgid "Cell"
msgstr "Šūna"
#: .\cookbook\templates\no_groups_info.html:5
#: .\cookbook\templates\no_groups_info.html:12
msgid "No Permissions"
msgstr "Nav Tiesību"
#: .\cookbook\templates\no_groups_info.html:17
msgid "You do not have any groups and therefor cannot use this application."
msgstr "Jūs neesat nevienā grupā un tādēļ nevarat izmantot šo lietotni."
#: .\cookbook\templates\no_groups_info.html:18
#: .\cookbook\templates\no_perm_info.html:15
msgid "Please contact your administrator."
msgstr "Lūdzu sazinieties ar savu administratoru."
#: .\cookbook\templates\no_perm_info.html:5
#: .\cookbook\templates\no_perm_info.html:12
msgid "No Permission"
msgstr "Nav Tiesību"
#: .\cookbook\templates\no_perm_info.html:15
msgid ""
"You do not have the required permissions to view this page or perform this "
"action."
msgstr ""
"Jums nav nepieciešamo atļauju, lai skatītu šo vietni vai veiktu šo darbību."
#: .\cookbook\templates\offline.html:5
msgid "Offline"
msgstr "Bezsaistē"
#: .\cookbook\templates\openid\login.html:27
#: .\cookbook\templates\socialaccount\authentication_error.html:27
msgid "Back"
msgstr "Atpakaļ"
#: .\cookbook\templates\rest_framework\api.html:5
msgid "Recipe Home"
msgstr "Recepšu Sākums"
#: .\cookbook\templates\rest_framework\api.html:11
msgid "API Documentation"
msgstr "API dokumentācija"
#: .\cookbook\templates\search_info.html:5
#: .\cookbook\templates\search_info.html:9
msgid "Search Settings"
msgstr "Meklēšanas Uzstādījumi"
#: .\cookbook\templates\search_info.html:10
msgid ""
"\n"
" Creating the best search experience is complicated and weighs "
"heavily on your personal configuration. \n"
" Changing any of the search settings can have significant impact on "
"the speed and quality of the results.\n"
" Search Methods, Trigrams and Full Text Search configurations are "
"only available if you are using Postgres for your database.\n"
" "
msgstr ""
"\n"
" Vislabākās meklēšanas pieredzes radīšana ir sarežģīta un ir ļoti "
"atkarīga no Jūsu veiktajiem uzstādījumiem. \n"
" Jebkura meklēšanas uzstādījuma izmaiņas var manāmi ietekmēt "
"meklēšanas ātrumu un kvalitāti.\n"
" Meklēšanas Metodes, Trigram un Pilnā Teksta Meklēšanas uzstādījumi "
"ir pieejami tikai, ja izmantojat Postgres kā savu datubāzi.\n"
" "
#: .\cookbook\templates\search_info.html:19
msgid "Search Methods"
msgstr "Meklēšanas Metodes"
#: .\cookbook\templates\search_info.html:23
msgid ""
" \n"
" Full text searches attempt to normalize the words provided to "
"match common variants. For example: 'forked', 'forking', 'forks' will all "
"normalize to 'fork'.\n"
" There are several methods available, described below, that will "
"control how the search behavior should react when multiple words are "
"searched.\n"
" Full technical details on how these operate can be viewed on Postgresql's website.\n"
" "
msgstr ""
" \n"
" Pilnā teksta meklēšanas mēģinājums vienkāršot dotos vārdus, lai "
"tie sakristu ar tipiskajiem variantiem. Piemēram: 'griezt', 'griezšana', "
"'griezums' tiks vienkāršots uz 'griez'.\n"
" Lai kontrolētu meklētāja darbību ievadot vairākus meklējamos "
"vārdus, ir pieejamas vairākas zemāk aprakstītās metodes.\n"
" Pilno tehnisko informāciju par tām var apskatīt Postgresql mājas lapā.\n"
" "
#: .\cookbook\templates\search_info.html:29
msgid ""
" \n"
" Simple searches ignore punctuation and common words such as "
"'the', 'a', 'and'. And will treat separate words as required.\n"
" Searching for 'apple or flour' will return any recipe that "
"includes both 'apple' and 'flour' anywhere in the fields that have been "
"selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:34
msgid ""
" \n"
" Phrase searches ignore punctuation, but will search for all of "
"the words in the exact order provided.\n"
" Searching for 'apple or flour' will only return a recipe that "
"includes the exact phrase 'apple or flour' in any of the fields that have "
"been selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:39
msgid ""
" \n"
" Web searches simulate functionality found on many web search "
"sites supporting special syntax.\n"
" Placing quotes around several words will convert those words "
"into a phrase.\n"
" 'or' is recognized as searching for the word (or phrase) "
"immediately before 'or' OR the word (or phrase) directly after.\n"
" '-' is recognized as searching for recipes that do not include "
"the word (or phrase) that comes immediately after. \n"
" For example searching for 'apple pie' or cherry -butter will "
"return any recipe that includes the phrase 'apple pie' or the word "
"'cherry' \n"
" in any field included in the full text search but exclude any "
"recipe that has the word 'butter' in any field included.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:48
msgid ""
" \n"
" Raw search is similar to Web except will take puncuation "
"operators such as '|', '&' and '()'\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:59
msgid ""
" \n"
" Another approach to searching that also requires Postgresql is "
"fuzzy search or trigram similarity. A trigram is a group of three "
"consecutive characters.\n"
" For example searching for 'apple' will create x trigrams 'app', "
"'ppl', 'ple' and will create a score of how closely words match the "
"generated trigrams.\n"
" One benefit of searching trigams is that a search for 'sandwich' "
"will find misspelled words such as 'sandwhich' that would be missed by other "
"methods.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:69
#, fuzzy
#| msgid "Search Recipe"
msgid "Search Fields"
msgstr "Meklēt recepti"
#: .\cookbook\templates\search_info.html:73
msgid ""
" \n"
" Unaccent is a special case in that it enables searching a field "
"'unaccented' for each search style attempting to ignore accented values. \n"
" For example when you enable unaccent for 'Name' any search "
"(starts with, contains, trigram) will attempt the search ignoring accented "
"characters.\n"
" \n"
" For the other options, you can enable search on any or all "
"fields and they will be combined together with an assumed 'OR'.\n"
" For example enabling 'Name' for Starts With, 'Name' and "
"'Description' for Partial Match and 'Ingredients' and 'Keywords' for Full "
"Search\n"
" and searching for 'apple' will generate a search that will "
"return recipes that have:\n"
" - A recipe name that starts with 'apple'\n"
" - OR a recipe name that contains 'apple'\n"
" - OR a recipe description that contains 'apple'\n"
" - OR a recipe that will have a full text search match ('apple' "
"or 'apples') in ingredients\n"
" - OR a recipe that will have a full text search match in "
"Keywords\n"
"\n"
" Combining too many fields in too many types of search can have a "
"negative impact on performance, create duplicate results or return "
"unexpected results.\n"
" For example, enabling fuzzy search or partial matches will "
"interfere with web search methods. \n"
" Searching for 'apple -pie' with fuzzy search and full text "
"search will return the recipe Apple Pie. Though it is not included in the "
"full text results, it does match the trigram results.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:95
#, fuzzy
#| msgid "Search"
msgid "Search Index"
msgstr "Meklēt"
#: .\cookbook\templates\search_info.html:99
msgid ""
" \n"
" Trigram search and Full Text Search both rely on database "
"indexes to perform effectively. \n"
" You can rebuild the indexes on all fields in the Admin page for "
"Recipes and selecting all recipes and running 'rebuild index for selected "
"recipes'\n"
" You can also rebuild indexes at the command line by executing "
"the management command 'python manage.py rebuildindex'\n"
" "
msgstr ""
#: .\cookbook\templates\setup.html:6
msgid "Cookbook Setup"
msgstr "Pavārgrāmatu iestatīšana"
#: .\cookbook\templates\setup.html:14
msgid "Setup"
msgstr "Uzstādīt"
#: .\cookbook\templates\setup.html:15
msgid ""
"To start using this application you must first create a superuser account."
msgstr ""
"Lai sāktu izmantot šo lietojumprogrammu, vispirms jāizveido superlietotāja "
"konts."
#: .\cookbook\templates\setup.html:20
msgid "Create Superuser account"
msgstr "Izveidojiet superlietotāja kontu"
#: .\cookbook\templates\socialaccount\authentication_error.html:7
#: .\cookbook\templates\socialaccount\authentication_error.html:23
msgid "Social Network Login Failure"
msgstr ""
#: .\cookbook\templates\socialaccount\authentication_error.html:25
msgid ""
"An error occurred while attempting to login via your social network account."
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:4
#: .\cookbook\templates\socialaccount\connections.html:7
msgid "Account Connections"
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:10
msgid ""
"You can sign in to your account using any of the following third party\n"
" accounts:"
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:44
msgid ""
"You currently have no social network accounts connected to this account."
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:47
msgid "Add a 3rd Party Account"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:5
#: .\cookbook\templates\socialaccount\signup.html:5
msgid "Signup"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:9
#, python-format
msgid "Connect %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:11
#, python-format
msgid "You are about to connect a new third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:13
#, python-format
msgid "Sign In Via %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:15
#, python-format
msgid "You are about to sign in using a third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:20
msgid "Continue"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:10
#, python-format
msgid ""
"You are about to use your\n"
" %(provider_name)s account to login to\n"
" %(site_name)s. As a final step, please complete the following form:"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:32
#, fuzzy
#| msgid "I accept the follwoing"
msgid "I accept the following"
msgstr "Es apstiprinu sekojošo"
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:23
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:31
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:39
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:47
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:55
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:63
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:71
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:79
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:87
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:95
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:103
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:111
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:119
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:127
msgid "Sign in using"
msgstr ""
#: .\cookbook\templates\space_overview.html:6
msgid "Overview"
msgstr "Pārskats"
#: .\cookbook\templates\space_overview.html:13
msgid "Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:17
msgid ""
"Recipes, foods, shopping lists and more are organized in spaces of one or "
"more people."
msgstr ""
#: .\cookbook\templates\space_overview.html:18
msgid ""
"You can either be invited into an existing space or create your own one."
msgstr ""
#: .\cookbook\templates\space_overview.html:25
msgid "Your Spaces"
msgstr "Jūsu Vietnes"
#: .\cookbook\templates\space_overview.html:53
msgid "Owner"
msgstr ""
#: .\cookbook\templates\space_overview.html:73
#: .\cookbook\templates\space_overview.html:83
msgid "Join Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:76
msgid "Join an existing space."
msgstr ""
#: .\cookbook\templates\space_overview.html:78
msgid ""
"To join an existing space either enter your invite token or click on the "
"invite link the space owner send you."
msgstr ""
#: .\cookbook\templates\space_overview.html:91
#: .\cookbook\templates\space_overview.html:100
#, fuzzy
#| msgid "Create User"
msgid "Create Space"
msgstr "Izveidot lietotāju"
#: .\cookbook\templates\space_overview.html:94
msgid "Create your own recipe space."
msgstr ""
#: .\cookbook\templates\space_overview.html:96
msgid "Start your own recipe space and invite other users to it."
msgstr ""
#: .\cookbook\templates\system.html:23
msgid "System"
msgstr "Sistēma"
#: .\cookbook\templates\system.html:24
#, fuzzy
#| msgid ""
#| "\n"
#| " Django Recipes is an open source free software application. It "
#| "can be found on\n"
#| " GitHub.\n"
#| " Changelogs can be found here.\n"
#| " "
msgid ""
"\n"
" Tandoor Recipes is an open source free software application. It can "
"be found on\n"
" GitHub.\n"
" Changelogs can be found here.\n"
" "
msgstr ""
"\n"
" Django Receptes ir atvērtā koda bezmaksas programmatūras "
"lietojumprogramma. To var atrast vietnē\n"
" GitHub.\n"
" Izmaiņu žurnāli ir atrodami šeit.\n"
" "
#: .\cookbook\templates\system.html:30
msgid "System Information"
msgstr "Sistēmas informācija"
#: .\cookbook\templates\system.html:51
msgid ""
"\n"
" You need to execute version.py in your update "
"script to generate version information (done automatically in docker).\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:56
msgid "Plugins"
msgstr ""
#: .\cookbook\templates\system.html:67
msgid "Media Serving"
msgstr "Multivides rādīšana"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:123 .\cookbook\templates\system.html:134
msgid "Warning"
msgstr "Brīdinājums"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:125 .\cookbook\templates\system.html:134
msgid "Ok"
msgstr "Ok"
#: .\cookbook\templates\system.html:70
msgid ""
"Serving media files directly using gunicorn/python is not recommend!\n"
" Please follow the steps described\n"
" here to update\n"
" your installation.\n"
" "
msgstr ""
"Rādīt multivides failus, izmantojot gunicorn / python, nav ieteicams"
"b>!\n"
" Lūdzu, izpildiet aprakstītās darbības\n"
" šeit, lai atjauninātu\n"
" jūsu instalāciju.\n"
" "
#: .\cookbook\templates\system.html:76 .\cookbook\templates\system.html:91
#: .\cookbook\templates\system.html:104 .\cookbook\templates\system.html:115
#: .\cookbook\views\views.py:168
msgid "Everything is fine!"
msgstr "Viss ir kārtībā!"
#: .\cookbook\templates\system.html:80
msgid "Secret Key"
msgstr "Slepenā atslēga"
#: .\cookbook\templates\system.html:84
msgid ""
"\n"
" You do not have a SECRET_KEY configured in your "
".env file. Django defaulted to the\n"
" standard key\n"
" provided with the installation which is publicly know and "
"insecure! Please set\n"
" SECRET_KEY int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" Jūsu failā .env nav konfigurēts SECRET_KEY"
"code>. Django izvēlējās \n"
" noklusējuma atslēgu, \n"
" kas atrodama komplektā ar instalāciju un ir publiski zināma un "
"nedroša! Lūdzu, iestatiet\n"
" SECRET_KEY konfigurācijas failā .env"
"code>.\n"
" "
#: .\cookbook\templates\system.html:94
msgid "Debug Mode"
msgstr "Atkļūdošanas režīms"
#: .\cookbook\templates\system.html:98
msgid ""
"\n"
" This application is still running in debug mode. This is most "
"likely not needed. Turn of debug mode by\n"
" setting\n"
" DEBUG=0 int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" Šī lietojumprogramma joprojām darbojas atkļūdošanas režīmā. Tas, "
"visticamāk, nav vajadzīgs. Atkļūdošanas režīma izslēgšanai\n"
" ir jāiestata\n"
" DEBUG = 0 konfigurācijas failā .env.\n"
" "
#: .\cookbook\templates\system.html:107
msgid "Allowed Hosts"
msgstr ""
#: .\cookbook\templates\system.html:111
msgid ""
"\n"
" Your allowed hosts are configured to allow every host. This "
"might be ok in some setups but should be avoided. Please see the docs about "
"this.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:118
msgid "Database"
msgstr "Datubāze"
#: .\cookbook\templates\system.html:121
msgid "Info"
msgstr "Info"
#: .\cookbook\templates\system.html:131 .\cookbook\templates\system.html:148
#, fuzzy
#| msgid "System Information"
msgid "Migrations"
msgstr "Sistēmas informācija"
#: .\cookbook\templates\system.html:137
msgid ""
"\n"
" Migrations should never fail!\n"
" Failed migrations will likely cause major parts of the app to "
"not function correctly.\n"
" If a migration fails make sure you are on the latest version and "
"if so please post the migration log and the overview below in a GitHub "
"issue.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "False"
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "True"
msgstr ""
#: .\cookbook\templates\system.html:268
msgid "Hide"
msgstr ""
#: .\cookbook\templates\system.html:271
#, fuzzy
#| msgid "Show Links"
msgid "Show"
msgstr "Rādīt saites"
#: .\cookbook\templates\test2.html:6
msgid "Export Recipes"
msgstr "Eksportēt receptes"
#: .\cookbook\templates\test2.html:14 .\cookbook\templates\test2.html:20
msgid "Export"
msgstr "Eksportēt"
#: .\cookbook\views\api.py:198 .\cookbook\views\api.py:326
#, fuzzy
#| msgid "Parameter filter_list incorrectly formatted"
msgid "Parameter updated_at incorrectly formatted"
msgstr "Parametrs filter_list ir nepareizi formatēts"
#: .\cookbook\views\api.py:351 .\cookbook\views\api.py:484
#, python-brace-format
msgid "No {self.basename} with id {pk} exists"
msgstr ""
#: .\cookbook\views\api.py:355
msgid "Cannot merge with the same object!"
msgstr ""
#: .\cookbook\views\api.py:362
#, python-brace-format
msgid "No {self.basename} with id {target} exists"
msgstr ""
#: .\cookbook\views\api.py:367
msgid "Cannot merge with child object!"
msgstr ""
#: .\cookbook\views\api.py:405
#, python-brace-format
msgid "{source.name} was merged successfully with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:411
#, python-brace-format
msgid "An error occurred attempting to merge {source.name} with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:493
#, python-brace-format
msgid "{child.name} was moved successfully to the root."
msgstr ""
#: .\cookbook\views\api.py:496 .\cookbook\views\api.py:514
msgid "An error occurred attempting to move "
msgstr ""
#: .\cookbook\views\api.py:499
msgid "Cannot move an object to itself!"
msgstr ""
#: .\cookbook\views\api.py:505
#, python-brace-format
msgid "No {self.basename} with id {parent} exists"
msgstr ""
#: .\cookbook\views\api.py:511
#, python-brace-format
msgid "{child.name} was moved successfully to parent {parent.name}"
msgstr ""
#: .\cookbook\views\api.py:992
#, python-brace-format
msgid "{obj.name} was removed from the shopping list."
msgstr ""
#: .\cookbook\views\api.py:997 .\cookbook\views\api.py:1627
#, python-brace-format
msgid "{obj.name} was added to the shopping list."
msgstr ""
#: .\cookbook\views\api.py:1239
msgid "Filter meal plans from date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1241
msgid "Filter meal plans to date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1244
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1404
msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1406
msgid "Query string matched (fuzzy) against object name."
msgstr ""
#: .\cookbook\views\api.py:1442
msgid ""
"Query string matched (fuzzy) against recipe name. In the future also "
"fulltext search."
msgstr ""
#: .\cookbook\views\api.py:1444
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr ""
#: .\cookbook\views\api.py:1445
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr ""
#: .\cookbook\views\api.py:1446
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1447
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1448
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1450
msgid "ID of food a recipe should have. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1451
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr ""
#: .\cookbook\views\api.py:1452
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1453
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr ""
#: .\cookbook\views\api.py:1454
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1456
msgid "ID of book a recipe should be in. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1457
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr ""
#: .\cookbook\views\api.py:1458
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1459
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr ""
#: .\cookbook\views\api.py:1460
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1462
msgid "ID of unit a recipe should have."
msgstr ""
#: .\cookbook\views\api.py:1464
msgid "Exact rating of recipe"
msgstr ""
#: .\cookbook\views\api.py:1465
msgid "Rating a recipe should have or greater."
msgstr ""
#: .\cookbook\views\api.py:1466
msgid "Rating a recipe should have or smaller."
msgstr ""
#: .\cookbook\views\api.py:1468
msgid "Filter recipes cooked X times."
msgstr ""
#: .\cookbook\views\api.py:1469
msgid "Filter recipes cooked X times or more."
msgstr ""
#: .\cookbook\views\api.py:1470
msgid "Filter recipes cooked X times or less."
msgstr ""
#: .\cookbook\views\api.py:1472
msgid "Filter recipes created on the given date."
msgstr ""
#: .\cookbook\views\api.py:1473
msgid "Filter recipes created on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1474
msgid "Filter recipes created on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1476 .\cookbook\views\api.py:1477
#: .\cookbook\views\api.py:1478
msgid "Filter recipes updated on the given date."
msgstr ""
#: .\cookbook\views\api.py:1480
msgid "Filter recipes last cooked on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1481
msgid "Filter recipes last cooked on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1483 .\cookbook\views\api.py:1484
msgid "Filter recipes lasts viewed on the given date."
msgstr ""
#: .\cookbook\views\api.py:1486
msgid "Filter recipes for ones created by the given user ID"
msgstr ""
#: .\cookbook\views\api.py:1487
msgid "If only internal recipes should be returned. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1488
msgid "Returns the results in randomized order. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1490
msgid ""
"Determines the order of the results. Options are: score,-score,name,-name,"
"lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-"
"created_at,lastviewed,-lastviewed"
msgstr ""
#: .\cookbook\views\api.py:1492
msgid "Returns new results first in search results. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1493
msgid ""
"Returns the given number of recently viewed recipes before search results "
"(if given)"
msgstr ""
#: .\cookbook\views\api.py:1494
msgid "ID of a custom filter. Returns all recipes matched by that filter."
msgstr ""
#: .\cookbook\views\api.py:1495
msgid "Filter recipes that can be made with OnHand food. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1773
msgid ""
"Return the PropertyTypes matching the property category. Repeat for "
"multiple."
msgstr ""
#: .\cookbook\views\api.py:1804 .\cookbook\views\api.py:1860
msgid "Returns only entries associated with the given mealplan id"
msgstr ""
#: .\cookbook\views\api.py:1858
msgid ""
"Returns only elements updated after the given timestamp in ISO 8601 format."
msgstr ""
#: .\cookbook\views\api.py:2031
msgid ""
"Return the Automations matching the automation type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2048
msgid ""
"Text field to store data that gets carried over to the UserSpace created "
"from the InviteLink"
msgstr ""
#: .\cookbook\views\api.py:2049
msgid "Only return InviteLinks that have not been used yet."
msgstr ""
#: .\cookbook\views\api.py:2076
msgid "Return the CustomFilters matching the model type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2176
msgid "Nothing to do."
msgstr ""
#: .\cookbook\views\api.py:2222
msgid "Invalid Url"
msgstr ""
#: .\cookbook\views\api.py:2228
msgid "Connection Refused."
msgstr ""
#: .\cookbook\views\api.py:2232
msgid "Bad URL Schema."
msgstr ""
#: .\cookbook\views\api.py:2257
#, fuzzy
#| msgid "The requested page could not be found."
msgid "No usable data could be found."
msgstr "Pieprasīto lapu nevarēja atrast."
#: .\cookbook\views\api.py:2286 .\cookbook\views\api.py:2434
msgid "You must select an AI provider to perform your request."
msgstr ""
#: .\cookbook\views\api.py:2293 .\cookbook\views\api.py:2441
msgid ""
"You don't have any credits remaining to use AI or AI features are not "
"enabled for your space."
msgstr ""
#: .\cookbook\views\api.py:2499 .\cookbook\views\api.py:2667
msgid "File is above space limit"
msgstr ""
#: .\cookbook\views\api.py:2522 .\cookbook\views\api.py:2684
msgid "Importing is not implemented for this provider"
msgstr ""
#: .\cookbook\views\api.py:2548
msgid ""
"The PDF Exporter is not enabled on this instance as it is still in an "
"experimental state."
msgstr ""
#: .\cookbook\views\api.py:2842
msgid "This feature is not yet available in the hosted version of tandoor!"
msgstr ""
#: .\cookbook\views\api.py:2863
msgid "Sync successful!"
msgstr "Sinhronizācija ir veiksmīga!"
#: .\cookbook\views\api.py:2866
msgid "Error synchronizing with Storage"
msgstr "Sinhronizējot ar krātuvi, radās kļūda"
#: .\cookbook\views\views.py:89
msgid "This feature is not available in the demo version!"
msgstr ""
#: .\cookbook\views\views.py:110
msgid ""
"You have successfully created your own recipe space. Start by adding some "
"recipes or invite other people to join you."
msgstr ""
#: .\cookbook\views\views.py:171
#, python-format
msgid "PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!"
msgstr ""
#: .\cookbook\views\views.py:174
#, python-format
msgid "You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended"
msgstr ""
#: .\cookbook\views\views.py:178
msgid "Unable to determine PostgreSQL version."
msgstr ""
#: .\cookbook\views\views.py:182
msgid ""
"This application is not running with a Postgres database backend. This is ok "
"but not recommended as some features only work with postgres databases."
msgstr ""
"Šī lietojumprogramma nedarbojas, izmantojot Postgres datubāzi. Tas ir labi, "
"bet nav ieteicams, jo dažas funkcijas darbojas tikai ar Postgres datu bāzēm."
#: .\cookbook\views\views.py:296
#, fuzzy
#| msgid ""
#| "The setup page can only be used to create the first user! If you have "
#| "forgotten your superuser credentials please consult the django "
#| "documentation on how to reset passwords."
msgid ""
"The setup page can only be used to create the first "
"user! If you have forgotten your superuser credentials "
"please consult the django documentation on how to reset passwords."
msgstr ""
"Iestatīšanas lapu var izmantot tikai pirmā lietotāja izveidei! Ja esat "
"aizmirsis sava superlietotāja informāciju, lūdzu, skatiet Django "
"dokumentāciju par paroļu atiestatīšanu."
#: .\cookbook\views\views.py:304
msgid "Passwords dont match!"
msgstr "Paroles nesakrīt!"
#: .\cookbook\views\views.py:312
msgid "User has been created, please login!"
msgstr "Lietotājs ir izveidots, lūdzu, piesakieties!"
#: .\cookbook\views\views.py:352
msgid ""
"Reporting share links is not enabled for this instance. Please notify the "
"page administrator to report problems."
msgstr ""
#: .\cookbook\views\views.py:357
msgid ""
"Recipe sharing link has been disabled! For additional information please "
"contact the page administrator."
msgstr ""
#: .\cookbook\views\views.py:383
msgid "Manage recipes, shopping list, meal plans and more."
msgstr ""
#: .\cookbook\views\views.py:397 .\cookbook\views\views.py:398
msgid "Plan"
msgstr "Plāns"
#: .\cookbook\views\views.py:399
msgid "View your meal Plan"
msgstr ""
#: .\cookbook\views\views.py:418
#, fuzzy
#| msgid "Shopping List"
msgid "View your shopping lists"
msgstr "Iepirkumu saraksts"
#~ msgid ""
#~ "Both fields are optional. If none are given the username will be "
#~ "displayed instead"
#~ msgstr ""
#~ "Abi lauki nav obligāti. Ja neviens nav norādīts, tā vietā tiks parādīts "
#~ "lietotājvārds"
#~ msgid "Name"
#~ msgstr "Vārds"
#~ msgid "Keywords"
#~ msgstr "Atslēgvārdi"
#~ msgid "Preparation time in minutes"
#~ msgstr "Pagatavošanas laiks minūtēs"
#~ msgid "Waiting time (cooking/baking) in minutes"
#~ msgstr "Gaidīšanas laiks (vārīšana / cepšana) minūtēs"
#~ msgid "Path"
#~ msgstr "Ceļš"
#~ msgid "Storage UID"
#~ msgstr "Krātuves UID"
#~ msgid "Add your comment: "
#~ msgstr "Pievienot komentāru: "
#~ msgid "Leave empty for dropbox and enter app password for nextcloud."
#~ msgstr "Atstājiet tukšu Dropbox un ievadiet lietotnes paroli Nextcloud."
#~ msgid "Leave empty for nextcloud and enter api token for dropbox."
#~ msgstr "Atstājiet tukšu Nextcloud un ievadiet API tokenu Dropbox."
#~ msgid ""
#~ "Leave empty for dropbox and enter only base url for nextcloud (/"
#~ "remote.php/webdav/ is added automatically)"
#~ msgstr ""
#~ "Atstājiet tukšu Dropbox un ievadiet tikai Nextcloud bāzes URL ( /"
#~ "remote.php/webdav/ tiek pievienots automātiski)"
#~ msgid ""
#~ "Long Lived Access Token for your HomeAssistant instance"
#~ msgstr ""
#~ "Ilgstoša Piekļuves Pilnvara priekš Jūsu HomeAssistant "
#~ "instances"
#~ msgid "Something like http://homeassistant.local:8123/api"
#~ msgstr "Piemēram http://homeassistant.local:8123/api"
#~ msgid "http://homeassistant.local:8123/api for example"
#~ msgstr "http://homeassistant.local:8123/api piemēram"
#~ msgid "Storage"
#~ msgstr "Krātuve"
#~ msgid "Active"
#~ msgstr "Aktīvs"
#~ msgid "Search String"
#~ msgstr "Meklēšanas virkne"
#~ msgid "File ID"
#~ msgstr "Faila ID"
#~ msgid ""
#~ "Determines how fuzzy a search is if it uses trigram similarity matching "
#~ "(e.g. low values mean more typos are ignored)."
#~ msgstr ""
#~ "Nosaka cik precīza ir meklēšana gadījumā, ja tiek izmantota trigram "
#~ "līdzība (jo zemāka vērtība, jo vairāk rakstīšanas kļūdas tiek ignorētas)."
#~ msgid ""
#~ "Select type method of search. Click here "
#~ "for full description of choices."
#~ msgstr ""
#~ "Izvēlies meklēšanas veidu. Spied šeit, lai "
#~ "apskatītu visas iespējas."
#~ msgid ""
#~ "Use fuzzy matching on units, keywords and ingredients when editing and "
#~ "importing recipes."
#~ msgstr ""
#~ "Izmanto aptuveno meklēšanu vienībām, atslēgas vārdiem un sastāvdaļām "
#~ "importējot un labojot receptes."
#~ msgid ""
#~ "Fields to search ignoring accents. Selecting this option can improve or "
#~ "degrade search quality depending on language"
#~ msgstr ""
#~ "Lauki, kurus meklējot ignorēt akcentus. Šī varianta izvēlēšanās var "
#~ "uzlabot vai pasliktināt meklēšanas kvalitāti atkarībā no valodas"
#~ msgid ""
#~ "Fields to search for partial matches. (e.g. searching for 'Pie' will "
#~ "return 'pie' and 'piece' and 'soapie')"
#~ msgstr ""
#~ "Lauki, kuros meklēt aptuveno līdzību. (piem. meklējot vārdu 'Kūka' tiks "
#~ "atrasts arī 'kūka' un 'ābolkūka')"
#~ msgid ""
#~ "Fields to search for beginning of word matches. (e.g. searching for 'sa' "
#~ "will return 'salad' and 'sandwich')"
#~ msgstr ""
#~ "Lauki, kuros meklēt vārdu līdzības sākumu. (piem meklējot 'la' atradīt "
#~ "'lapas' un 'laims')"
#~ msgid ""
#~ "Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find "
#~ "'recipe'.) Note: this option will conflict with 'web' and 'raw' methods "
#~ "of search."
#~ msgstr ""
#~ "Lauki, kuriem izmantot aptuveno meklēšanu. (piem. meklējot 'recpte' tiks "
#~ "atrasts 'recepte'.) Piezīme: šis variants konfliktēs ar 'web' un 'raw' "
#~ "meklēšanas metodēm."
#~ msgid ""
#~ "Fields to full text search. Note: 'web', 'phrase', and 'raw' search "
#~ "methods only function with fulltext fields."
#~ msgstr ""
#~ "Lauki priekš pilnās teksta meklēšanas. Piezīme: 'web', 'phrase' un 'raw' "
#~ "meklēšanas metodes darbojās tikai ar pilno teksta meklēšanu."
#, fuzzy
#~| msgid "Search"
#~ msgid "Search Method"
#~ msgstr "Meklēt"
#~ msgid "Fuzzy Lookups"
#~ msgstr "Aptuvenā meklēšana"
#~ msgid "Ignore Accent"
#~ msgstr "Ignorēt akcentus"
#~ msgid "Partial Match"
#~ msgstr "Daļēja Pieskaņošana"
#~ msgid "Starts With"
#~ msgstr "Sākās Ar"
#~ msgid "Fuzzy Search"
#~ msgstr "Izplūdusī Meklēšana"
#~ msgid "Full Text"
#~ msgstr "Pilnais Teksts"
#~ msgid " is part of a recipe step and cannot be deleted"
#~ msgstr " ir daļa no receptes soļiem un nevar izdzēst"
#~ msgid "Delete"
#~ msgstr "Izdzēst"
#~ msgid "Settings"
#~ msgstr "Iestatījumi"
#~ msgid "Email"
#~ msgstr "E-pasts"
#~ msgid "Password"
#~ msgstr "Parole"
#~ msgid "Foods"
#~ msgstr "Ēdieni"
#~ msgid "Units"
#~ msgstr "Vienības"
#~ msgid "Supermarket"
#~ msgstr "Veikals"
#~ msgid "Supermarket Category"
#~ msgstr "Veikala kategorija"
#~ msgid "Automations"
#~ msgstr "Automatizācijas"
#~ msgid "Files"
#~ msgstr "Faili"
#~ msgid "Batch Edit"
#~ msgstr "Rediģēt vairākus"
#~ msgid "History"
#~ msgstr "Vēsture"
#~ msgid "Ingredient Editor"
#~ msgstr "Sastāvdaļu Redaktors"
#~ msgid "Properties"
#~ msgstr "Uzstādījumi"
#~ msgid "Unit Conversions"
#~ msgstr "Vienību Pārveidošana"
#~ msgid "Create"
#~ msgstr "Izveidot"
#~ msgid "External Recipes"
#~ msgstr "Ārējās receptes"
#~ msgid "Space Settings"
#~ msgstr "Vietnes Iestatījumi"
#~ msgid "External Connectors"
#~ msgstr "Ārējie Savienojumi"
#~ msgid "Admin"
#~ msgstr "Administrators"
#~ msgid "Markdown Guide"
#~ msgstr "Markdown rokasgrāmata"
#~ msgid "GitHub"
#~ msgstr "Github"
#~ msgid "Translate Tandoor"
#~ msgstr "Tulkot Tandoor"
#~ msgid "API Browser"
#~ msgstr "API pārlūks"
#~ msgid "Log out"
#~ msgstr "Izlogoties"
#~ msgid "You are using the free version of Tandor"
#~ msgstr "Jūs izmantojat bezmaksas Tandoor versiju"
#~ msgid "Upgrade Now"
#~ msgstr "Uzlatojiet Tagad"
#~ msgid "Batch edit Category"
#~ msgstr "Rediģēt vairākas kategorijas uzreiz"
#~ msgid "Batch edit Recipes"
#~ msgstr "Rediģēt vairākas receptes uzreiz"
#~ msgid "Add the specified keywords to all recipes containing a word"
#~ msgstr ""
#~ "Pievienojiet norādītos atslēgvārdus visām receptēm, kurās ir atrodams "
#~ "vārds"
#~ msgid "Sync"
#~ msgstr "Sinhronizēt"
#~ msgid "Manage watched Folders"
#~ msgstr "Pārvaldīt vērotās mapes"
#~ msgid ""
#~ "On this Page you can manage all storage folder locations that should be "
#~ "monitored and synced."
#~ msgstr ""
#~ "Šajā lapā jūs varat pārvaldīt visas krātuves mapju atrašanās vietas, "
#~ "kuras jāuzrauga un jāsinhronizē."
#~ msgid "The path must be in the following format"
#~ msgstr "Ceļam jābūt šādā formātā"
#~ msgid "Save"
#~ msgstr "Saglabāt"
#~ msgid "Manage External Storage"
#~ msgstr "Pārvaldīt Ārējo Uzglabāšanu"
#~ msgid "Sync Now!"
#~ msgstr "Sinhronizēt tagad!"
#~ msgid "Show Recipes"
#~ msgstr "Pārādīt Receptes"
#~ msgid "Show Log"
#~ msgstr "Rādīt Žurnālu"
#~ msgid "Importing Recipes"
#~ msgstr "Recepšu importēšana"
#~ msgid ""
#~ "This can take a few minutes, depending on the number of recipes in sync, "
#~ "please wait."
#~ msgstr ""
#~ "Tas var aizņemt dažas minūtes, atkarībā no sinhronizēto recepšu skaita, "
#~ "lūdzu, uzgaidiet."
#~ msgid "Recipe Books"
#~ msgstr "Recepšu grāmatas"
#~ msgid "Import new Recipe"
#~ msgstr "Importēt jaunu recepti"
#~ msgid "Edit Recipe"
#~ msgstr "Rediģēt recepti"
#, python-format
#~ msgid "Are you sure you want to delete the %(title)s: %(object)s "
#~ msgstr "Vai tiešām vēlaties izdzēst %(title)s: %(object)s "
#~ msgid "This cannot be undone!"
#~ msgstr "Šo darbību nevar atsaukt!"
#~ msgid "Protected"
#~ msgstr "Aizsargāts"
#~ msgid "Cascade"
#~ msgstr "Kaskāde"
#~ msgid "Cancel"
#~ msgstr "Atcelt"
#~ msgid "Edit"
#~ msgstr "Rediģēt"
#~ msgid "View"
#~ msgstr "Skatīt"
#~ msgid "Delete original file"
#~ msgstr "Dzēst sākotnējo failu"
#~ msgid "List"
#~ msgstr "Saraksts"
#~ msgid "Filter"
#~ msgstr "Filtrs"
#~ msgid "Import all"
#~ msgstr "Importēt visu"
#~ msgid "New"
#~ msgstr "Jauns"
#~ msgid "previous"
#~ msgstr "iepriekšējais"
#~ msgid "next"
#~ msgstr "nākamais"
#~ msgid "View Log"
#~ msgstr "Skatīt žurnālu"
#~ msgid "Cook Log"
#~ msgstr "Pagatavošanas žurnāls"
#~ msgid "Import"
#~ msgstr "Importēt"
#~ msgid "Security Warning"
#~ msgstr "Drošības brīdinājums"
#~ msgid ""
#~ "\n"
#~ " The Password and Token field are stored as plain text"
#~ "b> inside the database.\n"
#~ " This is necessary because they are needed to make API requests, "
#~ "but it also increases the risk of\n"
#~ " someone stealing it.
\n"
#~ " To limit the possible damage tokens or accounts with limited "
#~ "access can be used.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Lauki Parole un Token datu bāzē tiek glabāti kā "
#~ "vienkārši teksti.\n"
#~ " Tas ir nepieciešams, jo tie ir nepieciešami, lai veiktu API "
#~ "pieprasījumus, taču tas arī palielina risku,\n"
#~ " ka kāds tos nozog.
\n"
#~ " Lai ierobežotu iespējamos bojājumus, varat izmantot tokenus vai "
#~ "kontus ar ierobežotu piekļuvi.\n"
#~ " "
#~ msgid "You are currently offline!"
#~ msgstr "Jūs šobrīd esat bezsaistē!"
#~ msgid ""
#~ "The recipes listed below are available for offline viewing because you "
#~ "have recently viewed them. Keep in mind that data might be outdated."
#~ msgstr ""
#~ "Zemāk redzamās receptes ir pieejamas bezsaistē, jo Jūs tās nesen esat "
#~ "apskatījuši. Atcerieties, ka šī informācija var būt novecojusi."
#~ msgid "Property Editor"
#~ msgstr "Uzstādījumu Redaktors"
#~ msgid "Comments"
#~ msgstr "Komentāri"
#~ msgid "by"
#~ msgstr "pēc"
#~ msgid "Comment"
#~ msgstr "Komentēt"
#, fuzzy
#~| msgid "Create User"
#~ msgid "Leave Space"
#~ msgstr "Izveidot lietotāju"
#~ msgid "URL Import"
#~ msgstr "URL importēšana"
#, python-format
#~ msgid "Batch edit done. %(count)d recipe was updated."
#~ msgid_plural "Batch edit done. %(count)d Recipes where updated."
#~ msgstr[0] ""
#~ "Partijas rediģēšana pabeigta. %(count)d recepte tika atjaunināta."
#~ msgstr[1] ""
#~ "Partijas rediģēšana pabeigta. %(count)d receptes tika atjauninātas."
#~ msgstr[2] ""
#~ "Partijas rediģēšana pabeigta. %(count)d receptes tika atjauninātas."
#~ msgid "Monitor"
#~ msgstr "Uzraudzīt"
#~ msgid "Storage Backend"
#~ msgstr "Krātuves aizmugursistēma"
#~ msgid ""
#~ "Could not delete this storage backend as it is used in at least one "
#~ "monitor."
#~ msgstr ""
#~ "Nevarēja izdzēst šo krātuves aizmugursistēmu, jo tā tiek izmantota vismaz "
#~ "vienā uzraugā."
#, fuzzy
#~| msgid "Storage Backend"
#~ msgid "Connectors Config Backend"
#~ msgstr "Krātuves aizmugursistēma"
#~ msgid "Invite Link"
#~ msgstr "Uzaicinājuma saite"
#~ msgid "You cannot edit this storage!"
#~ msgstr "Jūs nevarat rediģēt šo krātuvi!"
#~ msgid "Storage saved!"
#~ msgstr "Krātuve saglabāta!"
#~ msgid "There was an error updating this storage backend!"
#~ msgstr "Atjauninot šo krātuves aizmugursistēmu, radās kļūda!"
#, fuzzy
#~| msgid "Changes saved!"
#~ msgid "Config saved!"
#~ msgstr "Izmaiņas saglabātas!"
#~ msgid "Changes saved!"
#~ msgstr "Izmaiņas saglabātas!"
#~ msgid "Error saving changes!"
#~ msgstr "Saglabājot izmaiņas, radās kļūda!"
#~ msgid "Import Log"
#~ msgstr "Importēšanas žurnāls"
#~ msgid "Discovery"
#~ msgstr "Atklāšana"
#~ msgid "Shopping List"
#~ msgstr "Iepirkumu saraksts"
#, fuzzy
#~| msgid "Storage Backend"
#~ msgid "Connector Config Backend"
#~ msgstr "Krātuves aizmugursistēma"
#~ msgid "Invite Links"
#~ msgstr "Uzaicinājuma saites"
#, fuzzy
#~| msgid "Shopping Recipes"
#~ msgid "Shopping Categories"
#~ msgstr "Iepirkšanās receptes"
#, fuzzy
#~| msgid "Filter"
#~ msgid "Custom Filters"
#~ msgstr "Filtrs"
#~ msgid "Steps"
#~ msgstr "Soļi"
#~ msgid "Imported new recipe!"
#~ msgstr "Importēta jauna recepte!"
#~ msgid "There was an error importing this recipe!"
#~ msgstr "Importējot šo recepti, radās kļūda!"
#~ msgid "You do not have the required permissions to perform this action!"
#~ msgstr "Jums nav nepieciešamo atļauju, lai veiktu šo darbību!"
#~ msgid "Comment saved!"
#~ msgstr "Komentārs saglabāts!"
#~ msgid "Malformed Invite Link supplied!"
#~ msgstr "Nepareiza uzaicinājuma saite!"
#~ msgid "Invite Link not valid or already used!"
#~ msgstr "Uzaicinājuma saite nav derīga vai jau izmantota!"
#, fuzzy
#~| msgid "Ingredients"
#~ msgid "Ingredient decimal places"
#~ msgstr "Sastāvdaļas"
#, fuzzy
#~| msgid "Shopping list currently empty"
#~ msgid "Shopping list auto sync period"
#~ msgstr "Iepirkumu saraksts pašlaik ir tukšs"
#~ msgid ""
#~ "Color of the top navigation bar. Not all colors work with all themes, "
#~ "just try them out!"
#~ msgstr ""
#~ "Augšējās navigācijas joslas krāsa. Ne visas krāsas darbojas ar visām "
#~ "tēmām, vienkārši izmēģiniet tās!"
#~ msgid ""
#~ "Default Unit to be used when inserting a new ingredient into a recipe."
#~ msgstr ""
#~ "Noklusējuma vienība, ko izmantot, ievietojot receptē jaunu sastāvdaļu."
#~ msgid ""
#~ "Enables support for fractions in ingredient amounts (e.g. convert "
#~ "decimals to fractions automatically)"
#~ msgstr ""
#~ "Iespējot daļskaitļus sastāvdaļu daudzumos (piemēram, decimāldaļas "
#~ "automātiski pārveidot par daļskaitļiem)"
#, fuzzy
#~| msgid ""
#~| "Users with whom newly created meal plan/shopping list entries should be "
#~| "shared by default."
#~ msgid ""
#~ "Users with whom newly created meal plans should be shared by default."
#~ msgstr ""
#~ "Lietotāji, ar kuriem jaunizveidotie maltīšu saraksti/iepirkumu saraksti "
#~ "tiks kopīgoti pēc noklusējuma."
#, fuzzy
#~| msgid "Open Shopping List"
#~ msgid "Users with whom to share shopping lists."
#~ msgstr "Atvērt iepirkumu sarakstu"
#~ msgid "Number of decimals to round ingredients."
#~ msgstr "Ciparu skaits pēc komata decimāldaļām sastāvdaļās."
#~ msgid ""
#~ "If you want to be able to create and see comments underneath recipes."
#~ msgstr ""
#~ "Ja vēlaties, lai jūs varētu izveidot un redzēt komentārus zem receptēm."
#~ msgid ""
#~ "Setting to 0 will disable auto sync. When viewing a shopping list the "
#~ "list is updated every set seconds to sync changes someone else might have "
#~ "made. Useful when shopping with multiple people but might use a little "
#~ "bit of mobile data. If lower than instance limit it is reset when saving."
#~ msgstr ""
#~ "Iestatot 0, tiks atspējota automātiskā sinhronizācija. Apskatot iepirkumu "
#~ "sarakstu, saraksts tiek atjaunināts ik pēc noteiktām sekundēm, lai "
#~ "sinhronizētu citas personas veiktas izmaiņas. Noderīgi, iepērkoties ar "
#~ "vairākiem cilvēkiem, taču, iespējams, izmantos nedaudz vairāk mobilo "
#~ "datu. Ja tas ir zemāks par instances ierobežojumu, tas tiek atiestatīts, "
#~ "saglabājot."
#~ msgid "You must provide at least a recipe or a title."
#~ msgstr "Jums jānorāda vismaz recepte vai nosaukums."
#~ msgid "You can list default users to share recipes with in the settings."
#~ msgstr ""
#~ "Iestatījumos varat uzskaitīt noklusējuma lietotājus, ar kuriem koplietot "
#~ "receptes."
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "Lai formatētu šo lauku, varat izmantot Markdown. Skatiet dokumentus šeit "
#, fuzzy
#~| msgid "Shopping List"
#~ msgid "Share Shopping List"
#~ msgstr "Iepirkumu saraksts"
#~ msgid "List Prefix"
#~ msgstr "Saraksta prefikss"
#, fuzzy
#~| msgid "Food that should be replaced."
#~ msgid "Fields on food that should be inherited by default."
#~ msgstr "Ēdiens, kas būtu jāaizstāj."
#, fuzzy
#~| msgid "Show recently viewed recipes on search page."
#~ msgid "Show recipe counts on search filters"
#~ msgstr "Parādīt nesen skatītās receptes meklēšanas lapā."
#~ msgid "Recipe Book"
#~ msgstr "Recepšu grāmata"
#~ msgid "Bookmarks"
#~ msgstr "Grāmatzīmes"
#~ msgid "Ingredients"
#~ msgstr "Sastāvdaļas"
#, fuzzy
#~| msgid "Shopping Recipes"
#~ msgid "Show recent recipes"
#~ msgstr "Iepirkšanās receptes"
#, fuzzy
#~| msgid "Search"
#~ msgid "Search style"
#~ msgstr "Meklēt"
#~ msgid "Show recently viewed recipes on search page."
#~ msgstr "Parādīt nesen skatītās receptes meklēšanas lapā."
#~ msgid "Small"
#~ msgstr "Mazs"
#~ msgid "Large"
#~ msgstr "Liels"
#~ msgid "Edit Ingredients"
#~ msgstr "Rediģēt sastāvdaļas"
#~ msgid ""
#~ "\n"
#~ " The following form can be used if, accidentally, two (or more) "
#~ "units or ingredients where created that should be\n"
#~ " the same.\n"
#~ " It merges two units or ingredients and updates all recipes using "
#~ "them.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Šādu veidlapu var izmantot, ja nejauši izveidotas divas (vai "
#~ "vairāk) vienības vai sastāvdaļas, kam vajadzētu būt\n"
#~ " vienādām.\n"
#~ " Tas apvieno divas vienības vai sastāvdaļas un atjaunina visas "
#~ "receptes, kas izmanto tās.\n"
#~ " "
#~ msgid "Are you sure that you want to merge these two units?"
#~ msgstr "Vai tiešām vēlaties apvienot šīs divas vienības?"
#~ msgid "Are you sure that you want to merge these two ingredients?"
#~ msgstr "Vai tiešām vēlaties apvienot šīs divas sastāvdaļas?"
#~ msgid "Import Recipes"
#~ msgstr "Importēt receptes"
#~ msgid "Close"
#~ msgstr "Aizvērt"
#~ msgid "Open Recipe"
#~ msgstr "Atvērt recepti"
#~ msgid "Meal Plan View"
#~ msgstr "Maltītes plāna skats"
#~ msgid "Created by"
#~ msgstr "Izveidojis"
#~ msgid "Shared with"
#~ msgstr "Kopīgots ar"
#~ msgid "Never cooked before."
#~ msgstr "Nekad nav gatavojis."
#~ msgid "Other meals on this day"
#~ msgstr "Citas maltītes šajā dienā"
#~ msgid "Recipe Image"
#~ msgstr "Receptes attēls"
#~ msgid "Preparation time ca."
#~ msgstr "Pagatavošanas laiks apm."
#~ msgid "Waiting time ca."
#~ msgstr "Gaidīšanas laiks apm."
#~ msgid "External"
#~ msgstr "Ārējs"
#~ msgid "Log Cooking"
#~ msgstr "Veikt ierakstus pagatavošanas žurnālā"
#~ msgid "Account"
#~ msgstr "Konts"
#, fuzzy
#~| msgid "Settings"
#~ msgid "API-Settings"
#~ msgstr "Iestatījumi"
#, fuzzy
#~| msgid "Search String"
#~ msgid "Search-Settings"
#~ msgstr "Meklēšanas virkne"
#, fuzzy
#~| msgid "Search String"
#~ msgid "Shopping-Settings"
#~ msgstr "Meklēšanas virkne"
#, fuzzy
#~| msgid "Settings"
#~ msgid "Name Settings"
#~ msgstr "Iestatījumi"
#, fuzzy
#~| msgid "Settings"
#~ msgid "Account Settings"
#~ msgstr "Iestatījumi"
#, fuzzy
#~| msgid "Settings"
#~ msgid "Emails"
#~ msgstr "Iestatījumi"
#~ msgid "Language"
#~ msgstr "Valoda"
#~ msgid "Style"
#~ msgstr "Stils"
#~ msgid "API Token"
#~ msgstr "API Tokens"
#~ msgid ""
#~ "You can use both basic authentication and token based authentication to "
#~ "access the REST API."
#~ msgstr ""
#~ "Lai piekļūtu REST API, varat izmantot gan pamata autentifikāciju, gan "
#~ "tokena autentifikāciju."
#~ msgid ""
#~ "Use the token as an Authorization header prefixed by the word token as "
#~ "shown in the following examples:"
#~ msgstr ""
#~ "Izmantojiet token, kā Authorization header, kas pievienota vārdam token, "
#~ "kā parādīts šajos piemēros:"
#~ msgid "or"
#~ msgstr "vai"
#, fuzzy
#~| msgid "Shopping List"
#~ msgid "Shopping Settings"
#~ msgstr "Iepirkumu saraksts"
#~ msgid "Stats"
#~ msgstr "Statistika"
#~ msgid "Statistics"
#~ msgstr "Statistika"
#~ msgid "Number of objects"
#~ msgstr "Objektu skaits"
#~ msgid "Recipe Imports"
#~ msgstr "Recepšu imports"
#~ msgid "Objects stats"
#~ msgstr "Objektu statistika"
#~ msgid "Recipes without Keywords"
#~ msgstr "Receptes bez atslēgas vārdiem"
#~ msgid "Internal Recipes"
#~ msgstr "Iekšējās receptes"
#~ msgid "Show Links"
#~ msgstr "Rādīt saites"
#, fuzzy
#~| msgid "Invite Links"
#~ msgid "Invite User"
#~ msgstr "Uzaicinājuma saites"
#, fuzzy
#~| msgid "Admin"
#~ msgid "admin"
#~ msgstr "Administrators"
#, fuzzy
#~| msgid "You cannot edit this storage!"
#~ msgid "You cannot edit yourself."
#~ msgstr "Jūs nevarat rediģēt šo krātuvi!"
#, fuzzy
#~| msgid "There are no recipes in this book yet."
#~ msgid "There are no members in your space yet!"
#~ msgstr "Šajā grāmatā vēl nav receptes."
#, fuzzy
#~| msgid "You are not logged in and therefore cannot view this page!"
#~ msgid ""
#~ "You are already member of a space and therefore cannot join this one."
#~ msgstr "Jūs neesat pieteicies un tāpēc nevarat skatīt šo lapu!"
#, fuzzy
#~| msgid "Open Shopping List"
#~ msgid "Try the new shopping list"
#~ msgstr "Atvērt iepirkumu sarakstu"
#~ msgid "Search Recipe"
#~ msgstr "Meklēt recepti"
#~ msgid "Shopping Recipes"
#~ msgstr "Iepirkšanās receptes"
#~ msgid "No recipes selected"
#~ msgstr "Nav izvēlēta neviena recepte"
#~ msgid "Amount"
#~ msgstr "Summa"
#~ msgid "Select Unit"
#~ msgstr "Atlasiet vienību"
#~ msgid "Select"
#~ msgstr "Atlasīt"
#~ msgid "Select Food"
#~ msgstr "Atlasīt ēdienu"
#~ msgid "Select User"
#~ msgstr "Atlasīt lietotāju"
#~ msgid "Finished"
#~ msgstr "Pabeigts"
#, fuzzy
#~| msgid "You are offline, shopping list might not syncronize."
#~ msgid "You are offline, shopping list might not synchronize."
#~ msgstr "Jūs esat bezsaistē. Iepirkumu saraksts netiek sinhronizēts."
#~ msgid "Copy/Export"
#~ msgstr "Kopēt/eksportēt"
#, fuzzy
#~| msgid "Bookmark saved!"
#~ msgid "Bookmark Me!"
#~ msgstr "Grāmatzīme saglabāta!"
#~ msgid "Text"
#~ msgstr "Teskts"
#, fuzzy
#~| msgid "File ID"
#~ msgid "File"
#~ msgstr "Faila ID"
#~ msgid "Enter website URL"
#~ msgstr "Ievadiet vietnes URL"
#, fuzzy
#~| msgid "View Recipe"
#~ msgid "Preview Recipe Data"
#~ msgstr "Skatīt recepti"
#, fuzzy
#~| msgid "Preparation Time"
#~ msgid "Prep Time"
#~ msgstr "Pagatavošanas laiks"
#, fuzzy
#~| msgid "Time"
#~ msgid "Cook Time"
#~ msgstr "Laiks"
#, fuzzy
#~| msgid "Discovered Recipes"
#~ msgid "Discovered Attributes"
#~ msgstr "Atrastās receptes"
#, fuzzy
#~| msgid "Show as header"
#~ msgid "Show Blank Field"
#~ msgstr "Rādīt kā galveni"
#, fuzzy
#~| msgid "Delete Step"
#~ msgid "Delete Text"
#~ msgstr "Dzēst soli"
#, fuzzy
#~| msgid "Delete Recipe"
#~ msgid "Delete image"
#~ msgstr "Dzēst recepti"
#~ msgid "Recipe Name"
#~ msgstr "Receptes nosaukums"
#, fuzzy
#~| msgid "Recipe Markup Specification"
#~ msgid "Recipe Description"
#~ msgstr "Recepšu Markup specifikācija"
#~ msgid "Select one"
#~ msgstr "Izvēlies vienu"
#~ msgid "Note"
#~ msgstr "Piezīme"
#, fuzzy
#~| msgid "All Keywords"
#~ msgid "Add Keyword"
#~ msgstr "Visi atslēgvārdi"
#~ msgid "All Keywords"
#~ msgstr "Visi atslēgvārdi"
#~ msgid "Import all keywords, not only the ones already existing."
#~ msgstr "Importējiet visus atslēgvārdus, ne tikai jau esošos."
#~ msgid "Information"
#~ msgstr "Informācija"
#~ msgid ""
#~ " Only websites containing ld+json or microdata information can currently\n"
#~ " be imported. Most big recipe pages "
#~ "support this. If you site cannot be imported but\n"
#~ " you think\n"
#~ " it probably has some kind of "
#~ "structured data feel free to post an example in the\n"
#~ " github issues."
#~ msgstr ""
#~ " Pašlaik tikai vietnes, kurās tiek izmantots ld+json vai microdata ir "
#~ "iespējams\n"
#~ " importēt. Lielākā daļa lielo recepšu "
#~ "lapu to atbalsta. Ja jūsu vietni nevar importēt, bet\n"
#~ " tev šķiet,\n"
#~ " ka tai ir sava veida strukturēti "
#~ "dati, nekautrējieties ievietot piemēru\n"
#~ " Github."
#~ msgid "Google ld+json Info"
#~ msgstr "Google ld+json informācija"
#~ msgid "GitHub Issues"
#~ msgstr "GitHub Issues"
#~ msgid "Recipe Markup Specification"
#~ msgstr "Recepšu Markup specifikācija"
#~ msgid "The requested site provided malformed data and cannot be read."
#~ msgstr "Pieprasītā vietne sniedza nepareizus datus, kurus nevar nolasīt."
#~ msgid "The requested page could not be found."
#~ msgstr "Pieprasīto lapu nevarēja atrast."
#~ msgid ""
#~ "The requested site does not provide any recognized data format to import "
#~ "the recipe from."
#~ msgstr ""
#~ "Pieprasītajā vietnē nav norādīts atzīts datu formāts, no kura varētu "
#~ "importēt recepti."
#~ msgid "Shopping Lists"
#~ msgstr "Iepirkšanās saraksti"
#~ msgid "Time"
#~ msgstr "Laiks"
#~ msgid "Log Recipe Cooking"
#~ msgstr "Saglabāt recepšu pagatavošanu žurnālā"
#~ msgid "All fields are optional and can be left empty."
#~ msgstr "Visi lauki nav obligāti, un tos var atstāt tukšus."
#~ msgid "Rating"
#~ msgstr "Vērtējums"
#~ msgid "New unit that other gets replaced by."
#~ msgstr "Jauna vienība, ar kuru cits tiek aizstāts."
#~ msgid "Old Unit"
#~ msgstr "Vecā vienība"
#~ msgid "Unit that should be replaced."
#~ msgstr "Vienība, kas jāaizstāj."
#~ msgid "New Food"
#~ msgstr "Jauns ēdiens"
#~ msgid "New food that other gets replaced by."
#~ msgstr "Jauns ēdiens, ar kuru citi tiek aizstāti."
#~ msgid "Old Food"
#~ msgstr "Vecais ēdiens"
#~ msgid "New Entry"
#~ msgstr "Jauns ieraksts"
#~ msgid "Title"
#~ msgstr "Virsraksts"
#~ msgid "Note (optional)"
#~ msgstr "Piezīme (neobligāti)"
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "Lai formatētu šo lauku, varat izmantot Markdown. Skatiet dokumentāciju "
#~ "šeit"
#~ msgid "Create only note"
#~ msgstr "Izveidot tikai piezīmi"
#~ msgid "Number of Days"
#~ msgstr "Dienu skaits"
#~ msgid "Weekday offset"
#~ msgstr "Nedēļas dienas nobīde"
#~ msgid ""
#~ "Number of days starting from the first day of the week to offset the "
#~ "default view."
#~ msgstr ""
#~ "Dienu skaits, sākot no nedēļas pirmās dienas, lai nobīdītu noklusējuma "
#~ "skatu."
#~ msgid "Edit plan types"
#~ msgstr "Rediģēt plānu veidus"
#~ msgid "Show help"
#~ msgstr "Parādīt palīdzību"
#~ msgid "Week iCal export"
#~ msgstr "Nedēļas iCal eksports"
#~ msgid "Add to Shopping"
#~ msgstr "Pievienot iepirkumiem"
#~ msgid "New meal type"
#~ msgstr "Jauns maltītes veids"
#~ msgid "Meal Plan Help"
#~ msgstr "Ēdienreižu plāna palīdzība"
#~ msgid "Units merged!"
#~ msgstr "Vienības ir apvienotas!"
#~ msgid "Foods merged!"
#~ msgstr "Ēdieni apvienoti!"
#~ msgid "Utensils"
#~ msgstr "Piederumi"
#~ msgid "Storage Data"
#~ msgstr "Krātuves dati"
#~ msgid "Storage Backends"
#~ msgstr "Krātuves backendi"
#~ msgid "Configure Sync"
#~ msgstr "Konfigurēt sinhronizāciju"
#~ msgid "Discovered Recipes"
#~ msgstr "Atrastās receptes"
#~ msgid "Discovery Log"
#~ msgstr "Atrastās žurnāls"
#~ msgid "Units & Ingredients"
#~ msgstr "Vienības un sastāvdaļas"
#~ msgid "New Book"
#~ msgstr "Jauna grāmata"
#~ msgid "Toggle Recipes"
#~ msgstr "Pārslēgt receptes"
#~ msgid "There are no recipes in this book yet."
#~ msgstr "Šajā grāmatā vēl nav receptes."
#~ msgid "Waiting Time"
#~ msgstr "Gaidīšanas laiks"
#~ msgid "Select Keywords"
#~ msgstr "Atlasīt atslēgvārdus"
#~ msgid "Delete Step"
#~ msgstr "Dzēst soli"
#~ msgid "Step"
#~ msgstr "Solis"
#~ msgid "Show as header"
#~ msgstr "Rādīt kā galveni"
#~ msgid "Hide as header"
#~ msgstr "Slēpt kā galveni"
#~ msgid "Move Up"
#~ msgstr "Pārvietot uz augšu"
#~ msgid "Move Down"
#~ msgstr "Pārvietot uz leju"
#~ msgid "Step Name"
#~ msgstr "Soļa nosaukums"
#~ msgid "Step Type"
#~ msgstr "Soļa tips"
#~ msgid "Step time in Minutes"
#~ msgstr "Soļa laiks minūtēs"
#, fuzzy
#~| msgid "Select one"
#~ msgid "Select File"
#~ msgstr "Izvēlies vienu"
#, fuzzy
#~| msgid "Delete Recipe"
#~ msgid "Select Recipe"
#~ msgstr "Dzēst recepti"
#~ msgid "Delete Ingredient"
#~ msgstr "Dzēst sastāvdaļu"
#~ msgid "Make Header"
#~ msgstr "Izveidot galveni"
#~ msgid "Make Ingredient"
#~ msgstr "Pagatavot sastāvdaļu"
#~ msgid "Disable Amount"
#~ msgstr "Atspējot summu"
#~ msgid "Enable Amount"
#~ msgstr "Iespējot summu"
#~ msgid "Save & View"
#~ msgstr "Saglabāt un skatīt"
#~ msgid "Add Step"
#~ msgstr "Pievienot soli"
#~ msgid "Add Nutrition"
#~ msgstr "Pievienot uzturu"
#~ msgid "Remove Nutrition"
#~ msgstr "Noņemt uzturu"
#~ msgid "View Recipe"
#~ msgstr "Skatīt recepti"
#~ msgid "Delete Recipe"
#~ msgstr "Dzēst recepti"
#~ msgid ""
#~ "A username is not required, if left blank the new user can choose one."
#~ msgstr ""
#~ "Lietotājvārds nav nepieciešams. Ja tas tiks atstāts tukšs, lietotājs to "
#~ "varēs izvēlēties pats."
#~ msgid "Link"
#~ msgstr "Saite"
#~ msgid "Logout"
#~ msgstr "Izlogoties"
#~ msgid "Website Import"
#~ msgstr "Vietnes importēšana"
#~ msgid "There was an error creating a resource!"
#~ msgstr "Radot resursu, radās kļūda!"
#~ msgid ""
#~ "The requested page refused to provide any information (Status Code 403)."
#~ msgstr ""
#~ "Pieprasītā lapa atteicās sniegt jebkādu informāciju (statusa kods 403)."
#~ msgid ""
#~ "Include - [ ] in list for easier usage in markdown based "
#~ "documents."
#~ msgstr ""
#~ "Iekļaujiet - [] sarakstā, lai atvieglotu lietošanu "
#~ "dokumentos, kuru pamatā ir marķējums (markdown)."
#~ msgid "Backup & Restore"
#~ msgstr "Dublēšana un atjaunošana"
#~ msgid "Download Backup"
#~ msgstr "Lejupielādējiet dublējumu"
#~ msgid "Preference for given user already exists"
#~ msgstr "Priekšroka konkrētam lietotājam jau pastāv"
================================================
FILE: cookbook/locale/nb_NO/LC_MESSAGES/django.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-22 20:15+0200\n"
"PO-Revision-Date: 2026-02-11 08:39+0000\n"
"Last-Translator: Sigurd Fyllingsnes \n"
"Language-Team: Norwegian Bokmål \n"
"Language: nb_NO\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.13.3\n"
#: .\cookbook\forms.py:50
msgid "Default"
msgstr "Forvalg"
#: .\cookbook\forms.py:77
msgid ""
"To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything."
msgstr ""
"For å unngå duplikater, blir oppskrifter med samme navn som eksisterende "
"ignorert. Merk av denne boksen for å importere alt."
#: .\cookbook\forms.py:108
msgid "Maximum number of users for this space reached."
msgstr "Maksimalt antall brukere for dette området er nådd."
#: .\cookbook\forms.py:114
msgid "Email address already taken!"
msgstr "E‑postadressen er allerede i bruk!"
#: .\cookbook\forms.py:121
msgid ""
"An email address is not required but if present the invite link will be sent "
"to the user."
msgstr ""
"En e‑postadresse er ikke påkrevd, men hvis den oppgis, vil invitasjonslenken "
"bli sendt til brukeren."
#: .\cookbook\forms.py:133
msgid "Name already taken."
msgstr "Navnet er allerede i bruk."
#: .\cookbook\forms.py:144 .\cookbook\forms.py:158
msgid "Accept Terms and Privacy"
msgstr "Godta vilkår og personvern"
#: .\cookbook\helper\AllAuthCustomAdapter.py:41
msgid ""
"In order to prevent spam, the requested email was not send. Please wait a "
"few minutes and try again."
msgstr ""
"For å forhindre spam ble den forespurte e‑posten ikke sendt. Vent noen "
"minutter og prøv igjen."
#: .\cookbook\helper\permission_helper.py:165
#: .\cookbook\helper\permission_helper.py:186 .\cookbook\views\views.py:138
msgid "You are not logged in and therefore cannot view this page!"
msgstr "Du er ikke innlogget og kan derfor ikke vise siden!"
#: .\cookbook\helper\permission_helper.py:168
#: .\cookbook\helper\permission_helper.py:173
#: .\cookbook\helper\permission_helper.py:198
#: .\cookbook\helper\permission_helper.py:265
#: .\cookbook\helper\permission_helper.py:279
#: .\cookbook\helper\permission_helper.py:290
#: .\cookbook\helper\permission_helper.py:301
#: .\cookbook\helper\permission_helper.py:317
#: .\cookbook\helper\permission_helper.py:343
#: .\cookbook\helper\permission_helper.py:359
msgid "You do not have the required permissions to view this page!"
msgstr "Du har ikke påkrevd tilgang for å vise denne siden!"
#: .\cookbook\helper\permission_helper.py:191
#: .\cookbook\helper\permission_helper.py:214
#: .\cookbook\helper\permission_helper.py:236
#: .\cookbook\helper\permission_helper.py:251
msgid "You cannot interact with this object as it is not owned by you!"
msgstr "Du kan ikke samhandle med dette objektet, da det ikke tilhører deg!"
#: .\cookbook\helper\permission_helper.py:420
msgid "You have reached the maximum number of recipes for your space."
msgstr "Du har nådd det maksimale antallet oppskrifter for området ditt."
#: .\cookbook\helper\permission_helper.py:432
msgid "You have more users than allowed in your space."
msgstr "Du har flere brukere enn det som er tillatt i området ditt."
#: .\cookbook\helper\recipe_url_import.py:319
msgid "reverse rotation"
msgstr "reversert rotasjon"
#: .\cookbook\helper\recipe_url_import.py:320
msgid "careful rotation"
msgstr "forsiktig rotasjon"
#: .\cookbook\helper\recipe_url_import.py:321
msgid "knead"
msgstr "elte"
#: .\cookbook\helper\recipe_url_import.py:322
msgid "thicken"
msgstr "tykne"
#: .\cookbook\helper\recipe_url_import.py:323
msgid "warm up"
msgstr "varm opp"
#: .\cookbook\helper\recipe_url_import.py:324
msgid "ferment"
msgstr "fermentere"
#: .\cookbook\helper\recipe_url_import.py:325
msgid "slow cook"
msgstr "langtidskoke"
#: .\cookbook\helper\recipe_url_import.py:326
msgid "egg boiler"
msgstr "eggkoker"
#: .\cookbook\helper\recipe_url_import.py:327
msgid "kettle"
msgstr "vannkoker"
#: .\cookbook\helper\recipe_url_import.py:328
msgid "blend"
msgstr "blande"
#: .\cookbook\helper\recipe_url_import.py:329
msgid "pre-clean"
msgstr "forhåndsrengjøre"
#: .\cookbook\helper\recipe_url_import.py:330
msgid "high temperature"
msgstr "høy temperatur"
#: .\cookbook\helper\recipe_url_import.py:331
msgid "rice cooker"
msgstr "riskoker"
#: .\cookbook\helper\recipe_url_import.py:332
msgid "caramelize"
msgstr "karamellisere"
#: .\cookbook\helper\recipe_url_import.py:333
msgid "peeler"
msgstr "skreller"
#: .\cookbook\helper\recipe_url_import.py:334
msgid "slicer"
msgstr "skjæremaskin"
#: .\cookbook\helper\recipe_url_import.py:335
msgid "grater"
msgstr "rivjern"
#: .\cookbook\helper\recipe_url_import.py:336
msgid "spiralizer"
msgstr "spiralisering"
#: .\cookbook\helper\recipe_url_import.py:337
msgid "sous-vide"
msgstr "sous-vide"
#: .\cookbook\helper\shopping_helper.py:150
msgid "You must supply a servings size"
msgstr "Du må oppgi en porsjonsstørrelse"
#: .\cookbook\helper\template_helper.py:97
#: .\cookbook\helper\template_helper.py:99
#: .\cookbook\helper\template_helper.py:101
msgid "Could not parse template code."
msgstr "Kunne ikke analysere mal-koden."
#: .\cookbook\integration\copymethat.py:44
#: .\cookbook\integration\melarecipes.py:37
msgid "Favorite"
msgstr "Favoritt"
#: .\cookbook\integration\copymethat.py:50
msgid "I made this"
msgstr "Jeg har lagd denne"
#: .\cookbook\integration\integration.py:238
msgid ""
"Importer expected a .zip file. Did you choose the correct importer type for "
"your data ?"
msgstr ""
"Importøren forventet en .zip-fil. Har du valgt riktig type importør for "
"dataene dine?"
#: .\cookbook\integration\integration.py:241
msgid ""
"An unexpected error occurred during the import. Please make sure you have "
"uploaded a valid file."
msgstr ""
"Det oppstod en uventet feil under importen. Kontroller at du har lastet opp "
"en gyldig fil."
#: .\cookbook\integration\integration.py:246
msgid "The following recipes were ignored because they already existed:"
msgstr "Følgende oppskrifter ble ignorert fordi de allerede eksisterte:"
#: .\cookbook\integration\integration.py:250
#, python-format
msgid "Imported %s recipes."
msgstr "Importerte %s oppskrifter."
#: .\cookbook\integration\mealie1.py:210
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "Calories"
msgstr "Kalorier"
#: .\cookbook\integration\mealie1.py:211
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
msgid "Carbohydrates"
msgstr "Karbohydrater"
#: .\cookbook\integration\mealie1.py:212
msgid "Cholesterol"
msgstr "Kolesterol"
#: .\cookbook\integration\mealie1.py:213
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
#, fuzzy
#| msgid "Fats"
msgid "Fat"
msgstr "Fett"
#: .\cookbook\integration\mealie1.py:214
msgid "Fiber"
msgstr "Fiber"
#: .\cookbook\integration\mealie1.py:215
msgid "Protein"
msgstr "Protein"
#: .\cookbook\integration\mealie1.py:216
msgid "Saturated Fat"
msgstr "Mettet fett"
#: .\cookbook\integration\mealie1.py:217
msgid "Sodium"
msgstr "Natrium"
#: .\cookbook\integration\mealie1.py:218
msgid "Sugar"
msgstr "Sukker"
#: .\cookbook\integration\mealie1.py:219
msgid "Trans Fat"
msgstr "Transfett"
#: .\cookbook\integration\mealie1.py:220
msgid "Unsaturated Fat"
msgstr "Umettet fett"
#: .\cookbook\integration\openeats.py:28
msgid "Recipe source:"
msgstr "Oppskriftskilde:"
#: .\cookbook\integration\paprika.py:49
msgid "Notes"
msgstr "Notater"
#: .\cookbook\integration\paprika.py:52
msgid "Nutritional Information"
msgstr "Næringsinformasjon"
#: .\cookbook\integration\paprika.py:56
msgid "Source"
msgstr "Kilde"
#: .\cookbook\integration\recettetek.py:55
#: .\cookbook\integration\recipekeeper.py:70
msgid "Imported from"
msgstr "Importert fra"
#: .\cookbook\integration\saffron.py:23
msgid "Servings"
msgstr "Porsjoner"
#: .\cookbook\integration\saffron.py:25
msgid "Waiting time"
msgstr "Ventetid"
#: .\cookbook\integration\saffron.py:27
msgid "Preparation Time"
msgstr "Forberedelsestid"
#: .\cookbook\integration\saffron.py:29 .\cookbook\templates\index.html:6
msgid "Cookbook"
msgstr "Kokebok"
#: .\cookbook\integration\saffron.py:31
msgid "Section"
msgstr "Del"
#: .\cookbook\management\commands\fix_duplicate_properties.py:15
msgid "Fixes foods with "
msgstr "Fikser matvarer med "
#: .\cookbook\management\commands\rebuildindex.py:14
msgid "Rebuilds full text search index on Recipe"
msgstr "Bygger opp fulltekstsøk‑indeksen for oppskrifter på nytt"
#: .\cookbook\management\commands\rebuildindex.py:18
msgid "Only Postgresql databases use full text search, no index to rebuild"
msgstr ""
"Bare PostgreSQL‑databaser bruker fulltekstsøk; ingen indeks å gjenoppbygge"
#: .\cookbook\management\commands\rebuildindex.py:29
msgid "Recipe index rebuild complete."
msgstr "Gjenoppbygging av oppskriftsindeks fullført."
#: .\cookbook\management\commands\rebuildindex.py:31
msgid "Recipe index rebuild failed."
msgstr "Gjenoppbygging av oppskriftsindeks mislyktes."
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
msgid "Breakfast"
msgstr "Frokost"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:19
msgid "Lunch"
msgstr "Lunsj"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:24
msgid "Dinner"
msgstr "Middag"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:29 .\cookbook\models.py:971
msgid "Other"
msgstr "Annet"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "g"
msgstr "g"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "Proteins"
msgstr "Proteiner"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "kcal"
msgstr "kcal"
#: .\cookbook\models.py:325
msgid ""
"Maximum file storage for space in MB. 0 for unlimited, -1 to disable file "
"upload."
msgstr ""
"Maksimalt fil-lager for området i MB. 0 gir ubegrenset lagring, −1 "
"deaktiverer opplasting av filer."
#: .\cookbook\models.py:513
msgid "Search"
msgstr "Søk"
#: .\cookbook\models.py:514
msgid "Meal-Plan"
msgstr "Måltidsplan"
#: .\cookbook\models.py:515
msgid "Books"
msgstr "Bøker"
#: .\cookbook\models.py:516 .\cookbook\views\views.py:416
#: .\cookbook\views\views.py:417
msgid "Shopping"
msgstr "Handle"
#: .\cookbook\models.py:967
msgid "Nutrition"
msgstr "Næringsinnhold"
#: .\cookbook\models.py:968
msgid "Allergen"
msgstr "Allergen"
#: .\cookbook\models.py:969
msgid "Price"
msgstr "Pris"
#: .\cookbook\models.py:970
msgid "Goal"
msgstr "Mål"
#: .\cookbook\models.py:1467 .\cookbook\templates\search_info.html:28
msgid "Simple"
msgstr "Enkel"
#: .\cookbook\models.py:1468 .\cookbook\templates\search_info.html:33
msgid "Phrase"
msgstr "Frase"
#: .\cookbook\models.py:1469 .\cookbook\templates\search_info.html:38
msgid "Web"
msgstr "Web"
#: .\cookbook\models.py:1470 .\cookbook\templates\search_info.html:47
msgid "Raw"
msgstr "Rå"
#: .\cookbook\models.py:1525
msgid "Food Alias"
msgstr "Mat‑alias"
#: .\cookbook\models.py:1526
msgid "Unit Alias"
msgstr "Enhet-alias"
#: .\cookbook\models.py:1527
msgid "Keyword Alias"
msgstr "Nøkkelord-alias"
#: .\cookbook\models.py:1528
msgid "Description Replace"
msgstr "Erstatt beskrivelse"
#: .\cookbook\models.py:1529
msgid "Instruction Replace"
msgstr "Erstatt instruksjoner"
#: .\cookbook\models.py:1530
msgid "Never Unit"
msgstr "Aldri enhet"
#: .\cookbook\models.py:1531
msgid "Transpose Words"
msgstr "Bytt om ord"
#: .\cookbook\models.py:1532
msgid "Food Replace"
msgstr "Erstatt matvare"
#: .\cookbook\models.py:1533
msgid "Unit Replace"
msgstr "Erstatt enhet"
#: .\cookbook\models.py:1534
msgid "Name Replace"
msgstr "Erstatt navn"
#: .\cookbook\models.py:1564
msgid "Recipe"
msgstr "Oppskrift"
#: .\cookbook\models.py:1565
msgid "Food"
msgstr "Mat"
#: .\cookbook\models.py:1566
msgid "Keyword"
msgstr "Nøkkelord"
#: .\cookbook\serializer.py:262
msgid "File uploads are not enabled for this Space."
msgstr "Filopplasting er ikke aktivert for dette området."
#: .\cookbook\serializer.py:273
msgid "You have reached your file upload limit."
msgstr "Du har nådd grensen for filopplasting."
#: .\cookbook\serializer.py:281
msgid "The given file type is not allowed."
msgstr "Filtypen er ikke tillatt."
#: .\cookbook\serializer.py:421 .\cookbook\views\views.py:94
msgid ""
"You have the reached the maximum amount of spaces that can be owned by you."
msgstr "Du har nådd det maksimale antallet områder du kan eie."
#: .\cookbook\serializer.py:434
msgid "Space Name must be unique."
msgstr "Navnet på området må være unikt."
#: .\cookbook\serializer.py:469
msgid "Cannot modify Space owner permission."
msgstr "Kan ikke endre tillatelsen for område‑eier."
#: .\cookbook\serializer.py:1596
msgid "Hello"
msgstr "Hallo"
#: .\cookbook\serializer.py:1596
msgid "You have been invited by "
msgstr "Du har blitt invitert av "
#: .\cookbook\serializer.py:1598
msgid " to join their Tandoor Recipes space "
msgstr " til å bli med deres Tandoor oppskrift område "
#: .\cookbook\serializer.py:1600
msgid "Click the following link to activate your account: "
msgstr "Klikk på følgende lenke for å aktivere kontoen din: "
#: .\cookbook\serializer.py:1602
msgid ""
"If the link does not work use the following code to manually join the space: "
msgstr ""
"Hvis lenken ikke fungerer, bruk følgende kode for å bli med i området "
"manuelt: "
#: .\cookbook\serializer.py:1604
msgid "The invitation is valid until "
msgstr "Invitasjonen er gyldig til "
#: .\cookbook\serializer.py:1606
msgid ""
"Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub "
msgstr ""
"Tandoor Recipes er en åpen kildekode‑oppskriftsbehandler. Sjekk den ut på "
"GitHub "
#: .\cookbook\serializer.py:1609
msgid "Tandoor Recipes Invite"
msgstr "Invitasjon til Tandoor oppskrifter"
#: .\cookbook\serializer.py:1813
msgid "Existing shopping list to update"
msgstr "Eksisterende handleliste som skal oppdateres"
#: .\cookbook\serializer.py:1815
msgid ""
"List of ingredient IDs from the recipe to add, if not provided all "
"ingredients will be added."
msgstr ""
"Liste over ingrediens-ID-er fra oppskriften som skal legges til; hvis ingen "
"oppgis, legges alle ingredienser til."
#: .\cookbook\serializer.py:1817
msgid ""
"Providing a list_recipe ID and servings of 0 will delete that shopping list."
msgstr ""
"Hvis du angir en list_recipe-ID og porsjoner som 0, vil den handlelisten bli "
"slettet."
#: .\cookbook\serializer.py:1826
msgid "Amount of food to add to the shopping list"
msgstr "Mengde matvare som skal legges til handlelisten"
#: .\cookbook\serializer.py:1828
msgid "ID of unit to use for the shopping list"
msgstr "ID for enhet som skal brukes for handlelisten"
#: .\cookbook\serializer.py:1830
msgid "When set to true will delete all food from active shopping lists."
msgstr "Når satt til true vil all mat slettes fra aktive handlelister."
#: .\cookbook\templates\404.html:5
msgid "404 Error"
msgstr "404-feil"
#: .\cookbook\templates\404.html:18
msgid "The page you are looking for could not be found."
msgstr "Finner ikke siden du leter etter."
#: .\cookbook\templates\404.html:33
msgid "Take me Home"
msgstr "Tilbake til Startsiden"
#: .\cookbook\templates\404.html:35
msgid "Report a Bug"
msgstr "Rapporter en feil"
#: .\cookbook\templates\account\email.html:6
#: .\cookbook\templates\account\email.html:10
msgid "E-mail Addresses"
msgstr "E-postadresser"
#: .\cookbook\templates\account\email.html:12
msgid "The following e-mail addresses are associated with your account:"
msgstr "Følgende e‑postadresser er knyttet til kontoen din:"
#: .\cookbook\templates\account\email.html:29
msgid "Verified"
msgstr "Verifisert"
#: .\cookbook\templates\account\email.html:31
msgid "Unverified"
msgstr "Ubekreftet"
#: .\cookbook\templates\account\email.html:33
msgid "Primary"
msgstr "Primær"
#: .\cookbook\templates\account\email.html:40
msgid "Make Primary"
msgstr "Gjør til primæradresse"
#: .\cookbook\templates\account\email.html:42
msgid "Re-send Verification"
msgstr "Send verifisering på nytt"
#: .\cookbook\templates\account\email.html:43
#: .\cookbook\templates\socialaccount\connections.html:36
msgid "Remove"
msgstr "Fjern"
#: .\cookbook\templates\account\email.html:51
msgid "Warning:"
msgstr "Advarsel:"
#: .\cookbook\templates\account\email.html:51
msgid ""
"You currently do not have any e-mail address set up. You should really add "
"an e-mail address so you can receive notifications, reset your password, etc."
msgstr ""
"Du har for øyeblikket ingen e‑postadresse registrert. Du bør legge til en e‑"
"postadresse slik at du kan motta varsler, tilbakestille passordet ditt osv."
#: .\cookbook\templates\account\email.html:57
msgid "Add E-mail Address"
msgstr "Legg til e-postadresse"
#: .\cookbook\templates\account\email.html:62
msgid "Add E-mail"
msgstr "Legg til e-post"
#: .\cookbook\templates\account\email.html:72
msgid "Do you really want to remove the selected e-mail address?"
msgstr "Vil du virkelig fjerne den valgte e-postadressen?"
#: .\cookbook\templates\account\email_confirm.html:6
#: .\cookbook\templates\account\email_confirm.html:10
msgid "Confirm E-mail Address"
msgstr "Bekreft e-postadresse"
#: .\cookbook\templates\account\email_confirm.html:16
#, python-format
msgid ""
"Please confirm that\n"
" %(email)s is an e-mail address "
"for user %(user_display)s\n"
" ."
msgstr ""
"Vennligst bekreft at\n"
" %(email)s er en e‑postadresse "
"for bruker %(user_display)s\n"
" ."
#: .\cookbook\templates\account\email_confirm.html:22
msgid "Confirm"
msgstr "Bekreft"
#: .\cookbook\templates\account\email_confirm.html:29
#, python-format
msgid ""
"This e-mail confirmation link expired or is invalid. Please\n"
" issue a new e-mail confirmation "
"request."
msgstr ""
"Denne e-postbekreftelseslenken er utløpt eller ugyldig. Vennligst\n"
" be om en ny e-postbekreftelse."
#: .\cookbook\templates\account\login.html:8
#: .\cookbook\templates\openid\login.html:8
msgid "Login"
msgstr "Logg inn"
#: .\cookbook\templates\account\login.html:15
#: .\cookbook\templates\account\login.html:31
#: .\cookbook\templates\account\password_reset.html:39
#: .\cookbook\templates\account\password_reset_done.html:31
#: .\cookbook\templates\account\signup.html:68
#: .\cookbook\templates\account\signup_closed.html:15
#: .\cookbook\templates\openid\login.html:15
#: .\cookbook\templates\openid\login.html:26
#: .\cookbook\templates\socialaccount\authentication_error.html:15
msgid "Sign In"
msgstr "Logg in"
#: .\cookbook\templates\account\login.html:34
#: .\cookbook\templates\account\password_reset.html:41
#: .\cookbook\templates\account\password_reset_done.html:33
#: .\cookbook\templates\socialaccount\signup.html:8
#: .\cookbook\templates\socialaccount\signup.html:56
msgid "Sign Up"
msgstr "Registrer deg"
#: .\cookbook\templates\account\login.html:38
msgid "Lost your password?"
msgstr "Glemt passord?"
#: .\cookbook\templates\account\login.html:39
#: .\cookbook\templates\account\password_reset.html:29
msgid "Reset My Password"
msgstr "Reset passordet mitt"
#: .\cookbook\templates\account\login.html:50
msgid "Social Login"
msgstr "Sosial innlogging"
#: .\cookbook\templates\account\login.html:51
msgid "You can use any of the following providers to sign in."
msgstr "Velg en av følgende leverandører for å logge på."
#: .\cookbook\templates\account\logout.html:5
#: .\cookbook\templates\account\logout.html:9
#: .\cookbook\templates\account\logout.html:18
msgid "Sign Out"
msgstr "Logg ut"
#: .\cookbook\templates\account\logout.html:11
msgid "Are you sure you want to sign out?"
msgstr "Er du sikker på at du vil logge ut?"
#: .\cookbook\templates\account\password_change.html:6
#: .\cookbook\templates\account\password_change.html:10
#: .\cookbook\templates\account\password_change.html:15
#: .\cookbook\templates\account\password_reset_from_key.html:7
#: .\cookbook\templates\account\password_reset_from_key.html:13
#: .\cookbook\templates\account\password_reset_from_key_done.html:7
#: .\cookbook\templates\account\password_reset_from_key_done.html:13
msgid "Change Password"
msgstr "Bytt Passord"
#: .\cookbook\templates\account\password_change.html:16
msgid "Forgot Password?"
msgstr "Glemt Passord?"
#: .\cookbook\templates\account\password_reset.html:7
#: .\cookbook\templates\account\password_reset.html:13
#: .\cookbook\templates\account\password_reset_done.html:7
#: .\cookbook\templates\account\password_reset_done.html:18
msgid "Password Reset"
msgstr "Nullstill passord"
#: .\cookbook\templates\account\password_reset.html:24
msgid ""
"Forgotten your password? Enter your e-mail address below, and we'll send you "
"an e-mail allowing you to reset it."
msgstr ""
"Glemt passordet ditt? Skriv inn e-postadressen din nedenfor, så sender vi "
"deg en e-post som lar deg tilbakestille det."
#: .\cookbook\templates\account\password_reset.html:32
msgid "Password reset is disabled on this instance."
msgstr "Nullstilling av passord er skrudd av for denne instansen."
#: .\cookbook\templates\account\password_reset_done.html:25
msgid ""
"We have sent you an e-mail. Please contact us if you do not receive it "
"within a few minutes."
msgstr ""
"Vi har sendt deg en e-post. Ta kontakt med oss hvis du ikke mottar den i "
"løpet av noen minutter."
#: .\cookbook\templates\account\password_reset_from_key.html:13
msgid "Bad Token"
msgstr "Ugyldig token"
#: .\cookbook\templates\account\password_reset_from_key.html:25
#, python-format
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used.\n"
" Please request a new "
"password reset."
msgstr ""
"Lenken for tilbakestilling av passord var ugyldig, muligens fordi den "
"allerede er brukt.\n"
" Vennligst be om en ny "
"tilbakestilling av passord."
#: .\cookbook\templates\account\password_reset_from_key.html:33
msgid "change password"
msgstr "bytt passord"
#: .\cookbook\templates\account\password_reset_from_key.html:36
#: .\cookbook\templates\account\password_reset_from_key_done.html:19
msgid "Your password is now changed."
msgstr "Passordet ditt er nå endret."
#: .\cookbook\templates\account\password_set.html:6
#: .\cookbook\templates\account\password_set.html:10
#: .\cookbook\templates\account\password_set.html:15
msgid "Set Password"
msgstr "Sett passord"
#: .\cookbook\templates\account\signup.html:5
msgid "Register"
msgstr "Registrer"
#: .\cookbook\templates\account\signup.html:11
msgid "Create an Account"
msgstr "Opprett en konto"
#: .\cookbook\templates\account\signup.html:41
msgid "I accept the follwoing"
msgstr "Jeg godtar følgende"
#: .\cookbook\templates\account\signup.html:44
#: .\cookbook\templates\socialaccount\signup.html:35
msgid "Terms and Conditions"
msgstr "Vilkår og betingelser"
#: .\cookbook\templates\account\signup.html:47
#: .\cookbook\templates\socialaccount\signup.html:38
msgid "and"
msgstr "og"
#: .\cookbook\templates\account\signup.html:51
#: .\cookbook\templates\socialaccount\signup.html:42
msgid "Privacy Policy"
msgstr "Personvernerklæring"
#: .\cookbook\templates\account\signup.html:64
msgid "Create User"
msgstr "Opprett bruker"
#: .\cookbook\templates\account\signup.html:68
msgid "Already have an account?"
msgstr "Har du allerede en konto?"
#: .\cookbook\templates\account\signup_closed.html:5
#: .\cookbook\templates\account\signup_closed.html:11
msgid "Sign Up Closed"
msgstr "Registrering stengt"
#: .\cookbook\templates\account\signup_closed.html:13
msgid "We are sorry, but the sign up is currently closed."
msgstr "Beklager, men registreringen er for øyeblikket stengt."
#: .\cookbook\templates\frontend\tandoor.html:15
msgid "Tandoor Recipe Manager"
msgstr "Tandoor Oppskrift Administrator"
#: .\cookbook\templates\index.html:28
msgid "Search recipe ..."
msgstr "Søk etter oppskrift..."
#: .\cookbook\templates\index.html:43
msgid "New Recipe"
msgstr "Ny oppskrift"
#: .\cookbook\templates\index.html:46
msgid "Import Recipe"
msgstr "Importer oppskrift"
#: .\cookbook\templates\index.html:52
msgid "Advanced Search"
msgstr "Avansert søk"
#: .\cookbook\templates\index.html:56
msgid "Reset Search"
msgstr "Nullstill søk"
#: .\cookbook\templates\index.html:84
msgid "Last viewed"
msgstr "Sist sett"
#: .\cookbook\templates\index.html:86
msgid "Recipes"
msgstr "Oppskrifter"
#: .\cookbook\templates\index.html:93
msgid "Log in to view recipes"
msgstr "Logg inn for å se oppskrifter"
#: .\cookbook\templates\markdown_info.html:5
#: .\cookbook\templates\markdown_info.html:13
msgid "Markdown Info"
msgstr "Markdown-informasjon"
#: .\cookbook\templates\markdown_info.html:14
msgid ""
"\n"
" Markdown is lightweight markup language that can be used to format "
"plain text easily.\n"
" This site uses the Python Markdown library to\n"
" convert your text into nice looking HTML. Its full markdown "
"documentation can be found\n"
" here.\n"
" An incomplete but most likely sufficient documentation can be found "
"below.\n"
" "
msgstr ""
"\n"
" Markdown er et lettvekts markup språk som benyttes for å formatere "
"ren tekst.\n"
" Denne siden bruker biblioteket Python Markdown for\n"
" å konvertere teksten din til velformatert HTML. Fullstendig "
"dokumentasjon for markdown finner du\n"
" her.\n"
" En ufullstendig, men sannsynligvis tilstrekkelig dokumentasjon "
"finner du under her.\n"
" "
#: .\cookbook\templates\markdown_info.html:25
msgid "Headers"
msgstr "Overskrifter"
#: .\cookbook\templates\markdown_info.html:54
msgid "Formatting"
msgstr "Formatering"
#: .\cookbook\templates\markdown_info.html:56
#: .\cookbook\templates\markdown_info.html:72
msgid "Line breaks are inserted by adding two spaces after the end of a line"
msgstr ""
"Linjeskift er satt inn ved å sette inn to mellomrom på slutten av en linje"
#: .\cookbook\templates\markdown_info.html:57
#: .\cookbook\templates\markdown_info.html:73
#, fuzzy
#| msgid "or by leaving a blank line inbetween."
msgid "or by leaving a blank line in between."
msgstr "eller ved å sette inn en tom linje mellom."
#: .\cookbook\templates\markdown_info.html:59
#: .\cookbook\templates\markdown_info.html:74
msgid "This text is bold"
msgstr "Denne teksten er tykk"
#: .\cookbook\templates\markdown_info.html:60
#: .\cookbook\templates\markdown_info.html:75
msgid "This text is italic"
msgstr "Denne teksten er Kursiv"
#: .\cookbook\templates\markdown_info.html:61
#: .\cookbook\templates\markdown_info.html:77
msgid "Blockquotes are also possible"
msgstr "Det er også mulig å sitere avsnitt"
#: .\cookbook\templates\markdown_info.html:84
msgid "Lists"
msgstr "Lister"
#: .\cookbook\templates\markdown_info.html:85
msgid ""
"Lists can ordered or unordered. It is important to leave a blank line "
"before the list!"
msgstr ""
"Lister kan være ordnet eller uordnet. Det er viktig å legge inn en tom "
"linje før listen!"
#: .\cookbook\templates\markdown_info.html:87
#: .\cookbook\templates\markdown_info.html:108
msgid "Ordered List"
msgstr "Ordnet liste"
#: .\cookbook\templates\markdown_info.html:89
#: .\cookbook\templates\markdown_info.html:90
#: .\cookbook\templates\markdown_info.html:91
#: .\cookbook\templates\markdown_info.html:110
#: .\cookbook\templates\markdown_info.html:111
#: .\cookbook\templates\markdown_info.html:112
msgid "unordered list item"
msgstr "uordnet listepunkt"
#: .\cookbook\templates\markdown_info.html:93
#: .\cookbook\templates\markdown_info.html:114
msgid "Unordered List"
msgstr "Uordnet liste"
#: .\cookbook\templates\markdown_info.html:95
#: .\cookbook\templates\markdown_info.html:96
#: .\cookbook\templates\markdown_info.html:97
#: .\cookbook\templates\markdown_info.html:116
#: .\cookbook\templates\markdown_info.html:117
#: .\cookbook\templates\markdown_info.html:118
msgid "ordered list item"
msgstr "ordnet listepunkt"
#: .\cookbook\templates\markdown_info.html:125
msgid "Images & Links"
msgstr "Bilder og lenker"
#: .\cookbook\templates\markdown_info.html:126
msgid ""
"Links can be formatted with Markdown. This application also allows to paste "
"links directly into markdown fields without any formatting."
msgstr ""
"Lenker kan formateres med Markdown. Denne applikasjonen lar deg også lime "
"inn lenker direkte i Markdown-felt uten noen formatering."
#: .\cookbook\templates\markdown_info.html:132
#: .\cookbook\templates\markdown_info.html:145
msgid "This will become an image"
msgstr "Dette vil bli til et bilde"
#: .\cookbook\templates\markdown_info.html:152
msgid "Tables"
msgstr "Tabeller"
#: .\cookbook\templates\markdown_info.html:153
msgid ""
"Markdown tables are hard to create by hand. It is recommended to use a table "
"editor like this one."
msgstr ""
"Markdown-tabeller er vanskelige å lage for hånd. Det anbefales å bruke en "
"tabellredigerer som denne."
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:171
#: .\cookbook\templates\markdown_info.html:177
msgid "Table"
msgstr "Tabell"
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:172
msgid "Header"
msgstr "Overskrift"
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:178
msgid "Cell"
msgstr "Selle"
#: .\cookbook\templates\no_groups_info.html:5
#: .\cookbook\templates\no_groups_info.html:12
msgid "No Permissions"
msgstr "Ingen tilgang"
#: .\cookbook\templates\no_groups_info.html:17
msgid "You do not have any groups and therefor cannot use this application."
msgstr "Du har ingen grupper og kan derfor ikke bruke denne applikasjonen."
#: .\cookbook\templates\no_groups_info.html:18
#: .\cookbook\templates\no_perm_info.html:15
msgid "Please contact your administrator."
msgstr "Vennligst kontakt administratoren din."
#: .\cookbook\templates\no_perm_info.html:5
#: .\cookbook\templates\no_perm_info.html:12
msgid "No Permission"
msgstr "Ingen tilgang"
#: .\cookbook\templates\no_perm_info.html:15
msgid ""
"You do not have the required permissions to view this page or perform this "
"action."
msgstr ""
"Du har ikke de nødvendige tillatelsene til å vise denne siden eller utføre "
"denne handlingen."
#: .\cookbook\templates\offline.html:5
msgid "Offline"
msgstr "Frakoblet"
#: .\cookbook\templates\openid\login.html:27
#: .\cookbook\templates\socialaccount\authentication_error.html:27
msgid "Back"
msgstr "Tilbake"
#: .\cookbook\templates\rest_framework\api.html:5
msgid "Recipe Home"
msgstr "Oppskriftsoversikt"
#: .\cookbook\templates\rest_framework\api.html:11
msgid "API Documentation"
msgstr "API-dokumentasjon"
#: .\cookbook\templates\search_info.html:5
#: .\cookbook\templates\search_info.html:9
msgid "Search Settings"
msgstr "Søke Instillinger"
#: .\cookbook\templates\search_info.html:10
msgid ""
"\n"
" Creating the best search experience is complicated and weighs "
"heavily on your personal configuration. \n"
" Changing any of the search settings can have significant impact on "
"the speed and quality of the results.\n"
" Search Methods, Trigrams and Full Text Search configurations are "
"only available if you are using Postgres for your database.\n"
" "
msgstr ""
"\n"
" Å skape den beste søkeopplevelsen er komplisert og avhenger i stor "
"grad av dine egne innstillinger. \n"
" Endringer i søkeinnstillingene kan ha stor innvirkning på både "
"hastigheten og kvaliteten på resultatene.\n"
" Søkemetoder, trigrammer og fulltekstsøk-konfigurasjoner er bare "
"tilgjengelige hvis du bruker Postgres som database.\n"
" "
#: .\cookbook\templates\search_info.html:19
msgid "Search Methods"
msgstr "Søke Metode"
#: .\cookbook\templates\search_info.html:23
msgid ""
" \n"
" Full text searches attempt to normalize the words provided to "
"match common variants. For example: 'forked', 'forking', 'forks' will all "
"normalize to 'fork'.\n"
" There are several methods available, described below, that will "
"control how the search behavior should react when multiple words are "
"searched.\n"
" Full technical details on how these operate can be viewed on Postgresql's website.\n"
" "
msgstr ""
" \n"
" Fulltekstsøk forsøker å normalisere ordene som oppgis for å "
"matche vanlige varianter. For eksempel: «forked», «forking», «forks» vil "
"alle normaliseres til «fork».\n"
" Det finnes flere metoder, beskrevet nedenfor, som styrer hvordan "
"søket skal oppføre seg når flere ord søkes etter.\n"
" Full tekniske detaljer om hvordan disse fungerer kan ses på Postgresql sitt nettsted.\n"
" "
#: .\cookbook\templates\search_info.html:29
msgid ""
" \n"
" Simple searches ignore punctuation and common words such as "
"'the', 'a', 'and'. And will treat separate words as required.\n"
" Searching for 'apple or flour' will return any recipe that "
"includes both 'apple' and 'flour' anywhere in the fields that have been "
"selected for a full text search.\n"
" "
msgstr ""
" \n"
" Enkle søk ignorerer tegnsetting og vanlige ord som «the», «a» og "
"«and», og behandler separate ord som nødvendige.\n"
" Søk etter «eple or mel» vil returnere alle oppskrifter som "
"inneholder både «eple» og «mel» et eller annet sted i feltene som er valgt "
"for fulltekstsøk.\n"
" "
#: .\cookbook\templates\search_info.html:34
msgid ""
" \n"
" Phrase searches ignore punctuation, but will search for all of "
"the words in the exact order provided.\n"
" Searching for 'apple or flour' will only return a recipe that "
"includes the exact phrase 'apple or flour' in any of the fields that have "
"been selected for a full text search.\n"
" "
msgstr ""
" \n"
" Frasesøk ignorerer tegnsetting, men søker etter alle ordene i "
"nøyaktig den rekkefølgen de er oppgitt.\n"
" Søk etter «epple eller mel» vil bare returnere en oppskrift som "
"inneholder den eksakte frasen «epple eller mel» i et av feltene som er valgt "
"for fulltekstsøk.\n"
" "
#: .\cookbook\templates\search_info.html:39
msgid ""
" \n"
" Web searches simulate functionality found on many web search "
"sites supporting special syntax.\n"
" Placing quotes around several words will convert those words "
"into a phrase.\n"
" 'or' is recognized as searching for the word (or phrase) "
"immediately before 'or' OR the word (or phrase) directly after.\n"
" '-' is recognized as searching for recipes that do not include "
"the word (or phrase) that comes immediately after. \n"
" For example searching for 'apple pie' or cherry -butter will "
"return any recipe that includes the phrase 'apple pie' or the word "
"'cherry' \n"
" in any field included in the full text search but exclude any "
"recipe that has the word 'butter' in any field included.\n"
" "
msgstr ""
" \n"
" Web‑søk simulerer funksjonalitet som finnes på mange nettsøk‑"
"tjenester og støtter spesialsyntaks.\n"
" Å sette anførselstegn rundt flere ord gjør disse ordene om til "
"en frase.\n"
" 'eller' gjenkjennes som et søk etter ordet (eller frasen) rett "
"før «eller» ELLER ordet (eller frasen) rett etter.\n"
" '-' gjenkjennes som et søk etter oppskrifter som ikke inneholder "
"ordet (eller frasen) som kommer rett etter. \n"
" For eksempel vil et søk etter \"apple pie\" or cherry -butter "
"returnere alle oppskrifter som inneholder frasen \"apple pie\" eller ordet "
"\"cherry\" \n"
" i et hvilket som helst felt som er inkludert i fulltekstsøket, "
"men utelat alle oppskrifter som har ordet «butter» i et hvilket som helst av "
"de inkluderte feltene.\n"
" "
#: .\cookbook\templates\search_info.html:48
msgid ""
" \n"
" Raw search is similar to Web except will take puncuation "
"operators such as '|', '&' and '()'\n"
" "
msgstr ""
" \n"
" Rå‑søk ligner på Web‑søk, men tar også hensyn til "
"tegnsettingsoperatorer som |, & og ().\n"
" "
#: .\cookbook\templates\search_info.html:59
msgid ""
" \n"
" Another approach to searching that also requires Postgresql is "
"fuzzy search or trigram similarity. A trigram is a group of three "
"consecutive characters.\n"
" For example searching for 'apple' will create x trigrams 'app', "
"'ppl', 'ple' and will create a score of how closely words match the "
"generated trigrams.\n"
" One benefit of searching trigams is that a search for 'sandwich' "
"will find misspelled words such as 'sandwhich' that would be missed by other "
"methods.\n"
" "
msgstr ""
" \n"
" En annen tilnærming til søk som også krever PostgreSQL, er "
"uklart søk (fuzzy search) eller trigramsamsvar. Et trigram er en gruppe på "
"tre påfølgende tegn.\n"
" Søk etter «apple» vil for eksempel generere trigrammene «app», "
"«ppl» og «ple», og deretter gi en poengsum basert på hvor godt ordene "
"samsvarer med de genererte trigrammene.\n"
" En fordel med trigramsøk er at et søk etter «sandwich» også vil "
"finne feilstavede ord som «sandwhich», som ville blitt oversett av andre "
"metoder.\n"
" "
#: .\cookbook\templates\search_info.html:69
msgid "Search Fields"
msgstr "Søke Felt"
#: .\cookbook\templates\search_info.html:73
msgid ""
" \n"
" Unaccent is a special case in that it enables searching a field "
"'unaccented' for each search style attempting to ignore accented values. \n"
" For example when you enable unaccent for 'Name' any search "
"(starts with, contains, trigram) will attempt the search ignoring accented "
"characters.\n"
" \n"
" For the other options, you can enable search on any or all "
"fields and they will be combined together with an assumed 'OR'.\n"
" For example enabling 'Name' for Starts With, 'Name' and "
"'Description' for Partial Match and 'Ingredients' and 'Keywords' for Full "
"Search\n"
" and searching for 'apple' will generate a search that will "
"return recipes that have:\n"
" - A recipe name that starts with 'apple'\n"
" - OR a recipe name that contains 'apple'\n"
" - OR a recipe description that contains 'apple'\n"
" - OR a recipe that will have a full text search match ('apple' "
"or 'apples') in ingredients\n"
" - OR a recipe that will have a full text search match in "
"Keywords\n"
"\n"
" Combining too many fields in too many types of search can have a "
"negative impact on performance, create duplicate results or return "
"unexpected results.\n"
" For example, enabling fuzzy search or partial matches will "
"interfere with web search methods. \n"
" Searching for 'apple -pie' with fuzzy search and full text "
"search will return the recipe Apple Pie. Though it is not included in the "
"full text results, it does match the trigram results.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:95
msgid "Search Index"
msgstr "Søke Index"
#: .\cookbook\templates\search_info.html:99
msgid ""
" \n"
" Trigram search and Full Text Search both rely on database "
"indexes to perform effectively. \n"
" You can rebuild the indexes on all fields in the Admin page for "
"Recipes and selecting all recipes and running 'rebuild index for selected "
"recipes'\n"
" You can also rebuild indexes at the command line by executing "
"the management command 'python manage.py rebuildindex'\n"
" "
msgstr ""
" \n"
" Både trigramsøk og fulltekstsøk er avhengige av databaseindekser "
"for å fungere effektivt.\n"
"\n"
" Du kan gjenoppbygge indeksene for alle felt på admin-siden for "
"oppskrifter ved å velge alle oppskrifter og kjøre «rebuild index for "
"selected recipes». Du kan også gjenoppbygge indekser via "
"kommandolinjen ved å utføre styringskommandoen «python manage.py "
"rebuildindex».\n"
" "
#: .\cookbook\templates\setup.html:6
msgid "Cookbook Setup"
msgstr "Kokeboksoppsett"
#: .\cookbook\templates\setup.html:14
msgid "Setup"
msgstr "Installering"
#: .\cookbook\templates\setup.html:15
msgid ""
"To start using this application you must first create a superuser account."
msgstr ""
"For å begynne å bruke denne applikasjonen må du først opprette en "
"superbrukerkonto."
#: .\cookbook\templates\setup.html:20
msgid "Create Superuser account"
msgstr "Opprett superbruker-konto"
#: .\cookbook\templates\socialaccount\authentication_error.html:7
#: .\cookbook\templates\socialaccount\authentication_error.html:23
msgid "Social Network Login Failure"
msgstr "Sosial innlogging Feilet"
#: .\cookbook\templates\socialaccount\authentication_error.html:25
msgid ""
"An error occurred while attempting to login via your social network account."
msgstr ""
"Det oppsto en feil under forsøket på å logge inn med din konto fra sosiale "
"medier."
#: .\cookbook\templates\socialaccount\connections.html:4
#: .\cookbook\templates\socialaccount\connections.html:7
msgid "Account Connections"
msgstr "Kontotilkoblinger"
#: .\cookbook\templates\socialaccount\connections.html:10
msgid ""
"You can sign in to your account using any of the following third party\n"
" accounts:"
msgstr ""
"Du kan logge inn på kontoen din ved å bruke en av følgende tredjeparts- "
"kontoer:\n"
" kontoer:"
#: .\cookbook\templates\socialaccount\connections.html:44
msgid ""
"You currently have no social network accounts connected to this account."
msgstr ""
"Du har for øyeblikket ingen kontoer fra sosiale medier koblet til denne "
"kontoen."
#: .\cookbook\templates\socialaccount\connections.html:47
msgid "Add a 3rd Party Account"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:5
#: .\cookbook\templates\socialaccount\signup.html:5
msgid "Signup"
msgstr "Logg inn"
#: .\cookbook\templates\socialaccount\login.html:9
#, python-format
msgid "Connect %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:11
#, python-format
msgid "You are about to connect a new third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:13
#, python-format
msgid "Sign In Via %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:15
#, python-format
msgid "You are about to sign in using a third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:20
msgid "Continue"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:10
#, python-format
msgid ""
"You are about to use your\n"
" %(provider_name)s account to login to\n"
" %(site_name)s. As a final step, please complete the following form:"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:32
msgid "I accept the following"
msgstr ""
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:23
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:31
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:39
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:47
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:55
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:63
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:71
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:79
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:87
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:95
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:103
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:111
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:119
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:127
msgid "Sign in using"
msgstr "Logg inn med"
#: .\cookbook\templates\space_overview.html:6
msgid "Overview"
msgstr ""
#: .\cookbook\templates\space_overview.html:13
msgid "Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:17
msgid ""
"Recipes, foods, shopping lists and more are organized in spaces of one or "
"more people."
msgstr ""
#: .\cookbook\templates\space_overview.html:18
msgid ""
"You can either be invited into an existing space or create your own one."
msgstr ""
#: .\cookbook\templates\space_overview.html:25
msgid "Your Spaces"
msgstr ""
#: .\cookbook\templates\space_overview.html:53
msgid "Owner"
msgstr ""
#: .\cookbook\templates\space_overview.html:73
#: .\cookbook\templates\space_overview.html:83
msgid "Join Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:76
msgid "Join an existing space."
msgstr ""
#: .\cookbook\templates\space_overview.html:78
msgid ""
"To join an existing space either enter your invite token or click on the "
"invite link the space owner send you."
msgstr ""
#: .\cookbook\templates\space_overview.html:91
#: .\cookbook\templates\space_overview.html:100
msgid "Create Space"
msgstr "Opprett område"
#: .\cookbook\templates\space_overview.html:94
msgid "Create your own recipe space."
msgstr ""
#: .\cookbook\templates\space_overview.html:96
msgid "Start your own recipe space and invite other users to it."
msgstr ""
#: .\cookbook\templates\system.html:23
msgid "System"
msgstr "System"
#: .\cookbook\templates\system.html:24
msgid ""
"\n"
" Tandoor Recipes is an open source free software application. It can "
"be found on\n"
" GitHub.\n"
" Changelogs can be found here.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:30
msgid "System Information"
msgstr ""
#: .\cookbook\templates\system.html:51
msgid ""
"\n"
" You need to execute version.py in your update "
"script to generate version information (done automatically in docker).\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:56
msgid "Plugins"
msgstr ""
#: .\cookbook\templates\system.html:67
msgid "Media Serving"
msgstr ""
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:123 .\cookbook\templates\system.html:134
msgid "Warning"
msgstr "Advarsel"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:125 .\cookbook\templates\system.html:134
msgid "Ok"
msgstr "OK"
#: .\cookbook\templates\system.html:70
msgid ""
"Serving media files directly using gunicorn/python is not recommend!\n"
" Please follow the steps described\n"
" here to update\n"
" your installation.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:76 .\cookbook\templates\system.html:91
#: .\cookbook\templates\system.html:104 .\cookbook\templates\system.html:115
#: .\cookbook\views\views.py:168
msgid "Everything is fine!"
msgstr ""
#: .\cookbook\templates\system.html:80
msgid "Secret Key"
msgstr ""
#: .\cookbook\templates\system.html:84
msgid ""
"\n"
" You do not have a SECRET_KEY configured in your "
".env file. Django defaulted to the\n"
" standard key\n"
" provided with the installation which is publicly know and "
"insecure! Please set\n"
" SECRET_KEY int the .env configuration "
"file.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:94
msgid "Debug Mode"
msgstr "Avlusingsmodus"
#: .\cookbook\templates\system.html:98
msgid ""
"\n"
" This application is still running in debug mode. This is most "
"likely not needed. Turn of debug mode by\n"
" setting\n"
" DEBUG=0 int the .env configuration "
"file.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:107
msgid "Allowed Hosts"
msgstr ""
#: .\cookbook\templates\system.html:111
msgid ""
"\n"
" Your allowed hosts are configured to allow every host. This "
"might be ok in some setups but should be avoided. Please see the docs about "
"this.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:118
msgid "Database"
msgstr "Database"
#: .\cookbook\templates\system.html:121
msgid "Info"
msgstr "Info"
#: .\cookbook\templates\system.html:131 .\cookbook\templates\system.html:148
msgid "Migrations"
msgstr ""
#: .\cookbook\templates\system.html:137
msgid ""
"\n"
" Migrations should never fail!\n"
" Failed migrations will likely cause major parts of the app to "
"not function correctly.\n"
" If a migration fails make sure you are on the latest version and "
"if so please post the migration log and the overview below in a GitHub "
"issue.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "False"
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "True"
msgstr ""
#: .\cookbook\templates\system.html:268
msgid "Hide"
msgstr ""
#: .\cookbook\templates\system.html:271
msgid "Show"
msgstr "Vis"
#: .\cookbook\templates\test2.html:6
msgid "Export Recipes"
msgstr "Eksporter oppskrifter"
#: .\cookbook\templates\test2.html:14 .\cookbook\templates\test2.html:20
msgid "Export"
msgstr "Eksporter"
#: .\cookbook\views\api.py:198 .\cookbook\views\api.py:326
msgid "Parameter updated_at incorrectly formatted"
msgstr ""
#: .\cookbook\views\api.py:351 .\cookbook\views\api.py:484
#, python-brace-format
msgid "No {self.basename} with id {pk} exists"
msgstr ""
#: .\cookbook\views\api.py:355
msgid "Cannot merge with the same object!"
msgstr ""
#: .\cookbook\views\api.py:362
#, python-brace-format
msgid "No {self.basename} with id {target} exists"
msgstr ""
#: .\cookbook\views\api.py:367
msgid "Cannot merge with child object!"
msgstr ""
#: .\cookbook\views\api.py:405
#, python-brace-format
msgid "{source.name} was merged successfully with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:411
#, python-brace-format
msgid "An error occurred attempting to merge {source.name} with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:493
#, python-brace-format
msgid "{child.name} was moved successfully to the root."
msgstr ""
#: .\cookbook\views\api.py:496 .\cookbook\views\api.py:514
msgid "An error occurred attempting to move "
msgstr ""
#: .\cookbook\views\api.py:499
msgid "Cannot move an object to itself!"
msgstr ""
#: .\cookbook\views\api.py:505
#, python-brace-format
msgid "No {self.basename} with id {parent} exists"
msgstr ""
#: .\cookbook\views\api.py:511
#, python-brace-format
msgid "{child.name} was moved successfully to parent {parent.name}"
msgstr ""
#: .\cookbook\views\api.py:992
#, python-brace-format
msgid "{obj.name} was removed from the shopping list."
msgstr ""
#: .\cookbook\views\api.py:997 .\cookbook\views\api.py:1627
#, python-brace-format
msgid "{obj.name} was added to the shopping list."
msgstr ""
#: .\cookbook\views\api.py:1239
msgid "Filter meal plans from date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1241
msgid "Filter meal plans to date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1244
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1404
msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1406
msgid "Query string matched (fuzzy) against object name."
msgstr ""
#: .\cookbook\views\api.py:1442
msgid ""
"Query string matched (fuzzy) against recipe name. In the future also "
"fulltext search."
msgstr ""
#: .\cookbook\views\api.py:1444
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr ""
#: .\cookbook\views\api.py:1445
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr ""
#: .\cookbook\views\api.py:1446
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1447
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1448
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1450
msgid "ID of food a recipe should have. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1451
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr ""
#: .\cookbook\views\api.py:1452
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1453
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr ""
#: .\cookbook\views\api.py:1454
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1456
msgid "ID of book a recipe should be in. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1457
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr ""
#: .\cookbook\views\api.py:1458
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1459
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr ""
#: .\cookbook\views\api.py:1460
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1462
msgid "ID of unit a recipe should have."
msgstr ""
#: .\cookbook\views\api.py:1464
msgid "Exact rating of recipe"
msgstr ""
#: .\cookbook\views\api.py:1465
msgid "Rating a recipe should have or greater."
msgstr ""
#: .\cookbook\views\api.py:1466
msgid "Rating a recipe should have or smaller."
msgstr ""
#: .\cookbook\views\api.py:1468
msgid "Filter recipes cooked X times."
msgstr ""
#: .\cookbook\views\api.py:1469
msgid "Filter recipes cooked X times or more."
msgstr ""
#: .\cookbook\views\api.py:1470
msgid "Filter recipes cooked X times or less."
msgstr ""
#: .\cookbook\views\api.py:1472
msgid "Filter recipes created on the given date."
msgstr ""
#: .\cookbook\views\api.py:1473
msgid "Filter recipes created on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1474
msgid "Filter recipes created on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1476 .\cookbook\views\api.py:1477
#: .\cookbook\views\api.py:1478
msgid "Filter recipes updated on the given date."
msgstr ""
#: .\cookbook\views\api.py:1480
msgid "Filter recipes last cooked on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1481
msgid "Filter recipes last cooked on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1483 .\cookbook\views\api.py:1484
msgid "Filter recipes lasts viewed on the given date."
msgstr ""
#: .\cookbook\views\api.py:1486
msgid "Filter recipes for ones created by the given user ID"
msgstr ""
#: .\cookbook\views\api.py:1487
msgid "If only internal recipes should be returned. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1488
msgid "Returns the results in randomized order. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1490
msgid ""
"Determines the order of the results. Options are: score,-score,name,-name,"
"lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-"
"created_at,lastviewed,-lastviewed"
msgstr ""
#: .\cookbook\views\api.py:1492
msgid "Returns new results first in search results. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1493
msgid ""
"Returns the given number of recently viewed recipes before search results "
"(if given)"
msgstr ""
#: .\cookbook\views\api.py:1494
msgid "ID of a custom filter. Returns all recipes matched by that filter."
msgstr ""
#: .\cookbook\views\api.py:1495
msgid "Filter recipes that can be made with OnHand food. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1773
msgid ""
"Return the PropertyTypes matching the property category. Repeat for "
"multiple."
msgstr ""
#: .\cookbook\views\api.py:1804 .\cookbook\views\api.py:1860
msgid "Returns only entries associated with the given mealplan id"
msgstr ""
#: .\cookbook\views\api.py:1858
msgid ""
"Returns only elements updated after the given timestamp in ISO 8601 format."
msgstr ""
#: .\cookbook\views\api.py:2031
msgid ""
"Return the Automations matching the automation type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2048
msgid ""
"Text field to store data that gets carried over to the UserSpace created "
"from the InviteLink"
msgstr ""
#: .\cookbook\views\api.py:2049
msgid "Only return InviteLinks that have not been used yet."
msgstr ""
#: .\cookbook\views\api.py:2076
msgid "Return the CustomFilters matching the model type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2176
msgid "Nothing to do."
msgstr ""
#: .\cookbook\views\api.py:2222
msgid "Invalid Url"
msgstr ""
#: .\cookbook\views\api.py:2228
msgid "Connection Refused."
msgstr ""
#: .\cookbook\views\api.py:2232
msgid "Bad URL Schema."
msgstr ""
#: .\cookbook\views\api.py:2257
msgid "No usable data could be found."
msgstr ""
#: .\cookbook\views\api.py:2286 .\cookbook\views\api.py:2434
msgid "You must select an AI provider to perform your request."
msgstr ""
#: .\cookbook\views\api.py:2293 .\cookbook\views\api.py:2441
msgid ""
"You don't have any credits remaining to use AI or AI features are not "
"enabled for your space."
msgstr ""
#: .\cookbook\views\api.py:2499 .\cookbook\views\api.py:2667
msgid "File is above space limit"
msgstr ""
#: .\cookbook\views\api.py:2522 .\cookbook\views\api.py:2684
msgid "Importing is not implemented for this provider"
msgstr ""
#: .\cookbook\views\api.py:2548
msgid ""
"The PDF Exporter is not enabled on this instance as it is still in an "
"experimental state."
msgstr ""
#: .\cookbook\views\api.py:2842
msgid "This feature is not yet available in the hosted version of tandoor!"
msgstr ""
#: .\cookbook\views\api.py:2863
msgid "Sync successful!"
msgstr ""
#: .\cookbook\views\api.py:2866
msgid "Error synchronizing with Storage"
msgstr ""
#: .\cookbook\views\views.py:89
msgid "This feature is not available in the demo version!"
msgstr ""
#: .\cookbook\views\views.py:110
msgid ""
"You have successfully created your own recipe space. Start by adding some "
"recipes or invite other people to join you."
msgstr ""
#: .\cookbook\views\views.py:171
#, python-format
msgid "PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!"
msgstr ""
#: .\cookbook\views\views.py:174
#, python-format
msgid "You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended"
msgstr ""
#: .\cookbook\views\views.py:178
msgid "Unable to determine PostgreSQL version."
msgstr ""
#: .\cookbook\views\views.py:182
msgid ""
"This application is not running with a Postgres database backend. This is ok "
"but not recommended as some features only work with postgres databases."
msgstr ""
#: .\cookbook\views\views.py:296
msgid ""
"The setup page can only be used to create the first "
"user! If you have forgotten your superuser credentials "
"please consult the django documentation on how to reset passwords."
msgstr ""
#: .\cookbook\views\views.py:304
msgid "Passwords dont match!"
msgstr ""
#: .\cookbook\views\views.py:312
msgid "User has been created, please login!"
msgstr ""
#: .\cookbook\views\views.py:352
msgid ""
"Reporting share links is not enabled for this instance. Please notify the "
"page administrator to report problems."
msgstr ""
#: .\cookbook\views\views.py:357
msgid ""
"Recipe sharing link has been disabled! For additional information please "
"contact the page administrator."
msgstr ""
#: .\cookbook\views\views.py:383
msgid "Manage recipes, shopping list, meal plans and more."
msgstr ""
#: .\cookbook\views\views.py:397 .\cookbook\views\views.py:398
msgid "Plan"
msgstr "Plan"
#: .\cookbook\views\views.py:399
msgid "View your meal Plan"
msgstr ""
#: .\cookbook\views\views.py:418
msgid "View your shopping lists"
msgstr "Vis din handleliste"
#~ msgid ""
#~ "Both fields are optional. If none are given the username will be "
#~ "displayed instead"
#~ msgstr ""
#~ "Begge feltene er valgfrie. Hvis ingen blir oppgitt, vil brukernavnet "
#~ "vises i stedet"
#~ msgid "Name"
#~ msgstr "Navn"
#~ msgid "Keywords"
#~ msgstr "Nøkkelord"
#~ msgid "Preparation time in minutes"
#~ msgstr "Forberedelsestid i minutter"
#~ msgid "Waiting time (cooking/baking) in minutes"
#~ msgstr "Ventetid (til matlaging/baking) i minutter"
#~ msgid "Path"
#~ msgstr "Sti"
#~ msgid "Storage UID"
#~ msgstr "Lagring UID"
#~ msgid "Add your comment: "
#~ msgstr "Legg til din kommentar: "
#~ msgid "Leave empty for dropbox and enter app password for nextcloud."
#~ msgstr ""
#~ "La det stå tomt for Dropbox og skriv inn app-passordet for Nextcloud."
#~ msgid "Leave empty for nextcloud and enter api token for dropbox."
#~ msgstr "La det stå tomt for Nextcloud og skriv inn API-tokenet for Dropbox."
#~ msgid ""
#~ "Leave empty for dropbox and enter only base url for nextcloud (/"
#~ "remote.php/webdav/ is added automatically)"
#~ msgstr ""
#~ "La det stå tomt for Dropbox, og skriv bare inn grunn-URLen for Nextcloud "
#~ "(/remote.php/webdav/ blir lagt til automatisk)"
#~ msgid "Search String"
#~ msgstr "Søkestreng"
#~ msgid "File ID"
#~ msgstr "Fil-ID"
#, fuzzy
#~| msgid "Search"
#~ msgid "Search Method"
#~ msgstr "Søk"
#, fuzzy
#~| msgid "Search"
#~ msgid "Fuzzy Search"
#~ msgstr "Søk"
#, fuzzy
#~| msgid "Text"
#~ msgid "Full Text"
#~ msgstr "Tekst"
#~ msgid "Delete"
#~ msgstr "Slett"
#~ msgid "Settings"
#~ msgstr "Innstillinger"
#, fuzzy
#~| msgid "Password Reset"
#~ msgid "Password"
#~ msgstr "Nullstill passord"
#~ msgid "Foods"
#~ msgstr "Ny matvare"
#~ msgid "Units"
#~ msgstr "Enheter"
#~ msgid "Supermarket"
#~ msgstr "Butikk"
#, fuzzy
#~| msgid "Supermarket"
#~ msgid "Supermarket Category"
#~ msgstr "Butikk"
#, fuzzy
#~| msgid "Nutrition"
#~ msgid "Automations"
#~ msgstr "Næringsinnhold"
#, fuzzy
#~| msgid "File ID"
#~ msgid "Files"
#~ msgstr "Fil-ID"
#~ msgid "Batch Edit"
#~ msgstr "Oppdatere flere"
#~ msgid "History"
#~ msgstr "Historikk"
#~ msgid "Ingredient Editor"
#~ msgstr "Ingrediensredigerer"
#~ msgid "Properties"
#~ msgstr "Egenskaper"
#~ msgid "Create"
#~ msgstr "Opprett"
#, fuzzy
#~| msgid "Settings"
#~ msgid "Space Settings"
#~ msgstr "Innstillinger"
#~ msgid "Admin"
#~ msgstr "Administrasjon"
#~ msgid "Markdown Guide"
#~ msgstr "Markdown-guide"
#~ msgid "GitHub"
#~ msgstr "GitHub"
#~ msgid "API Browser"
#~ msgstr "API-utforsker"
#~ msgid "Batch edit Category"
#~ msgstr "Oppdater flere kategorier"
#~ msgid "Batch edit Recipes"
#~ msgstr "Oppdater flere oppskrifter"
#~ msgid "Add the specified keywords to all recipes containing a word"
#~ msgstr ""
#~ "Legg til spesifikt nøkkelord til alle oppskrifter som inneholder et ord"
#~ msgid "Sync"
#~ msgstr "Synkronisering"
#~ msgid "Manage watched Folders"
#~ msgstr "Behandle overvåkede mapper"
#~ msgid ""
#~ "On this Page you can manage all storage folder locations that should be "
#~ "monitored and synced."
#~ msgstr ""
#~ "Her kan du behandle alle lagringsmapper og plasseringer for monitorering "
#~ "og synkronisering."
#~ msgid "The path must be in the following format"
#~ msgstr "Stien må være i følgende format"
#~ msgid "Save"
#~ msgstr "Lagre"
#~ msgid "Sync Now!"
#~ msgstr "Synkroniser nå!"
#, fuzzy
#~| msgid "Recipes"
#~ msgid "Show Recipes"
#~ msgstr "Oppskrifter"
#, fuzzy
#~| msgid "View Log"
#~ msgid "Show Log"
#~ msgstr "Vis logg"
#~ msgid "Importing Recipes"
#~ msgstr "Importerer oppskrifter"
#~ msgid ""
#~ "This can take a few minutes, depending on the number of recipes in sync, "
#~ "please wait."
#~ msgstr ""
#~ "Dette kan ta noen minutter, avhenging av antall oppskrifter som skal "
#~ "synkroniseres. Vennligst vent."
#~ msgid "Recipe Books"
#~ msgstr "Oppskriftsbøker"
#~ msgid "Import new Recipe"
#~ msgstr "Importer ny oppskrift"
#~ msgid "Edit Recipe"
#~ msgstr "Rediger oppskrift"
#, python-format
#~ msgid "Are you sure you want to delete the %(title)s: %(object)s "
#~ msgstr "Er du sikker på at du vil slette %(title)s: %(object)s "
#~ msgid "Protected"
#~ msgstr "Beskyttet"
#~ msgid "Edit"
#~ msgstr "Rediger"
#~ msgid "View"
#~ msgstr "Vis"
#~ msgid "Delete original file"
#~ msgstr "Slett opprinnelig fil"
#~ msgid "List"
#~ msgstr "Liste"
#~ msgid "Filter"
#~ msgstr "Filtrer"
#~ msgid "Import all"
#~ msgstr "Importer alle"
#~ msgid "New"
#~ msgstr "Ny"
#~ msgid "previous"
#~ msgstr "forrige"
#~ msgid "next"
#~ msgstr "neste"
#~ msgid "View Log"
#~ msgstr "Vis logg"
#~ msgid "Cook Log"
#~ msgstr "Tilberedingslogg"
#~ msgid "Import"
#~ msgstr "Importér"
#~ msgid "Security Warning"
#~ msgstr "Sikkerhetsadvarsel"
#~ msgid ""
#~ "\n"
#~ " The Password and Token field are stored as plain text"
#~ "b> inside the database.\n"
#~ " This is necessary because they are needed to make API requests, "
#~ "but it also increases the risk of\n"
#~ " someone stealing it.
\n"
#~ " To limit the possible damage tokens or accounts with limited "
#~ "access can be used.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Passord og nøkkelfeltene er lagret som ren tekst i "
#~ "databasen.\n"
#~ " Dette er nødvendig for å kunne utføre API-forespørsler, men det "
#~ "øker samtidig risiko for\n"
#~ " uønsket tilgang til dem.
\n"
#~ " For å begrense kosekvensene av uønsket tilgang, kan nøkler eller "
#~ "kontoer med begrenset tilgang benyttes.\n"
#~ " "
#~ msgid "You are currently offline!"
#~ msgstr "Du er ikke tilkoblet!"
#~ msgid "Comments"
#~ msgstr "Kommentarer"
#~ msgid "by"
#~ msgstr "av"
#, fuzzy
#~| msgid "Social Login"
#~ msgid "Social"
#~ msgstr "Sosial innlogging"
#, fuzzy
#~| msgid "Description"
#~ msgid "Manage Subscription"
#~ msgstr "Beskrivelse"
#~ msgid "Shopping List"
#~ msgstr "Handleliste"
#, fuzzy
#~| msgid "Storage Backends"
#~ msgid "Connector Config Backend"
#~ msgstr "Lagringsplasser"
#, fuzzy
#~| msgid "Supermarket"
#~ msgid "Supermarkets"
#~ msgstr "Butikk"
#, fuzzy
#~| msgid "Shopping List"
#~ msgid "Shopping Categories"
#~ msgstr "Handleliste"
#, fuzzy
#~| msgid "Filter"
#~ msgid "Custom Filters"
#~ msgstr "Filtrer"
#~ msgid "Steps"
#~ msgstr "Trinn"
#~ msgid ""
#~ "Color of the top navigation bar. Not all colors work with all themes, "
#~ "just try them out!"
#~ msgstr ""
#~ "Farge på toppnavigasjonslinjen. Ikke alle farger fungerer med alle "
#~ "temaer, så bare prøv dem ut!"
#~ msgid ""
#~ "Default Unit to be used when inserting a new ingredient into a recipe."
#~ msgstr "Standard enhet når ny ingrediens legges til en oppskrift."
#~ msgid ""
#~ "Enables support for fractions in ingredient amounts (e.g. convert "
#~ "decimals to fractions automatically)"
#~ msgstr ""
#~ "Aktiverer støtte for deler av ingrediensmengde (konverterer feks. "
#~ "desimaler til deler automatisk)"
#~ msgid ""
#~ "Users with whom newly created meal plan/shopping list entries should be "
#~ "shared by default."
#~ msgstr ""
#~ "Brukere som oppretter nye måltidsplaner/handlelister, deler disse "
#~ "oppføringene som standard."
#~ msgid "Show recently viewed recipes on search page."
#~ msgstr "Vis nylig viste oppskrifter på søkesiden."
#~ msgid "Number of decimals to round ingredients."
#~ msgstr "Antall desimaler ingredienser skal avrundes til."
#~ msgid ""
#~ "If you want to be able to create and see comments underneath recipes."
#~ msgstr "Hvis du ønsker å opprette og se kommentarer under oppskrifter."
#~ msgid ""
#~ "Setting to 0 will disable auto sync. When viewing a shopping list the "
#~ "list is updated every set seconds to sync changes someone else might have "
#~ "made. Useful when shopping with multiple people but might use a little "
#~ "bit of mobile data. If lower than instance limit it is reset when saving."
#~ msgstr ""
#~ "0 vil deaktivere automatisk synkronisering. Når en handleliste vises, "
#~ "oppdateres listen med oppgitt antall sekunders mellomrom for å "
#~ "synkronisere endringer fra andre brukere. Nyttig dersom flere brukere "
#~ "handler samtidig. Datatrafikk oppstår når aktiv. Hvis verdien er lavere "
#~ "enn grensen, tilbakestilles den ved lagring."
#~ msgid "Makes the navbar stick to the top of the page."
#~ msgstr "Fest navigasjonslinjen til toppen av siden."
#~ msgid "New unit that other gets replaced by."
#~ msgstr "Ny enhet som erstatter den gamle."
#~ msgid "Old Unit"
#~ msgstr "Gammel enhet"
#~ msgid "Unit that should be replaced."
#~ msgstr "Enhet som skal erstattes."
#~ msgid "New food that other gets replaced by."
#~ msgstr "Ny matvare som erstatter den gamle."
#~ msgid "Old Food"
#~ msgstr "Gammel matvare"
#~ msgid "Food that should be replaced."
#~ msgstr "Matvare som bør erstattes."
#~ msgid "You must provide at least a recipe or a title."
#~ msgstr "Du må oppgi minst en oppskrift eller en tittel."
#~ msgid "You can list default users to share recipes with in the settings."
#~ msgstr ""
#~ "Du kan liste opp standardbrukere for å dele oppskrifter innen "
#~ "innstillingene."
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "Du kan bruke Markdown for å formatere dette feltet. Se dokumentasjonen her"
#~ msgid ""
#~ "A username is not required, if left blank the new user can choose one."
#~ msgstr ""
#~ "Et brukernavn er ikke påkrevd. Hvis det blir stående tomt, kan den nye "
#~ "brukeren velge ett selv."
#~ msgid "The requested site provided malformed data and cannot be read."
#~ msgstr ""
#~ "Nettstedet du har forespurt, har levert feilformatert data som ikke kan "
#~ "leses."
#~ msgid ""
#~ "The requested site does not provide any recognized data format to import "
#~ "the recipe from."
#~ msgstr ""
#~ "Det forespurte nettstedet gir ingen gjenkjennelig dataformat som kan "
#~ "importeres oppskriften fra."
#~ msgid "Small"
#~ msgstr "Liten"
#~ msgid "Large"
#~ msgstr "Stor"
#~ msgid "Time"
#~ msgstr "Tid"
#~ msgid "Link"
#~ msgstr "Lenke"
#~ msgid "Utensils"
#~ msgstr "Redskaper"
#~ msgid "Storage Data"
#~ msgstr "Datalagring"
#~ msgid "Configure Sync"
#~ msgstr "Konfigurer synkronisering"
#~ msgid "Discovered Recipes"
#~ msgstr "Oppdagede oppskrifter"
#~ msgid "Discovery Log"
#~ msgstr "Logg Oppdagelser"
#~ msgid "Statistics"
#~ msgstr "Statistikk"
#~ msgid "Units & Ingredients"
#~ msgstr "Enheter & Ingredienser"
#~ msgid "Logout"
#~ msgstr "Logg ut"
#~ msgid "New Book"
#~ msgstr "Ny bok"
#~ msgid "Toggle Recipes"
#~ msgstr "Veksle oppskrifter"
#~ msgid "There are no recipes in this book yet."
#~ msgstr "Det er foreløpig ingen oppskrifter i denne boken."
#~ msgid "Waiting Time"
#~ msgstr "Ventetid"
#~ msgid "Servings Text"
#~ msgstr "Porsjon beskrivelse"
#~ msgid "Select Keywords"
#~ msgstr "Velg nøkkelord"
#~ msgid "Add Keyword"
#~ msgstr "Legg til nøkkelord"
#~ msgid "Delete Step"
#~ msgstr "Fjern trinn"
#~ msgid "Step"
#~ msgstr "Trinn"
#~ msgid "Show as header"
#~ msgstr "Vis som overskrift"
#~ msgid "Hide as header"
#~ msgstr "Skjul overskrift"
#~ msgid "Move Up"
#~ msgstr "Flytt oppover"
#~ msgid "Move Down"
#~ msgstr "Flytt nedover"
#~ msgid "Step Name"
#~ msgstr "Trinn navn"
#~ msgid "Step Type"
#~ msgstr "Trinn type"
#~ msgid "Step time in Minutes"
#~ msgstr "Trinn tid i minutter"
#~ msgid "Select Unit"
#~ msgstr "Velg enhet"
#~ msgid "Select"
#~ msgstr "Velg"
#~ msgid "Select Food"
#~ msgstr "Velg mat"
#~ msgid "Note"
#~ msgstr "Notis"
#~ msgid "Delete Ingredient"
#~ msgstr "Slett ingrediens"
#~ msgid "Make Ingredient"
#~ msgstr "Opprett ingrediens"
#~ msgid "Disable Amount"
#~ msgstr "Deaktiver mengde"
#~ msgid "Enable Amount"
#~ msgstr "Aktiver mengde"
#~ msgid "Copy Template Reference"
#~ msgstr "Kopier mal-referanse"
#~ msgid "Save & View"
#~ msgstr "Lagre og vis"
#~ msgid "Add Step"
#~ msgstr "Legg til trinn"
#~ msgid "Add Nutrition"
#~ msgstr "Legg til næringsinnhold"
#~ msgid "Remove Nutrition"
#~ msgstr "Fjern næringsinnhold"
#~ msgid "View Recipe"
#~ msgstr "Vis oppskrift"
#~ msgid "Delete Recipe"
#~ msgstr "Slett oppskrift"
#~ msgid "Edit Ingredients"
#~ msgstr "Rediger ingrediens"
#~ msgid ""
#~ "\n"
#~ " The following form can be used if, accidentally, two (or more) "
#~ "units or ingredients where created that should be\n"
#~ " the same.\n"
#~ " It merges two units or ingredients and updates all recipes using "
#~ "them.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Følgende skjema kan brukes dersom, tilfeldigvis, to eller flere "
#~ "enheter eller ingredienser er opprettet,\n"
#~ " og burde være identiske.\n"
#~ " Det slår sammen to enheter eller ingredienser og oppdaterer alle "
#~ "oppskrifter som inneholder disse.\n"
#~ " "
#~ msgid "Are you sure that you want to merge these two units?"
#~ msgstr "Er du sikker på at du vil slå sammen disse enhetene?"
#~ msgid "Are you sure that you want to merge these two ingredients?"
#~ msgstr "Er du sikker på at du vil slå sammen disse ingrediensene?"
#~ msgid "Import Recipes"
#~ msgstr "Importer oppskrifter"
#~ msgid "Log Recipe Cooking"
#~ msgstr "Loggfør tilberedt oppskrift"
#~ msgid "All fields are optional and can be left empty."
#~ msgstr "Alle felt er valgfri og kan stå tomme."
#~ msgid "Rating"
#~ msgstr "Vurdering"
#~ msgid "Close"
#~ msgstr "Lukk"
#~ msgid "Open Recipe"
#~ msgstr "Åpne oppskrift"
#~ msgid "Website Import"
#~ msgstr "Importer fra nettside"
#~ msgid "New Entry"
#~ msgstr "Ny oppføring"
#~ msgid "Title"
#~ msgstr "Tittel"
#~ msgid "Note (optional)"
#~ msgstr "Merknad (valgfritt)"
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "Du kan bruke Markdown for å formatere dette feltet. Se dokumentasjonen "
#~ "her"
#~ msgid "Serving Count"
#~ msgstr "Antall porsjoner"
#~ msgid "Create only note"
#~ msgstr "Opprett kun en merknad"
#~ msgid "Shopping list currently empty"
#~ msgstr "Handlelisten er for øyeblikket tom"
#~ msgid "Number of Days"
#~ msgstr "Antall dager"
#~ msgid "Weekday offset"
#~ msgstr "Ukedagsforskyvning"
#~ msgid ""
#~ "Number of days starting from the first day of the week to offset the "
#~ "default view."
#~ msgstr ""
#~ "Antall dager fra den første dagen i uken for å endre standardvisningen."
#~ msgid "Edit plan types"
#~ msgstr "Rediger plantyper"
#~ msgid "Week iCal export"
#~ msgstr "Uke iCal-eksport"
#~ msgid "Created by"
#~ msgstr "Opprettet av"
#~ msgid "Shared with"
#~ msgstr "Delt med"
#~ msgid "Add to Shopping"
#~ msgstr "Legg til i handlelisten"
#~ msgid "New meal type"
#~ msgstr "Ny måltidstype"
#~ msgid "Meal Plan Help"
#~ msgstr "Hjelp for måltidsplanen"
#~ msgid "Meal Plan View"
#~ msgstr "Visning av måltidsplanen"
#~ msgid "Other meals on this day"
#~ msgstr "Andre måltider denne dagen"
#~ msgid "Account"
#~ msgstr "Konto"
#~ msgid "Language"
#~ msgstr "Språk"
#~ msgid "Style"
#~ msgstr "Stil"
#~ msgid "Entry Mode"
#~ msgstr "Oppføringsmodus"
#~ msgid "Add Entry"
#~ msgstr "Legg til oppføring"
#~ msgid "Amount"
#~ msgstr "Mengde"
#~ msgid "Select Supermarket"
#~ msgstr "Velg butikk"
#~ msgid "Select User"
#~ msgstr "Velg bruker"
#~ msgid "Finished"
#~ msgstr "Fullført"
================================================
FILE: cookbook/locale/nl/LC_MESSAGES/django.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
# Translators:
# 31a3ead7f9b1ec8ada1a36808eee4069_988cec9 <9478557dfb8b6cd81570ee9e754f1719_904168>, 2020
# Frank Engbers , 2020
# kampsj , 2021
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-08-01 15:04+0200\n"
"PO-Revision-Date: 2025-09-23 19:45+0000\n"
"Last-Translator: Justin Straver \n"
"Language-Team: Dutch \n"
"Language: nl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.13.1\n"
#: .\cookbook\forms.py:45
msgid ""
"Both fields are optional. If none are given the username will be displayed "
"instead"
msgstr ""
"Beide velden zijn optioneel. Indien niets is opgegeven wordt de "
"gebruikersnaam weergegeven"
#: .\cookbook\forms.py:62 .\cookbook\forms.py:246
msgid "Name"
msgstr "Naam"
#: .\cookbook\forms.py:62 .\cookbook\forms.py:246 .\cookbook\views\lists.py:103
msgid "Keywords"
msgstr "Trefwoorden"
#: .\cookbook\forms.py:62
msgid "Preparation time in minutes"
msgstr "Voorbereidingstijd in minuten"
#: .\cookbook\forms.py:62
msgid "Waiting time (cooking/baking) in minutes"
msgstr "Wachttijd in minuten (koken en bakken)"
#: .\cookbook\forms.py:63 .\cookbook\forms.py:222 .\cookbook\forms.py:246
msgid "Path"
msgstr "Pad"
#: .\cookbook\forms.py:63
msgid "Storage UID"
msgstr "Opslag UID"
#: .\cookbook\forms.py:93
msgid "Default"
msgstr "Standaard waarde"
#: .\cookbook\forms.py:121
msgid ""
"To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything."
msgstr ""
"Standaard worden dubbele recepten, op basis van de naam, genegeerd. Vink "
"deze optie aan om toch alles te importeren."
#: .\cookbook\forms.py:143
msgid "Add your comment: "
msgstr "Voeg een opmerking toe: "
#: .\cookbook\forms.py:151
msgid "Leave empty for dropbox and enter app password for nextcloud."
msgstr "Laat leeg voor dropbox en vul het app wachtwoord in voor nextcloud."
#: .\cookbook\forms.py:154
msgid "Leave empty for nextcloud and enter api token for dropbox."
msgstr "Laat leeg voor nextcloud en vul de api token in voor dropbox."
#: .\cookbook\forms.py:160
msgid ""
"Leave empty for dropbox and enter only base url for nextcloud (/remote."
"php/webdav/ is added automatically)"
msgstr ""
"Laat leeg voor dropbox en vul enkel de base url voor nextcloud in. (/"
"remote.php/webdav/ wordt automatisch toegevoegd.)"
#: .\cookbook\forms.py:188
msgid ""
"Long Lived Access Token for your HomeAssistant instance"
msgstr ""
"Toegangtokens met lange levensduur voor jouw HomeAssistant "
"installatie"
#: .\cookbook\forms.py:193
msgid "Something like http://homeassistant.local:8123/api"
msgstr "Bijvoorbeeld http://homeassistant.local:8123/api"
#: .\cookbook\forms.py:205
msgid "http://homeassistant.local:8123/api for example"
msgstr "http://homeassistant.local:8123/api bijvoorbeeld"
#: .\cookbook\forms.py:222 .\cookbook\views\edit.py:117
msgid "Storage"
msgstr "Opslag"
#: .\cookbook\forms.py:222
msgid "Active"
msgstr "Actief"
#: .\cookbook\forms.py:226
msgid "Search String"
msgstr "Zoekopdracht"
#: .\cookbook\forms.py:246
msgid "File ID"
msgstr "Bestands ID"
#: .\cookbook\forms.py:262
msgid "Maximum number of users for this space reached."
msgstr "Maximum aantal gebruikers voor deze ruimte bereikt."
#: .\cookbook\forms.py:268
msgid "Email address already taken!"
msgstr "E-mailadres reeds in gebruik!"
#: .\cookbook\forms.py:275
msgid ""
"An email address is not required but if present the invite link will be sent "
"to the user."
msgstr ""
"Een e-mailadres is niet vereist, maar indien aanwezig zal de "
"uitnodigingslink naar de gebruiker worden gestuurd."
#: .\cookbook\forms.py:287
msgid "Name already taken."
msgstr "Naam reeds in gebruik."
#: .\cookbook\forms.py:298
msgid "Accept Terms and Privacy"
msgstr "Accepteer voorwaarden"
#: .\cookbook\forms.py:332
msgid ""
"Determines how fuzzy a search is if it uses trigram similarity matching (e."
"g. low values mean more typos are ignored)."
msgstr ""
"Bepaalt hoe 'fuzzy' een zoekopdracht is als het trigram vergelijken gebruikt "
"(lage waarden betekenen bijvoorbeeld dat meer typefouten genegeerd worden)."
#: .\cookbook\forms.py:340
msgid ""
"Select type method of search. Click here for "
"full description of choices."
msgstr ""
"Selecteer zoekmethode. Klik hier voor een "
"beschrijving van de keuzes."
#: .\cookbook\forms.py:341
msgid ""
"Use fuzzy matching on units, keywords and ingredients when editing and "
"importing recipes."
msgstr ""
"Gebruik 'fuzzy' koppelen bij eenheden, etiketten en ingrediënten bij "
"bewerken en importeren van recepten."
#: .\cookbook\forms.py:342
msgid ""
"Fields to search ignoring accents. Selecting this option can improve or "
"degrade search quality depending on language"
msgstr ""
"Velden doorzoeken waarbij accenten genegeerd worden. Het selecteren van "
"deze optie kan de zoekkwaliteit afhankelijk van de taal, zowel verbeteren "
"als verslechteren"
#: .\cookbook\forms.py:343
msgid ""
"Fields to search for partial matches. (e.g. searching for 'Pie' will return "
"'pie' and 'piece' and 'soapie')"
msgstr ""
"Velden doorzoeken op gedeelde overeenkomsten. (zoeken op 'Appel' vindt "
"'appel', 'aardappel' en 'appelsap')"
#: .\cookbook\forms.py:344
msgid ""
"Fields to search for beginning of word matches. (e.g. searching for 'sa' "
"will return 'salad' and 'sandwich')"
msgstr ""
"Velden doorzoeken op overeenkomsten aan het begin van het woord. (zoeken op "
"'sa' vindt 'salade' en 'sandwich')"
#: .\cookbook\forms.py:345
msgid ""
"Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) "
"Note: this option will conflict with 'web' and 'raw' methods of search."
msgstr ""
"Velden 'fuzzy' doorzoeken. (zoeken op 'recetp' vindt ook 'recept') Noot: "
"deze optie conflicteert met de zoekmethoden 'web' en 'raw'."
#: .\cookbook\forms.py:346
msgid ""
"Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods "
"only function with fulltext fields."
msgstr ""
"Velden doorzoeken op volledige tekst. Noot: Web, Zin en Raw zoekmethoden "
"werken alleen met volledige tekstvelden."
#: .\cookbook\forms.py:350
msgid "Search Method"
msgstr "Zoekmethode"
#: .\cookbook\forms.py:350
msgid "Fuzzy Lookups"
msgstr "'Fuzzy' zoekopdrachten"
#: .\cookbook\forms.py:350
msgid "Ignore Accent"
msgstr "Negeer accent"
#: .\cookbook\forms.py:350
msgid "Partial Match"
msgstr "Gedeeltelijke overeenkomst"
#: .\cookbook\forms.py:350
msgid "Starts With"
msgstr "Begint met"
#: .\cookbook\forms.py:351
msgid "Fuzzy Search"
msgstr "'Fuzzy' zoeken"
#: .\cookbook\forms.py:351
msgid "Full Text"
msgstr "Volledige tekst"
#: .\cookbook\helper\AllAuthCustomAdapter.py:41
msgid ""
"In order to prevent spam, the requested email was not send. Please wait a "
"few minutes and try again."
msgstr ""
"Om spam te voorkomen werd de gevraagde e-mail niet verzonden. Wacht een paar "
"minuten en probeer het opnieuw."
#: .\cookbook\helper\permission_helper.py:164
#: .\cookbook\helper\permission_helper.py:187 .\cookbook\views\views.py:117
msgid "You are not logged in and therefore cannot view this page!"
msgstr "Je bent niet ingelogd en kan deze pagina daarom niet bekijken!"
#: .\cookbook\helper\permission_helper.py:168
#: .\cookbook\helper\permission_helper.py:174
#: .\cookbook\helper\permission_helper.py:199
#: .\cookbook\helper\permission_helper.py:266
#: .\cookbook\helper\permission_helper.py:280
#: .\cookbook\helper\permission_helper.py:291
#: .\cookbook\helper\permission_helper.py:302
#: .\cookbook\helper\permission_helper.py:318
#: .\cookbook\helper\permission_helper.py:341 .\cookbook\views\data.py:35
#: .\cookbook\views\views.py:127 .\cookbook\views\views.py:131
msgid "You do not have the required permissions to view this page!"
msgstr "Je hebt niet de benodigde machtigingen om deze pagina te bekijken!"
#: .\cookbook\helper\permission_helper.py:192
#: .\cookbook\helper\permission_helper.py:215
#: .\cookbook\helper\permission_helper.py:237
#: .\cookbook\helper\permission_helper.py:252
msgid "You cannot interact with this object as it is not owned by you!"
msgstr ""
"Interactie met dit object is niet mogelijk omdat je niet de eigenaar bent!"
#: .\cookbook\helper\permission_helper.py:402
msgid "You have reached the maximum number of recipes for your space."
msgstr "Je hebt het maximaal aantal recepten voor jouw ruimte bereikt."
#: .\cookbook\helper\permission_helper.py:414
msgid "You have more users than allowed in your space."
msgstr "Je hebt meer gebruikers dan toegestaan in jouw ruimte."
#: .\cookbook\helper\recipe_url_import.py:310
msgid "reverse rotation"
msgstr "omgekeerde rotatie"
#: .\cookbook\helper\recipe_url_import.py:311
msgid "careful rotation"
msgstr "rotire atentă"
#: .\cookbook\helper\recipe_url_import.py:312
msgid "knead"
msgstr "kneden"
#: .\cookbook\helper\recipe_url_import.py:313
msgid "thicken"
msgstr "verdikken"
#: .\cookbook\helper\recipe_url_import.py:314
msgid "warm up"
msgstr "opwarmen"
#: .\cookbook\helper\recipe_url_import.py:315
msgid "ferment"
msgstr "gisten"
#: .\cookbook\helper\recipe_url_import.py:316
msgid "sous-vide"
msgstr "sous-vide (vacuümgaren)"
#: .\cookbook\helper\shopping_helper.py:150
msgid "You must supply a servings size"
msgstr "Je moet een portiegrootte aanleveren"
#: .\cookbook\helper\template_helper.py:95
#: .\cookbook\helper\template_helper.py:97
msgid "Could not parse template code."
msgstr "Sjablooncode kon niet verwerkt worden."
#: .\cookbook\integration\copymethat.py:44
#: .\cookbook\integration\melarecipes.py:37
msgid "Favorite"
msgstr "Favoriet"
#: .\cookbook\integration\copymethat.py:50
msgid "I made this"
msgstr "Ik heb dit gemaakt"
#: .\cookbook\integration\integration.py:209
msgid ""
"Importer expected a .zip file. Did you choose the correct importer type for "
"your data ?"
msgstr ""
"De importtool verwachtte een .zip bestand. Heb je het juiste type gekozen?"
#: .\cookbook\integration\integration.py:212
msgid ""
"An unexpected error occurred during the import. Please make sure you have "
"uploaded a valid file."
msgstr ""
"Er is een onverwachte fout opgetreden tijdens het importeren. Controleer of "
"u een geldig bestand hebt geüpload."
#: .\cookbook\integration\integration.py:217
msgid "The following recipes were ignored because they already existed:"
msgstr "De volgende recepten zijn genegeerd omdat ze al bestonden:"
#: .\cookbook\integration\integration.py:221
#, python-format
msgid "Imported %s recipes."
msgstr "%s recepten geïmporteerd."
#: .\cookbook\integration\openeats.py:28
msgid "Recipe source:"
msgstr "Bron van het recept:"
#: .\cookbook\integration\paprika.py:49
msgid "Notes"
msgstr "Notities"
#: .\cookbook\integration\paprika.py:52
msgid "Nutritional Information"
msgstr "Voedingswaarde"
#: .\cookbook\integration\paprika.py:56
msgid "Source"
msgstr "Bron"
#: .\cookbook\integration\recettetek.py:54
#: .\cookbook\integration\recipekeeper.py:70
msgid "Imported from"
msgstr "Geïmporteerd van"
#: .\cookbook\integration\saffron.py:23
msgid "Servings"
msgstr "Porties"
#: .\cookbook\integration\saffron.py:25
msgid "Waiting time"
msgstr "Wachttijd"
#: .\cookbook\integration\saffron.py:27
msgid "Preparation Time"
msgstr "Bereidingstijd"
#: .\cookbook\integration\saffron.py:29 .\cookbook\templates\index.html:7
msgid "Cookbook"
msgstr "Kookboek"
#: .\cookbook\integration\saffron.py:31
msgid "Section"
msgstr "Sectie"
#: .\cookbook\management\commands\fix_duplicate_properties.py:15
msgid "Fixes foods with "
msgstr "Repareer voedingsmiddelen met "
#: .\cookbook\management\commands\rebuildindex.py:14
msgid "Rebuilds full text search index on Recipe"
msgstr "Herbouwt de volledige tekst zoekindex van Recept"
#: .\cookbook\management\commands\rebuildindex.py:18
msgid "Only Postgresql databases use full text search, no index to rebuild"
msgstr ""
"Alleen Postgresql databases gebruiken volledige tekst zoekmethoden, geen "
"index aanwezig om te herbouwen"
#: .\cookbook\management\commands\rebuildindex.py:29
msgid "Recipe index rebuild complete."
msgstr "Recept index herbouw afgerond."
#: .\cookbook\management\commands\rebuildindex.py:31
msgid "Recipe index rebuild failed."
msgstr "Recept index herbouw mislukt."
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
msgid "Breakfast"
msgstr "Ontbijt"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:19
msgid "Lunch"
msgstr "Lunch"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:24
msgid "Dinner"
msgstr "Avondeten"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:29 .\cookbook\models.py:919
msgid "Other"
msgstr "Overige"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
msgid "Fat"
msgstr "Vet"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "g"
msgstr "g"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
msgid "Carbohydrates"
msgstr "Koolhydraten"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "Proteins"
msgstr "Eiwitten"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "Calories"
msgstr "Calorieën"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "kcal"
msgstr "kcal"
#: .\cookbook\models.py:325
msgid ""
"Maximum file storage for space in MB. 0 for unlimited, -1 to disable file "
"upload."
msgstr ""
"Maximale bestandsopslag voor ruimte in MB. 0 voor onbeperkt, -1 om uploaden "
"van bestanden uit te schakelen."
#: .\cookbook\models.py:454 .\cookbook\templates\search.html:7
#: .\cookbook\templates\settings.html:18
msgid "Search"
msgstr "Zoeken"
#: .\cookbook\models.py:455 .\cookbook\templates\base.html:114
#: .\cookbook\templates\meal_plan.html:7
msgid "Meal-Plan"
msgstr "Maaltijdplan"
#: .\cookbook\models.py:456 .\cookbook\templates\base.html:122
#: .\cookbook\views\views.py:459
msgid "Books"
msgstr "Kookboeken"
#: .\cookbook\models.py:457 .\cookbook\templates\base.html:118
#: .\cookbook\views\views.py:460
msgid "Shopping"
msgstr "Winkelen"
#: .\cookbook\models.py:752
msgid " is part of a recipe step and cannot be deleted"
msgstr " is deel van een receptstap en kan niet verwijderd worden"
#: .\cookbook\models.py:918
msgid "Nutrition"
msgstr "Voedingswaarde"
#: .\cookbook\models.py:918
msgid "Allergen"
msgstr "Allergeen"
#: .\cookbook\models.py:919
msgid "Price"
msgstr "Prijs"
#: .\cookbook\models.py:919
msgid "Goal"
msgstr "Doel"
#: .\cookbook\models.py:1408 .\cookbook\templates\search_info.html:28
msgid "Simple"
msgstr "Simpel"
#: .\cookbook\models.py:1409 .\cookbook\templates\search_info.html:33
msgid "Phrase"
msgstr "Zin"
#: .\cookbook\models.py:1410 .\cookbook\templates\search_info.html:38
msgid "Web"
msgstr "Web"
#: .\cookbook\models.py:1411 .\cookbook\templates\search_info.html:47
msgid "Raw"
msgstr "Rauw"
#: .\cookbook\models.py:1467
msgid "Food Alias"
msgstr "Ingrediënt alias"
#: .\cookbook\models.py:1468
msgid "Unit Alias"
msgstr "Eenheid alias"
#: .\cookbook\models.py:1469
msgid "Keyword Alias"
msgstr "Etiket alias"
#: .\cookbook\models.py:1470
msgid "Description Replace"
msgstr "Verrvang beschrijving"
#: .\cookbook\models.py:1471
msgid "Instruction Replace"
msgstr "Vervang instructies"
#: .\cookbook\models.py:1472
msgid "Never Unit"
msgstr "Nooit eenheid"
#: .\cookbook\models.py:1473
msgid "Transpose Words"
msgstr "Omzetten Woorden"
#: .\cookbook\models.py:1474
msgid "Food Replace"
msgstr "Voedingsmiddelen vervangen"
#: .\cookbook\models.py:1475
msgid "Unit Replace"
msgstr "Eenheid Vervangen"
#: .\cookbook\models.py:1476
msgid "Name Replace"
msgstr "Naam Vervangen"
#: .\cookbook\models.py:1503 .\cookbook\views\delete.py:40
#: .\cookbook\views\edit.py:210 .\cookbook\views\new.py:39
msgid "Recipe"
msgstr "Recept"
#: .\cookbook\models.py:1504
msgid "Food"
msgstr "Ingrediënt"
#: .\cookbook\models.py:1505 .\cookbook\templates\base.html:149
msgid "Keyword"
msgstr "Trefwoord"
#: .\cookbook\serializer.py:222
msgid "File uploads are not enabled for this Space."
msgstr "Bestandsuploads zijn niet ingeschakeld voor deze Ruimte."
#: .\cookbook\serializer.py:233
msgid "You have reached your file upload limit."
msgstr "U heeft de uploadlimiet bereikt."
#: .\cookbook\serializer.py:328
msgid "Cannot modify Space owner permission."
msgstr "Kan de rechten van de ruimte-eigenaar niet wijzigen."
#: .\cookbook\serializer.py:1270
msgid "Hello"
msgstr "Hallo"
#: .\cookbook\serializer.py:1270
msgid "You have been invited by "
msgstr "Je bent uitgenodigd door "
#: .\cookbook\serializer.py:1272
msgid " to join their Tandoor Recipes space "
msgstr " om zijn/haar Tandoor Recepten ruimte "
#: .\cookbook\serializer.py:1274
msgid "Click the following link to activate your account: "
msgstr "Klik om de volgende link om je account te activeren: "
#: .\cookbook\serializer.py:1276
msgid ""
"If the link does not work use the following code to manually join the space: "
msgstr ""
"Als de linkt niet werkt, gebruik dan de volgende code om handmatig tot de "
"ruimte toe te treden: "
#: .\cookbook\serializer.py:1278
msgid "The invitation is valid until "
msgstr "De uitnodiging is geldig tot "
#: .\cookbook\serializer.py:1280
msgid ""
"Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub "
msgstr ""
"Tandoor Recepten is een Open Source recepten manager. Bekijk het op GitHub "
#: .\cookbook\serializer.py:1283
msgid "Tandoor Recipes Invite"
msgstr "Tandoor Recepten uitnodiging"
#: .\cookbook\serializer.py:1426
msgid "Existing shopping list to update"
msgstr "Bestaande boodschappenlijst is bijgewerkt"
#: .\cookbook\serializer.py:1428
msgid ""
"List of ingredient IDs from the recipe to add, if not provided all "
"ingredients will be added."
msgstr ""
"Lijst van ingrediënt ID's van het toe te voegen recept, als deze niet worden "
"opgegeven worden alle ingrediënten toegevoegd."
#: .\cookbook\serializer.py:1430
msgid ""
"Providing a list_recipe ID and servings of 0 will delete that shopping list."
msgstr ""
"Als je een list_recipe ID en portiegrootte van 0 opgeeft wordt dat "
"boodschappenlijstje verwijderd."
#: .\cookbook\serializer.py:1439
msgid "Amount of food to add to the shopping list"
msgstr "Hoeveelheid eten om aan het boodschappenlijstje toe te voegen"
#: .\cookbook\serializer.py:1441
msgid "ID of unit to use for the shopping list"
msgstr "ID of eenheid om te gebruik voor het boodschappenlijstje"
#: .\cookbook\serializer.py:1443
msgid "When set to true will delete all food from active shopping lists."
msgstr ""
"Wanneer ingesteld op waar, wordt al het voedsel van actieve "
"boodschappenlijstjes verwijderd."
#: .\cookbook\tables.py:69 .\cookbook\tables.py:83
#: .\cookbook\templates\generic\delete_template.html:7
#: .\cookbook\templates\generic\delete_template.html:15
#: .\cookbook\templates\generic\edit_template.html:28
msgid "Delete"
msgstr "Verwijder"
#: .\cookbook\templates\404.html:5
msgid "404 Error"
msgstr "404 Foutmelding"
#: .\cookbook\templates\404.html:18
msgid "The page you are looking for could not be found."
msgstr "De opgevraagde pagina kon niet gevonden worden."
#: .\cookbook\templates\404.html:33
msgid "Take me Home"
msgstr "Breng me Thuis"
#: .\cookbook\templates\404.html:35
msgid "Report a Bug"
msgstr "Rapporteer een bug"
#: .\cookbook\templates\account\email.html:6
#: .\cookbook\templates\account\email.html:17
msgid "E-mail Addresses"
msgstr "E-mailadressen"
#: .\cookbook\templates\account\email.html:12
#: .\cookbook\templates\account\password_change.html:11
#: .\cookbook\templates\account\password_set.html:11
#: .\cookbook\templates\base.html:331 .\cookbook\templates\settings.html:6
#: .\cookbook\templates\settings.html:17
#: .\cookbook\templates\socialaccount\connections.html:10
#: .\cookbook\templates\user_settings.html:8
msgid "Settings"
msgstr "Instellingen"
#: .\cookbook\templates\account\email.html:13
msgid "Email"
msgstr "E-mail"
#: .\cookbook\templates\account\email.html:19
msgid "The following e-mail addresses are associated with your account:"
msgstr "De volgende e-mailadressen zijn aan uw account gekoppeld:"
#: .\cookbook\templates\account\email.html:36
msgid "Verified"
msgstr "Geverifieerd"
#: .\cookbook\templates\account\email.html:38
msgid "Unverified"
msgstr "Ongeverifieerd"
#: .\cookbook\templates\account\email.html:40
msgid "Primary"
msgstr "Primair"
#: .\cookbook\templates\account\email.html:47
msgid "Make Primary"
msgstr "Stel in als eerste"
#: .\cookbook\templates\account\email.html:49
msgid "Re-send Verification"
msgstr "Verificatie opnieuw verzenden"
#: .\cookbook\templates\account\email.html:50
#: .\cookbook\templates\generic\delete_template.html:57
#: .\cookbook\templates\socialaccount\connections.html:44
msgid "Remove"
msgstr "Verwijder"
#: .\cookbook\templates\account\email.html:58
msgid "Warning:"
msgstr "Waarschuwing:"
#: .\cookbook\templates\account\email.html:58
msgid ""
"You currently do not have any e-mail address set up. You should really add "
"an e-mail address so you can receive notifications, reset your password, etc."
msgstr ""
"U hebt momenteel geen e-mailadres ingesteld. U zou een e-mailadres moeten "
"toevoegen zodat u meldingen kunt ontvangen, uw wachtwoord kunt resetten, enz."
#: .\cookbook\templates\account\email.html:64
msgid "Add E-mail Address"
msgstr "E-mailadres toevoegen"
#: .\cookbook\templates\account\email.html:69
msgid "Add E-mail"
msgstr "E-mail toevoegen"
#: .\cookbook\templates\account\email.html:79
msgid "Do you really want to remove the selected e-mail address?"
msgstr "Wilt u het geselecteerde e-mailadres echt verwijderen?"
#: .\cookbook\templates\account\email_confirm.html:6
#: .\cookbook\templates\account\email_confirm.html:10
msgid "Confirm E-mail Address"
msgstr "Bevestig e-mailadres"
#: .\cookbook\templates\account\email_confirm.html:16
#, python-format
msgid ""
"Please confirm that\n"
" %(email)s is an e-mail address "
"for user %(user_display)s\n"
" ."
msgstr ""
"Bevestig dat\n"
" %(email)s een e-mailadres is voor "
"gebruiker %(user_display)s\n"
" ."
#: .\cookbook\templates\account\email_confirm.html:22
#: .\cookbook\templates\generic\delete_template.html:72
msgid "Confirm"
msgstr "Bevestig"
#: .\cookbook\templates\account\email_confirm.html:29
#, python-format
msgid ""
"This e-mail confirmation link expired or is invalid. Please\n"
" issue a new e-mail confirmation "
"request."
msgstr ""
"Deze e-mail bevestigingslink is verlopen of ongeldig.\n"
"Vraag een nieuwe bevestigingslink aan."
#: .\cookbook\templates\account\login.html:8 .\cookbook\templates\base.html:388
#: .\cookbook\templates\openid\login.html:8
msgid "Login"
msgstr "Inloggen"
#: .\cookbook\templates\account\login.html:15
#: .\cookbook\templates\account\login.html:31
#: .\cookbook\templates\account\password_reset.html:39
#: .\cookbook\templates\account\password_reset_done.html:31
#: .\cookbook\templates\account\signup.html:69
#: .\cookbook\templates\account\signup_closed.html:15
#: .\cookbook\templates\openid\login.html:15
#: .\cookbook\templates\openid\login.html:26
#: .\cookbook\templates\socialaccount\authentication_error.html:15
msgid "Sign In"
msgstr "Log in"
#: .\cookbook\templates\account\login.html:34
#: .\cookbook\templates\account\password_reset.html:41
#: .\cookbook\templates\account\password_reset_done.html:33
#: .\cookbook\templates\socialaccount\signup.html:8
#: .\cookbook\templates\socialaccount\signup.html:57
msgid "Sign Up"
msgstr "Registreer"
#: .\cookbook\templates\account\login.html:38
msgid "Lost your password?"
msgstr "Wachtwoord vergeten?"
#: .\cookbook\templates\account\login.html:39
#: .\cookbook\templates\account\password_reset.html:29
msgid "Reset My Password"
msgstr "Reset wachtwoord"
#: .\cookbook\templates\account\login.html:50
msgid "Social Login"
msgstr "Socials login"
#: .\cookbook\templates\account\login.html:51
msgid "You can use any of the following providers to sign in."
msgstr "Je kan een van de volgende providers gebruiken om in te loggen."
#: .\cookbook\templates\account\logout.html:5
#: .\cookbook\templates\account\logout.html:9
#: .\cookbook\templates\account\logout.html:18
msgid "Sign Out"
msgstr "Log uit"
#: .\cookbook\templates\account\logout.html:11
msgid "Are you sure you want to sign out?"
msgstr "Weet je zeker dat je uit wil loggen?"
#: .\cookbook\templates\account\password_change.html:6
#: .\cookbook\templates\account\password_change.html:16
#: .\cookbook\templates\account\password_change.html:21
#: .\cookbook\templates\account\password_reset_from_key.html:7
#: .\cookbook\templates\account\password_reset_from_key.html:13
#: .\cookbook\templates\account\password_reset_from_key_done.html:7
#: .\cookbook\templates\account\password_reset_from_key_done.html:13
msgid "Change Password"
msgstr "Wijzig wachtwoord"
#: .\cookbook\templates\account\password_change.html:12
#: .\cookbook\templates\account\password_set.html:12
msgid "Password"
msgstr "Wachtwoord"
#: .\cookbook\templates\account\password_change.html:22
msgid "Forgot Password?"
msgstr "Wachtwoord vergeten?"
#: .\cookbook\templates\account\password_reset.html:7
#: .\cookbook\templates\account\password_reset.html:13
#: .\cookbook\templates\account\password_reset_done.html:7
#: .\cookbook\templates\account\password_reset_done.html:18
msgid "Password Reset"
msgstr "Wachtwoord reset"
#: .\cookbook\templates\account\password_reset.html:24
msgid ""
"Forgotten your password? Enter your e-mail address below, and we'll send you "
"an e-mail allowing you to reset it."
msgstr ""
"Wachtwoord vergeten? Vul je e-mail adres in en er wordt een e-mail link "
"toegestuurd waarmee je hem kan resetten."
#: .\cookbook\templates\account\password_reset.html:32
msgid "Password reset is disabled on this instance."
msgstr "Wachtwoord reset is gedeactiveerd op deze server."
#: .\cookbook\templates\account\password_reset_done.html:25
msgid ""
"We have sent you an e-mail. Please contact us if you do not receive it "
"within a few minutes."
msgstr ""
"Er is een e-mail verstuurd. Neem contact op als je deze niet binnen een paar "
"minuten ontvangen hebt."
#: .\cookbook\templates\account\password_reset_from_key.html:13
msgid "Bad Token"
msgstr "Bad token"
#: .\cookbook\templates\account\password_reset_from_key.html:25
#, python-format
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used.\n"
" Please request a new "
"password reset."
msgstr ""
"De link voor het opnieuw instellen van het wachtwoord was ongeldig, mogelijk "
"omdat hij al gebruikt is.\n"
" Vraag een nieuwe link "
"voor een wachtwoord reset aan."
#: .\cookbook\templates\account\password_reset_from_key.html:33
msgid "change password"
msgstr "wijzig wachtwoord"
#: .\cookbook\templates\account\password_reset_from_key.html:36
#: .\cookbook\templates\account\password_reset_from_key_done.html:19
msgid "Your password is now changed."
msgstr "Je wachtwoord is nu gewijzigd."
#: .\cookbook\templates\account\password_set.html:6
#: .\cookbook\templates\account\password_set.html:16
#: .\cookbook\templates\account\password_set.html:21
msgid "Set Password"
msgstr "Stel een wachtwoord in"
#: .\cookbook\templates\account\signup.html:6
msgid "Register"
msgstr "Registreer"
#: .\cookbook\templates\account\signup.html:12
msgid "Create an Account"
msgstr "Maak een account aan"
#: .\cookbook\templates\account\signup.html:42
#: .\cookbook\templates\socialaccount\signup.html:33
msgid "I accept the follwoing"
msgstr "Ik accepteer het volgende"
#: .\cookbook\templates\account\signup.html:45
#: .\cookbook\templates\socialaccount\signup.html:36
msgid "Terms and Conditions"
msgstr "Voorwaarden"
#: .\cookbook\templates\account\signup.html:48
#: .\cookbook\templates\socialaccount\signup.html:39
msgid "and"
msgstr "en"
#: .\cookbook\templates\account\signup.html:52
#: .\cookbook\templates\socialaccount\signup.html:43
msgid "Privacy Policy"
msgstr "Privacybeleid"
#: .\cookbook\templates\account\signup.html:65
msgid "Create User"
msgstr "Maak gebruiker aan"
#: .\cookbook\templates\account\signup.html:69
msgid "Already have an account?"
msgstr "Heb je al een account?"
#: .\cookbook\templates\account\signup_closed.html:5
#: .\cookbook\templates\account\signup_closed.html:11
msgid "Sign Up Closed"
msgstr "Registratie gesloten"
#: .\cookbook\templates\account\signup_closed.html:13
msgid "We are sorry, but the sign up is currently closed."
msgstr "Excuses, registratie is op dit moment gesloten."
#: .\cookbook\templates\api_info.html:5 .\cookbook\templates\base.html:378
#: .\cookbook\templates\rest_framework\api.html:11
msgid "API Documentation"
msgstr "API documentatie"
#: .\cookbook\templates\base.html:110 .\cookbook\templates\index.html:87
msgid "Recipes"
msgstr "Recepten"
#: .\cookbook\templates\base.html:161 .\cookbook\views\lists.py:120
msgid "Foods"
msgstr "Ingrediënten"
#: .\cookbook\templates\base.html:173 .\cookbook\views\lists.py:137
msgid "Units"
msgstr "Eenheden"
#: .\cookbook\templates\base.html:187
msgid "Supermarket"
msgstr "Supermarkt"
#: .\cookbook\templates\base.html:199
msgid "Supermarket Category"
msgstr "Supermarktcategorie"
#: .\cookbook\templates\base.html:211 .\cookbook\views\lists.py:186
msgid "Automations"
msgstr "Automatiseringen"
#: .\cookbook\templates\base.html:225 .\cookbook\views\lists.py:222
msgid "Files"
msgstr "Bestanden"
#: .\cookbook\templates\base.html:237
msgid "Batch Edit"
msgstr "Batchbewerking"
#: .\cookbook\templates\base.html:249 .\cookbook\templates\history.html:6
#: .\cookbook\templates\history.html:14
msgid "History"
msgstr "Geschiedenis"
#: .\cookbook\templates\base.html:263
#: .\cookbook\templates\ingredient_editor.html:7
#: .\cookbook\templates\ingredient_editor.html:13
msgid "Ingredient Editor"
msgstr "Ingrediënten editor"
#: .\cookbook\templates\base.html:275
#: .\cookbook\templates\export_response.html:7
#: .\cookbook\templates\test2.html:14 .\cookbook\templates\test2.html:20
msgid "Export"
msgstr "Exporteren"
#: .\cookbook\templates\base.html:287
msgid "Properties"
msgstr "Eigenschappen"
#: .\cookbook\templates\base.html:301 .\cookbook\views\lists.py:255
msgid "Unit Conversions"
msgstr "Eenheid omzetten"
#: .\cookbook\templates\base.html:318 .\cookbook\templates\index.html:47
msgid "Import Recipe"
msgstr "Recept importeren"
#: .\cookbook\templates\base.html:320
msgid "Create"
msgstr "Aanmaken"
#: .\cookbook\templates\base.html:333
#: .\cookbook\templates\generic\list_template.html:14
msgid "External Recipes"
msgstr "Externe recepten"
#: .\cookbook\templates\base.html:336 .\cookbook\templates\space_manage.html:15
msgid "Space Settings"
msgstr "Ruimte Instellingen"
#: .\cookbook\templates\base.html:340
msgid "External Connectors"
msgstr "Externe Connectors"
#: .\cookbook\templates\base.html:345 .\cookbook\templates\system.html:13
msgid "System"
msgstr "Systeem"
#: .\cookbook\templates\base.html:347
msgid "Admin"
msgstr "Beheer"
#: .\cookbook\templates\base.html:351
#: .\cookbook\templates\space_overview.html:25
msgid "Your Spaces"
msgstr "Jouw Ruimtes"
#: .\cookbook\templates\base.html:362
#: .\cookbook\templates\space_overview.html:6
msgid "Overview"
msgstr "Overzicht"
#: .\cookbook\templates\base.html:372
msgid "Markdown Guide"
msgstr "Markdown gids"
#: .\cookbook\templates\base.html:374
msgid "GitHub"
msgstr "Github"
#: .\cookbook\templates\base.html:376
msgid "Translate Tandoor"
msgstr "Vertaal Tandoor"
#: .\cookbook\templates\base.html:380
msgid "API Browser"
msgstr "API Browser"
#: .\cookbook\templates\base.html:383
msgid "Log out"
msgstr "Uitloggen"
#: .\cookbook\templates\base.html:406
msgid "You are using the free version of Tandor"
msgstr "Je gebruikt de gratis versie van Tandoor"
#: .\cookbook\templates\base.html:407
msgid "Upgrade Now"
msgstr "Upgrade nu"
#: .\cookbook\templates\batch\edit.html:6
msgid "Batch edit Category"
msgstr "Batch bewerking toepassen op categorie"
#: .\cookbook\templates\batch\edit.html:15
msgid "Batch edit Recipes"
msgstr "Batch bewerking toepassen op recepten"
#: .\cookbook\templates\batch\edit.html:20
msgid "Add the specified keywords to all recipes containing a word"
msgstr ""
"Voeg de gespecificeerde etiketten toe aan alle recepten die een woord "
"bevatten"
#: .\cookbook\templates\batch\monitor.html:6 .\cookbook\views\edit.py:75
msgid "Sync"
msgstr "Synchroniseren"
#: .\cookbook\templates\batch\monitor.html:10
msgid "Manage watched Folders"
msgstr "Gevolgde mappen beheren"
#: .\cookbook\templates\batch\monitor.html:14
msgid ""
"On this Page you can manage all storage folder locations that should be "
"monitored and synced."
msgstr ""
"Op deze pagina kan je alle opslag mappen die gesynchroniseerd en gemonitord "
"worden beheren."
#: .\cookbook\templates\batch\monitor.html:16
msgid "The path must be in the following format"
msgstr "Het pad dient het volgende format te hebben"
#: .\cookbook\templates\batch\monitor.html:20
#: .\cookbook\templates\forms\edit_import_recipe.html:14
#: .\cookbook\templates\generic\edit_template.html:23
#: .\cookbook\templates\generic\new_template.html:23
#: .\cookbook\templates\settings.html:57
msgid "Save"
msgstr "Opslaan"
#: .\cookbook\templates\batch\monitor.html:21
msgid "Manage External Storage"
msgstr "Beheer externe opslag"
#: .\cookbook\templates\batch\monitor.html:28
msgid "Sync Now!"
msgstr "Synchroniseer nu!"
#: .\cookbook\templates\batch\monitor.html:29
msgid "Show Recipes"
msgstr "Toon Recepten"
#: .\cookbook\templates\batch\monitor.html:30
msgid "Show Log"
msgstr "Toon Log"
#: .\cookbook\templates\batch\waiting.html:4
#: .\cookbook\templates\batch\waiting.html:10
msgid "Importing Recipes"
msgstr "Recepten aan het importeren"
#: .\cookbook\templates\batch\waiting.html:28
msgid ""
"This can take a few minutes, depending on the number of recipes in sync, "
"please wait."
msgstr ""
"Dit kan een aantal minuten duren, afhankelijk van het aantal documenten wat "
"op het moment gesynchroniseerd worden. Een ogenblik geduld alstublieft."
#: .\cookbook\templates\books.html:7
msgid "Recipe Books"
msgstr "Kookboeken"
#: .\cookbook\templates\export.html:7 .\cookbook\templates\test2.html:6
msgid "Export Recipes"
msgstr "Recepten exporteren"
#: .\cookbook\templates\forms\edit_import_recipe.html:5
#: .\cookbook\templates\forms\edit_import_recipe.html:9
msgid "Import new Recipe"
msgstr "Nieuw recept importeren"
#: .\cookbook\templates\forms\edit_internal_recipe.html:7
msgid "Edit Recipe"
msgstr "Recept bewerken"
#: .\cookbook\templates\generic\delete_template.html:21
#, python-format
msgid "Are you sure you want to delete the %(title)s: %(object)s "
msgstr "Weet je zeker dat je %(title)s: %(object)s wil verwijderen "
#: .\cookbook\templates\generic\delete_template.html:22
msgid "This cannot be undone!"
msgstr "Dit kan niet ongedaan gemaakt worden!"
#: .\cookbook\templates\generic\delete_template.html:27
msgid "Protected"
msgstr "Beschermd"
#: .\cookbook\templates\generic\delete_template.html:42
msgid "Cascade"
msgstr "Cascade"
#: .\cookbook\templates\generic\delete_template.html:73
msgid "Cancel"
msgstr "Annuleer"
#: .\cookbook\templates\generic\edit_template.html:6
#: .\cookbook\templates\generic\edit_template.html:14
msgid "Edit"
msgstr "Bewerken"
#: .\cookbook\templates\generic\edit_template.html:32
msgid "View"
msgstr "Bekijk"
#: .\cookbook\templates\generic\edit_template.html:36
msgid "Delete original file"
msgstr "Origineel bestand verwijderen"
#: .\cookbook\templates\generic\list_template.html:6
#: .\cookbook\templates\generic\list_template.html:22
msgid "List"
msgstr " "
#: .\cookbook\templates\generic\list_template.html:36
msgid "Filter"
msgstr "Filtreren"
#: .\cookbook\templates\generic\list_template.html:41
msgid "Import all"
msgstr "Alles importeren"
#: .\cookbook\templates\generic\new_template.html:6
#: .\cookbook\templates\generic\new_template.html:14
msgid "New"
msgstr "Nieuw"
#: .\cookbook\templates\generic\table_template.html:76
msgid "previous"
msgstr "vorige"
#: .\cookbook\templates\generic\table_template.html:98
msgid "next"
msgstr "volgende"
#: .\cookbook\templates\history.html:20
msgid "View Log"
msgstr "Logboek bekijken"
#: .\cookbook\templates\history.html:24
msgid "Cook Log"
msgstr "Kook logboek"
#: .\cookbook\templates\import_response.html:7 .\cookbook\views\delete.py:90
#: .\cookbook\views\edit.py:174
msgid "Import"
msgstr "Importeer"
#: .\cookbook\templates\include\storage_backend_warning.html:4
msgid "Security Warning"
msgstr "Veiligheidswaarschuwing"
#: .\cookbook\templates\include\storage_backend_warning.html:5
msgid ""
"\n"
" The Password and Token field are stored as plain text "
"inside the database.\n"
" This is necessary because they are needed to make API requests, but "
"it also increases the risk of\n"
" someone stealing it.
\n"
" To limit the possible damage tokens or accounts with limited access "
"can be used.\n"
" "
msgstr ""
"\n"
" Het wachtwoord en token veld worden als plain text "
"opgeslagen in de database.\n"
" Dit is nodig omdat deze benodigd zijn voor de API requests, Dit verhoogt "
"echter ook het risico van diefstal.
\n"
" Om mogelijke schade te beperken kun je gebruik maken van accounts met "
"gelimiteerde toegang.\n"
" "
#: .\cookbook\templates\index.html:29
msgid "Search recipe ..."
msgstr "Zoek recept ..."
#: .\cookbook\templates\index.html:44
msgid "New Recipe"
msgstr "Nieuw recept"
#: .\cookbook\templates\index.html:53
msgid "Advanced Search"
msgstr "Geavanceerde zoekopdracht"
#: .\cookbook\templates\index.html:57
msgid "Reset Search"
msgstr "Zoekopdracht opnieuw instellen"
#: .\cookbook\templates\index.html:85
msgid "Last viewed"
msgstr "Laatst bekeken"
#: .\cookbook\templates\index.html:94
msgid "Log in to view recipes"
msgstr "Log in om recepten te bekijken"
#: .\cookbook\templates\markdown_info.html:5
#: .\cookbook\templates\markdown_info.html:13
msgid "Markdown Info"
msgstr "Markdown informatie"
#: .\cookbook\templates\markdown_info.html:14
msgid ""
"\n"
" Markdown is lightweight markup language that can be used to format "
"plain text easily.\n"
" This site uses the Python Markdown library to\n"
" convert your text into nice looking HTML. Its full markdown "
"documentation can be found\n"
" here.\n"
" An incomplete but most likely sufficient documentation can be found "
"below.\n"
" "
msgstr ""
"\n"
" Markdown is een lichtgewicht opmaak taal die gebruikt kan worden om "
"tekst eenvoudig op te maken.\n"
" Deze site gebruikt de Python Markdown bibliotheek\n"
" om je tekst in mooi uitziende HTML om te zetten. De volledige "
"documentatie kan \n"
" hiergevonden worden.\n"
" Onvolledige, maar waarschijnlijk voldoende, informatie staat "
"hieronder.\n"
" "
#: .\cookbook\templates\markdown_info.html:25
msgid "Headers"
msgstr "Koppen"
#: .\cookbook\templates\markdown_info.html:54
msgid "Formatting"
msgstr "Opmaak"
#: .\cookbook\templates\markdown_info.html:56
#: .\cookbook\templates\markdown_info.html:72
msgid "Line breaks are inserted by adding two spaces after the end of a line"
msgstr ""
"Regeleindes worden toegevoegd door een regel te eindigen met twee spaties"
#: .\cookbook\templates\markdown_info.html:57
#: .\cookbook\templates\markdown_info.html:73
msgid "or by leaving a blank line in between."
msgstr "of door een witregel te gebruiken."
#: .\cookbook\templates\markdown_info.html:59
#: .\cookbook\templates\markdown_info.html:74
msgid "This text is bold"
msgstr "Deze tekst is dikgedrukt"
#: .\cookbook\templates\markdown_info.html:60
#: .\cookbook\templates\markdown_info.html:75
msgid "This text is italic"
msgstr "Deze tekst is cursief"
#: .\cookbook\templates\markdown_info.html:61
#: .\cookbook\templates\markdown_info.html:77
msgid "Blockquotes are also possible"
msgstr "Aanhalingstekens zijn ook mogelijk"
#: .\cookbook\templates\markdown_info.html:84
msgid "Lists"
msgstr "Lijsten"
#: .\cookbook\templates\markdown_info.html:85
msgid ""
"Lists can ordered or unordered. It is important to leave a blank line "
"before the list!"
msgstr ""
"Lijsten zijn geordend en ongeordend mogelijk. Het is belangrijk om een "
"lege regel voor de lijst te behouden!"
#: .\cookbook\templates\markdown_info.html:87
#: .\cookbook\templates\markdown_info.html:108
msgid "Ordered List"
msgstr "Geordende lijst"
#: .\cookbook\templates\markdown_info.html:89
#: .\cookbook\templates\markdown_info.html:90
#: .\cookbook\templates\markdown_info.html:91
#: .\cookbook\templates\markdown_info.html:110
#: .\cookbook\templates\markdown_info.html:111
#: .\cookbook\templates\markdown_info.html:112
msgid "unordered list item"
msgstr "Ongeordende lijstitem"
#: .\cookbook\templates\markdown_info.html:93
#: .\cookbook\templates\markdown_info.html:114
msgid "Unordered List"
msgstr "Ongeordende lijst"
#: .\cookbook\templates\markdown_info.html:95
#: .\cookbook\templates\markdown_info.html:96
#: .\cookbook\templates\markdown_info.html:97
#: .\cookbook\templates\markdown_info.html:116
#: .\cookbook\templates\markdown_info.html:117
#: .\cookbook\templates\markdown_info.html:118
msgid "ordered list item"
msgstr "Geordende lijstitem"
#: .\cookbook\templates\markdown_info.html:125
msgid "Images & Links"
msgstr "Afbeeldingen & Links"
#: .\cookbook\templates\markdown_info.html:126
msgid ""
"Links can be formatted with Markdown. This application also allows to paste "
"links directly into markdown fields without any formatting."
msgstr ""
"Links kunnen opgemaakt worden met Markdown. De applicatie laat het ook toe "
"om links direct te plakken zonder opmaak."
#: .\cookbook\templates\markdown_info.html:132
#: .\cookbook\templates\markdown_info.html:145
msgid "This will become an image"
msgstr "Dit wordt een afbeelding"
#: .\cookbook\templates\markdown_info.html:152
msgid "Tables"
msgstr "Tabellen"
#: .\cookbook\templates\markdown_info.html:153
msgid ""
"Markdown tables are hard to create by hand. It is recommended to use a table "
"editor like this one."
msgstr ""
"Het is lastig om met de hand Markdown tabellen te maken. Het wordt "
"aangeraden om een tabel 'editor' zoals deze "
"te gebruiken."
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:171
#: .\cookbook\templates\markdown_info.html:177
msgid "Table"
msgstr "Tabel"
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:172
msgid "Header"
msgstr "Kop"
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:178
msgid "Cell"
msgstr "Cel"
#: .\cookbook\templates\no_groups_info.html:5
#: .\cookbook\templates\no_groups_info.html:12
msgid "No Permissions"
msgstr "Geen rechten"
#: .\cookbook\templates\no_groups_info.html:17
msgid "You do not have any groups and therefor cannot use this application."
msgstr "Je hebt geen groepen en kan daarom deze applicatie niet gebruiken."
#: .\cookbook\templates\no_groups_info.html:18
#: .\cookbook\templates\no_perm_info.html:15
msgid "Please contact your administrator."
msgstr "Neem contact op met je beheerder."
#: .\cookbook\templates\no_perm_info.html:5
#: .\cookbook\templates\no_perm_info.html:12
msgid "No Permission"
msgstr "Geen rechten"
#: .\cookbook\templates\no_perm_info.html:15
msgid ""
"You do not have the required permissions to view this page or perform this "
"action."
msgstr ""
"Je beschikt niet over de juiste rechten om deze pagina te bekijken of deze "
"actie uit te voeren."
#: .\cookbook\templates\offline.html:6
msgid "Offline"
msgstr "Offline"
#: .\cookbook\templates\offline.html:19
msgid "You are currently offline!"
msgstr "Je bent op dit moment offline!"
#: .\cookbook\templates\offline.html:20
msgid ""
"The recipes listed below are available for offline viewing because you have "
"recently viewed them. Keep in mind that data might be outdated."
msgstr ""
"De recepten hieronder zijn beschikbaar om offline te bekijken omdat je ze "
"recent bekeken hebt. Houd er rekening mee dat de data mogelijk verouderd is."
#: .\cookbook\templates\openid\login.html:27
#: .\cookbook\templates\socialaccount\authentication_error.html:27
msgid "Back"
msgstr "Terug"
#: .\cookbook\templates\property_editor.html:7
msgid "Property Editor"
msgstr "Eigenschappen Editor"
#: .\cookbook\templates\recipe_view.html:36
msgid "Comments"
msgstr "Opmerkingen"
#: .\cookbook\templates\recipe_view.html:41
msgid "by"
msgstr "door"
#: .\cookbook\templates\recipe_view.html:59 .\cookbook\views\delete.py:146
#: .\cookbook\views\edit.py:156
msgid "Comment"
msgstr "Opmerking"
#: .\cookbook\templates\rest_framework\api.html:5
msgid "Recipe Home"
msgstr "Recept thuis"
#: .\cookbook\templates\search_info.html:5
#: .\cookbook\templates\search_info.html:9
#: .\cookbook\templates\settings.html:24
msgid "Search Settings"
msgstr "Zoekinstellingen"
#: .\cookbook\templates\search_info.html:10
msgid ""
"\n"
" Creating the best search experience is complicated and weighs "
"heavily on your personal configuration. \n"
" Changing any of the search settings can have significant impact on "
"the speed and quality of the results.\n"
" Search Methods, Trigrams and Full Text Search configurations are "
"only available if you are using Postgres for your database.\n"
" "
msgstr ""
"\n"
" Het maken van de beste zoekervaring is gecompliceerd en sterk "
"afhankelijk van je persoonlijke configuratie. \n"
" Het aanpassen van de zoekinstellingen kan een significante impact op "
"de snelheid en kwaliteit van de resultaten hebben.\n"
" Zoekmethoden Trigram en Volledige tekst zoeken zijn alleen "
"beschikbaar wanneer je Postgress als database gebruikt.\n"
" "
#: .\cookbook\templates\search_info.html:19
msgid "Search Methods"
msgstr "Zoekmethoden"
#: .\cookbook\templates\search_info.html:23
msgid ""
" \n"
" Full text searches attempt to normalize the words provided to "
"match common variants. For example: 'forked', 'forking', 'forks' will all "
"normalize to 'fork'.\n"
" There are several methods available, described below, that will "
"control how the search behavior should react when multiple words are "
"searched.\n"
" Full technical details on how these operate can be viewed on Postgresql's website.\n"
" "
msgstr ""
" \n"
" Volledige tekst zoeken probeert de woorden te normaliseren om "
"ook varianten te vinden. Bijvoorbeeld: 'appel' en 'appels' worden beiden "
"genormaliseerd naar 'appel'.\n"
" Er zijn verschillende zoekmethoden beschikbaar, hier beneden "
"beschreven, die het zoekgedrag bepalen wanneer er naar meerdere woorden "
"gezocht wordt.\n"
" Volledige technische details kunnen bekene worden op Postgresql's website.\n"
" "
#: .\cookbook\templates\search_info.html:29
msgid ""
" \n"
" Simple searches ignore punctuation and common words such as "
"'the', 'a', 'and'. And will treat separate words as required.\n"
" Searching for 'apple or flour' will return any recipe that "
"includes both 'apple' and 'flour' anywhere in the fields that have been "
"selected for a full text search.\n"
" "
msgstr ""
" \n"
" Simpel zoeken negeert interpunctie en veelgebruikte worden zoals "
"'de', 'het', 'een' of 'en'. Het behandelt de losse woorden zoals gevraagd\n"
" Zoeken naar 'appel' of bloem vindt elk recept dat zowel 'appel' "
"als 'bloem' ergens in de velden die geselecteerd zijn voor een zoekopdracht "
"bevat.\n"
" "
#: .\cookbook\templates\search_info.html:34
msgid ""
" \n"
" Phrase searches ignore punctuation, but will search for all of "
"the words in the exact order provided.\n"
" Searching for 'apple or flour' will only return a recipe that "
"includes the exact phrase 'apple or flour' in any of the fields that have "
"been selected for a full text search.\n"
" "
msgstr ""
" \n"
" Zin zoeken negeert interpunctie en zoekt naar alle woorden in de "
"volgorde waarin ze opgegeven zijn.\n"
" Zoeken naar 'appel of bloem' vindt alleen recepten waarbij de "
"exacte zin 'appel of bloem' in een van de velden die geselecteerd zijn voor "
"een zoekopdracht.\n"
" "
#: .\cookbook\templates\search_info.html:39
msgid ""
" \n"
" Web searches simulate functionality found on many web search "
"sites supporting special syntax.\n"
" Placing quotes around several words will convert those words "
"into a phrase.\n"
" 'or' is recognized as searching for the word (or phrase) "
"immediately before 'or' OR the word (or phrase) directly after.\n"
" '-' is recognized as searching for recipes that do not include "
"the word (or phrase) that comes immediately after. \n"
" For example searching for 'apple pie' or cherry -butter will "
"return any recipe that includes the phrase 'apple pie' or the word "
"'cherry' \n"
" in any field included in the full text search but exclude any "
"recipe that has the word 'butter' in any field included.\n"
" "
msgstr ""
" \n"
" Web zoeken simuleert functionaliteit zoals gevonden op veel "
"websites, met ondersteuning voor speciale tekens\n"
" Het plaatsen van aanhalingstekens om woorden zorgt ervoor dat ze "
"als zin behandeld worden.\n"
" 'or' kan worden gebruikt om te zoeken naar het woord (of de zin) "
"direct voor of na de 'or'.\n"
" '-' kan worden gebruikt om te zoeken naar recepten waarin het "
"woord (of zin) direct na de '-' niet voorkomt.\n"
" Bijvoorbeeld: zoeken naar \"'stamppot boerenkool' or kersen -"
"vlaai\" vindt recepten die de zin 'stamppot boerenkool' of het woord kersen "
"maar laat geen recepten zien waarbij het woord 'vlaai' in één van de "
"geselecteerde zoekvelden staat.\n"
" "
#: .\cookbook\templates\search_info.html:48
msgid ""
" \n"
" Raw search is similar to Web except will take puncuation "
"operators such as '|', '&' and '()'\n"
" "
msgstr ""
" \n"
" Raw zoeken is vergelijkbaar met Web met als toevoeging dat het "
"tekens zoals '|', '&' en '()' accepteert\n"
" "
#: .\cookbook\templates\search_info.html:59
msgid ""
" \n"
" Another approach to searching that also requires Postgresql is "
"fuzzy search or trigram similarity. A trigram is a group of three "
"consecutive characters.\n"
" For example searching for 'apple' will create x trigrams 'app', "
"'ppl', 'ple' and will create a score of how closely words match the "
"generated trigrams.\n"
" One benefit of searching trigams is that a search for 'sandwich' "
"will find misspelled words such as 'sandwhich' that would be missed by other "
"methods.\n"
" "
msgstr ""
" \n"
" Een andere benadering voor zoeken die ook Postgresql vereist is "
"'Fuzzy' of Trigram zoeken. Een Trigram is een groep van drie opvolgende "
"karakters.\n"
" Bijvoorbeeld: zoeken op 'appel' maakt 3 trigrams op 'app', 'ppe' "
"en 'pel' en maakt een score van hoe dicht de woorden overeenkomen met de "
"gegenereerde trigrams.\n"
" Eén voordeel van het zoeken met trigrams is dat een zoekopdracht "
"ook verkeerd gespelde woorden, die met andere zoekmethoden gemist worden, "
"vindt.\n"
" "
#: .\cookbook\templates\search_info.html:69
msgid "Search Fields"
msgstr "Zoekvelden"
#: .\cookbook\templates\search_info.html:73
msgid ""
" \n"
" Unaccent is a special case in that it enables searching a field "
"'unaccented' for each search style attempting to ignore accented values. \n"
" For example when you enable unaccent for 'Name' any search "
"(starts with, contains, trigram) will attempt the search ignoring accented "
"characters.\n"
" \n"
" For the other options, you can enable search on any or all "
"fields and they will be combined together with an assumed 'OR'.\n"
" For example enabling 'Name' for Starts With, 'Name' and "
"'Description' for Partial Match and 'Ingredients' and 'Keywords' for Full "
"Search\n"
" and searching for 'apple' will generate a search that will "
"return recipes that have:\n"
" - A recipe name that starts with 'apple'\n"
" - OR a recipe name that contains 'apple'\n"
" - OR a recipe description that contains 'apple'\n"
" - OR a recipe that will have a full text search match ('apple' "
"or 'apples') in ingredients\n"
" - OR a recipe that will have a full text search match in "
"Keywords\n"
"\n"
" Combining too many fields in too many types of search can have a "
"negative impact on performance, create duplicate results or return "
"unexpected results.\n"
" For example, enabling fuzzy search or partial matches will "
"interfere with web search methods. \n"
" Searching for 'apple -pie' with fuzzy search and full text "
"search will return the recipe Apple Pie. Though it is not included in the "
"full text results, it does match the trigram results.\n"
" "
msgstr ""
" \n"
" Ongeaccentueerd is een optie waarbij letters met accenten met de "
"gekozen zoekmethode genegeerd worden. \n"
" Wanneer je bijvoorbeeld ongeaccentueerd voor 'Naam' activeert "
"wordt bij elke zoekmethode geaccentueerde tekens genegeerd.\n"
" Voor de andere opties kan je zoeken op elk of alle velden "
"waarbij ze dan worden gecombineerd met een aangenomen 'OR'.\n"
" Bijvoorbeeld activatie van 'Naam' voor Begint met, 'Naam' en "
"'Beschrijving' voor Gedeeltelijke overeenkomst en 'Ingrediënten' en "
"'Etiketten' voor Volledig zoeken vindt de volgende recepten:\n"
" - Een receptnaam die begint met 'appel'\n"
" - OF een receptnaam die 'appel' bevat\n"
" - OF een receptbeschrijving die 'appel' bevat\n"
" - OF een recept met een volledige tekst overeenkomst ('appel' of "
"'appels') in Ingredienten\n"
" - OF een recept met een volledige tekst overeenkomst in "
"Etiketten\n"
"\n"
" Te veel velden combineren in te veel verschillende zoekmethoden "
"kan een negatieve impact op de prestaties hebben, dubbele resultaten creëren "
"of tot onverwachte resultaten leiden.\n"
" Het activeren van 'Fuzzy' zoeken of gedeeltelijke overeenkomsten "
"belemmert 'web' zoekmethoden. \n"
" Zoeken naar 'appel - taart' met 'Fuzzy' zoeken en volledige "
"tekst zoeken vindt het recept Appeltaart. Ondanks dat het niet in de "
"volledige tekst zoeken resultaten staat, komt het overeen met de "
"trigramresultaten.\n"
" "
#: .\cookbook\templates\search_info.html:95
msgid "Search Index"
msgstr "Zoekindex"
#: .\cookbook\templates\search_info.html:99
msgid ""
" \n"
" Trigram search and Full Text Search both rely on database "
"indexes to perform effectively. \n"
" You can rebuild the indexes on all fields in the Admin page for "
"Recipes and selecting all recipes and running 'rebuild index for selected "
"recipes'\n"
" You can also rebuild indexes at the command line by executing "
"the management command 'python manage.py rebuildindex'\n"
" "
msgstr ""
" \n"
" Trigram zoeken en Volledige tekst zoeken gebruiken beiden "
"database indices om effectief te kunnen zoeken. \n"
" Je kan de indices herbouwen op alle velden in de "
"Administratiepagina voor Recepten en vervolgens alle recepten te selecteren "
"en 'herbouw index voor geselecteerde recepten' te activeren.\n"
" Je kan ook indices herbouwen op de command line met het "
"managementcommando 'python manage.py rebuildindex'\n"
" "
#: .\cookbook\templates\settings.html:25
msgid ""
"There are many options to configure the search depending on your personal "
"preferences."
msgstr ""
"Er zijn vele mogelijkheden om het zoeken te configureren die afhangen van je "
"persoonlijke voorkeur."
#: .\cookbook\templates\settings.html:26
msgid ""
"Usually you do not need to configure any of them and can just stick "
"with either the default or one of the following presets."
msgstr ""
"Normaal gesproken is het niet nodig ze te configureren en kan je "
"gebruikmaken van het standaard profiel of de volgende vooraf ingestelde "
"profielen."
#: .\cookbook\templates\settings.html:27
msgid ""
"If you do want to configure the search you can read about the different "
"options here."
msgstr ""
"Als je het zoeken wil configureren kan je hier "
"over de verschillende opties lezen."
#: .\cookbook\templates\settings.html:32
msgid "Fuzzy"
msgstr "Fuzzy"
#: .\cookbook\templates\settings.html:33
msgid ""
"Find what you need even if your search or the recipe contains typos. Might "
"return more results than needed to make sure you find what you are looking "
"for."
msgstr ""
"Vind wat je nodig hebt, zelfs als je zoekopdracht of het recept typefouten "
"bevat. Mogelijk krijg je meer resultaten dan je nodig hebt, om zeker te "
"weten dat je vindt wat je nodig hebt."
#: .\cookbook\templates\settings.html:34
msgid "This is the default behavior"
msgstr "Dit is het standaard gedrag"
#: .\cookbook\templates\settings.html:37 .\cookbook\templates\settings.html:46
msgid "Apply"
msgstr "Pas toe"
#: .\cookbook\templates\settings.html:42
msgid "Precise"
msgstr "Nauwkeurig"
#: .\cookbook\templates\settings.html:43
msgid ""
"Allows fine control over search results but might not return results if too "
"many spelling mistakes are made."
msgstr ""
"Staat fijnmazige controle over zoekresultaten toe, maar toont mogelijk geen "
"resultaten als er te veel spelfouten gemaakt zijn."
#: .\cookbook\templates\settings.html:44
msgid "Perfect for large Databases"
msgstr "Perfect voor grote databases"
#: .\cookbook\templates\setup.html:6 .\cookbook\templates\system.html:5
msgid "Cookbook Setup"
msgstr "Kookboek configuratie"
#: .\cookbook\templates\setup.html:14
msgid "Setup"
msgstr "Setup"
#: .\cookbook\templates\setup.html:15
msgid ""
"To start using this application you must first create a superuser account."
msgstr ""
"Om te starten met de applicatie moet je eerst een superuser account aanmaken."
#: .\cookbook\templates\setup.html:20
msgid "Create Superuser account"
msgstr "Maak Superuser acount"
#: .\cookbook\templates\socialaccount\authentication_error.html:7
#: .\cookbook\templates\socialaccount\authentication_error.html:23
msgid "Social Network Login Failure"
msgstr "Inloggen op sociaal netwerk mislukt"
#: .\cookbook\templates\socialaccount\authentication_error.html:25
msgid ""
"An error occurred while attempting to login via your social network account."
msgstr ""
"Er is een fout opgetreden tijdens het inloggen via je sociale netwerk "
"account."
#: .\cookbook\templates\socialaccount\connections.html:4
#: .\cookbook\templates\socialaccount\connections.html:15
msgid "Account Connections"
msgstr "Account verbindingen"
#: .\cookbook\templates\socialaccount\connections.html:11
msgid "Social"
msgstr "Socials"
#: .\cookbook\templates\socialaccount\connections.html:18
msgid ""
"You can sign in to your account using any of the following third party\n"
" accounts:"
msgstr ""
"Je kan inloggen met een account van een van de onderstaande derde \n"
"partijen:"
#: .\cookbook\templates\socialaccount\connections.html:52
msgid ""
"You currently have no social network accounts connected to this account."
msgstr ""
"Je hebt op dit moment geen sociaalnetwerk account aan dit account gekoppeld."
#: .\cookbook\templates\socialaccount\connections.html:55
msgid "Add a 3rd Party Account"
msgstr "Voeg account van een 3e partij toe"
#: .\cookbook\templates\socialaccount\login.html:5
#: .\cookbook\templates\socialaccount\signup.html:5
msgid "Signup"
msgstr "Registratie"
#: .\cookbook\templates\socialaccount\login.html:9
#, python-format
msgid "Connect %(provider)s"
msgstr "Verbind %(provider)s"
#: .\cookbook\templates\socialaccount\login.html:11
#, python-format
msgid "You are about to connect a new third party account from %(provider)s."
msgstr ""
"Je staat op het punt een nieuw derde partij account van %(provider)s te "
"verbinden."
#: .\cookbook\templates\socialaccount\login.html:13
#, python-format
msgid "Sign In Via %(provider)s"
msgstr "Log in via %(provider)s"
#: .\cookbook\templates\socialaccount\login.html:15
#, python-format
msgid "You are about to sign in using a third party account from %(provider)s."
msgstr ""
"Je staat op het punt met een derde partij account van %(provider)s in te "
"loggen."
#: .\cookbook\templates\socialaccount\login.html:20
msgid "Continue"
msgstr "Doorgaan"
#: .\cookbook\templates\socialaccount\signup.html:10
#, python-format
msgid ""
"You are about to use your\n"
" %(provider_name)s account to login to\n"
" %(site_name)s. As a final step, please complete the following form:"
msgstr ""
"Je staat op het punt om met je\n"
"%(provider_name)s account in te loggen op\n"
"%(site_name)s. Vul als laatste stap het volgende formulier in:"
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:23
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:31
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:39
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:47
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:55
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:63
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:71
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:79
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:87
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:95
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:103
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:111
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:119
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:127
msgid "Sign in using"
msgstr "Log in met"
#: .\cookbook\templates\space_manage.html:7
msgid "Space Management"
msgstr "Ruimte Management"
#: .\cookbook\templates\space_manage.html:26
msgid "Space:"
msgstr "Ruimte:"
#: .\cookbook\templates\space_manage.html:27
msgid "Manage Subscription"
msgstr "Beheer abonnementen"
#: .\cookbook\templates\space_overview.html:13 .\cookbook\views\delete.py:184
msgid "Space"
msgstr "Ruimte"
#: .\cookbook\templates\space_overview.html:17
msgid ""
"Recipes, foods, shopping lists and more are organized in spaces of one or "
"more people."
msgstr ""
"Recepten, ingrediënten, boodschappenlijsten en meer zijn georganiseerd in "
"ruimtes van één of meer personen."
#: .\cookbook\templates\space_overview.html:18
msgid ""
"You can either be invited into an existing space or create your own one."
msgstr ""
"Je kan uitgenodigd worden in een bestaande ruimte of je eigen ruimte "
"aanmaken."
#: .\cookbook\templates\space_overview.html:53
msgid "Owner"
msgstr "Eigenaar"
#: .\cookbook\templates\space_overview.html:57
msgid "Leave Space"
msgstr "Verlaat Ruimte"
#: .\cookbook\templates\space_overview.html:78
#: .\cookbook\templates\space_overview.html:88
msgid "Join Space"
msgstr "Sluit aan bij Ruimte"
#: .\cookbook\templates\space_overview.html:81
msgid "Join an existing space."
msgstr "Sluit aan bij bestaande ruimte."
#: .\cookbook\templates\space_overview.html:83
msgid ""
"To join an existing space either enter your invite token or click on the "
"invite link the space owner send you."
msgstr ""
"Om je aan te sluiten bij een bestaande ruimte moet je jouw uitnodigingstoken "
"invoeren of op de uitnodingslink klikken die je ontvangen hebt."
#: .\cookbook\templates\space_overview.html:96
#: .\cookbook\templates\space_overview.html:105
msgid "Create Space"
msgstr "Maak ruimte aan"
#: .\cookbook\templates\space_overview.html:99
msgid "Create your own recipe space."
msgstr "Maak je eigen recepten ruimte."
#: .\cookbook\templates\space_overview.html:101
msgid "Start your own recipe space and invite other users to it."
msgstr "Start je eigen recepten ruimte en nodig andere gebruikers uit."
#: .\cookbook\templates\system.html:14
msgid ""
"\n"
" Django Recipes is an open source free software application. It can "
"be found on\n"
" GitHub.\n"
" Changelogs can be found here.\n"
" "
msgstr ""
"\n"
" Django Recipes is een open source gratis software applicatie. Het "
"kan gevonden worden op\n"
" GitHub.\n"
" Wijzigingenoverzichten kunnen hier gevonden worden.\n"
" "
#: .\cookbook\templates\system.html:20
msgid "System Information"
msgstr "Systeeminformatie"
#: .\cookbook\templates\system.html:41
msgid ""
"\n"
" You need to execute version.py in your update "
"script to generate version information (done automatically in docker).\n"
" "
msgstr ""
"\n"
" Je moet version.py uitvoeren in je update script om "
"versie informatie te genereren (gebeurt automatisch in docker).\n"
" "
#: .\cookbook\templates\system.html:46
msgid "Media Serving"
msgstr "Media aanbieder"
#: .\cookbook\templates\system.html:47 .\cookbook\templates\system.html:61
#: .\cookbook\templates\system.html:75 .\cookbook\templates\system.html:88
#: .\cookbook\templates\system.html:102 .\cookbook\templates\system.html:113
msgid "Warning"
msgstr "Waarschuwing"
#: .\cookbook\templates\system.html:47 .\cookbook\templates\system.html:61
#: .\cookbook\templates\system.html:75 .\cookbook\templates\system.html:88
#: .\cookbook\templates\system.html:104 .\cookbook\templates\system.html:113
msgid "Ok"
msgstr "Oké"
#: .\cookbook\templates\system.html:49
msgid ""
"Serving media files directly using gunicorn/python is not recommend!\n"
" Please follow the steps described\n"
" here to update\n"
" your installation.\n"
" "
msgstr ""
"Mediabestanden rechtstreeks aanbieden met gunicorn/python is niet "
"aanbevolen!\n"
" Volg de stappen zoals hier beschreven om je installatie te updaten.\n"
" "
#: .\cookbook\templates\system.html:55 .\cookbook\templates\system.html:70
#: .\cookbook\templates\system.html:83 .\cookbook\templates\system.html:94
#: .\cookbook\views\views.py:303
msgid "Everything is fine!"
msgstr "Alles is in orde!"
#: .\cookbook\templates\system.html:59
msgid "Secret Key"
msgstr "Geheime sleutel"
#: .\cookbook\templates\system.html:63
msgid ""
"\n"
" You do not have a SECRET_KEY configured in your "
".env file. Django defaulted to the\n"
" standard key\n"
" provided with the installation which is publicly know and "
"insecure! Please set\n"
" SECRET_KEY int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" Je hebt geen SECRET_KEY geconfigureerd in je."
"env bestand.\n"
" Django is overgegaan naar de standaard sleutel die openbaar en onveilig is! "
"Stel alsjeblieft SECRET_KEYin in het .env "
"configuratiebestand.\n"
" "
#: .\cookbook\templates\system.html:73
msgid "Debug Mode"
msgstr "Debug modus"
#: .\cookbook\templates\system.html:77
msgid ""
"\n"
" This application is still running in debug mode. This is most "
"likely not needed. Turn of debug mode by\n"
" setting\n"
" DEBUG=0 int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" Deze applicatie draait in debug modus. Dit is waarschijnlijk "
"niet nodig. Schakel debug modus uit door de \n"
" instelling\n"
" DEBUG=0 in het .envconfiguratiebestand aan te "
"passen.\n"
" "
#: .\cookbook\templates\system.html:86
msgid "Allowed Hosts"
msgstr "Hosts met toestemming"
#: .\cookbook\templates\system.html:90
msgid ""
"\n"
" Your allowed hosts are configured to allow every host. This "
"might be ok in some setups but should be avoided. Please see the docs about "
"this.\n"
" "
msgstr ""
"\n"
" Jouw 'hosts met toestemming' zijn geconfigureerd om alle hosts "
"toestemming te geven. Dit is in niet altijd fout maar zou eigenlijk "
"voorkomen moeten worden. Raadpleeg de documentatie hiervoor.\n"
" "
#: .\cookbook\templates\system.html:97
msgid "Database"
msgstr "Database"
#: .\cookbook\templates\system.html:100
msgid "Info"
msgstr "Info"
#: .\cookbook\templates\system.html:110 .\cookbook\templates\system.html:127
msgid "Migrations"
msgstr "Migraties"
#: .\cookbook\templates\system.html:116
msgid ""
"\n"
" Migrations should never fail!\n"
" Failed migrations will likely cause major parts of the app to "
"not function correctly.\n"
" If a migration fails make sure you are on the latest version and "
"if so please post the migration log and the overview below in a GitHub "
"issue.\n"
" "
msgstr ""
"\n"
" Migraties mogen nooit mislukken!\n"
" Mislukte migraties zullen er waarschijnlijk voor zorgen dat "
"grote delen van de app niet correct werken.\n"
" Als een migratie mislukt, zorg er dan voor dat de applicatie de "
"nieuwste versie is, blijft het probleem bestaan, plaats dan het "
"migratielogboek en het onderstaande overzicht in een GitHub-issue.\n"
" "
#: .\cookbook\templates\system.html:182
msgid "False"
msgstr "Niet waar"
#: .\cookbook\templates\system.html:182
msgid "True"
msgstr "Waar"
#: .\cookbook\templates\system.html:207
msgid "Hide"
msgstr "Verberg"
#: .\cookbook\templates\system.html:210
msgid "Show"
msgstr "Toon"
#: .\cookbook\templates\url_import.html:8
msgid "URL Import"
msgstr "Importeer URL"
#: .\cookbook\views\api.py:120 .\cookbook\views\api.py:213
#: .\cookbook\views\api.py:121 .\cookbook\views\api.py:214
msgid "Parameter updated_at incorrectly formatted"
msgstr "Parameter updatet_at is onjuist geformateerd"
#: .\cookbook\views\api.py:234 .\cookbook\views\api.py:340
#: .\cookbook\views\api.py:235 .\cookbook\views\api.py:341
#, python-brace-format
msgid "No {self.basename} with id {pk} exists"
msgstr "Er bestaat geen {self.basename} met id {pk}"
#: .\cookbook\views\api.py:238 .\cookbook\views\api.py:239
msgid "Cannot merge with the same object!"
msgstr "Kan niet met hetzelfde object samenvoegen!"
#: .\cookbook\views\api.py:245 .\cookbook\views\api.py:246
#, python-brace-format
msgid "No {self.basename} with id {target} exists"
msgstr "Er bestaat geen {self.basename} met id {target}"
#: .\cookbook\views\api.py:250 .\cookbook\views\api.py:251
msgid "Cannot merge with child object!"
msgstr "Kan niet met sub object samenvoegen!"
#: .\cookbook\views\api.py:288 .\cookbook\views\api.py:289
#, python-brace-format
msgid "{source.name} was merged successfully with {target.name}"
msgstr "{source.name} is succesvol samengevoegd met {target.name}"
#: .\cookbook\views\api.py:293 .\cookbook\views\api.py:294
#, python-brace-format
msgid "An error occurred attempting to merge {source.name} with {target.name}"
msgstr ""
"Er is een error opgetreden bij het samenvoegen van {source.name} met {target."
"name}"
#: .\cookbook\views\api.py:349 .\cookbook\views\api.py:350
#, python-brace-format
msgid "{child.name} was moved successfully to the root."
msgstr "{child.name} is succesvol verplaatst naar het hoogste niveau."
#: .\cookbook\views\api.py:352 .\cookbook\views\api.py:370
#: .\cookbook\views\api.py:353 .\cookbook\views\api.py:371
msgid "An error occurred attempting to move "
msgstr "Er is een error opgetreden bij het verplaatsen "
#: .\cookbook\views\api.py:355 .\cookbook\views\api.py:356
msgid "Cannot move an object to itself!"
msgstr "Kan object niet verplaatsen naar zichzelf!"
#: .\cookbook\views\api.py:361 .\cookbook\views\api.py:362
#, python-brace-format
msgid "No {self.basename} with id {parent} exists"
msgstr "Er bestaat geen {self.basename} met id {parent}"
#: .\cookbook\views\api.py:367 .\cookbook\views\api.py:368
#, python-brace-format
msgid "{child.name} was moved successfully to parent {parent.name}"
msgstr "{child.name} is succesvol verplaatst naar {parent.name}"
#: .\cookbook\views\api.py:589 .\cookbook\views\api.py:590
#, python-brace-format
msgid "{obj.name} was removed from the shopping list."
msgstr "{obj.name} is verwijderd van het boodschappenlijstje."
#: .\cookbook\views\api.py:594 .\cookbook\views\api.py:1037
#: .\cookbook\views\api.py:1050 .\cookbook\views\api.py:595
#: .\cookbook\views\api.py:1038 .\cookbook\views\api.py:1051
#, python-brace-format
msgid "{obj.name} was added to the shopping list."
msgstr "{obj.name} is toegevoegd aan het boodschappenlijstje."
#: .\cookbook\views\api.py:743
msgid "Filter meal plans from date (inclusive) in the format of YYYY-MM-DD."
msgstr ""
"Filter maaltijdplannen vanaf datum (inclusief) in het formaat JJJJ-MM-DD."
#: .\cookbook\views\api.py:744
msgid "Filter meal plans to date (inclusive) in the format of YYYY-MM-DD."
msgstr ""
"Filter maaltijdplannen tot nu toe (inclusief) in het formaat JJJJ-MM-DD."
#: .\cookbook\views\api.py:745
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
msgstr ""
"Filter maaltijdplannen met MealType ID. Herhaal parameter voor meerdere."
#: .\cookbook\views\api.py:872 .\cookbook\views\api.py:873
msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgstr ""
"ID van het recept waar de stap onderdeel van is. Herhaal parameter voor "
"meerdere."
#: .\cookbook\views\api.py:873 .\cookbook\views\api.py:874
msgid "Query string matched (fuzzy) against object name."
msgstr "Zoekterm komt overeen (fuzzy) met object naam."
#: .\cookbook\views\api.py:909 .\cookbook\views\api.py:910
msgid ""
"Query string matched (fuzzy) against recipe name. In the future also "
"fulltext search."
msgstr ""
"Zoekterm komt overeen (fuzzy) met recept naam. In de toekomst wordt zoeken "
"op volledige tekst ondersteund."
#: .\cookbook\views\api.py:910 .\cookbook\views\api.py:911
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr ""
"ID van etiket dat een recept moet hebben. Herhaal parameter voor meerdere. "
"Gelijkwaardig aan keywords_or"
#: .\cookbook\views\api.py:911 .\cookbook\views\api.py:912
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr ""
"Etiket ID, herhaal voor meerdere. Geeft recepten met elk geselecteerd etiket "
"weer"
#: .\cookbook\views\api.py:912 .\cookbook\views\api.py:913
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr ""
"Etiket ID, herhaal voor meerdere. Geeft recepten met alle geselecteerde "
"etiketten weer."
#: .\cookbook\views\api.py:913 .\cookbook\views\api.py:914
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr ""
"Etiket ID, herhaal voor meerdere. Sluit recepten met één van de etiketten "
"uit."
#: .\cookbook\views\api.py:914 .\cookbook\views\api.py:915
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr ""
"Etiket ID, herhaal voor meerdere. Sluit recepten met alle etiketten uit."
#: .\cookbook\views\api.py:915 .\cookbook\views\api.py:916
msgid "ID of food a recipe should have. For multiple repeat parameter."
msgstr ""
"ID van ingrediënt dat een recept moet hebben. Herhaal parameter voor "
"meerdere."
#: .\cookbook\views\api.py:916 .\cookbook\views\api.py:917
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr ""
"Ingrediënt ID, herhaal voor meerdere. Geeft recepten met elk ingrediënt weer"
#: .\cookbook\views\api.py:917 .\cookbook\views\api.py:918
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr ""
"Ingrediënt ID, herhaal voor meerdere. Geef recepten met alle ingrediënten "
"weer."
#: .\cookbook\views\api.py:918 .\cookbook\views\api.py:919
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr ""
"Ingrediënt ID, herhaal voor meerdere. sluit recepten met één van de "
"ingrediënten uit."
#: .\cookbook\views\api.py:919 .\cookbook\views\api.py:920
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr ""
"Ingrediënt ID, herhaal voor meerdere. Sluit recepten met alle ingrediënten "
"uit."
#: .\cookbook\views\api.py:920 .\cookbook\views\api.py:921
msgid "ID of unit a recipe should have."
msgstr "ID van eenheid dat een recept moet hebben."
#: .\cookbook\views\api.py:921 .\cookbook\views\api.py:922
msgid ""
"Rating a recipe should have or greater. [0 - 5] Negative value filters "
"rating less than."
msgstr "Een waardering van een recept gaat van 0 tot 5."
#: .\cookbook\views\api.py:922 .\cookbook\views\api.py:923
msgid "ID of book a recipe should be in. For multiple repeat parameter."
msgstr ""
"ID van een kookboek dat een recept moet bevatten. Herhaal parameter voor "
"meerdere."
#: .\cookbook\views\api.py:923 .\cookbook\views\api.py:924
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr ""
"Kookboek ID, herhaal voor meerdere. Geeft recepten uit de geselecteerde "
"kookboeken weer"
#: .\cookbook\views\api.py:924 .\cookbook\views\api.py:925
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr ""
"Kookboek IDs, herhaal voor meerdere. Geeft recepten weer uit alle kookboeken."
#: .\cookbook\views\api.py:925 .\cookbook\views\api.py:926
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr ""
"Kookboek IDs, herhaal voor meerdere. Sluit recepten uit elk van de "
"geselecteerde kookboeken uit."
#: .\cookbook\views\api.py:926 .\cookbook\views\api.py:927
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr ""
"Kookboek IDs, herhaal voor meerdere. Sluit recepten uit alle boeken uit."
#: .\cookbook\views\api.py:927 .\cookbook\views\api.py:928
msgid "If only internal recipes should be returned. [true/false]"
msgstr ""
"Wanneer alleen interne recepten gevonden moeten worden. [waar/onwaar]"
#: .\cookbook\views\api.py:928 .\cookbook\views\api.py:929
msgid "Returns the results in randomized order. [true/false]"
msgstr ""
"Geeft de resultaten in willekeurige volgorde weer. [waar/onwaar]"
#: .\cookbook\views\api.py:929 .\cookbook\views\api.py:930
msgid "Returns new results first in search results. [true/false]"
msgstr "Geeft nieuwe resultaten eerst weer. [waar/onwaar]"
#: .\cookbook\views\api.py:930 .\cookbook\views\api.py:931
msgid ""
"Filter recipes cooked X times or more. Negative values returns cooked less "
"than X times"
msgstr ""
"Filter recepten X maal of meer bereid. Negatieve waarden geven minder dan X "
"keer bereide recepten weer"
#: .\cookbook\views\api.py:931 .\cookbook\views\api.py:932
msgid ""
"Filter recipes last cooked on or after YYYY-MM-DD. Prepending - filters on "
"or before date."
msgstr ""
"Filter recepten op laatst bereid op of na JJJJ-MM-DD. Voorafgaand - filters "
"op of voor datum."
#: .\cookbook\views\api.py:932 .\cookbook\views\api.py:933
msgid ""
"Filter recipes created on or after YYYY-MM-DD. Prepending - filters on or "
"before date."
msgstr ""
"Filter recepten aangemaakt op of na JJJJ-MM-DD. Voorafgaand - filters op of "
"voor datum."
#: .\cookbook\views\api.py:933 .\cookbook\views\api.py:934
msgid ""
"Filter recipes updated on or after YYYY-MM-DD. Prepending - filters on or "
"before date."
msgstr ""
"Filter recepten op geüpdatet op of na JJJJ-MM-DD. Voorafgaand - filters op "
"of voor datum."
#: .\cookbook\views\api.py:934 .\cookbook\views\api.py:935
msgid ""
"Filter recipes lasts viewed on or after YYYY-MM-DD. Prepending - filters on "
"or before date."
msgstr ""
"Filter recepten op laatst bekeken op of na JJJJ-MM-DD. Voorafgaand - filters "
"op of voor datum."
#: .\cookbook\views\api.py:935 .\cookbook\views\api.py:936
msgid "Filter recipes that can be made with OnHand food. [true/false]"
msgstr ""
"Filter recepten die bereid kunnen worden met ingrediënten die op voorraad "
"zijn. [waar/onwaar]"
#: .\cookbook\views\api.py:1122 .\cookbook\views\api.py:1123
msgid ""
"Returns the shopping list entry with a primary key of id. Multiple values "
"allowed."
msgstr ""
"Geeft het boodschappenlijstje item met een primaire sleutel van id. "
"Meerdere waarden toegestaan."
#: .\cookbook\views\api.py:1126
msgid ""
"Filter shopping list entries on checked. [true, false, both, recent"
"b>]
- recent includes unchecked items and recently "
"completed items."
msgstr ""
"Filter boodschappenlijstjes op aangevinkt. [waar, onwaar, beide,recent"
"b>]
- recent bevat niet aangevinkte en recent voltooide items."
#: .\cookbook\views\api.py:1128 .\cookbook\views\api.py:1129
msgid "Returns the shopping list entries sorted by supermarket category order."
msgstr ""
"Geeft items op boodschappenlijstjes gesorteerd per supermarktcategorie weer."
#: .\cookbook\views\api.py:1211
msgid "Filter for entries with the given recipe"
msgstr "Filter op vermeldingen met het gegeven recept"
#: .\cookbook\views\api.py:1293
msgid ""
"Return the Automations matching the automation type. Multiple values "
"allowed."
msgstr ""
"Vraag de automatiseringen die overeenkomen met het automatiseringstype op. "
"Meerdere waarden toegestaan."
#: .\cookbook\views\api.py:1415 .\cookbook\views\api.py:1416
msgid "Nothing to do."
msgstr "Niks te doen."
#: .\cookbook\views\api.py:1445 .\cookbook\views\api.py:1443
msgid "Invalid Url"
msgstr "Ongeldige URL"
#: .\cookbook\views\api.py:1449 .\cookbook\views\api.py:1447
msgid "Connection Refused."
msgstr "Verbinding geweigerd."
#: .\cookbook\views\api.py:1451 .\cookbook\views\api.py:1449
msgid "Bad URL Schema."
msgstr "Verkeerd URL schema."
#: .\cookbook\views\api.py:1474 .\cookbook\views\api.py:1472
msgid "No usable data could be found."
msgstr "Er is geen bruikbare data gevonden."
#: .\cookbook\views\api.py:1547
msgid "File is above space limit"
msgstr "Bestand is boven de ruimte limiet"
#: .\cookbook\views\api.py:1566 .\cookbook\views\import_export.py:114
#: .\cookbook\views\api.py:1564
msgid "Importing is not implemented for this provider"
msgstr "Importeren is voor deze provider niet geïmplementeerd"
#: .\cookbook\views\api.py:1650 .\cookbook\views\data.py:30
#: .\cookbook\views\edit.py:88 .\cookbook\views\new.py:63
#: .\cookbook\views\new.py:82 .\cookbook\views\api.py:1648
msgid "This feature is not yet available in the hosted version of tandoor!"
msgstr "Deze optie is nog niet beschikbaar in de gehoste versie van Tandoor!"
#: .\cookbook\views\api.py:1671 .\cookbook\views\api.py:1669
msgid "Sync successful!"
msgstr "Synchronisatie succesvol!"
#: .\cookbook\views\api.py:1674 .\cookbook\views\api.py:1672
msgid "Error synchronizing with Storage"
msgstr "Er is een fout opgetreden bij het synchroniseren met Opslag"
#: .\cookbook\views\data.py:99
#, python-format
msgid "Batch edit done. %(count)d recipe was updated."
msgid_plural "Batch edit done. %(count)d Recipes where updated."
msgstr[0] "Batch bewerking voldaan. %(count)d het recept is ge-update."
msgstr[1] "Batch bewerking voldaan. %(count)d Recepten zijn geupdatet."
#: .\cookbook\views\delete.py:102
msgid "Monitor"
msgstr "Bewaker"
#: .\cookbook\views\delete.py:114 .\cookbook\views\lists.py:61
#: .\cookbook\views\new.py:69
msgid "Storage Backend"
msgstr "Opslag backend"
#: .\cookbook\views\delete.py:122
msgid ""
"Could not delete this storage backend as it is used in at least one monitor."
msgstr ""
"Dit Opslag backend kon niet verwijderd worden omdat het gebruikt wordt in "
"tenminste een Bewaker."
#: .\cookbook\views\delete.py:135
msgid "Connectors Config Backend"
msgstr "Connectors Configuratie backend"
#: .\cookbook\views\delete.py:157
msgid "Invite Link"
msgstr "Uitnodigingslink"
#: .\cookbook\views\delete.py:168
msgid "Space Membership"
msgstr "Ruimte Lidmaatschap"
#: .\cookbook\views\edit.py:84
msgid "You cannot edit this storage!"
msgstr "Je kan deze opslag niet bewerken!"
#: .\cookbook\views\edit.py:108
msgid "Storage saved!"
msgstr "Opslag opgeslagen!"
#: .\cookbook\views\edit.py:110
msgid "There was an error updating this storage backend!"
msgstr "Er is een fout opgetreden bij het updaten van deze opslag backend!"
#: .\cookbook\views\edit.py:134
msgid "Config saved!"
msgstr "Configuratie opgeslagen!"
#: .\cookbook\views\edit.py:142
msgid "ConnectorConfig"
msgstr "ConnectorConfiguratie"
#: .\cookbook\views\edit.py:198
msgid "Changes saved!"
msgstr "Wijzigingen opgeslagen!"
#: .\cookbook\views\edit.py:202
msgid "Error saving changes!"
msgstr "Fout bij het opslaan van de wijzigingen!"
#: .\cookbook\views\import_export.py:101
msgid ""
"The PDF Exporter is not enabled on this instance as it is still in an "
"experimental state."
msgstr ""
"De PDF exporter is niet ingeschakeld op deze instantie gezien het in een "
"experimentele staat is."
#: .\cookbook\views\lists.py:23
msgid "Import Log"
msgstr "Import logboek"
#: .\cookbook\views\lists.py:36
msgid "Discovery"
msgstr "Ontdekken"
#: .\cookbook\views\lists.py:46
msgid "Shopping List"
msgstr "Boodschappenlijst"
#: .\cookbook\views\lists.py:77 .\cookbook\views\new.py:98
msgid "Connector Config Backend"
msgstr "Connector Configuratie Backend"
#: .\cookbook\views\lists.py:91
msgid "Invite Links"
msgstr "Uitnodigingslink"
#: .\cookbook\views\lists.py:154
msgid "Supermarkets"
msgstr "Supermarkten"
#: .\cookbook\views\lists.py:170
msgid "Shopping Categories"
msgstr "Boodschappencategorieën"
#: .\cookbook\views\lists.py:202
msgid "Custom Filters"
msgstr "Aangepaste filters"
#: .\cookbook\views\lists.py:239
msgid "Steps"
msgstr "Stappen"
#: .\cookbook\views\lists.py:270
msgid "Property Types"
msgstr "Eigenschap Types"
#: .\cookbook\views\new.py:86
msgid "This feature is not enabled by the server admin!"
msgstr "Deze optie is niet ingeschakeld door de server administrator!"
#: .\cookbook\views\new.py:123
msgid "Imported new recipe!"
msgstr "Nieuw recept geïmporteerd!"
#: .\cookbook\views\new.py:126
msgid "There was an error importing this recipe!"
msgstr "Er is een fout opgetreden bij het importeren van dit recept!"
#: .\cookbook\views\views.py:69 .\cookbook\views\views.py:177
#: .\cookbook\views\views.py:204 .\cookbook\views\views.py:423
msgid "This feature is not available in the demo version!"
msgstr "Deze optie is niet beschikbaar in de demo versie!"
#: .\cookbook\views\views.py:74
msgid ""
"You have the reached the maximum amount of spaces that can be owned by you."
msgstr "Je hebt het maximaal aantal Ruimtes die jij kan aanmaken bereikt."
#: .\cookbook\views\views.py:89
msgid ""
"You have successfully created your own recipe space. Start by adding some "
"recipes or invite other people to join you."
msgstr ""
"Je hebt je eigen recepten ruimte succesvol aangemaakt. Start met het "
"toevoegen van recepten of nodig anderen uit om je te vergezellen."
#: .\cookbook\views\views.py:138
msgid "You do not have the required permissions to perform this action!"
msgstr "Je beschikt niet over de juiste rechten om deze actie uit te voeren!"
#: .\cookbook\views\views.py:149
msgid "Comment saved!"
msgstr "Opmerking opgeslagen!"
#: .\cookbook\views\views.py:240
msgid "You must select at least one field to search!"
msgstr "Je moet tenminste één veld om te doorzoeken selecteren!"
#: .\cookbook\views\views.py:243
msgid ""
"To use this search method you must select at least one full text search "
"field!"
msgstr ""
"Om deze zoekmethode te gebruiken moet je tenminste één volledig tekstveld "
"selecteren!"
#: .\cookbook\views\views.py:246
msgid "Fuzzy search is not compatible with this search method!"
msgstr "'Fuzzy' zoeken is niet te gebruiken met deze zoekmethode!"
#: .\cookbook\views\views.py:306
#, python-format
msgid "PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!"
msgstr ""
"PostgreSQL %(v)s is verouderd. Upgrade naar een volledig ondersteunde "
"versie!"
#: .\cookbook\views\views.py:309
#, python-format
msgid "You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended"
msgstr "Je gebruikt PostgreSQL %(v1)s. PostgreSQL %(v2)s wordt aanbevolen"
#: .\cookbook\views\views.py:313
msgid "Unable to determine PostgreSQL version."
msgstr "Kan PostgreSQL-versie niet bepalen."
#: .\cookbook\views\views.py:317
msgid ""
"This application is not running with a Postgres database backend. This is ok "
"but not recommended as some features only work with postgres databases."
msgstr ""
"Deze applicatie draait niet met een Postgres database als backend. Dit is ok "
"maar wordt niet aanbevolen omdat sommige functies alleen werken met Postgres "
"databases."
#: .\cookbook\views\views.py:360
msgid ""
"The setup page can only be used to create the first "
"user! If you have forgotten your superuser credentials "
"please consult the django documentation on how to reset passwords."
msgstr ""
"De setup pagina kan alleen gebruikt worden om de eerste gebruiker aan te "
"maken! Indien je de superuser inloggegevens bent "
"vergeten zal je de django documentatie moeten raadplegen voor een methode om "
"je wachtwoord te resetten."
#: .\cookbook\views\views.py:369
msgid "Passwords dont match!"
msgstr "Wachtwoorden komen niet overeen!"
#: .\cookbook\views\views.py:377
msgid "User has been created, please login!"
msgstr "Gebruiker is gecreëerd, Log in alstublieft!"
#: .\cookbook\views\views.py:393
msgid "Malformed Invite Link supplied!"
msgstr "Onjuiste uitnodigingslink opgegeven!"
#: .\cookbook\views\views.py:410
msgid "Successfully joined space."
msgstr "Succesvol toegetreden tot ruimte."
#: .\cookbook\views\views.py:416
msgid "Invite Link not valid or already used!"
msgstr "De uitnodigingslink is niet valide of al gebruikt!"
#: .\cookbook\views\views.py:432
msgid ""
"Reporting share links is not enabled for this instance. Please notify the "
"page administrator to report problems."
msgstr ""
"Het rapporteren van gedeelde links is niet geactiveerd voor deze instantie. "
"Rapporteer problemen bij de beheerder van de pagina."
#: .\cookbook\views\views.py:437
msgid ""
"Recipe sharing link has been disabled! For additional information please "
"contact the page administrator."
msgstr ""
"Links voor het delen van recepten zijn gedeactiveerd. Neem contact op met de "
"paginabeheerder voor aanvullende informatie."
#: .\cookbook\views\views.py:451
msgid "Manage recipes, shopping list, meal plans and more."
msgstr "Beheer recepten, boodschappen lijstjes, maaltijdplannen en meer."
#: .\cookbook\views\views.py:458
msgid "Plan"
msgstr "Plan"
#: .\cookbook\views\views.py:458
msgid "View your meal Plan"
msgstr "Bekijk jouw maaltijdplan"
#: .\cookbook\views\views.py:459
msgid "View your cookbooks"
msgstr "Bekijk jouw kookboeken"
#: .\cookbook\views\views.py:460
msgid "View your shopping lists"
msgstr "Bekijk jouw boodschappenlijst"
#~ msgid "Default unit"
#~ msgstr "Standaard eenheid"
#~ msgid "Use KJ"
#~ msgstr "Gebruik KJ"
#~ msgid "Theme"
#~ msgstr "Thema"
#~ msgid "Navbar color"
#~ msgstr "Navbar kleur"
#~ msgid "Sticky navbar"
#~ msgstr "Plak navbar"
#~ msgid "Default page"
#~ msgstr "Standaard pagina"
#~ msgid "Plan sharing"
#~ msgstr "Plan delen"
#~ msgid "Ingredient decimal places"
#~ msgstr "Ingrediënt decimalen"
#~ msgid "Shopping list auto sync period"
#~ msgstr "Boodschappenlijst auto sync periode"
#~ msgid "Left-handed mode"
#~ msgstr "Linkshandigen modus"
#~ msgid ""
#~ "Color of the top navigation bar. Not all colors work with all themes, "
#~ "just try them out!"
#~ msgstr ""
#~ "De kleur van de bovenste navigatie balk. Niet alle kleuren werken met "
#~ "alle thema's, je dient ze dus simpelweg uit te proberen!"
#~ msgid ""
#~ "Default Unit to be used when inserting a new ingredient into a recipe."
#~ msgstr ""
#~ "Standaard eenheid die gebruikt wordt wanneer een nieuw ingrediënt aan een "
#~ "recept wordt toegevoegd."
#~ msgid ""
#~ "Enables support for fractions in ingredient amounts (e.g. convert "
#~ "decimals to fractions automatically)"
#~ msgstr ""
#~ "Mogelijk maken van breuken bij ingrediënt aantallen (het automatisch "
#~ "converteren van decimalen naar breuken)"
#~ msgid "Display nutritional energy amounts in joules instead of calories"
#~ msgstr "Geef energiewaardes weer in joules in plaats van calorieën"
#~ msgid ""
#~ "Users with whom newly created meal plans should be shared by default."
#~ msgstr ""
#~ "Gebruikers waarmee een nieuwe maaltijdplannen standaard gedeeld moeten "
#~ "worden."
#~ msgid "Users with whom to share shopping lists."
#~ msgstr "Gebruikers waarmee boodschappenlijsten gedeeld moeten worden."
#~ msgid "Number of decimals to round ingredients."
#~ msgstr "Aantal decimalen om ingrediënten op af te ronden."
#~ msgid ""
#~ "If you want to be able to create and see comments underneath recipes."
#~ msgstr "Als je opmerkingen onder recepten wil kunnen maken en zien."
#~ msgid ""
#~ "Setting to 0 will disable auto sync. When viewing a shopping list the "
#~ "list is updated every set seconds to sync changes someone else might have "
#~ "made. Useful when shopping with multiple people but might use a little "
#~ "bit of mobile data. If lower than instance limit it is reset when saving."
#~ msgstr ""
#~ "0 schakelt automatische synchronisatie uit. Bij het bekijken van een "
#~ "boodschappenlijst wordt de lijst elke X seconden geüpdatet om wijzigingen "
#~ "die door een ander zijn gedaan op te halen. Handig wanneer meerdere "
#~ "mensen gelijktijdig boodschappen doen maar verbruikt mogelijk extra "
#~ "mobiele data. Wordt gereset bij opslaan wanneer de limiet niet bereikt is."
#~ msgid "Makes the navbar stick to the top of the page."
#~ msgstr "Zet de navbar vast aan de bovenkant van de pagina."
#~ msgid "Automatically add meal plan ingredients to shopping list."
#~ msgstr "Zet maaltijdplan ingrediënten automatisch op boodschappenlijst."
#~ msgid "Exclude ingredients that are on hand."
#~ msgstr "Sluit ingrediënten die op voorraad zijn uit."
#~ msgid "Will optimize the UI for use with your left hand."
#~ msgstr ""
#~ "Optimaliseert de gebruikersinterface voor gebruik met je linkerhand."
#~ msgid "You must provide at least a recipe or a title."
#~ msgstr "Je moet minimaal één recept of titel te specificeren."
#~ msgid "You can list default users to share recipes with in the settings."
#~ msgstr ""
#~ "Je kan in de instellingen standaard gebruikers in stellen om de recepten "
#~ "met te delen."
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "Je kunt markdown gebruiken om dit veld te op te maken. Bekijk de documentatie hier"
#~ msgid ""
#~ "Users will see all items you add to your shopping list. They must add "
#~ "you to see items on their list."
#~ msgstr ""
#~ "Gebruikers zien alle items die je op je boodschappenlijst zet. Ze moeten "
#~ "jou toevoegen om items op hun lijst te zien."
#~ msgid ""
#~ "When adding a meal plan to the shopping list (manually or automatically), "
#~ "include all related recipes."
#~ msgstr ""
#~ "Als een maaltijdplan aan de boodschappenlijst toegevoegd wordt (handmatig "
#~ "of automatisch), neem dan alle recepten op."
#~ msgid ""
#~ "When adding a meal plan to the shopping list (manually or automatically), "
#~ "exclude ingredients that are on hand."
#~ msgstr ""
#~ "Als een maaltijdplan aan de boodschappenlijst toegevoegd wordt (handmatig "
#~ "of automatisch), sluit ingrediënten die op voorraad zijn dan uit."
#~ msgid "Default number of hours to delay a shopping list entry."
#~ msgstr "Standaard aantal uren om een boodschappenlijst item te vertragen."
#~ msgid "Filter shopping list to only include supermarket categories."
#~ msgstr ""
#~ "Filter boodschappenlijst om alleen supermarktcategorieën te bevatten."
#~ msgid "Days of recent shopping list entries to display."
#~ msgstr "Dagen van recente boodschappenlijst items weer te geven."
#~ msgid "Mark food 'On Hand' when checked off shopping list."
#~ msgstr ""
#~ "Markeer eten 'Op voorraad' wanneer het van het boodschappenlijstje is "
#~ "afgevinkt."
#~ msgid "Delimiter to use for CSV exports."
#~ msgstr "Scheidingsteken te gebruiken voor CSV exports."
#~ msgid "Prefix to add when copying list to the clipboard."
#~ msgstr ""
#~ "Toe te voegen Voorvoegsel bij het kopiëren van een lijst naar het "
#~ "klembord."
#~ msgid "Share Shopping List"
#~ msgstr "Deel boodschappenlijst"
#~ msgid "Autosync"
#~ msgstr "Autosync"
#~ msgid "Auto Add Meal Plan"
#~ msgstr "Voeg maaltijdplan automatisch toe"
#~ msgid "Exclude On Hand"
#~ msgstr "Sluit op voorraad uit"
#~ msgid "Include Related"
#~ msgstr "Neem gerelateerde op"
#~ msgid "Default Delay Hours"
#~ msgstr "Standaard vertraging in uren"
#~ msgid "Filter to Supermarket"
#~ msgstr "Filter op supermarkt"
#~ msgid "Recent Days"
#~ msgstr "Afgelopen dagen"
#~ msgid "CSV Delimiter"
#~ msgstr "CSV scheidingsteken"
#~ msgid "List Prefix"
#~ msgstr "Lijst voorvoegsel"
#~ msgid "Auto On Hand"
#~ msgstr "Auto op voorraad"
#~ msgid "Reset Food Inheritance"
#~ msgstr "Herstel Ingrediënt overname"
#~ msgid "Reset all food to inherit the fields configured."
#~ msgstr ""
#~ "Herstel alle ingrediënten om de geconfigureerde velden over te nemen."
#~ msgid "Fields on food that should be inherited by default."
#~ msgstr "Velden van ingrediënten die standaard overgenomen moeten worden."
#~ msgid "Show recipe counts on search filters"
#~ msgstr "Toon recepten teller bij zoekfilters"
#~ msgid "Use the plural form for units and food inside this space."
#~ msgstr "Gebruik de meervoudsvorm voor eenheden en voedsel in deze ruimte."
#~ msgid "One of queryset or hash_key must be provided"
#~ msgstr "Er moet een queryset of hash_key opgegeven worden"
#~ msgid "Profile"
#~ msgstr "Profiel"
#~ msgid "Recipe Book"
#~ msgstr "Kookboek"
#~ msgid "Bookmarks"
#~ msgstr "Bladwijzers"
#~ msgid "Ingredients"
#~ msgstr "Ingrediënten"
#~ msgid "Show recent recipes"
#~ msgstr "Toon recente recepten"
#~ msgid "Search style"
#~ msgstr "Zoekstijl"
#~ msgid "Show recently viewed recipes on search page."
#~ msgstr "Geef recent bekeken recepten op de zoekpagina weer."
#~ msgid "Small"
#~ msgstr "Klein"
#~ msgid "Large"
#~ msgstr "Groot"
#~ msgid "Edit Ingredients"
#~ msgstr "Ingrediënten bewerken"
#~ msgid ""
#~ "\n"
#~ " The following form can be used if, accidentally, two (or more) "
#~ "units or ingredients where created that should be\n"
#~ " the same.\n"
#~ " It merges two units or ingredients and updates all recipes using "
#~ "them.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Het volgende formulier kan worden gebruikt wanneer per ongeluk "
#~ "twee (of meer) eenheden of ingrediënten zijn gemaakt die eigenlijk\n"
#~ " hetzelfde zijn\n"
#~ " Het voegt de twee eenheden of ingrediënten samen en past alle "
#~ "bijbehorende recepten aan.\n"
#~ " "
#~ msgid "Are you sure that you want to merge these two units?"
#~ msgstr "Weet je zeker dat je deze twee eenheden wil samenvoegen?"
#~ msgid "Are you sure that you want to merge these two ingredients?"
#~ msgstr "Weet je zeker dat je deze ingrediënten wil samenvoegen?"
#~ msgid "Import Recipes"
#~ msgstr "Recepten importeren"
#~ msgid "Close"
#~ msgstr "Sluiten"
#~ msgid "Open Recipe"
#~ msgstr "Open recept"
#~ msgid "Meal Plan View"
#~ msgstr "Maaltijdenplan bekijken"
#~ msgid "Created by"
#~ msgstr "Gemaakt door"
#~ msgid "Shared with"
#~ msgstr "Gedeeld met"
#~ msgid "Last cooked"
#~ msgstr "Laatst bereid"
#~ msgid "Never cooked before."
#~ msgstr "Nog nooit bereid."
#~ msgid "Other meals on this day"
#~ msgstr "Andere maaltijden op deze dag"
#~ msgid "Recipe Image"
#~ msgstr "Recept afbeelding"
#~ msgid "Preparation time ca."
#~ msgstr "Geschatte voorbereidingstijd"
#~ msgid "Waiting time ca."
#~ msgstr "Geschatte wachttijd"
#~ msgid "External"
#~ msgstr "Externe"
#~ msgid "Log Cooking"
#~ msgstr "Bereiding loggen"
#~ msgid "Account"
#~ msgstr "Account"
#~ msgid "Preferences"
#~ msgstr "Voorkeuren"
#~ msgid "API-Settings"
#~ msgstr "API-instellingen"
#~ msgid "Search-Settings"
#~ msgstr "Zoek instellingen"
#~ msgid "Shopping-Settings"
#~ msgstr "Boodschappen instellingen"
#~ msgid "Name Settings"
#~ msgstr "Naam instellingen"
#~ msgid "Account Settings"
#~ msgstr "Account instellingen"
#~ msgid "Emails"
#~ msgstr "E-mails"
#~ msgid "Language"
#~ msgstr "Taal"
#~ msgid "Style"
#~ msgstr "Stijl"
#~ msgid "API Token"
#~ msgstr "API Token"
#~ msgid ""
#~ "You can use both basic authentication and token based authentication to "
#~ "access the REST API."
#~ msgstr ""
#~ "Je kan zowel basale verificatie als verificatie op basis van tokens "
#~ "gebruiken om toegang tot de REST API te krijgen."
#~ msgid ""
#~ "Use the token as an Authorization header prefixed by the word token as "
#~ "shown in the following examples:"
#~ msgstr ""
#~ "Gebruik de token als een 'Authorization header'voorafgegaan door het "
#~ "woord token zoals in de volgende voorbeelden:"
#~ msgid "or"
#~ msgstr "of"
#~ msgid "Shopping Settings"
#~ msgstr "Boodschappen instellingen"
#~ msgid "Stats"
#~ msgstr "Statistieken"
#~ msgid "Statistics"
#~ msgstr "Statistieken"
#~ msgid "Number of objects"
#~ msgstr "Aantal objecten"
#~ msgid "Recipe Imports"
#~ msgstr "Geïmporteerde recepten"
#~ msgid "Objects stats"
#~ msgstr "Object statistieken"
#~ msgid "Recipes without Keywords"
#~ msgstr "Recepten zonder etiketten"
#~ msgid "Internal Recipes"
#~ msgstr "Interne recepten"
#~ msgid "Show Links"
#~ msgstr "Toon links"
#~ msgid "A user is required"
#~ msgstr "Een gebruiker is verplicht"
#~ msgid "Invite User"
#~ msgstr "Nodig gebruiker uit"
#~ msgid "User"
#~ msgstr "Gebruiker"
#~ msgid "Groups"
#~ msgstr "Groepen"
#~ msgid "admin"
#~ msgstr "Beheerder"
#~ msgid "user"
#~ msgstr "gebruiker"
#~ msgid "guest"
#~ msgstr "gast"
#~ msgid "remove"
#~ msgstr "verwijder"
#~ msgid "Update"
#~ msgstr "Update"
#~ msgid "You cannot edit yourself."
#~ msgstr "Je kan jezelf niet bewerken."
#~ msgid "There are no members in your space yet!"
#~ msgstr "Er zitten nog geen leden in jouw ruimte!"
#~ msgid "Invite link successfully send to user."
#~ msgstr "Uitnodigingslink succesvol verstuurd naar gebruiker."
#~ msgid ""
#~ "You have send to many emails, please share the link manually or wait a "
#~ "few hours."
#~ msgstr ""
#~ "Je hebt te veel e-mails verstuurd, deel de link handmatig of wacht enkele "
#~ "uren."
#~ msgid "Email could not be sent to user. Please share the link manually."
#~ msgstr ""
#~ "E-mail aan gebruiker kon niet verzonden worden, deel de link handmatig."
#~ msgid ""
#~ "You are already member of a space and therefore cannot join this one."
#~ msgstr ""
#~ "Je bent al lid van een ruimte en kan daardoor niet toetreden tot deze."
#~ msgid "Try the new shopping list"
#~ msgstr "Probeer de nieuwe boodschappenlijst"
#~ msgid "Search Recipe"
#~ msgstr "Zoek recept"
#~ msgid "Shopping Recipes"
#~ msgstr "Recepten op boodschappenlijst"
#~ msgid "No recipes selected"
#~ msgstr "Geen recepten geselecteerd"
#~ msgid "Entry Mode"
#~ msgstr "Invoermodus"
#~ msgid "Add Entry"
#~ msgstr "Voeg toe aan boodschappenlijst"
#~ msgid "Amount"
#~ msgstr "Hoeveelheid"
#~ msgid "Select Unit"
#~ msgstr "Selecteer eenheid"
#~ msgid "Select"
#~ msgstr "Selecteer"
#~ msgid "Select Food"
#~ msgstr "Selecteer ingrediënt"
#~ msgid "Select Supermarket"
#~ msgstr "Selecteer supermarkt"
#~ msgid "Select User"
#~ msgstr "Selecteer gebruiker"
#~ msgid "Finished"
#~ msgstr "Afgerond"
#~ msgid "You are offline, shopping list might not synchronize."
#~ msgstr "Je bent offline, de boodschappenlijst synchroniseert mogelijk niet."
#~ msgid "Copy/Export"
#~ msgstr "Kopieër/exporteer"
#~ msgid "Drag me to your bookmarks to import recipes from anywhere"
#~ msgstr ""
#~ "Sleep mij naar je bladwijzers om overal recepten vandaan te kunnen "
#~ "importeren"
#~ msgid "Bookmark Me!"
#~ msgstr "Sla mij op als bladwijzer!"
#~ msgid "URL"
#~ msgstr "URL"
#~ msgid "App"
#~ msgstr "App"
#~ msgid "Text"
#~ msgstr "Tekst"
#~ msgid "File"
#~ msgstr "Bestand"
#~ msgid "Enter website URL"
#~ msgstr "Vul website URL in"
#~ msgid "Select recipe files to import or drop them here..."
#~ msgstr ""
#~ "Selecteer receptbestanden om te importeren of sleep ze hier naar toe..."
#~ msgid "Paste json or html source here to load recipe."
#~ msgstr "Plak json of html bron om recept te laden."
#~ msgid "Preview Recipe Data"
#~ msgstr "Bekijk recept gegevens"
#~ msgid ""
#~ "Drag recipe attributes from the right into the appropriate box below."
#~ msgstr ""
#~ "Sleep eigenschappen van het recept van rechts naar het juiste vlak "
#~ "beneden."
#~ msgid "Clear Contents"
#~ msgstr "Wis inhoud"
#~ msgid "Text dragged here will be appended to the name."
#~ msgstr "Hierheen gesleepte tekst wordt aan de naam toegevoegd."
#~ msgid "Text dragged here will be appended to the description."
#~ msgstr "Hierheen gesleepte tekst wordt aan de beschrijving toegevoegd."
#~ msgid "Keywords dragged here will be appended to current list"
#~ msgstr "Hierheen gesleepte Etiketten worden aan de huidige lijst toegevoegd"
#~ msgid "Image"
#~ msgstr "Afbeelding"
#~ msgid "Prep Time"
#~ msgstr "Voorbereidingstijd"
#~ msgid "Cook Time"
#~ msgstr "Kooktijd"
#~ msgid "Ingredients dragged here will be appended to current list."
#~ msgstr ""
#~ "Hierheen gesleepte Ingrediënten worden aan de huidige lijst toegevoegd."
#~ msgid ""
#~ "Recipe instructions dragged here will be appended to current instructions."
#~ msgstr ""
#~ "Hierheen gesleepte Recept instructies worden aan de huidige lijst "
#~ "toegevoegd."
#~ msgid "Discovered Attributes"
#~ msgstr "Ontdekte Eigenschappen"
#~ msgid ""
#~ "Drag recipe attributes from below into the appropriate box on the left. "
#~ "Click any node to display its full properties."
#~ msgstr ""
#~ "Sleep recept eigenschappen van beneden naar de juiste doos aan de "
#~ "linkerzijde. Klik er op om alle eigenschappen te zien."
#~ msgid "Show Blank Field"
#~ msgstr "Toon Leeg Veld"
#~ msgid "Blank Field"
#~ msgstr "Leeg Veld"
#~ msgid "Items dragged to Blank Field will be appended."
#~ msgstr "Naar Leeg Veld gesleepte items worden toegevoegd."
#~ msgid "Delete Text"
#~ msgstr "Verwijder tekst"
#~ msgid "Delete image"
#~ msgstr "Verwijder afbeelding"
#~ msgid "Recipe Name"
#~ msgstr "Naam Recept"
#~ msgid "Recipe Description"
#~ msgstr "Beschrijving recept"
#~ msgid "Select one"
#~ msgstr "Selecteer één"
#~ msgid "Note"
#~ msgstr "Notitie"
#~ msgid "Add Keyword"
#~ msgstr "Voeg Etiket toe"
#~ msgid "All Keywords"
#~ msgstr "Alle etiketten"
#~ msgid "Import all keywords, not only the ones already existing."
#~ msgstr "Importeer alle etiketten, niet alleen de bestaande."
#~ msgid "Information"
#~ msgstr "Informatie"
#~ msgid ""
#~ " Only websites containing ld+json or microdata information can currently\n"
#~ " be imported. Most big recipe pages "
#~ "support this. If you site cannot be imported but\n"
#~ " you think\n"
#~ " it probably has some kind of "
#~ "structured data feel free to post an example in the\n"
#~ " github issues."
#~ msgstr ""
#~ " Alleen websites die Id+json of microdata informatie bevatten kunnen op "
#~ "dit moment geïmporteerd worden. \n"
#~ " De meeste grote recepten websites ondersteunen dit. Als jouw website "
#~ "niet geïmporteerd kan worden maar \n"
#~ " je denkt dat het waarschijnlijk gestructureerde data bevat, voel je dan "
#~ "vrij om een voorbeeld te posten in \n"
#~ " de GitHub issues."
#~ msgid "Google ld+json Info"
#~ msgstr "Google Id+json Info"
#~ msgid "GitHub Issues"
#~ msgstr "GitHub issues"
#~ msgid "Recipe Markup Specification"
#~ msgstr "Recept opmaak specificatie"
#~ msgid "Rating a recipe should have. [0 - 5]"
#~ msgstr "Waardering die een recept moet hebben. [0 - 5]"
#~ msgid ""
#~ "If recipe should have all (AND=false) or any (OR=true) of the "
#~ "provided keywords."
#~ msgstr ""
#~ "Als een recept alle moet hebben (AND=onwaar) of sommige (OR=waar) "
#~ "van de gegeven zoektermen."
#~ msgid ""
#~ "If recipe should have all (AND=false) or any (OR=true) of the "
#~ "provided foods."
#~ msgstr ""
#~ "Als een recept alle moet hebben (AND=onwaar) of sommige (OR=waar) "
#~ "van de gegeven ingrediënten."
#~ msgid ""
#~ "If recipe should be in all (AND=false) or any (OR=true) of the "
#~ "provided books."
#~ msgstr ""
#~ "Als een recept alle moet hebben (AND=onwaar) of sommige (OR=waar) "
#~ "van de gegeven boeken."
#~ msgid "The requested site provided malformed data and cannot be read."
#~ msgstr ""
#~ "De opgevraagde site heeft misvormde data verstrekt en kan niet gelezen "
#~ "worden."
#~ msgid "The requested page could not be found."
#~ msgstr "De opgevraagde pagina kon niet gevonden worden."
#~ msgid ""
#~ "The requested site does not provide any recognized data format to import "
#~ "the recipe from."
#~ msgstr ""
#~ "De opgevraagde site biedt geen bekend gegevensformaat aan om het recept "
#~ "van te importeren."
#~ msgid "I couldn't find anything to do."
#~ msgstr "Ik kon niks vinden om te doen."
#~ msgid "Shopping Lists"
#~ msgstr "Boodschappenlijst"
#~ msgid "You must supply a recipe or mealplan"
#~ msgstr "Je moet een recept of maaltijdplan aanleveren"
#~ msgid "Time"
#~ msgstr "Tijd"
#~ msgid "Log Recipe Cooking"
#~ msgstr "logboek recept koken"
#~ msgid "All fields are optional and can be left empty."
#~ msgstr "Alle velden zijn optioneel en mogen leeg gelaten worden."
#~ msgid "Rating"
#~ msgstr "Beoordeling"
#~ msgid "Exporting is not implemented for this provider"
#~ msgstr "Exporteren is voor deze provider niet geïmplementeerd"
#~ msgid "No {self.basename} with id {child} exists"
#~ msgstr "Er bestaat geen {self.basename} met id {child}"
#~ msgid "New unit that other gets replaced by."
#~ msgstr "Nieuwe eenheid waarmee de andere wordt vervangen."
#~ msgid "Old Unit"
#~ msgstr "Oude eenheid"
#~ msgid "Unit that should be replaced."
#~ msgstr "Eenheid die vervangen dient te worden."
#~ msgid "New Food"
#~ msgstr "Nieuw Ingredïent"
#~ msgid "New food that other gets replaced by."
#~ msgstr "Nieuw Ingredïent dat Oud Ingrediënt vervangt."
#~ msgid "Old Food"
#~ msgstr "Oud Ingrediënt"
#~ msgid "New Entry"
#~ msgstr "Nieuw item"
#~ msgid "Title"
#~ msgstr "Titel"
#~ msgid "Note (optional)"
#~ msgstr "Notitie (optioneel)"
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "Je kan markdown gebruiken om dit veld op te maken. Zie de documentatie"
#~ msgid "Serving Count"
#~ msgstr "Portie teller"
#~ msgid "Create only note"
#~ msgstr "Maak alleen een notitie"
#~ msgid "Number of Days"
#~ msgstr "Aantal dagen"
#~ msgid "Weekday offset"
#~ msgstr "Weekdag aanpassing"
#~ msgid ""
#~ "Number of days starting from the first day of the week to offset the "
#~ "default view."
#~ msgstr ""
#~ "Aantal dagen startende met de eerste dag van de week om het standaard "
#~ "overzicht aan te passen."
#~ msgid "Edit plan types"
#~ msgstr "Bewerk maaltijdplan types"
#~ msgid "Show help"
#~ msgstr "Help"
#~ msgid "Week iCal export"
#~ msgstr "Exporteer week als iCal"
#~ msgid "Add to Shopping"
#~ msgstr "Voeg toe aan boodschappenlijst"
#~ msgid "New meal type"
#~ msgstr "Nieuwe maaltijdsoort"
#~ msgid "Meal Plan Help"
#~ msgstr "Maaltijdplanner hulp"
#~ msgid ""
#~ "\n"
#~ " The meal plan module allows planning of "
#~ "meals both with recipes and notes.
\n"
#~ " Simply select a recipe from the list of "
#~ "recently viewed recipes or search the one you\n"
#~ " want and drag it to the desired plan "
#~ "position. You can also add a note and a title and\n"
#~ " then drag the recipe to create a plan "
#~ "entry with a custom title and note. Creating only\n"
#~ " Notes is possible by dragging the create "
#~ "note box into the plan.
\n"
#~ " Click on a recipe in order to open the "
#~ "detailed view. There you can also add it to the\n"
#~ " shopping list. You can also add all "
#~ "recipes of a day to the shopping list by\n"
#~ " clicking the shopping cart at the top of "
#~ "the table.
\n"
#~ " Since a common use case is to plan meals "
#~ "together you can define\n"
#~ " users you want to share your plan with in "
#~ "the settings.\n"
#~ "
\n"
#~ " You can also edit the types of meals you "
#~ "want to plan. If you share your plan with\n"
#~ " someone with\n"
#~ " different meals, their meal types will "
#~ "appear in your list as well. To prevent\n"
#~ " duplicates (e.g. Other and Misc.)\n"
#~ " name your meal types the same as the "
#~ "users you share your meals with and they will be\n"
#~ " merged.
\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " De maaltijdplan module maakt plannen van "
#~ "maaltijden met recepten en notities mogelijk.
\n"
#~ " Selecteer een recept van de lijst recent bekeken recepten of zoek "
#~ "naar\n"
#~ " het gewenste recept en sleep het naar de juiste positie in het "
#~ "maaltijdplan. Je kan ook eerst een notitie en titel toevoegen en dan het "
#~ "recept naar de juiste positie slepen om een unieke maaltijdplan "
#~ "inschrijving te maken.\n"
#~ " Alleen notities aanmaken is mogelijk door het Maak notitie vlak in het "
#~ "maaltijdplan te slepen.
\n"
#~ " Klik op een recept om de gedetailleerde weergave te openen. Daar kan "
#~ "je het ook toevoegen aan je boodschappenlijst.\n"
#~ " Je kan ook alle recepten van een dag aan je boodschappenlijst toevoegen "
#~ "door op het winkelwagentje boven aan de tabel te klikken.
\n"
#~ " Omdat maaltijden samen gepland kunnen worden kan je in de "
#~ "instellingen kiezen met welke gebruikers je het maaltijd plan wil delen."
#~ "p>\n"
#~ "
Je kan ook het type maaltijd dat je wil plannen bewerken. Als je een "
#~ "maaltijdplan deelt met iemand met andere maaltijden, dan zullen hun "
#~ "maaltijdtypes ook in jouw lijst verschijnen. Geef, om dubbelingen (zoals "
#~ "Overig en Anders) te voorkomen, je maaltijdtypes daarom dezelfde naam als "
#~ "de gebruikers waarmee je maaltijdplannen deelt. In dat geval worden de "
#~ "maaltijden samengevoegd.
\n"
#~ " "
#~ msgid "Units merged!"
#~ msgstr "Eenheden samengevoegd!"
#~ msgid "Foods merged!"
#~ msgstr "Ingrediënten samengevoegd!"
#~ msgid "Utensils"
#~ msgstr "Kookgerei"
#~ msgid "Storage Data"
#~ msgstr "Dataopslag"
#~ msgid "Storage Backends"
#~ msgstr "Opslag Backends"
#~ msgid "Configure Sync"
#~ msgstr "Synchronisatie configureren"
#~ msgid "Discovered Recipes"
#~ msgstr "Ontdekte recepten"
#~ msgid "Discovery Log"
#~ msgstr "Ontdekkingslogboek"
#~ msgid "Units & Ingredients"
#~ msgstr "Eenheden & Ingrediënten"
#~ msgid "New Book"
#~ msgstr "Nieuw boek"
#~ msgid "Toggle Recipes"
#~ msgstr "Recepten in/uitschakelen"
#~ msgid "There are no recipes in this book yet."
#~ msgstr "In dit boek bestaan nog geen recepten."
#~ msgid "Waiting Time"
#~ msgstr "Wachttijd"
#~ msgid "Servings Text"
#~ msgstr "Porties tekst"
#~ msgid "Select Keywords"
#~ msgstr "Selecteer etiketten"
#~ msgid "Delete Step"
#~ msgstr "Verwijder stap"
#~ msgid "Step"
#~ msgstr "Stap"
#~ msgid "Show as header"
#~ msgstr "Laat als kop zien"
#~ msgid "Hide as header"
#~ msgstr "Verbergen als kop"
#~ msgid "Move Up"
#~ msgstr "Verplaats omhoog"
#~ msgid "Move Down"
#~ msgstr "Verplaats omlaag"
#~ msgid "Step Name"
#~ msgstr "Stap naam"
#~ msgid "Step Type"
#~ msgstr "Stap type"
#~ msgid "Step time in Minutes"
#~ msgstr "Tijdsduur stap in minuten"
#~ msgid "Select File"
#~ msgstr "Selecteer bestand"
#~ msgid "Select Recipe"
#~ msgstr "Selecteer recept"
#~ msgid "Delete Ingredient"
#~ msgstr "Verwijder ingrediënt"
#~ msgid "Make Header"
#~ msgstr "Stel in als kop"
#~ msgid "Make Ingredient"
#~ msgstr "Maak ingrediënt"
#~ msgid "Disable Amount"
#~ msgstr "Hoeveelheid uitschakelen"
#~ msgid "Enable Amount"
#~ msgstr "Hoeveelheid inschakelen"
#~ msgid "Copy Template Reference"
#~ msgstr "Kopieer sjabloon referentie"
#~ msgid "Save & View"
#~ msgstr "Opslaan & bekijken"
#~ msgid "Add Step"
#~ msgstr "Voeg stap toe"
#~ msgid "Add Nutrition"
#~ msgstr "Voedingswaarde toevoegen"
#~ msgid "Remove Nutrition"
#~ msgstr "Voedingswaarde verwijderen"
#~ msgid "View Recipe"
#~ msgstr "Bekijk recept"
#~ msgid "Delete Recipe"
#~ msgstr "Verwijder recept"
#~ msgid "Password Settings"
#~ msgstr "Wachtwoord instellingen"
#~ msgid "Email Settings"
#~ msgstr "E-mail instellingen"
#~ msgid "Manage Social Accounts"
#~ msgstr "Beheer sociale media accounts"
#~ msgid ""
#~ "A username is not required, if left blank the new user can choose one."
#~ msgstr ""
#~ "Een gebruikersnaam is niet verplicht. Als het veld leeg is kan de "
#~ "gebruiker er een kiezen."
#~ msgid "Link"
#~ msgstr "Link"
#~ msgid "Logout"
#~ msgstr "Uitloggen"
#~ msgid "Website Import"
#~ msgstr "Importeer website"
#~ msgid "You are not a member of any space."
#~ msgstr "Je bent geen lid van een ruimte."
#~ msgid "There was an error creating a resource!"
#~ msgstr "Er is een fout opgetreden bij het maken van een hulpbron!"
#~ msgid "Enter json directly"
#~ msgstr "Geef json direct op"
#~ msgid ""
#~ "The requested page refused to provide any information (Status Code 403)."
#~ msgstr ""
#~ "De opgevraagde pagina weigert informatie te verstrekken (Statuscode 403)."
#~ msgid "Could not parse correctly..."
#~ msgstr "Kon niet goed verwerken.."
#~ msgid "Number of servings"
#~ msgstr "Porties"
#~ msgid ""
#~ "Include - [ ] in list for easier usage in markdown based "
#~ "documents."
#~ msgstr ""
#~ "Voeg -[ ]in de lijst toe voor gemakkelijker gebruik in op "
#~ "markdown gebaseerde documenten."
#~ msgid "Backup & Restore"
#~ msgstr "Backup & Herstel"
#~ msgid "Download Backup"
#~ msgstr "Download Backup"
#~ msgid "Preference for given user already exists"
#~ msgstr "Voorkeur voor gebruiker bestaat al"
#~ msgid "This recipe is already linked to the book!"
#~ msgstr "Dit recept is al aan het boek gekoppeld!"
================================================
FILE: cookbook/locale/nn/LC_MESSAGES/django.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-22 20:15+0200\n"
"PO-Revision-Date: 2026-02-10 12:07+0000\n"
"Last-Translator: Sigurd Fyllingsnes \n"
"Language-Team: Norwegian Nynorsk \n"
"Language: nn\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.13.3\n"
#: .\cookbook\forms.py:50
msgid "Default"
msgstr "Standard"
#: .\cookbook\forms.py:77
msgid ""
"To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything."
msgstr ""
#: .\cookbook\forms.py:108
msgid "Maximum number of users for this space reached."
msgstr ""
#: .\cookbook\forms.py:114
msgid "Email address already taken!"
msgstr ""
#: .\cookbook\forms.py:121
msgid ""
"An email address is not required but if present the invite link will be sent "
"to the user."
msgstr ""
#: .\cookbook\forms.py:133
msgid "Name already taken."
msgstr ""
#: .\cookbook\forms.py:144 .\cookbook\forms.py:158
msgid "Accept Terms and Privacy"
msgstr ""
#: .\cookbook\helper\AllAuthCustomAdapter.py:41
msgid ""
"In order to prevent spam, the requested email was not send. Please wait a "
"few minutes and try again."
msgstr ""
#: .\cookbook\helper\permission_helper.py:165
#: .\cookbook\helper\permission_helper.py:186 .\cookbook\views\views.py:138
msgid "You are not logged in and therefore cannot view this page!"
msgstr ""
#: .\cookbook\helper\permission_helper.py:168
#: .\cookbook\helper\permission_helper.py:173
#: .\cookbook\helper\permission_helper.py:198
#: .\cookbook\helper\permission_helper.py:265
#: .\cookbook\helper\permission_helper.py:279
#: .\cookbook\helper\permission_helper.py:290
#: .\cookbook\helper\permission_helper.py:301
#: .\cookbook\helper\permission_helper.py:317
#: .\cookbook\helper\permission_helper.py:343
#: .\cookbook\helper\permission_helper.py:359
msgid "You do not have the required permissions to view this page!"
msgstr ""
#: .\cookbook\helper\permission_helper.py:191
#: .\cookbook\helper\permission_helper.py:214
#: .\cookbook\helper\permission_helper.py:236
#: .\cookbook\helper\permission_helper.py:251
msgid "You cannot interact with this object as it is not owned by you!"
msgstr ""
#: .\cookbook\helper\permission_helper.py:420
msgid "You have reached the maximum number of recipes for your space."
msgstr ""
#: .\cookbook\helper\permission_helper.py:432
msgid "You have more users than allowed in your space."
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:319
msgid "reverse rotation"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:320
msgid "careful rotation"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:321
msgid "knead"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:322
msgid "thicken"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:323
msgid "warm up"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:324
msgid "ferment"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:325
msgid "slow cook"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:326
msgid "egg boiler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:327
msgid "kettle"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:328
msgid "blend"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:329
msgid "pre-clean"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:330
msgid "high temperature"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:331
msgid "rice cooker"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:332
msgid "caramelize"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:333
msgid "peeler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:334
msgid "slicer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:335
msgid "grater"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:336
msgid "spiralizer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:337
msgid "sous-vide"
msgstr ""
#: .\cookbook\helper\shopping_helper.py:150
msgid "You must supply a servings size"
msgstr ""
#: .\cookbook\helper\template_helper.py:97
#: .\cookbook\helper\template_helper.py:99
#: .\cookbook\helper\template_helper.py:101
msgid "Could not parse template code."
msgstr ""
#: .\cookbook\integration\copymethat.py:44
#: .\cookbook\integration\melarecipes.py:37
msgid "Favorite"
msgstr ""
#: .\cookbook\integration\copymethat.py:50
msgid "I made this"
msgstr ""
#: .\cookbook\integration\integration.py:238
msgid ""
"Importer expected a .zip file. Did you choose the correct importer type for "
"your data ?"
msgstr ""
#: .\cookbook\integration\integration.py:241
msgid ""
"An unexpected error occurred during the import. Please make sure you have "
"uploaded a valid file."
msgstr ""
#: .\cookbook\integration\integration.py:246
msgid "The following recipes were ignored because they already existed:"
msgstr ""
#: .\cookbook\integration\integration.py:250
#, python-format
msgid "Imported %s recipes."
msgstr ""
#: .\cookbook\integration\mealie1.py:210
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "Calories"
msgstr ""
#: .\cookbook\integration\mealie1.py:211
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
msgid "Carbohydrates"
msgstr ""
#: .\cookbook\integration\mealie1.py:212
msgid "Cholesterol"
msgstr ""
#: .\cookbook\integration\mealie1.py:213
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
msgid "Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:214
msgid "Fiber"
msgstr ""
#: .\cookbook\integration\mealie1.py:215
msgid "Protein"
msgstr ""
#: .\cookbook\integration\mealie1.py:216
msgid "Saturated Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:217
msgid "Sodium"
msgstr ""
#: .\cookbook\integration\mealie1.py:218
msgid "Sugar"
msgstr ""
#: .\cookbook\integration\mealie1.py:219
msgid "Trans Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:220
msgid "Unsaturated Fat"
msgstr ""
#: .\cookbook\integration\openeats.py:28
msgid "Recipe source:"
msgstr ""
#: .\cookbook\integration\paprika.py:49
msgid "Notes"
msgstr ""
#: .\cookbook\integration\paprika.py:52
msgid "Nutritional Information"
msgstr ""
#: .\cookbook\integration\paprika.py:56
msgid "Source"
msgstr ""
#: .\cookbook\integration\recettetek.py:55
#: .\cookbook\integration\recipekeeper.py:70
msgid "Imported from"
msgstr ""
#: .\cookbook\integration\saffron.py:23
msgid "Servings"
msgstr ""
#: .\cookbook\integration\saffron.py:25
msgid "Waiting time"
msgstr ""
#: .\cookbook\integration\saffron.py:27
msgid "Preparation Time"
msgstr ""
#: .\cookbook\integration\saffron.py:29 .\cookbook\templates\index.html:6
msgid "Cookbook"
msgstr ""
#: .\cookbook\integration\saffron.py:31
msgid "Section"
msgstr ""
#: .\cookbook\management\commands\fix_duplicate_properties.py:15
msgid "Fixes foods with "
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:14
msgid "Rebuilds full text search index on Recipe"
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:18
msgid "Only Postgresql databases use full text search, no index to rebuild"
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:29
msgid "Recipe index rebuild complete."
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:31
msgid "Recipe index rebuild failed."
msgstr ""
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
msgid "Breakfast"
msgstr ""
#: .\cookbook\migrations\0047_auto_20200602_1133.py:19
msgid "Lunch"
msgstr ""
#: .\cookbook\migrations\0047_auto_20200602_1133.py:24
msgid "Dinner"
msgstr ""
#: .\cookbook\migrations\0047_auto_20200602_1133.py:29 .\cookbook\models.py:971
msgid "Other"
msgstr ""
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "g"
msgstr ""
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "Proteins"
msgstr ""
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "kcal"
msgstr ""
#: .\cookbook\models.py:325
msgid ""
"Maximum file storage for space in MB. 0 for unlimited, -1 to disable file "
"upload."
msgstr ""
#: .\cookbook\models.py:513
msgid "Search"
msgstr ""
#: .\cookbook\models.py:514
msgid "Meal-Plan"
msgstr ""
#: .\cookbook\models.py:515
msgid "Books"
msgstr ""
#: .\cookbook\models.py:516 .\cookbook\views\views.py:416
#: .\cookbook\views\views.py:417
msgid "Shopping"
msgstr ""
#: .\cookbook\models.py:967
msgid "Nutrition"
msgstr ""
#: .\cookbook\models.py:968
msgid "Allergen"
msgstr ""
#: .\cookbook\models.py:969
msgid "Price"
msgstr ""
#: .\cookbook\models.py:970
msgid "Goal"
msgstr ""
#: .\cookbook\models.py:1467 .\cookbook\templates\search_info.html:28
msgid "Simple"
msgstr ""
#: .\cookbook\models.py:1468 .\cookbook\templates\search_info.html:33
msgid "Phrase"
msgstr ""
#: .\cookbook\models.py:1469 .\cookbook\templates\search_info.html:38
msgid "Web"
msgstr ""
#: .\cookbook\models.py:1470 .\cookbook\templates\search_info.html:47
msgid "Raw"
msgstr ""
#: .\cookbook\models.py:1525
msgid "Food Alias"
msgstr ""
#: .\cookbook\models.py:1526
msgid "Unit Alias"
msgstr ""
#: .\cookbook\models.py:1527
msgid "Keyword Alias"
msgstr ""
#: .\cookbook\models.py:1528
msgid "Description Replace"
msgstr ""
#: .\cookbook\models.py:1529
msgid "Instruction Replace"
msgstr ""
#: .\cookbook\models.py:1530
msgid "Never Unit"
msgstr ""
#: .\cookbook\models.py:1531
msgid "Transpose Words"
msgstr ""
#: .\cookbook\models.py:1532
msgid "Food Replace"
msgstr ""
#: .\cookbook\models.py:1533
msgid "Unit Replace"
msgstr ""
#: .\cookbook\models.py:1534
msgid "Name Replace"
msgstr ""
#: .\cookbook\models.py:1564
msgid "Recipe"
msgstr ""
#: .\cookbook\models.py:1565
msgid "Food"
msgstr ""
#: .\cookbook\models.py:1566
msgid "Keyword"
msgstr ""
#: .\cookbook\serializer.py:262
msgid "File uploads are not enabled for this Space."
msgstr ""
#: .\cookbook\serializer.py:273
msgid "You have reached your file upload limit."
msgstr ""
#: .\cookbook\serializer.py:281
msgid "The given file type is not allowed."
msgstr ""
#: .\cookbook\serializer.py:421 .\cookbook\views\views.py:94
msgid ""
"You have the reached the maximum amount of spaces that can be owned by you."
msgstr ""
#: .\cookbook\serializer.py:434
msgid "Space Name must be unique."
msgstr ""
#: .\cookbook\serializer.py:469
msgid "Cannot modify Space owner permission."
msgstr ""
#: .\cookbook\serializer.py:1596
msgid "Hello"
msgstr ""
#: .\cookbook\serializer.py:1596
msgid "You have been invited by "
msgstr ""
#: .\cookbook\serializer.py:1598
msgid " to join their Tandoor Recipes space "
msgstr ""
#: .\cookbook\serializer.py:1600
msgid "Click the following link to activate your account: "
msgstr ""
#: .\cookbook\serializer.py:1602
msgid ""
"If the link does not work use the following code to manually join the space: "
msgstr ""
#: .\cookbook\serializer.py:1604
msgid "The invitation is valid until "
msgstr ""
#: .\cookbook\serializer.py:1606
msgid ""
"Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub "
msgstr ""
#: .\cookbook\serializer.py:1609
msgid "Tandoor Recipes Invite"
msgstr ""
#: .\cookbook\serializer.py:1813
msgid "Existing shopping list to update"
msgstr ""
#: .\cookbook\serializer.py:1815
msgid ""
"List of ingredient IDs from the recipe to add, if not provided all "
"ingredients will be added."
msgstr ""
#: .\cookbook\serializer.py:1817
msgid ""
"Providing a list_recipe ID and servings of 0 will delete that shopping list."
msgstr ""
#: .\cookbook\serializer.py:1826
msgid "Amount of food to add to the shopping list"
msgstr ""
#: .\cookbook\serializer.py:1828
msgid "ID of unit to use for the shopping list"
msgstr ""
#: .\cookbook\serializer.py:1830
msgid "When set to true will delete all food from active shopping lists."
msgstr ""
#: .\cookbook\templates\404.html:5
msgid "404 Error"
msgstr ""
#: .\cookbook\templates\404.html:18
msgid "The page you are looking for could not be found."
msgstr ""
#: .\cookbook\templates\404.html:33
msgid "Take me Home"
msgstr ""
#: .\cookbook\templates\404.html:35
msgid "Report a Bug"
msgstr ""
#: .\cookbook\templates\account\email.html:6
#: .\cookbook\templates\account\email.html:10
msgid "E-mail Addresses"
msgstr ""
#: .\cookbook\templates\account\email.html:12
msgid "The following e-mail addresses are associated with your account:"
msgstr ""
#: .\cookbook\templates\account\email.html:29
msgid "Verified"
msgstr ""
#: .\cookbook\templates\account\email.html:31
msgid "Unverified"
msgstr ""
#: .\cookbook\templates\account\email.html:33
msgid "Primary"
msgstr ""
#: .\cookbook\templates\account\email.html:40
msgid "Make Primary"
msgstr ""
#: .\cookbook\templates\account\email.html:42
msgid "Re-send Verification"
msgstr ""
#: .\cookbook\templates\account\email.html:43
#: .\cookbook\templates\socialaccount\connections.html:36
msgid "Remove"
msgstr ""
#: .\cookbook\templates\account\email.html:51
msgid "Warning:"
msgstr ""
#: .\cookbook\templates\account\email.html:51
msgid ""
"You currently do not have any e-mail address set up. You should really add "
"an e-mail address so you can receive notifications, reset your password, etc."
msgstr ""
#: .\cookbook\templates\account\email.html:57
msgid "Add E-mail Address"
msgstr ""
#: .\cookbook\templates\account\email.html:62
msgid "Add E-mail"
msgstr ""
#: .\cookbook\templates\account\email.html:72
msgid "Do you really want to remove the selected e-mail address?"
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:6
#: .\cookbook\templates\account\email_confirm.html:10
msgid "Confirm E-mail Address"
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:16
#, python-format
msgid ""
"Please confirm that\n"
" %(email)s is an e-mail address "
"for user %(user_display)s\n"
" ."
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:22
msgid "Confirm"
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:29
#, python-format
msgid ""
"This e-mail confirmation link expired or is invalid. Please\n"
" issue a new e-mail confirmation "
"request."
msgstr ""
#: .\cookbook\templates\account\login.html:8
#: .\cookbook\templates\openid\login.html:8
msgid "Login"
msgstr ""
#: .\cookbook\templates\account\login.html:15
#: .\cookbook\templates\account\login.html:31
#: .\cookbook\templates\account\password_reset.html:39
#: .\cookbook\templates\account\password_reset_done.html:31
#: .\cookbook\templates\account\signup.html:68
#: .\cookbook\templates\account\signup_closed.html:15
#: .\cookbook\templates\openid\login.html:15
#: .\cookbook\templates\openid\login.html:26
#: .\cookbook\templates\socialaccount\authentication_error.html:15
msgid "Sign In"
msgstr ""
#: .\cookbook\templates\account\login.html:34
#: .\cookbook\templates\account\password_reset.html:41
#: .\cookbook\templates\account\password_reset_done.html:33
#: .\cookbook\templates\socialaccount\signup.html:8
#: .\cookbook\templates\socialaccount\signup.html:56
msgid "Sign Up"
msgstr ""
#: .\cookbook\templates\account\login.html:38
msgid "Lost your password?"
msgstr ""
#: .\cookbook\templates\account\login.html:39
#: .\cookbook\templates\account\password_reset.html:29
msgid "Reset My Password"
msgstr ""
#: .\cookbook\templates\account\login.html:50
msgid "Social Login"
msgstr ""
#: .\cookbook\templates\account\login.html:51
msgid "You can use any of the following providers to sign in."
msgstr ""
#: .\cookbook\templates\account\logout.html:5
#: .\cookbook\templates\account\logout.html:9
#: .\cookbook\templates\account\logout.html:18
msgid "Sign Out"
msgstr ""
#: .\cookbook\templates\account\logout.html:11
msgid "Are you sure you want to sign out?"
msgstr ""
#: .\cookbook\templates\account\password_change.html:6
#: .\cookbook\templates\account\password_change.html:10
#: .\cookbook\templates\account\password_change.html:15
#: .\cookbook\templates\account\password_reset_from_key.html:7
#: .\cookbook\templates\account\password_reset_from_key.html:13
#: .\cookbook\templates\account\password_reset_from_key_done.html:7
#: .\cookbook\templates\account\password_reset_from_key_done.html:13
msgid "Change Password"
msgstr ""
#: .\cookbook\templates\account\password_change.html:16
msgid "Forgot Password?"
msgstr ""
#: .\cookbook\templates\account\password_reset.html:7
#: .\cookbook\templates\account\password_reset.html:13
#: .\cookbook\templates\account\password_reset_done.html:7
#: .\cookbook\templates\account\password_reset_done.html:18
msgid "Password Reset"
msgstr ""
#: .\cookbook\templates\account\password_reset.html:24
msgid ""
"Forgotten your password? Enter your e-mail address below, and we'll send you "
"an e-mail allowing you to reset it."
msgstr ""
#: .\cookbook\templates\account\password_reset.html:32
msgid "Password reset is disabled on this instance."
msgstr ""
#: .\cookbook\templates\account\password_reset_done.html:25
msgid ""
"We have sent you an e-mail. Please contact us if you do not receive it "
"within a few minutes."
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:13
msgid "Bad Token"
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:25
#, python-format
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used.\n"
" Please request a new "
"password reset."
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:33
msgid "change password"
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:36
#: .\cookbook\templates\account\password_reset_from_key_done.html:19
msgid "Your password is now changed."
msgstr ""
#: .\cookbook\templates\account\password_set.html:6
#: .\cookbook\templates\account\password_set.html:10
#: .\cookbook\templates\account\password_set.html:15
msgid "Set Password"
msgstr ""
#: .\cookbook\templates\account\signup.html:5
msgid "Register"
msgstr ""
#: .\cookbook\templates\account\signup.html:11
msgid "Create an Account"
msgstr ""
#: .\cookbook\templates\account\signup.html:41
msgid "I accept the follwoing"
msgstr ""
#: .\cookbook\templates\account\signup.html:44
#: .\cookbook\templates\socialaccount\signup.html:35
msgid "Terms and Conditions"
msgstr ""
#: .\cookbook\templates\account\signup.html:47
#: .\cookbook\templates\socialaccount\signup.html:38
msgid "and"
msgstr ""
#: .\cookbook\templates\account\signup.html:51
#: .\cookbook\templates\socialaccount\signup.html:42
msgid "Privacy Policy"
msgstr ""
#: .\cookbook\templates\account\signup.html:64
msgid "Create User"
msgstr ""
#: .\cookbook\templates\account\signup.html:68
msgid "Already have an account?"
msgstr ""
#: .\cookbook\templates\account\signup_closed.html:5
#: .\cookbook\templates\account\signup_closed.html:11
msgid "Sign Up Closed"
msgstr ""
#: .\cookbook\templates\account\signup_closed.html:13
msgid "We are sorry, but the sign up is currently closed."
msgstr ""
#: .\cookbook\templates\frontend\tandoor.html:15
msgid "Tandoor Recipe Manager"
msgstr ""
#: .\cookbook\templates\index.html:28
msgid "Search recipe ..."
msgstr ""
#: .\cookbook\templates\index.html:43
msgid "New Recipe"
msgstr ""
#: .\cookbook\templates\index.html:46
msgid "Import Recipe"
msgstr ""
#: .\cookbook\templates\index.html:52
msgid "Advanced Search"
msgstr ""
#: .\cookbook\templates\index.html:56
msgid "Reset Search"
msgstr ""
#: .\cookbook\templates\index.html:84
msgid "Last viewed"
msgstr ""
#: .\cookbook\templates\index.html:86
msgid "Recipes"
msgstr ""
#: .\cookbook\templates\index.html:93
msgid "Log in to view recipes"
msgstr ""
#: .\cookbook\templates\markdown_info.html:5
#: .\cookbook\templates\markdown_info.html:13
msgid "Markdown Info"
msgstr ""
#: .\cookbook\templates\markdown_info.html:14
msgid ""
"\n"
" Markdown is lightweight markup language that can be used to format "
"plain text easily.\n"
" This site uses the Python Markdown library to\n"
" convert your text into nice looking HTML. Its full markdown "
"documentation can be found\n"
" here.\n"
" An incomplete but most likely sufficient documentation can be found "
"below.\n"
" "
msgstr ""
#: .\cookbook\templates\markdown_info.html:25
msgid "Headers"
msgstr ""
#: .\cookbook\templates\markdown_info.html:54
msgid "Formatting"
msgstr ""
#: .\cookbook\templates\markdown_info.html:56
#: .\cookbook\templates\markdown_info.html:72
msgid "Line breaks are inserted by adding two spaces after the end of a line"
msgstr ""
#: .\cookbook\templates\markdown_info.html:57
#: .\cookbook\templates\markdown_info.html:73
msgid "or by leaving a blank line in between."
msgstr ""
#: .\cookbook\templates\markdown_info.html:59
#: .\cookbook\templates\markdown_info.html:74
msgid "This text is bold"
msgstr ""
#: .\cookbook\templates\markdown_info.html:60
#: .\cookbook\templates\markdown_info.html:75
msgid "This text is italic"
msgstr ""
#: .\cookbook\templates\markdown_info.html:61
#: .\cookbook\templates\markdown_info.html:77
msgid "Blockquotes are also possible"
msgstr ""
#: .\cookbook\templates\markdown_info.html:84
msgid "Lists"
msgstr ""
#: .\cookbook\templates\markdown_info.html:85
msgid ""
"Lists can ordered or unordered. It is important to leave a blank line "
"before the list!"
msgstr ""
#: .\cookbook\templates\markdown_info.html:87
#: .\cookbook\templates\markdown_info.html:108
msgid "Ordered List"
msgstr ""
#: .\cookbook\templates\markdown_info.html:89
#: .\cookbook\templates\markdown_info.html:90
#: .\cookbook\templates\markdown_info.html:91
#: .\cookbook\templates\markdown_info.html:110
#: .\cookbook\templates\markdown_info.html:111
#: .\cookbook\templates\markdown_info.html:112
msgid "unordered list item"
msgstr ""
#: .\cookbook\templates\markdown_info.html:93
#: .\cookbook\templates\markdown_info.html:114
msgid "Unordered List"
msgstr ""
#: .\cookbook\templates\markdown_info.html:95
#: .\cookbook\templates\markdown_info.html:96
#: .\cookbook\templates\markdown_info.html:97
#: .\cookbook\templates\markdown_info.html:116
#: .\cookbook\templates\markdown_info.html:117
#: .\cookbook\templates\markdown_info.html:118
msgid "ordered list item"
msgstr ""
#: .\cookbook\templates\markdown_info.html:125
msgid "Images & Links"
msgstr ""
#: .\cookbook\templates\markdown_info.html:126
msgid ""
"Links can be formatted with Markdown. This application also allows to paste "
"links directly into markdown fields without any formatting."
msgstr ""
#: .\cookbook\templates\markdown_info.html:132
#: .\cookbook\templates\markdown_info.html:145
msgid "This will become an image"
msgstr ""
#: .\cookbook\templates\markdown_info.html:152
msgid "Tables"
msgstr ""
#: .\cookbook\templates\markdown_info.html:153
msgid ""
"Markdown tables are hard to create by hand. It is recommended to use a table "
"editor like this one."
msgstr ""
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:171
#: .\cookbook\templates\markdown_info.html:177
msgid "Table"
msgstr ""
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:172
msgid "Header"
msgstr ""
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:178
msgid "Cell"
msgstr ""
#: .\cookbook\templates\no_groups_info.html:5
#: .\cookbook\templates\no_groups_info.html:12
msgid "No Permissions"
msgstr ""
#: .\cookbook\templates\no_groups_info.html:17
msgid "You do not have any groups and therefor cannot use this application."
msgstr ""
#: .\cookbook\templates\no_groups_info.html:18
#: .\cookbook\templates\no_perm_info.html:15
msgid "Please contact your administrator."
msgstr ""
#: .\cookbook\templates\no_perm_info.html:5
#: .\cookbook\templates\no_perm_info.html:12
msgid "No Permission"
msgstr ""
#: .\cookbook\templates\no_perm_info.html:15
msgid ""
"You do not have the required permissions to view this page or perform this "
"action."
msgstr ""
#: .\cookbook\templates\offline.html:5
msgid "Offline"
msgstr ""
#: .\cookbook\templates\openid\login.html:27
#: .\cookbook\templates\socialaccount\authentication_error.html:27
msgid "Back"
msgstr ""
#: .\cookbook\templates\rest_framework\api.html:5
msgid "Recipe Home"
msgstr ""
#: .\cookbook\templates\rest_framework\api.html:11
msgid "API Documentation"
msgstr ""
#: .\cookbook\templates\search_info.html:5
#: .\cookbook\templates\search_info.html:9
msgid "Search Settings"
msgstr ""
#: .\cookbook\templates\search_info.html:10
msgid ""
"\n"
" Creating the best search experience is complicated and weighs "
"heavily on your personal configuration. \n"
" Changing any of the search settings can have significant impact on "
"the speed and quality of the results.\n"
" Search Methods, Trigrams and Full Text Search configurations are "
"only available if you are using Postgres for your database.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:19
msgid "Search Methods"
msgstr ""
#: .\cookbook\templates\search_info.html:23
msgid ""
" \n"
" Full text searches attempt to normalize the words provided to "
"match common variants. For example: 'forked', 'forking', 'forks' will all "
"normalize to 'fork'.\n"
" There are several methods available, described below, that will "
"control how the search behavior should react when multiple words are "
"searched.\n"
" Full technical details on how these operate can be viewed on Postgresql's website.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:29
msgid ""
" \n"
" Simple searches ignore punctuation and common words such as "
"'the', 'a', 'and'. And will treat separate words as required.\n"
" Searching for 'apple or flour' will return any recipe that "
"includes both 'apple' and 'flour' anywhere in the fields that have been "
"selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:34
msgid ""
" \n"
" Phrase searches ignore punctuation, but will search for all of "
"the words in the exact order provided.\n"
" Searching for 'apple or flour' will only return a recipe that "
"includes the exact phrase 'apple or flour' in any of the fields that have "
"been selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:39
msgid ""
" \n"
" Web searches simulate functionality found on many web search "
"sites supporting special syntax.\n"
" Placing quotes around several words will convert those words "
"into a phrase.\n"
" 'or' is recognized as searching for the word (or phrase) "
"immediately before 'or' OR the word (or phrase) directly after.\n"
" '-' is recognized as searching for recipes that do not include "
"the word (or phrase) that comes immediately after. \n"
" For example searching for 'apple pie' or cherry -butter will "
"return any recipe that includes the phrase 'apple pie' or the word "
"'cherry' \n"
" in any field included in the full text search but exclude any "
"recipe that has the word 'butter' in any field included.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:48
msgid ""
" \n"
" Raw search is similar to Web except will take puncuation "
"operators such as '|', '&' and '()'\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:59
msgid ""
" \n"
" Another approach to searching that also requires Postgresql is "
"fuzzy search or trigram similarity. A trigram is a group of three "
"consecutive characters.\n"
" For example searching for 'apple' will create x trigrams 'app', "
"'ppl', 'ple' and will create a score of how closely words match the "
"generated trigrams.\n"
" One benefit of searching trigams is that a search for 'sandwich' "
"will find misspelled words such as 'sandwhich' that would be missed by other "
"methods.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:69
msgid "Search Fields"
msgstr ""
#: .\cookbook\templates\search_info.html:73
msgid ""
" \n"
" Unaccent is a special case in that it enables searching a field "
"'unaccented' for each search style attempting to ignore accented values. \n"
" For example when you enable unaccent for 'Name' any search "
"(starts with, contains, trigram) will attempt the search ignoring accented "
"characters.\n"
" \n"
" For the other options, you can enable search on any or all "
"fields and they will be combined together with an assumed 'OR'.\n"
" For example enabling 'Name' for Starts With, 'Name' and "
"'Description' for Partial Match and 'Ingredients' and 'Keywords' for Full "
"Search\n"
" and searching for 'apple' will generate a search that will "
"return recipes that have:\n"
" - A recipe name that starts with 'apple'\n"
" - OR a recipe name that contains 'apple'\n"
" - OR a recipe description that contains 'apple'\n"
" - OR a recipe that will have a full text search match ('apple' "
"or 'apples') in ingredients\n"
" - OR a recipe that will have a full text search match in "
"Keywords\n"
"\n"
" Combining too many fields in too many types of search can have a "
"negative impact on performance, create duplicate results or return "
"unexpected results.\n"
" For example, enabling fuzzy search or partial matches will "
"interfere with web search methods. \n"
" Searching for 'apple -pie' with fuzzy search and full text "
"search will return the recipe Apple Pie. Though it is not included in the "
"full text results, it does match the trigram results.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:95
msgid "Search Index"
msgstr ""
#: .\cookbook\templates\search_info.html:99
msgid ""
" \n"
" Trigram search and Full Text Search both rely on database "
"indexes to perform effectively. \n"
" You can rebuild the indexes on all fields in the Admin page for "
"Recipes and selecting all recipes and running 'rebuild index for selected "
"recipes'\n"
" You can also rebuild indexes at the command line by executing "
"the management command 'python manage.py rebuildindex'\n"
" "
msgstr ""
#: .\cookbook\templates\setup.html:6
msgid "Cookbook Setup"
msgstr ""
#: .\cookbook\templates\setup.html:14
msgid "Setup"
msgstr ""
#: .\cookbook\templates\setup.html:15
msgid ""
"To start using this application you must first create a superuser account."
msgstr ""
#: .\cookbook\templates\setup.html:20
msgid "Create Superuser account"
msgstr ""
#: .\cookbook\templates\socialaccount\authentication_error.html:7
#: .\cookbook\templates\socialaccount\authentication_error.html:23
msgid "Social Network Login Failure"
msgstr ""
#: .\cookbook\templates\socialaccount\authentication_error.html:25
msgid ""
"An error occurred while attempting to login via your social network account."
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:4
#: .\cookbook\templates\socialaccount\connections.html:7
msgid "Account Connections"
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:10
msgid ""
"You can sign in to your account using any of the following third party\n"
" accounts:"
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:44
msgid ""
"You currently have no social network accounts connected to this account."
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:47
msgid "Add a 3rd Party Account"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:5
#: .\cookbook\templates\socialaccount\signup.html:5
msgid "Signup"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:9
#, python-format
msgid "Connect %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:11
#, python-format
msgid "You are about to connect a new third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:13
#, python-format
msgid "Sign In Via %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:15
#, python-format
msgid "You are about to sign in using a third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:20
msgid "Continue"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:10
#, python-format
msgid ""
"You are about to use your\n"
" %(provider_name)s account to login to\n"
" %(site_name)s. As a final step, please complete the following form:"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:32
msgid "I accept the following"
msgstr ""
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:23
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:31
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:39
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:47
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:55
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:63
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:71
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:79
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:87
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:95
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:103
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:111
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:119
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:127
msgid "Sign in using"
msgstr ""
#: .\cookbook\templates\space_overview.html:6
msgid "Overview"
msgstr ""
#: .\cookbook\templates\space_overview.html:13
msgid "Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:17
msgid ""
"Recipes, foods, shopping lists and more are organized in spaces of one or "
"more people."
msgstr ""
#: .\cookbook\templates\space_overview.html:18
msgid ""
"You can either be invited into an existing space or create your own one."
msgstr ""
#: .\cookbook\templates\space_overview.html:25
msgid "Your Spaces"
msgstr ""
#: .\cookbook\templates\space_overview.html:53
msgid "Owner"
msgstr ""
#: .\cookbook\templates\space_overview.html:73
#: .\cookbook\templates\space_overview.html:83
msgid "Join Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:76
msgid "Join an existing space."
msgstr ""
#: .\cookbook\templates\space_overview.html:78
msgid ""
"To join an existing space either enter your invite token or click on the "
"invite link the space owner send you."
msgstr ""
#: .\cookbook\templates\space_overview.html:91
#: .\cookbook\templates\space_overview.html:100
msgid "Create Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:94
msgid "Create your own recipe space."
msgstr ""
#: .\cookbook\templates\space_overview.html:96
msgid "Start your own recipe space and invite other users to it."
msgstr ""
#: .\cookbook\templates\system.html:23
msgid "System"
msgstr ""
#: .\cookbook\templates\system.html:24
msgid ""
"\n"
" Tandoor Recipes is an open source free software application. It can "
"be found on\n"
" GitHub.\n"
" Changelogs can be found here.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:30
msgid "System Information"
msgstr ""
#: .\cookbook\templates\system.html:51
msgid ""
"\n"
" You need to execute version.py in your update "
"script to generate version information (done automatically in docker).\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:56
msgid "Plugins"
msgstr ""
#: .\cookbook\templates\system.html:67
msgid "Media Serving"
msgstr ""
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:123 .\cookbook\templates\system.html:134
msgid "Warning"
msgstr ""
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:125 .\cookbook\templates\system.html:134
msgid "Ok"
msgstr ""
#: .\cookbook\templates\system.html:70
msgid ""
"Serving media files directly using gunicorn/python is not recommend!\n"
" Please follow the steps described\n"
" here to update\n"
" your installation.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:76 .\cookbook\templates\system.html:91
#: .\cookbook\templates\system.html:104 .\cookbook\templates\system.html:115
#: .\cookbook\views\views.py:168
msgid "Everything is fine!"
msgstr ""
#: .\cookbook\templates\system.html:80
msgid "Secret Key"
msgstr ""
#: .\cookbook\templates\system.html:84
msgid ""
"\n"
" You do not have a SECRET_KEY configured in your "
".env file. Django defaulted to the\n"
" standard key\n"
" provided with the installation which is publicly know and "
"insecure! Please set\n"
" SECRET_KEY int the .env configuration "
"file.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:94
msgid "Debug Mode"
msgstr ""
#: .\cookbook\templates\system.html:98
msgid ""
"\n"
" This application is still running in debug mode. This is most "
"likely not needed. Turn of debug mode by\n"
" setting\n"
" DEBUG=0 int the .env configuration "
"file.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:107
msgid "Allowed Hosts"
msgstr ""
#: .\cookbook\templates\system.html:111
msgid ""
"\n"
" Your allowed hosts are configured to allow every host. This "
"might be ok in some setups but should be avoided. Please see the docs about "
"this.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:118
msgid "Database"
msgstr ""
#: .\cookbook\templates\system.html:121
msgid "Info"
msgstr ""
#: .\cookbook\templates\system.html:131 .\cookbook\templates\system.html:148
msgid "Migrations"
msgstr ""
#: .\cookbook\templates\system.html:137
msgid ""
"\n"
" Migrations should never fail!\n"
" Failed migrations will likely cause major parts of the app to "
"not function correctly.\n"
" If a migration fails make sure you are on the latest version and "
"if so please post the migration log and the overview below in a GitHub "
"issue.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "False"
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "True"
msgstr ""
#: .\cookbook\templates\system.html:268
msgid "Hide"
msgstr ""
#: .\cookbook\templates\system.html:271
msgid "Show"
msgstr ""
#: .\cookbook\templates\test2.html:6
msgid "Export Recipes"
msgstr ""
#: .\cookbook\templates\test2.html:14 .\cookbook\templates\test2.html:20
msgid "Export"
msgstr ""
#: .\cookbook\views\api.py:198 .\cookbook\views\api.py:326
msgid "Parameter updated_at incorrectly formatted"
msgstr ""
#: .\cookbook\views\api.py:351 .\cookbook\views\api.py:484
#, python-brace-format
msgid "No {self.basename} with id {pk} exists"
msgstr ""
#: .\cookbook\views\api.py:355
msgid "Cannot merge with the same object!"
msgstr ""
#: .\cookbook\views\api.py:362
#, python-brace-format
msgid "No {self.basename} with id {target} exists"
msgstr ""
#: .\cookbook\views\api.py:367
msgid "Cannot merge with child object!"
msgstr ""
#: .\cookbook\views\api.py:405
#, python-brace-format
msgid "{source.name} was merged successfully with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:411
#, python-brace-format
msgid "An error occurred attempting to merge {source.name} with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:493
#, python-brace-format
msgid "{child.name} was moved successfully to the root."
msgstr ""
#: .\cookbook\views\api.py:496 .\cookbook\views\api.py:514
msgid "An error occurred attempting to move "
msgstr ""
#: .\cookbook\views\api.py:499
msgid "Cannot move an object to itself!"
msgstr ""
#: .\cookbook\views\api.py:505
#, python-brace-format
msgid "No {self.basename} with id {parent} exists"
msgstr ""
#: .\cookbook\views\api.py:511
#, python-brace-format
msgid "{child.name} was moved successfully to parent {parent.name}"
msgstr ""
#: .\cookbook\views\api.py:992
#, python-brace-format
msgid "{obj.name} was removed from the shopping list."
msgstr ""
#: .\cookbook\views\api.py:997 .\cookbook\views\api.py:1627
#, python-brace-format
msgid "{obj.name} was added to the shopping list."
msgstr ""
#: .\cookbook\views\api.py:1239
msgid "Filter meal plans from date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1241
msgid "Filter meal plans to date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1244
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1404
msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1406
msgid "Query string matched (fuzzy) against object name."
msgstr ""
#: .\cookbook\views\api.py:1442
msgid ""
"Query string matched (fuzzy) against recipe name. In the future also "
"fulltext search."
msgstr ""
#: .\cookbook\views\api.py:1444
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr ""
#: .\cookbook\views\api.py:1445
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr ""
#: .\cookbook\views\api.py:1446
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1447
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1448
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1450
msgid "ID of food a recipe should have. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1451
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr ""
#: .\cookbook\views\api.py:1452
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1453
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr ""
#: .\cookbook\views\api.py:1454
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1456
msgid "ID of book a recipe should be in. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1457
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr ""
#: .\cookbook\views\api.py:1458
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1459
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr ""
#: .\cookbook\views\api.py:1460
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1462
msgid "ID of unit a recipe should have."
msgstr ""
#: .\cookbook\views\api.py:1464
msgid "Exact rating of recipe"
msgstr ""
#: .\cookbook\views\api.py:1465
msgid "Rating a recipe should have or greater."
msgstr ""
#: .\cookbook\views\api.py:1466
msgid "Rating a recipe should have or smaller."
msgstr ""
#: .\cookbook\views\api.py:1468
msgid "Filter recipes cooked X times."
msgstr ""
#: .\cookbook\views\api.py:1469
msgid "Filter recipes cooked X times or more."
msgstr ""
#: .\cookbook\views\api.py:1470
msgid "Filter recipes cooked X times or less."
msgstr ""
#: .\cookbook\views\api.py:1472
msgid "Filter recipes created on the given date."
msgstr ""
#: .\cookbook\views\api.py:1473
msgid "Filter recipes created on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1474
msgid "Filter recipes created on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1476 .\cookbook\views\api.py:1477
#: .\cookbook\views\api.py:1478
msgid "Filter recipes updated on the given date."
msgstr ""
#: .\cookbook\views\api.py:1480
msgid "Filter recipes last cooked on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1481
msgid "Filter recipes last cooked on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1483 .\cookbook\views\api.py:1484
msgid "Filter recipes lasts viewed on the given date."
msgstr ""
#: .\cookbook\views\api.py:1486
msgid "Filter recipes for ones created by the given user ID"
msgstr ""
#: .\cookbook\views\api.py:1487
msgid "If only internal recipes should be returned. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1488
msgid "Returns the results in randomized order. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1490
msgid ""
"Determines the order of the results. Options are: score,-score,name,-name,"
"lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-"
"created_at,lastviewed,-lastviewed"
msgstr ""
#: .\cookbook\views\api.py:1492
msgid "Returns new results first in search results. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1493
msgid ""
"Returns the given number of recently viewed recipes before search results "
"(if given)"
msgstr ""
#: .\cookbook\views\api.py:1494
msgid "ID of a custom filter. Returns all recipes matched by that filter."
msgstr ""
#: .\cookbook\views\api.py:1495
msgid "Filter recipes that can be made with OnHand food. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1773
msgid ""
"Return the PropertyTypes matching the property category. Repeat for "
"multiple."
msgstr ""
#: .\cookbook\views\api.py:1804 .\cookbook\views\api.py:1860
msgid "Returns only entries associated with the given mealplan id"
msgstr ""
#: .\cookbook\views\api.py:1858
msgid ""
"Returns only elements updated after the given timestamp in ISO 8601 format."
msgstr ""
#: .\cookbook\views\api.py:2031
msgid ""
"Return the Automations matching the automation type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2048
msgid ""
"Text field to store data that gets carried over to the UserSpace created "
"from the InviteLink"
msgstr ""
#: .\cookbook\views\api.py:2049
msgid "Only return InviteLinks that have not been used yet."
msgstr ""
#: .\cookbook\views\api.py:2076
msgid "Return the CustomFilters matching the model type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2176
msgid "Nothing to do."
msgstr ""
#: .\cookbook\views\api.py:2222
msgid "Invalid Url"
msgstr ""
#: .\cookbook\views\api.py:2228
msgid "Connection Refused."
msgstr ""
#: .\cookbook\views\api.py:2232
msgid "Bad URL Schema."
msgstr ""
#: .\cookbook\views\api.py:2257
msgid "No usable data could be found."
msgstr ""
#: .\cookbook\views\api.py:2286 .\cookbook\views\api.py:2434
msgid "You must select an AI provider to perform your request."
msgstr ""
#: .\cookbook\views\api.py:2293 .\cookbook\views\api.py:2441
msgid ""
"You don't have any credits remaining to use AI or AI features are not "
"enabled for your space."
msgstr ""
#: .\cookbook\views\api.py:2499 .\cookbook\views\api.py:2667
msgid "File is above space limit"
msgstr ""
#: .\cookbook\views\api.py:2522 .\cookbook\views\api.py:2684
msgid "Importing is not implemented for this provider"
msgstr ""
#: .\cookbook\views\api.py:2548
msgid ""
"The PDF Exporter is not enabled on this instance as it is still in an "
"experimental state."
msgstr ""
#: .\cookbook\views\api.py:2842
msgid "This feature is not yet available in the hosted version of tandoor!"
msgstr ""
#: .\cookbook\views\api.py:2863
msgid "Sync successful!"
msgstr ""
#: .\cookbook\views\api.py:2866
msgid "Error synchronizing with Storage"
msgstr ""
#: .\cookbook\views\views.py:89
msgid "This feature is not available in the demo version!"
msgstr ""
#: .\cookbook\views\views.py:110
msgid ""
"You have successfully created your own recipe space. Start by adding some "
"recipes or invite other people to join you."
msgstr ""
#: .\cookbook\views\views.py:171
#, python-format
msgid "PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!"
msgstr ""
#: .\cookbook\views\views.py:174
#, python-format
msgid "You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended"
msgstr ""
#: .\cookbook\views\views.py:178
msgid "Unable to determine PostgreSQL version."
msgstr ""
#: .\cookbook\views\views.py:182
msgid ""
"This application is not running with a Postgres database backend. This is ok "
"but not recommended as some features only work with postgres databases."
msgstr ""
#: .\cookbook\views\views.py:296
msgid ""
"The setup page can only be used to create the first "
"user! If you have forgotten your superuser credentials "
"please consult the django documentation on how to reset passwords."
msgstr ""
#: .\cookbook\views\views.py:304
msgid "Passwords dont match!"
msgstr ""
#: .\cookbook\views\views.py:312
msgid "User has been created, please login!"
msgstr ""
#: .\cookbook\views\views.py:352
msgid ""
"Reporting share links is not enabled for this instance. Please notify the "
"page administrator to report problems."
msgstr ""
#: .\cookbook\views\views.py:357
msgid ""
"Recipe sharing link has been disabled! For additional information please "
"contact the page administrator."
msgstr ""
#: .\cookbook\views\views.py:383
msgid "Manage recipes, shopping list, meal plans and more."
msgstr ""
#: .\cookbook\views\views.py:397 .\cookbook\views\views.py:398
msgid "Plan"
msgstr ""
#: .\cookbook\views\views.py:399
msgid "View your meal Plan"
msgstr ""
#: .\cookbook\views\views.py:418
msgid "View your shopping lists"
msgstr ""
================================================
FILE: cookbook/locale/pl/LC_MESSAGES/django.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
# Translators:
# retmas , 2021
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-22 20:15+0200\n"
"PO-Revision-Date: 2026-01-05 22:15+0000\n"
"Last-Translator: tei444 \n"
"Language-Team: Polish \n"
"Language: pl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && "
"(n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || "
"(n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
"X-Generator: Weblate 5.13.3\n"
#: .\cookbook\forms.py:50
msgid "Default"
msgstr "Domyślne"
#: .\cookbook\forms.py:77
msgid ""
"To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything."
msgstr ""
"Aby zapobiec duplikatom, przepisy o tej samej nazwie co istniejące zostaną "
"pominięte. Zaznacz to pole, aby zaimportować wszystko."
#: .\cookbook\forms.py:108
msgid "Maximum number of users for this space reached."
msgstr "Osiągnięto maksymalną liczbę użytkowników dla tej przestrzeni."
#: .\cookbook\forms.py:114
msgid "Email address already taken!"
msgstr "Wybrany adres email jest już zajęty!"
#: .\cookbook\forms.py:121
msgid ""
"An email address is not required but if present the invite link will be sent "
"to the user."
msgstr ""
"Adres email nie jest wymagany, jednak gdy zostanie podany link z "
"zaproszeniem zostanie wysłany do użytkownika."
#: .\cookbook\forms.py:133
msgid "Name already taken."
msgstr "Podana nazwa jest już zajęta."
#: .\cookbook\forms.py:144 .\cookbook\forms.py:158
msgid "Accept Terms and Privacy"
msgstr "Zaakceptuj Regulamin oraz Politykę Prywatności"
#: .\cookbook\helper\AllAuthCustomAdapter.py:41
msgid ""
"In order to prevent spam, the requested email was not send. Please wait a "
"few minutes and try again."
msgstr ""
"Aby zapobiec spamowi, żądany e-mail nie został wysłany. Proszę poczekać "
"kilka minut i spróbować ponownie."
#: .\cookbook\helper\permission_helper.py:165
#: .\cookbook\helper\permission_helper.py:186 .\cookbook\views\views.py:138
msgid "You are not logged in and therefore cannot view this page!"
msgstr "Nie jesteś zalogowany, nie możesz więc wyświetlić tej strony!"
#: .\cookbook\helper\permission_helper.py:168
#: .\cookbook\helper\permission_helper.py:173
#: .\cookbook\helper\permission_helper.py:198
#: .\cookbook\helper\permission_helper.py:265
#: .\cookbook\helper\permission_helper.py:279
#: .\cookbook\helper\permission_helper.py:290
#: .\cookbook\helper\permission_helper.py:301
#: .\cookbook\helper\permission_helper.py:317
#: .\cookbook\helper\permission_helper.py:343
#: .\cookbook\helper\permission_helper.py:359
msgid "You do not have the required permissions to view this page!"
msgstr "Nie masz wystarczających uprawnień do wyświetlenia tej strony!"
#: .\cookbook\helper\permission_helper.py:191
#: .\cookbook\helper\permission_helper.py:214
#: .\cookbook\helper\permission_helper.py:236
#: .\cookbook\helper\permission_helper.py:251
msgid "You cannot interact with this object as it is not owned by you!"
msgstr ""
"Nie możesz przeprowadzać interkacji z tym obiektem ponieważ nie jesteś jego "
"właścicielem!"
#: .\cookbook\helper\permission_helper.py:420
msgid "You have reached the maximum number of recipes for your space."
msgstr "Osiągnąłeś maksymalną liczbę przepisów dla swojej przestrzeni."
#: .\cookbook\helper\permission_helper.py:432
msgid "You have more users than allowed in your space."
msgstr "Masz więcej użytkowników, niż jest dozwolone w Twojej przestrzeni."
#: .\cookbook\helper\recipe_url_import.py:319
msgid "reverse rotation"
msgstr "rotacja wsteczna"
#: .\cookbook\helper\recipe_url_import.py:320
msgid "careful rotation"
msgstr "delikatna rotacja"
#: .\cookbook\helper\recipe_url_import.py:321
msgid "knead"
msgstr "zagnieść"
#: .\cookbook\helper\recipe_url_import.py:322
msgid "thicken"
msgstr "zagęścić"
#: .\cookbook\helper\recipe_url_import.py:323
msgid "warm up"
msgstr "podgrzać"
#: .\cookbook\helper\recipe_url_import.py:324
msgid "ferment"
msgstr "doprowadzić do wrzenia"
#: .\cookbook\helper\recipe_url_import.py:325
#, fuzzy
#| msgid "Last cooked"
msgid "slow cook"
msgstr "Ostatnio przyrządzane"
#: .\cookbook\helper\recipe_url_import.py:326
msgid "egg boiler"
msgstr "jajowar"
#: .\cookbook\helper\recipe_url_import.py:327
msgid "kettle"
msgstr "czajnik"
#: .\cookbook\helper\recipe_url_import.py:328
msgid "blend"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:329
msgid "pre-clean"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:330
msgid "high temperature"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:331
msgid "rice cooker"
msgstr "Ryżowar"
#: .\cookbook\helper\recipe_url_import.py:332
msgid "caramelize"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:333
msgid "peeler"
msgstr "obieraczka"
#: .\cookbook\helper\recipe_url_import.py:334
msgid "slicer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:335
msgid "grater"
msgstr "tarka"
#: .\cookbook\helper\recipe_url_import.py:336
msgid "spiralizer"
msgstr "spiralizer"
#: .\cookbook\helper\recipe_url_import.py:337
msgid "sous-vide"
msgstr "gotować w próżni"
#: .\cookbook\helper\shopping_helper.py:150
msgid "You must supply a servings size"
msgstr "Musisz podać wielkość porcji"
#: .\cookbook\helper\template_helper.py:97
#: .\cookbook\helper\template_helper.py:99
#: .\cookbook\helper\template_helper.py:101
msgid "Could not parse template code."
msgstr "Nie można przeanalizować kodu szablonu."
#: .\cookbook\integration\copymethat.py:44
#: .\cookbook\integration\melarecipes.py:37
msgid "Favorite"
msgstr "Ulubione"
#: .\cookbook\integration\copymethat.py:50
msgid "I made this"
msgstr "Stworzyłem to"
#: .\cookbook\integration\integration.py:238
msgid ""
"Importer expected a .zip file. Did you choose the correct importer type for "
"your data ?"
msgstr ""
"Importer spodziewał się pliku .zip. Czy wybrano poprawny tym importera dla "
"twoich danych?"
#: .\cookbook\integration\integration.py:241
msgid ""
"An unexpected error occurred during the import. Please make sure you have "
"uploaded a valid file."
msgstr ""
"Wystąpił nieoczekiwany błąd podczas importu. Upewnij się, że przesłałeś "
"prawidłowy plik."
#: .\cookbook\integration\integration.py:246
msgid "The following recipes were ignored because they already existed:"
msgstr "Następujące przepisy zostały pominięte, ponieważ już istnieją:"
#: .\cookbook\integration\integration.py:250
#, python-format
msgid "Imported %s recipes."
msgstr "Zaimportowano %s przepisów."
#: .\cookbook\integration\mealie1.py:210
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "Calories"
msgstr "Kalorie"
#: .\cookbook\integration\mealie1.py:211
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
msgid "Carbohydrates"
msgstr "Węglowodany"
#: .\cookbook\integration\mealie1.py:212
msgid "Cholesterol"
msgstr "Cholesterol"
#: .\cookbook\integration\mealie1.py:213
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
msgid "Fat"
msgstr "Tłuszcze"
#: .\cookbook\integration\mealie1.py:214
msgid "Fiber"
msgstr "Błonnik"
#: .\cookbook\integration\mealie1.py:215
#, fuzzy
#| msgid "Proteins"
msgid "Protein"
msgstr "Białka"
#: .\cookbook\integration\mealie1.py:216
msgid "Saturated Fat"
msgstr "Tłuszcze nasycone"
#: .\cookbook\integration\mealie1.py:217
msgid "Sodium"
msgstr "Sód"
#: .\cookbook\integration\mealie1.py:218
msgid "Sugar"
msgstr "Cukry"
#: .\cookbook\integration\mealie1.py:219
msgid "Trans Fat"
msgstr "Tłuszcze Trans"
#: .\cookbook\integration\mealie1.py:220
msgid "Unsaturated Fat"
msgstr "Tłuszcze nienasycone"
#: .\cookbook\integration\openeats.py:28
msgid "Recipe source:"
msgstr "Źródło przepisu:"
#: .\cookbook\integration\paprika.py:49
msgid "Notes"
msgstr "Notatki"
#: .\cookbook\integration\paprika.py:52
msgid "Nutritional Information"
msgstr "Informacje o wartości odżywczej"
#: .\cookbook\integration\paprika.py:56
msgid "Source"
msgstr "Źródło"
#: .\cookbook\integration\recettetek.py:55
#: .\cookbook\integration\recipekeeper.py:70
msgid "Imported from"
msgstr "Zaimportowane z"
#: .\cookbook\integration\saffron.py:23
msgid "Servings"
msgstr "Porcje"
#: .\cookbook\integration\saffron.py:25
msgid "Waiting time"
msgstr "Czas oczekiwania"
#: .\cookbook\integration\saffron.py:27
msgid "Preparation Time"
msgstr "Czas przygotowania"
#: .\cookbook\integration\saffron.py:29 .\cookbook\templates\index.html:6
msgid "Cookbook"
msgstr "Książka kucharska"
#: .\cookbook\integration\saffron.py:31
msgid "Section"
msgstr "Sekcja"
#: .\cookbook\management\commands\fix_duplicate_properties.py:15
msgid "Fixes foods with "
msgstr "Poprawia potrawy z "
#: .\cookbook\management\commands\rebuildindex.py:14
msgid "Rebuilds full text search index on Recipe"
msgstr "Odbudowuje indeks wyszukiwania pełno tekstowego dla przepisu"
#: .\cookbook\management\commands\rebuildindex.py:18
msgid "Only Postgresql databases use full text search, no index to rebuild"
msgstr ""
"Pełno tekstowe wyszukiwanie jest używane tylko w bazach danych PostgreSQL, "
"brak indeksu do odbudowy"
#: .\cookbook\management\commands\rebuildindex.py:29
msgid "Recipe index rebuild complete."
msgstr "Odbudowa indeksu przepisu zakończona."
#: .\cookbook\management\commands\rebuildindex.py:31
msgid "Recipe index rebuild failed."
msgstr "Odbudowa indeksu przepisu nie powiodła się."
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
msgid "Breakfast"
msgstr "Śniadanie"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:19
msgid "Lunch"
msgstr "Lunch"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:24
msgid "Dinner"
msgstr "Obiad"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:29 .\cookbook\models.py:971
msgid "Other"
msgstr "Inne"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "g"
msgstr "g"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "Proteins"
msgstr "Białka"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "kcal"
msgstr "kcal"
#: .\cookbook\models.py:325
msgid ""
"Maximum file storage for space in MB. 0 for unlimited, -1 to disable file "
"upload."
msgstr ""
"Maksymalna pojemność na pliki dla przestrzeni w MB. 0 oznacza brak limitu, "
"-1 wyłącza możliwość przesyłania plików."
#: .\cookbook\models.py:513
msgid "Search"
msgstr "Szukaj"
#: .\cookbook\models.py:514
msgid "Meal-Plan"
msgstr "Plan posiłków"
#: .\cookbook\models.py:515
msgid "Books"
msgstr "Książki"
#: .\cookbook\models.py:516 .\cookbook\views\views.py:416
#: .\cookbook\views\views.py:417
msgid "Shopping"
msgstr "Zakupy"
#: .\cookbook\models.py:967
msgid "Nutrition"
msgstr "Wartość odżywcza"
#: .\cookbook\models.py:968
msgid "Allergen"
msgstr "Alergeny"
#: .\cookbook\models.py:969
msgid "Price"
msgstr "Cena"
#: .\cookbook\models.py:970
msgid "Goal"
msgstr ""
#: .\cookbook\models.py:1467 .\cookbook\templates\search_info.html:28
msgid "Simple"
msgstr ""
#: .\cookbook\models.py:1468 .\cookbook\templates\search_info.html:33
msgid "Phrase"
msgstr ""
#: .\cookbook\models.py:1469 .\cookbook\templates\search_info.html:38
msgid "Web"
msgstr ""
#: .\cookbook\models.py:1470 .\cookbook\templates\search_info.html:47
msgid "Raw"
msgstr ""
#: .\cookbook\models.py:1525
msgid "Food Alias"
msgstr "Alias produktu"
#: .\cookbook\models.py:1526
msgid "Unit Alias"
msgstr "Alias jednostki"
#: .\cookbook\models.py:1527
msgid "Keyword Alias"
msgstr "Alias słowa kluczowego"
#: .\cookbook\models.py:1528
msgid "Description Replace"
msgstr "Zmiana opisu"
#: .\cookbook\models.py:1529
msgid "Instruction Replace"
msgstr "Zmiana instrukcji"
#: .\cookbook\models.py:1530
msgid "Never Unit"
msgstr "Jednostka nigdy"
#: .\cookbook\models.py:1531
msgid "Transpose Words"
msgstr "Transponuj słowa"
#: .\cookbook\models.py:1532
msgid "Food Replace"
msgstr "Zmiana składników żywności"
#: .\cookbook\models.py:1533
msgid "Unit Replace"
msgstr "Zmiana jednostki"
#: .\cookbook\models.py:1534
msgid "Name Replace"
msgstr "Zmiana nazwy"
#: .\cookbook\models.py:1564
msgid "Recipe"
msgstr "Przepis"
#: .\cookbook\models.py:1565
msgid "Food"
msgstr "Jedzenie"
#: .\cookbook\models.py:1566
msgid "Keyword"
msgstr "Słowo kluczowe"
#: .\cookbook\serializer.py:262
msgid "File uploads are not enabled for this Space."
msgstr ""
#: .\cookbook\serializer.py:273
msgid "You have reached your file upload limit."
msgstr ""
#: .\cookbook\serializer.py:281
msgid "The given file type is not allowed."
msgstr ""
#: .\cookbook\serializer.py:421 .\cookbook\views\views.py:94
msgid ""
"You have the reached the maximum amount of spaces that can be owned by you."
msgstr "Zapełniłeś maksymalną przestrzeń, która jest przypisana do Ciebie."
#: .\cookbook\serializer.py:434
msgid "Space Name must be unique."
msgstr "Nazwa Przestrzeni musi być unikalna."
#: .\cookbook\serializer.py:469
msgid "Cannot modify Space owner permission."
msgstr ""
#: .\cookbook\serializer.py:1596
msgid "Hello"
msgstr "Cześć"
#: .\cookbook\serializer.py:1596
msgid "You have been invited by "
msgstr ""
#: .\cookbook\serializer.py:1598
msgid " to join their Tandoor Recipes space "
msgstr ""
#: .\cookbook\serializer.py:1600
msgid "Click the following link to activate your account: "
msgstr ""
#: .\cookbook\serializer.py:1602
msgid ""
"If the link does not work use the following code to manually join the space: "
msgstr ""
#: .\cookbook\serializer.py:1604
msgid "The invitation is valid until "
msgstr ""
#: .\cookbook\serializer.py:1606
msgid ""
"Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub "
msgstr ""
#: .\cookbook\serializer.py:1609
msgid "Tandoor Recipes Invite"
msgstr ""
#: .\cookbook\serializer.py:1813
msgid "Existing shopping list to update"
msgstr ""
#: .\cookbook\serializer.py:1815
msgid ""
"List of ingredient IDs from the recipe to add, if not provided all "
"ingredients will be added."
msgstr ""
#: .\cookbook\serializer.py:1817
msgid ""
"Providing a list_recipe ID and servings of 0 will delete that shopping list."
msgstr ""
#: .\cookbook\serializer.py:1826
msgid "Amount of food to add to the shopping list"
msgstr ""
#: .\cookbook\serializer.py:1828
msgid "ID of unit to use for the shopping list"
msgstr ""
#: .\cookbook\serializer.py:1830
msgid "When set to true will delete all food from active shopping lists."
msgstr ""
#: .\cookbook\templates\404.html:5
msgid "404 Error"
msgstr "Błąd 404"
#: .\cookbook\templates\404.html:18
msgid "The page you are looking for could not be found."
msgstr "Strona której szukasz nie została odnaleziona."
#: .\cookbook\templates\404.html:33
msgid "Take me Home"
msgstr "Zabierz mnie na stronę główną"
#: .\cookbook\templates\404.html:35
msgid "Report a Bug"
msgstr "Raportuj błąd"
#: .\cookbook\templates\account\email.html:6
#: .\cookbook\templates\account\email.html:10
msgid "E-mail Addresses"
msgstr "Adresy Email"
#: .\cookbook\templates\account\email.html:12
msgid "The following e-mail addresses are associated with your account:"
msgstr "Poniższe adresy email są połączone z twoim kontem:"
#: .\cookbook\templates\account\email.html:29
msgid "Verified"
msgstr "Potwierdzony"
#: .\cookbook\templates\account\email.html:31
msgid "Unverified"
msgstr "Niepotwierdzony"
#: .\cookbook\templates\account\email.html:33
msgid "Primary"
msgstr "Główny"
#: .\cookbook\templates\account\email.html:40
msgid "Make Primary"
msgstr "Ustaw jako główny"
#: .\cookbook\templates\account\email.html:42
msgid "Re-send Verification"
msgstr ""
#: .\cookbook\templates\account\email.html:43
#: .\cookbook\templates\socialaccount\connections.html:36
msgid "Remove"
msgstr "Usuń"
#: .\cookbook\templates\account\email.html:51
msgid "Warning:"
msgstr "Uwaga:"
#: .\cookbook\templates\account\email.html:51
msgid ""
"You currently do not have any e-mail address set up. You should really add "
"an e-mail address so you can receive notifications, reset your password, etc."
msgstr ""
"Aktualnie nie masz powiązanego adresu email. Powinieneś powiązać adres email "
"by móc dostawać powiadomienia, resetować hasło itp."
#: .\cookbook\templates\account\email.html:57
msgid "Add E-mail Address"
msgstr "Dodaj Adres Email"
#: .\cookbook\templates\account\email.html:62
msgid "Add E-mail"
msgstr "Dodaj Email"
#: .\cookbook\templates\account\email.html:72
msgid "Do you really want to remove the selected e-mail address?"
msgstr "Czy na pewno chcesz usunąć wybrany adres email?"
#: .\cookbook\templates\account\email_confirm.html:6
#: .\cookbook\templates\account\email_confirm.html:10
msgid "Confirm E-mail Address"
msgstr "Potwierdź adres email"
#: .\cookbook\templates\account\email_confirm.html:16
#, python-format
msgid ""
"Please confirm that\n"
" %(email)s is an e-mail address "
"for user %(user_display)s\n"
" ."
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:22
msgid "Confirm"
msgstr "Potwierdź"
#: .\cookbook\templates\account\email_confirm.html:29
#, python-format
msgid ""
"This e-mail confirmation link expired or is invalid. Please\n"
" issue a new e-mail confirmation "
"request."
msgstr ""
#: .\cookbook\templates\account\login.html:8
#: .\cookbook\templates\openid\login.html:8
msgid "Login"
msgstr "Zaloguj się"
#: .\cookbook\templates\account\login.html:15
#: .\cookbook\templates\account\login.html:31
#: .\cookbook\templates\account\password_reset.html:39
#: .\cookbook\templates\account\password_reset_done.html:31
#: .\cookbook\templates\account\signup.html:68
#: .\cookbook\templates\account\signup_closed.html:15
#: .\cookbook\templates\openid\login.html:15
#: .\cookbook\templates\openid\login.html:26
#: .\cookbook\templates\socialaccount\authentication_error.html:15
msgid "Sign In"
msgstr "Zaloguj się"
#: .\cookbook\templates\account\login.html:34
#: .\cookbook\templates\account\password_reset.html:41
#: .\cookbook\templates\account\password_reset_done.html:33
#: .\cookbook\templates\socialaccount\signup.html:8
#: .\cookbook\templates\socialaccount\signup.html:56
msgid "Sign Up"
msgstr "Zarejestruj się"
#: .\cookbook\templates\account\login.html:38
msgid "Lost your password?"
msgstr ""
#: .\cookbook\templates\account\login.html:39
#: .\cookbook\templates\account\password_reset.html:29
msgid "Reset My Password"
msgstr ""
#: .\cookbook\templates\account\login.html:50
msgid "Social Login"
msgstr "Logowanie społecznościowe"
#: .\cookbook\templates\account\login.html:51
msgid "You can use any of the following providers to sign in."
msgstr ""
"Możesz użyć dowolnego z poniższych dostawców w celu zarejestrowania się."
#: .\cookbook\templates\account\logout.html:5
#: .\cookbook\templates\account\logout.html:9
#: .\cookbook\templates\account\logout.html:18
msgid "Sign Out"
msgstr "Wyloguj"
#: .\cookbook\templates\account\logout.html:11
msgid "Are you sure you want to sign out?"
msgstr "Czy na pewno chcesz się wylogować?"
#: .\cookbook\templates\account\password_change.html:6
#: .\cookbook\templates\account\password_change.html:10
#: .\cookbook\templates\account\password_change.html:15
#: .\cookbook\templates\account\password_reset_from_key.html:7
#: .\cookbook\templates\account\password_reset_from_key.html:13
#: .\cookbook\templates\account\password_reset_from_key_done.html:7
#: .\cookbook\templates\account\password_reset_from_key_done.html:13
msgid "Change Password"
msgstr "Zmień hasło"
#: .\cookbook\templates\account\password_change.html:16
msgid "Forgot Password?"
msgstr "Nie pamiętasz hasła?"
#: .\cookbook\templates\account\password_reset.html:7
#: .\cookbook\templates\account\password_reset.html:13
#: .\cookbook\templates\account\password_reset_done.html:7
#: .\cookbook\templates\account\password_reset_done.html:18
msgid "Password Reset"
msgstr "Reset hasła"
#: .\cookbook\templates\account\password_reset.html:24
msgid ""
"Forgotten your password? Enter your e-mail address below, and we'll send you "
"an e-mail allowing you to reset it."
msgstr ""
"Zapomniane hasło? Wpisz swój adres email poniżej, a my wyślemy Ci wiadomość, "
"która pozwoli je zresetować."
#: .\cookbook\templates\account\password_reset.html:32
msgid "Password reset is disabled on this instance."
msgstr "Resetowanie hasła jest wyłączone w tej instancji."
#: .\cookbook\templates\account\password_reset_done.html:25
msgid ""
"We have sent you an e-mail. Please contact us if you do not receive it "
"within a few minutes."
msgstr ""
"Wysłano email. Proszę skontaktuj się z nami jeśli nie przyjdzie w ciągu "
"kilku minut."
#: .\cookbook\templates\account\password_reset_from_key.html:13
msgid "Bad Token"
msgstr "Niepoprawny token"
#: .\cookbook\templates\account\password_reset_from_key.html:25
#, python-format
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used.\n"
" Please request a new "
"password reset."
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:33
msgid "change password"
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:36
#: .\cookbook\templates\account\password_reset_from_key_done.html:19
msgid "Your password is now changed."
msgstr "Hasło zostało zmienione."
#: .\cookbook\templates\account\password_set.html:6
#: .\cookbook\templates\account\password_set.html:10
#: .\cookbook\templates\account\password_set.html:15
msgid "Set Password"
msgstr "Ustaw Hasło"
#: .\cookbook\templates\account\signup.html:5
msgid "Register"
msgstr "Zarejestruj się"
#: .\cookbook\templates\account\signup.html:11
msgid "Create an Account"
msgstr "Utwórz konto"
#: .\cookbook\templates\account\signup.html:41
msgid "I accept the follwoing"
msgstr "Akceptuję poniższe"
#: .\cookbook\templates\account\signup.html:44
#: .\cookbook\templates\socialaccount\signup.html:35
msgid "Terms and Conditions"
msgstr "Regulamin"
#: .\cookbook\templates\account\signup.html:47
#: .\cookbook\templates\socialaccount\signup.html:38
msgid "and"
msgstr "oraz"
#: .\cookbook\templates\account\signup.html:51
#: .\cookbook\templates\socialaccount\signup.html:42
msgid "Privacy Policy"
msgstr "Polityka Prywatności"
#: .\cookbook\templates\account\signup.html:64
msgid "Create User"
msgstr "Utwórz użytkownika"
#: .\cookbook\templates\account\signup.html:68
msgid "Already have an account?"
msgstr "Masz już konto?"
#: .\cookbook\templates\account\signup_closed.html:5
#: .\cookbook\templates\account\signup_closed.html:11
msgid "Sign Up Closed"
msgstr "Rejestracja Wyłączona"
#: .\cookbook\templates\account\signup_closed.html:13
msgid "We are sorry, but the sign up is currently closed."
msgstr "Przepraszamy, ale rejestracja jest aktualnie zamknięta."
#: .\cookbook\templates\frontend\tandoor.html:15
msgid "Tandoor Recipe Manager"
msgstr ""
#: .\cookbook\templates\index.html:28
msgid "Search recipe ..."
msgstr "Wyszukaj przepis ..."
#: .\cookbook\templates\index.html:43
msgid "New Recipe"
msgstr "Nowy przepis"
#: .\cookbook\templates\index.html:46
msgid "Import Recipe"
msgstr "Importuj przepis"
#: .\cookbook\templates\index.html:52
msgid "Advanced Search"
msgstr "Zaawansowane wyszukiwanie"
#: .\cookbook\templates\index.html:56
msgid "Reset Search"
msgstr "Wyzeruj wyszukiwanie"
#: .\cookbook\templates\index.html:84
msgid "Last viewed"
msgstr "Ostatnio przeglądane"
#: .\cookbook\templates\index.html:86
msgid "Recipes"
msgstr "Przepisy"
#: .\cookbook\templates\index.html:93
msgid "Log in to view recipes"
msgstr "Zaloguj się w celu przeglądania przepisów"
#: .\cookbook\templates\markdown_info.html:5
#: .\cookbook\templates\markdown_info.html:13
msgid "Markdown Info"
msgstr "Informacje o języku Markdown"
#: .\cookbook\templates\markdown_info.html:14
msgid ""
"\n"
" Markdown is lightweight markup language that can be used to format "
"plain text easily.\n"
" This site uses the Python Markdown library to\n"
" convert your text into nice looking HTML. Its full markdown "
"documentation can be found\n"
" here.\n"
" An incomplete but most likely sufficient documentation can be found "
"below.\n"
" "
msgstr ""
"\n"
" Markdown jest prostym językiem znaczników przeznaczonym do łatwego "
"formatowania teksu.\n"
" Ta strona używa biblioteki Python Markdown w celu \n"
" konwertowania Twojego tekstu w ładnie wyglądający HTML. Pełna "
"dokumentacja markdown znajduje się\n"
" tutaj.\n"
" Niekompletna, ale w większości przypadków wystarczająca dokumentacja "
"znajduje się poniżej.\n"
" "
#: .\cookbook\templates\markdown_info.html:25
msgid "Headers"
msgstr "Nagłówki"
#: .\cookbook\templates\markdown_info.html:54
msgid "Formatting"
msgstr "Formatowanie"
#: .\cookbook\templates\markdown_info.html:56
#: .\cookbook\templates\markdown_info.html:72
msgid "Line breaks are inserted by adding two spaces after the end of a line"
msgstr ""
"Podział linii jest realizowany poprzez dodanie dwóch spacji na końcu linii"
#: .\cookbook\templates\markdown_info.html:57
#: .\cookbook\templates\markdown_info.html:73
msgid "or by leaving a blank line in between."
msgstr ""
"lub poprzez pozostawienie pustej linii pomiędzy tekstem, który ma zostać "
"podzielony."
#: .\cookbook\templates\markdown_info.html:59
#: .\cookbook\templates\markdown_info.html:74
msgid "This text is bold"
msgstr "Ten tekst jest pogrubiony"
#: .\cookbook\templates\markdown_info.html:60
#: .\cookbook\templates\markdown_info.html:75
msgid "This text is italic"
msgstr "Ten tekst jest napisany kursywą"
#: .\cookbook\templates\markdown_info.html:61
#: .\cookbook\templates\markdown_info.html:77
msgid "Blockquotes are also possible"
msgstr "Cytaty blokowe również są możliwe"
#: .\cookbook\templates\markdown_info.html:84
msgid "Lists"
msgstr "Listy"
#: .\cookbook\templates\markdown_info.html:85
msgid ""
"Lists can ordered or unordered. It is important to leave a blank line "
"before the list!"
msgstr ""
"Listy mogą być uporządkowane lub nieuporządkowane. Ważne jest, żeby "
"pozostawić pustą linię przed listą!"
#: .\cookbook\templates\markdown_info.html:87
#: .\cookbook\templates\markdown_info.html:108
msgid "Ordered List"
msgstr "Lista uporządkowana"
#: .\cookbook\templates\markdown_info.html:89
#: .\cookbook\templates\markdown_info.html:90
#: .\cookbook\templates\markdown_info.html:91
#: .\cookbook\templates\markdown_info.html:110
#: .\cookbook\templates\markdown_info.html:111
#: .\cookbook\templates\markdown_info.html:112
msgid "unordered list item"
msgstr "element listy nieuporządkowanej"
#: .\cookbook\templates\markdown_info.html:93
#: .\cookbook\templates\markdown_info.html:114
msgid "Unordered List"
msgstr "Lista nieuporządkowana"
#: .\cookbook\templates\markdown_info.html:95
#: .\cookbook\templates\markdown_info.html:96
#: .\cookbook\templates\markdown_info.html:97
#: .\cookbook\templates\markdown_info.html:116
#: .\cookbook\templates\markdown_info.html:117
#: .\cookbook\templates\markdown_info.html:118
msgid "ordered list item"
msgstr "element listy uporządkowanej"
#: .\cookbook\templates\markdown_info.html:125
msgid "Images & Links"
msgstr "Obrazki oraz Linki"
#: .\cookbook\templates\markdown_info.html:126
msgid ""
"Links can be formatted with Markdown. This application also allows to paste "
"links directly into markdown fields without any formatting."
msgstr ""
"Linki mogą być formatowane przy użyciu Markdown. Ta aplikacja pozwala "
"również na wklejanie linków bezpośrednio do pól Markdown bez żadnego "
"formatowania."
#: .\cookbook\templates\markdown_info.html:132
#: .\cookbook\templates\markdown_info.html:145
msgid "This will become an image"
msgstr "To stanie się obrazkiem"
#: .\cookbook\templates\markdown_info.html:152
msgid "Tables"
msgstr "Tabele"
#: .\cookbook\templates\markdown_info.html:153
msgid ""
"Markdown tables are hard to create by hand. It is recommended to use a table "
"editor like this one."
msgstr ""
"Tabele w Markdown trudno stworzyć z ręki. Zalecane jest użycie edytora "
"tablic takiego jak ten."
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:171
#: .\cookbook\templates\markdown_info.html:177
msgid "Table"
msgstr "Tablica"
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:172
msgid "Header"
msgstr "Nagłówek"
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:178
msgid "Cell"
msgstr "Komórka"
#: .\cookbook\templates\no_groups_info.html:5
#: .\cookbook\templates\no_groups_info.html:12
msgid "No Permissions"
msgstr "Brak uprawnień"
#: .\cookbook\templates\no_groups_info.html:17
msgid "You do not have any groups and therefor cannot use this application."
msgstr "Nie masz żadnych grup i dlatego nie możesz korzystać z tej aplikacji."
#: .\cookbook\templates\no_groups_info.html:18
#: .\cookbook\templates\no_perm_info.html:15
msgid "Please contact your administrator."
msgstr "Proszę skontaktuj się z administratorem."
#: .\cookbook\templates\no_perm_info.html:5
#: .\cookbook\templates\no_perm_info.html:12
msgid "No Permission"
msgstr "Brak uprawnienia"
#: .\cookbook\templates\no_perm_info.html:15
msgid ""
"You do not have the required permissions to view this page or perform this "
"action."
msgstr ""
"Nie masz wymaganych uprawnień, aby wyświetlić tę stronę lub wykonać tę "
"czynność."
#: .\cookbook\templates\offline.html:5
msgid "Offline"
msgstr "Nie podłączone"
#: .\cookbook\templates\openid\login.html:27
#: .\cookbook\templates\socialaccount\authentication_error.html:27
msgid "Back"
msgstr "Wstecz"
#: .\cookbook\templates\rest_framework\api.html:5
msgid "Recipe Home"
msgstr "Strona główna Przepisów"
#: .\cookbook\templates\rest_framework\api.html:11
msgid "API Documentation"
msgstr "Dokumentacja API"
#: .\cookbook\templates\search_info.html:5
#: .\cookbook\templates\search_info.html:9
msgid "Search Settings"
msgstr "Wyszukaj ustawienia"
#: .\cookbook\templates\search_info.html:10
msgid ""
"\n"
" Creating the best search experience is complicated and weighs "
"heavily on your personal configuration. \n"
" Changing any of the search settings can have significant impact on "
"the speed and quality of the results.\n"
" Search Methods, Trigrams and Full Text Search configurations are "
"only available if you are using Postgres for your database.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:19
msgid "Search Methods"
msgstr "Metody Wyszukiwania"
#: .\cookbook\templates\search_info.html:23
msgid ""
" \n"
" Full text searches attempt to normalize the words provided to "
"match common variants. For example: 'forked', 'forking', 'forks' will all "
"normalize to 'fork'.\n"
" There are several methods available, described below, that will "
"control how the search behavior should react when multiple words are "
"searched.\n"
" Full technical details on how these operate can be viewed on Postgresql's website.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:29
msgid ""
" \n"
" Simple searches ignore punctuation and common words such as "
"'the', 'a', 'and'. And will treat separate words as required.\n"
" Searching for 'apple or flour' will return any recipe that "
"includes both 'apple' and 'flour' anywhere in the fields that have been "
"selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:34
msgid ""
" \n"
" Phrase searches ignore punctuation, but will search for all of "
"the words in the exact order provided.\n"
" Searching for 'apple or flour' will only return a recipe that "
"includes the exact phrase 'apple or flour' in any of the fields that have "
"been selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:39
msgid ""
" \n"
" Web searches simulate functionality found on many web search "
"sites supporting special syntax.\n"
" Placing quotes around several words will convert those words "
"into a phrase.\n"
" 'or' is recognized as searching for the word (or phrase) "
"immediately before 'or' OR the word (or phrase) directly after.\n"
" '-' is recognized as searching for recipes that do not include "
"the word (or phrase) that comes immediately after. \n"
" For example searching for 'apple pie' or cherry -butter will "
"return any recipe that includes the phrase 'apple pie' or the word "
"'cherry' \n"
" in any field included in the full text search but exclude any "
"recipe that has the word 'butter' in any field included.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:48
msgid ""
" \n"
" Raw search is similar to Web except will take puncuation "
"operators such as '|', '&' and '()'\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:59
msgid ""
" \n"
" Another approach to searching that also requires Postgresql is "
"fuzzy search or trigram similarity. A trigram is a group of three "
"consecutive characters.\n"
" For example searching for 'apple' will create x trigrams 'app', "
"'ppl', 'ple' and will create a score of how closely words match the "
"generated trigrams.\n"
" One benefit of searching trigams is that a search for 'sandwich' "
"will find misspelled words such as 'sandwhich' that would be missed by other "
"methods.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:69
msgid "Search Fields"
msgstr "Szukaj w polach"
#: .\cookbook\templates\search_info.html:73
msgid ""
" \n"
" Unaccent is a special case in that it enables searching a field "
"'unaccented' for each search style attempting to ignore accented values. \n"
" For example when you enable unaccent for 'Name' any search "
"(starts with, contains, trigram) will attempt the search ignoring accented "
"characters.\n"
" \n"
" For the other options, you can enable search on any or all "
"fields and they will be combined together with an assumed 'OR'.\n"
" For example enabling 'Name' for Starts With, 'Name' and "
"'Description' for Partial Match and 'Ingredients' and 'Keywords' for Full "
"Search\n"
" and searching for 'apple' will generate a search that will "
"return recipes that have:\n"
" - A recipe name that starts with 'apple'\n"
" - OR a recipe name that contains 'apple'\n"
" - OR a recipe description that contains 'apple'\n"
" - OR a recipe that will have a full text search match ('apple' "
"or 'apples') in ingredients\n"
" - OR a recipe that will have a full text search match in "
"Keywords\n"
"\n"
" Combining too many fields in too many types of search can have a "
"negative impact on performance, create duplicate results or return "
"unexpected results.\n"
" For example, enabling fuzzy search or partial matches will "
"interfere with web search methods. \n"
" Searching for 'apple -pie' with fuzzy search and full text "
"search will return the recipe Apple Pie. Though it is not included in the "
"full text results, it does match the trigram results.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:95
msgid "Search Index"
msgstr "Indeks Wyszukiwania"
#: .\cookbook\templates\search_info.html:99
msgid ""
" \n"
" Trigram search and Full Text Search both rely on database "
"indexes to perform effectively. \n"
" You can rebuild the indexes on all fields in the Admin page for "
"Recipes and selecting all recipes and running 'rebuild index for selected "
"recipes'\n"
" You can also rebuild indexes at the command line by executing "
"the management command 'python manage.py rebuildindex'\n"
" "
msgstr ""
#: .\cookbook\templates\setup.html:6
msgid "Cookbook Setup"
msgstr "Konfiguracja Książki kucharskiej"
#: .\cookbook\templates\setup.html:14
msgid "Setup"
msgstr "Konfiguracja"
#: .\cookbook\templates\setup.html:15
msgid ""
"To start using this application you must first create a superuser account."
msgstr ""
"Aby rozpocząć korzystanie z tej aplikacji, musisz najpierw utworzyć konto "
"super użytkownika."
#: .\cookbook\templates\setup.html:20
msgid "Create Superuser account"
msgstr "Utwórz konto super użytkownika"
#: .\cookbook\templates\socialaccount\authentication_error.html:7
#: .\cookbook\templates\socialaccount\authentication_error.html:23
msgid "Social Network Login Failure"
msgstr "Błąd logowania społecznościowego"
#: .\cookbook\templates\socialaccount\authentication_error.html:25
msgid ""
"An error occurred while attempting to login via your social network account."
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:4
#: .\cookbook\templates\socialaccount\connections.html:7
msgid "Account Connections"
msgstr "Połączenie kont"
#: .\cookbook\templates\socialaccount\connections.html:10
msgid ""
"You can sign in to your account using any of the following third party\n"
" accounts:"
msgstr ""
"Możesz zalogować się na swoje konto za pomocą dowolnego z następujących "
"kont\n"
" zewnętrznych:"
#: .\cookbook\templates\socialaccount\connections.html:44
msgid ""
"You currently have no social network accounts connected to this account."
msgstr ""
"Obecnie nie masz kont sieci społecznościowych połączonych z tym kontem."
#: .\cookbook\templates\socialaccount\connections.html:47
msgid "Add a 3rd Party Account"
msgstr "Dodaj konto firmy zewnętrznej"
#: .\cookbook\templates\socialaccount\login.html:5
#: .\cookbook\templates\socialaccount\signup.html:5
msgid "Signup"
msgstr "Zarejestruj"
#: .\cookbook\templates\socialaccount\login.html:9
#, python-format
msgid "Connect %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:11
#, python-format
msgid "You are about to connect a new third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:13
#, python-format
msgid "Sign In Via %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:15
#, python-format
msgid "You are about to sign in using a third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:20
msgid "Continue"
msgstr "Dalej"
#: .\cookbook\templates\socialaccount\signup.html:10
#, python-format
msgid ""
"You are about to use your\n"
" %(provider_name)s account to login to\n"
" %(site_name)s. As a final step, please complete the following form:"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:32
msgid "I accept the following"
msgstr ""
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:23
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:31
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:39
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:47
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:55
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:63
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:71
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:79
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:87
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:95
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:103
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:111
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:119
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:127
msgid "Sign in using"
msgstr "Zaloguj się za pomocą"
#: .\cookbook\templates\space_overview.html:6
msgid "Overview"
msgstr ""
#: .\cookbook\templates\space_overview.html:13
msgid "Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:17
msgid ""
"Recipes, foods, shopping lists and more are organized in spaces of one or "
"more people."
msgstr ""
#: .\cookbook\templates\space_overview.html:18
msgid ""
"You can either be invited into an existing space or create your own one."
msgstr ""
#: .\cookbook\templates\space_overview.html:25
msgid "Your Spaces"
msgstr ""
#: .\cookbook\templates\space_overview.html:53
msgid "Owner"
msgstr ""
#: .\cookbook\templates\space_overview.html:73
#: .\cookbook\templates\space_overview.html:83
msgid "Join Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:76
msgid "Join an existing space."
msgstr ""
#: .\cookbook\templates\space_overview.html:78
msgid ""
"To join an existing space either enter your invite token or click on the "
"invite link the space owner send you."
msgstr ""
#: .\cookbook\templates\space_overview.html:91
#: .\cookbook\templates\space_overview.html:100
msgid "Create Space"
msgstr "Utwórz przestrzeń roboczą"
#: .\cookbook\templates\space_overview.html:94
msgid "Create your own recipe space."
msgstr ""
#: .\cookbook\templates\space_overview.html:96
msgid "Start your own recipe space and invite other users to it."
msgstr ""
#: .\cookbook\templates\system.html:23
msgid "System"
msgstr "System"
#: .\cookbook\templates\system.html:24
#, fuzzy
#| msgid ""
#| "\n"
#| " Django Recipes is an open source free software application. It "
#| "can be found on\n"
#| " GitHub.\n"
#| " Changelogs can be found here.\n"
#| " "
msgid ""
"\n"
" Tandoor Recipes is an open source free software application. It can "
"be found on\n"
" GitHub.\n"
" Changelogs can be found here.\n"
" "
msgstr ""
"\n"
" Django Recipes to darmowa aplikacja typu open source. Można ją "
"znaleźć na\n"
" GitHub.\n"
" Dziennik zmian można znaleźć tutaj.\n"
" "
#: .\cookbook\templates\system.html:30
msgid "System Information"
msgstr "Informacje o systemie"
#: .\cookbook\templates\system.html:51
msgid ""
"\n"
" You need to execute version.py in your update "
"script to generate version information (done automatically in docker).\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:56
msgid "Plugins"
msgstr ""
#: .\cookbook\templates\system.html:67
msgid "Media Serving"
msgstr "Obsługa multimediów"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:123 .\cookbook\templates\system.html:134
msgid "Warning"
msgstr "Uwaga"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:125 .\cookbook\templates\system.html:134
msgid "Ok"
msgstr "Ok"
#: .\cookbook\templates\system.html:70
msgid ""
"Serving media files directly using gunicorn/python is not recommend!\n"
" Please follow the steps described\n"
" here to update\n"
" your installation.\n"
" "
msgstr ""
"Udostępnianie plików multimedialnych bezpośrednio przy użyciu gunicorn/"
"python nie jest rekomendowane!\n"
" Postępuj zgodnie z opisanymi \n"
" tutaj krokami w celu\n"
" uaktualnienia swojej instalacji.\n"
" "
#: .\cookbook\templates\system.html:76 .\cookbook\templates\system.html:91
#: .\cookbook\templates\system.html:104 .\cookbook\templates\system.html:115
#: .\cookbook\views\views.py:168
msgid "Everything is fine!"
msgstr "Wszystko w porządku!"
#: .\cookbook\templates\system.html:80
msgid "Secret Key"
msgstr "Sekretny klucz"
#: .\cookbook\templates\system.html:84
msgid ""
"\n"
" You do not have a SECRET_KEY configured in your "
".env file. Django defaulted to the\n"
" standard key\n"
" provided with the installation which is publicly know and "
"insecure! Please set\n"
" SECRET_KEY int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" Nie posiadasz skonfigurowanego SECRET_KEY w swoim "
"pliku .env. Django domyślnie\n"
" korzysta ze standardowego klucza\n"
" dostarczonego z instalacją, który jest publicznie znany i "
"niezabezpieczony! Proszę ustawić\n"
" SECRET_KEY w pliku konfiguracyjnym .env"
"code>.\n"
" "
#: .\cookbook\templates\system.html:94
msgid "Debug Mode"
msgstr "Tryb debugowania"
#: .\cookbook\templates\system.html:98
msgid ""
"\n"
" This application is still running in debug mode. This is most "
"likely not needed. Turn of debug mode by\n"
" setting\n"
" DEBUG=0 int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" Ta aplikacja nadal działa w trybie debugowania. "
"Najprawdopodobniej nie jest to potrzebne. Wyłącz tryb debugowania,\n"
" ustawiając\n"
" DEBUG=0 w pliku konfiguracyjnym .env.\n"
" "
#: .\cookbook\templates\system.html:107
msgid "Allowed Hosts"
msgstr ""
#: .\cookbook\templates\system.html:111
msgid ""
"\n"
" Your allowed hosts are configured to allow every host. This "
"might be ok in some setups but should be avoided. Please see the docs about "
"this.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:118
msgid "Database"
msgstr "Baza danych"
#: .\cookbook\templates\system.html:121
msgid "Info"
msgstr "Informacje"
#: .\cookbook\templates\system.html:131 .\cookbook\templates\system.html:148
msgid "Migrations"
msgstr ""
#: .\cookbook\templates\system.html:137
msgid ""
"\n"
" Migrations should never fail!\n"
" Failed migrations will likely cause major parts of the app to "
"not function correctly.\n"
" If a migration fails make sure you are on the latest version and "
"if so please post the migration log and the overview below in a GitHub "
"issue.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "False"
msgstr "Fałsz"
#: .\cookbook\templates\system.html:238
msgid "True"
msgstr "Prawda"
#: .\cookbook\templates\system.html:268
msgid "Hide"
msgstr "Ukryj"
#: .\cookbook\templates\system.html:271
msgid "Show"
msgstr "Pokaż"
#: .\cookbook\templates\test2.html:6
msgid "Export Recipes"
msgstr "Eksportuj przepisy"
#: .\cookbook\templates\test2.html:14 .\cookbook\templates\test2.html:20
msgid "Export"
msgstr "Eksportuj"
#: .\cookbook\views\api.py:198 .\cookbook\views\api.py:326
msgid "Parameter updated_at incorrectly formatted"
msgstr "Nieprawidłowo sformatowany parametr updated_at"
#: .\cookbook\views\api.py:351 .\cookbook\views\api.py:484
#, python-brace-format
msgid "No {self.basename} with id {pk} exists"
msgstr ""
#: .\cookbook\views\api.py:355
msgid "Cannot merge with the same object!"
msgstr "Nie można scalić tego samego obiektu!"
#: .\cookbook\views\api.py:362
#, python-brace-format
msgid "No {self.basename} with id {target} exists"
msgstr ""
#: .\cookbook\views\api.py:367
msgid "Cannot merge with child object!"
msgstr "Nie można scalić tego obiektu z jego dzieckiem!"
#: .\cookbook\views\api.py:405
#, python-brace-format
msgid "{source.name} was merged successfully with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:411
#, python-brace-format
msgid "An error occurred attempting to merge {source.name} with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:493
#, python-brace-format
msgid "{child.name} was moved successfully to the root."
msgstr ""
#: .\cookbook\views\api.py:496 .\cookbook\views\api.py:514
msgid "An error occurred attempting to move "
msgstr ""
#: .\cookbook\views\api.py:499
msgid "Cannot move an object to itself!"
msgstr ""
#: .\cookbook\views\api.py:505
#, python-brace-format
msgid "No {self.basename} with id {parent} exists"
msgstr ""
#: .\cookbook\views\api.py:511
#, python-brace-format
msgid "{child.name} was moved successfully to parent {parent.name}"
msgstr ""
#: .\cookbook\views\api.py:992
#, python-brace-format
msgid "{obj.name} was removed from the shopping list."
msgstr ""
#: .\cookbook\views\api.py:997 .\cookbook\views\api.py:1627
#, python-brace-format
msgid "{obj.name} was added to the shopping list."
msgstr ""
#: .\cookbook\views\api.py:1239
msgid "Filter meal plans from date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1241
msgid "Filter meal plans to date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1244
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1404
msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1406
msgid "Query string matched (fuzzy) against object name."
msgstr ""
#: .\cookbook\views\api.py:1442
msgid ""
"Query string matched (fuzzy) against recipe name. In the future also "
"fulltext search."
msgstr ""
#: .\cookbook\views\api.py:1444
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr ""
#: .\cookbook\views\api.py:1445
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr ""
#: .\cookbook\views\api.py:1446
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1447
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1448
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1450
msgid "ID of food a recipe should have. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1451
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr ""
#: .\cookbook\views\api.py:1452
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1453
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr ""
#: .\cookbook\views\api.py:1454
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1456
msgid "ID of book a recipe should be in. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1457
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr ""
#: .\cookbook\views\api.py:1458
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1459
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr ""
#: .\cookbook\views\api.py:1460
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1462
msgid "ID of unit a recipe should have."
msgstr ""
#: .\cookbook\views\api.py:1464
msgid "Exact rating of recipe"
msgstr ""
#: .\cookbook\views\api.py:1465
msgid "Rating a recipe should have or greater."
msgstr ""
#: .\cookbook\views\api.py:1466
msgid "Rating a recipe should have or smaller."
msgstr ""
#: .\cookbook\views\api.py:1468
msgid "Filter recipes cooked X times."
msgstr ""
#: .\cookbook\views\api.py:1469
msgid "Filter recipes cooked X times or more."
msgstr ""
#: .\cookbook\views\api.py:1470
msgid "Filter recipes cooked X times or less."
msgstr ""
#: .\cookbook\views\api.py:1472
msgid "Filter recipes created on the given date."
msgstr ""
#: .\cookbook\views\api.py:1473
msgid "Filter recipes created on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1474
msgid "Filter recipes created on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1476 .\cookbook\views\api.py:1477
#: .\cookbook\views\api.py:1478
msgid "Filter recipes updated on the given date."
msgstr ""
#: .\cookbook\views\api.py:1480
msgid "Filter recipes last cooked on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1481
msgid "Filter recipes last cooked on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1483 .\cookbook\views\api.py:1484
msgid "Filter recipes lasts viewed on the given date."
msgstr ""
#: .\cookbook\views\api.py:1486
msgid "Filter recipes for ones created by the given user ID"
msgstr ""
#: .\cookbook\views\api.py:1487
msgid "If only internal recipes should be returned. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1488
msgid "Returns the results in randomized order. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1490
msgid ""
"Determines the order of the results. Options are: score,-score,name,-name,"
"lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-"
"created_at,lastviewed,-lastviewed"
msgstr ""
#: .\cookbook\views\api.py:1492
msgid "Returns new results first in search results. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1493
msgid ""
"Returns the given number of recently viewed recipes before search results "
"(if given)"
msgstr ""
#: .\cookbook\views\api.py:1494
msgid "ID of a custom filter. Returns all recipes matched by that filter."
msgstr ""
#: .\cookbook\views\api.py:1495
msgid "Filter recipes that can be made with OnHand food. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1773
msgid ""
"Return the PropertyTypes matching the property category. Repeat for "
"multiple."
msgstr ""
#: .\cookbook\views\api.py:1804 .\cookbook\views\api.py:1860
msgid "Returns only entries associated with the given mealplan id"
msgstr ""
#: .\cookbook\views\api.py:1858
msgid ""
"Returns only elements updated after the given timestamp in ISO 8601 format."
msgstr ""
#: .\cookbook\views\api.py:2031
msgid ""
"Return the Automations matching the automation type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2048
msgid ""
"Text field to store data that gets carried over to the UserSpace created "
"from the InviteLink"
msgstr ""
#: .\cookbook\views\api.py:2049
msgid "Only return InviteLinks that have not been used yet."
msgstr ""
#: .\cookbook\views\api.py:2076
msgid "Return the CustomFilters matching the model type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2176
msgid "Nothing to do."
msgstr ""
#: .\cookbook\views\api.py:2222
msgid "Invalid Url"
msgstr ""
#: .\cookbook\views\api.py:2228
msgid "Connection Refused."
msgstr ""
#: .\cookbook\views\api.py:2232
msgid "Bad URL Schema."
msgstr ""
#: .\cookbook\views\api.py:2257
msgid "No usable data could be found."
msgstr "Nie znaleziono żadnych przydatnych danych."
#: .\cookbook\views\api.py:2286 .\cookbook\views\api.py:2434
msgid "You must select an AI provider to perform your request."
msgstr ""
#: .\cookbook\views\api.py:2293 .\cookbook\views\api.py:2441
msgid ""
"You don't have any credits remaining to use AI or AI features are not "
"enabled for your space."
msgstr ""
"Osiągnięto limit użyć AI lub funkcje AI nie są włączone w tej przestrzeni "
"roboczej."
#: .\cookbook\views\api.py:2499 .\cookbook\views\api.py:2667
msgid "File is above space limit"
msgstr "Plik przekracza limit miejsca"
#: .\cookbook\views\api.py:2522 .\cookbook\views\api.py:2684
msgid "Importing is not implemented for this provider"
msgstr "Importowanie dla tego usługodawcy nie zostało zaimplementowane"
#: .\cookbook\views\api.py:2548
msgid ""
"The PDF Exporter is not enabled on this instance as it is still in an "
"experimental state."
msgstr ""
"Eksporter PDF nie jest włączony w tej instancji, ponieważ jest on wciąż w "
"stanie eksperymentalnym."
#: .\cookbook\views\api.py:2842
msgid "This feature is not yet available in the hosted version of tandoor!"
msgstr "Ta funkcja nie jest jeszcze dostępna w hostowanej wersji tandoor!"
#: .\cookbook\views\api.py:2863
msgid "Sync successful!"
msgstr "Synchronizacja powiodła się!"
#: .\cookbook\views\api.py:2866
msgid "Error synchronizing with Storage"
msgstr "Błąd synchronizacji z magazynem"
#: .\cookbook\views\views.py:89
msgid "This feature is not available in the demo version!"
msgstr "Ta funkcja nie jest dostępna w wersji demo!"
#: .\cookbook\views\views.py:110
msgid ""
"You have successfully created your own recipe space. Start by adding some "
"recipes or invite other people to join you."
msgstr ""
"Pomyślnie utworzyłeś własną przestrzeń z przepisami. Zacznij od dodania "
"kilku przepisów lub zaproś inne osoby, aby do ciebie dołączyły."
#: .\cookbook\views\views.py:171
#, python-format
msgid "PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!"
msgstr ""
"PostgreSQL %(v)s jest przestarzały. Uaktualnij do w pełni obsługiwanej "
"wersji!!"
#: .\cookbook\views\views.py:174
#, python-format
msgid "You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended"
msgstr "Używasz PostgreSQL %(v1)s. Zalecany jest PostgreSQL %(v2)s"
#: .\cookbook\views\views.py:178
msgid "Unable to determine PostgreSQL version."
msgstr "Nie udało się ustalić wersji PostgreSQL."
#: .\cookbook\views\views.py:182
msgid ""
"This application is not running with a Postgres database backend. This is ok "
"but not recommended as some features only work with postgres databases."
msgstr ""
"Ta aplikacja nie została uruchomiona z bazą danych Postgres. Jest to "
"możliwe, ale nie zalecane, ponieważ niektóre funkcje działają tylko z bazami "
"danych Postgres."
#: .\cookbook\views\views.py:296
msgid ""
"The setup page can only be used to create the first "
"user! If you have forgotten your superuser credentials "
"please consult the django documentation on how to reset passwords."
msgstr ""
"Inicjalna strona konfiguracji służy wyłącznie do utworzenia pierwszego "
"użytkownika! Jeżeli zapomniałeś dane uwierzytelniające "
"superużytkownika, proszę skonsultuj się z dokumentacją django w celu resetu "
"hasła."
#: .\cookbook\views\views.py:304
msgid "Passwords dont match!"
msgstr "Hasła się nie zgadzają!"
#: .\cookbook\views\views.py:312
msgid "User has been created, please login!"
msgstr "Użytkownik został utworzony, proszę się zalogować!"
#: .\cookbook\views\views.py:352
msgid ""
"Reporting share links is not enabled for this instance. Please notify the "
"page administrator to report problems."
msgstr ""
#: .\cookbook\views\views.py:357
msgid ""
"Recipe sharing link has been disabled! For additional information please "
"contact the page administrator."
msgstr ""
#: .\cookbook\views\views.py:383
msgid "Manage recipes, shopping list, meal plans and more."
msgstr ""
#: .\cookbook\views\views.py:397 .\cookbook\views\views.py:398
msgid "Plan"
msgstr "Planowanie"
#: .\cookbook\views\views.py:399
msgid "View your meal Plan"
msgstr ""
#: .\cookbook\views\views.py:418
msgid "View your shopping lists"
msgstr "Zobacz twoją listę zakupów"
#~ msgid ""
#~ "Both fields are optional. If none are given the username will be "
#~ "displayed instead"
#~ msgstr ""
#~ "Oba pola są opcjonalne. Jeśli żadne nie jest wypełnione, wyświetlona "
#~ "zostanie nazwa użytkownika"
#~ msgid "Name"
#~ msgstr "Nazwa"
#~ msgid "Keywords"
#~ msgstr "Słowa kluczowe"
#~ msgid "Preparation time in minutes"
#~ msgstr "Czas przygotowania w minutach"
#~ msgid "Waiting time (cooking/baking) in minutes"
#~ msgstr "Czas oczekiwania (gotowania/pieczenia) w minutach"
#~ msgid "Path"
#~ msgstr "Ścieżka"
#~ msgid "Storage UID"
#~ msgstr "UID magazynu"
#~ msgid "Add your comment: "
#~ msgstr "Dodaj komentarz: "
#~ msgid "Leave empty for dropbox and enter app password for nextcloud."
#~ msgstr ""
#~ "Pozostaw puste dla dropbox, uzupełnij hasłem aplikacji dla nextcloud."
#~ msgid "Leave empty for nextcloud and enter api token for dropbox."
#~ msgstr "Pozostaw puste dla nextcloud, uzupełnij tokenem api dla dropbox."
#~ msgid ""
#~ "Leave empty for dropbox and enter only base url for nextcloud (/"
#~ "remote.php/webdav/ is added automatically)"
#~ msgstr ""
#~ "Pozostaw pusty dla dropbox, uzupełnij jedynie ścieżką podstawową dla "
#~ "nextcloud (/remote.php/webdav/ dodane jest automatycznie)"
#~ msgid ""
#~ "Long Lived Access Token for your HomeAssistant instance"
#~ msgstr ""
#~ "Long Lived Access Token dla Twojej instancji Home Assistant"
#~ msgid "Something like http://homeassistant.local:8123/api"
#~ msgstr "Coś w stylu http://homeassistant.local:8123/api"
#~ msgid "http://homeassistant.local:8123/api for example"
#~ msgstr "na przykład http://homeassistant.local:8123/api"
#~ msgid "Storage"
#~ msgstr "Magazyn"
#~ msgid "Active"
#~ msgstr "Aktywuj"
#~ msgid "Search String"
#~ msgstr "Wyszukaj tekst"
#~ msgid "File ID"
#~ msgstr "Numer identyfikacyjny pliku"
#~ msgid ""
#~ "Determines how fuzzy a search is if it uses trigram similarity matching "
#~ "(e.g. low values mean more typos are ignored)."
#~ msgstr ""
#~ "Określa, jak niedokładne może być wyszukiwanie, jeśli wykorzystuje "
#~ "dopasowanie podobieństwa trigramowego (np. niskie wartości oznaczają, że "
#~ "więcej literówek jest ignorowanych)."
#~ msgid ""
#~ "Select type method of search. Click here "
#~ "for full description of choices."
#~ msgstr ""
#~ "Wybierz metodę wyszukiwania. Kliknij tutaj, "
#~ "aby zapoznać się z pełnym opisem dostępnych opcji."
#~ msgid ""
#~ "Use fuzzy matching on units, keywords and ingredients when editing and "
#~ "importing recipes."
#~ msgstr ""
#~ "Użyj niedokładnego dopasowywania dla jednostek, słów kluczowych i "
#~ "składników podczas edytowania i importowania przepisów."
#~ msgid ""
#~ "Fields to search ignoring accents. Selecting this option can improve or "
#~ "degrade search quality depending on language"
#~ msgstr ""
#~ "Pola do przeszukiwania z pominięciem akcentów. Wybranie tej opcji może "
#~ "poprawić lub pogorszyć jakość wyszukiwania w zależności od języka"
#~ msgid ""
#~ "Fields to search for partial matches. (e.g. searching for 'Pie' will "
#~ "return 'pie' and 'piece' and 'soapie')"
#~ msgstr ""
#~ "Pola do przeszukiwania częściowych dopasowań (np. wyszukiwanie 'Ciasto' "
#~ "zwróci 'ciasto', 'ciasteczko' i 'ciastolina')"
#~ msgid ""
#~ "Fields to search for beginning of word matches. (e.g. searching for 'sa' "
#~ "will return 'salad' and 'sandwich')"
#~ msgstr ""
#~ "Pola do przeszukiwania dopasowań na początku wyrazu (np. wyszukiwanie "
#~ "'sa' zwróci 'sałatka' i 'sandwich')"
#~ msgid ""
#~ "Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find "
#~ "'recipe'.) Note: this option will conflict with 'web' and 'raw' methods "
#~ "of search."
#~ msgstr ""
#~ "Pola do wyszukiwania z użyciem 'niedokładnego' dopasowywania (np. "
#~ "wyszukiwanie 'przepisz' znajdzie 'przepis'). Uwaga: ta opcja będzie "
#~ "kolidować z metodami wyszukiwania 'web' i 'raw'."
#~ msgid ""
#~ "Fields to full text search. Note: 'web', 'phrase', and 'raw' search "
#~ "methods only function with fulltext fields."
#~ msgstr ""
#~ "Pola do wyszukiwania pełno tekstowego. Uwaga: metody wyszukiwania 'web', "
#~ "'phrase' i 'raw' działają wyłącznie z polami pełno tekstowymi."
#~ msgid "Search Method"
#~ msgstr "Metoda wyszukiwania"
#~ msgid "Fuzzy Lookups"
#~ msgstr "Niedokładne dopasowanie"
#~ msgid "Ignore Accent"
#~ msgstr "Ignoruj akcenty"
#~ msgid "Partial Match"
#~ msgstr "Częściowe dopasowanie"
#~ msgid "Starts With"
#~ msgstr "Zaczyna się od"
#~ msgid "Fuzzy Search"
#~ msgstr "Niedokładne wyszukiwanie"
#, fuzzy
#~| msgid "Text"
#~ msgid "Full Text"
#~ msgstr "Tekst"
#~ msgid "Delete"
#~ msgstr "Usuń"
#~ msgid "Settings"
#~ msgstr "Ustawienia"
#, fuzzy
#~| msgid "Password Reset"
#~ msgid "Password"
#~ msgstr "Reset hasła"
#, fuzzy
#~| msgid "Food"
#~ msgid "Foods"
#~ msgstr "Jedzenie"
#~ msgid "Units"
#~ msgstr "Jednostki"
#~ msgid "Supermarket"
#~ msgstr "Sklep"
#, fuzzy
#~| msgid "Supermarket"
#~ msgid "Supermarket Category"
#~ msgstr "Sklep"
#, fuzzy
#~| msgid "Information"
#~ msgid "Automations"
#~ msgstr "Informacja"
#, fuzzy
#~| msgid "File ID"
#~ msgid "Files"
#~ msgstr "Numer identyfikacyjny pliku"
#~ msgid "Batch Edit"
#~ msgstr "Edycja zbiorcza"
#~ msgid "History"
#~ msgstr "Historia"
#, fuzzy
#~| msgid "Ingredients"
#~ msgid "Ingredient Editor"
#~ msgstr "Składniki"
#, fuzzy
#~| msgid "Account Connections"
#~ msgid "Unit Conversions"
#~ msgstr "Połączenie kont"
#~ msgid "Create"
#~ msgstr "Utwórz"
#~ msgid "External Recipes"
#~ msgstr "Przepisy zewnętrzne"
#, fuzzy
#~| msgid "Settings"
#~ msgid "Space Settings"
#~ msgstr "Ustawienia"
#, fuzzy
#~| msgid "External Recipes"
#~ msgid "External Connectors"
#~ msgstr "Przepisy zewnętrzne"
#~ msgid "Admin"
#~ msgstr "Administracja"
#~ msgid "Markdown Guide"
#~ msgstr "Przewodnik markdown"
#~ msgid "GitHub"
#~ msgstr "GitHub"
#~ msgid "API Browser"
#~ msgstr "Przeglądarka API"
#~ msgid "Batch edit Category"
#~ msgstr "Edytuj zbiorczo kategorie"
#~ msgid "Batch edit Recipes"
#~ msgstr "Edytuj zbiorczo przepisy"
#~ msgid "Add the specified keywords to all recipes containing a word"
#~ msgstr ""
#~ "Dodaj przedstawione słowa kluczowe do wszystkich przepisów zawierających "
#~ "słowo"
#~ msgid "Sync"
#~ msgstr "Synchronizuj"
#~ msgid "Manage watched Folders"
#~ msgstr "Zarządaj obserwowanymi katalogami"
#~ msgid ""
#~ "On this Page you can manage all storage folder locations that should be "
#~ "monitored and synced."
#~ msgstr ""
#~ "Na tej stronie możesz zarządzać wszystkimi katalogami na udziale pamięci "
#~ "masowej które będą monitorowane i synchronizowane."
#~ msgid "The path must be in the following format"
#~ msgstr "Ścieżka musi być w następującym formacie"
#~ msgid "Save"
#~ msgstr "Zapisz"
#~ msgid "Sync Now!"
#~ msgstr "Synchronizuj teraz!"
#, fuzzy
#~| msgid "Shopping Recipes"
#~ msgid "Show Recipes"
#~ msgstr "Zakupy do Przepisów"
#, fuzzy
#~| msgid "Show Links"
#~ msgid "Show Log"
#~ msgstr "Wyświetl linki"
#~ msgid "Importing Recipes"
#~ msgstr "Importuj przepisy"
#~ msgid ""
#~ "This can take a few minutes, depending on the number of recipes in sync, "
#~ "please wait."
#~ msgstr ""
#~ "Proces ten może zająć pare minut, w zależności od ilości aktualnie "
#~ "zsynchronizowanych przepisów, proszę poczekaj."
#~ msgid "Recipe Books"
#~ msgstr "Książki z przepisami"
#~ msgid "Import new Recipe"
#~ msgstr "Importuj nowy przepis"
#~ msgid "Edit Recipe"
#~ msgstr "Edytuj przepis"
#, python-format
#~ msgid "Are you sure you want to delete the %(title)s: %(object)s "
#~ msgstr "Czy na pewno chcesz usunąć %(title)s: %(object)s "
#~ msgid "Edit"
#~ msgstr "Edytuj"
#~ msgid "View"
#~ msgstr "Wyświetl"
#~ msgid "Delete original file"
#~ msgstr "Usuń oryginalny plik"
#~ msgid "List"
#~ msgstr "Lista"
#~ msgid "Filter"
#~ msgstr "Filtr"
#~ msgid "Import all"
#~ msgstr "Zaimportuj wszystkie"
#~ msgid "New"
#~ msgstr "Nowy"
#~ msgid "previous"
#~ msgstr "poprzedni"
#~ msgid "next"
#~ msgstr "następny"
#~ msgid "View Log"
#~ msgstr "Zobacz dziennik"
#~ msgid "Cook Log"
#~ msgstr "Dziennik gotowań"
#~ msgid "Import"
#~ msgstr "Importuj"
#~ msgid "Security Warning"
#~ msgstr "Ostrzeżenie bezpieczeństwa"
#~ msgid ""
#~ "\n"
#~ " The Password and Token field are stored as plain text"
#~ "b> inside the database.\n"
#~ " This is necessary because they are needed to make API requests, "
#~ "but it also increases the risk of\n"
#~ " someone stealing it.
\n"
#~ " To limit the possible damage tokens or accounts with limited "
#~ "access can be used.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Pola Hasło oraz Token są zapisane jawnym tekstem "
#~ "wewnątrz bazy danych.\n"
#~ " To jest konieczne ponieważ są one potrzebne do tworzenia wywołań "
#~ "API, ale zwiększa to również ryzyko,\n"
#~ " że ktoś je wykradnie.
\n"
#~ " W celu ograniczenia możliwych szkód należy używać kont i tokenów "
#~ "z ograniczonym dostępem.\n"
#~ " "
#~ msgid "You are currently offline!"
#~ msgstr "Jesteś obecnie offline!"
#~ msgid ""
#~ "The recipes listed below are available for offline viewing because you "
#~ "have recently viewed them. Keep in mind that data might be outdated."
#~ msgstr ""
#~ "Przepisy wymienione poniżej są dostępne do przeglądania w trybie offline, "
#~ "ponieważ ostatnio je oglądałeś. Pamiętaj, że dane mogą być nieaktualne."
#~ msgid "Comments"
#~ msgstr "Uwagi"
#~ msgid "by"
#~ msgstr "przez"
#~ msgid "Comment"
#~ msgstr "Komentarz"
#, fuzzy
#~| msgid "Social Login"
#~ msgid "Social"
#~ msgstr "Logowanie społecznościowe"
#, fuzzy
#~| msgid "Description"
#~ msgid "Manage Subscription"
#~ msgstr "Opis"
#~ msgid "URL Import"
#~ msgstr "Importuj z URL"
#, python-format
#~ msgid "Batch edit done. %(count)d recipe was updated."
#~ msgid_plural "Batch edit done. %(count)d Recipes where updated."
#~ msgstr[0] "Edycja zbiorcza zakończona. Zaktualizowano %(count)d przepis."
#~ msgstr[1] "Edycja zbiorcza zakończona. Zaktualizowano %(count)d przepisy."
#~ msgstr[2] "Edycja zbiorcza zakończona. Zaktualizowano %(count)d przepisów."
#~ msgstr[3] "Edycja zbiorcza zakończona. Zaktualizowano przepisy: %(count)d."
#~ msgid "Monitor"
#~ msgstr "Monitor"
#~ msgid "Storage Backend"
#~ msgstr "Obsługa Magazynów"
#~ msgid ""
#~ "Could not delete this storage backend as it is used in at least one "
#~ "monitor."
#~ msgstr ""
#~ "Nie można usunąć tego typu Magazynu, ponieważ jest on używany w co "
#~ "najmniej jednym monitorze."
#~ msgid "Connectors Config Backend"
#~ msgstr "Konfiguracja łączników - Backend"
#~ msgid "Invite Link"
#~ msgstr "Link z zaproszeniem"
#~ msgid "Space Membership"
#~ msgstr "Przestrzeń dla członków"
#~ msgid "You cannot edit this storage!"
#~ msgstr "Nie możesz edytować tego Magazynu!"
#~ msgid "Storage saved!"
#~ msgstr "Magazyn zapisany!"
#~ msgid "There was an error updating this storage backend!"
#~ msgstr "Podczas aktualizowania tego Magazynu wystąpił błąd!"
#~ msgid "Config saved!"
#~ msgstr "Zapisano konfigurację!"
#~ msgid "ConnectorConfig"
#~ msgstr "Konfiguracja konektora"
#~ msgid "Changes saved!"
#~ msgstr "Zapisano zmiany!"
#~ msgid "Error saving changes!"
#~ msgstr "Błąd zapisu zmian!"
#~ msgid "Import Log"
#~ msgstr "Dziennik importów"
#~ msgid "Discovery"
#~ msgstr "Wykrywanie"
#~ msgid "Shopping List"
#~ msgstr "Lista zakupów"
#~ msgid "Connector Config Backend"
#~ msgstr "Konfiguracja łącznika - Backend"
#~ msgid "Invite Links"
#~ msgstr "Linki z zaproszeniami"
#~ msgid "Supermarkets"
#~ msgstr "Supermarkety"
#~ msgid "Shopping Categories"
#~ msgstr "Kategorie zakupów"
#~ msgid "Custom Filters"
#~ msgstr "Filtry niestandardowe"
#~ msgid "Steps"
#~ msgstr "Kroki"
#~ msgid "Property Types"
#~ msgstr "Typy właściwości"
#~ msgid "This feature is not enabled by the server admin!"
#~ msgstr "Ta funkcja nie jest włączona przez administratora serwera!"
#~ msgid "Imported new recipe!"
#~ msgstr "Nowy przepis został zaimportowany!"
#~ msgid "There was an error importing this recipe!"
#~ msgstr "Wystąpił błąd podczas importu tego przepisu!"
#~ msgid "You do not have the required permissions to perform this action!"
#~ msgstr "Nie masz wystarczających uprawnień do wykonania tej akcji!"
#~ msgid "Comment saved!"
#~ msgstr "Komentarz zapisany!"
#~ msgid "You must select at least one field to search!"
#~ msgstr "Wybierz przynajmniej jedno pole aby przeszukiwać!"
#~ msgid ""
#~ "To use this search method you must select at least one full text search "
#~ "field!"
#~ msgstr ""
#~ "Aby skorzystać z tej metody wyszukiwania, musisz wybrać przynajmniej "
#~ "jedno pole wyszukiwania pełnotekstowego!"
#~ msgid "Fuzzy search is not compatible with this search method!"
#~ msgstr ""
#~ "Wyszukiwanie rozmyte nie jest kompatybilne z tą metodą wyszukiwania!"
#~ msgid "Malformed Invite Link supplied!"
#~ msgstr "Użyty został nieprawidłowy odnośnik zaproszenia!"
#~ msgid "Successfully joined space."
#~ msgstr "Pomyślnie dołączono do przestrzeni."
#~ msgid "Invite Link not valid or already used!"
#~ msgstr "Odnośnik zaproszenia jest nieprawidłowy bądź został już użyty!"
#~ msgid ""
#~ "Color of the top navigation bar. Not all colors work with all themes, "
#~ "just try them out!"
#~ msgstr ""
#~ "Kolor górnego paska nawigacji. Nie wszystkie kolory współgrają z "
#~ "wszystkimi skórkami, po prostu je wypróbuj!"
#~ msgid ""
#~ "Default Unit to be used when inserting a new ingredient into a recipe."
#~ msgstr ""
#~ "Domyślna jednostka która będzie użyta przy dodawaniu nowego składnika do "
#~ "przepisu."
#~ msgid ""
#~ "Enables support for fractions in ingredient amounts (e.g. convert "
#~ "decimals to fractions automatically)"
#~ msgstr ""
#~ "Włącza obsługę ułamków w ilości składników (np. autoamtyczne "
#~ "konwertowanie liczb dziesiętnych na ułamki)"
#~ msgid ""
#~ "Users with whom newly created meal plan/shopping list entries should be "
#~ "shared by default."
#~ msgstr ""
#~ "Użytkownicy z którymi nowo utworzone plany posiłków/listy zakupów powinny "
#~ "być domyślnie wspołdzielone."
#~ msgid "Show recently viewed recipes on search page."
#~ msgstr "Pokaż niedawno odwiedzane przepisy na liście wyszukiwań."
#~ msgid "Number of decimals to round ingredients."
#~ msgstr ""
#~ "Ilość liczb dziesiętnych po kropce do którego składniki będą zaokrąglane."
#~ msgid ""
#~ "If you want to be able to create and see comments underneath recipes."
#~ msgstr ""
#~ "Jeśli chcesz mieć możliwość tworzenia i widzenia komentarzy pod "
#~ "przepisami."
#~ msgid ""
#~ "Setting to 0 will disable auto sync. When viewing a shopping list the "
#~ "list is updated every set seconds to sync changes someone else might have "
#~ "made. Useful when shopping with multiple people but might use a little "
#~ "bit of mobile data. If lower than instance limit it is reset when saving."
#~ msgstr ""
#~ "Ustawienie na 0 wyłącza automatyczną synchronizację. Spoglądając na listę "
#~ "zakupów jest ona aktualizowana co ustawioną sekundę w celu synchronizacji "
#~ "zmian które mógł wprowadzić ktoś inny. Przydatne w przypadku zakupów "
#~ "przez parę osób, może jednak używać trochę danych mobilnych. Jeśli "
#~ "wartość jest mniejsza niż ilość instancji, jest ona resetowana przy "
#~ "zapisie."
#~ msgid "Makes the navbar stick to the top of the page."
#~ msgstr "Sprawia, że górny pasek nawigacji jest zawsze widoczny u góry."
#~ msgid "Number of servings"
#~ msgstr "Ilość porcji"
#~ msgid ""
#~ "Include - [ ] in list for easier usage in markdown based "
#~ "documents."
#~ msgstr ""
#~ "Zawrzyj - [ ] w liście w celu łatwiejszego użycia w "
#~ "dokumentach bazowanych na markdown."
#~ msgid "New unit that other gets replaced by."
#~ msgstr "Nowa jednostka która zamieni poprzednią."
#~ msgid "Old Unit"
#~ msgstr "Stara jednostka"
#~ msgid "Unit that should be replaced."
#~ msgstr "Jednostka która zostanie zamieniona."
#~ msgid "New Food"
#~ msgstr "Nowy posiłek"
#~ msgid "New food that other gets replaced by."
#~ msgstr "Nowy posiłek który zamieni poprzedni."
#~ msgid "Old Food"
#~ msgstr "Stary posiłek"
#~ msgid "Food that should be replaced."
#~ msgstr "Posiłek który zostanie zamieniony."
#~ msgid "You must provide at least a recipe or a title."
#~ msgstr "Musisz podać co najmniej przepis lub tytuł."
#~ msgid "You can list default users to share recipes with in the settings."
#~ msgstr ""
#~ "Możesz wyświetlić domyślnych użytkowników z którymi możesz wspóldzielić "
#~ "przepis w ustawieniach."
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "Możesz użyć markdown do formatowania tego pola. Spojrzj na dokumenty tutaj"
#~ msgid ""
#~ "A username is not required, if left blank the new user can choose one."
#~ msgstr ""
#~ "Nazwa użytkownika nie jest wymagana, pozostawiona pusta umożliwia nowemu "
#~ "użytkownikowi wybranie samemu."
#~ msgid "The requested site provided malformed data and cannot be read."
#~ msgstr ""
#~ "Żądana strona zwróciła zniekształcone dane, nie może więc zostać "
#~ "obsłużona."
#~ msgid ""
#~ "The requested site does not provide any recognized data format to import "
#~ "the recipe from."
#~ msgstr ""
#~ "Żądana strona nie dostarcza rozpoznywalnego formatu danych w celu importu "
#~ "przepisu."
#~ msgid "Small"
#~ msgstr "Mały"
#~ msgid "Large"
#~ msgstr "Duży"
#~ msgid "Time"
#~ msgstr "Czas"
#~ msgid "Link"
#~ msgstr "Odnośnik"
#~ msgid "Utensils"
#~ msgstr "Przybory"
#~ msgid "Storage Data"
#~ msgstr "Przechowywanie danych"
#~ msgid "Storage Backends"
#~ msgstr "Pamięć masowa"
#~ msgid "Configure Sync"
#~ msgstr "Synchronizacja konfiguracji"
#~ msgid "Discovered Recipes"
#~ msgstr "Wykryte przepisy"
#~ msgid "Discovery Log"
#~ msgstr "Dziennik wykrycia"
#~ msgid "Statistics"
#~ msgstr "Statystyki"
#~ msgid "Units & Ingredients"
#~ msgstr "Jednostki i składniki"
#~ msgid "Logout"
#~ msgstr "Wyloguj"
#~ msgid "New Book"
#~ msgstr "Nowa książka"
#~ msgid "Toggle Recipes"
#~ msgstr "Przełącz przepisy"
#~ msgid "There are no recipes in this book yet."
#~ msgstr "W tej książce nie ma na ten moment żadnego przepisu."
#~ msgid "Waiting Time"
#~ msgstr "Czas oczekiwania"
#~ msgid "Servings Text"
#~ msgstr "Tekst porcji"
#~ msgid "Select Keywords"
#~ msgstr "Zaznacz słowa kluczowe"
#~ msgid "Delete Step"
#~ msgstr "Usuń krok"
#~ msgid "Step"
#~ msgstr "Krok"
#~ msgid "Show as header"
#~ msgstr "Pokaż jako nagłówek"
#~ msgid "Hide as header"
#~ msgstr "Ukryj jako nagłówek"
#~ msgid "Move Up"
#~ msgstr "Przenieś w górę"
#~ msgid "Move Down"
#~ msgstr "Przenieś w dół"
#~ msgid "Step Name"
#~ msgstr "Nazwa kroku"
#~ msgid "Step Type"
#~ msgstr "Typ kroku"
#~ msgid "Step time in Minutes"
#~ msgstr "Czas kroku mierzony w minutach"
#~ msgid "Select Unit"
#~ msgstr "Wybierz jednostkę"
#~ msgid "Select"
#~ msgstr "Zaznacz"
#~ msgid "Select Food"
#~ msgstr "Wybierz pożywienie"
#~ msgid "Delete Ingredient"
#~ msgstr "Usuń składnik"
#~ msgid "Make Ingredient"
#~ msgstr "Utwórz składnik"
#~ msgid "Disable Amount"
#~ msgstr "Wyłącz ilość"
#~ msgid "Enable Amount"
#~ msgstr "Wlącz ilość"
#~ msgid "Copy Template Reference"
#~ msgstr "Kopiuj odniesienie do szablonu"
#~ msgid "Save & View"
#~ msgstr "Zapisz i wyświetl"
#~ msgid "Add Step"
#~ msgstr "Dodaj krok"
#~ msgid "Add Nutrition"
#~ msgstr "Dodaj wartość odżywczą"
#~ msgid "Remove Nutrition"
#~ msgstr "Usuń wartość odżywczą"
#~ msgid "View Recipe"
#~ msgstr "Pokaż przepis"
#~ msgid "Delete Recipe"
#~ msgstr "Usuń przepis"
#~ msgid "Edit Ingredients"
#~ msgstr "Edytuj składniki"
#~ msgid ""
#~ "\n"
#~ " The following form can be used if, accidentally, two (or more) "
#~ "units or ingredients where created that should be\n"
#~ " the same.\n"
#~ " It merges two units or ingredients and updates all recipes using "
#~ "them.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Formularz tego może zostać użyty w przypadku gdy dwie (lub "
#~ "więcej) jednostki lub składniki zostały utworzone a są one\n"
#~ " takie same.\n"
#~ " Następuje scalenie obu jednostek lub składników i aktualizuje "
#~ "przepisy które je używały.\n"
#~ " "
#~ msgid "Are you sure that you want to merge these two units?"
#~ msgstr "Czy na pewno chcesz połączyć obie jednostki?"
#~ msgid "Are you sure that you want to merge these two ingredients?"
#~ msgstr "Czy na pewno chcesz połączyć oba składniki?"
#~ msgid "Import Recipes"
#~ msgstr "Importuj przepisy"
#~ msgid "Log Recipe Cooking"
#~ msgstr "Dziennik gotowanych przepisów"
#~ msgid "All fields are optional and can be left empty."
#~ msgstr "Wszystkie pola są opcjonalne i mogą pozostać puste."
#~ msgid "Rating"
#~ msgstr "Ocena"
#~ msgid "Close"
#~ msgstr "Zamknij"
#~ msgid "Open Recipe"
#~ msgstr "Otwórz przepis"
#~ msgid "Website Import"
#~ msgstr "Import z WWW"
#~ msgid "New Entry"
#~ msgstr "Nowy wpis"
#~ msgid "Title"
#~ msgstr "Tytuł"
#~ msgid "Note (optional)"
#~ msgstr "Notatka (opcjonalna)"
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "Możesz użyć Markdown do sformatowania tego pola. Sprawdź tę "
#~ "dokumentację"
#~ msgid "Serving Count"
#~ msgstr "Liczba porcji"
#~ msgid "Create only note"
#~ msgstr "Stwórz tylko natatkę"
#~ msgid "Shopping list currently empty"
#~ msgstr "Lista zakupów obecnie jest pusta"
#~ msgid "Open Shopping List"
#~ msgstr "Otwórz Listę zakupów"
#~ msgid "Number of Days"
#~ msgstr "Liczba dni"
#~ msgid "Weekday offset"
#~ msgstr "Przesunięcie dni tygodnia"
#~ msgid ""
#~ "Number of days starting from the first day of the week to offset the "
#~ "default view."
#~ msgstr ""
#~ "Liczba dni począwszy od pierwszego dnia tygodnia aby ustawić domyślny "
#~ "widok."
#~ msgid "Edit plan types"
#~ msgstr "Edytuj typy planów"
#~ msgid "Week iCal export"
#~ msgstr "Eksport planu tygodniowego do pliku iCal"
#~ msgid "Created by"
#~ msgstr "Stworzone przez"
#~ msgid "Shared with"
#~ msgstr "Współdzielone z"
#~ msgid "Add to Shopping"
#~ msgstr "Dodaj do zakupów"
#~ msgid "New meal type"
#~ msgstr "Nowy typ posiłku"
#~ msgid "Meal Plan Help"
#~ msgstr "Pomoc dla Planu posiłków"
#~ msgid ""
#~ "\n"
#~ " The meal plan module allows planning of "
#~ "meals both with recipes and notes.
\n"
#~ " Simply select a recipe from the list of "
#~ "recently viewed recipes or search the one you\n"
#~ " want and drag it to the desired plan "
#~ "position. You can also add a note and a title and\n"
#~ " then drag the recipe to create a plan "
#~ "entry with a custom title and note. Creating only\n"
#~ " Notes is possible by dragging the create "
#~ "note box into the plan.
\n"
#~ " Click on a recipe in order to open the "
#~ "detailed view. There you can also add it to the\n"
#~ " shopping list. You can also add all "
#~ "recipes of a day to the shopping list by\n"
#~ " clicking the shopping cart at the top of "
#~ "the table.
\n"
#~ " Since a common use case is to plan meals "
#~ "together you can define\n"
#~ " users you want to share your plan with in "
#~ "the settings.\n"
#~ "
\n"
#~ " You can also edit the types of meals you "
#~ "want to plan. If you share your plan with\n"
#~ " someone with\n"
#~ " different meals, their meal types will "
#~ "appear in your list as well. To prevent\n"
#~ " duplicates (e.g. Other and Misc.)\n"
#~ " name your meal types the same as the "
#~ "users you share your meals with and they will be\n"
#~ " merged.
\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Moduł planowania posiłków pozwala na "
#~ "planowanie zarówno przy wykorzystaniu przepisów jak i notatek.
\n"
#~ " Po prostu wybierz przepis z listy ostatnio "
#~ "oglądanych przepisów lub wyszukaj ten\n"
#~ " który chcesz i przeciągnij go do wybranej "
#~ "pozycji na planie. Możesz również dodać tytuł i notatkę\n"
#~ " i wtedy przeciągnąć przepis w celu "
#~ "stworzenia pozycji na planie z własnym tytułem i notatką. Tworzenie "
#~ "tylko\n"
#~ " Notatek jest możliwe poprzez "
#~ "przeciągnięcie bloku Stwórz tylko notatkę na plan.
\n"
#~ " Kliknij na przepisie w celu otworzenia "
#~ "podglądu szczegółów. Stąd możesz dodać go do\n"
#~ " listy zakupów. Możesz również dodać "
#~ "wszystkie przepisy z dnia do listy zakupów poprzez\n"
#~ " kliknięcie wózka sklepowego na górze "
#~ "tabeli.
\n"
#~ " Ze względu na to że powszechne jest "
#~ "wspólne planowanie posiłków, możesz wskazać\n"
#~ " użytkowników z którymi chcesz "
#~ "współdzielić swój plan w ustawieniach.\n"
#~ "
\n"
#~ " Możesz również edytować typy posiłków, "
#~ "które chcesz planować. Jeżeli współdzielisz swój plan\n"
#~ " z kimś z innymi posiłkami,\n"
#~ " ich typy posiłków również pojawią się na "
#~ "twojej liście. Aby zapobiec\n"
#~ " duplikowaniu (np. Inne i Różne)\n"
#~ " nazywaj swoje typy posiłków tak samo jak "
#~ "użytkownicy z którymi współdzielisz posiłki a wtedy zostaną\n"
#~ " połączone.
\n"
#~ " "
#~ msgid "Meal Plan View"
#~ msgstr "Podgląd Planu posiłków"
#~ msgid "Never cooked before."
#~ msgstr "Nigdy dotąd nie ugotowane."
#~ msgid "Other meals on this day"
#~ msgstr "Inne posiłki tego dnia"
#~ msgid "Recipe Image"
#~ msgstr "Obraz dla przepisu"
#~ msgid "Preparation time ca."
#~ msgstr "Czas przygotowania około"
#~ msgid "Waiting time ca."
#~ msgstr "Czas oczekiwania około"
#~ msgid "External"
#~ msgstr "Zewnętrzny"
#~ msgid "Log Cooking"
#~ msgstr "Dziennik gotowania"
#~ msgid "Account"
#~ msgstr "Konto"
#~ msgid "Link social account"
#~ msgstr "Połącz konto społecznościowe"
#~ msgid "Language"
#~ msgstr "Język"
#~ msgid "Style"
#~ msgstr "Styl"
#~ msgid ""
#~ "You can use both basic authentication and token based authentication to "
#~ "access the REST API."
#~ msgstr ""
#~ "Aby uzyskać dostęp do interfejsu REST API, można użyć zarówno "
#~ "uwierzytelniania podstawowego, jak i uwierzytelniania opartego na "
#~ "tokenach."
#~ msgid ""
#~ "Use the token as an Authorization header prefixed by the word token as "
#~ "shown in the following examples:"
#~ msgstr ""
#~ "Użyj tokena jako nagłówka autoryzacji poprzedzonego słowem token, jak "
#~ "pokazano w następujących przykładach:"
#~ msgid "or"
#~ msgstr "lub"
#~ msgid "No recipes selected"
#~ msgstr "Nie wybrano przepisów"
#~ msgid "Entry Mode"
#~ msgstr "Tryb wprowadzania"
#~ msgid "Add Entry"
#~ msgstr "Dodaj pozycję"
#~ msgid "Amount"
#~ msgstr "Ilość"
#~ msgid "Select Supermarket"
#~ msgstr "Wybierz sklep"
#~ msgid "Select User"
#~ msgstr "Wybierz użytkownika"
#~ msgid "Finished"
#~ msgstr "Skończone"
#~ msgid "You are offline, shopping list might not syncronize."
#~ msgstr "Jesteś offline, lista zakupów może się nie zsynchronizować."
#~ msgid "Copy/Export"
#~ msgstr "Kopiuj/Eksportuj"
#~ msgid "List Prefix"
#~ msgstr "Prefiks listy"
#~ msgid "There was an error creating a resource!"
#~ msgstr "Wystąpił błąd podczas tworzenia zasobu!"
#~ msgid "Stats"
#~ msgstr "Statystyki"
#~ msgid "Number of objects"
#~ msgstr "Liczba obiektów"
#~ msgid "Recipe Imports"
#~ msgstr "Import przepisów"
#~ msgid "Objects stats"
#~ msgstr "Statystyki obiektów"
#~ msgid "Recipes without Keywords"
#~ msgstr "Przepisy bez słów kluczowych"
#~ msgid "Internal Recipes"
#~ msgstr "Przepisy zapisane lokalnie"
#~ msgid "Backup & Restore"
#~ msgstr "Kopie zapasowe"
#~ msgid "Download Backup"
#~ msgstr "Pobierz kopię zapasową"
#~ msgid "Enter website URL"
#~ msgstr "Wpisz adres URL witryny"
#~ msgid "Recipe Name"
#~ msgstr "Nazwa przepisu"
#~ msgid "Select one"
#~ msgstr "Wybierz jeden"
#~ msgid "All Keywords"
#~ msgstr "Wszystkie słowa kluczowe"
#~ msgid "Import all keywords, not only the ones already existing."
#~ msgstr "Importuj wszystkie słowa kluczowe, nie tylko te już istniejące."
#~ msgid ""
#~ " Only websites containing ld+json or microdata information can currently\n"
#~ " be imported. Most big recipe pages "
#~ "support this. If you site cannot be imported but\n"
#~ " you think\n"
#~ " it probably has some kind of "
#~ "structured data feel free to post an example in the\n"
#~ " github issues."
#~ msgstr ""
#~ " Obecnie można importować tylko witryny internetowe zawierające "
#~ "informacje o ld+json lub mikrodanych.\n"
#~ " Obsługuje to większość dużych stron z "
#~ "przepisami. Jeśli Twoja witryna nie może zostać zaimportowana,\n"
#~ " ale uważasz,\n"
#~ " że prawdopodobnie zawiera jakieś "
#~ "uporządkowane dane, możesz zamieścić przykład\n"
#~ " na github."
#~ msgid "Google ld+json Info"
#~ msgstr "Informacje o Google ld+json"
#~ msgid "GitHub Issues"
#~ msgstr "Problemy na GitHub"
#~ msgid "Recipe Markup Specification"
#~ msgstr "Specyfikacja znaczników przepisów"
#~ msgid "Preference for given user already exists"
#~ msgstr "Preferencja dla danego użytkownika już istnieje"
#~ msgid ""
#~ "The requested page refused to provide any information (Status Code 403)."
#~ msgstr "Żądana strona odmówiła podania jakichkolwiek informacji (Kod 403)."
#~ msgid "Recipe Book"
#~ msgstr "Książka z przepisami"
#~ msgid "Bookmarks"
#~ msgstr "Zakładki"
#~ msgid "Units merged!"
#~ msgstr "Jednostki scalone!"
#~ msgid "Foods merged!"
#~ msgstr "Posiłki scalone!"
#~ msgid "Exporting is not implemented for this provider"
#~ msgstr "Eksportowanie dla tego usługodawcy nie zostało zaimplementowane"
#~ msgid "This recipe is already linked to the book!"
#~ msgstr "Ten przepis jest już powiązany z książką!"
#~ msgid "Bookmark saved!"
#~ msgstr "Zakładka zapisana!"
================================================
FILE: cookbook/locale/pt/LC_MESSAGES/django.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
# Translators:
# Henrique Diogo Silva , 2020
# João Cunha , 2020
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-22 20:15+0200\n"
"PO-Revision-Date: 2025-02-21 10:58+0000\n"
"Last-Translator: Filipe Neves \n"
"Language-Team: Portuguese \n"
"Language: pt\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Weblate 5.8.4\n"
#: .\cookbook\forms.py:50
msgid "Default"
msgstr "Predefinição"
#: .\cookbook\forms.py:77
msgid ""
"To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything."
msgstr ""
"Para evitar repetições, receitas com o mesmo nome de receitas já existentes "
"são ignoradas. Marque esta caixa para importar tudo."
#: .\cookbook\forms.py:108
msgid "Maximum number of users for this space reached."
msgstr "Número máximo de utilizadores alcançado."
#: .\cookbook\forms.py:114
msgid "Email address already taken!"
msgstr "Endereço email já utilizado!"
#: .\cookbook\forms.py:121
msgid ""
"An email address is not required but if present the invite link will be sent "
"to the user."
msgstr ""
"Um endereço de email não é obrigatório mas se fornecido será enviada uma "
"mensagem ao utilizador."
#: .\cookbook\forms.py:133
msgid "Name already taken."
msgstr "Nome já existente."
#: .\cookbook\forms.py:144 .\cookbook\forms.py:158
msgid "Accept Terms and Privacy"
msgstr "Aceitar Termos e Condições"
#: .\cookbook\helper\AllAuthCustomAdapter.py:41
msgid ""
"In order to prevent spam, the requested email was not send. Please wait a "
"few minutes and try again."
msgstr ""
"Para evitar spam, o email solicitado não foi enviado. Por favor, aguarde "
"alguns minutos e tente novamente."
#: .\cookbook\helper\permission_helper.py:165
#: .\cookbook\helper\permission_helper.py:186 .\cookbook\views\views.py:138
msgid "You are not logged in and therefore cannot view this page!"
msgstr "Autenticação necessária para aceder a esta página!"
#: .\cookbook\helper\permission_helper.py:168
#: .\cookbook\helper\permission_helper.py:173
#: .\cookbook\helper\permission_helper.py:198
#: .\cookbook\helper\permission_helper.py:265
#: .\cookbook\helper\permission_helper.py:279
#: .\cookbook\helper\permission_helper.py:290
#: .\cookbook\helper\permission_helper.py:301
#: .\cookbook\helper\permission_helper.py:317
#: .\cookbook\helper\permission_helper.py:343
#: .\cookbook\helper\permission_helper.py:359
msgid "You do not have the required permissions to view this page!"
msgstr "Sem permissões para aceder a esta página!"
#: .\cookbook\helper\permission_helper.py:191
#: .\cookbook\helper\permission_helper.py:214
#: .\cookbook\helper\permission_helper.py:236
#: .\cookbook\helper\permission_helper.py:251
msgid "You cannot interact with this object as it is not owned by you!"
msgstr "Não pode interagir com este objeto, pois não é seu!"
#: .\cookbook\helper\permission_helper.py:420
msgid "You have reached the maximum number of recipes for your space."
msgstr "Atingiu o número máximo de receitas para o seu espaço."
#: .\cookbook\helper\permission_helper.py:432
msgid "You have more users than allowed in your space."
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:319
msgid "reverse rotation"
msgstr "rotação reversa"
#: .\cookbook\helper\recipe_url_import.py:320
msgid "careful rotation"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:321
msgid "knead"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:322
msgid "thicken"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:323
msgid "warm up"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:324
msgid "ferment"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:325
#, fuzzy
#| msgid "Last cooked"
msgid "slow cook"
msgstr "Última cozinhada"
#: .\cookbook\helper\recipe_url_import.py:326
msgid "egg boiler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:327
msgid "kettle"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:328
msgid "blend"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:329
msgid "pre-clean"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:330
msgid "high temperature"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:331
msgid "rice cooker"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:332
msgid "caramelize"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:333
msgid "peeler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:334
msgid "slicer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:335
msgid "grater"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:336
msgid "spiralizer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:337
msgid "sous-vide"
msgstr ""
#: .\cookbook\helper\shopping_helper.py:150
msgid "You must supply a servings size"
msgstr "É necessário inserir uma receita ou um título"
#: .\cookbook\helper\template_helper.py:97
#: .\cookbook\helper\template_helper.py:99
#: .\cookbook\helper\template_helper.py:101
msgid "Could not parse template code."
msgstr ""
#: .\cookbook\integration\copymethat.py:44
#: .\cookbook\integration\melarecipes.py:37
msgid "Favorite"
msgstr ""
#: .\cookbook\integration\copymethat.py:50
msgid "I made this"
msgstr ""
#: .\cookbook\integration\integration.py:238
msgid ""
"Importer expected a .zip file. Did you choose the correct importer type for "
"your data ?"
msgstr ""
#: .\cookbook\integration\integration.py:241
msgid ""
"An unexpected error occurred during the import. Please make sure you have "
"uploaded a valid file."
msgstr ""
#: .\cookbook\integration\integration.py:246
msgid "The following recipes were ignored because they already existed:"
msgstr ""
#: .\cookbook\integration\integration.py:250
#, python-format
msgid "Imported %s recipes."
msgstr "%s receitas importadas."
#: .\cookbook\integration\mealie1.py:210
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "Calories"
msgstr ""
#: .\cookbook\integration\mealie1.py:211
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
msgid "Carbohydrates"
msgstr ""
#: .\cookbook\integration\mealie1.py:212
msgid "Cholesterol"
msgstr ""
#: .\cookbook\integration\mealie1.py:213
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
msgid "Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:214
msgid "Fiber"
msgstr ""
#: .\cookbook\integration\mealie1.py:215
msgid "Protein"
msgstr ""
#: .\cookbook\integration\mealie1.py:216
msgid "Saturated Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:217
msgid "Sodium"
msgstr ""
#: .\cookbook\integration\mealie1.py:218
msgid "Sugar"
msgstr ""
#: .\cookbook\integration\mealie1.py:219
msgid "Trans Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:220
msgid "Unsaturated Fat"
msgstr ""
#: .\cookbook\integration\openeats.py:28
msgid "Recipe source:"
msgstr "Fonte da Receita:"
#: .\cookbook\integration\paprika.py:49
msgid "Notes"
msgstr "Notas"
#: .\cookbook\integration\paprika.py:52
msgid "Nutritional Information"
msgstr ""
#: .\cookbook\integration\paprika.py:56
msgid "Source"
msgstr ""
#: .\cookbook\integration\recettetek.py:55
#: .\cookbook\integration\recipekeeper.py:70
msgid "Imported from"
msgstr "Importado de"
#: .\cookbook\integration\saffron.py:23
msgid "Servings"
msgstr "Porções"
#: .\cookbook\integration\saffron.py:25
msgid "Waiting time"
msgstr ""
#: .\cookbook\integration\saffron.py:27
msgid "Preparation Time"
msgstr ""
#: .\cookbook\integration\saffron.py:29 .\cookbook\templates\index.html:6
msgid "Cookbook"
msgstr "Livro de refeições"
#: .\cookbook\integration\saffron.py:31
msgid "Section"
msgstr ""
#: .\cookbook\management\commands\fix_duplicate_properties.py:15
msgid "Fixes foods with "
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:14
msgid "Rebuilds full text search index on Recipe"
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:18
msgid "Only Postgresql databases use full text search, no index to rebuild"
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:29
msgid "Recipe index rebuild complete."
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:31
msgid "Recipe index rebuild failed."
msgstr ""
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
msgid "Breakfast"
msgstr "Pequeno-almoço"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:19
msgid "Lunch"
msgstr "Almoço"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:24
msgid "Dinner"
msgstr "Jantar"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:29 .\cookbook\models.py:971
msgid "Other"
msgstr "Outro"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "g"
msgstr ""
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "Proteins"
msgstr ""
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "kcal"
msgstr ""
#: .\cookbook\models.py:325
msgid ""
"Maximum file storage for space in MB. 0 for unlimited, -1 to disable file "
"upload."
msgstr ""
#: .\cookbook\models.py:513
msgid "Search"
msgstr "Procurar"
#: .\cookbook\models.py:514
msgid "Meal-Plan"
msgstr "Plano de refeição"
#: .\cookbook\models.py:515
msgid "Books"
msgstr "Livros"
#: .\cookbook\models.py:516 .\cookbook\views\views.py:416
#: .\cookbook\views\views.py:417
msgid "Shopping"
msgstr "Compras"
#: .\cookbook\models.py:967
msgid "Nutrition"
msgstr ""
#: .\cookbook\models.py:968
#, fuzzy
#| msgid "Merge"
msgid "Allergen"
msgstr "Juntar"
#: .\cookbook\models.py:969
msgid "Price"
msgstr ""
#: .\cookbook\models.py:970
msgid "Goal"
msgstr ""
#: .\cookbook\models.py:1467 .\cookbook\templates\search_info.html:28
msgid "Simple"
msgstr ""
#: .\cookbook\models.py:1468 .\cookbook\templates\search_info.html:33
msgid "Phrase"
msgstr ""
#: .\cookbook\models.py:1469 .\cookbook\templates\search_info.html:38
msgid "Web"
msgstr ""
#: .\cookbook\models.py:1470 .\cookbook\templates\search_info.html:47
msgid "Raw"
msgstr ""
#: .\cookbook\models.py:1525
msgid "Food Alias"
msgstr "Apelido do Alimento"
#: .\cookbook\models.py:1526
msgid "Unit Alias"
msgstr "Apelido da Unidade"
#: .\cookbook\models.py:1527
msgid "Keyword Alias"
msgstr "Apelido de Palavra-chave"
#: .\cookbook\models.py:1528
msgid "Description Replace"
msgstr ""
#: .\cookbook\models.py:1529
msgid "Instruction Replace"
msgstr "Substituir Instruções"
#: .\cookbook\models.py:1530
#, fuzzy
#| msgid "New Unit"
msgid "Never Unit"
msgstr "Nova Unidade"
#: .\cookbook\models.py:1531
msgid "Transpose Words"
msgstr ""
#: .\cookbook\models.py:1532
#, fuzzy
#| msgid "Food Alias"
msgid "Food Replace"
msgstr "Apelido do Alimento"
#: .\cookbook\models.py:1533
#, fuzzy
#| msgid "Instruction Replace"
msgid "Unit Replace"
msgstr "Substituir Instruções"
#: .\cookbook\models.py:1534
msgid "Name Replace"
msgstr ""
#: .\cookbook\models.py:1564
msgid "Recipe"
msgstr "Receita"
#: .\cookbook\models.py:1565
msgid "Food"
msgstr "Alimento"
#: .\cookbook\models.py:1566
msgid "Keyword"
msgstr "Palavra-chave"
#: .\cookbook\serializer.py:262
msgid "File uploads are not enabled for this Space."
msgstr ""
#: .\cookbook\serializer.py:273
msgid "You have reached your file upload limit."
msgstr ""
#: .\cookbook\serializer.py:281
msgid "The given file type is not allowed."
msgstr ""
#: .\cookbook\serializer.py:421 .\cookbook\views\views.py:94
msgid ""
"You have the reached the maximum amount of spaces that can be owned by you."
msgstr ""
#: .\cookbook\serializer.py:434
msgid "Space Name must be unique."
msgstr ""
#: .\cookbook\serializer.py:469
msgid "Cannot modify Space owner permission."
msgstr ""
#: .\cookbook\serializer.py:1596
msgid "Hello"
msgstr ""
#: .\cookbook\serializer.py:1596
msgid "You have been invited by "
msgstr ""
#: .\cookbook\serializer.py:1598
msgid " to join their Tandoor Recipes space "
msgstr ""
#: .\cookbook\serializer.py:1600
msgid "Click the following link to activate your account: "
msgstr ""
#: .\cookbook\serializer.py:1602
msgid ""
"If the link does not work use the following code to manually join the space: "
msgstr ""
#: .\cookbook\serializer.py:1604
msgid "The invitation is valid until "
msgstr ""
#: .\cookbook\serializer.py:1606
msgid ""
"Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub "
msgstr ""
#: .\cookbook\serializer.py:1609
msgid "Tandoor Recipes Invite"
msgstr ""
#: .\cookbook\serializer.py:1813
msgid "Existing shopping list to update"
msgstr ""
#: .\cookbook\serializer.py:1815
msgid ""
"List of ingredient IDs from the recipe to add, if not provided all "
"ingredients will be added."
msgstr ""
#: .\cookbook\serializer.py:1817
msgid ""
"Providing a list_recipe ID and servings of 0 will delete that shopping list."
msgstr ""
#: .\cookbook\serializer.py:1826
msgid "Amount of food to add to the shopping list"
msgstr ""
#: .\cookbook\serializer.py:1828
msgid "ID of unit to use for the shopping list"
msgstr ""
#: .\cookbook\serializer.py:1830
msgid "When set to true will delete all food from active shopping lists."
msgstr ""
#: .\cookbook\templates\404.html:5
msgid "404 Error"
msgstr "Erro 404"
#: .\cookbook\templates\404.html:18
msgid "The page you are looking for could not be found."
msgstr "Esta página parece não existir."
#: .\cookbook\templates\404.html:33
msgid "Take me Home"
msgstr "Início"
#: .\cookbook\templates\404.html:35
msgid "Report a Bug"
msgstr "Reportar defeito"
#: .\cookbook\templates\account\email.html:6
#: .\cookbook\templates\account\email.html:10
msgid "E-mail Addresses"
msgstr ""
#: .\cookbook\templates\account\email.html:12
msgid "The following e-mail addresses are associated with your account:"
msgstr ""
#: .\cookbook\templates\account\email.html:29
msgid "Verified"
msgstr ""
#: .\cookbook\templates\account\email.html:31
msgid "Unverified"
msgstr ""
#: .\cookbook\templates\account\email.html:33
msgid "Primary"
msgstr ""
#: .\cookbook\templates\account\email.html:40
msgid "Make Primary"
msgstr "Tornar Primeiro"
#: .\cookbook\templates\account\email.html:42
msgid "Re-send Verification"
msgstr ""
#: .\cookbook\templates\account\email.html:43
#: .\cookbook\templates\socialaccount\connections.html:36
msgid "Remove"
msgstr ""
#: .\cookbook\templates\account\email.html:51
msgid "Warning:"
msgstr ""
#: .\cookbook\templates\account\email.html:51
msgid ""
"You currently do not have any e-mail address set up. You should really add "
"an e-mail address so you can receive notifications, reset your password, etc."
msgstr ""
#: .\cookbook\templates\account\email.html:57
msgid "Add E-mail Address"
msgstr ""
#: .\cookbook\templates\account\email.html:62
msgid "Add E-mail"
msgstr ""
#: .\cookbook\templates\account\email.html:72
msgid "Do you really want to remove the selected e-mail address?"
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:6
#: .\cookbook\templates\account\email_confirm.html:10
msgid "Confirm E-mail Address"
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:16
#, python-format
msgid ""
"Please confirm that\n"
" %(email)s is an e-mail address "
"for user %(user_display)s\n"
" ."
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:22
msgid "Confirm"
msgstr "Confirme"
#: .\cookbook\templates\account\email_confirm.html:29
#, python-format
msgid ""
"This e-mail confirmation link expired or is invalid. Please\n"
" issue a new e-mail confirmation "
"request."
msgstr ""
#: .\cookbook\templates\account\login.html:8
#: .\cookbook\templates\openid\login.html:8
msgid "Login"
msgstr "Iniciar sessão"
#: .\cookbook\templates\account\login.html:15
#: .\cookbook\templates\account\login.html:31
#: .\cookbook\templates\account\password_reset.html:39
#: .\cookbook\templates\account\password_reset_done.html:31
#: .\cookbook\templates\account\signup.html:68
#: .\cookbook\templates\account\signup_closed.html:15
#: .\cookbook\templates\openid\login.html:15
#: .\cookbook\templates\openid\login.html:26
#: .\cookbook\templates\socialaccount\authentication_error.html:15
msgid "Sign In"
msgstr ""
#: .\cookbook\templates\account\login.html:34
#: .\cookbook\templates\account\password_reset.html:41
#: .\cookbook\templates\account\password_reset_done.html:33
#: .\cookbook\templates\socialaccount\signup.html:8
#: .\cookbook\templates\socialaccount\signup.html:56
msgid "Sign Up"
msgstr ""
#: .\cookbook\templates\account\login.html:38
msgid "Lost your password?"
msgstr ""
#: .\cookbook\templates\account\login.html:39
#: .\cookbook\templates\account\password_reset.html:29
msgid "Reset My Password"
msgstr ""
#: .\cookbook\templates\account\login.html:50
msgid "Social Login"
msgstr ""
#: .\cookbook\templates\account\login.html:51
msgid "You can use any of the following providers to sign in."
msgstr ""
#: .\cookbook\templates\account\logout.html:5
#: .\cookbook\templates\account\logout.html:9
#: .\cookbook\templates\account\logout.html:18
msgid "Sign Out"
msgstr ""
#: .\cookbook\templates\account\logout.html:11
msgid "Are you sure you want to sign out?"
msgstr ""
#: .\cookbook\templates\account\password_change.html:6
#: .\cookbook\templates\account\password_change.html:10
#: .\cookbook\templates\account\password_change.html:15
#: .\cookbook\templates\account\password_reset_from_key.html:7
#: .\cookbook\templates\account\password_reset_from_key.html:13
#: .\cookbook\templates\account\password_reset_from_key_done.html:7
#: .\cookbook\templates\account\password_reset_from_key_done.html:13
msgid "Change Password"
msgstr ""
#: .\cookbook\templates\account\password_change.html:16
msgid "Forgot Password?"
msgstr ""
#: .\cookbook\templates\account\password_reset.html:7
#: .\cookbook\templates\account\password_reset.html:13
#: .\cookbook\templates\account\password_reset_done.html:7
#: .\cookbook\templates\account\password_reset_done.html:18
msgid "Password Reset"
msgstr ""
#: .\cookbook\templates\account\password_reset.html:24
msgid ""
"Forgotten your password? Enter your e-mail address below, and we'll send you "
"an e-mail allowing you to reset it."
msgstr ""
#: .\cookbook\templates\account\password_reset.html:32
msgid "Password reset is disabled on this instance."
msgstr ""
#: .\cookbook\templates\account\password_reset_done.html:25
msgid ""
"We have sent you an e-mail. Please contact us if you do not receive it "
"within a few minutes."
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:13
msgid "Bad Token"
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:25
#, python-format
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used.\n"
" Please request a new "
"password reset."
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:33
msgid "change password"
msgstr "alterar senha"
#: .\cookbook\templates\account\password_reset_from_key.html:36
#: .\cookbook\templates\account\password_reset_from_key_done.html:19
msgid "Your password is now changed."
msgstr ""
#: .\cookbook\templates\account\password_set.html:6
#: .\cookbook\templates\account\password_set.html:10
#: .\cookbook\templates\account\password_set.html:15
msgid "Set Password"
msgstr ""
#: .\cookbook\templates\account\signup.html:5
msgid "Register"
msgstr ""
#: .\cookbook\templates\account\signup.html:11
msgid "Create an Account"
msgstr ""
#: .\cookbook\templates\account\signup.html:41
msgid "I accept the follwoing"
msgstr ""
#: .\cookbook\templates\account\signup.html:44
#: .\cookbook\templates\socialaccount\signup.html:35
msgid "Terms and Conditions"
msgstr ""
#: .\cookbook\templates\account\signup.html:47
#: .\cookbook\templates\socialaccount\signup.html:38
msgid "and"
msgstr ""
#: .\cookbook\templates\account\signup.html:51
#: .\cookbook\templates\socialaccount\signup.html:42
msgid "Privacy Policy"
msgstr ""
#: .\cookbook\templates\account\signup.html:64
msgid "Create User"
msgstr ""
#: .\cookbook\templates\account\signup.html:68
msgid "Already have an account?"
msgstr ""
#: .\cookbook\templates\account\signup_closed.html:5
#: .\cookbook\templates\account\signup_closed.html:11
msgid "Sign Up Closed"
msgstr ""
#: .\cookbook\templates\account\signup_closed.html:13
msgid "We are sorry, but the sign up is currently closed."
msgstr ""
#: .\cookbook\templates\frontend\tandoor.html:15
msgid "Tandoor Recipe Manager"
msgstr ""
#: .\cookbook\templates\index.html:28
msgid "Search recipe ..."
msgstr "Procure receita ..."
#: .\cookbook\templates\index.html:43
msgid "New Recipe"
msgstr "Nova Receita"
#: .\cookbook\templates\index.html:46
msgid "Import Recipe"
msgstr "Importar Receita"
#: .\cookbook\templates\index.html:52
msgid "Advanced Search"
msgstr "Pesquisa avançada"
#: .\cookbook\templates\index.html:56
msgid "Reset Search"
msgstr "Redefinir Procura"
#: .\cookbook\templates\index.html:84
msgid "Last viewed"
msgstr "Ultimo visto"
#: .\cookbook\templates\index.html:86
msgid "Recipes"
msgstr "Receitas"
#: .\cookbook\templates\index.html:93
msgid "Log in to view recipes"
msgstr ""
#: .\cookbook\templates\markdown_info.html:5
#: .\cookbook\templates\markdown_info.html:13
msgid "Markdown Info"
msgstr "Informação Markdown"
#: .\cookbook\templates\markdown_info.html:14
msgid ""
"\n"
" Markdown is lightweight markup language that can be used to format "
"plain text easily.\n"
" This site uses the Python Markdown library to\n"
" convert your text into nice looking HTML. Its full markdown "
"documentation can be found\n"
" here.\n"
" An incomplete but most likely sufficient documentation can be found "
"below.\n"
" "
msgstr ""
#: .\cookbook\templates\markdown_info.html:25
msgid "Headers"
msgstr "Cabeçalhos"
#: .\cookbook\templates\markdown_info.html:54
msgid "Formatting"
msgstr "Formatação"
#: .\cookbook\templates\markdown_info.html:56
#: .\cookbook\templates\markdown_info.html:72
msgid "Line breaks are inserted by adding two spaces after the end of a line"
msgstr ""
"As quebras de linha são inseridas adicionando dois espaços após o final de "
"uma linha "
#: .\cookbook\templates\markdown_info.html:57
#: .\cookbook\templates\markdown_info.html:73
msgid "or by leaving a blank line in between."
msgstr "ou deixando uma linha em branco no meio."
#: .\cookbook\templates\markdown_info.html:59
#: .\cookbook\templates\markdown_info.html:74
msgid "This text is bold"
msgstr "Este texto está em negrito"
#: .\cookbook\templates\markdown_info.html:60
#: .\cookbook\templates\markdown_info.html:75
msgid "This text is italic"
msgstr ""
#: .\cookbook\templates\markdown_info.html:61
#: .\cookbook\templates\markdown_info.html:77
msgid "Blockquotes are also possible"
msgstr ""
#: .\cookbook\templates\markdown_info.html:84
msgid "Lists"
msgstr "Listas"
#: .\cookbook\templates\markdown_info.html:85
msgid ""
"Lists can ordered or unordered. It is important to leave a blank line "
"before the list!"
msgstr ""
"As listas podem ser ordenadas ou não ordenadas. É importante deixar uma "
"linha em branco antes da lista!"
#: .\cookbook\templates\markdown_info.html:87
#: .\cookbook\templates\markdown_info.html:108
msgid "Ordered List"
msgstr "Lista Ordenada"
#: .\cookbook\templates\markdown_info.html:89
#: .\cookbook\templates\markdown_info.html:90
#: .\cookbook\templates\markdown_info.html:91
#: .\cookbook\templates\markdown_info.html:110
#: .\cookbook\templates\markdown_info.html:111
#: .\cookbook\templates\markdown_info.html:112
msgid "unordered list item"
msgstr "item da lista não ordenada"
#: .\cookbook\templates\markdown_info.html:93
#: .\cookbook\templates\markdown_info.html:114
msgid "Unordered List"
msgstr "Lista não ordenada"
#: .\cookbook\templates\markdown_info.html:95
#: .\cookbook\templates\markdown_info.html:96
#: .\cookbook\templates\markdown_info.html:97
#: .\cookbook\templates\markdown_info.html:116
#: .\cookbook\templates\markdown_info.html:117
#: .\cookbook\templates\markdown_info.html:118
msgid "ordered list item"
msgstr "item da lista ordenada"
#: .\cookbook\templates\markdown_info.html:125
msgid "Images & Links"
msgstr "Imagens & Links"
#: .\cookbook\templates\markdown_info.html:126
msgid ""
"Links can be formatted with Markdown. This application also allows to paste "
"links directly into markdown fields without any formatting."
msgstr ""
#: .\cookbook\templates\markdown_info.html:132
#: .\cookbook\templates\markdown_info.html:145
msgid "This will become an image"
msgstr ""
#: .\cookbook\templates\markdown_info.html:152
msgid "Tables"
msgstr "Tabelas"
#: .\cookbook\templates\markdown_info.html:153
msgid ""
"Markdown tables are hard to create by hand. It is recommended to use a table "
"editor like this one."
msgstr ""
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:171
#: .\cookbook\templates\markdown_info.html:177
msgid "Table"
msgstr "Tabela"
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:172
msgid "Header"
msgstr "Cabeçalho"
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:178
msgid "Cell"
msgstr "Célula"
#: .\cookbook\templates\no_groups_info.html:5
#: .\cookbook\templates\no_groups_info.html:12
msgid "No Permissions"
msgstr ""
#: .\cookbook\templates\no_groups_info.html:17
msgid "You do not have any groups and therefor cannot use this application."
msgstr "Você não tem nenhum grupo e, portanto, não pode usar este aplicativo."
#: .\cookbook\templates\no_groups_info.html:18
#: .\cookbook\templates\no_perm_info.html:15
msgid "Please contact your administrator."
msgstr ""
#: .\cookbook\templates\no_perm_info.html:5
#: .\cookbook\templates\no_perm_info.html:12
msgid "No Permission"
msgstr ""
#: .\cookbook\templates\no_perm_info.html:15
msgid ""
"You do not have the required permissions to view this page or perform this "
"action."
msgstr ""
"Você não tem as permissões necessárias para visualizar esta página ou "
"executar esta ação."
#: .\cookbook\templates\offline.html:5
msgid "Offline"
msgstr ""
#: .\cookbook\templates\openid\login.html:27
#: .\cookbook\templates\socialaccount\authentication_error.html:27
msgid "Back"
msgstr ""
#: .\cookbook\templates\rest_framework\api.html:5
msgid "Recipe Home"
msgstr ""
#: .\cookbook\templates\rest_framework\api.html:11
msgid "API Documentation"
msgstr "Documentação API"
#: .\cookbook\templates\search_info.html:5
#: .\cookbook\templates\search_info.html:9
msgid "Search Settings"
msgstr "Configurações de Pesquisa"
#: .\cookbook\templates\search_info.html:10
msgid ""
"\n"
" Creating the best search experience is complicated and weighs "
"heavily on your personal configuration. \n"
" Changing any of the search settings can have significant impact on "
"the speed and quality of the results.\n"
" Search Methods, Trigrams and Full Text Search configurations are "
"only available if you are using Postgres for your database.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:19
msgid "Search Methods"
msgstr "Métodos de Pesquisa"
#: .\cookbook\templates\search_info.html:23
msgid ""
" \n"
" Full text searches attempt to normalize the words provided to "
"match common variants. For example: 'forked', 'forking', 'forks' will all "
"normalize to 'fork'.\n"
" There are several methods available, described below, that will "
"control how the search behavior should react when multiple words are "
"searched.\n"
" Full technical details on how these operate can be viewed on Postgresql's website.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:29
msgid ""
" \n"
" Simple searches ignore punctuation and common words such as "
"'the', 'a', 'and'. And will treat separate words as required.\n"
" Searching for 'apple or flour' will return any recipe that "
"includes both 'apple' and 'flour' anywhere in the fields that have been "
"selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:34
msgid ""
" \n"
" Phrase searches ignore punctuation, but will search for all of "
"the words in the exact order provided.\n"
" Searching for 'apple or flour' will only return a recipe that "
"includes the exact phrase 'apple or flour' in any of the fields that have "
"been selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:39
msgid ""
" \n"
" Web searches simulate functionality found on many web search "
"sites supporting special syntax.\n"
" Placing quotes around several words will convert those words "
"into a phrase.\n"
" 'or' is recognized as searching for the word (or phrase) "
"immediately before 'or' OR the word (or phrase) directly after.\n"
" '-' is recognized as searching for recipes that do not include "
"the word (or phrase) that comes immediately after. \n"
" For example searching for 'apple pie' or cherry -butter will "
"return any recipe that includes the phrase 'apple pie' or the word "
"'cherry' \n"
" in any field included in the full text search but exclude any "
"recipe that has the word 'butter' in any field included.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:48
msgid ""
" \n"
" Raw search is similar to Web except will take puncuation "
"operators such as '|', '&' and '()'\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:59
msgid ""
" \n"
" Another approach to searching that also requires Postgresql is "
"fuzzy search or trigram similarity. A trigram is a group of three "
"consecutive characters.\n"
" For example searching for 'apple' will create x trigrams 'app', "
"'ppl', 'ple' and will create a score of how closely words match the "
"generated trigrams.\n"
" One benefit of searching trigams is that a search for 'sandwich' "
"will find misspelled words such as 'sandwhich' that would be missed by other "
"methods.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:69
msgid "Search Fields"
msgstr "Campos de Pesquisa"
#: .\cookbook\templates\search_info.html:73
msgid ""
" \n"
" Unaccent is a special case in that it enables searching a field "
"'unaccented' for each search style attempting to ignore accented values. \n"
" For example when you enable unaccent for 'Name' any search "
"(starts with, contains, trigram) will attempt the search ignoring accented "
"characters.\n"
" \n"
" For the other options, you can enable search on any or all "
"fields and they will be combined together with an assumed 'OR'.\n"
" For example enabling 'Name' for Starts With, 'Name' and "
"'Description' for Partial Match and 'Ingredients' and 'Keywords' for Full "
"Search\n"
" and searching for 'apple' will generate a search that will "
"return recipes that have:\n"
" - A recipe name that starts with 'apple'\n"
" - OR a recipe name that contains 'apple'\n"
" - OR a recipe description that contains 'apple'\n"
" - OR a recipe that will have a full text search match ('apple' "
"or 'apples') in ingredients\n"
" - OR a recipe that will have a full text search match in "
"Keywords\n"
"\n"
" Combining too many fields in too many types of search can have a "
"negative impact on performance, create duplicate results or return "
"unexpected results.\n"
" For example, enabling fuzzy search or partial matches will "
"interfere with web search methods. \n"
" Searching for 'apple -pie' with fuzzy search and full text "
"search will return the recipe Apple Pie. Though it is not included in the "
"full text results, it does match the trigram results.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:95
msgid "Search Index"
msgstr "Índice de Pesquisa"
#: .\cookbook\templates\search_info.html:99
msgid ""
" \n"
" Trigram search and Full Text Search both rely on database "
"indexes to perform effectively. \n"
" You can rebuild the indexes on all fields in the Admin page for "
"Recipes and selecting all recipes and running 'rebuild index for selected "
"recipes'\n"
" You can also rebuild indexes at the command line by executing "
"the management command 'python manage.py rebuildindex'\n"
" "
msgstr ""
#: .\cookbook\templates\setup.html:6
msgid "Cookbook Setup"
msgstr ""
#: .\cookbook\templates\setup.html:14
msgid "Setup"
msgstr ""
#: .\cookbook\templates\setup.html:15
msgid ""
"To start using this application you must first create a superuser account."
msgstr ""
#: .\cookbook\templates\setup.html:20
msgid "Create Superuser account"
msgstr ""
#: .\cookbook\templates\socialaccount\authentication_error.html:7
#: .\cookbook\templates\socialaccount\authentication_error.html:23
msgid "Social Network Login Failure"
msgstr ""
#: .\cookbook\templates\socialaccount\authentication_error.html:25
msgid ""
"An error occurred while attempting to login via your social network account."
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:4
#: .\cookbook\templates\socialaccount\connections.html:7
msgid "Account Connections"
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:10
msgid ""
"You can sign in to your account using any of the following third party\n"
" accounts:"
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:44
msgid ""
"You currently have no social network accounts connected to this account."
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:47
msgid "Add a 3rd Party Account"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:5
#: .\cookbook\templates\socialaccount\signup.html:5
msgid "Signup"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:9
#, python-format
msgid "Connect %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:11
#, python-format
msgid "You are about to connect a new third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:13
#, python-format
msgid "Sign In Via %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:15
#, python-format
msgid "You are about to sign in using a third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:20
msgid "Continue"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:10
#, python-format
msgid ""
"You are about to use your\n"
" %(provider_name)s account to login to\n"
" %(site_name)s. As a final step, please complete the following form:"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:32
msgid "I accept the following"
msgstr ""
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:23
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:31
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:39
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:47
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:55
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:63
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:71
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:79
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:87
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:95
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:103
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:111
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:119
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:127
msgid "Sign in using"
msgstr ""
#: .\cookbook\templates\space_overview.html:6
msgid "Overview"
msgstr ""
#: .\cookbook\templates\space_overview.html:13
msgid "Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:17
msgid ""
"Recipes, foods, shopping lists and more are organized in spaces of one or "
"more people."
msgstr ""
#: .\cookbook\templates\space_overview.html:18
msgid ""
"You can either be invited into an existing space or create your own one."
msgstr ""
#: .\cookbook\templates\space_overview.html:25
msgid "Your Spaces"
msgstr "Seus Espaços"
#: .\cookbook\templates\space_overview.html:53
msgid "Owner"
msgstr ""
#: .\cookbook\templates\space_overview.html:73
#: .\cookbook\templates\space_overview.html:83
msgid "Join Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:76
msgid "Join an existing space."
msgstr ""
#: .\cookbook\templates\space_overview.html:78
msgid ""
"To join an existing space either enter your invite token or click on the "
"invite link the space owner send you."
msgstr ""
#: .\cookbook\templates\space_overview.html:91
#: .\cookbook\templates\space_overview.html:100
msgid "Create Space"
msgstr "Criar Espaço"
#: .\cookbook\templates\space_overview.html:94
msgid "Create your own recipe space."
msgstr ""
#: .\cookbook\templates\space_overview.html:96
msgid "Start your own recipe space and invite other users to it."
msgstr ""
#: .\cookbook\templates\system.html:23
msgid "System"
msgstr "Sistema"
#: .\cookbook\templates\system.html:24
msgid ""
"\n"
" Tandoor Recipes is an open source free software application. It can "
"be found on\n"
" GitHub.\n"
" Changelogs can be found here.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:30
msgid "System Information"
msgstr ""
#: .\cookbook\templates\system.html:51
msgid ""
"\n"
" You need to execute version.py in your update "
"script to generate version information (done automatically in docker).\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:56
msgid "Plugins"
msgstr ""
#: .\cookbook\templates\system.html:67
msgid "Media Serving"
msgstr ""
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:123 .\cookbook\templates\system.html:134
msgid "Warning"
msgstr ""
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:125 .\cookbook\templates\system.html:134
msgid "Ok"
msgstr ""
#: .\cookbook\templates\system.html:70
msgid ""
"Serving media files directly using gunicorn/python is not recommend!\n"
" Please follow the steps described\n"
" here to update\n"
" your installation.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:76 .\cookbook\templates\system.html:91
#: .\cookbook\templates\system.html:104 .\cookbook\templates\system.html:115
#: .\cookbook\views\views.py:168
msgid "Everything is fine!"
msgstr ""
#: .\cookbook\templates\system.html:80
msgid "Secret Key"
msgstr ""
#: .\cookbook\templates\system.html:84
msgid ""
"\n"
" You do not have a SECRET_KEY configured in your "
".env file. Django defaulted to the\n"
" standard key\n"
" provided with the installation which is publicly know and "
"insecure! Please set\n"
" SECRET_KEY int the .env configuration "
"file.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:94
msgid "Debug Mode"
msgstr ""
#: .\cookbook\templates\system.html:98
msgid ""
"\n"
" This application is still running in debug mode. This is most "
"likely not needed. Turn of debug mode by\n"
" setting\n"
" DEBUG=0 int the .env configuration "
"file.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:107
msgid "Allowed Hosts"
msgstr ""
#: .\cookbook\templates\system.html:111
msgid ""
"\n"
" Your allowed hosts are configured to allow every host. This "
"might be ok in some setups but should be avoided. Please see the docs about "
"this.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:118
msgid "Database"
msgstr ""
#: .\cookbook\templates\system.html:121
msgid "Info"
msgstr ""
#: .\cookbook\templates\system.html:131 .\cookbook\templates\system.html:148
#, fuzzy
#| msgid "Use fractions"
msgid "Migrations"
msgstr "Usar frações"
#: .\cookbook\templates\system.html:137
msgid ""
"\n"
" Migrations should never fail!\n"
" Failed migrations will likely cause major parts of the app to "
"not function correctly.\n"
" If a migration fails make sure you are on the latest version and "
"if so please post the migration log and the overview below in a GitHub "
"issue.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "False"
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "True"
msgstr ""
#: .\cookbook\templates\system.html:268
msgid "Hide"
msgstr ""
#: .\cookbook\templates\system.html:271
#, fuzzy
#| msgid "Show Log"
msgid "Show"
msgstr "Mostrar Log"
#: .\cookbook\templates\test2.html:6
msgid "Export Recipes"
msgstr "Exportar Receitas"
#: .\cookbook\templates\test2.html:14 .\cookbook\templates\test2.html:20
msgid "Export"
msgstr "Exportar"
#: .\cookbook\views\api.py:198 .\cookbook\views\api.py:326
msgid "Parameter updated_at incorrectly formatted"
msgstr ""
#: .\cookbook\views\api.py:351 .\cookbook\views\api.py:484
#, python-brace-format
msgid "No {self.basename} with id {pk} exists"
msgstr ""
#: .\cookbook\views\api.py:355
msgid "Cannot merge with the same object!"
msgstr ""
#: .\cookbook\views\api.py:362
#, python-brace-format
msgid "No {self.basename} with id {target} exists"
msgstr ""
#: .\cookbook\views\api.py:367
msgid "Cannot merge with child object!"
msgstr ""
#: .\cookbook\views\api.py:405
#, python-brace-format
msgid "{source.name} was merged successfully with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:411
#, python-brace-format
msgid "An error occurred attempting to merge {source.name} with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:493
#, python-brace-format
msgid "{child.name} was moved successfully to the root."
msgstr ""
#: .\cookbook\views\api.py:496 .\cookbook\views\api.py:514
msgid "An error occurred attempting to move "
msgstr ""
#: .\cookbook\views\api.py:499
msgid "Cannot move an object to itself!"
msgstr ""
#: .\cookbook\views\api.py:505
#, python-brace-format
msgid "No {self.basename} with id {parent} exists"
msgstr ""
#: .\cookbook\views\api.py:511
#, python-brace-format
msgid "{child.name} was moved successfully to parent {parent.name}"
msgstr ""
#: .\cookbook\views\api.py:992
#, python-brace-format
msgid "{obj.name} was removed from the shopping list."
msgstr ""
#: .\cookbook\views\api.py:997 .\cookbook\views\api.py:1627
#, python-brace-format
msgid "{obj.name} was added to the shopping list."
msgstr ""
#: .\cookbook\views\api.py:1239
msgid "Filter meal plans from date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1241
msgid "Filter meal plans to date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1244
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1404
msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1406
msgid "Query string matched (fuzzy) against object name."
msgstr ""
#: .\cookbook\views\api.py:1442
msgid ""
"Query string matched (fuzzy) against recipe name. In the future also "
"fulltext search."
msgstr ""
#: .\cookbook\views\api.py:1444
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr ""
#: .\cookbook\views\api.py:1445
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr ""
#: .\cookbook\views\api.py:1446
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1447
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1448
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1450
msgid "ID of food a recipe should have. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1451
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr ""
#: .\cookbook\views\api.py:1452
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1453
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr ""
#: .\cookbook\views\api.py:1454
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1456
msgid "ID of book a recipe should be in. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1457
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr ""
#: .\cookbook\views\api.py:1458
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1459
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr ""
#: .\cookbook\views\api.py:1460
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1462
msgid "ID of unit a recipe should have."
msgstr ""
#: .\cookbook\views\api.py:1464
msgid "Exact rating of recipe"
msgstr ""
#: .\cookbook\views\api.py:1465
msgid "Rating a recipe should have or greater."
msgstr ""
#: .\cookbook\views\api.py:1466
msgid "Rating a recipe should have or smaller."
msgstr ""
#: .\cookbook\views\api.py:1468
msgid "Filter recipes cooked X times."
msgstr ""
#: .\cookbook\views\api.py:1469
msgid "Filter recipes cooked X times or more."
msgstr ""
#: .\cookbook\views\api.py:1470
msgid "Filter recipes cooked X times or less."
msgstr ""
#: .\cookbook\views\api.py:1472
msgid "Filter recipes created on the given date."
msgstr ""
#: .\cookbook\views\api.py:1473
msgid "Filter recipes created on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1474
msgid "Filter recipes created on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1476 .\cookbook\views\api.py:1477
#: .\cookbook\views\api.py:1478
msgid "Filter recipes updated on the given date."
msgstr ""
#: .\cookbook\views\api.py:1480
msgid "Filter recipes last cooked on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1481
msgid "Filter recipes last cooked on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1483 .\cookbook\views\api.py:1484
msgid "Filter recipes lasts viewed on the given date."
msgstr ""
#: .\cookbook\views\api.py:1486
msgid "Filter recipes for ones created by the given user ID"
msgstr ""
#: .\cookbook\views\api.py:1487
msgid "If only internal recipes should be returned. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1488
msgid "Returns the results in randomized order. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1490
msgid ""
"Determines the order of the results. Options are: score,-score,name,-name,"
"lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-"
"created_at,lastviewed,-lastviewed"
msgstr ""
#: .\cookbook\views\api.py:1492
msgid "Returns new results first in search results. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1493
msgid ""
"Returns the given number of recently viewed recipes before search results "
"(if given)"
msgstr ""
#: .\cookbook\views\api.py:1494
msgid "ID of a custom filter. Returns all recipes matched by that filter."
msgstr ""
#: .\cookbook\views\api.py:1495
msgid "Filter recipes that can be made with OnHand food. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1773
msgid ""
"Return the PropertyTypes matching the property category. Repeat for "
"multiple."
msgstr ""
#: .\cookbook\views\api.py:1804 .\cookbook\views\api.py:1860
msgid "Returns only entries associated with the given mealplan id"
msgstr ""
#: .\cookbook\views\api.py:1858
msgid ""
"Returns only elements updated after the given timestamp in ISO 8601 format."
msgstr ""
#: .\cookbook\views\api.py:2031
msgid ""
"Return the Automations matching the automation type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2048
msgid ""
"Text field to store data that gets carried over to the UserSpace created "
"from the InviteLink"
msgstr ""
#: .\cookbook\views\api.py:2049
msgid "Only return InviteLinks that have not been used yet."
msgstr ""
#: .\cookbook\views\api.py:2076
msgid "Return the CustomFilters matching the model type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2176
msgid "Nothing to do."
msgstr ""
#: .\cookbook\views\api.py:2222
msgid "Invalid Url"
msgstr ""
#: .\cookbook\views\api.py:2228
msgid "Connection Refused."
msgstr ""
#: .\cookbook\views\api.py:2232
msgid "Bad URL Schema."
msgstr ""
#: .\cookbook\views\api.py:2257
msgid "No usable data could be found."
msgstr ""
#: .\cookbook\views\api.py:2286 .\cookbook\views\api.py:2434
msgid "You must select an AI provider to perform your request."
msgstr ""
#: .\cookbook\views\api.py:2293 .\cookbook\views\api.py:2441
msgid ""
"You don't have any credits remaining to use AI or AI features are not "
"enabled for your space."
msgstr ""
#: .\cookbook\views\api.py:2499 .\cookbook\views\api.py:2667
msgid "File is above space limit"
msgstr ""
#: .\cookbook\views\api.py:2522 .\cookbook\views\api.py:2684
msgid "Importing is not implemented for this provider"
msgstr ""
#: .\cookbook\views\api.py:2548
msgid ""
"The PDF Exporter is not enabled on this instance as it is still in an "
"experimental state."
msgstr ""
#: .\cookbook\views\api.py:2842
msgid "This feature is not yet available in the hosted version of tandoor!"
msgstr ""
#: .\cookbook\views\api.py:2863
msgid "Sync successful!"
msgstr ""
#: .\cookbook\views\api.py:2866
msgid "Error synchronizing with Storage"
msgstr ""
#: .\cookbook\views\views.py:89
msgid "This feature is not available in the demo version!"
msgstr ""
#: .\cookbook\views\views.py:110
msgid ""
"You have successfully created your own recipe space. Start by adding some "
"recipes or invite other people to join you."
msgstr ""
#: .\cookbook\views\views.py:171
#, python-format
msgid "PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!"
msgstr ""
#: .\cookbook\views\views.py:174
#, python-format
msgid "You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended"
msgstr ""
#: .\cookbook\views\views.py:178
msgid "Unable to determine PostgreSQL version."
msgstr ""
#: .\cookbook\views\views.py:182
msgid ""
"This application is not running with a Postgres database backend. This is ok "
"but not recommended as some features only work with postgres databases."
msgstr ""
#: .\cookbook\views\views.py:296
msgid ""
"The setup page can only be used to create the first "
"user! If you have forgotten your superuser credentials "
"please consult the django documentation on how to reset passwords."
msgstr ""
#: .\cookbook\views\views.py:304
msgid "Passwords dont match!"
msgstr ""
#: .\cookbook\views\views.py:312
msgid "User has been created, please login!"
msgstr ""
#: .\cookbook\views\views.py:352
msgid ""
"Reporting share links is not enabled for this instance. Please notify the "
"page administrator to report problems."
msgstr ""
#: .\cookbook\views\views.py:357
msgid ""
"Recipe sharing link has been disabled! For additional information please "
"contact the page administrator."
msgstr ""
#: .\cookbook\views\views.py:383
msgid "Manage recipes, shopping list, meal plans and more."
msgstr ""
#: .\cookbook\views\views.py:397 .\cookbook\views\views.py:398
#, fuzzy
#| msgid "Meal-Plan"
msgid "Plan"
msgstr "Plano de refeição"
#: .\cookbook\views\views.py:399
msgid "View your meal Plan"
msgstr ""
#: .\cookbook\views\views.py:418
#, fuzzy
#| msgid "Shopping"
msgid "View your shopping lists"
msgstr "Compras"
#~ msgid ""
#~ "Both fields are optional. If none are given the username will be "
#~ "displayed instead"
#~ msgstr ""
#~ "Ambos os campos são opcionais. Se nenhum for preenchido o nome de "
#~ "utilizador será apresentado"
#~ msgid "Name"
#~ msgstr "Nome"
#~ msgid "Keywords"
#~ msgstr "Palavras-chave"
#~ msgid "Preparation time in minutes"
#~ msgstr "Tempo de preparação em minutos"
#~ msgid "Waiting time (cooking/baking) in minutes"
#~ msgstr "Tempo de espera (cozedura) em minutos"
#~ msgid "Path"
#~ msgstr "Caminho"
#~ msgid "Storage UID"
#~ msgstr "UID de armazenamento"
#~ msgid "Add your comment: "
#~ msgstr "Adicionar comentário: "
#~ msgid "Leave empty for dropbox and enter app password for nextcloud."
#~ msgstr ""
#~ "Deixar vazio para Dropbox e inserir palavra-passe de aplicação para "
#~ "Nextcloud."
#~ msgid "Leave empty for nextcloud and enter api token for dropbox."
#~ msgstr "Deixar vazio para Nextcloud e inserir token api para Dropbox."
#~ msgid ""
#~ "Leave empty for dropbox and enter only base url for nextcloud (/"
#~ "remote.php/webdav/ is added automatically)"
#~ msgstr ""
#~ "Deixar vazio para Dropbox e inserir apenas url base para Nextcloud "
#~ "(/remote.php/webdav/é adicionado automaticamente)"
#~ msgid ""
#~ "Long Lived Access Token for your HomeAssistant instance"
#~ msgstr ""
#~ "Token de longa duraçãopara a sua instância HomeAssistant"
#~ msgid "Something like http://homeassistant.local:8123/api"
#~ msgstr "Algo como http://homeassistant.local:8123/api"
#~ msgid "http://homeassistant.local:8123/api for example"
#~ msgstr "http://homeassistant.local:8123/api por exemplo"
#~ msgid "Storage"
#~ msgstr "Armazenamento"
#~ msgid "Active"
#~ msgstr "Ativo"
#~ msgid "Search String"
#~ msgstr "Procurar"
#~ msgid "File ID"
#~ msgstr "ID the ficheiro"
#~ msgid ""
#~ "Determines how fuzzy a search is if it uses trigram similarity matching "
#~ "(e.g. low values mean more typos are ignored)."
#~ msgstr ""
#~ "Determina o quão difusa uma pesquisa é se esta utilizar uma "
#~ "correspondência de semelhança de trigrama (valores mais baixos significam "
#~ "que mais erros são ignorados)."
#~ msgid ""
#~ "Select type method of search. Click here "
#~ "for full description of choices."
#~ msgstr ""
#~ "Selecionar o método de pesquisa. Uma descrição completa das opções pode "
#~ "ser encontrada aqui."
#~ msgid ""
#~ "Use fuzzy matching on units, keywords and ingredients when editing and "
#~ "importing recipes."
#~ msgstr ""
#~ "Utilizar correspondência difusa em unidades, palavras-chave e "
#~ "ingredientes ao editar e importar receitas."
#~ msgid ""
#~ "Fields to search ignoring accents. Selecting this option can improve or "
#~ "degrade search quality depending on language"
#~ msgstr ""
#~ "Campos de pesquisa que ignoram pontuação. Esta opção pode aumentar ou "
#~ "diminuir a qualidade de pesquisa dependendo da língua em uso"
#~ msgid ""
#~ "Fields to search for partial matches. (e.g. searching for 'Pie' will "
#~ "return 'pie' and 'piece' and 'soapie')"
#~ msgstr ""
#~ "Campos a pesquisar por correspondência. (Ex: pesquisar por 'mor' retorna "
#~ "'morango' e 'amora')"
#~ msgid ""
#~ "Fields to search for beginning of word matches. (e.g. searching for 'sa' "
#~ "will return 'salad' and 'sandwich')"
#~ msgstr ""
#~ "Campos a pesquisar para correspondências no início da palavra. (por "
#~ "exemplo, pesquisar por \"sa\" retornará \"salada\" e \"sanduíche\")"
#~ msgid ""
#~ "Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find "
#~ "'recipe'.) Note: this option will conflict with 'web' and 'raw' methods "
#~ "of search."
#~ msgstr ""
#~ "Campos para pesquisa aproximada. (por exemplo, pesquisar por \"recita\" "
#~ "encontrará \"receita\"). Nota: esta opção entra em conflito com os "
#~ "métodos de pesquisa \"web\" e \"raw\"."
#~ msgid "Search Method"
#~ msgstr "Método de Pesquisa"
#~ msgid "Fuzzy Lookups"
#~ msgstr "Pesquisas Aproximadas"
#~ msgid "Ignore Accent"
#~ msgstr "Ignorar pronúncia"
#~ msgid "Partial Match"
#~ msgstr "Correspondência parcial"
#~ msgid "Starts With"
#~ msgstr "Começa com"
#~ msgid "Fuzzy Search"
#~ msgstr "Pesquisa Fuzzy"
#~ msgid "Full Text"
#~ msgstr "Texto Completo"
#~ msgid "Delete"
#~ msgstr "Apagar"
#~ msgid "Settings"
#~ msgstr "Definições"
#~ msgid "Password"
#~ msgstr "Senha"
#~ msgid "Foods"
#~ msgstr "Alimentos"
#~ msgid "Units"
#~ msgstr "Unidades"
#~ msgid "Supermarket Category"
#~ msgstr "Categoria de Supermercado"
#~ msgid "Files"
#~ msgstr "Arquivos"
#~ msgid "Batch Edit"
#~ msgstr "Editor em massa"
#~ msgid "History"
#~ msgstr "Histórico"
#~ msgid "Ingredient Editor"
#~ msgstr "Editor de Ingrediente"
#~ msgid "Create"
#~ msgstr "Criar"
#~ msgid "Space Settings"
#~ msgstr "Configurar Espaço"
#~ msgid "Admin"
#~ msgstr "Administração"
#~ msgid "GitHub"
#~ msgstr "GitHub"
#~ msgid "API Browser"
#~ msgstr "Navegador de API"
#~ msgid "Batch edit Category"
#~ msgstr "Editar Categorias em massa"
#~ msgid "Batch edit Recipes"
#~ msgstr "Editar Receitas em massa"
#~ msgid "Add the specified keywords to all recipes containing a word"
#~ msgstr ""
#~ "Adicionar palavras-chave a todas as receitas que contenham uma palavra"
#~ msgid "Sync"
#~ msgstr "Sincronizar"
#~ msgid "Manage watched Folders"
#~ msgstr "Gerir pastas vigiadas"
#~ msgid "The path must be in the following format"
#~ msgstr "O caminho deve estar no seguinte formato"
#~ msgid "Save"
#~ msgstr "Gravar"
#~ msgid "Sync Now!"
#~ msgstr "Sincronizar Agora!"
#~ msgid "Show Recipes"
#~ msgstr "Mostrar Receitas"
#~ msgid "Show Log"
#~ msgstr "Mostrar Log"
#~ msgid "Importing Recipes"
#~ msgstr "A importar Receitas"
#~ msgid ""
#~ "This can take a few minutes, depending on the number of recipes in sync, "
#~ "please wait."
#~ msgstr ""
#~ "Este processo pode demorar alguns minutos, dependendo do número de "
#~ "receitas a ser importadas."
#~ msgid "Recipe Books"
#~ msgstr "Livros de Receitas"
#~ msgid "Import new Recipe"
#~ msgstr "Importar nova Receita"
#~ msgid "Edit Recipe"
#~ msgstr "Editar Receita"
#, python-format
#~ msgid "Are you sure you want to delete the %(title)s: %(object)s "
#~ msgstr "Tem certeza que deseja apagar %(title)s: %(object)s "
#~ msgid "Edit"
#~ msgstr "Editar"
#~ msgid "View"
#~ msgstr "Ver"
#~ msgid "Delete original file"
#~ msgstr "Apagar ficheiro original"
#~ msgid "List"
#~ msgstr "Listar"
#~ msgid "Filter"
#~ msgstr "Filtrar"
#~ msgid "Import all"
#~ msgstr "Importar tudo"
#~ msgid "New"
#~ msgstr "Novo"
#~ msgid "previous"
#~ msgstr "Anterior"
#~ msgid "next"
#~ msgstr "Seguinte"
#~ msgid "View Log"
#~ msgstr "Ver Registro"
#~ msgid "Cook Log"
#~ msgstr "Registro Cook"
#~ msgid "Import"
#~ msgstr "Importar"
#~ msgid "Security Warning"
#~ msgstr "Alerta de Segurança"
#~ msgid ""
#~ "\n"
#~ " The Password and Token field are stored as plain text"
#~ "b> inside the database.\n"
#~ " This is necessary because they are needed to make API requests, "
#~ "but it also increases the risk of\n"
#~ " someone stealing it.
\n"
#~ " To limit the possible damage tokens or accounts with limited "
#~ "access can be used.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Os campos de senha e Token são armazenados na base de "
#~ "dados como texto simples.\n"
#~ " Isto é necessário porque eles são usados para fazer pedidos à "
#~ "API, mas também aumenta o risco\n"
#~ " de alguém os roubar.
\n"
#~ " Para limitar os possíveis danos, tokens e contas com acesso "
#~ "limitado podem ser usadas.\n"
#~ " "
#, fuzzy
#~| msgid "Ingredient Editor"
#~ msgid "Property Editor"
#~ msgstr "Editor de Ingrediente"
#~ msgid "Comments"
#~ msgstr "Comentários"
#~ msgid "by"
#~ msgstr "por"
#~ msgid "Leave Space"
#~ msgstr "Sair do Espaço"
#~ msgid "Custom Filters"
#~ msgstr "Filtros Customizados"
#~ msgid "Steps"
#~ msgstr "Passos"
#~ msgid "Default unit"
#~ msgstr "Unidade predefinida"
#~ msgid "Use KJ"
#~ msgstr "Usar KJ"
#~ msgid "Theme"
#~ msgstr "Tema"
#~ msgid "Navbar color"
#~ msgstr "Cor de barra de navegação"
#~ msgid "Sticky navbar"
#~ msgstr "Prender barra de navegação"
#~ msgid "Default page"
#~ msgstr "Página predefinida"
#~ msgid "Plan sharing"
#~ msgstr "Partilha de planos"
#~ msgid "Ingredient decimal places"
#~ msgstr "Casas decimais de ingredientes"
#~ msgid "Shopping list auto sync period"
#~ msgstr "Período de sincronização automática"
#~ msgid "Left-handed mode"
#~ msgstr "Modo canhoto"
#~ msgid ""
#~ "Color of the top navigation bar. Not all colors work with all themes, "
#~ "just try them out!"
#~ msgstr ""
#~ "Cor da barra de navegação superior. Nem todas as cores funcionam com "
#~ "todos os temas, apenas experimente!"
#~ msgid ""
#~ "Default Unit to be used when inserting a new ingredient into a recipe."
#~ msgstr ""
#~ "Unidade defeito a ser usada quando um novo ingrediente for inserido."
#~ msgid ""
#~ "Enables support for fractions in ingredient amounts (e.g. convert "
#~ "decimals to fractions automatically)"
#~ msgstr ""
#~ "Utilizar frações para apresentar quantidades de ingredientes decimais "
#~ "(converter quantidades decimais para frações automáticamente)"
#~ msgid "Display nutritional energy amounts in joules instead of calories"
#~ msgstr ""
#~ "Mostrar quantidades de energia nutricional em joules em vez de calorias"
#~ msgid ""
#~ "Users with whom newly created meal plans should be shared by default."
#~ msgstr ""
#~ "Utilizadores com os quais novos planos de refeições devem ser partilhados "
#~ "por defeito."
#~ msgid "Users with whom to share shopping lists."
#~ msgstr ""
#~ "Utilizadores com os quais novas listas de compras serão partilhadas."
#~ msgid "Number of decimals to round ingredients."
#~ msgstr "Número de casas decimais para arredondamentos."
#~ msgid ""
#~ "If you want to be able to create and see comments underneath recipes."
#~ msgstr "Ativar a funcionalidade comentar receitas."
#~ msgid ""
#~ "Setting to 0 will disable auto sync. When viewing a shopping list the "
#~ "list is updated every set seconds to sync changes someone else might have "
#~ "made. Useful when shopping with multiple people but might use a little "
#~ "bit of mobile data. If lower than instance limit it is reset when saving."
#~ msgstr ""
#~ "Definir esta opção como 0 desativará a sincronização automática. Ao "
#~ "visualizar uma lista de compras, a lista é atualizada a cada período aqui "
#~ "definido para sincronizar as alterações que outro utilizador possa ter "
#~ "feito. Útil ao fazer compras com vários utilizadores, mas pode aumentar o "
#~ "uso de dados móveis."
#~ msgid "Makes the navbar stick to the top of the page."
#~ msgstr "Mantém a barra de navegação no topo da página."
#~ msgid "You must provide at least a recipe or a title."
#~ msgstr "É necessário inserir uma receita ou um título."
#~ msgid "You can list default users to share recipes with in the settings."
#~ msgstr ""
#~ "É possível escolher os utilizadores com quem partilhar receitas por "
#~ "defeitos nas definições."
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "É possível utilizar markdown para editar este campo. Documentação disponível aqui"
#~ msgid "Share Shopping List"
#~ msgstr "Compartilhar Lista de Compras"
#~ msgid "Fields on food that should be inherited by default."
#~ msgstr "Campos do alimento que devem ser herdados por padrão."
#~ msgid "Show recipe counts on search filters"
#~ msgstr "Mostrar receitas recentes na página de pesquisa"
#~ msgid "Ingredients"
#~ msgstr "Ingredientes"
#~ msgid "Show recent recipes"
#~ msgstr "Mostrar receitas recentes"
#~ msgid "Search style"
#~ msgstr "Estilo de pesquisa"
#~ msgid "Show recently viewed recipes on search page."
#~ msgstr "Mostrar receitas recentes na página de pesquisa."
#~ msgid "Small"
#~ msgstr "Pequeno"
#~ msgid "Large"
#~ msgstr "Grande"
#~ msgid "Edit Ingredients"
#~ msgstr "Editar ingredientes"
#~ msgid ""
#~ "\n"
#~ " The following form can be used if, accidentally, two (or more) "
#~ "units or ingredients where created that should be\n"
#~ " the same.\n"
#~ " It merges two units or ingredients and updates all recipes using "
#~ "them.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ "A seguinte formula pode ser usada quando duas (ou mais) unidades ou "
#~ "ingredientes foram criadas, mas deveriam ser iguais.\n"
#~ "Junta duas unidades ou ingredientes e atualiza todas as receitas que as "
#~ "estejam a usar. "
#~ msgid "Import Recipes"
#~ msgstr "Importar Receitas"
#~ msgid "Close"
#~ msgstr "Fechar"
#~ msgid "Open Recipe"
#~ msgstr "Abrir Receita"
#, fuzzy
#~| msgid "Settings"
#~ msgid "API-Settings"
#~ msgstr "Definições"
#, fuzzy
#~| msgid "Search String"
#~ msgid "Search-Settings"
#~ msgstr "Procurar"
#, fuzzy
#~| msgid "Search String"
#~ msgid "Shopping-Settings"
#~ msgstr "Procurar"
#, fuzzy
#~| msgid "Settings"
#~ msgid "Name Settings"
#~ msgstr "Definições"
#, fuzzy
#~| msgid "Settings"
#~ msgid "Account Settings"
#~ msgstr "Definições"
#, fuzzy
#~| msgid "Settings"
#~ msgid "Emails"
#~ msgstr "Definições"
#, fuzzy
#~| msgid "Settings"
#~ msgid "Shopping Settings"
#~ msgstr "Definições"
#~ msgid "Statistics"
#~ msgstr "Estatísticas"
#, fuzzy
#~| msgid "Admin"
#~ msgid "admin"
#~ msgstr "Administração"
#, fuzzy
#~| msgid "There are no recipes in this book yet."
#~ msgid "There are no members in your space yet!"
#~ msgstr "Ainda não há receitas neste livro."
#, fuzzy
#~| msgid "You are not logged in and therefore cannot view this page!"
#~ msgid ""
#~ "You are already member of a space and therefore cannot join this one."
#~ msgstr "Autenticação necessária para aceder a esta página!"
#~ msgid "Search Recipe"
#~ msgstr "Procure Receita"
#~ msgid "Select Unit"
#~ msgstr "Selecionar Unidade"
#~ msgid "Select"
#~ msgstr "Selecionar"
#~ msgid "Text"
#~ msgstr "Texto"
#, fuzzy
#~| msgid "File ID"
#~ msgid "File"
#~ msgstr "ID the ficheiro"
#, fuzzy
#~| msgid "View Recipe"
#~ msgid "Preview Recipe Data"
#~ msgstr "Ver Receita"
#, fuzzy
#~| msgid "Time"
#~ msgid "Prep Time"
#~ msgstr "Tempo"
#, fuzzy
#~| msgid "Time"
#~ msgid "Cook Time"
#~ msgstr "Tempo"
#, fuzzy
#~| msgid "Discovered Recipes"
#~ msgid "Discovered Attributes"
#~ msgstr "Descobrir Receitas"
#, fuzzy
#~| msgid "Show as header"
#~ msgid "Show Blank Field"
#~ msgstr "Mostrar como cabeçalho"
#, fuzzy
#~| msgid "Delete Step"
#~ msgid "Delete Text"
#~ msgstr "Apagar Passo"
#, fuzzy
#~| msgid "Delete Recipe"
#~ msgid "Delete image"
#~ msgstr "Apagar Receita"
#~ msgid "Note"
#~ msgstr "Nota"
#, fuzzy
#~| msgid "Keyword"
#~ msgid "Add Keyword"
#~ msgstr "Palavra-chave"
#~ msgid ""
#~ "The requested site does not provide any recognized data format to import "
#~ "the recipe from."
#~ msgstr "Esta página não contém uma receita que eu consiga entender."
#~ msgid "Time"
#~ msgstr "Tempo"
#~ msgid "Log Recipe Cooking"
#~ msgstr "Registro Receita Cooking"
#~ msgid "All fields are optional and can be left empty."
#~ msgstr "Todos os campos são opcionais e podem ser deixados vazios. "
#~ msgid "Rating"
#~ msgstr "Classificação "
#~ msgid "New unit that other gets replaced by."
#~ msgstr "Nova unidade substituta."
#~ msgid "Old Unit"
#~ msgstr "Unidade Anterior"
#~ msgid "Unit that should be replaced."
#~ msgstr "Unidade a ser alterada."
#~ msgid "New Food"
#~ msgstr "Novo Prato"
#~ msgid "New food that other gets replaced by."
#~ msgstr "Novo prato a ser alterado."
#~ msgid "Old Food"
#~ msgstr "Prato Anterior"
#~ msgid "New Entry"
#~ msgstr "Nova Entrada"
#~ msgid "Title"
#~ msgstr "Título"
#~ msgid "Note (optional)"
#~ msgstr "Nota (opcional)"
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "Pode usar markdown para formatar este campo. Veja a documentação "
#~ "aqui "
#~ msgid "Utensils"
#~ msgstr "Utensílios"
#~ msgid "Storage Data"
#~ msgstr "Dados de armazenamento"
#~ msgid "Configure Sync"
#~ msgstr "Configurar sincronização"
#~ msgid "Discovered Recipes"
#~ msgstr "Descobrir Receitas"
#~ msgid "Units & Ingredients"
#~ msgstr "Unidades e Ingredientes"
#~ msgid "New Book"
#~ msgstr "Novo Livro"
#~ msgid "There are no recipes in this book yet."
#~ msgstr "Ainda não há receitas neste livro."
#~ msgid "Waiting Time"
#~ msgstr "Tempo de Espera"
#~ msgid "Select Keywords"
#~ msgstr "Escolher Palavras-chave"
#~ msgid "Delete Step"
#~ msgstr "Apagar Passo"
#~ msgid "Step"
#~ msgstr "Passo"
#~ msgid "Show as header"
#~ msgstr "Mostrar como cabeçalho"
#~ msgid "Hide as header"
#~ msgstr "Esconder como cabeçalho"
#~ msgid "Move Up"
#~ msgstr "Mover para cima"
#~ msgid "Move Down"
#~ msgstr "Mover para baixo"
#~ msgid "Step Name"
#~ msgstr "Nome do passo"
#~ msgid "Step Type"
#~ msgstr "Tipo de passo"
#~ msgid "Step time in Minutes"
#~ msgstr "Tempo de passo em minutos"
#, fuzzy
#~| msgid "Select Unit"
#~ msgid "Select File"
#~ msgstr "Selecionar Unidade"
#, fuzzy
#~| msgid "Delete Recipe"
#~ msgid "Select Recipe"
#~ msgstr "Apagar Receita"
#~ msgid "Delete Ingredient"
#~ msgstr "Apagar Ingrediente"
#~ msgid "Make Header"
#~ msgstr "Adicionar Cabeçalho"
#~ msgid "Make Ingredient"
#~ msgstr "Adicionar Ingrediente"
#~ msgid "Disable Amount"
#~ msgstr "Desativar Quantidade"
#~ msgid "Enable Amount"
#~ msgstr "Ativar Quantidade"
#~ msgid "Save & View"
#~ msgstr "Gravar e Ver"
#~ msgid "Add Step"
#~ msgstr "Adicionar Passo"
#~ msgid "View Recipe"
#~ msgstr "Ver Receita"
#~ msgid "Delete Recipe"
#~ msgstr "Apagar Receita"
#~ msgid ""
#~ "A username is not required, if left blank the new user can choose one."
#~ msgstr ""
#~ "Um nome de utilizador não é obrigatório. Se deixado em branco o novo "
#~ "utilizador pode escolher o seu nome."
#~ msgid "Link"
#~ msgstr "Ligação"
#~ msgid "Logout"
#~ msgstr "Sair"
#~ msgid "Website Import"
#~ msgstr "Importar Website"
#~ msgid ""
#~ "Include - [ ] in list for easier usage in markdown based "
#~ "documents."
#~ msgstr ""
#~ "Incluir - [ ] na lista para facilitar o uso de markdown "
================================================
FILE: cookbook/locale/pt_BR/LC_MESSAGES/django.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-22 20:15+0200\n"
"PO-Revision-Date: 2025-05-18 10:58+0000\n"
"Last-Translator: querty \n"
"Language-Team: Portuguese (Brazil) \n"
"Language: pt_BR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 5.8.4\n"
#: .\cookbook\forms.py:50
msgid "Default"
msgstr "Padrão"
#: .\cookbook\forms.py:77
msgid ""
"To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything."
msgstr ""
"Para evitar repetições, receitas com o mesmo nome de receitas já existentes "
"são ignoradas. Marque esta caixa para importar tudo."
#: .\cookbook\forms.py:108
msgid "Maximum number of users for this space reached."
msgstr "Número máximo de usuários para este espaço atingido."
#: .\cookbook\forms.py:114
msgid "Email address already taken!"
msgstr "Endereço de email já utilizado!"
#: .\cookbook\forms.py:121
msgid ""
"An email address is not required but if present the invite link will be sent "
"to the user."
msgstr ""
"Um endereço de email não é obrigatório mas se fornecido será enviada uma "
"mensagem ao usuário."
#: .\cookbook\forms.py:133
msgid "Name already taken."
msgstr "Nome já existente."
#: .\cookbook\forms.py:144 .\cookbook\forms.py:158
msgid "Accept Terms and Privacy"
msgstr "Termos de Aceite e Privacidade"
#: .\cookbook\helper\AllAuthCustomAdapter.py:41
msgid ""
"In order to prevent spam, the requested email was not send. Please wait a "
"few minutes and try again."
msgstr ""
"Para prevenir spam, o email requisitado não pode ser enviado. Por favor, "
"aguarde alguns minutos e tente novamente."
#: .\cookbook\helper\permission_helper.py:165
#: .\cookbook\helper\permission_helper.py:186 .\cookbook\views\views.py:138
msgid "You are not logged in and therefore cannot view this page!"
msgstr "Autenticação necessária para acessar esta página!"
#: .\cookbook\helper\permission_helper.py:168
#: .\cookbook\helper\permission_helper.py:173
#: .\cookbook\helper\permission_helper.py:198
#: .\cookbook\helper\permission_helper.py:265
#: .\cookbook\helper\permission_helper.py:279
#: .\cookbook\helper\permission_helper.py:290
#: .\cookbook\helper\permission_helper.py:301
#: .\cookbook\helper\permission_helper.py:317
#: .\cookbook\helper\permission_helper.py:343
#: .\cookbook\helper\permission_helper.py:359
msgid "You do not have the required permissions to view this page!"
msgstr "Sem permissões para acessar esta página!"
#: .\cookbook\helper\permission_helper.py:191
#: .\cookbook\helper\permission_helper.py:214
#: .\cookbook\helper\permission_helper.py:236
#: .\cookbook\helper\permission_helper.py:251
msgid "You cannot interact with this object as it is not owned by you!"
msgstr ""
"Você não pode interagir com esse objeto, pois ele não é sua propriedade!"
#: .\cookbook\helper\permission_helper.py:420
msgid "You have reached the maximum number of recipes for your space."
msgstr "Você alcançou o número limite de receitas para o seu Espaço."
#: .\cookbook\helper\permission_helper.py:432
msgid "You have more users than allowed in your space."
msgstr "Você tem mais usuários que o permitido em seu Espaço."
#: .\cookbook\helper\recipe_url_import.py:319
msgid "reverse rotation"
msgstr "rotação reversa"
#: .\cookbook\helper\recipe_url_import.py:320
msgid "careful rotation"
msgstr "rotação cuidadosa"
#: .\cookbook\helper\recipe_url_import.py:321
msgid "knead"
msgstr "amassar"
#: .\cookbook\helper\recipe_url_import.py:322
msgid "thicken"
msgstr "engrossar"
#: .\cookbook\helper\recipe_url_import.py:323
msgid "warm up"
msgstr "pré aquecer"
#: .\cookbook\helper\recipe_url_import.py:324
msgid "ferment"
msgstr "fermentar"
#: .\cookbook\helper\recipe_url_import.py:325
msgid "slow cook"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:326
msgid "egg boiler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:327
msgid "kettle"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:328
msgid "blend"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:329
msgid "pre-clean"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:330
msgid "high temperature"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:331
msgid "rice cooker"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:332
msgid "caramelize"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:333
msgid "peeler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:334
msgid "slicer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:335
msgid "grater"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:336
msgid "spiralizer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:337
msgid "sous-vide"
msgstr "sous-vide"
#: .\cookbook\helper\shopping_helper.py:150
msgid "You must supply a servings size"
msgstr "Você precisa informar um tamanho de porção"
#: .\cookbook\helper\template_helper.py:97
#: .\cookbook\helper\template_helper.py:99
#: .\cookbook\helper\template_helper.py:101
msgid "Could not parse template code."
msgstr "Não foi possível analisar o código do modelo."
#: .\cookbook\integration\copymethat.py:44
#: .\cookbook\integration\melarecipes.py:37
msgid "Favorite"
msgstr "Favorito"
#: .\cookbook\integration\copymethat.py:50
msgid "I made this"
msgstr "Eu fiz isso"
#: .\cookbook\integration\integration.py:238
msgid ""
"Importer expected a .zip file. Did you choose the correct importer type for "
"your data ?"
msgstr ""
"O importador esperava um arquivo .zip. Você escolheu o tipo de importador "
"correto para os seus dados?"
#: .\cookbook\integration\integration.py:241
msgid ""
"An unexpected error occurred during the import. Please make sure you have "
"uploaded a valid file."
msgstr ""
"Um erro inesperado aconteceu durante a importação. Por favor, verifique se "
"enviou um arquivo válido."
#: .\cookbook\integration\integration.py:246
msgid "The following recipes were ignored because they already existed:"
msgstr "A seguintes receitas foram ignoradas, pois já existem:"
#: .\cookbook\integration\integration.py:250
#, python-format
msgid "Imported %s recipes."
msgstr "%s receitas importadas."
#: .\cookbook\integration\mealie1.py:210
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "Calories"
msgstr "Calorias"
#: .\cookbook\integration\mealie1.py:211
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
msgid "Carbohydrates"
msgstr "Carboidratos"
#: .\cookbook\integration\mealie1.py:212
msgid "Cholesterol"
msgstr ""
#: .\cookbook\integration\mealie1.py:213
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
msgid "Fat"
msgstr "Gordura"
#: .\cookbook\integration\mealie1.py:214
msgid "Fiber"
msgstr ""
#: .\cookbook\integration\mealie1.py:215
#, fuzzy
#| msgid "Proteins"
msgid "Protein"
msgstr "Proteínas"
#: .\cookbook\integration\mealie1.py:216
msgid "Saturated Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:217
msgid "Sodium"
msgstr ""
#: .\cookbook\integration\mealie1.py:218
msgid "Sugar"
msgstr ""
#: .\cookbook\integration\mealie1.py:219
msgid "Trans Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:220
msgid "Unsaturated Fat"
msgstr ""
#: .\cookbook\integration\openeats.py:28
msgid "Recipe source:"
msgstr "Fonte da receita:"
#: .\cookbook\integration\paprika.py:49
msgid "Notes"
msgstr "Notas"
#: .\cookbook\integration\paprika.py:52
msgid "Nutritional Information"
msgstr "Informação Nutricional"
#: .\cookbook\integration\paprika.py:56
msgid "Source"
msgstr "Fonte"
#: .\cookbook\integration\recettetek.py:55
#: .\cookbook\integration\recipekeeper.py:70
msgid "Imported from"
msgstr "Importado de"
#: .\cookbook\integration\saffron.py:23
msgid "Servings"
msgstr "Porções"
#: .\cookbook\integration\saffron.py:25
msgid "Waiting time"
msgstr "Tempo de espera"
#: .\cookbook\integration\saffron.py:27
msgid "Preparation Time"
msgstr "Tempo de Preparação"
#: .\cookbook\integration\saffron.py:29 .\cookbook\templates\index.html:6
msgid "Cookbook"
msgstr "Livro de Receita"
#: .\cookbook\integration\saffron.py:31
msgid "Section"
msgstr "Seção"
#: .\cookbook\management\commands\fix_duplicate_properties.py:15
msgid "Fixes foods with "
msgstr "Corrige comidas com "
#: .\cookbook\management\commands\rebuildindex.py:14
msgid "Rebuilds full text search index on Recipe"
msgstr "Reconstrói o índice de busca de texto por completo da Receita"
#: .\cookbook\management\commands\rebuildindex.py:18
msgid "Only Postgresql databases use full text search, no index to rebuild"
msgstr ""
"Apenas bancos de dados Postgresql utilizam busca por texto completo, não há "
"índice para reconstruir"
#: .\cookbook\management\commands\rebuildindex.py:29
msgid "Recipe index rebuild complete."
msgstr "Reconstrução de índice de receita concluído."
#: .\cookbook\management\commands\rebuildindex.py:31
msgid "Recipe index rebuild failed."
msgstr "Reconstrução de índice de Receita falhou."
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
msgid "Breakfast"
msgstr "Café da Manhã"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:19
msgid "Lunch"
msgstr "Almoço"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:24
msgid "Dinner"
msgstr "Jantar"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:29 .\cookbook\models.py:971
msgid "Other"
msgstr "Outro"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "g"
msgstr "g"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "Proteins"
msgstr "Proteínas"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "kcal"
msgstr "Kcal"
#: .\cookbook\models.py:325
msgid ""
"Maximum file storage for space in MB. 0 for unlimited, -1 to disable file "
"upload."
msgstr ""
"Limite máximo de armazenamento de arquivos em MB. 0 para ilimitado, −1 para "
"impedir o envio de arquivos."
#: .\cookbook\models.py:513
msgid "Search"
msgstr "Pesquisa"
#: .\cookbook\models.py:514
msgid "Meal-Plan"
msgstr "Plano de Refeição"
#: .\cookbook\models.py:515
msgid "Books"
msgstr "Livros"
#: .\cookbook\models.py:516 .\cookbook\views\views.py:416
#: .\cookbook\views\views.py:417
msgid "Shopping"
msgstr "Compras"
#: .\cookbook\models.py:967
msgid "Nutrition"
msgstr "Informações nutricionais"
#: .\cookbook\models.py:968
msgid "Allergen"
msgstr "Alérgenos"
#: .\cookbook\models.py:969
msgid "Price"
msgstr "Preço"
#: .\cookbook\models.py:970
msgid "Goal"
msgstr "Meta"
#: .\cookbook\models.py:1467 .\cookbook\templates\search_info.html:28
msgid "Simple"
msgstr "Simples"
#: .\cookbook\models.py:1468 .\cookbook\templates\search_info.html:33
msgid "Phrase"
msgstr "Frase"
#: .\cookbook\models.py:1469 .\cookbook\templates\search_info.html:38
msgid "Web"
msgstr "Web"
#: .\cookbook\models.py:1470 .\cookbook\templates\search_info.html:47
msgid "Raw"
msgstr "Cru"
#: .\cookbook\models.py:1525
msgid "Food Alias"
msgstr "Apelido do Alimento"
#: .\cookbook\models.py:1526
msgid "Unit Alias"
msgstr "Apelido da Unidade"
#: .\cookbook\models.py:1527
msgid "Keyword Alias"
msgstr "Apelido da Palavra-chave"
#: .\cookbook\models.py:1528
msgid "Description Replace"
msgstr "Substituir Descrição"
#: .\cookbook\models.py:1529
msgid "Instruction Replace"
msgstr "Substituir Instruções"
#: .\cookbook\models.py:1530
msgid "Never Unit"
msgstr "Nunca usar como unidade"
#: .\cookbook\models.py:1531
msgid "Transpose Words"
msgstr "Transpor palavras"
#: .\cookbook\models.py:1532
msgid "Food Replace"
msgstr "Substituir comida"
#: .\cookbook\models.py:1533
msgid "Unit Replace"
msgstr "Substituir unidade"
#: .\cookbook\models.py:1534
msgid "Name Replace"
msgstr "Substituir nome"
#: .\cookbook\models.py:1564
msgid "Recipe"
msgstr "Receita"
#: .\cookbook\models.py:1565
msgid "Food"
msgstr "Comida"
#: .\cookbook\models.py:1566
msgid "Keyword"
msgstr "Palavra-chave"
#: .\cookbook\serializer.py:262
msgid "File uploads are not enabled for this Space."
msgstr "Envio de arquivos não são permitidos nesse Espaço."
#: .\cookbook\serializer.py:273
msgid "You have reached your file upload limit."
msgstr "Você alcançou o limite do envio de arquivos."
#: .\cookbook\serializer.py:281
msgid "The given file type is not allowed."
msgstr ""
#: .\cookbook\serializer.py:421 .\cookbook\views\views.py:94
msgid ""
"You have the reached the maximum amount of spaces that can be owned by you."
msgstr "Você atingiu o máximo de Espaços que podem ser controlados por você."
#: .\cookbook\serializer.py:434
msgid "Space Name must be unique."
msgstr ""
#: .\cookbook\serializer.py:469
msgid "Cannot modify Space owner permission."
msgstr "Não é possível modificar as permissões do dono do Espaço."
#: .\cookbook\serializer.py:1596
msgid "Hello"
msgstr "Olá"
#: .\cookbook\serializer.py:1596
msgid "You have been invited by "
msgstr "Você foi convidado por "
#: .\cookbook\serializer.py:1598
msgid " to join their Tandoor Recipes space "
msgstr " para se juntar ao Espaço "
#: .\cookbook\serializer.py:1600
msgid "Click the following link to activate your account: "
msgstr "Clique no seguinte link para ativar a sua conta: "
#: .\cookbook\serializer.py:1602
msgid ""
"If the link does not work use the following code to manually join the space: "
msgstr ""
"Se o link não funcionar use o seguinte código para manualmente entrar no "
"Espaço: "
#: .\cookbook\serializer.py:1604
msgid "The invitation is valid until "
msgstr "O convite é válido até "
#: .\cookbook\serializer.py:1606
msgid ""
"Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub "
msgstr ""
"Tandoor Recipes é um gerenciador de receitas de código aberto. Visite-nos no "
"GitHub "
#: .\cookbook\serializer.py:1609
msgid "Tandoor Recipes Invite"
msgstr "Convite para Tandoor Recipes"
#: .\cookbook\serializer.py:1813
msgid "Existing shopping list to update"
msgstr "Lista de compras pré-existente que será atualizada"
#: .\cookbook\serializer.py:1815
msgid ""
"List of ingredient IDs from the recipe to add, if not provided all "
"ingredients will be added."
msgstr ""
"Lista de IDs de ingredientes da receita para adicionar, se não for "
"preenchido todos os ingredientes serão adicionados."
#: .\cookbook\serializer.py:1817
msgid ""
"Providing a list_recipe ID and servings of 0 will delete that shopping list."
msgstr ""
"Preenchendo o list_recipe ID e porções com 0, deletará essa lista de compra."
#: .\cookbook\serializer.py:1826
msgid "Amount of food to add to the shopping list"
msgstr "Quantidade da comida para adicionar na lista de compras"
#: .\cookbook\serializer.py:1828
msgid "ID of unit to use for the shopping list"
msgstr "ID de uma unidade para usar na lista de compras"
#: .\cookbook\serializer.py:1830
msgid "When set to true will delete all food from active shopping lists."
msgstr "Quando ativo, deletará todas as comidas da lista de compra."
#: .\cookbook\templates\404.html:5
msgid "404 Error"
msgstr "Erro 404"
#: .\cookbook\templates\404.html:18
msgid "The page you are looking for could not be found."
msgstr "Página não encontrada."
#: .\cookbook\templates\404.html:33
msgid "Take me Home"
msgstr "Leve-me pra Home Page"
#: .\cookbook\templates\404.html:35
msgid "Report a Bug"
msgstr "Reportar um Bug"
#: .\cookbook\templates\account\email.html:6
#: .\cookbook\templates\account\email.html:10
msgid "E-mail Addresses"
msgstr "Endereço de E-mail"
#: .\cookbook\templates\account\email.html:12
msgid "The following e-mail addresses are associated with your account:"
msgstr "os e-mails seguintes estão associados com sua conta:"
#: .\cookbook\templates\account\email.html:29
msgid "Verified"
msgstr "Verificado"
#: .\cookbook\templates\account\email.html:31
msgid "Unverified"
msgstr "Não verificado"
#: .\cookbook\templates\account\email.html:33
msgid "Primary"
msgstr "Primário"
#: .\cookbook\templates\account\email.html:40
msgid "Make Primary"
msgstr "Tornar Primário"
#: .\cookbook\templates\account\email.html:42
msgid "Re-send Verification"
msgstr "Reenviar Verificação"
#: .\cookbook\templates\account\email.html:43
#: .\cookbook\templates\socialaccount\connections.html:36
msgid "Remove"
msgstr "Remover"
#: .\cookbook\templates\account\email.html:51
msgid "Warning:"
msgstr "Alerta:"
#: .\cookbook\templates\account\email.html:51
msgid ""
"You currently do not have any e-mail address set up. You should really add "
"an e-mail address so you can receive notifications, reset your password, etc."
msgstr ""
"Você atualmente não possui nenhum e-mail cadastrado. Você deveria adicionar "
"um e-mail para poder receber notificações, ressetar seu password, etc."
#: .\cookbook\templates\account\email.html:57
msgid "Add E-mail Address"
msgstr "Incluir Endereço de E-mail"
#: .\cookbook\templates\account\email.html:62
msgid "Add E-mail"
msgstr "Incluir E-mail"
#: .\cookbook\templates\account\email.html:72
msgid "Do you really want to remove the selected e-mail address?"
msgstr "Você tem certeza que deseja remover o e-mail selecionado?"
#: .\cookbook\templates\account\email_confirm.html:6
#: .\cookbook\templates\account\email_confirm.html:10
msgid "Confirm E-mail Address"
msgstr "Confirmar Endereço de E-mail"
#: .\cookbook\templates\account\email_confirm.html:16
#, python-format
msgid ""
"Please confirm that\n"
" %(email)s is an e-mail address "
"for user %(user_display)s\n"
" ."
msgstr ""
"Por favor, confirme que\n"
" %(email)s é um endereço de e-"
"mail do usuário %(user_display)s\n"
" ."
#: .\cookbook\templates\account\email_confirm.html:22
msgid "Confirm"
msgstr "Confirmar"
#: .\cookbook\templates\account\email_confirm.html:29
#, python-format
msgid ""
"This e-mail confirmation link expired or is invalid. Please\n"
" issue a new e-mail confirmation "
"request."
msgstr ""
"Link de confirmação de e-mail expirado ou inválido. Por favor\n"
"enviar um novo pedido de confirmação de e-mail."
#: .\cookbook\templates\account\login.html:8
#: .\cookbook\templates\openid\login.html:8
msgid "Login"
msgstr "Login"
#: .\cookbook\templates\account\login.html:15
#: .\cookbook\templates\account\login.html:31
#: .\cookbook\templates\account\password_reset.html:39
#: .\cookbook\templates\account\password_reset_done.html:31
#: .\cookbook\templates\account\signup.html:68
#: .\cookbook\templates\account\signup_closed.html:15
#: .\cookbook\templates\openid\login.html:15
#: .\cookbook\templates\openid\login.html:26
#: .\cookbook\templates\socialaccount\authentication_error.html:15
msgid "Sign In"
msgstr "Entrar"
#: .\cookbook\templates\account\login.html:34
#: .\cookbook\templates\account\password_reset.html:41
#: .\cookbook\templates\account\password_reset_done.html:33
#: .\cookbook\templates\socialaccount\signup.html:8
#: .\cookbook\templates\socialaccount\signup.html:56
msgid "Sign Up"
msgstr "Cadastrar"
#: .\cookbook\templates\account\login.html:38
msgid "Lost your password?"
msgstr "Esqueceu sua senha?"
#: .\cookbook\templates\account\login.html:39
#: .\cookbook\templates\account\password_reset.html:29
msgid "Reset My Password"
msgstr "Resetar Minha Senha"
#: .\cookbook\templates\account\login.html:50
msgid "Social Login"
msgstr "Entre com Rede Social"
#: .\cookbook\templates\account\login.html:51
msgid "You can use any of the following providers to sign in."
msgstr "Você pode utilizar qualquer dos seguintes providers para logar-se."
#: .\cookbook\templates\account\logout.html:5
#: .\cookbook\templates\account\logout.html:9
#: .\cookbook\templates\account\logout.html:18
msgid "Sign Out"
msgstr "Sair (Log Out)"
#: .\cookbook\templates\account\logout.html:11
msgid "Are you sure you want to sign out?"
msgstr "Você tem certeza que deseja sair?"
#: .\cookbook\templates\account\password_change.html:6
#: .\cookbook\templates\account\password_change.html:10
#: .\cookbook\templates\account\password_change.html:15
#: .\cookbook\templates\account\password_reset_from_key.html:7
#: .\cookbook\templates\account\password_reset_from_key.html:13
#: .\cookbook\templates\account\password_reset_from_key_done.html:7
#: .\cookbook\templates\account\password_reset_from_key_done.html:13
msgid "Change Password"
msgstr "Alterar Senha"
#: .\cookbook\templates\account\password_change.html:16
msgid "Forgot Password?"
msgstr "Esqueceu a Senha?"
#: .\cookbook\templates\account\password_reset.html:7
#: .\cookbook\templates\account\password_reset.html:13
#: .\cookbook\templates\account\password_reset_done.html:7
#: .\cookbook\templates\account\password_reset_done.html:18
msgid "Password Reset"
msgstr "Resetar Senha"
#: .\cookbook\templates\account\password_reset.html:24
msgid ""
"Forgotten your password? Enter your e-mail address below, and we'll send you "
"an e-mail allowing you to reset it."
msgstr ""
"Perdeu sua senha? Entre com seu e-mail abaixo e enviaremos instruções de "
"como reseta-la."
#: .\cookbook\templates\account\password_reset.html:32
msgid "Password reset is disabled on this instance."
msgstr "Recuperação de senha está desabilitada nessa instância."
#: .\cookbook\templates\account\password_reset_done.html:25
msgid ""
"We have sent you an e-mail. Please contact us if you do not receive it "
"within a few minutes."
msgstr ""
"Nós enviamos um e-mail. Por favor, entre em contato se não o receber dentro "
"de alguns minutos."
#: .\cookbook\templates\account\password_reset_from_key.html:13
msgid "Bad Token"
msgstr "Token Inválido"
#: .\cookbook\templates\account\password_reset_from_key.html:25
#, python-format
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used.\n"
" Please request a new "
"password reset."
msgstr ""
"Link para redefinição de senha inválido, possivelmente por já ter sido "
"usado.\n"
" Por favor, solicite para redefinir senha novamente."
#: .\cookbook\templates\account\password_reset_from_key.html:33
msgid "change password"
msgstr "alterar senha"
#: .\cookbook\templates\account\password_reset_from_key.html:36
#: .\cookbook\templates\account\password_reset_from_key_done.html:19
msgid "Your password is now changed."
msgstr "Sua senha foi alterada."
#: .\cookbook\templates\account\password_set.html:6
#: .\cookbook\templates\account\password_set.html:10
#: .\cookbook\templates\account\password_set.html:15
msgid "Set Password"
msgstr "Definir Senha"
#: .\cookbook\templates\account\signup.html:5
msgid "Register"
msgstr "Registrar"
#: .\cookbook\templates\account\signup.html:11
msgid "Create an Account"
msgstr "Criar uma Conta"
#: .\cookbook\templates\account\signup.html:41
msgid "I accept the follwoing"
msgstr "Eu aceito os seguintes"
#: .\cookbook\templates\account\signup.html:44
#: .\cookbook\templates\socialaccount\signup.html:35
msgid "Terms and Conditions"
msgstr "Termos e Condições"
#: .\cookbook\templates\account\signup.html:47
#: .\cookbook\templates\socialaccount\signup.html:38
msgid "and"
msgstr "e"
#: .\cookbook\templates\account\signup.html:51
#: .\cookbook\templates\socialaccount\signup.html:42
msgid "Privacy Policy"
msgstr "Política de Privacidade"
#: .\cookbook\templates\account\signup.html:64
msgid "Create User"
msgstr "Criar Usuário"
#: .\cookbook\templates\account\signup.html:68
msgid "Already have an account?"
msgstr "Já possui uma conta?"
#: .\cookbook\templates\account\signup_closed.html:5
#: .\cookbook\templates\account\signup_closed.html:11
msgid "Sign Up Closed"
msgstr "O cadastro está desabilitado"
#: .\cookbook\templates\account\signup_closed.html:13
msgid "We are sorry, but the sign up is currently closed."
msgstr "Desculpe, login não está disponível no momento."
#: .\cookbook\templates\frontend\tandoor.html:15
#, fuzzy
#| msgid "Tandoor Recipes Invite"
msgid "Tandoor Recipe Manager"
msgstr "Convite para Tandoor Recipes"
#: .\cookbook\templates\index.html:28
msgid "Search recipe ..."
msgstr "Pesquisar receita ..."
#: .\cookbook\templates\index.html:43
msgid "New Recipe"
msgstr "Nova Receita"
#: .\cookbook\templates\index.html:46
msgid "Import Recipe"
msgstr "Importar Receita"
#: .\cookbook\templates\index.html:52
msgid "Advanced Search"
msgstr "Pesquisa Avançada"
#: .\cookbook\templates\index.html:56
msgid "Reset Search"
msgstr "Reinicializar Pesquisa"
#: .\cookbook\templates\index.html:84
msgid "Last viewed"
msgstr "Último visualizado"
#: .\cookbook\templates\index.html:86
msgid "Recipes"
msgstr "Receitas"
#: .\cookbook\templates\index.html:93
msgid "Log in to view recipes"
msgstr "Faça Login para ver as receitas"
#: .\cookbook\templates\markdown_info.html:5
#: .\cookbook\templates\markdown_info.html:13
msgid "Markdown Info"
msgstr "Informação Markdown"
#: .\cookbook\templates\markdown_info.html:14
msgid ""
"\n"
" Markdown is lightweight markup language that can be used to format "
"plain text easily.\n"
" This site uses the Python Markdown library to\n"
" convert your text into nice looking HTML. Its full markdown "
"documentation can be found\n"
" here.\n"
" An incomplete but most likely sufficient documentation can be found "
"below.\n"
" "
msgstr ""
"\n"
" Markdown é uma linguagem leve que pode ser facilmente utilziada para "
"formatar texto puro.\n"
" Este site utiliza a biblioteca Python Markdown para \n"
" converter o seu texto em um belo HTML. A documentação completa do "
"markdown pode ser encontrada em\n"
" here.\n"
" Uma documentação incompleta, mas provavelmente suficiente, pode ser "
"encontrada abaixo..\n"
" "
#: .\cookbook\templates\markdown_info.html:25
msgid "Headers"
msgstr "Cabeçalhos"
#: .\cookbook\templates\markdown_info.html:54
msgid "Formatting"
msgstr "Formatando"
#: .\cookbook\templates\markdown_info.html:56
#: .\cookbook\templates\markdown_info.html:72
msgid "Line breaks are inserted by adding two spaces after the end of a line"
msgstr ""
"As quebras de linha são inseridas adicionando dois espaços no final da linha"
#: .\cookbook\templates\markdown_info.html:57
#: .\cookbook\templates\markdown_info.html:73
msgid "or by leaving a blank line in between."
msgstr "ou deixando uma linha em branco no meio."
#: .\cookbook\templates\markdown_info.html:59
#: .\cookbook\templates\markdown_info.html:74
msgid "This text is bold"
msgstr "Este texto está em negrito"
#: .\cookbook\templates\markdown_info.html:60
#: .\cookbook\templates\markdown_info.html:75
msgid "This text is italic"
msgstr "Este texto está em itálico"
#: .\cookbook\templates\markdown_info.html:61
#: .\cookbook\templates\markdown_info.html:77
msgid "Blockquotes are also possible"
msgstr "Citação em bloco também é permitido"
#: .\cookbook\templates\markdown_info.html:84
msgid "Lists"
msgstr "Listas"
#: .\cookbook\templates\markdown_info.html:85
msgid ""
"Lists can ordered or unordered. It is important to leave a blank line "
"before the list!"
msgstr ""
"As listas podem ser ordenadas ou não ordenadas. É importante deixar uma "
"linha em branco antes da lista!"
#: .\cookbook\templates\markdown_info.html:87
#: .\cookbook\templates\markdown_info.html:108
msgid "Ordered List"
msgstr "Lista Ordenada"
#: .\cookbook\templates\markdown_info.html:89
#: .\cookbook\templates\markdown_info.html:90
#: .\cookbook\templates\markdown_info.html:91
#: .\cookbook\templates\markdown_info.html:110
#: .\cookbook\templates\markdown_info.html:111
#: .\cookbook\templates\markdown_info.html:112
msgid "unordered list item"
msgstr "item da lista não ordenada"
#: .\cookbook\templates\markdown_info.html:93
#: .\cookbook\templates\markdown_info.html:114
msgid "Unordered List"
msgstr "Lista Não Ordenada"
#: .\cookbook\templates\markdown_info.html:95
#: .\cookbook\templates\markdown_info.html:96
#: .\cookbook\templates\markdown_info.html:97
#: .\cookbook\templates\markdown_info.html:116
#: .\cookbook\templates\markdown_info.html:117
#: .\cookbook\templates\markdown_info.html:118
msgid "ordered list item"
msgstr "item da lista ordenada"
#: .\cookbook\templates\markdown_info.html:125
msgid "Images & Links"
msgstr "Imagens e Links"
#: .\cookbook\templates\markdown_info.html:126
msgid ""
"Links can be formatted with Markdown. This application also allows to paste "
"links directly into markdown fields without any formatting."
msgstr ""
"Os links podem ser formatadso com a linguaem Markdown. Essa aplicação também "
"aceita links direto em Markdown, sem formatação."
#: .\cookbook\templates\markdown_info.html:132
#: .\cookbook\templates\markdown_info.html:145
msgid "This will become an image"
msgstr "Isso se tornará uma imagem"
#: .\cookbook\templates\markdown_info.html:152
msgid "Tables"
msgstr "Tabelas"
#: .\cookbook\templates\markdown_info.html:153
msgid ""
"Markdown tables are hard to create by hand. It is recommended to use a table "
"editor like this one."
msgstr ""
"Tabelas Markdown são difíceis de serem criadas manualmente. É recomendado "
"utilizar um editor de tabelas tipo este."
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:171
#: .\cookbook\templates\markdown_info.html:177
msgid "Table"
msgstr "Tabela"
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:172
msgid "Header"
msgstr "Cabeçalho"
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:178
msgid "Cell"
msgstr "Célula"
#: .\cookbook\templates\no_groups_info.html:5
#: .\cookbook\templates\no_groups_info.html:12
msgid "No Permissions"
msgstr "Sem permissão"
#: .\cookbook\templates\no_groups_info.html:17
msgid "You do not have any groups and therefor cannot use this application."
msgstr "Você não tem nenhum grupo e, portanto, não pode usar este aplicativo."
#: .\cookbook\templates\no_groups_info.html:18
#: .\cookbook\templates\no_perm_info.html:15
msgid "Please contact your administrator."
msgstr "Por favor contate seu administrador."
#: .\cookbook\templates\no_perm_info.html:5
#: .\cookbook\templates\no_perm_info.html:12
msgid "No Permission"
msgstr "Sem Permissão"
#: .\cookbook\templates\no_perm_info.html:15
msgid ""
"You do not have the required permissions to view this page or perform this "
"action."
msgstr ""
"Você não tem as permissões necessárias para visualizar esta página ou "
"executar esta ação."
#: .\cookbook\templates\offline.html:5
msgid "Offline"
msgstr "Offline"
#: .\cookbook\templates\openid\login.html:27
#: .\cookbook\templates\socialaccount\authentication_error.html:27
msgid "Back"
msgstr "Voltar"
#: .\cookbook\templates\rest_framework\api.html:5
msgid "Recipe Home"
msgstr "Início - Receitas"
#: .\cookbook\templates\rest_framework\api.html:11
msgid "API Documentation"
msgstr "Documentação da API"
#: .\cookbook\templates\search_info.html:5
#: .\cookbook\templates\search_info.html:9
msgid "Search Settings"
msgstr "Configurações da Pesquisa"
#: .\cookbook\templates\search_info.html:10
msgid ""
"\n"
" Creating the best search experience is complicated and weighs "
"heavily on your personal configuration. \n"
" Changing any of the search settings can have significant impact on "
"the speed and quality of the results.\n"
" Search Methods, Trigrams and Full Text Search configurations are "
"only available if you are using Postgres for your database.\n"
" "
msgstr ""
"\n"
" Criar a melhor experiência de busca é complidado e depende muito de "
"sua configuração pessoal. \n"
" Alterar qualquer uma das configurações de busca pode impactar "
"significativamente a qualidade e rapidez dos resultados.\n"
" Configurações de Métodos de busca, Trigrams e de Busca Textual só "
"estão disponíveis de você estiver utilizado um banco de dados Postgres.\n"
" "
#: .\cookbook\templates\search_info.html:19
msgid "Search Methods"
msgstr "Métodos de Pesquisa"
#: .\cookbook\templates\search_info.html:23
msgid ""
" \n"
" Full text searches attempt to normalize the words provided to "
"match common variants. For example: 'forked', 'forking', 'forks' will all "
"normalize to 'fork'.\n"
" There are several methods available, described below, that will "
"control how the search behavior should react when multiple words are "
"searched.\n"
" Full technical details on how these operate can be viewed on Postgresql's website.\n"
" "
msgstr ""
" \n"
" Pesquisas textuais tentam normalizar as palacras fornecidas para "
"coincidir com variantes comuns. Por exemplo: 'forked', 'forking', 'forks' "
"irão normalizar para 'fork'.\n"
" Há vários métodos disponívies, descritos abaixo, que irão "
"controlar como a busca deve se comportar quando múltiplas palavras forem "
"buscadas.\n"
" Detalhes técnicos completos sobre como essas busas operam podem "
"ser visualizados no site do PostgreSQL.\n"
" "
#: .\cookbook\templates\search_info.html:29
msgid ""
" \n"
" Simple searches ignore punctuation and common words such as "
"'the', 'a', 'and'. And will treat separate words as required.\n"
" Searching for 'apple or flour' will return any recipe that "
"includes both 'apple' and 'flour' anywhere in the fields that have been "
"selected for a full text search.\n"
" "
msgstr ""
" \n"
" Buscas simples ignoram a pontuação e palavras comuns como 'e', "
"'ou', 'a'... E vão tratar palavas separadas conforme solicitado.\n"
" Buscar por 'maçã ou farinha' irá retornar qualquer receita que "
"inclua 'maçã' e 'farinha' em qualquer um dos campos selecionados para busca "
"textual completa.\n"
" "
#: .\cookbook\templates\search_info.html:34
msgid ""
" \n"
" Phrase searches ignore punctuation, but will search for all of "
"the words in the exact order provided.\n"
" Searching for 'apple or flour' will only return a recipe that "
"includes the exact phrase 'apple or flour' in any of the fields that have "
"been selected for a full text search.\n"
" "
msgstr ""
" \n"
" Buscas por frases ignoram pontuação, mas irão buscar por todas "
"as palavras na ordem exata em que foram fornecidas.\n"
" Buscar por 'maçã ou farinha' irá retornar uma receita que "
"contenha exatamente a frase 'maça ou farinha' em qualquer um dos campos "
"selecionados para busca textual completa.\n"
" "
#: .\cookbook\templates\search_info.html:39
msgid ""
" \n"
" Web searches simulate functionality found on many web search "
"sites supporting special syntax.\n"
" Placing quotes around several words will convert those words "
"into a phrase.\n"
" 'or' is recognized as searching for the word (or phrase) "
"immediately before 'or' OR the word (or phrase) directly after.\n"
" '-' is recognized as searching for recipes that do not include "
"the word (or phrase) that comes immediately after. \n"
" For example searching for 'apple pie' or cherry -butter will "
"return any recipe that includes the phrase 'apple pie' or the word "
"'cherry' \n"
" in any field included in the full text search but exclude any "
"recipe that has the word 'butter' in any field included.\n"
" "
msgstr ""
" \n"
" Busca Web simula a funcionalidade encontrada em muitos sites de "
"buscas suportando sintaxe especial.\n"
" Colocar aspas ao redor de várias palacras vai convertê-las em "
"uma frase.\n"
" 'or' realiza a busca por palavras (ou frases) imediatamente "
"antes 'ou' depois do termo OR.\n"
" '-' realiza a busca por receitas que NÃO incluam a palavra (ou "
"frase) imediatamente após o simbolo. \n"
" Por exemplo, buscar por 'torta de maçã' ou cereja -manteiga vai "
"retornar qualquer receita que inclua a frase 'torta de maçã' ou a palacra "
"'cereja' \n"
" em qualquer campo incluído na busca textual mas vai excluir "
"receitas que incluam a palavra 'manteiga' nos campos selecionados.\n"
" "
#: .\cookbook\templates\search_info.html:48
msgid ""
" \n"
" Raw search is similar to Web except will take puncuation "
"operators such as '|', '&' and '()'\n"
" "
msgstr ""
" \n"
" Busca crua é similar à busca Web mas irá aceitar operadores como "
"'|', '&' e '()'\n"
" "
#: .\cookbook\templates\search_info.html:59
msgid ""
" \n"
" Another approach to searching that also requires Postgresql is "
"fuzzy search or trigram similarity. A trigram is a group of three "
"consecutive characters.\n"
" For example searching for 'apple' will create x trigrams 'app', "
"'ppl', 'ple' and will create a score of how closely words match the "
"generated trigrams.\n"
" One benefit of searching trigams is that a search for 'sandwich' "
"will find misspelled words such as 'sandwhich' that would be missed by other "
"methods.\n"
" "
msgstr ""
" \n"
" Outra abordagem de busca que também exige o Postgresql é a busca "
"'difusa' (fuzzy) ou similaridade trigram. Um trigram é um grupo de três "
"caracteres consecutivos.\n"
" Por exemplo, buscar por 'maçã' irá criar os trigrams 'maç' e "
"'aça', e irá criar uma pontuação do qual coincidentes são as palavras que "
"possuem esses trigams gerados.\n"
" Uma vantagem da busca por trigrams é que a busca por 'sanduiche' "
"vai encontrar palavras grafadas incorretamente como 'sanduixe' que seriam "
"ignoradas por outros métodos.\n"
" "
#: .\cookbook\templates\search_info.html:69
msgid "Search Fields"
msgstr "Campos de Pesquisa"
#: .\cookbook\templates\search_info.html:73
msgid ""
" \n"
" Unaccent is a special case in that it enables searching a field "
"'unaccented' for each search style attempting to ignore accented values. \n"
" For example when you enable unaccent for 'Name' any search "
"(starts with, contains, trigram) will attempt the search ignoring accented "
"characters.\n"
" \n"
" For the other options, you can enable search on any or all "
"fields and they will be combined together with an assumed 'OR'.\n"
" For example enabling 'Name' for Starts With, 'Name' and "
"'Description' for Partial Match and 'Ingredients' and 'Keywords' for Full "
"Search\n"
" and searching for 'apple' will generate a search that will "
"return recipes that have:\n"
" - A recipe name that starts with 'apple'\n"
" - OR a recipe name that contains 'apple'\n"
" - OR a recipe description that contains 'apple'\n"
" - OR a recipe that will have a full text search match ('apple' "
"or 'apples') in ingredients\n"
" - OR a recipe that will have a full text search match in "
"Keywords\n"
"\n"
" Combining too many fields in too many types of search can have a "
"negative impact on performance, create duplicate results or return "
"unexpected results.\n"
" For example, enabling fuzzy search or partial matches will "
"interfere with web search methods. \n"
" Searching for 'apple -pie' with fuzzy search and full text "
"search will return the recipe Apple Pie. Though it is not included in the "
"full text results, it does match the trigram results.\n"
" "
msgstr ""
" \n"
" Unaccent é um caso especial que habilita a busca por um campo "
"'não acentuado' para cada estilo de busca tentando ignorar valores "
"acentuados. \n"
" Por exemplo, quando você habilita unaccent para 'Nome' em "
"qualquer busca (começa com, contém, trigram) o sistema irá realizar a busca "
"ignorando os caracteres acentuados.\n"
" \n"
" Para outras opções, você pode habilitar a busca em qualquer "
"campo ou em todos os campos e eles serão combinados presumindo a clausula "
"'OR' (OU).\n"
" Por exemplo, habilitar 'Nome' para Começa Com, 'Nome' e "
"'Descrição' para Correspondência Parcial e 'Ingredientes' e 'Palavras-chave' "
"para Busca Completa\n"
" e buscando por 'maçã' irá resultar numa busca por receitas que "
"contenham:\n"
" - Uma receita que o nome começa com 'maçã''\n"
" - OU uma receita que o nome contenha 'maçã'\n"
" - OU uma receita que a crescrição contenha 'maçã'\n"
" - OU uma receita que contenha uma busca completa que coincida "
"com ('maçã' ou 'maçãs') nos ingredientes\n"
" - OU uma receita que contenha uma coincidência nas Palavras-"
"chave\n"
"\n"
" Combinar muitos campos ou muitos tipos de buscas pode impactar "
"negativamente a performance, criar resultados duplicados ou retornar "
"resultados inexperados.\n"
" Por exemplo, habilitar a busca 'difusa' (fuzzy) ou coincidência "
"parcial vai interferir com métodos de busca web. \n"
" Buscar por 'torta -maçã' com busca 'difusa' (fuzzy) e busca "
"textual completa irá retornar a receita 'Torta de Maçã'. Apesar de não "
"estar incluída nos resultados da busca textual completa, há coincidência nos "
"resultados dos trigrams.\n"
" "
#: .\cookbook\templates\search_info.html:95
msgid "Search Index"
msgstr "Índice de Pesquisa"
#: .\cookbook\templates\search_info.html:99
msgid ""
" \n"
" Trigram search and Full Text Search both rely on database "
"indexes to perform effectively. \n"
" You can rebuild the indexes on all fields in the Admin page for "
"Recipes and selecting all recipes and running 'rebuild index for selected "
"recipes'\n"
" You can also rebuild indexes at the command line by executing "
"the management command 'python manage.py rebuildindex'\n"
" "
msgstr ""
" \n"
" Buysca Trigram e Busca textual ambas depende de bom desenpenho "
"do indexadores da base de dados. \n"
" Você pode reconstruir os indexadores em todos os campos através "
"da página de Admin em Receitas, selecionando todas as receitas e rodando "
"'reconstruir index para as receitas selecionadas'\n"
" Você também pode reconstruir os indexadores através da linha de "
"comando executando o commando 'python manage.py rebuildindex'\n"
" "
#: .\cookbook\templates\setup.html:6
msgid "Cookbook Setup"
msgstr "Configuração do Livro de Receitas"
#: .\cookbook\templates\setup.html:14
msgid "Setup"
msgstr "Configuração"
#: .\cookbook\templates\setup.html:15
msgid ""
"To start using this application you must first create a superuser account."
msgstr ""
"Para começar a utilizar esse aplicativo você precisa criar uma conta de "
"super usuário primeiro."
#: .\cookbook\templates\setup.html:20
msgid "Create Superuser account"
msgstr "Criar conta Superusuário"
#: .\cookbook\templates\socialaccount\authentication_error.html:7
#: .\cookbook\templates\socialaccount\authentication_error.html:23
msgid "Social Network Login Failure"
msgstr "Falha na conexão com a Rede Social"
#: .\cookbook\templates\socialaccount\authentication_error.html:25
msgid ""
"An error occurred while attempting to login via your social network account."
msgstr ""
"Um erro ocorreu ao tentar a conexão através da sua conta de rede social."
#: .\cookbook\templates\socialaccount\connections.html:4
#: .\cookbook\templates\socialaccount\connections.html:7
msgid "Account Connections"
msgstr "Conexões de Conta"
#: .\cookbook\templates\socialaccount\connections.html:10
msgid ""
"You can sign in to your account using any of the following third party\n"
" accounts:"
msgstr ""
"Você pode acessar sua conta utilizado qualquer uma das \n"
"contas terceiras:"
#: .\cookbook\templates\socialaccount\connections.html:44
msgid ""
"You currently have no social network accounts connected to this account."
msgstr ""
"Atualmente você não tem nenhuma conta de rede social conectada com essa "
"conta."
#: .\cookbook\templates\socialaccount\connections.html:47
msgid "Add a 3rd Party Account"
msgstr "Incluir uma conta de terceiros"
#: .\cookbook\templates\socialaccount\login.html:5
#: .\cookbook\templates\socialaccount\signup.html:5
msgid "Signup"
msgstr "Inscrever-se"
#: .\cookbook\templates\socialaccount\login.html:9
#, python-format
msgid "Connect %(provider)s"
msgstr "Conectar %(provider)s"
#: .\cookbook\templates\socialaccount\login.html:11
#, python-format
msgid "You are about to connect a new third party account from %(provider)s."
msgstr ""
"Você está prestes a conectar uma nova conta de terceiros do %(provider)s."
#: .\cookbook\templates\socialaccount\login.html:13
#, python-format
msgid "Sign In Via %(provider)s"
msgstr "Acessar com %(provider)s"
#: .\cookbook\templates\socialaccount\login.html:15
#, python-format
msgid "You are about to sign in using a third party account from %(provider)s."
msgstr "Você está acessando utilizando uma conta de terceiros de %(provider)s."
#: .\cookbook\templates\socialaccount\login.html:20
msgid "Continue"
msgstr "Seguir"
#: .\cookbook\templates\socialaccount\signup.html:10
#, python-format
msgid ""
"You are about to use your\n"
" %(provider_name)s account to login to\n"
" %(site_name)s. As a final step, please complete the following form:"
msgstr ""
"Você está preste a utilizar sua conta\n"
" %(provider_name)s para acessar o site\n"
" %(site_name)s. Como etapa final, por favor, complete o formulário a "
"seguir:"
#: .\cookbook\templates\socialaccount\signup.html:32
#, fuzzy
#| msgid "I accept the follwoing"
msgid "I accept the following"
msgstr "Eu aceito os seguintes"
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:23
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:31
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:39
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:47
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:55
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:63
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:71
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:79
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:87
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:95
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:103
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:111
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:119
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:127
msgid "Sign in using"
msgstr "Acessar utilizando"
#: .\cookbook\templates\space_overview.html:6
msgid "Overview"
msgstr "Resumo"
#: .\cookbook\templates\space_overview.html:13
msgid "Space"
msgstr "Espaço"
#: .\cookbook\templates\space_overview.html:17
msgid ""
"Recipes, foods, shopping lists and more are organized in spaces of one or "
"more people."
msgstr ""
"Receitas, comidas, listas de compras e mais são organizadas em espaços com "
"uma ou mais pessoas."
#: .\cookbook\templates\space_overview.html:18
msgid ""
"You can either be invited into an existing space or create your own one."
msgstr ""
"Você pode ser convidado para um Espaço existente ou criar ou seu próprio "
"Espaço."
#: .\cookbook\templates\space_overview.html:25
msgid "Your Spaces"
msgstr "Seu Espaço"
#: .\cookbook\templates\space_overview.html:53
msgid "Owner"
msgstr "Proprietário"
#: .\cookbook\templates\space_overview.html:73
#: .\cookbook\templates\space_overview.html:83
msgid "Join Space"
msgstr "Entrar no Espaço"
#: .\cookbook\templates\space_overview.html:76
msgid "Join an existing space."
msgstr "Entrar em um espaço existente."
#: .\cookbook\templates\space_overview.html:78
msgid ""
"To join an existing space either enter your invite token or click on the "
"invite link the space owner send you."
msgstr ""
"Para se juntar a um espaço existente insira seu token de convite ou clique "
"no link de convite que o proprietário enviou."
#: .\cookbook\templates\space_overview.html:91
#: .\cookbook\templates\space_overview.html:100
msgid "Create Space"
msgstr "Criar Espaço"
#: .\cookbook\templates\space_overview.html:94
msgid "Create your own recipe space."
msgstr "Criar seu próprio espaço de receita."
#: .\cookbook\templates\space_overview.html:96
msgid "Start your own recipe space and invite other users to it."
msgstr "Inicie seu próprio espaço de receitas e convide outros usuários."
#: .\cookbook\templates\system.html:23
msgid "System"
msgstr "Sistema"
#: .\cookbook\templates\system.html:24
#, fuzzy
#| msgid ""
#| "\n"
#| " Django Recipes is an open source free software application. It "
#| "can be found on\n"
#| " GitHub.\n"
#| " Changelogs can be found here.\n"
#| " "
msgid ""
"\n"
" Tandoor Recipes is an open source free software application. It can "
"be found on\n"
" GitHub.\n"
" Changelogs can be found here.\n"
" "
msgstr ""
"\n"
" O Django Recipes é uma aplicação gratuita e de código aberto. Pode "
"ser encontada no link\n"
" GitHub.\n"
" Os changelogs pode ser encontrados no link here.\n"
" "
#: .\cookbook\templates\system.html:30
msgid "System Information"
msgstr "Informações do Sistema"
#: .\cookbook\templates\system.html:51
msgid ""
"\n"
" You need to execute version.py in your update "
"script to generate version information (done automatically in docker).\n"
" "
msgstr ""
"\n"
" Você precisa executar o version.py no seu script de "
"atualização para gerar a a informação de versão (executado automaticamente "
"no docker).\n"
" "
#: .\cookbook\templates\system.html:56
msgid "Plugins"
msgstr ""
#: .\cookbook\templates\system.html:67
msgid "Media Serving"
msgstr "Servidor Multimídia"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:123 .\cookbook\templates\system.html:134
msgid "Warning"
msgstr "Alerta"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:125 .\cookbook\templates\system.html:134
msgid "Ok"
msgstr "Ok"
#: .\cookbook\templates\system.html:70
msgid ""
"Serving media files directly using gunicorn/python is not recommend!\n"
" Please follow the steps described\n"
" here to update\n"
" your installation.\n"
" "
msgstr ""
"Servir arquivos de mídia diretamente utilizando gunicorn/python não é "
"recomendado!\n"
" Por favor, siga os passos descritos\n"
" aqui para atualizar\n"
" sua instalação.\n"
" "
#: .\cookbook\templates\system.html:76 .\cookbook\templates\system.html:91
#: .\cookbook\templates\system.html:104 .\cookbook\templates\system.html:115
#: .\cookbook\views\views.py:168
msgid "Everything is fine!"
msgstr "Tudo está bem!"
#: .\cookbook\templates\system.html:80
msgid "Secret Key"
msgstr "Chave Secreta"
#: .\cookbook\templates\system.html:84
msgid ""
"\n"
" You do not have a SECRET_KEY configured in your "
".env file. Django defaulted to the\n"
" standard key\n"
" provided with the installation which is publicly know and "
"insecure! Please set\n"
" SECRET_KEY int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" Você não tem a SECRET_KEY confiurada em seu arquivo "
".env. O Django assumiu a \n"
" standard key\n"
" provida com a instalação, que é pública e insegura! Por favor, "
"defina a \n"
" SECRET_KEY int no arquivo .env de "
"configuração.\n"
" "
#: .\cookbook\templates\system.html:94
msgid "Debug Mode"
msgstr "Modo Debug"
#: .\cookbook\templates\system.html:98
msgid ""
"\n"
" This application is still running in debug mode. This is most "
"likely not needed. Turn of debug mode by\n"
" setting\n"
" DEBUG=0 int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" Essa aplicação ainda está rodando em modo debug. Isso "
"provavelmente não é necessário. Desative o modo debug\n"
" definindo\n"
" DEBUG=0 int no arquivo .env de "
"configuração.\n"
" "
#: .\cookbook\templates\system.html:107
msgid "Allowed Hosts"
msgstr "Hosts Autorizados"
#: .\cookbook\templates\system.html:111
msgid ""
"\n"
" Your allowed hosts are configured to allow every host. This "
"might be ok in some setups but should be avoided. Please see the docs about "
"this.\n"
" "
msgstr ""
"\n"
" Você configurou seus hosts permitidos para permitir qualquer "
"host. Isso pode ser adquado em algumas instalações mas deve ser evitado. Por "
"favor, veja a documentação sobre isso..\n"
" "
#: .\cookbook\templates\system.html:118
msgid "Database"
msgstr "Banco de Dados"
#: .\cookbook\templates\system.html:121
msgid "Info"
msgstr "Informação"
#: .\cookbook\templates\system.html:131 .\cookbook\templates\system.html:148
msgid "Migrations"
msgstr "MIgrações"
#: .\cookbook\templates\system.html:137
msgid ""
"\n"
" Migrations should never fail!\n"
" Failed migrations will likely cause major parts of the app to "
"not function correctly.\n"
" If a migration fails make sure you are on the latest version and "
"if so please post the migration log and the overview below in a GitHub "
"issue.\n"
" "
msgstr ""
"\n"
" Migrações nunca deve falhar!\n"
" Migrações com falha muito provavelmente impedirão grande parte "
"do app de funcionar corretamente.\n"
" Se uma migração falhar, confirme que você está na última versão "
"e, se for o caso, poste o log de migração e o overview abaixo em uma issue "
"do GitHub.\n"
" "
#: .\cookbook\templates\system.html:238
msgid "False"
msgstr "Falso"
#: .\cookbook\templates\system.html:238
msgid "True"
msgstr "Verdadeiro"
#: .\cookbook\templates\system.html:268
msgid "Hide"
msgstr "Ocultar"
#: .\cookbook\templates\system.html:271
msgid "Show"
msgstr "Exibir"
#: .\cookbook\templates\test2.html:6
msgid "Export Recipes"
msgstr "Exportar Receitas"
#: .\cookbook\templates\test2.html:14 .\cookbook\templates\test2.html:20
msgid "Export"
msgstr "Exportar"
#: .\cookbook\views\api.py:198 .\cookbook\views\api.py:326
msgid "Parameter updated_at incorrectly formatted"
msgstr "Parâmetro updated_at formatado incorretamente"
#: .\cookbook\views\api.py:351 .\cookbook\views\api.py:484
#, python-brace-format
msgid "No {self.basename} with id {pk} exists"
msgstr "Nenhum {self.basename} com o id {pk} existe"
#: .\cookbook\views\api.py:355
msgid "Cannot merge with the same object!"
msgstr "Não é possível mescar com o mesmo objeto!"
#: .\cookbook\views\api.py:362
#, python-brace-format
msgid "No {self.basename} with id {target} exists"
msgstr "Nenhum {self.basename} com o id {target} existe"
#: .\cookbook\views\api.py:367
msgid "Cannot merge with child object!"
msgstr "Impossível mesclar com o objeto filho!"
#: .\cookbook\views\api.py:405
#, python-brace-format
msgid "{source.name} was merged successfully with {target.name}"
msgstr "{source.name} foi mesclado com sucesso com {target.name}"
#: .\cookbook\views\api.py:411
#, python-brace-format
msgid "An error occurred attempting to merge {source.name} with {target.name}"
msgstr "Um erro ocorreu ao tentar mesclar {source.name} com {target.name}"
#: .\cookbook\views\api.py:493
#, python-brace-format
msgid "{child.name} was moved successfully to the root."
msgstr "{child.name} foi movido para o root com sucesso."
#: .\cookbook\views\api.py:496 .\cookbook\views\api.py:514
msgid "An error occurred attempting to move "
msgstr "Um erro ocorreu ao tentar mover "
#: .\cookbook\views\api.py:499
msgid "Cannot move an object to itself!"
msgstr "Impossível mover um objeto para sí mesmo!"
#: .\cookbook\views\api.py:505
#, python-brace-format
msgid "No {self.basename} with id {parent} exists"
msgstr "Nenhum {self.basename} com o id {parent} existe"
#: .\cookbook\views\api.py:511
#, python-brace-format
msgid "{child.name} was moved successfully to parent {parent.name}"
msgstr "{child.name} foi movido com sucesso para o pai {parent.name}"
#: .\cookbook\views\api.py:992
#, python-brace-format
msgid "{obj.name} was removed from the shopping list."
msgstr "{obj.name} foi removido da lista de compras."
#: .\cookbook\views\api.py:997 .\cookbook\views\api.py:1627
#, python-brace-format
msgid "{obj.name} was added to the shopping list."
msgstr "{obj.name} foi incluído na lista de compras."
#: .\cookbook\views\api.py:1239
#, fuzzy
#| msgid "Filter meal plans from date (inclusive) in the format of YYYY-MM-DD."
msgid "Filter meal plans from date (inclusive)."
msgstr ""
"Filtrar planos de refeição a partir da data (incluindo) no formato AAAA-MM-"
"DD."
#: .\cookbook\views\api.py:1241
#, fuzzy
#| msgid "Filter meal plans to date (inclusive) in the format of YYYY-MM-DD."
msgid "Filter meal plans to date (inclusive)."
msgstr ""
"Filtrar planos de refeição até a data (incluindo) no formato AAAA-MM-DD."
#: .\cookbook\views\api.py:1244
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
msgstr ""
"Filtrar os planos rFilter meal plans with MealType ID. For multiple repeat "
"parameter."
#: .\cookbook\views\api.py:1404
msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgstr "ID de uma receita da qual o Passo seja parte. Para múltiplos."
#: .\cookbook\views\api.py:1406
msgid "Query string matched (fuzzy) against object name."
msgstr "Query string correspondeu (fuzzy) com o nome de um objeto."
#: .\cookbook\views\api.py:1442
msgid ""
"Query string matched (fuzzy) against recipe name. In the future also "
"fulltext search."
msgstr ""
"Query string correspondeu (fuzzy) com o nome de uma receita. No futuro busca "
"de texto completo."
#: .\cookbook\views\api.py:1444
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr ""
"ID de uma palavra-chave que uma receita deva conter. Repita o parâmetro para "
"múltiplos. Equivalente a keywords_or"
#: .\cookbook\views\api.py:1445
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr ""
"IDs de Palavras-chave, repita para múltiplos. Retorna receitas com qualquer "
"uma das palavras-chave"
#: .\cookbook\views\api.py:1446
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr ""
"IDs de Palavras-chave, repita para múltiplos. Retorna receitas com todas as "
"palavras-chave."
#: .\cookbook\views\api.py:1447
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr ""
"IDs de Palavras-chave, repita para múltiplos. Exclui receitas com qualquer "
"uma das palavras-chave."
#: .\cookbook\views\api.py:1448
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr ""
"IDs de Palavras-chave, repita para múltiplos. Exclui receitas com todas as "
"palavras-chave."
#: .\cookbook\views\api.py:1450
msgid "ID of food a recipe should have. For multiple repeat parameter."
msgstr ""
"ID de um alimentos que a receita deva conter. Repita o parâmetro para "
"múltiplos."
#: .\cookbook\views\api.py:1451
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr ""
"IDs de alimentos, repita para múltiplos. Retorna receitas com qualquer um "
"dos alimentos"
#: .\cookbook\views\api.py:1452
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr ""
"IDs de alimentos, repita para múltiplos. Retorna receitas com todos os "
"alimentos."
#: .\cookbook\views\api.py:1453
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr ""
"IDs de alimentos, repita para múltiplos. Exclui receitas com qualquer um dos "
"alimentos."
#: .\cookbook\views\api.py:1454
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr ""
"IDs de alimentos, repita para múltiplos. Exclui receitas com todos os "
"alimentos."
#: .\cookbook\views\api.py:1456
msgid "ID of book a recipe should be in. For multiple repeat parameter."
msgstr ""
"ID de um livro no qual uma receita deva estar. Repita o parâmetro para "
"múltiplos."
#: .\cookbook\views\api.py:1457
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr ""
"IDs de Livros, repita para múltiplos. Retorna receitas em qualquer um dos "
"livros"
#: .\cookbook\views\api.py:1458
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr ""
"IDs de Livros, repita para múltiplos. Retorna receitas em todos os livros."
#: .\cookbook\views\api.py:1459
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr ""
"IDs de Livros, repita para múltiplos. Exclui receitas em qualquer um dos "
"livros."
#: .\cookbook\views\api.py:1460
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr ""
"IDs de Livros, repita para múltiplos. Exclui receitas em todos os livros."
#: .\cookbook\views\api.py:1462
msgid "ID of unit a recipe should have."
msgstr "ID de uma unidade que uma receita deva conter."
#: .\cookbook\views\api.py:1464
msgid "Exact rating of recipe"
msgstr ""
#: .\cookbook\views\api.py:1465
#, fuzzy
#| msgid "ID of unit a recipe should have."
msgid "Rating a recipe should have or greater."
msgstr "ID de uma unidade que uma receita deva conter."
#: .\cookbook\views\api.py:1466
#, fuzzy
#| msgid "ID of unit a recipe should have."
msgid "Rating a recipe should have or smaller."
msgstr "ID de uma unidade que uma receita deva conter."
#: .\cookbook\views\api.py:1468
msgid "Filter recipes cooked X times."
msgstr ""
#: .\cookbook\views\api.py:1469
#, fuzzy
#| msgid ""
#| "Filter recipes cooked X times or more. Negative values returns cooked "
#| "less than X times"
msgid "Filter recipes cooked X times or more."
msgstr ""
"Filtra receitas cozidas X vezes ou mais. Valores negativos retornam "
"receitas cozidas menos de X vezes"
#: .\cookbook\views\api.py:1470
msgid "Filter recipes cooked X times or less."
msgstr ""
#: .\cookbook\views\api.py:1472
#, fuzzy
#| msgid "Filter for entries with the given recipe"
msgid "Filter recipes created on the given date."
msgstr "Filtra os itens pela receita definida"
#: .\cookbook\views\api.py:1473
#, fuzzy
#| msgid ""
#| "Filter recipes created on or after YYYY-MM-DD. Prepending - filters on or "
#| "before date."
msgid "Filter recipes created on the given date or after."
msgstr ""
"Filtra receitas criadas em ou após AAA-MM-DD. Acressidas - filtra na data ou "
"antes."
#: .\cookbook\views\api.py:1474
#, fuzzy
#| msgid ""
#| "Filter recipes created on or after YYYY-MM-DD. Prepending - filters on or "
#| "before date."
msgid "Filter recipes created on the given date or before."
msgstr ""
"Filtra receitas criadas em ou após AAA-MM-DD. Acressidas - filtra na data ou "
"antes."
#: .\cookbook\views\api.py:1476 .\cookbook\views\api.py:1477
#: .\cookbook\views\api.py:1478
#, fuzzy
#| msgid "Filter for entries with the given recipe"
msgid "Filter recipes updated on the given date."
msgstr "Filtra os itens pela receita definida"
#: .\cookbook\views\api.py:1480
#, fuzzy
#| msgid ""
#| "Filter recipes last cooked on or after YYYY-MM-DD. Prepending - filters "
#| "on or before date."
msgid "Filter recipes last cooked on the given date or after."
msgstr ""
"Filtra receitas cozidas em ou após AAA-MM-DD. Acressidas - filtra na data ou "
"antes."
#: .\cookbook\views\api.py:1481
#, fuzzy
#| msgid ""
#| "Filter recipes last cooked on or after YYYY-MM-DD. Prepending - filters "
#| "on or before date."
msgid "Filter recipes last cooked on the given date or before."
msgstr ""
"Filtra receitas cozidas em ou após AAA-MM-DD. Acressidas - filtra na data ou "
"antes."
#: .\cookbook\views\api.py:1483 .\cookbook\views\api.py:1484
#, fuzzy
#| msgid ""
#| "Filter recipes lasts viewed on or after YYYY-MM-DD. Prepending - filters "
#| "on or before date."
msgid "Filter recipes lasts viewed on the given date."
msgstr ""
"Filtra receitas visualizadas por último em ou após AAA-MM-DD. Acressidas - "
"filtra na data ou antes."
#: .\cookbook\views\api.py:1486
#, fuzzy
#| msgid "Filter for entries with the given recipe"
msgid "Filter recipes for ones created by the given user ID"
msgstr "Filtra os itens pela receita definida"
#: .\cookbook\views\api.py:1487
msgid "If only internal recipes should be returned. [true/false]"
msgstr ""
"Se apenas receitas internas devem ser retornadas. [verdadeiro/falso]"
#: .\cookbook\views\api.py:1488
msgid "Returns the results in randomized order. [true/false]"
msgstr "Retorna resultados em ordem aleatória. [verdadeiro/falso]"
#: .\cookbook\views\api.py:1490
msgid ""
"Determines the order of the results. Options are: score,-score,name,-name,"
"lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-"
"created_at,lastviewed,-lastviewed"
msgstr ""
#: .\cookbook\views\api.py:1492
msgid "Returns new results first in search results. [true/false]"
msgstr ""
"Retorna novos resultados primeiros na pesquisa. [verdadeiro/falso]"
#: .\cookbook\views\api.py:1493
msgid ""
"Returns the given number of recently viewed recipes before search results "
"(if given)"
msgstr ""
#: .\cookbook\views\api.py:1494
msgid "ID of a custom filter. Returns all recipes matched by that filter."
msgstr ""
#: .\cookbook\views\api.py:1495
msgid "Filter recipes that can be made with OnHand food. [true/false]"
msgstr ""
"Filtra receitas que podem ser preparadas com produtos à mão. [verdadeiro/"
"falso]"
#: .\cookbook\views\api.py:1773
msgid ""
"Return the PropertyTypes matching the property category. Repeat for "
"multiple."
msgstr ""
#: .\cookbook\views\api.py:1804 .\cookbook\views\api.py:1860
#, fuzzy
#| msgid "Filter for entries with the given recipe"
msgid "Returns only entries associated with the given mealplan id"
msgstr "Filtra os itens pela receita definida"
#: .\cookbook\views\api.py:1858
msgid ""
"Returns only elements updated after the given timestamp in ISO 8601 format."
msgstr ""
#: .\cookbook\views\api.py:2031
#, fuzzy
#| msgid ""
#| "Return the Automations matching the automation type. Multiple values "
#| "allowed."
msgid ""
"Return the Automations matching the automation type. Repeat for multiple."
msgstr ""
"Retorna as Automações correspondetes ao tipo de automação. Permitido "
"múltiplos valores."
#: .\cookbook\views\api.py:2048
msgid ""
"Text field to store data that gets carried over to the UserSpace created "
"from the InviteLink"
msgstr ""
#: .\cookbook\views\api.py:2049
msgid "Only return InviteLinks that have not been used yet."
msgstr ""
#: .\cookbook\views\api.py:2076
#, fuzzy
#| msgid ""
#| "Return the Automations matching the automation type. Multiple values "
#| "allowed."
msgid "Return the CustomFilters matching the model type. Repeat for multiple."
msgstr ""
"Retorna as Automações correspondetes ao tipo de automação. Permitido "
"múltiplos valores."
#: .\cookbook\views\api.py:2176
msgid "Nothing to do."
msgstr "Nada para fazer."
#: .\cookbook\views\api.py:2222
msgid "Invalid Url"
msgstr "Url Inválida"
#: .\cookbook\views\api.py:2228
msgid "Connection Refused."
msgstr "Conexão Recusada."
#: .\cookbook\views\api.py:2232
msgid "Bad URL Schema."
msgstr "Esquema de URL Inválido."
#: .\cookbook\views\api.py:2257
msgid "No usable data could be found."
msgstr "Nenhum dado utilizável foi encontrado."
#: .\cookbook\views\api.py:2286 .\cookbook\views\api.py:2434
msgid "You must select an AI provider to perform your request."
msgstr ""
#: .\cookbook\views\api.py:2293 .\cookbook\views\api.py:2441
msgid ""
"You don't have any credits remaining to use AI or AI features are not "
"enabled for your space."
msgstr ""
#: .\cookbook\views\api.py:2499 .\cookbook\views\api.py:2667
msgid "File is above space limit"
msgstr "Arquivo excede o limite de espaço"
#: .\cookbook\views\api.py:2522 .\cookbook\views\api.py:2684
msgid "Importing is not implemented for this provider"
msgstr "A importação não está implementada por esse provedor"
#: .\cookbook\views\api.py:2548
msgid ""
"The PDF Exporter is not enabled on this instance as it is still in an "
"experimental state."
msgstr ""
"O Exportar PDF não está habilitado nesta instância pois ainda está em "
"estágio experimental."
#: .\cookbook\views\api.py:2842
msgid "This feature is not yet available in the hosted version of tandoor!"
msgstr "Essa função ainda não está disponível na versão hospedada do Tandoor!"
#: .\cookbook\views\api.py:2863
msgid "Sync successful!"
msgstr "Sincronização realizada com sucesso!"
#: .\cookbook\views\api.py:2866
msgid "Error synchronizing with Storage"
msgstr "Erro ao sincronizar com o Armazenamento"
#: .\cookbook\views\views.py:89
msgid "This feature is not available in the demo version!"
msgstr "Essa função não está disponível na versão demo!"
#: .\cookbook\views\views.py:110
msgid ""
"You have successfully created your own recipe space. Start by adding some "
"recipes or invite other people to join you."
msgstr ""
"Você criou seu próprio espaço de receitas. Começe adicionando algumas "
"receitas ou convide outras pessoas para se juntar a você."
#: .\cookbook\views\views.py:171
#, python-format
msgid "PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!"
msgstr "O PostgreSQL %(v)s foi deprecado. Atualize para uma versão suportada!"
#: .\cookbook\views\views.py:174
#, python-format
msgid "You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended"
msgstr ""
"Você está rodando com o PostgreSQL %(v1)s. Recomenda-se o uso do PostgreSQL "
"%(v2)s"
#: .\cookbook\views\views.py:178
msgid "Unable to determine PostgreSQL version."
msgstr "Impossível determinar a versão do PostgreSQL."
#: .\cookbook\views\views.py:182
msgid ""
"This application is not running with a Postgres database backend. This is ok "
"but not recommended as some features only work with postgres databases."
msgstr ""
"Essa aplicação não está rodando com um banco de dados Postgres no backend. "
"Isso é possível mas não recomendado pois algumas funções só funcionam em "
"bancos de dados Postgres."
#: .\cookbook\views\views.py:296
msgid ""
"The setup page can only be used to create the first "
"user! If you have forgotten your superuser credentials "
"please consult the django documentation on how to reset passwords."
msgstr ""
"Essa página de configuração só pode ser utilizada para a criação do primeiro "
"usuário! Se você esqueceu suas credenciais de super usuário, por favor, "
"consulte a documentação do django sobre como redefinir senhas."
#: .\cookbook\views\views.py:304
msgid "Passwords dont match!"
msgstr "As senhas não conferem!"
#: .\cookbook\views\views.py:312
msgid "User has been created, please login!"
msgstr "Usuário criado, acesse por favor!"
#: .\cookbook\views\views.py:352
msgid ""
"Reporting share links is not enabled for this instance. Please notify the "
"page administrator to report problems."
msgstr ""
"Reportar links de compartilhamento não habilitado nesta instância. Por "
"favor, notifique o administrador da página para reportar problemas."
#: .\cookbook\views\views.py:357
msgid ""
"Recipe sharing link has been disabled! For additional information please "
"contact the page administrator."
msgstr ""
"Compartilhamento de receitas através de links foi desabilitado! Para maiores "
"informações, entre em contato com o administrador da página."
#: .\cookbook\views\views.py:383
msgid "Manage recipes, shopping list, meal plans and more."
msgstr "Gerencie receitas, listas de compras, planos de refeições e mais."
#: .\cookbook\views\views.py:397 .\cookbook\views\views.py:398
msgid "Plan"
msgstr "Plano"
#: .\cookbook\views\views.py:399
msgid "View your meal Plan"
msgstr "Visualize os seus Planos de refeições"
#: .\cookbook\views\views.py:418
msgid "View your shopping lists"
msgstr "Visualizar suas Listas de Compras"
#~ msgid ""
#~ "Both fields are optional. If none are given the username will be "
#~ "displayed instead"
#~ msgstr ""
#~ "Ambos os campos são opcionais. Se nenhum for preenchido, o nome de login "
#~ "será mostrado"
#~ msgid "Name"
#~ msgstr "Nome"
#~ msgid "Keywords"
#~ msgstr "Palavras-chave"
#~ msgid "Preparation time in minutes"
#~ msgstr "Tempo de preparação em minutos"
#~ msgid "Waiting time (cooking/baking) in minutes"
#~ msgstr "Tempo de espera (cozimento) em minutos"
#~ msgid "Path"
#~ msgstr "Caminho"
#~ msgid "Storage UID"
#~ msgstr "UID de armazenamento"
#~ msgid "Add your comment: "
#~ msgstr "Incluir seu comentário: "
#~ msgid "Leave empty for dropbox and enter app password for nextcloud."
#~ msgstr ""
#~ "Deixar vazio para Dropbox e inserir senha de aplicação para Nextcloud."
#~ msgid "Leave empty for nextcloud and enter api token for dropbox."
#~ msgstr "Deixar vazio para Nextcloud e inserir token api para Dropbox."
#~ msgid ""
#~ "Leave empty for dropbox and enter only base url for nextcloud (/"
#~ "remote.php/webdav/ is added automatically)"
#~ msgstr ""
#~ "Deixar vazio para Dropbox e inserir apenas url base para Nextcloud "
#~ "(/remote.php/webdav/é adicionado automaticamente)"
#~ msgid ""
#~ "Long Lived Access Token for your HomeAssistant instance"
#~ msgstr ""
#~ "Token de longa duração para sua instância do HomeAssistant"
#~ msgid "Something like http://homeassistant.local:8123/api"
#~ msgstr "Algo como http://homeassistant.local:8123/api"
#~ msgid "http://homeassistant.local:8123/api for example"
#~ msgstr "http://homeassistant.local:8123/api por exemplo"
#~ msgid "Storage"
#~ msgstr "Armazenamento"
#~ msgid "Active"
#~ msgstr "Ativo"
#~ msgid "Search String"
#~ msgstr "String de Pesquisa"
#~ msgid "File ID"
#~ msgstr "ID do Arquivo"
#~ msgid ""
#~ "Determines how fuzzy a search is if it uses trigram similarity matching "
#~ "(e.g. low values mean more typos are ignored)."
#~ msgstr ""
#~ "Determina o quão 'difusa' (fuzzy) uma pesquisa é se esta utilizando uma "
#~ "correspondência de semelhança de trigrama (valores mais baixos significam "
#~ "que mais erros são ignorados)."
#~ msgid ""
#~ "Select type method of search. Click here "
#~ "for full description of choices."
#~ msgstr ""
#~ "Selecionar o método de pesquisa. Uma descrição completa das opções pode "
#~ "ser encontrada aqui."
#~ msgid ""
#~ "Use fuzzy matching on units, keywords and ingredients when editing and "
#~ "importing recipes."
#~ msgstr ""
#~ "Utilizar correspondência fonética em unidades, palavras-chave e "
#~ "ingredientes ao editar e importar receitas."
#~ msgid ""
#~ "Fields to search ignoring accents. Selecting this option can improve or "
#~ "degrade search quality depending on language"
#~ msgstr ""
#~ "Campos de pesquisa que ignoram pontuação. Esta opção pode aumentar ou "
#~ "diminuir a qualidade de pesquisa dependendo do idioma em uso"
#~ msgid ""
#~ "Fields to search for partial matches. (e.g. searching for 'Pie' will "
#~ "return 'pie' and 'piece' and 'soapie')"
#~ msgstr ""
#~ "Campos a serem usados para pesquisa parcial (ex: pesquisar 'Pão' pode "
#~ "retornar 'pão', 'pãozinho' e 'pão de queijo')"
#~ msgid ""
#~ "Fields to search for beginning of word matches. (e.g. searching for 'sa' "
#~ "will return 'salad' and 'sandwich')"
#~ msgstr ""
#~ "Campos que devem ser buscados para correspondências de prefixo. (por "
#~ "exemplo, buscar por 'sa' retornará 'salada' e 'sanduíche')"
#~ msgid ""
#~ "Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find "
#~ "'recipe'.) Note: this option will conflict with 'web' and 'raw' methods "
#~ "of search."
#~ msgstr ""
#~ "Campos que devem usar busca 'difusa' (fuzzy). (por exemplo, procurando "
#~ "por 'recita' vai encontrar 'receita'). Nota: essa opção conflitará com "
#~ "os métodos de pesquisa 'web' e 'raw'."
#~ msgid ""
#~ "Fields to full text search. Note: 'web', 'phrase', and 'raw' search "
#~ "methods only function with fulltext fields."
#~ msgstr ""
#~ "Campos para fazer busca do texto completo. Nota: 'os métodos de pesquisa "
#~ "'web', 'frase' e 'raw' funcionam somente com campos de texto completo."
#~ msgid "Search Method"
#~ msgstr "Método de Pesquisa"
#~ msgid "Fuzzy Lookups"
#~ msgstr "Buscas 'Difusas' (fuzzy)"
#~ msgid "Ignore Accent"
#~ msgstr "Ignorar Acentos"
#~ msgid "Partial Match"
#~ msgstr "Correspondência parcial"
#~ msgid "Starts With"
#~ msgstr "Inicia Com"
#~ msgid "Fuzzy Search"
#~ msgstr "Pesquisa Fuzzy"
#~ msgid "Full Text"
#~ msgstr "Texto Completo"
#~ msgid " is part of a recipe step and cannot be deleted"
#~ msgstr " é parte de uma etapa de uma receita, e não pode ser excluído"
#~ msgid "Delete"
#~ msgstr "Apagar"
#~ msgid "Settings"
#~ msgstr "Configurações"
#~ msgid "Email"
#~ msgstr "Email"
#~ msgid "Password"
#~ msgstr "Senha"
#~ msgid "Foods"
#~ msgstr "Alimentos"
#~ msgid "Units"
#~ msgstr "Unidades"
#~ msgid "Supermarket"
#~ msgstr "Supermercado"
#~ msgid "Supermarket Category"
#~ msgstr "Categoria de Supermercado"
#~ msgid "Automations"
#~ msgstr "Automações"
#~ msgid "Files"
#~ msgstr "Arquivos"
#~ msgid "Batch Edit"
#~ msgstr "Edição em Lote"
#~ msgid "History"
#~ msgstr "Histórico"
#~ msgid "Ingredient Editor"
#~ msgstr "Editor de Ingredientes"
#~ msgid "Properties"
#~ msgstr "Propriedades"
#~ msgid "Unit Conversions"
#~ msgstr "Conversões de Medidas"
#~ msgid "Create"
#~ msgstr "Criar"
#~ msgid "External Recipes"
#~ msgstr "Receitas Externas"
#~ msgid "Space Settings"
#~ msgstr "Configurar Espaço"
#~ msgid "External Connectors"
#~ msgstr "Conexões externas"
#~ msgid "Admin"
#~ msgstr "Admin"
#~ msgid "Markdown Guide"
#~ msgstr "Guia de linguágem Markdown"
#~ msgid "GitHub"
#~ msgstr "GitHub"
#~ msgid "Translate Tandoor"
#~ msgstr "Traduzir Tandoor"
#~ msgid "API Browser"
#~ msgstr "API Browser"
#~ msgid "Log out"
#~ msgstr "Logout"
#~ msgid "You are using the free version of Tandor"
#~ msgstr "Você está utilizando a versão gratúita de Tandor"
#~ msgid "Upgrade Now"
#~ msgstr "Compre Já"
#~ msgid "Batch edit Category"
#~ msgstr "Editar Categorias em Lote"
#~ msgid "Batch edit Recipes"
#~ msgstr "Editar Receitas em Lote"
#~ msgid "Add the specified keywords to all recipes containing a word"
#~ msgstr ""
#~ "Adicionar palavras-chave a todas as receitas que contenham uma palavra"
#~ msgid "Sync"
#~ msgstr "Sincronizar"
#~ msgid "Manage watched Folders"
#~ msgstr "Gerenciar pastas monitoradas"
#~ msgid ""
#~ "On this Page you can manage all storage folder locations that should be "
#~ "monitored and synced."
#~ msgstr ""
#~ "Nessa página, você pode configurar todo os lugares de armazenamento que "
#~ "devem ser monitorados e sincronizados."
#~ msgid "The path must be in the following format"
#~ msgstr "O caminho deve estar no seguinte formato"
#~ msgid "Save"
#~ msgstr "Gravar"
#~ msgid "Manage External Storage"
#~ msgstr "Gerenciar Armazenamento Externo"
#~ msgid "Sync Now!"
#~ msgstr "Sincronizar Agora!"
#~ msgid "Show Recipes"
#~ msgstr "Mostrar Receitas"
#~ msgid "Show Log"
#~ msgstr "Mostrar Log"
#~ msgid "Importing Recipes"
#~ msgstr "Importando Receitas"
#~ msgid ""
#~ "This can take a few minutes, depending on the number of recipes in sync, "
#~ "please wait."
#~ msgstr ""
#~ "Este processo pode demorar alguns minutos, dependendo do número de "
#~ "receitas a serem importadas."
#~ msgid "Recipe Books"
#~ msgstr "Livros de Receita"
#~ msgid "Import new Recipe"
#~ msgstr "Importar nova Receita"
#~ msgid "Edit Recipe"
#~ msgstr "Editar Receita"
#, python-format
#~ msgid "Are you sure you want to delete the %(title)s: %(object)s "
#~ msgstr "Tem certeza que deseja apagar %(title)s: %(object)s "
#~ msgid "This cannot be undone!"
#~ msgstr "Essa ação não pode ser desfeita!"
#~ msgid "Protected"
#~ msgstr "Protegido"
#~ msgid "Cascade"
#~ msgstr "Cascata"
#~ msgid "Cancel"
#~ msgstr "Cancelar"
#~ msgid "Edit"
#~ msgstr "Editar"
#~ msgid "View"
#~ msgstr "Visualizar"
#~ msgid "Delete original file"
#~ msgstr "Apagar arquivo original"
#~ msgid "List"
#~ msgstr "Lista"
#~ msgid "Filter"
#~ msgstr "Filtro"
#~ msgid "Import all"
#~ msgstr "Importar tudo"
#~ msgid "New"
#~ msgstr "Novo"
#~ msgid "previous"
#~ msgstr "anterior"
#~ msgid "next"
#~ msgstr "próximo"
#~ msgid "View Log"
#~ msgstr "Mostrar Log"
#~ msgid "Cook Log"
#~ msgstr "Histórico de cocção"
#~ msgid "Import"
#~ msgstr "Importar"
#~ msgid "Security Warning"
#~ msgstr "Alerta de Segurança"
#~ msgid ""
#~ "\n"
#~ " The Password and Token field are stored as plain text"
#~ "b> inside the database.\n"
#~ " This is necessary because they are needed to make API requests, "
#~ "but it also increases the risk of\n"
#~ " someone stealing it.
\n"
#~ " To limit the possible damage tokens or accounts with limited "
#~ "access can be used.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " O campo Senha e Token são armazenados como texto puro "
#~ "b> no banco de dados.\n"
#~ " Isso é necessário pois são utilizados para realizar solicitações "
#~ "da API, mas isso também aumenta o risco de\n"
#~ " alguém roubá-los.
\n"
#~ " Para limitar o possível dano, tokens ou contar com acesso "
#~ "limitado podem ser utilizadas.\n"
#~ " "
#~ msgid "You are currently offline!"
#~ msgstr "Você está atualmente offline!"
#~ msgid ""
#~ "The recipes listed below are available for offline viewing because you "
#~ "have recently viewed them. Keep in mind that data might be outdated."
#~ msgstr ""
#~ "As receitas abaixo estão disponíveis de forma offline pois você "
#~ "recentemente as visualizou. Tenha em mente que os dados podem estar "
#~ "ultrapassados."
#~ msgid "Property Editor"
#~ msgstr "Editor"
#~ msgid "Comments"
#~ msgstr "Comentários"
#~ msgid "by"
#~ msgstr "por"
#~ msgid "Comment"
#~ msgstr "Comentário"
#~ msgid ""
#~ "There are many options to configure the search depending on your personal "
#~ "preferences."
#~ msgstr "Existem mutas opções de configurações para a busca."
#~ msgid ""
#~ "Usually you do not need to configure any of them and can just "
#~ "stick with either the default or one of the following presets."
#~ msgstr ""
#~ "Normalmente você não precisa configurar nada e pode apenas "
#~ "utilizar as definições padrão ou uma das definições seguintes."
#~ msgid ""
#~ "If you do want to configure the search you can read about the different "
#~ "options here."
#~ msgstr ""
#~ "Se deseja configurar a busca leia sobre as diferentes opções aqui."
#~ msgid "Fuzzy"
#~ msgstr "Difuso"
#~ msgid ""
#~ "Find what you need even if your search or the recipe contains typos. "
#~ "Might return more results than needed to make sure you find what you are "
#~ "looking for."
#~ msgstr ""
#~ "Encontre o que procura mesmo se sua busca ou a receita contiver error. "
#~ "Pode retornar mais resultados que o esperado para garantir que você "
#~ "encontre o que procura."
#~ msgid "This is the default behavior"
#~ msgstr "Esse é o comportamento padrão"
#~ msgid "Apply"
#~ msgstr "Aplicar"
#~ msgid "Precise"
#~ msgstr "Preciso"
#~ msgid ""
#~ "Allows fine control over search results but might not return results if "
#~ "too many spelling mistakes are made."
#~ msgstr ""
#~ "Permite um controle fino sobre os resultados mas pode não retornar nenhum "
#~ "resultado caso haja muitos erros de ortografia."
#~ msgid "Perfect for large Databases"
#~ msgstr "Perfeito para grandes Bancos de Dados"
#~ msgid "Social"
#~ msgstr "Social"
#~ msgid "Space Management"
#~ msgstr "Gerenciamento de Espaço"
#~ msgid "Space:"
#~ msgstr "Espaço:"
#~ msgid "Manage Subscription"
#~ msgstr "Gerenciar Assinatura"
#~ msgid "Leave Space"
#~ msgstr "Sair do Espaço"
#~ msgid "URL Import"
#~ msgstr "Importar URL"
#~ msgid ""
#~ "Rating a recipe should have or greater. [0 - 5] Negative value filters "
#~ "rating less than."
#~ msgstr ""
#~ "Avaliação que uma receita deva ter ou maior [0 - 5] Valores negativos "
#~ "filtram receitas com avaliação menor que."
#~ msgid ""
#~ "Filter recipes updated on or after YYYY-MM-DD. Prepending - filters on or "
#~ "before date."
#~ msgstr ""
#~ "Filtra receitas atualizadas em ou após AAA-MM-DD. Acressidas - filtra na "
#~ "data ou antes."
#~ msgid ""
#~ "Returns the shopping list entry with a primary key of id. Multiple "
#~ "values allowed."
#~ msgstr ""
#~ "Retorna a lista de comprar com a key primária do id. Permitido múltiplos "
#~ "valores."
#~ msgid ""
#~ "Filter shopping list entries on checked. [true, false, both, recent"
#~ "b>]
- recent includes unchecked items and recently "
#~ "completed items."
#~ msgstr ""
#~ "Filtra itens da lista de compra marcados. [verdadeiro, falso, ambos, "
#~ "recentes]
- recentes inclui itens não "
#~ "marcados e os completados recentemente."
#~ msgid ""
#~ "Returns the shopping list entries sorted by supermarket category order."
#~ msgstr ""
#~ "Retorna os itens da lista de compras ordenados por categoria do "
#~ "supermercado."
#, python-format
#~ msgid "Batch edit done. %(count)d recipe was updated."
#~ msgid_plural "Batch edit done. %(count)d Recipes where updated."
#~ msgstr[0] "Edição em lote concluida. %(count)d receita atualizada."
#~ msgstr[1] "Edição em lote concluida. %(count)d receitas atualizadas."
#~ msgid "Monitor"
#~ msgstr "Monitor"
#~ msgid "Storage Backend"
#~ msgstr "Armazenamento Backend"
#~ msgid ""
#~ "Could not delete this storage backend as it is used in at least one "
#~ "monitor."
#~ msgstr ""
#~ "Não foi possível apagar esse armazenamento back-end por ele está em uso "
#~ "em pelo menos um monitor."
#~ msgid "Connectors Config Backend"
#~ msgstr "Conectores Config Backen"
#~ msgid "Invite Link"
#~ msgstr "Link de Convite"
#~ msgid "Space Membership"
#~ msgstr "Membros do Espaço"
#~ msgid "You cannot edit this storage!"
#~ msgstr "Você não pode editar esse armazenamento!"
#~ msgid "Storage saved!"
#~ msgstr "Armazenamento salvo!"
#~ msgid "There was an error updating this storage backend!"
#~ msgstr "Ocorreu um erro ao atualizar esse armazenamento back-end!"
#~ msgid "Config saved!"
#~ msgstr "Configuração gravada!"
#~ msgid "ConnectorConfig"
#~ msgstr "ConnectoresConfig"
#~ msgid "Changes saved!"
#~ msgstr "Alterações salvas!"
#~ msgid "Error saving changes!"
#~ msgstr "Erro ao salvar mudanças!"
#~ msgid "Import Log"
#~ msgstr "Importar Log"
#~ msgid "Discovery"
#~ msgstr "Descoberta"
#~ msgid "Shopping List"
#~ msgstr "Lista de Compras"
#~ msgid "Connector Config Backend"
#~ msgstr "Conector Config Backen"
#~ msgid "Invite Links"
#~ msgstr "Links de Convite"
#~ msgid "Supermarkets"
#~ msgstr "Supermercados"
#~ msgid "Shopping Categories"
#~ msgstr "Categorias de Compras"
#~ msgid "Custom Filters"
#~ msgstr "Filtros Customizados"
#~ msgid "Steps"
#~ msgstr "Etapas"
#~ msgid "Property Types"
#~ msgstr "Tipos de Propriedades"
#~ msgid "This feature is not enabled by the server admin!"
#~ msgstr "Essa função não está habilitada pelo administrador do servidor!"
#~ msgid "Imported new recipe!"
#~ msgstr "Nova receita importada!"
#~ msgid "There was an error importing this recipe!"
#~ msgstr "Ocorreu um erro ao importar essa receita!"
#~ msgid "You do not have the required permissions to perform this action!"
#~ msgstr "Você não tem a permissão necessária para realizar esta ação!"
#~ msgid "Comment saved!"
#~ msgstr "Comentário gravado!"
#~ msgid "You must select at least one field to search!"
#~ msgstr "Você precisa selecionar pelo menos um campo para realizar a busca!"
#~ msgid ""
#~ "To use this search method you must select at least one full text search "
#~ "field!"
#~ msgstr ""
#~ "Para utilizar esse método de busca você deve selecionar pelo menos um "
#~ "campo de texto!"
#~ msgid "Fuzzy search is not compatible with this search method!"
#~ msgstr "Busca Fuzzy não compatível com esse método de busca!"
#~ msgid "Malformed Invite Link supplied!"
#~ msgstr "Link mal formatado fornecido!"
#~ msgid "Successfully joined space."
#~ msgstr "Ingresso bem sucedido no espaço."
#~ msgid "Invite Link not valid or already used!"
#~ msgstr "Lik de convite inválido ou já utilizado!"
#~ msgid "View your cookbooks"
#~ msgstr "Visualize seus livros de receitas"
#~ msgid "Default unit"
#~ msgstr "Unidade padrão"
#~ msgid "Use KJ"
#~ msgstr "Usar KJ"
#~ msgid "Theme"
#~ msgstr "Tema"
#~ msgid "Navbar color"
#~ msgstr "Cor da Barra de :Navegação"
#~ msgid "Sticky navbar"
#~ msgstr "Fixar barra"
#~ msgid "Default page"
#~ msgstr "Página padrão"
#~ msgid "Show recent recipes"
#~ msgstr "Mostrar receitas recentes"
#~ msgid "Search style"
#~ msgstr "Estilo da busca"
#~ msgid "Plan sharing"
#~ msgstr "Compartilhar plano"
#~ msgid "Ingredient decimal places"
#~ msgstr "Número de decimais (ingredientes)"
#~ msgid "Shopping list auto sync period"
#~ msgstr "Sincronizar automaticamente período da Lista de compras"
#~ msgid ""
#~ "Color of the top navigation bar. Not all colors work with all themes, "
#~ "just try them out!"
#~ msgstr ""
#~ "Cor da barra superior. Nem todas as cores funcionam com todos os temas, "
#~ "teste!"
#~ msgid ""
#~ "Default Unit to be used when inserting a new ingredient into a recipe."
#~ msgstr "Unidade padrão ao inserir novo ingrediente em uma receita."
#~ msgid ""
#~ "Enables support for fractions in ingredient amounts (e.g. convert "
#~ "decimals to fractions automatically)"
#~ msgstr ""
#~ "Habilitar suporte para frações em quantidade dos ingredientes (ex. "
#~ "Converte decimais para frações automaticamente)"
#~ msgid "Display nutritional energy amounts in joules instead of calories"
#~ msgstr "Exibir informações nutricionais em Joules ao invés de Calorias"
#~ msgid ""
#~ "Users with whom newly created meal plans should be shared by default."
#~ msgstr ""
#~ "Usuários com os quais novos planos de refeição devem ser compartilhados "
#~ "por padrão."
#~ msgid "Users with whom to share shopping lists."
#~ msgstr "Usuários com os quais novas listas de compras serão compartilhadas."
#~ msgid "Number of decimals to round ingredients."
#~ msgstr "Número de casas decimais para arredondamento dos ingredientes."
#~ msgid ""
#~ "Setting to 0 will disable auto sync. When viewing a shopping list the "
#~ "list is updated every set seconds to sync changes someone else might have "
#~ "made. Useful when shopping with multiple people but might use a little "
#~ "bit of mobile data. If lower than instance limit it is reset when saving."
#~ msgstr ""
#~ "Definir esta opção como 0 desativará a sincronização automática. Ao "
#~ "visualizar uma lista de compras, a lista é atualizada a cada período aqui "
#~ "definido para sincronizar as alterações que outro usuário possa ter "
#~ "feito. Útil ao fazer compras com vários usuários, mas pode aumentar o uso "
#~ "de dados móveis."
#~ msgid "Makes the navbar stick to the top of the page."
#~ msgstr "Mantém a barra de navegação no topo da página."
#~ msgid "You must provide at least a recipe or a title."
#~ msgstr "Você precisa informar ao menos uma receita ou um título."
#~ msgid "You can list default users to share recipes with in the settings."
#~ msgstr ""
#~ "É possível escolher os usuários com quem compartilhar receitas por padrão "
#~ "nas definições."
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "É possível utilizar markdown para editar este campo. Documentação disponível aqui"
#~ msgid "Share Shopping List"
#~ msgstr "Compartilhar Lista de Compras"
#~ msgid "Autosync"
#~ msgstr "Sincronização automática"
#~ msgid "Auto Add Meal Plan"
#~ msgstr "Auto Incluir Plano de Refeição"
#~ msgid "Filter to Supermarket"
#~ msgstr "Filtro para Supermercado"
#~ msgid "Recent Days"
#~ msgstr "Dias Recentes"
#~ msgid "CSV Delimiter"
#~ msgstr "Delimitador CSV"
#~ msgid "List Prefix"
#~ msgstr "Lista de Prefixos"
#~ msgid "Fields on food that should be inherited by default."
#~ msgstr "Campos do alimento que devem ser herdados por padrão."
#~ msgid "Show recipe counts on search filters"
#~ msgstr "Mostrar contador de receitas nos filtros de pesquisa"
#~ msgid "Small"
#~ msgstr "Pequeno"
#~ msgid "Large"
#~ msgstr "Grande"
#~ msgid "A user is required"
#~ msgstr "Um usuário é obrigatório"
#~ msgid "Edit Ingredients"
#~ msgstr "Editar Ingredientes"
#~ msgid "Are you sure that you want to merge these two ingredients?"
#~ msgstr "Tem certeza que deseja mesclar estes dois ingredientes?"
#~ msgid "Try the new shopping list"
#~ msgstr "Tentar a nova lista de compras"
#~ msgid "Import Recipes"
#~ msgstr "Importar Receitas"
#~ msgid "Close"
#~ msgstr "Fechar"
#~ msgid "Open Recipe"
#~ msgstr "Abrir Receita"
#~ msgid "Meal Plan View"
#~ msgstr "Visualizar Plano de Refeição"
#~ msgid "Created by"
#~ msgstr "Criado por"
#~ msgid "Shared with"
#~ msgstr "Compartilhado com"
#~ msgid "Other meals on this day"
#~ msgstr "Outras refeições neste dia"
#~ msgid "Recipe Image"
#~ msgstr "Imagem da Receita"
#~ msgid "External"
#~ msgstr "Externo"
#~ msgid "Account"
#~ msgstr "Conta"
#~ msgid "Preferences"
#~ msgstr "Preferências"
#~ msgid "API-Settings"
#~ msgstr "API-Configurações"
#~ msgid "Search-Settings"
#~ msgstr "Pesquisa-Configurações"
#~ msgid "Shopping-Settings"
#~ msgstr "Compras-Configurações"
#~ msgid "Name Settings"
#~ msgstr "Configurações de Nome"
#~ msgid "Account Settings"
#~ msgstr "Configurações de Conta"
#~ msgid "Emails"
#~ msgstr "Emails"
#~ msgid "Language"
#~ msgstr "Idioma"
#~ msgid "Style"
#~ msgstr "Estilo"
#~ msgid "API Token"
#~ msgstr "Token API"
#~ msgid "or"
#~ msgstr "ou"
#~ msgid "Shopping Settings"
#~ msgstr "Configurações de Compras"
#~ msgid "Search Recipe"
#~ msgstr "Pesquisar Receita"
#~ msgid "No recipes selected"
#~ msgstr "Nenhuma receita selecionada"
#~ msgid "Entry Mode"
#~ msgstr "Modo Entrada"
#~ msgid "Add Entry"
#~ msgstr "Incluir Entrada"
#~ msgid "Amount"
#~ msgstr "Quantidade"
#~ msgid "Select"
#~ msgstr "Selecionar"
#~ msgid "Select Food"
#~ msgstr "Selecionar Alimento"
#~ msgid "Select Supermarket"
#~ msgstr "Selecionar Supermercado"
#~ msgid "Select User"
#~ msgstr "Selecionar Usuário"
#~ msgid "Finished"
#~ msgstr "Finalizado"
#~ msgid "Copy/Export"
#~ msgstr "Copiar/Exportar"
#~ msgid "Number of objects"
#~ msgstr "Número de objetos"
#~ msgid "Recipes without Keywords"
#~ msgstr "Receitas sem Palavras-chaves"
#~ msgid "Internal Recipes"
#~ msgstr "Receitas Internas"
#~ msgid "Invite User"
#~ msgstr "Convidar Usuário"
#~ msgid "User"
#~ msgstr "Usuário"
#~ msgid "Groups"
#~ msgstr "Grupos"
#~ msgid "admin"
#~ msgstr "admin"
#~ msgid "user"
#~ msgstr "usuário"
#~ msgid "guest"
#~ msgstr "convidado"
#~ msgid "remove"
#~ msgstr "remover"
#~ msgid "Update"
#~ msgstr "Atualizar"
#~ msgid "There are no members in your space yet!"
#~ msgstr "Ainda não há membros no seu espaço!"
#~ msgid "Stats"
#~ msgstr "Estatísticas"
#~ msgid "Statistics"
#~ msgstr "Estatísticas"
#~ msgid "Show Links"
#~ msgstr "Mostrar Links"
#~ msgid "URL"
#~ msgstr "URL"
#~ msgid "App"
#~ msgstr "App"
#~ msgid "Text"
#~ msgstr "Texto"
#~ msgid "File"
#~ msgstr "Arquivo"
#~ msgid "Enter website URL"
#~ msgstr "Entre a URL do website"
#~ msgid "Paste json or html source here to load recipe."
#~ msgstr "Colar aqui o código json ou html para carregar a receita."
#~ msgid "Clear Contents"
#~ msgstr "Limpar Conteúdo"
#~ msgid "Image"
#~ msgstr "Imagem"
#~ msgid "Prep Time"
#~ msgstr "Tempo de Preparação"
#~ msgid "Cook Time"
#~ msgstr "Tempo de Cozimento"
#~ msgid "Discovered Attributes"
#~ msgstr "Atributos Descobertos"
#~ msgid "Show Blank Field"
#~ msgstr "Mostrar Campo em Branco"
#~ msgid "Blank Field"
#~ msgstr "Campo Branco"
#~ msgid "Delete Text"
#~ msgstr "Apagar Texto"
#~ msgid "Delete image"
#~ msgstr "Apagar Imagem"
#~ msgid "Recipe Name"
#~ msgstr "Nome da Receita"
#~ msgid "Recipe Description"
#~ msgstr "Descrição da Receita"
#~ msgid "Select one"
#~ msgstr "Selecione um"
#~ msgid "Note"
#~ msgstr "Nota"
#~ msgid "Add Keyword"
#~ msgstr "Incluir Palavra-chave"
#~ msgid "All Keywords"
#~ msgstr "Todas as Palavras-chaves"
#~ msgid "Information"
#~ msgstr "Informação"
#~ msgid "GitHub Issues"
#~ msgstr "Issues GitHub"
#~ msgid "Recipe Book"
#~ msgstr "Livro de Receita"
#~ msgid "Shopping Lists"
#~ msgstr "Listas de Compras"
================================================
FILE: cookbook/locale/rn/LC_MESSAGES/django.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-22 20:15+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
#: .\cookbook\forms.py:50
msgid "Default"
msgstr ""
#: .\cookbook\forms.py:77
msgid ""
"To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything."
msgstr ""
#: .\cookbook\forms.py:108
msgid "Maximum number of users for this space reached."
msgstr ""
#: .\cookbook\forms.py:114
msgid "Email address already taken!"
msgstr ""
#: .\cookbook\forms.py:121
msgid ""
"An email address is not required but if present the invite link will be sent "
"to the user."
msgstr ""
#: .\cookbook\forms.py:133
msgid "Name already taken."
msgstr ""
#: .\cookbook\forms.py:144 .\cookbook\forms.py:158
msgid "Accept Terms and Privacy"
msgstr ""
#: .\cookbook\helper\AllAuthCustomAdapter.py:41
msgid ""
"In order to prevent spam, the requested email was not send. Please wait a "
"few minutes and try again."
msgstr ""
#: .\cookbook\helper\permission_helper.py:165
#: .\cookbook\helper\permission_helper.py:186 .\cookbook\views\views.py:138
msgid "You are not logged in and therefore cannot view this page!"
msgstr ""
#: .\cookbook\helper\permission_helper.py:168
#: .\cookbook\helper\permission_helper.py:173
#: .\cookbook\helper\permission_helper.py:198
#: .\cookbook\helper\permission_helper.py:265
#: .\cookbook\helper\permission_helper.py:279
#: .\cookbook\helper\permission_helper.py:290
#: .\cookbook\helper\permission_helper.py:301
#: .\cookbook\helper\permission_helper.py:317
#: .\cookbook\helper\permission_helper.py:343
#: .\cookbook\helper\permission_helper.py:359
msgid "You do not have the required permissions to view this page!"
msgstr ""
#: .\cookbook\helper\permission_helper.py:191
#: .\cookbook\helper\permission_helper.py:214
#: .\cookbook\helper\permission_helper.py:236
#: .\cookbook\helper\permission_helper.py:251
msgid "You cannot interact with this object as it is not owned by you!"
msgstr ""
#: .\cookbook\helper\permission_helper.py:420
msgid "You have reached the maximum number of recipes for your space."
msgstr ""
#: .\cookbook\helper\permission_helper.py:432
msgid "You have more users than allowed in your space."
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:319
msgid "reverse rotation"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:320
msgid "careful rotation"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:321
msgid "knead"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:322
msgid "thicken"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:323
msgid "warm up"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:324
msgid "ferment"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:325
msgid "slow cook"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:326
msgid "egg boiler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:327
msgid "kettle"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:328
msgid "blend"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:329
msgid "pre-clean"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:330
msgid "high temperature"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:331
msgid "rice cooker"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:332
msgid "caramelize"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:333
msgid "peeler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:334
msgid "slicer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:335
msgid "grater"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:336
msgid "spiralizer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:337
msgid "sous-vide"
msgstr ""
#: .\cookbook\helper\shopping_helper.py:150
msgid "You must supply a servings size"
msgstr ""
#: .\cookbook\helper\template_helper.py:97
#: .\cookbook\helper\template_helper.py:99
#: .\cookbook\helper\template_helper.py:101
msgid "Could not parse template code."
msgstr ""
#: .\cookbook\integration\copymethat.py:44
#: .\cookbook\integration\melarecipes.py:37
msgid "Favorite"
msgstr ""
#: .\cookbook\integration\copymethat.py:50
msgid "I made this"
msgstr ""
#: .\cookbook\integration\integration.py:238
msgid ""
"Importer expected a .zip file. Did you choose the correct importer type for "
"your data ?"
msgstr ""
#: .\cookbook\integration\integration.py:241
msgid ""
"An unexpected error occurred during the import. Please make sure you have "
"uploaded a valid file."
msgstr ""
#: .\cookbook\integration\integration.py:246
msgid "The following recipes were ignored because they already existed:"
msgstr ""
#: .\cookbook\integration\integration.py:250
#, python-format
msgid "Imported %s recipes."
msgstr ""
#: .\cookbook\integration\mealie1.py:210
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "Calories"
msgstr ""
#: .\cookbook\integration\mealie1.py:211
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
msgid "Carbohydrates"
msgstr ""
#: .\cookbook\integration\mealie1.py:212
msgid "Cholesterol"
msgstr ""
#: .\cookbook\integration\mealie1.py:213
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
msgid "Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:214
msgid "Fiber"
msgstr ""
#: .\cookbook\integration\mealie1.py:215
msgid "Protein"
msgstr ""
#: .\cookbook\integration\mealie1.py:216
msgid "Saturated Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:217
msgid "Sodium"
msgstr ""
#: .\cookbook\integration\mealie1.py:218
msgid "Sugar"
msgstr ""
#: .\cookbook\integration\mealie1.py:219
msgid "Trans Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:220
msgid "Unsaturated Fat"
msgstr ""
#: .\cookbook\integration\openeats.py:28
msgid "Recipe source:"
msgstr ""
#: .\cookbook\integration\paprika.py:49
msgid "Notes"
msgstr ""
#: .\cookbook\integration\paprika.py:52
msgid "Nutritional Information"
msgstr ""
#: .\cookbook\integration\paprika.py:56
msgid "Source"
msgstr ""
#: .\cookbook\integration\recettetek.py:55
#: .\cookbook\integration\recipekeeper.py:70
msgid "Imported from"
msgstr ""
#: .\cookbook\integration\saffron.py:23
msgid "Servings"
msgstr ""
#: .\cookbook\integration\saffron.py:25
msgid "Waiting time"
msgstr ""
#: .\cookbook\integration\saffron.py:27
msgid "Preparation Time"
msgstr ""
#: .\cookbook\integration\saffron.py:29 .\cookbook\templates\index.html:6
msgid "Cookbook"
msgstr ""
#: .\cookbook\integration\saffron.py:31
msgid "Section"
msgstr ""
#: .\cookbook\management\commands\fix_duplicate_properties.py:15
msgid "Fixes foods with "
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:14
msgid "Rebuilds full text search index on Recipe"
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:18
msgid "Only Postgresql databases use full text search, no index to rebuild"
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:29
msgid "Recipe index rebuild complete."
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:31
msgid "Recipe index rebuild failed."
msgstr ""
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
msgid "Breakfast"
msgstr ""
#: .\cookbook\migrations\0047_auto_20200602_1133.py:19
msgid "Lunch"
msgstr ""
#: .\cookbook\migrations\0047_auto_20200602_1133.py:24
msgid "Dinner"
msgstr ""
#: .\cookbook\migrations\0047_auto_20200602_1133.py:29 .\cookbook\models.py:971
msgid "Other"
msgstr ""
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "g"
msgstr ""
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "Proteins"
msgstr ""
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "kcal"
msgstr ""
#: .\cookbook\models.py:325
msgid ""
"Maximum file storage for space in MB. 0 for unlimited, -1 to disable file "
"upload."
msgstr ""
#: .\cookbook\models.py:513
msgid "Search"
msgstr ""
#: .\cookbook\models.py:514
msgid "Meal-Plan"
msgstr ""
#: .\cookbook\models.py:515
msgid "Books"
msgstr ""
#: .\cookbook\models.py:516 .\cookbook\views\views.py:416
#: .\cookbook\views\views.py:417
msgid "Shopping"
msgstr ""
#: .\cookbook\models.py:967
msgid "Nutrition"
msgstr ""
#: .\cookbook\models.py:968
msgid "Allergen"
msgstr ""
#: .\cookbook\models.py:969
msgid "Price"
msgstr ""
#: .\cookbook\models.py:970
msgid "Goal"
msgstr ""
#: .\cookbook\models.py:1467 .\cookbook\templates\search_info.html:28
msgid "Simple"
msgstr ""
#: .\cookbook\models.py:1468 .\cookbook\templates\search_info.html:33
msgid "Phrase"
msgstr ""
#: .\cookbook\models.py:1469 .\cookbook\templates\search_info.html:38
msgid "Web"
msgstr ""
#: .\cookbook\models.py:1470 .\cookbook\templates\search_info.html:47
msgid "Raw"
msgstr ""
#: .\cookbook\models.py:1525
msgid "Food Alias"
msgstr ""
#: .\cookbook\models.py:1526
msgid "Unit Alias"
msgstr ""
#: .\cookbook\models.py:1527
msgid "Keyword Alias"
msgstr ""
#: .\cookbook\models.py:1528
msgid "Description Replace"
msgstr ""
#: .\cookbook\models.py:1529
msgid "Instruction Replace"
msgstr ""
#: .\cookbook\models.py:1530
msgid "Never Unit"
msgstr ""
#: .\cookbook\models.py:1531
msgid "Transpose Words"
msgstr ""
#: .\cookbook\models.py:1532
msgid "Food Replace"
msgstr ""
#: .\cookbook\models.py:1533
msgid "Unit Replace"
msgstr ""
#: .\cookbook\models.py:1534
msgid "Name Replace"
msgstr ""
#: .\cookbook\models.py:1564
msgid "Recipe"
msgstr ""
#: .\cookbook\models.py:1565
msgid "Food"
msgstr ""
#: .\cookbook\models.py:1566
msgid "Keyword"
msgstr ""
#: .\cookbook\serializer.py:262
msgid "File uploads are not enabled for this Space."
msgstr ""
#: .\cookbook\serializer.py:273
msgid "You have reached your file upload limit."
msgstr ""
#: .\cookbook\serializer.py:281
msgid "The given file type is not allowed."
msgstr ""
#: .\cookbook\serializer.py:421 .\cookbook\views\views.py:94
msgid ""
"You have the reached the maximum amount of spaces that can be owned by you."
msgstr ""
#: .\cookbook\serializer.py:434
msgid "Space Name must be unique."
msgstr ""
#: .\cookbook\serializer.py:469
msgid "Cannot modify Space owner permission."
msgstr ""
#: .\cookbook\serializer.py:1596
msgid "Hello"
msgstr ""
#: .\cookbook\serializer.py:1596
msgid "You have been invited by "
msgstr ""
#: .\cookbook\serializer.py:1598
msgid " to join their Tandoor Recipes space "
msgstr ""
#: .\cookbook\serializer.py:1600
msgid "Click the following link to activate your account: "
msgstr ""
#: .\cookbook\serializer.py:1602
msgid ""
"If the link does not work use the following code to manually join the space: "
msgstr ""
#: .\cookbook\serializer.py:1604
msgid "The invitation is valid until "
msgstr ""
#: .\cookbook\serializer.py:1606
msgid ""
"Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub "
msgstr ""
#: .\cookbook\serializer.py:1609
msgid "Tandoor Recipes Invite"
msgstr ""
#: .\cookbook\serializer.py:1813
msgid "Existing shopping list to update"
msgstr ""
#: .\cookbook\serializer.py:1815
msgid ""
"List of ingredient IDs from the recipe to add, if not provided all "
"ingredients will be added."
msgstr ""
#: .\cookbook\serializer.py:1817
msgid ""
"Providing a list_recipe ID and servings of 0 will delete that shopping list."
msgstr ""
#: .\cookbook\serializer.py:1826
msgid "Amount of food to add to the shopping list"
msgstr ""
#: .\cookbook\serializer.py:1828
msgid "ID of unit to use for the shopping list"
msgstr ""
#: .\cookbook\serializer.py:1830
msgid "When set to true will delete all food from active shopping lists."
msgstr ""
#: .\cookbook\templates\404.html:5
msgid "404 Error"
msgstr ""
#: .\cookbook\templates\404.html:18
msgid "The page you are looking for could not be found."
msgstr ""
#: .\cookbook\templates\404.html:33
msgid "Take me Home"
msgstr ""
#: .\cookbook\templates\404.html:35
msgid "Report a Bug"
msgstr ""
#: .\cookbook\templates\account\email.html:6
#: .\cookbook\templates\account\email.html:10
msgid "E-mail Addresses"
msgstr ""
#: .\cookbook\templates\account\email.html:12
msgid "The following e-mail addresses are associated with your account:"
msgstr ""
#: .\cookbook\templates\account\email.html:29
msgid "Verified"
msgstr ""
#: .\cookbook\templates\account\email.html:31
msgid "Unverified"
msgstr ""
#: .\cookbook\templates\account\email.html:33
msgid "Primary"
msgstr ""
#: .\cookbook\templates\account\email.html:40
msgid "Make Primary"
msgstr ""
#: .\cookbook\templates\account\email.html:42
msgid "Re-send Verification"
msgstr ""
#: .\cookbook\templates\account\email.html:43
#: .\cookbook\templates\socialaccount\connections.html:36
msgid "Remove"
msgstr ""
#: .\cookbook\templates\account\email.html:51
msgid "Warning:"
msgstr ""
#: .\cookbook\templates\account\email.html:51
msgid ""
"You currently do not have any e-mail address set up. You should really add "
"an e-mail address so you can receive notifications, reset your password, etc."
msgstr ""
#: .\cookbook\templates\account\email.html:57
msgid "Add E-mail Address"
msgstr ""
#: .\cookbook\templates\account\email.html:62
msgid "Add E-mail"
msgstr ""
#: .\cookbook\templates\account\email.html:72
msgid "Do you really want to remove the selected e-mail address?"
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:6
#: .\cookbook\templates\account\email_confirm.html:10
msgid "Confirm E-mail Address"
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:16
#, python-format
msgid ""
"Please confirm that\n"
" %(email)s is an e-mail address "
"for user %(user_display)s\n"
" ."
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:22
msgid "Confirm"
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:29
#, python-format
msgid ""
"This e-mail confirmation link expired or is invalid. Please\n"
" issue a new e-mail confirmation "
"request."
msgstr ""
#: .\cookbook\templates\account\login.html:8
#: .\cookbook\templates\openid\login.html:8
msgid "Login"
msgstr ""
#: .\cookbook\templates\account\login.html:15
#: .\cookbook\templates\account\login.html:31
#: .\cookbook\templates\account\password_reset.html:39
#: .\cookbook\templates\account\password_reset_done.html:31
#: .\cookbook\templates\account\signup.html:68
#: .\cookbook\templates\account\signup_closed.html:15
#: .\cookbook\templates\openid\login.html:15
#: .\cookbook\templates\openid\login.html:26
#: .\cookbook\templates\socialaccount\authentication_error.html:15
msgid "Sign In"
msgstr ""
#: .\cookbook\templates\account\login.html:34
#: .\cookbook\templates\account\password_reset.html:41
#: .\cookbook\templates\account\password_reset_done.html:33
#: .\cookbook\templates\socialaccount\signup.html:8
#: .\cookbook\templates\socialaccount\signup.html:56
msgid "Sign Up"
msgstr ""
#: .\cookbook\templates\account\login.html:38
msgid "Lost your password?"
msgstr ""
#: .\cookbook\templates\account\login.html:39
#: .\cookbook\templates\account\password_reset.html:29
msgid "Reset My Password"
msgstr ""
#: .\cookbook\templates\account\login.html:50
msgid "Social Login"
msgstr ""
#: .\cookbook\templates\account\login.html:51
msgid "You can use any of the following providers to sign in."
msgstr ""
#: .\cookbook\templates\account\logout.html:5
#: .\cookbook\templates\account\logout.html:9
#: .\cookbook\templates\account\logout.html:18
msgid "Sign Out"
msgstr ""
#: .\cookbook\templates\account\logout.html:11
msgid "Are you sure you want to sign out?"
msgstr ""
#: .\cookbook\templates\account\password_change.html:6
#: .\cookbook\templates\account\password_change.html:10
#: .\cookbook\templates\account\password_change.html:15
#: .\cookbook\templates\account\password_reset_from_key.html:7
#: .\cookbook\templates\account\password_reset_from_key.html:13
#: .\cookbook\templates\account\password_reset_from_key_done.html:7
#: .\cookbook\templates\account\password_reset_from_key_done.html:13
msgid "Change Password"
msgstr ""
#: .\cookbook\templates\account\password_change.html:16
msgid "Forgot Password?"
msgstr ""
#: .\cookbook\templates\account\password_reset.html:7
#: .\cookbook\templates\account\password_reset.html:13
#: .\cookbook\templates\account\password_reset_done.html:7
#: .\cookbook\templates\account\password_reset_done.html:18
msgid "Password Reset"
msgstr ""
#: .\cookbook\templates\account\password_reset.html:24
msgid ""
"Forgotten your password? Enter your e-mail address below, and we'll send you "
"an e-mail allowing you to reset it."
msgstr ""
#: .\cookbook\templates\account\password_reset.html:32
msgid "Password reset is disabled on this instance."
msgstr ""
#: .\cookbook\templates\account\password_reset_done.html:25
msgid ""
"We have sent you an e-mail. Please contact us if you do not receive it "
"within a few minutes."
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:13
msgid "Bad Token"
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:25
#, python-format
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used.\n"
" Please request a new "
"password reset."
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:33
msgid "change password"
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:36
#: .\cookbook\templates\account\password_reset_from_key_done.html:19
msgid "Your password is now changed."
msgstr ""
#: .\cookbook\templates\account\password_set.html:6
#: .\cookbook\templates\account\password_set.html:10
#: .\cookbook\templates\account\password_set.html:15
msgid "Set Password"
msgstr ""
#: .\cookbook\templates\account\signup.html:5
msgid "Register"
msgstr ""
#: .\cookbook\templates\account\signup.html:11
msgid "Create an Account"
msgstr ""
#: .\cookbook\templates\account\signup.html:41
msgid "I accept the follwoing"
msgstr ""
#: .\cookbook\templates\account\signup.html:44
#: .\cookbook\templates\socialaccount\signup.html:35
msgid "Terms and Conditions"
msgstr ""
#: .\cookbook\templates\account\signup.html:47
#: .\cookbook\templates\socialaccount\signup.html:38
msgid "and"
msgstr ""
#: .\cookbook\templates\account\signup.html:51
#: .\cookbook\templates\socialaccount\signup.html:42
msgid "Privacy Policy"
msgstr ""
#: .\cookbook\templates\account\signup.html:64
msgid "Create User"
msgstr ""
#: .\cookbook\templates\account\signup.html:68
msgid "Already have an account?"
msgstr ""
#: .\cookbook\templates\account\signup_closed.html:5
#: .\cookbook\templates\account\signup_closed.html:11
msgid "Sign Up Closed"
msgstr ""
#: .\cookbook\templates\account\signup_closed.html:13
msgid "We are sorry, but the sign up is currently closed."
msgstr ""
#: .\cookbook\templates\frontend\tandoor.html:15
msgid "Tandoor Recipe Manager"
msgstr ""
#: .\cookbook\templates\index.html:28
msgid "Search recipe ..."
msgstr ""
#: .\cookbook\templates\index.html:43
msgid "New Recipe"
msgstr ""
#: .\cookbook\templates\index.html:46
msgid "Import Recipe"
msgstr ""
#: .\cookbook\templates\index.html:52
msgid "Advanced Search"
msgstr ""
#: .\cookbook\templates\index.html:56
msgid "Reset Search"
msgstr ""
#: .\cookbook\templates\index.html:84
msgid "Last viewed"
msgstr ""
#: .\cookbook\templates\index.html:86
msgid "Recipes"
msgstr ""
#: .\cookbook\templates\index.html:93
msgid "Log in to view recipes"
msgstr ""
#: .\cookbook\templates\markdown_info.html:5
#: .\cookbook\templates\markdown_info.html:13
msgid "Markdown Info"
msgstr ""
#: .\cookbook\templates\markdown_info.html:14
msgid ""
"\n"
" Markdown is lightweight markup language that can be used to format "
"plain text easily.\n"
" This site uses the Python Markdown library to\n"
" convert your text into nice looking HTML. Its full markdown "
"documentation can be found\n"
" here.\n"
" An incomplete but most likely sufficient documentation can be found "
"below.\n"
" "
msgstr ""
#: .\cookbook\templates\markdown_info.html:25
msgid "Headers"
msgstr ""
#: .\cookbook\templates\markdown_info.html:54
msgid "Formatting"
msgstr ""
#: .\cookbook\templates\markdown_info.html:56
#: .\cookbook\templates\markdown_info.html:72
msgid "Line breaks are inserted by adding two spaces after the end of a line"
msgstr ""
#: .\cookbook\templates\markdown_info.html:57
#: .\cookbook\templates\markdown_info.html:73
msgid "or by leaving a blank line in between."
msgstr ""
#: .\cookbook\templates\markdown_info.html:59
#: .\cookbook\templates\markdown_info.html:74
msgid "This text is bold"
msgstr ""
#: .\cookbook\templates\markdown_info.html:60
#: .\cookbook\templates\markdown_info.html:75
msgid "This text is italic"
msgstr ""
#: .\cookbook\templates\markdown_info.html:61
#: .\cookbook\templates\markdown_info.html:77
msgid "Blockquotes are also possible"
msgstr ""
#: .\cookbook\templates\markdown_info.html:84
msgid "Lists"
msgstr ""
#: .\cookbook\templates\markdown_info.html:85
msgid ""
"Lists can ordered or unordered. It is important to leave a blank line "
"before the list!"
msgstr ""
#: .\cookbook\templates\markdown_info.html:87
#: .\cookbook\templates\markdown_info.html:108
msgid "Ordered List"
msgstr ""
#: .\cookbook\templates\markdown_info.html:89
#: .\cookbook\templates\markdown_info.html:90
#: .\cookbook\templates\markdown_info.html:91
#: .\cookbook\templates\markdown_info.html:110
#: .\cookbook\templates\markdown_info.html:111
#: .\cookbook\templates\markdown_info.html:112
msgid "unordered list item"
msgstr ""
#: .\cookbook\templates\markdown_info.html:93
#: .\cookbook\templates\markdown_info.html:114
msgid "Unordered List"
msgstr ""
#: .\cookbook\templates\markdown_info.html:95
#: .\cookbook\templates\markdown_info.html:96
#: .\cookbook\templates\markdown_info.html:97
#: .\cookbook\templates\markdown_info.html:116
#: .\cookbook\templates\markdown_info.html:117
#: .\cookbook\templates\markdown_info.html:118
msgid "ordered list item"
msgstr ""
#: .\cookbook\templates\markdown_info.html:125
msgid "Images & Links"
msgstr ""
#: .\cookbook\templates\markdown_info.html:126
msgid ""
"Links can be formatted with Markdown. This application also allows to paste "
"links directly into markdown fields without any formatting."
msgstr ""
#: .\cookbook\templates\markdown_info.html:132
#: .\cookbook\templates\markdown_info.html:145
msgid "This will become an image"
msgstr ""
#: .\cookbook\templates\markdown_info.html:152
msgid "Tables"
msgstr ""
#: .\cookbook\templates\markdown_info.html:153
msgid ""
"Markdown tables are hard to create by hand. It is recommended to use a table "
"editor like this one."
msgstr ""
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:171
#: .\cookbook\templates\markdown_info.html:177
msgid "Table"
msgstr ""
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:172
msgid "Header"
msgstr ""
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:178
msgid "Cell"
msgstr ""
#: .\cookbook\templates\no_groups_info.html:5
#: .\cookbook\templates\no_groups_info.html:12
msgid "No Permissions"
msgstr ""
#: .\cookbook\templates\no_groups_info.html:17
msgid "You do not have any groups and therefor cannot use this application."
msgstr ""
#: .\cookbook\templates\no_groups_info.html:18
#: .\cookbook\templates\no_perm_info.html:15
msgid "Please contact your administrator."
msgstr ""
#: .\cookbook\templates\no_perm_info.html:5
#: .\cookbook\templates\no_perm_info.html:12
msgid "No Permission"
msgstr ""
#: .\cookbook\templates\no_perm_info.html:15
msgid ""
"You do not have the required permissions to view this page or perform this "
"action."
msgstr ""
#: .\cookbook\templates\offline.html:5
msgid "Offline"
msgstr ""
#: .\cookbook\templates\openid\login.html:27
#: .\cookbook\templates\socialaccount\authentication_error.html:27
msgid "Back"
msgstr ""
#: .\cookbook\templates\rest_framework\api.html:5
msgid "Recipe Home"
msgstr ""
#: .\cookbook\templates\rest_framework\api.html:11
msgid "API Documentation"
msgstr ""
#: .\cookbook\templates\search_info.html:5
#: .\cookbook\templates\search_info.html:9
msgid "Search Settings"
msgstr ""
#: .\cookbook\templates\search_info.html:10
msgid ""
"\n"
" Creating the best search experience is complicated and weighs "
"heavily on your personal configuration. \n"
" Changing any of the search settings can have significant impact on "
"the speed and quality of the results.\n"
" Search Methods, Trigrams and Full Text Search configurations are "
"only available if you are using Postgres for your database.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:19
msgid "Search Methods"
msgstr ""
#: .\cookbook\templates\search_info.html:23
msgid ""
" \n"
" Full text searches attempt to normalize the words provided to "
"match common variants. For example: 'forked', 'forking', 'forks' will all "
"normalize to 'fork'.\n"
" There are several methods available, described below, that will "
"control how the search behavior should react when multiple words are "
"searched.\n"
" Full technical details on how these operate can be viewed on Postgresql's website.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:29
msgid ""
" \n"
" Simple searches ignore punctuation and common words such as "
"'the', 'a', 'and'. And will treat separate words as required.\n"
" Searching for 'apple or flour' will return any recipe that "
"includes both 'apple' and 'flour' anywhere in the fields that have been "
"selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:34
msgid ""
" \n"
" Phrase searches ignore punctuation, but will search for all of "
"the words in the exact order provided.\n"
" Searching for 'apple or flour' will only return a recipe that "
"includes the exact phrase 'apple or flour' in any of the fields that have "
"been selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:39
msgid ""
" \n"
" Web searches simulate functionality found on many web search "
"sites supporting special syntax.\n"
" Placing quotes around several words will convert those words "
"into a phrase.\n"
" 'or' is recognized as searching for the word (or phrase) "
"immediately before 'or' OR the word (or phrase) directly after.\n"
" '-' is recognized as searching for recipes that do not include "
"the word (or phrase) that comes immediately after. \n"
" For example searching for 'apple pie' or cherry -butter will "
"return any recipe that includes the phrase 'apple pie' or the word "
"'cherry' \n"
" in any field included in the full text search but exclude any "
"recipe that has the word 'butter' in any field included.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:48
msgid ""
" \n"
" Raw search is similar to Web except will take puncuation "
"operators such as '|', '&' and '()'\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:59
msgid ""
" \n"
" Another approach to searching that also requires Postgresql is "
"fuzzy search or trigram similarity. A trigram is a group of three "
"consecutive characters.\n"
" For example searching for 'apple' will create x trigrams 'app', "
"'ppl', 'ple' and will create a score of how closely words match the "
"generated trigrams.\n"
" One benefit of searching trigams is that a search for 'sandwich' "
"will find misspelled words such as 'sandwhich' that would be missed by other "
"methods.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:69
msgid "Search Fields"
msgstr ""
#: .\cookbook\templates\search_info.html:73
msgid ""
" \n"
" Unaccent is a special case in that it enables searching a field "
"'unaccented' for each search style attempting to ignore accented values. \n"
" For example when you enable unaccent for 'Name' any search "
"(starts with, contains, trigram) will attempt the search ignoring accented "
"characters.\n"
" \n"
" For the other options, you can enable search on any or all "
"fields and they will be combined together with an assumed 'OR'.\n"
" For example enabling 'Name' for Starts With, 'Name' and "
"'Description' for Partial Match and 'Ingredients' and 'Keywords' for Full "
"Search\n"
" and searching for 'apple' will generate a search that will "
"return recipes that have:\n"
" - A recipe name that starts with 'apple'\n"
" - OR a recipe name that contains 'apple'\n"
" - OR a recipe description that contains 'apple'\n"
" - OR a recipe that will have a full text search match ('apple' "
"or 'apples') in ingredients\n"
" - OR a recipe that will have a full text search match in "
"Keywords\n"
"\n"
" Combining too many fields in too many types of search can have a "
"negative impact on performance, create duplicate results or return "
"unexpected results.\n"
" For example, enabling fuzzy search or partial matches will "
"interfere with web search methods. \n"
" Searching for 'apple -pie' with fuzzy search and full text "
"search will return the recipe Apple Pie. Though it is not included in the "
"full text results, it does match the trigram results.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:95
msgid "Search Index"
msgstr ""
#: .\cookbook\templates\search_info.html:99
msgid ""
" \n"
" Trigram search and Full Text Search both rely on database "
"indexes to perform effectively. \n"
" You can rebuild the indexes on all fields in the Admin page for "
"Recipes and selecting all recipes and running 'rebuild index for selected "
"recipes'\n"
" You can also rebuild indexes at the command line by executing "
"the management command 'python manage.py rebuildindex'\n"
" "
msgstr ""
#: .\cookbook\templates\setup.html:6
msgid "Cookbook Setup"
msgstr ""
#: .\cookbook\templates\setup.html:14
msgid "Setup"
msgstr ""
#: .\cookbook\templates\setup.html:15
msgid ""
"To start using this application you must first create a superuser account."
msgstr ""
#: .\cookbook\templates\setup.html:20
msgid "Create Superuser account"
msgstr ""
#: .\cookbook\templates\socialaccount\authentication_error.html:7
#: .\cookbook\templates\socialaccount\authentication_error.html:23
msgid "Social Network Login Failure"
msgstr ""
#: .\cookbook\templates\socialaccount\authentication_error.html:25
msgid ""
"An error occurred while attempting to login via your social network account."
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:4
#: .\cookbook\templates\socialaccount\connections.html:7
msgid "Account Connections"
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:10
msgid ""
"You can sign in to your account using any of the following third party\n"
" accounts:"
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:44
msgid ""
"You currently have no social network accounts connected to this account."
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:47
msgid "Add a 3rd Party Account"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:5
#: .\cookbook\templates\socialaccount\signup.html:5
msgid "Signup"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:9
#, python-format
msgid "Connect %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:11
#, python-format
msgid "You are about to connect a new third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:13
#, python-format
msgid "Sign In Via %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:15
#, python-format
msgid "You are about to sign in using a third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:20
msgid "Continue"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:10
#, python-format
msgid ""
"You are about to use your\n"
" %(provider_name)s account to login to\n"
" %(site_name)s. As a final step, please complete the following form:"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:32
msgid "I accept the following"
msgstr ""
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:23
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:31
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:39
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:47
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:55
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:63
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:71
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:79
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:87
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:95
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:103
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:111
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:119
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:127
msgid "Sign in using"
msgstr ""
#: .\cookbook\templates\space_overview.html:6
msgid "Overview"
msgstr ""
#: .\cookbook\templates\space_overview.html:13
msgid "Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:17
msgid ""
"Recipes, foods, shopping lists and more are organized in spaces of one or "
"more people."
msgstr ""
#: .\cookbook\templates\space_overview.html:18
msgid ""
"You can either be invited into an existing space or create your own one."
msgstr ""
#: .\cookbook\templates\space_overview.html:25
msgid "Your Spaces"
msgstr ""
#: .\cookbook\templates\space_overview.html:53
msgid "Owner"
msgstr ""
#: .\cookbook\templates\space_overview.html:73
#: .\cookbook\templates\space_overview.html:83
msgid "Join Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:76
msgid "Join an existing space."
msgstr ""
#: .\cookbook\templates\space_overview.html:78
msgid ""
"To join an existing space either enter your invite token or click on the "
"invite link the space owner send you."
msgstr ""
#: .\cookbook\templates\space_overview.html:91
#: .\cookbook\templates\space_overview.html:100
msgid "Create Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:94
msgid "Create your own recipe space."
msgstr ""
#: .\cookbook\templates\space_overview.html:96
msgid "Start your own recipe space and invite other users to it."
msgstr ""
#: .\cookbook\templates\system.html:23
msgid "System"
msgstr ""
#: .\cookbook\templates\system.html:24
msgid ""
"\n"
" Tandoor Recipes is an open source free software application. It can "
"be found on\n"
" GitHub.\n"
" Changelogs can be found here.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:30
msgid "System Information"
msgstr ""
#: .\cookbook\templates\system.html:51
msgid ""
"\n"
" You need to execute version.py in your update "
"script to generate version information (done automatically in docker).\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:56
msgid "Plugins"
msgstr ""
#: .\cookbook\templates\system.html:67
msgid "Media Serving"
msgstr ""
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:123 .\cookbook\templates\system.html:134
msgid "Warning"
msgstr ""
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:125 .\cookbook\templates\system.html:134
msgid "Ok"
msgstr ""
#: .\cookbook\templates\system.html:70
msgid ""
"Serving media files directly using gunicorn/python is not recommend!\n"
" Please follow the steps described\n"
" here to update\n"
" your installation.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:76 .\cookbook\templates\system.html:91
#: .\cookbook\templates\system.html:104 .\cookbook\templates\system.html:115
#: .\cookbook\views\views.py:168
msgid "Everything is fine!"
msgstr ""
#: .\cookbook\templates\system.html:80
msgid "Secret Key"
msgstr ""
#: .\cookbook\templates\system.html:84
msgid ""
"\n"
" You do not have a SECRET_KEY configured in your "
".env file. Django defaulted to the\n"
" standard key\n"
" provided with the installation which is publicly know and "
"insecure! Please set\n"
" SECRET_KEY int the .env configuration "
"file.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:94
msgid "Debug Mode"
msgstr ""
#: .\cookbook\templates\system.html:98
msgid ""
"\n"
" This application is still running in debug mode. This is most "
"likely not needed. Turn of debug mode by\n"
" setting\n"
" DEBUG=0 int the .env configuration "
"file.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:107
msgid "Allowed Hosts"
msgstr ""
#: .\cookbook\templates\system.html:111
msgid ""
"\n"
" Your allowed hosts are configured to allow every host. This "
"might be ok in some setups but should be avoided. Please see the docs about "
"this.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:118
msgid "Database"
msgstr ""
#: .\cookbook\templates\system.html:121
msgid "Info"
msgstr ""
#: .\cookbook\templates\system.html:131 .\cookbook\templates\system.html:148
msgid "Migrations"
msgstr ""
#: .\cookbook\templates\system.html:137
msgid ""
"\n"
" Migrations should never fail!\n"
" Failed migrations will likely cause major parts of the app to "
"not function correctly.\n"
" If a migration fails make sure you are on the latest version and "
"if so please post the migration log and the overview below in a GitHub "
"issue.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "False"
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "True"
msgstr ""
#: .\cookbook\templates\system.html:268
msgid "Hide"
msgstr ""
#: .\cookbook\templates\system.html:271
msgid "Show"
msgstr ""
#: .\cookbook\templates\test2.html:6
msgid "Export Recipes"
msgstr ""
#: .\cookbook\templates\test2.html:14 .\cookbook\templates\test2.html:20
msgid "Export"
msgstr ""
#: .\cookbook\views\api.py:198 .\cookbook\views\api.py:326
msgid "Parameter updated_at incorrectly formatted"
msgstr ""
#: .\cookbook\views\api.py:351 .\cookbook\views\api.py:484
#, python-brace-format
msgid "No {self.basename} with id {pk} exists"
msgstr ""
#: .\cookbook\views\api.py:355
msgid "Cannot merge with the same object!"
msgstr ""
#: .\cookbook\views\api.py:362
#, python-brace-format
msgid "No {self.basename} with id {target} exists"
msgstr ""
#: .\cookbook\views\api.py:367
msgid "Cannot merge with child object!"
msgstr ""
#: .\cookbook\views\api.py:405
#, python-brace-format
msgid "{source.name} was merged successfully with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:411
#, python-brace-format
msgid "An error occurred attempting to merge {source.name} with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:493
#, python-brace-format
msgid "{child.name} was moved successfully to the root."
msgstr ""
#: .\cookbook\views\api.py:496 .\cookbook\views\api.py:514
msgid "An error occurred attempting to move "
msgstr ""
#: .\cookbook\views\api.py:499
msgid "Cannot move an object to itself!"
msgstr ""
#: .\cookbook\views\api.py:505
#, python-brace-format
msgid "No {self.basename} with id {parent} exists"
msgstr ""
#: .\cookbook\views\api.py:511
#, python-brace-format
msgid "{child.name} was moved successfully to parent {parent.name}"
msgstr ""
#: .\cookbook\views\api.py:992
#, python-brace-format
msgid "{obj.name} was removed from the shopping list."
msgstr ""
#: .\cookbook\views\api.py:997 .\cookbook\views\api.py:1627
#, python-brace-format
msgid "{obj.name} was added to the shopping list."
msgstr ""
#: .\cookbook\views\api.py:1239
msgid "Filter meal plans from date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1241
msgid "Filter meal plans to date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1244
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1404
msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1406
msgid "Query string matched (fuzzy) against object name."
msgstr ""
#: .\cookbook\views\api.py:1442
msgid ""
"Query string matched (fuzzy) against recipe name. In the future also "
"fulltext search."
msgstr ""
#: .\cookbook\views\api.py:1444
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr ""
#: .\cookbook\views\api.py:1445
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr ""
#: .\cookbook\views\api.py:1446
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1447
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1448
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1450
msgid "ID of food a recipe should have. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1451
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr ""
#: .\cookbook\views\api.py:1452
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1453
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr ""
#: .\cookbook\views\api.py:1454
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1456
msgid "ID of book a recipe should be in. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1457
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr ""
#: .\cookbook\views\api.py:1458
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1459
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr ""
#: .\cookbook\views\api.py:1460
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1462
msgid "ID of unit a recipe should have."
msgstr ""
#: .\cookbook\views\api.py:1464
msgid "Exact rating of recipe"
msgstr ""
#: .\cookbook\views\api.py:1465
msgid "Rating a recipe should have or greater."
msgstr ""
#: .\cookbook\views\api.py:1466
msgid "Rating a recipe should have or smaller."
msgstr ""
#: .\cookbook\views\api.py:1468
msgid "Filter recipes cooked X times."
msgstr ""
#: .\cookbook\views\api.py:1469
msgid "Filter recipes cooked X times or more."
msgstr ""
#: .\cookbook\views\api.py:1470
msgid "Filter recipes cooked X times or less."
msgstr ""
#: .\cookbook\views\api.py:1472
msgid "Filter recipes created on the given date."
msgstr ""
#: .\cookbook\views\api.py:1473
msgid "Filter recipes created on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1474
msgid "Filter recipes created on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1476 .\cookbook\views\api.py:1477
#: .\cookbook\views\api.py:1478
msgid "Filter recipes updated on the given date."
msgstr ""
#: .\cookbook\views\api.py:1480
msgid "Filter recipes last cooked on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1481
msgid "Filter recipes last cooked on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1483 .\cookbook\views\api.py:1484
msgid "Filter recipes lasts viewed on the given date."
msgstr ""
#: .\cookbook\views\api.py:1486
msgid "Filter recipes for ones created by the given user ID"
msgstr ""
#: .\cookbook\views\api.py:1487
msgid "If only internal recipes should be returned. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1488
msgid "Returns the results in randomized order. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1490
msgid ""
"Determines the order of the results. Options are: score,-score,name,-name,"
"lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-"
"created_at,lastviewed,-lastviewed"
msgstr ""
#: .\cookbook\views\api.py:1492
msgid "Returns new results first in search results. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1493
msgid ""
"Returns the given number of recently viewed recipes before search results "
"(if given)"
msgstr ""
#: .\cookbook\views\api.py:1494
msgid "ID of a custom filter. Returns all recipes matched by that filter."
msgstr ""
#: .\cookbook\views\api.py:1495
msgid "Filter recipes that can be made with OnHand food. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1773
msgid ""
"Return the PropertyTypes matching the property category. Repeat for "
"multiple."
msgstr ""
#: .\cookbook\views\api.py:1804 .\cookbook\views\api.py:1860
msgid "Returns only entries associated with the given mealplan id"
msgstr ""
#: .\cookbook\views\api.py:1858
msgid ""
"Returns only elements updated after the given timestamp in ISO 8601 format."
msgstr ""
#: .\cookbook\views\api.py:2031
msgid ""
"Return the Automations matching the automation type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2048
msgid ""
"Text field to store data that gets carried over to the UserSpace created "
"from the InviteLink"
msgstr ""
#: .\cookbook\views\api.py:2049
msgid "Only return InviteLinks that have not been used yet."
msgstr ""
#: .\cookbook\views\api.py:2076
msgid "Return the CustomFilters matching the model type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2176
msgid "Nothing to do."
msgstr ""
#: .\cookbook\views\api.py:2222
msgid "Invalid Url"
msgstr ""
#: .\cookbook\views\api.py:2228
msgid "Connection Refused."
msgstr ""
#: .\cookbook\views\api.py:2232
msgid "Bad URL Schema."
msgstr ""
#: .\cookbook\views\api.py:2257
msgid "No usable data could be found."
msgstr ""
#: .\cookbook\views\api.py:2286 .\cookbook\views\api.py:2434
msgid "You must select an AI provider to perform your request."
msgstr ""
#: .\cookbook\views\api.py:2293 .\cookbook\views\api.py:2441
msgid ""
"You don't have any credits remaining to use AI or AI features are not "
"enabled for your space."
msgstr ""
#: .\cookbook\views\api.py:2499 .\cookbook\views\api.py:2667
msgid "File is above space limit"
msgstr ""
#: .\cookbook\views\api.py:2522 .\cookbook\views\api.py:2684
msgid "Importing is not implemented for this provider"
msgstr ""
#: .\cookbook\views\api.py:2548
msgid ""
"The PDF Exporter is not enabled on this instance as it is still in an "
"experimental state."
msgstr ""
#: .\cookbook\views\api.py:2842
msgid "This feature is not yet available in the hosted version of tandoor!"
msgstr ""
#: .\cookbook\views\api.py:2863
msgid "Sync successful!"
msgstr ""
#: .\cookbook\views\api.py:2866
msgid "Error synchronizing with Storage"
msgstr ""
#: .\cookbook\views\views.py:89
msgid "This feature is not available in the demo version!"
msgstr ""
#: .\cookbook\views\views.py:110
msgid ""
"You have successfully created your own recipe space. Start by adding some "
"recipes or invite other people to join you."
msgstr ""
#: .\cookbook\views\views.py:171
#, python-format
msgid "PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!"
msgstr ""
#: .\cookbook\views\views.py:174
#, python-format
msgid "You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended"
msgstr ""
#: .\cookbook\views\views.py:178
msgid "Unable to determine PostgreSQL version."
msgstr ""
#: .\cookbook\views\views.py:182
msgid ""
"This application is not running with a Postgres database backend. This is ok "
"but not recommended as some features only work with postgres databases."
msgstr ""
#: .\cookbook\views\views.py:296
msgid ""
"The setup page can only be used to create the first "
"user! If you have forgotten your superuser credentials "
"please consult the django documentation on how to reset passwords."
msgstr ""
#: .\cookbook\views\views.py:304
msgid "Passwords dont match!"
msgstr ""
#: .\cookbook\views\views.py:312
msgid "User has been created, please login!"
msgstr ""
#: .\cookbook\views\views.py:352
msgid ""
"Reporting share links is not enabled for this instance. Please notify the "
"page administrator to report problems."
msgstr ""
#: .\cookbook\views\views.py:357
msgid ""
"Recipe sharing link has been disabled! For additional information please "
"contact the page administrator."
msgstr ""
#: .\cookbook\views\views.py:383
msgid "Manage recipes, shopping list, meal plans and more."
msgstr ""
#: .\cookbook\views\views.py:397 .\cookbook\views\views.py:398
msgid "Plan"
msgstr ""
#: .\cookbook\views\views.py:399
msgid "View your meal Plan"
msgstr ""
#: .\cookbook\views\views.py:418
msgid "View your shopping lists"
msgstr ""
================================================
FILE: cookbook/locale/ro/LC_MESSAGES/django.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-22 20:15+0200\n"
"PO-Revision-Date: 2025-02-16 14:58+0000\n"
"Last-Translator: Cots Partier \n"
"Language-Team: Romanian \n"
"Language: ro\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < "
"20)) ? 1 : 2;\n"
"X-Generator: Weblate 5.8.4\n"
#: .\cookbook\forms.py:50
msgid "Default"
msgstr "Standard"
#: .\cookbook\forms.py:77
msgid ""
"To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything."
msgstr ""
"Pentru a preveni duplicatele, rețetele cu același nume ca și cele existente "
"sunt ignorate. Bifați această casetă pentru a importa totul."
#: .\cookbook\forms.py:108
msgid "Maximum number of users for this space reached."
msgstr "Numărul maxim de utilizatori pentru acest spațiu atins."
#: .\cookbook\forms.py:114
msgid "Email address already taken!"
msgstr "Adresa de e-mail deja în uz!"
#: .\cookbook\forms.py:121
msgid ""
"An email address is not required but if present the invite link will be sent "
"to the user."
msgstr ""
"Nu este necesară o adresă de e-mail, dar dacă este prezentă, linkul de "
"invitație va fi trimis utilizatorului."
#: .\cookbook\forms.py:133
msgid "Name already taken."
msgstr "Nume deja în uz."
#: .\cookbook\forms.py:144 .\cookbook\forms.py:158
msgid "Accept Terms and Privacy"
msgstr "Acceptă condițiile și politicile de confidențialitate"
#: .\cookbook\helper\AllAuthCustomAdapter.py:41
msgid ""
"In order to prevent spam, the requested email was not send. Please wait a "
"few minutes and try again."
msgstr ""
"Pentru a preveni spam-ul, e-mailul solicitat nu a fost trimis. Vă rugăm să "
"așteptați câteva minute și încercați din nou."
#: .\cookbook\helper\permission_helper.py:165
#: .\cookbook\helper\permission_helper.py:186 .\cookbook\views\views.py:138
msgid "You are not logged in and therefore cannot view this page!"
msgstr ""
"Nu sunteți conectat și, prin urmare, nu puteți vizualiza această pagină!"
#: .\cookbook\helper\permission_helper.py:168
#: .\cookbook\helper\permission_helper.py:173
#: .\cookbook\helper\permission_helper.py:198
#: .\cookbook\helper\permission_helper.py:265
#: .\cookbook\helper\permission_helper.py:279
#: .\cookbook\helper\permission_helper.py:290
#: .\cookbook\helper\permission_helper.py:301
#: .\cookbook\helper\permission_helper.py:317
#: .\cookbook\helper\permission_helper.py:343
#: .\cookbook\helper\permission_helper.py:359
msgid "You do not have the required permissions to view this page!"
msgstr "Nu aveți permisiunile necesare pentru a vizualiza această pagină!"
#: .\cookbook\helper\permission_helper.py:191
#: .\cookbook\helper\permission_helper.py:214
#: .\cookbook\helper\permission_helper.py:236
#: .\cookbook\helper\permission_helper.py:251
msgid "You cannot interact with this object as it is not owned by you!"
msgstr ""
"Nu poți interacționa cu acest obiect, deoarece nu este deținut de tine!"
#: .\cookbook\helper\permission_helper.py:420
msgid "You have reached the maximum number of recipes for your space."
msgstr "Ai ajuns la numărul maxim de rețete pentru spațiul dvs."
#: .\cookbook\helper\permission_helper.py:432
msgid "You have more users than allowed in your space."
msgstr "Aveți mai mulți utilizatori decât este permis în spațiul dvs."
#: .\cookbook\helper\recipe_url_import.py:319
msgid "reverse rotation"
msgstr "rotație inversă"
#: .\cookbook\helper\recipe_url_import.py:320
msgid "careful rotation"
msgstr "rotire atentă"
#: .\cookbook\helper\recipe_url_import.py:321
msgid "knead"
msgstr "frământă"
#: .\cookbook\helper\recipe_url_import.py:322
msgid "thicken"
msgstr "se îngroașă"
#: .\cookbook\helper\recipe_url_import.py:323
msgid "warm up"
msgstr "încălzire"
#: .\cookbook\helper\recipe_url_import.py:324
msgid "ferment"
msgstr "ferment"
#: .\cookbook\helper\recipe_url_import.py:325
#, fuzzy
#| msgid "Last cooked"
msgid "slow cook"
msgstr "Ultima gătită"
#: .\cookbook\helper\recipe_url_import.py:326
msgid "egg boiler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:327
msgid "kettle"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:328
msgid "blend"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:329
msgid "pre-clean"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:330
msgid "high temperature"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:331
msgid "rice cooker"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:332
msgid "caramelize"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:333
msgid "peeler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:334
msgid "slicer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:335
msgid "grater"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:336
msgid "spiralizer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:337
msgid "sous-vide"
msgstr "sous-vide"
#: .\cookbook\helper\shopping_helper.py:150
msgid "You must supply a servings size"
msgstr "Trebuie să specificați dimensiunea porției"
#: .\cookbook\helper\template_helper.py:97
#: .\cookbook\helper\template_helper.py:99
#: .\cookbook\helper\template_helper.py:101
msgid "Could not parse template code."
msgstr "Nu s-a putut analiza codul șablonului."
#: .\cookbook\integration\copymethat.py:44
#: .\cookbook\integration\melarecipes.py:37
msgid "Favorite"
msgstr "Favorit"
#: .\cookbook\integration\copymethat.py:50
msgid "I made this"
msgstr "Am făcut acest lucru"
#: .\cookbook\integration\integration.py:238
msgid ""
"Importer expected a .zip file. Did you choose the correct importer type for "
"your data ?"
msgstr ""
"Importatorul se aștepta la un fișier.zip. Ați ales tipul corect de "
"importator pentru datele dvs.?"
#: .\cookbook\integration\integration.py:241
msgid ""
"An unexpected error occurred during the import. Please make sure you have "
"uploaded a valid file."
msgstr ""
"A apărut o eroare neașteptată în timpul importului. Asigurați-vă că ați "
"încărcat un fișier valid."
#: .\cookbook\integration\integration.py:246
msgid "The following recipes were ignored because they already existed:"
msgstr "Următoarele rețete au fost ignorate pentru că existau deja:"
#: .\cookbook\integration\integration.py:250
#, python-format
msgid "Imported %s recipes."
msgstr "%s rețete importate."
#: .\cookbook\integration\mealie1.py:210
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "Calories"
msgstr "Calorii"
#: .\cookbook\integration\mealie1.py:211
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
msgid "Carbohydrates"
msgstr "Carbohidrați"
#: .\cookbook\integration\mealie1.py:212
msgid "Cholesterol"
msgstr ""
#: .\cookbook\integration\mealie1.py:213
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
msgid "Fat"
msgstr "Grăsime"
#: .\cookbook\integration\mealie1.py:214
msgid "Fiber"
msgstr ""
#: .\cookbook\integration\mealie1.py:215
#, fuzzy
#| msgid "Proteins"
msgid "Protein"
msgstr "Proteine"
#: .\cookbook\integration\mealie1.py:216
msgid "Saturated Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:217
msgid "Sodium"
msgstr ""
#: .\cookbook\integration\mealie1.py:218
msgid "Sugar"
msgstr ""
#: .\cookbook\integration\mealie1.py:219
msgid "Trans Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:220
msgid "Unsaturated Fat"
msgstr ""
#: .\cookbook\integration\openeats.py:28
msgid "Recipe source:"
msgstr "Sursa rețetei:"
#: .\cookbook\integration\paprika.py:49
msgid "Notes"
msgstr "Note"
#: .\cookbook\integration\paprika.py:52
msgid "Nutritional Information"
msgstr "Informații nutriționale"
#: .\cookbook\integration\paprika.py:56
msgid "Source"
msgstr "Sursă"
#: .\cookbook\integration\recettetek.py:55
#: .\cookbook\integration\recipekeeper.py:70
msgid "Imported from"
msgstr "Importat din"
#: .\cookbook\integration\saffron.py:23
msgid "Servings"
msgstr "Porții"
#: .\cookbook\integration\saffron.py:25
msgid "Waiting time"
msgstr "Timp de așteptare"
#: .\cookbook\integration\saffron.py:27
msgid "Preparation Time"
msgstr "Timp de pregătire"
#: .\cookbook\integration\saffron.py:29 .\cookbook\templates\index.html:6
msgid "Cookbook"
msgstr "Bucate"
#: .\cookbook\integration\saffron.py:31
msgid "Section"
msgstr "Secțiune"
#: .\cookbook\management\commands\fix_duplicate_properties.py:15
msgid "Fixes foods with "
msgstr "Corectează alimentele cu "
#: .\cookbook\management\commands\rebuildindex.py:14
msgid "Rebuilds full text search index on Recipe"
msgstr "Reconstruiește indexul de căutare text complet pe rețetă"
#: .\cookbook\management\commands\rebuildindex.py:18
msgid "Only Postgresql databases use full text search, no index to rebuild"
msgstr ""
"Numai bazele de date Postgress utilizează ccăutarea textului integral, nici "
"un index de reconstruit"
#: .\cookbook\management\commands\rebuildindex.py:29
msgid "Recipe index rebuild complete."
msgstr "Index rețetă reconstruit complet."
#: .\cookbook\management\commands\rebuildindex.py:31
msgid "Recipe index rebuild failed."
msgstr "Reconstruirea index-ului rețetă nu a reușit."
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
msgid "Breakfast"
msgstr "Mic dejun"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:19
msgid "Lunch"
msgstr "Prânz"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:24
msgid "Dinner"
msgstr "Cină"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:29 .\cookbook\models.py:971
msgid "Other"
msgstr "Altele"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "g"
msgstr "g"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "Proteins"
msgstr "Proteine"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "kcal"
msgstr "kcal"
#: .\cookbook\models.py:325
msgid ""
"Maximum file storage for space in MB. 0 for unlimited, -1 to disable file "
"upload."
msgstr ""
"Spațiu maxim de stocare a fișierelor pentru spațiu în MB. 0 pentru "
"nelimitat, -1 pentru a dezactiva încărcarea fișierelor."
#: .\cookbook\models.py:513
msgid "Search"
msgstr "Căutare"
#: .\cookbook\models.py:514
msgid "Meal-Plan"
msgstr "Plan de alimentare"
#: .\cookbook\models.py:515
msgid "Books"
msgstr "Cărți"
#: .\cookbook\models.py:516 .\cookbook\views\views.py:416
#: .\cookbook\views\views.py:417
msgid "Shopping"
msgstr "Cumpărături"
#: .\cookbook\models.py:967
msgid "Nutrition"
msgstr "Nutriție"
#: .\cookbook\models.py:968
msgid "Allergen"
msgstr "Alergen"
#: .\cookbook\models.py:969
msgid "Price"
msgstr "Preț"
#: .\cookbook\models.py:970
msgid "Goal"
msgstr "Obiectiv"
#: .\cookbook\models.py:1467 .\cookbook\templates\search_info.html:28
msgid "Simple"
msgstr "Simplu"
#: .\cookbook\models.py:1468 .\cookbook\templates\search_info.html:33
msgid "Phrase"
msgstr "Frază"
#: .\cookbook\models.py:1469 .\cookbook\templates\search_info.html:38
msgid "Web"
msgstr "Web"
#: .\cookbook\models.py:1470 .\cookbook\templates\search_info.html:47
msgid "Raw"
msgstr "Crud"
#: .\cookbook\models.py:1525
msgid "Food Alias"
msgstr "Pseudonim produse alimentare"
#: .\cookbook\models.py:1526
msgid "Unit Alias"
msgstr "Pseudonim unități"
#: .\cookbook\models.py:1527
msgid "Keyword Alias"
msgstr "Pseudonim cuvânt cheie"
#: .\cookbook\models.py:1528
msgid "Description Replace"
msgstr "Înlocuire Descriere"
#: .\cookbook\models.py:1529
msgid "Instruction Replace"
msgstr "Înlocuire Instrucțiuni"
#: .\cookbook\models.py:1530
msgid "Never Unit"
msgstr "Unitate nulă"
#: .\cookbook\models.py:1531
msgid "Transpose Words"
msgstr "Schimbă Ordinea Cuvintelor"
#: .\cookbook\models.py:1532
msgid "Food Replace"
msgstr "Aliment echivalent"
#: .\cookbook\models.py:1533
msgid "Unit Replace"
msgstr "Unitate echivalentă"
#: .\cookbook\models.py:1534
msgid "Name Replace"
msgstr "Înlocuire Nume"
#: .\cookbook\models.py:1564
msgid "Recipe"
msgstr "Rețetă"
#: .\cookbook\models.py:1565
msgid "Food"
msgstr "Mâncare"
#: .\cookbook\models.py:1566
msgid "Keyword"
msgstr "Cuvânt cheie"
#: .\cookbook\serializer.py:262
msgid "File uploads are not enabled for this Space."
msgstr "Încărcările de fișiere nu sunt permise pentru acest spațiu."
#: .\cookbook\serializer.py:273
msgid "You have reached your file upload limit."
msgstr "Ați atins limita de încărcare a fișierelor."
#: .\cookbook\serializer.py:281
msgid "The given file type is not allowed."
msgstr ""
#: .\cookbook\serializer.py:421 .\cookbook\views\views.py:94
msgid ""
"You have the reached the maximum amount of spaces that can be owned by you."
msgstr "Ai ajuns la numărul maxim de spații pe care le poți deține."
#: .\cookbook\serializer.py:434
msgid "Space Name must be unique."
msgstr ""
#: .\cookbook\serializer.py:469
msgid "Cannot modify Space owner permission."
msgstr "Nu se poate modifica permisiunea proprietarului spațiului."
#: .\cookbook\serializer.py:1596
msgid "Hello"
msgstr "Bună"
#: .\cookbook\serializer.py:1596
msgid "You have been invited by "
msgstr "Ați fost invitat de "
#: .\cookbook\serializer.py:1598
msgid " to join their Tandoor Recipes space "
msgstr " pentru a vă alătura la spațiul lor de rețete Tandoor "
#: .\cookbook\serializer.py:1600
msgid "Click the following link to activate your account: "
msgstr "Faceți clic pe următorul link pentru a vă activa contul: "
#: .\cookbook\serializer.py:1602
msgid ""
"If the link does not work use the following code to manually join the space: "
msgstr ""
"Dacă linkul nu funcționează, utilizați următorul cod pentru a vă alătura "
"manual spațiului: "
#: .\cookbook\serializer.py:1604
msgid "The invitation is valid until "
msgstr "Invitația este valabilă până la "
#: .\cookbook\serializer.py:1606
msgid ""
"Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub "
msgstr ""
"Tandoor Recipes este un manager de rețete Open Source. Priviți pe GitHub "
#: .\cookbook\serializer.py:1609
msgid "Tandoor Recipes Invite"
msgstr "Invitație Tandoor Recipes"
#: .\cookbook\serializer.py:1813
msgid "Existing shopping list to update"
msgstr "Lista de cumpărături existentă de actualizat"
#: .\cookbook\serializer.py:1815
msgid ""
"List of ingredient IDs from the recipe to add, if not provided all "
"ingredients will be added."
msgstr ""
"ID-urile ingredientelor din rețetă pentru a fi adăugate, dacă nu sunt "
"specificate toate ingrediente vor fi adăugate."
#: .\cookbook\serializer.py:1817
msgid ""
"Providing a list_recipe ID and servings of 0 will delete that shopping list."
msgstr ""
"Furnizarea unui ID de rețetă și un număr de porții egal cu 0 va șterge lista "
"cumpărături."
#: .\cookbook\serializer.py:1826
msgid "Amount of food to add to the shopping list"
msgstr "Cantitatea de mâncare pentru a fi adăugată în lista cumpărături"
#: .\cookbook\serializer.py:1828
msgid "ID of unit to use for the shopping list"
msgstr "ID-ul unității pentru a fi utilizat în lista de cumpărături"
#: .\cookbook\serializer.py:1830
msgid "When set to true will delete all food from active shopping lists."
msgstr ""
"Când este activ se șterge toată mâncarea din listele de cumpărături active."
#: .\cookbook\templates\404.html:5
msgid "404 Error"
msgstr "Eroare 404"
#: .\cookbook\templates\404.html:18
msgid "The page you are looking for could not be found."
msgstr "Pagina pe care o căutați nu a putut fi găsită."
#: .\cookbook\templates\404.html:33
msgid "Take me Home"
msgstr "Du-ma acasă"
#: .\cookbook\templates\404.html:35
msgid "Report a Bug"
msgstr "Raportați o eroare"
#: .\cookbook\templates\account\email.html:6
#: .\cookbook\templates\account\email.html:10
msgid "E-mail Addresses"
msgstr "Adrese e-mail"
#: .\cookbook\templates\account\email.html:12
msgid "The following e-mail addresses are associated with your account:"
msgstr "Următoarele adrese de e-mail sunt asociate contului dvs.:"
#: .\cookbook\templates\account\email.html:29
msgid "Verified"
msgstr "Verificat"
#: .\cookbook\templates\account\email.html:31
msgid "Unverified"
msgstr "Neverificat"
#: .\cookbook\templates\account\email.html:33
msgid "Primary"
msgstr "Principal"
#: .\cookbook\templates\account\email.html:40
msgid "Make Primary"
msgstr "Setează ca principal"
#: .\cookbook\templates\account\email.html:42
msgid "Re-send Verification"
msgstr "Retrimite verificare"
#: .\cookbook\templates\account\email.html:43
#: .\cookbook\templates\socialaccount\connections.html:36
msgid "Remove"
msgstr "Elimină"
#: .\cookbook\templates\account\email.html:51
msgid "Warning:"
msgstr "Atenție:"
#: .\cookbook\templates\account\email.html:51
msgid ""
"You currently do not have any e-mail address set up. You should really add "
"an e-mail address so you can receive notifications, reset your password, etc."
msgstr ""
"În prezent, nu aveți nicio adresă de e-mail configurată. Ar trebui să "
"adăugați într-adevăr o adresă de e-mail, astfel încât să puteți primi "
"notificări, resetați parola etc."
#: .\cookbook\templates\account\email.html:57
msgid "Add E-mail Address"
msgstr "Adăugă adresa de E-mail"
#: .\cookbook\templates\account\email.html:62
msgid "Add E-mail"
msgstr "Adaugă E-mail"
#: .\cookbook\templates\account\email.html:72
msgid "Do you really want to remove the selected e-mail address?"
msgstr "Chiar doriți să eliminați adresa de e-mail selectată?"
#: .\cookbook\templates\account\email_confirm.html:6
#: .\cookbook\templates\account\email_confirm.html:10
msgid "Confirm E-mail Address"
msgstr "Confirmarea adresei E-mail"
#: .\cookbook\templates\account\email_confirm.html:16
#, python-format
msgid ""
"Please confirm that\n"
" %(email)s is an e-mail address "
"for user %(user_display)s\n"
" ."
msgstr ""
"Vă rugăm să confirmați că:\n"
" %(email)s este adresa de e-mail "
"a utilizatorului %(user_display)s\n"
" ."
#: .\cookbook\templates\account\email_confirm.html:22
msgid "Confirm"
msgstr "Confirmă"
#: .\cookbook\templates\account\email_confirm.html:29
#, python-format
msgid ""
"This e-mail confirmation link expired or is invalid. Please\n"
" issue a new e-mail confirmation "
"request."
msgstr ""
"Acest link de confirmare prin e-mail a expirat sau nu este valid. Vă rugăm\n"
" să emiteți o nouă solicitare de "
"confirmare prin e-mail."
#: .\cookbook\templates\account\login.html:8
#: .\cookbook\templates\openid\login.html:8
msgid "Login"
msgstr "Conectare"
#: .\cookbook\templates\account\login.html:15
#: .\cookbook\templates\account\login.html:31
#: .\cookbook\templates\account\password_reset.html:39
#: .\cookbook\templates\account\password_reset_done.html:31
#: .\cookbook\templates\account\signup.html:68
#: .\cookbook\templates\account\signup_closed.html:15
#: .\cookbook\templates\openid\login.html:15
#: .\cookbook\templates\openid\login.html:26
#: .\cookbook\templates\socialaccount\authentication_error.html:15
msgid "Sign In"
msgstr "Autentificare"
#: .\cookbook\templates\account\login.html:34
#: .\cookbook\templates\account\password_reset.html:41
#: .\cookbook\templates\account\password_reset_done.html:33
#: .\cookbook\templates\socialaccount\signup.html:8
#: .\cookbook\templates\socialaccount\signup.html:56
msgid "Sign Up"
msgstr "Înregistrare"
#: .\cookbook\templates\account\login.html:38
msgid "Lost your password?"
msgstr "V-ați pierdut parola?"
#: .\cookbook\templates\account\login.html:39
#: .\cookbook\templates\account\password_reset.html:29
msgid "Reset My Password"
msgstr "Resetarea parolei mele"
#: .\cookbook\templates\account\login.html:50
msgid "Social Login"
msgstr "Autentificare utilizând rețeaua socială"
#: .\cookbook\templates\account\login.html:51
msgid "You can use any of the following providers to sign in."
msgstr "Puteți utiliza oricare dintre următorii furnizori pentru a vă conecta."
#: .\cookbook\templates\account\logout.html:5
#: .\cookbook\templates\account\logout.html:9
#: .\cookbook\templates\account\logout.html:18
msgid "Sign Out"
msgstr "Deconectare"
#: .\cookbook\templates\account\logout.html:11
msgid "Are you sure you want to sign out?"
msgstr "Sunteți sigur că doriți să vă deconectați?"
#: .\cookbook\templates\account\password_change.html:6
#: .\cookbook\templates\account\password_change.html:10
#: .\cookbook\templates\account\password_change.html:15
#: .\cookbook\templates\account\password_reset_from_key.html:7
#: .\cookbook\templates\account\password_reset_from_key.html:13
#: .\cookbook\templates\account\password_reset_from_key_done.html:7
#: .\cookbook\templates\account\password_reset_from_key_done.html:13
msgid "Change Password"
msgstr "Schimbare parolă"
#: .\cookbook\templates\account\password_change.html:16
msgid "Forgot Password?"
msgstr "Ai uitat parola?"
#: .\cookbook\templates\account\password_reset.html:7
#: .\cookbook\templates\account\password_reset.html:13
#: .\cookbook\templates\account\password_reset_done.html:7
#: .\cookbook\templates\account\password_reset_done.html:18
msgid "Password Reset"
msgstr "Resetare parolă"
#: .\cookbook\templates\account\password_reset.html:24
msgid ""
"Forgotten your password? Enter your e-mail address below, and we'll send you "
"an e-mail allowing you to reset it."
msgstr ""
"V-ați uitat parola? Introduceți adresa de e-mail de mai jos și vă vom "
"trimite un e-mail care vă permite să o resetați."
#: .\cookbook\templates\account\password_reset.html:32
msgid "Password reset is disabled on this instance."
msgstr "Resetarea parolei este dezactivată în această instanță."
#: .\cookbook\templates\account\password_reset_done.html:25
msgid ""
"We have sent you an e-mail. Please contact us if you do not receive it "
"within a few minutes."
msgstr ""
"V-am trimis un e-mail. Vă rugăm să ne contactați dacă nu îl primiți în "
"câteva minute."
#: .\cookbook\templates\account\password_reset_from_key.html:13
msgid "Bad Token"
msgstr "Token invalid"
#: .\cookbook\templates\account\password_reset_from_key.html:25
#, python-format
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used.\n"
" Please request a new "
"password reset."
msgstr ""
"Linkul de resetare a parolei nu a fost valid, posibil pentru că a fost deja "
"utilizat.\n"
" Vă rugam să cereți o "
"nouă resetare a parolei."
#: .\cookbook\templates\account\password_reset_from_key.html:33
msgid "change password"
msgstr "schimbare parolă"
#: .\cookbook\templates\account\password_reset_from_key.html:36
#: .\cookbook\templates\account\password_reset_from_key_done.html:19
msgid "Your password is now changed."
msgstr "Parola este acum schimbată."
#: .\cookbook\templates\account\password_set.html:6
#: .\cookbook\templates\account\password_set.html:10
#: .\cookbook\templates\account\password_set.html:15
msgid "Set Password"
msgstr "Setare parolă"
#: .\cookbook\templates\account\signup.html:5
msgid "Register"
msgstr "Înregistrare"
#: .\cookbook\templates\account\signup.html:11
msgid "Create an Account"
msgstr "Create cont"
#: .\cookbook\templates\account\signup.html:41
msgid "I accept the follwoing"
msgstr "Accept următoarele"
#: .\cookbook\templates\account\signup.html:44
#: .\cookbook\templates\socialaccount\signup.html:35
msgid "Terms and Conditions"
msgstr "Termeni și condiții"
#: .\cookbook\templates\account\signup.html:47
#: .\cookbook\templates\socialaccount\signup.html:38
msgid "and"
msgstr "și"
#: .\cookbook\templates\account\signup.html:51
#: .\cookbook\templates\socialaccount\signup.html:42
msgid "Privacy Policy"
msgstr "Politica de confidențialitate"
#: .\cookbook\templates\account\signup.html:64
msgid "Create User"
msgstr "Creare utilizator"
#: .\cookbook\templates\account\signup.html:68
msgid "Already have an account?"
msgstr "Aveți deja un cont?"
#: .\cookbook\templates\account\signup_closed.html:5
#: .\cookbook\templates\account\signup_closed.html:11
msgid "Sign Up Closed"
msgstr "Înscrierea închisă"
#: .\cookbook\templates\account\signup_closed.html:13
msgid "We are sorry, but the sign up is currently closed."
msgstr "Ne pare rău, dar înscrierea este în prezent închisă."
#: .\cookbook\templates\frontend\tandoor.html:15
#, fuzzy
#| msgid "Tandoor Recipes Invite"
msgid "Tandoor Recipe Manager"
msgstr "Invitație Tandoor Recipes"
#: .\cookbook\templates\index.html:28
msgid "Search recipe ..."
msgstr "Căutare rețetă ..."
#: .\cookbook\templates\index.html:43
msgid "New Recipe"
msgstr "Rețetă nouă"
#: .\cookbook\templates\index.html:46
msgid "Import Recipe"
msgstr "Importă rețeta"
#: .\cookbook\templates\index.html:52
msgid "Advanced Search"
msgstr "Căutare avansată"
#: .\cookbook\templates\index.html:56
msgid "Reset Search"
msgstr "Resetarea căutării"
#: .\cookbook\templates\index.html:84
msgid "Last viewed"
msgstr "Ultima vizualizare"
#: .\cookbook\templates\index.html:86
msgid "Recipes"
msgstr "Rețete"
#: .\cookbook\templates\index.html:93
msgid "Log in to view recipes"
msgstr "Conectați-vă pentru a vizualiza rețetele"
#: .\cookbook\templates\markdown_info.html:5
#: .\cookbook\templates\markdown_info.html:13
msgid "Markdown Info"
msgstr "Informații Markdown"
#: .\cookbook\templates\markdown_info.html:14
msgid ""
"\n"
" Markdown is lightweight markup language that can be used to format "
"plain text easily.\n"
" This site uses the Python Markdown library to\n"
" convert your text into nice looking HTML. Its full markdown "
"documentation can be found\n"
" here.\n"
" An incomplete but most likely sufficient documentation can be found "
"below.\n"
" "
msgstr ""
"\n"
" Markdown este un limbaj de marcare ușor, care poate fi folosit "
"pentru formatarea textul simplu cu ușurință.\n"
" Acest site utilizează biblioteca Python Markdown pentru a\n"
" converti textului într-un document HTML frumos stilizat. "
"Documentația completă Markdown poate fi găsită\n"
" aici.\n"
" O documentație incompletă, dar cel mai probabil suficientă poate fi "
"găsită mai jos.\n"
" "
#: .\cookbook\templates\markdown_info.html:25
msgid "Headers"
msgstr "Anteturi"
#: .\cookbook\templates\markdown_info.html:54
msgid "Formatting"
msgstr "Formatare"
#: .\cookbook\templates\markdown_info.html:56
#: .\cookbook\templates\markdown_info.html:72
msgid "Line breaks are inserted by adding two spaces after the end of a line"
msgstr ""
"Sfârșiturile de linie sunt inserate prin adăugarea a două spații după "
"sfârșitul unei linii"
#: .\cookbook\templates\markdown_info.html:57
#: .\cookbook\templates\markdown_info.html:73
msgid "or by leaving a blank line in between."
msgstr "sau lăsând o linie goală între ele."
#: .\cookbook\templates\markdown_info.html:59
#: .\cookbook\templates\markdown_info.html:74
msgid "This text is bold"
msgstr "Acest text este îngroșat"
#: .\cookbook\templates\markdown_info.html:60
#: .\cookbook\templates\markdown_info.html:75
msgid "This text is italic"
msgstr "Acest text este cursiv"
#: .\cookbook\templates\markdown_info.html:61
#: .\cookbook\templates\markdown_info.html:77
msgid "Blockquotes are also possible"
msgstr "Ghilimelele bloc sunt, de asemenea, posibile"
#: .\cookbook\templates\markdown_info.html:84
msgid "Lists"
msgstr "Liste"
#: .\cookbook\templates\markdown_info.html:85
msgid ""
"Lists can ordered or unordered. It is important to leave a blank line "
"before the list!"
msgstr ""
"Listele pot fi ordonate sau neordonate. Este important să lăsați o linie "
"goală înainte de listă!"
#: .\cookbook\templates\markdown_info.html:87
#: .\cookbook\templates\markdown_info.html:108
msgid "Ordered List"
msgstr "Listă ordonată"
#: .\cookbook\templates\markdown_info.html:89
#: .\cookbook\templates\markdown_info.html:90
#: .\cookbook\templates\markdown_info.html:91
#: .\cookbook\templates\markdown_info.html:110
#: .\cookbook\templates\markdown_info.html:111
#: .\cookbook\templates\markdown_info.html:112
msgid "unordered list item"
msgstr "element a listei neordonate"
#: .\cookbook\templates\markdown_info.html:93
#: .\cookbook\templates\markdown_info.html:114
msgid "Unordered List"
msgstr "Listă neordonată"
#: .\cookbook\templates\markdown_info.html:95
#: .\cookbook\templates\markdown_info.html:96
#: .\cookbook\templates\markdown_info.html:97
#: .\cookbook\templates\markdown_info.html:116
#: .\cookbook\templates\markdown_info.html:117
#: .\cookbook\templates\markdown_info.html:118
msgid "ordered list item"
msgstr "element a listei ordonate"
#: .\cookbook\templates\markdown_info.html:125
msgid "Images & Links"
msgstr "Imagini și link-uri"
#: .\cookbook\templates\markdown_info.html:126
msgid ""
"Links can be formatted with Markdown. This application also allows to paste "
"links directly into markdown fields without any formatting."
msgstr ""
"Link-urile pot fi formatate cu Markdown. Această aplicație permite, de "
"asemenea, să lipiți linkuri direct în câmpurile de marcare fără nicio "
"formatare."
#: .\cookbook\templates\markdown_info.html:132
#: .\cookbook\templates\markdown_info.html:145
msgid "This will become an image"
msgstr "Aceasta va deveni o imagine"
#: .\cookbook\templates\markdown_info.html:152
msgid "Tables"
msgstr "Tabele"
#: .\cookbook\templates\markdown_info.html:153
msgid ""
"Markdown tables are hard to create by hand. It is recommended to use a table "
"editor like this one."
msgstr ""
"Tabelele Markdown sunt cu greu create de mână. Este recomandat folosirea "
"unor editoare de tabele precum acesta."
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:171
#: .\cookbook\templates\markdown_info.html:177
msgid "Table"
msgstr "Tabel"
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:172
msgid "Header"
msgstr "Antet"
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:178
msgid "Cell"
msgstr "Celulă"
#: .\cookbook\templates\no_groups_info.html:5
#: .\cookbook\templates\no_groups_info.html:12
msgid "No Permissions"
msgstr "Fără permisiuni"
#: .\cookbook\templates\no_groups_info.html:17
msgid "You do not have any groups and therefor cannot use this application."
msgstr ""
"Nu aveți nici un grup și de aceea nu se poate utiliza această aplicație."
#: .\cookbook\templates\no_groups_info.html:18
#: .\cookbook\templates\no_perm_info.html:15
msgid "Please contact your administrator."
msgstr "Vă rugăm să contactați administratorul."
#: .\cookbook\templates\no_perm_info.html:5
#: .\cookbook\templates\no_perm_info.html:12
msgid "No Permission"
msgstr "Fără permisiune"
#: .\cookbook\templates\no_perm_info.html:15
msgid ""
"You do not have the required permissions to view this page or perform this "
"action."
msgstr ""
"Nu aveți permisiunile necesare pentru a vizualiza această pagină sau pentru "
"a efectua această acțiune."
#: .\cookbook\templates\offline.html:5
msgid "Offline"
msgstr "Offline"
#: .\cookbook\templates\openid\login.html:27
#: .\cookbook\templates\socialaccount\authentication_error.html:27
msgid "Back"
msgstr "Înapoi"
#: .\cookbook\templates\rest_framework\api.html:5
msgid "Recipe Home"
msgstr "Rețetă acasă"
#: .\cookbook\templates\rest_framework\api.html:11
msgid "API Documentation"
msgstr "Documentare API"
#: .\cookbook\templates\search_info.html:5
#: .\cookbook\templates\search_info.html:9
msgid "Search Settings"
msgstr "Setări de căutare"
#: .\cookbook\templates\search_info.html:10
msgid ""
"\n"
" Creating the best search experience is complicated and weighs "
"heavily on your personal configuration. \n"
" Changing any of the search settings can have significant impact on "
"the speed and quality of the results.\n"
" Search Methods, Trigrams and Full Text Search configurations are "
"only available if you are using Postgres for your database.\n"
" "
msgstr ""
"\n"
" Crearea celei mai bune experiențe de căutare este complicată și "
"cântărește foarte mult asupra configurației personale. \n"
" Modificarea oricăreia dintre setările de căutare poate avea un "
"impact semnificativ asupra vitezei și calității rezultatelor.\n"
" Configurațiile metode de căutare, trigrame și căutare text complet "
"sunt disponibile numai dacă utilizați Postgres pentru baza de date.\n"
" "
#: .\cookbook\templates\search_info.html:19
msgid "Search Methods"
msgstr "Metode de căutare"
#: .\cookbook\templates\search_info.html:23
msgid ""
" \n"
" Full text searches attempt to normalize the words provided to "
"match common variants. For example: 'forked', 'forking', 'forks' will all "
"normalize to 'fork'.\n"
" There are several methods available, described below, that will "
"control how the search behavior should react when multiple words are "
"searched.\n"
" Full technical details on how these operate can be viewed on Postgresql's website.\n"
" "
msgstr ""
" \n"
" Căutările de text complet încearcă să normalizeze cuvintele "
"furnizate pentru a se potrivi variantelor comune. De exemplu: 'bifurcat', "
"'furcă', 'furculițe' se vor normaliza la 'furculiță'.\n"
" Există mai multe metode disponibile, descrise mai jos, care vor "
"controla modul în care comportamentul de căutare ar trebui să reacționeze "
"atunci când sunt căutate mai multe cuvinte.\n"
" Detalii tehnice complete cu privire la modul în care acestea "
"funcționează pot fi vizualizate pe website-ul "
"Postgresql.\n"
" "
#: .\cookbook\templates\search_info.html:29
msgid ""
" \n"
" Simple searches ignore punctuation and common words such as "
"'the', 'a', 'and'. And will treat separate words as required.\n"
" Searching for 'apple or flour' will return any recipe that "
"includes both 'apple' and 'flour' anywhere in the fields that have been "
"selected for a full text search.\n"
" "
msgstr ""
" \n"
" Căutările simple ignoră semnele de punctuație și cuvintele "
"comune, cum ar fi 'de', 'și', 'a', și va trata cuvinte separate după cum "
"este necesar.\n"
" Căutarea 'măr sau făină' va returna orice rețetă care include "
"atât 'măr', cât și 'făină' oriunde în câmpurile care au fost selectate "
"pentru o căutare completă de text.\n"
" "
#: .\cookbook\templates\search_info.html:34
msgid ""
" \n"
" Phrase searches ignore punctuation, but will search for all of "
"the words in the exact order provided.\n"
" Searching for 'apple or flour' will only return a recipe that "
"includes the exact phrase 'apple or flour' in any of the fields that have "
"been selected for a full text search.\n"
" "
msgstr ""
" \n"
" Căutările de expresii ignoră semnele de punctuație, dar vor "
"căuta toate cuvintele în ordinea exactă furnizată.\n"
" Căutarea 'măr sau făină' va returna doar o rețetă care include "
"expresia exactă 'măr sau făină' în oricare dintre câmpurile care au fost "
"selectate pentru o căutare completă a textului.\n"
" "
#: .\cookbook\templates\search_info.html:39
msgid ""
" \n"
" Web searches simulate functionality found on many web search "
"sites supporting special syntax.\n"
" Placing quotes around several words will convert those words "
"into a phrase.\n"
" 'or' is recognized as searching for the word (or phrase) "
"immediately before 'or' OR the word (or phrase) directly after.\n"
" '-' is recognized as searching for recipes that do not include "
"the word (or phrase) that comes immediately after. \n"
" For example searching for 'apple pie' or cherry -butter will "
"return any recipe that includes the phrase 'apple pie' or the word "
"'cherry' \n"
" in any field included in the full text search but exclude any "
"recipe that has the word 'butter' in any field included.\n"
" "
msgstr ""
" \n"
" Căutările web simulează funcționalitatea găsită pe multe site-"
"uri de căutare web care acceptă sintaxa specială.\n"
" Plasarea ghilimelelor în jurul mai multor cuvinte va converti "
"aceste cuvinte într-o frază.\n"
" 'sau' este recunoscut pentru căutarea pentru cuvântul (sau "
"fraza) imediat înainte de 'sau' SAU cuvântul (sau fraza) direct după.\n"
" '-' este recunoscut pentru căutarea rețetelor care nu includ "
"cuvântul (sau fraza) care vine imediat după. \n"
" De exemplu, căutarea 'plăcintei cu mere' sau a untului de cireșe "
"va returna orice rețetă care include expresia 'plăcintă cu mere' sau "
"cuvântul 'cireș' \n"
" în orice câmp inclus în căutarea completă a textului, dar "
"exclude orice rețetă care are cuvântul 'unt' în orice câmp inclus.\n"
" "
#: .\cookbook\templates\search_info.html:48
msgid ""
" \n"
" Raw search is similar to Web except will take puncuation "
"operators such as '|', '&' and '()'\n"
" "
msgstr ""
" \n"
" Căutarea brută este similară cu web-ul, cu excepția caracterelor "
"speciale, cum ar fi '|', '&' și '()'\n"
" "
#: .\cookbook\templates\search_info.html:59
msgid ""
" \n"
" Another approach to searching that also requires Postgresql is "
"fuzzy search or trigram similarity. A trigram is a group of three "
"consecutive characters.\n"
" For example searching for 'apple' will create x trigrams 'app', "
"'ppl', 'ple' and will create a score of how closely words match the "
"generated trigrams.\n"
" One benefit of searching trigams is that a search for 'sandwich' "
"will find misspelled words such as 'sandwhich' that would be missed by other "
"methods.\n"
" "
msgstr ""
" \n"
" O altă abordare a căutării care, de asemenea, necesită "
"Postgresql este căutarea vagă, sau similitudinea trigramelor. O trigramă "
"este un grup de trei caractere consecutive.\n"
" De exemplu, căutarea 'mărului' va crea x trigrame 'măr', 'ăru', "
"'rul' și va crea un scor al cât de strâns se potrivesc cuvintele cu "
"trigramele generate.\n"
" Un beneficiu de căutare a trigramelor este că o căutare pentru "
"'plăcintă' va găsi cuvinte scrise greșit, cum ar fi \"plăcine\", care ar fi "
"ratat de alte metode.\n"
" "
#: .\cookbook\templates\search_info.html:69
msgid "Search Fields"
msgstr "Câmpuri de căutare"
#: .\cookbook\templates\search_info.html:73
msgid ""
" \n"
" Unaccent is a special case in that it enables searching a field "
"'unaccented' for each search style attempting to ignore accented values. \n"
" For example when you enable unaccent for 'Name' any search "
"(starts with, contains, trigram) will attempt the search ignoring accented "
"characters.\n"
" \n"
" For the other options, you can enable search on any or all "
"fields and they will be combined together with an assumed 'OR'.\n"
" For example enabling 'Name' for Starts With, 'Name' and "
"'Description' for Partial Match and 'Ingredients' and 'Keywords' for Full "
"Search\n"
" and searching for 'apple' will generate a search that will "
"return recipes that have:\n"
" - A recipe name that starts with 'apple'\n"
" - OR a recipe name that contains 'apple'\n"
" - OR a recipe description that contains 'apple'\n"
" - OR a recipe that will have a full text search match ('apple' "
"or 'apples') in ingredients\n"
" - OR a recipe that will have a full text search match in "
"Keywords\n"
"\n"
" Combining too many fields in too many types of search can have a "
"negative impact on performance, create duplicate results or return "
"unexpected results.\n"
" For example, enabling fuzzy search or partial matches will "
"interfere with web search methods. \n"
" Searching for 'apple -pie' with fuzzy search and full text "
"search will return the recipe Apple Pie. Though it is not included in the "
"full text results, it does match the trigram results.\n"
" "
msgstr ""
" \n"
" Unaccent este un caz special prin faptul că permite căutarea "
"unui câmp 'neaccentuat' pentru fiecare stil de căutare care încearcă să "
"ignore valorile accentuate. \n"
" De exemplu, atunci când activați unaccent pentru 'Nume', orice "
"căutare (începe cu, conține, trigramă) va încerca căutarea ignorând "
"caracterele accentuate.\n"
" \n"
" Pentru celelalte opțiuni, puteți activa căutarea pe oricare sau "
"pe toate câmpurile și acestea vor fi combinate împreună cu un presupus "
"'SAU'.\n"
" De exemplu, activarea 'Nume' pentru începe cu, 'Nume' și "
"'Descriere' pentru potrivire parțială și 'Ingrediente' și 'Cuvinte cheie' "
"pentru căutare completă\n"
" și căutarea de 'mărului' va genera o căutare care va returna "
"rețete care au:\n"
" - Un nume de rețetă care începe cu 'măr'\n"
" - SAU un nume de rețetă care conține 'măr''\n"
" - SAU o descriere a rețetei care conține 'măr'\n"
" - SAU o rețetă care va avea o potrivire completă de căutare text "
"('măr' sau 'mere') în ingrediente\n"
" - SAU o rețetă care va avea un text complet de căutare se "
"potrivesc în cuvinte cheie\n"
"\n"
" Combinarea prea multor câmpuri în prea multe tipuri de căutare "
"poate avea un impact negativ asupra performanței, poate crea rezultate "
"dublate sau poate returna rezultate neașteptate.\n"
" De exemplu, activarea căutării vage sau a potrivirilor parțiale "
"va interfera cu metodele de căutare pe web. \n"
" Căutarea 'plăcinte -mere' cu căutare vagă și căutare text "
"complet va returna rețeta plăcintelor cu mere. Deși nu este inclus în "
"rezultatele textului complet, se potrivește cu rezultatele trigramei.\n"
" "
#: .\cookbook\templates\search_info.html:95
msgid "Search Index"
msgstr "Index de căutare"
#: .\cookbook\templates\search_info.html:99
msgid ""
" \n"
" Trigram search and Full Text Search both rely on database "
"indexes to perform effectively. \n"
" You can rebuild the indexes on all fields in the Admin page for "
"Recipes and selecting all recipes and running 'rebuild index for selected "
"recipes'\n"
" You can also rebuild indexes at the command line by executing "
"the management command 'python manage.py rebuildindex'\n"
" "
msgstr ""
" \n"
" Trigrama de căutare și full text search ambele se bazează pe "
"indexurile bazei de date pentru a efectua în mod eficient. \n"
" Puteți reconstrui indexurile pe toate câmpurile din pagina Admin "
"pentru rețete și selectând toate rețetele și rulând 'reconstrui index pentru "
"rețetele selectate'\n"
" De asemenea, puteți reconstrui indexurile la linia de comandă "
"executând comanda de gestionare 'python manage.py rebuildindex'\n"
" "
#: .\cookbook\templates\setup.html:6
msgid "Cookbook Setup"
msgstr "Setarea cărții de bucate"
#: .\cookbook\templates\setup.html:14
msgid "Setup"
msgstr "Setare"
#: .\cookbook\templates\setup.html:15
msgid ""
"To start using this application you must first create a superuser account."
msgstr ""
"Pentru a începe să utilizați această aplicație, trebuie mai întâi să creați "
"un cont superuser."
#: .\cookbook\templates\setup.html:20
msgid "Create Superuser account"
msgstr "Creează cont superuser"
#: .\cookbook\templates\socialaccount\authentication_error.html:7
#: .\cookbook\templates\socialaccount\authentication_error.html:23
msgid "Social Network Login Failure"
msgstr "Eșec la conectarea la rețeaua socială"
#: .\cookbook\templates\socialaccount\authentication_error.html:25
msgid ""
"An error occurred while attempting to login via your social network account."
msgstr ""
"S-a produs o eroare în timp ce se încearca autentificarea prin contul tău de "
"rețea socială."
#: .\cookbook\templates\socialaccount\connections.html:4
#: .\cookbook\templates\socialaccount\connections.html:7
msgid "Account Connections"
msgstr "Conexiuni de cont"
#: .\cookbook\templates\socialaccount\connections.html:10
msgid ""
"You can sign in to your account using any of the following third party\n"
" accounts:"
msgstr ""
"Vă puteți conecta la cont utilizând oricare dintre următoarele terțe părți\n"
" de conturi:"
#: .\cookbook\templates\socialaccount\connections.html:44
msgid ""
"You currently have no social network accounts connected to this account."
msgstr "În prezent, nu aveți conturi de rețea socială conectate la acest cont."
#: .\cookbook\templates\socialaccount\connections.html:47
msgid "Add a 3rd Party Account"
msgstr "Adăugarea unui cont a părților terțe"
#: .\cookbook\templates\socialaccount\login.html:5
#: .\cookbook\templates\socialaccount\signup.html:5
msgid "Signup"
msgstr "Înregistrare"
#: .\cookbook\templates\socialaccount\login.html:9
#, python-format
msgid "Connect %(provider)s"
msgstr "Conectează %(provider)s"
#: .\cookbook\templates\socialaccount\login.html:11
#, python-format
msgid "You are about to connect a new third party account from %(provider)s."
msgstr ""
"Sunteți pe cale să conectați un nou cont de terță parte din %(provider)s."
#: .\cookbook\templates\socialaccount\login.html:13
#, python-format
msgid "Sign In Via %(provider)s"
msgstr "Intră în cont prin %(provider)s"
#: .\cookbook\templates\socialaccount\login.html:15
#, python-format
msgid "You are about to sign in using a third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:20
msgid "Continue"
msgstr "Continuă"
#: .\cookbook\templates\socialaccount\signup.html:10
#, python-format
msgid ""
"You are about to use your\n"
" %(provider_name)s account to login to\n"
" %(site_name)s. As a final step, please complete the following form:"
msgstr ""
"Sunteți pe cale de a utiliza\n"
" contul %(provider_name)s pentru a vă conecta la \n"
" %(site_name)s. Ca un pas final, vă rugăm să completați următorul "
"formular:"
#: .\cookbook\templates\socialaccount\signup.html:32
#, fuzzy
#| msgid "I accept the follwoing"
msgid "I accept the following"
msgstr "Accept următoarele"
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:23
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:31
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:39
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:47
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:55
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:63
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:71
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:79
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:87
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:95
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:103
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:111
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:119
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:127
msgid "Sign in using"
msgstr "Conectați-vă utilizând"
#: .\cookbook\templates\space_overview.html:6
msgid "Overview"
msgstr "Sumar"
#: .\cookbook\templates\space_overview.html:13
msgid "Space"
msgstr "Spațiu"
#: .\cookbook\templates\space_overview.html:17
msgid ""
"Recipes, foods, shopping lists and more are organized in spaces of one or "
"more people."
msgstr ""
"Rețetele, alimentele, listele de cumpărături și multe altele sunt organizate "
"în spațiile uneia sau mai multor persoane."
#: .\cookbook\templates\space_overview.html:18
msgid ""
"You can either be invited into an existing space or create your own one."
msgstr ""
"Puteți fi invitat într-un spațiu existent sau puteți crea propriul spațiu."
#: .\cookbook\templates\space_overview.html:25
msgid "Your Spaces"
msgstr "Spațiul tău"
#: .\cookbook\templates\space_overview.html:53
msgid "Owner"
msgstr "Proprietar"
#: .\cookbook\templates\space_overview.html:73
#: .\cookbook\templates\space_overview.html:83
msgid "Join Space"
msgstr "Alăturați-vă spațiului"
#: .\cookbook\templates\space_overview.html:76
msgid "Join an existing space."
msgstr "Alăturați-vă unui spațiu existent."
#: .\cookbook\templates\space_overview.html:78
msgid ""
"To join an existing space either enter your invite token or click on the "
"invite link the space owner send you."
msgstr ""
"Pentru a vă alătura unui spațiu existent, introduceți simbolul de invitație "
"sau faceți clic pe linkul de invitație pe care vi-l trimite proprietarul "
"spațiului."
#: .\cookbook\templates\space_overview.html:91
#: .\cookbook\templates\space_overview.html:100
msgid "Create Space"
msgstr "Creare spațiu"
#: .\cookbook\templates\space_overview.html:94
msgid "Create your own recipe space."
msgstr "Creați-vă propriul spațiu de rețete."
#: .\cookbook\templates\space_overview.html:96
msgid "Start your own recipe space and invite other users to it."
msgstr ""
"Începeți propriul spațiu de rețete și invitați alți utilizatori la acesta."
#: .\cookbook\templates\system.html:23
msgid "System"
msgstr "Sistem"
#: .\cookbook\templates\system.html:24
#, fuzzy
#| msgid ""
#| "\n"
#| " Django Recipes is an open source free software application. It "
#| "can be found on\n"
#| " GitHub.\n"
#| " Changelogs can be found here.\n"
#| " "
msgid ""
"\n"
" Tandoor Recipes is an open source free software application. It can "
"be found on\n"
" GitHub.\n"
" Changelogs can be found here.\n"
" "
msgstr ""
"\n"
" Django Recipes este o aplicație software gratuită open source. "
"Acesta poate fi găsită pe\n"
" GitHub.\n"
" Istoricul modificării poate fi găsit aici.\n"
" "
#: .\cookbook\templates\system.html:30
msgid "System Information"
msgstr "Informații despre sistem"
#: .\cookbook\templates\system.html:51
msgid ""
"\n"
" You need to execute version.py in your update "
"script to generate version information (done automatically in docker).\n"
" "
msgstr ""
"\n"
" Trebuie să executați version.py în scripturile de "
"actualizare pentru a genera informații despre versiune (realizate automat în "
"docker).\n"
" "
#: .\cookbook\templates\system.html:56
msgid "Plugins"
msgstr ""
#: .\cookbook\templates\system.html:67
msgid "Media Serving"
msgstr "Livrare conținut media"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:123 .\cookbook\templates\system.html:134
msgid "Warning"
msgstr "Atenționare"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:125 .\cookbook\templates\system.html:134
msgid "Ok"
msgstr "Ok"
#: .\cookbook\templates\system.html:70
msgid ""
"Serving media files directly using gunicorn/python is not recommend!\n"
" Please follow the steps described\n"
" here to update\n"
" your installation.\n"
" "
msgstr ""
"Servirea fișierelor media direct folosind gunicorn/python nu este "
"recomandată !\n"
" Vă rugăm să urmați pașii descriși\n"
" aici pentru a actualiza\n"
" instalarea dvs..\n"
" "
#: .\cookbook\templates\system.html:76 .\cookbook\templates\system.html:91
#: .\cookbook\templates\system.html:104 .\cookbook\templates\system.html:115
#: .\cookbook\views\views.py:168
msgid "Everything is fine!"
msgstr "Totul este bine!"
#: .\cookbook\templates\system.html:80
msgid "Secret Key"
msgstr "Cheie secretă"
#: .\cookbook\templates\system.html:84
msgid ""
"\n"
" You do not have a SECRET_KEY configured in your "
".env file. Django defaulted to the\n"
" standard key\n"
" provided with the installation which is publicly know and "
"insecure! Please set\n"
" SECRET_KEY int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" Nu aveți un SECRET_KEY configurat în fișierul "
".env. Django utilizează implicit\n"
" cheia standard\n"
" prevazuta cu instalația care este cunoscuta public si nesigura! "
"Vă rugăm să setați\n"
" SECRET_KEY în fișierul de configurare .env"
"code>.\n"
" "
#: .\cookbook\templates\system.html:94
msgid "Debug Mode"
msgstr "Mod de depanare"
#: .\cookbook\templates\system.html:98
msgid ""
"\n"
" This application is still running in debug mode. This is most "
"likely not needed. Turn of debug mode by\n"
" setting\n"
" DEBUG=0 int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" Această aplicație se execută încă în modul de depanare. Acest "
"lucru nu este cel mai probabil necesar. Rândul său, modul de depanare prin\n"
" setarea\n"
" DEBUG=0 in zona .env a fișierului de "
"configurare.\n"
" "
#: .\cookbook\templates\system.html:107
msgid "Allowed Hosts"
msgstr "Domenii Permise"
#: .\cookbook\templates\system.html:111
msgid ""
"\n"
" Your allowed hosts are configured to allow every host. This "
"might be ok in some setups but should be avoided. Please see the docs about "
"this.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:118
msgid "Database"
msgstr "Bază de date"
#: .\cookbook\templates\system.html:121
msgid "Info"
msgstr "Informație"
#: .\cookbook\templates\system.html:131 .\cookbook\templates\system.html:148
msgid "Migrations"
msgstr "Migrări"
#: .\cookbook\templates\system.html:137
msgid ""
"\n"
" Migrations should never fail!\n"
" Failed migrations will likely cause major parts of the app to "
"not function correctly.\n"
" If a migration fails make sure you are on the latest version and "
"if so please post the migration log and the overview below in a GitHub "
"issue.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "False"
msgstr "Fals"
#: .\cookbook\templates\system.html:238
msgid "True"
msgstr "Adevărat"
#: .\cookbook\templates\system.html:268
msgid "Hide"
msgstr "Ascunde"
#: .\cookbook\templates\system.html:271
msgid "Show"
msgstr "Afișare jurnal"
#: .\cookbook\templates\test2.html:6
msgid "Export Recipes"
msgstr "Exportă rețete"
#: .\cookbook\templates\test2.html:14 .\cookbook\templates\test2.html:20
msgid "Export"
msgstr "Exportă"
#: .\cookbook\views\api.py:198 .\cookbook\views\api.py:326
msgid "Parameter updated_at incorrectly formatted"
msgstr "Parametrul updated_at formatat incorect"
#: .\cookbook\views\api.py:351 .\cookbook\views\api.py:484
#, python-brace-format
msgid "No {self.basename} with id {pk} exists"
msgstr "Nu există {self.basename} cu id {pk}"
#: .\cookbook\views\api.py:355
msgid "Cannot merge with the same object!"
msgstr "Nu se poate uni cu același obiect!"
#: .\cookbook\views\api.py:362
#, python-brace-format
msgid "No {self.basename} with id {target} exists"
msgstr "Nu există {self.basename} cu id {target}"
#: .\cookbook\views\api.py:367
msgid "Cannot merge with child object!"
msgstr "Nu se poate uni cu obiect copil!"
#: .\cookbook\views\api.py:405
#, python-brace-format
msgid "{source.name} was merged successfully with {target.name}"
msgstr "{source.name} a fost unit cu succes cu {target.name}"
#: .\cookbook\views\api.py:411
#, python-brace-format
msgid "An error occurred attempting to merge {source.name} with {target.name}"
msgstr ""
"A apărut o eroare la încercarea de a uni {source.name} cu {target.name}"
#: .\cookbook\views\api.py:493
#, python-brace-format
msgid "{child.name} was moved successfully to the root."
msgstr "{child.name} a fost mutat cu succes la rădăcină."
#: .\cookbook\views\api.py:496 .\cookbook\views\api.py:514
msgid "An error occurred attempting to move "
msgstr "A apărut o eroare la încercarea de a muta "
#: .\cookbook\views\api.py:499
msgid "Cannot move an object to itself!"
msgstr "Nu se poate muta un obiect la sine!"
#: .\cookbook\views\api.py:505
#, python-brace-format
msgid "No {self.basename} with id {parent} exists"
msgstr "Nu există {self.basename} cu id {parent}"
#: .\cookbook\views\api.py:511
#, python-brace-format
msgid "{child.name} was moved successfully to parent {parent.name}"
msgstr "{child.name} a fost mutat cu succes la părintele {parent.name}"
#: .\cookbook\views\api.py:992
#, python-brace-format
msgid "{obj.name} was removed from the shopping list."
msgstr "{obj.name} a fost șters din lista de cumpărături."
#: .\cookbook\views\api.py:997 .\cookbook\views\api.py:1627
#, python-brace-format
msgid "{obj.name} was added to the shopping list."
msgstr "{obj.name} a fost adăugat la lista de cumpărături."
#: .\cookbook\views\api.py:1239
#, fuzzy
#| msgid "Filter meal plans from date (inclusive) in the format of YYYY-MM-DD."
msgid "Filter meal plans from date (inclusive)."
msgstr ""
"Filtrează planurile de masă din data (inclusiv) în formatul AAAA-LL-ZZ."
#: .\cookbook\views\api.py:1241
#, fuzzy
#| msgid "Filter meal plans to date (inclusive) in the format of YYYY-MM-DD."
msgid "Filter meal plans to date (inclusive)."
msgstr ""
"Filtrează planurile de masă până la data (inclusiv) în formatul AAAA-LL-ZZ."
#: .\cookbook\views\api.py:1244
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
msgstr ""
"Filtrează planurile de masă cu ID-ul tipului de rețetă. Pentru mai multe "
"repetă parametrul."
#: .\cookbook\views\api.py:1404
msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgstr ""
"ID-ul rețetei din care face pasul face parte. Pentru mai multe repetă "
"parametrul."
#: .\cookbook\views\api.py:1406
msgid "Query string matched (fuzzy) against object name."
msgstr ""
#: .\cookbook\views\api.py:1442
msgid ""
"Query string matched (fuzzy) against recipe name. In the future also "
"fulltext search."
msgstr ""
#: .\cookbook\views\api.py:1444
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr ""
#: .\cookbook\views\api.py:1445
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr ""
#: .\cookbook\views\api.py:1446
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1447
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1448
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1450
msgid "ID of food a recipe should have. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1451
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr ""
#: .\cookbook\views\api.py:1452
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1453
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr ""
#: .\cookbook\views\api.py:1454
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1456
msgid "ID of book a recipe should be in. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1457
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr ""
#: .\cookbook\views\api.py:1458
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1459
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr ""
#: .\cookbook\views\api.py:1460
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1462
msgid "ID of unit a recipe should have."
msgstr ""
#: .\cookbook\views\api.py:1464
msgid "Exact rating of recipe"
msgstr ""
#: .\cookbook\views\api.py:1465
msgid "Rating a recipe should have or greater."
msgstr ""
#: .\cookbook\views\api.py:1466
msgid "Rating a recipe should have or smaller."
msgstr ""
#: .\cookbook\views\api.py:1468
msgid "Filter recipes cooked X times."
msgstr ""
#: .\cookbook\views\api.py:1469
msgid "Filter recipes cooked X times or more."
msgstr ""
#: .\cookbook\views\api.py:1470
msgid "Filter recipes cooked X times or less."
msgstr ""
#: .\cookbook\views\api.py:1472
msgid "Filter recipes created on the given date."
msgstr ""
#: .\cookbook\views\api.py:1473
msgid "Filter recipes created on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1474
msgid "Filter recipes created on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1476 .\cookbook\views\api.py:1477
#: .\cookbook\views\api.py:1478
msgid "Filter recipes updated on the given date."
msgstr ""
#: .\cookbook\views\api.py:1480
msgid "Filter recipes last cooked on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1481
msgid "Filter recipes last cooked on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1483 .\cookbook\views\api.py:1484
msgid "Filter recipes lasts viewed on the given date."
msgstr ""
#: .\cookbook\views\api.py:1486
msgid "Filter recipes for ones created by the given user ID"
msgstr ""
#: .\cookbook\views\api.py:1487
msgid "If only internal recipes should be returned. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1488
msgid "Returns the results in randomized order. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1490
msgid ""
"Determines the order of the results. Options are: score,-score,name,-name,"
"lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-"
"created_at,lastviewed,-lastviewed"
msgstr ""
#: .\cookbook\views\api.py:1492
msgid "Returns new results first in search results. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1493
msgid ""
"Returns the given number of recently viewed recipes before search results "
"(if given)"
msgstr ""
#: .\cookbook\views\api.py:1494
msgid "ID of a custom filter. Returns all recipes matched by that filter."
msgstr ""
#: .\cookbook\views\api.py:1495
msgid "Filter recipes that can be made with OnHand food. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1773
msgid ""
"Return the PropertyTypes matching the property category. Repeat for "
"multiple."
msgstr ""
#: .\cookbook\views\api.py:1804 .\cookbook\views\api.py:1860
msgid "Returns only entries associated with the given mealplan id"
msgstr ""
#: .\cookbook\views\api.py:1858
msgid ""
"Returns only elements updated after the given timestamp in ISO 8601 format."
msgstr ""
#: .\cookbook\views\api.py:2031
msgid ""
"Return the Automations matching the automation type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2048
msgid ""
"Text field to store data that gets carried over to the UserSpace created "
"from the InviteLink"
msgstr ""
#: .\cookbook\views\api.py:2049
msgid "Only return InviteLinks that have not been used yet."
msgstr ""
#: .\cookbook\views\api.py:2076
msgid "Return the CustomFilters matching the model type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2176
msgid "Nothing to do."
msgstr "Nimic de făcut."
#: .\cookbook\views\api.py:2222
msgid "Invalid Url"
msgstr ""
#: .\cookbook\views\api.py:2228
msgid "Connection Refused."
msgstr ""
#: .\cookbook\views\api.py:2232
msgid "Bad URL Schema."
msgstr ""
#: .\cookbook\views\api.py:2257
msgid "No usable data could be found."
msgstr "Nu au putut fi găsite date utilizabile."
#: .\cookbook\views\api.py:2286 .\cookbook\views\api.py:2434
msgid "You must select an AI provider to perform your request."
msgstr ""
#: .\cookbook\views\api.py:2293 .\cookbook\views\api.py:2441
msgid ""
"You don't have any credits remaining to use AI or AI features are not "
"enabled for your space."
msgstr ""
#: .\cookbook\views\api.py:2499 .\cookbook\views\api.py:2667
msgid "File is above space limit"
msgstr ""
#: .\cookbook\views\api.py:2522 .\cookbook\views\api.py:2684
msgid "Importing is not implemented for this provider"
msgstr "Importul nu este implementat pentru acest furnizor"
#: .\cookbook\views\api.py:2548
msgid ""
"The PDF Exporter is not enabled on this instance as it is still in an "
"experimental state."
msgstr ""
#: .\cookbook\views\api.py:2842
msgid "This feature is not yet available in the hosted version of tandoor!"
msgstr ""
"Această funcție nu este încă disponibilă în versiunea găzduită a tandoor!"
#: .\cookbook\views\api.py:2863
msgid "Sync successful!"
msgstr "Sincronizare de succes!"
#: .\cookbook\views\api.py:2866
msgid "Error synchronizing with Storage"
msgstr "Eroare la sincronizarea cu stocarea"
#: .\cookbook\views\views.py:89
msgid "This feature is not available in the demo version!"
msgstr "Această funcție nu este disponibilă în versiunea demo!"
#: .\cookbook\views\views.py:110
msgid ""
"You have successfully created your own recipe space. Start by adding some "
"recipes or invite other people to join you."
msgstr ""
"V-ați creat cu succes propriul spațiu de rețete. Începeți prin a adăuga "
"câteva rețete sau invitați alte persoane să vi se alăture."
#: .\cookbook\views\views.py:171
#, python-format
msgid "PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!"
msgstr ""
#: .\cookbook\views\views.py:174
#, python-format
msgid "You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended"
msgstr ""
#: .\cookbook\views\views.py:178
msgid "Unable to determine PostgreSQL version."
msgstr ""
#: .\cookbook\views\views.py:182
msgid ""
"This application is not running with a Postgres database backend. This is ok "
"but not recommended as some features only work with postgres databases."
msgstr ""
"Această aplicație nu se execută cu un backend de bază de date Postgres. "
"Acest lucru este ok, dar nu este recomandat deoarece unele caracteristicile "
"funcționează numai cu baze de date Postgres."
#: .\cookbook\views\views.py:296
msgid ""
"The setup page can only be used to create the first "
"user! If you have forgotten your superuser credentials "
"please consult the django documentation on how to reset passwords."
msgstr ""
"Pagina de inițializare poate fi utilizată numai pentru a crea primul "
"utilizator! Dacă ați uitat datele superutilizatorului, vă rugăm să "
"consultați documentația Django despre cum să resetați parolele."
#: .\cookbook\views\views.py:304
msgid "Passwords dont match!"
msgstr "Parolele nu se potrivesc!"
#: .\cookbook\views\views.py:312
msgid "User has been created, please login!"
msgstr "Utilizatorul a fost creat, vă rugăm să vă autentificați!"
#: .\cookbook\views\views.py:352
msgid ""
"Reporting share links is not enabled for this instance. Please notify the "
"page administrator to report problems."
msgstr ""
"Raportarea linkurilor de partajare nu este activată pentru această instanță. "
"Vă rugăm să anunțați administratorul paginii pentru a raporta probleme."
#: .\cookbook\views\views.py:357
msgid ""
"Recipe sharing link has been disabled! For additional information please "
"contact the page administrator."
msgstr ""
"Partajare link-urilor de rețete a fost dezactivată! Pentru informații "
"suplimentare, vă rugăm să contactați administratorul paginii."
#: .\cookbook\views\views.py:383
msgid "Manage recipes, shopping list, meal plans and more."
msgstr ""
#: .\cookbook\views\views.py:397 .\cookbook\views\views.py:398
msgid "Plan"
msgstr "Plan"
#: .\cookbook\views\views.py:399
msgid "View your meal Plan"
msgstr ""
#: .\cookbook\views\views.py:418
msgid "View your shopping lists"
msgstr "Vezi listele de cumpărături"
#~ msgid ""
#~ "Both fields are optional. If none are given the username will be "
#~ "displayed instead"
#~ msgstr ""
#~ "Ambele câmpuri sunt opționale. Dacă niciuna nu este setată, numele de "
#~ "utilizator va fi afișată în schimb"
#~ msgid "Name"
#~ msgstr "Nume"
#~ msgid "Keywords"
#~ msgstr "Cuvinte cheie"
#~ msgid "Preparation time in minutes"
#~ msgstr "Timp de pregătire în minute"
#~ msgid "Waiting time (cooking/baking) in minutes"
#~ msgstr "Timp de așteptare (gătit/coacere) în minute"
#~ msgid "Path"
#~ msgstr "Drum"
#~ msgid "Storage UID"
#~ msgstr "UID de stocare"
#~ msgid "Add your comment: "
#~ msgstr "Adaugă comentariul tău: "
#~ msgid "Leave empty for dropbox and enter app password for nextcloud."
#~ msgstr ""
#~ "Lăsați gol pentru dropbox și introduceți parola aplicației pentru "
#~ "nextcloud."
#~ msgid "Leave empty for nextcloud and enter api token for dropbox."
#~ msgstr ""
#~ "Lăsați gol pentru nextcloud și introduceți token-ul API pentru dropbox."
#~ msgid ""
#~ "Leave empty for dropbox and enter only base url for nextcloud (/"
#~ "remote.php/webdav/ is added automatically)"
#~ msgstr ""
#~ "Lăsați gol pentru dropbox și introduceți numai URL-ul de bază pentru "
#~ "nextcloud (/remote.php/webdav/ este adăugat automat)"
#~ msgid ""
#~ "Long Lived Access Token for your HomeAssistant instance"
#~ msgstr ""
#~ "Token the acces pentru instanța ta de HomeAssistant"
#~ msgid "Something like http://homeassistant.local:8123/api"
#~ msgstr "Asemănător cu http://homeassistant.local:8123/api"
#~ msgid "http://homeassistant.local:8123/api for example"
#~ msgstr "http://homeassistant.local:8123/api de exemplu"
#~ msgid "Storage"
#~ msgstr "Stocare"
#~ msgid "Active"
#~ msgstr "Activ"
#~ msgid "Search String"
#~ msgstr "Șir de căutare"
#~ msgid "File ID"
#~ msgstr "ID-ul fișierului"
#~ msgid ""
#~ "Determines how fuzzy a search is if it uses trigram similarity matching "
#~ "(e.g. low values mean more typos are ignored)."
#~ msgstr ""
#~ "Determină cât de vagă este o căutare dacă utilizează potrivirea "
#~ "similitudinii trigramelor (de exemplu, valorile scăzute înseamnă că mai "
#~ "multe greșeli de scriere sunt ignorate)."
#~ msgid ""
#~ "Select type method of search. Click here "
#~ "for full description of choices."
#~ msgstr ""
#~ "Selectează metoda de căutare. Apasă aici "
#~ "pentru descrierea completă a alegerilor."
#~ msgid ""
#~ "Use fuzzy matching on units, keywords and ingredients when editing and "
#~ "importing recipes."
#~ msgstr ""
#~ "Utilizați potrivirea vagă pe unități, cuvinte cheie și ingrediente atunci "
#~ "când editați și importați rețete."
#~ msgid ""
#~ "Fields to search ignoring accents. Selecting this option can improve or "
#~ "degrade search quality depending on language"
#~ msgstr ""
#~ "Câmpuri pentru a căuta ignorând accente. Selectarea acestei opțiuni "
#~ "poate îmbunătăți sau degrada calitatea căutării în funcție de limbă"
#~ msgid ""
#~ "Fields to search for partial matches. (e.g. searching for 'Pie' will "
#~ "return 'pie' and 'piece' and 'soapie')"
#~ msgstr ""
#~ "Câmpuri pentru a căuta potriviri parțiale. (de exemplu, căutarea "
#~ "'Plăcintei' va returna 'plăcintă' și 'plăci' și 'simplă')"
#~ msgid ""
#~ "Fields to search for beginning of word matches. (e.g. searching for 'sa' "
#~ "will return 'salad' and 'sandwich')"
#~ msgstr ""
#~ "Câmpuri pentru a căuta începutul potrivirilor de cuvinte. (ex. căutarea "
#~ "'sa' va returna 'salată' și 'sandwich')"
#~ msgid ""
#~ "Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find "
#~ "'recipe'.) Note: this option will conflict with 'web' and 'raw' methods "
#~ "of search."
#~ msgstr ""
#~ "Câmpuri pentru căutarea 'vagă'. (ex., căutarea 'rețetă' va găsi "
#~ "'rețetă'. Notă: această opțiune va intra în conflict cu metodele de "
#~ "căutare 'web' și 'raw'."
#~ msgid ""
#~ "Fields to full text search. Note: 'web', 'phrase', and 'raw' search "
#~ "methods only function with fulltext fields."
#~ msgstr ""
#~ "Câmpuri pentru căutarea completă a textului. Notă: metodele de căutare "
#~ "'web', 'frază' și 'raw' funcționează numai cu câmpuri full-text."
#~ msgid "Search Method"
#~ msgstr "Metodă de căutare"
#~ msgid "Fuzzy Lookups"
#~ msgstr "Căutare vagă"
#~ msgid "Ignore Accent"
#~ msgstr "Ignoră accent"
#~ msgid "Partial Match"
#~ msgstr "Potrivire parțială"
#~ msgid "Starts With"
#~ msgstr "Începe cu"
#~ msgid "Fuzzy Search"
#~ msgstr "Căutare vagă"
#~ msgid "Full Text"
#~ msgstr "Text complet"
#~ msgid " is part of a recipe step and cannot be deleted"
#~ msgstr " face parte dintr-un pas de rețetă și nu poate fi șters"
#~ msgid "Delete"
#~ msgstr "Ștergere"
#~ msgid "Settings"
#~ msgstr "Setări"
#~ msgid "Email"
#~ msgstr "E-mail"
#~ msgid "Password"
#~ msgstr "Parola"
#~ msgid "Foods"
#~ msgstr "Alimente"
#~ msgid "Units"
#~ msgstr "Unități"
#~ msgid "Supermarket"
#~ msgstr "Supermarket"
#~ msgid "Supermarket Category"
#~ msgstr "Categorie supermarket"
#~ msgid "Automations"
#~ msgstr "Automatizări"
#~ msgid "Files"
#~ msgstr "Fișiere"
#~ msgid "Batch Edit"
#~ msgstr "Editare în mod batch"
#~ msgid "History"
#~ msgstr "Istoric"
#~ msgid "Ingredient Editor"
#~ msgstr "Editor de Ingrediente"
#~ msgid "Properties"
#~ msgstr "Atribute"
#~ msgid "Unit Conversions"
#~ msgstr "Conversii de unități"
#~ msgid "Create"
#~ msgstr "Creează"
#~ msgid "External Recipes"
#~ msgstr "Rețete externe"
#~ msgid "Space Settings"
#~ msgstr "Setări spațiu"
#~ msgid "External Connectors"
#~ msgstr "Conectori externi"
#~ msgid "Admin"
#~ msgstr "Admin"
#~ msgid "Markdown Guide"
#~ msgstr "Ghid Markdown"
#~ msgid "GitHub"
#~ msgstr "GitHub"
#~ msgid "Translate Tandoor"
#~ msgstr "Traducere Tandoor"
#~ msgid "API Browser"
#~ msgstr "Browser API"
#~ msgid "Log out"
#~ msgstr "Deconectare"
#~ msgid "You are using the free version of Tandor"
#~ msgstr "Utilizați o versiune gratuită de Tandor"
#~ msgid "Upgrade Now"
#~ msgstr "Actualizează acum"
#~ msgid "Batch edit Category"
#~ msgstr "Editează categoria in mod batch"
#~ msgid "Batch edit Recipes"
#~ msgstr "Editează rețetele in mod batch"
#~ msgid "Add the specified keywords to all recipes containing a word"
#~ msgstr ""
#~ "Adăugați cuvintele cheie specificate la toate rețetele care conțin un "
#~ "cuvânt"
#~ msgid "Sync"
#~ msgstr "Sincronizare"
#~ msgid "Manage watched Folders"
#~ msgstr "Gestionarea folderelor urmărite"
#~ msgid ""
#~ "On this Page you can manage all storage folder locations that should be "
#~ "monitored and synced."
#~ msgstr ""
#~ "Pe această Pagină puteți gestiona toate locațiile folderului de stocare "
#~ "care ar trebui monitorizate și sincronizate."
#~ msgid "The path must be in the following format"
#~ msgstr "Calea trebuie să fie în următorul format"
#~ msgid "Save"
#~ msgstr "Salvare"
#~ msgid "Manage External Storage"
#~ msgstr "Gestionarea spațiului de stocare extern"
#~ msgid "Sync Now!"
#~ msgstr "Sincronizează acum!"
#~ msgid "Show Recipes"
#~ msgstr "Afișare rețete"
#~ msgid "Show Log"
#~ msgstr "Afișare jurnal"
#~ msgid "Importing Recipes"
#~ msgstr "Importă rețete"
#~ msgid ""
#~ "This can take a few minutes, depending on the number of recipes in sync, "
#~ "please wait."
#~ msgstr ""
#~ "Acest lucru poate dura câteva minute, în funcție de numărul de rețete "
#~ "sincronizate, vă rugăm să așteptați."
#~ msgid "Recipe Books"
#~ msgstr "Cărți de rețete"
#~ msgid "Import new Recipe"
#~ msgstr "Importă rețetă nouă"
#~ msgid "Edit Recipe"
#~ msgstr "Editează rețetă"
#, python-format
#~ msgid "Are you sure you want to delete the %(title)s: %(object)s "
#~ msgstr "Sunteți sigur că doriți să ștergeți %(title)s: %(object)s "
#~ msgid "This cannot be undone!"
#~ msgstr "Este ireversibil!"
#~ msgid "Protected"
#~ msgstr "Protejat"
#~ msgid "Cascade"
#~ msgstr "Cascadă"
#~ msgid "Cancel"
#~ msgstr "Anulare"
#~ msgid "Edit"
#~ msgstr "Editează"
#~ msgid "View"
#~ msgstr "Vizualizare"
#~ msgid "Delete original file"
#~ msgstr "Ștergerea fișierului original"
#~ msgid "List"
#~ msgstr "Listă"
#~ msgid "Filter"
#~ msgstr "Filtru"
#~ msgid "Import all"
#~ msgstr "Importare toate"
#~ msgid "New"
#~ msgstr "Nou"
#~ msgid "previous"
#~ msgstr "precedent"
#~ msgid "next"
#~ msgstr "următor"
#~ msgid "View Log"
#~ msgstr "Vizionare jurnal"
#~ msgid "Cook Log"
#~ msgstr "Jurnal de pregătire"
#~ msgid "Import"
#~ msgstr "Importă"
#~ msgid "Security Warning"
#~ msgstr "Avertizare de securitate"
#~ msgid ""
#~ "\n"
#~ " The Password and Token field are stored as plain text"
#~ "b> inside the database.\n"
#~ " This is necessary because they are needed to make API requests, "
#~ "but it also increases the risk of\n"
#~ " someone stealing it.
\n"
#~ " To limit the possible damage tokens or accounts with limited "
#~ "access can be used.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Câmpurile de Parolă și Token sunt stocate ca text "
#~ "simplu în interiorul bazei de date.\n"
#~ " Acest lucru este necesar deoarece acestea sunt necesare pentru a "
#~ "face cereri API, dar, de asemenea, crește riscul de\n"
#~ " furt.
\n"
#~ " Limitarea posibilelor deteriorări ale token-urilor sau conturilor "
#~ "cu acces limitat ce pot fi utilizate.\n"
#~ " "
#~ msgid "You are currently offline!"
#~ msgstr "Sunteți în prezent offline!"
#~ msgid ""
#~ "The recipes listed below are available for offline viewing because you "
#~ "have recently viewed them. Keep in mind that data might be outdated."
#~ msgstr ""
#~ "Rețetele enumerate mai jos sunt disponibile pentru vizualizare offline, "
#~ "deoarece le-ați vizualizat recent. Rețineți că datele pot fi învechite."
#~ msgid "Property Editor"
#~ msgstr "Editor atribute"
#~ msgid "Comments"
#~ msgstr "Comentarii"
#~ msgid "by"
#~ msgstr "de"
#~ msgid "Comment"
#~ msgstr "Comentariu"
#~ msgid ""
#~ "There are many options to configure the search depending on your personal "
#~ "preferences."
#~ msgstr ""
#~ "Există multe opțiuni pentru a configura căutarea în funcție de "
#~ "preferințele personale."
#~ msgid ""
#~ "Usually you do not need to configure any of them and can just "
#~ "stick with either the default or one of the following presets."
#~ msgstr ""
#~ "De obicei, nu aveți nevoie să configurați niciuna dintre ele și "
#~ "poate rămâne doar cu implicit sau una dintre următoarele presetări."
#~ msgid ""
#~ "If you do want to configure the search you can read about the different "
#~ "options here."
#~ msgstr ""
#~ "Dacă doriți să configurați căutarea, puteți citi despre diferitele "
#~ "opțiuni aici."
#~ msgid "Fuzzy"
#~ msgstr "Vag"
#~ msgid ""
#~ "Find what you need even if your search or the recipe contains typos. "
#~ "Might return more results than needed to make sure you find what you are "
#~ "looking for."
#~ msgstr ""
#~ "Găsiți ceea ce aveți nevoie, chiar dacă căutarea sau rețeta conține "
#~ "greșeli de scriere. S-ar putea returna mai multe rezultate decât este "
#~ "necesar pentru a vă asigura că găsiți ceea ce căutați."
#~ msgid "This is the default behavior"
#~ msgstr "Acesta este comportamentul implicit"
#~ msgid "Apply"
#~ msgstr "Aplică"
#~ msgid "Precise"
#~ msgstr "Precis"
#~ msgid ""
#~ "Allows fine control over search results but might not return results if "
#~ "too many spelling mistakes are made."
#~ msgstr ""
#~ "Permite un control fin asupra rezultatelor căutării, dar este posibil să "
#~ "nu returneze rezultate dacă se fac prea multe greșeli de ortografie."
#~ msgid "Perfect for large Databases"
#~ msgstr "Perfect pentru bazele de date mari"
#~ msgid "Social"
#~ msgstr "Social"
#~ msgid "Space Management"
#~ msgstr "Managementul spațiului"
#~ msgid "Space:"
#~ msgstr "Spațiu:"
#~ msgid "Manage Subscription"
#~ msgstr "Gestionarea abonamentului"
#~ msgid "Leave Space"
#~ msgstr "Părăsire spațiu"
#~ msgid "URL Import"
#~ msgstr "Importare URL"
#, python-format
#~ msgid "Batch edit done. %(count)d recipe was updated."
#~ msgid_plural "Batch edit done. %(count)d Recipes where updated."
#~ msgstr[0] ""
#~ "Editarea de tip batch completă. %(count)d rețetă a fost actualizată."
#~ msgstr[1] ""
#~ "Editarea de tip batch completă. %(count)d rețete au fost actualizate."
#~ msgstr[2] ""
#~ "Editarea de tip batch completă. %(count)d rețete au fost actualizate."
#~ msgid "Monitor"
#~ msgstr "Monitorizare"
#~ msgid "Storage Backend"
#~ msgstr "Backend de stocare"
#~ msgid ""
#~ "Could not delete this storage backend as it is used in at least one "
#~ "monitor."
#~ msgstr ""
#~ "Nu s-a putut șterge acest backend de stocare, deoarece este utilizat în "
#~ "cel puțin un supervizor."
#~ msgid "Connectors Config Backend"
#~ msgstr "Configurare Backend Conectori"
#~ msgid "Invite Link"
#~ msgstr "Link de invitare"
#~ msgid "Space Membership"
#~ msgstr "Membri spațiu"
#~ msgid "You cannot edit this storage!"
#~ msgstr "Nu puteți edita acest spațiu de stocare!"
#~ msgid "Storage saved!"
#~ msgstr "Spațiu de stocare salvat!"
#~ msgid "There was an error updating this storage backend!"
#~ msgstr "A existat o eroare la actualizarea acestui backend de stocare!"
#~ msgid "Config saved!"
#~ msgstr "Configurare salvată!"
#~ msgid "Changes saved!"
#~ msgstr "Modificări salvate!"
#~ msgid "Error saving changes!"
#~ msgstr "Eroare la salvarea modificărilor!"
#~ msgid "Import Log"
#~ msgstr "Jurnal de import"
#~ msgid "Discovery"
#~ msgstr "Descoperă"
#~ msgid "Shopping List"
#~ msgstr "Listă de cumpărături"
#~ msgid "Connector Config Backend"
#~ msgstr "Configurare Backend pentru Conector"
#~ msgid "Invite Links"
#~ msgstr "Link-uri de invitație"
#~ msgid "Supermarkets"
#~ msgstr "Supermarketuri"
#~ msgid "Shopping Categories"
#~ msgstr "Categorii de cumpărături"
#~ msgid "Custom Filters"
#~ msgstr "Filtre Personalizate"
#~ msgid "This feature is not enabled by the server admin!"
#~ msgstr "Această funcție nu a fost activată de către administrator!"
#~ msgid "Imported new recipe!"
#~ msgstr "Rețetă nouă importată!"
#~ msgid "There was an error importing this recipe!"
#~ msgstr "A existat o eroare la importul acestei rețete!"
#~ msgid "You do not have the required permissions to perform this action!"
#~ msgstr "Nu aveți permisiunile necesare pentru a efectua această acțiune!"
#~ msgid "Comment saved!"
#~ msgstr "Comentariu salvat!"
#~ msgid "You must select at least one field to search!"
#~ msgstr "Trebuie să selectați cel puțin un câmp pentru a căuta!"
#~ msgid ""
#~ "To use this search method you must select at least one full text search "
#~ "field!"
#~ msgstr ""
#~ "Pentru a utiliza această metodă de căutare, trebuie să selectați cel "
#~ "puțin un câmp de căutare text complet!"
#~ msgid "Fuzzy search is not compatible with this search method!"
#~ msgstr "Căutarea vagă nu este compatibilă cu această metodă de căutare!"
#~ msgid "Malformed Invite Link supplied!"
#~ msgstr "Link-ul de invitație este furnizat malformat!"
#~ msgid "Successfully joined space."
#~ msgstr "Spațiu alăturat cu succes."
#~ msgid "Invite Link not valid or already used!"
#~ msgstr "Link-ul de invitație nu este valid sau deja utilizat!"
#~ msgid "View your cookbooks"
#~ msgstr "Vizualizează cărțile de bucate"
#~ msgid "Default unit"
#~ msgstr "Unitate implicită"
#~ msgid "Use KJ"
#~ msgstr "Utilizare KJ"
#~ msgid "Theme"
#~ msgstr "Teme"
#~ msgid "Navbar color"
#~ msgstr "Culoarea barei de navigare"
#~ msgid "Sticky navbar"
#~ msgstr "Bară de navigare lipicioasă"
#~ msgid "Default page"
#~ msgstr "Pagină implicită"
#~ msgid "Show recent recipes"
#~ msgstr "Afișează rețete recente"
#~ msgid "Search style"
#~ msgstr "Căutare stil"
#~ msgid "Plan sharing"
#~ msgstr "Partajarea planurilor"
#~ msgid "Ingredient decimal places"
#~ msgstr "Zecimale ale ingredientelor"
#~ msgid "Shopping list auto sync period"
#~ msgstr "Perioada de sincronizare automată a listei de cumpărături"
#~ msgid ""
#~ "Color of the top navigation bar. Not all colors work with all themes, "
#~ "just try them out!"
#~ msgstr ""
#~ "Culoarea barei de navigare de sus. Nu toate culorile funcționează cu "
#~ "toate temele, doar încercați-le!"
#~ msgid ""
#~ "Default Unit to be used when inserting a new ingredient into a recipe."
#~ msgstr ""
#~ "Unitate implicită care trebuie utilizată la introducerea unui ingredient "
#~ "nou într-o rețetă."
#~ msgid ""
#~ "Enables support for fractions in ingredient amounts (e.g. convert "
#~ "decimals to fractions automatically)"
#~ msgstr ""
#~ "Permite suport pentru fracții în cantități de ingrediente (de exemplu, "
#~ "conversia zecimalelor în fracții automat)"
#~ msgid "Display nutritional energy amounts in joules instead of calories"
#~ msgstr ""
#~ "Afișați cantitățile de energie nutrițională în Jouli în loc de calorii"
#~ msgid ""
#~ "Users with whom newly created meal plan/shopping list entries should be "
#~ "shared by default."
#~ msgstr ""
#~ "Utilizatorii cu care intrările din planul alimentar/lista de cumpărături "
#~ "nou create ar trebui să fie partajate în mod implicit."
#~ msgid "Show recently viewed recipes on search page."
#~ msgstr "Afișați rețetele vizualizate recent pe pagina de căutare."
#~ msgid "Number of decimals to round ingredients."
#~ msgstr "Numărul de zecimale la ingrediente rotunde."
#~ msgid ""
#~ "If you want to be able to create and see comments underneath recipes."
#~ msgstr "Dacă doriți să puteți crea și vedea comentarii sub rețete."
#~ msgid ""
#~ "Setting to 0 will disable auto sync. When viewing a shopping list the "
#~ "list is updated every set seconds to sync changes someone else might have "
#~ "made. Useful when shopping with multiple people but might use a little "
#~ "bit of mobile data. If lower than instance limit it is reset when saving."
#~ msgstr ""
#~ "Setarea la 0 va dezactiva sincronizarea automată. Atunci când vizualizați "
#~ "o listă de cumpărături, lista este actualizată la fiecare câteva secunde "
#~ "setate pentru a sincroniza modificările pe care altcineva le-ar fi putut "
#~ "face. Util atunci când faceți cumpărături cu mai multe persoane, dar ar "
#~ "putea folosi un pic de date mobile. Dacă este mai mică decât limita "
#~ "instanței, aceasta este resetată la salvare."
#~ msgid "Makes the navbar stick to the top of the page."
#~ msgstr "Face ca bara de navigare să se lipească în partea de sus a paginii."
#~ msgid "You must provide at least a recipe or a title."
#~ msgstr "Trebuie să furnizați cel puțin o rețetă sau un titlu."
#~ msgid "You can list default users to share recipes with in the settings."
#~ msgstr ""
#~ "Puteți lista utilizatorii impliciți cu care să partajați rețete în setări."
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "Puteți utiliza markdown pentru a formata acest câmp. Vezi documentația aici"
#~ msgid "Small"
#~ msgstr "Mic"
#~ msgid "Large"
#~ msgstr "Mare"
#~ msgid "Text"
#~ msgstr "Text"
#~ msgid "Time"
#~ msgstr "Timp"
#~ msgid "File"
#~ msgstr "Fișier"
#~ msgid "Edit Ingredients"
#~ msgstr "Editează ingrediente"
#~ msgid ""
#~ "\n"
#~ " The following form can be used if, accidentally, two (or more) "
#~ "units or ingredients where created that should be\n"
#~ " the same.\n"
#~ " It merges two units or ingredients and updates all recipes using "
#~ "them.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Următorul formular poate fi utilizat dacă, accidental, două (sau "
#~ "mai multe) unități sau ingrediente în cazul în care au fost create care "
#~ "ar trebui să fie\n"
#~ " la fel.\n"
#~ " Îmbină două unități sau ingrediente și actualizează toate "
#~ "rețetele folosindu-le.\n"
#~ " "
#~ msgid "Are you sure that you want to merge these two units?"
#~ msgstr "Sunteți sigur că doriți să uniți aceste două unități?"
#~ msgid "Are you sure that you want to merge these two ingredients?"
#~ msgstr "Sunteți sigur că doriți să uniți aceste două ingrediente?"
#~ msgid "Import Recipes"
#~ msgstr "Importă rețete"
#~ msgid "Log Recipe Cooking"
#~ msgstr "Jurnal rețetă de gătire"
#~ msgid "All fields are optional and can be left empty."
#~ msgstr "Toate câmpurile sunt opționale și pot fi lăsate goale."
#~ msgid "Rating"
#~ msgstr "Evaluare"
#~ msgid "Close"
#~ msgstr "Închide"
#~ msgid "Open Recipe"
#~ msgstr "Deschide rețetă"
#~ msgid "Meal Plan View"
#~ msgstr "Vizualizarea planului de alimentare"
#~ msgid "Created by"
#~ msgstr "Creat de"
#~ msgid "Shared with"
#~ msgstr "Partajat cu"
#~ msgid "Never cooked before."
#~ msgstr "Niciodată gătită înainte."
#~ msgid "Other meals on this day"
#~ msgstr "Alte mese în această zi"
#~ msgid "Recipe Image"
#~ msgstr "Imagine rețetă"
#~ msgid "Preparation time ca."
#~ msgstr "Timp de pregătire cca."
#~ msgid "Waiting time ca."
#~ msgstr "Timp de așteptare cca."
#~ msgid "External"
#~ msgstr "Extern"
#~ msgid "Log Cooking"
#~ msgstr "Jurnal de pregătire"
#~ msgid "Account"
#~ msgstr "Cont"
#~ msgid "Preferences"
#~ msgstr "Preferințe"
#~ msgid "API-Settings"
#~ msgstr "Setări API"
#~ msgid "Search-Settings"
#~ msgstr "Setări de căutare"
#~ msgid "Name Settings"
#~ msgstr "Setări de nume"
#~ msgid "Account Settings"
#~ msgstr "Setpri cont"
#~ msgid "Emails"
#~ msgstr "E-mailuri"
#~ msgid "Language"
#~ msgstr "Limbă"
#~ msgid "Style"
#~ msgstr "Stil"
#~ msgid "API Token"
#~ msgstr "API Token"
#~ msgid ""
#~ "You can use both basic authentication and token based authentication to "
#~ "access the REST API."
#~ msgstr ""
#~ "Puteți utiliza atât autentificarea de bază, cât și autentificarea bazată "
#~ "pe token pentru a accesa REST API."
#~ msgid ""
#~ "Use the token as an Authorization header prefixed by the word token as "
#~ "shown in the following examples:"
#~ msgstr ""
#~ "Utilizați token-ul ca antet de autorizare prefixat de token-ul "
#~ "cuvântului, așa cum se arată în următoarele exemple:"
#~ msgid "or"
#~ msgstr "sau"
#~ msgid "Try the new shopping list"
#~ msgstr "Încercați noua listă de cumpărături"
#~ msgid "Search Recipe"
#~ msgstr "Căutare rețetă"
#~ msgid "Shopping Recipes"
#~ msgstr "Rețete de cumpărături"
#~ msgid "No recipes selected"
#~ msgstr "Nici o rețetă selectată"
#~ msgid "Entry Mode"
#~ msgstr "Mod de intrare"
#~ msgid "Add Entry"
#~ msgstr "Adăugare intrare"
#~ msgid "Amount"
#~ msgstr "Cantitate"
#~ msgid "Select"
#~ msgstr "Selectare"
#~ msgid "Select Food"
#~ msgstr "Selectare mâncare"
#~ msgid "Select Supermarket"
#~ msgstr "Selectare supermarket"
#~ msgid "Select User"
#~ msgstr "Selectare utilizator"
#~ msgid "Finished"
#~ msgstr "Finisat"
#~ msgid "You are offline, shopping list might not syncronize."
#~ msgstr ""
#~ "Sunteți offline, este posibil ca lista de cumpărături să nu se "
#~ "sincronizeze."
#~ msgid "Copy/Export"
#~ msgstr "Copiere/Exportare"
#~ msgid "List Prefix"
#~ msgstr "Prefix listă"
#~ msgid "Number of objects"
#~ msgstr "Numărul de obiecte"
#~ msgid "Recipe Imports"
#~ msgstr "Importuri de rețete"
#~ msgid "Objects stats"
#~ msgstr "Statistici obiecte"
#~ msgid "Recipes without Keywords"
#~ msgstr "Rețete fără cuvinte cheie"
#~ msgid "Internal Recipes"
#~ msgstr "Rețete interne"
#~ msgid "Invite User"
#~ msgstr "Invită utilizator"
#~ msgid "User"
#~ msgstr "Utilizator"
#~ msgid "Groups"
#~ msgstr "Grupe"
#~ msgid "admin"
#~ msgstr "admin"
#~ msgid "user"
#~ msgstr "utilizator"
#~ msgid "guest"
#~ msgstr "oaspete"
#~ msgid "remove"
#~ msgstr "eliminare"
#~ msgid "Update"
#~ msgstr "Actualizare"
#~ msgid "You cannot edit yourself."
#~ msgstr "Nu te poți edita singur pe tine."
#~ msgid "There are no members in your space yet!"
#~ msgstr "Încă nu există membri în spațiul dumneavoastră!"
#~ msgid "Stats"
#~ msgstr "Atribute"
#~ msgid "Statistics"
#~ msgstr "Statistici"
#~ msgid "Show Links"
#~ msgstr "Afișează link-uri"
#~ msgid "Drag me to your bookmarks to import recipes from anywhere"
#~ msgstr "Trageți-mă în marcajele dvs., pentru a importa rețete de oriunde"
#~ msgid "Bookmark Me!"
#~ msgstr "Marcaj-mă!"
#~ msgid "URL"
#~ msgstr "URL"
#~ msgid "App"
#~ msgstr "Aplicație"
#~ msgid "Enter website URL"
#~ msgstr "Introduceți adresa URL a site-ului web"
#~ msgid "Select recipe files to import or drop them here..."
#~ msgstr ""
#~ "Selectați fișierele de rețetă pentru a le importa sau a le fixa aici..."
#~ msgid "Paste json or html source here to load recipe."
#~ msgstr "Plasează sursa JSON sau HTML aici pentru a încărca rețeta."
#~ msgid "Preview Recipe Data"
#~ msgstr "Previzualizați datele rețetei"
#~ msgid ""
#~ "Drag recipe attributes from the right into the appropriate box below."
#~ msgstr ""
#~ "Trageți atributele rețetei din dreapta în caseta corespunzătoare de mai "
#~ "jos."
#~ msgid "Clear Contents"
#~ msgstr "Curățare conținut"
#~ msgid "Text dragged here will be appended to the name."
#~ msgstr "Textul tras aici va fi adăugat la nume."
#~ msgid "Text dragged here will be appended to the description."
#~ msgstr "Textul tras aici va fi adăugat la descriere."
#~ msgid "Keywords dragged here will be appended to current list"
#~ msgstr "Cuvintele cheie trase aici vor fi anexate la lista curentă"
#~ msgid "Image"
#~ msgstr "Imagine"
#~ msgid "Prep Time"
#~ msgstr "Timp de pregătire"
#~ msgid "Cook Time"
#~ msgstr "Timp de gătire"
#~ msgid "Ingredients dragged here will be appended to current list."
#~ msgstr "Ingredientele trase aici vor fi anexate la lista curentă."
#~ msgid ""
#~ "Recipe instructions dragged here will be appended to current instructions."
#~ msgstr ""
#~ "Instrucțiunile de rețetă trase aici vor fi anexate la instrucțiunile "
#~ "curente."
#~ msgid "Discovered Attributes"
#~ msgstr "Atribute descoperite"
#~ msgid ""
#~ "Drag recipe attributes from below into the appropriate box on the left. "
#~ "Click any node to display its full properties."
#~ msgstr ""
#~ "Trageți atributele rețetei de mai jos în caseta corespunzătoare din "
#~ "stânga. Faceți clic pe orice nod pentru a afișa proprietățile sale "
#~ "complete."
#~ msgid "Show Blank Field"
#~ msgstr "Afișare câmp gol"
#~ msgid "Blank Field"
#~ msgstr "Câmp gol"
#~ msgid "Items dragged to Blank Field will be appended."
#~ msgstr "Elementele trase în câmpul gol vor fi adăugate."
#~ msgid "Delete Text"
#~ msgstr "Ștergere text"
#~ msgid "Delete image"
#~ msgstr "Ștergere imagine"
#~ msgid "Recipe Name"
#~ msgstr "Nume rețetă"
#~ msgid "Recipe Description"
#~ msgstr "Descriere rețetă"
#~ msgid "Select one"
#~ msgstr "Selectați una"
#~ msgid "Note"
#~ msgstr "Notă"
#~ msgid "Add Keyword"
#~ msgstr "Adaugă cuvânt cheie"
#~ msgid "All Keywords"
#~ msgstr "Toate cuvintele cheie"
#~ msgid "Import all keywords, not only the ones already existing."
#~ msgstr "Importă toate cuvintele cheie, nu numai cele deja existente."
#~ msgid "Information"
#~ msgstr "Informație"
#~ msgid ""
#~ " Only websites containing ld+json or microdata information can currently\n"
#~ " be imported. Most big recipe pages "
#~ "support this. If you site cannot be imported but\n"
#~ " you think\n"
#~ " it probably has some kind of "
#~ "structured data feel free to post an example in the\n"
#~ " github issues."
#~ msgstr ""
#~ " Numai site-urile web care conțin informații despre ld+json sau "
#~ "informații microdata în prezent\n"
#~ " pot fi importate. Majoritatea pagini "
#~ "importante de rețete au suport pentru acest lucru. Dacă site-ul nu poate "
#~ "fi importat, dar\n"
#~ " crezi că\n"
#~ " acesta are, probabil, un fel de date "
#~ "structurate nu ezitați să posta un exemplu în\n"
#~ " github probleme."
#~ msgid "Google ld+json Info"
#~ msgstr "Informații Google ld+json"
#~ msgid "GitHub Issues"
#~ msgstr "GitHub Probleme"
#~ msgid "Recipe Markup Specification"
#~ msgstr "Specificații a Markup-ului de rețetă"
#, python-brace-format
#~ msgid "No {self.basename} with id {child} exists"
#~ msgstr "Nu există {self.basename} cu id {child}"
#~ msgid "The requested site provided malformed data and cannot be read."
#~ msgstr "Site-ul solicitat a furnizat date eronate și nu poate fi citit."
#~ msgid "The requested page could not be found."
#~ msgstr "Pagina solicitată nu a putut fi găsită."
#~ msgid ""
#~ "The requested site does not provide any recognized data format to import "
#~ "the recipe from."
#~ msgstr ""
#~ "Site-ul solicitat nu oferă niciun format de date recunoscut din care să "
#~ "importați rețeta."
#~ msgid "I couldn't find anything to do."
#~ msgstr "Nu am putut găsi nimic de făcut."
#~ msgid "Recipe Book"
#~ msgstr "Carte de rețete"
#~ msgid "Bookmarks"
#~ msgstr "Marcaje"
#~ msgid "Exporting is not implemented for this provider"
#~ msgstr "Exportul nu este implementat pentru acest furnizor"
#~ msgid "Shopping Lists"
#~ msgstr "Liste de cumpărături"
#~ msgid "Invite link successfully send to user."
#~ msgstr "Link-ul invita cu succes trimite la utilizator."
#~ msgid ""
#~ "You have sent too many emails, please share the link manually or wait a "
#~ "few hours."
#~ msgstr ""
#~ "Ați trimis prea multe e-mailuri, vă rugăm să partajați manual linkul sau "
#~ "să așteptați câteva ore."
#~ msgid "Email could not be sent to user. Please share the link manually."
#~ msgstr ""
#~ "E-mailul nu a putut fi trimis utilizatorului. Vă rugăm să partajați link-"
#~ "ul manual."
#~ msgid ""
#~ "You are already member of a space and therefore cannot join this one."
#~ msgstr ""
#~ "Sunteți deja membru al unui spațiu și, prin urmare, nu vă puteți alătura "
#~ "acestuia."
================================================
FILE: cookbook/locale/ru/LC_MESSAGES/django.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-22 20:15+0200\n"
"PO-Revision-Date: 2025-08-01 08:40+0000\n"
"Last-Translator: Aleksey \n"
"Language-Team: Russian \n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
"X-Generator: Weblate 5.8.4\n"
#: .\cookbook\forms.py:50
msgid "Default"
msgstr "По умолчанию"
#: .\cookbook\forms.py:77
msgid ""
"To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything."
msgstr ""
"Во избежание дублирования рецепты с тем же именем, что и существующие, "
"игнорируются. Установите этот флажок, чтобы импортировать все."
#: .\cookbook\forms.py:108
msgid "Maximum number of users for this space reached."
msgstr ""
"Достигнуто максимальное количество пользователей для этого пространства."
#: .\cookbook\forms.py:114
msgid "Email address already taken!"
msgstr "Этот email уже используется!"
#: .\cookbook\forms.py:121
msgid ""
"An email address is not required but if present the invite link will be sent "
"to the user."
msgstr ""
"Адрес электронной почты не обязателен, но если он указан, ссылка-приглашение "
"будет отправлена пользователю."
#: .\cookbook\forms.py:133
msgid "Name already taken."
msgstr "Имя уже используется."
#: .\cookbook\forms.py:144 .\cookbook\forms.py:158
msgid "Accept Terms and Privacy"
msgstr "Принять условия пользования и конфиденциальности"
#: .\cookbook\helper\AllAuthCustomAdapter.py:41
msgid ""
"In order to prevent spam, the requested email was not send. Please wait a "
"few minutes and try again."
msgstr ""
"Во избежание спама запрошенное электронное письмо не было отправлено. "
"Подождите несколько минут и попробуйте снова."
#: .\cookbook\helper\permission_helper.py:165
#: .\cookbook\helper\permission_helper.py:186 .\cookbook\views\views.py:138
msgid "You are not logged in and therefore cannot view this page!"
msgstr "Вы не вошли в систему и поэтому не можете просматривать эту страницу!"
#: .\cookbook\helper\permission_helper.py:168
#: .\cookbook\helper\permission_helper.py:173
#: .\cookbook\helper\permission_helper.py:198
#: .\cookbook\helper\permission_helper.py:265
#: .\cookbook\helper\permission_helper.py:279
#: .\cookbook\helper\permission_helper.py:290
#: .\cookbook\helper\permission_helper.py:301
#: .\cookbook\helper\permission_helper.py:317
#: .\cookbook\helper\permission_helper.py:343
#: .\cookbook\helper\permission_helper.py:359
msgid "You do not have the required permissions to view this page!"
msgstr "У вас нет необходимых разрешений для просмотра этой страницы!"
#: .\cookbook\helper\permission_helper.py:191
#: .\cookbook\helper\permission_helper.py:214
#: .\cookbook\helper\permission_helper.py:236
#: .\cookbook\helper\permission_helper.py:251
msgid "You cannot interact with this object as it is not owned by you!"
msgstr ""
"Вы не можете взаимодействовать с этим объектом, так как он не принадлежит "
"вам!"
#: .\cookbook\helper\permission_helper.py:420
msgid "You have reached the maximum number of recipes for your space."
msgstr "Вы достигли максимального количества рецептов для вашего пространства."
#: .\cookbook\helper\permission_helper.py:432
msgid "You have more users than allowed in your space."
msgstr "У вас больше пользователей, чем разрешено в вашем пространстве."
#: .\cookbook\helper\recipe_url_import.py:319
msgid "reverse rotation"
msgstr "обратное вращение"
#: .\cookbook\helper\recipe_url_import.py:320
msgid "careful rotation"
msgstr "осторожное вращение"
#: .\cookbook\helper\recipe_url_import.py:321
msgid "knead"
msgstr "замесить"
#: .\cookbook\helper\recipe_url_import.py:322
msgid "thicken"
msgstr "загустить"
#: .\cookbook\helper\recipe_url_import.py:323
msgid "warm up"
msgstr "разогреть"
#: .\cookbook\helper\recipe_url_import.py:324
msgid "ferment"
msgstr "ферментировать"
#: .\cookbook\helper\recipe_url_import.py:325
msgid "slow cook"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:326
msgid "egg boiler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:327
msgid "kettle"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:328
msgid "blend"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:329
msgid "pre-clean"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:330
msgid "high temperature"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:331
msgid "rice cooker"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:332
msgid "caramelize"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:333
msgid "peeler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:334
msgid "slicer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:335
msgid "grater"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:336
msgid "spiralizer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:337
msgid "sous-vide"
msgstr "су-вид"
#: .\cookbook\helper\shopping_helper.py:150
msgid "You must supply a servings size"
msgstr "Вы должны указать размер порции"
#: .\cookbook\helper\template_helper.py:97
#: .\cookbook\helper\template_helper.py:99
#: .\cookbook\helper\template_helper.py:101
msgid "Could not parse template code."
msgstr "Не удалось разобрать код шаблона."
#: .\cookbook\integration\copymethat.py:44
#: .\cookbook\integration\melarecipes.py:37
msgid "Favorite"
msgstr "Избранное"
#: .\cookbook\integration\copymethat.py:50
msgid "I made this"
msgstr "Я это сделал"
#: .\cookbook\integration\integration.py:238
msgid ""
"Importer expected a .zip file. Did you choose the correct importer type for "
"your data ?"
msgstr ""
"Импортер требует файл .zip. Вы выбрали правильный тип импортера для ваших "
"данных?"
#: .\cookbook\integration\integration.py:241
msgid ""
"An unexpected error occurred during the import. Please make sure you have "
"uploaded a valid file."
msgstr ""
"Во время импорта произошла непредвиденная ошибка. Пожалуйста, убедитесь, что "
"вы загрузили корректный файл."
#: .\cookbook\integration\integration.py:246
msgid "The following recipes were ignored because they already existed:"
msgstr "Следующие рецепты были проигнорированы, так как уже существуют:"
#: .\cookbook\integration\integration.py:250
#, python-format
msgid "Imported %s recipes."
msgstr "Импортировано %s рецептов."
#: .\cookbook\integration\mealie1.py:210
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "Calories"
msgstr "Калории"
#: .\cookbook\integration\mealie1.py:211
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
msgid "Carbohydrates"
msgstr "Углеводы"
#: .\cookbook\integration\mealie1.py:212
msgid "Cholesterol"
msgstr ""
#: .\cookbook\integration\mealie1.py:213
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
msgid "Fat"
msgstr "Толстый"
#: .\cookbook\integration\mealie1.py:214
msgid "Fiber"
msgstr ""
#: .\cookbook\integration\mealie1.py:215
#, fuzzy
#| msgid "Proteins"
msgid "Protein"
msgstr "Протеины"
#: .\cookbook\integration\mealie1.py:216
msgid "Saturated Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:217
msgid "Sodium"
msgstr ""
#: .\cookbook\integration\mealie1.py:218
msgid "Sugar"
msgstr ""
#: .\cookbook\integration\mealie1.py:219
msgid "Trans Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:220
msgid "Unsaturated Fat"
msgstr ""
#: .\cookbook\integration\openeats.py:28
msgid "Recipe source:"
msgstr "Источник рецепта:"
#: .\cookbook\integration\paprika.py:49
msgid "Notes"
msgstr "Заметки"
#: .\cookbook\integration\paprika.py:52
msgid "Nutritional Information"
msgstr "Пищевая ценность"
#: .\cookbook\integration\paprika.py:56
msgid "Source"
msgstr "Источник"
#: .\cookbook\integration\recettetek.py:55
#: .\cookbook\integration\recipekeeper.py:70
msgid "Imported from"
msgstr "Импортировано из"
#: .\cookbook\integration\saffron.py:23
#, fuzzy
msgid "Servings"
msgstr "Порции"
#: .\cookbook\integration\saffron.py:25
msgid "Waiting time"
msgstr "Время ожидания"
#: .\cookbook\integration\saffron.py:27
msgid "Preparation Time"
msgstr "Время подготовки"
#: .\cookbook\integration\saffron.py:29 .\cookbook\templates\index.html:6
msgid "Cookbook"
msgstr "Книга рецептов"
#: .\cookbook\integration\saffron.py:31
msgid "Section"
msgstr "Раздел"
#: .\cookbook\management\commands\fix_duplicate_properties.py:15
msgid "Fixes foods with "
msgstr "Исправляет продукты с "
#: .\cookbook\management\commands\rebuildindex.py:14
msgid "Rebuilds full text search index on Recipe"
msgstr "Перестраивает полнотекстовый индекс поиска для рецептов"
#: .\cookbook\management\commands\rebuildindex.py:18
msgid "Only Postgresql databases use full text search, no index to rebuild"
msgstr ""
"Полнотекстовый поиск используется только в базах данных Postgresql, индекс "
"перестраивать не нужно"
#: .\cookbook\management\commands\rebuildindex.py:29
msgid "Recipe index rebuild complete."
msgstr "Перестройка индекса рецептов завершена."
#: .\cookbook\management\commands\rebuildindex.py:31
msgid "Recipe index rebuild failed."
msgstr "Перестройка индекса рецептов не удалась."
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
msgid "Breakfast"
msgstr "Завтрак"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:19
msgid "Lunch"
msgstr "Обед"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:24
msgid "Dinner"
msgstr "Ужин"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:29 .\cookbook\models.py:971
msgid "Other"
msgstr "Другое"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "g"
msgstr "г"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "Proteins"
msgstr "Протеины"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "kcal"
msgstr "ккал"
#: .\cookbook\models.py:325
msgid ""
"Maximum file storage for space in MB. 0 for unlimited, -1 to disable file "
"upload."
msgstr ""
"Максимальный объем файлового хранилища в МБ. 0 — без ограничений, -1 — чтобы "
"отключить загрузку файлов."
#: .\cookbook\models.py:513
msgid "Search"
msgstr "Поиск"
#: .\cookbook\models.py:514
msgid "Meal-Plan"
msgstr "План питания"
#: .\cookbook\models.py:515
msgid "Books"
msgstr "Книги рецептов"
#: .\cookbook\models.py:516 .\cookbook\views\views.py:416
#: .\cookbook\views\views.py:417
msgid "Shopping"
msgstr "Покупки"
#: .\cookbook\models.py:967
msgid "Nutrition"
msgstr "Питательная ценность"
#: .\cookbook\models.py:968
msgid "Allergen"
msgstr "Аллерген"
#: .\cookbook\models.py:969
msgid "Price"
msgstr "Цена"
#: .\cookbook\models.py:970
msgid "Goal"
msgstr "Цель"
#: .\cookbook\models.py:1467 .\cookbook\templates\search_info.html:28
msgid "Simple"
msgstr "Простой"
#: .\cookbook\models.py:1468 .\cookbook\templates\search_info.html:33
msgid "Phrase"
msgstr ""
#: .\cookbook\models.py:1469 .\cookbook\templates\search_info.html:38
msgid "Web"
msgstr "Веб"
#: .\cookbook\models.py:1470 .\cookbook\templates\search_info.html:47
msgid "Raw"
msgstr "Необработанный"
#: .\cookbook\models.py:1525
msgid "Food Alias"
msgstr "Синоним продукта"
#: .\cookbook\models.py:1526
msgid "Unit Alias"
msgstr "Синоним единицы измерения"
#: .\cookbook\models.py:1527
#, fuzzy
#| msgid "Keywords"
msgid "Keyword Alias"
msgstr "Ключевые слова"
#: .\cookbook\models.py:1528
msgid "Description Replace"
msgstr "Замена описания"
#: .\cookbook\models.py:1529
msgid "Instruction Replace"
msgstr "Замена инструкции"
#: .\cookbook\models.py:1530
#, fuzzy
#| msgid "New Unit"
msgid "Never Unit"
msgstr "Без единицы"
#: .\cookbook\models.py:1531
msgid "Transpose Words"
msgstr "Транспонировать слова"
#: .\cookbook\models.py:1532
msgid "Food Replace"
msgstr "Замена продукта"
#: .\cookbook\models.py:1533
msgid "Unit Replace"
msgstr "Замена единицы измерения"
#: .\cookbook\models.py:1534
msgid "Name Replace"
msgstr "Замена названия"
#: .\cookbook\models.py:1564
msgid "Recipe"
msgstr "Рецепт"
#: .\cookbook\models.py:1565
msgid "Food"
msgstr "Продукт"
#: .\cookbook\models.py:1566
msgid "Keyword"
msgstr "Ключевое слово"
#: .\cookbook\serializer.py:262
msgid "File uploads are not enabled for this Space."
msgstr "Загрузка файлов не разрешена для этого пространства."
#: .\cookbook\serializer.py:273
msgid "You have reached your file upload limit."
msgstr "Вы достигли лимита загрузки файлов."
#: .\cookbook\serializer.py:281
msgid "The given file type is not allowed."
msgstr ""
#: .\cookbook\serializer.py:421 .\cookbook\views\views.py:94
msgid ""
"You have the reached the maximum amount of spaces that can be owned by you."
msgstr ""
#: .\cookbook\serializer.py:434
msgid "Space Name must be unique."
msgstr ""
#: .\cookbook\serializer.py:469
msgid "Cannot modify Space owner permission."
msgstr "Нельзя изменить разрешения владельца пространства."
#: .\cookbook\serializer.py:1596
msgid "Hello"
msgstr "Привет"
#: .\cookbook\serializer.py:1596
msgid "You have been invited by "
msgstr "Вас пригласил "
#: .\cookbook\serializer.py:1598
msgid " to join their Tandoor Recipes space "
msgstr " присоединиться к их пространству рецептов Tandoor "
#: .\cookbook\serializer.py:1600
msgid "Click the following link to activate your account: "
msgstr "Нажмите на следующую ссылку, чтобы активировать аккаунт: "
#: .\cookbook\serializer.py:1602
msgid ""
"If the link does not work use the following code to manually join the space: "
msgstr ""
"Если ссылка не работает, используйте следующий код для ручного присоединения "
"к пространству: "
#: .\cookbook\serializer.py:1604
msgid "The invitation is valid until "
msgstr "Приглашение действительно до "
#: .\cookbook\serializer.py:1606
msgid ""
"Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub "
msgstr ""
"Tandoor Recipes — это открытый менеджер рецептов. Посмотрите его на GitHub "
#: .\cookbook\serializer.py:1609
msgid "Tandoor Recipes Invite"
msgstr "Приглашение в Tandoor Recipes"
#: .\cookbook\serializer.py:1813
msgid "Existing shopping list to update"
msgstr "Существующий список покупок для обновления"
#: .\cookbook\serializer.py:1815
msgid ""
"List of ingredient IDs from the recipe to add, if not provided all "
"ingredients will be added."
msgstr ""
"Список ID ингредиентов из рецепта для добавления, если не указано — будут "
"добавлены все."
#: .\cookbook\serializer.py:1817
msgid ""
"Providing a list_recipe ID and servings of 0 will delete that shopping list."
msgstr ""
"Если указать ID списка рецептов и порции 0 — этот список покупок будет "
"удалён."
#: .\cookbook\serializer.py:1826
msgid "Amount of food to add to the shopping list"
msgstr "Количество продукта для добавления в список покупок"
#: .\cookbook\serializer.py:1828
msgid "ID of unit to use for the shopping list"
msgstr "ID единицы измерения для списка покупок"
#: .\cookbook\serializer.py:1830
msgid "When set to true will delete all food from active shopping lists."
msgstr "Если включено, удалит все продукты из активных списков покупок."
#: .\cookbook\templates\404.html:5
msgid "404 Error"
msgstr "Ошибка 404"
#: .\cookbook\templates\404.html:18
msgid "The page you are looking for could not be found."
msgstr "Страница, которую вы ищете, не найдена."
#: .\cookbook\templates\404.html:33
msgid "Take me Home"
msgstr "На главную"
#: .\cookbook\templates\404.html:35
msgid "Report a Bug"
msgstr "Сообщить об ошибке"
#: .\cookbook\templates\account\email.html:6
#: .\cookbook\templates\account\email.html:10
msgid "E-mail Addresses"
msgstr "Электронные адреса"
#: .\cookbook\templates\account\email.html:12
msgid "The following e-mail addresses are associated with your account:"
msgstr "Следующие электронные адреса связаны с вашим аккаунтом:"
#: .\cookbook\templates\account\email.html:29
msgid "Verified"
msgstr "Подтверждён"
#: .\cookbook\templates\account\email.html:31
msgid "Unverified"
msgstr "Не подтверждён"
#: .\cookbook\templates\account\email.html:33
msgid "Primary"
msgstr "Основной"
#: .\cookbook\templates\account\email.html:40
msgid "Make Primary"
msgstr "Сделать основным"
#: .\cookbook\templates\account\email.html:42
msgid "Re-send Verification"
msgstr "Отправить подтверждение повторно"
#: .\cookbook\templates\account\email.html:43
#: .\cookbook\templates\socialaccount\connections.html:36
msgid "Remove"
msgstr "Удалить"
#: .\cookbook\templates\account\email.html:51
msgid "Warning:"
msgstr "Внимание:"
#: .\cookbook\templates\account\email.html:51
msgid ""
"You currently do not have any e-mail address set up. You should really add "
"an e-mail address so you can receive notifications, reset your password, etc."
msgstr ""
"У вас пока не добавлен ни один электронный адрес. Рекомендуется добавить "
"адрес для получения уведомлений, сброса пароля и т.д."
#: .\cookbook\templates\account\email.html:57
msgid "Add E-mail Address"
msgstr "Добавить электронный адрес"
#: .\cookbook\templates\account\email.html:62
msgid "Add E-mail"
msgstr "Добавить e-mail"
#: .\cookbook\templates\account\email.html:72
msgid "Do you really want to remove the selected e-mail address?"
msgstr "Вы действительно хотите удалить выбранный электронный адрес?"
#: .\cookbook\templates\account\email_confirm.html:6
#: .\cookbook\templates\account\email_confirm.html:10
msgid "Confirm E-mail Address"
msgstr "Подтвердить электронный адрес"
#: .\cookbook\templates\account\email_confirm.html:16
#, python-format
msgid ""
"Please confirm that\n"
" %(email)s is an e-mail address "
"for user %(user_display)s\n"
" ."
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:22
msgid "Confirm"
msgstr "Подтвердить"
#: .\cookbook\templates\account\email_confirm.html:29
#, python-format
msgid ""
"This e-mail confirmation link expired or is invalid. Please\n"
" issue a new e-mail confirmation "
"request."
msgstr ""
#: .\cookbook\templates\account\login.html:8
#: .\cookbook\templates\openid\login.html:8
msgid "Login"
msgstr "Логин"
#: .\cookbook\templates\account\login.html:15
#: .\cookbook\templates\account\login.html:31
#: .\cookbook\templates\account\password_reset.html:39
#: .\cookbook\templates\account\password_reset_done.html:31
#: .\cookbook\templates\account\signup.html:68
#: .\cookbook\templates\account\signup_closed.html:15
#: .\cookbook\templates\openid\login.html:15
#: .\cookbook\templates\openid\login.html:26
#: .\cookbook\templates\socialaccount\authentication_error.html:15
msgid "Sign In"
msgstr "Войти"
#: .\cookbook\templates\account\login.html:34
#: .\cookbook\templates\account\password_reset.html:41
#: .\cookbook\templates\account\password_reset_done.html:33
#: .\cookbook\templates\socialaccount\signup.html:8
#: .\cookbook\templates\socialaccount\signup.html:56
msgid "Sign Up"
msgstr "Зарегистрироваться"
#: .\cookbook\templates\account\login.html:38
msgid "Lost your password?"
msgstr "Забыли пароль?"
#: .\cookbook\templates\account\login.html:39
#: .\cookbook\templates\account\password_reset.html:29
msgid "Reset My Password"
msgstr "Сбросить мой пароль"
#: .\cookbook\templates\account\login.html:50
msgid "Social Login"
msgstr "Вход через соцсети"
#: .\cookbook\templates\account\login.html:51
msgid "You can use any of the following providers to sign in."
msgstr "Для входа вы можете использовать любой из следующих сервисов."
#: .\cookbook\templates\account\logout.html:5
#: .\cookbook\templates\account\logout.html:9
#: .\cookbook\templates\account\logout.html:18
#, fuzzy
msgid "Sign Out"
msgstr "Выйти"
#: .\cookbook\templates\account\logout.html:11
msgid "Are you sure you want to sign out?"
msgstr "Вы действительно хотите выйти?"
#: .\cookbook\templates\account\password_change.html:6
#: .\cookbook\templates\account\password_change.html:10
#: .\cookbook\templates\account\password_change.html:15
#: .\cookbook\templates\account\password_reset_from_key.html:7
#: .\cookbook\templates\account\password_reset_from_key.html:13
#: .\cookbook\templates\account\password_reset_from_key_done.html:7
#: .\cookbook\templates\account\password_reset_from_key_done.html:13
msgid "Change Password"
msgstr "Изменить пароль"
#: .\cookbook\templates\account\password_change.html:16
#, fuzzy
msgid "Forgot Password?"
msgstr "Забыли пароль?"
#: .\cookbook\templates\account\password_reset.html:7
#: .\cookbook\templates\account\password_reset.html:13
#: .\cookbook\templates\account\password_reset_done.html:7
#: .\cookbook\templates\account\password_reset_done.html:18
msgid "Password Reset"
msgstr "Сброс пароля"
#: .\cookbook\templates\account\password_reset.html:24
msgid ""
"Forgotten your password? Enter your e-mail address below, and we'll send you "
"an e-mail allowing you to reset it."
msgstr ""
"Забыли пароль? Введите свой электронный адрес ниже, и мы отправим вам письмо "
"для сброса пароля."
#: .\cookbook\templates\account\password_reset.html:32
msgid "Password reset is disabled on this instance."
msgstr "Сброс пароля отключён для этого экземпляра."
#: .\cookbook\templates\account\password_reset_done.html:25
msgid ""
"We have sent you an e-mail. Please contact us if you do not receive it "
"within a few minutes."
msgstr ""
"Мы отправили вам электронное письмо. Пожалуйста, свяжитесь с нами, если не "
"получите его в течение нескольких минут."
#: .\cookbook\templates\account\password_reset_from_key.html:13
msgid "Bad Token"
msgstr "Неверный токен"
#: .\cookbook\templates\account\password_reset_from_key.html:25
#, python-format
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used.\n"
" Please request a new "
"password reset."
msgstr ""
"Ссылка для сброса пароля недействительна, возможно, она уже была "
"использована.\n"
" Пожалуйста, запросите новую "
"ссылку для сброса пароля."
#: .\cookbook\templates\account\password_reset_from_key.html:33
msgid "change password"
msgstr "изменить пароль"
#: .\cookbook\templates\account\password_reset_from_key.html:36
#: .\cookbook\templates\account\password_reset_from_key_done.html:19
msgid "Your password is now changed."
msgstr "Ваш пароль изменён."
#: .\cookbook\templates\account\password_set.html:6
#: .\cookbook\templates\account\password_set.html:10
#: .\cookbook\templates\account\password_set.html:15
msgid "Set Password"
msgstr "Установить пароль"
#: .\cookbook\templates\account\signup.html:5
#, fuzzy
msgid "Register"
msgstr "Зарегистрироваться"
#: .\cookbook\templates\account\signup.html:11
msgid "Create an Account"
msgstr "Создать аккаунт"
#: .\cookbook\templates\account\signup.html:41
msgid "I accept the follwoing"
msgstr "Я принимаю следующие"
#: .\cookbook\templates\account\signup.html:44
#: .\cookbook\templates\socialaccount\signup.html:35
msgid "Terms and Conditions"
msgstr "Условия и положения"
#: .\cookbook\templates\account\signup.html:47
#: .\cookbook\templates\socialaccount\signup.html:38
msgid "and"
msgstr "и"
#: .\cookbook\templates\account\signup.html:51
#: .\cookbook\templates\socialaccount\signup.html:42
msgid "Privacy Policy"
msgstr "Политику конфиденциальности"
#: .\cookbook\templates\account\signup.html:64
msgid "Create User"
msgstr "Создать пользователя"
#: .\cookbook\templates\account\signup.html:68
msgid "Already have an account?"
msgstr "Уже есть аккаунт?"
#: .\cookbook\templates\account\signup_closed.html:5
#: .\cookbook\templates\account\signup_closed.html:11
msgid "Sign Up Closed"
msgstr "Регистрация закрыта"
#: .\cookbook\templates\account\signup_closed.html:13
msgid "We are sorry, but the sign up is currently closed."
msgstr "К сожалению, регистрация сейчас закрыта."
#: .\cookbook\templates\frontend\tandoor.html:15
#, fuzzy
#| msgid "Tandoor Recipes Invite"
msgid "Tandoor Recipe Manager"
msgstr "Приглашение в Tandoor Recipes"
#: .\cookbook\templates\index.html:28
msgid "Search recipe ..."
msgstr "Поиск рецепта ..."
#: .\cookbook\templates\index.html:43
msgid "New Recipe"
msgstr "Новый рецепт"
#: .\cookbook\templates\index.html:46
msgid "Import Recipe"
msgstr "Импортировать рецепт"
#: .\cookbook\templates\index.html:52
msgid "Advanced Search"
msgstr "Расширенный поиск"
#: .\cookbook\templates\index.html:56
msgid "Reset Search"
msgstr "Сбросить поиск"
#: .\cookbook\templates\index.html:84
msgid "Last viewed"
msgstr "Недавно просмотренные"
#: .\cookbook\templates\index.html:86
msgid "Recipes"
msgstr "Рецепты"
#: .\cookbook\templates\index.html:93
msgid "Log in to view recipes"
msgstr "Войдите, чтобы просмотреть рецепты"
#: .\cookbook\templates\markdown_info.html:5
#: .\cookbook\templates\markdown_info.html:13
msgid "Markdown Info"
msgstr "Информация о Markdown"
#: .\cookbook\templates\markdown_info.html:14
msgid ""
"\n"
" Markdown is lightweight markup language that can be used to format "
"plain text easily.\n"
" This site uses the Python Markdown library to\n"
" convert your text into nice looking HTML. Its full markdown "
"documentation can be found\n"
" here.\n"
" An incomplete but most likely sufficient documentation can be found "
"below.\n"
" "
msgstr ""
#: .\cookbook\templates\markdown_info.html:25
msgid "Headers"
msgstr ""
#: .\cookbook\templates\markdown_info.html:54
msgid "Formatting"
msgstr ""
#: .\cookbook\templates\markdown_info.html:56
#: .\cookbook\templates\markdown_info.html:72
msgid "Line breaks are inserted by adding two spaces after the end of a line"
msgstr ""
#: .\cookbook\templates\markdown_info.html:57
#: .\cookbook\templates\markdown_info.html:73
msgid "or by leaving a blank line in between."
msgstr ""
#: .\cookbook\templates\markdown_info.html:59
#: .\cookbook\templates\markdown_info.html:74
msgid "This text is bold"
msgstr ""
#: .\cookbook\templates\markdown_info.html:60
#: .\cookbook\templates\markdown_info.html:75
msgid "This text is italic"
msgstr ""
#: .\cookbook\templates\markdown_info.html:61
#: .\cookbook\templates\markdown_info.html:77
msgid "Blockquotes are also possible"
msgstr ""
#: .\cookbook\templates\markdown_info.html:84
msgid "Lists"
msgstr ""
#: .\cookbook\templates\markdown_info.html:85
msgid ""
"Lists can ordered or unordered. It is important to leave a blank line "
"before the list!"
msgstr ""
#: .\cookbook\templates\markdown_info.html:87
#: .\cookbook\templates\markdown_info.html:108
msgid "Ordered List"
msgstr ""
#: .\cookbook\templates\markdown_info.html:89
#: .\cookbook\templates\markdown_info.html:90
#: .\cookbook\templates\markdown_info.html:91
#: .\cookbook\templates\markdown_info.html:110
#: .\cookbook\templates\markdown_info.html:111
#: .\cookbook\templates\markdown_info.html:112
msgid "unordered list item"
msgstr ""
#: .\cookbook\templates\markdown_info.html:93
#: .\cookbook\templates\markdown_info.html:114
msgid "Unordered List"
msgstr ""
#: .\cookbook\templates\markdown_info.html:95
#: .\cookbook\templates\markdown_info.html:96
#: .\cookbook\templates\markdown_info.html:97
#: .\cookbook\templates\markdown_info.html:116
#: .\cookbook\templates\markdown_info.html:117
#: .\cookbook\templates\markdown_info.html:118
msgid "ordered list item"
msgstr ""
#: .\cookbook\templates\markdown_info.html:125
msgid "Images & Links"
msgstr ""
#: .\cookbook\templates\markdown_info.html:126
msgid ""
"Links can be formatted with Markdown. This application also allows to paste "
"links directly into markdown fields without any formatting."
msgstr ""
#: .\cookbook\templates\markdown_info.html:132
#: .\cookbook\templates\markdown_info.html:145
msgid "This will become an image"
msgstr ""
#: .\cookbook\templates\markdown_info.html:152
msgid "Tables"
msgstr ""
#: .\cookbook\templates\markdown_info.html:153
msgid ""
"Markdown tables are hard to create by hand. It is recommended to use a table "
"editor like this one."
msgstr ""
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:171
#: .\cookbook\templates\markdown_info.html:177
msgid "Table"
msgstr ""
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:172
msgid "Header"
msgstr ""
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:178
msgid "Cell"
msgstr ""
#: .\cookbook\templates\no_groups_info.html:5
#: .\cookbook\templates\no_groups_info.html:12
msgid "No Permissions"
msgstr ""
#: .\cookbook\templates\no_groups_info.html:17
msgid "You do not have any groups and therefor cannot use this application."
msgstr ""
#: .\cookbook\templates\no_groups_info.html:18
#: .\cookbook\templates\no_perm_info.html:15
msgid "Please contact your administrator."
msgstr ""
#: .\cookbook\templates\no_perm_info.html:5
#: .\cookbook\templates\no_perm_info.html:12
msgid "No Permission"
msgstr ""
#: .\cookbook\templates\no_perm_info.html:15
msgid ""
"You do not have the required permissions to view this page or perform this "
"action."
msgstr ""
#: .\cookbook\templates\offline.html:5
msgid "Offline"
msgstr ""
#: .\cookbook\templates\openid\login.html:27
#: .\cookbook\templates\socialaccount\authentication_error.html:27
msgid "Back"
msgstr ""
#: .\cookbook\templates\rest_framework\api.html:5
msgid "Recipe Home"
msgstr ""
#: .\cookbook\templates\rest_framework\api.html:11
msgid "API Documentation"
msgstr "Документация API"
#: .\cookbook\templates\search_info.html:5
#: .\cookbook\templates\search_info.html:9
msgid "Search Settings"
msgstr ""
#: .\cookbook\templates\search_info.html:10
msgid ""
"\n"
" Creating the best search experience is complicated and weighs "
"heavily on your personal configuration. \n"
" Changing any of the search settings can have significant impact on "
"the speed and quality of the results.\n"
" Search Methods, Trigrams and Full Text Search configurations are "
"only available if you are using Postgres for your database.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:19
msgid "Search Methods"
msgstr ""
#: .\cookbook\templates\search_info.html:23
msgid ""
" \n"
" Full text searches attempt to normalize the words provided to "
"match common variants. For example: 'forked', 'forking', 'forks' will all "
"normalize to 'fork'.\n"
" There are several methods available, described below, that will "
"control how the search behavior should react when multiple words are "
"searched.\n"
" Full technical details on how these operate can be viewed on Postgresql's website.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:29
msgid ""
" \n"
" Simple searches ignore punctuation and common words such as "
"'the', 'a', 'and'. And will treat separate words as required.\n"
" Searching for 'apple or flour' will return any recipe that "
"includes both 'apple' and 'flour' anywhere in the fields that have been "
"selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:34
msgid ""
" \n"
" Phrase searches ignore punctuation, but will search for all of "
"the words in the exact order provided.\n"
" Searching for 'apple or flour' will only return a recipe that "
"includes the exact phrase 'apple or flour' in any of the fields that have "
"been selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:39
msgid ""
" \n"
" Web searches simulate functionality found on many web search "
"sites supporting special syntax.\n"
" Placing quotes around several words will convert those words "
"into a phrase.\n"
" 'or' is recognized as searching for the word (or phrase) "
"immediately before 'or' OR the word (or phrase) directly after.\n"
" '-' is recognized as searching for recipes that do not include "
"the word (or phrase) that comes immediately after. \n"
" For example searching for 'apple pie' or cherry -butter will "
"return any recipe that includes the phrase 'apple pie' or the word "
"'cherry' \n"
" in any field included in the full text search but exclude any "
"recipe that has the word 'butter' in any field included.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:48
msgid ""
" \n"
" Raw search is similar to Web except will take puncuation "
"operators such as '|', '&' and '()'\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:59
msgid ""
" \n"
" Another approach to searching that also requires Postgresql is "
"fuzzy search or trigram similarity. A trigram is a group of three "
"consecutive characters.\n"
" For example searching for 'apple' will create x trigrams 'app', "
"'ppl', 'ple' and will create a score of how closely words match the "
"generated trigrams.\n"
" One benefit of searching trigams is that a search for 'sandwich' "
"will find misspelled words such as 'sandwhich' that would be missed by other "
"methods.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:69
msgid "Search Fields"
msgstr ""
#: .\cookbook\templates\search_info.html:73
msgid ""
" \n"
" Unaccent is a special case in that it enables searching a field "
"'unaccented' for each search style attempting to ignore accented values. \n"
" For example when you enable unaccent for 'Name' any search "
"(starts with, contains, trigram) will attempt the search ignoring accented "
"characters.\n"
" \n"
" For the other options, you can enable search on any or all "
"fields and they will be combined together with an assumed 'OR'.\n"
" For example enabling 'Name' for Starts With, 'Name' and "
"'Description' for Partial Match and 'Ingredients' and 'Keywords' for Full "
"Search\n"
" and searching for 'apple' will generate a search that will "
"return recipes that have:\n"
" - A recipe name that starts with 'apple'\n"
" - OR a recipe name that contains 'apple'\n"
" - OR a recipe description that contains 'apple'\n"
" - OR a recipe that will have a full text search match ('apple' "
"or 'apples') in ingredients\n"
" - OR a recipe that will have a full text search match in "
"Keywords\n"
"\n"
" Combining too many fields in too many types of search can have a "
"negative impact on performance, create duplicate results or return "
"unexpected results.\n"
" For example, enabling fuzzy search or partial matches will "
"interfere with web search methods. \n"
" Searching for 'apple -pie' with fuzzy search and full text "
"search will return the recipe Apple Pie. Though it is not included in the "
"full text results, it does match the trigram results.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:95
msgid "Search Index"
msgstr ""
#: .\cookbook\templates\search_info.html:99
msgid ""
" \n"
" Trigram search and Full Text Search both rely on database "
"indexes to perform effectively. \n"
" You can rebuild the indexes on all fields in the Admin page for "
"Recipes and selecting all recipes and running 'rebuild index for selected "
"recipes'\n"
" You can also rebuild indexes at the command line by executing "
"the management command 'python manage.py rebuildindex'\n"
" "
msgstr ""
#: .\cookbook\templates\setup.html:6
msgid "Cookbook Setup"
msgstr ""
#: .\cookbook\templates\setup.html:14
msgid "Setup"
msgstr ""
#: .\cookbook\templates\setup.html:15
msgid ""
"To start using this application you must first create a superuser account."
msgstr ""
#: .\cookbook\templates\setup.html:20
msgid "Create Superuser account"
msgstr ""
#: .\cookbook\templates\socialaccount\authentication_error.html:7
#: .\cookbook\templates\socialaccount\authentication_error.html:23
msgid "Social Network Login Failure"
msgstr ""
#: .\cookbook\templates\socialaccount\authentication_error.html:25
msgid ""
"An error occurred while attempting to login via your social network account."
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:4
#: .\cookbook\templates\socialaccount\connections.html:7
msgid "Account Connections"
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:10
msgid ""
"You can sign in to your account using any of the following third party\n"
" accounts:"
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:44
msgid ""
"You currently have no social network accounts connected to this account."
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:47
msgid "Add a 3rd Party Account"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:5
#: .\cookbook\templates\socialaccount\signup.html:5
msgid "Signup"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:9
#, python-format
msgid "Connect %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:11
#, python-format
msgid "You are about to connect a new third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:13
#, python-format
msgid "Sign In Via %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:15
#, python-format
msgid "You are about to sign in using a third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:20
msgid "Continue"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:10
#, python-format
msgid ""
"You are about to use your\n"
" %(provider_name)s account to login to\n"
" %(site_name)s. As a final step, please complete the following form:"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:32
#, fuzzy
#| msgid "I accept the follwoing"
msgid "I accept the following"
msgstr "Я принимаю следующие"
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:23
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:31
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:39
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:47
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:55
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:63
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:71
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:79
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:87
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:95
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:103
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:111
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:119
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:127
msgid "Sign in using"
msgstr ""
#: .\cookbook\templates\space_overview.html:6
msgid "Overview"
msgstr "Обзор"
#: .\cookbook\templates\space_overview.html:13
msgid "Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:17
msgid ""
"Recipes, foods, shopping lists and more are organized in spaces of one or "
"more people."
msgstr ""
#: .\cookbook\templates\space_overview.html:18
msgid ""
"You can either be invited into an existing space or create your own one."
msgstr ""
#: .\cookbook\templates\space_overview.html:25
msgid "Your Spaces"
msgstr "Ваши пространства"
#: .\cookbook\templates\space_overview.html:53
msgid "Owner"
msgstr ""
#: .\cookbook\templates\space_overview.html:73
#: .\cookbook\templates\space_overview.html:83
msgid "Join Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:76
msgid "Join an existing space."
msgstr ""
#: .\cookbook\templates\space_overview.html:78
msgid ""
"To join an existing space either enter your invite token or click on the "
"invite link the space owner send you."
msgstr ""
#: .\cookbook\templates\space_overview.html:91
#: .\cookbook\templates\space_overview.html:100
msgid "Create Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:94
msgid "Create your own recipe space."
msgstr ""
#: .\cookbook\templates\space_overview.html:96
msgid "Start your own recipe space and invite other users to it."
msgstr ""
#: .\cookbook\templates\system.html:23
msgid "System"
msgstr "Система"
#: .\cookbook\templates\system.html:24
msgid ""
"\n"
" Tandoor Recipes is an open source free software application. It can "
"be found on\n"
" GitHub.\n"
" Changelogs can be found here.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:30
msgid "System Information"
msgstr ""
#: .\cookbook\templates\system.html:51
msgid ""
"\n"
" You need to execute version.py in your update "
"script to generate version information (done automatically in docker).\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:56
msgid "Plugins"
msgstr ""
#: .\cookbook\templates\system.html:67
msgid "Media Serving"
msgstr ""
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:123 .\cookbook\templates\system.html:134
msgid "Warning"
msgstr ""
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:125 .\cookbook\templates\system.html:134
msgid "Ok"
msgstr ""
#: .\cookbook\templates\system.html:70
msgid ""
"Serving media files directly using gunicorn/python is not recommend!\n"
" Please follow the steps described\n"
" here to update\n"
" your installation.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:76 .\cookbook\templates\system.html:91
#: .\cookbook\templates\system.html:104 .\cookbook\templates\system.html:115
#: .\cookbook\views\views.py:168
msgid "Everything is fine!"
msgstr ""
#: .\cookbook\templates\system.html:80
msgid "Secret Key"
msgstr ""
#: .\cookbook\templates\system.html:84
msgid ""
"\n"
" You do not have a SECRET_KEY configured in your "
".env file. Django defaulted to the\n"
" standard key\n"
" provided with the installation which is publicly know and "
"insecure! Please set\n"
" SECRET_KEY int the .env configuration "
"file.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:94
msgid "Debug Mode"
msgstr ""
#: .\cookbook\templates\system.html:98
msgid ""
"\n"
" This application is still running in debug mode. This is most "
"likely not needed. Turn of debug mode by\n"
" setting\n"
" DEBUG=0 int the .env configuration "
"file.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:107
msgid "Allowed Hosts"
msgstr ""
#: .\cookbook\templates\system.html:111
msgid ""
"\n"
" Your allowed hosts are configured to allow every host. This "
"might be ok in some setups but should be avoided. Please see the docs about "
"this.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:118
msgid "Database"
msgstr ""
#: .\cookbook\templates\system.html:121
msgid "Info"
msgstr ""
#: .\cookbook\templates\system.html:131 .\cookbook\templates\system.html:148
msgid "Migrations"
msgstr ""
#: .\cookbook\templates\system.html:137
msgid ""
"\n"
" Migrations should never fail!\n"
" Failed migrations will likely cause major parts of the app to "
"not function correctly.\n"
" If a migration fails make sure you are on the latest version and "
"if so please post the migration log and the overview below in a GitHub "
"issue.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "False"
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "True"
msgstr ""
#: .\cookbook\templates\system.html:268
msgid "Hide"
msgstr ""
#: .\cookbook\templates\system.html:271
msgid "Show"
msgstr ""
#: .\cookbook\templates\test2.html:6
msgid "Export Recipes"
msgstr "Экспорт рецептов"
#: .\cookbook\templates\test2.html:14 .\cookbook\templates\test2.html:20
msgid "Export"
msgstr "Экспортировать"
#: .\cookbook\views\api.py:198 .\cookbook\views\api.py:326
msgid "Parameter updated_at incorrectly formatted"
msgstr ""
#: .\cookbook\views\api.py:351 .\cookbook\views\api.py:484
#, python-brace-format
msgid "No {self.basename} with id {pk} exists"
msgstr ""
#: .\cookbook\views\api.py:355
msgid "Cannot merge with the same object!"
msgstr ""
#: .\cookbook\views\api.py:362
#, python-brace-format
msgid "No {self.basename} with id {target} exists"
msgstr ""
#: .\cookbook\views\api.py:367
msgid "Cannot merge with child object!"
msgstr ""
#: .\cookbook\views\api.py:405
#, python-brace-format
msgid "{source.name} was merged successfully with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:411
#, python-brace-format
msgid "An error occurred attempting to merge {source.name} with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:493
#, python-brace-format
msgid "{child.name} was moved successfully to the root."
msgstr ""
#: .\cookbook\views\api.py:496 .\cookbook\views\api.py:514
msgid "An error occurred attempting to move "
msgstr ""
#: .\cookbook\views\api.py:499
msgid "Cannot move an object to itself!"
msgstr ""
#: .\cookbook\views\api.py:505
#, python-brace-format
msgid "No {self.basename} with id {parent} exists"
msgstr ""
#: .\cookbook\views\api.py:511
#, python-brace-format
msgid "{child.name} was moved successfully to parent {parent.name}"
msgstr ""
#: .\cookbook\views\api.py:992
#, python-brace-format
msgid "{obj.name} was removed from the shopping list."
msgstr ""
#: .\cookbook\views\api.py:997 .\cookbook\views\api.py:1627
#, python-brace-format
msgid "{obj.name} was added to the shopping list."
msgstr ""
#: .\cookbook\views\api.py:1239
msgid "Filter meal plans from date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1241
msgid "Filter meal plans to date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1244
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1404
msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1406
msgid "Query string matched (fuzzy) against object name."
msgstr ""
#: .\cookbook\views\api.py:1442
msgid ""
"Query string matched (fuzzy) against recipe name. In the future also "
"fulltext search."
msgstr ""
#: .\cookbook\views\api.py:1444
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr ""
#: .\cookbook\views\api.py:1445
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr ""
#: .\cookbook\views\api.py:1446
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1447
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1448
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1450
msgid "ID of food a recipe should have. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1451
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr ""
#: .\cookbook\views\api.py:1452
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1453
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr ""
#: .\cookbook\views\api.py:1454
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1456
msgid "ID of book a recipe should be in. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1457
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr ""
#: .\cookbook\views\api.py:1458
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1459
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr ""
#: .\cookbook\views\api.py:1460
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1462
msgid "ID of unit a recipe should have."
msgstr ""
#: .\cookbook\views\api.py:1464
msgid "Exact rating of recipe"
msgstr ""
#: .\cookbook\views\api.py:1465
msgid "Rating a recipe should have or greater."
msgstr ""
#: .\cookbook\views\api.py:1466
msgid "Rating a recipe should have or smaller."
msgstr ""
#: .\cookbook\views\api.py:1468
msgid "Filter recipes cooked X times."
msgstr ""
#: .\cookbook\views\api.py:1469
msgid "Filter recipes cooked X times or more."
msgstr ""
#: .\cookbook\views\api.py:1470
msgid "Filter recipes cooked X times or less."
msgstr ""
#: .\cookbook\views\api.py:1472
msgid "Filter recipes created on the given date."
msgstr ""
#: .\cookbook\views\api.py:1473
msgid "Filter recipes created on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1474
msgid "Filter recipes created on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1476 .\cookbook\views\api.py:1477
#: .\cookbook\views\api.py:1478
msgid "Filter recipes updated on the given date."
msgstr ""
#: .\cookbook\views\api.py:1480
msgid "Filter recipes last cooked on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1481
msgid "Filter recipes last cooked on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1483 .\cookbook\views\api.py:1484
msgid "Filter recipes lasts viewed on the given date."
msgstr ""
#: .\cookbook\views\api.py:1486
msgid "Filter recipes for ones created by the given user ID"
msgstr ""
#: .\cookbook\views\api.py:1487
msgid "If only internal recipes should be returned. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1488
msgid "Returns the results in randomized order. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1490
msgid ""
"Determines the order of the results. Options are: score,-score,name,-name,"
"lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-"
"created_at,lastviewed,-lastviewed"
msgstr ""
#: .\cookbook\views\api.py:1492
msgid "Returns new results first in search results. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1493
msgid ""
"Returns the given number of recently viewed recipes before search results "
"(if given)"
msgstr ""
#: .\cookbook\views\api.py:1494
msgid "ID of a custom filter. Returns all recipes matched by that filter."
msgstr ""
#: .\cookbook\views\api.py:1495
msgid "Filter recipes that can be made with OnHand food. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1773
msgid ""
"Return the PropertyTypes matching the property category. Repeat for "
"multiple."
msgstr ""
#: .\cookbook\views\api.py:1804 .\cookbook\views\api.py:1860
msgid "Returns only entries associated with the given mealplan id"
msgstr ""
#: .\cookbook\views\api.py:1858
msgid ""
"Returns only elements updated after the given timestamp in ISO 8601 format."
msgstr ""
#: .\cookbook\views\api.py:2031
msgid ""
"Return the Automations matching the automation type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2048
msgid ""
"Text field to store data that gets carried over to the UserSpace created "
"from the InviteLink"
msgstr ""
#: .\cookbook\views\api.py:2049
msgid "Only return InviteLinks that have not been used yet."
msgstr ""
#: .\cookbook\views\api.py:2076
msgid "Return the CustomFilters matching the model type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2176
msgid "Nothing to do."
msgstr ""
#: .\cookbook\views\api.py:2222
msgid "Invalid Url"
msgstr ""
#: .\cookbook\views\api.py:2228
msgid "Connection Refused."
msgstr ""
#: .\cookbook\views\api.py:2232
msgid "Bad URL Schema."
msgstr ""
#: .\cookbook\views\api.py:2257
msgid "No usable data could be found."
msgstr ""
#: .\cookbook\views\api.py:2286 .\cookbook\views\api.py:2434
msgid "You must select an AI provider to perform your request."
msgstr ""
#: .\cookbook\views\api.py:2293 .\cookbook\views\api.py:2441
msgid ""
"You don't have any credits remaining to use AI or AI features are not "
"enabled for your space."
msgstr ""
#: .\cookbook\views\api.py:2499 .\cookbook\views\api.py:2667
msgid "File is above space limit"
msgstr ""
#: .\cookbook\views\api.py:2522 .\cookbook\views\api.py:2684
msgid "Importing is not implemented for this provider"
msgstr ""
#: .\cookbook\views\api.py:2548
msgid ""
"The PDF Exporter is not enabled on this instance as it is still in an "
"experimental state."
msgstr ""
#: .\cookbook\views\api.py:2842
msgid "This feature is not yet available in the hosted version of tandoor!"
msgstr ""
#: .\cookbook\views\api.py:2863
msgid "Sync successful!"
msgstr ""
#: .\cookbook\views\api.py:2866
msgid "Error synchronizing with Storage"
msgstr ""
#: .\cookbook\views\views.py:89
msgid "This feature is not available in the demo version!"
msgstr ""
#: .\cookbook\views\views.py:110
msgid ""
"You have successfully created your own recipe space. Start by adding some "
"recipes or invite other people to join you."
msgstr ""
#: .\cookbook\views\views.py:171
#, python-format
msgid "PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!"
msgstr ""
#: .\cookbook\views\views.py:174
#, python-format
msgid "You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended"
msgstr ""
#: .\cookbook\views\views.py:178
msgid "Unable to determine PostgreSQL version."
msgstr ""
#: .\cookbook\views\views.py:182
msgid ""
"This application is not running with a Postgres database backend. This is ok "
"but not recommended as some features only work with postgres databases."
msgstr ""
#: .\cookbook\views\views.py:296
msgid ""
"The setup page can only be used to create the first "
"user! If you have forgotten your superuser credentials "
"please consult the django documentation on how to reset passwords."
msgstr ""
#: .\cookbook\views\views.py:304
msgid "Passwords dont match!"
msgstr ""
#: .\cookbook\views\views.py:312
msgid "User has been created, please login!"
msgstr ""
#: .\cookbook\views\views.py:352
msgid ""
"Reporting share links is not enabled for this instance. Please notify the "
"page administrator to report problems."
msgstr ""
#: .\cookbook\views\views.py:357
msgid ""
"Recipe sharing link has been disabled! For additional information please "
"contact the page administrator."
msgstr ""
#: .\cookbook\views\views.py:383
msgid "Manage recipes, shopping list, meal plans and more."
msgstr ""
#: .\cookbook\views\views.py:397 .\cookbook\views\views.py:398
msgid "Plan"
msgstr ""
#: .\cookbook\views\views.py:399
msgid "View your meal Plan"
msgstr ""
#: .\cookbook\views\views.py:418
msgid "View your shopping lists"
msgstr ""
#~ msgid ""
#~ "Both fields are optional. If none are given the username will be "
#~ "displayed instead"
#~ msgstr ""
#~ "Оба поля необязательны. Если ничего не указано, вместо этого будет "
#~ "отображаться имя пользователя"
#~ msgid "Name"
#~ msgstr "Название"
#~ msgid "Keywords"
#~ msgstr "Ключевые слова"
#~ msgid "Preparation time in minutes"
#~ msgstr "Время приготовления в минутах"
#~ msgid "Waiting time (cooking/baking) in minutes"
#~ msgstr "Время ожидания (выпечка) в минутах"
#~ msgid "Path"
#~ msgstr "Путь"
#~ msgid "Storage UID"
#~ msgstr "UID хранилища"
#~ msgid "Add your comment: "
#~ msgstr "Добавить комментарий: "
#~ msgid "Leave empty for dropbox and enter app password for nextcloud."
#~ msgstr ""
#~ "Оставьте поле пустым для Dropbox и введите пароль приложения для "
#~ "nextcloud."
#~ msgid "Leave empty for nextcloud and enter api token for dropbox."
#~ msgstr "Оставьте поле пустым для nextcloud и введите токен api для dropbox."
#~ msgid ""
#~ "Leave empty for dropbox and enter only base url for nextcloud (/"
#~ "remote.php/webdav/ is added automatically)"
#~ msgstr ""
#~ "Оставьте пустым для dropbox и введите только базовый URL для nextcloud "
#~ "( /remote.php/webdav/ добавляется автоматически)"
#~ msgid ""
#~ "Long Lived Access Token for your HomeAssistant instance"
#~ msgstr ""
#~ "Длительный токен доступа для вашей установки HomeAssistant"
#~ msgid "Something like http://homeassistant.local:8123/api"
#~ msgstr "Что-то вроде http://homeassistant.local:8123/api"
#~ msgid "http://homeassistant.local:8123/api for example"
#~ msgstr "Например, http://homeassistant.local:8123/api"
#~ msgid "Storage"
#~ msgstr "Хранилище"
#~ msgid "Active"
#~ msgstr "Активный"
#~ msgid "Search String"
#~ msgstr "Текст поискового запроса"
#~ msgid "File ID"
#~ msgstr "ID файла"
#~ msgid ""
#~ "Determines how fuzzy a search is if it uses trigram similarity matching "
#~ "(e.g. low values mean more typos are ignored)."
#~ msgstr ""
#~ "Определяет степень нечёткости поиска при использовании сопоставления по "
#~ "триграммам (например, низкие значения означают, что больше опечаток "
#~ "игнорируется)."
#, fuzzy
#~| msgid ""
#~| "Select type method of search. Click here "
#~| "for full desciption of choices."
#~ msgid ""
#~ "Select type method of search. Click here "
#~ "for full description of choices."
#~ msgstr ""
#~ "Выберите тип метода поиска. Щелкните здесь "
#~ "a> для получения полного описания вариантов."
#~ msgid ""
#~ "Use fuzzy matching on units, keywords and ingredients when editing and "
#~ "importing recipes."
#~ msgstr ""
#~ "Используйте нечеткое соответствие единиц измерения, ключевых слов и "
#~ "ингредиентов при редактировании и импорте рецептов."
#~ msgid ""
#~ "Fields to search ignoring accents. Selecting this option can improve or "
#~ "degrade search quality depending on language"
#~ msgstr ""
#~ "Поля для поиска без акцентов. Выбор этого параметра может улучшить или "
#~ "ухудшить качество поиска в зависимости от языка"
#~ msgid ""
#~ "Fields to search for partial matches. (e.g. searching for 'Pie' will "
#~ "return 'pie' and 'piece' and 'soapie')"
#~ msgstr ""
#~ "Поля для поиска по частичному совпадению. (например, поиск по слову «Pie» "
#~ "вернёт «pie», «piece» и «soapie»)"
#~ msgid ""
#~ "Fields to search for beginning of word matches. (e.g. searching for 'sa' "
#~ "will return 'salad' and 'sandwich')"
#~ msgstr ""
#~ "Поля для поиска по совпадению начала слова. (например, поиск по «sa» "
#~ "вернёт «salad» и «sandwich»)"
#~ msgid ""
#~ "Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find "
#~ "'recipe'.) Note: this option will conflict with 'web' and 'raw' methods "
#~ "of search."
#~ msgstr ""
#~ "Поля для нечёткого поиска. (например, поиск по слову «recpie» найдёт "
#~ "«recipe»). Примечание: эта опция конфликтует с методами поиска «web» и "
#~ "«raw»."
#~ msgid ""
#~ "Fields to full text search. Note: 'web', 'phrase', and 'raw' search "
#~ "methods only function with fulltext fields."
#~ msgstr ""
#~ "Поля для полнотекстового поиска. Примечание: методы поиска «web», "
#~ "«phrase» и «raw» работают только с полнотекстовыми полями."
#~ msgid "Search Method"
#~ msgstr "Способ поиска"
#~ msgid "Fuzzy Lookups"
#~ msgstr "Нечёткое сопоставление"
#~ msgid "Ignore Accent"
#~ msgstr "Игнорировать акценты"
#~ msgid "Partial Match"
#~ msgstr "Частичное совпадение"
#~ msgid "Starts With"
#~ msgstr "Начинается с"
#~ msgid "Fuzzy Search"
#~ msgstr "Неточный поиск"
#~ msgid "Full Text"
#~ msgstr "Полный текст"
#~ msgid " is part of a recipe step and cannot be deleted"
#~ msgstr " является частью шага рецепта и не может быть удален"
#~ msgid "Delete"
#~ msgstr "Удалить"
#~ msgid "Settings"
#~ msgstr "Настройки"
#~ msgid "Email"
#~ msgstr "Электронная почта"
#~ msgid "Password"
#~ msgstr "Пароль"
#~ msgid "Foods"
#~ msgstr "Продукты"
#, fuzzy
#~ msgid "Units"
#~ msgstr "Единицы измерения"
#~ msgid "Supermarket"
#~ msgstr "Супермаркет"
#~ msgid "Supermarket Category"
#~ msgstr "Категория супермаркета"
#, fuzzy
#~ msgid "Automations"
#~ msgstr "Автоматизация"
#~ msgid "Files"
#~ msgstr "Файлы"
#~ msgid "Batch Edit"
#~ msgstr "Пакетное редактирование"
#~ msgid "History"
#~ msgstr "История"
#, fuzzy
#~| msgid "Ingredients"
#~ msgid "Ingredient Editor"
#~ msgstr "Ингредиенты"
#~ msgid "Properties"
#~ msgstr "Свойства"
#~ msgid "Unit Conversions"
#~ msgstr "Конвертация единиц"
#~ msgid "Create"
#~ msgstr "Создать"
#~ msgid "External Recipes"
#~ msgstr "Внешние рецепты"
#~ msgid "Space Settings"
#~ msgstr "Настройки пространства"
#~ msgid "External Connectors"
#~ msgstr "Внешние соединения"
#~ msgid "Admin"
#~ msgstr "Администрирование"
#~ msgid "Markdown Guide"
#~ msgstr "Руководство по Markdown"
#~ msgid "GitHub"
#~ msgstr "GitHub"
#~ msgid "Translate Tandoor"
#~ msgstr "Перевести Tandoor"
#~ msgid "API Browser"
#~ msgstr "Браузер API"
#~ msgid "Log out"
#~ msgstr "Выйти"
#~ msgid "You are using the free version of Tandor"
#~ msgstr "Вы используете бесплатную версию Tandor"
#~ msgid "Upgrade Now"
#~ msgstr "Обновить сейчас"
#~ msgid "Batch edit Category"
#~ msgstr "Пакетное редактирование категорий"
#~ msgid "Batch edit Recipes"
#~ msgstr "Пакетное редактирование рецептов"
#~ msgid "Add the specified keywords to all recipes containing a word"
#~ msgstr ""
#~ "Добавить указанные ключевые слова ко всем рецептам, содержащим слово"
#~ msgid "Sync"
#~ msgstr "Синхронизировать"
#~ msgid "Manage watched Folders"
#~ msgstr "Управлять отслеживаемыми папками"
#~ msgid ""
#~ "On this Page you can manage all storage folder locations that should be "
#~ "monitored and synced."
#~ msgstr ""
#~ "На этой странице вы можете управлять всеми папками для хранения, которые "
#~ "нужно отслеживать и синхронизировать."
#~ msgid "The path must be in the following format"
#~ msgstr "Путь должен быть в следующем формате"
#~ msgid "Save"
#~ msgstr "Сохранить"
#~ msgid "Manage External Storage"
#~ msgstr "Управлять внешним хранилищем"
#~ msgid "Sync Now!"
#~ msgstr "Синхронизировать сейчас!"
#~ msgid "Show Recipes"
#~ msgstr "Показать рецепты"
#~ msgid "Show Log"
#~ msgstr "Показать журнал"
#~ msgid "Importing Recipes"
#~ msgstr "Импорт рецептов"
#~ msgid ""
#~ "This can take a few minutes, depending on the number of recipes in sync, "
#~ "please wait."
#~ msgstr ""
#~ "Это может занять несколько минут, в зависимости от количества рецептов в "
#~ "синхронизации. Пожалуйста, подождите."
#~ msgid "Recipe Books"
#~ msgstr "Кулинарные книги"
#~ msgid "Import new Recipe"
#~ msgstr "Импорт новых рецептов"
#~ msgid "Edit Recipe"
#~ msgstr "Редактировать рецепт"
#, python-format
#~ msgid "Are you sure you want to delete the %(title)s: %(object)s "
#~ msgstr "Вы уверены, что хотите удалить %(title)s: %(object)s "
#~ msgid "This cannot be undone!"
#~ msgstr "Это действие необратимо!"
#~ msgid "Protected"
#~ msgstr "Защищённый"
#~ msgid "Cascade"
#~ msgstr "Каскад"
#~ msgid "Cancel"
#~ msgstr "Отмена"
#~ msgid "Edit"
#~ msgstr "Редактировать"
#~ msgid "View"
#~ msgstr "Просмотреть"
#~ msgid "Delete original file"
#~ msgstr "Удалить оригинальный файл"
#~ msgid "List"
#~ msgstr "Список"
#~ msgid "Filter"
#~ msgstr "Фильтр"
#~ msgid "Import all"
#~ msgstr "Импортировать всё"
#~ msgid "New"
#~ msgstr "Новый"
#~ msgid "previous"
#~ msgstr "предыдущий"
#~ msgid "next"
#~ msgstr "следующий"
#~ msgid "View Log"
#~ msgstr "Просмотреть журнал"
#, fuzzy
#~ msgid "Cook Log"
#~ msgstr "Журнал приготовления"
#~ msgid "Import"
#~ msgstr "Импортировать"
#~ msgid "Security Warning"
#~ msgstr "Предупреждение о безопасности"
#~ msgid ""
#~ "Color of the top navigation bar. Not all colors work with all themes, "
#~ "just try them out!"
#~ msgstr ""
#~ "Цвет навигационной панели. Не все цвета работают с некоторыми темами, "
#~ "пробуйте !"
#~ msgid ""
#~ "Default Unit to be used when inserting a new ingredient into a recipe."
#~ msgstr ""
#~ "Будут использоваться единицы измерения по умолчания при добавлении нового "
#~ "ингредиента в рецепт."
#~ msgid ""
#~ "Enables support for fractions in ingredient amounts (e.g. convert "
#~ "decimals to fractions automatically)"
#~ msgstr "Автоматический использовать десятичный формат цифр в ингредиентах"
#~ msgid ""
#~ "Users with whom newly created meal plan/shopping list entries should be "
#~ "shared by default."
#~ msgstr "Пользователи, к которым по умолчанию будет отправлен новый рецепт."
#~ msgid "Show recently viewed recipes on search page."
#~ msgstr "Показывать недавно просмотренные рецпты на странице поиска."
#~ msgid "Number of decimals to round ingredients."
#~ msgstr "Количество десятичных знаков для округления ингредиентов."
#~ msgid ""
#~ "If you want to be able to create and see comments underneath recipes."
#~ msgstr ""
#~ "Если хотите иметь возможность создавать и видеть комментарии под "
#~ "рецептами."
#~ msgid ""
#~ "Setting to 0 will disable auto sync. When viewing a shopping list the "
#~ "list is updated every set seconds to sync changes someone else might have "
#~ "made. Useful when shopping with multiple people but might use a little "
#~ "bit of mobile data. If lower than instance limit it is reset when saving."
#~ msgstr ""
#~ "Установка на 0 отключит автосинхронизацию. При просмотре списка покупок "
#~ "он обновляется каждые заданные секунды для синхронизации изменений, "
#~ "которые могли быть внесены кем-то другим. Полезно при совершении покупок "
#~ "с несколькими людьми, но при этом может потребоваться немного мобильных "
#~ "данных. Если значение ниже предела экземпляра, оно сбрасывается при "
#~ "сохранении."
#~ msgid "Makes the navbar stick to the top of the page."
#~ msgstr "Оставляет навигационную панель в верхней части страницы."
#~ msgid "New unit that other gets replaced by."
#~ msgstr "Новый юнит, который заменяется другим."
#~ msgid "Old Unit"
#~ msgstr "Старый юнит"
#~ msgid "Unit that should be replaced."
#~ msgstr "Юнит, который должен быть заменен."
#~ msgid "New food that other gets replaced by."
#~ msgstr "Новая продукт, которую заменяют другие."
#~ msgid "Old Food"
#~ msgstr "Старый продукт"
#~ msgid "Food that should be replaced."
#~ msgstr "Продукт, который должен быть заменен."
#~ msgid "You must provide at least a recipe or a title."
#~ msgstr "Вы должны предоставить хотя бы рецепт или название."
#~ msgid "You can list default users to share recipes with in the settings."
#~ msgstr ""
#~ "Вы можете перечислить пользователей по умолчанию, с которыми можно "
#~ "делиться рецептами, в настройках."
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "Для форматирования этого поля можно использовать markdown. См. документацию здесь "
#~ msgid "user"
#~ msgstr "пользователь"
================================================
FILE: cookbook/locale/sl/LC_MESSAGES/django.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-22 20:15+0200\n"
"PO-Revision-Date: 2025-12-01 06:08+0000\n"
"Last-Translator: \"Matjaž T.\" \n"
"Language-Team: Slovenian \n"
"Language: sl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || "
"n%100==4 ? 2 : 3;\n"
"X-Generator: Weblate 5.13.3\n"
#: .\cookbook\forms.py:50
msgid "Default"
msgstr "Privzeto"
#: .\cookbook\forms.py:77
msgid ""
"To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything."
msgstr ""
"V primeru, da želite preprečiti dvojnike receptov z enakim imenom kot so "
"obstoječi. Če želite uvoziti vse, potrdite to polje."
#: .\cookbook\forms.py:108
msgid "Maximum number of users for this space reached."
msgstr "Maksimalno število uporabnikov za ta prostor je doseženo."
#: .\cookbook\forms.py:114
msgid "Email address already taken!"
msgstr "Email naslov je že v uporabi!"
#: .\cookbook\forms.py:121
msgid ""
"An email address is not required but if present the invite link will be sent "
"to the user."
msgstr ""
"E-poštni naslov ni potreben, vendar če je vnešeno, bo povabilo poslano do "
"uporabnika."
#: .\cookbook\forms.py:133
msgid "Name already taken."
msgstr "Ime je že zasedeno."
#: .\cookbook\forms.py:144 .\cookbook\forms.py:158
msgid "Accept Terms and Privacy"
msgstr "Sprejmi pogoje uporabe"
#: .\cookbook\helper\AllAuthCustomAdapter.py:41
msgid ""
"In order to prevent spam, the requested email was not send. Please wait a "
"few minutes and try again."
msgstr ""
"Da bi preprečili vsiljeno pošto, zahtevana e-pošta ni bila poslana. "
"Počakajte nekaj minut in poskusite znova."
#: .\cookbook\helper\permission_helper.py:165
#: .\cookbook\helper\permission_helper.py:186 .\cookbook\views\views.py:138
msgid "You are not logged in and therefore cannot view this page!"
msgstr "Niste prijavljeni in si zato ne morete ogledati te strani!"
#: .\cookbook\helper\permission_helper.py:168
#: .\cookbook\helper\permission_helper.py:173
#: .\cookbook\helper\permission_helper.py:198
#: .\cookbook\helper\permission_helper.py:265
#: .\cookbook\helper\permission_helper.py:279
#: .\cookbook\helper\permission_helper.py:290
#: .\cookbook\helper\permission_helper.py:301
#: .\cookbook\helper\permission_helper.py:317
#: .\cookbook\helper\permission_helper.py:343
#: .\cookbook\helper\permission_helper.py:359
msgid "You do not have the required permissions to view this page!"
msgstr "Nimate potrebnih dovoljenj za ogled te strani!"
#: .\cookbook\helper\permission_helper.py:191
#: .\cookbook\helper\permission_helper.py:214
#: .\cookbook\helper\permission_helper.py:236
#: .\cookbook\helper\permission_helper.py:251
msgid "You cannot interact with this object as it is not owned by you!"
msgstr "S tem predmetom ne morete komunicirati, ker ni v vaši lasti!"
#: .\cookbook\helper\permission_helper.py:420
msgid "You have reached the maximum number of recipes for your space."
msgstr "Dosegli ste največje število receptov za svoj prostor."
#: .\cookbook\helper\permission_helper.py:432
msgid "You have more users than allowed in your space."
msgstr "V vašem prostoru imate več uporabnikov, kot je dovoljeno."
#: .\cookbook\helper\recipe_url_import.py:319
msgid "reverse rotation"
msgstr "obratno vrtenje"
#: .\cookbook\helper\recipe_url_import.py:320
msgid "careful rotation"
msgstr "previdno vrtenje"
#: .\cookbook\helper\recipe_url_import.py:321
msgid "knead"
msgstr "gnetemo"
#: .\cookbook\helper\recipe_url_import.py:322
msgid "thicken"
msgstr "zgostimo"
#: .\cookbook\helper\recipe_url_import.py:323
msgid "warm up"
msgstr "pogrejemo"
#: .\cookbook\helper\recipe_url_import.py:324
msgid "ferment"
msgstr "fermentiramo"
#: .\cookbook\helper\recipe_url_import.py:325
msgid "slow cook"
msgstr "počasno kuhanje"
#: .\cookbook\helper\recipe_url_import.py:326
msgid "egg boiler"
msgstr "kuhalnik jajc"
#: .\cookbook\helper\recipe_url_import.py:327
msgid "kettle"
msgstr "kotel"
#: .\cookbook\helper\recipe_url_import.py:328
msgid "blend"
msgstr "mešanica"
#: .\cookbook\helper\recipe_url_import.py:329
msgid "pre-clean"
msgstr "predhodno čiščenje"
#: .\cookbook\helper\recipe_url_import.py:330
msgid "high temperature"
msgstr "visoka temperatura"
#: .\cookbook\helper\recipe_url_import.py:331
msgid "rice cooker"
msgstr "kuhalnik riža"
#: .\cookbook\helper\recipe_url_import.py:332
msgid "caramelize"
msgstr "karamelizirati"
#: .\cookbook\helper\recipe_url_import.py:333
msgid "peeler"
msgstr "lupilec"
#: .\cookbook\helper\recipe_url_import.py:334
msgid "slicer"
msgstr "rezalnik"
#: .\cookbook\helper\recipe_url_import.py:335
msgid "grater"
msgstr "strgalo"
#: .\cookbook\helper\recipe_url_import.py:336
msgid "spiralizer"
msgstr "spiralizator"
#: .\cookbook\helper\recipe_url_import.py:337
msgid "sous-vide"
msgstr "vakumsko"
#: .\cookbook\helper\shopping_helper.py:150
msgid "You must supply a servings size"
msgstr "Navesti morate velikost obrokov"
#: .\cookbook\helper\template_helper.py:97
#: .\cookbook\helper\template_helper.py:99
#: .\cookbook\helper\template_helper.py:101
msgid "Could not parse template code."
msgstr "Kode predloge ni bilo mogoče razčleniti."
#: .\cookbook\integration\copymethat.py:44
#: .\cookbook\integration\melarecipes.py:37
msgid "Favorite"
msgstr "Priljubljeno"
#: .\cookbook\integration\copymethat.py:50
msgid "I made this"
msgstr "Naredil/a sem to"
#: .\cookbook\integration\integration.py:238
msgid ""
"Importer expected a .zip file. Did you choose the correct importer type for "
"your data ?"
msgstr ""
"Uvoznik je pričakoval datoteko .zip. Ali ste za svoje podatke izbrali "
"pravilno vrsto uvoznika?"
#: .\cookbook\integration\integration.py:241
msgid ""
"An unexpected error occurred during the import. Please make sure you have "
"uploaded a valid file."
msgstr ""
"Med uvozom je prišlo do nepričakovane napake. Preverite, ali ste naložili "
"veljavno datoteko."
#: .\cookbook\integration\integration.py:246
msgid "The following recipes were ignored because they already existed:"
msgstr "Naslednji recepti so bili prezrti, ker že obstajajo:"
#: .\cookbook\integration\integration.py:250
#, python-format
msgid "Imported %s recipes."
msgstr "Uvoženih %s receptov."
#: .\cookbook\integration\mealie1.py:210
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "Calories"
msgstr "Kalorije"
#: .\cookbook\integration\mealie1.py:211
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
msgid "Carbohydrates"
msgstr "Ogljikovi hidrati"
#: .\cookbook\integration\mealie1.py:212
msgid "Cholesterol"
msgstr "Holesterol"
#: .\cookbook\integration\mealie1.py:213
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
msgid "Fat"
msgstr "Maščoba"
#: .\cookbook\integration\mealie1.py:214
msgid "Fiber"
msgstr "Vlaknine"
#: .\cookbook\integration\mealie1.py:215
msgid "Protein"
msgstr "Beljakovine"
#: .\cookbook\integration\mealie1.py:216
msgid "Saturated Fat"
msgstr "Nasičene maščobe"
#: .\cookbook\integration\mealie1.py:217
msgid "Sodium"
msgstr "Natrij"
#: .\cookbook\integration\mealie1.py:218
msgid "Sugar"
msgstr "Sladkor"
#: .\cookbook\integration\mealie1.py:219
msgid "Trans Fat"
msgstr "Trans maščobe"
#: .\cookbook\integration\mealie1.py:220
msgid "Unsaturated Fat"
msgstr "Nenasičene maščobe"
#: .\cookbook\integration\openeats.py:28
msgid "Recipe source:"
msgstr "Vir recepta:"
#: .\cookbook\integration\paprika.py:49
msgid "Notes"
msgstr "Opombe"
#: .\cookbook\integration\paprika.py:52
msgid "Nutritional Information"
msgstr "Informacije o hranilni vrednosti"
#: .\cookbook\integration\paprika.py:56
msgid "Source"
msgstr "Vir"
#: .\cookbook\integration\recettetek.py:55
#: .\cookbook\integration\recipekeeper.py:70
msgid "Imported from"
msgstr "Uvoženo iz"
#: .\cookbook\integration\saffron.py:23
msgid "Servings"
msgstr "Porcije"
#: .\cookbook\integration\saffron.py:25
msgid "Waiting time"
msgstr "Čakalna doba"
#: .\cookbook\integration\saffron.py:27
msgid "Preparation Time"
msgstr "Čas Priprave"
#: .\cookbook\integration\saffron.py:29 .\cookbook\templates\index.html:6
msgid "Cookbook"
msgstr "Kuharska knjiga"
#: .\cookbook\integration\saffron.py:31
msgid "Section"
msgstr "Razdelek"
#: .\cookbook\management\commands\fix_duplicate_properties.py:15
msgid "Fixes foods with "
msgstr "Popravlja živila z "
#: .\cookbook\management\commands\rebuildindex.py:14
msgid "Rebuilds full text search index on Recipe"
msgstr "Ponovno zgradi indeks iskanja po celotnem besedilu na receptu"
#: .\cookbook\management\commands\rebuildindex.py:18
msgid "Only Postgresql databases use full text search, no index to rebuild"
msgstr ""
"Samo zbirke podatkov Postgresql uporabljajo iskanje po celotnem besedilu, "
"brez indeksa za ponovno izgradnjo"
#: .\cookbook\management\commands\rebuildindex.py:29
msgid "Recipe index rebuild complete."
msgstr "Ponovna izgradnja indeksa receptov je končana."
#: .\cookbook\management\commands\rebuildindex.py:31
msgid "Recipe index rebuild failed."
msgstr "Ponovna izdelava indeksa receptov ni uspela."
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
msgid "Breakfast"
msgstr "Zajtrk"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:19
msgid "Lunch"
msgstr "Kosilo"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:24
msgid "Dinner"
msgstr "Večerja"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:29 .\cookbook\models.py:971
msgid "Other"
msgstr "Ostalo"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "g"
msgstr "g"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "Proteins"
msgstr "Beljakovine"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "kcal"
msgstr "kcal"
#: .\cookbook\models.py:325
msgid ""
"Maximum file storage for space in MB. 0 for unlimited, -1 to disable file "
"upload."
msgstr ""
"Največji prostor za shranjevanje datotek v MB. 0 za neomejeno, -1 za "
"onemogočanje nalaganja datotek."
#: .\cookbook\models.py:513
msgid "Search"
msgstr "Iskanje"
#: .\cookbook\models.py:514
msgid "Meal-Plan"
msgstr "Načrt obrokov"
#: .\cookbook\models.py:515
msgid "Books"
msgstr "Knjige"
#: .\cookbook\models.py:516 .\cookbook\views\views.py:416
#: .\cookbook\views\views.py:417
msgid "Shopping"
msgstr "Nakupovanje"
#: .\cookbook\models.py:967
msgid "Nutrition"
msgstr "Prehrana"
#: .\cookbook\models.py:968
msgid "Allergen"
msgstr "Alergen"
#: .\cookbook\models.py:969
msgid "Price"
msgstr "Cena"
#: .\cookbook\models.py:970
msgid "Goal"
msgstr "Cilj"
#: .\cookbook\models.py:1467 .\cookbook\templates\search_info.html:28
msgid "Simple"
msgstr "Enostavno"
#: .\cookbook\models.py:1468 .\cookbook\templates\search_info.html:33
msgid "Phrase"
msgstr "Fraza"
#: .\cookbook\models.py:1469 .\cookbook\templates\search_info.html:38
msgid "Web"
msgstr "Splet"
#: .\cookbook\models.py:1470 .\cookbook\templates\search_info.html:47
msgid "Raw"
msgstr "Surovo"
#: .\cookbook\models.py:1525
msgid "Food Alias"
msgstr "Vzdevki hrane"
#: .\cookbook\models.py:1526
msgid "Unit Alias"
msgstr "Vzdevek enot"
#: .\cookbook\models.py:1527
msgid "Keyword Alias"
msgstr "Vzdevek ključne besede"
#: .\cookbook\models.py:1528
msgid "Description Replace"
msgstr "Nadomestitev opisa"
#: .\cookbook\models.py:1529
msgid "Instruction Replace"
msgstr "Nadomestitev navodil"
#: .\cookbook\models.py:1530
msgid "Never Unit"
msgstr "Enota nikoli"
#: .\cookbook\models.py:1531
msgid "Transpose Words"
msgstr "Prenešene besede"
#: .\cookbook\models.py:1532
msgid "Food Replace"
msgstr "Nadomestitev hrane"
#: .\cookbook\models.py:1533
msgid "Unit Replace"
msgstr "Nadomestitev enote"
#: .\cookbook\models.py:1534
msgid "Name Replace"
msgstr "Nadomestitev imena"
#: .\cookbook\models.py:1564
msgid "Recipe"
msgstr "Recept"
#: .\cookbook\models.py:1565
msgid "Food"
msgstr "Hrana"
#: .\cookbook\models.py:1566
msgid "Keyword"
msgstr "Ključna beseda"
#: .\cookbook\serializer.py:262
msgid "File uploads are not enabled for this Space."
msgstr "Nalaganje datotek ni omogočeno za ta prostor."
#: .\cookbook\serializer.py:273
msgid "You have reached your file upload limit."
msgstr "Dosegli ste omejitev nalaganja datotek."
#: .\cookbook\serializer.py:281
msgid "The given file type is not allowed."
msgstr "Navedena vrsta datoteke ni dovoljena."
#: .\cookbook\serializer.py:421 .\cookbook\views\views.py:94
msgid ""
"You have the reached the maximum amount of spaces that can be owned by you."
msgstr "Dosegli ste največje število prostorov, ki so lahko v vaši lasti."
#: .\cookbook\serializer.py:434
msgid "Space Name must be unique."
msgstr "Ime prostora mora biti edinstveno."
#: .\cookbook\serializer.py:469
msgid "Cannot modify Space owner permission."
msgstr "Dovoljenja lastnika prostora ni mogoče spremeniti."
#: .\cookbook\serializer.py:1596
msgid "Hello"
msgstr "Hej"
#: .\cookbook\serializer.py:1596
msgid "You have been invited by "
msgstr "Povabil/a te je "
#: .\cookbook\serializer.py:1598
msgid " to join their Tandoor Recipes space "
msgstr " da se pridružiš njihovemu prostoru Tandoor Recipes "
#: .\cookbook\serializer.py:1600
msgid "Click the following link to activate your account: "
msgstr "Kliknite naslednjo povezavo, da aktiviraš svoj račun: "
#: .\cookbook\serializer.py:1602
msgid ""
"If the link does not work use the following code to manually join the space: "
msgstr ""
"Če povezava ne deluje, uporabi naslednjo kodo, da se ročno pridružiš "
"prostoru: "
#: .\cookbook\serializer.py:1604
msgid "The invitation is valid until "
msgstr "Vabilo velja do "
#: .\cookbook\serializer.py:1606
msgid ""
"Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub "
msgstr ""
"Tandoor Recipes je odprtokodni upravitelj receptov. Preverite na GitHubu "
#: .\cookbook\serializer.py:1609
msgid "Tandoor Recipes Invite"
msgstr "Tandoor Recepti vabilo"
#: .\cookbook\serializer.py:1813
msgid "Existing shopping list to update"
msgstr "Obstoječi nakupovalni seznam za posodobitev"
#: .\cookbook\serializer.py:1815
msgid ""
"List of ingredient IDs from the recipe to add, if not provided all "
"ingredients will be added."
msgstr ""
"Seznam ID-jev sestavin iz recepta, ki jih želite dodati, če niso navedeni, "
"bodo dodane vse sestavine."
#: .\cookbook\serializer.py:1817
msgid ""
"Providing a list_recipe ID and servings of 0 will delete that shopping list."
msgstr ""
"Če navedete ID recepta in porcije 0, boste ta nakupovalni seznam izbrisali."
#: .\cookbook\serializer.py:1826
msgid "Amount of food to add to the shopping list"
msgstr "Količina hrane, ki jo želite dodati na nakupovalni seznam"
#: .\cookbook\serializer.py:1828
msgid "ID of unit to use for the shopping list"
msgstr "ID enote za uporabo za nakupovalni seznam"
#: .\cookbook\serializer.py:1830
msgid "When set to true will delete all food from active shopping lists."
msgstr ""
"Če je nastavljeno na da, bo izbrisal vso hrano iz aktivnih nakupovalnih "
"seznamov."
#: .\cookbook\templates\404.html:5
msgid "404 Error"
msgstr "Napaka 404"
#: .\cookbook\templates\404.html:18
msgid "The page you are looking for could not be found."
msgstr "Strani, ki jo iščete, ni bilo mogoče najti."
#: .\cookbook\templates\404.html:33
msgid "Take me Home"
msgstr "Pelji me domov"
#: .\cookbook\templates\404.html:35
msgid "Report a Bug"
msgstr "Prijavi Napako"
#: .\cookbook\templates\account\email.html:6
#: .\cookbook\templates\account\email.html:10
msgid "E-mail Addresses"
msgstr "E-mail Naslovi"
#: .\cookbook\templates\account\email.html:12
msgid "The following e-mail addresses are associated with your account:"
msgstr "Z vašim računom so povezani naslednji e-poštni naslovi:"
#: .\cookbook\templates\account\email.html:29
msgid "Verified"
msgstr "Preverjeno"
#: .\cookbook\templates\account\email.html:31
msgid "Unverified"
msgstr "Nepreverjeno"
#: .\cookbook\templates\account\email.html:33
msgid "Primary"
msgstr "Primarni"
#: .\cookbook\templates\account\email.html:40
msgid "Make Primary"
msgstr "Naj bo primarno"
#: .\cookbook\templates\account\email.html:42
msgid "Re-send Verification"
msgstr "Ponovno pošlji potrditev"
#: .\cookbook\templates\account\email.html:43
#: .\cookbook\templates\socialaccount\connections.html:36
msgid "Remove"
msgstr "Odstrani"
#: .\cookbook\templates\account\email.html:51
msgid "Warning:"
msgstr "Opozorilo:"
#: .\cookbook\templates\account\email.html:51
msgid ""
"You currently do not have any e-mail address set up. You should really add "
"an e-mail address so you can receive notifications, reset your password, etc."
msgstr ""
"Trenutno nimate nastavljenega e-poštnega naslova. Res bi morali dodati e-"
"poštni naslov, da boste lahko prejemali obvestila, ponastavili geslo itd."
#: .\cookbook\templates\account\email.html:57
msgid "Add E-mail Address"
msgstr "Dodaj e-poštni naslov"
#: .\cookbook\templates\account\email.html:62
msgid "Add E-mail"
msgstr "Dodaj E-mail"
#: .\cookbook\templates\account\email.html:72
msgid "Do you really want to remove the selected e-mail address?"
msgstr "Ali res želiš odstraniti izbrani e-poštni naslov?"
#: .\cookbook\templates\account\email_confirm.html:6
#: .\cookbook\templates\account\email_confirm.html:10
msgid "Confirm E-mail Address"
msgstr "Potrdi e-poštni naslov"
#: .\cookbook\templates\account\email_confirm.html:16
#, python-format
msgid ""
"Please confirm that\n"
" %(email)s is an e-mail address "
"for user %(user_display)s\n"
" ."
msgstr ""
"Prosim potrdi\n"
" %(email)s kot e-poštni naslov "
"uporabnika %(user_display)s\n"
" ."
#: .\cookbook\templates\account\email_confirm.html:22
msgid "Confirm"
msgstr "Potrdi"
#: .\cookbook\templates\account\email_confirm.html:29
#, python-format
msgid ""
"This e-mail confirmation link expired or is invalid. Please\n"
" issue a new e-mail confirmation "
"request."
msgstr ""
"Ta potrditvena povezava po e-pošti je potekla ali ni veljavna. Prosim pošlji "
"na\n"
"Prosim pošlji na novo potrditveno zahtevo."
#: .\cookbook\templates\account\login.html:8
#: .\cookbook\templates\openid\login.html:8
msgid "Login"
msgstr "Prijava"
#: .\cookbook\templates\account\login.html:15
#: .\cookbook\templates\account\login.html:31
#: .\cookbook\templates\account\password_reset.html:39
#: .\cookbook\templates\account\password_reset_done.html:31
#: .\cookbook\templates\account\signup.html:68
#: .\cookbook\templates\account\signup_closed.html:15
#: .\cookbook\templates\openid\login.html:15
#: .\cookbook\templates\openid\login.html:26
#: .\cookbook\templates\socialaccount\authentication_error.html:15
msgid "Sign In"
msgstr "Prijavi se"
#: .\cookbook\templates\account\login.html:34
#: .\cookbook\templates\account\password_reset.html:41
#: .\cookbook\templates\account\password_reset_done.html:33
#: .\cookbook\templates\socialaccount\signup.html:8
#: .\cookbook\templates\socialaccount\signup.html:56
msgid "Sign Up"
msgstr "Registriraj se"
#: .\cookbook\templates\account\login.html:38
msgid "Lost your password?"
msgstr "Ste izgubili geslo?"
#: .\cookbook\templates\account\login.html:39
#: .\cookbook\templates\account\password_reset.html:29
msgid "Reset My Password"
msgstr "Ponastavi moje geslo"
#: .\cookbook\templates\account\login.html:50
msgid "Social Login"
msgstr "Socialna prijava"
#: .\cookbook\templates\account\login.html:51
msgid "You can use any of the following providers to sign in."
msgstr "Za prijavo lahko uporabite katerega koli od naslednjih ponudnikov."
#: .\cookbook\templates\account\logout.html:5
#: .\cookbook\templates\account\logout.html:9
#: .\cookbook\templates\account\logout.html:18
msgid "Sign Out"
msgstr "Odjava"
#: .\cookbook\templates\account\logout.html:11
msgid "Are you sure you want to sign out?"
msgstr "Ste prepričani, da se želite odjaviti?"
#: .\cookbook\templates\account\password_change.html:6
#: .\cookbook\templates\account\password_change.html:10
#: .\cookbook\templates\account\password_change.html:15
#: .\cookbook\templates\account\password_reset_from_key.html:7
#: .\cookbook\templates\account\password_reset_from_key.html:13
#: .\cookbook\templates\account\password_reset_from_key_done.html:7
#: .\cookbook\templates\account\password_reset_from_key_done.html:13
msgid "Change Password"
msgstr "Spremeni geslo"
#: .\cookbook\templates\account\password_change.html:16
msgid "Forgot Password?"
msgstr "Pozabljeno geslo?"
#: .\cookbook\templates\account\password_reset.html:7
#: .\cookbook\templates\account\password_reset.html:13
#: .\cookbook\templates\account\password_reset_done.html:7
#: .\cookbook\templates\account\password_reset_done.html:18
msgid "Password Reset"
msgstr "Ponastavitev gesla"
#: .\cookbook\templates\account\password_reset.html:24
msgid ""
"Forgotten your password? Enter your e-mail address below, and we'll send you "
"an e-mail allowing you to reset it."
msgstr ""
"Ste pozabili geslo? Spodaj vnesite svoj e-poštni naslov in poslali vam bomo "
"e-poštno sporočilo za ponastavitev."
#: .\cookbook\templates\account\password_reset.html:32
msgid "Password reset is disabled on this instance."
msgstr "Ponastavitev gesla je v tem primeru onemogočena."
#: .\cookbook\templates\account\password_reset_done.html:25
msgid ""
"We have sent you an e-mail. Please contact us if you do not receive it "
"within a few minutes."
msgstr ""
"Poslali smo vam e-pošto. Če ga ne prejmete v nekaj minutah, nas "
"kontaktirajte."
#: .\cookbook\templates\account\password_reset_from_key.html:13
msgid "Bad Token"
msgstr "Slab žeton"
#: .\cookbook\templates\account\password_reset_from_key.html:25
#, python-format
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used.\n"
" Please request a new "
"password reset."
msgstr ""
"Povezava za ponastavitev gesla je bila neveljavna, morda zato, ker je bila "
"že uporabljena.\n"
" Zahtevajte novo ponastavitev gesla."
#: .\cookbook\templates\account\password_reset_from_key.html:33
msgid "change password"
msgstr "spremeni geslo"
#: .\cookbook\templates\account\password_reset_from_key.html:36
#: .\cookbook\templates\account\password_reset_from_key_done.html:19
msgid "Your password is now changed."
msgstr "Vaše geslo je zdaj spremenjeno."
#: .\cookbook\templates\account\password_set.html:6
#: .\cookbook\templates\account\password_set.html:10
#: .\cookbook\templates\account\password_set.html:15
msgid "Set Password"
msgstr "Nastavi geslo"
#: .\cookbook\templates\account\signup.html:5
msgid "Register"
msgstr "Registrirajte se"
#: .\cookbook\templates\account\signup.html:11
msgid "Create an Account"
msgstr "Ustvari račun"
#: .\cookbook\templates\account\signup.html:41
msgid "I accept the follwoing"
msgstr "Sprejemam naslednje"
#: .\cookbook\templates\account\signup.html:44
#: .\cookbook\templates\socialaccount\signup.html:35
msgid "Terms and Conditions"
msgstr "Pogoji in določila"
#: .\cookbook\templates\account\signup.html:47
#: .\cookbook\templates\socialaccount\signup.html:38
msgid "and"
msgstr "in"
#: .\cookbook\templates\account\signup.html:51
#: .\cookbook\templates\socialaccount\signup.html:42
msgid "Privacy Policy"
msgstr "Politika zasebnosti"
#: .\cookbook\templates\account\signup.html:64
msgid "Create User"
msgstr "Ustvari uporabnika"
#: .\cookbook\templates\account\signup.html:68
msgid "Already have an account?"
msgstr "Že imate račun?"
#: .\cookbook\templates\account\signup_closed.html:5
#: .\cookbook\templates\account\signup_closed.html:11
msgid "Sign Up Closed"
msgstr "Registracije zaprte"
#: .\cookbook\templates\account\signup_closed.html:13
msgid "We are sorry, but the sign up is currently closed."
msgstr "Žal nam je, vendar so registracije trenutno zaprte."
#: .\cookbook\templates\frontend\tandoor.html:15
msgid "Tandoor Recipe Manager"
msgstr "Urejevalnik Tandoor Recepti"
#: .\cookbook\templates\index.html:28
msgid "Search recipe ..."
msgstr "Iskanje recepta..."
#: .\cookbook\templates\index.html:43
msgid "New Recipe"
msgstr "Nov Recept"
#: .\cookbook\templates\index.html:46
msgid "Import Recipe"
msgstr "Uvozi recept"
#: .\cookbook\templates\index.html:52
msgid "Advanced Search"
msgstr "Napredno iskanje"
#: .\cookbook\templates\index.html:56
msgid "Reset Search"
msgstr "Ponastavi iskanje"
#: .\cookbook\templates\index.html:84
msgid "Last viewed"
msgstr "Nazadnje ogledano"
#: .\cookbook\templates\index.html:86
msgid "Recipes"
msgstr "Recepti"
#: .\cookbook\templates\index.html:93
msgid "Log in to view recipes"
msgstr "Za ogled receptov se prijavite"
#: .\cookbook\templates\markdown_info.html:5
#: .\cookbook\templates\markdown_info.html:13
msgid "Markdown Info"
msgstr "Označitvene informacije"
#: .\cookbook\templates\markdown_info.html:14
msgid ""
"\n"
" Markdown is lightweight markup language that can be used to format "
"plain text easily.\n"
" This site uses the Python Markdown library to\n"
" convert your text into nice looking HTML. Its full markdown "
"documentation can be found\n"
" here.\n"
" An incomplete but most likely sufficient documentation can be found "
"below.\n"
" "
msgstr ""
"\n"
" Markdown je lahek označevalni jezik, ki ga je mogoče uporabiti za "
"enostavno oblikovanje navadnega besedila.\n"
" Ta stran uporablja Python Markdown knjižnico za\n"
" pretvorbo besedila v lepo izoblikvano HTML obliko. Njegovo celotno "
"dokumentacijo se lahko ogledate tukaj\n"
" .\n"
" Spodaj je na voljo nepopolna, a najverjetneje zadostna "
"dokumentacija.\n"
" "
#: .\cookbook\templates\markdown_info.html:25
msgid "Headers"
msgstr "Glave"
#: .\cookbook\templates\markdown_info.html:54
msgid "Formatting"
msgstr "Oblikovanje"
#: .\cookbook\templates\markdown_info.html:56
#: .\cookbook\templates\markdown_info.html:72
msgid "Line breaks are inserted by adding two spaces after the end of a line"
msgstr ""
"Prelome vrstice vstavimo tako, da na koncu vrstice dodamo dva presledka"
#: .\cookbook\templates\markdown_info.html:57
#: .\cookbook\templates\markdown_info.html:73
msgid "or by leaving a blank line in between."
msgstr "ali tako, da vmes pustite prazno vrstico."
#: .\cookbook\templates\markdown_info.html:59
#: .\cookbook\templates\markdown_info.html:74
msgid "This text is bold"
msgstr "To besedilo je krepko"
#: .\cookbook\templates\markdown_info.html:60
#: .\cookbook\templates\markdown_info.html:75
msgid "This text is italic"
msgstr "To besedilo je poševno"
#: .\cookbook\templates\markdown_info.html:61
#: .\cookbook\templates\markdown_info.html:77
msgid "Blockquotes are also possible"
msgstr "Možni so tudi narekovaji"
#: .\cookbook\templates\markdown_info.html:84
msgid "Lists"
msgstr "Seznami"
#: .\cookbook\templates\markdown_info.html:85
msgid ""
"Lists can ordered or unordered. It is important to leave a blank line "
"before the list!"
msgstr ""
"Seznami so lahko urejeni ali neurejeni. Pomembno je, da pred seznamom "
"pustite prazno vrstico!"
#: .\cookbook\templates\markdown_info.html:87
#: .\cookbook\templates\markdown_info.html:108
msgid "Ordered List"
msgstr "Urejeni seznam"
#: .\cookbook\templates\markdown_info.html:89
#: .\cookbook\templates\markdown_info.html:90
#: .\cookbook\templates\markdown_info.html:91
#: .\cookbook\templates\markdown_info.html:110
#: .\cookbook\templates\markdown_info.html:111
#: .\cookbook\templates\markdown_info.html:112
msgid "unordered list item"
msgstr "neurejen element seznama"
#: .\cookbook\templates\markdown_info.html:93
#: .\cookbook\templates\markdown_info.html:114
msgid "Unordered List"
msgstr "Neurejen seznam"
#: .\cookbook\templates\markdown_info.html:95
#: .\cookbook\templates\markdown_info.html:96
#: .\cookbook\templates\markdown_info.html:97
#: .\cookbook\templates\markdown_info.html:116
#: .\cookbook\templates\markdown_info.html:117
#: .\cookbook\templates\markdown_info.html:118
msgid "ordered list item"
msgstr "urejen element seznama"
#: .\cookbook\templates\markdown_info.html:125
msgid "Images & Links"
msgstr "Slike & povezave"
#: .\cookbook\templates\markdown_info.html:126
msgid ""
"Links can be formatted with Markdown. This application also allows to paste "
"links directly into markdown fields without any formatting."
msgstr ""
"Povezave je mogoče formatirati z Markdown. Ta aplikacija omogoča tudi "
"lepljenje povezav neposredno v polja za označevanje brez oblikovanja."
#: .\cookbook\templates\markdown_info.html:132
#: .\cookbook\templates\markdown_info.html:145
msgid "This will become an image"
msgstr "To bo postalo slika"
#: .\cookbook\templates\markdown_info.html:152
msgid "Tables"
msgstr "Mize"
#: .\cookbook\templates\markdown_info.html:153
msgid ""
"Markdown tables are hard to create by hand. It is recommended to use a table "
"editor like this one."
msgstr ""
"Markdown tabele je težko ustvariti ročno. Priporočljivo je, da uporabite "
"urejevalnik tabel, kot je ."
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:171
#: .\cookbook\templates\markdown_info.html:177
msgid "Table"
msgstr "Miza"
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:172
msgid "Header"
msgstr "Glava"
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:178
msgid "Cell"
msgstr "Celica"
#: .\cookbook\templates\no_groups_info.html:5
#: .\cookbook\templates\no_groups_info.html:12
msgid "No Permissions"
msgstr "Brez dovoljenj"
#: .\cookbook\templates\no_groups_info.html:17
msgid "You do not have any groups and therefor cannot use this application."
msgstr "Nimate nobene skupine in zato ne morete uporabljati te aplikacije."
#: .\cookbook\templates\no_groups_info.html:18
#: .\cookbook\templates\no_perm_info.html:15
msgid "Please contact your administrator."
msgstr "Obrnite se na skrbnika."
#: .\cookbook\templates\no_perm_info.html:5
#: .\cookbook\templates\no_perm_info.html:12
msgid "No Permission"
msgstr "Brez dovoljenja"
#: .\cookbook\templates\no_perm_info.html:15
msgid ""
"You do not have the required permissions to view this page or perform this "
"action."
msgstr ""
"Nimate potrebnih dovoljenj za ogled te strani ali izvedbo tega dejanja."
#: .\cookbook\templates\offline.html:5
msgid "Offline"
msgstr "Ni povezave"
#: .\cookbook\templates\openid\login.html:27
#: .\cookbook\templates\socialaccount\authentication_error.html:27
msgid "Back"
msgstr "Nazaj"
#: .\cookbook\templates\rest_framework\api.html:5
msgid "Recipe Home"
msgstr "Recept Domov"
#: .\cookbook\templates\rest_framework\api.html:11
msgid "API Documentation"
msgstr "API dokumentacija"
#: .\cookbook\templates\search_info.html:5
#: .\cookbook\templates\search_info.html:9
msgid "Search Settings"
msgstr "Nastavitve iskanja"
#: .\cookbook\templates\search_info.html:10
msgid ""
"\n"
" Creating the best search experience is complicated and weighs "
"heavily on your personal configuration. \n"
" Changing any of the search settings can have significant impact on "
"the speed and quality of the results.\n"
" Search Methods, Trigrams and Full Text Search configurations are "
"only available if you are using Postgres for your database.\n"
" "
msgstr ""
"\n"
" Ustvarjanje najboljše izkušnje iskanja je zapleteno in močno vpliva "
"na vašo osebno konfiguracijo. \n"
" Spreminjanje katere koli nastavitve iskanja lahko pomembno vpliva na "
"hitrost in kakovost rezultatov.\n"
" Konfiguracije metod iskanja, trigramov in iskanja po celotnem "
"besedilu so na voljo le, če za svojo bazo podatkov uporabljate Postgres.\n"
" "
#: .\cookbook\templates\search_info.html:19
msgid "Search Methods"
msgstr "Metode iskanja"
#: .\cookbook\templates\search_info.html:23
msgid ""
" \n"
" Full text searches attempt to normalize the words provided to "
"match common variants. For example: 'forked', 'forking', 'forks' will all "
"normalize to 'fork'.\n"
" There are several methods available, described below, that will "
"control how the search behavior should react when multiple words are "
"searched.\n"
" Full technical details on how these operate can be viewed on Postgresql's website.\n"
" "
msgstr ""
" \n"
" Iskanje po celotnem besedilu poskuša normalizirati navedene "
"besede, da se ujemajo s pogostimi različicami. Na primer: 'forked', "
"'forking', 'forks' se vse normalizira v 'fork'.\n"
" Na voljo je več metod, opisanih spodaj, ki bodo nadzirale, kako "
"naj se obnašanje iskanja odzove, ko se išče več besed.\n"
" Vse tehnične podrobnosti o njihovem delovanju si lahko ogledate "
"na Postgresql spletni strani .\n"
" "
#: .\cookbook\templates\search_info.html:29
msgid ""
" \n"
" Simple searches ignore punctuation and common words such as "
"'the', 'a', 'and'. And will treat separate words as required.\n"
" Searching for 'apple or flour' will return any recipe that "
"includes both 'apple' and 'flour' anywhere in the fields that have been "
"selected for a full text search.\n"
" "
msgstr ""
" \n"
" Preprosta iskanja ne upoštevajo ločil in pogostih besed, kot so "
"'ko', 'ali', 'in'. In po potrebi obravnava ločene besede.\n"
" Iskanje 'jabolko ali moka' bo vrnilo vse recepte, ki vključujejo "
"'jabolko' in 'moko' kjer koli v poljih, ki so bila izbrana za iskanje po "
"celotnem besedilu.\n"
" "
#: .\cookbook\templates\search_info.html:34
msgid ""
" \n"
" Phrase searches ignore punctuation, but will search for all of "
"the words in the exact order provided.\n"
" Searching for 'apple or flour' will only return a recipe that "
"includes the exact phrase 'apple or flour' in any of the fields that have "
"been selected for a full text search.\n"
" "
msgstr ""
" \n"
" Iskanje fraz ne upošteva ločil, vendar bo iskalo vse besede v "
"točno navedenem vrstnem redu.\n"
" Iskanje 'jabolko ali moka' bo vrnilo le recept, ki vključuje "
"natančno besedno zvezo 'jabolko ali moka' v katerem koli od polj, ki so bila "
"izbrana za iskanje po celotnem besedilu.\n"
" "
#: .\cookbook\templates\search_info.html:39
msgid ""
" \n"
" Web searches simulate functionality found on many web search "
"sites supporting special syntax.\n"
" Placing quotes around several words will convert those words "
"into a phrase.\n"
" 'or' is recognized as searching for the word (or phrase) "
"immediately before 'or' OR the word (or phrase) directly after.\n"
" '-' is recognized as searching for recipes that do not include "
"the word (or phrase) that comes immediately after. \n"
" For example searching for 'apple pie' or cherry -butter will "
"return any recipe that includes the phrase 'apple pie' or the word "
"'cherry' \n"
" in any field included in the full text search but exclude any "
"recipe that has the word 'butter' in any field included.\n"
" "
msgstr ""
" \n"
" Spletna iskanja simulirajo funkcionalnost, ki jo najdete na "
"številnih spletnih iskalnikih, ki podpirajo posebno sintakso.\n"
" Postavitev narekovajev okoli več besed bo te besede pretvorila v "
"frazo.\n"
" 'ali' je prepoznan kot iskanje besede (ali besedne zveze) "
"neposredno pred 'ali' ALI besede (ali besedne zveze) neposredno za njim.\n"
" '-' »-« je prepoznan kot iskanje receptov, ki ne vključujejo "
"besede (ali fraze), ki sledi takoj za tem.\n"
" Iskanje na primer 'jabolčna pita' ali češnjevo -maslo bo vrnilo "
"vse recepte, ki vključujejo besedno zvezo 'jabolčna pita' ali besedo "
"'češnja'\n"
" v katerem koli polju, vključenem v iskanje po celotnem besedilu, "
"vendar izključite vse recepte, ki vsebujejo besedo \"maslo\" v katerem koli "
"vključenem polju.\n"
" "
#: .\cookbook\templates\search_info.html:48
msgid ""
" \n"
" Raw search is similar to Web except will take puncuation "
"operators such as '|', '&' and '()'\n"
" "
msgstr ""
" \n"
" Surovo iskanje je podobno spletnemu, le da uporablja ločila, kot "
"so '|', '&' in '()'\n"
" "
#: .\cookbook\templates\search_info.html:59
msgid ""
" \n"
" Another approach to searching that also requires Postgresql is "
"fuzzy search or trigram similarity. A trigram is a group of three "
"consecutive characters.\n"
" For example searching for 'apple' will create x trigrams 'app', "
"'ppl', 'ple' and will create a score of how closely words match the "
"generated trigrams.\n"
" One benefit of searching trigams is that a search for 'sandwich' "
"will find misspelled words such as 'sandwhich' that would be missed by other "
"methods.\n"
" "
msgstr ""
" \n"
" Drug pristop k iskanju, ki prav tako zahteva Postgresql, je "
"mehko iskanje ali podobnost trigramov. Trigram je skupina treh zaporednih "
"znakov.\n"
" Če na primer iščete 'apple', boste ustvarili x trigramov 'app', "
"'ppl', 'ple' in ustvarili oceno, kako natančno se besede ujemajo z "
"ustvarjenimi trigrami.\n"
" Ena od prednosti iskanja po trigamah je, da bo iskanje 'sendvič' "
"našlo napačno črkovane besede, kot je 'sendvič', ki bi jih druge metode "
"zgrešile.\n"
" "
#: .\cookbook\templates\search_info.html:69
msgid "Search Fields"
msgstr "Iskalna polja"
#: .\cookbook\templates\search_info.html:73
msgid ""
" \n"
" Unaccent is a special case in that it enables searching a field "
"'unaccented' for each search style attempting to ignore accented values. \n"
" For example when you enable unaccent for 'Name' any search "
"(starts with, contains, trigram) will attempt the search ignoring accented "
"characters.\n"
" \n"
" For the other options, you can enable search on any or all "
"fields and they will be combined together with an assumed 'OR'.\n"
" For example enabling 'Name' for Starts With, 'Name' and "
"'Description' for Partial Match and 'Ingredients' and 'Keywords' for Full "
"Search\n"
" and searching for 'apple' will generate a search that will "
"return recipes that have:\n"
" - A recipe name that starts with 'apple'\n"
" - OR a recipe name that contains 'apple'\n"
" - OR a recipe description that contains 'apple'\n"
" - OR a recipe that will have a full text search match ('apple' "
"or 'apples') in ingredients\n"
" - OR a recipe that will have a full text search match in "
"Keywords\n"
"\n"
" Combining too many fields in too many types of search can have a "
"negative impact on performance, create duplicate results or return "
"unexpected results.\n"
" For example, enabling fuzzy search or partial matches will "
"interfere with web search methods. \n"
" Searching for 'apple -pie' with fuzzy search and full text "
"search will return the recipe Apple Pie. Though it is not included in the "
"full text results, it does match the trigram results.\n"
" "
msgstr ""
" \n"
" Nenaglas je poseben primer, saj omogoča iskanje po polju "
"'nenaglašeno' za vsak slog iskanja, ki poskuša prezreti naglašene "
"vrednosti.\n"
" Na primer, ko omogočite nenaglas za 'Ime', bo vsako iskanje "
"(začne se z, vsebuje, trigram) poskušalo iskati brez upoštevanja naglašenih "
"znakov.\n"
" \n"
" Za druge možnosti lahko omogočite iskanje po katerem koli ali "
"vseh poljih in združena bodo skupaj z domnevnim 'ALI'.\n"
" Na primer omogočite »Ime« za Začne se z, »Ime« in »Opis« za "
"delno ujemanje ter »Sestavine« in »Ključne besede« za celotno iskanje\n"
" in iskanje 'jabolko' bo ustvarilo iskanje, ki bo vrnilo recepte, "
"ki imajo:\n"
" - Ime recepta, ki se začne z \"jabolko\"\n"
" - ALI ime recepta, ki vsebuje 'jabolko'\n"
" - ALI opis recepta, ki vsebuje \"jabolko\"\n"
" - ALI recept, ki bo vseboval ujemanje celotnega besedila "
"('jabolko' ali 'jabolka') v sestavinah\n"
" - ALI recept, ki bo vseboval ujemanje celotnega besedila v "
"ključnih besedah\n"
"\n"
" Združevanje preveč polj v preveč vrstah iskanja lahko negativno "
"vpliva na uspešnost, ustvari podvojene rezultate ali vrne nepričakovane "
"rezultate.\n"
" Na primer, omogočanje mehkega iskanja ali delnih ujemanj bo "
"motilo metode spletnega iskanja.\n"
" Iskanje 'jabolčna -pita' z mehkim iskanjem in iskanjem po "
"celotnem besedilu bo vrnilo recept za jabolčno pito. Čeprav ni vključen v "
"rezultate celotnega besedila, se ujema z rezultati trigrama.\n"
" "
#: .\cookbook\templates\search_info.html:95
msgid "Search Index"
msgstr "Indeks iskanja"
#: .\cookbook\templates\search_info.html:99
msgid ""
" \n"
" Trigram search and Full Text Search both rely on database "
"indexes to perform effectively. \n"
" You can rebuild the indexes on all fields in the Admin page for "
"Recipes and selecting all recipes and running 'rebuild index for selected "
"recipes'\n"
" You can also rebuild indexes at the command line by executing "
"the management command 'python manage.py rebuildindex'\n"
" "
msgstr ""
" \n"
" Iskanje po trigramu in iskanje po celotnem besedilu se za "
"učinkovito delovanje zanašata na indekse baze podatkov.\n"
" Znova lahko zgradite indekse na vseh poljih na skrbniški strani "
"za recepte in izberete vse recepte ter zaženete 'ponovno zgradi indeks za "
"izbrane recepte'\n"
" Indekse lahko znova zgradite tudi v ukazni vrstici, tako da "
"izvedete upravljalni ukaz 'python manage.py rebuildindex'\n"
" "
#: .\cookbook\templates\setup.html:6
msgid "Cookbook Setup"
msgstr "Nastavitev kuharske knjige"
#: .\cookbook\templates\setup.html:14
msgid "Setup"
msgstr "Nastavitev"
#: .\cookbook\templates\setup.html:15
msgid ""
"To start using this application you must first create a superuser account."
msgstr ""
"Če želite začeti uporabljati to aplikacijo, morate najprej ustvariti račun "
"superuporabnika."
#: .\cookbook\templates\setup.html:20
msgid "Create Superuser account"
msgstr "Ustvari račun superuporabnika"
#: .\cookbook\templates\socialaccount\authentication_error.html:7
#: .\cookbook\templates\socialaccount\authentication_error.html:23
msgid "Social Network Login Failure"
msgstr "Napaka pri prijavi s socialnim omrežjem"
#: .\cookbook\templates\socialaccount\authentication_error.html:25
msgid ""
"An error occurred while attempting to login via your social network account."
msgstr ""
"Pri poskusu prijave prek vašega računa socialnega omrežja je prišlo do "
"napake."
#: .\cookbook\templates\socialaccount\connections.html:4
#: .\cookbook\templates\socialaccount\connections.html:7
msgid "Account Connections"
msgstr "Povezave računov"
#: .\cookbook\templates\socialaccount\connections.html:10
msgid ""
"You can sign in to your account using any of the following third party\n"
" accounts:"
msgstr ""
"V svoj račun se lahko prijavite s katerim koli računom od naslednjih "
"tretjih \n"
" oseb:"
#: .\cookbook\templates\socialaccount\connections.html:44
msgid ""
"You currently have no social network accounts connected to this account."
msgstr ""
"Trenutno nimate nobenega računa družbenega omrežja, povezanega s tem računom."
#: .\cookbook\templates\socialaccount\connections.html:47
msgid "Add a 3rd Party Account"
msgstr "Dodajte račun tretje osebe"
#: .\cookbook\templates\socialaccount\login.html:5
#: .\cookbook\templates\socialaccount\signup.html:5
msgid "Signup"
msgstr "Registracija"
#: .\cookbook\templates\socialaccount\login.html:9
#, python-format
msgid "Connect %(provider)s"
msgstr "Poveži %(provider)s"
#: .\cookbook\templates\socialaccount\login.html:11
#, python-format
msgid "You are about to connect a new third party account from %(provider)s."
msgstr "Povezali boste nov račun tretje osebe od %(provider)s."
#: .\cookbook\templates\socialaccount\login.html:13
#, python-format
msgid "Sign In Via %(provider)s"
msgstr "Prijavite se prek %(provider)s"
#: .\cookbook\templates\socialaccount\login.html:15
#, python-format
msgid "You are about to sign in using a third party account from %(provider)s."
msgstr "Prijavili se boste z računom tretje osebe %(provider)s."
#: .\cookbook\templates\socialaccount\login.html:20
msgid "Continue"
msgstr "Nadaljuj"
#: .\cookbook\templates\socialaccount\signup.html:10
#, python-format
msgid ""
"You are about to use your\n"
" %(provider_name)s account to login to\n"
" %(site_name)s. As a final step, please complete the following form:"
msgstr ""
"Uporabili boste svoj\n"
" %(provider_name)s račun za prijavo v\n"
" %(site_name)s. Kot zadnji korak izpolnite naslednji obrazec:"
#: .\cookbook\templates\socialaccount\signup.html:32
msgid "I accept the following"
msgstr "Sprejemam naslednje"
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:23
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:31
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:39
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:47
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:55
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:63
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:71
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:79
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:87
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:95
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:103
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:111
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:119
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:127
msgid "Sign in using"
msgstr "Prijavi se z uporabo"
#: .\cookbook\templates\space_overview.html:6
msgid "Overview"
msgstr "Pregled"
#: .\cookbook\templates\space_overview.html:13
msgid "Space"
msgstr "Prostor"
#: .\cookbook\templates\space_overview.html:17
msgid ""
"Recipes, foods, shopping lists and more are organized in spaces of one or "
"more people."
msgstr ""
"Recepti, živila, nakupovalni seznami in drugo so organizirani v prostorih "
"ene ali več oseb."
#: .\cookbook\templates\space_overview.html:18
msgid ""
"You can either be invited into an existing space or create your own one."
msgstr "Lahko ste povabljeni v obstoječi prostor ali ustvarite svojega."
#: .\cookbook\templates\space_overview.html:25
msgid "Your Spaces"
msgstr "Vaš prostor"
#: .\cookbook\templates\space_overview.html:53
msgid "Owner"
msgstr "Lastnik"
#: .\cookbook\templates\space_overview.html:73
#: .\cookbook\templates\space_overview.html:83
msgid "Join Space"
msgstr "Pridruži se prostoru"
#: .\cookbook\templates\space_overview.html:76
msgid "Join an existing space."
msgstr "Pridruži se obstoječemu prostoru."
#: .\cookbook\templates\space_overview.html:78
msgid ""
"To join an existing space either enter your invite token or click on the "
"invite link the space owner send you."
msgstr ""
"Če se želite pridružiti obstoječemu prostoru, vnesite žeton za povabilo ali "
"kliknite povezavo s povabilom, ki vam jo pošlje lastnik prostora."
#: .\cookbook\templates\space_overview.html:91
#: .\cookbook\templates\space_overview.html:100
msgid "Create Space"
msgstr "Ustvari prostor"
#: .\cookbook\templates\space_overview.html:94
msgid "Create your own recipe space."
msgstr "Ustvarite svoj prostor za recepte."
#: .\cookbook\templates\space_overview.html:96
msgid "Start your own recipe space and invite other users to it."
msgstr "Odprite svoj prostor za recepte in vanj povabite druge uporabnike."
#: .\cookbook\templates\system.html:23
msgid "System"
msgstr "Sistem"
#: .\cookbook\templates\system.html:24
msgid ""
"\n"
" Tandoor Recipes is an open source free software application. It can "
"be found on\n"
" GitHub.\n"
" Changelogs can be found here.\n"
" "
msgstr ""
"\n"
" Tandoor Recipes je odprtokodna brezplačna programska oprema. Najdete "
"jo na\n"
" GitHub.\n"
" Dnevnike sprememb najdete tukaj.\n"
" "
#: .\cookbook\templates\system.html:30
msgid "System Information"
msgstr "Informacije o sistemu"
#: .\cookbook\templates\system.html:51
msgid ""
"\n"
" You need to execute version.py in your update "
"script to generate version information (done automatically in docker).\n"
" "
msgstr ""
"\n"
" Morate izvesti version.py v skripti za "
"posodabljanje, da ustvarite informacije o različici (izvedeno samodejno v "
"vrstici Docker).\n"
" "
#: .\cookbook\templates\system.html:56
msgid "Plugins"
msgstr "Vtičniki"
#: .\cookbook\templates\system.html:67
msgid "Media Serving"
msgstr "Medijski servis"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:123 .\cookbook\templates\system.html:134
msgid "Warning"
msgstr "Opozorilo"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:125 .\cookbook\templates\system.html:134
msgid "Ok"
msgstr "V redu"
#: .\cookbook\templates\system.html:70
msgid ""
"Serving media files directly using gunicorn/python is not recommend!\n"
" Please follow the steps described\n"
" here to update\n"
" your installation.\n"
" "
msgstr ""
"Streženje medijskih datotek neposredno z gunicorn/python ni "
"priporočljivo!\n"
" Sledite tukaj opisanim korakom\n"
" za posodobitev\n"
" vaše namestitve.\n"
" "
#: .\cookbook\templates\system.html:76 .\cookbook\templates\system.html:91
#: .\cookbook\templates\system.html:104 .\cookbook\templates\system.html:115
#: .\cookbook\views\views.py:168
msgid "Everything is fine!"
msgstr "Vse je v redu!"
#: .\cookbook\templates\system.html:80
msgid "Secret Key"
msgstr "Skrivni ključ"
#: .\cookbook\templates\system.html:84
msgid ""
"\n"
" You do not have a SECRET_KEY configured in your "
".env file. Django defaulted to the\n"
" standard key\n"
" provided with the installation which is publicly know and "
"insecure! Please set\n"
" SECRET_KEY int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" V datoteki .env nimate konfiguriranega "
"SECRET_KEY. Django je privzeto uporabil\n"
" standardni ključ,\n"
" priložen namestitvi, ki je javno znan in nevaren! Prosim "
"nastavi\n"
" SECRET_KEY v .env konfiguracijsko "
"datoteko.\n"
" "
#: .\cookbook\templates\system.html:94
msgid "Debug Mode"
msgstr "Način odpravljanja napak"
#: .\cookbook\templates\system.html:98
msgid ""
"\n"
" This application is still running in debug mode. This is most "
"likely not needed. Turn of debug mode by\n"
" setting\n"
" DEBUG=0 int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" Ta aplikacija se še vedno izvaja v načinu za odpravljanje napak. "
"To najverjetneje ni potrebno. Izklopite način za odpravljanje napak z\n"
" nastavitvijo\n"
" DEBUG=0 v .env konfiguracijski "
"datoteki.\n"
" "
#: .\cookbook\templates\system.html:107
msgid "Allowed Hosts"
msgstr "Dovoljeni gostitelji"
#: .\cookbook\templates\system.html:111
msgid ""
"\n"
" Your allowed hosts are configured to allow every host. This "
"might be ok in some setups but should be avoided. Please see the docs about "
"this.\n"
" "
msgstr ""
"\n"
" Vaši dovoljeni gostitelji so konfigurirani tako, da dovolijo vse "
"gostitelje. To je morda v redu pri nekaterih nastavitvah, vendar se ga je "
"treba izogibati. Oglejte si dokumentacijo o tem.\n"
" "
#: .\cookbook\templates\system.html:118
msgid "Database"
msgstr "Baza podatkov"
#: .\cookbook\templates\system.html:121
msgid "Info"
msgstr "Informacije"
#: .\cookbook\templates\system.html:131 .\cookbook\templates\system.html:148
msgid "Migrations"
msgstr "Migracije"
#: .\cookbook\templates\system.html:137
msgid ""
"\n"
" Migrations should never fail!\n"
" Failed migrations will likely cause major parts of the app to "
"not function correctly.\n"
" If a migration fails make sure you are on the latest version and "
"if so please post the migration log and the overview below in a GitHub "
"issue.\n"
" "
msgstr ""
"\n"
" Selitve ne smejo nikoli spodleteti!\n"
" Neuspele selitve bodo verjetno povzročile nepravilno delovanje "
"večjih delov aplikacije.\n"
" Če selitev ne uspe, se prepričajte, da uporabljate najnovejšo "
"različico, in če je tako, objavite dnevnik selitve in spodnji pregled v "
"številki GitHub.\n"
" "
#: .\cookbook\templates\system.html:238
msgid "False"
msgstr "Nepravilno"
#: .\cookbook\templates\system.html:238
msgid "True"
msgstr "Pravilno"
#: .\cookbook\templates\system.html:268
msgid "Hide"
msgstr "Skrij"
#: .\cookbook\templates\system.html:271
msgid "Show"
msgstr "Prikaži"
#: .\cookbook\templates\test2.html:6
msgid "Export Recipes"
msgstr "Izvoz receptov"
#: .\cookbook\templates\test2.html:14 .\cookbook\templates\test2.html:20
msgid "Export"
msgstr "Izvozi"
#: .\cookbook\views\api.py:198 .\cookbook\views\api.py:326
msgid "Parameter updated_at incorrectly formatted"
msgstr "Parameter posodobljeno_ob ni pravilno oblikovan"
#: .\cookbook\views\api.py:351 .\cookbook\views\api.py:484
#, python-brace-format
msgid "No {self.basename} with id {pk} exists"
msgstr "Nobena {self.basename} z id {pk} ne obstaja"
#: .\cookbook\views\api.py:355
msgid "Cannot merge with the same object!"
msgstr "Ni mogoče spojiti z istim objektom!"
#: .\cookbook\views\api.py:362
#, python-brace-format
msgid "No {self.basename} with id {target} exists"
msgstr "Nobena {self.basename} z id {target} ne obstaja"
#: .\cookbook\views\api.py:367
msgid "Cannot merge with child object!"
msgstr "Ni mogoče spojiti s podrejenim predmetom!"
#: .\cookbook\views\api.py:405
#, python-brace-format
msgid "{source.name} was merged successfully with {target.name}"
msgstr "{source.name} je bil uspešno združen z {target.name}"
#: .\cookbook\views\api.py:411
#, python-brace-format
msgid "An error occurred attempting to merge {source.name} with {target.name}"
msgstr ""
"Pri poskusu združitve {source.name} z {target.name} je prišlo do napake"
#: .\cookbook\views\api.py:493
#, python-brace-format
msgid "{child.name} was moved successfully to the root."
msgstr "{child.name} je bil uspešno premaknjen v koren."
#: .\cookbook\views\api.py:496 .\cookbook\views\api.py:514
msgid "An error occurred attempting to move "
msgstr "Pri poskusu premikanja je prišlo do napake "
#: .\cookbook\views\api.py:499
msgid "Cannot move an object to itself!"
msgstr "Predmeta ni mogoče premakniti k sebi!"
#: .\cookbook\views\api.py:505
#, python-brace-format
msgid "No {self.basename} with id {parent} exists"
msgstr "Noben {self.basename} z id {parent} ne obstaja"
#: .\cookbook\views\api.py:511
#, python-brace-format
msgid "{child.name} was moved successfully to parent {parent.name}"
msgstr "{child.name} je bil uspešno premaknjen k staršu {parent.name}"
#: .\cookbook\views\api.py:992
#, python-brace-format
msgid "{obj.name} was removed from the shopping list."
msgstr "{obj.name} je bil odstranjen z nakupovalnega seznama."
#: .\cookbook\views\api.py:997 .\cookbook\views\api.py:1627
#, python-brace-format
msgid "{obj.name} was added to the shopping list."
msgstr "{obj.name} je bil dodan na nakupovalni seznam."
#: .\cookbook\views\api.py:1239
msgid "Filter meal plans from date (inclusive)."
msgstr "Filtriraj načrte obrokov od datuma (vključno)."
#: .\cookbook\views\api.py:1241
msgid "Filter meal plans to date (inclusive)."
msgstr "Filtriraj načrte obrokov do danes (vključno)."
#: .\cookbook\views\api.py:1244
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
msgstr ""
"Filtrirajte načrte obrokov z ID-jem MealType. Za parameter večkratne "
"ponovitve."
#: .\cookbook\views\api.py:1404
msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgstr "ID recepta, katerega del je korak. Za parameter večkratne ponovitve."
#: .\cookbook\views\api.py:1406
msgid "Query string matched (fuzzy) against object name."
msgstr "Poizvedbeni niz se ujema (mehko) z imenom predmeta."
#: .\cookbook\views\api.py:1442
msgid ""
"Query string matched (fuzzy) against recipe name. In the future also "
"fulltext search."
msgstr ""
"Poizvedbeni niz se ujema (mehko) z imenom recepta. V prihodnosti tudi "
"iskanje po celotnem besedilu."
#: .\cookbook\views\api.py:1444
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr ""
"ID ključne besede, ki bi jo moral imeti recept. Za parameter večkratne "
"ponovitve. Enakovredno ključnim besedam_ali"
#: .\cookbook\views\api.py:1445
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr ""
"ID-ji ključnih besed, ponovite za več. Vrne recepte s katero koli od "
"ključnih besed"
#: .\cookbook\views\api.py:1446
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr ""
"ID-ji ključnih besed, ponovite za več. Vrne recepte z vsemi ključnimi "
"besedami."
#: .\cookbook\views\api.py:1447
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr ""
"ID-ji ključnih besed, ponovite za več. Izključite recepte s katero koli od "
"ključnih besed."
#: .\cookbook\views\api.py:1448
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr ""
"ID-ji ključnih besed, ponovite za več. Izključite recepte z vsemi ključnimi "
"besedami."
#: .\cookbook\views\api.py:1450
msgid "ID of food a recipe should have. For multiple repeat parameter."
msgstr "ID živila, ki ga mora imeti recept. Za parameter večkratne ponovitve."
#: .\cookbook\views\api.py:1451
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr "ID-ji hrane, ponovite za večkrat. Vrne recepte s katerim koli živilom"
#: .\cookbook\views\api.py:1452
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr "ID-ji hrane, ponovite za večkrat. Vrne recepte z vsemi živili."
#: .\cookbook\views\api.py:1453
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr "ID-ji hrane, ponovite za večkrat. Izključi recepte s katerim od živil."
#: .\cookbook\views\api.py:1454
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr "ID-ji hrane, ponovite za večkrat. Izključi recepte z vsemi živili."
#: .\cookbook\views\api.py:1456
msgid "ID of book a recipe should be in. For multiple repeat parameter."
msgstr ""
"ID knjige, v kateri mora biti recept. Za parameter večkratne ponovitve."
#: .\cookbook\views\api.py:1457
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr "ID-ji knjig, ponovite za več. Vrne recepte s katero koli knjigo"
#: .\cookbook\views\api.py:1458
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr "ID-ji knjig, ponovite za več. Vrne recepte z vsemi knjigami."
#: .\cookbook\views\api.py:1459
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr "ID-ji knjig, ponovite za več. Izključi recepte s katero koli knjigo."
#: .\cookbook\views\api.py:1460
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr "ID-ji knjig, ponovite za več. Izključi recepte z vsemi knjigami."
#: .\cookbook\views\api.py:1462
msgid "ID of unit a recipe should have."
msgstr "ID enote, ki bi jo moral imeti recept."
#: .\cookbook\views\api.py:1464
msgid "Exact rating of recipe"
msgstr "Natančna ocena recepta"
#: .\cookbook\views\api.py:1465
msgid "Rating a recipe should have or greater."
msgstr "Ocena, ki bi jo moral imeti recept, ali višja."
#: .\cookbook\views\api.py:1466
msgid "Rating a recipe should have or smaller."
msgstr "Ocena, ki jo mora imeti recept, je 1 ali manjša."
#: .\cookbook\views\api.py:1468
msgid "Filter recipes cooked X times."
msgstr "Filtriraj recepte, kuhane X-krat."
#: .\cookbook\views\api.py:1469
msgid "Filter recipes cooked X times or more."
msgstr "Filtriraj recepte, kuhane X-krat ali večkrat."
#: .\cookbook\views\api.py:1470
msgid "Filter recipes cooked X times or less."
msgstr "Filtriraj recepte, kuhane X-krat ali manj."
#: .\cookbook\views\api.py:1472
msgid "Filter recipes created on the given date."
msgstr "Filtriraj recepte, ustvarjene na določen datum."
#: .\cookbook\views\api.py:1473
msgid "Filter recipes created on the given date or after."
msgstr "Filtriraj recepte, ustvarjene na določen datum ali pozneje."
#: .\cookbook\views\api.py:1474
msgid "Filter recipes created on the given date or before."
msgstr "Filtriraj recepte, ustvarjene na določen datum ali prej."
#: .\cookbook\views\api.py:1476 .\cookbook\views\api.py:1477
#: .\cookbook\views\api.py:1478
msgid "Filter recipes updated on the given date."
msgstr "Filtriraj recepte, posodobljene na navedeni datum."
#: .\cookbook\views\api.py:1480
msgid "Filter recipes last cooked on the given date or after."
msgstr ""
"Filtriraj recepte, ki so bili nazadnje kuhani na določen datum ali pozneje."
#: .\cookbook\views\api.py:1481
msgid "Filter recipes last cooked on the given date or before."
msgstr ""
"Filtriraj recepte, ki so bili nazadnje kuhani na določen datum ali prej."
#: .\cookbook\views\api.py:1483 .\cookbook\views\api.py:1484
msgid "Filter recipes lasts viewed on the given date."
msgstr "Filtriraj recepte, ki so bili nazadnje ogledani na določen datum."
#: .\cookbook\views\api.py:1486
msgid "Filter recipes for ones created by the given user ID"
msgstr "Filtriraj recepte po tistih, ki jih je ustvaril dani uporabniški ID"
#: .\cookbook\views\api.py:1487
msgid "If only internal recipes should be returned. [true/false]"
msgstr ""
"Če bi iskanje moralo vrniti samo interne recepte. [potrdi/zavrzi]"
#: .\cookbook\views\api.py:1488
msgid "Returns the results in randomized order. [true/false]"
msgstr "Vrne rezultate v naključnem vrstnem redu. [potrdi/zavrzi]"
#: .\cookbook\views\api.py:1490
msgid ""
"Determines the order of the results. Options are: score,-score,name,-name,"
"lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-"
"created_at,lastviewed,-lastviewed"
msgstr ""
"Določa vrstni red rezultatov. Možnosti so: "
"score,-score,name,-name,lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-created_at,lastviewed,-lastviewed"
""
#: .\cookbook\views\api.py:1492
msgid "Returns new results first in search results. [true/false]"
msgstr "Vrne nove rezultate prve med rezultati iskanja. [potrdi/zavrzi]"
#: .\cookbook\views\api.py:1493
msgid ""
"Returns the given number of recently viewed recipes before search results "
"(if given)"
msgstr ""
"Vrne podano število nedavno ogledanih receptov pred rezultati iskanja "
"(če je podano)"
#: .\cookbook\views\api.py:1494
msgid "ID of a custom filter. Returns all recipes matched by that filter."
msgstr "ID filtra po meri. Vrne vse recepte, ki se ujemajo s tem filtrom."
#: .\cookbook\views\api.py:1495
msgid "Filter recipes that can be made with OnHand food. [true/false]"
msgstr ""
"Filtrirajte recepte, ki jih lahko pripravite s hrano OnHand. [potrdi/"
"zavrzi]"
#: .\cookbook\views\api.py:1773
msgid ""
"Return the PropertyTypes matching the property category. Repeat for "
"multiple."
msgstr ""
"Vrne vrste lastnosti (PropertyTypes), ki ustrezajo kategoriji lastnosti. "
"Ponovite za več vrst lastnosti."
#: .\cookbook\views\api.py:1804 .\cookbook\views\api.py:1860
msgid "Returns only entries associated with the given mealplan id"
msgstr "Vrne samo vnose, povezane z danim ID-jem načrta obrokov"
#: .\cookbook\views\api.py:1858
msgid ""
"Returns only elements updated after the given timestamp in ISO 8601 format."
msgstr ""
"Vrne samo elemente, posodobljene po danem časovnem žigu v formatu ISO 8601."
#: .\cookbook\views\api.py:2031
msgid ""
"Return the Automations matching the automation type. Repeat for multiple."
msgstr ""
"Vrne avtomatizacije, ki ustrezajo vrsti avtomatizacije. Ponovite za več "
"avtomatizacij."
#: .\cookbook\views\api.py:2048
msgid ""
"Text field to store data that gets carried over to the UserSpace created "
"from the InviteLink"
msgstr ""
"Besedilno polje za shranjevanje podatkov, ki se prenesejo v uporabniški "
"prostor, ustvarjen iz InviteLink"
#: .\cookbook\views\api.py:2049
msgid "Only return InviteLinks that have not been used yet."
msgstr "Vrni samo povezave InviteLink, ki še niso bile uporabljene."
#: .\cookbook\views\api.py:2076
msgid "Return the CustomFilters matching the model type. Repeat for multiple."
msgstr ""
"Vrne filtre CustomFilters, ki ustrezajo vrsti modela. Ponovite za več "
"filtrov."
#: .\cookbook\views\api.py:2176
msgid "Nothing to do."
msgstr "Nič za narediti."
#: .\cookbook\views\api.py:2222
msgid "Invalid Url"
msgstr "Neveljaven URL"
#: .\cookbook\views\api.py:2228
msgid "Connection Refused."
msgstr "Povezava zavrnjena."
#: .\cookbook\views\api.py:2232
msgid "Bad URL Schema."
msgstr "Slaba shema URL-ja."
#: .\cookbook\views\api.py:2257
msgid "No usable data could be found."
msgstr "Uporabnih podatkov ni bilo mogoče najti."
#: .\cookbook\views\api.py:2286 .\cookbook\views\api.py:2434
msgid "You must select an AI provider to perform your request."
msgstr "Za izvedbo vaše zahteve morate izbrati ponudnika umetne inteligence."
#: .\cookbook\views\api.py:2293 .\cookbook\views\api.py:2441
msgid ""
"You don't have any credits remaining to use AI or AI features are not "
"enabled for your space."
msgstr ""
"Nimate več kreditov za uporabo umetne inteligence ali pa funkcije umetne "
"inteligence niso omogočene za vaš prostor."
#: .\cookbook\views\api.py:2499 .\cookbook\views\api.py:2667
msgid "File is above space limit"
msgstr "Datoteka presega omejitev prostora"
#: .\cookbook\views\api.py:2522 .\cookbook\views\api.py:2684
msgid "Importing is not implemented for this provider"
msgstr "Uvoz ni implementiran za tega ponudnika"
#: .\cookbook\views\api.py:2548
msgid ""
"The PDF Exporter is not enabled on this instance as it is still in an "
"experimental state."
msgstr ""
"Izvoznik PDF v tem primeru ni omogočen, ker je še vedno v poskusnem stanju."
#: .\cookbook\views\api.py:2842
msgid "This feature is not yet available in the hosted version of tandoor!"
msgstr "Ta funkcija še ni na voljo v gostujoči različici tandoorja!"
#: .\cookbook\views\api.py:2863
msgid "Sync successful!"
msgstr "Sinhronizacija uspela!"
#: .\cookbook\views\api.py:2866
msgid "Error synchronizing with Storage"
msgstr "Napaka pri sinhronizaciji s shrambo"
#: .\cookbook\views\views.py:89
msgid "This feature is not available in the demo version!"
msgstr "Ta funkcija ni na voljo v demo različici!"
#: .\cookbook\views\views.py:110
msgid ""
"You have successfully created your own recipe space. Start by adding some "
"recipes or invite other people to join you."
msgstr ""
"Uspešno ste ustvarili svoj prostor za recepte. Začnite tako, da dodate nekaj "
"receptov ali povabite druge ljudi, da se vam pridružijo."
#: .\cookbook\views\views.py:171
#, python-format
msgid "PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!"
msgstr ""
"PostgreSQL %(v)s je zastarel. Nadgradite na popolnoma podprto različico!"
#: .\cookbook\views\views.py:174
#, python-format
msgid "You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended"
msgstr "Uporabljate PostgreSQL %(v1)s. Priporočen je PostgreSQL %(v2)s"
#: .\cookbook\views\views.py:178
msgid "Unable to determine PostgreSQL version."
msgstr "Ni mogoče določiti različice PostgreSQL."
#: .\cookbook\views\views.py:182
msgid ""
"This application is not running with a Postgres database backend. This is ok "
"but not recommended as some features only work with postgres databases."
msgstr ""
"Ta aplikacija se ne izvaja z zaledjem baze podatkov Postgres. To je v redu, "
"vendar ni priporočljivo, saj nekatere funkcije delujejo samo z bazami "
"podatkov Postgres."
#: .\cookbook\views\views.py:296
msgid ""
"The setup page can only be used to create the first "
"user! If you have forgotten your superuser credentials "
"please consult the django documentation on how to reset passwords."
msgstr ""
"Nastavitveno stran lahko uporabite samo za ustvarjanje prvega "
"uporabnika! Če ste pozabili poverilnice superuporabnika, "
"si oglejte dokumentacijo django za ponastavitev gesel."
#: .\cookbook\views\views.py:304
msgid "Passwords dont match!"
msgstr "Gesli se ne ujemata!"
#: .\cookbook\views\views.py:312
msgid "User has been created, please login!"
msgstr "Uporabnik je bil ustvarjen, prijavite se!"
#: .\cookbook\views\views.py:352
msgid ""
"Reporting share links is not enabled for this instance. Please notify the "
"page administrator to report problems."
msgstr ""
"Poročanje o povezavah za skupno rabo ni omogočeno za ta primer. Za prijavo "
"težav obvestite skrbnika strani."
#: .\cookbook\views\views.py:357
msgid ""
"Recipe sharing link has been disabled! For additional information please "
"contact the page administrator."
msgstr ""
"Povezava za deljenje receptov je onemogočena! Za dodatne informacije se "
"obrnite na skrbnika strani."
#: .\cookbook\views\views.py:383
msgid "Manage recipes, shopping list, meal plans and more."
msgstr "Upravljajte recepte, nakupovalne sezname, načrte obrokov in drugo."
#: .\cookbook\views\views.py:397 .\cookbook\views\views.py:398
msgid "Plan"
msgstr "Načrt"
#: .\cookbook\views\views.py:399
msgid "View your meal Plan"
msgstr "Oglejte si svoj načrt obrokov"
#: .\cookbook\views\views.py:418
msgid "View your shopping lists"
msgstr "Oglejte si nakupovalne sezname"
#~ msgid ""
#~ "Both fields are optional. If none are given the username will be "
#~ "displayed instead"
#~ msgstr ""
#~ "Obe polji sta opcijski. V primeru, da ju pustimo prazni bo prikazano "
#~ "uporabniško ime"
#~ msgid "Name"
#~ msgstr "Ime"
#~ msgid "Keywords"
#~ msgstr "Ključne besede"
#~ msgid "Preparation time in minutes"
#~ msgstr "Priprava v minutah"
#~ msgid "Waiting time (cooking/baking) in minutes"
#~ msgstr "Čas čakanja v minutah"
#~ msgid "Path"
#~ msgstr "Pot"
#~ msgid "Storage UID"
#~ msgstr "UID shrambe"
#~ msgid "Add your comment: "
#~ msgstr "Dodaj komentar: "
#~ msgid "Leave empty for dropbox and enter app password for nextcloud."
#~ msgstr "Pusti prazno za dropbox in vnesi geslo za nextcloud."
#~ msgid "Leave empty for nextcloud and enter api token for dropbox."
#~ msgstr "Pusti prazno za nextcloud in vnesi API žeton za dropbox."
#~ msgid ""
#~ "Leave empty for dropbox and enter only base url for nextcloud (/"
#~ "remote.php/webdav/ is added automatically)"
#~ msgstr ""
#~ "Pusti prazno za dropbox in vnesi URL za nextcloud (/remote.php/"
#~ "webdav/ je dodano avtomatsko)"
#~ msgid ""
#~ "Long Lived Access Token for your HomeAssistant instance"
#~ msgstr ""
#~ "Dolgoročen dostopni žeton za vašo HomeAssistant instanco"
#~ msgid "Something like http://homeassistant.local:8123/api"
#~ msgstr "Nekaj podobnega http://homeassistant.local:8123/api"
#~ msgid "http://homeassistant.local:8123/api for example"
#~ msgstr "http://homeassistant.local:8123/api na primer"
#~ msgid "Storage"
#~ msgstr "Shramba"
#~ msgid "Active"
#~ msgstr "Aktivno"
#~ msgid "Search String"
#~ msgstr "Iskalni niz"
#~ msgid "File ID"
#~ msgstr "ID datoteke"
#~ msgid ""
#~ "Determines how fuzzy a search is if it uses trigram similarity matching "
#~ "(e.g. low values mean more typos are ignored)."
#~ msgstr ""
#~ "Določa, kakšno je iskanje, če uporablja trigram podobnost ujemanje (npr. "
#~ "nizke vrednosti pomenijo več, tipkanje se prezre)."
#~ msgid ""
#~ "Select type method of search. Click here "
#~ "for full description of choices."
#~ msgstr ""
#~ "Izberi metodo iskanja. Klikni tukaj za "
#~ "prikaz vseh izbir."
#~ msgid ""
#~ "Use fuzzy matching on units, keywords and ingredients when editing and "
#~ "importing recipes."
#~ msgstr ""
#~ "Pri urejanju in uvozu receptov uporabite mehka ujemanja na enotah, "
#~ "ključnih besedah in sestavinah."
#~ msgid ""
#~ "Fields to search ignoring accents. Selecting this option can improve or "
#~ "degrade search quality depending on language"
#~ msgstr ""
#~ "Polja za iskanje prezrtih naglasov. Če izberete to možnost, lahko "
#~ "izboljšate ali poslabšate kakovost iskanja, odvisno od jezika"
#~ msgid ""
#~ "Fields to search for partial matches. (e.g. searching for 'Pie' will "
#~ "return 'pie' and 'piece' and 'soapie')"
#~ msgstr ""
#~ "Polja za iskanje delnih ujemajev. (npr. iskanje \"Pie\" vrne \"pie\" in "
#~ "\"piece\" in \"soapie\")"
#~ msgid ""
#~ "Fields to search for beginning of word matches. (e.g. searching for 'sa' "
#~ "will return 'salad' and 'sandwich')"
#~ msgstr ""
#~ "Polja za iskanje začetka ujemanja besed. (npr. iskanje \"sa\" vrne \"salad"
#~ "\" in \"sandwich\")"
#~ msgid ""
#~ "Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find "
#~ "'recipe'.) Note: this option will conflict with 'web' and 'raw' methods "
#~ "of search."
#~ msgstr ""
#~ "Polja za 'mehko' iskanje. (npr. če iščete 'recept', boste našli "
#~ "'recept'.) Opomba: ta možnost bo v nasprotju z metodama iskanja 'web' in "
#~ "'raw'."
#~ msgid ""
#~ "Fields to full text search. Note: 'web', 'phrase', and 'raw' search "
#~ "methods only function with fulltext fields."
#~ msgstr ""
#~ "Polja za iskanje po celotnem besedilu. Opomba: metode iskanja 'web', "
#~ "'phrase' in 'raw' delujejo samo s polnimi besedilnimi polji."
#~ msgid "Search Method"
#~ msgstr "Metoda iskanja"
#~ msgid "Fuzzy Lookups"
#~ msgstr "Mehka iskanja"
#~ msgid "Ignore Accent"
#~ msgstr "Prezri naglas"
#~ msgid "Partial Match"
#~ msgstr "Delno ujemanje"
#~ msgid "Starts With"
#~ msgstr "Se začne s/z"
#~ msgid "Fuzzy Search"
#~ msgstr "Mehko iskanje"
#~ msgid "Full Text"
#~ msgstr "Celotno besedilo"
#~ msgid " is part of a recipe step and cannot be deleted"
#~ msgstr " je del koraka recepta in ga ni mogoče izbrisati"
#~ msgid "Delete"
#~ msgstr "Izbriši"
#~ msgid "Settings"
#~ msgstr "Nastavitve"
#~ msgid "Email"
#~ msgstr "E-pošta"
#~ msgid "Password"
#~ msgstr "Geslo"
#~ msgid "Foods"
#~ msgstr "Živila"
#~ msgid "Units"
#~ msgstr "Enote"
#~ msgid "Supermarket"
#~ msgstr "Trgovina"
#~ msgid "Supermarket Category"
#~ msgstr "Kategorije trgovin"
#~ msgid "Automations"
#~ msgstr "Avtomatizacije"
#~ msgid "Files"
#~ msgstr "Datoteke"
#~ msgid "Batch Edit"
#~ msgstr "Paketno urejanje"
#~ msgid "History"
#~ msgstr "Zgodovina"
#~ msgid "Ingredient Editor"
#~ msgstr "Urejevalnik sestavin"
#~ msgid "Properties"
#~ msgstr "Lastnosti"
#~ msgid "Unit Conversions"
#~ msgstr "Pretvorbe enot"
#~ msgid "Create"
#~ msgstr "Ustvari"
#~ msgid "External Recipes"
#~ msgstr "Zunanji recepti"
#~ msgid "Space Settings"
#~ msgstr "Nastavitve prostora"
#~ msgid "External Connectors"
#~ msgstr "Zunanji priključki"
#~ msgid "Admin"
#~ msgstr "Skrbnik"
#~ msgid "Markdown Guide"
#~ msgstr "Označitveni vodnik"
#~ msgid "GitHub"
#~ msgstr "GitHub"
#~ msgid "Translate Tandoor"
#~ msgstr "Prevedi Tandoor"
#~ msgid "API Browser"
#~ msgstr "Brskalnik API"
#~ msgid "Log out"
#~ msgstr "Odjavi se"
#~ msgid "You are using the free version of Tandor"
#~ msgstr "Uporabljate brezplačno različico Tandorja"
#~ msgid "Upgrade Now"
#~ msgstr "Nadgradi zdaj"
#~ msgid "Batch edit Category"
#~ msgstr "Paketno urejanje kategorije"
#~ msgid "Batch edit Recipes"
#~ msgstr "Paketno urejanje receptov"
#~ msgid "Add the specified keywords to all recipes containing a word"
#~ msgstr "Dodajte navedene ključne besede vsem receptom, ki vsebujejo besedo"
#~ msgid "Sync"
#~ msgstr "Sinhronizacija"
#~ msgid "Manage watched Folders"
#~ msgstr "Upravljanje opazovanih map"
#~ msgid ""
#~ "On this Page you can manage all storage folder locations that should be "
#~ "monitored and synced."
#~ msgstr ""
#~ "Na tej strani lahko upravljate vse lokacije map za shranjevanje, ki jih "
#~ "je treba spremljati in sinhronizirati."
#~ msgid "The path must be in the following format"
#~ msgstr "Pot mora biti v naslednji obliki"
#~ msgid "Save"
#~ msgstr "Shrani"
#~ msgid "Manage External Storage"
#~ msgstr "Upravljanje zunanjega pomnilnika"
#~ msgid "Sync Now!"
#~ msgstr "Sinhroniziraj zdaj!"
#~ msgid "Show Recipes"
#~ msgstr "Prikaži recepte"
#~ msgid "Show Log"
#~ msgstr "Prikaži dnevnik"
#~ msgid "Importing Recipes"
#~ msgstr "Uvažanje receptov"
#~ msgid ""
#~ "This can take a few minutes, depending on the number of recipes in sync, "
#~ "please wait."
#~ msgstr ""
#~ "To lahko traja nekaj minut, odvisno od števila sinhroniziranih receptov, "
#~ "počakajte."
#~ msgid "Recipe Books"
#~ msgstr "Knjige z recepti"
#~ msgid "Import new Recipe"
#~ msgstr "Uvozi nov recept"
#~ msgid "Edit Recipe"
#~ msgstr "Uredi recept"
#, python-format
#~ msgid "Are you sure you want to delete the %(title)s: %(object)s "
#~ msgstr ""
#~ "Ali ste prepričani, da želite izbrisati %(title)s: %(object)s "
#~ msgid "This cannot be undone!"
#~ msgstr "Tega ni mogoče razveljaviti!"
#~ msgid "Protected"
#~ msgstr "Zaščiteno"
#~ msgid "Cascade"
#~ msgstr "Kaskada"
#~ msgid "Cancel"
#~ msgstr "Prekliči"
#~ msgid "Edit"
#~ msgstr "Uredi"
#~ msgid "View"
#~ msgstr "Pogled"
#~ msgid "Delete original file"
#~ msgstr "Izbriši izvirno datoteko"
#~ msgid "List"
#~ msgstr "Seznam"
#~ msgid "Filter"
#~ msgstr "Filter"
#~ msgid "Import all"
#~ msgstr "Uvozi vse"
#~ msgid "New"
#~ msgstr "Nov"
#~ msgid "previous"
#~ msgstr "prejšnji"
#~ msgid "next"
#~ msgstr "naslednji"
#~ msgid "View Log"
#~ msgstr "Ogled dnevnika"
#~ msgid "Cook Log"
#~ msgstr "Kuharski dnevnik"
#~ msgid "Import"
#~ msgstr "Uvoz"
#~ msgid "Security Warning"
#~ msgstr "Varnostno opozorilo"
#~ msgid ""
#~ "\n"
#~ " The Password and Token field are stored as plain text"
#~ "b> inside the database.\n"
#~ " This is necessary because they are needed to make API requests, "
#~ "but it also increases the risk of\n"
#~ " someone stealing it.
\n"
#~ " To limit the possible damage tokens or accounts with limited "
#~ "access can be used.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Polji Geslo in Žeton sta shranjena kot navadno "
#~ "besedilo v bazi podatkov.\n"
#~ " To je potrebno, ker so potrebni za izpolnjevanje zahtev API, "
#~ "vendar tudi povečuje tveganje za\n"
#~ " nekdo ga ukrade.
\n"
#~ " Za omejitev možne škode je mogoče uporabiti žetone ali račune z "
#~ "omejenim dostopom.\n"
#~ " "
#~ msgid "You are currently offline!"
#~ msgstr "Trenutno ste brez povezave!"
#~ msgid ""
#~ "The recipes listed below are available for offline viewing because you "
#~ "have recently viewed them. Keep in mind that data might be outdated."
#~ msgstr ""
#~ "Spodaj navedeni recepti so na voljo za ogled brez povezave, ker ste si "
#~ "jih nedavno ogledali. Upoštevajte, da so podatki morda zastareli."
#~ msgid "Property Editor"
#~ msgstr "Urejevalnik lastnosti"
#~ msgid "Comments"
#~ msgstr "Komentarji"
#~ msgid "by"
#~ msgstr "od"
#~ msgid "Comment"
#~ msgstr "Komentar"
#~ msgid ""
#~ "There are many options to configure the search depending on your personal "
#~ "preferences."
#~ msgstr ""
#~ "Obstaja veliko možnosti za konfiguracijo iskanja glede na vaše osebne "
#~ "nastavitve."
#~ msgid ""
#~ "Usually you do not need to configure any of them and can just "
#~ "stick with either the default or one of the following presets."
#~ msgstr ""
#~ "Običajno vam ni treba konfigurirati nobenega od njih in lahko "
#~ "preprosto izberete privzeto ali eno od naslednjih prednastavitev."
#~ msgid ""
#~ "If you do want to configure the search you can read about the different "
#~ "options here."
#~ msgstr ""
#~ "Če želite konfigurirati iskanje, si lahko tukaj preberete o različnih "
#~ "možnostih ."
#~ msgid "Fuzzy"
#~ msgstr "mehko"
#~ msgid ""
#~ "Find what you need even if your search or the recipe contains typos. "
#~ "Might return more results than needed to make sure you find what you are "
#~ "looking for."
#~ msgstr ""
#~ "Poišče kar potrebujete, tudi če vaše iskanje ali recept vsebuje tipkarske "
#~ "napake. Morda vrne več rezultatov, kot je potrebno, da zagotovo najde "
#~ "tisto, kar iščete."
#~ msgid "This is the default behavior"
#~ msgstr "To je privzeto vedenje"
#~ msgid "Apply"
#~ msgstr "Uporabi"
#~ msgid "Precise"
#~ msgstr "Natančno"
#~ msgid ""
#~ "Allows fine control over search results but might not return results if "
#~ "too many spelling mistakes are made."
#~ msgstr ""
#~ "Omogoča natančen nadzor nad rezultati iskanja, vendar morda ne vrne "
#~ "rezultatov, če je narejenih preveč črkovalnih napak."
#~ msgid "Perfect for large Databases"
#~ msgstr "Popolno za velike baze podatkov"
#~ msgid "Social"
#~ msgstr "Socialno"
#~ msgid "Space Management"
#~ msgstr "Upravljanje prostora"
#~ msgid "Space:"
#~ msgstr "Prostor:"
#~ msgid "Manage Subscription"
#~ msgstr "Upravljanje naročnine"
#~ msgid "Leave Space"
#~ msgstr "Zapusti prostor"
#~ msgid "URL Import"
#~ msgstr "uvoz URL"
#~ msgid ""
#~ "Rating a recipe should have or greater. [0 - 5] Negative value filters "
#~ "rating less than."
#~ msgstr ""
#~ "Ocena, ki bi jo recept moral imeti ali več. [0–5] Negativna vrednost "
#~ "filtrira oceno manj kot."
#~ msgid ""
#~ "Filter recipes updated on or after YYYY-MM-DD. Prepending - filters on or "
#~ "before date."
#~ msgstr ""
#~ "Filtrirajte recepte, posodobljene LLLL-MM-DD ali pozneje. Pred - filtrira "
#~ "na ali pred datumom."
#~ msgid ""
#~ "Returns the shopping list entry with a primary key of id. Multiple "
#~ "values allowed."
#~ msgstr ""
#~ "Vrne vnos nakupovalnega seznama s primarnim ključem id. Dovoljenih je več "
#~ "vrednosti."
#~ msgid ""
#~ "Filter shopping list entries on checked. [true, false, both, recent"
#~ "b>]
- recent includes unchecked items and recently "
#~ "completed items."
#~ msgstr ""
#~ "Filtrirajte vnose na nakupovalnem seznamu. [potrdi, zavrzi, oboje, "
#~ "nedavno]
- nedavno vključuje nepreverjene "
#~ "elemente in nedavno dokončane elemente."
#~ msgid ""
#~ "Returns the shopping list entries sorted by supermarket category order."
#~ msgstr ""
#~ "Vrne vnose nakupovalnega seznama, razvrščene po vrstnem redu kategorije "
#~ "trgovine."
#, python-format
#~ msgid "Batch edit done. %(count)d recipe was updated."
#~ msgid_plural "Batch edit done. %(count)d Recipes where updated."
#~ msgstr[0] ""
#~ "Paketno urejanje opravljeno. %(count)d recept je bil posodobljen."
#~ msgstr[1] ""
#~ "Paketno urejanje opravljeno. %(count)d recepta sta bila posodobljena."
#~ msgstr[2] ""
#~ "Paketno urejanje opravljeno. %(count)d recepti je bil posodobljeni."
#~ msgstr[3] ""
#~ "Paketno urejanje opravljeno. %(count)d receptov je bil posodobljenih."
#~ msgid "Monitor"
#~ msgstr "Nadzor"
#~ msgid "Storage Backend"
#~ msgstr "Zaledje za shranjevanje"
#~ msgid ""
#~ "Could not delete this storage backend as it is used in at least one "
#~ "monitor."
#~ msgstr ""
#~ "Tega ozadja za shranjevanje ni bilo mogoče izbrisati, ker se uporablja v "
#~ "vsaj enem nadzoru."
#~ msgid "Connectors Config Backend"
#~ msgstr "Zaledje konfiguracije priključkov"
#~ msgid "Invite Link"
#~ msgstr "Povezava za povabilo"
#~ msgid "Space Membership"
#~ msgstr "Članstvo v prostoru"
#~ msgid "You cannot edit this storage!"
#~ msgstr "Tega prostora za shranjevanje ne morete urejati!"
#~ msgid "Storage saved!"
#~ msgstr "Prostor za shranjevanje shranjen!"
#~ msgid "There was an error updating this storage backend!"
#~ msgstr "Pri posodabljanju tega zaledja za shranjevanje je prišlo do napake!"
#~ msgid "Config saved!"
#~ msgstr "Konfiguracija shranjena!"
#~ msgid "ConnectorConfig"
#~ msgstr "Priključna konfiguracija"
#~ msgid "Changes saved!"
#~ msgstr "Spremembe shranjene!"
#~ msgid "Error saving changes!"
#~ msgstr "Napaka pri shranjevanju sprememb!"
#~ msgid "Import Log"
#~ msgstr "Dnevnik uvoza"
#~ msgid "Discovery"
#~ msgstr "Odkritje"
#~ msgid "Shopping List"
#~ msgstr "Nakupovalni seznam"
#~ msgid "Connector Config Backend"
#~ msgstr "Zaledje konfiguracije priključka"
#~ msgid "Invite Links"
#~ msgstr "Povezave za povabilo"
#~ msgid "Supermarkets"
#~ msgstr "Trgovine"
#~ msgid "Shopping Categories"
#~ msgstr "Nakupovalne kategorije"
#~ msgid "Custom Filters"
#~ msgstr "Filtri po meri"
#~ msgid "Steps"
#~ msgstr "Koraki"
#~ msgid "Property Types"
#~ msgstr "Vrste nepremičnin"
#~ msgid "This feature is not enabled by the server admin!"
#~ msgstr "Te funkcije ni omogočil skrbnik strežnika!"
#~ msgid "Imported new recipe!"
#~ msgstr "Nov recept uvožen!"
#~ msgid "There was an error importing this recipe!"
#~ msgstr "Pri uvozu tega recepta je prišlo do napake!"
#~ msgid "You do not have the required permissions to perform this action!"
#~ msgstr "Nimate potrebnih dovoljenj za izvedbo tega dejanja!"
#~ msgid "Comment saved!"
#~ msgstr "Komentar shranjen!"
#~ msgid "You must select at least one field to search!"
#~ msgstr "Za iskanje morate izbrati vsaj eno polje!"
#~ msgid ""
#~ "To use this search method you must select at least one full text search "
#~ "field!"
#~ msgstr ""
#~ "Za uporabo te metode iskanja morate izbrati vsaj eno iskalno polje po "
#~ "celotnem besedilu!"
#~ msgid "Fuzzy search is not compatible with this search method!"
#~ msgstr "Mehko iskanje ni združljivo s to metodo iskanja!"
#~ msgid "Malformed Invite Link supplied!"
#~ msgstr "Priložena napačna povezava za povabilo!"
#~ msgid "Successfully joined space."
#~ msgstr "Uspešno pridružen prostoru."
#~ msgid "Invite Link not valid or already used!"
#~ msgstr "Povezava za povabilo ni veljavna ali je že uporabljena!"
#~ msgid "View your cookbooks"
#~ msgstr "Oglejte si svoje kuharske knjige"
#~ msgid "Default unit"
#~ msgstr "Privzeta enota"
#~ msgid "Use KJ"
#~ msgstr "Uporabi KJ"
#~ msgid "Theme"
#~ msgstr "Tema"
#~ msgid "Navbar color"
#~ msgstr "Barva navigacijske vrstice"
#~ msgid "Sticky navbar"
#~ msgstr "Lepljiva navigacijska vrstica"
#~ msgid "Default page"
#~ msgstr "Privzeta stran"
#~ msgid "Show recent recipes"
#~ msgstr "Prikaži nedavne recepte"
#~ msgid "Search style"
#~ msgstr "Vrsta iskalnika"
#~ msgid "Plan sharing"
#~ msgstr "Deli planer"
#~ msgid "Ingredient decimal places"
#~ msgstr "Decimalno mesto pri sestavini"
#~ msgid "Shopping list auto sync period"
#~ msgstr "Čas avtomatske sinhronizacije pri nakupovalnem listku"
#~ msgid ""
#~ "Color of the top navigation bar. Not all colors work with all themes, "
#~ "just try them out!"
#~ msgstr ""
#~ "Barva zgornje vrstice za krmarjenje. Ne delujejo vse barve z vsemi temami!"
#~ msgid ""
#~ "Default Unit to be used when inserting a new ingredient into a recipe."
#~ msgstr ""
#~ "Privzeta enota, ki se uporablja pri vstavljanju nove sestavine v recept."
#~ msgid ""
#~ "Enables support for fractions in ingredient amounts (e.g. convert "
#~ "decimals to fractions automatically)"
#~ msgstr ""
#~ "Omogoča podporo ulomkom/frakcijam v količinah sestavin (npr. samodejno "
#~ "pretvori decimalke v ulomke)"
#~ msgid "Display nutritional energy amounts in joules instead of calories"
#~ msgstr "Prikazuj hranilne energijske količine v joules namesto v kalorijah"
#~ msgid ""
#~ "Users with whom newly created meal plan/shopping list entries should be "
#~ "shared by default."
#~ msgstr ""
#~ "Uporabniki, s katerimi je privzeto deljen novo ustvarjen načrt ali "
#~ "nakupovalni listek."
#~ msgid "Show recently viewed recipes on search page."
#~ msgstr "Prikaži nedavno videne recepte na iskalniku."
#~ msgid "Number of decimals to round ingredients."
#~ msgstr "Število decimalk, ki so zaokrožene pri sestavinah."
#~ msgid ""
#~ "If you want to be able to create and see comments underneath recipes."
#~ msgstr "V primeru, da želite ustvariti in videti komentarje pod recepti."
#~ msgid ""
#~ "Setting to 0 will disable auto sync. When viewing a shopping list the "
#~ "list is updated every set seconds to sync changes someone else might have "
#~ "made. Useful when shopping with multiple people but might use a little "
#~ "bit of mobile data. If lower than instance limit it is reset when saving."
#~ msgstr ""
#~ "Nastavitev na 0 bo onemogočila avtomatsko sinhronizacijo. V pogledu "
#~ "nakupovalnega listka, se seznam osvežuje vsake toliko sekund, če nekdo "
#~ "drug naredi spremembo. To je najbolj uporabno, če nakupovalni listek "
#~ "delimo z večimi osebami. Paziti je potrebno, saj porabi nekaj podatkov v "
#~ "mobilnem omrežju."
#~ msgid "Makes the navbar stick to the top of the page."
#~ msgstr "Nastavi navigacijsko vrstico na vrh strani."
#~ msgid "You must provide at least a recipe or a title."
#~ msgstr "Vpisati moraš vsaj recept ali naslov."
#~ msgid "You can list default users to share recipes with in the settings."
#~ msgstr "Seznam uporabnikov za deljenje receptov lahko vidiš v nastavitvah."
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "Lahko uporabiš \"markdown\", da urediš to polje. Preveri tukaj"
#~ msgid "Close"
#~ msgstr "Zapri"
#~ msgid "Language"
#~ msgstr "Jezik"
#~ msgid "user"
#~ msgstr "uporabnik"
#~ msgid "URL"
#~ msgstr "URL"
================================================
FILE: cookbook/locale/sv/LC_MESSAGES/django.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-22 20:15+0200\n"
"PO-Revision-Date: 2025-08-10 11:36+0000\n"
"Last-Translator: Elias Sjögreen \n"
"Language-Team: Swedish \n"
"Language: sv\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.8.4\n"
#: .\cookbook\forms.py:50
msgid "Default"
msgstr "Förvalt"
#: .\cookbook\forms.py:77
msgid ""
"To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything."
msgstr ""
"För att förhindra att dubbletter ignoreras för recept med samma namn som "
"befintliga. Markera den här rutan för att importera allt."
#: .\cookbook\forms.py:108
msgid "Maximum number of users for this space reached."
msgstr "Maximalt antal användare för detta utrymme har uppnåtts."
#: .\cookbook\forms.py:114
msgid "Email address already taken!"
msgstr "E-postadressen har redan tagits!"
#: .\cookbook\forms.py:121
msgid ""
"An email address is not required but if present the invite link will be sent "
"to the user."
msgstr ""
"En e-postadress krävs inte, men om den finns kommer inbjudningslänken att "
"skickas till användaren."
#: .\cookbook\forms.py:133
msgid "Name already taken."
msgstr "Namnet är redan taget."
#: .\cookbook\forms.py:144 .\cookbook\forms.py:158
msgid "Accept Terms and Privacy"
msgstr "Acceptera villkor och integritetspolicy"
#: .\cookbook\helper\AllAuthCustomAdapter.py:41
msgid ""
"In order to prevent spam, the requested email was not send. Please wait a "
"few minutes and try again."
msgstr ""
"För att förhindra spam, så skickades inte den begärda e-posten. Vänta några "
"minuter och försök igen."
#: .\cookbook\helper\permission_helper.py:165
#: .\cookbook\helper\permission_helper.py:186 .\cookbook\views\views.py:138
msgid "You are not logged in and therefore cannot view this page!"
msgstr "Du är inte inloggad och kan därför inte se denna sida!"
#: .\cookbook\helper\permission_helper.py:168
#: .\cookbook\helper\permission_helper.py:173
#: .\cookbook\helper\permission_helper.py:198
#: .\cookbook\helper\permission_helper.py:265
#: .\cookbook\helper\permission_helper.py:279
#: .\cookbook\helper\permission_helper.py:290
#: .\cookbook\helper\permission_helper.py:301
#: .\cookbook\helper\permission_helper.py:317
#: .\cookbook\helper\permission_helper.py:343
#: .\cookbook\helper\permission_helper.py:359
msgid "You do not have the required permissions to view this page!"
msgstr "Du har inte behörighet att se denna sida!"
#: .\cookbook\helper\permission_helper.py:191
#: .\cookbook\helper\permission_helper.py:214
#: .\cookbook\helper\permission_helper.py:236
#: .\cookbook\helper\permission_helper.py:251
msgid "You cannot interact with this object as it is not owned by you!"
msgstr "Du kan inte interagera med detta objekt för att det ägs inte av dig!"
#: .\cookbook\helper\permission_helper.py:420
msgid "You have reached the maximum number of recipes for your space."
msgstr "Du har nått det maximala antalet recept för din utrymme."
#: .\cookbook\helper\permission_helper.py:432
msgid "You have more users than allowed in your space."
msgstr "Du har mer användare än tillåtet för ditt utrymme."
#: .\cookbook\helper\recipe_url_import.py:319
msgid "reverse rotation"
msgstr "återvänd rotering"
#: .\cookbook\helper\recipe_url_import.py:320
msgid "careful rotation"
msgstr "försiktig rotering"
#: .\cookbook\helper\recipe_url_import.py:321
msgid "knead"
msgstr "knåda"
#: .\cookbook\helper\recipe_url_import.py:322
msgid "thicken"
msgstr "förtjocka"
#: .\cookbook\helper\recipe_url_import.py:323
msgid "warm up"
msgstr "Uppvärm"
#: .\cookbook\helper\recipe_url_import.py:324
msgid "ferment"
msgstr "fermentera"
#: .\cookbook\helper\recipe_url_import.py:325
#, fuzzy
#| msgid "Last cooked"
msgid "slow cook"
msgstr "Senast tillagad"
#: .\cookbook\helper\recipe_url_import.py:326
msgid "egg boiler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:327
msgid "kettle"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:328
msgid "blend"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:329
msgid "pre-clean"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:330
msgid "high temperature"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:331
msgid "rice cooker"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:332
msgid "caramelize"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:333
msgid "peeler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:334
msgid "slicer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:335
msgid "grater"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:336
msgid "spiralizer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:337
msgid "sous-vide"
msgstr "sous-vide"
#: .\cookbook\helper\shopping_helper.py:150
msgid "You must supply a servings size"
msgstr "Du måste ange en portionstorlek"
#: .\cookbook\helper\template_helper.py:97
#: .\cookbook\helper\template_helper.py:99
#: .\cookbook\helper\template_helper.py:101
msgid "Could not parse template code."
msgstr "Det gick inte att läsa mallkoden."
#: .\cookbook\integration\copymethat.py:44
#: .\cookbook\integration\melarecipes.py:37
msgid "Favorite"
msgstr "Favorit"
#: .\cookbook\integration\copymethat.py:50
msgid "I made this"
msgstr "Jag gjorde den här"
#: .\cookbook\integration\integration.py:238
msgid ""
"Importer expected a .zip file. Did you choose the correct importer type for "
"your data ?"
msgstr ""
"Importören förväntade sig en .zip fil. Valde du rätt importtyp för din data?"
#: .\cookbook\integration\integration.py:241
msgid ""
"An unexpected error occurred during the import. Please make sure you have "
"uploaded a valid file."
msgstr ""
"Ett oväntat fel uppstod under importeringen. Se till att du har laddat upp "
"en giltig fil."
#: .\cookbook\integration\integration.py:246
msgid "The following recipes were ignored because they already existed:"
msgstr "Följande recept blev ignorerade för att de redan finns:"
#: .\cookbook\integration\integration.py:250
#, python-format
msgid "Imported %s recipes."
msgstr "Importerade %s recept."
#: .\cookbook\integration\mealie1.py:210
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "Calories"
msgstr "Kalorier"
#: .\cookbook\integration\mealie1.py:211
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
msgid "Carbohydrates"
msgstr "Kolhydrater"
#: .\cookbook\integration\mealie1.py:212
msgid "Cholesterol"
msgstr ""
#: .\cookbook\integration\mealie1.py:213
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
msgid "Fat"
msgstr "Fett"
#: .\cookbook\integration\mealie1.py:214
msgid "Fiber"
msgstr ""
#: .\cookbook\integration\mealie1.py:215
#, fuzzy
#| msgid "Proteins"
msgid "Protein"
msgstr "Protein"
#: .\cookbook\integration\mealie1.py:216
msgid "Saturated Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:217
msgid "Sodium"
msgstr ""
#: .\cookbook\integration\mealie1.py:218
msgid "Sugar"
msgstr ""
#: .\cookbook\integration\mealie1.py:219
msgid "Trans Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:220
msgid "Unsaturated Fat"
msgstr ""
#: .\cookbook\integration\openeats.py:28
msgid "Recipe source:"
msgstr "Recept källa:"
#: .\cookbook\integration\paprika.py:49
msgid "Notes"
msgstr "Anteckningar"
#: .\cookbook\integration\paprika.py:52
msgid "Nutritional Information"
msgstr "Näringsinnehåll"
#: .\cookbook\integration\paprika.py:56
msgid "Source"
msgstr "Källa"
#: .\cookbook\integration\recettetek.py:55
#: .\cookbook\integration\recipekeeper.py:70
msgid "Imported from"
msgstr "Importerade ifrån"
#: .\cookbook\integration\saffron.py:23
msgid "Servings"
msgstr "Portioner"
#: .\cookbook\integration\saffron.py:25
msgid "Waiting time"
msgstr "Väntetid"
#: .\cookbook\integration\saffron.py:27
msgid "Preparation Time"
msgstr "Förberedelsetid"
#: .\cookbook\integration\saffron.py:29 .\cookbook\templates\index.html:6
msgid "Cookbook"
msgstr "Kokbok"
#: .\cookbook\integration\saffron.py:31
msgid "Section"
msgstr "Sektion"
#: .\cookbook\management\commands\fix_duplicate_properties.py:15
msgid "Fixes foods with "
msgstr "Korrigerar livsmedel med "
#: .\cookbook\management\commands\rebuildindex.py:14
msgid "Rebuilds full text search index on Recipe"
msgstr "Återuppbygger fulltextsökningens index för Recept"
#: .\cookbook\management\commands\rebuildindex.py:18
msgid "Only Postgresql databases use full text search, no index to rebuild"
msgstr ""
"Endast Postgresql-databaser använder fulltextsökning, inget index att "
"återuppbygga med"
#: .\cookbook\management\commands\rebuildindex.py:29
msgid "Recipe index rebuild complete."
msgstr "Recept index återbyggning färdig."
#: .\cookbook\management\commands\rebuildindex.py:31
msgid "Recipe index rebuild failed."
msgstr "Recept index återbyggning misslyckades."
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
msgid "Breakfast"
msgstr "Frukost"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:19
msgid "Lunch"
msgstr "Lunch"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:24
msgid "Dinner"
msgstr "Kvällsmat"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:29 .\cookbook\models.py:971
msgid "Other"
msgstr "Annat"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "g"
msgstr "g"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "Proteins"
msgstr "Protein"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "kcal"
msgstr "kcal"
#: .\cookbook\models.py:325
msgid ""
"Maximum file storage for space in MB. 0 for unlimited, -1 to disable file "
"upload."
msgstr ""
"Maximal lagringsutrymme för utrymme i MB. 0 för obegränsad, -1 för att "
"inaktivera filuppladdning."
#: .\cookbook\models.py:513
msgid "Search"
msgstr "Sök"
#: .\cookbook\models.py:514
msgid "Meal-Plan"
msgstr "Matsedel"
#: .\cookbook\models.py:515
msgid "Books"
msgstr "Böcker"
#: .\cookbook\models.py:516 .\cookbook\views\views.py:416
#: .\cookbook\views\views.py:417
msgid "Shopping"
msgstr "Handla"
#: .\cookbook\models.py:967
msgid "Nutrition"
msgstr "Näringsinnehåll"
#: .\cookbook\models.py:968
msgid "Allergen"
msgstr "Allergen"
#: .\cookbook\models.py:969
msgid "Price"
msgstr "Pris"
#: .\cookbook\models.py:970
msgid "Goal"
msgstr "Mål"
#: .\cookbook\models.py:1467 .\cookbook\templates\search_info.html:28
msgid "Simple"
msgstr "Enkel"
#: .\cookbook\models.py:1468 .\cookbook\templates\search_info.html:33
msgid "Phrase"
msgstr "Fras"
#: .\cookbook\models.py:1469 .\cookbook\templates\search_info.html:38
msgid "Web"
msgstr "Internet"
#: .\cookbook\models.py:1470 .\cookbook\templates\search_info.html:47
msgid "Raw"
msgstr "Rå"
#: .\cookbook\models.py:1525
msgid "Food Alias"
msgstr "Alternativt namn för mat"
#: .\cookbook\models.py:1526
msgid "Unit Alias"
msgstr "Enhetsalias"
#: .\cookbook\models.py:1527
msgid "Keyword Alias"
msgstr "Nyckelordsalias"
#: .\cookbook\models.py:1528
msgid "Description Replace"
msgstr "Beskrivning Ersätt"
#: .\cookbook\models.py:1529
msgid "Instruction Replace"
msgstr "Instruktioner Ersätt"
#: .\cookbook\models.py:1530
msgid "Never Unit"
msgstr "Aldrig Enhet"
#: .\cookbook\models.py:1531
msgid "Transpose Words"
msgstr "Transponera ord"
#: .\cookbook\models.py:1532
msgid "Food Replace"
msgstr "Ersätt mat"
#: .\cookbook\models.py:1533
msgid "Unit Replace"
msgstr "Ersätt Enhet"
#: .\cookbook\models.py:1534
msgid "Name Replace"
msgstr "Ersättningsnamn"
#: .\cookbook\models.py:1564
msgid "Recipe"
msgstr "Recept"
#: .\cookbook\models.py:1565
msgid "Food"
msgstr "Livsmedel"
#: .\cookbook\models.py:1566
msgid "Keyword"
msgstr "Nyckelord"
#: .\cookbook\serializer.py:262
msgid "File uploads are not enabled for this Space."
msgstr "Filuppladdning är inte aktiverat för det här utrymmet."
#: .\cookbook\serializer.py:273
msgid "You have reached your file upload limit."
msgstr "Du har nått din maxgräns för uppladdningar."
#: .\cookbook\serializer.py:281
msgid "The given file type is not allowed."
msgstr ""
#: .\cookbook\serializer.py:421 .\cookbook\views\views.py:94
msgid ""
"You have the reached the maximum amount of spaces that can be owned by you."
msgstr ""
#: .\cookbook\serializer.py:434
msgid "Space Name must be unique."
msgstr ""
#: .\cookbook\serializer.py:469
msgid "Cannot modify Space owner permission."
msgstr "Kan inte modifiera utrymmets ägar-rättigheter."
#: .\cookbook\serializer.py:1596
msgid "Hello"
msgstr "Hej"
#: .\cookbook\serializer.py:1596
msgid "You have been invited by "
msgstr "Du har bjudits in av "
#: .\cookbook\serializer.py:1598
msgid " to join their Tandoor Recipes space "
msgstr " för att ansluta till deras Tandoor recept utrymme "
#: .\cookbook\serializer.py:1600
msgid "Click the following link to activate your account: "
msgstr "Klicka på länken för att aktivera ditt konto: "
#: .\cookbook\serializer.py:1602
msgid ""
"If the link does not work use the following code to manually join the space: "
msgstr ""
"Om länken inte fungerar kan du testa följande kod för att manuellt aktivera "
"ditt utrymme: "
#: .\cookbook\serializer.py:1604
msgid "The invitation is valid until "
msgstr "Inbjudningen är giltig till "
#: .\cookbook\serializer.py:1606
msgid ""
"Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub "
msgstr ""
"Tandoor recept är en recept-hanterar med öppen källkod. Se mer på GitHub "
#: .\cookbook\serializer.py:1609
msgid "Tandoor Recipes Invite"
msgstr "Tandoor recept inbjudan"
#: .\cookbook\serializer.py:1813
msgid "Existing shopping list to update"
msgstr "Existerande inköpslistor att uppdatera"
#: .\cookbook\serializer.py:1815
msgid ""
"List of ingredient IDs from the recipe to add, if not provided all "
"ingredients will be added."
msgstr ""
"Lista med ingrediens ID:n från receptet att lägga till, om inget angetts "
"kommer alla ingredienser bli tillagda."
#: .\cookbook\serializer.py:1817
msgid ""
"Providing a list_recipe ID and servings of 0 will delete that shopping list."
msgstr "Anges ett list_recept ID och 0 portioner kommer den listan raderas."
#: .\cookbook\serializer.py:1826
msgid "Amount of food to add to the shopping list"
msgstr "Mängd av ingrediens att lägga till på inköpslistan"
#: .\cookbook\serializer.py:1828
msgid "ID of unit to use for the shopping list"
msgstr "ID eller enhet att använda för inköpslistan"
#: .\cookbook\serializer.py:1830
msgid "When set to true will delete all food from active shopping lists."
msgstr ""
"Om det här alternativet är aktiverat kommer alla matvaror att raderas från "
"de aktiva inköpslistorna."
#: .\cookbook\templates\404.html:5
msgid "404 Error"
msgstr "404-fel"
#: .\cookbook\templates\404.html:18
msgid "The page you are looking for could not be found."
msgstr "Sidan du letar efter kunde inte hittas."
#: .\cookbook\templates\404.html:33
msgid "Take me Home"
msgstr "Ta mig tillbaka"
#: .\cookbook\templates\404.html:35
msgid "Report a Bug"
msgstr "Rapportera en bugg"
#: .\cookbook\templates\account\email.html:6
#: .\cookbook\templates\account\email.html:10
msgid "E-mail Addresses"
msgstr "Email adresser"
#: .\cookbook\templates\account\email.html:12
msgid "The following e-mail addresses are associated with your account:"
msgstr "Följande epost-addresser är associerade med ditt konto:"
#: .\cookbook\templates\account\email.html:29
msgid "Verified"
msgstr "Bekräftad"
#: .\cookbook\templates\account\email.html:31
msgid "Unverified"
msgstr "Obekräktad"
#: .\cookbook\templates\account\email.html:33
msgid "Primary"
msgstr "Primär"
#: .\cookbook\templates\account\email.html:40
msgid "Make Primary"
msgstr "Markera som primär"
#: .\cookbook\templates\account\email.html:42
msgid "Re-send Verification"
msgstr "Sänd verifikationen igen"
#: .\cookbook\templates\account\email.html:43
#: .\cookbook\templates\socialaccount\connections.html:36
msgid "Remove"
msgstr "Ta bort"
#: .\cookbook\templates\account\email.html:51
msgid "Warning:"
msgstr "Varning:"
#: .\cookbook\templates\account\email.html:51
msgid ""
"You currently do not have any e-mail address set up. You should really add "
"an e-mail address so you can receive notifications, reset your password, etc."
msgstr ""
"Just nu har du inga e-post adresser konfigurerade. Du borde verkligen lägga "
"till en e-post adress så att du kan får notiser, återställa ditt lösenord, "
"mm."
#: .\cookbook\templates\account\email.html:57
msgid "Add E-mail Address"
msgstr "Lägg till en e-post adress"
#: .\cookbook\templates\account\email.html:62
msgid "Add E-mail"
msgstr "Lägg till email"
#: .\cookbook\templates\account\email.html:72
msgid "Do you really want to remove the selected e-mail address?"
msgstr "Vill du verkligen ta bort den valda e-postadressen?"
#: .\cookbook\templates\account\email_confirm.html:6
#: .\cookbook\templates\account\email_confirm.html:10
msgid "Confirm E-mail Address"
msgstr "Bekräfta e-postadress"
#: .\cookbook\templates\account\email_confirm.html:16
#, python-format
msgid ""
"Please confirm that\n"
" %(email)s is an e-mail address "
"for user %(user_display)s\n"
" ."
msgstr ""
"Vänligen bekräfra att\n"
" %(email)s är e-postadressen för "
"användaren %(user_display)s\n"
" ."
#: .\cookbook\templates\account\email_confirm.html:22
msgid "Confirm"
msgstr "Bekräfta"
#: .\cookbook\templates\account\email_confirm.html:29
#, python-format
msgid ""
"This e-mail confirmation link expired or is invalid. Please\n"
" issue a new e-mail confirmation "
"request."
msgstr ""
"Denna e-post bekräftelselänk är utgången eller felaktig. Vänligen\n"
" skapa en ny e-postbekräftelsebegäran"
"a>."
#: .\cookbook\templates\account\login.html:8
#: .\cookbook\templates\openid\login.html:8
msgid "Login"
msgstr "Logga in"
#: .\cookbook\templates\account\login.html:15
#: .\cookbook\templates\account\login.html:31
#: .\cookbook\templates\account\password_reset.html:39
#: .\cookbook\templates\account\password_reset_done.html:31
#: .\cookbook\templates\account\signup.html:68
#: .\cookbook\templates\account\signup_closed.html:15
#: .\cookbook\templates\openid\login.html:15
#: .\cookbook\templates\openid\login.html:26
#: .\cookbook\templates\socialaccount\authentication_error.html:15
msgid "Sign In"
msgstr "Logga in"
#: .\cookbook\templates\account\login.html:34
#: .\cookbook\templates\account\password_reset.html:41
#: .\cookbook\templates\account\password_reset_done.html:33
#: .\cookbook\templates\socialaccount\signup.html:8
#: .\cookbook\templates\socialaccount\signup.html:56
msgid "Sign Up"
msgstr "Registrera dig"
#: .\cookbook\templates\account\login.html:38
msgid "Lost your password?"
msgstr "Glömt ditt lösenord?"
#: .\cookbook\templates\account\login.html:39
#: .\cookbook\templates\account\password_reset.html:29
msgid "Reset My Password"
msgstr "Återställ mitt lösenord"
#: .\cookbook\templates\account\login.html:50
msgid "Social Login"
msgstr "Social inloggning"
#: .\cookbook\templates\account\login.html:51
msgid "You can use any of the following providers to sign in."
msgstr "Du kan använda någon av följande leverantörer för att logga in."
#: .\cookbook\templates\account\logout.html:5
#: .\cookbook\templates\account\logout.html:9
#: .\cookbook\templates\account\logout.html:18
msgid "Sign Out"
msgstr "Logga ut"
#: .\cookbook\templates\account\logout.html:11
msgid "Are you sure you want to sign out?"
msgstr "Är du säker på att du vill logga ut?"
#: .\cookbook\templates\account\password_change.html:6
#: .\cookbook\templates\account\password_change.html:10
#: .\cookbook\templates\account\password_change.html:15
#: .\cookbook\templates\account\password_reset_from_key.html:7
#: .\cookbook\templates\account\password_reset_from_key.html:13
#: .\cookbook\templates\account\password_reset_from_key_done.html:7
#: .\cookbook\templates\account\password_reset_from_key_done.html:13
msgid "Change Password"
msgstr "Ändra lösenord"
#: .\cookbook\templates\account\password_change.html:16
msgid "Forgot Password?"
msgstr "Glömt lösenord?"
#: .\cookbook\templates\account\password_reset.html:7
#: .\cookbook\templates\account\password_reset.html:13
#: .\cookbook\templates\account\password_reset_done.html:7
#: .\cookbook\templates\account\password_reset_done.html:18
msgid "Password Reset"
msgstr "Återställ lösenord"
#: .\cookbook\templates\account\password_reset.html:24
msgid ""
"Forgotten your password? Enter your e-mail address below, and we'll send you "
"an e-mail allowing you to reset it."
msgstr ""
"Glömt ditt lösenord? Ange din e-postadress nedanför så skickar vi ett "
"återställningmail."
#: .\cookbook\templates\account\password_reset.html:32
msgid "Password reset is disabled on this instance."
msgstr "Återställning av lösenord är avaktiverat på denna instans."
#: .\cookbook\templates\account\password_reset_done.html:25
msgid ""
"We have sent you an e-mail. Please contact us if you do not receive it "
"within a few minutes."
msgstr ""
"Vi har skickat ett e-postmeddelande till dig. Om du inte har fått det inom "
"några minuter, vänligen kontakta oss."
#: .\cookbook\templates\account\password_reset_from_key.html:13
#, fuzzy
#| msgid "API Token"
msgid "Bad Token"
msgstr "API Token"
#: .\cookbook\templates\account\password_reset_from_key.html:25
#, python-format
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used.\n"
" Please request a new "
"password reset."
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:33
msgid "change password"
msgstr "byt lösenord"
#: .\cookbook\templates\account\password_reset_from_key.html:36
#: .\cookbook\templates\account\password_reset_from_key_done.html:19
msgid "Your password is now changed."
msgstr ""
#: .\cookbook\templates\account\password_set.html:6
#: .\cookbook\templates\account\password_set.html:10
#: .\cookbook\templates\account\password_set.html:15
#, fuzzy
#| msgid "Password Reset"
msgid "Set Password"
msgstr "Återställ lösenord"
#: .\cookbook\templates\account\signup.html:5
msgid "Register"
msgstr "Registrera"
#: .\cookbook\templates\account\signup.html:11
#, fuzzy
#| msgid "Create your Account"
msgid "Create an Account"
msgstr "Skapa ditt konto"
#: .\cookbook\templates\account\signup.html:41
msgid "I accept the follwoing"
msgstr ""
#: .\cookbook\templates\account\signup.html:44
#: .\cookbook\templates\socialaccount\signup.html:35
msgid "Terms and Conditions"
msgstr ""
#: .\cookbook\templates\account\signup.html:47
#: .\cookbook\templates\socialaccount\signup.html:38
msgid "and"
msgstr "och"
#: .\cookbook\templates\account\signup.html:51
#: .\cookbook\templates\socialaccount\signup.html:42
msgid "Privacy Policy"
msgstr "Integritetspolicy"
#: .\cookbook\templates\account\signup.html:64
msgid "Create User"
msgstr "Skapa användare"
#: .\cookbook\templates\account\signup.html:68
msgid "Already have an account?"
msgstr ""
#: .\cookbook\templates\account\signup_closed.html:5
#: .\cookbook\templates\account\signup_closed.html:11
msgid "Sign Up Closed"
msgstr ""
#: .\cookbook\templates\account\signup_closed.html:13
msgid "We are sorry, but the sign up is currently closed."
msgstr ""
#: .\cookbook\templates\frontend\tandoor.html:15
#, fuzzy
#| msgid "Tandoor Recipes Invite"
msgid "Tandoor Recipe Manager"
msgstr "Tandoor recept inbjudan"
#: .\cookbook\templates\index.html:28
msgid "Search recipe ..."
msgstr "Sök på recept ..."
#: .\cookbook\templates\index.html:43
msgid "New Recipe"
msgstr "Nytt recept"
#: .\cookbook\templates\index.html:46
msgid "Import Recipe"
msgstr "Importera recept"
#: .\cookbook\templates\index.html:52
msgid "Advanced Search"
msgstr "Avancerad sökning"
#: .\cookbook\templates\index.html:56
msgid "Reset Search"
msgstr "Återställ sökning"
#: .\cookbook\templates\index.html:84
msgid "Last viewed"
msgstr "Senast öppnade"
#: .\cookbook\templates\index.html:86
msgid "Recipes"
msgstr "Recept"
#: .\cookbook\templates\index.html:93
msgid "Log in to view recipes"
msgstr "Logga in för att se recept"
#: .\cookbook\templates\markdown_info.html:5
#: .\cookbook\templates\markdown_info.html:13
msgid "Markdown Info"
msgstr "Markdown information"
#: .\cookbook\templates\markdown_info.html:14
msgid ""
"\n"
" Markdown is lightweight markup language that can be used to format "
"plain text easily.\n"
" This site uses the Python Markdown library to\n"
" convert your text into nice looking HTML. Its full markdown "
"documentation can be found\n"
" here.\n"
" An incomplete but most likely sufficient documentation can be found "
"below.\n"
" "
msgstr ""
"\n"
" Markdown är ett lätt märkningsspråk som kan användas för att enkelt "
"formatera vanlig text.\n"
" Den här webbplatsen använder Python Markdown-biblioteket för att\n"
" konvertera din text till snygg HTML. Dess fullständiga markdown-"
"dokumentation finns\n"
" här.\n"
" En ofullständig men troligen tillräcklig dokumentation finns "
"nedan.\n"
" "
#: .\cookbook\templates\markdown_info.html:25
msgid "Headers"
msgstr "Rubriker"
#: .\cookbook\templates\markdown_info.html:54
msgid "Formatting"
msgstr "Formatering"
#: .\cookbook\templates\markdown_info.html:56
#: .\cookbook\templates\markdown_info.html:72
msgid "Line breaks are inserted by adding two spaces after the end of a line"
msgstr ""
"Radbrytningar infogas genom att lägga till två blanksteg efter slutet av en "
"rad"
#: .\cookbook\templates\markdown_info.html:57
#: .\cookbook\templates\markdown_info.html:73
#, fuzzy
#| msgid "or by leaving a blank line inbetween."
msgid "or by leaving a blank line in between."
msgstr "eller genom att lämna en tom rad däremellan."
#: .\cookbook\templates\markdown_info.html:59
#: .\cookbook\templates\markdown_info.html:74
msgid "This text is bold"
msgstr "Denna text är fetstil"
#: .\cookbook\templates\markdown_info.html:60
#: .\cookbook\templates\markdown_info.html:75
msgid "This text is italic"
msgstr "Denna text är kursiv"
#: .\cookbook\templates\markdown_info.html:61
#: .\cookbook\templates\markdown_info.html:77
msgid "Blockquotes are also possible"
msgstr "Blockcitat är också möjligt"
#: .\cookbook\templates\markdown_info.html:84
msgid "Lists"
msgstr "Listor"
#: .\cookbook\templates\markdown_info.html:85
#, fuzzy
#| msgid ""
#| "Lists can ordered or unorderd. It is important to leave a blank line "
#| "before the list!"
msgid ""
"Lists can ordered or unordered. It is important to leave a blank line "
"before the list!"
msgstr ""
"Listor kan vara ordnade eller oordnade. Det är viktigt att lämna en tom "
"rad före listan!"
#: .\cookbook\templates\markdown_info.html:87
#: .\cookbook\templates\markdown_info.html:108
msgid "Ordered List"
msgstr "Ordnad lista"
#: .\cookbook\templates\markdown_info.html:89
#: .\cookbook\templates\markdown_info.html:90
#: .\cookbook\templates\markdown_info.html:91
#: .\cookbook\templates\markdown_info.html:110
#: .\cookbook\templates\markdown_info.html:111
#: .\cookbook\templates\markdown_info.html:112
msgid "unordered list item"
msgstr "oordnat listobjekt"
#: .\cookbook\templates\markdown_info.html:93
#: .\cookbook\templates\markdown_info.html:114
msgid "Unordered List"
msgstr "Oordnad lista"
#: .\cookbook\templates\markdown_info.html:95
#: .\cookbook\templates\markdown_info.html:96
#: .\cookbook\templates\markdown_info.html:97
#: .\cookbook\templates\markdown_info.html:116
#: .\cookbook\templates\markdown_info.html:117
#: .\cookbook\templates\markdown_info.html:118
msgid "ordered list item"
msgstr "ordnat listobjekt"
#: .\cookbook\templates\markdown_info.html:125
msgid "Images & Links"
msgstr "Bilder & länkar"
#: .\cookbook\templates\markdown_info.html:126
msgid ""
"Links can be formatted with Markdown. This application also allows to paste "
"links directly into markdown fields without any formatting."
msgstr ""
"Länkar kan formateras med Markdown. Denna applikation gör det också möjligt "
"att klistra in länkar direkt i markdown-fält utan någon formatering."
#: .\cookbook\templates\markdown_info.html:132
#: .\cookbook\templates\markdown_info.html:145
msgid "This will become an image"
msgstr "Detta kommer att bli en bild"
#: .\cookbook\templates\markdown_info.html:152
msgid "Tables"
msgstr "Tabeller"
#: .\cookbook\templates\markdown_info.html:153
msgid ""
"Markdown tables are hard to create by hand. It is recommended to use a table "
"editor like this one."
msgstr ""
"Markdown-tabeller är svåra att skapa för hand. Vi rekommenderar att du "
"använder en tabellredigerare som denna."
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:171
#: .\cookbook\templates\markdown_info.html:177
msgid "Table"
msgstr "Tabell"
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:172
msgid "Header"
msgstr "Sidhuvud"
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:178
msgid "Cell"
msgstr "Cell"
#: .\cookbook\templates\no_groups_info.html:5
#: .\cookbook\templates\no_groups_info.html:12
msgid "No Permissions"
msgstr "Inga behörigheter"
#: .\cookbook\templates\no_groups_info.html:17
msgid "You do not have any groups and therefor cannot use this application."
msgstr "Du har inga grupper och kan därför inte använda denna applikation."
#: .\cookbook\templates\no_groups_info.html:18
#: .\cookbook\templates\no_perm_info.html:15
msgid "Please contact your administrator."
msgstr "Kontakta din administratör."
#: .\cookbook\templates\no_perm_info.html:5
#: .\cookbook\templates\no_perm_info.html:12
msgid "No Permission"
msgstr "Ingen behörighet"
#: .\cookbook\templates\no_perm_info.html:15
msgid ""
"You do not have the required permissions to view this page or perform this "
"action."
msgstr ""
"Du har inte de nödvändiga behörigheterna för att visa den här sidan eller "
"utföra den här åtgärden."
#: .\cookbook\templates\offline.html:5
msgid "Offline"
msgstr "Offline"
#: .\cookbook\templates\openid\login.html:27
#: .\cookbook\templates\socialaccount\authentication_error.html:27
msgid "Back"
msgstr "Tillbaka"
#: .\cookbook\templates\rest_framework\api.html:5
msgid "Recipe Home"
msgstr "Recept Hem"
#: .\cookbook\templates\rest_framework\api.html:11
msgid "API Documentation"
msgstr "API-dokumentation"
#: .\cookbook\templates\search_info.html:5
#: .\cookbook\templates\search_info.html:9
#, fuzzy
#| msgid "Search String"
msgid "Search Settings"
msgstr "Söksträng"
#: .\cookbook\templates\search_info.html:10
msgid ""
"\n"
" Creating the best search experience is complicated and weighs "
"heavily on your personal configuration. \n"
" Changing any of the search settings can have significant impact on "
"the speed and quality of the results.\n"
" Search Methods, Trigrams and Full Text Search configurations are "
"only available if you are using Postgres for your database.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:19
#, fuzzy
#| msgid "Search"
msgid "Search Methods"
msgstr "Sök"
#: .\cookbook\templates\search_info.html:23
msgid ""
" \n"
" Full text searches attempt to normalize the words provided to "
"match common variants. For example: 'forked', 'forking', 'forks' will all "
"normalize to 'fork'.\n"
" There are several methods available, described below, that will "
"control how the search behavior should react when multiple words are "
"searched.\n"
" Full technical details on how these operate can be viewed on Postgresql's website.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:29
msgid ""
" \n"
" Simple searches ignore punctuation and common words such as "
"'the', 'a', 'and'. And will treat separate words as required.\n"
" Searching for 'apple or flour' will return any recipe that "
"includes both 'apple' and 'flour' anywhere in the fields that have been "
"selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:34
msgid ""
" \n"
" Phrase searches ignore punctuation, but will search for all of "
"the words in the exact order provided.\n"
" Searching for 'apple or flour' will only return a recipe that "
"includes the exact phrase 'apple or flour' in any of the fields that have "
"been selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:39
msgid ""
" \n"
" Web searches simulate functionality found on many web search "
"sites supporting special syntax.\n"
" Placing quotes around several words will convert those words "
"into a phrase.\n"
" 'or' is recognized as searching for the word (or phrase) "
"immediately before 'or' OR the word (or phrase) directly after.\n"
" '-' is recognized as searching for recipes that do not include "
"the word (or phrase) that comes immediately after. \n"
" For example searching for 'apple pie' or cherry -butter will "
"return any recipe that includes the phrase 'apple pie' or the word "
"'cherry' \n"
" in any field included in the full text search but exclude any "
"recipe that has the word 'butter' in any field included.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:48
msgid ""
" \n"
" Raw search is similar to Web except will take puncuation "
"operators such as '|', '&' and '()'\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:59
msgid ""
" \n"
" Another approach to searching that also requires Postgresql is "
"fuzzy search or trigram similarity. A trigram is a group of three "
"consecutive characters.\n"
" For example searching for 'apple' will create x trigrams 'app', "
"'ppl', 'ple' and will create a score of how closely words match the "
"generated trigrams.\n"
" One benefit of searching trigams is that a search for 'sandwich' "
"will find misspelled words such as 'sandwhich' that would be missed by other "
"methods.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:69
#, fuzzy
#| msgid "Search Recipe"
msgid "Search Fields"
msgstr "Sök recept"
#: .\cookbook\templates\search_info.html:73
msgid ""
" \n"
" Unaccent is a special case in that it enables searching a field "
"'unaccented' for each search style attempting to ignore accented values. \n"
" For example when you enable unaccent for 'Name' any search "
"(starts with, contains, trigram) will attempt the search ignoring accented "
"characters.\n"
" \n"
" For the other options, you can enable search on any or all "
"fields and they will be combined together with an assumed 'OR'.\n"
" For example enabling 'Name' for Starts With, 'Name' and "
"'Description' for Partial Match and 'Ingredients' and 'Keywords' for Full "
"Search\n"
" and searching for 'apple' will generate a search that will "
"return recipes that have:\n"
" - A recipe name that starts with 'apple'\n"
" - OR a recipe name that contains 'apple'\n"
" - OR a recipe description that contains 'apple'\n"
" - OR a recipe that will have a full text search match ('apple' "
"or 'apples') in ingredients\n"
" - OR a recipe that will have a full text search match in "
"Keywords\n"
"\n"
" Combining too many fields in too many types of search can have a "
"negative impact on performance, create duplicate results or return "
"unexpected results.\n"
" For example, enabling fuzzy search or partial matches will "
"interfere with web search methods. \n"
" Searching for 'apple -pie' with fuzzy search and full text "
"search will return the recipe Apple Pie. Though it is not included in the "
"full text results, it does match the trigram results.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:95
#, fuzzy
#| msgid "Search"
msgid "Search Index"
msgstr "Sök"
#: .\cookbook\templates\search_info.html:99
msgid ""
" \n"
" Trigram search and Full Text Search both rely on database "
"indexes to perform effectively. \n"
" You can rebuild the indexes on all fields in the Admin page for "
"Recipes and selecting all recipes and running 'rebuild index for selected "
"recipes'\n"
" You can also rebuild indexes at the command line by executing "
"the management command 'python manage.py rebuildindex'\n"
" "
msgstr ""
#: .\cookbook\templates\setup.html:6
msgid "Cookbook Setup"
msgstr "Kokboksinställning"
#: .\cookbook\templates\setup.html:14
msgid "Setup"
msgstr "Konfiguration"
#: .\cookbook\templates\setup.html:15
msgid ""
"To start using this application you must first create a superuser account."
msgstr ""
"För att börja använda denna applikation måste du först skapa ett "
"superanvändarkonto."
#: .\cookbook\templates\setup.html:20
msgid "Create Superuser account"
msgstr "Skapa ett superanvändarkonto"
#: .\cookbook\templates\socialaccount\authentication_error.html:7
#: .\cookbook\templates\socialaccount\authentication_error.html:23
#, fuzzy
#| msgid "Social Login"
msgid "Social Network Login Failure"
msgstr "Social inloggning"
#: .\cookbook\templates\socialaccount\authentication_error.html:25
msgid ""
"An error occurred while attempting to login via your social network account."
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:4
#: .\cookbook\templates\socialaccount\connections.html:7
msgid "Account Connections"
msgstr "Kontokopplingar"
#: .\cookbook\templates\socialaccount\connections.html:10
msgid ""
"You can sign in to your account using any of the following third party\n"
" accounts:"
msgstr ""
"Du kan logga in på ditt konto med någon av följande tredje parts\n"
" konton:"
#: .\cookbook\templates\socialaccount\connections.html:44
msgid ""
"You currently have no social network accounts connected to this account."
msgstr ""
"Du har för närvarande inga sociala nätverkskonton kopplade till det här "
"kontot."
#: .\cookbook\templates\socialaccount\connections.html:47
msgid "Add a 3rd Party Account"
msgstr "Lägg till ett konto från tredje part"
#: .\cookbook\templates\socialaccount\login.html:5
#: .\cookbook\templates\socialaccount\signup.html:5
#, fuzzy
#| msgid "Sign Out"
msgid "Signup"
msgstr "Logga ut"
#: .\cookbook\templates\socialaccount\login.html:9
#, python-format
msgid "Connect %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:11
#, python-format
msgid "You are about to connect a new third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:13
#, python-format
msgid "Sign In Via %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:15
#, python-format
msgid "You are about to sign in using a third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:20
msgid "Continue"
msgstr "Fortsätt"
#: .\cookbook\templates\socialaccount\signup.html:10
#, python-format
msgid ""
"You are about to use your\n"
" %(provider_name)s account to login to\n"
" %(site_name)s. As a final step, please complete the following form:"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:32
msgid "I accept the following"
msgstr ""
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:23
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:31
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:39
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:47
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:55
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:63
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:71
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:79
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:87
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:95
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:103
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:111
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:119
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:127
#, fuzzy
#| msgid "Sign In"
msgid "Sign in using"
msgstr "Logga in"
#: .\cookbook\templates\space_overview.html:6
msgid "Overview"
msgstr "Överblick"
#: .\cookbook\templates\space_overview.html:13
#, fuzzy
#| msgid "No Space"
msgid "Space"
msgstr "Inget utrymme"
#: .\cookbook\templates\space_overview.html:17
msgid ""
"Recipes, foods, shopping lists and more are organized in spaces of one or "
"more people."
msgstr ""
#: .\cookbook\templates\space_overview.html:18
msgid ""
"You can either be invited into an existing space or create your own one."
msgstr ""
#: .\cookbook\templates\space_overview.html:25
#, fuzzy
#| msgid "No Space"
msgid "Your Spaces"
msgstr "Inget utrymme"
#: .\cookbook\templates\space_overview.html:53
msgid "Owner"
msgstr "Ägare"
#: .\cookbook\templates\space_overview.html:73
#: .\cookbook\templates\space_overview.html:83
#, fuzzy
#| msgid "No Space"
msgid "Join Space"
msgstr "Inget utrymme"
#: .\cookbook\templates\space_overview.html:76
msgid "Join an existing space."
msgstr ""
#: .\cookbook\templates\space_overview.html:78
msgid ""
"To join an existing space either enter your invite token or click on the "
"invite link the space owner send you."
msgstr ""
#: .\cookbook\templates\space_overview.html:91
#: .\cookbook\templates\space_overview.html:100
#, fuzzy
#| msgid "Create User"
msgid "Create Space"
msgstr "Skapa användare"
#: .\cookbook\templates\space_overview.html:94
msgid "Create your own recipe space."
msgstr ""
#: .\cookbook\templates\space_overview.html:96
msgid "Start your own recipe space and invite other users to it."
msgstr ""
#: .\cookbook\templates\system.html:23
msgid "System"
msgstr "System"
#: .\cookbook\templates\system.html:24
#, fuzzy
#| msgid ""
#| "\n"
#| " Django Recipes is an open source free software application. It "
#| "can be found on\n"
#| " GitHub.\n"
#| " Changelogs can be found here.\n"
#| " "
msgid ""
"\n"
" Tandoor Recipes is an open source free software application. It can "
"be found on\n"
" GitHub.\n"
" Changelogs can be found here.\n"
" "
msgstr ""
"\n"
" Tandoor Recipes är ett gratisprogram med öppen källkod. Den finns "
"på\n"
" GitHub.\n"
" Ändringsloggar finns här.\n"
" "
#: .\cookbook\templates\system.html:30
msgid "System Information"
msgstr "Systeminformation"
#: .\cookbook\templates\system.html:51
msgid ""
"\n"
" You need to execute version.py in your update "
"script to generate version information (done automatically in docker).\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:56
msgid "Plugins"
msgstr ""
#: .\cookbook\templates\system.html:67
msgid "Media Serving"
msgstr "Mediaservering"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:123 .\cookbook\templates\system.html:134
msgid "Warning"
msgstr "Varning"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:125 .\cookbook\templates\system.html:134
msgid "Ok"
msgstr "Ok"
#: .\cookbook\templates\system.html:70
msgid ""
"Serving media files directly using gunicorn/python is not recommend!\n"
" Please follow the steps described\n"
" here to update\n"
" your installation.\n"
" "
msgstr ""
"Att visa mediafiler direkt med gunicorn/python rekommenderas inte!\n"
" Följ stegen som beskrivs\n"
" här för att uppdatera\n"
" din installation.\n"
" "
#: .\cookbook\templates\system.html:76 .\cookbook\templates\system.html:91
#: .\cookbook\templates\system.html:104 .\cookbook\templates\system.html:115
#: .\cookbook\views\views.py:168
msgid "Everything is fine!"
msgstr "Allting är bra!"
#: .\cookbook\templates\system.html:80
msgid "Secret Key"
msgstr "Hemlig nyckel"
#: .\cookbook\templates\system.html:84
msgid ""
"\n"
" You do not have a SECRET_KEY configured in your "
".env file. Django defaulted to the\n"
" standard key\n"
" provided with the installation which is publicly know and "
"insecure! Please set\n"
" SECRET_KEY int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" Du har inte en SECRET_KEY konfigurerad i din ."
"env-fil. Django ställde som standard till\n"
" standardnyckel\n"
" försedd med installationen som är allmänt känd och osäker! "
"Vänligen ställ in\n"
" SECRET_KEY i konfigurationsfilen .env"
"code>.\n"
" "
#: .\cookbook\templates\system.html:94
msgid "Debug Mode"
msgstr "Felsökningsläge"
#: .\cookbook\templates\system.html:98
msgid ""
"\n"
" This application is still running in debug mode. This is most "
"likely not needed. Turn of debug mode by\n"
" setting\n"
" DEBUG=0 int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" Denna applikation körs fortfarande i felsökningsläge. Detta "
"behövs sannolikt inte. Stäng av felsökningsläget med\n"
" att sätta\n"
" DEBUG=0 i konfigurationsfilen .env.\n"
" "
#: .\cookbook\templates\system.html:107
msgid "Allowed Hosts"
msgstr ""
#: .\cookbook\templates\system.html:111
msgid ""
"\n"
" Your allowed hosts are configured to allow every host. This "
"might be ok in some setups but should be avoided. Please see the docs about "
"this.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:118
msgid "Database"
msgstr "Databas"
#: .\cookbook\templates\system.html:121
msgid "Info"
msgstr "Info"
#: .\cookbook\templates\system.html:131 .\cookbook\templates\system.html:148
msgid "Migrations"
msgstr "migrationer"
#: .\cookbook\templates\system.html:137
msgid ""
"\n"
" Migrations should never fail!\n"
" Failed migrations will likely cause major parts of the app to "
"not function correctly.\n"
" If a migration fails make sure you are on the latest version and "
"if so please post the migration log and the overview below in a GitHub "
"issue.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "False"
msgstr "Falsk"
#: .\cookbook\templates\system.html:238
msgid "True"
msgstr "Sann"
#: .\cookbook\templates\system.html:268
msgid "Hide"
msgstr "Göm"
#: .\cookbook\templates\system.html:271
#, fuzzy
#| msgid "Show help"
msgid "Show"
msgstr "Visa hjälp"
#: .\cookbook\templates\test2.html:6
msgid "Export Recipes"
msgstr "Exportera recept"
#: .\cookbook\templates\test2.html:14 .\cookbook\templates\test2.html:20
msgid "Export"
msgstr "Exportera"
#: .\cookbook\views\api.py:198 .\cookbook\views\api.py:326
msgid "Parameter updated_at incorrectly formatted"
msgstr "Parameter updated_at felaktigt formaterad"
#: .\cookbook\views\api.py:351 .\cookbook\views\api.py:484
#, python-brace-format
msgid "No {self.basename} with id {pk} exists"
msgstr ""
#: .\cookbook\views\api.py:355
msgid "Cannot merge with the same object!"
msgstr "Kan inte slås samman med samma objekt!"
#: .\cookbook\views\api.py:362
#, python-brace-format
msgid "No {self.basename} with id {target} exists"
msgstr ""
#: .\cookbook\views\api.py:367
#, fuzzy
#| msgid "Cannot merge with the same object!"
msgid "Cannot merge with child object!"
msgstr "Kan inte slås samman med samma objekt!"
#: .\cookbook\views\api.py:405
#, python-brace-format
msgid "{source.name} was merged successfully with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:411
#, python-brace-format
msgid "An error occurred attempting to merge {source.name} with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:493
#, python-brace-format
msgid "{child.name} was moved successfully to the root."
msgstr ""
#: .\cookbook\views\api.py:496 .\cookbook\views\api.py:514
msgid "An error occurred attempting to move "
msgstr ""
#: .\cookbook\views\api.py:499
msgid "Cannot move an object to itself!"
msgstr ""
#: .\cookbook\views\api.py:505
#, python-brace-format
msgid "No {self.basename} with id {parent} exists"
msgstr ""
#: .\cookbook\views\api.py:511
#, python-brace-format
msgid "{child.name} was moved successfully to parent {parent.name}"
msgstr ""
#: .\cookbook\views\api.py:992
#, python-brace-format
msgid "{obj.name} was removed from the shopping list."
msgstr ""
#: .\cookbook\views\api.py:997 .\cookbook\views\api.py:1627
#, python-brace-format
msgid "{obj.name} was added to the shopping list."
msgstr ""
#: .\cookbook\views\api.py:1239
msgid "Filter meal plans from date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1241
msgid "Filter meal plans to date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1244
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1404
msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1406
msgid "Query string matched (fuzzy) against object name."
msgstr ""
#: .\cookbook\views\api.py:1442
msgid ""
"Query string matched (fuzzy) against recipe name. In the future also "
"fulltext search."
msgstr ""
#: .\cookbook\views\api.py:1444
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr ""
#: .\cookbook\views\api.py:1445
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr ""
#: .\cookbook\views\api.py:1446
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1447
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1448
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1450
msgid "ID of food a recipe should have. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1451
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr ""
#: .\cookbook\views\api.py:1452
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1453
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr ""
#: .\cookbook\views\api.py:1454
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1456
msgid "ID of book a recipe should be in. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1457
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr ""
#: .\cookbook\views\api.py:1458
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1459
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr ""
#: .\cookbook\views\api.py:1460
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1462
msgid "ID of unit a recipe should have."
msgstr ""
#: .\cookbook\views\api.py:1464
msgid "Exact rating of recipe"
msgstr ""
#: .\cookbook\views\api.py:1465
msgid "Rating a recipe should have or greater."
msgstr ""
#: .\cookbook\views\api.py:1466
msgid "Rating a recipe should have or smaller."
msgstr ""
#: .\cookbook\views\api.py:1468
msgid "Filter recipes cooked X times."
msgstr ""
#: .\cookbook\views\api.py:1469
msgid "Filter recipes cooked X times or more."
msgstr ""
#: .\cookbook\views\api.py:1470
msgid "Filter recipes cooked X times or less."
msgstr ""
#: .\cookbook\views\api.py:1472
msgid "Filter recipes created on the given date."
msgstr ""
#: .\cookbook\views\api.py:1473
msgid "Filter recipes created on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1474
msgid "Filter recipes created on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1476 .\cookbook\views\api.py:1477
#: .\cookbook\views\api.py:1478
msgid "Filter recipes updated on the given date."
msgstr ""
#: .\cookbook\views\api.py:1480
msgid "Filter recipes last cooked on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1481
msgid "Filter recipes last cooked on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1483 .\cookbook\views\api.py:1484
msgid "Filter recipes lasts viewed on the given date."
msgstr ""
#: .\cookbook\views\api.py:1486
msgid "Filter recipes for ones created by the given user ID"
msgstr ""
#: .\cookbook\views\api.py:1487
msgid "If only internal recipes should be returned. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1488
msgid "Returns the results in randomized order. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1490
msgid ""
"Determines the order of the results. Options are: score,-score,name,-name,"
"lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-"
"created_at,lastviewed,-lastviewed"
msgstr ""
#: .\cookbook\views\api.py:1492
msgid "Returns new results first in search results. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1493
msgid ""
"Returns the given number of recently viewed recipes before search results "
"(if given)"
msgstr ""
#: .\cookbook\views\api.py:1494
msgid "ID of a custom filter. Returns all recipes matched by that filter."
msgstr ""
#: .\cookbook\views\api.py:1495
msgid "Filter recipes that can be made with OnHand food. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1773
msgid ""
"Return the PropertyTypes matching the property category. Repeat for "
"multiple."
msgstr ""
#: .\cookbook\views\api.py:1804 .\cookbook\views\api.py:1860
msgid "Returns only entries associated with the given mealplan id"
msgstr ""
#: .\cookbook\views\api.py:1858
msgid ""
"Returns only elements updated after the given timestamp in ISO 8601 format."
msgstr ""
#: .\cookbook\views\api.py:2031
msgid ""
"Return the Automations matching the automation type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2048
msgid ""
"Text field to store data that gets carried over to the UserSpace created "
"from the InviteLink"
msgstr ""
#: .\cookbook\views\api.py:2049
msgid "Only return InviteLinks that have not been used yet."
msgstr ""
#: .\cookbook\views\api.py:2076
msgid "Return the CustomFilters matching the model type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2176
msgid "Nothing to do."
msgstr ""
#: .\cookbook\views\api.py:2222
msgid "Invalid Url"
msgstr ""
#: .\cookbook\views\api.py:2228
msgid "Connection Refused."
msgstr ""
#: .\cookbook\views\api.py:2232
msgid "Bad URL Schema."
msgstr ""
#: .\cookbook\views\api.py:2257
#, fuzzy
#| msgid "The requested page could not be found."
msgid "No usable data could be found."
msgstr "Sidan kunde inte hittas."
#: .\cookbook\views\api.py:2286 .\cookbook\views\api.py:2434
msgid "You must select an AI provider to perform your request."
msgstr ""
#: .\cookbook\views\api.py:2293 .\cookbook\views\api.py:2441
msgid ""
"You don't have any credits remaining to use AI or AI features are not "
"enabled for your space."
msgstr ""
#: .\cookbook\views\api.py:2499 .\cookbook\views\api.py:2667
msgid "File is above space limit"
msgstr ""
#: .\cookbook\views\api.py:2522 .\cookbook\views\api.py:2684
msgid "Importing is not implemented for this provider"
msgstr "Importering är inte implementerad för denna leverantör"
#: .\cookbook\views\api.py:2548
msgid ""
"The PDF Exporter is not enabled on this instance as it is still in an "
"experimental state."
msgstr ""
#: .\cookbook\views\api.py:2842
#, fuzzy
#| msgid "This feature is not available in the demo version!"
msgid "This feature is not yet available in the hosted version of tandoor!"
msgstr "Denna funktion är inte tillgänglig i demoversionen!"
#: .\cookbook\views\api.py:2863
msgid "Sync successful!"
msgstr "Synkroniseringen lyckades!"
#: .\cookbook\views\api.py:2866
msgid "Error synchronizing with Storage"
msgstr "Fel vid synkronisering med lagring"
#: .\cookbook\views\views.py:89
msgid "This feature is not available in the demo version!"
msgstr "Denna funktion är inte tillgänglig i demoversionen!"
#: .\cookbook\views\views.py:110
msgid ""
"You have successfully created your own recipe space. Start by adding some "
"recipes or invite other people to join you."
msgstr ""
#: .\cookbook\views\views.py:171
#, python-format
msgid "PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!"
msgstr ""
#: .\cookbook\views\views.py:174
#, python-format
msgid "You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended"
msgstr ""
#: .\cookbook\views\views.py:178
msgid "Unable to determine PostgreSQL version."
msgstr ""
#: .\cookbook\views\views.py:182
#, fuzzy
#| msgid ""
#| "\n"
#| " This application is not running with a Postgres database "
#| "backend. This is ok but not recommended as some\n"
#| " features only work with postgres databases.\n"
#| " "
msgid ""
"This application is not running with a Postgres database backend. This is ok "
"but not recommended as some features only work with postgres databases."
msgstr ""
"\n"
" Denna applikation körs inte med en Postgres-databasbackend. "
"Detta är ok men rekommenderas inte då vissa\n"
" funktioner bara fungerar med postgres-databaser.\n"
" "
#: .\cookbook\views\views.py:296
#, fuzzy
#| msgid ""
#| "The setup page can only be used to create the first user! If you have "
#| "forgotten your superuser credentials please consult the django "
#| "documentation on how to reset passwords."
msgid ""
"The setup page can only be used to create the first "
"user! If you have forgotten your superuser credentials "
"please consult the django documentation on how to reset passwords."
msgstr ""
"Inställningssidan kan endast användas för att skapa den första användaren! "
"Om du har glömt dina superanvändaruppgifter, vänligen läs django-"
"dokumentationen om hur du återställer lösenord."
#: .\cookbook\views\views.py:304
msgid "Passwords dont match!"
msgstr "Lösenord matchar inte!"
#: .\cookbook\views\views.py:312
msgid "User has been created, please login!"
msgstr "Användaren har skapats, vänligen logga in!"
#: .\cookbook\views\views.py:352
msgid ""
"Reporting share links is not enabled for this instance. Please notify the "
"page administrator to report problems."
msgstr ""
#: .\cookbook\views\views.py:357
msgid ""
"Recipe sharing link has been disabled! For additional information please "
"contact the page administrator."
msgstr ""
#: .\cookbook\views\views.py:383
msgid "Manage recipes, shopping list, meal plans and more."
msgstr ""
#: .\cookbook\views\views.py:397 .\cookbook\views\views.py:398
msgid "Plan"
msgstr "Planen"
#: .\cookbook\views\views.py:399
msgid "View your meal Plan"
msgstr ""
#: .\cookbook\views\views.py:418
#, fuzzy
#| msgid "Shopping Lists"
msgid "View your shopping lists"
msgstr "Inköpslistor"
#~ msgid ""
#~ "Both fields are optional. If none are given the username will be "
#~ "displayed instead"
#~ msgstr ""
#~ "Båda fälten är valfria. Om inga anges kommer användarnamnet visas istället"
#~ msgid "Name"
#~ msgstr "Namn"
#~ msgid "Keywords"
#~ msgstr "Nyckelord"
#~ msgid "Preparation time in minutes"
#~ msgstr "Förberedelsetid i minuter"
#~ msgid "Waiting time (cooking/baking) in minutes"
#~ msgstr "Väntetid (tillagning/bakning) i minuter"
#~ msgid "Path"
#~ msgstr "Sökväg"
#~ msgid "Storage UID"
#~ msgstr "Lagrings-UID"
#~ msgid "Add your comment: "
#~ msgstr "Lägg till din kommentar: "
#~ msgid "Leave empty for dropbox and enter app password for nextcloud."
#~ msgstr "Lämna tom för Dropbox och skriv ditt app-lösenord till Nextcloud."
#~ msgid "Leave empty for nextcloud and enter api token for dropbox."
#~ msgstr "Lämna tom för nextcloud och skriv in api-nyckel för dropbox."
#~ msgid ""
#~ "Leave empty for dropbox and enter only base url for nextcloud (/"
#~ "remote.php/webdav/ is added automatically)"
#~ msgstr ""
#~ "Lämna tom för dropbox och skriv in endast baswebbadress för nextcloud "
#~ "(/remote.php/webdav/ läggs till automatiskt)"
#~ msgid ""
#~ "Long Lived Access Token for your HomeAssistant instance"
#~ msgstr ""
#~ "Långt varande Access Token för din HomeAssistant instans"
#~ msgid "Something like http://homeassistant.local:8123/api"
#~ msgstr "Till exempel http://homeassistant.local:8123/api"
#~ msgid "http://homeassistant.local:8123/api for example"
#~ msgstr "http://homeassistant.local:8123/api till exempel"
#~ msgid "Storage"
#~ msgstr "Lagring"
#~ msgid "Active"
#~ msgstr "Aktiv"
#~ msgid "Search String"
#~ msgstr "Söksträng"
#~ msgid "File ID"
#~ msgstr "Fil-ID"
#~ msgid ""
#~ "Determines how fuzzy a search is if it uses trigram similarity matching "
#~ "(e.g. low values mean more typos are ignored)."
#~ msgstr ""
#~ "Bestämmer hur fuzzy en sökning är om den använder trigramlikhetsmatchning "
#~ "(t.ex. låga värden innebär att fler stavfel ignoreras)."
#~ msgid ""
#~ "Select type method of search. Click here "
#~ "for full description of choices."
#~ msgstr ""
#~ "Välj typ av sökmetod. Klicka här för "
#~ "fullständig beskrivning av alternativen."
#~ msgid ""
#~ "Use fuzzy matching on units, keywords and ingredients when editing and "
#~ "importing recipes."
#~ msgstr ""
#~ "Använd \"fuzzy\" matchning på enheter, nyckelord och ingredienser när du "
#~ "redigerar och importerar recept."
#~ msgid ""
#~ "Fields to search ignoring accents. Selecting this option can improve or "
#~ "degrade search quality depending on language"
#~ msgstr ""
#~ "Fält att söka medan man ignorerar accenter. Val av detta alternativ kan "
#~ "förbättra eller försämra sökkvaliteten beroende på språk"
#~ msgid ""
#~ "Fields to search for partial matches. (e.g. searching for 'Pie' will "
#~ "return 'pie' and 'piece' and 'soapie')"
#~ msgstr ""
#~ "Fält att söka efter delvisa matchningar. (t.ex. att söka efter 'Pie' "
#~ "kommer att returnera 'pie' och 'piece' och 'soapie')"
#~ msgid ""
#~ "Fields to search for beginning of word matches. (e.g. searching for 'sa' "
#~ "will return 'salad' and 'sandwich')"
#~ msgstr ""
#~ "Fält att söka för matchningar i början av ord. (t.ex. att söka efter 'sa' "
#~ "kommer att returnera 'salad' och 'sandwich')"
#~ msgid ""
#~ "Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find "
#~ "'recipe'.) Note: this option will conflict with 'web' and 'raw' methods "
#~ "of search."
#~ msgstr ""
#~ "Fält för 'fuzzy'-sökning. (t.ex. att söka efter 'recpie' kommer att hitta "
#~ "'recipe'.Observera: detta alternativet kommer att komma i konflikt med "
#~ "sökmetoderna 'web' och 'raw'.)"
#~ msgid ""
#~ "Fields to full text search. Note: 'web', 'phrase', and 'raw' search "
#~ "methods only function with fulltext fields."
#~ msgstr ""
#~ "Fält för fulltextsökning. Observera: Sökmetoderna 'web', 'phrase' och "
#~ "'raw' fungerar endast med fulltextfält."
#~ msgid "Search Method"
#~ msgstr "Sök Metod"
#~ msgid "Fuzzy Lookups"
#~ msgstr "Fuzzy uppslagning"
#~ msgid "Ignore Accent"
#~ msgstr "Ignorera accent"
#~ msgid "Partial Match"
#~ msgstr "Delvis matchning"
#~ msgid "Starts With"
#~ msgstr "Börjar med"
#~ msgid "Fuzzy Search"
#~ msgstr "Fuzzy Sök"
#~ msgid "Full Text"
#~ msgstr "Hel Text"
#~ msgid " is part of a recipe step and cannot be deleted"
#~ msgstr " är en del av ett receptsteg och kan inte tas bort"
#~ msgid "Delete"
#~ msgstr "Radera"
#~ msgid "Settings"
#~ msgstr "Inställningar"
#~ msgid "Email"
#~ msgstr "Email"
#~ msgid "Password"
#~ msgstr "Lösenord"
#~ msgid "Foods"
#~ msgstr "Livsmedel"
#~ msgid "Units"
#~ msgstr "Enheter"
#~ msgid "Supermarket"
#~ msgstr "Mataffär"
#~ msgid "Supermarket Category"
#~ msgstr "Mataffärkategori"
#~ msgid "Automations"
#~ msgstr "Automationer"
#~ msgid "Files"
#~ msgstr "Filer"
#~ msgid "Batch Edit"
#~ msgstr "Mängdredigera"
#~ msgid "History"
#~ msgstr "Historia"
#, fuzzy
#~| msgid "Ingredients"
#~ msgid "Ingredient Editor"
#~ msgstr "Ingredienser"
#~ msgid "Properties"
#~ msgstr "Egenskaper"
#, fuzzy
#~| msgid "Account Connections"
#~ msgid "Unit Conversions"
#~ msgstr "Kontokopplingar"
#~ msgid "Create"
#~ msgstr "Skapa"
#~ msgid "External Recipes"
#~ msgstr "Externa recept"
#, fuzzy
#~| msgid "Settings"
#~ msgid "Space Settings"
#~ msgstr "Inställningar"
#, fuzzy
#~| msgid "External Recipes"
#~ msgid "External Connectors"
#~ msgstr "Externa recept"
#~ msgid "Admin"
#~ msgstr "Administration"
#~ msgid "Markdown Guide"
#~ msgstr "Markdown-guide"
#~ msgid "GitHub"
#~ msgstr "Github"
#~ msgid "Translate Tandoor"
#~ msgstr "Översätt Tandoor"
#~ msgid "API Browser"
#~ msgstr "API-browser"
#~ msgid "Batch edit Category"
#~ msgstr "Kategori av mängdredering"
#~ msgid "Batch edit Recipes"
#~ msgstr "Mängdredigera recept"
#~ msgid "Add the specified keywords to all recipes containing a word"
#~ msgstr ""
#~ "Lägg till de specificerade nyckelorden till alla recept som innehåller "
#~ "ett ord"
#~ msgid "Sync"
#~ msgstr "Synk"
#~ msgid "Manage watched Folders"
#~ msgstr "Hantera bevakade mappar"
#~ msgid ""
#~ "On this Page you can manage all storage folder locations that should be "
#~ "monitored and synced."
#~ msgstr ""
#~ "På den här sidan kan du hantera alla lagringsmappar som ska övervakas och "
#~ "synkroniseras."
#~ msgid "The path must be in the following format"
#~ msgstr "Sökvägen måste ha följande format"
#~ msgid "Save"
#~ msgstr "Spara"
#~ msgid "Sync Now!"
#~ msgstr "Synka nu!"
#, fuzzy
#~| msgid "Shopping Recipes"
#~ msgid "Show Recipes"
#~ msgstr "Shopping recept"
#, fuzzy
#~| msgid "Show Links"
#~ msgid "Show Log"
#~ msgstr "Visa länkar"
#~ msgid "Importing Recipes"
#~ msgstr "Importerar recept"
#~ msgid ""
#~ "This can take a few minutes, depending on the number of recipes in sync, "
#~ "please wait."
#~ msgstr ""
#~ "Detta kan ta några minuter, beroende på antalet av recept i synkningen, "
#~ "var god vänta kvar."
#~ msgid "Recipe Books"
#~ msgstr "Kokböcker"
#~ msgid "Import new Recipe"
#~ msgstr "Importera nytt recept"
#~ msgid "Edit Recipe"
#~ msgstr "Redigera recept"
#, python-format
#~ msgid "Are you sure you want to delete the %(title)s: %(object)s "
#~ msgstr "Är du säker på att vill ta bort denna %(title)s: %(object)s "
#~ msgid "Protected"
#~ msgstr "Beskyddad"
#~ msgid "Cascade"
#~ msgstr "Kaskad"
#~ msgid "Cancel"
#~ msgstr "Avbryt"
#~ msgid "Edit"
#~ msgstr "Redigera"
#~ msgid "View"
#~ msgstr "Visa"
#~ msgid "Delete original file"
#~ msgstr "Ta bort originalfil"
#~ msgid "List"
#~ msgstr "Lista"
#~ msgid "Filter"
#~ msgstr "Filter"
#~ msgid "Import all"
#~ msgstr "Importera alla"
#~ msgid "New"
#~ msgstr "Ny"
#~ msgid "previous"
#~ msgstr "föregående"
#~ msgid "next"
#~ msgstr "nästa"
#~ msgid "View Log"
#~ msgstr "Visa logg"
#~ msgid "Cook Log"
#~ msgstr "Tillagningslogg"
#~ msgid "Import"
#~ msgstr "Importera"
#~ msgid "Security Warning"
#~ msgstr "Säkerhetsvarning"
#~ msgid ""
#~ "\n"
#~ " The Password and Token field are stored as plain text"
#~ "b> inside the database.\n"
#~ " This is necessary because they are needed to make API requests, "
#~ "but it also increases the risk of\n"
#~ " someone stealing it.
\n"
#~ " To limit the possible damage tokens or accounts with limited "
#~ "access can be used.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Fältet Lösenord och token lagras som oformaterad text"
#~ "b> i databasen.\n"
#~ " Detta är nödvändigt eftersom de behövs för att göra API-"
#~ "förfrågningar, men det ökar också risken för\n"
#~ " någon som stjäl den.
\n"
#~ " För att begränsa möjlig skada kan tokens eller konton med "
#~ "begränsad åtkomst användas.\n"
#~ " "
#~ msgid "You are currently offline!"
#~ msgstr "Du är för närvarande offline!"
#~ msgid ""
#~ "The recipes listed below are available for offline viewing because you "
#~ "have recently viewed them. Keep in mind that data might be outdated."
#~ msgstr ""
#~ "Recepten nedan är tillgängliga för offlinevisning eftersom du nyligen har "
#~ "tittat på dem. Tänk på att data kan vara inaktuella."
#~ msgid "Comments"
#~ msgstr "Kommentarer"
#~ msgid "by"
#~ msgstr "av"
#~ msgid "Comment"
#~ msgstr "Kommentar"
#~ msgid "Fuzzy"
#~ msgstr "Fuzzy"
#~ msgid "Apply"
#~ msgstr "Använd"
#~ msgid "Precise"
#~ msgstr "Exakt"
#, fuzzy
#~| msgid "Social Login"
#~ msgid "Social"
#~ msgstr "Social inloggning"
#, fuzzy
#~| msgid "No Space"
#~ msgid "Space:"
#~ msgstr "Inget utrymme"
#, fuzzy
#~| msgid "Description"
#~ msgid "Manage Subscription"
#~ msgstr "Beskrivning"
#, fuzzy
#~| msgid "No Space"
#~ msgid "Leave Space"
#~ msgstr "Inget utrymme"
#~ msgid "URL Import"
#~ msgstr "URL-import"
#, python-format
#~ msgid "Batch edit done. %(count)d recipe was updated."
#~ msgid_plural "Batch edit done. %(count)d Recipes where updated."
#~ msgstr[0] "Batchredigering klar. %(count)d recept uppdaterades."
#~ msgstr[1] "Batchredigering klar. %(count)d recepten uppdaterades."
#~ msgid "Monitor"
#~ msgstr "Övervaka"
#~ msgid "Storage Backend"
#~ msgstr "Backend för lagring"
#~ msgid ""
#~ "Could not delete this storage backend as it is used in at least one "
#~ "monitor."
#~ msgstr ""
#~ "Det gick inte att ta bort denna lagringsbackend eftersom den används i "
#~ "minst en övervakning."
#, fuzzy
#~| msgid "Storage Backend"
#~ msgid "Connectors Config Backend"
#~ msgstr "Backend för lagring"
#~ msgid "Invite Link"
#~ msgstr "Inbjudningslänk"
#~ msgid "You cannot edit this storage!"
#~ msgstr "Du kan inte redigera denna lagring!"
#~ msgid "Storage saved!"
#~ msgstr "Lagring sparad!"
#~ msgid "There was an error updating this storage backend!"
#~ msgstr "Det uppstod ett fel när denna lagringsbackend skulle uppdateras!"
#, fuzzy
#~| msgid "Changes saved!"
#~ msgid "Config saved!"
#~ msgstr "Ändringar sparade!"
#~ msgid "ConnectorConfig"
#~ msgstr "Anslutningskonfiguration"
#~ msgid "Changes saved!"
#~ msgstr "Ändringar sparade!"
#~ msgid "Error saving changes!"
#~ msgstr "Det gick inte att spara ändringar!"
#~ msgid "Import Log"
#~ msgstr "Import logg"
#~ msgid "Discovery"
#~ msgstr "Upptäck"
#~ msgid "Shopping List"
#~ msgstr "Inköpslista"
#, fuzzy
#~| msgid "Storage Backend"
#~ msgid "Connector Config Backend"
#~ msgstr "Backend för lagring"
#~ msgid "Invite Links"
#~ msgstr "Inbjudningslänkar"
#~ msgid "Supermarkets"
#~ msgstr "Mataffärer"
#, fuzzy
#~| msgid "Shopping Recipes"
#~ msgid "Shopping Categories"
#~ msgstr "Shopping recept"
#, fuzzy
#~| msgid "Filter"
#~ msgid "Custom Filters"
#~ msgstr "Filter"
#~ msgid "Steps"
#~ msgstr "Steg"
#, fuzzy
#~| msgid "This feature is not available in the demo version!"
#~ msgid "This feature is not enabled by the server admin!"
#~ msgstr "Denna funktion är inte tillgänglig i demoversionen!"
#~ msgid "Imported new recipe!"
#~ msgstr "Importerat nytt recept!"
#~ msgid "There was an error importing this recipe!"
#~ msgstr "Det uppstod ett fel när det här receptet skulle importeras!"
#~ msgid "You do not have the required permissions to perform this action!"
#~ msgstr ""
#~ "Du har inte de nödvändiga behörigheterna för att utföra den här åtgärden!"
#~ msgid "Comment saved!"
#~ msgstr "Kommentaren sparad!"
#~ msgid "Malformed Invite Link supplied!"
#~ msgstr "Felaktig inbjudningslänk!"
#~ msgid "Invite Link not valid or already used!"
#~ msgstr "Inbjudningslänken är inte giltig eller redan använd!"
#~ msgid ""
#~ "Color of the top navigation bar. Not all colors work with all themes, "
#~ "just try them out!"
#~ msgstr ""
#~ "Färg på översta fältet i menyn. Alla färger fungerar inte med alla teman, "
#~ "testa dem bara!"
#~ msgid ""
#~ "Default Unit to be used when inserting a new ingredient into a recipe."
#~ msgstr "Förvald enhet när nya ingredienser läggs till i ett recept."
#~ msgid ""
#~ "Enables support for fractions in ingredient amounts (e.g. convert "
#~ "decimals to fractions automatically)"
#~ msgstr ""
#~ "Aktiverar stöd för bråktal till mängd av ingredienser. Ersätter "
#~ "decimaltal med bråktal, exempel 0.5 med ½"
#~ msgid ""
#~ "Users with whom newly created meal plan/shopping list entries should be "
#~ "shared by default."
#~ msgstr ""
#~ "Användare att dela nyligen skapade matsedlar/handelslistor med "
#~ "automatiskt."
#~ msgid "Show recently viewed recipes on search page."
#~ msgstr "Visa nyligen visade recept på söksidan."
#~ msgid "Number of decimals to round ingredients."
#~ msgstr "Antal decimaler att avrunda till."
#~ msgid ""
#~ "If you want to be able to create and see comments underneath recipes."
#~ msgstr "Om du vill att skriva och se kommentarer under recept."
#~ msgid ""
#~ "Setting to 0 will disable auto sync. When viewing a shopping list the "
#~ "list is updated every set seconds to sync changes someone else might have "
#~ "made. Useful when shopping with multiple people but might use a little "
#~ "bit of mobile data. If lower than instance limit it is reset when saving."
#~ msgstr ""
#~ "Inställning satt till 0 inaktiverar automatisk synkronisering. När du "
#~ "tittar på en inköpslista uppdateras listan varje sekund för att "
#~ "synkronisera ändringar som någon annan kan ha gjort. Användbart när du "
#~ "handlar med flera personer men kan använda lite mobildata. Om den är "
#~ "lägre än instansgränsen återställs den när du sparar."
#~ msgid "Makes the navbar stick to the top of the page."
#~ msgstr "Gör så att navigationsmenyn är fast på övre delen av sidan."
#~ msgid "New unit that other gets replaced by."
#~ msgstr "Ny enhet som den andra blir utbytt med."
#~ msgid "Old Unit"
#~ msgstr "Gamla enhet"
#~ msgid "Unit that should be replaced."
#~ msgstr "Enhet som borde ersättas."
#~ msgid "New Food"
#~ msgstr "Nya ingrediensen"
#~ msgid "New food that other gets replaced by."
#~ msgstr "Nya ingrediensen att ersätta den gamla med."
#~ msgid "Old Food"
#~ msgstr "Gamla ingredienser"
#~ msgid "Food that should be replaced."
#~ msgstr "Ingrediensen som ska bytas ut."
#~ msgid "You must provide at least a recipe or a title."
#~ msgstr "Du måste åtminstone ange ett recept eller titel."
#~ msgid "You can list default users to share recipes with in the settings."
#~ msgstr "Du kan ange förvalda användare att dela recept med i inställningar."
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "Du kan använda markdown för att formatera detta fält. Se dokumentation här"
#~ msgid ""
#~ "A username is not required, if left blank the new user can choose one."
#~ msgstr ""
#~ "Ett användarnamn är inte nödvändigt, om det är tomt så får den nya "
#~ "användare välja ett själv."
#~ msgid "The requested site provided malformed data and cannot be read."
#~ msgstr "Den begärda sidan angav felaktig data och kan då inte läsas."
#~ msgid ""
#~ "The requested site does not provide any recognized data format to import "
#~ "the recipe from."
#~ msgstr ""
#~ "Den begärda sidan innehåller ingen data i något format som stöds för att "
#~ "kunna importera recept ifrån."
#~ msgid "Small"
#~ msgstr "Liten"
#~ msgid "Large"
#~ msgstr "Stor"
#~ msgid "Time"
#~ msgstr "Tid"
#~ msgid "Link"
#~ msgstr "Länk"
#~ msgid "Utensils"
#~ msgstr "Utensilier"
#~ msgid "Storage Data"
#~ msgstr "Lagringsdata"
#~ msgid "Storage Backends"
#~ msgstr "Lagringsmetoder"
#~ msgid "Configure Sync"
#~ msgstr "Konfigurera Synk"
#~ msgid "Discovered Recipes"
#~ msgstr "Upptäckta recept"
#~ msgid "Discovery Log"
#~ msgstr "Logg av upptäckter"
#~ msgid "Statistics"
#~ msgstr "Statistik"
#~ msgid "Units & Ingredients"
#~ msgstr "Enheter & ingredienser"
#~ msgid "Logout"
#~ msgstr "Logga ut"
#~ msgid "New Book"
#~ msgstr "Ny bok"
#~ msgid "Toggle Recipes"
#~ msgstr "Växla recept"
#~ msgid "There are no recipes in this book yet."
#~ msgstr "Det är inga recept i detta bok ännu."
#~ msgid "Waiting Time"
#~ msgstr "Väntetid"
#~ msgid "Servings Text"
#~ msgstr "Portionstext"
#~ msgid "Select Keywords"
#~ msgstr "Välj nyckelord"
#~ msgid "Add Keyword"
#~ msgstr "Lägg till nyckelord"
#~ msgid "Delete Step"
#~ msgstr "Ta bort steg"
#~ msgid "Step"
#~ msgstr "Steg"
#~ msgid "Show as header"
#~ msgstr "Visa som rubrik"
#~ msgid "Hide as header"
#~ msgstr "Visa inte som en rubrik"
#~ msgid "Move Up"
#~ msgstr "Flytta upp"
#~ msgid "Move Down"
#~ msgstr "Flytta ner"
#~ msgid "Step Name"
#~ msgstr "Namn på steget"
#~ msgid "Step Type"
#~ msgstr "Stegtyp"
#~ msgid "Step time in Minutes"
#~ msgstr "Tid i minuter för steget"
#~ msgid "Select Unit"
#~ msgstr "Välj enhet"
#~ msgid "Select"
#~ msgstr "Välj"
#~ msgid "Select Food"
#~ msgstr "Välj mat"
#~ msgid "Note"
#~ msgstr "Anteckning"
#~ msgid "Delete Ingredient"
#~ msgstr "Ta bort ingrediens"
#~ msgid "Make Ingredient"
#~ msgstr "Skapa ingrediens"
#~ msgid "Disable Amount"
#~ msgstr "Inaktivera antal"
#~ msgid "Enable Amount"
#~ msgstr "Aktivera antal"
#~ msgid "Copy Template Reference"
#~ msgstr "Kopiera mallreferens"
#~ msgid "Save & View"
#~ msgstr "Spara och visa"
#~ msgid "Add Step"
#~ msgstr "Lägg till steg"
#~ msgid "Add Nutrition"
#~ msgstr "Lägg till näring"
#~ msgid "Remove Nutrition"
#~ msgstr "Ta bort näring"
#~ msgid "View Recipe"
#~ msgstr "Visa recept"
#~ msgid "Delete Recipe"
#~ msgstr "Ta bort recept"
#~ msgid "Edit Ingredients"
#~ msgstr "Redigera ingredienser"
#~ msgid ""
#~ "\n"
#~ " The following form can be used if, accidentally, two (or more) "
#~ "units or ingredients where created that should be\n"
#~ " the same.\n"
#~ " It merges two units or ingredients and updates all recipes using "
#~ "them.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Följande formulär kan användas om det har råkats skapats två "
#~ "(eller fler) enheter eller ingredienser har skapats där de borde \n"
#~ " vara samma.\n"
#~ " Formuläret för ihop två enheter eller ingredienser och uppdaterar "
#~ "alla recept som använder dem\n"
#~ " "
#~ msgid "Are you sure that you want to merge these two units?"
#~ msgstr "Är du säker på att du vill slå samman dessa två enheter?"
#~ msgid "Are you sure that you want to merge these two ingredients?"
#~ msgstr "Är du säker på att du vill föra ihop dessa två ingredienser?"
#~ msgid "Import Recipes"
#~ msgstr "Importera recept"
#~ msgid "Log Recipe Cooking"
#~ msgstr "Logga tillagningen av receptet"
#~ msgid "All fields are optional and can be left empty."
#~ msgstr "Alla rutor är valfria och kan lämnas tomma."
#~ msgid "Rating"
#~ msgstr "Betyg"
#~ msgid "Close"
#~ msgstr "Stäng"
#~ msgid "Open Recipe"
#~ msgstr "Öppna recept"
#~ msgid "Website Import"
#~ msgstr "Importera från hemsida"
#~ msgid "New Entry"
#~ msgstr "Ny post"
#~ msgid "Title"
#~ msgstr "Titel"
#~ msgid "Note (optional)"
#~ msgstr "Anmärkning (valfritt)"
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "Du kan använda markdown för att formatera det här fältet. Se dokumenten "
#~ "här"
#~ msgid "Serving Count"
#~ msgstr "Antal portioner"
#~ msgid "Create only note"
#~ msgstr "Skapa endast notering"
#~ msgid "Shopping list currently empty"
#~ msgstr "Inköpslistan är för närvarande tom"
#~ msgid "Open Shopping List"
#~ msgstr "Öppna inköpslistan"
#~ msgid "Number of Days"
#~ msgstr "Antal dagar"
#~ msgid "Weekday offset"
#~ msgstr "Veckodagsförskjutning"
#~ msgid ""
#~ "Number of days starting from the first day of the week to offset the "
#~ "default view."
#~ msgstr ""
#~ "Antal dagar från den första dagen i veckan för att kompensera för "
#~ "standardvyn."
#~ msgid "Edit plan types"
#~ msgstr "Redigera plantyper"
#~ msgid "Week iCal export"
#~ msgstr "Vecka iCal-export"
#~ msgid "Created by"
#~ msgstr "Skapad av"
#~ msgid "Shared with"
#~ msgstr "Delad med"
#~ msgid "Add to Shopping"
#~ msgstr "Lägg till i inköpslista"
#~ msgid "New meal type"
#~ msgstr "Ny måltidstyp"
#~ msgid "Meal Plan Help"
#~ msgstr "Hjälp med måltidsplaner"
#~ msgid ""
#~ "\n"
#~ " The meal plan module allows planning of "
#~ "meals both with recipes and notes.
\n"
#~ " Simply select a recipe from the list of "
#~ "recently viewed recipes or search the one you\n"
#~ " want and drag it to the desired plan "
#~ "position. You can also add a note and a title and\n"
#~ " then drag the recipe to create a plan "
#~ "entry with a custom title and note. Creating only\n"
#~ " Notes is possible by dragging the create "
#~ "note box into the plan.
\n"
#~ " Click on a recipe in order to open the "
#~ "detailed view. There you can also add it to the\n"
#~ " shopping list. You can also add all "
#~ "recipes of a day to the shopping list by\n"
#~ " clicking the shopping cart at the top of "
#~ "the table.
\n"
#~ " Since a common use case is to plan meals "
#~ "together you can define\n"
#~ " users you want to share your plan with in "
#~ "the settings.\n"
#~ "
\n"
#~ " You can also edit the types of meals you "
#~ "want to plan. If you share your plan with\n"
#~ " someone with\n"
#~ " different meals, their meal types will "
#~ "appear in your list as well. To prevent\n"
#~ " duplicates (e.g. Other and Misc.)\n"
#~ " name your meal types the same as the "
#~ "users you share your meals with and they will be\n"
#~ " merged.
\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " Målplansmodulen tillåter planering av "
#~ "måltider både med recept och anteckningar.
\n"
#~ " Välj bara ett recept från listan över "
#~ "nyligen visade recept eller sök på det du\n"
#~ " vill och dra den till önskad "
#~ "planposition. Du kan också lägga till en anteckning och en titel och\n"
#~ " dra sedan receptet för att skapa en "
#~ "planpost med en anpassad titel och anteckning. Skapar endast\n"
#~ " Anteckningar är möjligt genom att dra "
#~ "rutan Skapa anteckningar till planen.
\n"
#~ " Klicka på ett recept för att öppna den "
#~ "detaljerade vyn. Där kan du också lägga till den i\n"
#~ " inköpslista. Du kan också lägga till alla "
#~ "recept för en dag till inköpslistan genom att\n"
#~ " klicka på kundvagnen högst upp i tabellen."
#~ "
\n"
#~ " Eftersom ett vanligt fall är att planera "
#~ "måltider tillsammans kan du definiera\n"
#~ " användare som du vill dela din plan med i "
#~ "inställningarna.\n"
#~ "
\n"
#~ " Du kan också redigera de typer av måltider "
#~ "du vill planera. Om du delar din plan med\n"
#~ " någon med\n"
#~ " olika måltider, kommer deras måltidstyper "
#~ "också att visas i din lista. Att förebygga\n"
#~ " dubbletter (t.ex. Övrigt och Övrigt)\n"
#~ " namnge dina måltidstyper på samma sätt "
#~ "som användarna du delar dina måltider med och då kommer de att bli\n"
#~ " sammanslagna.
\n"
#~ " "
#~ msgid "Meal Plan View"
#~ msgstr "Vy över måltidsplan"
#~ msgid "Never cooked before."
#~ msgstr "Aldrig lagat mat förut."
#~ msgid "Other meals on this day"
#~ msgstr "Andra måltider denna dag"
#~ msgid "You are not a member of any space."
#~ msgstr "Du är inte medlem i något utrymme."
#~ msgid "Recipe Image"
#~ msgstr "Receptbild"
#~ msgid "Preparation time ca."
#~ msgstr "Förberedelsetid ca."
#~ msgid "Waiting time ca."
#~ msgstr "Väntetid ca."
#~ msgid "External"
#~ msgstr "Extern"
#~ msgid "Log Cooking"
#~ msgstr "Logga matlagning"
#~ msgid "Account"
#~ msgstr "Konto"
#~ msgid "Link social account"
#~ msgstr "Länka socialt konto"
#~ msgid "Language"
#~ msgstr "Språk"
#~ msgid "Style"
#~ msgstr "Stil"
#~ msgid ""
#~ "You can use both basic authentication and token based authentication to "
#~ "access the REST API."
#~ msgstr ""
#~ "Du kan använda både grundläggande autentisering och tokenbaserad "
#~ "autentisering för att komma åt REST API."
#~ msgid ""
#~ "Use the token as an Authorization header prefixed by the word token as "
#~ "shown in the following examples:"
#~ msgstr ""
#~ "Använd token som en auktoriseringsrubrik med ordet token som prefix som "
#~ "visas i följande exempel:"
#~ msgid "or"
#~ msgstr "eller"
#~ msgid "No recipes selected"
#~ msgstr "Inga recept har valts"
#~ msgid "Entry Mode"
#~ msgstr "Ingångsläge"
#~ msgid "Add Entry"
#~ msgstr "Lägg till post"
#~ msgid "Amount"
#~ msgstr "Belopp"
#~ msgid "Select Supermarket"
#~ msgstr "Välj mataffär"
#~ msgid "Select User"
#~ msgstr "Välj användare"
#~ msgid "Finished"
#~ msgstr "Avslutad"
#~ msgid "You are offline, shopping list might not syncronize."
#~ msgstr "Du är offline, inköpslistan kanske inte synkroniseras."
#~ msgid "Copy/Export"
#~ msgstr "Kopiera/exportera"
#~ msgid "List Prefix"
#~ msgstr "Lista prefix"
#~ msgid "There was an error creating a resource!"
#~ msgstr "Det gick inte att skapa en resurs!"
#~ msgid "Stats"
#~ msgstr "Statistik"
#~ msgid "Number of objects"
#~ msgstr "Antal objekt"
#~ msgid "Recipe Imports"
#~ msgstr "Receptimport"
#~ msgid "Objects stats"
#~ msgstr "Objektstatistik"
#~ msgid "Recipes without Keywords"
#~ msgstr "Recept utan nyckelord"
#~ msgid "Internal Recipes"
#~ msgstr "Interna recept"
#~ msgid "Enter website URL"
#~ msgstr "Ange webbadress"
#~ msgid "Enter json directly"
#~ msgstr "Ange json direkt"
#~ msgid "Recipe Name"
#~ msgstr "Receptnamn"
#~ msgid "Recipe Description"
#~ msgstr "Receptbeskrivning"
#~ msgid "Select one"
#~ msgstr "Välj en"
#~ msgid "All Keywords"
#~ msgstr "Alla nyckelord"
#~ msgid "Import all keywords, not only the ones already existing."
#~ msgstr "Importera alla sökord, inte bara de som redan finns."
#~ msgid ""
#~ " Only websites containing ld+json or microdata information can currently\n"
#~ " be imported. Most big recipe pages "
#~ "support this. If you site cannot be imported but\n"
#~ " you think\n"
#~ " it probably has some kind of "
#~ "structured data feel free to post an example in the\n"
#~ " github issues."
#~ msgstr ""
#~ " Endast webbplatser som innehåller ld+json- eller mikrodatainformation "
#~ "kan för närvarande\n"
#~ " importeras. De flesta stora "
#~ "receptsidor stödjer detta. Om din webbplats inte kan importeras men\n"
#~ " tror du\n"
#~ " den har förmodligen någon form av "
#~ "strukturerad data, posta gärna ett exempel i\n"
#~ " github issues."
#~ msgid "Google ld+json Info"
#~ msgstr "Google ld+json info"
#~ msgid "GitHub Issues"
#~ msgstr "GitHub Problem"
#~ msgid "Recipe Markup Specification"
#~ msgstr "Specifikation för receptmärkning"
#~ msgid ""
#~ "The requested page refused to provide any information (Status Code 403)."
#~ msgstr "Den begärda sidan vägrade att ge någon information (Statuskod 403)."
#~ msgid "Could not parse correctly..."
#~ msgstr "Kunde inte tolka korrekt..."
#~ msgid "Recipe Book"
#~ msgstr "Receptbok"
#~ msgid "Bookmarks"
#~ msgstr "Bokmärken"
#~ msgid "Units merged!"
#~ msgstr "Enheter sammanslagna!"
#~ msgid "Foods merged!"
#~ msgstr "Livsmedel sammanslagna!"
#~ msgid "Exporting is not implemented for this provider"
#~ msgstr "Export är inte implementerat för denna leverantör"
================================================
FILE: cookbook/locale/tr/LC_MESSAGES/django.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
# Translators:
# Emre S, 2020
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-22 20:15+0200\n"
"PO-Revision-Date: 2025-01-20 05:20+0000\n"
"Last-Translator: Yigit \n"
"Language-Team: Turkish \n"
"Language: tr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Generator: Weblate 5.8.4\n"
#: .\cookbook\forms.py:50
msgid "Default"
msgstr "Varsayılan"
#: .\cookbook\forms.py:77
msgid ""
"To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything."
msgstr ""
"Varolan tariflerden benzer isimli olanlar mükerrerliği engellemek için "
"gözardı edilecektir. Tümünü içeri aktarmak için bu kutucuğu işaretleyin."
#: .\cookbook\forms.py:108
msgid "Maximum number of users for this space reached."
msgstr "Bu alan için maksimum kullanıcı sayısına ulaşıldı."
#: .\cookbook\forms.py:114
msgid "Email address already taken!"
msgstr "Email adresi zaten alınmış!"
#: .\cookbook\forms.py:121
msgid ""
"An email address is not required but if present the invite link will be sent "
"to the user."
msgstr ""
"Email adresi zorunlu değildir fakat verilmesi halinde davet linki "
"kullanıcıya gönderilecektir."
#: .\cookbook\forms.py:133
msgid "Name already taken."
msgstr "İsim zaten alınmış."
#: .\cookbook\forms.py:144 .\cookbook\forms.py:158
msgid "Accept Terms and Privacy"
msgstr "Koşulları ve Gizliliği Onayla"
#: .\cookbook\helper\AllAuthCustomAdapter.py:41
msgid ""
"In order to prevent spam, the requested email was not send. Please wait a "
"few minutes and try again."
msgstr ""
"İstenmeyen e-postayı önlemek için istenen e-posta gönderilemedi. Lütfen "
"birkaç dakika bekleyin ve tekrar deneyin."
#: .\cookbook\helper\permission_helper.py:165
#: .\cookbook\helper\permission_helper.py:186 .\cookbook\views\views.py:138
msgid "You are not logged in and therefore cannot view this page!"
msgstr "Giriş yapmadınız ve bu nedenle bu sayfayı görüntüleyemezsiniz!"
#: .\cookbook\helper\permission_helper.py:168
#: .\cookbook\helper\permission_helper.py:173
#: .\cookbook\helper\permission_helper.py:198
#: .\cookbook\helper\permission_helper.py:265
#: .\cookbook\helper\permission_helper.py:279
#: .\cookbook\helper\permission_helper.py:290
#: .\cookbook\helper\permission_helper.py:301
#: .\cookbook\helper\permission_helper.py:317
#: .\cookbook\helper\permission_helper.py:343
#: .\cookbook\helper\permission_helper.py:359
msgid "You do not have the required permissions to view this page!"
msgstr "Bu sayfayı görüntülemek için gerekli izinlere sahip değilsiniz!"
#: .\cookbook\helper\permission_helper.py:191
#: .\cookbook\helper\permission_helper.py:214
#: .\cookbook\helper\permission_helper.py:236
#: .\cookbook\helper\permission_helper.py:251
msgid "You cannot interact with this object as it is not owned by you!"
msgstr "Bu nesne size ait olmadığı için onunla etkileşime giremezsiniz!"
#: .\cookbook\helper\permission_helper.py:420
msgid "You have reached the maximum number of recipes for your space."
msgstr "Alanınız için maksimum tarif sayısına ulaştınız."
#: .\cookbook\helper\permission_helper.py:432
msgid "You have more users than allowed in your space."
msgstr "Alanınızda izin verilenden daha fazla kullanıcı var."
#: .\cookbook\helper\recipe_url_import.py:319
msgid "reverse rotation"
msgstr "ters dönüş"
#: .\cookbook\helper\recipe_url_import.py:320
msgid "careful rotation"
msgstr "dikkatli dönüş"
#: .\cookbook\helper\recipe_url_import.py:321
msgid "knead"
msgstr "yoğur"
#: .\cookbook\helper\recipe_url_import.py:322
msgid "thicken"
msgstr "kalınlaştır"
#: .\cookbook\helper\recipe_url_import.py:323
msgid "warm up"
msgstr "ısıt"
#: .\cookbook\helper\recipe_url_import.py:324
msgid "ferment"
msgstr "mayala"
#: .\cookbook\helper\recipe_url_import.py:325
msgid "slow cook"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:326
msgid "egg boiler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:327
msgid "kettle"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:328
msgid "blend"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:329
msgid "pre-clean"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:330
msgid "high temperature"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:331
msgid "rice cooker"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:332
msgid "caramelize"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:333
msgid "peeler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:334
msgid "slicer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:335
msgid "grater"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:336
msgid "spiralizer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:337
msgid "sous-vide"
msgstr "sous-vide"
#: .\cookbook\helper\shopping_helper.py:150
msgid "You must supply a servings size"
msgstr "Bir porsiyon büyüklüğü vermelisiniz"
#: .\cookbook\helper\template_helper.py:97
#: .\cookbook\helper\template_helper.py:99
#: .\cookbook\helper\template_helper.py:101
msgid "Could not parse template code."
msgstr "Şablon kodu ayrıştırılamadı."
#: .\cookbook\integration\copymethat.py:44
#: .\cookbook\integration\melarecipes.py:37
msgid "Favorite"
msgstr "Favori"
#: .\cookbook\integration\copymethat.py:50
msgid "I made this"
msgstr "Bunu yaptım"
#: .\cookbook\integration\integration.py:238
msgid ""
"Importer expected a .zip file. Did you choose the correct importer type for "
"your data ?"
msgstr ""
#: .\cookbook\integration\integration.py:241
msgid ""
"An unexpected error occurred during the import. Please make sure you have "
"uploaded a valid file."
msgstr ""
#: .\cookbook\integration\integration.py:246
msgid "The following recipes were ignored because they already existed:"
msgstr "Aşağıdaki tarifler zaten mevcut olduğu için göz ardı edildi:"
#: .\cookbook\integration\integration.py:250
#, python-format
msgid "Imported %s recipes."
msgstr "%s tarif içe aktarıldı."
#: .\cookbook\integration\mealie1.py:210
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "Calories"
msgstr ""
#: .\cookbook\integration\mealie1.py:211
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
msgid "Carbohydrates"
msgstr ""
#: .\cookbook\integration\mealie1.py:212
msgid "Cholesterol"
msgstr ""
#: .\cookbook\integration\mealie1.py:213
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
msgid "Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:214
msgid "Fiber"
msgstr ""
#: .\cookbook\integration\mealie1.py:215
msgid "Protein"
msgstr ""
#: .\cookbook\integration\mealie1.py:216
msgid "Saturated Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:217
msgid "Sodium"
msgstr ""
#: .\cookbook\integration\mealie1.py:218
msgid "Sugar"
msgstr ""
#: .\cookbook\integration\mealie1.py:219
msgid "Trans Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:220
msgid "Unsaturated Fat"
msgstr ""
#: .\cookbook\integration\openeats.py:28
msgid "Recipe source:"
msgstr "Tarif kaynağı:"
#: .\cookbook\integration\paprika.py:49
msgid "Notes"
msgstr "Notlar"
#: .\cookbook\integration\paprika.py:52
msgid "Nutritional Information"
msgstr "Beslenme Bilgileri"
#: .\cookbook\integration\paprika.py:56
msgid "Source"
msgstr "Kaynak"
#: .\cookbook\integration\recettetek.py:55
#: .\cookbook\integration\recipekeeper.py:70
msgid "Imported from"
msgstr ""
#: .\cookbook\integration\saffron.py:23
msgid "Servings"
msgstr "Porsiyon"
#: .\cookbook\integration\saffron.py:25
msgid "Waiting time"
msgstr "Bekleme süresi"
#: .\cookbook\integration\saffron.py:27
msgid "Preparation Time"
msgstr "Hazırlık Süresi"
#: .\cookbook\integration\saffron.py:29 .\cookbook\templates\index.html:6
msgid "Cookbook"
msgstr "Yemek kitabı"
#: .\cookbook\integration\saffron.py:31
msgid "Section"
msgstr "Bölüm"
#: .\cookbook\management\commands\fix_duplicate_properties.py:15
msgid "Fixes foods with "
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:14
msgid "Rebuilds full text search index on Recipe"
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:18
msgid "Only Postgresql databases use full text search, no index to rebuild"
msgstr ""
"Yalnızca Postgresql veritabanları tam metin araması kullanır, yeniden "
"oluşturulacak dizin yoktur"
#: .\cookbook\management\commands\rebuildindex.py:29
msgid "Recipe index rebuild complete."
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:31
msgid "Recipe index rebuild failed."
msgstr ""
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
msgid "Breakfast"
msgstr ""
#: .\cookbook\migrations\0047_auto_20200602_1133.py:19
msgid "Lunch"
msgstr ""
#: .\cookbook\migrations\0047_auto_20200602_1133.py:24
msgid "Dinner"
msgstr ""
#: .\cookbook\migrations\0047_auto_20200602_1133.py:29 .\cookbook\models.py:971
msgid "Other"
msgstr ""
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "g"
msgstr ""
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "Proteins"
msgstr ""
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "kcal"
msgstr ""
#: .\cookbook\models.py:325
msgid ""
"Maximum file storage for space in MB. 0 for unlimited, -1 to disable file "
"upload."
msgstr ""
#: .\cookbook\models.py:513
msgid "Search"
msgstr ""
#: .\cookbook\models.py:514
msgid "Meal-Plan"
msgstr ""
#: .\cookbook\models.py:515
msgid "Books"
msgstr ""
#: .\cookbook\models.py:516 .\cookbook\views\views.py:416
#: .\cookbook\views\views.py:417
msgid "Shopping"
msgstr ""
#: .\cookbook\models.py:967
msgid "Nutrition"
msgstr ""
#: .\cookbook\models.py:968
msgid "Allergen"
msgstr ""
#: .\cookbook\models.py:969
msgid "Price"
msgstr ""
#: .\cookbook\models.py:970
msgid "Goal"
msgstr ""
#: .\cookbook\models.py:1467 .\cookbook\templates\search_info.html:28
msgid "Simple"
msgstr ""
#: .\cookbook\models.py:1468 .\cookbook\templates\search_info.html:33
msgid "Phrase"
msgstr ""
#: .\cookbook\models.py:1469 .\cookbook\templates\search_info.html:38
msgid "Web"
msgstr ""
#: .\cookbook\models.py:1470 .\cookbook\templates\search_info.html:47
msgid "Raw"
msgstr ""
#: .\cookbook\models.py:1525
msgid "Food Alias"
msgstr ""
#: .\cookbook\models.py:1526
msgid "Unit Alias"
msgstr ""
#: .\cookbook\models.py:1527
msgid "Keyword Alias"
msgstr ""
#: .\cookbook\models.py:1528
msgid "Description Replace"
msgstr ""
#: .\cookbook\models.py:1529
msgid "Instruction Replace"
msgstr ""
#: .\cookbook\models.py:1530
msgid "Never Unit"
msgstr ""
#: .\cookbook\models.py:1531
msgid "Transpose Words"
msgstr ""
#: .\cookbook\models.py:1532
msgid "Food Replace"
msgstr ""
#: .\cookbook\models.py:1533
msgid "Unit Replace"
msgstr ""
#: .\cookbook\models.py:1534
msgid "Name Replace"
msgstr ""
#: .\cookbook\models.py:1564
msgid "Recipe"
msgstr ""
#: .\cookbook\models.py:1565
msgid "Food"
msgstr ""
#: .\cookbook\models.py:1566
msgid "Keyword"
msgstr ""
#: .\cookbook\serializer.py:262
msgid "File uploads are not enabled for this Space."
msgstr ""
#: .\cookbook\serializer.py:273
msgid "You have reached your file upload limit."
msgstr ""
#: .\cookbook\serializer.py:281
msgid "The given file type is not allowed."
msgstr ""
#: .\cookbook\serializer.py:421 .\cookbook\views\views.py:94
msgid ""
"You have the reached the maximum amount of spaces that can be owned by you."
msgstr ""
#: .\cookbook\serializer.py:434
msgid "Space Name must be unique."
msgstr ""
#: .\cookbook\serializer.py:469
msgid "Cannot modify Space owner permission."
msgstr ""
#: .\cookbook\serializer.py:1596
msgid "Hello"
msgstr ""
#: .\cookbook\serializer.py:1596
msgid "You have been invited by "
msgstr ""
#: .\cookbook\serializer.py:1598
msgid " to join their Tandoor Recipes space "
msgstr ""
#: .\cookbook\serializer.py:1600
msgid "Click the following link to activate your account: "
msgstr ""
#: .\cookbook\serializer.py:1602
msgid ""
"If the link does not work use the following code to manually join the space: "
msgstr ""
#: .\cookbook\serializer.py:1604
msgid "The invitation is valid until "
msgstr ""
#: .\cookbook\serializer.py:1606
msgid ""
"Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub "
msgstr ""
#: .\cookbook\serializer.py:1609
msgid "Tandoor Recipes Invite"
msgstr ""
#: .\cookbook\serializer.py:1813
msgid "Existing shopping list to update"
msgstr ""
#: .\cookbook\serializer.py:1815
msgid ""
"List of ingredient IDs from the recipe to add, if not provided all "
"ingredients will be added."
msgstr ""
#: .\cookbook\serializer.py:1817
msgid ""
"Providing a list_recipe ID and servings of 0 will delete that shopping list."
msgstr ""
#: .\cookbook\serializer.py:1826
msgid "Amount of food to add to the shopping list"
msgstr ""
#: .\cookbook\serializer.py:1828
msgid "ID of unit to use for the shopping list"
msgstr ""
#: .\cookbook\serializer.py:1830
msgid "When set to true will delete all food from active shopping lists."
msgstr ""
#: .\cookbook\templates\404.html:5
msgid "404 Error"
msgstr ""
#: .\cookbook\templates\404.html:18
msgid "The page you are looking for could not be found."
msgstr ""
#: .\cookbook\templates\404.html:33
msgid "Take me Home"
msgstr ""
#: .\cookbook\templates\404.html:35
msgid "Report a Bug"
msgstr ""
#: .\cookbook\templates\account\email.html:6
#: .\cookbook\templates\account\email.html:10
msgid "E-mail Addresses"
msgstr ""
#: .\cookbook\templates\account\email.html:12
msgid "The following e-mail addresses are associated with your account:"
msgstr ""
#: .\cookbook\templates\account\email.html:29
msgid "Verified"
msgstr ""
#: .\cookbook\templates\account\email.html:31
msgid "Unverified"
msgstr ""
#: .\cookbook\templates\account\email.html:33
msgid "Primary"
msgstr ""
#: .\cookbook\templates\account\email.html:40
msgid "Make Primary"
msgstr ""
#: .\cookbook\templates\account\email.html:42
msgid "Re-send Verification"
msgstr ""
#: .\cookbook\templates\account\email.html:43
#: .\cookbook\templates\socialaccount\connections.html:36
msgid "Remove"
msgstr ""
#: .\cookbook\templates\account\email.html:51
msgid "Warning:"
msgstr ""
#: .\cookbook\templates\account\email.html:51
msgid ""
"You currently do not have any e-mail address set up. You should really add "
"an e-mail address so you can receive notifications, reset your password, etc."
msgstr ""
#: .\cookbook\templates\account\email.html:57
msgid "Add E-mail Address"
msgstr ""
#: .\cookbook\templates\account\email.html:62
msgid "Add E-mail"
msgstr ""
#: .\cookbook\templates\account\email.html:72
msgid "Do you really want to remove the selected e-mail address?"
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:6
#: .\cookbook\templates\account\email_confirm.html:10
msgid "Confirm E-mail Address"
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:16
#, python-format
msgid ""
"Please confirm that\n"
" %(email)s is an e-mail address "
"for user %(user_display)s\n"
" ."
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:22
msgid "Confirm"
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:29
#, python-format
msgid ""
"This e-mail confirmation link expired or is invalid. Please\n"
" issue a new e-mail confirmation "
"request."
msgstr ""
#: .\cookbook\templates\account\login.html:8
#: .\cookbook\templates\openid\login.html:8
msgid "Login"
msgstr ""
#: .\cookbook\templates\account\login.html:15
#: .\cookbook\templates\account\login.html:31
#: .\cookbook\templates\account\password_reset.html:39
#: .\cookbook\templates\account\password_reset_done.html:31
#: .\cookbook\templates\account\signup.html:68
#: .\cookbook\templates\account\signup_closed.html:15
#: .\cookbook\templates\openid\login.html:15
#: .\cookbook\templates\openid\login.html:26
#: .\cookbook\templates\socialaccount\authentication_error.html:15
msgid "Sign In"
msgstr ""
#: .\cookbook\templates\account\login.html:34
#: .\cookbook\templates\account\password_reset.html:41
#: .\cookbook\templates\account\password_reset_done.html:33
#: .\cookbook\templates\socialaccount\signup.html:8
#: .\cookbook\templates\socialaccount\signup.html:56
msgid "Sign Up"
msgstr ""
#: .\cookbook\templates\account\login.html:38
msgid "Lost your password?"
msgstr ""
#: .\cookbook\templates\account\login.html:39
#: .\cookbook\templates\account\password_reset.html:29
msgid "Reset My Password"
msgstr ""
#: .\cookbook\templates\account\login.html:50
msgid "Social Login"
msgstr ""
#: .\cookbook\templates\account\login.html:51
msgid "You can use any of the following providers to sign in."
msgstr ""
#: .\cookbook\templates\account\logout.html:5
#: .\cookbook\templates\account\logout.html:9
#: .\cookbook\templates\account\logout.html:18
msgid "Sign Out"
msgstr ""
#: .\cookbook\templates\account\logout.html:11
msgid "Are you sure you want to sign out?"
msgstr ""
#: .\cookbook\templates\account\password_change.html:6
#: .\cookbook\templates\account\password_change.html:10
#: .\cookbook\templates\account\password_change.html:15
#: .\cookbook\templates\account\password_reset_from_key.html:7
#: .\cookbook\templates\account\password_reset_from_key.html:13
#: .\cookbook\templates\account\password_reset_from_key_done.html:7
#: .\cookbook\templates\account\password_reset_from_key_done.html:13
msgid "Change Password"
msgstr ""
#: .\cookbook\templates\account\password_change.html:16
msgid "Forgot Password?"
msgstr ""
#: .\cookbook\templates\account\password_reset.html:7
#: .\cookbook\templates\account\password_reset.html:13
#: .\cookbook\templates\account\password_reset_done.html:7
#: .\cookbook\templates\account\password_reset_done.html:18
msgid "Password Reset"
msgstr ""
#: .\cookbook\templates\account\password_reset.html:24
msgid ""
"Forgotten your password? Enter your e-mail address below, and we'll send you "
"an e-mail allowing you to reset it."
msgstr ""
#: .\cookbook\templates\account\password_reset.html:32
msgid "Password reset is disabled on this instance."
msgstr ""
#: .\cookbook\templates\account\password_reset_done.html:25
msgid ""
"We have sent you an e-mail. Please contact us if you do not receive it "
"within a few minutes."
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:13
msgid "Bad Token"
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:25
#, python-format
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used.\n"
" Please request a new "
"password reset."
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:33
msgid "change password"
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:36
#: .\cookbook\templates\account\password_reset_from_key_done.html:19
msgid "Your password is now changed."
msgstr ""
#: .\cookbook\templates\account\password_set.html:6
#: .\cookbook\templates\account\password_set.html:10
#: .\cookbook\templates\account\password_set.html:15
msgid "Set Password"
msgstr ""
#: .\cookbook\templates\account\signup.html:5
msgid "Register"
msgstr ""
#: .\cookbook\templates\account\signup.html:11
msgid "Create an Account"
msgstr ""
#: .\cookbook\templates\account\signup.html:41
msgid "I accept the follwoing"
msgstr ""
#: .\cookbook\templates\account\signup.html:44
#: .\cookbook\templates\socialaccount\signup.html:35
msgid "Terms and Conditions"
msgstr ""
#: .\cookbook\templates\account\signup.html:47
#: .\cookbook\templates\socialaccount\signup.html:38
msgid "and"
msgstr ""
#: .\cookbook\templates\account\signup.html:51
#: .\cookbook\templates\socialaccount\signup.html:42
msgid "Privacy Policy"
msgstr ""
#: .\cookbook\templates\account\signup.html:64
msgid "Create User"
msgstr ""
#: .\cookbook\templates\account\signup.html:68
msgid "Already have an account?"
msgstr ""
#: .\cookbook\templates\account\signup_closed.html:5
#: .\cookbook\templates\account\signup_closed.html:11
msgid "Sign Up Closed"
msgstr ""
#: .\cookbook\templates\account\signup_closed.html:13
msgid "We are sorry, but the sign up is currently closed."
msgstr ""
#: .\cookbook\templates\frontend\tandoor.html:15
msgid "Tandoor Recipe Manager"
msgstr ""
#: .\cookbook\templates\index.html:28
msgid "Search recipe ..."
msgstr ""
#: .\cookbook\templates\index.html:43
msgid "New Recipe"
msgstr ""
#: .\cookbook\templates\index.html:46
msgid "Import Recipe"
msgstr ""
#: .\cookbook\templates\index.html:52
msgid "Advanced Search"
msgstr ""
#: .\cookbook\templates\index.html:56
msgid "Reset Search"
msgstr ""
#: .\cookbook\templates\index.html:84
msgid "Last viewed"
msgstr ""
#: .\cookbook\templates\index.html:86
msgid "Recipes"
msgstr ""
#: .\cookbook\templates\index.html:93
msgid "Log in to view recipes"
msgstr ""
#: .\cookbook\templates\markdown_info.html:5
#: .\cookbook\templates\markdown_info.html:13
msgid "Markdown Info"
msgstr ""
#: .\cookbook\templates\markdown_info.html:14
msgid ""
"\n"
" Markdown is lightweight markup language that can be used to format "
"plain text easily.\n"
" This site uses the Python Markdown library to\n"
" convert your text into nice looking HTML. Its full markdown "
"documentation can be found\n"
" here.\n"
" An incomplete but most likely sufficient documentation can be found "
"below.\n"
" "
msgstr ""
#: .\cookbook\templates\markdown_info.html:25
msgid "Headers"
msgstr ""
#: .\cookbook\templates\markdown_info.html:54
msgid "Formatting"
msgstr ""
#: .\cookbook\templates\markdown_info.html:56
#: .\cookbook\templates\markdown_info.html:72
msgid "Line breaks are inserted by adding two spaces after the end of a line"
msgstr ""
#: .\cookbook\templates\markdown_info.html:57
#: .\cookbook\templates\markdown_info.html:73
msgid "or by leaving a blank line in between."
msgstr ""
#: .\cookbook\templates\markdown_info.html:59
#: .\cookbook\templates\markdown_info.html:74
msgid "This text is bold"
msgstr ""
#: .\cookbook\templates\markdown_info.html:60
#: .\cookbook\templates\markdown_info.html:75
msgid "This text is italic"
msgstr ""
#: .\cookbook\templates\markdown_info.html:61
#: .\cookbook\templates\markdown_info.html:77
msgid "Blockquotes are also possible"
msgstr ""
#: .\cookbook\templates\markdown_info.html:84
msgid "Lists"
msgstr ""
#: .\cookbook\templates\markdown_info.html:85
msgid ""
"Lists can ordered or unordered. It is important to leave a blank line "
"before the list!"
msgstr ""
#: .\cookbook\templates\markdown_info.html:87
#: .\cookbook\templates\markdown_info.html:108
msgid "Ordered List"
msgstr ""
#: .\cookbook\templates\markdown_info.html:89
#: .\cookbook\templates\markdown_info.html:90
#: .\cookbook\templates\markdown_info.html:91
#: .\cookbook\templates\markdown_info.html:110
#: .\cookbook\templates\markdown_info.html:111
#: .\cookbook\templates\markdown_info.html:112
msgid "unordered list item"
msgstr ""
#: .\cookbook\templates\markdown_info.html:93
#: .\cookbook\templates\markdown_info.html:114
msgid "Unordered List"
msgstr ""
#: .\cookbook\templates\markdown_info.html:95
#: .\cookbook\templates\markdown_info.html:96
#: .\cookbook\templates\markdown_info.html:97
#: .\cookbook\templates\markdown_info.html:116
#: .\cookbook\templates\markdown_info.html:117
#: .\cookbook\templates\markdown_info.html:118
msgid "ordered list item"
msgstr ""
#: .\cookbook\templates\markdown_info.html:125
msgid "Images & Links"
msgstr ""
#: .\cookbook\templates\markdown_info.html:126
msgid ""
"Links can be formatted with Markdown. This application also allows to paste "
"links directly into markdown fields without any formatting."
msgstr ""
#: .\cookbook\templates\markdown_info.html:132
#: .\cookbook\templates\markdown_info.html:145
msgid "This will become an image"
msgstr ""
#: .\cookbook\templates\markdown_info.html:152
msgid "Tables"
msgstr ""
#: .\cookbook\templates\markdown_info.html:153
msgid ""
"Markdown tables are hard to create by hand. It is recommended to use a table "
"editor like this one."
msgstr ""
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:171
#: .\cookbook\templates\markdown_info.html:177
msgid "Table"
msgstr ""
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:172
msgid "Header"
msgstr ""
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:178
msgid "Cell"
msgstr ""
#: .\cookbook\templates\no_groups_info.html:5
#: .\cookbook\templates\no_groups_info.html:12
msgid "No Permissions"
msgstr ""
#: .\cookbook\templates\no_groups_info.html:17
msgid "You do not have any groups and therefor cannot use this application."
msgstr ""
#: .\cookbook\templates\no_groups_info.html:18
#: .\cookbook\templates\no_perm_info.html:15
msgid "Please contact your administrator."
msgstr ""
#: .\cookbook\templates\no_perm_info.html:5
#: .\cookbook\templates\no_perm_info.html:12
msgid "No Permission"
msgstr ""
#: .\cookbook\templates\no_perm_info.html:15
msgid ""
"You do not have the required permissions to view this page or perform this "
"action."
msgstr ""
#: .\cookbook\templates\offline.html:5
msgid "Offline"
msgstr ""
#: .\cookbook\templates\openid\login.html:27
#: .\cookbook\templates\socialaccount\authentication_error.html:27
msgid "Back"
msgstr ""
#: .\cookbook\templates\rest_framework\api.html:5
msgid "Recipe Home"
msgstr ""
#: .\cookbook\templates\rest_framework\api.html:11
msgid "API Documentation"
msgstr ""
#: .\cookbook\templates\search_info.html:5
#: .\cookbook\templates\search_info.html:9
msgid "Search Settings"
msgstr ""
#: .\cookbook\templates\search_info.html:10
msgid ""
"\n"
" Creating the best search experience is complicated and weighs "
"heavily on your personal configuration. \n"
" Changing any of the search settings can have significant impact on "
"the speed and quality of the results.\n"
" Search Methods, Trigrams and Full Text Search configurations are "
"only available if you are using Postgres for your database.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:19
msgid "Search Methods"
msgstr ""
#: .\cookbook\templates\search_info.html:23
msgid ""
" \n"
" Full text searches attempt to normalize the words provided to "
"match common variants. For example: 'forked', 'forking', 'forks' will all "
"normalize to 'fork'.\n"
" There are several methods available, described below, that will "
"control how the search behavior should react when multiple words are "
"searched.\n"
" Full technical details on how these operate can be viewed on Postgresql's website.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:29
msgid ""
" \n"
" Simple searches ignore punctuation and common words such as "
"'the', 'a', 'and'. And will treat separate words as required.\n"
" Searching for 'apple or flour' will return any recipe that "
"includes both 'apple' and 'flour' anywhere in the fields that have been "
"selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:34
msgid ""
" \n"
" Phrase searches ignore punctuation, but will search for all of "
"the words in the exact order provided.\n"
" Searching for 'apple or flour' will only return a recipe that "
"includes the exact phrase 'apple or flour' in any of the fields that have "
"been selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:39
msgid ""
" \n"
" Web searches simulate functionality found on many web search "
"sites supporting special syntax.\n"
" Placing quotes around several words will convert those words "
"into a phrase.\n"
" 'or' is recognized as searching for the word (or phrase) "
"immediately before 'or' OR the word (or phrase) directly after.\n"
" '-' is recognized as searching for recipes that do not include "
"the word (or phrase) that comes immediately after. \n"
" For example searching for 'apple pie' or cherry -butter will "
"return any recipe that includes the phrase 'apple pie' or the word "
"'cherry' \n"
" in any field included in the full text search but exclude any "
"recipe that has the word 'butter' in any field included.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:48
msgid ""
" \n"
" Raw search is similar to Web except will take puncuation "
"operators such as '|', '&' and '()'\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:59
msgid ""
" \n"
" Another approach to searching that also requires Postgresql is "
"fuzzy search or trigram similarity. A trigram is a group of three "
"consecutive characters.\n"
" For example searching for 'apple' will create x trigrams 'app', "
"'ppl', 'ple' and will create a score of how closely words match the "
"generated trigrams.\n"
" One benefit of searching trigams is that a search for 'sandwich' "
"will find misspelled words such as 'sandwhich' that would be missed by other "
"methods.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:69
msgid "Search Fields"
msgstr ""
#: .\cookbook\templates\search_info.html:73
msgid ""
" \n"
" Unaccent is a special case in that it enables searching a field "
"'unaccented' for each search style attempting to ignore accented values. \n"
" For example when you enable unaccent for 'Name' any search "
"(starts with, contains, trigram) will attempt the search ignoring accented "
"characters.\n"
" \n"
" For the other options, you can enable search on any or all "
"fields and they will be combined together with an assumed 'OR'.\n"
" For example enabling 'Name' for Starts With, 'Name' and "
"'Description' for Partial Match and 'Ingredients' and 'Keywords' for Full "
"Search\n"
" and searching for 'apple' will generate a search that will "
"return recipes that have:\n"
" - A recipe name that starts with 'apple'\n"
" - OR a recipe name that contains 'apple'\n"
" - OR a recipe description that contains 'apple'\n"
" - OR a recipe that will have a full text search match ('apple' "
"or 'apples') in ingredients\n"
" - OR a recipe that will have a full text search match in "
"Keywords\n"
"\n"
" Combining too many fields in too many types of search can have a "
"negative impact on performance, create duplicate results or return "
"unexpected results.\n"
" For example, enabling fuzzy search or partial matches will "
"interfere with web search methods. \n"
" Searching for 'apple -pie' with fuzzy search and full text "
"search will return the recipe Apple Pie. Though it is not included in the "
"full text results, it does match the trigram results.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:95
msgid "Search Index"
msgstr ""
#: .\cookbook\templates\search_info.html:99
msgid ""
" \n"
" Trigram search and Full Text Search both rely on database "
"indexes to perform effectively. \n"
" You can rebuild the indexes on all fields in the Admin page for "
"Recipes and selecting all recipes and running 'rebuild index for selected "
"recipes'\n"
" You can also rebuild indexes at the command line by executing "
"the management command 'python manage.py rebuildindex'\n"
" "
msgstr ""
#: .\cookbook\templates\setup.html:6
msgid "Cookbook Setup"
msgstr ""
#: .\cookbook\templates\setup.html:14
msgid "Setup"
msgstr ""
#: .\cookbook\templates\setup.html:15
msgid ""
"To start using this application you must first create a superuser account."
msgstr ""
#: .\cookbook\templates\setup.html:20
msgid "Create Superuser account"
msgstr ""
#: .\cookbook\templates\socialaccount\authentication_error.html:7
#: .\cookbook\templates\socialaccount\authentication_error.html:23
msgid "Social Network Login Failure"
msgstr ""
#: .\cookbook\templates\socialaccount\authentication_error.html:25
msgid ""
"An error occurred while attempting to login via your social network account."
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:4
#: .\cookbook\templates\socialaccount\connections.html:7
msgid "Account Connections"
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:10
msgid ""
"You can sign in to your account using any of the following third party\n"
" accounts:"
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:44
msgid ""
"You currently have no social network accounts connected to this account."
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:47
msgid "Add a 3rd Party Account"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:5
#: .\cookbook\templates\socialaccount\signup.html:5
msgid "Signup"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:9
#, python-format
msgid "Connect %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:11
#, python-format
msgid "You are about to connect a new third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:13
#, python-format
msgid "Sign In Via %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:15
#, python-format
msgid "You are about to sign in using a third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:20
msgid "Continue"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:10
#, python-format
msgid ""
"You are about to use your\n"
" %(provider_name)s account to login to\n"
" %(site_name)s. As a final step, please complete the following form:"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:32
msgid "I accept the following"
msgstr ""
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:23
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:31
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:39
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:47
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:55
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:63
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:71
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:79
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:87
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:95
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:103
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:111
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:119
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:127
msgid "Sign in using"
msgstr ""
#: .\cookbook\templates\space_overview.html:6
msgid "Overview"
msgstr ""
#: .\cookbook\templates\space_overview.html:13
msgid "Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:17
msgid ""
"Recipes, foods, shopping lists and more are organized in spaces of one or "
"more people."
msgstr ""
#: .\cookbook\templates\space_overview.html:18
msgid ""
"You can either be invited into an existing space or create your own one."
msgstr ""
#: .\cookbook\templates\space_overview.html:25
msgid "Your Spaces"
msgstr ""
#: .\cookbook\templates\space_overview.html:53
msgid "Owner"
msgstr ""
#: .\cookbook\templates\space_overview.html:73
#: .\cookbook\templates\space_overview.html:83
msgid "Join Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:76
msgid "Join an existing space."
msgstr ""
#: .\cookbook\templates\space_overview.html:78
msgid ""
"To join an existing space either enter your invite token or click on the "
"invite link the space owner send you."
msgstr ""
#: .\cookbook\templates\space_overview.html:91
#: .\cookbook\templates\space_overview.html:100
msgid "Create Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:94
msgid "Create your own recipe space."
msgstr ""
#: .\cookbook\templates\space_overview.html:96
msgid "Start your own recipe space and invite other users to it."
msgstr ""
#: .\cookbook\templates\system.html:23
msgid "System"
msgstr ""
#: .\cookbook\templates\system.html:24
msgid ""
"\n"
" Tandoor Recipes is an open source free software application. It can "
"be found on\n"
" GitHub.\n"
" Changelogs can be found here.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:30
msgid "System Information"
msgstr ""
#: .\cookbook\templates\system.html:51
msgid ""
"\n"
" You need to execute version.py in your update "
"script to generate version information (done automatically in docker).\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:56
msgid "Plugins"
msgstr ""
#: .\cookbook\templates\system.html:67
msgid "Media Serving"
msgstr ""
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:123 .\cookbook\templates\system.html:134
msgid "Warning"
msgstr ""
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:125 .\cookbook\templates\system.html:134
msgid "Ok"
msgstr ""
#: .\cookbook\templates\system.html:70
msgid ""
"Serving media files directly using gunicorn/python is not recommend!\n"
" Please follow the steps described\n"
" here to update\n"
" your installation.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:76 .\cookbook\templates\system.html:91
#: .\cookbook\templates\system.html:104 .\cookbook\templates\system.html:115
#: .\cookbook\views\views.py:168
msgid "Everything is fine!"
msgstr ""
#: .\cookbook\templates\system.html:80
msgid "Secret Key"
msgstr ""
#: .\cookbook\templates\system.html:84
msgid ""
"\n"
" You do not have a SECRET_KEY configured in your "
".env file. Django defaulted to the\n"
" standard key\n"
" provided with the installation which is publicly know and "
"insecure! Please set\n"
" SECRET_KEY int the .env configuration "
"file.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:94
msgid "Debug Mode"
msgstr ""
#: .\cookbook\templates\system.html:98
msgid ""
"\n"
" This application is still running in debug mode. This is most "
"likely not needed. Turn of debug mode by\n"
" setting\n"
" DEBUG=0 int the .env configuration "
"file.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:107
msgid "Allowed Hosts"
msgstr ""
#: .\cookbook\templates\system.html:111
msgid ""
"\n"
" Your allowed hosts are configured to allow every host. This "
"might be ok in some setups but should be avoided. Please see the docs about "
"this.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:118
msgid "Database"
msgstr ""
#: .\cookbook\templates\system.html:121
msgid "Info"
msgstr ""
#: .\cookbook\templates\system.html:131 .\cookbook\templates\system.html:148
msgid "Migrations"
msgstr "Göçler"
#: .\cookbook\templates\system.html:137
msgid ""
"\n"
" Migrations should never fail!\n"
" Failed migrations will likely cause major parts of the app to "
"not function correctly.\n"
" If a migration fails make sure you are on the latest version and "
"if so please post the migration log and the overview below in a GitHub "
"issue.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "False"
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "True"
msgstr ""
#: .\cookbook\templates\system.html:268
msgid "Hide"
msgstr ""
#: .\cookbook\templates\system.html:271
msgid "Show"
msgstr ""
#: .\cookbook\templates\test2.html:6
msgid "Export Recipes"
msgstr ""
#: .\cookbook\templates\test2.html:14 .\cookbook\templates\test2.html:20
msgid "Export"
msgstr ""
#: .\cookbook\views\api.py:198 .\cookbook\views\api.py:326
msgid "Parameter updated_at incorrectly formatted"
msgstr ""
#: .\cookbook\views\api.py:351 .\cookbook\views\api.py:484
#, python-brace-format
msgid "No {self.basename} with id {pk} exists"
msgstr ""
#: .\cookbook\views\api.py:355
msgid "Cannot merge with the same object!"
msgstr ""
#: .\cookbook\views\api.py:362
#, python-brace-format
msgid "No {self.basename} with id {target} exists"
msgstr ""
#: .\cookbook\views\api.py:367
msgid "Cannot merge with child object!"
msgstr ""
#: .\cookbook\views\api.py:405
#, python-brace-format
msgid "{source.name} was merged successfully with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:411
#, python-brace-format
msgid "An error occurred attempting to merge {source.name} with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:493
#, python-brace-format
msgid "{child.name} was moved successfully to the root."
msgstr ""
#: .\cookbook\views\api.py:496 .\cookbook\views\api.py:514
msgid "An error occurred attempting to move "
msgstr ""
#: .\cookbook\views\api.py:499
msgid "Cannot move an object to itself!"
msgstr ""
#: .\cookbook\views\api.py:505
#, python-brace-format
msgid "No {self.basename} with id {parent} exists"
msgstr ""
#: .\cookbook\views\api.py:511
#, python-brace-format
msgid "{child.name} was moved successfully to parent {parent.name}"
msgstr ""
#: .\cookbook\views\api.py:992
#, python-brace-format
msgid "{obj.name} was removed from the shopping list."
msgstr ""
#: .\cookbook\views\api.py:997 .\cookbook\views\api.py:1627
#, python-brace-format
msgid "{obj.name} was added to the shopping list."
msgstr ""
#: .\cookbook\views\api.py:1239
msgid "Filter meal plans from date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1241
msgid "Filter meal plans to date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1244
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1404
msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1406
msgid "Query string matched (fuzzy) against object name."
msgstr ""
#: .\cookbook\views\api.py:1442
msgid ""
"Query string matched (fuzzy) against recipe name. In the future also "
"fulltext search."
msgstr ""
#: .\cookbook\views\api.py:1444
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr ""
#: .\cookbook\views\api.py:1445
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr ""
#: .\cookbook\views\api.py:1446
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1447
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1448
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1450
msgid "ID of food a recipe should have. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1451
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr ""
#: .\cookbook\views\api.py:1452
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1453
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr ""
#: .\cookbook\views\api.py:1454
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1456
msgid "ID of book a recipe should be in. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1457
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr ""
#: .\cookbook\views\api.py:1458
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1459
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr ""
#: .\cookbook\views\api.py:1460
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1462
msgid "ID of unit a recipe should have."
msgstr ""
#: .\cookbook\views\api.py:1464
msgid "Exact rating of recipe"
msgstr ""
#: .\cookbook\views\api.py:1465
msgid "Rating a recipe should have or greater."
msgstr ""
#: .\cookbook\views\api.py:1466
msgid "Rating a recipe should have or smaller."
msgstr ""
#: .\cookbook\views\api.py:1468
msgid "Filter recipes cooked X times."
msgstr ""
#: .\cookbook\views\api.py:1469
msgid "Filter recipes cooked X times or more."
msgstr ""
#: .\cookbook\views\api.py:1470
msgid "Filter recipes cooked X times or less."
msgstr ""
#: .\cookbook\views\api.py:1472
msgid "Filter recipes created on the given date."
msgstr ""
#: .\cookbook\views\api.py:1473
msgid "Filter recipes created on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1474
msgid "Filter recipes created on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1476 .\cookbook\views\api.py:1477
#: .\cookbook\views\api.py:1478
msgid "Filter recipes updated on the given date."
msgstr ""
#: .\cookbook\views\api.py:1480
msgid "Filter recipes last cooked on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1481
msgid "Filter recipes last cooked on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1483 .\cookbook\views\api.py:1484
msgid "Filter recipes lasts viewed on the given date."
msgstr ""
#: .\cookbook\views\api.py:1486
msgid "Filter recipes for ones created by the given user ID"
msgstr ""
#: .\cookbook\views\api.py:1487
msgid "If only internal recipes should be returned. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1488
msgid "Returns the results in randomized order. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1490
msgid ""
"Determines the order of the results. Options are: score,-score,name,-name,"
"lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-"
"created_at,lastviewed,-lastviewed"
msgstr ""
#: .\cookbook\views\api.py:1492
msgid "Returns new results first in search results. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1493
msgid ""
"Returns the given number of recently viewed recipes before search results "
"(if given)"
msgstr ""
#: .\cookbook\views\api.py:1494
msgid "ID of a custom filter. Returns all recipes matched by that filter."
msgstr ""
#: .\cookbook\views\api.py:1495
msgid "Filter recipes that can be made with OnHand food. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1773
msgid ""
"Return the PropertyTypes matching the property category. Repeat for "
"multiple."
msgstr ""
#: .\cookbook\views\api.py:1804 .\cookbook\views\api.py:1860
msgid "Returns only entries associated with the given mealplan id"
msgstr ""
#: .\cookbook\views\api.py:1858
msgid ""
"Returns only elements updated after the given timestamp in ISO 8601 format."
msgstr ""
#: .\cookbook\views\api.py:2031
msgid ""
"Return the Automations matching the automation type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2048
msgid ""
"Text field to store data that gets carried over to the UserSpace created "
"from the InviteLink"
msgstr ""
#: .\cookbook\views\api.py:2049
msgid "Only return InviteLinks that have not been used yet."
msgstr ""
#: .\cookbook\views\api.py:2076
msgid "Return the CustomFilters matching the model type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2176
msgid "Nothing to do."
msgstr ""
#: .\cookbook\views\api.py:2222
msgid "Invalid Url"
msgstr ""
#: .\cookbook\views\api.py:2228
msgid "Connection Refused."
msgstr ""
#: .\cookbook\views\api.py:2232
msgid "Bad URL Schema."
msgstr ""
#: .\cookbook\views\api.py:2257
msgid "No usable data could be found."
msgstr ""
#: .\cookbook\views\api.py:2286 .\cookbook\views\api.py:2434
msgid "You must select an AI provider to perform your request."
msgstr ""
#: .\cookbook\views\api.py:2293 .\cookbook\views\api.py:2441
msgid ""
"You don't have any credits remaining to use AI or AI features are not "
"enabled for your space."
msgstr ""
#: .\cookbook\views\api.py:2499 .\cookbook\views\api.py:2667
msgid "File is above space limit"
msgstr ""
#: .\cookbook\views\api.py:2522 .\cookbook\views\api.py:2684
msgid "Importing is not implemented for this provider"
msgstr ""
#: .\cookbook\views\api.py:2548
msgid ""
"The PDF Exporter is not enabled on this instance as it is still in an "
"experimental state."
msgstr ""
#: .\cookbook\views\api.py:2842
msgid "This feature is not yet available in the hosted version of tandoor!"
msgstr ""
#: .\cookbook\views\api.py:2863
msgid "Sync successful!"
msgstr ""
#: .\cookbook\views\api.py:2866
msgid "Error synchronizing with Storage"
msgstr ""
#: .\cookbook\views\views.py:89
msgid "This feature is not available in the demo version!"
msgstr ""
#: .\cookbook\views\views.py:110
msgid ""
"You have successfully created your own recipe space. Start by adding some "
"recipes or invite other people to join you."
msgstr ""
#: .\cookbook\views\views.py:171
#, python-format
msgid "PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!"
msgstr ""
#: .\cookbook\views\views.py:174
#, python-format
msgid "You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended"
msgstr ""
#: .\cookbook\views\views.py:178
msgid "Unable to determine PostgreSQL version."
msgstr ""
#: .\cookbook\views\views.py:182
msgid ""
"This application is not running with a Postgres database backend. This is ok "
"but not recommended as some features only work with postgres databases."
msgstr ""
#: .\cookbook\views\views.py:296
msgid ""
"The setup page can only be used to create the first "
"user! If you have forgotten your superuser credentials "
"please consult the django documentation on how to reset passwords."
msgstr ""
#: .\cookbook\views\views.py:304
msgid "Passwords dont match!"
msgstr ""
#: .\cookbook\views\views.py:312
msgid "User has been created, please login!"
msgstr ""
#: .\cookbook\views\views.py:352
msgid ""
"Reporting share links is not enabled for this instance. Please notify the "
"page administrator to report problems."
msgstr ""
#: .\cookbook\views\views.py:357
msgid ""
"Recipe sharing link has been disabled! For additional information please "
"contact the page administrator."
msgstr ""
#: .\cookbook\views\views.py:383
msgid "Manage recipes, shopping list, meal plans and more."
msgstr ""
#: .\cookbook\views\views.py:397 .\cookbook\views\views.py:398
msgid "Plan"
msgstr ""
#: .\cookbook\views\views.py:399
msgid "View your meal Plan"
msgstr ""
#: .\cookbook\views\views.py:418
msgid "View your shopping lists"
msgstr "Alışveriş listelerinizi görüntüleyin"
#~ msgid ""
#~ "Both fields are optional. If none are given the username will be "
#~ "displayed instead"
#~ msgstr ""
#~ "Her iki değer de tercihe bağlıdır. Hiç birisi verilmezse yerlerine "
#~ "kullanıcı adı gösterilecektir"
#~ msgid "Name"
#~ msgstr "İsim"
#~ msgid "Keywords"
#~ msgstr "Anahtar kelimeler"
#~ msgid "Preparation time in minutes"
#~ msgstr "Hazırlık süresi dakika cinsinden"
#~ msgid "Waiting time (cooking/baking) in minutes"
#~ msgstr "Bekleme süresi (pişirme/fırınlama) dakika cinsinden"
#~ msgid "Path"
#~ msgstr "Adres"
#~ msgid "Storage UID"
#~ msgstr "Saklama UID (biricik tanımlayıcı)"
#~ msgid "Add your comment: "
#~ msgstr "Yorum ekleyin: "
#~ msgid "Leave empty for dropbox and enter app password for nextcloud."
#~ msgstr ""
#~ "Dropbox için boş bırakın ve Nextcloud için uygulama şifresini girin."
#~ msgid "Leave empty for nextcloud and enter api token for dropbox."
#~ msgstr "Nextcloud için boş bırakın ve Dropbox için API anahtarını girin."
#~ msgid ""
#~ "Leave empty for dropbox and enter only base url for nextcloud (/"
#~ "remote.php/webdav/ is added automatically)"
#~ msgstr ""
#~ "Dropbox için boş bırakın ve Nextcloud için yalnızca ana URL'yi "
#~ "girin(/remote.php/webdav/ otomatik olarak eklenir)"
#~ msgid ""
#~ "Long Lived Access Token for your HomeAssistant instance"
#~ msgstr ""
#~ "HomeAssistant uygulamanız için Uzun Süreli Erişim Anahtarı"
#~ "a>"
#~ msgid "Something like http://homeassistant.local:8123/api"
#~ msgstr "Örneğin http://homeassistant.local:8123/api"
#~ msgid "http://homeassistant.local:8123/api for example"
#~ msgstr "http://homeassistant.local:8123/api örneğin"
#~ msgid "Storage"
#~ msgstr "Depolama"
#~ msgid "Active"
#~ msgstr "Aktif"
#~ msgid "Search String"
#~ msgstr "Arama Sorgusu"
#~ msgid "File ID"
#~ msgstr "Dosya ID"
#~ msgid ""
#~ "Determines how fuzzy a search is if it uses trigram similarity matching "
#~ "(e.g. low values mean more typos are ignored)."
#~ msgstr ""
#~ "Trigram benzerlik eşleşmesi kullanılması halinde aramanın ne kadar "
#~ "bulanık olduğunu belirler (ör. düşük değerler daha fazla yazım hatasını "
#~ "gözardı eder)."
#~ msgid ""
#~ "Select type method of search. Click here "
#~ "for full description of choices."
#~ msgstr ""
#~ "Arama tipi metodunu seçin. Seçeneklerin tam açıklamasını görmek için buraya tıklayın."
#~ msgid ""
#~ "Use fuzzy matching on units, keywords and ingredients when editing and "
#~ "importing recipes."
#~ msgstr ""
#~ "Tarifleri düzenlerken ve içeri aktarırken birimler, anahtar kelimeler ve "
#~ "malzemelerde bulanık eşleştirme kullan."
#~ msgid "Search Method"
#~ msgstr "Arama Metodu"
#~ msgid "Ignore Accent"
#~ msgstr "Harflerdeki Vurguları Görmezden Gel"
#~ msgid "Partial Match"
#~ msgstr "Kısmi Eşleşme"
#~ msgid "Starts With"
#~ msgstr "İle başlayan"
#~ msgid "Full Text"
#~ msgstr "Tam Metin"
#~ msgid "Ingredient Editor"
#~ msgstr "Malzeme Editörü"
#~ msgid "Property Editor"
#~ msgstr "Özellik Editörü"
#~ msgid "Comments"
#~ msgstr "Yorumlar"
#~ msgid "Default unit"
#~ msgstr "Varsayılan birim"
#~ msgid "Use KJ"
#~ msgstr "KiloJoule kullan"
#~ msgid "Theme"
#~ msgstr "Tema"
#~ msgid "Navbar color"
#~ msgstr "Gezinti çubuğu rengi"
#~ msgid "Sticky navbar"
#~ msgstr "Yapışkan gezinti çubuğu"
#~ msgid "Default page"
#~ msgstr "Varsayılan sayfa"
#~ msgid "Plan sharing"
#~ msgstr "Plan paylaşımı"
#~ msgid "Ingredient decimal places"
#~ msgstr "Malzeme ondalık virgül yeri"
#~ msgid "Shopping list auto sync period"
#~ msgstr "Alışveriş listesinin otomatik eşleşme sıklığı"
#~ msgid "Left-handed mode"
#~ msgstr "Solaklar için"
#~ msgid ""
#~ "Color of the top navigation bar. Not all colors work with all themes, "
#~ "just try them out!"
#~ msgstr ""
#~ "Gezinti çubuğunun rengi. Bütün renkeler bütün temalarla çalışmayabilir, "
#~ "önce deneyin!"
#~ msgid ""
#~ "Default Unit to be used when inserting a new ingredient into a recipe."
#~ msgstr ""
#~ "Bir tarife yeni bir malzeme eklenirken kullanılacak Varsayılan Birim."
#~ msgid ""
#~ "Enables support for fractions in ingredient amounts (e.g. convert "
#~ "decimals to fractions automatically)"
#~ msgstr ""
#~ "Malzeme miktarı için kesir desteğini etkinleştir (örn. ondalıkları kesire "
#~ "otomatik çevir)"
#~ msgid "Display nutritional energy amounts in joules instead of calories"
#~ msgstr "Besin değerlerini kalori yerine jul olarak görüntüle"
#~ msgid "Number of decimals to round ingredients."
#~ msgstr "Malzeme birimleri için yuvarlanma basamağı."
#~ msgid ""
#~ "If you want to be able to create and see comments underneath recipes."
#~ msgstr "Tariflerin altında yorumlar oluşturup görebilmek istiyorsanız."
#~ msgid ""
#~ "Setting to 0 will disable auto sync. When viewing a shopping list the "
#~ "list is updated every set seconds to sync changes someone else might have "
#~ "made. Useful when shopping with multiple people but might use a little "
#~ "bit of mobile data. If lower than instance limit it is reset when saving."
#~ msgstr ""
#~ "0 olarak ayarlamak, otomatik senkronizasyonu devre dışı bırakır. Bir "
#~ "alışveriş listesini görüntülerken liste, başka birinin yapmış olabileceği "
#~ "değişiklikleri senkronize etmek için her saniyede bir güncellenir. Birden "
#~ "fazla kişiyle alışveriş yaparken kullanışlıdır, ancak biraz mobil veri "
#~ "kullanabilir. Örnek sınırından düşükse, kaydederken sıfırlanır."
#~ msgid "Automatically add meal plan ingredients to shopping list."
#~ msgstr ""
#~ "Otomatik olarak yemek planındaki malzemeleri alışveriş listesine ekle."
#~ msgid "Exclude ingredients that are on hand."
#~ msgstr "Var olan malzemeleri hariç tut."
#~ msgid "Show recipe counts on search filters"
#~ msgstr "süzülen tarifleri arama sayfasında göster."
#~ msgid "Ingredients"
#~ msgstr "Malzemeler"
#, fuzzy
#~| msgid "Show recently viewed recipes on search page."
#~ msgid "Show recent recipes"
#~ msgstr "Son görüntülenen tarifleri arama sayfasında göster."
#~ msgid "Show recently viewed recipes on search page."
#~ msgstr "Son görüntülenen tarifleri arama sayfasında göster."
================================================
FILE: cookbook/locale/tr/id/LC_MESSAGES/django.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-07-12 19:20+0200\n"
"PO-Revision-Date: 2022-10-01 16:38+0000\n"
"Last-Translator: wella \n"
"Language-Team: Indonesian \n"
"Language: id\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 4.10.1\n"
#: .\cookbook\filters.py:23 .\cookbook\templates\forms\ingredients.html:34
#: .\cookbook\templates\stats.html:28
msgid "Ingredients"
msgstr "bahan-bahan"
#: .\cookbook\forms.py:53
msgid "Default unit"
msgstr "Unit bawaan"
#: .\cookbook\forms.py:54
msgid "Use fractions"
msgstr "Gunakan pecahan"
#: .\cookbook\forms.py:55
msgid "Use KJ"
msgstr "Gunakan KJ"
#: .\cookbook\forms.py:56
msgid "Theme"
msgstr "Tema"
#: .\cookbook\forms.py:57
msgid "Navbar color"
msgstr "Warna Navigasi"
#: .\cookbook\forms.py:58
msgid "Sticky navbar"
msgstr "Sticky navbar"
#: .\cookbook\forms.py:59
msgid "Default page"
msgstr "Halaman default"
#: .\cookbook\forms.py:60
msgid "Show recent recipes"
msgstr "Tampilkan resep terbaru"
#: .\cookbook\forms.py:61
msgid "Search style"
msgstr "Cari style"
#: .\cookbook\forms.py:62
msgid "Plan sharing"
msgstr "Berbagi rencana"
#: .\cookbook\forms.py:63
msgid "Ingredient decimal places"
msgstr "Tempat desimal bahan"
#: .\cookbook\forms.py:64
msgid "Shopping list auto sync period"
msgstr "Periode sinkronisasi otomatis daftar belanja"
#: .\cookbook\forms.py:65 .\cookbook\templates\recipe_view.html:21
#: .\cookbook\templates\stats.html:47
msgid "Comments"
msgstr "Komen"
#: .\cookbook\forms.py:66
msgid "Left-handed mode"
msgstr "Mode tangan kiri"
#: .\cookbook\forms.py:70
msgid ""
"Color of the top navigation bar. Not all colors work with all themes, just "
"try them out!"
msgstr ""
"Warna bilah navigasi atas. Tidak semua warna bekerja dengan semua tema, coba "
"saja!"
#: .\cookbook\forms.py:72
msgid "Default Unit to be used when inserting a new ingredient into a recipe."
msgstr ""
"Default Unit yang akan digunakan saat memasukkan bahan baru ke dalam resep."
#: .\cookbook\forms.py:74
msgid ""
"Enables support for fractions in ingredient amounts (e.g. convert decimals "
"to fractions automatically)"
msgstr ""
"Mengaktifkan dukungan untuk pecahan dalam jumlah bahan (misalnya, mengubah "
"desimal menjadi pecahan secara otomatis)"
#: .\cookbook\forms.py:76
msgid "Display nutritional energy amounts in joules instead of calories"
msgstr "Tampilkan jumlah energi nutrisi dalam joule, bukan kalori"
#: .\cookbook\forms.py:77
msgid "Users with whom newly created meal plans should be shared by default."
msgstr ""
"Pengguna dengan siapa rencana makan yang baru dibuat harus dibagikan secara "
"default."
#: .\cookbook\forms.py:78
msgid "Users with whom to share shopping lists."
msgstr "Pengguna yang ingin berbagi daftar belanja."
#: .\cookbook\forms.py:80
msgid "Show recently viewed recipes on search page."
msgstr "Tampilkan resep yang baru dilihat di halaman pencarian."
#: .\cookbook\forms.py:81
msgid "Number of decimals to round ingredients."
msgstr "Jumlah desimal untuk bahan."
#: .\cookbook\forms.py:82
msgid "If you want to be able to create and see comments underneath recipes."
msgstr "Jika Anda ingin dapat membuat dan melihat komentar di bawah resep."
#: .\cookbook\forms.py:84 .\cookbook\forms.py:496
msgid ""
"Setting to 0 will disable auto sync. When viewing a shopping list the list "
"is updated every set seconds to sync changes someone else might have made. "
"Useful when shopping with multiple people but might use a little bit of "
"mobile data. If lower than instance limit it is reset when saving."
msgstr ""
"Menyetel ke 0 akan menonaktifkan sinkronisasi otomatis. Saat melihat daftar "
"belanja, daftar diperbarui setiap detik untuk menyinkronkan perubahan yang "
"mungkin dibuat orang lain. Berguna saat berbelanja dengan banyak orang "
"tetapi mungkin menggunakan sedikit data seluler. Jika lebih rendah dari "
"batas instance, reset saat menyimpan."
#: .\cookbook\forms.py:87
msgid "Makes the navbar stick to the top of the page."
msgstr "Membuat navbar menempel di bagian atas halaman."
#: .\cookbook\forms.py:88 .\cookbook\forms.py:499
msgid "Automatically add meal plan ingredients to shopping list."
msgstr "Secara otomatis menambahkan bahan rencana makan ke daftar belanja."
#: .\cookbook\forms.py:89
msgid "Exclude ingredients that are on hand."
msgstr "Kecualikan bahan-bahan yang ada."
#: .\cookbook\forms.py:90
msgid "Will optimize the UI for use with your left hand."
msgstr ""
#: .\cookbook\forms.py:107
msgid ""
"Both fields are optional. If none are given the username will be displayed "
"instead"
msgstr ""
#: .\cookbook\forms.py:128 .\cookbook\forms.py:301
msgid "Name"
msgstr ""
#: .\cookbook\forms.py:129 .\cookbook\forms.py:302
#: .\cookbook\templates\stats.html:24 .\cookbook\views\lists.py:88
msgid "Keywords"
msgstr ""
#: .\cookbook\forms.py:130
msgid "Preparation time in minutes"
msgstr ""
#: .\cookbook\forms.py:131
msgid "Waiting time (cooking/baking) in minutes"
msgstr ""
#: .\cookbook\forms.py:132 .\cookbook\forms.py:270 .\cookbook\forms.py:303
msgid "Path"
msgstr ""
#: .\cookbook\forms.py:133
msgid "Storage UID"
msgstr ""
#: .\cookbook\forms.py:165
msgid "Default"
msgstr ""
#: .\cookbook\forms.py:177
msgid ""
"To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything."
msgstr ""
#: .\cookbook\forms.py:200
msgid "Add your comment: "
msgstr ""
#: .\cookbook\forms.py:215
msgid "Leave empty for dropbox and enter app password for nextcloud."
msgstr ""
#: .\cookbook\forms.py:222
msgid "Leave empty for nextcloud and enter api token for dropbox."
msgstr ""
#: .\cookbook\forms.py:231
msgid ""
"Leave empty for dropbox and enter only base url for nextcloud (/remote."
"php/webdav/ is added automatically)"
msgstr ""
#: .\cookbook\forms.py:269 .\cookbook\views\edit.py:157
msgid "Storage"
msgstr ""
#: .\cookbook\forms.py:271
msgid "Active"
msgstr ""
#: .\cookbook\forms.py:277
msgid "Search String"
msgstr ""
#: .\cookbook\forms.py:304
msgid "File ID"
msgstr ""
#: .\cookbook\forms.py:326
msgid "You must provide at least a recipe or a title."
msgstr ""
#: .\cookbook\forms.py:339
msgid "You can list default users to share recipes with in the settings."
msgstr ""
#: .\cookbook\forms.py:340
msgid ""
"You can use markdown to format this field. See the docs here"
msgstr ""
#: .\cookbook\forms.py:366
msgid "Maximum number of users for this space reached."
msgstr ""
#: .\cookbook\forms.py:372
msgid "Email address already taken!"
msgstr ""
#: .\cookbook\forms.py:380
msgid ""
"An email address is not required but if present the invite link will be sent "
"to the user."
msgstr ""
#: .\cookbook\forms.py:395
msgid "Name already taken."
msgstr ""
#: .\cookbook\forms.py:406
msgid "Accept Terms and Privacy"
msgstr ""
#: .\cookbook\forms.py:438
msgid ""
"Determines how fuzzy a search is if it uses trigram similarity matching (e."
"g. low values mean more typos are ignored)."
msgstr ""
#: .\cookbook\forms.py:448
msgid ""
"Select type method of search. Click here for "
"full description of choices."
msgstr ""
#: .\cookbook\forms.py:449
msgid ""
"Use fuzzy matching on units, keywords and ingredients when editing and "
"importing recipes."
msgstr ""
#: .\cookbook\forms.py:451
msgid ""
"Fields to search ignoring accents. Selecting this option can improve or "
"degrade search quality depending on language"
msgstr ""
#: .\cookbook\forms.py:453
msgid ""
"Fields to search for partial matches. (e.g. searching for 'Pie' will return "
"'pie' and 'piece' and 'soapie')"
msgstr ""
#: .\cookbook\forms.py:455
msgid ""
"Fields to search for beginning of word matches. (e.g. searching for 'sa' "
"will return 'salad' and 'sandwich')"
msgstr ""
#: .\cookbook\forms.py:457
msgid ""
"Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) "
"Note: this option will conflict with 'web' and 'raw' methods of search."
msgstr ""
#: .\cookbook\forms.py:459
msgid ""
"Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods "
"only function with fulltext fields."
msgstr ""
#: .\cookbook\forms.py:463
msgid "Search Method"
msgstr ""
#: .\cookbook\forms.py:464
msgid "Fuzzy Lookups"
msgstr ""
#: .\cookbook\forms.py:465
msgid "Ignore Accent"
msgstr ""
#: .\cookbook\forms.py:466
msgid "Partial Match"
msgstr ""
#: .\cookbook\forms.py:467
msgid "Starts With"
msgstr ""
#: .\cookbook\forms.py:468
msgid "Fuzzy Search"
msgstr ""
#: .\cookbook\forms.py:469
msgid "Full Text"
msgstr ""
#: .\cookbook\forms.py:494
msgid ""
"Users will see all items you add to your shopping list. They must add you "
"to see items on their list."
msgstr ""
#: .\cookbook\forms.py:500
msgid ""
"When adding a meal plan to the shopping list (manually or automatically), "
"include all related recipes."
msgstr ""
#: .\cookbook\forms.py:501
msgid ""
"When adding a meal plan to the shopping list (manually or automatically), "
"exclude ingredients that are on hand."
msgstr ""
#: .\cookbook\forms.py:502
msgid "Default number of hours to delay a shopping list entry."
msgstr ""
#: .\cookbook\forms.py:503
msgid "Filter shopping list to only include supermarket categories."
msgstr ""
#: .\cookbook\forms.py:504
msgid "Days of recent shopping list entries to display."
msgstr ""
#: .\cookbook\forms.py:505
msgid "Mark food 'On Hand' when checked off shopping list."
msgstr ""
#: .\cookbook\forms.py:506
msgid "Delimiter to use for CSV exports."
msgstr ""
#: .\cookbook\forms.py:507
msgid "Prefix to add when copying list to the clipboard."
msgstr ""
#: .\cookbook\forms.py:511
msgid "Share Shopping List"
msgstr ""
#: .\cookbook\forms.py:512
msgid "Autosync"
msgstr ""
#: .\cookbook\forms.py:513
msgid "Auto Add Meal Plan"
msgstr ""
#: .\cookbook\forms.py:514
msgid "Exclude On Hand"
msgstr ""
#: .\cookbook\forms.py:515
msgid "Include Related"
msgstr ""
#: .\cookbook\forms.py:516
msgid "Default Delay Hours"
msgstr ""
#: .\cookbook\forms.py:517
msgid "Filter to Supermarket"
msgstr ""
#: .\cookbook\forms.py:518
msgid "Recent Days"
msgstr ""
#: .\cookbook\forms.py:519
msgid "CSV Delimiter"
msgstr ""
#: .\cookbook\forms.py:520
msgid "List Prefix"
msgstr ""
#: .\cookbook\forms.py:521
msgid "Auto On Hand"
msgstr ""
#: .\cookbook\forms.py:531
msgid "Reset Food Inheritance"
msgstr ""
#: .\cookbook\forms.py:532
msgid "Reset all food to inherit the fields configured."
msgstr ""
#: .\cookbook\forms.py:544
msgid "Fields on food that should be inherited by default."
msgstr ""
#: .\cookbook\forms.py:545
msgid "Show recipe counts on search filters"
msgstr ""
#: .\cookbook\helper\AllAuthCustomAdapter.py:36
msgid ""
"In order to prevent spam, the requested email was not send. Please wait a "
"few minutes and try again."
msgstr ""
#: .\cookbook\helper\permission_helper.py:149
#: .\cookbook\helper\permission_helper.py:172 .\cookbook\views\views.py:152
msgid "You are not logged in and therefore cannot view this page!"
msgstr ""
#: .\cookbook\helper\permission_helper.py:153
#: .\cookbook\helper\permission_helper.py:159
#: .\cookbook\helper\permission_helper.py:184
#: .\cookbook\helper\permission_helper.py:254
#: .\cookbook\helper\permission_helper.py:268
#: .\cookbook\helper\permission_helper.py:279
#: .\cookbook\helper\permission_helper.py:290 .\cookbook\views\data.py:33
#: .\cookbook\views\views.py:163 .\cookbook\views\views.py:170
#: .\cookbook\views\views.py:249
msgid "You do not have the required permissions to view this page!"
msgstr ""
#: .\cookbook\helper\permission_helper.py:177
#: .\cookbook\helper\permission_helper.py:200
#: .\cookbook\helper\permission_helper.py:222
#: .\cookbook\helper\permission_helper.py:237
msgid "You cannot interact with this object as it is not owned by you!"
msgstr ""
#: .\cookbook\helper\permission_helper.py:321
msgid "You have reached the maximum number of recipes for your space."
msgstr ""
#: .\cookbook\helper\permission_helper.py:333
msgid "You have more users than allowed in your space."
msgstr ""
#: .\cookbook\helper\recipe_search.py:565
msgid "One of queryset or hash_key must be provided"
msgstr ""
#: .\cookbook\helper\shopping_helper.py:152
msgid "You must supply a servings size"
msgstr ""
#: .\cookbook\helper\template_helper.py:64
#: .\cookbook\helper\template_helper.py:66
msgid "Could not parse template code."
msgstr ""
#: .\cookbook\integration\copymethat.py:41
#: .\cookbook\integration\melarecipes.py:37
msgid "Favorite"
msgstr ""
#: .\cookbook\integration\copymethat.py:70
#: .\cookbook\integration\recettetek.py:54
#: .\cookbook\integration\recipekeeper.py:63
msgid "Imported from"
msgstr ""
#: .\cookbook\integration\integration.py:223
msgid ""
"Importer expected a .zip file. Did you choose the correct importer type for "
"your data ?"
msgstr ""
#: .\cookbook\integration\integration.py:226
msgid ""
"An unexpected error occurred during the import. Please make sure you have "
"uploaded a valid file."
msgstr ""
#: .\cookbook\integration\integration.py:231
msgid "The following recipes were ignored because they already existed:"
msgstr ""
#: .\cookbook\integration\integration.py:235
#, python-format
msgid "Imported %s recipes."
msgstr ""
#: .\cookbook\integration\paprika.py:46
msgid "Notes"
msgstr ""
#: .\cookbook\integration\paprika.py:49
msgid "Nutritional Information"
msgstr ""
#: .\cookbook\integration\paprika.py:53
msgid "Source"
msgstr ""
#: .\cookbook\integration\saffron.py:23
msgid "Servings"
msgstr ""
#: .\cookbook\integration\saffron.py:25
msgid "Waiting time"
msgstr ""
#: .\cookbook\integration\saffron.py:27
msgid "Preparation Time"
msgstr ""
#: .\cookbook\integration\saffron.py:29
#: .\cookbook\templates\forms\ingredients.html:7
#: .\cookbook\templates\index.html:7
msgid "Cookbook"
msgstr ""
#: .\cookbook\integration\saffron.py:31
msgid "Section"
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:14
msgid "Rebuilds full text search index on Recipe"
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:18
msgid "Only Postgresql databases use full text search, no index to rebuild"
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:29
msgid "Recipe index rebuild complete."
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:31
msgid "Recipe index rebuild failed."
msgstr ""
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
msgid "Breakfast"
msgstr ""
#: .\cookbook\migrations\0047_auto_20200602_1133.py:19
msgid "Lunch"
msgstr ""
#: .\cookbook\migrations\0047_auto_20200602_1133.py:24
msgid "Dinner"
msgstr ""
#: .\cookbook\migrations\0047_auto_20200602_1133.py:29
msgid "Other"
msgstr ""
#: .\cookbook\models.py:251
msgid ""
"Maximum file storage for space in MB. 0 for unlimited, -1 to disable file "
"upload."
msgstr ""
#: .\cookbook\models.py:353 .\cookbook\templates\search.html:7
#: .\cookbook\templates\space_manage.html:7
msgid "Search"
msgstr ""
#: .\cookbook\models.py:354 .\cookbook\templates\base.html:107
#: .\cookbook\templates\meal_plan.html:7 .\cookbook\views\delete.py:178
#: .\cookbook\views\edit.py:211 .\cookbook\views\new.py:179
msgid "Meal-Plan"
msgstr ""
#: .\cookbook\models.py:355 .\cookbook\templates\base.html:115
msgid "Books"
msgstr ""
#: .\cookbook\models.py:363
msgid "Small"
msgstr ""
#: .\cookbook\models.py:363
msgid "Large"
msgstr ""
#: .\cookbook\models.py:363 .\cookbook\templates\generic\new_template.html:6
#: .\cookbook\templates\generic\new_template.html:14
msgid "New"
msgstr ""
#: .\cookbook\models.py:584
msgid " is part of a recipe step and cannot be deleted"
msgstr ""
#: .\cookbook\models.py:1162 .\cookbook\templates\search_info.html:28
msgid "Simple"
msgstr ""
#: .\cookbook\models.py:1163 .\cookbook\templates\search_info.html:33
msgid "Phrase"
msgstr ""
#: .\cookbook\models.py:1164 .\cookbook\templates\search_info.html:38
msgid "Web"
msgstr ""
#: .\cookbook\models.py:1165 .\cookbook\templates\search_info.html:47
msgid "Raw"
msgstr ""
#: .\cookbook\models.py:1203
msgid "Food Alias"
msgstr ""
#: .\cookbook\models.py:1203
msgid "Unit Alias"
msgstr ""
#: .\cookbook\models.py:1203
msgid "Keyword Alias"
msgstr ""
#: .\cookbook\models.py:1227
#: .\cookbook\templates\include\recipe_open_modal.html:7
#: .\cookbook\views\delete.py:36 .\cookbook\views\edit.py:251
#: .\cookbook\views\new.py:48
msgid "Recipe"
msgstr ""
#: .\cookbook\models.py:1228
msgid "Food"
msgstr ""
#: .\cookbook\models.py:1229 .\cookbook\templates\base.html:138
msgid "Keyword"
msgstr ""
#: .\cookbook\serializer.py:207
msgid "Cannot modify Space owner permission."
msgstr ""
#: .\cookbook\serializer.py:290
msgid "File uploads are not enabled for this Space."
msgstr ""
#: .\cookbook\serializer.py:301
msgid "You have reached your file upload limit."
msgstr ""
#: .\cookbook\serializer.py:1081
msgid "Hello"
msgstr ""
#: .\cookbook\serializer.py:1081
msgid "You have been invited by "
msgstr ""
#: .\cookbook\serializer.py:1082
msgid " to join their Tandoor Recipes space "
msgstr ""
#: .\cookbook\serializer.py:1083
msgid "Click the following link to activate your account: "
msgstr ""
#: .\cookbook\serializer.py:1084
msgid ""
"If the link does not work use the following code to manually join the space: "
msgstr ""
#: .\cookbook\serializer.py:1085
msgid "The invitation is valid until "
msgstr ""
#: .\cookbook\serializer.py:1086
msgid ""
"Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub "
msgstr ""
#: .\cookbook\serializer.py:1089
msgid "Tandoor Recipes Invite"
msgstr ""
#: .\cookbook\serializer.py:1209
msgid "Existing shopping list to update"
msgstr ""
#: .\cookbook\serializer.py:1211
msgid ""
"List of ingredient IDs from the recipe to add, if not provided all "
"ingredients will be added."
msgstr ""
#: .\cookbook\serializer.py:1213
msgid ""
"Providing a list_recipe ID and servings of 0 will delete that shopping list."
msgstr ""
#: .\cookbook\serializer.py:1222
msgid "Amount of food to add to the shopping list"
msgstr ""
#: .\cookbook\serializer.py:1224
msgid "ID of unit to use for the shopping list"
msgstr ""
#: .\cookbook\serializer.py:1226
msgid "When set to true will delete all food from active shopping lists."
msgstr ""
#: .\cookbook\tables.py:36 .\cookbook\templates\generic\edit_template.html:6
#: .\cookbook\templates\generic\edit_template.html:14
#: .\cookbook\templates\recipes_table.html:82
msgid "Edit"
msgstr ""
#: .\cookbook\tables.py:116 .\cookbook\tables.py:131
#: .\cookbook\templates\generic\delete_template.html:7
#: .\cookbook\templates\generic\delete_template.html:15
#: .\cookbook\templates\generic\edit_template.html:28
#: .\cookbook\templates\recipes_table.html:90
msgid "Delete"
msgstr ""
#: .\cookbook\templates\404.html:5
msgid "404 Error"
msgstr ""
#: .\cookbook\templates\404.html:18
msgid "The page you are looking for could not be found."
msgstr ""
#: .\cookbook\templates\404.html:33
msgid "Take me Home"
msgstr ""
#: .\cookbook\templates\404.html:35
msgid "Report a Bug"
msgstr ""
#: .\cookbook\templates\account\email.html:6
#: .\cookbook\templates\account\email.html:17
msgid "E-mail Addresses"
msgstr ""
#: .\cookbook\templates\account\email.html:12
#: .\cookbook\templates\account\password_change.html:11
#: .\cookbook\templates\account\password_set.html:11
#: .\cookbook\templates\base.html:293 .\cookbook\templates\settings.html:6
#: .\cookbook\templates\settings.html:17
#: .\cookbook\templates\socialaccount\connections.html:10
msgid "Settings"
msgstr ""
#: .\cookbook\templates\account\email.html:13
msgid "Email"
msgstr ""
#: .\cookbook\templates\account\email.html:19
msgid "The following e-mail addresses are associated with your account:"
msgstr ""
#: .\cookbook\templates\account\email.html:36
msgid "Verified"
msgstr ""
#: .\cookbook\templates\account\email.html:38
msgid "Unverified"
msgstr ""
#: .\cookbook\templates\account\email.html:40
msgid "Primary"
msgstr ""
#: .\cookbook\templates\account\email.html:47
msgid "Make Primary"
msgstr ""
#: .\cookbook\templates\account\email.html:49
msgid "Re-send Verification"
msgstr ""
#: .\cookbook\templates\account\email.html:50
#: .\cookbook\templates\generic\delete_template.html:57
#: .\cookbook\templates\socialaccount\connections.html:44
msgid "Remove"
msgstr ""
#: .\cookbook\templates\account\email.html:58
msgid "Warning:"
msgstr ""
#: .\cookbook\templates\account\email.html:58
msgid ""
"You currently do not have any e-mail address set up. You should really add "
"an e-mail address so you can receive notifications, reset your password, etc."
msgstr ""
#: .\cookbook\templates\account\email.html:64
msgid "Add E-mail Address"
msgstr ""
#: .\cookbook\templates\account\email.html:69
msgid "Add E-mail"
msgstr ""
#: .\cookbook\templates\account\email.html:79
msgid "Do you really want to remove the selected e-mail address?"
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:6
#: .\cookbook\templates\account\email_confirm.html:10
msgid "Confirm E-mail Address"
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:16
#, python-format
msgid ""
"Please confirm that\n"
" %(email)s is an e-mail address "
"for user %(user_display)s\n"
" ."
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:22
#: .\cookbook\templates\generic\delete_template.html:72
msgid "Confirm"
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:29
#, python-format
msgid ""
"This e-mail confirmation link expired or is invalid. Please\n"
" issue a new e-mail confirmation "
"request."
msgstr ""
#: .\cookbook\templates\account\login.html:8
#: .\cookbook\templates\base.html:340 .\cookbook\templates\openid\login.html:8
msgid "Login"
msgstr ""
#: .\cookbook\templates\account\login.html:15
#: .\cookbook\templates\account\login.html:31
#: .\cookbook\templates\account\signup.html:69
#: .\cookbook\templates\account\signup_closed.html:15
#: .\cookbook\templates\openid\login.html:15
#: .\cookbook\templates\openid\login.html:26
#: .\cookbook\templates\socialaccount\authentication_error.html:15
msgid "Sign In"
msgstr ""
#: .\cookbook\templates\account\login.html:34
#: .\cookbook\templates\socialaccount\signup.html:8
#: .\cookbook\templates\socialaccount\signup.html:57
msgid "Sign Up"
msgstr ""
#: .\cookbook\templates\account\login.html:39
#: .\cookbook\templates\account\login.html:41
#: .\cookbook\templates\account\password_reset.html:29
msgid "Reset My Password"
msgstr ""
#: .\cookbook\templates\account\login.html:40
msgid "Lost your password?"
msgstr ""
#: .\cookbook\templates\account\login.html:52
msgid "Social Login"
msgstr ""
#: .\cookbook\templates\account\login.html:53
msgid "You can use any of the following providers to sign in."
msgstr ""
#: .\cookbook\templates\account\logout.html:5
#: .\cookbook\templates\account\logout.html:9
#: .\cookbook\templates\account\logout.html:18
msgid "Sign Out"
msgstr ""
#: .\cookbook\templates\account\logout.html:11
msgid "Are you sure you want to sign out?"
msgstr ""
#: .\cookbook\templates\account\password_change.html:6
#: .\cookbook\templates\account\password_change.html:16
#: .\cookbook\templates\account\password_change.html:21
#: .\cookbook\templates\account\password_reset_from_key.html:7
#: .\cookbook\templates\account\password_reset_from_key.html:13
#: .\cookbook\templates\account\password_reset_from_key_done.html:7
#: .\cookbook\templates\account\password_reset_from_key_done.html:13
msgid "Change Password"
msgstr ""
#: .\cookbook\templates\account\password_change.html:12
#: .\cookbook\templates\account\password_set.html:12
#: .\cookbook\templates\settings.html:76
msgid "Password"
msgstr ""
#: .\cookbook\templates\account\password_change.html:22
msgid "Forgot Password?"
msgstr ""
#: .\cookbook\templates\account\password_reset.html:7
#: .\cookbook\templates\account\password_reset.html:13
#: .\cookbook\templates\account\password_reset_done.html:7
#: .\cookbook\templates\account\password_reset_done.html:10
msgid "Password Reset"
msgstr ""
#: .\cookbook\templates\account\password_reset.html:24
msgid ""
"Forgotten your password? Enter your e-mail address below, and we'll send you "
"an e-mail allowing you to reset it."
msgstr ""
#: .\cookbook\templates\account\password_reset.html:32
msgid "Password reset is disabled on this instance."
msgstr ""
#: .\cookbook\templates\account\password_reset_done.html:16
msgid ""
"We have sent you an e-mail. Please contact us if you do not receive it "
"within a few minutes."
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:13
msgid "Bad Token"
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:25
#, python-format
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used.\n"
" Please request a new "
"password reset."
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:33
msgid "change password"
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:36
#: .\cookbook\templates\account\password_reset_from_key_done.html:19
msgid "Your password is now changed."
msgstr ""
#: .\cookbook\templates\account\password_set.html:6
#: .\cookbook\templates\account\password_set.html:16
#: .\cookbook\templates\account\password_set.html:21
msgid "Set Password"
msgstr ""
#: .\cookbook\templates\account\signup.html:6
msgid "Register"
msgstr ""
#: .\cookbook\templates\account\signup.html:12
msgid "Create an Account"
msgstr ""
#: .\cookbook\templates\account\signup.html:42
#: .\cookbook\templates\socialaccount\signup.html:33
msgid "I accept the follwoing"
msgstr ""
#: .\cookbook\templates\account\signup.html:45
#: .\cookbook\templates\socialaccount\signup.html:36
msgid "Terms and Conditions"
msgstr ""
#: .\cookbook\templates\account\signup.html:48
#: .\cookbook\templates\socialaccount\signup.html:39
msgid "and"
msgstr ""
#: .\cookbook\templates\account\signup.html:52
#: .\cookbook\templates\socialaccount\signup.html:43
msgid "Privacy Policy"
msgstr ""
#: .\cookbook\templates\account\signup.html:65
msgid "Create User"
msgstr ""
#: .\cookbook\templates\account\signup.html:69
msgid "Already have an account?"
msgstr ""
#: .\cookbook\templates\account\signup_closed.html:5
#: .\cookbook\templates\account\signup_closed.html:11
msgid "Sign Up Closed"
msgstr ""
#: .\cookbook\templates\account\signup_closed.html:13
msgid "We are sorry, but the sign up is currently closed."
msgstr ""
#: .\cookbook\templates\api_info.html:5 .\cookbook\templates\base.html:330
#: .\cookbook\templates\rest_framework\api.html:11
msgid "API Documentation"
msgstr ""
#: .\cookbook\templates\base.html:103 .\cookbook\templates\index.html:87
#: .\cookbook\templates\stats.html:22
msgid "Recipes"
msgstr ""
#: .\cookbook\templates\base.html:111
msgid "Shopping"
msgstr ""
#: .\cookbook\templates\base.html:150 .\cookbook\views\lists.py:105
msgid "Foods"
msgstr ""
#: .\cookbook\templates\base.html:162
#: .\cookbook\templates\forms\ingredients.html:24
#: .\cookbook\templates\stats.html:26 .\cookbook\views\lists.py:122
msgid "Units"
msgstr ""
#: .\cookbook\templates\base.html:176 .\cookbook\templates\supermarket.html:7
msgid "Supermarket"
msgstr ""
#: .\cookbook\templates\base.html:188
msgid "Supermarket Category"
msgstr ""
#: .\cookbook\templates\base.html:200 .\cookbook\views\lists.py:171
msgid "Automations"
msgstr ""
#: .\cookbook\templates\base.html:214 .\cookbook\views\lists.py:207
msgid "Files"
msgstr ""
#: .\cookbook\templates\base.html:226
msgid "Batch Edit"
msgstr ""
#: .\cookbook\templates\base.html:238 .\cookbook\templates\history.html:6
#: .\cookbook\templates\history.html:14
msgid "History"
msgstr ""
#: .\cookbook\templates\base.html:252
#: .\cookbook\templates\ingredient_editor.html:7
#: .\cookbook\templates\ingredient_editor.html:13
msgid "Ingredient Editor"
msgstr ""
#: .\cookbook\templates\base.html:264
#: .\cookbook\templates\export_response.html:7
#: .\cookbook\templates\test2.html:14 .\cookbook\templates\test2.html:20
msgid "Export"
msgstr ""
#: .\cookbook\templates\base.html:280 .\cookbook\templates\index.html:47
msgid "Import Recipe"
msgstr ""
#: .\cookbook\templates\base.html:282
msgid "Create"
msgstr ""
#: .\cookbook\templates\base.html:295
#: .\cookbook\templates\generic\list_template.html:14
#: .\cookbook\templates\stats.html:43
msgid "External Recipes"
msgstr ""
#: .\cookbook\templates\base.html:298
#: .\cookbook\templates\space_manage.html:15
msgid "Space Settings"
msgstr ""
#: .\cookbook\templates\base.html:303 .\cookbook\templates\system.html:13
msgid "System"
msgstr ""
#: .\cookbook\templates\base.html:305
msgid "Admin"
msgstr ""
#: .\cookbook\templates\base.html:309
#: .\cookbook\templates\space_overview.html:25
msgid "Your Spaces"
msgstr ""
#: .\cookbook\templates\base.html:320
#: .\cookbook\templates\space_overview.html:6
msgid "Overview"
msgstr ""
#: .\cookbook\templates\base.html:324
msgid "Markdown Guide"
msgstr ""
#: .\cookbook\templates\base.html:326
msgid "GitHub"
msgstr ""
#: .\cookbook\templates\base.html:328
msgid "Translate Tandoor"
msgstr ""
#: .\cookbook\templates\base.html:332
msgid "API Browser"
msgstr ""
#: .\cookbook\templates\base.html:335
msgid "Log out"
msgstr ""
#: .\cookbook\templates\base.html:357
msgid "You are using the free version of Tandor"
msgstr ""
#: .\cookbook\templates\base.html:358
msgid "Upgrade Now"
msgstr ""
#: .\cookbook\templates\batch\edit.html:6
msgid "Batch edit Category"
msgstr ""
#: .\cookbook\templates\batch\edit.html:15
msgid "Batch edit Recipes"
msgstr ""
#: .\cookbook\templates\batch\edit.html:20
msgid "Add the specified keywords to all recipes containing a word"
msgstr ""
#: .\cookbook\templates\batch\monitor.html:6 .\cookbook\views\edit.py:73
msgid "Sync"
msgstr ""
#: .\cookbook\templates\batch\monitor.html:10
msgid "Manage watched Folders"
msgstr ""
#: .\cookbook\templates\batch\monitor.html:14
msgid ""
"On this Page you can manage all storage folder locations that should be "
"monitored and synced."
msgstr ""
#: .\cookbook\templates\batch\monitor.html:16
msgid "The path must be in the following format"
msgstr ""
#: .\cookbook\templates\batch\monitor.html:20
#: .\cookbook\templates\forms\edit_import_recipe.html:14
#: .\cookbook\templates\generic\edit_template.html:23
#: .\cookbook\templates\generic\new_template.html:23
#: .\cookbook\templates\settings.html:70
#: .\cookbook\templates\settings.html:112
#: .\cookbook\templates\settings.html:130
#: .\cookbook\templates\settings.html:202
#: .\cookbook\templates\settings.html:213
msgid "Save"
msgstr ""
#: .\cookbook\templates\batch\monitor.html:21
msgid "Manage External Storage"
msgstr ""
#: .\cookbook\templates\batch\monitor.html:28
msgid "Sync Now!"
msgstr ""
#: .\cookbook\templates\batch\monitor.html:29
msgid "Show Recipes"
msgstr ""
#: .\cookbook\templates\batch\monitor.html:30
msgid "Show Log"
msgstr ""
#: .\cookbook\templates\batch\waiting.html:4
#: .\cookbook\templates\batch\waiting.html:10
msgid "Importing Recipes"
msgstr ""
#: .\cookbook\templates\batch\waiting.html:28
msgid ""
"This can take a few minutes, depending on the number of recipes in sync, "
"please wait."
msgstr ""
#: .\cookbook\templates\books.html:7
msgid "Recipe Books"
msgstr ""
#: .\cookbook\templates\export.html:8 .\cookbook\templates\test2.html:6
msgid "Export Recipes"
msgstr ""
#: .\cookbook\templates\forms\edit_import_recipe.html:5
#: .\cookbook\templates\forms\edit_import_recipe.html:9
msgid "Import new Recipe"
msgstr ""
#: .\cookbook\templates\forms\edit_internal_recipe.html:7
msgid "Edit Recipe"
msgstr ""
#: .\cookbook\templates\forms\ingredients.html:15
msgid "Edit Ingredients"
msgstr ""
#: .\cookbook\templates\forms\ingredients.html:16
msgid ""
"\n"
" The following form can be used if, accidentally, two (or more) units "
"or ingredients where created that should be\n"
" the same.\n"
" It merges two units or ingredients and updates all recipes using "
"them.\n"
" "
msgstr ""
#: .\cookbook\templates\forms\ingredients.html:26
msgid "Are you sure that you want to merge these two units?"
msgstr ""
#: .\cookbook\templates\forms\ingredients.html:31
#: .\cookbook\templates\forms\ingredients.html:40
msgid "Merge"
msgstr ""
#: .\cookbook\templates\forms\ingredients.html:36
msgid "Are you sure that you want to merge these two ingredients?"
msgstr ""
#: .\cookbook\templates\generic\delete_template.html:21
#, python-format
msgid "Are you sure you want to delete the %(title)s: %(object)s "
msgstr ""
#: .\cookbook\templates\generic\delete_template.html:22
msgid "This cannot be undone!"
msgstr ""
#: .\cookbook\templates\generic\delete_template.html:27
msgid "Protected"
msgstr ""
#: .\cookbook\templates\generic\delete_template.html:42
msgid "Cascade"
msgstr ""
#: .\cookbook\templates\generic\delete_template.html:73
msgid "Cancel"
msgstr ""
#: .\cookbook\templates\generic\edit_template.html:32
msgid "View"
msgstr ""
#: .\cookbook\templates\generic\edit_template.html:36
msgid "Delete original file"
msgstr ""
#: .\cookbook\templates\generic\list_template.html:6
#: .\cookbook\templates\generic\list_template.html:22
msgid "List"
msgstr ""
#: .\cookbook\templates\generic\list_template.html:36
msgid "Filter"
msgstr ""
#: .\cookbook\templates\generic\list_template.html:41
msgid "Import all"
msgstr ""
#: .\cookbook\templates\generic\table_template.html:76
#: .\cookbook\templates\recipes_table.html:121
msgid "previous"
msgstr ""
#: .\cookbook\templates\generic\table_template.html:98
#: .\cookbook\templates\recipes_table.html:143
msgid "next"
msgstr ""
#: .\cookbook\templates\history.html:20
msgid "View Log"
msgstr ""
#: .\cookbook\templates\history.html:24
msgid "Cook Log"
msgstr ""
#: .\cookbook\templates\import.html:6
msgid "Import Recipes"
msgstr ""
#: .\cookbook\templates\import.html:14 .\cookbook\templates\import.html:20
#: .\cookbook\templates\import_response.html:7 .\cookbook\views\delete.py:86
#: .\cookbook\views\edit.py:191
msgid "Import"
msgstr ""
#: .\cookbook\templates\include\recipe_open_modal.html:18
msgid "Close"
msgstr ""
#: .\cookbook\templates\include\recipe_open_modal.html:32
msgid "Open Recipe"
msgstr ""
#: .\cookbook\templates\include\storage_backend_warning.html:4
msgid "Security Warning"
msgstr ""
#: .\cookbook\templates\include\storage_backend_warning.html:5
msgid ""
"\n"
" The Password and Token field are stored as plain text "
"inside the database.\n"
" This is necessary because they are needed to make API requests, but "
"it also increases the risk of\n"
" someone stealing it.
\n"
" To limit the possible damage tokens or accounts with limited access "
"can be used.\n"
" "
msgstr ""
#: .\cookbook\templates\index.html:29
msgid "Search recipe ..."
msgstr ""
#: .\cookbook\templates\index.html:44
msgid "New Recipe"
msgstr ""
#: .\cookbook\templates\index.html:53
msgid "Advanced Search"
msgstr ""
#: .\cookbook\templates\index.html:57
msgid "Reset Search"
msgstr ""
#: .\cookbook\templates\index.html:85
msgid "Last viewed"
msgstr ""
#: .\cookbook\templates\index.html:94
msgid "Log in to view recipes"
msgstr ""
#: .\cookbook\templates\markdown_info.html:5
#: .\cookbook\templates\markdown_info.html:13
msgid "Markdown Info"
msgstr ""
#: .\cookbook\templates\markdown_info.html:14
msgid ""
"\n"
" Markdown is lightweight markup language that can be used to format "
"plain text easily.\n"
" This site uses the Python Markdown library to\n"
" convert your text into nice looking HTML. Its full markdown "
"documentation can be found\n"
" here.\n"
" An incomplete but most likely sufficient documentation can be found "
"below.\n"
" "
msgstr ""
#: .\cookbook\templates\markdown_info.html:25
msgid "Headers"
msgstr ""
#: .\cookbook\templates\markdown_info.html:54
msgid "Formatting"
msgstr ""
#: .\cookbook\templates\markdown_info.html:56
#: .\cookbook\templates\markdown_info.html:72
msgid "Line breaks are inserted by adding two spaces after the end of a line"
msgstr ""
#: .\cookbook\templates\markdown_info.html:57
#: .\cookbook\templates\markdown_info.html:73
msgid "or by leaving a blank line in between."
msgstr ""
#: .\cookbook\templates\markdown_info.html:59
#: .\cookbook\templates\markdown_info.html:74
msgid "This text is bold"
msgstr ""
#: .\cookbook\templates\markdown_info.html:60
#: .\cookbook\templates\markdown_info.html:75
msgid "This text is italic"
msgstr ""
#: .\cookbook\templates\markdown_info.html:61
#: .\cookbook\templates\markdown_info.html:77
msgid "Blockquotes are also possible"
msgstr ""
#: .\cookbook\templates\markdown_info.html:84
msgid "Lists"
msgstr ""
#: .\cookbook\templates\markdown_info.html:85
msgid ""
"Lists can ordered or unordered. It is important to leave a blank line "
"before the list!"
msgstr ""
#: .\cookbook\templates\markdown_info.html:87
#: .\cookbook\templates\markdown_info.html:108
msgid "Ordered List"
msgstr ""
#: .\cookbook\templates\markdown_info.html:89
#: .\cookbook\templates\markdown_info.html:90
#: .\cookbook\templates\markdown_info.html:91
#: .\cookbook\templates\markdown_info.html:110
#: .\cookbook\templates\markdown_info.html:111
#: .\cookbook\templates\markdown_info.html:112
msgid "unordered list item"
msgstr ""
#: .\cookbook\templates\markdown_info.html:93
#: .\cookbook\templates\markdown_info.html:114
msgid "Unordered List"
msgstr ""
#: .\cookbook\templates\markdown_info.html:95
#: .\cookbook\templates\markdown_info.html:96
#: .\cookbook\templates\markdown_info.html:97
#: .\cookbook\templates\markdown_info.html:116
#: .\cookbook\templates\markdown_info.html:117
#: .\cookbook\templates\markdown_info.html:118
msgid "ordered list item"
msgstr ""
#: .\cookbook\templates\markdown_info.html:125
msgid "Images & Links"
msgstr ""
#: .\cookbook\templates\markdown_info.html:126
msgid ""
"Links can be formatted with Markdown. This application also allows to paste "
"links directly into markdown fields without any formatting."
msgstr ""
#: .\cookbook\templates\markdown_info.html:132
#: .\cookbook\templates\markdown_info.html:145
msgid "This will become an image"
msgstr ""
#: .\cookbook\templates\markdown_info.html:152
msgid "Tables"
msgstr ""
#: .\cookbook\templates\markdown_info.html:153
msgid ""
"Markdown tables are hard to create by hand. It is recommended to use a table "
"editor like this one."
msgstr ""
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:171
#: .\cookbook\templates\markdown_info.html:177
msgid "Table"
msgstr ""
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:172
msgid "Header"
msgstr ""
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:178
msgid "Cell"
msgstr ""
#: .\cookbook\templates\meal_plan_entry.html:6
msgid "Meal Plan View"
msgstr ""
#: .\cookbook\templates\meal_plan_entry.html:18
msgid "Created by"
msgstr ""
#: .\cookbook\templates\meal_plan_entry.html:20
msgid "Shared with"
msgstr ""
#: .\cookbook\templates\meal_plan_entry.html:48
#: .\cookbook\templates\recipes_table.html:64
msgid "Last cooked"
msgstr ""
#: .\cookbook\templates\meal_plan_entry.html:50
msgid "Never cooked before."
msgstr ""
#: .\cookbook\templates\meal_plan_entry.html:76
msgid "Other meals on this day"
msgstr ""
#: .\cookbook\templates\no_groups_info.html:5
#: .\cookbook\templates\no_groups_info.html:12
msgid "No Permissions"
msgstr ""
#: .\cookbook\templates\no_groups_info.html:17
msgid "You do not have any groups and therefor cannot use this application."
msgstr ""
#: .\cookbook\templates\no_groups_info.html:18
#: .\cookbook\templates\no_perm_info.html:15
msgid "Please contact your administrator."
msgstr ""
#: .\cookbook\templates\no_perm_info.html:5
#: .\cookbook\templates\no_perm_info.html:12
msgid "No Permission"
msgstr ""
#: .\cookbook\templates\no_perm_info.html:15
msgid ""
"You do not have the required permissions to view this page or perform this "
"action."
msgstr ""
#: .\cookbook\templates\offline.html:6
msgid "Offline"
msgstr ""
#: .\cookbook\templates\offline.html:19
msgid "You are currently offline!"
msgstr ""
#: .\cookbook\templates\offline.html:20
msgid ""
"The recipes listed below are available for offline viewing because you have "
"recently viewed them. Keep in mind that data might be outdated."
msgstr ""
#: .\cookbook\templates\openid\login.html:27
#: .\cookbook\templates\socialaccount\authentication_error.html:27
msgid "Back"
msgstr ""
#: .\cookbook\templates\recipe_view.html:26
msgid "by"
msgstr ""
#: .\cookbook\templates\recipe_view.html:44 .\cookbook\views\delete.py:144
#: .\cookbook\views\edit.py:171
msgid "Comment"
msgstr ""
#: .\cookbook\templates\recipes_table.html:19
#: .\cookbook\templates\recipes_table.html:23
msgid "Recipe Image"
msgstr ""
#: .\cookbook\templates\recipes_table.html:51
msgid "Preparation time ca."
msgstr ""
#: .\cookbook\templates\recipes_table.html:57
msgid "Waiting time ca."
msgstr ""
#: .\cookbook\templates\recipes_table.html:60
msgid "External"
msgstr ""
#: .\cookbook\templates\recipes_table.html:86
msgid "Log Cooking"
msgstr ""
#: .\cookbook\templates\rest_framework\api.html:5
msgid "Recipe Home"
msgstr ""
#: .\cookbook\templates\search_info.html:5
#: .\cookbook\templates\search_info.html:9
#: .\cookbook\templates\settings.html:172
msgid "Search Settings"
msgstr ""
#: .\cookbook\templates\search_info.html:10
msgid ""
"\n"
" Creating the best search experience is complicated and weighs "
"heavily on your personal configuration. \n"
" Changing any of the search settings can have significant impact on "
"the speed and quality of the results.\n"
" Search Methods, Trigrams and Full Text Search configurations are "
"only available if you are using Postgres for your database.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:19
msgid "Search Methods"
msgstr ""
#: .\cookbook\templates\search_info.html:23
msgid ""
" \n"
" Full text searches attempt to normalize the words provided to "
"match common variants. For example: 'forked', 'forking', 'forks' will all "
"normalize to 'fork'.\n"
" There are several methods available, described below, that will "
"control how the search behavior should react when multiple words are "
"searched.\n"
" Full technical details on how these operate can be viewed on Postgresql's website.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:29
msgid ""
" \n"
" Simple searches ignore punctuation and common words such as "
"'the', 'a', 'and'. And will treat separate words as required.\n"
" Searching for 'apple or flour' will return any recipe that "
"includes both 'apple' and 'flour' anywhere in the fields that have been "
"selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:34
msgid ""
" \n"
" Phrase searches ignore punctuation, but will search for all of "
"the words in the exact order provided.\n"
" Searching for 'apple or flour' will only return a recipe that "
"includes the exact phrase 'apple or flour' in any of the fields that have "
"been selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:39
msgid ""
" \n"
" Web searches simulate functionality found on many web search "
"sites supporting special syntax.\n"
" Placing quotes around several words will convert those words "
"into a phrase.\n"
" 'or' is recognized as searching for the word (or phrase) "
"immediately before 'or' OR the word (or phrase) directly after.\n"
" '-' is recognized as searching for recipes that do not include "
"the word (or phrase) that comes immediately after. \n"
" For example searching for 'apple pie' or cherry -butter will "
"return any recipe that includes the phrase 'apple pie' or the word "
"'cherry' \n"
" in any field included in the full text search but exclude any "
"recipe that has the word 'butter' in any field included.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:48
msgid ""
" \n"
" Raw search is similar to Web except will take puncuation "
"operators such as '|', '&' and '()'\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:59
msgid ""
" \n"
" Another approach to searching that also requires Postgresql is "
"fuzzy search or trigram similarity. A trigram is a group of three "
"consecutive characters.\n"
" For example searching for 'apple' will create x trigrams 'app', "
"'ppl', 'ple' and will create a score of how closely words match the "
"generated trigrams.\n"
" One benefit of searching trigams is that a search for 'sandwich' "
"will find misspelled words such as 'sandwhich' that would be missed by other "
"methods.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:69
msgid "Search Fields"
msgstr ""
#: .\cookbook\templates\search_info.html:73
msgid ""
" \n"
" Unaccent is a special case in that it enables searching a field "
"'unaccented' for each search style attempting to ignore accented values. \n"
" For example when you enable unaccent for 'Name' any search "
"(starts with, contains, trigram) will attempt the search ignoring accented "
"characters.\n"
" \n"
" For the other options, you can enable search on any or all "
"fields and they will be combined together with an assumed 'OR'.\n"
" For example enabling 'Name' for Starts With, 'Name' and "
"'Description' for Partial Match and 'Ingredients' and 'Keywords' for Full "
"Search\n"
" and searching for 'apple' will generate a search that will "
"return recipes that have:\n"
" - A recipe name that starts with 'apple'\n"
" - OR a recipe name that contains 'apple'\n"
" - OR a recipe description that contains 'apple'\n"
" - OR a recipe that will have a full text search match ('apple' "
"or 'apples') in ingredients\n"
" - OR a recipe that will have a full text search match in "
"Keywords\n"
"\n"
" Combining too many fields in too many types of search can have a "
"negative impact on performance, create duplicate results or return "
"unexpected results.\n"
" For example, enabling fuzzy search or partial matches will "
"interfere with web search methods. \n"
" Searching for 'apple -pie' with fuzzy search and full text "
"search will return the recipe Apple Pie. Though it is not included in the "
"full text results, it does match the trigram results.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:95
msgid "Search Index"
msgstr ""
#: .\cookbook\templates\search_info.html:99
msgid ""
" \n"
" Trigram search and Full Text Search both rely on database "
"indexes to perform effectively. \n"
" You can rebuild the indexes on all fields in the Admin page for "
"Recipes and selecting all recipes and running 'rebuild index for selected "
"recipes'\n"
" You can also rebuild indexes at the command line by executing "
"the management command 'python manage.py rebuildindex'\n"
" "
msgstr ""
#: .\cookbook\templates\settings.html:28
msgid "Account"
msgstr ""
#: .\cookbook\templates\settings.html:35
msgid "Preferences"
msgstr ""
#: .\cookbook\templates\settings.html:42
msgid "API-Settings"
msgstr ""
#: .\cookbook\templates\settings.html:49
msgid "Search-Settings"
msgstr ""
#: .\cookbook\templates\settings.html:56
msgid "Shopping-Settings"
msgstr ""
#: .\cookbook\templates\settings.html:65
msgid "Name Settings"
msgstr ""
#: .\cookbook\templates\settings.html:73
msgid "Account Settings"
msgstr ""
#: .\cookbook\templates\settings.html:75
msgid "Emails"
msgstr ""
#: .\cookbook\templates\settings.html:78
#: .\cookbook\templates\socialaccount\connections.html:11
msgid "Social"
msgstr ""
#: .\cookbook\templates\settings.html:91
msgid "Language"
msgstr ""
#: .\cookbook\templates\settings.html:121
msgid "Style"
msgstr ""
#: .\cookbook\templates\settings.html:142
msgid "API Token"
msgstr ""
#: .\cookbook\templates\settings.html:143
msgid ""
"You can use both basic authentication and token based authentication to "
"access the REST API."
msgstr ""
#: .\cookbook\templates\settings.html:160
msgid ""
"Use the token as an Authorization header prefixed by the word token as shown "
"in the following examples:"
msgstr ""
#: .\cookbook\templates\settings.html:162
msgid "or"
msgstr ""
#: .\cookbook\templates\settings.html:173
msgid ""
"There are many options to configure the search depending on your personal "
"preferences."
msgstr ""
#: .\cookbook\templates\settings.html:174
msgid ""
"Usually you do not need to configure any of them and can just stick "
"with either the default or one of the following presets."
msgstr ""
#: .\cookbook\templates\settings.html:175
msgid ""
"If you do want to configure the search you can read about the different "
"options here."
msgstr ""
#: .\cookbook\templates\settings.html:180
msgid "Fuzzy"
msgstr ""
#: .\cookbook\templates\settings.html:181
msgid ""
"Find what you need even if your search or the recipe contains typos. Might "
"return more results than needed to make sure you find what you are looking "
"for."
msgstr ""
#: .\cookbook\templates\settings.html:182
msgid "This is the default behavior"
msgstr ""
#: .\cookbook\templates\settings.html:183
#: .\cookbook\templates\settings.html:191
msgid "Apply"
msgstr ""
#: .\cookbook\templates\settings.html:188
msgid "Precise"
msgstr ""
#: .\cookbook\templates\settings.html:189
msgid ""
"Allows fine control over search results but might not return results if too "
"many spelling mistakes are made."
msgstr ""
#: .\cookbook\templates\settings.html:190
msgid "Perfect for large Databases"
msgstr ""
#: .\cookbook\templates\settings.html:207
msgid "Shopping Settings"
msgstr ""
#: .\cookbook\templates\setup.html:6 .\cookbook\templates\system.html:5
msgid "Cookbook Setup"
msgstr ""
#: .\cookbook\templates\setup.html:14
msgid "Setup"
msgstr ""
#: .\cookbook\templates\setup.html:15
msgid ""
"To start using this application you must first create a superuser account."
msgstr ""
#: .\cookbook\templates\setup.html:20
msgid "Create Superuser account"
msgstr ""
#: .\cookbook\templates\socialaccount\authentication_error.html:7
#: .\cookbook\templates\socialaccount\authentication_error.html:23
msgid "Social Network Login Failure"
msgstr ""
#: .\cookbook\templates\socialaccount\authentication_error.html:25
msgid ""
"An error occurred while attempting to login via your social network account."
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:4
#: .\cookbook\templates\socialaccount\connections.html:15
msgid "Account Connections"
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:18
msgid ""
"You can sign in to your account using any of the following third party\n"
" accounts:"
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:52
msgid ""
"You currently have no social network accounts connected to this account."
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:55
msgid "Add a 3rd Party Account"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:5
#: .\cookbook\templates\socialaccount\signup.html:5
msgid "Signup"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:9
#, python-format
msgid "Connect %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:11
#, python-format
msgid "You are about to connect a new third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:13
#, python-format
msgid "Sign In Via %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:15
#, python-format
msgid "You are about to sign in using a third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:20
msgid "Continue"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:10
#, python-format
msgid ""
"You are about to use your\n"
" %(provider_name)s account to login to\n"
" %(site_name)s. As a final step, please complete the following form:"
msgstr ""
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:23
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:31
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:39
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:47
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:55
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:63
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:71
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:79
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:87
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:95
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:103
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:111
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:119
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:127
msgid "Sign in using"
msgstr ""
#: .\cookbook\templates\space_manage.html:26
msgid "Space:"
msgstr ""
#: .\cookbook\templates\space_manage.html:27
msgid "Manage Subscription"
msgstr ""
#: .\cookbook\templates\space_overview.html:13 .\cookbook\views\delete.py:216
msgid "Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:17
msgid ""
"Recipes, foods, shopping lists and more are organized in spaces of one or "
"more people."
msgstr ""
#: .\cookbook\templates\space_overview.html:18
msgid ""
"You can either be invited into an existing space or create your own one."
msgstr ""
#: .\cookbook\templates\space_overview.html:45
msgid "Owner"
msgstr ""
#: .\cookbook\templates\space_overview.html:49
msgid "Leave Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:70
#: .\cookbook\templates\space_overview.html:80
msgid "Join Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:73
msgid "Join an existing space."
msgstr ""
#: .\cookbook\templates\space_overview.html:75
msgid ""
"To join an existing space either enter your invite token or click on the "
"invite link the space owner send you."
msgstr ""
#: .\cookbook\templates\space_overview.html:88
#: .\cookbook\templates\space_overview.html:97
msgid "Create Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:91
msgid "Create your own recipe space."
msgstr ""
#: .\cookbook\templates\space_overview.html:93
msgid "Start your own recipe space and invite other users to it."
msgstr ""
#: .\cookbook\templates\stats.html:4
msgid "Stats"
msgstr ""
#: .\cookbook\templates\stats.html:10
msgid "Statistics"
msgstr ""
#: .\cookbook\templates\stats.html:19
msgid "Number of objects"
msgstr ""
#: .\cookbook\templates\stats.html:30
msgid "Recipe Imports"
msgstr ""
#: .\cookbook\templates\stats.html:38
msgid "Objects stats"
msgstr ""
#: .\cookbook\templates\stats.html:41
msgid "Recipes without Keywords"
msgstr ""
#: .\cookbook\templates\stats.html:45
msgid "Internal Recipes"
msgstr ""
#: .\cookbook\templates\system.html:20
msgid "System Information"
msgstr ""
#: .\cookbook\templates\system.html:22
msgid ""
"\n"
" Django Recipes is an open source free software application. It can "
"be found on\n"
" GitHub.\n"
" Changelogs can be found here.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:36
msgid "Media Serving"
msgstr ""
#: .\cookbook\templates\system.html:37 .\cookbook\templates\system.html:52
#: .\cookbook\templates\system.html:68
msgid "Warning"
msgstr ""
#: .\cookbook\templates\system.html:37 .\cookbook\templates\system.html:52
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:83
msgid "Ok"
msgstr ""
#: .\cookbook\templates\system.html:39
msgid ""
"Serving media files directly using gunicorn/python is not recommend!\n"
" Please follow the steps described\n"
" here to update\n"
" your installation.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:45 .\cookbook\templates\system.html:61
#: .\cookbook\templates\system.html:76 .\cookbook\templates\system.html:90
msgid "Everything is fine!"
msgstr ""
#: .\cookbook\templates\system.html:50
msgid "Secret Key"
msgstr ""
#: .\cookbook\templates\system.html:54
msgid ""
"\n"
" You do not have a SECRET_KEY configured in your "
".env file. Django defaulted to the\n"
" standard key\n"
" provided with the installation which is publicly know and "
"insecure! Please set\n"
" SECRET_KEY int the .env configuration "
"file.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:66
msgid "Debug Mode"
msgstr ""
#: .\cookbook\templates\system.html:70
msgid ""
"\n"
" This application is still running in debug mode. This is most "
"likely not needed. Turn of debug mode by\n"
" setting\n"
" DEBUG=0 int the .env configuration "
"file.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:81
msgid "Database"
msgstr ""
#: .\cookbook\templates\system.html:83
msgid "Info"
msgstr ""
#: .\cookbook\templates\system.html:85
msgid ""
"\n"
" This application is not running with a Postgres database "
"backend. This is ok but not recommended as some\n"
" features only work with postgres databases.\n"
" "
msgstr ""
#: .\cookbook\templates\url_import.html:8
msgid "URL Import"
msgstr ""
#: .\cookbook\views\api.py:105 .\cookbook\views\api.py:197
msgid "Parameter updated_at incorrectly formatted"
msgstr ""
#: .\cookbook\views\api.py:217 .\cookbook\views\api.py:320
msgid "No {self.basename} with id {pk} exists"
msgstr ""
#: .\cookbook\views\api.py:221
msgid "Cannot merge with the same object!"
msgstr ""
#: .\cookbook\views\api.py:228
msgid "No {self.basename} with id {target} exists"
msgstr ""
#: .\cookbook\views\api.py:233
msgid "Cannot merge with child object!"
msgstr ""
#: .\cookbook\views\api.py:266
msgid "{source.name} was merged successfully with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:271
msgid "An error occurred attempting to merge {source.name} with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:329
msgid "{child.name} was moved successfully to the root."
msgstr ""
#: .\cookbook\views\api.py:332 .\cookbook\views\api.py:350
msgid "An error occurred attempting to move "
msgstr ""
#: .\cookbook\views\api.py:335
msgid "Cannot move an object to itself!"
msgstr ""
#: .\cookbook\views\api.py:341
msgid "No {self.basename} with id {parent} exists"
msgstr ""
#: .\cookbook\views\api.py:347
msgid "{child.name} was moved successfully to parent {parent.name}"
msgstr ""
#: .\cookbook\views\api.py:542
msgid "{obj.name} was removed from the shopping list."
msgstr ""
#: .\cookbook\views\api.py:547 .\cookbook\views\api.py:879
#: .\cookbook\views\api.py:892
msgid "{obj.name} was added to the shopping list."
msgstr ""
#: .\cookbook\views\api.py:674
msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:676
msgid "Query string matched (fuzzy) against object name."
msgstr ""
#: .\cookbook\views\api.py:720
msgid ""
"Query string matched (fuzzy) against recipe name. In the future also "
"fulltext search."
msgstr ""
#: .\cookbook\views\api.py:722
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr ""
#: .\cookbook\views\api.py:725
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr ""
#: .\cookbook\views\api.py:728
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:731
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr ""
#: .\cookbook\views\api.py:734
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:736
msgid "ID of food a recipe should have. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:739
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr ""
#: .\cookbook\views\api.py:741
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:743
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr ""
#: .\cookbook\views\api.py:745
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:746
msgid "ID of unit a recipe should have."
msgstr ""
#: .\cookbook\views\api.py:748
msgid ""
"Rating a recipe should have or greater. [0 - 5] Negative value filters "
"rating less than."
msgstr ""
#: .\cookbook\views\api.py:749
msgid "ID of book a recipe should be in. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:751
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr ""
#: .\cookbook\views\api.py:753
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:755
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr ""
#: .\cookbook\views\api.py:757
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:759
msgid "If only internal recipes should be returned. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:761
msgid "Returns the results in randomized order. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:763
msgid "Returns new results first in search results. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:765
msgid ""
"Filter recipes cooked X times or more. Negative values returns cooked less "
"than X times"
msgstr ""
#: .\cookbook\views\api.py:767
msgid ""
"Filter recipes last cooked on or after YYYY-MM-DD. Prepending - filters on "
"or before date."
msgstr ""
#: .\cookbook\views\api.py:769
msgid ""
"Filter recipes created on or after YYYY-MM-DD. Prepending - filters on or "
"before date."
msgstr ""
#: .\cookbook\views\api.py:771
msgid ""
"Filter recipes updated on or after YYYY-MM-DD. Prepending - filters on or "
"before date."
msgstr ""
#: .\cookbook\views\api.py:773
msgid ""
"Filter recipes lasts viewed on or after YYYY-MM-DD. Prepending - filters on "
"or before date."
msgstr ""
#: .\cookbook\views\api.py:775
msgid "Filter recipes that can be made with OnHand food. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:937
msgid ""
"Returns the shopping list entry with a primary key of id. Multiple values "
"allowed."
msgstr ""
#: .\cookbook\views\api.py:942
msgid ""
"Filter shopping list entries on checked. [true, false, both, recent]"
"
- recent includes unchecked items and recently completed items."
msgstr ""
#: .\cookbook\views\api.py:945
msgid "Returns the shopping list entries sorted by supermarket category order."
msgstr ""
#: .\cookbook\views\api.py:1140
msgid "Nothing to do."
msgstr ""
#: .\cookbook\views\api.py:1160
msgid "Invalid Url"
msgstr ""
#: .\cookbook\views\api.py:1167
msgid "Connection Refused."
msgstr ""
#: .\cookbook\views\api.py:1172
msgid "Bad URL Schema."
msgstr ""
#: .\cookbook\views\api.py:1195
msgid "No usable data could be found."
msgstr ""
#: .\cookbook\views\api.py:1303 .\cookbook\views\data.py:28
#: .\cookbook\views\edit.py:120 .\cookbook\views\new.py:90
msgid "This feature is not yet available in the hosted version of tandoor!"
msgstr ""
#: .\cookbook\views\api.py:1325
msgid "Sync successful!"
msgstr ""
#: .\cookbook\views\api.py:1330
msgid "Error synchronizing with Storage"
msgstr ""
#: .\cookbook\views\data.py:97
#, python-format
msgid "Batch edit done. %(count)d recipe was updated."
msgid_plural "Batch edit done. %(count)d Recipes where updated."
msgstr[0] ""
#: .\cookbook\views\delete.py:98
msgid "Monitor"
msgstr ""
#: .\cookbook\views\delete.py:122 .\cookbook\views\lists.py:62
#: .\cookbook\views\new.py:96
msgid "Storage Backend"
msgstr ""
#: .\cookbook\views\delete.py:132
msgid ""
"Could not delete this storage backend as it is used in at least one monitor."
msgstr ""
#: .\cookbook\views\delete.py:155
msgid "Recipe Book"
msgstr ""
#: .\cookbook\views\delete.py:167
msgid "Bookmarks"
msgstr ""
#: .\cookbook\views\delete.py:189
msgid "Invite Link"
msgstr ""
#: .\cookbook\views\delete.py:200
msgid "Space Membership"
msgstr ""
#: .\cookbook\views\edit.py:116
msgid "You cannot edit this storage!"
msgstr ""
#: .\cookbook\views\edit.py:140
msgid "Storage saved!"
msgstr ""
#: .\cookbook\views\edit.py:146
msgid "There was an error updating this storage backend!"
msgstr ""
#: .\cookbook\views\edit.py:239
msgid "Changes saved!"
msgstr ""
#: .\cookbook\views\edit.py:243
msgid "Error saving changes!"
msgstr ""
#: .\cookbook\views\import_export.py:111 .\cookbook\views\import_export.py:150
msgid "Importing is not implemented for this provider"
msgstr ""
#: .\cookbook\views\import_export.py:137
msgid ""
"The PDF Exporter is not enabled on this instance as it is still in an "
"experimental state."
msgstr ""
#: .\cookbook\views\lists.py:24
msgid "Import Log"
msgstr ""
#: .\cookbook\views\lists.py:37
msgid "Discovery"
msgstr ""
#: .\cookbook\views\lists.py:47
msgid "Shopping List"
msgstr ""
#: .\cookbook\views\lists.py:76
msgid "Invite Links"
msgstr ""
#: .\cookbook\views\lists.py:139
msgid "Supermarkets"
msgstr ""
#: .\cookbook\views\lists.py:155
msgid "Shopping Categories"
msgstr ""
#: .\cookbook\views\lists.py:187
msgid "Custom Filters"
msgstr ""
#: .\cookbook\views\lists.py:224
msgid "Steps"
msgstr ""
#: .\cookbook\views\new.py:121
msgid "Imported new recipe!"
msgstr ""
#: .\cookbook\views\new.py:124
msgid "There was an error importing this recipe!"
msgstr ""
#: .\cookbook\views\views.py:124
msgid ""
"You have successfully created your own recipe space. Start by adding some "
"recipes or invite other people to join you."
msgstr ""
#: .\cookbook\views\views.py:178
msgid "You do not have the required permissions to perform this action!"
msgstr ""
#: .\cookbook\views\views.py:189
msgid "Comment saved!"
msgstr ""
#: .\cookbook\views\views.py:264
msgid "This feature is not available in the demo version!"
msgstr ""
#: .\cookbook\views\views.py:324
msgid "You must select at least one field to search!"
msgstr ""
#: .\cookbook\views\views.py:329
msgid ""
"To use this search method you must select at least one full text search "
"field!"
msgstr ""
#: .\cookbook\views\views.py:333
msgid "Fuzzy search is not compatible with this search method!"
msgstr ""
#: .\cookbook\views\views.py:463
msgid ""
"The setup page can only be used to create the first user! If you have "
"forgotten your superuser credentials please consult the django documentation "
"on how to reset passwords."
msgstr ""
#: .\cookbook\views\views.py:470
msgid "Passwords dont match!"
msgstr ""
#: .\cookbook\views\views.py:478
msgid "User has been created, please login!"
msgstr ""
#: .\cookbook\views\views.py:494
msgid "Malformed Invite Link supplied!"
msgstr ""
#: .\cookbook\views\views.py:510
msgid "Successfully joined space."
msgstr ""
#: .\cookbook\views\views.py:516
msgid "Invite Link not valid or already used!"
msgstr ""
#: .\cookbook\views\views.py:530
msgid ""
"Reporting share links is not enabled for this instance. Please notify the "
"page administrator to report problems."
msgstr ""
#: .\cookbook\views\views.py:536
msgid ""
"Recipe sharing link has been disabled! For additional information please "
"contact the page administrator."
msgstr ""
================================================
FILE: cookbook/locale/uk/LC_MESSAGES/django.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-08-01 15:04+0200\n"
"PO-Revision-Date: 2026-02-17 00:44+0000\n"
"Last-Translator: SerhiiOS \n"
"Language-Team: Ukrainian \n"
"Language: uk\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
"X-Generator: Weblate 5.13.3\n"
#: .\cookbook\forms.py:45
msgid ""
"Both fields are optional. If none are given the username will be displayed "
"instead"
msgstr ""
"Обидва поля є необов'язковими. Якщо жодне з них не вказано, замість них буде "
"відображено ім'я користувача"
#: .\cookbook\forms.py:62 .\cookbook\forms.py:246
msgid "Name"
msgstr "Назва"
#: .\cookbook\forms.py:62 .\cookbook\forms.py:246 .\cookbook\views\lists.py:103
msgid "Keywords"
msgstr "Ключові слова"
#: .\cookbook\forms.py:62
msgid "Preparation time in minutes"
msgstr "Час приготування у хвилинах"
#: .\cookbook\forms.py:62
msgid "Waiting time (cooking/baking) in minutes"
msgstr "Час очікування (варіння/випічка) у хвилинах"
#: .\cookbook\forms.py:63 .\cookbook\forms.py:222 .\cookbook\forms.py:246
msgid "Path"
msgstr "Шлях"
#: .\cookbook\forms.py:63
msgid "Storage UID"
msgstr "UID сховища"
#: .\cookbook\forms.py:93
msgid "Default"
msgstr "За замовчуванням"
#: .\cookbook\forms.py:121
msgid ""
"To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything."
msgstr ""
"Щоб запобігти дублюванням, рецепти з назвами, що вже існують, "
"ігноруватимуться. Установіть цей прапорець, щоб імпортувати все."
#: .\cookbook\forms.py:143
msgid "Add your comment: "
msgstr "Додайте ваш коментар: "
#: .\cookbook\forms.py:151
msgid "Leave empty for dropbox and enter app password for nextcloud."
msgstr "Залиште порожнім для dropbox і введіть api ключі для nextcloud."
#: .\cookbook\forms.py:154
msgid "Leave empty for nextcloud and enter api token for dropbox."
msgstr "Залиште порожнім для nextcloud і введіть api ключі для dropbox."
#: .\cookbook\forms.py:160
msgid ""
"Leave empty for dropbox and enter only base url for nextcloud (/remote."
"php/webdav/ is added automatically)"
msgstr ""
"Залиште порожнім для dropbox і введіть лише базовий url для nextcloud "
"(/remote.php/webdav/ буде додано автоматично)"
#: .\cookbook\forms.py:188
msgid ""
"Long Lived Access Token for your HomeAssistant instance"
msgstr ""
"Довговічний токен доступу для вашої інстанції HomeAssistant"
#: .\cookbook\forms.py:193
msgid "Something like http://homeassistant.local:8123/api"
msgstr "Щось на кшталт http://homeassistant.local:8123/api"
#: .\cookbook\forms.py:205
msgid "http://homeassistant.local:8123/api for example"
msgstr "http://homeassistant.local:8123/api наприклад"
#: .\cookbook\forms.py:222 .\cookbook\views\edit.py:117
msgid "Storage"
msgstr "Сховище"
#: .\cookbook\forms.py:222
msgid "Active"
msgstr "Активний"
#: .\cookbook\forms.py:226
msgid "Search String"
msgstr "Рядок пошуку"
#: .\cookbook\forms.py:246
msgid "File ID"
msgstr "ID файлу"
#: .\cookbook\forms.py:262
msgid "Maximum number of users for this space reached."
msgstr "Досягнута максимальна кількість користувачів для цього простору."
#: .\cookbook\forms.py:268
msgid "Email address already taken!"
msgstr "Ця адреса електронної пошти вже зайнята!"
#: .\cookbook\forms.py:275
msgid ""
"An email address is not required but if present the invite link will be sent "
"to the user."
msgstr ""
"Адреса електронної пошти не обов'язкова, але якщо вона вказана, "
"користувачеві буде надіслано посилання для запрошення."
#: .\cookbook\forms.py:287
msgid "Name already taken."
msgstr "Ім'я вже зайняте."
#: .\cookbook\forms.py:298
msgid "Accept Terms and Privacy"
msgstr "Прийняти умови використування та конфіденційності"
#: .\cookbook\forms.py:332
msgid ""
"Determines how fuzzy a search is if it uses trigram similarity matching (e."
"g. low values mean more typos are ignored)."
msgstr ""
"Визначає, наскільки нечітким є пошук, якщо він використовує зіставлення за "
"схожістю триграм (наприклад, низькі значення означають, що більше "
"друкарських помилок ігнорується)."
#: .\cookbook\forms.py:340
msgid ""
"Select type method of search. Click here for "
"full description of choices."
msgstr ""
"Виберіть метод пошуку. Натисніть тут для "
"повного опису опцій."
#: .\cookbook\forms.py:341
msgid ""
"Use fuzzy matching on units, keywords and ingredients when editing and "
"importing recipes."
msgstr ""
"Використовуйте нечітке зіставлення одиниць вимірювання, ключових слів та "
"інгредієнтів під час редагування та імпорту рецептів."
#: .\cookbook\forms.py:342
msgid ""
"Fields to search ignoring accents. Selecting this option can improve or "
"degrade search quality depending on language"
msgstr ""
"Поля для пошуку без урахування діакритичних знаків. Вибір цієї опції може "
"покращити або погіршити якість пошуку залежно від мови"
#: .\cookbook\forms.py:343
msgid ""
"Fields to search for partial matches. (e.g. searching for 'Pie' will return "
"'pie' and 'piece' and 'soapie')"
msgstr ""
"Поля для пошуку часткових збігів. (наприклад, пошук за запитом «Pie» поверне "
"«pie», «piece» та «soapie»)"
#: .\cookbook\forms.py:344
msgid ""
"Fields to search for beginning of word matches. (e.g. searching for 'sa' "
"will return 'salad' and 'sandwich')"
msgstr ""
"Поля для пошуку збігів на початку слів. (наприклад, пошук за запитом «sa» "
"поверне «salad» та «sandwich»)"
#: .\cookbook\forms.py:345
msgid ""
"Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) "
"Note: this option will conflict with 'web' and 'raw' methods of search."
msgstr ""
"Поля для «нечіткого» пошуку. (наприклад, пошук за запитом «recpie» знайде "
"«recipe»). Примітка: цей параметр конфліктуватиме з методами пошуку «web» та "
"«raw»."
#: .\cookbook\forms.py:346
msgid ""
"Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods "
"only function with fulltext fields."
msgstr ""
"Поля для повнотекстового пошуку. Примітка: методи пошуку «web», «phrase» та "
"«raw» працюють лише з повнотекстовими полями."
#: .\cookbook\forms.py:350
msgid "Search Method"
msgstr "Метод Пошуку"
#: .\cookbook\forms.py:350
msgid "Fuzzy Lookups"
msgstr "Нечіткі пошуки"
#: .\cookbook\forms.py:350
msgid "Ignore Accent"
msgstr "Ігнорувати акцент"
#: .\cookbook\forms.py:350
msgid "Partial Match"
msgstr "Частковий збіг"
#: .\cookbook\forms.py:350
msgid "Starts With"
msgstr "Починається З"
#: .\cookbook\forms.py:351
msgid "Fuzzy Search"
msgstr "Нечіткий пошук"
#: .\cookbook\forms.py:351
msgid "Full Text"
msgstr "Повний текст"
#: .\cookbook\helper\AllAuthCustomAdapter.py:41
msgid ""
"In order to prevent spam, the requested email was not send. Please wait a "
"few minutes and try again."
msgstr ""
"Щоб запобігти спаму, запитуваний електронний лист не було надіслано. Будь "
"ласка, зачекайте кілька хвилин і спробуйте ще раз."
#: .\cookbook\helper\permission_helper.py:164
#: .\cookbook\helper\permission_helper.py:187 .\cookbook\views\views.py:117
msgid "You are not logged in and therefore cannot view this page!"
msgstr "Ви не ввійшли в систему, тому не можете переглянути цю сторінку!"
#: .\cookbook\helper\permission_helper.py:168
#: .\cookbook\helper\permission_helper.py:174
#: .\cookbook\helper\permission_helper.py:199
#: .\cookbook\helper\permission_helper.py:266
#: .\cookbook\helper\permission_helper.py:280
#: .\cookbook\helper\permission_helper.py:291
#: .\cookbook\helper\permission_helper.py:302
#: .\cookbook\helper\permission_helper.py:318
#: .\cookbook\helper\permission_helper.py:341 .\cookbook\views\data.py:35
#: .\cookbook\views\views.py:127 .\cookbook\views\views.py:131
msgid "You do not have the required permissions to view this page!"
msgstr "У вас немає необхідних дозволів для перегляду цієї сторінки!"
#: .\cookbook\helper\permission_helper.py:192
#: .\cookbook\helper\permission_helper.py:215
#: .\cookbook\helper\permission_helper.py:237
#: .\cookbook\helper\permission_helper.py:252
msgid "You cannot interact with this object as it is not owned by you!"
msgstr "Ви не можете взаємодіяти з цим об'єктом, оскільки він вам не належить!"
#: .\cookbook\helper\permission_helper.py:402
msgid "You have reached the maximum number of recipes for your space."
msgstr "Ви досягли максимальної кількості рецептів для вашого простору."
#: .\cookbook\helper\permission_helper.py:414
msgid "You have more users than allowed in your space."
msgstr "У вашому просторі більше користувачів, ніж дозволено."
#: .\cookbook\helper\recipe_url_import.py:310
msgid "reverse rotation"
msgstr "зворотне обертання"
#: .\cookbook\helper\recipe_url_import.py:311
msgid "careful rotation"
msgstr "обережне обертання"
#: .\cookbook\helper\recipe_url_import.py:312
msgid "knead"
msgstr "замісити"
#: .\cookbook\helper\recipe_url_import.py:313
msgid "thicken"
msgstr "згустити"
#: .\cookbook\helper\recipe_url_import.py:314
msgid "warm up"
msgstr "розігріти"
#: .\cookbook\helper\recipe_url_import.py:315
msgid "ferment"
msgstr "ферментувати"
#: .\cookbook\helper\recipe_url_import.py:316
msgid "sous-vide"
msgstr "су-від"
#: .\cookbook\helper\shopping_helper.py:150
msgid "You must supply a servings size"
msgstr "Ви повинні вказати розмір порції"
#: .\cookbook\helper\template_helper.py:95
#: .\cookbook\helper\template_helper.py:97
msgid "Could not parse template code."
msgstr "Не вдалося проаналізувати код шаблону."
#: .\cookbook\integration\copymethat.py:44
#: .\cookbook\integration\melarecipes.py:37
msgid "Favorite"
msgstr "Улюблене"
#: .\cookbook\integration\copymethat.py:50
msgid "I made this"
msgstr "Я зробив це"
#: .\cookbook\integration\integration.py:209
msgid ""
"Importer expected a .zip file. Did you choose the correct importer type for "
"your data ?"
msgstr ""
"Імпортер очікував ZIP-файл. Чи ви обрали правильний тип імпортера для своїх "
"даних?"
#: .\cookbook\integration\integration.py:212
msgid ""
"An unexpected error occurred during the import. Please make sure you have "
"uploaded a valid file."
msgstr ""
"Під час імпорту сталася несподівана помилка. Переконайтеся, що ви "
"завантажили дійсний файл."
#: .\cookbook\integration\integration.py:217
msgid "The following recipes were ignored because they already existed:"
msgstr "Наступні рецепти були проігноровані, оскільки вони вже існували:"
#: .\cookbook\integration\integration.py:221
#, python-format
msgid "Imported %s recipes."
msgstr "Імпортовано %s рецептів."
#: .\cookbook\integration\openeats.py:28
msgid "Recipe source:"
msgstr "Джерело рецепту:"
#: .\cookbook\integration\paprika.py:49
msgid "Notes"
msgstr "Нотатки"
#: .\cookbook\integration\paprika.py:52
msgid "Nutritional Information"
msgstr "Інформація про поживність"
#: .\cookbook\integration\paprika.py:56
msgid "Source"
msgstr "Джерело"
#: .\cookbook\integration\recettetek.py:54
#: .\cookbook\integration\recipekeeper.py:70
msgid "Imported from"
msgstr "Імпортовано з"
#: .\cookbook\integration\saffron.py:23
msgid "Servings"
msgstr "Порції"
#: .\cookbook\integration\saffron.py:25
msgid "Waiting time"
msgstr "Час очікування"
#: .\cookbook\integration\saffron.py:27
msgid "Preparation Time"
msgstr "Час підготовки"
#: .\cookbook\integration\saffron.py:29 .\cookbook\templates\index.html:7
msgid "Cookbook"
msgstr "Кулінарна книга"
#: .\cookbook\integration\saffron.py:31
msgid "Section"
msgstr "Розділ"
#: .\cookbook\management\commands\fix_duplicate_properties.py:15
msgid "Fixes foods with "
msgstr "Виправити продукт з "
#: .\cookbook\management\commands\rebuildindex.py:14
msgid "Rebuilds full text search index on Recipe"
msgstr "Перебудовує індекс повнотекстового пошуку по рецептам"
#: .\cookbook\management\commands\rebuildindex.py:18
msgid "Only Postgresql databases use full text search, no index to rebuild"
msgstr ""
"Тільки бази даних Postgresql використовують повнотекстовий пошук, без "
"індексу для перебудови"
#: .\cookbook\management\commands\rebuildindex.py:29
msgid "Recipe index rebuild complete."
msgstr "Перебудова індексу рецептів завершена."
#: .\cookbook\management\commands\rebuildindex.py:31
msgid "Recipe index rebuild failed."
msgstr "Не вдалося перебудувати індекс рецептів."
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
msgid "Breakfast"
msgstr "Сніданок"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:19
msgid "Lunch"
msgstr "Обід"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:24
msgid "Dinner"
msgstr "Вечеря"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:29 .\cookbook\models.py:919
msgid "Other"
msgstr "Інше"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
msgid "Fat"
msgstr "Жир"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "g"
msgstr "г"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
msgid "Carbohydrates"
msgstr "Вуглеводи"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "Proteins"
msgstr "Білки"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "Calories"
msgstr "Калорії"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "kcal"
msgstr "ккал"
#: .\cookbook\models.py:325
msgid ""
"Maximum file storage for space in MB. 0 for unlimited, -1 to disable file "
"upload."
msgstr ""
"Максимальний обсяг файлів для зберігання в МБ. 0 для необмеженого обсягу, -1 "
"для відключення завантаження файлів."
#: .\cookbook\models.py:454 .\cookbook\templates\search.html:7
#: .\cookbook\templates\settings.html:18
msgid "Search"
msgstr "Пошук"
#: .\cookbook\models.py:455 .\cookbook\templates\base.html:114
#: .\cookbook\templates\meal_plan.html:7
msgid "Meal-Plan"
msgstr "Меню"
#: .\cookbook\models.py:456 .\cookbook\templates\base.html:122
#: .\cookbook\views\views.py:459
msgid "Books"
msgstr "Книги"
#: .\cookbook\models.py:457 .\cookbook\templates\base.html:118
#: .\cookbook\views\views.py:460
msgid "Shopping"
msgstr "Покупки"
#: .\cookbook\models.py:752
msgid " is part of a recipe step and cannot be deleted"
msgstr " є частиною кроку рецепта та не може бути видалений"
#: .\cookbook\models.py:918
msgid "Nutrition"
msgstr "Харчова цінність"
#: .\cookbook\models.py:918
msgid "Allergen"
msgstr "Алерген"
#: .\cookbook\models.py:919
msgid "Price"
msgstr "Ціна"
#: .\cookbook\models.py:919
msgid "Goal"
msgstr "Мета"
#: .\cookbook\models.py:1408 .\cookbook\templates\search_info.html:28
msgid "Simple"
msgstr "Простий"
#: .\cookbook\models.py:1409 .\cookbook\templates\search_info.html:33
msgid "Phrase"
msgstr "Фраза"
#: .\cookbook\models.py:1410 .\cookbook\templates\search_info.html:38
msgid "Web"
msgstr "Веб"
#: .\cookbook\models.py:1411 .\cookbook\templates\search_info.html:47
msgid "Raw"
msgstr "Сирий"
#: .\cookbook\models.py:1467
msgid "Food Alias"
msgstr "Псевдонім продукту"
#: .\cookbook\models.py:1468
msgid "Unit Alias"
msgstr "Псевдонім одиниці вимірювання"
#: .\cookbook\models.py:1469
msgid "Keyword Alias"
msgstr "Псевдонім ключового слова"
#: .\cookbook\models.py:1470
msgid "Description Replace"
msgstr "Замінити опис"
#: .\cookbook\models.py:1471
msgid "Instruction Replace"
msgstr "Замінити інструкції"
#: .\cookbook\models.py:1472
msgid "Never Unit"
msgstr "Без одиниц вимірнювання"
#: .\cookbook\models.py:1473
msgid "Transpose Words"
msgstr "Переставити слова"
#: .\cookbook\models.py:1474
msgid "Food Replace"
msgstr "Заміна їжі"
#: .\cookbook\models.py:1475
msgid "Unit Replace"
msgstr "Заміна одиниці вимірювання"
#: .\cookbook\models.py:1476
msgid "Name Replace"
msgstr "Заміна назви"
#: .\cookbook\models.py:1503 .\cookbook\views\delete.py:40
#: .\cookbook\views\edit.py:210 .\cookbook\views\new.py:39
msgid "Recipe"
msgstr "Рецепт"
#: .\cookbook\models.py:1504
msgid "Food"
msgstr "Інгредієнти"
#: .\cookbook\models.py:1505 .\cookbook\templates\base.html:149
msgid "Keyword"
msgstr "Ключове слово"
#: .\cookbook\serializer.py:222
msgid "File uploads are not enabled for this Space."
msgstr "Завантаження файлів не дозволено в цьому просторі."
#: .\cookbook\serializer.py:233
msgid "You have reached your file upload limit."
msgstr "Ви досягли ліміту завантаження файлів."
#: .\cookbook\serializer.py:328
msgid "Cannot modify Space owner permission."
msgstr "Неможливо змінити права власника простору."
#: .\cookbook\serializer.py:1270
msgid "Hello"
msgstr "Вітаю"
#: .\cookbook\serializer.py:1270
msgid "You have been invited by "
msgstr "Вас було запрошено "
#: .\cookbook\serializer.py:1272
msgid " to join their Tandoor Recipes space "
msgstr " долучитись до їхнього Tandoor Рецепти простору "
#: .\cookbook\serializer.py:1274
msgid "Click the following link to activate your account: "
msgstr "Натісніть на посилання щоб активувати ваш акаунт: "
#: .\cookbook\serializer.py:1276
msgid ""
"If the link does not work use the following code to manually join the space: "
msgstr ""
"Якщо посилання не працює, використовуйте код, щоб самостійно доєднатись до "
"простору: "
#: .\cookbook\serializer.py:1278
msgid "The invitation is valid until "
msgstr "Запрощення дійсне до "
#: .\cookbook\serializer.py:1280
msgid ""
"Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub "
msgstr ""
"Tandoor Рецепти — це менеджер рецептів з відкритим кодом. Перегляньте його "
"на GitHub. "
#: .\cookbook\serializer.py:1283
msgid "Tandoor Recipes Invite"
msgstr "Tandoor Рецепти запрошення"
#: .\cookbook\serializer.py:1426
msgid "Existing shopping list to update"
msgstr "Існуючий список покупок для оновлення"
#: .\cookbook\serializer.py:1428
msgid ""
"List of ingredient IDs from the recipe to add, if not provided all "
"ingredients will be added."
msgstr ""
"Список ID інгредієнтів з рецепту, які потрібно додати. Якщо не обрані, "
"будуть додані всі інгредієнти."
#: .\cookbook\serializer.py:1430
msgid ""
"Providing a list_recipe ID and servings of 0 will delete that shopping list."
msgstr ""
"Вказавши list_recipe ID та кількість порцій 0, ви видалите цей список "
"покупок."
#: .\cookbook\serializer.py:1439
msgid "Amount of food to add to the shopping list"
msgstr "Кількість продуктів, які потрібно додати до списку покупок"
#: .\cookbook\serializer.py:1441
msgid "ID of unit to use for the shopping list"
msgstr "ID одиниці вимірювання, що використовується для списку покупок"
#: .\cookbook\serializer.py:1443
msgid "When set to true will delete all food from active shopping lists."
msgstr ""
"Якщо встановлено значення true, всі продукти будуть видалені з активних "
"списків покупок."
#: .\cookbook\tables.py:69 .\cookbook\tables.py:83
#: .\cookbook\templates\generic\delete_template.html:7
#: .\cookbook\templates\generic\delete_template.html:15
#: .\cookbook\templates\generic\edit_template.html:28
msgid "Delete"
msgstr "Видалити"
#: .\cookbook\templates\404.html:5
msgid "404 Error"
msgstr "Помилка 404"
#: .\cookbook\templates\404.html:18
msgid "The page you are looking for could not be found."
msgstr "Сторінка, яку ви шукаєте, не знайдена."
#: .\cookbook\templates\404.html:33
msgid "Take me Home"
msgstr "На головну"
#: .\cookbook\templates\404.html:35
msgid "Report a Bug"
msgstr "Повідомити про помилку"
#: .\cookbook\templates\account\email.html:6
#: .\cookbook\templates\account\email.html:17
msgid "E-mail Addresses"
msgstr "Адреси e-mail"
#: .\cookbook\templates\account\email.html:12
#: .\cookbook\templates\account\password_change.html:11
#: .\cookbook\templates\account\password_set.html:11
#: .\cookbook\templates\base.html:331 .\cookbook\templates\settings.html:6
#: .\cookbook\templates\settings.html:17
#: .\cookbook\templates\socialaccount\connections.html:10
#: .\cookbook\templates\user_settings.html:8
msgid "Settings"
msgstr "Налаштування"
#: .\cookbook\templates\account\email.html:13
msgid "Email"
msgstr "Email"
#: .\cookbook\templates\account\email.html:19
msgid "The following e-mail addresses are associated with your account:"
msgstr "Наступні адреси email пов'язані з вашим обліковим записом:"
#: .\cookbook\templates\account\email.html:36
msgid "Verified"
msgstr "Перевірено"
#: .\cookbook\templates\account\email.html:38
msgid "Unverified"
msgstr "Неперевірено"
#: .\cookbook\templates\account\email.html:40
msgid "Primary"
msgstr "Головний"
#: .\cookbook\templates\account\email.html:47
msgid "Make Primary"
msgstr "Зробити головним"
#: .\cookbook\templates\account\email.html:49
msgid "Re-send Verification"
msgstr "Повторно надіслати підтвердження"
#: .\cookbook\templates\account\email.html:50
#: .\cookbook\templates\generic\delete_template.html:57
#: .\cookbook\templates\socialaccount\connections.html:44
msgid "Remove"
msgstr "Прибрати"
#: .\cookbook\templates\account\email.html:58
msgid "Warning:"
msgstr "Попередження:"
#: .\cookbook\templates\account\email.html:58
msgid ""
"You currently do not have any e-mail address set up. You should really add "
"an e-mail address so you can receive notifications, reset your password, etc."
msgstr ""
"Наразі у вас не налаштовано жодної адреси електронної пошти. Вам слід додати "
"адресу електронної пошти, щоб отримувати сповіщення, скидати пароль тощо."
#: .\cookbook\templates\account\email.html:64
msgid "Add E-mail Address"
msgstr "Додати адресу електронної пошти"
#: .\cookbook\templates\account\email.html:69
msgid "Add E-mail"
msgstr "Додати електрону пошту"
#: .\cookbook\templates\account\email.html:79
msgid "Do you really want to remove the selected e-mail address?"
msgstr "Ви дійсно хочете видалити вибрану адресу електронної пошти?"
#: .\cookbook\templates\account\email_confirm.html:6
#: .\cookbook\templates\account\email_confirm.html:10
msgid "Confirm E-mail Address"
msgstr "Підтвердити адресу електронної пошти"
#: .\cookbook\templates\account\email_confirm.html:16
#, python-format
msgid ""
"Please confirm that\n"
" %(email)s is an e-mail address "
"for user %(user_display)s\n"
" ."
msgstr ""
"Будь ласка, підтвердьте\n"
" %(email)s e-mail для "
"користувача %(user_display)s\n"
" ."
#: .\cookbook\templates\account\email_confirm.html:22
#: .\cookbook\templates\generic\delete_template.html:72
msgid "Confirm"
msgstr "Підтвердити"
#: .\cookbook\templates\account\email_confirm.html:29
#, python-format
msgid ""
"This e-mail confirmation link expired or is invalid. Please\n"
" issue a new e-mail confirmation "
"request."
msgstr ""
"Це посилання для підтвердження електронної пошти прострочено або недійсне. "
"Будь ласка,\n"
" надішліть новий запит на підтвердження "
"електронної пошти."
#: .\cookbook\templates\account\login.html:8 .\cookbook\templates\base.html:388
#: .\cookbook\templates\openid\login.html:8
msgid "Login"
msgstr "Вхід"
#: .\cookbook\templates\account\login.html:15
#: .\cookbook\templates\account\login.html:31
#: .\cookbook\templates\account\password_reset.html:39
#: .\cookbook\templates\account\password_reset_done.html:31
#: .\cookbook\templates\account\signup.html:69
#: .\cookbook\templates\account\signup_closed.html:15
#: .\cookbook\templates\openid\login.html:15
#: .\cookbook\templates\openid\login.html:26
#: .\cookbook\templates\socialaccount\authentication_error.html:15
msgid "Sign In"
msgstr "Увійти"
#: .\cookbook\templates\account\login.html:34
#: .\cookbook\templates\account\password_reset.html:41
#: .\cookbook\templates\account\password_reset_done.html:33
#: .\cookbook\templates\socialaccount\signup.html:8
#: .\cookbook\templates\socialaccount\signup.html:57
msgid "Sign Up"
msgstr "Зареєструватись"
#: .\cookbook\templates\account\login.html:38
msgid "Lost your password?"
msgstr "Загубили пароль?"
#: .\cookbook\templates\account\login.html:39
#: .\cookbook\templates\account\password_reset.html:29
msgid "Reset My Password"
msgstr "Скинути мій пароль"
#: .\cookbook\templates\account\login.html:50
msgid "Social Login"
msgstr "Вхід через соцмережи"
#: .\cookbook\templates\account\login.html:51
msgid "You can use any of the following providers to sign in."
msgstr "Ви можете використовувати будь-що з наступного для входу."
#: .\cookbook\templates\account\logout.html:5
#: .\cookbook\templates\account\logout.html:9
#: .\cookbook\templates\account\logout.html:18
msgid "Sign Out"
msgstr "Вихід"
#: .\cookbook\templates\account\logout.html:11
msgid "Are you sure you want to sign out?"
msgstr "Ви впевнені, що хочете вийти?"
#: .\cookbook\templates\account\password_change.html:6
#: .\cookbook\templates\account\password_change.html:16
#: .\cookbook\templates\account\password_change.html:21
#: .\cookbook\templates\account\password_reset_from_key.html:7
#: .\cookbook\templates\account\password_reset_from_key.html:13
#: .\cookbook\templates\account\password_reset_from_key_done.html:7
#: .\cookbook\templates\account\password_reset_from_key_done.html:13
msgid "Change Password"
msgstr "Змінити пароль"
#: .\cookbook\templates\account\password_change.html:12
#: .\cookbook\templates\account\password_set.html:12
msgid "Password"
msgstr "Пароль"
#: .\cookbook\templates\account\password_change.html:22
msgid "Forgot Password?"
msgstr "Забули пароль?"
#: .\cookbook\templates\account\password_reset.html:7
#: .\cookbook\templates\account\password_reset.html:13
#: .\cookbook\templates\account\password_reset_done.html:7
#: .\cookbook\templates\account\password_reset_done.html:18
msgid "Password Reset"
msgstr "Скинути пароль"
#: .\cookbook\templates\account\password_reset.html:24
msgid ""
"Forgotten your password? Enter your e-mail address below, and we'll send you "
"an e-mail allowing you to reset it."
msgstr ""
"Забули пароль? Введіть свою адресу електронної пошти нижче, і ми надішлемо "
"вам електронного листа, який дозволить вам його змінити."
#: .\cookbook\templates\account\password_reset.html:32
msgid "Password reset is disabled on this instance."
msgstr "Скидання пароля на цьому сервері відключено."
#: .\cookbook\templates\account\password_reset_done.html:25
msgid ""
"We have sent you an e-mail. Please contact us if you do not receive it "
"within a few minutes."
msgstr ""
"Ми надіслали вам електронного листа. Якщо ви не отримаєте його протягом "
"декількох хвилин, зв'яжіться з нами."
#: .\cookbook\templates\account\password_reset_from_key.html:13
msgid "Bad Token"
msgstr "Некоректний токен"
#: .\cookbook\templates\account\password_reset_from_key.html:25
#, python-format
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used.\n"
" Please request a new "
"password reset."
msgstr ""
"Посилання для скидання пароля було недійсним, можливо, тому що воно вже було "
"використано.\n"
" Будь ласка, запросіть нове посилання для скидання пароля."
#: .\cookbook\templates\account\password_reset_from_key.html:33
msgid "change password"
msgstr "змінити пароль"
#: .\cookbook\templates\account\password_reset_from_key.html:36
#: .\cookbook\templates\account\password_reset_from_key_done.html:19
msgid "Your password is now changed."
msgstr "Ваш пароль тепер змінено."
#: .\cookbook\templates\account\password_set.html:6
#: .\cookbook\templates\account\password_set.html:16
#: .\cookbook\templates\account\password_set.html:21
msgid "Set Password"
msgstr "Встановити пароль"
#: .\cookbook\templates\account\signup.html:6
msgid "Register"
msgstr "Зареєструватись"
#: .\cookbook\templates\account\signup.html:12
msgid "Create an Account"
msgstr "Створити акаунт"
#: .\cookbook\templates\account\signup.html:42
#: .\cookbook\templates\socialaccount\signup.html:33
msgid "I accept the follwoing"
msgstr "Я приймаю наступне"
#: .\cookbook\templates\account\signup.html:45
#: .\cookbook\templates\socialaccount\signup.html:36
msgid "Terms and Conditions"
msgstr "Умови та положення"
#: .\cookbook\templates\account\signup.html:48
#: .\cookbook\templates\socialaccount\signup.html:39
msgid "and"
msgstr "і"
#: .\cookbook\templates\account\signup.html:52
#: .\cookbook\templates\socialaccount\signup.html:43
msgid "Privacy Policy"
msgstr "Політика конфіденційності"
#: .\cookbook\templates\account\signup.html:65
msgid "Create User"
msgstr "Створити користувача"
#: .\cookbook\templates\account\signup.html:69
msgid "Already have an account?"
msgstr "Вже є акаунт?"
#: .\cookbook\templates\account\signup_closed.html:5
#: .\cookbook\templates\account\signup_closed.html:11
msgid "Sign Up Closed"
msgstr "Реєстрація закрита"
#: .\cookbook\templates\account\signup_closed.html:13
msgid "We are sorry, but the sign up is currently closed."
msgstr "На жаль, реєстрація наразі закрита."
#: .\cookbook\templates\api_info.html:5 .\cookbook\templates\base.html:378
#: .\cookbook\templates\rest_framework\api.html:11
msgid "API Documentation"
msgstr "API документація"
#: .\cookbook\templates\base.html:110 .\cookbook\templates\index.html:87
msgid "Recipes"
msgstr "Рецепти"
#: .\cookbook\templates\base.html:161 .\cookbook\views\lists.py:120
msgid "Foods"
msgstr "Продукти"
#: .\cookbook\templates\base.html:173 .\cookbook\views\lists.py:137
msgid "Units"
msgstr "Одиниці вимірювання"
#: .\cookbook\templates\base.html:187
msgid "Supermarket"
msgstr "Супермаркет"
#: .\cookbook\templates\base.html:199
msgid "Supermarket Category"
msgstr "Категорія супермаркетів"
#: .\cookbook\templates\base.html:211 .\cookbook\views\lists.py:186
msgid "Automations"
msgstr "Автоматизації"
#: .\cookbook\templates\base.html:225 .\cookbook\views\lists.py:222
msgid "Files"
msgstr "Файли"
#: .\cookbook\templates\base.html:237
msgid "Batch Edit"
msgstr "Групове редагування"
#: .\cookbook\templates\base.html:249 .\cookbook\templates\history.html:6
#: .\cookbook\templates\history.html:14
msgid "History"
msgstr "Історія"
#: .\cookbook\templates\base.html:263
#: .\cookbook\templates\ingredient_editor.html:7
#: .\cookbook\templates\ingredient_editor.html:13
msgid "Ingredient Editor"
msgstr "Редактор Інгредієнтів"
#: .\cookbook\templates\base.html:275
#: .\cookbook\templates\export_response.html:7
#: .\cookbook\templates\test2.html:14 .\cookbook\templates\test2.html:20
msgid "Export"
msgstr "Експорт"
#: .\cookbook\templates\base.html:287
msgid "Properties"
msgstr "Властивості"
#: .\cookbook\templates\base.html:301 .\cookbook\views\lists.py:255
msgid "Unit Conversions"
msgstr "Конвертація одиниць вимірювання"
#: .\cookbook\templates\base.html:318 .\cookbook\templates\index.html:47
msgid "Import Recipe"
msgstr "Імпортувати рецепт"
#: .\cookbook\templates\base.html:320
msgid "Create"
msgstr "Створити"
#: .\cookbook\templates\base.html:333
#: .\cookbook\templates\generic\list_template.html:14
msgid "External Recipes"
msgstr "Зовнішні рецепти"
#: .\cookbook\templates\base.html:336 .\cookbook\templates\space_manage.html:15
msgid "Space Settings"
msgstr "Налаштування простору"
#: .\cookbook\templates\base.html:340
msgid "External Connectors"
msgstr "Зовнішні Конектори"
#: .\cookbook\templates\base.html:345 .\cookbook\templates\system.html:13
msgid "System"
msgstr "Система"
#: .\cookbook\templates\base.html:347
msgid "Admin"
msgstr "Адмін"
#: .\cookbook\templates\base.html:351
#: .\cookbook\templates\space_overview.html:25
msgid "Your Spaces"
msgstr "Ваші простори"
#: .\cookbook\templates\base.html:362
#: .\cookbook\templates\space_overview.html:6
msgid "Overview"
msgstr "Огляд"
#: .\cookbook\templates\base.html:372
msgid "Markdown Guide"
msgstr "Посібник з Markdown"
#: .\cookbook\templates\base.html:374
msgid "GitHub"
msgstr "GitHub"
#: .\cookbook\templates\base.html:376
msgid "Translate Tandoor"
msgstr "Переклад Tandoor"
#: .\cookbook\templates\base.html:380
msgid "API Browser"
msgstr "Браузер API"
#: .\cookbook\templates\base.html:383
msgid "Log out"
msgstr "Вийти"
#: .\cookbook\templates\base.html:406
msgid "You are using the free version of Tandor"
msgstr "Ви використовуєте безкоштовну версію Tandor"
#: .\cookbook\templates\base.html:407
msgid "Upgrade Now"
msgstr "Оновити Зараз"
#: .\cookbook\templates\batch\edit.html:6
msgid "Batch edit Category"
msgstr "Групове редагування Категорії"
#: .\cookbook\templates\batch\edit.html:15
msgid "Batch edit Recipes"
msgstr "Групове редагування Рецептів"
#: .\cookbook\templates\batch\edit.html:20
msgid "Add the specified keywords to all recipes containing a word"
msgstr "Додати вказані ключові слова до всіх рецептів, що містять слово"
#: .\cookbook\templates\batch\monitor.html:6 .\cookbook\views\edit.py:75
msgid "Sync"
msgstr "Синхронізація"
#: .\cookbook\templates\batch\monitor.html:10
msgid "Manage watched Folders"
msgstr "Керувати папками, що відстежуються"
#: .\cookbook\templates\batch\monitor.html:14
msgid ""
"On this Page you can manage all storage folder locations that should be "
"monitored and synced."
msgstr ""
"На цій сторінці ви можете керувати всіма папками для зберігання, які слід "
"контролювати та синхронізувати."
#: .\cookbook\templates\batch\monitor.html:16
msgid "The path must be in the following format"
msgstr "Шлях повинен мати такий формат"
#: .\cookbook\templates\batch\monitor.html:20
#: .\cookbook\templates\forms\edit_import_recipe.html:14
#: .\cookbook\templates\generic\edit_template.html:23
#: .\cookbook\templates\generic\new_template.html:23
#: .\cookbook\templates\settings.html:57
msgid "Save"
msgstr "Зберігти"
#: .\cookbook\templates\batch\monitor.html:21
msgid "Manage External Storage"
msgstr "Керувати зовнішнім сховищем"
#: .\cookbook\templates\batch\monitor.html:28
msgid "Sync Now!"
msgstr "Синхронізувати зараз!"
#: .\cookbook\templates\batch\monitor.html:29
msgid "Show Recipes"
msgstr "Показати рецепти"
#: .\cookbook\templates\batch\monitor.html:30
msgid "Show Log"
msgstr "Показати лог"
#: .\cookbook\templates\batch\waiting.html:4
#: .\cookbook\templates\batch\waiting.html:10
msgid "Importing Recipes"
msgstr "Імпорт рецептів"
#: .\cookbook\templates\batch\waiting.html:28
msgid ""
"This can take a few minutes, depending on the number of recipes in sync, "
"please wait."
msgstr ""
"Це може зайняти кілька хвилин, залежно від кількості синхронізованих "
"рецептів. Будь ласка, зачекайте."
#: .\cookbook\templates\books.html:7
msgid "Recipe Books"
msgstr "Книги рецептів"
#: .\cookbook\templates\export.html:7 .\cookbook\templates\test2.html:6
msgid "Export Recipes"
msgstr "Експорт рецептів"
#: .\cookbook\templates\forms\edit_import_recipe.html:5
#: .\cookbook\templates\forms\edit_import_recipe.html:9
msgid "Import new Recipe"
msgstr "Імпортувати новий рецепт"
#: .\cookbook\templates\forms\edit_internal_recipe.html:7
msgid "Edit Recipe"
msgstr "Редагувати рецепт"
#: .\cookbook\templates\generic\delete_template.html:21
#, python-format
msgid "Are you sure you want to delete the %(title)s: %(object)s "
msgstr "Ви впевнені, що хочете видалити %(title)s: %(object)s? "
#: .\cookbook\templates\generic\delete_template.html:22
msgid "This cannot be undone!"
msgstr "Це не можна скасувати!"
#: .\cookbook\templates\generic\delete_template.html:27
msgid "Protected"
msgstr "Захищено"
#: .\cookbook\templates\generic\delete_template.html:42
msgid "Cascade"
msgstr "Каскад"
#: .\cookbook\templates\generic\delete_template.html:73
msgid "Cancel"
msgstr "Відміна"
#: .\cookbook\templates\generic\edit_template.html:6
#: .\cookbook\templates\generic\edit_template.html:14
msgid "Edit"
msgstr "Редагувати"
#: .\cookbook\templates\generic\edit_template.html:32
msgid "View"
msgstr "Перегляд"
#: .\cookbook\templates\generic\edit_template.html:36
msgid "Delete original file"
msgstr "Видалити оригінальний файл"
#: .\cookbook\templates\generic\list_template.html:6
#: .\cookbook\templates\generic\list_template.html:22
msgid "List"
msgstr "Список"
#: .\cookbook\templates\generic\list_template.html:36
msgid "Filter"
msgstr "Фільтр"
#: .\cookbook\templates\generic\list_template.html:41
msgid "Import all"
msgstr "Імпортувати все"
#: .\cookbook\templates\generic\new_template.html:6
#: .\cookbook\templates\generic\new_template.html:14
msgid "New"
msgstr "Новий"
#: .\cookbook\templates\generic\table_template.html:76
msgid "previous"
msgstr "попередній"
#: .\cookbook\templates\generic\table_template.html:98
msgid "next"
msgstr "наступний"
#: .\cookbook\templates\history.html:20
msgid "View Log"
msgstr "Подивитись лог"
#: .\cookbook\templates\history.html:24
msgid "Cook Log"
msgstr "Лог готування"
#: .\cookbook\templates\import_response.html:7 .\cookbook\views\delete.py:90
#: .\cookbook\views\edit.py:174
msgid "Import"
msgstr "Імпорт"
#: .\cookbook\templates\include\storage_backend_warning.html:4
msgid "Security Warning"
msgstr "Попередження про безпеку"
#: .\cookbook\templates\include\storage_backend_warning.html:5
msgid ""
"\n"
" The Password and Token field are stored as plain text "
"inside the database.\n"
" This is necessary because they are needed to make API requests, but "
"it also increases the risk of\n"
" someone stealing it.
\n"
" To limit the possible damage tokens or accounts with limited access "
"can be used.\n"
" "
msgstr ""
"\n"
" Поля пароль і токен зберігаються як простий текст в "
"базі даних.\n"
" Це необхідно, оскільки вони потрібні для здійснення запитів API, але "
"це також збільшує ризик\n"
" їхнього викрадення
\n"
" Щоб обмежити можливий збиток, можна використовувати токени або "
"облікові записи з обмеженим доступом.\n"
" "
#: .\cookbook\templates\index.html:29
msgid "Search recipe ..."
msgstr "Пошук рецепту..."
#: .\cookbook\templates\index.html:44
msgid "New Recipe"
msgstr "Новий рецепт"
#: .\cookbook\templates\index.html:53
msgid "Advanced Search"
msgstr "Розширений пошук"
#: .\cookbook\templates\index.html:57
msgid "Reset Search"
msgstr "Скинути пошук"
#: .\cookbook\templates\index.html:85
msgid "Last viewed"
msgstr "Останній переглянутий"
#: .\cookbook\templates\index.html:94
msgid "Log in to view recipes"
msgstr "Ввійти щоб подивитись рецепти"
#: .\cookbook\templates\markdown_info.html:5
#: .\cookbook\templates\markdown_info.html:13
msgid "Markdown Info"
msgstr "Інформація про Markdown"
#: .\cookbook\templates\markdown_info.html:14
msgid ""
"\n"
" Markdown is lightweight markup language that can be used to format "
"plain text easily.\n"
" This site uses the Python Markdown library to\n"
" convert your text into nice looking HTML. Its full markdown "
"documentation can be found\n"
" here.\n"
" An incomplete but most likely sufficient documentation can be found "
"below.\n"
" "
msgstr ""
"\n"
" Markdown - проста мова розмітки, яку можна використовувати для "
"простого форматування звичайного тексту.\n"
" Цей сайт використовує бібліотеку Python Markdown "
"для\n"
" перетворення вашого тексту в приємний на вигляд HTML. Повну "
"документацію щодо Markdown можна знайти\n"
" here.\n"
" Неповну, але, найімовірніше, достатню документацію можна знайти "
"нижче.\n"
" "
#: .\cookbook\templates\markdown_info.html:25
msgid "Headers"
msgstr "Заголовки"
#: .\cookbook\templates\markdown_info.html:54
msgid "Formatting"
msgstr "Форматування"
#: .\cookbook\templates\markdown_info.html:56
#: .\cookbook\templates\markdown_info.html:72
msgid "Line breaks are inserted by adding two spaces after the end of a line"
msgstr ""
"Розриви рядків вставляються шляхом додавання двох пробілів після кінця рядка"
#: .\cookbook\templates\markdown_info.html:57
#: .\cookbook\templates\markdown_info.html:73
msgid "or by leaving a blank line in between."
msgstr "або залишивши порожній рядок між ними."
#: .\cookbook\templates\markdown_info.html:59
#: .\cookbook\templates\markdown_info.html:74
msgid "This text is bold"
msgstr "Цей текст виділено жирним шрифтом"
#: .\cookbook\templates\markdown_info.html:60
#: .\cookbook\templates\markdown_info.html:75
msgid "This text is italic"
msgstr "Цей текст виділено курсивом"
#: .\cookbook\templates\markdown_info.html:61
#: .\cookbook\templates\markdown_info.html:77
msgid "Blockquotes are also possible"
msgstr "Також можливі цитати"
#: .\cookbook\templates\markdown_info.html:84
msgid "Lists"
msgstr "Списки"
#: .\cookbook\templates\markdown_info.html:85
msgid ""
"Lists can ordered or unordered. It is important to leave a blank line "
"before the list!"
msgstr ""
"Списки можуть бути впорядкованими або невпорядкованими. Важливо залишати "
"порожній рядок перед списком!"
#: .\cookbook\templates\markdown_info.html:87
#: .\cookbook\templates\markdown_info.html:108
msgid "Ordered List"
msgstr "Упорядкований список"
#: .\cookbook\templates\markdown_info.html:89
#: .\cookbook\templates\markdown_info.html:90
#: .\cookbook\templates\markdown_info.html:91
#: .\cookbook\templates\markdown_info.html:110
#: .\cookbook\templates\markdown_info.html:111
#: .\cookbook\templates\markdown_info.html:112
msgid "unordered list item"
msgstr "елемент неупорядкованого списку"
#: .\cookbook\templates\markdown_info.html:93
#: .\cookbook\templates\markdown_info.html:114
msgid "Unordered List"
msgstr "Неупорядкований список"
#: .\cookbook\templates\markdown_info.html:95
#: .\cookbook\templates\markdown_info.html:96
#: .\cookbook\templates\markdown_info.html:97
#: .\cookbook\templates\markdown_info.html:116
#: .\cookbook\templates\markdown_info.html:117
#: .\cookbook\templates\markdown_info.html:118
msgid "ordered list item"
msgstr "елемент упорядкованого списку"
#: .\cookbook\templates\markdown_info.html:125
msgid "Images & Links"
msgstr "Зображення та посилання"
#: .\cookbook\templates\markdown_info.html:126
msgid ""
"Links can be formatted with Markdown. This application also allows to paste "
"links directly into markdown fields without any formatting."
msgstr ""
"Посилання можна форматувати за допомогою Markdown. Ця програма також "
"дозволяє вставляти посилання безпосередньо в поля Markdown без будь-якого "
"форматування."
#: .\cookbook\templates\markdown_info.html:132
#: .\cookbook\templates\markdown_info.html:145
msgid "This will become an image"
msgstr "Це стане зображенням"
#: .\cookbook\templates\markdown_info.html:152
msgid "Tables"
msgstr "Таблиці"
#: .\cookbook\templates\markdown_info.html:153
msgid ""
"Markdown tables are hard to create by hand. It is recommended to use a table "
"editor like this one."
msgstr ""
"Таблиці Markdown важко створювати вручну. Рекомендується використовувати "
"редактор таблиць, такий якось цей."
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:171
#: .\cookbook\templates\markdown_info.html:177
msgid "Table"
msgstr "Таблиця"
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:172
msgid "Header"
msgstr "Шапка"
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:178
msgid "Cell"
msgstr "Клітинка"
#: .\cookbook\templates\no_groups_info.html:5
#: .\cookbook\templates\no_groups_info.html:12
msgid "No Permissions"
msgstr "Не має дозволів"
#: .\cookbook\templates\no_groups_info.html:17
msgid "You do not have any groups and therefor cannot use this application."
msgstr "У вас немає груп, тому ви не можете користуватися цією програмою."
#: .\cookbook\templates\no_groups_info.html:18
#: .\cookbook\templates\no_perm_info.html:15
msgid "Please contact your administrator."
msgstr "Зверніться до вашого адміністратора."
#: .\cookbook\templates\no_perm_info.html:5
#: .\cookbook\templates\no_perm_info.html:12
msgid "No Permission"
msgstr "Нема доступів"
#: .\cookbook\templates\no_perm_info.html:15
msgid ""
"You do not have the required permissions to view this page or perform this "
"action."
msgstr ""
"Ви не маєте необхідних прав для перегляду цієї сторінки або виконання цієї "
"дії."
#: .\cookbook\templates\offline.html:6
msgid "Offline"
msgstr "Оффлайн"
#: .\cookbook\templates\offline.html:19
msgid "You are currently offline!"
msgstr "Ви наразі оффлайн!"
#: .\cookbook\templates\offline.html:20
msgid ""
"The recipes listed below are available for offline viewing because you have "
"recently viewed them. Keep in mind that data might be outdated."
msgstr ""
"Наведені нижче рецепти доступні для перегляду в автономному режимі, оскільки "
"ви нещодавно їх переглядали. Майте на увазі, що дані можуть бути застарілими."
#: .\cookbook\templates\openid\login.html:27
#: .\cookbook\templates\socialaccount\authentication_error.html:27
msgid "Back"
msgstr "Назад"
#: .\cookbook\templates\property_editor.html:7
msgid "Property Editor"
msgstr "Редактор властивостей"
#: .\cookbook\templates\recipe_view.html:36
msgid "Comments"
msgstr "Коментарі"
#: .\cookbook\templates\recipe_view.html:41
msgid "by"
msgstr " "
#: .\cookbook\templates\recipe_view.html:59 .\cookbook\views\delete.py:146
#: .\cookbook\views\edit.py:156
msgid "Comment"
msgstr "Коментар"
#: .\cookbook\templates\rest_framework\api.html:5
msgid "Recipe Home"
msgstr "Головна рецептів"
#: .\cookbook\templates\search_info.html:5
#: .\cookbook\templates\search_info.html:9
#: .\cookbook\templates\settings.html:24
msgid "Search Settings"
msgstr "Налаштування пошуку"
#: .\cookbook\templates\search_info.html:10
msgid ""
"\n"
" Creating the best search experience is complicated and weighs "
"heavily on your personal configuration. \n"
" Changing any of the search settings can have significant impact on "
"the speed and quality of the results.\n"
" Search Methods, Trigrams and Full Text Search configurations are "
"only available if you are using Postgres for your database.\n"
" "
msgstr ""
"\n"
" Створення найкращого досвіду пошуку є складним завданням і значною "
"мірою залежить від ваших особистих налаштувань. \n"
" Зміна будь-яких налаштувань пошуку може мати значний вплив на "
"швидкість і якість результатів.\n"
" Налаштування методів пошуку, триграм і повнотекстового пошуку "
"доступні лише в тому випадку, якщо ви використовуєте Postgres для своєї бази "
"даних.\n"
" "
#: .\cookbook\templates\search_info.html:19
msgid "Search Methods"
msgstr "Методи пошуку"
#: .\cookbook\templates\search_info.html:23
msgid ""
" \n"
" Full text searches attempt to normalize the words provided to "
"match common variants. For example: 'forked', 'forking', 'forks' will all "
"normalize to 'fork'.\n"
" There are several methods available, described below, that will "
"control how the search behavior should react when multiple words are "
"searched.\n"
" Full technical details on how these operate can be viewed on Postgresql's website.\n"
" "
msgstr ""
" \n"
" FПовний текстовий пошук намагається нормалізувати введені слова, "
"щоб вони відповідали поширеним варіантам. Наприклад: «forked», «forking», "
"«forks» будуть нормалізовані до «fork».\n"
" Існує кілька методів, описаних нижче, які контролюють поведінку "
"пошуку при пошуку декількох слів.\n"
" Повні технічні деталі щодо їхнього функціонування можна "
"переглянути на Postgresql's website.\n"
" "
#: .\cookbook\templates\search_info.html:29
msgid ""
" \n"
" Simple searches ignore punctuation and common words such as "
"'the', 'a', 'and'. And will treat separate words as required.\n"
" Searching for 'apple or flour' will return any recipe that "
"includes both 'apple' and 'flour' anywhere in the fields that have been "
"selected for a full text search.\n"
" "
msgstr ""
" \n"
" Простий пошук ігнорує розділові знаки та загальні слова, такі як "
"«та», «і», «з». І буде розглядати окремі слова як потрібно.\n"
" Пошук за «яблуко or борошно» поверне будь-який рецепт, який "
"містить як «яблуко», так і «борошно» в будь-якому місці полів, що були "
"вибрані для повнотекстового пошуку.\n"
" "
#: .\cookbook\templates\search_info.html:34
msgid ""
" \n"
" Phrase searches ignore punctuation, but will search for all of "
"the words in the exact order provided.\n"
" Searching for 'apple or flour' will only return a recipe that "
"includes the exact phrase 'apple or flour' in any of the fields that have "
"been selected for a full text search.\n"
" "
msgstr ""
" \n"
" Пошук за фразою ігнорує розділові знаки, але шукає всі слова в "
"точно заданому порядку.\n"
" Пошук за запитом «яблуко or борошно» поверне тільки рецепти, які "
"містять точну фразу «яблуко або борошно» в будь-якому з полів, вибраних для "
"повнотекстового пошуку\n"
" "
#: .\cookbook\templates\search_info.html:39
msgid ""
" \n"
" Web searches simulate functionality found on many web search "
"sites supporting special syntax.\n"
" Placing quotes around several words will convert those words "
"into a phrase.\n"
" 'or' is recognized as searching for the word (or phrase) "
"immediately before 'or' OR the word (or phrase) directly after.\n"
" '-' is recognized as searching for recipes that do not include "
"the word (or phrase) that comes immediately after. \n"
" For example searching for 'apple pie' or cherry -butter will "
"return any recipe that includes the phrase 'apple pie' or the word "
"'cherry' \n"
" in any field included in the full text search but exclude any "
"recipe that has the word 'butter' in any field included.\n"
" "
msgstr ""
" \n"
" Веб-пошук імітує функціональність багатьох веб-пошукових сайтів, "
"що підтримують спеціальний синтакс пошуку.\n"
" Якщо кілька слів взяти в лапки, вони перетворяться на фразу.\n"
" 'or' розпізнається як пошук слова (або фрази), що стоїть "
"безпосередньо перед “or”, АБО слова (або фрази), що стоїть безпосередньо "
"після нього.\n"
" Символ '-' розпізнається як пошук рецептів, які не містять "
"слова (або фрази), що йде безпосередньо після нього. \n"
" Наприклад, пошук «яблучний пиріг» or вишня -масло поверне будь-"
"який рецепт, що містить фразу «яблучний пиріг» або слово «вишня» \n"
" в будь-якому полі, включеному в повнотекстовий пошук, але "
"виключить будь-який рецепт, що містить слово «масло» в будь-якому полі.\n"
" "
#: .\cookbook\templates\search_info.html:48
msgid ""
" \n"
" Raw search is similar to Web except will take puncuation "
"operators such as '|', '&' and '()'\n"
" "
msgstr ""
" \n"
" Простий пошук схожий на веб-пошук, за винятком того, що він "
"приймає оператори пунктуації, такі як '|', '&' and '()'\n"
" "
#: .\cookbook\templates\search_info.html:59
msgid ""
" \n"
" Another approach to searching that also requires Postgresql is "
"fuzzy search or trigram similarity. A trigram is a group of three "
"consecutive characters.\n"
" For example searching for 'apple' will create x trigrams 'app', "
"'ppl', 'ple' and will create a score of how closely words match the "
"generated trigrams.\n"
" One benefit of searching trigams is that a search for 'sandwich' "
"will find misspelled words such as 'sandwhich' that would be missed by other "
"methods.\n"
" "
msgstr ""
" \n"
" Іншим підходом до пошуку, який також вимагає Postgresql, є "
"нечіткий пошук або схожість триграм. Триграма - це група з трьох послідовних "
"символів.\n"
" Наприклад, пошук слова «яблуко» створить x триграм «ябл», «блу», "
"«лук», «уко»і обчислить ступінь відповідності слів згенерованим триграм.\n"
" Однією з переваг пошуку за триграмами є те, що пошук слова «піца»"
" знайде слова з помилками, такі як «піцца», які не були б знайдені іншими "
"методами.\n"
" "
#: .\cookbook\templates\search_info.html:69
msgid "Search Fields"
msgstr "Поля пошуку"
#: .\cookbook\templates\search_info.html:73
msgid ""
" \n"
" Unaccent is a special case in that it enables searching a field "
"'unaccented' for each search style attempting to ignore accented values. \n"
" For example when you enable unaccent for 'Name' any search "
"(starts with, contains, trigram) will attempt the search ignoring accented "
"characters.\n"
" \n"
" For the other options, you can enable search on any or all "
"fields and they will be combined together with an assumed 'OR'.\n"
" For example enabling 'Name' for Starts With, 'Name' and "
"'Description' for Partial Match and 'Ingredients' and 'Keywords' for Full "
"Search\n"
" and searching for 'apple' will generate a search that will "
"return recipes that have:\n"
" - A recipe name that starts with 'apple'\n"
" - OR a recipe name that contains 'apple'\n"
" - OR a recipe description that contains 'apple'\n"
" - OR a recipe that will have a full text search match ('apple' "
"or 'apples') in ingredients\n"
" - OR a recipe that will have a full text search match in "
"Keywords\n"
"\n"
" Combining too many fields in too many types of search can have a "
"negative impact on performance, create duplicate results or return "
"unexpected results.\n"
" For example, enabling fuzzy search or partial matches will "
"interfere with web search methods. \n"
" Searching for 'apple -pie' with fuzzy search and full text "
"search will return the recipe Apple Pie. Though it is not included in the "
"full text results, it does match the trigram results.\n"
" "
msgstr ""
" \n"
" Безакцентовий пошук є особливим випадком, оскільки дозволяє "
"здійснювати пошук у полі «без акцентів» для кожного стилю пошуку, "
"намагаючись ігнорувати значення з акцентами. \n"
" Наприклад, коли ви вмикаєте безакцентовий пошук для поля «Назва»"
", будь-який пошук (\"починається з\", \"містить\", \"триграма\") буде "
"намагатися здійснити пошук, ігноруючи символи з акцентами.\n"
" \n"
" Для інших опцій ви можете ввімкнути пошук у будь-якому або всіх "
"полях, і вони будуть об'єднані разом із припущенням 'OR'.\n"
" Наприклад, увімкнувши поле «Назва» для пошуку «Починається з», "
"«Назва» та «Опис» для пошуку «Часткове співпадіння» та «Інгредієнти» та «"
"Ключові слова» для пошуку «Повний пошук»\n"
" і шукаючи «яблуко», буде згенеровано пошук, який поверне "
"рецепти, що мають:\n"
" - Назва рецепта, що починається з «яблуко»\n"
" - АБО назва рецепта, що містить «яблуко»\n"
" - АБО опис рецепта, що містить «яблуко»\n"
" - АБО рецепт, що має повний текстовий збіг "
"(«яблуко» або «яблука») в інгредієнтах\n"
" - АБО рецепт, що має повний текстовий збіг у ключових словах\n"
"\n"
" Поєднання занадто багатьох полів у занадто багатьох типах пошуку "
"може негативно вплинути на продуктивність, створити дублікати результатів "
"або повернути несподівані результати.\n"
" Наприклад, увімкнення нечіткого пошуку або часткових збігів "
"заважатиме методам веб-пошуку. \n"
" Пошук «яблучний -пиріг» з нечітким пошуком і повнотекстовим "
"пошуком поверне рецепт «Яблучного Пиріг». Хоча він не включений у "
"результати повнотекстового пошуку, він відповідає результатам триграми.\n"
" "
#: .\cookbook\templates\search_info.html:95
msgid "Search Index"
msgstr "Пошуковий індекс"
#: .\cookbook\templates\search_info.html:99
msgid ""
" \n"
" Trigram search and Full Text Search both rely on database "
"indexes to perform effectively. \n"
" You can rebuild the indexes on all fields in the Admin page for "
"Recipes and selecting all recipes and running 'rebuild index for selected "
"recipes'\n"
" You can also rebuild indexes at the command line by executing "
"the management command 'python manage.py rebuildindex'\n"
" "
msgstr ""
" \n"
" Ефективна робота триграмного пошуку та повнотекстового пошуку "
"залежить від індексів бази даних. \n"
" Ви можете перебудувати індекси для всіх полів на сторінці "
"адміністрування рецептів, вибравши всі рецепти та запустивши функцію «"
"Перебудувати індекс для вибраних рецептів»\n"
" Ви також можете перебудувати індекси в командному рядку, "
"виконавши команду управління «python manage.py rebuildindex»\n"
" "
#: .\cookbook\templates\settings.html:25
msgid ""
"There are many options to configure the search depending on your personal "
"preferences."
msgstr ""
"Існує багато варіантів налаштування пошуку залежно від ваших особистих "
"уподобань."
#: .\cookbook\templates\settings.html:26
msgid ""
"Usually you do not need to configure any of them and can just stick "
"with either the default or one of the following presets."
msgstr ""
"Зазвичай вам не потрібно налаштовувати жодного з них і можна просто "
"залишити стандартні налаштування або вибрати один із наступних пресетів."
#: .\cookbook\templates\settings.html:27
msgid ""
"If you do want to configure the search you can read about the different "
"options here."
msgstr ""
"Якщо ви дійсно хочете налаштувати пошук, ви можете прочитати про різні опції "
"тут."
#: .\cookbook\templates\settings.html:32
msgid "Fuzzy"
msgstr "Нечіткий"
#: .\cookbook\templates\settings.html:33
msgid ""
"Find what you need even if your search or the recipe contains typos. Might "
"return more results than needed to make sure you find what you are looking "
"for."
msgstr ""
"Знайдіть те, що вам потрібно, навіть якщо у вашому пошуку або рецепті є "
"помилки. Можливо, буде повернуто більше результатів, ніж потрібно, щоб ви "
"точно знайшли те, що шукаєте."
#: .\cookbook\templates\settings.html:34
msgid "This is the default behavior"
msgstr "Це поведінка за замовчуванням"
#: .\cookbook\templates\settings.html:37 .\cookbook\templates\settings.html:46
msgid "Apply"
msgstr "Застосувати"
#: .\cookbook\templates\settings.html:42
msgid "Precise"
msgstr "Точний"
#: .\cookbook\templates\settings.html:43
msgid ""
"Allows fine control over search results but might not return results if too "
"many spelling mistakes are made."
msgstr ""
"Дозволяє точно контролювати результати пошуку, але може не повертати "
"результати, якщо зроблено занадто багато орфографічних помилок."
#: .\cookbook\templates\settings.html:44
msgid "Perfect for large Databases"
msgstr "Ідеально для великих баз даних"
#: .\cookbook\templates\setup.html:6 .\cookbook\templates\system.html:5
msgid "Cookbook Setup"
msgstr "Налаштування кулінарної книги"
#: .\cookbook\templates\setup.html:14
msgid "Setup"
msgstr "Початкое налаштування"
#: .\cookbook\templates\setup.html:15
msgid ""
"To start using this application you must first create a superuser account."
msgstr ""
"Щоб почати користуватися цією програмою, спочатку потрібно створити "
"обліковий запис суперкористувача."
#: .\cookbook\templates\setup.html:20
msgid "Create Superuser account"
msgstr "Створити акаунт Суперкористувача"
#: .\cookbook\templates\socialaccount\authentication_error.html:7
#: .\cookbook\templates\socialaccount\authentication_error.html:23
msgid "Social Network Login Failure"
msgstr "Помилка під час входу в соціальну мережу"
#: .\cookbook\templates\socialaccount\authentication_error.html:25
msgid ""
"An error occurred while attempting to login via your social network account."
msgstr ""
"Під час спроби увійти через ваш обліковий запис у соціальній мережі сталася "
"помилка."
#: .\cookbook\templates\socialaccount\connections.html:4
#: .\cookbook\templates\socialaccount\connections.html:15
msgid "Account Connections"
msgstr "Підключення облікових записів"
#: .\cookbook\templates\socialaccount\connections.html:11
msgid "Social"
msgstr "Соцмережи"
#: .\cookbook\templates\socialaccount\connections.html:18
msgid ""
"You can sign in to your account using any of the following third party\n"
" accounts:"
msgstr ""
"Ви можете увійти у свій обліковий запис, використовуючи будь-який із "
"наступних облікових записів\n"
" сторонніх сервісів:"
#: .\cookbook\templates\socialaccount\connections.html:52
msgid ""
"You currently have no social network accounts connected to this account."
msgstr "У вас зараз не має акаунтів соцмереж прив'язаних до цього акаунту."
#: .\cookbook\templates\socialaccount\connections.html:55
msgid "Add a 3rd Party Account"
msgstr "Додати сторонній акаунт"
#: .\cookbook\templates\socialaccount\login.html:5
#: .\cookbook\templates\socialaccount\signup.html:5
msgid "Signup"
msgstr "Реєстрація"
#: .\cookbook\templates\socialaccount\login.html:9
#, python-format
msgid "Connect %(provider)s"
msgstr "Підключити %(provider)s"
#: .\cookbook\templates\socialaccount\login.html:11
#, python-format
msgid "You are about to connect a new third party account from %(provider)s."
msgstr "Ви збираєтеся підключити новий обліковий запис від %(provider)s."
#: .\cookbook\templates\socialaccount\login.html:13
#, python-format
msgid "Sign In Via %(provider)s"
msgstr "Увійти через %(provider)s"
#: .\cookbook\templates\socialaccount\login.html:15
#, python-format
msgid "You are about to sign in using a third party account from %(provider)s."
msgstr "Ви збираєтеся увійти за допомогою облікового запису від %(provider)s."
#: .\cookbook\templates\socialaccount\login.html:20
msgid "Continue"
msgstr "Продовжити"
#: .\cookbook\templates\socialaccount\signup.html:10
#, python-format
msgid ""
"You are about to use your\n"
" %(provider_name)s account to login to\n"
" %(site_name)s. As a final step, please complete the following form:"
msgstr ""
"Ви збираєтесь використати свій\n"
" %(provider_name)s акаунт щоб увійти в\n"
" %(site_name)s. В якості останнього кроку, будь ласка, заповніть "
"наступну форму:"
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:23
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:31
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:39
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:47
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:55
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:63
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:71
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:79
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:87
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:95
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:103
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:111
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:119
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:127
msgid "Sign in using"
msgstr "Увійти, використовуючи"
#: .\cookbook\templates\space_manage.html:7
msgid "Space Management"
msgstr "Управління простором"
#: .\cookbook\templates\space_manage.html:26
msgid "Space:"
msgstr "Простір:"
#: .\cookbook\templates\space_manage.html:27
msgid "Manage Subscription"
msgstr "Управління підписками"
#: .\cookbook\templates\space_overview.html:13 .\cookbook\views\delete.py:184
msgid "Space"
msgstr "Простір"
#: .\cookbook\templates\space_overview.html:17
msgid ""
"Recipes, foods, shopping lists and more are organized in spaces of one or "
"more people."
msgstr ""
"Рецепти, продукти харчування, списки покупок та інше організовані в "
"просторах для однієї або декількох осіб."
#: .\cookbook\templates\space_overview.html:18
msgid ""
"You can either be invited into an existing space or create your own one."
msgstr "Ви можете бути запрошені в існуючий простір або створити свій власний."
#: .\cookbook\templates\space_overview.html:53
msgid "Owner"
msgstr "Власник"
#: .\cookbook\templates\space_overview.html:57
msgid "Leave Space"
msgstr "Покинути простір"
#: .\cookbook\templates\space_overview.html:78
#: .\cookbook\templates\space_overview.html:88
msgid "Join Space"
msgstr "Доєднатися до простроу"
#: .\cookbook\templates\space_overview.html:81
msgid "Join an existing space."
msgstr "Доєднатися до існуючого простору."
#: .\cookbook\templates\space_overview.html:83
msgid ""
"To join an existing space either enter your invite token or click on the "
"invite link the space owner send you."
msgstr ""
"Щоб приєднатися до існуючого простору, введіть свій токен запрошення або "
"натисніть на посилання запрошення, яке вам надіслав власник простору."
#: .\cookbook\templates\space_overview.html:96
#: .\cookbook\templates\space_overview.html:105
msgid "Create Space"
msgstr "Створити простір"
#: .\cookbook\templates\space_overview.html:99
msgid "Create your own recipe space."
msgstr "Створіть власний простір для рецептів."
#: .\cookbook\templates\space_overview.html:101
msgid "Start your own recipe space and invite other users to it."
msgstr ""
"Створіть власний простір для рецептів і запросіть до нього інших "
"користувачів."
#: .\cookbook\templates\system.html:14
msgid ""
"\n"
" Django Recipes is an open source free software application. It can "
"be found on\n"
" GitHub.\n"
" Changelogs can be found here.\n"
" "
msgstr ""
"\n"
" Django Recipes - це безкоштовна програма з відкритим кодом. Її можна "
"знайти на\n"
" GitHub.\n"
" Список змін знаходиться тут.\n"
" "
#: .\cookbook\templates\system.html:20
msgid "System Information"
msgstr "Інформація про систему"
#: .\cookbook\templates\system.html:41
msgid ""
"\n"
" You need to execute version.py in your update "
"script to generate version information (done automatically in docker).\n"
" "
msgstr ""
"\n"
" Вам потрібно виконатиversion.py iу вашому скрипті "
"оновлення, щоб згенерувати інформацію про версію "
"(у Docker це робиться автоматично).\n"
" "
#: .\cookbook\templates\system.html:46
msgid "Media Serving"
msgstr "Медіа-сервіс"
#: .\cookbook\templates\system.html:47 .\cookbook\templates\system.html:61
#: .\cookbook\templates\system.html:75 .\cookbook\templates\system.html:88
#: .\cookbook\templates\system.html:102 .\cookbook\templates\system.html:113
msgid "Warning"
msgstr "Застереження"
#: .\cookbook\templates\system.html:47 .\cookbook\templates\system.html:61
#: .\cookbook\templates\system.html:75 .\cookbook\templates\system.html:88
#: .\cookbook\templates\system.html:104 .\cookbook\templates\system.html:113
msgid "Ok"
msgstr "Ок"
#: .\cookbook\templates\system.html:49
msgid ""
"Serving media files directly using gunicorn/python is not recommend!\n"
" Please follow the steps described\n"
" here to update\n"
" your installation.\n"
" "
msgstr ""
"Осблуговування медіа-файлів напряму з gunicorn/python не "
"рекомендується!\n"
" Будь ласка, виконайте кроки, описані\n"
" тут щоб "
"оновити\n"
" вашу інсталяцію.\n"
" "
#: .\cookbook\templates\system.html:55 .\cookbook\templates\system.html:70
#: .\cookbook\templates\system.html:83 .\cookbook\templates\system.html:94
#: .\cookbook\views\views.py:303
msgid "Everything is fine!"
msgstr "Все добре!"
#: .\cookbook\templates\system.html:59
msgid "Secret Key"
msgstr "Секретний ключ"
#: .\cookbook\templates\system.html:63
msgid ""
"\n"
" You do not have a SECRET_KEY configured in your "
".env file. Django defaulted to the\n"
" standard key\n"
" provided with the installation which is publicly know and "
"insecure! Please set\n"
" SECRET_KEY int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" У вас не має SECRET_KEY налаштованого у вашому "
".env файлі. Django за замовчуванням використовує\n"
" стандартний ключ\n"
" що надається під час інсталяції та є широковідомим і "
"небезпечним! Будь ласка налаштуйте\n"
" SECRET_KEY у вашому .env "
"конфігураційному файлі.\n"
" "
#: .\cookbook\templates\system.html:73
msgid "Debug Mode"
msgstr "Режим налагодження"
#: .\cookbook\templates\system.html:77
msgid ""
"\n"
" This application is still running in debug mode. This is most "
"likely not needed. Turn of debug mode by\n"
" setting\n"
" DEBUG=0 int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" Ця програма все ще працює в режимі налагодження. Швидше за все, "
"це не потрібно. Вимкніть режим налагодження,\n"
" встановившие\n"
" DEBUG=0 у .env конфігураційному файлі."
"\n"
" "
#: .\cookbook\templates\system.html:86
msgid "Allowed Hosts"
msgstr "Дозволені хости"
#: .\cookbook\templates\system.html:90
msgid ""
"\n"
" Your allowed hosts are configured to allow every host. This "
"might be ok in some setups but should be avoided. Please see the docs about "
"this.\n"
" "
msgstr ""
"\n"
" Ваші дозволені хости налаштовані так, щоб дозволяти всі хости. У "
"деяких випадках це може бути прийнятним, але слід уникати такого "
"налаштування. Дивіться документацію з цього приводу..\n"
" "
#: .\cookbook\templates\system.html:97
msgid "Database"
msgstr "База даних"
#: .\cookbook\templates\system.html:100
msgid "Info"
msgstr "Інфо"
#: .\cookbook\templates\system.html:110 .\cookbook\templates\system.html:127
msgid "Migrations"
msgstr "Міграції"
#: .\cookbook\templates\system.html:116
msgid ""
"\n"
" Migrations should never fail!\n"
" Failed migrations will likely cause major parts of the app to "
"not function correctly.\n"
" If a migration fails make sure you are on the latest version and "
"if so please post the migration log and the overview below in a GitHub "
"issue.\n"
" "
msgstr ""
"\n"
" Міграції ніколи не повинні завершуватися з помилкою!\n"
" Неуспішні міграції, ймовірно, призведуть до того, що основні "
"частини програми не будуть працювати належним чином.\n"
" Якщо міграція завершилася з помилкою, переконайтеся, що ви "
"використовуєте останню версію, і, якщо це так, опублікуйте журнал міграції "
"та огляд нижче в GitHub issue.\n"
" "
#: .\cookbook\templates\system.html:182
msgid "False"
msgstr "Неправильно"
#: .\cookbook\templates\system.html:182
msgid "True"
msgstr "Правильно"
#: .\cookbook\templates\system.html:207
msgid "Hide"
msgstr "Приховати"
#: .\cookbook\templates\system.html:210
msgid "Show"
msgstr "Показати"
#: .\cookbook\templates\url_import.html:8
msgid "URL Import"
msgstr "Імпорт URL"
#: .\cookbook\views\api.py:120 .\cookbook\views\api.py:213
msgid "Parameter updated_at incorrectly formatted"
msgstr "Параметр updated_at має неправильний формат"
#: .\cookbook\views\api.py:234 .\cookbook\views\api.py:340
#, python-brace-format
msgid "No {self.basename} with id {pk} exists"
msgstr "Не існує {self.basename} з id {pk}"
#: .\cookbook\views\api.py:238
msgid "Cannot merge with the same object!"
msgstr "Неможливо об'єднати з тим самим об'єктом!"
#: .\cookbook\views\api.py:245
#, python-brace-format
msgid "No {self.basename} with id {target} exists"
msgstr "Ні існує {self.basename} з id {target}"
#: .\cookbook\views\api.py:250
msgid "Cannot merge with child object!"
msgstr "Неможливо об'єднати з піделементм!"
#: .\cookbook\views\api.py:288
#, python-brace-format
msgid "{source.name} was merged successfully with {target.name}"
msgstr "{source.name} успішно обʼєднано з {target.name}"
#: .\cookbook\views\api.py:293
#, python-brace-format
msgid "An error occurred attempting to merge {source.name} with {target.name}"
msgstr "Сталася помилка під час спроби обʼєднати {source.name} з {target.name}"
#: .\cookbook\views\api.py:349
#, python-brace-format
msgid "{child.name} was moved successfully to the root."
msgstr "{child.name} успішно переміщенно в корінь."
#: .\cookbook\views\api.py:352 .\cookbook\views\api.py:370
msgid "An error occurred attempting to move "
msgstr "Помилка виникла під час спроби перемістити "
#: .\cookbook\views\api.py:355
msgid "Cannot move an object to itself!"
msgstr "Не можна перемістити обʼєкт в самого себе!"
#: .\cookbook\views\api.py:361
#, python-brace-format
msgid "No {self.basename} with id {parent} exists"
msgstr "Не існує {self.basename} з {parent}"
#: .\cookbook\views\api.py:367
#, python-brace-format
msgid "{child.name} was moved successfully to parent {parent.name}"
msgstr "{child.name} було успішно переміщено до {parent.name}"
#: .\cookbook\views\api.py:589
#, python-brace-format
msgid "{obj.name} was removed from the shopping list."
msgstr "{obj.name} було видалено з листа покупок."
#: .\cookbook\views\api.py:594 .\cookbook\views\api.py:1037
#: .\cookbook\views\api.py:1050
#, python-brace-format
msgid "{obj.name} was added to the shopping list."
msgstr "{obj.name} було додано до списку покупок."
#: .\cookbook\views\api.py:742
msgid "Filter meal plans from date (inclusive) in the format of YYYY-MM-DD."
msgstr "Фільтруйте меню за датою (включно) у форматі РРРР-ММ-ДД."
#: .\cookbook\views\api.py:743
msgid "Filter meal plans to date (inclusive) in the format of YYYY-MM-DD."
msgstr ""
"Відфільтруйте меню до сьогоднішнього дня (включно) у форматі РРРР-ММ-ДД."
#: .\cookbook\views\api.py:744
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
msgstr "Фільтруйте меню за Меню ID. Дозволено кілька повторів."
#: .\cookbook\views\api.py:872
msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgstr "ID рецепта, в якому знаходиться цей крок. Дозволено кілька повторів."
#: .\cookbook\views\api.py:873
msgid "Query string matched (fuzzy) against object name."
msgstr "Рядок запиту збігається (нечітко) з назвою об'єкта."
#: .\cookbook\views\api.py:909
msgid ""
"Query string matched (fuzzy) against recipe name. In the future also "
"fulltext search."
msgstr ""
"Рядок запиту збігається (нечітко) з назвою рецепта. У майбутньому також буде "
"доступний повнотекстовий пошук."
#: .\cookbook\views\api.py:910
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr ""
"ID ключового слова, яке повинно бути в рецепті. Дозволено кілька повторів. "
"Еквівалентно keywords_or"
#: .\cookbook\views\api.py:911
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr ""
"ID ключових слів (дозволено кілька повторів). Повертає рецепти, які "
"відповідають хоча б одному ключовому слову"
#: .\cookbook\views\api.py:912
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr ""
"ID ключових слів (дозволено кілька повторів). Повертає рецепти, які "
"відповідають всім ключовомим словам."
#: .\cookbook\views\api.py:913
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr ""
"ID ключових слів (дозволено кілька повторів). Виключає рецепти, які "
"відповідають хоча б одному ключовому слову."
#: .\cookbook\views\api.py:914
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr ""
"ID ключових слів (дозволено кілька повторів). Виключає рецепти, які "
"відповідають всім ключовомим словам."
#: .\cookbook\views\api.py:915
msgid "ID of food a recipe should have. For multiple repeat parameter."
msgstr ""
"Ідентифікатор продукту, який повинен бути в рецепті. Дозволено кілька "
"повторів."
#: .\cookbook\views\api.py:916
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr ""
"ID продукту, дозволено кілька повторів. Повертає рецепти де є будь-який з "
"продуктів"
#: .\cookbook\views\api.py:917
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr ""
"ID продукту, дозволено кілька повторів. Повертає рецепти де є всі продукти."
#: .\cookbook\views\api.py:918
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr ""
"ID продукту, дозволено кілька повторів. Виключає рецепти де є будь-який з "
"продуктів."
#: .\cookbook\views\api.py:919
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr ""
"ID продукту, дозволено кілька повторів. Виключає рецепти де є всі продукти."
#: .\cookbook\views\api.py:920
msgid "ID of unit a recipe should have."
msgstr "ID одиниці вимірювання яку мусить мати рецепт."
#: .\cookbook\views\api.py:921
msgid ""
"Rating a recipe should have or greater. [0 - 5] Negative value filters "
"rating less than."
msgstr ""
"Оцінка рецепту повинна бути не нижче. [0 - 5] Негативне значення фільтрує "
"оцінки нижче."
#: .\cookbook\views\api.py:922
msgid "ID of book a recipe should be in. For multiple repeat parameter."
msgstr "ID книги, в якій повинен бути рецепт. Дозволено кілька повторів."
#: .\cookbook\views\api.py:923
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr "ID книг, дозволено кілька повторів. Повертає рецепти з будь-якою з книг"
#: .\cookbook\views\api.py:924
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr "ID книг, дозволено кілька повторів. Повертає рецепти з усіма книгами."
#: .\cookbook\views\api.py:925
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr ""
"ID книг, дозволено кілька повторів. Виключає рецепти з будь-якою з книг."
#: .\cookbook\views\api.py:926
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr "ID книг, дозволено кілька повторів. Виключає рецепти з усіма книгами."
#: .\cookbook\views\api.py:927
msgid "If only internal recipes should be returned. [true/false]"
msgstr "Якщо повинні повертатися тільки внутрішні рецепти. [true/false]"
#: .\cookbook\views\api.py:928
msgid "Returns the results in randomized order. [true/false]"
msgstr "Повертає результати у випадковому порядку. [true/false]"
#: .\cookbook\views\api.py:929
msgid "Returns new results first in search results. [true/false]"
msgstr ""
"Повертає нові результати першими в результатах пошуку. [true/false]"
#: .\cookbook\views\api.py:930
msgid ""
"Filter recipes cooked X times or more. Negative values returns cooked less "
"than X times"
msgstr ""
"Відфільтрувати рецепти, приготовані X разів або більше. Від'ємні значення "
"повертають рецепти, приготовані менше ніж X разів"
#: .\cookbook\views\api.py:931
msgid ""
"Filter recipes last cooked on or after YYYY-MM-DD. Prepending - filters on "
"or before date."
msgstr ""
"Фільтрувати рецепти, приготовані в останній раз в день YYYY-MM-DD або "
"пізніше. Додавання префікса - фільтрує за датою або раніше."
#: .\cookbook\views\api.py:932
msgid ""
"Filter recipes created on or after YYYY-MM-DD. Prepending - filters on or "
"before date."
msgstr ""
"Фільтрувати рецепти, створені в день YYYY-MM-DD або пізніше. Додавання "
"префікса - фільтрує за датою або раніше."
#: .\cookbook\views\api.py:933
msgid ""
"Filter recipes updated on or after YYYY-MM-DD. Prepending - filters on or "
"before date."
msgstr ""
"Рецепти фільтрів оновлено в день YYYY-MM-DD або пізніше. Додавання префікса -"
" фільтрує за датою або раніше."
#: .\cookbook\views\api.py:934
msgid ""
"Filter recipes lasts viewed on or after YYYY-MM-DD. Prepending - filters on "
"or before date."
msgstr ""
"Фільтрувати рецепти, переглянуті в день або після YYYY-MM-DD. Додавання "
"префікса - фільтрує за датою або до дати."
#: .\cookbook\views\api.py:935
msgid "Filter recipes that can be made with OnHand food. [true/false]"
msgstr ""
"Фільтрувати рецепти, які можна приготувати з продуктами, що є в наявності. "
"[true/false]"
#: .\cookbook\views\api.py:1122
msgid ""
"Returns the shopping list entry with a primary key of id. Multiple values "
"allowed."
msgstr ""
"Повертає запис списку покупок з первинним ключем id. Допускається кілька "
"значень."
#: .\cookbook\views\api.py:1125
msgid ""
"Filter shopping list entries on checked. [true, false, both, recent"
"b>]
- recent includes unchecked items and recently "
"completed items."
msgstr ""
"Фільтрувати записи у списку покупок за позначкою. "
"[true, false, both, нещодавно]
- нещодавно "
"включає непозначені елементи та нещодавно виконані елементи."
#: .\cookbook\views\api.py:1128
msgid "Returns the shopping list entries sorted by supermarket category order."
msgstr ""
"Повертає записи списку покупок, відсортовані за категоріями супермаркетів."
#: .\cookbook\views\api.py:1210
msgid "Filter for entries with the given recipe"
msgstr "Фільтр для записів із заданим рецептом"
#: .\cookbook\views\api.py:1292
msgid ""
"Return the Automations matching the automation type. Multiple values "
"allowed."
msgstr ""
"Повернути автоматизації, що відповідають типу автоматизації. Допускається "
"кілька значень."
#: .\cookbook\views\api.py:1415
msgid "Nothing to do."
msgstr "Нічого робити."
#: .\cookbook\views\api.py:1445
msgid "Invalid Url"
msgstr "Неправильний URL"
#: .\cookbook\views\api.py:1449
msgid "Connection Refused."
msgstr "Підключення відхилено."
#: .\cookbook\views\api.py:1451
msgid "Bad URL Schema."
msgstr "Погана схема URL."
#: .\cookbook\views\api.py:1474
msgid "No usable data could be found."
msgstr "Не вдалося знайти корисних даних."
#: .\cookbook\views\api.py:1549
msgid "File is above space limit"
msgstr "Файл перевищує обмеження розміру"
#: .\cookbook\views\api.py:1566 .\cookbook\views\import_export.py:114
msgid "Importing is not implemented for this provider"
msgstr "Імпорт не реалізований для цього постачальника"
#: .\cookbook\views\api.py:1650 .\cookbook\views\data.py:30
#: .\cookbook\views\edit.py:88 .\cookbook\views\new.py:63
#: .\cookbook\views\new.py:82
msgid "This feature is not yet available in the hosted version of tandoor!"
msgstr "Ця функція ще не доступна в хостинговій версії Tandoor!"
#: .\cookbook\views\api.py:1671
msgid "Sync successful!"
msgstr "Синхронізація успішна!"
#: .\cookbook\views\api.py:1674
msgid "Error synchronizing with Storage"
msgstr "Помилка синхронізації зі сховищем"
#: .\cookbook\views\data.py:99
#, python-format
msgid "Batch edit done. %(count)d recipe was updated."
msgid_plural "Batch edit done. %(count)d Recipes where updated."
msgstr[0] "Групове редагування завершено. %(count)d рецепт оновлено."
msgstr[1] "Групове редагування завершено. %(count)d рецепти оновлено."
msgstr[2] "Групове редагування завершено. %(count)d рецептів оновлено."
#: .\cookbook\views\delete.py:102
msgid "Monitor"
msgstr "Моніторінг"
#: .\cookbook\views\delete.py:114 .\cookbook\views\lists.py:61
#: .\cookbook\views\new.py:69
msgid "Storage Backend"
msgstr "Сховище (бекенд)"
#: .\cookbook\views\delete.py:122
msgid ""
"Could not delete this storage backend as it is used in at least one monitor."
msgstr ""
"Не вдалося видалити це сховище для бекенду, оскільки воно використовується "
"принаймні в одному моніторінгу."
#: .\cookbook\views\delete.py:135
msgid "Connectors Config Backend"
msgstr "Конфігурація конекторів для бекенду"
#: .\cookbook\views\delete.py:157
msgid "Invite Link"
msgstr "Посилання для запрошення"
#: .\cookbook\views\delete.py:168
msgid "Space Membership"
msgstr "Участь в просторі"
#: .\cookbook\views\edit.py:84
msgid "You cannot edit this storage!"
msgstr "Ви не можете редагувати це сховище!"
#: .\cookbook\views\edit.py:108
msgid "Storage saved!"
msgstr "Сховище збережено!"
#: .\cookbook\views\edit.py:110
msgid "There was an error updating this storage backend!"
msgstr "Сталася помилка під час оновлення цього сховища!"
#: .\cookbook\views\edit.py:134
msgid "Config saved!"
msgstr "Конфігурація збережена!"
#: .\cookbook\views\edit.py:142
msgid "ConnectorConfig"
msgstr "Конфігурація конектора"
#: .\cookbook\views\edit.py:198
msgid "Changes saved!"
msgstr "Зміни збережені!"
#: .\cookbook\views\edit.py:202
msgid "Error saving changes!"
msgstr "Помилка під час збереження змін!"
#: .\cookbook\views\import_export.py:101
msgid ""
"The PDF Exporter is not enabled on this instance as it is still in an "
"experimental state."
msgstr ""
"PDF експорт не включений на цьому сервері, бо це все ще експериментальна "
"функція."
#: .\cookbook\views\lists.py:23
msgid "Import Log"
msgstr "Журнал імпорту"
#: .\cookbook\views\lists.py:36
msgid "Discovery"
msgstr "Огляд"
#: .\cookbook\views\lists.py:46
msgid "Shopping List"
msgstr "Список покупок"
#: .\cookbook\views\lists.py:77 .\cookbook\views\new.py:98
msgid "Connector Config Backend"
msgstr "Конфігурація коннектора для бекенду"
#: .\cookbook\views\lists.py:91
msgid "Invite Links"
msgstr "Посилання для запрошеннь"
#: .\cookbook\views\lists.py:154
msgid "Supermarkets"
msgstr "Супермаркети"
#: .\cookbook\views\lists.py:170
msgid "Shopping Categories"
msgstr "Категорії покупок"
#: .\cookbook\views\lists.py:202
msgid "Custom Filters"
msgstr "Спеціальні фільтри"
#: .\cookbook\views\lists.py:239
msgid "Steps"
msgstr "Кроки"
#: .\cookbook\views\lists.py:270
msgid "Property Types"
msgstr "Типи об'єктів"
#: .\cookbook\views\new.py:86
msgid "This feature is not enabled by the server admin!"
msgstr "Ця функція не включена адміністратором сервера!"
#: .\cookbook\views\new.py:123
msgid "Imported new recipe!"
msgstr "Імпортовано новий рецепт!"
#: .\cookbook\views\new.py:126
msgid "There was an error importing this recipe!"
msgstr "Сталася помилка під час імпортування цього рецепту!"
#: .\cookbook\views\views.py:69 .\cookbook\views\views.py:177
#: .\cookbook\views\views.py:204 .\cookbook\views\views.py:423
msgid "This feature is not available in the demo version!"
msgstr "Ця функція не доступна в демо версії!"
#: .\cookbook\views\views.py:74
msgid ""
"You have the reached the maximum amount of spaces that can be owned by you."
msgstr "Ви досягли максимального обсягу місця на дисках, яке може належати вам."
#: .\cookbook\views\views.py:89
msgid ""
"You have successfully created your own recipe space. Start by adding some "
"recipes or invite other people to join you."
msgstr ""
"Ви успішно створили власний простір для рецептів. Почніть з додавання "
"рецептів або запросіть інших людей приєднатися до вас."
#: .\cookbook\views\views.py:138
msgid "You do not have the required permissions to perform this action!"
msgstr "Ви не маєте необхідних прав для виконання цієї дії!"
#: .\cookbook\views\views.py:149
msgid "Comment saved!"
msgstr "Коментар збережено!"
#: .\cookbook\views\views.py:240
msgid "You must select at least one field to search!"
msgstr "Ви мусите обрати хоча б одне поле для пошуку!"
#: .\cookbook\views\views.py:243
msgid ""
"To use this search method you must select at least one full text search "
"field!"
msgstr ""
"Щоб скористатися цим методом пошуку, ви повинні вибрати принаймні одне поле "
"для повнотекстового пошуку!"
#: .\cookbook\views\views.py:246
msgid "Fuzzy search is not compatible with this search method!"
msgstr "Нечіткий пошук не сумісний з цим методом пошуку!"
#: .\cookbook\views\views.py:306
#, python-format
msgid "PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!"
msgstr ""
"PostgreSQL %(v)s є застарілим. Оновіть до повністю підтримуваної версії!"
#: .\cookbook\views\views.py:309
#, python-format
msgid "You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended"
msgstr ""
"Ви використовуєте PostgreSQL %(v1)s. Рекомендується використовувати "
"PostgreSQL %(v2)s"
#: .\cookbook\views\views.py:313
msgid "Unable to determine PostgreSQL version."
msgstr "Не вдалося визначити версію PostgreSQL."
#: .\cookbook\views\views.py:317
msgid ""
"This application is not running with a Postgres database backend. This is ok "
"but not recommended as some features only work with postgres databases."
msgstr ""
"Ця програма не працює з базою даних Postgres. Це нормально, але не "
"рекомендується, оскільки деякі функції працюють тільки з базами даних "
"Postgres."
#: .\cookbook\views\views.py:360
msgid ""
"The setup page can only be used to create the first "
"user! If you have forgotten your superuser credentials "
"please consult the django documentation on how to reset passwords."
msgstr ""
"Сторінка налаштувань може бути використана тільки для створення першого "
"користувача! Якщо ви забули свої облікові дані "
"суперкористувача, будь ласка, зверніться до документації Django, щоб "
"дізнатися, як скинути пароль."
#: .\cookbook\views\views.py:369
msgid "Passwords dont match!"
msgstr "Паролі не збігаються!"
#: .\cookbook\views\views.py:377
msgid "User has been created, please login!"
msgstr "Користувач створено, будь ласка, увійдіть!"
#: .\cookbook\views\views.py:393
msgid "Malformed Invite Link supplied!"
msgstr "Надано неправильно сформоване посилання для запрошення!"
#: .\cookbook\views\views.py:410
msgid "Successfully joined space."
msgstr "Успішно долучились до простору."
#: .\cookbook\views\views.py:416
msgid "Invite Link not valid or already used!"
msgstr "Посилання для запрошення недійсне або вже використане!"
#: .\cookbook\views\views.py:432
msgid ""
"Reporting share links is not enabled for this instance. Please notify the "
"page administrator to report problems."
msgstr ""
"Повідомлення про посилання для спільного доступу не ввімкнено для цього "
"екземпляра. Будь ласка, повідомте адміністратора сторінки про проблеми."
#: .\cookbook\views\views.py:437
msgid ""
"Recipe sharing link has been disabled! For additional information please "
"contact the page administrator."
msgstr ""
"Посилання для обміну рецептами було вимкнено! Для отримання додаткової "
"інформації зверніться до адміністратора сторінки."
#: .\cookbook\views\views.py:451
msgid "Manage recipes, shopping list, meal plans and more."
msgstr "Керуйте рецептами, списком покупок, меню тощо."
#: .\cookbook\views\views.py:458
msgid "Plan"
msgstr "План"
#: .\cookbook\views\views.py:458
msgid "View your meal Plan"
msgstr "Перегляньте ваше меню"
#: .\cookbook\views\views.py:459
msgid "View your cookbooks"
msgstr "Перегляньте ваші кулінарні книги"
#: .\cookbook\views\views.py:460
msgid "View your shopping lists"
msgstr "Перегляньте ваші листи покупок"
#~ msgid "user"
#~ msgstr "користувач"
================================================
FILE: cookbook/locale/vi/LC_MESSAGES/django.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
# Translators:
# Hieu, 2021
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-22 20:15+0200\n"
"PO-Revision-Date: 2020-06-02 19:28+0000\n"
"Last-Translator: Hieu, 2021\n"
"Language-Team: Vietnamese (https://www.transifex.com/django-recipes/"
"teams/110507/vi/)\n"
"Language: vi\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: .\cookbook\forms.py:50
msgid "Default"
msgstr ""
#: .\cookbook\forms.py:77
msgid ""
"To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything."
msgstr ""
#: .\cookbook\forms.py:108
msgid "Maximum number of users for this space reached."
msgstr ""
#: .\cookbook\forms.py:114
msgid "Email address already taken!"
msgstr ""
#: .\cookbook\forms.py:121
msgid ""
"An email address is not required but if present the invite link will be sent "
"to the user."
msgstr ""
#: .\cookbook\forms.py:133
msgid "Name already taken."
msgstr ""
#: .\cookbook\forms.py:144 .\cookbook\forms.py:158
msgid "Accept Terms and Privacy"
msgstr ""
#: .\cookbook\helper\AllAuthCustomAdapter.py:41
msgid ""
"In order to prevent spam, the requested email was not send. Please wait a "
"few minutes and try again."
msgstr ""
#: .\cookbook\helper\permission_helper.py:165
#: .\cookbook\helper\permission_helper.py:186 .\cookbook\views\views.py:138
msgid "You are not logged in and therefore cannot view this page!"
msgstr "Bạn chưa đăng nhập nên không thể xem trang này!"
#: .\cookbook\helper\permission_helper.py:168
#: .\cookbook\helper\permission_helper.py:173
#: .\cookbook\helper\permission_helper.py:198
#: .\cookbook\helper\permission_helper.py:265
#: .\cookbook\helper\permission_helper.py:279
#: .\cookbook\helper\permission_helper.py:290
#: .\cookbook\helper\permission_helper.py:301
#: .\cookbook\helper\permission_helper.py:317
#: .\cookbook\helper\permission_helper.py:343
#: .\cookbook\helper\permission_helper.py:359
msgid "You do not have the required permissions to view this page!"
msgstr "Bạn không có đủ quyền cần thiết để xem trang này!"
#: .\cookbook\helper\permission_helper.py:191
#: .\cookbook\helper\permission_helper.py:214
#: .\cookbook\helper\permission_helper.py:236
#: .\cookbook\helper\permission_helper.py:251
msgid "You cannot interact with this object as it is not owned by you!"
msgstr ""
#: .\cookbook\helper\permission_helper.py:420
msgid "You have reached the maximum number of recipes for your space."
msgstr ""
#: .\cookbook\helper\permission_helper.py:432
msgid "You have more users than allowed in your space."
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:319
msgid "reverse rotation"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:320
msgid "careful rotation"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:321
msgid "knead"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:322
msgid "thicken"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:323
msgid "warm up"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:324
msgid "ferment"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:325
#, fuzzy
#| msgid "Last cooked"
msgid "slow cook"
msgstr "Nấu lần cuối"
#: .\cookbook\helper\recipe_url_import.py:326
msgid "egg boiler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:327
msgid "kettle"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:328
msgid "blend"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:329
msgid "pre-clean"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:330
msgid "high temperature"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:331
msgid "rice cooker"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:332
msgid "caramelize"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:333
msgid "peeler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:334
msgid "slicer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:335
msgid "grater"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:336
msgid "spiralizer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:337
msgid "sous-vide"
msgstr ""
#: .\cookbook\helper\shopping_helper.py:150
msgid "You must supply a servings size"
msgstr ""
#: .\cookbook\helper\template_helper.py:97
#: .\cookbook\helper\template_helper.py:99
#: .\cookbook\helper\template_helper.py:101
msgid "Could not parse template code."
msgstr ""
#: .\cookbook\integration\copymethat.py:44
#: .\cookbook\integration\melarecipes.py:37
msgid "Favorite"
msgstr ""
#: .\cookbook\integration\copymethat.py:50
msgid "I made this"
msgstr ""
#: .\cookbook\integration\integration.py:238
msgid ""
"Importer expected a .zip file. Did you choose the correct importer type for "
"your data ?"
msgstr ""
#: .\cookbook\integration\integration.py:241
msgid ""
"An unexpected error occurred during the import. Please make sure you have "
"uploaded a valid file."
msgstr ""
#: .\cookbook\integration\integration.py:246
msgid "The following recipes were ignored because they already existed:"
msgstr ""
#: .\cookbook\integration\integration.py:250
#, fuzzy, python-format
#| msgid "Imported new recipe!"
msgid "Imported %s recipes."
msgstr "Đã nhập công thức mới!"
#: .\cookbook\integration\mealie1.py:210
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "Calories"
msgstr ""
#: .\cookbook\integration\mealie1.py:211
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
msgid "Carbohydrates"
msgstr ""
#: .\cookbook\integration\mealie1.py:212
msgid "Cholesterol"
msgstr ""
#: .\cookbook\integration\mealie1.py:213
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
msgid "Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:214
msgid "Fiber"
msgstr ""
#: .\cookbook\integration\mealie1.py:215
msgid "Protein"
msgstr ""
#: .\cookbook\integration\mealie1.py:216
msgid "Saturated Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:217
msgid "Sodium"
msgstr ""
#: .\cookbook\integration\mealie1.py:218
msgid "Sugar"
msgstr ""
#: .\cookbook\integration\mealie1.py:219
msgid "Trans Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:220
msgid "Unsaturated Fat"
msgstr ""
#: .\cookbook\integration\openeats.py:28
#, fuzzy
#| msgid "Recipes"
msgid "Recipe source:"
msgstr "Công thức"
#: .\cookbook\integration\paprika.py:49
#, fuzzy
#| msgid "Note"
msgid "Notes"
msgstr "Ghi chú"
#: .\cookbook\integration\paprika.py:52
#, fuzzy
#| msgid "Information"
msgid "Nutritional Information"
msgstr "Thông tin"
#: .\cookbook\integration\paprika.py:56
msgid "Source"
msgstr ""
#: .\cookbook\integration\recettetek.py:55
#: .\cookbook\integration\recipekeeper.py:70
msgid "Imported from"
msgstr "Nhập từ"
#: .\cookbook\integration\saffron.py:23
msgid "Servings"
msgstr "Khẩu phần"
#: .\cookbook\integration\saffron.py:25
msgid "Waiting time"
msgstr ""
#: .\cookbook\integration\saffron.py:27
msgid "Preparation Time"
msgstr "Thời gian Chuẩn bị"
#: .\cookbook\integration\saffron.py:29 .\cookbook\templates\index.html:6
msgid "Cookbook"
msgstr ""
#: .\cookbook\integration\saffron.py:31
msgid "Section"
msgstr ""
#: .\cookbook\management\commands\fix_duplicate_properties.py:15
msgid "Fixes foods with "
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:14
msgid "Rebuilds full text search index on Recipe"
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:18
msgid "Only Postgresql databases use full text search, no index to rebuild"
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:29
msgid "Recipe index rebuild complete."
msgstr ""
#: .\cookbook\management\commands\rebuildindex.py:31
msgid "Recipe index rebuild failed."
msgstr ""
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
msgid "Breakfast"
msgstr "Bữa sáng"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:19
msgid "Lunch"
msgstr "Bữa trưa"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:24
msgid "Dinner"
msgstr "Bữa tối"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:29 .\cookbook\models.py:971
msgid "Other"
msgstr "Khác"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "g"
msgstr ""
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "Proteins"
msgstr ""
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "kcal"
msgstr ""
#: .\cookbook\models.py:325
msgid ""
"Maximum file storage for space in MB. 0 for unlimited, -1 to disable file "
"upload."
msgstr ""
#: .\cookbook\models.py:513
msgid "Search"
msgstr "Tìm kiếm"
#: .\cookbook\models.py:514
msgid "Meal-Plan"
msgstr "Kế hoạch Khẩu phần ăn"
#: .\cookbook\models.py:515
msgid "Books"
msgstr "Sách"
#: .\cookbook\models.py:516 .\cookbook\views\views.py:416
#: .\cookbook\views\views.py:417
msgid "Shopping"
msgstr ""
#: .\cookbook\models.py:967
msgid "Nutrition"
msgstr ""
#: .\cookbook\models.py:968
#, fuzzy
#| msgid "Merge"
msgid "Allergen"
msgstr "Gộp"
#: .\cookbook\models.py:969
msgid "Price"
msgstr ""
#: .\cookbook\models.py:970
msgid "Goal"
msgstr ""
#: .\cookbook\models.py:1467 .\cookbook\templates\search_info.html:28
msgid "Simple"
msgstr ""
#: .\cookbook\models.py:1468 .\cookbook\templates\search_info.html:33
msgid "Phrase"
msgstr ""
#: .\cookbook\models.py:1469 .\cookbook\templates\search_info.html:38
msgid "Web"
msgstr ""
#: .\cookbook\models.py:1470 .\cookbook\templates\search_info.html:47
msgid "Raw"
msgstr ""
#: .\cookbook\models.py:1525
msgid "Food Alias"
msgstr ""
#: .\cookbook\models.py:1526
msgid "Unit Alias"
msgstr ""
#: .\cookbook\models.py:1527
#, fuzzy
#| msgid "Keywords"
msgid "Keyword Alias"
msgstr "Từ khóa"
#: .\cookbook\models.py:1528
msgid "Description Replace"
msgstr ""
#: .\cookbook\models.py:1529
#, fuzzy
#| msgid "Instructions"
msgid "Instruction Replace"
msgstr "Hướng dẫn"
#: .\cookbook\models.py:1530
#, fuzzy
#| msgid "New Unit"
msgid "Never Unit"
msgstr "Đơn vị mới"
#: .\cookbook\models.py:1531
msgid "Transpose Words"
msgstr ""
#: .\cookbook\models.py:1532
msgid "Food Replace"
msgstr ""
#: .\cookbook\models.py:1533
#, fuzzy
#| msgid "Edit Recipe"
msgid "Unit Replace"
msgstr "Sửa Công thức"
#: .\cookbook\models.py:1534
msgid "Name Replace"
msgstr ""
#: .\cookbook\models.py:1564
msgid "Recipe"
msgstr "Công thức"
#: .\cookbook\models.py:1565
msgid "Food"
msgstr ""
#: .\cookbook\models.py:1566
msgid "Keyword"
msgstr "Từ khóa"
#: .\cookbook\serializer.py:262
msgid "File uploads are not enabled for this Space."
msgstr ""
#: .\cookbook\serializer.py:273
msgid "You have reached your file upload limit."
msgstr ""
#: .\cookbook\serializer.py:281
msgid "The given file type is not allowed."
msgstr ""
#: .\cookbook\serializer.py:421 .\cookbook\views\views.py:94
msgid ""
"You have the reached the maximum amount of spaces that can be owned by you."
msgstr ""
#: .\cookbook\serializer.py:434
msgid "Space Name must be unique."
msgstr ""
#: .\cookbook\serializer.py:469
msgid "Cannot modify Space owner permission."
msgstr ""
#: .\cookbook\serializer.py:1596
msgid "Hello"
msgstr ""
#: .\cookbook\serializer.py:1596
msgid "You have been invited by "
msgstr ""
#: .\cookbook\serializer.py:1598
msgid " to join their Tandoor Recipes space "
msgstr ""
#: .\cookbook\serializer.py:1600
msgid "Click the following link to activate your account: "
msgstr ""
#: .\cookbook\serializer.py:1602
msgid ""
"If the link does not work use the following code to manually join the space: "
msgstr ""
#: .\cookbook\serializer.py:1604
msgid "The invitation is valid until "
msgstr ""
#: .\cookbook\serializer.py:1606
msgid ""
"Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub "
msgstr ""
#: .\cookbook\serializer.py:1609
msgid "Tandoor Recipes Invite"
msgstr ""
#: .\cookbook\serializer.py:1813
msgid "Existing shopping list to update"
msgstr ""
#: .\cookbook\serializer.py:1815
msgid ""
"List of ingredient IDs from the recipe to add, if not provided all "
"ingredients will be added."
msgstr ""
#: .\cookbook\serializer.py:1817
msgid ""
"Providing a list_recipe ID and servings of 0 will delete that shopping list."
msgstr ""
#: .\cookbook\serializer.py:1826
msgid "Amount of food to add to the shopping list"
msgstr ""
#: .\cookbook\serializer.py:1828
msgid "ID of unit to use for the shopping list"
msgstr ""
#: .\cookbook\serializer.py:1830
msgid "When set to true will delete all food from active shopping lists."
msgstr ""
#: .\cookbook\templates\404.html:5
msgid "404 Error"
msgstr "Lỗi 404"
#: .\cookbook\templates\404.html:18
msgid "The page you are looking for could not be found."
msgstr "Không tìm thấy trang bạn đang tìm kiếm."
#: .\cookbook\templates\404.html:33
msgid "Take me Home"
msgstr ""
#: .\cookbook\templates\404.html:35
msgid "Report a Bug"
msgstr "Báo cáo một Lỗi"
#: .\cookbook\templates\account\email.html:6
#: .\cookbook\templates\account\email.html:10
msgid "E-mail Addresses"
msgstr ""
#: .\cookbook\templates\account\email.html:12
msgid "The following e-mail addresses are associated with your account:"
msgstr ""
#: .\cookbook\templates\account\email.html:29
msgid "Verified"
msgstr ""
#: .\cookbook\templates\account\email.html:31
msgid "Unverified"
msgstr ""
#: .\cookbook\templates\account\email.html:33
msgid "Primary"
msgstr ""
#: .\cookbook\templates\account\email.html:40
msgid "Make Primary"
msgstr ""
#: .\cookbook\templates\account\email.html:42
msgid "Re-send Verification"
msgstr ""
#: .\cookbook\templates\account\email.html:43
#: .\cookbook\templates\socialaccount\connections.html:36
msgid "Remove"
msgstr ""
#: .\cookbook\templates\account\email.html:51
#, fuzzy
#| msgid "Warning"
msgid "Warning:"
msgstr "Cảnh báo"
#: .\cookbook\templates\account\email.html:51
msgid ""
"You currently do not have any e-mail address set up. You should really add "
"an e-mail address so you can receive notifications, reset your password, etc."
msgstr ""
#: .\cookbook\templates\account\email.html:57
msgid "Add E-mail Address"
msgstr ""
#: .\cookbook\templates\account\email.html:62
msgid "Add E-mail"
msgstr ""
#: .\cookbook\templates\account\email.html:72
msgid "Do you really want to remove the selected e-mail address?"
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:6
#: .\cookbook\templates\account\email_confirm.html:10
msgid "Confirm E-mail Address"
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:16
#, python-format
msgid ""
"Please confirm that\n"
" %(email)s is an e-mail address "
"for user %(user_display)s\n"
" ."
msgstr ""
#: .\cookbook\templates\account\email_confirm.html:22
msgid "Confirm"
msgstr "Xác nhận"
#: .\cookbook\templates\account\email_confirm.html:29
#, python-format
msgid ""
"This e-mail confirmation link expired or is invalid. Please\n"
" issue a new e-mail confirmation "
"request."
msgstr ""
#: .\cookbook\templates\account\login.html:8
#: .\cookbook\templates\openid\login.html:8
msgid "Login"
msgstr "Đăng nhập"
#: .\cookbook\templates\account\login.html:15
#: .\cookbook\templates\account\login.html:31
#: .\cookbook\templates\account\password_reset.html:39
#: .\cookbook\templates\account\password_reset_done.html:31
#: .\cookbook\templates\account\signup.html:68
#: .\cookbook\templates\account\signup_closed.html:15
#: .\cookbook\templates\openid\login.html:15
#: .\cookbook\templates\openid\login.html:26
#: .\cookbook\templates\socialaccount\authentication_error.html:15
msgid "Sign In"
msgstr ""
#: .\cookbook\templates\account\login.html:34
#: .\cookbook\templates\account\password_reset.html:41
#: .\cookbook\templates\account\password_reset_done.html:33
#: .\cookbook\templates\socialaccount\signup.html:8
#: .\cookbook\templates\socialaccount\signup.html:56
msgid "Sign Up"
msgstr ""
#: .\cookbook\templates\account\login.html:38
msgid "Lost your password?"
msgstr ""
#: .\cookbook\templates\account\login.html:39
#: .\cookbook\templates\account\password_reset.html:29
msgid "Reset My Password"
msgstr ""
#: .\cookbook\templates\account\login.html:50
msgid "Social Login"
msgstr ""
#: .\cookbook\templates\account\login.html:51
msgid "You can use any of the following providers to sign in."
msgstr ""
#: .\cookbook\templates\account\logout.html:5
#: .\cookbook\templates\account\logout.html:9
#: .\cookbook\templates\account\logout.html:18
msgid "Sign Out"
msgstr ""
#: .\cookbook\templates\account\logout.html:11
msgid "Are you sure you want to sign out?"
msgstr ""
#: .\cookbook\templates\account\password_change.html:6
#: .\cookbook\templates\account\password_change.html:10
#: .\cookbook\templates\account\password_change.html:15
#: .\cookbook\templates\account\password_reset_from_key.html:7
#: .\cookbook\templates\account\password_reset_from_key.html:13
#: .\cookbook\templates\account\password_reset_from_key_done.html:7
#: .\cookbook\templates\account\password_reset_from_key_done.html:13
#, fuzzy
#| msgid "Changes saved!"
msgid "Change Password"
msgstr "Đã lưu các thay đổi!"
#: .\cookbook\templates\account\password_change.html:16
msgid "Forgot Password?"
msgstr ""
#: .\cookbook\templates\account\password_reset.html:7
#: .\cookbook\templates\account\password_reset.html:13
#: .\cookbook\templates\account\password_reset_done.html:7
#: .\cookbook\templates\account\password_reset_done.html:18
msgid "Password Reset"
msgstr ""
#: .\cookbook\templates\account\password_reset.html:24
msgid ""
"Forgotten your password? Enter your e-mail address below, and we'll send you "
"an e-mail allowing you to reset it."
msgstr ""
#: .\cookbook\templates\account\password_reset.html:32
msgid "Password reset is disabled on this instance."
msgstr ""
#: .\cookbook\templates\account\password_reset_done.html:25
msgid ""
"We have sent you an e-mail. Please contact us if you do not receive it "
"within a few minutes."
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:13
#, fuzzy
#| msgid "API Token"
msgid "Bad Token"
msgstr "Token API"
#: .\cookbook\templates\account\password_reset_from_key.html:25
#, python-format
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used.\n"
" Please request a new "
"password reset."
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:33
msgid "change password"
msgstr ""
#: .\cookbook\templates\account\password_reset_from_key.html:36
#: .\cookbook\templates\account\password_reset_from_key_done.html:19
msgid "Your password is now changed."
msgstr ""
#: .\cookbook\templates\account\password_set.html:6
#: .\cookbook\templates\account\password_set.html:10
#: .\cookbook\templates\account\password_set.html:15
msgid "Set Password"
msgstr ""
#: .\cookbook\templates\account\signup.html:5
msgid "Register"
msgstr "Đăng ký"
#: .\cookbook\templates\account\signup.html:11
#, fuzzy
#| msgid "Create your Account"
msgid "Create an Account"
msgstr "Tạo Tài khoản"
#: .\cookbook\templates\account\signup.html:41
msgid "I accept the follwoing"
msgstr ""
#: .\cookbook\templates\account\signup.html:44
#: .\cookbook\templates\socialaccount\signup.html:35
msgid "Terms and Conditions"
msgstr ""
#: .\cookbook\templates\account\signup.html:47
#: .\cookbook\templates\socialaccount\signup.html:38
msgid "and"
msgstr ""
#: .\cookbook\templates\account\signup.html:51
#: .\cookbook\templates\socialaccount\signup.html:42
msgid "Privacy Policy"
msgstr ""
#: .\cookbook\templates\account\signup.html:64
msgid "Create User"
msgstr "Tạo Người dùng"
#: .\cookbook\templates\account\signup.html:68
msgid "Already have an account?"
msgstr ""
#: .\cookbook\templates\account\signup_closed.html:5
#: .\cookbook\templates\account\signup_closed.html:11
msgid "Sign Up Closed"
msgstr ""
#: .\cookbook\templates\account\signup_closed.html:13
msgid "We are sorry, but the sign up is currently closed."
msgstr ""
#: .\cookbook\templates\frontend\tandoor.html:15
msgid "Tandoor Recipe Manager"
msgstr ""
#: .\cookbook\templates\index.html:28
msgid "Search recipe ..."
msgstr ""
#: .\cookbook\templates\index.html:43
msgid "New Recipe"
msgstr "Công thức Mới"
#: .\cookbook\templates\index.html:46
msgid "Import Recipe"
msgstr "Nhập Công thức"
#: .\cookbook\templates\index.html:52
msgid "Advanced Search"
msgstr ""
#: .\cookbook\templates\index.html:56
msgid "Reset Search"
msgstr ""
#: .\cookbook\templates\index.html:84
msgid "Last viewed"
msgstr "Lần cuối xem"
#: .\cookbook\templates\index.html:86
msgid "Recipes"
msgstr "Công thức"
#: .\cookbook\templates\index.html:93
msgid "Log in to view recipes"
msgstr "Đăng nhập để xem công thức"
#: .\cookbook\templates\markdown_info.html:5
#: .\cookbook\templates\markdown_info.html:13
msgid "Markdown Info"
msgstr "Thông tin Markdown"
#: .\cookbook\templates\markdown_info.html:14
msgid ""
"\n"
" Markdown is lightweight markup language that can be used to format "
"plain text easily.\n"
" This site uses the Python Markdown library to\n"
" convert your text into nice looking HTML. Its full markdown "
"documentation can be found\n"
" here.\n"
" An incomplete but most likely sufficient documentation can be found "
"below.\n"
" "
msgstr ""
#: .\cookbook\templates\markdown_info.html:25
msgid "Headers"
msgstr ""
#: .\cookbook\templates\markdown_info.html:54
msgid "Formatting"
msgstr ""
#: .\cookbook\templates\markdown_info.html:56
#: .\cookbook\templates\markdown_info.html:72
msgid "Line breaks are inserted by adding two spaces after the end of a line"
msgstr ""
#: .\cookbook\templates\markdown_info.html:57
#: .\cookbook\templates\markdown_info.html:73
msgid "or by leaving a blank line in between."
msgstr ""
#: .\cookbook\templates\markdown_info.html:59
#: .\cookbook\templates\markdown_info.html:74
msgid "This text is bold"
msgstr ""
#: .\cookbook\templates\markdown_info.html:60
#: .\cookbook\templates\markdown_info.html:75
msgid "This text is italic"
msgstr ""
#: .\cookbook\templates\markdown_info.html:61
#: .\cookbook\templates\markdown_info.html:77
msgid "Blockquotes are also possible"
msgstr ""
#: .\cookbook\templates\markdown_info.html:84
msgid "Lists"
msgstr ""
#: .\cookbook\templates\markdown_info.html:85
msgid ""
"Lists can ordered or unordered. It is important to leave a blank line "
"before the list!"
msgstr ""
#: .\cookbook\templates\markdown_info.html:87
#: .\cookbook\templates\markdown_info.html:108
msgid "Ordered List"
msgstr ""
#: .\cookbook\templates\markdown_info.html:89
#: .\cookbook\templates\markdown_info.html:90
#: .\cookbook\templates\markdown_info.html:91
#: .\cookbook\templates\markdown_info.html:110
#: .\cookbook\templates\markdown_info.html:111
#: .\cookbook\templates\markdown_info.html:112
msgid "unordered list item"
msgstr ""
#: .\cookbook\templates\markdown_info.html:93
#: .\cookbook\templates\markdown_info.html:114
msgid "Unordered List"
msgstr "Danh sách không sắp xếp"
#: .\cookbook\templates\markdown_info.html:95
#: .\cookbook\templates\markdown_info.html:96
#: .\cookbook\templates\markdown_info.html:97
#: .\cookbook\templates\markdown_info.html:116
#: .\cookbook\templates\markdown_info.html:117
#: .\cookbook\templates\markdown_info.html:118
msgid "ordered list item"
msgstr ""
#: .\cookbook\templates\markdown_info.html:125
msgid "Images & Links"
msgstr ""
#: .\cookbook\templates\markdown_info.html:126
msgid ""
"Links can be formatted with Markdown. This application also allows to paste "
"links directly into markdown fields without any formatting."
msgstr ""
#: .\cookbook\templates\markdown_info.html:132
#: .\cookbook\templates\markdown_info.html:145
msgid "This will become an image"
msgstr ""
#: .\cookbook\templates\markdown_info.html:152
msgid "Tables"
msgstr ""
#: .\cookbook\templates\markdown_info.html:153
msgid ""
"Markdown tables are hard to create by hand. It is recommended to use a table "
"editor like this one."
msgstr ""
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:171
#: .\cookbook\templates\markdown_info.html:177
msgid "Table"
msgstr "Bảng"
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:172
msgid "Header"
msgstr ""
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:178
msgid "Cell"
msgstr ""
#: .\cookbook\templates\no_groups_info.html:5
#: .\cookbook\templates\no_groups_info.html:12
msgid "No Permissions"
msgstr ""
#: .\cookbook\templates\no_groups_info.html:17
#, fuzzy
#| msgid "You are not logged in and therefore cannot view this page!"
msgid "You do not have any groups and therefor cannot use this application."
msgstr "Bạn chưa đăng nhập nên không thể xem trang này!"
#: .\cookbook\templates\no_groups_info.html:18
#: .\cookbook\templates\no_perm_info.html:15
msgid "Please contact your administrator."
msgstr ""
#: .\cookbook\templates\no_perm_info.html:5
#: .\cookbook\templates\no_perm_info.html:12
msgid "No Permission"
msgstr ""
#: .\cookbook\templates\no_perm_info.html:15
#, fuzzy
#| msgid "You do not have the required permissions to perform this action!"
msgid ""
"You do not have the required permissions to view this page or perform this "
"action."
msgstr "Bạn không có đủ quyền cần thiết để thực hiện hành động này!"
#: .\cookbook\templates\offline.html:5
msgid "Offline"
msgstr ""
#: .\cookbook\templates\openid\login.html:27
#: .\cookbook\templates\socialaccount\authentication_error.html:27
msgid "Back"
msgstr ""
#: .\cookbook\templates\rest_framework\api.html:5
msgid "Recipe Home"
msgstr ""
#: .\cookbook\templates\rest_framework\api.html:11
msgid "API Documentation"
msgstr "Tài liệu API"
#: .\cookbook\templates\search_info.html:5
#: .\cookbook\templates\search_info.html:9
#, fuzzy
#| msgid "Search String"
msgid "Search Settings"
msgstr "Chuỗi Tìm kiếm"
#: .\cookbook\templates\search_info.html:10
msgid ""
"\n"
" Creating the best search experience is complicated and weighs "
"heavily on your personal configuration. \n"
" Changing any of the search settings can have significant impact on "
"the speed and quality of the results.\n"
" Search Methods, Trigrams and Full Text Search configurations are "
"only available if you are using Postgres for your database.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:19
#, fuzzy
#| msgid "Search"
msgid "Search Methods"
msgstr "Tìm kiếm"
#: .\cookbook\templates\search_info.html:23
msgid ""
" \n"
" Full text searches attempt to normalize the words provided to "
"match common variants. For example: 'forked', 'forking', 'forks' will all "
"normalize to 'fork'.\n"
" There are several methods available, described below, that will "
"control how the search behavior should react when multiple words are "
"searched.\n"
" Full technical details on how these operate can be viewed on Postgresql's website.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:29
msgid ""
" \n"
" Simple searches ignore punctuation and common words such as "
"'the', 'a', 'and'. And will treat separate words as required.\n"
" Searching for 'apple or flour' will return any recipe that "
"includes both 'apple' and 'flour' anywhere in the fields that have been "
"selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:34
msgid ""
" \n"
" Phrase searches ignore punctuation, but will search for all of "
"the words in the exact order provided.\n"
" Searching for 'apple or flour' will only return a recipe that "
"includes the exact phrase 'apple or flour' in any of the fields that have "
"been selected for a full text search.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:39
msgid ""
" \n"
" Web searches simulate functionality found on many web search "
"sites supporting special syntax.\n"
" Placing quotes around several words will convert those words "
"into a phrase.\n"
" 'or' is recognized as searching for the word (or phrase) "
"immediately before 'or' OR the word (or phrase) directly after.\n"
" '-' is recognized as searching for recipes that do not include "
"the word (or phrase) that comes immediately after. \n"
" For example searching for 'apple pie' or cherry -butter will "
"return any recipe that includes the phrase 'apple pie' or the word "
"'cherry' \n"
" in any field included in the full text search but exclude any "
"recipe that has the word 'butter' in any field included.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:48
msgid ""
" \n"
" Raw search is similar to Web except will take puncuation "
"operators such as '|', '&' and '()'\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:59
msgid ""
" \n"
" Another approach to searching that also requires Postgresql is "
"fuzzy search or trigram similarity. A trigram is a group of three "
"consecutive characters.\n"
" For example searching for 'apple' will create x trigrams 'app', "
"'ppl', 'ple' and will create a score of how closely words match the "
"generated trigrams.\n"
" One benefit of searching trigams is that a search for 'sandwich' "
"will find misspelled words such as 'sandwhich' that would be missed by other "
"methods.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:69
#, fuzzy
#| msgid "Search"
msgid "Search Fields"
msgstr "Tìm kiếm"
#: .\cookbook\templates\search_info.html:73
msgid ""
" \n"
" Unaccent is a special case in that it enables searching a field "
"'unaccented' for each search style attempting to ignore accented values. \n"
" For example when you enable unaccent for 'Name' any search "
"(starts with, contains, trigram) will attempt the search ignoring accented "
"characters.\n"
" \n"
" For the other options, you can enable search on any or all "
"fields and they will be combined together with an assumed 'OR'.\n"
" For example enabling 'Name' for Starts With, 'Name' and "
"'Description' for Partial Match and 'Ingredients' and 'Keywords' for Full "
"Search\n"
" and searching for 'apple' will generate a search that will "
"return recipes that have:\n"
" - A recipe name that starts with 'apple'\n"
" - OR a recipe name that contains 'apple'\n"
" - OR a recipe description that contains 'apple'\n"
" - OR a recipe that will have a full text search match ('apple' "
"or 'apples') in ingredients\n"
" - OR a recipe that will have a full text search match in "
"Keywords\n"
"\n"
" Combining too many fields in too many types of search can have a "
"negative impact on performance, create duplicate results or return "
"unexpected results.\n"
" For example, enabling fuzzy search or partial matches will "
"interfere with web search methods. \n"
" Searching for 'apple -pie' with fuzzy search and full text "
"search will return the recipe Apple Pie. Though it is not included in the "
"full text results, it does match the trigram results.\n"
" "
msgstr ""
#: .\cookbook\templates\search_info.html:95
#, fuzzy
#| msgid "Search"
msgid "Search Index"
msgstr "Tìm kiếm"
#: .\cookbook\templates\search_info.html:99
msgid ""
" \n"
" Trigram search and Full Text Search both rely on database "
"indexes to perform effectively. \n"
" You can rebuild the indexes on all fields in the Admin page for "
"Recipes and selecting all recipes and running 'rebuild index for selected "
"recipes'\n"
" You can also rebuild indexes at the command line by executing "
"the management command 'python manage.py rebuildindex'\n"
" "
msgstr ""
#: .\cookbook\templates\setup.html:6
msgid "Cookbook Setup"
msgstr ""
#: .\cookbook\templates\setup.html:14
msgid "Setup"
msgstr ""
#: .\cookbook\templates\setup.html:15
msgid ""
"To start using this application you must first create a superuser account."
msgstr ""
#: .\cookbook\templates\setup.html:20
msgid "Create Superuser account"
msgstr ""
#: .\cookbook\templates\socialaccount\authentication_error.html:7
#: .\cookbook\templates\socialaccount\authentication_error.html:23
msgid "Social Network Login Failure"
msgstr ""
#: .\cookbook\templates\socialaccount\authentication_error.html:25
msgid ""
"An error occurred while attempting to login via your social network account."
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:4
#: .\cookbook\templates\socialaccount\connections.html:7
msgid "Account Connections"
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:10
msgid ""
"You can sign in to your account using any of the following third party\n"
" accounts:"
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:44
msgid ""
"You currently have no social network accounts connected to this account."
msgstr ""
#: .\cookbook\templates\socialaccount\connections.html:47
msgid "Add a 3rd Party Account"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:5
#: .\cookbook\templates\socialaccount\signup.html:5
msgid "Signup"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:9
#, python-format
msgid "Connect %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:11
#, python-format
msgid "You are about to connect a new third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:13
#, python-format
msgid "Sign In Via %(provider)s"
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:15
#, python-format
msgid "You are about to sign in using a third party account from %(provider)s."
msgstr ""
#: .\cookbook\templates\socialaccount\login.html:20
msgid "Continue"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:10
#, python-format
msgid ""
"You are about to use your\n"
" %(provider_name)s account to login to\n"
" %(site_name)s. As a final step, please complete the following form:"
msgstr ""
#: .\cookbook\templates\socialaccount\signup.html:32
msgid "I accept the following"
msgstr ""
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:23
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:31
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:39
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:47
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:55
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:63
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:71
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:79
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:87
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:95
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:103
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:111
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:119
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:127
msgid "Sign in using"
msgstr ""
#: .\cookbook\templates\space_overview.html:6
msgid "Overview"
msgstr ""
#: .\cookbook\templates\space_overview.html:13
msgid "Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:17
msgid ""
"Recipes, foods, shopping lists and more are organized in spaces of one or "
"more people."
msgstr ""
#: .\cookbook\templates\space_overview.html:18
msgid ""
"You can either be invited into an existing space or create your own one."
msgstr ""
#: .\cookbook\templates\space_overview.html:25
msgid "Your Spaces"
msgstr ""
#: .\cookbook\templates\space_overview.html:53
msgid "Owner"
msgstr ""
#: .\cookbook\templates\space_overview.html:73
#: .\cookbook\templates\space_overview.html:83
msgid "Join Space"
msgstr ""
#: .\cookbook\templates\space_overview.html:76
msgid "Join an existing space."
msgstr ""
#: .\cookbook\templates\space_overview.html:78
msgid ""
"To join an existing space either enter your invite token or click on the "
"invite link the space owner send you."
msgstr ""
#: .\cookbook\templates\space_overview.html:91
#: .\cookbook\templates\space_overview.html:100
#, fuzzy
#| msgid "Create User"
msgid "Create Space"
msgstr "Tạo Người dùng"
#: .\cookbook\templates\space_overview.html:94
msgid "Create your own recipe space."
msgstr ""
#: .\cookbook\templates\space_overview.html:96
msgid "Start your own recipe space and invite other users to it."
msgstr ""
#: .\cookbook\templates\system.html:23
msgid "System"
msgstr "Hệ thống"
#: .\cookbook\templates\system.html:24
#, fuzzy
#| msgid ""
#| "\n"
#| " Django Recipes is an open source free software application. It "
#| "can be found on\n"
#| " GitHub.\n"
#| " Changelogs can be found here.\n"
#| " "
msgid ""
"\n"
" Tandoor Recipes is an open source free software application. It can "
"be found on\n"
" GitHub.\n"
" Changelogs can be found here.\n"
" "
msgstr ""
"\n"
" Django Recipes là một phần mã nguồn mở miễn phí. Nó có thể được tìm "
"thấy tại\n"
" GitHub.\n"
" Changelogs có thể tìm thấy tại here."
#: .\cookbook\templates\system.html:30
msgid "System Information"
msgstr "Thông tin Hệ thống"
#: .\cookbook\templates\system.html:51
msgid ""
"\n"
" You need to execute version.py in your update "
"script to generate version information (done automatically in docker).\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:56
msgid "Plugins"
msgstr ""
#: .\cookbook\templates\system.html:67
msgid "Media Serving"
msgstr ""
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:123 .\cookbook\templates\system.html:134
msgid "Warning"
msgstr "Cảnh báo"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:125 .\cookbook\templates\system.html:134
msgid "Ok"
msgstr "Ok"
#: .\cookbook\templates\system.html:70
msgid ""
"Serving media files directly using gunicorn/python is not recommend!\n"
" Please follow the steps described\n"
" here to update\n"
" your installation.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:76 .\cookbook\templates\system.html:91
#: .\cookbook\templates\system.html:104 .\cookbook\templates\system.html:115
#: .\cookbook\views\views.py:168
msgid "Everything is fine!"
msgstr "Mọi thứ đều ổn!"
#: .\cookbook\templates\system.html:80
msgid "Secret Key"
msgstr "Secret Key"
#: .\cookbook\templates\system.html:84
msgid ""
"\n"
" You do not have a SECRET_KEY configured in your "
".env file. Django defaulted to the\n"
" standard key\n"
" provided with the installation which is publicly know and "
"insecure! Please set\n"
" SECRET_KEY int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
"Bạn không có một SECRET_KEY đã được cấu hình trong tệp ."
"env. Django mặc định đặt\n"
"khóa tiêu chuẩn\n"
"được cung cấp trong quá trình cài đặt, khóa này được biết đến công khai và "
"không an toàn! Xin vui lòng đặt\n"
"SECRET_KEY trong tệp cấu hình .env.\n"
" "
#: .\cookbook\templates\system.html:94
msgid "Debug Mode"
msgstr "Chế độ Debug"
#: .\cookbook\templates\system.html:98
msgid ""
"\n"
" This application is still running in debug mode. This is most "
"likely not needed. Turn of debug mode by\n"
" setting\n"
" DEBUG=0 int the .env configuration "
"file.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:107
msgid "Allowed Hosts"
msgstr ""
#: .\cookbook\templates\system.html:111
msgid ""
"\n"
" Your allowed hosts are configured to allow every host. This "
"might be ok in some setups but should be avoided. Please see the docs about "
"this.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:118
msgid "Database"
msgstr "Cơ sở dữ liệu"
#: .\cookbook\templates\system.html:121
msgid "Info"
msgstr "Thông tin"
#: .\cookbook\templates\system.html:131 .\cookbook\templates\system.html:148
msgid "Migrations"
msgstr ""
#: .\cookbook\templates\system.html:137
msgid ""
"\n"
" Migrations should never fail!\n"
" Failed migrations will likely cause major parts of the app to "
"not function correctly.\n"
" If a migration fails make sure you are on the latest version and "
"if so please post the migration log and the overview below in a GitHub "
"issue.\n"
" "
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "False"
msgstr ""
#: .\cookbook\templates\system.html:238
msgid "True"
msgstr ""
#: .\cookbook\templates\system.html:268
msgid "Hide"
msgstr ""
#: .\cookbook\templates\system.html:271
msgid "Show"
msgstr ""
#: .\cookbook\templates\test2.html:6
msgid "Export Recipes"
msgstr "Kết xuất Công thức"
#: .\cookbook\templates\test2.html:14 .\cookbook\templates\test2.html:20
msgid "Export"
msgstr "Kết xuất"
#: .\cookbook\views\api.py:198 .\cookbook\views\api.py:326
msgid "Parameter updated_at incorrectly formatted"
msgstr ""
#: .\cookbook\views\api.py:351 .\cookbook\views\api.py:484
#, python-brace-format
msgid "No {self.basename} with id {pk} exists"
msgstr ""
#: .\cookbook\views\api.py:355
msgid "Cannot merge with the same object!"
msgstr ""
#: .\cookbook\views\api.py:362
#, python-brace-format
msgid "No {self.basename} with id {target} exists"
msgstr ""
#: .\cookbook\views\api.py:367
msgid "Cannot merge with child object!"
msgstr ""
#: .\cookbook\views\api.py:405
#, python-brace-format
msgid "{source.name} was merged successfully with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:411
#, python-brace-format
msgid "An error occurred attempting to merge {source.name} with {target.name}"
msgstr ""
#: .\cookbook\views\api.py:493
#, python-brace-format
msgid "{child.name} was moved successfully to the root."
msgstr ""
#: .\cookbook\views\api.py:496 .\cookbook\views\api.py:514
msgid "An error occurred attempting to move "
msgstr ""
#: .\cookbook\views\api.py:499
msgid "Cannot move an object to itself!"
msgstr ""
#: .\cookbook\views\api.py:505
#, python-brace-format
msgid "No {self.basename} with id {parent} exists"
msgstr ""
#: .\cookbook\views\api.py:511
#, python-brace-format
msgid "{child.name} was moved successfully to parent {parent.name}"
msgstr ""
#: .\cookbook\views\api.py:992
#, python-brace-format
msgid "{obj.name} was removed from the shopping list."
msgstr ""
#: .\cookbook\views\api.py:997 .\cookbook\views\api.py:1627
#, python-brace-format
msgid "{obj.name} was added to the shopping list."
msgstr ""
#: .\cookbook\views\api.py:1239
msgid "Filter meal plans from date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1241
msgid "Filter meal plans to date (inclusive)."
msgstr ""
#: .\cookbook\views\api.py:1244
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1404
msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1406
msgid "Query string matched (fuzzy) against object name."
msgstr ""
#: .\cookbook\views\api.py:1442
msgid ""
"Query string matched (fuzzy) against recipe name. In the future also "
"fulltext search."
msgstr ""
#: .\cookbook\views\api.py:1444
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr ""
#: .\cookbook\views\api.py:1445
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr ""
#: .\cookbook\views\api.py:1446
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1447
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1448
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr ""
#: .\cookbook\views\api.py:1450
msgid "ID of food a recipe should have. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1451
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr ""
#: .\cookbook\views\api.py:1452
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1453
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr ""
#: .\cookbook\views\api.py:1454
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr ""
#: .\cookbook\views\api.py:1456
msgid "ID of book a recipe should be in. For multiple repeat parameter."
msgstr ""
#: .\cookbook\views\api.py:1457
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr ""
#: .\cookbook\views\api.py:1458
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1459
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr ""
#: .\cookbook\views\api.py:1460
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr ""
#: .\cookbook\views\api.py:1462
msgid "ID of unit a recipe should have."
msgstr ""
#: .\cookbook\views\api.py:1464
msgid "Exact rating of recipe"
msgstr ""
#: .\cookbook\views\api.py:1465
msgid "Rating a recipe should have or greater."
msgstr ""
#: .\cookbook\views\api.py:1466
msgid "Rating a recipe should have or smaller."
msgstr ""
#: .\cookbook\views\api.py:1468
msgid "Filter recipes cooked X times."
msgstr ""
#: .\cookbook\views\api.py:1469
msgid "Filter recipes cooked X times or more."
msgstr ""
#: .\cookbook\views\api.py:1470
msgid "Filter recipes cooked X times or less."
msgstr ""
#: .\cookbook\views\api.py:1472
msgid "Filter recipes created on the given date."
msgstr ""
#: .\cookbook\views\api.py:1473
msgid "Filter recipes created on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1474
msgid "Filter recipes created on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1476 .\cookbook\views\api.py:1477
#: .\cookbook\views\api.py:1478
msgid "Filter recipes updated on the given date."
msgstr ""
#: .\cookbook\views\api.py:1480
msgid "Filter recipes last cooked on the given date or after."
msgstr ""
#: .\cookbook\views\api.py:1481
msgid "Filter recipes last cooked on the given date or before."
msgstr ""
#: .\cookbook\views\api.py:1483 .\cookbook\views\api.py:1484
msgid "Filter recipes lasts viewed on the given date."
msgstr ""
#: .\cookbook\views\api.py:1486
msgid "Filter recipes for ones created by the given user ID"
msgstr ""
#: .\cookbook\views\api.py:1487
msgid "If only internal recipes should be returned. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1488
msgid "Returns the results in randomized order. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1490
msgid ""
"Determines the order of the results. Options are: score,-score,name,-name,"
"lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-"
"created_at,lastviewed,-lastviewed"
msgstr ""
#: .\cookbook\views\api.py:1492
msgid "Returns new results first in search results. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1493
msgid ""
"Returns the given number of recently viewed recipes before search results "
"(if given)"
msgstr ""
#: .\cookbook\views\api.py:1494
msgid "ID of a custom filter. Returns all recipes matched by that filter."
msgstr ""
#: .\cookbook\views\api.py:1495
msgid "Filter recipes that can be made with OnHand food. [true/false]"
msgstr ""
#: .\cookbook\views\api.py:1773
msgid ""
"Return the PropertyTypes matching the property category. Repeat for "
"multiple."
msgstr ""
#: .\cookbook\views\api.py:1804 .\cookbook\views\api.py:1860
msgid "Returns only entries associated with the given mealplan id"
msgstr ""
#: .\cookbook\views\api.py:1858
msgid ""
"Returns only elements updated after the given timestamp in ISO 8601 format."
msgstr ""
#: .\cookbook\views\api.py:2031
msgid ""
"Return the Automations matching the automation type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2048
msgid ""
"Text field to store data that gets carried over to the UserSpace created "
"from the InviteLink"
msgstr ""
#: .\cookbook\views\api.py:2049
msgid "Only return InviteLinks that have not been used yet."
msgstr ""
#: .\cookbook\views\api.py:2076
msgid "Return the CustomFilters matching the model type. Repeat for multiple."
msgstr ""
#: .\cookbook\views\api.py:2176
msgid "Nothing to do."
msgstr ""
#: .\cookbook\views\api.py:2222
msgid "Invalid Url"
msgstr ""
#: .\cookbook\views\api.py:2228
msgid "Connection Refused."
msgstr ""
#: .\cookbook\views\api.py:2232
msgid "Bad URL Schema."
msgstr ""
#: .\cookbook\views\api.py:2257
#, fuzzy
#| msgid "The requested page could not be found."
msgid "No usable data could be found."
msgstr "Không thể tìm thấy trang được yêu cầu."
#: .\cookbook\views\api.py:2286 .\cookbook\views\api.py:2434
msgid "You must select an AI provider to perform your request."
msgstr ""
#: .\cookbook\views\api.py:2293 .\cookbook\views\api.py:2441
msgid ""
"You don't have any credits remaining to use AI or AI features are not "
"enabled for your space."
msgstr ""
#: .\cookbook\views\api.py:2499 .\cookbook\views\api.py:2667
msgid "File is above space limit"
msgstr ""
#: .\cookbook\views\api.py:2522 .\cookbook\views\api.py:2684
msgid "Importing is not implemented for this provider"
msgstr ""
#: .\cookbook\views\api.py:2548
msgid ""
"The PDF Exporter is not enabled on this instance as it is still in an "
"experimental state."
msgstr ""
#: .\cookbook\views\api.py:2842
msgid "This feature is not yet available in the hosted version of tandoor!"
msgstr ""
#: .\cookbook\views\api.py:2863
msgid "Sync successful!"
msgstr "Đồng bộ thành công!"
#: .\cookbook\views\api.py:2866
msgid "Error synchronizing with Storage"
msgstr ""
#: .\cookbook\views\views.py:89
msgid "This feature is not available in the demo version!"
msgstr ""
#: .\cookbook\views\views.py:110
msgid ""
"You have successfully created your own recipe space. Start by adding some "
"recipes or invite other people to join you."
msgstr ""
#: .\cookbook\views\views.py:171
#, python-format
msgid "PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!"
msgstr ""
#: .\cookbook\views\views.py:174
#, python-format
msgid "You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended"
msgstr ""
#: .\cookbook\views\views.py:178
msgid "Unable to determine PostgreSQL version."
msgstr ""
#: .\cookbook\views\views.py:182
msgid ""
"This application is not running with a Postgres database backend. This is ok "
"but not recommended as some features only work with postgres databases."
msgstr ""
#: .\cookbook\views\views.py:296
#, fuzzy
#| msgid ""
#| "The setup page can only be used to create the first user! If you have "
#| "forgotten your superuser credentials please consult the django "
#| "documentation on how to reset passwords."
msgid ""
"The setup page can only be used to create the first "
"user! If you have forgotten your superuser credentials "
"please consult the django documentation on how to reset passwords."
msgstr ""
"Trang thiết lập có thể sử dụng để tạo người dùng đầu tiên! Nếu bạn quyên mất "
"định danh của tài khoản quản trị của bạn, xin hãy tham khảo tài liệu django "
"để xem cách đặt lại mật khẩu."
#: .\cookbook\views\views.py:304
msgid "Passwords dont match!"
msgstr "Mật khẩu không trùng khớp!"
#: .\cookbook\views\views.py:312
msgid "User has been created, please login!"
msgstr "Người dùng đã được tạo, xin hãy đăng nhập!"
#: .\cookbook\views\views.py:352
msgid ""
"Reporting share links is not enabled for this instance. Please notify the "
"page administrator to report problems."
msgstr ""
#: .\cookbook\views\views.py:357
msgid ""
"Recipe sharing link has been disabled! For additional information please "
"contact the page administrator."
msgstr ""
#: .\cookbook\views\views.py:383
msgid "Manage recipes, shopping list, meal plans and more."
msgstr ""
#: .\cookbook\views\views.py:397 .\cookbook\views\views.py:398
msgid "Plan"
msgstr "Kế hoạch"
#: .\cookbook\views\views.py:399
msgid "View your meal Plan"
msgstr ""
#: .\cookbook\views\views.py:418
#, fuzzy
#| msgid "Shopping Lists"
msgid "View your shopping lists"
msgstr "Danh sách Mua sắm"
#~ msgid ""
#~ "Both fields are optional. If none are given the username will be "
#~ "displayed instead"
#~ msgstr ""
#~ "Cả hai trường tên đều là tùy chọn. Nếu không trường tên nào được cung "
#~ "cấp, tên đăng nhập sẽ được hiển thị thay."
#~ msgid "Name"
#~ msgstr "Tên"
#~ msgid "Keywords"
#~ msgstr "Từ khóa"
#~ msgid "Preparation time in minutes"
#~ msgstr "Thời gian chuẩn bị theo phút"
#~ msgid "Waiting time (cooking/baking) in minutes"
#~ msgstr "Thời gian đợi (nấu/nướng) theo phút"
#~ msgid "Path"
#~ msgstr "Đường dẫn"
#~ msgid "Storage UID"
#~ msgstr "UID Lưu trữ"
#~ msgid "Add your comment: "
#~ msgstr "Thêm bình luận:"
#~ msgid "Leave empty for dropbox and enter app password for nextcloud."
#~ msgstr "Đặt trống cho Dropbox và nhập mật khẩu cho Nextcloud."
#~ msgid "Leave empty for nextcloud and enter api token for dropbox."
#~ msgstr "Đặt trống cho Nextcloud và nhập token API cho Dropbox."
#~ msgid ""
#~ "Leave empty for dropbox and enter only base url for nextcloud (/"
#~ "remote.php/webdav/ is added automatically)"
#~ msgstr ""
#~ "Đặt trống cho Dropbox và nhập duy nhất URL gốc của Nextcloud (/"
#~ "remote.php/webdav/ được tự động thêm vào)"
#~ msgid "Search String"
#~ msgstr "Chuỗi Tìm kiếm"
#~ msgid "File ID"
#~ msgstr "ID Tệp"
#, fuzzy
#~| msgid "Search"
#~ msgid "Search Method"
#~ msgstr "Tìm kiếm"
#, fuzzy
#~| msgid "Search"
#~ msgid "Fuzzy Search"
#~ msgstr "Tìm kiếm"
#~ msgid "Delete"
#~ msgstr "Xóa"
#~ msgid "Settings"
#~ msgstr "Cài đặt"
#, fuzzy
#~| msgid "New Food"
#~ msgid "Foods"
#~ msgstr "Món Mới"
#, fuzzy
#~| msgid "Information"
#~ msgid "Automations"
#~ msgstr "Thông tin"
#, fuzzy
#~| msgid "File ID"
#~ msgid "Files"
#~ msgstr "ID Tệp"
#~ msgid "History"
#~ msgstr "Lịch sử"
#, fuzzy
#~| msgid "Ingredients"
#~ msgid "Ingredient Editor"
#~ msgstr "Nguyên liệu"
#~ msgid "External Recipes"
#~ msgstr "Công thức Ngoại"
#, fuzzy
#~| msgid "Settings"
#~ msgid "Space Settings"
#~ msgstr "Cài đặt"
#, fuzzy
#~| msgid "External Recipes"
#~ msgid "External Connectors"
#~ msgstr "Công thức Ngoại"
#~ msgid "Admin"
#~ msgstr "Quản trị"
#~ msgid "Markdown Guide"
#~ msgstr "Hướng dẫn Markdown"
#~ msgid "GitHub"
#~ msgstr "GitHub"
#~ msgid "API Browser"
#~ msgstr "API Trình duyệt"
#~ msgid "Sync"
#~ msgstr "Đồng bộ"
#~ msgid "Manage watched Folders"
#~ msgstr "Quản lý các Thư mục được theo dõi"
#~ msgid "The path must be in the following format"
#~ msgstr "Đường dẫn cần được đặt ở định dạng sau"
#~ msgid "Save"
#~ msgstr "Lưu"
#~ msgid "Sync Now!"
#~ msgstr "Đồng bộ ngay!"
#, fuzzy
#~| msgid "Recipes"
#~ msgid "Show Recipes"
#~ msgstr "Công thức"
#, fuzzy
#~| msgid "Show Links"
#~ msgid "Show Log"
#~ msgstr "Hiển thị các Liên kết"
#~ msgid "Importing Recipes"
#~ msgstr "Đang nhập Công thức"
#~ msgid ""
#~ "This can take a few minutes, depending on the number of recipes in sync, "
#~ "please wait."
#~ msgstr ""
#~ "Quá trình này có thể mất ít phút, tùy thuộc vào số lượng công thức sẽ "
#~ "đồng bộ, xin vui lòng đợi."
#~ msgid "Recipe Books"
#~ msgstr "Sách Công thức"
#~ msgid "Import new Recipe"
#~ msgstr "Nhập Công thức mới"
#~ msgid "Edit Recipe"
#~ msgstr "Sửa Công thức"
#~ msgid "Edit"
#~ msgstr "Sửa"
#~ msgid "View"
#~ msgstr "Xem"
#~ msgid "Delete original file"
#~ msgstr "Xóa tệp gốc"
#~ msgid "List"
#~ msgstr "Danh sách"
#~ msgid "Filter"
#~ msgstr "Lọc"
#~ msgid "Import all"
#~ msgstr "Nhập tất cả"
#~ msgid "New"
#~ msgstr "Mới"
#~ msgid "previous"
#~ msgstr "trước"
#~ msgid "next"
#~ msgstr "sau"
#~ msgid "Security Warning"
#~ msgstr "Cảnh báo Bảo mật"
#~ msgid ""
#~ "\n"
#~ " The Password and Token field are stored as plain text"
#~ "b> inside the database.\n"
#~ " This is necessary because they are needed to make API requests, "
#~ "but it also increases the risk of\n"
#~ " someone stealing it.
\n"
#~ " To limit the possible damage tokens or accounts with limited "
#~ "access can be used.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ "Trường Mật khẩu và token được đặt dưới dạng đoạn văn bản thuần "
#~ "túy trong cơ sở dữ liệu\n"
#~ "Điều này là cần thiết bởi vì chúng sẽ cần trong việc tạo các yêu cầu API, "
#~ "nhưng cũng đồng thời sẽ tăng rủi ro \n"
#~ "trong việc ai đó có thể sẽ lấy trộm chúng.
\n"
#~ "Để hạn chết khả năng gây thiệt hại, bạn có thể sử dụng các token hoặc tài "
#~ "khoản với quyền truy cập bị giới hạn."
#~ msgid "Comments"
#~ msgstr "Bình luận"
#~ msgid "by"
#~ msgstr "bởi"
#~ msgid "Comment"
#~ msgstr "Bình luận"
#~ msgid "URL Import"
#~ msgstr "Nhập URL"
#~ msgid "Invite Link"
#~ msgstr "Liên kết Mời"
#, fuzzy
#~| msgid "Changes saved!"
#~ msgid "Config saved!"
#~ msgstr "Đã lưu các thay đổi!"
#~ msgid "Changes saved!"
#~ msgstr "Đã lưu các thay đổi!"
#~ msgid "Error saving changes!"
#~ msgstr "Lỗi khi lưu các thay đổi!"
#~ msgid "Invite Links"
#~ msgstr "Các liên kết Mời"
#, fuzzy
#~| msgid "Shopping Lists"
#~ msgid "Shopping Categories"
#~ msgstr "Danh sách Mua sắm"
#, fuzzy
#~| msgid "Filter"
#~ msgid "Custom Filters"
#~ msgstr "Lọc"
#~ msgid "Steps"
#~ msgstr "Các bước"
#~ msgid "Imported new recipe!"
#~ msgstr "Đã nhập công thức mới!"
#~ msgid "There was an error importing this recipe!"
#~ msgstr "Đã có lỗi xảy ra khi nhập công thức này!"
#~ msgid "You do not have the required permissions to perform this action!"
#~ msgstr "Bạn không có đủ quyền cần thiết để thực hiện hành động này!"
#~ msgid "Comment saved!"
#~ msgstr "Đã lưu Bình luận!"
#~ msgid "Invite Link not valid or already used!"
#~ msgstr "Liên kết mời không hợp lệ hoặc đã được sử dụng!"
#~ msgid ""
#~ "Color of the top navigation bar. Not all colors work with all themes, "
#~ "just try them out!"
#~ msgstr ""
#~ "Màu của thanh điều hướng trên. Không phải tất cả các màu sắc đều phối hợp "
#~ "tốt với chủ đề giao diện, hãy thử chúng!"
#~ msgid ""
#~ "Default Unit to be used when inserting a new ingredient into a recipe."
#~ msgstr ""
#~ "Đơn vị mặc định được sử dụng khi thêm vào một nguyên liệu mới trong công "
#~ "thức."
#~ msgid ""
#~ "Enables support for fractions in ingredient amounts (e.g. convert "
#~ "decimals to fractions automatically)"
#~ msgstr ""
#~ "Bật hỗ trợ cho phân số trong số lượng nguyên liệu (ví dụ: tự động chuyển "
#~ "đổi từ số thập phân sang phân số)"
#~ msgid "Show recently viewed recipes on search page."
#~ msgstr "Hiển thị các công thức đã xem gần đây ở trang tìm kiếm."
#~ msgid "Number of decimals to round ingredients."
#~ msgstr "Số thập phân để làm tròn nguyên liệu."
#~ msgid ""
#~ "If you want to be able to create and see comments underneath recipes."
#~ msgstr "Nếu bạn muốn tạo và xem bình luận ở dưới công thức."
#~ msgid ""
#~ "Setting to 0 will disable auto sync. When viewing a shopping list the "
#~ "list is updated every set seconds to sync changes someone else might have "
#~ "made. Useful when shopping with multiple people but might use a little "
#~ "bit of mobile data. If lower than instance limit it is reset when saving."
#~ msgstr ""
#~ "Đặt giá trị 0 sẽ tắt tự động đồng bộ. Khi xem danh sách mua sắm, danh "
#~ "sách được cập nhật theo mỗi giá trị giây, để đồng bộ với bất kỳ thay đổi "
#~ "nào mà người khác có thể tạo ra. Hữu dụng khi mua sắm cùng với nhiều "
#~ "người, nhưng có thể sử dụng nhiều hơn dữ liệu di động. Nếu giá trị nhỏ "
#~ "hơn giới hạn của hệ thống, nó sẽ được đặt lại sau khi lưu."
#~ msgid ""
#~ "Include - [ ] in list for easier usage in markdown based "
#~ "documents."
#~ msgstr ""
#~ "Thêm -[ ] trong danh sách để sử dụng dễ dàng hơn trong đoạn "
#~ "văn bản markdown."
#~ msgid "New unit that other gets replaced by."
#~ msgstr "Đơn vị mới thay thế cho các đơn vị khác."
#~ msgid "Old Unit"
#~ msgstr "Đơn vị Cũ"
#~ msgid "Unit that should be replaced."
#~ msgstr "Đơn vị nên được thay thế."
#~ msgid "New food that other gets replaced by."
#~ msgstr "Món mới thay thế cho món khác."
#~ msgid "Old Food"
#~ msgstr "Món Cũ"
#~ msgid ""
#~ "A username is not required, if left blank the new user can choose one."
#~ msgstr ""
#~ "Tên đăng nhập không bắt buộc, nếu bỏ trống thì người dùng mới có thể chọn "
#~ "tên."
#~ msgid "Small"
#~ msgstr "Nhỏ"
#~ msgid "Large"
#~ msgstr "Lớn"
#~ msgid "Time"
#~ msgstr "Thời gian"
#~ msgid "Link"
#~ msgstr "Liên kết"
#~ msgid "Configure Sync"
#~ msgstr "Cấu hình Đồng bộ"
#~ msgid "Statistics"
#~ msgstr "Thống kê"
#~ msgid "Units & Ingredients"
#~ msgstr "Đơn vị & Nguyên liệu"
#~ msgid "Logout"
#~ msgstr "Đăng xuất"
#~ msgid "New Book"
#~ msgstr "Sách Mới"
#~ msgid "There are no recipes in this book yet."
#~ msgstr "Hiện chưa có công thức nào trong sách này."
#~ msgid "Waiting Time"
#~ msgstr "Thời gian Chờ"
#~ msgid "Select Keywords"
#~ msgstr "Chọn các Từ khóa"
#~ msgid "Move Up"
#~ msgstr "Chuyển Lên"
#~ msgid "Move Down"
#~ msgstr "Chuyển Xuống"
#~ msgid "Select"
#~ msgstr "Chọn"
#~ msgid "Disable Amount"
#~ msgstr "Tắt Số lượng"
#~ msgid "Enable Amount"
#~ msgstr "Bật Số lượng"
#~ msgid "Save & View"
#~ msgstr "Lưu & Xem"
#~ msgid "View Recipe"
#~ msgstr "Xem Công thức"
#~ msgid "Delete Recipe"
#~ msgstr "Xóa Công thức"
#~ msgid "Edit Ingredients"
#~ msgstr "Sửa Nguyên liệu"
#~ msgid "Import Recipes"
#~ msgstr "Nhập Công thức"
#~ msgid "Rating"
#~ msgstr "Đánh giá"
#~ msgid "Close"
#~ msgstr "Đóng"
#~ msgid "Open Recipe"
#~ msgstr "Mở Công thức"
#~ msgid "Created by"
#~ msgstr "Được tạo bởi"
#~ msgid "Shared with"
#~ msgstr "Đã chia sẻ với"
#~ msgid "Meal Plan View"
#~ msgstr "Xem Kế hoạch Khẩu phần ăn"
#~ msgid "Account"
#~ msgstr "Tài khoản"
#~ msgid "Language"
#~ msgstr "Ngôn ngữ"
#~ msgid ""
#~ "You can use both basic authentication and token based authentication to "
#~ "access the REST API."
#~ msgstr ""
#~ "Bạn có thể sử dụng cả hai phương thức xác thực cơ bản và xác thực bằng "
#~ "token để truy cập đến REST API."
#~ msgid ""
#~ "Use the token as an Authorization header prefixed by the word token as "
#~ "shown in the following examples:"
#~ msgstr ""
#~ "Sử dụng token ở tiền tố Authorization header với từ token như hiển thị ở "
#~ "ví dụ dưới đây:"
#~ msgid "or"
#~ msgstr "hoặc"
#~ msgid "No recipes selected"
#~ msgstr "Không có công thức nào được chọn"
#~ msgid "Amount"
#~ msgstr "Số lượng"
#~ msgid "Select User"
#~ msgstr "Chọn người dùng"
#~ msgid "Finished"
#~ msgstr "Hoàn thành"
#~ msgid "Copy/Export"
#~ msgstr "Sao chép/Kết xuất"
#~ msgid "There was an error creating a resource!"
#~ msgstr "Đã xảy ra lỗi khi tạo một tài nguyên!"
#~ msgid "Stats"
#~ msgstr "Thống kê"
#~ msgid "Number of objects"
#~ msgstr "Số lượng các đối tượng"
#~ msgid "Recipe Imports"
#~ msgstr "Công thức được nhập"
#~ msgid "Objects stats"
#~ msgstr "Thống kê các đối tượng"
#~ msgid "Recipes without Keywords"
#~ msgstr "Công thức không có Từ khóa"
#~ msgid "Internal Recipes"
#~ msgstr "Công thức Nội"
#~ msgid "Backup & Restore"
#~ msgstr "Sao lưu & Khôi phục"
#~ msgid "Download Backup"
#~ msgstr "Tải về bản Sao lưu"
#~ msgid "Enter website URL"
#~ msgstr "Nhập URL website"
#~ msgid "Recipe Name"
#~ msgstr "Tên Công thức"
#~ msgid "Select one"
#~ msgstr "Chọn một"
#~ msgid "All Keywords"
#~ msgstr "Tất cả các Từ khóa"
#~ msgid "Google ld+json Info"
#~ msgstr "Google Id+ Thông tin json"
#~ msgid "GitHub Issues"
#~ msgstr "GitHub Issues"
#~ msgid "Preference for given user already exists"
#~ msgstr "Đã tồn tại cài đặt cho người dùng này"
#~ msgid "Bookmarks"
#~ msgstr "Đánh dấu"
#~ msgid "Units merged!"
#~ msgstr "Đã gộp các Đơn vị!"
#~ msgid "Bookmark saved!"
#~ msgstr "Đã lưu Đánh dấu!"
================================================
FILE: cookbook/locale/zh_CN/LC_MESSAGES/django.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-22 20:15+0200\n"
"PO-Revision-Date: 2024-11-04 10:29+0000\n"
"Last-Translator: Johnny Ip \n"
"Language-Team: Chinese (Simplified) \n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 5.6.2\n"
#: .\cookbook\forms.py:50
msgid "Default"
msgstr "默认"
#: .\cookbook\forms.py:77
msgid ""
"To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything."
msgstr ""
"为防止重复,忽略与现有同名的菜谱。选中此框可导入所有内容(危险操作,请先备"
"份)。"
#: .\cookbook\forms.py:108
msgid "Maximum number of users for this space reached."
msgstr "已达到该空间的最大用户数。"
#: .\cookbook\forms.py:114
msgid "Email address already taken!"
msgstr "电子邮件地址已被注册!"
#: .\cookbook\forms.py:121
msgid ""
"An email address is not required but if present the invite link will be sent "
"to the user."
msgstr "电子邮件地址不是必需的,但如果存在,邀请链接将被发送给用户。"
#: .\cookbook\forms.py:133
msgid "Name already taken."
msgstr "名字已被占用。"
#: .\cookbook\forms.py:144 .\cookbook\forms.py:158
msgid "Accept Terms and Privacy"
msgstr "接受条款及隐私政策"
#: .\cookbook\helper\AllAuthCustomAdapter.py:41
msgid ""
"In order to prevent spam, the requested email was not send. Please wait a "
"few minutes and try again."
msgstr "为了防止垃圾邮件,所要求的电子邮件没有被发送。请等待几分钟后再试。"
#: .\cookbook\helper\permission_helper.py:165
#: .\cookbook\helper\permission_helper.py:186 .\cookbook\views\views.py:138
msgid "You are not logged in and therefore cannot view this page!"
msgstr "你没有登录,因此不能查看这个页面!"
#: .\cookbook\helper\permission_helper.py:168
#: .\cookbook\helper\permission_helper.py:173
#: .\cookbook\helper\permission_helper.py:198
#: .\cookbook\helper\permission_helper.py:265
#: .\cookbook\helper\permission_helper.py:279
#: .\cookbook\helper\permission_helper.py:290
#: .\cookbook\helper\permission_helper.py:301
#: .\cookbook\helper\permission_helper.py:317
#: .\cookbook\helper\permission_helper.py:343
#: .\cookbook\helper\permission_helper.py:359
msgid "You do not have the required permissions to view this page!"
msgstr "你没有必要的权限来查看这个页面!"
#: .\cookbook\helper\permission_helper.py:191
#: .\cookbook\helper\permission_helper.py:214
#: .\cookbook\helper\permission_helper.py:236
#: .\cookbook\helper\permission_helper.py:251
msgid "You cannot interact with this object as it is not owned by you!"
msgstr "你不能与此对象交互,因为它不属于你!"
#: .\cookbook\helper\permission_helper.py:420
msgid "You have reached the maximum number of recipes for your space."
msgstr "你已经达到了空间的菜谱的最大数量。"
#: .\cookbook\helper\permission_helper.py:432
msgid "You have more users than allowed in your space."
msgstr "你的空间中的用户数超过了允许的数量。"
#: .\cookbook\helper\recipe_url_import.py:319
msgid "reverse rotation"
msgstr "反向旋转"
#: .\cookbook\helper\recipe_url_import.py:320
msgid "careful rotation"
msgstr "小心旋转"
#: .\cookbook\helper\recipe_url_import.py:321
msgid "knead"
msgstr "揉"
#: .\cookbook\helper\recipe_url_import.py:322
msgid "thicken"
msgstr "增稠"
#: .\cookbook\helper\recipe_url_import.py:323
msgid "warm up"
msgstr "预热"
#: .\cookbook\helper\recipe_url_import.py:324
msgid "ferment"
msgstr "发酵"
#: .\cookbook\helper\recipe_url_import.py:325
#, fuzzy
#| msgid "Last cooked"
msgid "slow cook"
msgstr "最近烹饪"
#: .\cookbook\helper\recipe_url_import.py:326
msgid "egg boiler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:327
msgid "kettle"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:328
msgid "blend"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:329
msgid "pre-clean"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:330
msgid "high temperature"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:331
msgid "rice cooker"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:332
msgid "caramelize"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:333
msgid "peeler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:334
msgid "slicer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:335
msgid "grater"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:336
msgid "spiralizer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:337
msgid "sous-vide"
msgstr "真空烹调法"
#: .\cookbook\helper\shopping_helper.py:150
msgid "You must supply a servings size"
msgstr "你必须提供一些份量"
#: .\cookbook\helper\template_helper.py:97
#: .\cookbook\helper\template_helper.py:99
#: .\cookbook\helper\template_helper.py:101
msgid "Could not parse template code."
msgstr "无法解析模板代码。"
#: .\cookbook\integration\copymethat.py:44
#: .\cookbook\integration\melarecipes.py:37
msgid "Favorite"
msgstr "喜欢"
#: .\cookbook\integration\copymethat.py:50
msgid "I made this"
msgstr "我做的"
#: .\cookbook\integration\integration.py:238
msgid ""
"Importer expected a .zip file. Did you choose the correct importer type for "
"your data ?"
msgstr "需要一个 .zip 文件。你是否为数据选择了正确的导入器类型?"
#: .\cookbook\integration\integration.py:241
msgid ""
"An unexpected error occurred during the import. Please make sure you have "
"uploaded a valid file."
msgstr "在导入过程中发生了一个意外的错误。请确认你已经上传了一个有效的文件。"
#: .\cookbook\integration\integration.py:246
msgid "The following recipes were ignored because they already existed:"
msgstr "以下菜谱被忽略了,因为它们已经存在了:"
#: .\cookbook\integration\integration.py:250
#, python-format
msgid "Imported %s recipes."
msgstr "导入了%s菜谱。"
#: .\cookbook\integration\mealie1.py:210
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "Calories"
msgstr "卡路里"
#: .\cookbook\integration\mealie1.py:211
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
msgid "Carbohydrates"
msgstr "碳水化合物"
#: .\cookbook\integration\mealie1.py:212
msgid "Cholesterol"
msgstr ""
#: .\cookbook\integration\mealie1.py:213
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
msgid "Fat"
msgstr "脂肪"
#: .\cookbook\integration\mealie1.py:214
msgid "Fiber"
msgstr ""
#: .\cookbook\integration\mealie1.py:215
#, fuzzy
#| msgid "Proteins"
msgid "Protein"
msgstr "蛋白质"
#: .\cookbook\integration\mealie1.py:216
msgid "Saturated Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:217
msgid "Sodium"
msgstr ""
#: .\cookbook\integration\mealie1.py:218
msgid "Sugar"
msgstr ""
#: .\cookbook\integration\mealie1.py:219
msgid "Trans Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:220
msgid "Unsaturated Fat"
msgstr ""
#: .\cookbook\integration\openeats.py:28
msgid "Recipe source:"
msgstr "菜谱来源:"
#: .\cookbook\integration\paprika.py:49
msgid "Notes"
msgstr "说明"
#: .\cookbook\integration\paprika.py:52
msgid "Nutritional Information"
msgstr "营养信息"
#: .\cookbook\integration\paprika.py:56
msgid "Source"
msgstr "来源"
#: .\cookbook\integration\recettetek.py:55
#: .\cookbook\integration\recipekeeper.py:70
msgid "Imported from"
msgstr "导入"
#: .\cookbook\integration\saffron.py:23
msgid "Servings"
msgstr "份量"
#: .\cookbook\integration\saffron.py:25
msgid "Waiting time"
msgstr "等待时间"
#: .\cookbook\integration\saffron.py:27
msgid "Preparation Time"
msgstr "准备时间"
#: .\cookbook\integration\saffron.py:29 .\cookbook\templates\index.html:6
msgid "Cookbook"
msgstr "烹饪手册"
#: .\cookbook\integration\saffron.py:31
msgid "Section"
msgstr "部分"
#: .\cookbook\management\commands\fix_duplicate_properties.py:15
msgid "Fixes foods with "
msgstr "修复食谱中的重复字段 "
#: .\cookbook\management\commands\rebuildindex.py:14
msgid "Rebuilds full text search index on Recipe"
msgstr "在菜谱上重建全文搜索索引"
#: .\cookbook\management\commands\rebuildindex.py:18
msgid "Only Postgresql databases use full text search, no index to rebuild"
msgstr "仅 PostgreSQL 数据库使用全文搜索,没有重建索引"
#: .\cookbook\management\commands\rebuildindex.py:29
msgid "Recipe index rebuild complete."
msgstr "菜谱索引重建完成。"
#: .\cookbook\management\commands\rebuildindex.py:31
msgid "Recipe index rebuild failed."
msgstr "菜谱索引重建失败。"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
msgid "Breakfast"
msgstr "早餐"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:19
msgid "Lunch"
msgstr "午餐"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:24
msgid "Dinner"
msgstr "晚餐"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:29 .\cookbook\models.py:971
msgid "Other"
msgstr "其他"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "g"
msgstr "克"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "Proteins"
msgstr "蛋白质"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "kcal"
msgstr "千卡"
#: .\cookbook\models.py:325
msgid ""
"Maximum file storage for space in MB. 0 for unlimited, -1 to disable file "
"upload."
msgstr "空间的最大文件存储量,单位为 MB。0表示无限制,-1表示禁止上传文件。"
#: .\cookbook\models.py:513
msgid "Search"
msgstr "搜索"
#: .\cookbook\models.py:514
msgid "Meal-Plan"
msgstr "膳食计划"
#: .\cookbook\models.py:515
msgid "Books"
msgstr "烹饪手册"
#: .\cookbook\models.py:516 .\cookbook\views\views.py:416
#: .\cookbook\views\views.py:417
msgid "Shopping"
msgstr "购物"
#: .\cookbook\models.py:967
msgid "Nutrition"
msgstr "营养"
#: .\cookbook\models.py:968
msgid "Allergen"
msgstr "过敏原"
#: .\cookbook\models.py:969
msgid "Price"
msgstr "价格"
#: .\cookbook\models.py:970
msgid "Goal"
msgstr "目标"
#: .\cookbook\models.py:1467 .\cookbook\templates\search_info.html:28
msgid "Simple"
msgstr "简明"
#: .\cookbook\models.py:1468 .\cookbook\templates\search_info.html:33
msgid "Phrase"
msgstr "短语"
#: .\cookbook\models.py:1469 .\cookbook\templates\search_info.html:38
msgid "Web"
msgstr "网络"
#: .\cookbook\models.py:1470 .\cookbook\templates\search_info.html:47
msgid "Raw"
msgstr "原始"
#: .\cookbook\models.py:1525
msgid "Food Alias"
msgstr "食物别名"
#: .\cookbook\models.py:1526
msgid "Unit Alias"
msgstr "单位别名"
#: .\cookbook\models.py:1527
msgid "Keyword Alias"
msgstr "关键词别名"
#: .\cookbook\models.py:1528
msgid "Description Replace"
msgstr "描述"
#: .\cookbook\models.py:1529
msgid "Instruction Replace"
msgstr "指示"
#: .\cookbook\models.py:1530
msgid "Never Unit"
msgstr "禁用单位识别"
#: .\cookbook\models.py:1531
msgid "Transpose Words"
msgstr "字符串转置"
#: .\cookbook\models.py:1532
msgid "Food Replace"
msgstr "食物替换"
#: .\cookbook\models.py:1533
msgid "Unit Replace"
msgstr "单位替换"
#: .\cookbook\models.py:1534
msgid "Name Replace"
msgstr "名称替换"
#: .\cookbook\models.py:1564
msgid "Recipe"
msgstr "菜谱"
#: .\cookbook\models.py:1565
msgid "Food"
msgstr "食物"
#: .\cookbook\models.py:1566
msgid "Keyword"
msgstr "关键词"
#: .\cookbook\serializer.py:262
msgid "File uploads are not enabled for this Space."
msgstr "未为此空间启用文件上传。"
#: .\cookbook\serializer.py:273
msgid "You have reached your file upload limit."
msgstr "你已达到文件上传的限制。"
#: .\cookbook\serializer.py:281
msgid "The given file type is not allowed."
msgstr ""
#: .\cookbook\serializer.py:421 .\cookbook\views\views.py:94
msgid ""
"You have the reached the maximum amount of spaces that can be owned by you."
msgstr "你拥有的空间数量已经达到上限。"
#: .\cookbook\serializer.py:434
msgid "Space Name must be unique."
msgstr ""
#: .\cookbook\serializer.py:469
msgid "Cannot modify Space owner permission."
msgstr "无法修改空间所有者权限。"
#: .\cookbook\serializer.py:1596
msgid "Hello"
msgstr "你好"
#: .\cookbook\serializer.py:1596
msgid "You have been invited by "
msgstr "您已被邀请至 "
#: .\cookbook\serializer.py:1598
msgid " to join their Tandoor Recipes space "
msgstr " 加入他们的 Tandoor 食谱空间 "
#: .\cookbook\serializer.py:1600
msgid "Click the following link to activate your account: "
msgstr "点击以下链接激活您的帐户: "
#: .\cookbook\serializer.py:1602
msgid ""
"If the link does not work use the following code to manually join the space: "
msgstr "如果链接不起作用,请使用下面的代码手动加入空间: "
#: .\cookbook\serializer.py:1604
msgid "The invitation is valid until "
msgstr "邀请有效期至 "
#: .\cookbook\serializer.py:1606
msgid ""
"Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub "
msgstr "Tandoor 是一个开源食谱管理器。 在 GitHub 上查看 "
#: .\cookbook\serializer.py:1609
msgid "Tandoor Recipes Invite"
msgstr "Tandoor 食谱邀请"
#: .\cookbook\serializer.py:1813
msgid "Existing shopping list to update"
msgstr "要更新现有的购物清单"
#: .\cookbook\serializer.py:1815
msgid ""
"List of ingredient IDs from the recipe to add, if not provided all "
"ingredients will be added."
msgstr "要添加的食谱中食材识别符列表,不提供则添加所有食材。"
#: .\cookbook\serializer.py:1817
msgid ""
"Providing a list_recipe ID and servings of 0 will delete that shopping list."
msgstr "提供一个菜谱列表识别符或份数为0将删除该购物清单。"
#: .\cookbook\serializer.py:1826
msgid "Amount of food to add to the shopping list"
msgstr "要添加到购物清单中的食物数量"
#: .\cookbook\serializer.py:1828
msgid "ID of unit to use for the shopping list"
msgstr "用于购物清单的单位识别符"
#: .\cookbook\serializer.py:1830
msgid "When set to true will delete all food from active shopping lists."
msgstr "当设置为 true 时,将从活动的购物列表中删除所有食物。"
#: .\cookbook\templates\404.html:5
msgid "404 Error"
msgstr "404 错误"
#: .\cookbook\templates\404.html:18
msgid "The page you are looking for could not be found."
msgstr "找不到你要找的页面。"
#: .\cookbook\templates\404.html:33
msgid "Take me Home"
msgstr "回到主页"
#: .\cookbook\templates\404.html:35
msgid "Report a Bug"
msgstr "报告一个错误"
#: .\cookbook\templates\account\email.html:6
#: .\cookbook\templates\account\email.html:10
msgid "E-mail Addresses"
msgstr "电子邮件地址"
#: .\cookbook\templates\account\email.html:12
msgid "The following e-mail addresses are associated with your account:"
msgstr "以下电子邮件地址与你的帐号相关联:"
#: .\cookbook\templates\account\email.html:29
msgid "Verified"
msgstr "已验证"
#: .\cookbook\templates\account\email.html:31
msgid "Unverified"
msgstr "未验证"
#: .\cookbook\templates\account\email.html:33
msgid "Primary"
msgstr "主要"
#: .\cookbook\templates\account\email.html:40
msgid "Make Primary"
msgstr "当做主要"
#: .\cookbook\templates\account\email.html:42
msgid "Re-send Verification"
msgstr "重新发送验证"
#: .\cookbook\templates\account\email.html:43
#: .\cookbook\templates\socialaccount\connections.html:36
msgid "Remove"
msgstr "移除"
#: .\cookbook\templates\account\email.html:51
msgid "Warning:"
msgstr "警告:"
#: .\cookbook\templates\account\email.html:51
msgid ""
"You currently do not have any e-mail address set up. You should really add "
"an e-mail address so you can receive notifications, reset your password, etc."
msgstr ""
"你目前没有设置任何电子邮件地址。你真的应该添加一个电子邮件地址,这样你就可以"
"收到通知,重置你的密码等等。"
#: .\cookbook\templates\account\email.html:57
msgid "Add E-mail Address"
msgstr "添加电子邮件地址"
#: .\cookbook\templates\account\email.html:62
msgid "Add E-mail"
msgstr "添加电子邮件"
#: .\cookbook\templates\account\email.html:72
msgid "Do you really want to remove the selected e-mail address?"
msgstr "你真的想删除选定的电子邮件地址吗?"
#: .\cookbook\templates\account\email_confirm.html:6
#: .\cookbook\templates\account\email_confirm.html:10
msgid "Confirm E-mail Address"
msgstr "确认电子邮件地址"
#: .\cookbook\templates\account\email_confirm.html:16
#, python-format
msgid ""
"Please confirm that\n"
" %(email)s is an e-mail address "
"for user %(user_display)s\n"
" ."
msgstr ""
"请确认\n"
" %(email)s 是用户 "
"%(user_display)s 的电子邮件地址\n"
" ."
#: .\cookbook\templates\account\email_confirm.html:22
msgid "Confirm"
msgstr "确认"
#: .\cookbook\templates\account\email_confirm.html:29
#, python-format
msgid ""
"This e-mail confirmation link expired or is invalid. Please\n"
" issue a new e-mail confirmation "
"request."
msgstr ""
"此电子邮件确认链接已过期或无效。请\n"
" 发起新的电子邮件确认请求。"
#: .\cookbook\templates\account\login.html:8
#: .\cookbook\templates\openid\login.html:8
msgid "Login"
msgstr "登录"
#: .\cookbook\templates\account\login.html:15
#: .\cookbook\templates\account\login.html:31
#: .\cookbook\templates\account\password_reset.html:39
#: .\cookbook\templates\account\password_reset_done.html:31
#: .\cookbook\templates\account\signup.html:68
#: .\cookbook\templates\account\signup_closed.html:15
#: .\cookbook\templates\openid\login.html:15
#: .\cookbook\templates\openid\login.html:26
#: .\cookbook\templates\socialaccount\authentication_error.html:15
msgid "Sign In"
msgstr "登录"
#: .\cookbook\templates\account\login.html:34
#: .\cookbook\templates\account\password_reset.html:41
#: .\cookbook\templates\account\password_reset_done.html:33
#: .\cookbook\templates\socialaccount\signup.html:8
#: .\cookbook\templates\socialaccount\signup.html:56
msgid "Sign Up"
msgstr "注册"
#: .\cookbook\templates\account\login.html:38
msgid "Lost your password?"
msgstr "遗失密码?"
#: .\cookbook\templates\account\login.html:39
#: .\cookbook\templates\account\password_reset.html:29
msgid "Reset My Password"
msgstr "重置我的密码"
#: .\cookbook\templates\account\login.html:50
msgid "Social Login"
msgstr "关联登录"
#: .\cookbook\templates\account\login.html:51
msgid "You can use any of the following providers to sign in."
msgstr "你可以使用以下任意提供程序来登录。"
#: .\cookbook\templates\account\logout.html:5
#: .\cookbook\templates\account\logout.html:9
#: .\cookbook\templates\account\logout.html:18
msgid "Sign Out"
msgstr "退出"
#: .\cookbook\templates\account\logout.html:11
msgid "Are you sure you want to sign out?"
msgstr "你确定要退出吗?"
#: .\cookbook\templates\account\password_change.html:6
#: .\cookbook\templates\account\password_change.html:10
#: .\cookbook\templates\account\password_change.html:15
#: .\cookbook\templates\account\password_reset_from_key.html:7
#: .\cookbook\templates\account\password_reset_from_key.html:13
#: .\cookbook\templates\account\password_reset_from_key_done.html:7
#: .\cookbook\templates\account\password_reset_from_key_done.html:13
msgid "Change Password"
msgstr "更改密码"
#: .\cookbook\templates\account\password_change.html:16
msgid "Forgot Password?"
msgstr "忘记密码?"
#: .\cookbook\templates\account\password_reset.html:7
#: .\cookbook\templates\account\password_reset.html:13
#: .\cookbook\templates\account\password_reset_done.html:7
#: .\cookbook\templates\account\password_reset_done.html:18
msgid "Password Reset"
msgstr "密码重置"
#: .\cookbook\templates\account\password_reset.html:24
msgid ""
"Forgotten your password? Enter your e-mail address below, and we'll send you "
"an e-mail allowing you to reset it."
msgstr ""
"忘记密码了吗?请在下面输入你的电子邮件地址,我们将向你发送一封电子邮件,允许"
"你重新设置密码。"
#: .\cookbook\templates\account\password_reset.html:32
msgid "Password reset is disabled on this instance."
msgstr "该实例上的密码重置被禁用。"
#: .\cookbook\templates\account\password_reset_done.html:25
msgid ""
"We have sent you an e-mail. Please contact us if you do not receive it "
"within a few minutes."
msgstr "我们已经向你发送了一封电子邮件。如果你在几分钟内没有收到,请联系我们。"
#: .\cookbook\templates\account\password_reset_from_key.html:13
msgid "Bad Token"
msgstr "坏令牌"
#: .\cookbook\templates\account\password_reset_from_key.html:25
#, python-format
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used.\n"
" Please request a new "
"password reset."
msgstr ""
"密码重置链接无效,可能是因为它已经被使用。\n"
" 请重新请求 重设密码"
"a>。"
#: .\cookbook\templates\account\password_reset_from_key.html:33
msgid "change password"
msgstr "更改密码"
#: .\cookbook\templates\account\password_reset_from_key.html:36
#: .\cookbook\templates\account\password_reset_from_key_done.html:19
msgid "Your password is now changed."
msgstr "你的密码现在更改了。"
#: .\cookbook\templates\account\password_set.html:6
#: .\cookbook\templates\account\password_set.html:10
#: .\cookbook\templates\account\password_set.html:15
msgid "Set Password"
msgstr "设置密码"
#: .\cookbook\templates\account\signup.html:5
msgid "Register"
msgstr "注册"
#: .\cookbook\templates\account\signup.html:11
msgid "Create an Account"
msgstr "创建帐号"
#: .\cookbook\templates\account\signup.html:41
msgid "I accept the follwoing"
msgstr "我接受以下"
#: .\cookbook\templates\account\signup.html:44
#: .\cookbook\templates\socialaccount\signup.html:35
msgid "Terms and Conditions"
msgstr "条款及细则"
#: .\cookbook\templates\account\signup.html:47
#: .\cookbook\templates\socialaccount\signup.html:38
msgid "and"
msgstr "和"
#: .\cookbook\templates\account\signup.html:51
#: .\cookbook\templates\socialaccount\signup.html:42
msgid "Privacy Policy"
msgstr "隐私政策"
#: .\cookbook\templates\account\signup.html:64
msgid "Create User"
msgstr "创建用户"
#: .\cookbook\templates\account\signup.html:68
msgid "Already have an account?"
msgstr "已有帐号?"
#: .\cookbook\templates\account\signup_closed.html:5
#: .\cookbook\templates\account\signup_closed.html:11
msgid "Sign Up Closed"
msgstr "注册已关闭"
#: .\cookbook\templates\account\signup_closed.html:13
msgid "We are sorry, but the sign up is currently closed."
msgstr "我们很抱歉,但目前注册已经结束。"
#: .\cookbook\templates\frontend\tandoor.html:15
#, fuzzy
#| msgid "Tandoor Recipes Invite"
msgid "Tandoor Recipe Manager"
msgstr "Tandoor 食谱邀请"
#: .\cookbook\templates\index.html:28
msgid "Search recipe ..."
msgstr "搜索菜谱……"
#: .\cookbook\templates\index.html:43
msgid "New Recipe"
msgstr "新菜谱"
#: .\cookbook\templates\index.html:46
msgid "Import Recipe"
msgstr "导入菜谱"
#: .\cookbook\templates\index.html:52
msgid "Advanced Search"
msgstr "高级搜索"
#: .\cookbook\templates\index.html:56
msgid "Reset Search"
msgstr "重置搜索"
#: .\cookbook\templates\index.html:84
msgid "Last viewed"
msgstr "最近查看"
#: .\cookbook\templates\index.html:86
msgid "Recipes"
msgstr "菜谱"
#: .\cookbook\templates\index.html:93
msgid "Log in to view recipes"
msgstr "登录查看菜谱"
#: .\cookbook\templates\markdown_info.html:5
#: .\cookbook\templates\markdown_info.html:13
msgid "Markdown Info"
msgstr "Markdown 信息"
#: .\cookbook\templates\markdown_info.html:14
msgid ""
"\n"
" Markdown is lightweight markup language that can be used to format "
"plain text easily.\n"
" This site uses the Python Markdown library to\n"
" convert your text into nice looking HTML. Its full markdown "
"documentation can be found\n"
" here.\n"
" An incomplete but most likely sufficient documentation can be found "
"below.\n"
" "
msgstr ""
"\n"
" Markdown 是轻量标记语言,很方便格式化纯文本。\n"
" 本站使用 Python Markdown 库转换你的文本信息成好看的 HTML。\n"
" 完整的 Markdown 文档可 点击这里 查看。\n"
" 下面可以找到一个不完整但很可能够用的文档。\n"
" "
#: .\cookbook\templates\markdown_info.html:25
msgid "Headers"
msgstr "标题"
#: .\cookbook\templates\markdown_info.html:54
msgid "Formatting"
msgstr "格式化"
#: .\cookbook\templates\markdown_info.html:56
#: .\cookbook\templates\markdown_info.html:72
msgid "Line breaks are inserted by adding two spaces after the end of a line"
msgstr "通过在行尾后添加两个空格插入换行符"
#: .\cookbook\templates\markdown_info.html:57
#: .\cookbook\templates\markdown_info.html:73
msgid "or by leaving a blank line in between."
msgstr "或者在中间留一个空行。"
#: .\cookbook\templates\markdown_info.html:59
#: .\cookbook\templates\markdown_info.html:74
msgid "This text is bold"
msgstr "这个文本是粗体的"
#: .\cookbook\templates\markdown_info.html:60
#: .\cookbook\templates\markdown_info.html:75
msgid "This text is italic"
msgstr "这个文本是斜体的"
#: .\cookbook\templates\markdown_info.html:61
#: .\cookbook\templates\markdown_info.html:77
msgid "Blockquotes are also possible"
msgstr "块引用也可以"
#: .\cookbook\templates\markdown_info.html:84
msgid "Lists"
msgstr "列表"
#: .\cookbook\templates\markdown_info.html:85
msgid ""
"Lists can ordered or unordered. It is important to leave a blank line "
"before the list!"
msgstr "列表可以是有序或无序的。重要的是 在列表前留下一个空行!"
#: .\cookbook\templates\markdown_info.html:87
#: .\cookbook\templates\markdown_info.html:108
msgid "Ordered List"
msgstr "有序列表"
#: .\cookbook\templates\markdown_info.html:89
#: .\cookbook\templates\markdown_info.html:90
#: .\cookbook\templates\markdown_info.html:91
#: .\cookbook\templates\markdown_info.html:110
#: .\cookbook\templates\markdown_info.html:111
#: .\cookbook\templates\markdown_info.html:112
msgid "unordered list item"
msgstr "无序列表项"
#: .\cookbook\templates\markdown_info.html:93
#: .\cookbook\templates\markdown_info.html:114
msgid "Unordered List"
msgstr "无序列表"
#: .\cookbook\templates\markdown_info.html:95
#: .\cookbook\templates\markdown_info.html:96
#: .\cookbook\templates\markdown_info.html:97
#: .\cookbook\templates\markdown_info.html:116
#: .\cookbook\templates\markdown_info.html:117
#: .\cookbook\templates\markdown_info.html:118
msgid "ordered list item"
msgstr "有序列表项"
#: .\cookbook\templates\markdown_info.html:125
msgid "Images & Links"
msgstr "图片和链接"
#: .\cookbook\templates\markdown_info.html:126
msgid ""
"Links can be formatted with Markdown. This application also allows to paste "
"links directly into markdown fields without any formatting."
msgstr ""
"链接可以使用 Markdown 格式化。此应用程序还允许粘贴链接到 markdown 字段而无需"
"格式化。"
#: .\cookbook\templates\markdown_info.html:132
#: .\cookbook\templates\markdown_info.html:145
msgid "This will become an image"
msgstr "这将变成一个图像"
#: .\cookbook\templates\markdown_info.html:152
msgid "Tables"
msgstr "表格"
#: .\cookbook\templates\markdown_info.html:153
msgid ""
"Markdown tables are hard to create by hand. It is recommended to use a table "
"editor like this one."
msgstr ""
"Markdown 表格难以手写。推荐使用像 这个 的表"
"格编辑器。"
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:171
#: .\cookbook\templates\markdown_info.html:177
msgid "Table"
msgstr "表格"
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:172
msgid "Header"
msgstr "头部"
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:178
msgid "Cell"
msgstr "单元格"
#: .\cookbook\templates\no_groups_info.html:5
#: .\cookbook\templates\no_groups_info.html:12
msgid "No Permissions"
msgstr "没有权限"
#: .\cookbook\templates\no_groups_info.html:17
msgid "You do not have any groups and therefor cannot use this application."
msgstr "你没有任何组,因此无法使用此应用程序。"
#: .\cookbook\templates\no_groups_info.html:18
#: .\cookbook\templates\no_perm_info.html:15
msgid "Please contact your administrator."
msgstr "请联系你的管理员。"
#: .\cookbook\templates\no_perm_info.html:5
#: .\cookbook\templates\no_perm_info.html:12
msgid "No Permission"
msgstr "没有权限"
#: .\cookbook\templates\no_perm_info.html:15
msgid ""
"You do not have the required permissions to view this page or perform this "
"action."
msgstr "你没有必要的权限来查看此页面或执行此操作。"
#: .\cookbook\templates\offline.html:5
msgid "Offline"
msgstr "离线"
#: .\cookbook\templates\openid\login.html:27
#: .\cookbook\templates\socialaccount\authentication_error.html:27
msgid "Back"
msgstr "返回"
#: .\cookbook\templates\rest_framework\api.html:5
msgid "Recipe Home"
msgstr "菜谱主页"
#: .\cookbook\templates\rest_framework\api.html:11
msgid "API Documentation"
msgstr "应用程序接口文档"
#: .\cookbook\templates\search_info.html:5
#: .\cookbook\templates\search_info.html:9
msgid "Search Settings"
msgstr "搜索设置"
#: .\cookbook\templates\search_info.html:10
msgid ""
"\n"
" Creating the best search experience is complicated and weighs "
"heavily on your personal configuration. \n"
" Changing any of the search settings can have significant impact on "
"the speed and quality of the results.\n"
" Search Methods, Trigrams and Full Text Search configurations are "
"only available if you are using Postgres for your database.\n"
" "
msgstr ""
"\n"
" 创造最佳的搜索体验非常复杂,并且对您的个人配置有很大影响。\n"
" 改变任何搜索设置都可能对搜索结果的速度和质量产生重大影响。\n"
" 只有在数据库使用 Postgres 时,才可以使用搜索方法、卦和全文搜索配"
"置。\n"
" "
#: .\cookbook\templates\search_info.html:19
msgid "Search Methods"
msgstr "搜索方法"
#: .\cookbook\templates\search_info.html:23
msgid ""
" \n"
" Full text searches attempt to normalize the words provided to "
"match common variants. For example: 'forked', 'forking', 'forks' will all "
"normalize to 'fork'.\n"
" There are several methods available, described below, that will "
"control how the search behavior should react when multiple words are "
"searched.\n"
" Full technical details on how these operate can be viewed on Postgresql's website.\n"
" "
msgstr ""
" \n"
" 全文搜索尝试规范化提供的单词以匹配常见变体。例"
"如:“forked”、“forking”、“forks”都将归为“fork”。\n"
" 下面将介绍几种可用的方法,可以控制搜索多个单词时搜索行为的反"
"应。\n"
" 关于这些操作的完整技术细节可以在 Postgresql的网站 上查看。\n"
" "
#: .\cookbook\templates\search_info.html:29
msgid ""
" \n"
" Simple searches ignore punctuation and common words such as "
"'the', 'a', 'and'. And will treat separate words as required.\n"
" Searching for 'apple or flour' will return any recipe that "
"includes both 'apple' and 'flour' anywhere in the fields that have been "
"selected for a full text search.\n"
" "
msgstr ""
" \n"
" 简单搜索会忽略标点符号和常用词,如“the”、“a”、“and”。并将根据需要"
"处理单独的单词。\n"
" 搜索“apple or flour”将会在全文搜索中返回任意包"
"含“apple”和“flour”的菜谱。\n"
" "
#: .\cookbook\templates\search_info.html:34
msgid ""
" \n"
" Phrase searches ignore punctuation, but will search for all of "
"the words in the exact order provided.\n"
" Searching for 'apple or flour' will only return a recipe that "
"includes the exact phrase 'apple or flour' in any of the fields that have "
"been selected for a full text search.\n"
" "
msgstr ""
" \n"
" 短语搜索会忽略标点符号,但会按照搜索顺序查询所有单词。\n"
" 搜索“苹果或面粉”将只返回一个食谱,这个食谱包含进行全文搜索时准确"
"的字段短语“苹果或面粉”。\n"
" "
#: .\cookbook\templates\search_info.html:39
msgid ""
" \n"
" Web searches simulate functionality found on many web search "
"sites supporting special syntax.\n"
" Placing quotes around several words will convert those words "
"into a phrase.\n"
" 'or' is recognized as searching for the word (or phrase) "
"immediately before 'or' OR the word (or phrase) directly after.\n"
" '-' is recognized as searching for recipes that do not include "
"the word (or phrase) that comes immediately after. \n"
" For example searching for 'apple pie' or cherry -butter will "
"return any recipe that includes the phrase 'apple pie' or the word "
"'cherry' \n"
" in any field included in the full text search but exclude any "
"recipe that has the word 'butter' in any field included.\n"
" "
msgstr ""
" \n"
" 网页搜索模拟许多支持特殊语法的网页搜索站点上的功能。\n"
" 在几个单词周围加上引号会将这些单词转换为一个短语。\n"
" 'or' 被识别为搜索紧接在 'or' 之前的单词(或短语)或紧随其后的单词"
"(或短语)。\n"
" '-' 被识别为搜索不包含紧随其后的单词(或短语)的食谱。 \n"
" 例如,搜索 “苹果派” 或“樱桃 -黄油” 将返回任何包含短语“苹果"
"派”或“樱桃”的食谱 \n"
" 与在全文搜索中包含的任何 “樱桃” 字段中,但排除包含单词“黄油”的任"
"何食谱。\n"
" "
#: .\cookbook\templates\search_info.html:48
msgid ""
" \n"
" Raw search is similar to Web except will take puncuation "
"operators such as '|', '&' and '()'\n"
" "
msgstr ""
" \n"
" 原始搜索与网页类似,不同的是会采用标点运算符,例如 '|', '&' 和 "
"'()'\n"
" "
#: .\cookbook\templates\search_info.html:59
msgid ""
" \n"
" Another approach to searching that also requires Postgresql is "
"fuzzy search or trigram similarity. A trigram is a group of three "
"consecutive characters.\n"
" For example searching for 'apple' will create x trigrams 'app', "
"'ppl', 'ple' and will create a score of how closely words match the "
"generated trigrams.\n"
" One benefit of searching trigams is that a search for 'sandwich' "
"will find misspelled words such as 'sandwhich' that would be missed by other "
"methods.\n"
" "
msgstr ""
" \n"
" 另一种也需要 PostgreSQL 的搜索方法是模糊搜索或三元组。 三元组是一"
"组三个连续的字符。\n"
" 例如,搜索“apple”将创建 x 个三元组“app”、“ppl”、“ple”,并将创建单"
"词与生成的三元组匹配程度的分数。\n"
" 使用模糊搜索或三元组一个好处是搜索“sandwich”会找到拼写错误的单"
"词,例如“sandwhich”,而其他方法会漏掉这些单词。\n"
" "
#: .\cookbook\templates\search_info.html:69
msgid "Search Fields"
msgstr "搜索字段"
#: .\cookbook\templates\search_info.html:73
msgid ""
" \n"
" Unaccent is a special case in that it enables searching a field "
"'unaccented' for each search style attempting to ignore accented values. \n"
" For example when you enable unaccent for 'Name' any search "
"(starts with, contains, trigram) will attempt the search ignoring accented "
"characters.\n"
" \n"
" For the other options, you can enable search on any or all "
"fields and they will be combined together with an assumed 'OR'.\n"
" For example enabling 'Name' for Starts With, 'Name' and "
"'Description' for Partial Match and 'Ingredients' and 'Keywords' for Full "
"Search\n"
" and searching for 'apple' will generate a search that will "
"return recipes that have:\n"
" - A recipe name that starts with 'apple'\n"
" - OR a recipe name that contains 'apple'\n"
" - OR a recipe description that contains 'apple'\n"
" - OR a recipe that will have a full text search match ('apple' "
"or 'apples') in ingredients\n"
" - OR a recipe that will have a full text search match in "
"Keywords\n"
"\n"
" Combining too many fields in too many types of search can have a "
"negative impact on performance, create duplicate results or return "
"unexpected results.\n"
" For example, enabling fuzzy search or partial matches will "
"interfere with web search methods. \n"
" Searching for 'apple -pie' with fuzzy search and full text "
"search will return the recipe Apple Pie. Though it is not included in the "
"full text results, it does match the trigram results.\n"
" "
msgstr ""
" \n"
" 不重音 是一种特殊情况,因为它可以为每个尝试忽略重音值的搜索进行搜"
"索“不重音”字段。 \n"
" 例如,当您为“名字”启用不重音时,任何搜索(开头、包含、三元组)都"
"将尝试搜索忽略重音字符。\n"
" \n"
" 对于其他选项,您可以在任一或所有字段上启用搜索,它们将与假定"
"的“or”组合在一起。\n"
" 例如,为 起始于 启用“名字”,为 部分匹配 启用“名字”和“描述”,为 全"
"文搜索 启用“食材”和“关键字”\n"
" 并搜索“苹果”将生成一个搜索,该搜索将返回具有以下内容的食谱:\n"
" - 以“苹果”开头的食谱名称\n"
" - 或包含“苹果”的食谱名称\n"
" - 或包含“苹果”的食谱描述\n"
" - 或在食材中具有全文搜索匹配(“苹果”或“很多苹果”)的食谱\n"
" - 或将在关键字中进行全文搜索匹配的食谱\n"
"\n"
" 在多种类型搜索中组合大量字段可能会对性能产生负面影响、创建重复结"
"果或返回意外结果。\n"
" 例如,启用模糊搜索或部分匹配会干扰网络搜索算法。 \n"
" 使用模糊搜索或全文搜索进行搜索“苹果 -派”将返回食谱 苹果派。虽然它"
"不包含在全文结果中,但它确实与三元组结果匹配。\n"
" "
#: .\cookbook\templates\search_info.html:95
msgid "Search Index"
msgstr "搜索索引"
#: .\cookbook\templates\search_info.html:99
msgid ""
" \n"
" Trigram search and Full Text Search both rely on database "
"indexes to perform effectively. \n"
" You can rebuild the indexes on all fields in the Admin page for "
"Recipes and selecting all recipes and running 'rebuild index for selected "
"recipes'\n"
" You can also rebuild indexes at the command line by executing "
"the management command 'python manage.py rebuildindex'\n"
" "
msgstr ""
" \n"
" 三元搜索和全文搜索都依赖于数据库索引执行。 \n"
" 你可以在“食谱”的“管理”页面中的所有字段上重建索引并选择任一食谱运"
"行“为所选食谱重建索引”\n"
" 你还可以通过执行管理命令“python manage.py rebuildindex”在命令行重"
"建索引\n"
" "
#: .\cookbook\templates\setup.html:6
msgid "Cookbook Setup"
msgstr "安装菜谱应用"
#: .\cookbook\templates\setup.html:14
msgid "Setup"
msgstr "安装"
#: .\cookbook\templates\setup.html:15
msgid ""
"To start using this application you must first create a superuser account."
msgstr "要开始使用这个应用程序,你必须先创建一个超级用户帐号。"
#: .\cookbook\templates\setup.html:20
msgid "Create Superuser account"
msgstr "创建超级用户帐号"
#: .\cookbook\templates\socialaccount\authentication_error.html:7
#: .\cookbook\templates\socialaccount\authentication_error.html:23
msgid "Social Network Login Failure"
msgstr "社交网络登录失败"
#: .\cookbook\templates\socialaccount\authentication_error.html:25
msgid ""
"An error occurred while attempting to login via your social network account."
msgstr "尝试通过您的社交网络帐户登录时出错。"
#: .\cookbook\templates\socialaccount\connections.html:4
#: .\cookbook\templates\socialaccount\connections.html:7
msgid "Account Connections"
msgstr "帐号连接"
#: .\cookbook\templates\socialaccount\connections.html:10
msgid ""
"You can sign in to your account using any of the following third party\n"
" accounts:"
msgstr ""
"你可以使用以下任何第三方登录您的帐户\n"
" 账户:"
#: .\cookbook\templates\socialaccount\connections.html:44
msgid ""
"You currently have no social network accounts connected to this account."
msgstr "你目前没有与此帐号连接的社交网络帐号。"
#: .\cookbook\templates\socialaccount\connections.html:47
msgid "Add a 3rd Party Account"
msgstr "添加第三方帐号"
#: .\cookbook\templates\socialaccount\login.html:5
#: .\cookbook\templates\socialaccount\signup.html:5
msgid "Signup"
msgstr "注册"
#: .\cookbook\templates\socialaccount\login.html:9
#, python-format
msgid "Connect %(provider)s"
msgstr "连接 %(provider)s"
#: .\cookbook\templates\socialaccount\login.html:11
#, python-format
msgid "You are about to connect a new third party account from %(provider)s."
msgstr "你即将从 %(provider)s 连接一个新的第三方帐户。"
#: .\cookbook\templates\socialaccount\login.html:13
#, python-format
msgid "Sign In Via %(provider)s"
msgstr "通过 %(provider)s 登录"
#: .\cookbook\templates\socialaccount\login.html:15
#, python-format
msgid "You are about to sign in using a third party account from %(provider)s."
msgstr "你即将使用 %(provider)s 的第三方帐户登录。"
#: .\cookbook\templates\socialaccount\login.html:20
msgid "Continue"
msgstr "继续"
#: .\cookbook\templates\socialaccount\signup.html:10
#, python-format
msgid ""
"You are about to use your\n"
" %(provider_name)s account to login to\n"
" %(site_name)s. As a final step, please complete the following form:"
msgstr ""
"你即将使用你的\n"
" %(provider_name)s 账户登录\n"
" %(site_name)s。 最后一步, 请填写以下表单:"
#: .\cookbook\templates\socialaccount\signup.html:32
#, fuzzy
#| msgid "I accept the follwoing"
msgid "I accept the following"
msgstr "我接受以下"
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:23
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:31
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:39
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:47
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:55
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:63
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:71
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:79
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:87
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:95
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:103
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:111
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:119
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:127
msgid "Sign in using"
msgstr "登录使用"
#: .\cookbook\templates\space_overview.html:6
msgid "Overview"
msgstr "概述"
#: .\cookbook\templates\space_overview.html:13
msgid "Space"
msgstr "空间"
#: .\cookbook\templates\space_overview.html:17
msgid ""
"Recipes, foods, shopping lists and more are organized in spaces of one or "
"more people."
msgstr "菜谱、食物、购物清单等都组织在一个人或多个人的空间中。"
#: .\cookbook\templates\space_overview.html:18
msgid ""
"You can either be invited into an existing space or create your own one."
msgstr "你可以被邀请进入现有空间,也可以创建自己的空间。"
#: .\cookbook\templates\space_overview.html:25
msgid "Your Spaces"
msgstr "你的空间"
#: .\cookbook\templates\space_overview.html:53
msgid "Owner"
msgstr "所有者"
#: .\cookbook\templates\space_overview.html:73
#: .\cookbook\templates\space_overview.html:83
msgid "Join Space"
msgstr "加入空间"
#: .\cookbook\templates\space_overview.html:76
msgid "Join an existing space."
msgstr "加入一个现有的空间。"
#: .\cookbook\templates\space_overview.html:78
msgid ""
"To join an existing space either enter your invite token or click on the "
"invite link the space owner send you."
msgstr ""
"要加入一个现有的空间,要么输入你的邀请令牌,要么单击空间所有者发送给你的邀请"
"链接。"
#: .\cookbook\templates\space_overview.html:91
#: .\cookbook\templates\space_overview.html:100
msgid "Create Space"
msgstr "创建空间"
#: .\cookbook\templates\space_overview.html:94
msgid "Create your own recipe space."
msgstr "创建你自己的菜谱空间。"
#: .\cookbook\templates\space_overview.html:96
msgid "Start your own recipe space and invite other users to it."
msgstr "创建自己的食谱空间,并邀请其他用户加入。"
#: .\cookbook\templates\system.html:23
msgid "System"
msgstr "系统"
#: .\cookbook\templates\system.html:24
#, fuzzy
#| msgid ""
#| "\n"
#| " Django Recipes is an open source free software application. It "
#| "can be found on\n"
#| " GitHub.\n"
#| " Changelogs can be found here.\n"
#| " "
msgid ""
"\n"
" Tandoor Recipes is an open source free software application. It can "
"be found on\n"
" GitHub.\n"
" Changelogs can be found here.\n"
" "
msgstr ""
"\n"
" Django Recipes 是一个开源应用程序。\n"
" 你可以在 GitHub"
"a> 中找到。\n"
" 更新日志在 这里。\n"
" "
#: .\cookbook\templates\system.html:30
msgid "System Information"
msgstr "系统信息"
#: .\cookbook\templates\system.html:51
msgid ""
"\n"
" You need to execute version.py in your update "
"script to generate version information (done automatically in docker).\n"
" "
msgstr ""
"\n"
" 您需要在您的更新脚本中执行 version.py 生成版本信息"
"(Docker实例中会自动执行)。\n"
" "
#: .\cookbook\templates\system.html:56
msgid "Plugins"
msgstr ""
#: .\cookbook\templates\system.html:67
msgid "Media Serving"
msgstr "媒体服务"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:123 .\cookbook\templates\system.html:134
msgid "Warning"
msgstr "警告"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:125 .\cookbook\templates\system.html:134
msgid "Ok"
msgstr "好的"
#: .\cookbook\templates\system.html:70
msgid ""
"Serving media files directly using gunicorn/python is not recommend!\n"
" Please follow the steps described\n"
" here to update\n"
" your installation.\n"
" "
msgstr ""
"不推荐 使用 gunicorn/python 提供媒体文件!\n"
" 请按照\n"
" 这里 描述的步骤\n"
" 操作更新安装。\n"
" "
#: .\cookbook\templates\system.html:76 .\cookbook\templates\system.html:91
#: .\cookbook\templates\system.html:104 .\cookbook\templates\system.html:115
#: .\cookbook\views\views.py:168
msgid "Everything is fine!"
msgstr "一切都好!"
#: .\cookbook\templates\system.html:80
msgid "Secret Key"
msgstr "密钥"
#: .\cookbook\templates\system.html:84
msgid ""
"\n"
" You do not have a SECRET_KEY configured in your "
".env file. Django defaulted to the\n"
" standard key\n"
" provided with the installation which is publicly know and "
"insecure! Please set\n"
" SECRET_KEY int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" 您没有在 .env 文件中配置 SECRET_KEY。 "
"Django 默认为\n"
" 标准键\n"
" 提供公开但并不安全的安装! 请设置\n"
" SECRET_KEY 在 .env 文件中配置。\n"
" "
#: .\cookbook\templates\system.html:94
msgid "Debug Mode"
msgstr "调试模式"
#: .\cookbook\templates\system.html:98
msgid ""
"\n"
" This application is still running in debug mode. This is most "
"likely not needed. Turn of debug mode by\n"
" setting\n"
" DEBUG=0 int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" 此应用程序仍在调试模式下运行。 这是不必要的。 调试模式由\n"
" 设置\n"
" DEBUG=0 在 .env 文件中配置\n"
" "
#: .\cookbook\templates\system.html:107
msgid "Allowed Hosts"
msgstr "域名白名单"
#: .\cookbook\templates\system.html:111
msgid ""
"\n"
" Your allowed hosts are configured to allow every host. This "
"might be ok in some setups but should be avoided. Please see the docs about "
"this.\n"
" "
msgstr ""
"\n"
" 您未配置域名白名单,本实例可以通过任意域名访问,某些特殊情况下可"
"能有用,但请尽量避免这样配置,请参考文档配置 ALLOWED_HOSTS 。\n"
" "
#: .\cookbook\templates\system.html:118
msgid "Database"
msgstr "数据库"
#: .\cookbook\templates\system.html:121
msgid "Info"
msgstr "信息"
#: .\cookbook\templates\system.html:131 .\cookbook\templates\system.html:148
msgid "Migrations"
msgstr "数据库结构变更"
#: .\cookbook\templates\system.html:137
msgid ""
"\n"
" Migrations should never fail!\n"
" Failed migrations will likely cause major parts of the app to "
"not function correctly.\n"
" If a migration fails make sure you are on the latest version and "
"if so please post the migration log and the overview below in a GitHub "
"issue.\n"
" "
msgstr ""
"\n"
" 正常情况下数据库迁移不应产生任何报错!\n"
" 数据库迁移失败很可能导致本应用的功能出错或完全不可用。\n"
" 如果您在最新版本上数据库迁移依然产生错误,请将数据库迁移日志和页"
"面下方的信息反馈至 Github Issue 中。\n"
" "
#: .\cookbook\templates\system.html:238
msgid "False"
msgstr "否"
#: .\cookbook\templates\system.html:238
msgid "True"
msgstr "是"
#: .\cookbook\templates\system.html:268
msgid "Hide"
msgstr "隐藏"
#: .\cookbook\templates\system.html:271
msgid "Show"
msgstr "显示"
#: .\cookbook\templates\test2.html:6
msgid "Export Recipes"
msgstr "导出菜谱"
#: .\cookbook\templates\test2.html:14 .\cookbook\templates\test2.html:20
msgid "Export"
msgstr "导出"
#: .\cookbook\views\api.py:198 .\cookbook\views\api.py:326
msgid "Parameter updated_at incorrectly formatted"
msgstr "参数 updated_at 格式不正确"
#: .\cookbook\views\api.py:351 .\cookbook\views\api.py:484
#, python-brace-format
msgid "No {self.basename} with id {pk} exists"
msgstr "不存在ID是 {pk} 的 {self.basename}"
#: .\cookbook\views\api.py:355
msgid "Cannot merge with the same object!"
msgstr "无法与同一对象合并!"
#: .\cookbook\views\api.py:362
#, python-brace-format
msgid "No {self.basename} with id {target} exists"
msgstr "不存在 ID 为 {target} 的 {self.basename}"
#: .\cookbook\views\api.py:367
msgid "Cannot merge with child object!"
msgstr "无法与子对象合并!"
#: .\cookbook\views\api.py:405
#, python-brace-format
msgid "{source.name} was merged successfully with {target.name}"
msgstr "{source.name} 已成功与 {target.name} 合并"
#: .\cookbook\views\api.py:411
#, python-brace-format
msgid "An error occurred attempting to merge {source.name} with {target.name}"
msgstr "视图合并 {source.name} 和 {target.name} 时出错"
#: .\cookbook\views\api.py:493
#, python-brace-format
msgid "{child.name} was moved successfully to the root."
msgstr "{child.name} 已成功移动到根目录。"
#: .\cookbook\views\api.py:496 .\cookbook\views\api.py:514
msgid "An error occurred attempting to move "
msgstr "尝试移动时出错 "
#: .\cookbook\views\api.py:499
msgid "Cannot move an object to itself!"
msgstr "无法将对象移动到自身!"
#: .\cookbook\views\api.py:505
#, python-brace-format
msgid "No {self.basename} with id {parent} exists"
msgstr "不存在 ID 为 {parent} 的 {self.basename}"
#: .\cookbook\views\api.py:511
#, python-brace-format
msgid "{child.name} was moved successfully to parent {parent.name}"
msgstr "{child.name} 成功移动到父节点 {parent.name}"
#: .\cookbook\views\api.py:992
#, python-brace-format
msgid "{obj.name} was removed from the shopping list."
msgstr "{obj.name} 已从购物清单中删除。"
#: .\cookbook\views\api.py:997 .\cookbook\views\api.py:1627
#, python-brace-format
msgid "{obj.name} was added to the shopping list."
msgstr "{obj.name} 已添加到购物清单中。"
#: .\cookbook\views\api.py:1239
#, fuzzy
#| msgid "Filter meal plans from date (inclusive) in the format of YYYY-MM-DD."
msgid "Filter meal plans from date (inclusive)."
msgstr "指定开始日期以过滤膳食计划(包含选择的日期),日期格式为 YYYY-MM-DD。"
#: .\cookbook\views\api.py:1241
#, fuzzy
#| msgid "Filter meal plans to date (inclusive) in the format of YYYY-MM-DD."
msgid "Filter meal plans to date (inclusive)."
msgstr "指定结束日期以过滤膳食计划(包含选择的日期),日期格式为 YYYY-MM-DD。"
#: .\cookbook\views\api.py:1244
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
msgstr "指定 MealType ID 以过滤膳食计划,重复此参数以选择多个对象。"
#: .\cookbook\views\api.py:1404
msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgstr "食谱中的步骤ID。 对于多个重复参数。"
#: .\cookbook\views\api.py:1406
msgid "Query string matched (fuzzy) against object name."
msgstr "请求参数与对象名称匹配(模糊)。"
#: .\cookbook\views\api.py:1442
msgid ""
"Query string matched (fuzzy) against recipe name. In the future also "
"fulltext search."
msgstr "请求参数与食谱名称匹配(模糊)。 未来会添加全文搜索。"
#: .\cookbook\views\api.py:1444
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr "菜谱应包含的关键字 ID。 对于多个重复参数。 相当于keywords_or"
#: .\cookbook\views\api.py:1445
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr "允许多个关键字 ID。 返回带有任一关键字的食谱"
#: .\cookbook\views\api.py:1446
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr "允许多个关键字 ID。 返回带有所有关键字的食谱。"
#: .\cookbook\views\api.py:1447
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr "允许多个关键字 ID。 排除带有任一关键字的食谱。"
#: .\cookbook\views\api.py:1448
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr "允许多个关键字 ID。 排除带有所有关键字的食谱。"
#: .\cookbook\views\api.py:1450
msgid "ID of food a recipe should have. For multiple repeat parameter."
msgstr "食谱中食物带有ID。并可添加多个食物。"
#: .\cookbook\views\api.py:1451
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr "食谱中食物带有ID。并可添加多个食物"
#: .\cookbook\views\api.py:1452
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr "食谱中食物带有ID。返回包含任何食物的食谱。"
#: .\cookbook\views\api.py:1453
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr "食谱中食物带有ID。排除包含任一食物的食谱。"
#: .\cookbook\views\api.py:1454
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr "食谱中食物带有ID。排除包含所有食物的食谱。"
#: .\cookbook\views\api.py:1456
msgid "ID of book a recipe should be in. For multiple repeat parameter."
msgstr "烹饪书应该在食谱中具有ID。并且可以添加多本。"
#: .\cookbook\views\api.py:1457
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr "书的ID允许多个。返回包含任一书籍的食谱"
#: .\cookbook\views\api.py:1458
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr "书的ID允许多个。返回包含所有书籍的食谱。"
#: .\cookbook\views\api.py:1459
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr "书的ID允许多个。排除包含任一书籍的食谱。"
#: .\cookbook\views\api.py:1460
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr "书的ID允许多个。排除包含所有书籍的食谱。"
#: .\cookbook\views\api.py:1462
msgid "ID of unit a recipe should have."
msgstr "食谱应具有单一ID。"
#: .\cookbook\views\api.py:1464
msgid "Exact rating of recipe"
msgstr ""
#: .\cookbook\views\api.py:1465
#, fuzzy
#| msgid "ID of unit a recipe should have."
msgid "Rating a recipe should have or greater."
msgstr "食谱应具有单一ID。"
#: .\cookbook\views\api.py:1466
#, fuzzy
#| msgid "ID of unit a recipe should have."
msgid "Rating a recipe should have or smaller."
msgstr "食谱应具有单一ID。"
#: .\cookbook\views\api.py:1468
msgid "Filter recipes cooked X times."
msgstr ""
#: .\cookbook\views\api.py:1469
#, fuzzy
#| msgid ""
#| "Filter recipes cooked X times or more. Negative values returns cooked "
#| "less than X times"
msgid "Filter recipes cooked X times or more."
msgstr "筛选烹饪 X 次或更多次的食谱。 负值返回烹饪少于 X 次"
#: .\cookbook\views\api.py:1470
msgid "Filter recipes cooked X times or less."
msgstr ""
#: .\cookbook\views\api.py:1472
#, fuzzy
#| msgid "Filter for entries with the given recipe"
msgid "Filter recipes created on the given date."
msgstr "筛选包含所选食谱的对象"
#: .\cookbook\views\api.py:1473
#, fuzzy
#| msgid ""
#| "Filter recipes created on or after YYYY-MM-DD. Prepending - filters on or "
#| "before date."
msgid "Filter recipes created on the given date or after."
msgstr "筛选在 YYYY-MM-DD 或之后创建的食谱。 前置 - 在日期或日期之前过滤。"
#: .\cookbook\views\api.py:1474
#, fuzzy
#| msgid ""
#| "Filter recipes created on or after YYYY-MM-DD. Prepending - filters on or "
#| "before date."
msgid "Filter recipes created on the given date or before."
msgstr "筛选在 YYYY-MM-DD 或之后创建的食谱。 前置 - 在日期或日期之前过滤。"
#: .\cookbook\views\api.py:1476 .\cookbook\views\api.py:1477
#: .\cookbook\views\api.py:1478
#, fuzzy
#| msgid "Filter for entries with the given recipe"
msgid "Filter recipes updated on the given date."
msgstr "筛选包含所选食谱的对象"
#: .\cookbook\views\api.py:1480
#, fuzzy
#| msgid ""
#| "Filter recipes last cooked on or after YYYY-MM-DD. Prepending - filters "
#| "on or before date."
msgid "Filter recipes last cooked on the given date or after."
msgstr ""
"筛选最后烹饪在 YYYY-MM-DD 当天或之后的食谱。 前置 - 在日期或日期之前筛选。"
#: .\cookbook\views\api.py:1481
#, fuzzy
#| msgid ""
#| "Filter recipes last cooked on or after YYYY-MM-DD. Prepending - filters "
#| "on or before date."
msgid "Filter recipes last cooked on the given date or before."
msgstr ""
"筛选最后烹饪在 YYYY-MM-DD 当天或之后的食谱。 前置 - 在日期或日期之前筛选。"
#: .\cookbook\views\api.py:1483 .\cookbook\views\api.py:1484
#, fuzzy
#| msgid ""
#| "Filter recipes lasts viewed on or after YYYY-MM-DD. Prepending - filters "
#| "on or before date."
msgid "Filter recipes lasts viewed on the given date."
msgstr ""
"筛选最后查看时间是在 YYYY-MM-DD 或之后的食谱。 前置 - 在日期或日期之前筛选。"
#: .\cookbook\views\api.py:1486
#, fuzzy
#| msgid "Filter for entries with the given recipe"
msgid "Filter recipes for ones created by the given user ID"
msgstr "筛选包含所选食谱的对象"
#: .\cookbook\views\api.py:1487
msgid "If only internal recipes should be returned. [true/false]"
msgstr "只返回内部食谱。 [true/false]"
#: .\cookbook\views\api.py:1488
msgid "Returns the results in randomized order. [true/false]"
msgstr "按随机排序返回结果。 [true/ false ]"
#: .\cookbook\views\api.py:1490
msgid ""
"Determines the order of the results. Options are: score,-score,name,-name,"
"lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-"
"created_at,lastviewed,-lastviewed"
msgstr ""
#: .\cookbook\views\api.py:1492
msgid "Returns new results first in search results. [true/false]"
msgstr "在搜索结果中首先返回新结果。 [是/否]"
#: .\cookbook\views\api.py:1493
msgid ""
"Returns the given number of recently viewed recipes before search results "
"(if given)"
msgstr ""
#: .\cookbook\views\api.py:1494
msgid "ID of a custom filter. Returns all recipes matched by that filter."
msgstr ""
#: .\cookbook\views\api.py:1495
msgid "Filter recipes that can be made with OnHand food. [true/false]"
msgstr "筛选可以直接用手制作的食谱。 [真/假]"
#: .\cookbook\views\api.py:1773
msgid ""
"Return the PropertyTypes matching the property category. Repeat for "
"multiple."
msgstr ""
#: .\cookbook\views\api.py:1804 .\cookbook\views\api.py:1860
#, fuzzy
#| msgid "Filter for entries with the given recipe"
msgid "Returns only entries associated with the given mealplan id"
msgstr "筛选包含所选食谱的对象"
#: .\cookbook\views\api.py:1858
msgid ""
"Returns only elements updated after the given timestamp in ISO 8601 format."
msgstr ""
#: .\cookbook\views\api.py:2031
#, fuzzy
#| msgid ""
#| "Return the Automations matching the automation type. Multiple values "
#| "allowed."
msgid ""
"Return the Automations matching the automation type. Repeat for multiple."
msgstr "返回与自动化类型相匹配的自动化条目。 允许多个值。"
#: .\cookbook\views\api.py:2048
msgid ""
"Text field to store data that gets carried over to the UserSpace created "
"from the InviteLink"
msgstr ""
#: .\cookbook\views\api.py:2049
msgid "Only return InviteLinks that have not been used yet."
msgstr ""
#: .\cookbook\views\api.py:2076
#, fuzzy
#| msgid ""
#| "Return the Automations matching the automation type. Multiple values "
#| "allowed."
msgid "Return the CustomFilters matching the model type. Repeat for multiple."
msgstr "返回与自动化类型相匹配的自动化条目。 允许多个值。"
#: .\cookbook\views\api.py:2176
msgid "Nothing to do."
msgstr "无事可做。"
#: .\cookbook\views\api.py:2222
msgid "Invalid Url"
msgstr "无效网址"
#: .\cookbook\views\api.py:2228
msgid "Connection Refused."
msgstr "连接被拒绝。"
#: .\cookbook\views\api.py:2232
msgid "Bad URL Schema."
msgstr "错误的 URL Schema。"
#: .\cookbook\views\api.py:2257
msgid "No usable data could be found."
msgstr "找不到可用的数据。"
#: .\cookbook\views\api.py:2286 .\cookbook\views\api.py:2434
msgid "You must select an AI provider to perform your request."
msgstr ""
#: .\cookbook\views\api.py:2293 .\cookbook\views\api.py:2441
msgid ""
"You don't have any credits remaining to use AI or AI features are not "
"enabled for your space."
msgstr ""
#: .\cookbook\views\api.py:2499 .\cookbook\views\api.py:2667
msgid "File is above space limit"
msgstr "文件大小超出空间限值"
#: .\cookbook\views\api.py:2522 .\cookbook\views\api.py:2684
msgid "Importing is not implemented for this provider"
msgstr "此提供程序未实现导入"
#: .\cookbook\views\api.py:2548
msgid ""
"The PDF Exporter is not enabled on this instance as it is still in an "
"experimental state."
msgstr "此实例上未启用 PDF 导出器,因为它仍处于实验状态。"
#: .\cookbook\views\api.py:2842
msgid "This feature is not yet available in the hosted version of tandoor!"
msgstr "此功能在 Tandoor 的托管版本中尚不可用!"
#: .\cookbook\views\api.py:2863
msgid "Sync successful!"
msgstr "同步成功!"
#: .\cookbook\views\api.py:2866
msgid "Error synchronizing with Storage"
msgstr "与存储同步时出错"
#: .\cookbook\views\views.py:89
msgid "This feature is not available in the demo version!"
msgstr "此功能在演示版本中不可用!"
#: .\cookbook\views\views.py:110
msgid ""
"You have successfully created your own recipe space. Start by adding some "
"recipes or invite other people to join you."
msgstr "你已成功创建自己的菜谱空间。 首先添加一些菜谱或邀请其他人加入。"
#: .\cookbook\views\views.py:171
#, python-format
msgid "PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!"
msgstr "PostgreSQL %(v)s 版本过时。请升级到支持的版本!"
#: .\cookbook\views\views.py:174
#, python-format
msgid "You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended"
msgstr "您运行的 PostgreSQL 版本是 %(v1)s。推荐版本为 PostgreSQL %(v2)s"
#: .\cookbook\views\views.py:178
msgid "Unable to determine PostgreSQL version."
msgstr "无法确认 PostgreSQL 版本。"
#: .\cookbook\views\views.py:182
msgid ""
"This application is not running with a Postgres database backend. This is ok "
"but not recommended as some features only work with postgres databases."
msgstr ""
"此应用未使用 PostgreSQL 数据库作为后端。 这是可运行的配置,但不推荐,因为部分"
"功能仅在 PostgreSQL 数据库下可用。"
#: .\cookbook\views\views.py:296
msgid ""
"The setup page can only be used to create the first "
"user! If you have forgotten your superuser credentials "
"please consult the django documentation on how to reset passwords."
msgstr ""
"设置页面只能用于创建第一个用户! 如果您忘记了超级用户凭"
"据,请参阅 Django 文档,了解如何重置密码。"
#: .\cookbook\views\views.py:304
msgid "Passwords dont match!"
msgstr "密码不匹配!"
#: .\cookbook\views\views.py:312
msgid "User has been created, please login!"
msgstr "用户已创建,请登录!"
#: .\cookbook\views\views.py:352
msgid ""
"Reporting share links is not enabled for this instance. Please notify the "
"page administrator to report problems."
msgstr "未为此实例启用报告共享链接。请通知页面管理员报告问题。"
#: .\cookbook\views\views.py:357
msgid ""
"Recipe sharing link has been disabled! For additional information please "
"contact the page administrator."
msgstr "菜谱共享链接已被禁用!有关更多信息,请与页面管理员联系。"
#: .\cookbook\views\views.py:383
msgid "Manage recipes, shopping list, meal plans and more."
msgstr "管理菜谱、购物清单、膳食计划等。"
#: .\cookbook\views\views.py:397 .\cookbook\views\views.py:398
msgid "Plan"
msgstr "计划"
#: .\cookbook\views\views.py:399
msgid "View your meal Plan"
msgstr "查看您的膳食计划"
#: .\cookbook\views\views.py:418
msgid "View your shopping lists"
msgstr "查看你的购物清单"
#~ msgid ""
#~ "Both fields are optional. If none are given the username will be "
#~ "displayed instead"
#~ msgstr "这两个字段都是可选的。如果没有给出,将显示用户名"
#~ msgid "Name"
#~ msgstr "名字"
#~ msgid "Keywords"
#~ msgstr "关键词"
#~ msgid "Preparation time in minutes"
#~ msgstr "准备时间(分钟)"
#~ msgid "Waiting time (cooking/baking) in minutes"
#~ msgstr "等候(烹饪、烘焙等)时间(分钟)"
#~ msgid "Path"
#~ msgstr "路径"
#~ msgid "Storage UID"
#~ msgstr "存储 UID"
#~ msgid "Add your comment: "
#~ msgstr "发表评论: "
#~ msgid "Leave empty for dropbox and enter app password for nextcloud."
#~ msgstr "Dropbox 留空并输入 Nextcloud 应用密码。"
#~ msgid "Leave empty for nextcloud and enter api token for dropbox."
#~ msgstr "Nextcloud 留空并输入 Dropbox API 令牌。"
#~ msgid ""
#~ "Leave empty for dropbox and enter only base url for nextcloud (/"
#~ "remote.php/webdav/ is added automatically)"
#~ msgstr ""
#~ "Dropbox 留空并输入基础 Nextcloud 网址(/remote.php/webdav/ 会"
#~ "自动添加)"
#~ msgid ""
#~ "Long Lived Access Token for your HomeAssistant instance"
#~ msgstr ""
#~ "您的HomeAssistant示例的长期访问令牌"
#~ msgid "Something like http://homeassistant.local:8123/api"
#~ msgstr "形如 http://homeassistant.local:8123/api"
#~ msgid "http://homeassistant.local:8123/api for example"
#~ msgstr "例如 http://homeassistant.local:8123/api"
#~ msgid "Storage"
#~ msgstr "存储"
#~ msgid "Active"
#~ msgstr "活跃"
#~ msgid "Search String"
#~ msgstr "搜索字符串"
#~ msgid "File ID"
#~ msgstr "文件编号"
#~ msgid ""
#~ "Determines how fuzzy a search is if it uses trigram similarity matching "
#~ "(e.g. low values mean more typos are ignored)."
#~ msgstr ""
#~ "确定使用三元图相似性匹配时搜索的模糊程度(例如,较低的值意味着忽略更多的打"
#~ "字错误)。"
#~ msgid ""
#~ "Select type method of search. Click here "
#~ "for full description of choices."
#~ msgstr ""
#~ "选择搜索类型方法。 点击此处 查看选项的完整"
#~ "说明。"
#~ msgid ""
#~ "Use fuzzy matching on units, keywords and ingredients when editing and "
#~ "importing recipes."
#~ msgstr "编辑和导入菜谱时,对单位、关键词和食材使用模糊匹配。"
#~ msgid ""
#~ "Fields to search ignoring accents. Selecting this option can improve or "
#~ "degrade search quality depending on language"
#~ msgstr "忽略搜索字段的重音。此选项会因语言差异导致搜索质量产生变化"
#~ msgid ""
#~ "Fields to search for partial matches. (e.g. searching for 'Pie' will "
#~ "return 'pie' and 'piece' and 'soapie')"
#~ msgstr ""
#~ "用于搜索部分匹配的字段。(如搜索“Pie”会返回“pie”、“piece”和“soapie”)"
#~ msgid ""
#~ "Fields to search for beginning of word matches. (e.g. searching for 'sa' "
#~ "will return 'salad' and 'sandwich')"
#~ msgstr "用于搜索开头匹配的字段。(如搜索“sa”会返回“salad”和“sandwich”)"
#~ msgid ""
#~ "Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find "
#~ "'recipe'.) Note: this option will conflict with 'web' and 'raw' methods "
#~ "of search."
#~ msgstr ""
#~ "“模糊”搜索字段。(例如搜索“recpie”将会找到“recipe”。)注意:此选项将"
#~ "与“web”和“raw”搜索方法冲突。"
#~ msgid ""
#~ "Fields to full text search. Note: 'web', 'phrase', and 'raw' search "
#~ "methods only function with fulltext fields."
#~ msgstr "全文搜索字段。“web”、“phrase”和“raw”搜索方法仅适用于全文字段。"
#~ msgid "Search Method"
#~ msgstr "搜索方法"
#~ msgid "Fuzzy Lookups"
#~ msgstr "模糊查找"
#~ msgid "Ignore Accent"
#~ msgstr "忽略重音"
#~ msgid "Partial Match"
#~ msgstr "部分匹配"
#~ msgid "Starts With"
#~ msgstr "起始于"
#~ msgid "Fuzzy Search"
#~ msgstr "模糊搜索"
#~ msgid "Full Text"
#~ msgstr "全文"
#~ msgid " is part of a recipe step and cannot be deleted"
#~ msgstr " 是菜谱步骤的一部分,不能删除"
#~ msgid "Delete"
#~ msgstr "删除"
#~ msgid "Settings"
#~ msgstr "设置"
#~ msgid "Email"
#~ msgstr "电子邮件"
#~ msgid "Password"
#~ msgstr "密码"
#~ msgid "Foods"
#~ msgstr "食物"
#~ msgid "Units"
#~ msgstr "单位"
#~ msgid "Supermarket"
#~ msgstr "超市"
#~ msgid "Supermarket Category"
#~ msgstr "超市类型"
#~ msgid "Automations"
#~ msgstr "自动化"
#~ msgid "Files"
#~ msgstr "文件"
#~ msgid "Batch Edit"
#~ msgstr "批量编辑"
#~ msgid "History"
#~ msgstr "历史"
#~ msgid "Ingredient Editor"
#~ msgstr "食材编辑器"
#~ msgid "Properties"
#~ msgstr "属性"
#~ msgid "Unit Conversions"
#~ msgstr "单位转换"
#~ msgid "Create"
#~ msgstr "创建"
#~ msgid "External Recipes"
#~ msgstr "外部菜谱"
#~ msgid "Space Settings"
#~ msgstr "空间设置"
#~ msgid "External Connectors"
#~ msgstr "外部连接器"
#~ msgid "Admin"
#~ msgstr "管理员"
#~ msgid "Markdown Guide"
#~ msgstr "Markdown 手册"
#~ msgid "GitHub"
#~ msgstr "GitHub"
#~ msgid "Translate Tandoor"
#~ msgstr "翻译 Tandoor"
#~ msgid "API Browser"
#~ msgstr "应用程序接口浏览器"
#~ msgid "Log out"
#~ msgstr "退出"
#~ msgid "You are using the free version of Tandor"
#~ msgstr "你正在使用免费版的 Tandoor"
#~ msgid "Upgrade Now"
#~ msgstr "现在升级"
#~ msgid "Batch edit Category"
#~ msgstr "批量编辑类型"
#~ msgid "Batch edit Recipes"
#~ msgstr "批量编辑菜谱"
#~ msgid "Add the specified keywords to all recipes containing a word"
#~ msgstr "将指定的关键词添加到包含单词的所有菜谱中"
#~ msgid "Sync"
#~ msgstr "同步"
#~ msgid "Manage watched Folders"
#~ msgstr "管理关注的文件夹"
#~ msgid ""
#~ "On this Page you can manage all storage folder locations that should be "
#~ "monitored and synced."
#~ msgstr "在此页面上,你可以管理应该监视和同步的所有存储文件夹位置。"
#~ msgid "The path must be in the following format"
#~ msgstr "路径必须采用以下格式"
#~ msgid "Save"
#~ msgstr "保存"
#~ msgid "Manage External Storage"
#~ msgstr "管理外部存储"
#~ msgid "Sync Now!"
#~ msgstr "现在同步!"
#~ msgid "Show Recipes"
#~ msgstr "显示菜谱"
#~ msgid "Show Log"
#~ msgstr "显示记录"
#~ msgid "Importing Recipes"
#~ msgstr "导入配方"
#~ msgid ""
#~ "This can take a few minutes, depending on the number of recipes in sync, "
#~ "please wait."
#~ msgstr "这可能需要几分钟,取决于同步的菜谱数量,请等待。"
#~ msgid "Recipe Books"
#~ msgstr "烹饪手册"
#~ msgid "Import new Recipe"
#~ msgstr "导入新菜谱"
#~ msgid "Edit Recipe"
#~ msgstr "编辑菜谱"
#, python-format
#~ msgid "Are you sure you want to delete the %(title)s: %(object)s "
#~ msgstr "你确定要删除 %(title)s:%(object)s "
#~ msgid "This cannot be undone!"
#~ msgstr "这个不能撤销!"
#~ msgid "Protected"
#~ msgstr "受保护的"
#~ msgid "Cascade"
#~ msgstr "串联"
#~ msgid "Cancel"
#~ msgstr "取消"
#~ msgid "Edit"
#~ msgstr "编辑"
#~ msgid "View"
#~ msgstr "查看"
#~ msgid "Delete original file"
#~ msgstr "删除原文件"
#~ msgid "List"
#~ msgstr "清单"
#~ msgid "Filter"
#~ msgstr "筛选"
#~ msgid "Import all"
#~ msgstr "全部导入"
#~ msgid "New"
#~ msgstr "新"
#~ msgid "previous"
#~ msgstr "之前"
#~ msgid "next"
#~ msgstr "之后"
#~ msgid "View Log"
#~ msgstr "查看记录"
#~ msgid "Cook Log"
#~ msgstr "烹饪记录"
#~ msgid "Import"
#~ msgstr "导入"
#~ msgid "Security Warning"
#~ msgstr "安全警告"
#~ msgid ""
#~ "\n"
#~ " The Password and Token field are stored as plain text"
#~ "b> inside the database.\n"
#~ " This is necessary because they are needed to make API requests, "
#~ "but it also increases the risk of\n"
#~ " someone stealing it.
\n"
#~ " To limit the possible damage tokens or accounts with limited "
#~ "access can be used.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " 密码和令牌字段在数据库中存储为明文。\n"
#~ " 这是必要的,因为它们需要发出应用程序接口请求,但这也增加了被窃取的"
#~ "风险。
\n"
#~ " 为了限制可能的损害,可以使用访问受限的令牌或帐户。\n"
#~ " "
#~ msgid "You are currently offline!"
#~ msgstr "你目前处于离线状态!"
#~ msgid ""
#~ "The recipes listed below are available for offline viewing because you "
#~ "have recently viewed them. Keep in mind that data might be outdated."
#~ msgstr ""
#~ "下面列出的菜谱可以离线查看,因为你最近已经查看过了。请记住,数据可能是过时"
#~ "的。"
#~ msgid "Property Editor"
#~ msgstr "属性编辑器"
#~ msgid "Comments"
#~ msgstr "评论"
#~ msgid "by"
#~ msgstr "评论者"
#~ msgid "Comment"
#~ msgstr "评论"
#~ msgid ""
#~ "There are many options to configure the search depending on your personal "
#~ "preferences."
#~ msgstr "根据个人偏好,有许多选项可以配置搜索。"
#~ msgid ""
#~ "Usually you do not need to configure any of them and can just "
#~ "stick with either the default or one of the following presets."
#~ msgstr ""
#~ "通常你 不需要 配置它们中的任何一个,只需使用默认设置或以下预设值之"
#~ "一。"
#~ msgid ""
#~ "If you do want to configure the search you can read about the different "
#~ "options here."
#~ msgstr ""
#~ "如果你想要配置搜索,可以在 这里 阅读不同的选"
#~ "项。"
#~ msgid "Fuzzy"
#~ msgstr "模糊"
#~ msgid ""
#~ "Find what you need even if your search or the recipe contains typos. "
#~ "Might return more results than needed to make sure you find what you are "
#~ "looking for."
#~ msgstr ""
#~ "即使你的搜索或菜谱中有拼写错误,也要找到你需要的东西。可能会返回比需要更多"
#~ "的结果,以确保你找到所需的内容。"
#~ msgid "This is the default behavior"
#~ msgstr "这是默认行为"
#~ msgid "Apply"
#~ msgstr "应用"
#~ msgid "Precise"
#~ msgstr "精确"
#~ msgid ""
#~ "Allows fine control over search results but might not return results if "
#~ "too many spelling mistakes are made."
#~ msgstr ""
#~ "允许对搜索结果进行精细控制,但如果出现太多拼写错误,则可能不会返回结果。"
#~ msgid "Perfect for large Databases"
#~ msgstr "非常适合大型数据库"
#~ msgid "Social"
#~ msgstr "社交"
#~ msgid "Space Management"
#~ msgstr "空间管理"
#~ msgid "Space:"
#~ msgstr "空间:"
#~ msgid "Manage Subscription"
#~ msgstr "管理订阅"
#~ msgid "Leave Space"
#~ msgstr "留出空间"
#~ msgid "URL Import"
#~ msgstr "链接导入"
#~ msgid ""
#~ "Rating a recipe should have or greater. [0 - 5] Negative value filters "
#~ "rating less than."
#~ msgstr "配方的评分范围从 0 到 5。"
#~ msgid ""
#~ "Filter recipes updated on or after YYYY-MM-DD. Prepending - filters on or "
#~ "before date."
#~ msgstr "筛选在 YYYY-MM-DD 或之后更新的食谱。 前置 - 在日期或日期之前筛选。"
#~ msgid ""
#~ "Returns the shopping list entry with a primary key of id. Multiple "
#~ "values allowed."
#~ msgstr "返回主键为 id 的购物清单条目。 允许多个值。"
#~ msgid ""
#~ "Filter shopping list entries on checked. [true, false, both, recent"
#~ "b>]
- recent includes unchecked items and recently "
#~ "completed items."
#~ msgstr ""
#~ "勾选并筛选购物清单列表。 [真, 假, 两者都有, 最近"
#~ "b>]
- 最近包括未选中的项目和最近完成的项目。"
#~ msgid ""
#~ "Returns the shopping list entries sorted by supermarket category order."
#~ msgstr "返回按超市分类排序的购物清单列表。"
#, python-format
#~ msgid "Batch edit done. %(count)d recipe was updated."
#~ msgid_plural "Batch edit done. %(count)d Recipes where updated."
#~ msgstr[0] "批量编辑完成。%(count)d 个菜谱已更新。"
#~ msgid "Monitor"
#~ msgstr "监测"
#~ msgid "Storage Backend"
#~ msgstr "存储后端"
#~ msgid ""
#~ "Could not delete this storage backend as it is used in at least one "
#~ "monitor."
#~ msgstr "无法删除此存储后端,因为它至少在一台显示器中使用。"
#~ msgid "Connectors Config Backend"
#~ msgstr "连接器后端配置"
#~ msgid "Invite Link"
#~ msgstr "邀请链接"
#~ msgid "Space Membership"
#~ msgstr "成员"
#~ msgid "You cannot edit this storage!"
#~ msgstr "你不能编辑此存储空间!"
#~ msgid "Storage saved!"
#~ msgstr "存储已保存!"
#~ msgid "There was an error updating this storage backend!"
#~ msgstr "更新此存储后端时出错!"
#~ msgid "Config saved!"
#~ msgstr "配置已保存!"
#~ msgid "ConnectorConfig"
#~ msgstr "连接器配置"
#~ msgid "Changes saved!"
#~ msgstr "更改已保存!"
#~ msgid "Error saving changes!"
#~ msgstr "保存更改时出错!"
#~ msgid "Import Log"
#~ msgstr "导入日志"
#~ msgid "Discovery"
#~ msgstr "探索"
#~ msgid "Shopping List"
#~ msgstr "采购单"
#~ msgid "Connector Config Backend"
#~ msgstr "连接器后端配置"
#~ msgid "Invite Links"
#~ msgstr "邀请链接"
#~ msgid "Supermarkets"
#~ msgstr "超市"
#~ msgid "Shopping Categories"
#~ msgstr "购物类别"
#~ msgid "Custom Filters"
#~ msgstr "自定义筛选"
#~ msgid "Steps"
#~ msgstr "步骤"
#~ msgid "Property Types"
#~ msgstr "属性类型"
#~ msgid "This feature is not enabled by the server admin!"
#~ msgstr "此功能被服务器管理员禁用!"
#~ msgid "Imported new recipe!"
#~ msgstr "导入新菜谱!"
#~ msgid "There was an error importing this recipe!"
#~ msgstr "导入此菜谱时出错!"
#~ msgid "You do not have the required permissions to perform this action!"
#~ msgstr "您没有执行此操作所需的权限!"
#~ msgid "Comment saved!"
#~ msgstr "评论已保存!"
#~ msgid "You must select at least one field to search!"
#~ msgstr "你必须至少选择一个字段进行搜索!"
#~ msgid ""
#~ "To use this search method you must select at least one full text search "
#~ "field!"
#~ msgstr "要使用此搜索方法,至少选择一个全文搜索字段!"
#~ msgid "Fuzzy search is not compatible with this search method!"
#~ msgstr "模糊搜索与此搜索方法不兼容!"
#~ msgid "Malformed Invite Link supplied!"
#~ msgstr "提供了格式错误的邀请链接!"
#~ msgid "Successfully joined space."
#~ msgstr "成功加入空间。"
#~ msgid "Invite Link not valid or already used!"
#~ msgstr "邀请链接无效或已使用!"
#~ msgid "View your cookbooks"
#~ msgstr "查看你的烹饪手册"
#~ msgid "Default unit"
#~ msgstr "默认单位"
#~ msgid "Use KJ"
#~ msgstr "使用千焦"
#~ msgid "Theme"
#~ msgstr "主题"
#~ msgid "Navbar color"
#~ msgstr "导航栏颜色"
#~ msgid "Sticky navbar"
#~ msgstr "悬浮导航栏"
#~ msgid "Default page"
#~ msgstr "默认页面"
#~ msgid "Plan sharing"
#~ msgstr "计划分享"
#~ msgid "Ingredient decimal places"
#~ msgstr "食材小数位"
#~ msgid "Shopping list auto sync period"
#~ msgstr "购物清单自动同步周期"
#~ msgid "Left-handed mode"
#~ msgstr "左手模式"
#~ msgid ""
#~ "Color of the top navigation bar. Not all colors work with all themes, "
#~ "just try them out!"
#~ msgstr ""
#~ "顶部导航栏的颜色。并非所有的颜色都适用于所有的主题,只要试一试就可以了!"
#~ msgid ""
#~ "Default Unit to be used when inserting a new ingredient into a recipe."
#~ msgstr "在菜谱中插入新食材时使用的默认单位。"
#~ msgid ""
#~ "Enables support for fractions in ingredient amounts (e.g. convert "
#~ "decimals to fractions automatically)"
#~ msgstr "启用对食材数量的分数支持(例如自动将小数转换为分数)"
#~ msgid "Display nutritional energy amounts in joules instead of calories"
#~ msgstr "用焦耳来显示营养能量而不是卡路里"
#~ msgid ""
#~ "Users with whom newly created meal plans should be shared by default."
#~ msgstr "默认情况下,将自动与用户共享新创建的膳食计划。"
#~ msgid "Users with whom to share shopping lists."
#~ msgstr "与之共享购物清单的用户。"
#~ msgid "Number of decimals to round ingredients."
#~ msgstr "四舍五入食材的小数点数量。"
#~ msgid ""
#~ "If you want to be able to create and see comments underneath recipes."
#~ msgstr "如果你希望能够在菜谱下面创建并看到评论。"
#~ msgid ""
#~ "Setting to 0 will disable auto sync. When viewing a shopping list the "
#~ "list is updated every set seconds to sync changes someone else might have "
#~ "made. Useful when shopping with multiple people but might use a little "
#~ "bit of mobile data. If lower than instance limit it is reset when saving."
#~ msgstr ""
#~ "设置为0将禁用自动同步。当查看购物清单时,清单会每隔几秒钟更新一次,以同步"
#~ "其他人可能做出的改变。在与多人一起购物时很有用,但可能会消耗一点移动数据。"
#~ "如果低于实例限制,它将在保存时被重置。"
#~ msgid "Makes the navbar stick to the top of the page."
#~ msgstr "使导航栏悬浮在页面的顶部。"
#~ msgid "Automatically add meal plan ingredients to shopping list."
#~ msgstr "自动将膳食计划食材添加到购物清单中。"
#~ msgid "Exclude ingredients that are on hand."
#~ msgstr "排除现有食材。"
#~ msgid "Will optimize the UI for use with your left hand."
#~ msgstr "将使用左手模式优化界面显示。"
#~ msgid "You must provide at least a recipe or a title."
#~ msgstr "你必须至少提供一份菜谱或一个标题。"
#~ msgid "You can list default users to share recipes with in the settings."
#~ msgstr "你可以在设置中列出默认用户来分享菜谱。"
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "可以使用 Markdown 设置此字段格式。查看文档"
#~ msgid ""
#~ "Users will see all items you add to your shopping list. They must add "
#~ "you to see items on their list."
#~ msgstr ""
#~ "用户将看到你添加到购物清单中的所有商品。他们必须将你添加到列表才能看到他们"
#~ "清单上的项目。"
#~ msgid ""
#~ "When adding a meal plan to the shopping list (manually or automatically), "
#~ "include all related recipes."
#~ msgstr "将膳食计划(手动或自动)添加到购物清单时,包括所有相关食谱。"
#~ msgid ""
#~ "When adding a meal plan to the shopping list (manually or automatically), "
#~ "exclude ingredients that are on hand."
#~ msgstr "将膳食计划(手动或自动)添加到购物清单时,排除现有食材。"
#~ msgid "Default number of hours to delay a shopping list entry."
#~ msgstr "延迟购物清单条目的默认小时数。"
#~ msgid "Filter shopping list to only include supermarket categories."
#~ msgstr "筛选购物清单仅包含超市分类。"
#~ msgid "Days of recent shopping list entries to display."
#~ msgstr "显示最近几天的购物清单列表。"
#~ msgid "Mark food 'On Hand' when checked off shopping list."
#~ msgstr "在核对购物清单时,将食物标记为“入手”。"
#~ msgid "Delimiter to use for CSV exports."
#~ msgstr "用于 CSV 导出的分隔符。"
#~ msgid "Prefix to add when copying list to the clipboard."
#~ msgstr "将清单复制到剪贴板时要添加的前缀。"
#~ msgid "Share Shopping List"
#~ msgstr "分享购物清单"
#~ msgid "Autosync"
#~ msgstr "自动同步"
#~ msgid "Auto Add Meal Plan"
#~ msgstr "自动添加膳食计划"
#~ msgid "Exclude On Hand"
#~ msgstr "排除现有"
#~ msgid "Include Related"
#~ msgstr "包括相关"
#~ msgid "Default Delay Hours"
#~ msgstr "默认延迟时间"
#~ msgid "Filter to Supermarket"
#~ msgstr "按超市筛选"
#~ msgid "Recent Days"
#~ msgstr "最近几天"
#~ msgid "CSV Delimiter"
#~ msgstr "CSV 分隔符"
#~ msgid "List Prefix"
#~ msgstr "清单前缀"
#~ msgid "Auto On Hand"
#~ msgstr "自动入手"
#~ msgid "Reset Food Inheritance"
#~ msgstr "重置食物材料"
#~ msgid "Reset all food to inherit the fields configured."
#~ msgstr "重置所有食物以继承配置的字段。"
#~ msgid "Fields on food that should be inherited by default."
#~ msgstr "默认情况下应继承的食物上的字段。"
#~ msgid "Show recipe counts on search filters"
#~ msgstr "显示搜索筛选器上的食谱计数"
#~ msgid "Use the plural form for units and food inside this space."
#~ msgstr "在此空间内使用复数形式表示单位和食物。"
#~ msgid "One of queryset or hash_key must be provided"
#~ msgstr "必须提供 queryset 或 hash_key 之一"
#~ msgid "Profile"
#~ msgstr "简介"
#~ msgid "Recipe Book"
#~ msgstr "菜谱书"
#~ msgid "Bookmarks"
#~ msgstr "书签"
#~ msgid "Ingredients"
#~ msgstr "食材"
#~ msgid "Show recent recipes"
#~ msgstr "显示最近的菜谱"
#~ msgid "Search style"
#~ msgstr "搜索风格"
#~ msgid "Show recently viewed recipes on search page."
#~ msgstr "在搜索页面上显示最近查看的菜谱。"
#~ msgid "Small"
#~ msgstr "小"
#~ msgid "Large"
#~ msgstr "大"
#~ msgid "Edit Ingredients"
#~ msgstr "编辑食材"
#~ msgid ""
#~ "\n"
#~ " The following form can be used if, accidentally, two (or more) "
#~ "units or ingredients where created that should be\n"
#~ " the same.\n"
#~ " It merges two units or ingredients and updates all recipes using "
#~ "them.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " 如果意外创建两个(或更多)单位的相同食材,则可以使用下面的\n"
#~ " 表格。\n"
#~ " 可以合并两个单位的食材并使用它们更新所有菜谱。\n"
#~ " "
#~ msgid "Are you sure that you want to merge these two units?"
#~ msgstr "你确定要合并这两个单位吗?"
#~ msgid "Are you sure that you want to merge these two ingredients?"
#~ msgstr "你确定要合并这两种食材吗?"
#~ msgid "Import Recipes"
#~ msgstr "导入菜谱"
#~ msgid "Close"
#~ msgstr "关闭"
#~ msgid "Open Recipe"
#~ msgstr "打开菜谱"
#~ msgid "Meal Plan View"
#~ msgstr "膳食计划视图"
#~ msgid "Created by"
#~ msgstr "创建者"
#~ msgid "Shared with"
#~ msgstr "分享自"
#~ msgid "Never cooked before."
#~ msgstr "从来没有烹饪。"
#~ msgid "Other meals on this day"
#~ msgstr "这天的其他餐点"
#~ msgid "Recipe Image"
#~ msgstr "菜谱图像"
#~ msgid "Preparation time ca."
#~ msgstr "准备时间约"
#~ msgid "Waiting time ca."
#~ msgstr "等待时间约"
#~ msgid "External"
#~ msgstr "外部"
#~ msgid "Log Cooking"
#~ msgstr "烹饪记录"
#~ msgid "Account"
#~ msgstr "账户"
#~ msgid "Preferences"
#~ msgstr "首选项"
#~ msgid "API-Settings"
#~ msgstr "应用程序接口设置"
#~ msgid "Search-Settings"
#~ msgstr "搜索设置"
#~ msgid "Shopping-Settings"
#~ msgstr "购物设置"
#~ msgid "Name Settings"
#~ msgstr "名字设置"
#~ msgid "Account Settings"
#~ msgstr "帐号设置"
#~ msgid "Emails"
#~ msgstr "电子邮件"
#~ msgid "Language"
#~ msgstr "语言"
#~ msgid "Style"
#~ msgstr "样式"
#~ msgid "API Token"
#~ msgstr "应用程序接口令牌"
#~ msgid ""
#~ "You can use both basic authentication and token based authentication to "
#~ "access the REST API."
#~ msgstr ""
#~ "您可以使用基本身份验证和基于令牌的身份验证来访问表现层状态转换应用程序接口"
#~ "(REST API)。"
#~ msgid ""
#~ "Use the token as an Authorization header prefixed by the word token as "
#~ "shown in the following examples:"
#~ msgstr "使用令牌作为授权标头,前缀为单词令牌,如以下示例所示:"
#~ msgid "or"
#~ msgstr "或"
#~ msgid "Shopping Settings"
#~ msgstr "购物设置"
#~ msgid "Stats"
#~ msgstr "统计数据"
#~ msgid "Statistics"
#~ msgstr "统计数据"
#~ msgid "Number of objects"
#~ msgstr "对象数"
#~ msgid "Recipe Imports"
#~ msgstr "食谱导入"
#~ msgid "Objects stats"
#~ msgstr "对象统计"
#~ msgid "Recipes without Keywords"
#~ msgstr "菜谱没有关键字"
#~ msgid "Internal Recipes"
#~ msgstr "内部菜谱"
#~ msgid "Show Links"
#~ msgstr "显示链接"
#~ msgid "A user is required"
#~ msgstr "需要一个用户"
#~ msgid "Invite User"
#~ msgstr "邀请用户"
#~ msgid "User"
#~ msgstr "用户"
#~ msgid "Groups"
#~ msgstr "群组"
#~ msgid "admin"
#~ msgstr "管理"
#~ msgid "user"
#~ msgstr "用户"
#~ msgid "guest"
#~ msgstr "访客"
#~ msgid "remove"
#~ msgstr "删除"
#~ msgid "Update"
#~ msgstr "更新"
#~ msgid "You cannot edit yourself."
#~ msgstr "你不能编辑自己。"
#~ msgid "There are no members in your space yet!"
#~ msgstr "你的空间里还没有成员!"
#~ msgid ""
#~ "You are already member of a space and therefore cannot join this one."
#~ msgstr "你已是空间的成员,因此未能加入。"
#~ msgid "Try the new shopping list"
#~ msgstr "试试新的购物清单"
#~ msgid "Search Recipe"
#~ msgstr "搜索菜谱"
#~ msgid "Shopping Recipes"
#~ msgstr "购物菜谱"
#~ msgid "No recipes selected"
#~ msgstr "没选择菜谱"
#, fuzzy
#~ msgid "Entry Mode"
#~ msgstr "条目模式"
#, fuzzy
#~ msgid "Add Entry"
#~ msgstr "添加条目"
#~ msgid "Amount"
#~ msgstr "数量"
#~ msgid "Select Unit"
#~ msgstr "选择单位"
#~ msgid "Select"
#~ msgstr "选择"
#~ msgid "Select Food"
#~ msgstr "选择食物"
#~ msgid "Select Supermarket"
#~ msgstr "选择超市"
#~ msgid "Select User"
#~ msgstr "选择用户"
#~ msgid "Finished"
#~ msgstr "完成"
#, fuzzy
#~| msgid "You are offline, shopping list might not syncronize."
#~ msgid "You are offline, shopping list might not synchronize."
#~ msgstr "你已离线,购物清单可能无法同步。"
#~ msgid "Copy/Export"
#~ msgstr "复制或导出"
#~ msgid "Drag me to your bookmarks to import recipes from anywhere"
#~ msgstr "将我拖到书签以从任何地方导入食谱"
#~ msgid "Bookmark Me!"
#~ msgstr "给我加书签!"
#~ msgid "URL"
#~ msgstr "链接"
#~ msgid "App"
#~ msgstr "应用程序"
#~ msgid "Text"
#~ msgstr "文本"
#~ msgid "File"
#~ msgstr "文件"
#~ msgid "Enter website URL"
#~ msgstr "输入网站链接"
#~ msgid "Select recipe files to import or drop them here..."
#~ msgstr "选择要导入的菜谱文件或将其放到此处……"
#~ msgid "Paste json or html source here to load recipe."
#~ msgstr "将 json 或 html 源代码粘贴到此处以加载菜谱。"
#~ msgid "Preview Recipe Data"
#~ msgstr "预览菜谱数据"
#~ msgid ""
#~ "Drag recipe attributes from the right into the appropriate box below."
#~ msgstr "将菜谱属性从右侧拖动到下面相应的框中。"
#~ msgid "Clear Contents"
#~ msgstr "清除内容"
#~ msgid "Text dragged here will be appended to the name."
#~ msgstr "拖动到此处的文本将附加到名称中。"
#~ msgid "Text dragged here will be appended to the description."
#~ msgstr "拖动到此处的文本将附加到描述中。"
#~ msgid "Keywords dragged here will be appended to current list"
#~ msgstr "拖动到这里的关键字将被添加到当前列表"
#~ msgid "Image"
#~ msgstr "图片"
#~ msgid "Prep Time"
#~ msgstr "准备时间"
#~ msgid "Cook Time"
#~ msgstr "烹调时间"
#~ msgid "Ingredients dragged here will be appended to current list."
#~ msgstr "拖动到这里的材料将被添加到当前列表。"
#~ msgid "Show Blank Field"
#~ msgstr "显示空白域"
#~ msgid "Blank Field"
#~ msgstr "空白域"
#~ msgid "Delete Text"
#~ msgstr "删除文本"
#~ msgid "Delete image"
#~ msgstr "删除影像"
#~ msgid "Recipe Name"
#~ msgstr "菜谱名称"
#~ msgid "Recipe Description"
#~ msgstr "菜谱描述"
#~ msgid "Select one"
#~ msgstr "选择一项"
#~ msgid "Note"
#~ msgstr "笔记"
#~ msgid "Add Keyword"
#~ msgstr "添加关键词"
#~ msgid "All Keywords"
#~ msgstr "所有关键词"
#~ msgid "Import all keywords, not only the ones already existing."
#~ msgstr "导入所有关键词,并不只是现有的。"
#~ msgid "Information"
#~ msgstr "更多信息"
#~ msgid "GitHub Issues"
#~ msgstr "GitHub 问题"
#~ msgid "Recipe Markup Specification"
#~ msgstr "菜谱标记规范"
#~ msgid "The requested site provided malformed data and cannot be read."
#~ msgstr "请求的站点提供了格式错误的数据,无法读取。"
#~ msgid "The requested page could not be found."
#~ msgstr "找不到请求的页面。"
#~ msgid ""
#~ "The requested site does not provide any recognized data format to import "
#~ "the recipe from."
#~ msgstr "请求的站点未提供任何可识别的数据格式,无法从中导入菜谱。"
#~ msgid "I couldn't find anything to do."
#~ msgstr "无所事事。"
#~ msgid "Shopping Lists"
#~ msgstr "购物清单"
#~ msgid "You must supply a recipe or mealplan"
#~ msgstr "你必须提供菜谱或膳食计划"
#~ msgid "Time"
#~ msgstr "时间"
#~ msgid "Log Recipe Cooking"
#~ msgstr "菜谱烹饪记录"
#~ msgid "All fields are optional and can be left empty."
#~ msgstr "所有字段都是可选的,可以留空。"
#~ msgid "Rating"
#~ msgstr "评分"
#~ msgid "Exporting is not implemented for this provider"
#~ msgstr "此提供程序未实现导出"
#, fuzzy
#~ msgid "New unit that other gets replaced by."
#~ msgstr "新的单位被其他取代."
#~ msgid "Old Unit"
#~ msgstr "旧单位"
#~ msgid "Unit that should be replaced."
#~ msgstr "单位应被取代."
#~ msgid "New Food"
#~ msgstr "新的食品"
#, fuzzy
#~ msgid "New food that other gets replaced by."
#~ msgstr "新食品被其他取代."
#~ msgid "Old Food"
#~ msgstr "旧的食品"
#~ msgid "Utensils"
#~ msgstr "厨具"
#~ msgid "Storage Data"
#~ msgstr "存储数据"
#~ msgid "Storage Backends"
#~ msgstr "存储后端"
#~ msgid "Configure Sync"
#~ msgstr "配置同步"
#, fuzzy
#~| msgid "Select one"
#~ msgid "Select Recipe"
#~ msgstr "选择一项"
================================================
FILE: cookbook/locale/zh_Hant/LC_MESSAGES/django.po
================================================
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-22 20:15+0200\n"
"PO-Revision-Date: 2025-08-02 07:49+0000\n"
"Last-Translator: TC Kuo \n"
"Language-Team: Chinese (Traditional Han script) \n"
"Language: zh_Hant\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 5.8.4\n"
#: .\cookbook\forms.py:50
msgid "Default"
msgstr "預設"
#: .\cookbook\forms.py:77
msgid ""
"To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything."
msgstr ""
"為防止重複,忽略與現有同名的食譜。選中此框可導入所有內容(包括同名食譜)。"
#: .\cookbook\forms.py:108
msgid "Maximum number of users for this space reached."
msgstr "已達到該空間的最大用戶數。"
#: .\cookbook\forms.py:114
msgid "Email address already taken!"
msgstr "電子郵件地址已被註冊!"
#: .\cookbook\forms.py:121
msgid ""
"An email address is not required but if present the invite link will be sent "
"to the user."
msgstr "電子郵件地址不是必需的,但如果存在,邀請鏈接將被發送給用戶。"
#: .\cookbook\forms.py:133
msgid "Name already taken."
msgstr "名字已被占用。"
#: .\cookbook\forms.py:144 .\cookbook\forms.py:158
msgid "Accept Terms and Privacy"
msgstr "接受條款及隱私政策"
#: .\cookbook\helper\AllAuthCustomAdapter.py:41
msgid ""
"In order to prevent spam, the requested email was not send. Please wait a "
"few minutes and try again."
msgstr "為了防止垃圾郵件,所要求的電子郵件沒有被發送。請等待幾分鐘後再試。"
#: .\cookbook\helper\permission_helper.py:165
#: .\cookbook\helper\permission_helper.py:186 .\cookbook\views\views.py:138
msgid "You are not logged in and therefore cannot view this page!"
msgstr "你还沒有登錄,因此不能查看這個頁面!"
#: .\cookbook\helper\permission_helper.py:168
#: .\cookbook\helper\permission_helper.py:173
#: .\cookbook\helper\permission_helper.py:198
#: .\cookbook\helper\permission_helper.py:265
#: .\cookbook\helper\permission_helper.py:279
#: .\cookbook\helper\permission_helper.py:290
#: .\cookbook\helper\permission_helper.py:301
#: .\cookbook\helper\permission_helper.py:317
#: .\cookbook\helper\permission_helper.py:343
#: .\cookbook\helper\permission_helper.py:359
msgid "You do not have the required permissions to view this page!"
msgstr "你沒有必要的權限來查看這個頁面!"
#: .\cookbook\helper\permission_helper.py:191
#: .\cookbook\helper\permission_helper.py:214
#: .\cookbook\helper\permission_helper.py:236
#: .\cookbook\helper\permission_helper.py:251
msgid "You cannot interact with this object as it is not owned by you!"
msgstr "你不能與此對象互動,因為它不屬於你!"
#: .\cookbook\helper\permission_helper.py:420
msgid "You have reached the maximum number of recipes for your space."
msgstr "你已達到空間的最大食譜數量。"
#: .\cookbook\helper\permission_helper.py:432
msgid "You have more users than allowed in your space."
msgstr "你的空間中用戶數量超過允許的數量。"
#: .\cookbook\helper\recipe_url_import.py:319
msgid "reverse rotation"
msgstr "反向旋轉"
#: .\cookbook\helper\recipe_url_import.py:320
msgid "careful rotation"
msgstr "小心旋轉"
#: .\cookbook\helper\recipe_url_import.py:321
msgid "knead"
msgstr "揉"
#: .\cookbook\helper\recipe_url_import.py:322
msgid "thicken"
msgstr "增稠"
#: .\cookbook\helper\recipe_url_import.py:323
msgid "warm up"
msgstr "加熱"
#: .\cookbook\helper\recipe_url_import.py:324
msgid "ferment"
msgstr "發酵"
#: .\cookbook\helper\recipe_url_import.py:325
msgid "slow cook"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:326
msgid "egg boiler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:327
msgid "kettle"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:328
msgid "blend"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:329
msgid "pre-clean"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:330
msgid "high temperature"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:331
msgid "rice cooker"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:332
msgid "caramelize"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:333
msgid "peeler"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:334
msgid "slicer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:335
msgid "grater"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:336
msgid "spiralizer"
msgstr ""
#: .\cookbook\helper\recipe_url_import.py:337
msgid "sous-vide"
msgstr "低溫烹調"
#: .\cookbook\helper\shopping_helper.py:150
msgid "You must supply a servings size"
msgstr "你必須提供份量大小"
#: .\cookbook\helper\template_helper.py:97
#: .\cookbook\helper\template_helper.py:99
#: .\cookbook\helper\template_helper.py:101
msgid "Could not parse template code."
msgstr "無法解析模板代碼。"
#: .\cookbook\integration\copymethat.py:44
#: .\cookbook\integration\melarecipes.py:37
msgid "Favorite"
msgstr "收藏"
#: .\cookbook\integration\copymethat.py:50
msgid "I made this"
msgstr "我做了這個"
#: .\cookbook\integration\integration.py:238
msgid ""
"Importer expected a .zip file. Did you choose the correct importer type for "
"your data ?"
msgstr "導入需要一個 .zip 文件。你是否為數據選擇了正確的導入器類型?"
#: .\cookbook\integration\integration.py:241
msgid ""
"An unexpected error occurred during the import. Please make sure you have "
"uploaded a valid file."
msgstr "在導入過程中發生了一個意外的錯誤。請確認你上傳的文件是否有效。"
#: .\cookbook\integration\integration.py:246
msgid "The following recipes were ignored because they already existed:"
msgstr "以下食譜被忽略了,因為它們已經存在了:"
#: .\cookbook\integration\integration.py:250
#, python-format
msgid "Imported %s recipes."
msgstr "導入了%s食譜。"
#: .\cookbook\integration\mealie1.py:210
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "Calories"
msgstr "卡路里"
#: .\cookbook\integration\mealie1.py:211
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
msgid "Carbohydrates"
msgstr "碳水化合物"
#: .\cookbook\integration\mealie1.py:212
msgid "Cholesterol"
msgstr ""
#: .\cookbook\integration\mealie1.py:213
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
msgid "Fat"
msgstr "脂肪"
#: .\cookbook\integration\mealie1.py:214
msgid "Fiber"
msgstr ""
#: .\cookbook\integration\mealie1.py:215
#, fuzzy
#| msgid "Proteins"
msgid "Protein"
msgstr "蛋白質"
#: .\cookbook\integration\mealie1.py:216
msgid "Saturated Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:217
msgid "Sodium"
msgstr ""
#: .\cookbook\integration\mealie1.py:218
msgid "Sugar"
msgstr ""
#: .\cookbook\integration\mealie1.py:219
msgid "Trans Fat"
msgstr ""
#: .\cookbook\integration\mealie1.py:220
msgid "Unsaturated Fat"
msgstr ""
#: .\cookbook\integration\openeats.py:28
msgid "Recipe source:"
msgstr "食譜來源:"
#: .\cookbook\integration\paprika.py:49
msgid "Notes"
msgstr "說明"
#: .\cookbook\integration\paprika.py:52
msgid "Nutritional Information"
msgstr "營養資訊"
#: .\cookbook\integration\paprika.py:56
msgid "Source"
msgstr "來源"
#: .\cookbook\integration\recettetek.py:55
#: .\cookbook\integration\recipekeeper.py:70
msgid "Imported from"
msgstr "導入"
#: .\cookbook\integration\saffron.py:23
msgid "Servings"
msgstr "份量"
#: .\cookbook\integration\saffron.py:25
msgid "Waiting time"
msgstr "等待時間"
#: .\cookbook\integration\saffron.py:27
msgid "Preparation Time"
msgstr "準備時間"
#: .\cookbook\integration\saffron.py:29 .\cookbook\templates\index.html:6
msgid "Cookbook"
msgstr "菜譜"
#: .\cookbook\integration\saffron.py:31
msgid "Section"
msgstr "部分"
#: .\cookbook\management\commands\fix_duplicate_properties.py:15
msgid "Fixes foods with "
msgstr "修復具有重複屬性的食物 "
#: .\cookbook\management\commands\rebuildindex.py:14
msgid "Rebuilds full text search index on Recipe"
msgstr "重建食譜的全文搜索索引"
#: .\cookbook\management\commands\rebuildindex.py:18
msgid "Only Postgresql databases use full text search, no index to rebuild"
msgstr "只有Postgresql數據庫使用全文搜索,無需重建索引"
#: .\cookbook\management\commands\rebuildindex.py:29
msgid "Recipe index rebuild complete."
msgstr "食譜索引重建完成。"
#: .\cookbook\management\commands\rebuildindex.py:31
msgid "Recipe index rebuild failed."
msgstr "食譜索引重建失敗。"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
msgid "Breakfast"
msgstr "早餐"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:19
msgid "Lunch"
msgstr "午餐"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:24
msgid "Dinner"
msgstr "晚餐"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:29 .\cookbook\models.py:971
msgid "Other"
msgstr "其他"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
#: .\cookbook\migrations\0190_auto_20230525_1506.py:18
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "g"
msgstr "克"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:19
msgid "Proteins"
msgstr "蛋白質"
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
msgid "kcal"
msgstr "千卡"
#: .\cookbook\models.py:325
msgid ""
"Maximum file storage for space in MB. 0 for unlimited, -1 to disable file "
"upload."
msgstr "空間的最大文件存儲量,單位為 MB。0表示無限製,-1表示禁止上傳文件。"
#: .\cookbook\models.py:513
msgid "Search"
msgstr "搜索"
#: .\cookbook\models.py:514
msgid "Meal-Plan"
msgstr "膳食計劃"
#: .\cookbook\models.py:515
msgid "Books"
msgstr "烹飪手冊"
#: .\cookbook\models.py:516 .\cookbook\views\views.py:416
#: .\cookbook\views\views.py:417
msgid "Shopping"
msgstr "購物"
#: .\cookbook\models.py:967
msgid "Nutrition"
msgstr "營養"
#: .\cookbook\models.py:968
msgid "Allergen"
msgstr "過敏原"
#: .\cookbook\models.py:969
msgid "Price"
msgstr "價格"
#: .\cookbook\models.py:970
msgid "Goal"
msgstr "目標"
#: .\cookbook\models.py:1467 .\cookbook\templates\search_info.html:28
msgid "Simple"
msgstr "簡單"
#: .\cookbook\models.py:1468 .\cookbook\templates\search_info.html:33
msgid "Phrase"
msgstr "短語"
#: .\cookbook\models.py:1469 .\cookbook\templates\search_info.html:38
msgid "Web"
msgstr "網絡"
#: .\cookbook\models.py:1470 .\cookbook\templates\search_info.html:47
msgid "Raw"
msgstr "原始"
#: .\cookbook\models.py:1525
msgid "Food Alias"
msgstr "食物別名"
#: .\cookbook\models.py:1526
msgid "Unit Alias"
msgstr "單位別名"
#: .\cookbook\models.py:1527
msgid "Keyword Alias"
msgstr "關鍵詞別名"
#: .\cookbook\models.py:1528
msgid "Description Replace"
msgstr "描述替換"
#: .\cookbook\models.py:1529
msgid "Instruction Replace"
msgstr "指示替換"
#: .\cookbook\models.py:1530
msgid "Never Unit"
msgstr "禁用單位識別"
#: .\cookbook\models.py:1531
msgid "Transpose Words"
msgstr "轉換詞語"
#: .\cookbook\models.py:1532
msgid "Food Replace"
msgstr "食物替換"
#: .\cookbook\models.py:1533
msgid "Unit Replace"
msgstr "單位替換"
#: .\cookbook\models.py:1534
msgid "Name Replace"
msgstr "名稱替換"
#: .\cookbook\models.py:1564
msgid "Recipe"
msgstr "食譜"
#: .\cookbook\models.py:1565
msgid "Food"
msgstr "食物"
#: .\cookbook\models.py:1566
msgid "Keyword"
msgstr "關鍵詞"
#: .\cookbook\serializer.py:262
msgid "File uploads are not enabled for this Space."
msgstr "未為此空間啟用文件上傳。"
#: .\cookbook\serializer.py:273
msgid "You have reached your file upload limit."
msgstr "你已達到文件上傳的限製。"
#: .\cookbook\serializer.py:281
msgid "The given file type is not allowed."
msgstr ""
#: .\cookbook\serializer.py:421 .\cookbook\views\views.py:94
msgid ""
"You have the reached the maximum amount of spaces that can be owned by you."
msgstr "你已達到可以擁有的最大空間數量。"
#: .\cookbook\serializer.py:434
msgid "Space Name must be unique."
msgstr ""
#: .\cookbook\serializer.py:469
msgid "Cannot modify Space owner permission."
msgstr "無法修改空間所有者權限。"
#: .\cookbook\serializer.py:1596
msgid "Hello"
msgstr "你好"
#: .\cookbook\serializer.py:1596
msgid "You have been invited by "
msgstr "你已被邀請由 "
#: .\cookbook\serializer.py:1598
msgid " to join their Tandoor Recipes space "
msgstr " 加入他們的Tandoor Recipes空間 "
#: .\cookbook\serializer.py:1600
msgid "Click the following link to activate your account: "
msgstr "點擊以下鏈接激活你的賬戶: "
#: .\cookbook\serializer.py:1602
msgid ""
"If the link does not work use the following code to manually join the space: "
msgstr "如果鏈接無法使用,請使用以下代碼手動加入空間: "
#: .\cookbook\serializer.py:1604
msgid "The invitation is valid until "
msgstr "邀請有效期至 "
#: .\cookbook\serializer.py:1606
msgid ""
"Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub "
msgstr "Tandoor Recipes是一個開源的食譜管理器。在GitHub上查看 "
#: .\cookbook\serializer.py:1609
msgid "Tandoor Recipes Invite"
msgstr "Tandoor Recipes邀請"
#: .\cookbook\serializer.py:1813
msgid "Existing shopping list to update"
msgstr "現有購物清單更新"
#: .\cookbook\serializer.py:1815
msgid ""
"List of ingredient IDs from the recipe to add, if not provided all "
"ingredients will be added."
msgstr "從食譜中添加的成分ID列表,如果未提供,將添加所有成分。"
#: .\cookbook\serializer.py:1817
msgid ""
"Providing a list_recipe ID and servings of 0 will delete that shopping list."
msgstr "提供一個list_recipe ID和0份量將刪除該購物清單。"
#: .\cookbook\serializer.py:1826
msgid "Amount of food to add to the shopping list"
msgstr "添加到購物清單的食物數量"
#: .\cookbook\serializer.py:1828
msgid "ID of unit to use for the shopping list"
msgstr "用於購物清單的單位ID"
#: .\cookbook\serializer.py:1830
msgid "When set to true will delete all food from active shopping lists."
msgstr "設置為true時,將刪除所有活動購物清單中的食物。"
#: .\cookbook\templates\404.html:5
msgid "404 Error"
msgstr "404錯誤"
#: .\cookbook\templates\404.html:18
msgid "The page you are looking for could not be found."
msgstr "找不到你要找的頁面。"
#: .\cookbook\templates\404.html:33
msgid "Take me Home"
msgstr "回到主頁"
#: .\cookbook\templates\404.html:35
msgid "Report a Bug"
msgstr "報告一個錯誤"
#: .\cookbook\templates\account\email.html:6
#: .\cookbook\templates\account\email.html:10
msgid "E-mail Addresses"
msgstr "電子郵件地址"
#: .\cookbook\templates\account\email.html:12
msgid "The following e-mail addresses are associated with your account:"
msgstr "以下電子郵件地址與你的賬戶相關聯:"
#: .\cookbook\templates\account\email.html:29
msgid "Verified"
msgstr "已驗證"
#: .\cookbook\templates\account\email.html:31
msgid "Unverified"
msgstr "未驗證"
#: .\cookbook\templates\account\email.html:33
msgid "Primary"
msgstr "主要"
#: .\cookbook\templates\account\email.html:40
msgid "Make Primary"
msgstr "設為主要"
#: .\cookbook\templates\account\email.html:42
msgid "Re-send Verification"
msgstr "重新發送驗證"
#: .\cookbook\templates\account\email.html:43
#: .\cookbook\templates\socialaccount\connections.html:36
msgid "Remove"
msgstr "移除"
#: .\cookbook\templates\account\email.html:51
msgid "Warning:"
msgstr "警告:"
#: .\cookbook\templates\account\email.html:51
msgid ""
"You currently do not have any e-mail address set up. You should really add "
"an e-mail address so you can receive notifications, reset your password, etc."
msgstr ""
"你目前沒有設置任何電子郵件地址。你應該添加一個電子郵件地址,以便接收通知、重"
"置密碼等。"
#: .\cookbook\templates\account\email.html:57
msgid "Add E-mail Address"
msgstr "添加電子郵件地址"
#: .\cookbook\templates\account\email.html:62
msgid "Add E-mail"
msgstr "添加電子郵件"
#: .\cookbook\templates\account\email.html:72
msgid "Do you really want to remove the selected e-mail address?"
msgstr "你真的想要移除選定的電子郵件地址嗎?"
#: .\cookbook\templates\account\email_confirm.html:6
#: .\cookbook\templates\account\email_confirm.html:10
msgid "Confirm E-mail Address"
msgstr "確認電子郵件地址"
#: .\cookbook\templates\account\email_confirm.html:16
#, python-format
msgid ""
"Please confirm that\n"
" %(email)s is an e-mail address "
"for user %(user_display)s\n"
" ."
msgstr ""
"請確認\n"
" %(email)s 是用戶 "
"%(user_display)s 的電子郵件地址\n"
" 。"
#: .\cookbook\templates\account\email_confirm.html:22
msgid "Confirm"
msgstr "確認"
#: .\cookbook\templates\account\email_confirm.html:29
#, python-format
msgid ""
"This e-mail confirmation link expired or is invalid. Please\n"
" issue a new e-mail confirmation "
"request."
msgstr ""
"此電子郵件確認鏈接已過期或無效。請\n"
" 發出新的電子郵件確認請求。"
#: .\cookbook\templates\account\login.html:8
#: .\cookbook\templates\openid\login.html:8
msgid "Login"
msgstr "登錄"
#: .\cookbook\templates\account\login.html:15
#: .\cookbook\templates\account\login.html:31
#: .\cookbook\templates\account\password_reset.html:39
#: .\cookbook\templates\account\password_reset_done.html:31
#: .\cookbook\templates\account\signup.html:68
#: .\cookbook\templates\account\signup_closed.html:15
#: .\cookbook\templates\openid\login.html:15
#: .\cookbook\templates\openid\login.html:26
#: .\cookbook\templates\socialaccount\authentication_error.html:15
msgid "Sign In"
msgstr "登錄"
#: .\cookbook\templates\account\login.html:34
#: .\cookbook\templates\account\password_reset.html:41
#: .\cookbook\templates\account\password_reset_done.html:33
#: .\cookbook\templates\socialaccount\signup.html:8
#: .\cookbook\templates\socialaccount\signup.html:56
msgid "Sign Up"
msgstr "註冊"
#: .\cookbook\templates\account\login.html:38
msgid "Lost your password?"
msgstr "忘記密碼?"
#: .\cookbook\templates\account\login.html:39
#: .\cookbook\templates\account\password_reset.html:29
msgid "Reset My Password"
msgstr "重置我的密碼"
#: .\cookbook\templates\account\login.html:50
msgid "Social Login"
msgstr "社交登錄"
#: .\cookbook\templates\account\login.html:51
msgid "You can use any of the following providers to sign in."
msgstr "你可以使用以下任何提供商登錄。"
#: .\cookbook\templates\account\logout.html:5
#: .\cookbook\templates\account\logout.html:9
#: .\cookbook\templates\account\logout.html:18
msgid "Sign Out"
msgstr "登出"
#: .\cookbook\templates\account\logout.html:11
msgid "Are you sure you want to sign out?"
msgstr "你確定要登出嗎?"
#: .\cookbook\templates\account\password_change.html:6
#: .\cookbook\templates\account\password_change.html:10
#: .\cookbook\templates\account\password_change.html:15
#: .\cookbook\templates\account\password_reset_from_key.html:7
#: .\cookbook\templates\account\password_reset_from_key.html:13
#: .\cookbook\templates\account\password_reset_from_key_done.html:7
#: .\cookbook\templates\account\password_reset_from_key_done.html:13
msgid "Change Password"
msgstr "更改密碼"
#: .\cookbook\templates\account\password_change.html:16
msgid "Forgot Password?"
msgstr "忘記密碼?"
#: .\cookbook\templates\account\password_reset.html:7
#: .\cookbook\templates\account\password_reset.html:13
#: .\cookbook\templates\account\password_reset_done.html:7
#: .\cookbook\templates\account\password_reset_done.html:18
msgid "Password Reset"
msgstr "重置密碼"
#: .\cookbook\templates\account\password_reset.html:24
msgid ""
"Forgotten your password? Enter your e-mail address below, and we'll send you "
"an e-mail allowing you to reset it."
msgstr ""
"忘記密碼了嗎?在下方輸入你的電子郵件地址,我們會發送一封電子郵件讓你重置密"
"碼。"
#: .\cookbook\templates\account\password_reset.html:32
msgid "Password reset is disabled on this instance."
msgstr "此實例上已禁用密碼重置。"
#: .\cookbook\templates\account\password_reset_done.html:25
msgid ""
"We have sent you an e-mail. Please contact us if you do not receive it "
"within a few minutes."
msgstr "我們已經發送了一封電子郵件給你。如果幾分鐘內沒有收到,請聯繫我們。"
#: .\cookbook\templates\account\password_reset_from_key.html:13
msgid "Bad Token"
msgstr "無效的令牌"
#: .\cookbook\templates\account\password_reset_from_key.html:25
#, python-format
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used.\n"
" Please request a new "
"password reset."
msgstr ""
"密碼重置鏈接無效,可能是因為它已經被使用過。\n"
" 請請求新的密碼重置。"
#: .\cookbook\templates\account\password_reset_from_key.html:33
msgid "change password"
msgstr "更改密碼"
#: .\cookbook\templates\account\password_reset_from_key.html:36
#: .\cookbook\templates\account\password_reset_from_key_done.html:19
msgid "Your password is now changed."
msgstr "你的密碼已更改。"
#: .\cookbook\templates\account\password_set.html:6
#: .\cookbook\templates\account\password_set.html:10
#: .\cookbook\templates\account\password_set.html:15
msgid "Set Password"
msgstr "設置密碼"
#: .\cookbook\templates\account\signup.html:5
msgid "Register"
msgstr "註冊"
#: .\cookbook\templates\account\signup.html:11
msgid "Create an Account"
msgstr "創建賬戶"
#: .\cookbook\templates\account\signup.html:41
msgid "I accept the follwoing"
msgstr "我接受以下"
#: .\cookbook\templates\account\signup.html:44
#: .\cookbook\templates\socialaccount\signup.html:35
msgid "Terms and Conditions"
msgstr "條款和條件"
#: .\cookbook\templates\account\signup.html:47
#: .\cookbook\templates\socialaccount\signup.html:38
msgid "and"
msgstr "和"
#: .\cookbook\templates\account\signup.html:51
#: .\cookbook\templates\socialaccount\signup.html:42
msgid "Privacy Policy"
msgstr "隱私政策"
#: .\cookbook\templates\account\signup.html:64
msgid "Create User"
msgstr "創建用戶"
#: .\cookbook\templates\account\signup.html:68
msgid "Already have an account?"
msgstr "已經有賬戶了?"
#: .\cookbook\templates\account\signup_closed.html:5
#: .\cookbook\templates\account\signup_closed.html:11
msgid "Sign Up Closed"
msgstr "註冊已關閉"
#: .\cookbook\templates\account\signup_closed.html:13
msgid "We are sorry, but the sign up is currently closed."
msgstr "很抱歉,目前註冊已關閉。"
#: .\cookbook\templates\frontend\tandoor.html:15
#, fuzzy
#| msgid "Tandoor Recipes Invite"
msgid "Tandoor Recipe Manager"
msgstr "Tandoor Recipes邀請"
#: .\cookbook\templates\index.html:28
msgid "Search recipe ..."
msgstr "搜索食譜 ..."
#: .\cookbook\templates\index.html:43
msgid "New Recipe"
msgstr "新食譜"
#: .\cookbook\templates\index.html:46
msgid "Import Recipe"
msgstr "導入食譜"
#: .\cookbook\templates\index.html:52
msgid "Advanced Search"
msgstr "高級搜索"
#: .\cookbook\templates\index.html:56
msgid "Reset Search"
msgstr "重置搜索"
#: .\cookbook\templates\index.html:84
msgid "Last viewed"
msgstr "最近查看"
#: .\cookbook\templates\index.html:86
msgid "Recipes"
msgstr "食譜"
#: .\cookbook\templates\index.html:93
msgid "Log in to view recipes"
msgstr "登錄以查看食譜"
#: .\cookbook\templates\markdown_info.html:5
#: .\cookbook\templates\markdown_info.html:13
msgid "Markdown Info"
msgstr "Markdown 信息"
#: .\cookbook\templates\markdown_info.html:14
msgid ""
"\n"
" Markdown is lightweight markup language that can be used to format "
"plain text easily.\n"
" This site uses the Python Markdown library to\n"
" convert your text into nice looking HTML. Its full markdown "
"documentation can be found\n"
" here.\n"
" An incomplete but most likely sufficient documentation can be found "
"below.\n"
" "
msgstr ""
"\n"
" Markdown 是一種輕量級標記語言,可以用來輕鬆地格式化純文本。\n"
" 本站使用 Python Markdown 庫來\n"
" 將你的文本轉換成漂亮的 HTML。完整的 Markdown 文檔可以在\n"
" 這裡找到。\n"
" 以下是一些不完整但可能足夠的文檔。\n"
" "
#: .\cookbook\templates\markdown_info.html:25
msgid "Headers"
msgstr "標題"
#: .\cookbook\templates\markdown_info.html:54
msgid "Formatting"
msgstr "格式化"
#: .\cookbook\templates\markdown_info.html:56
#: .\cookbook\templates\markdown_info.html:72
msgid "Line breaks are inserted by adding two spaces after the end of a line"
msgstr "在行尾添加兩個空格來插入換行"
#: .\cookbook\templates\markdown_info.html:57
#: .\cookbook\templates\markdown_info.html:73
msgid "or by leaving a blank line in between."
msgstr "或在中間留一個空行。"
#: .\cookbook\templates\markdown_info.html:59
#: .\cookbook\templates\markdown_info.html:74
msgid "This text is bold"
msgstr "這段文字是粗體"
#: .\cookbook\templates\markdown_info.html:60
#: .\cookbook\templates\markdown_info.html:75
msgid "This text is italic"
msgstr "這段文字是斜體"
#: .\cookbook\templates\markdown_info.html:61
#: .\cookbook\templates\markdown_info.html:77
msgid "Blockquotes are also possible"
msgstr "也可以使用引用"
#: .\cookbook\templates\markdown_info.html:84
msgid "Lists"
msgstr "列表"
#: .\cookbook\templates\markdown_info.html:85
msgid ""
"Lists can ordered or unordered. It is important to leave a blank line "
"before the list!"
msgstr "列表可以是有序或無序的。在列表前留一個空行很重要!"
#: .\cookbook\templates\markdown_info.html:87
#: .\cookbook\templates\markdown_info.html:108
msgid "Ordered List"
msgstr "有序列表"
#: .\cookbook\templates\markdown_info.html:89
#: .\cookbook\templates\markdown_info.html:90
#: .\cookbook\templates\markdown_info.html:91
#: .\cookbook\templates\markdown_info.html:110
#: .\cookbook\templates\markdown_info.html:111
#: .\cookbook\templates\markdown_info.html:112
msgid "unordered list item"
msgstr "無序列表項"
#: .\cookbook\templates\markdown_info.html:93
#: .\cookbook\templates\markdown_info.html:114
msgid "Unordered List"
msgstr "無序列表"
#: .\cookbook\templates\markdown_info.html:95
#: .\cookbook\templates\markdown_info.html:96
#: .\cookbook\templates\markdown_info.html:97
#: .\cookbook\templates\markdown_info.html:116
#: .\cookbook\templates\markdown_info.html:117
#: .\cookbook\templates\markdown_info.html:118
msgid "ordered list item"
msgstr "有序列表項"
#: .\cookbook\templates\markdown_info.html:125
msgid "Images & Links"
msgstr "圖片和鏈接"
#: .\cookbook\templates\markdown_info.html:126
msgid ""
"Links can be formatted with Markdown. This application also allows to paste "
"links directly into markdown fields without any formatting."
msgstr ""
"鏈接可以用 Markdown 格式化。此應用程序還允許將鏈接直接粘貼到 Markdown 字段中"
"而無需任何格式化。"
#: .\cookbook\templates\markdown_info.html:132
#: .\cookbook\templates\markdown_info.html:145
msgid "This will become an image"
msgstr "這將成為一張圖片"
#: .\cookbook\templates\markdown_info.html:152
msgid "Tables"
msgstr "表格"
#: .\cookbook\templates\markdown_info.html:153
msgid ""
"Markdown tables are hard to create by hand. It is recommended to use a table "
"editor like this one."
msgstr ""
"Markdown 表格很難手動創建。建議使用像這樣的表"
"格編輯器。"
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:171
#: .\cookbook\templates\markdown_info.html:177
msgid "Table"
msgstr "表格"
#: .\cookbook\templates\markdown_info.html:155
#: .\cookbook\templates\markdown_info.html:172
msgid "Header"
msgstr "標題"
#: .\cookbook\templates\markdown_info.html:157
#: .\cookbook\templates\markdown_info.html:178
msgid "Cell"
msgstr "單元格"
#: .\cookbook\templates\no_groups_info.html:5
#: .\cookbook\templates\no_groups_info.html:12
msgid "No Permissions"
msgstr "無權限"
#: .\cookbook\templates\no_groups_info.html:17
msgid "You do not have any groups and therefor cannot use this application."
msgstr "你沒有任何群組,因此無法使用此應用程序。"
#: .\cookbook\templates\no_groups_info.html:18
#: .\cookbook\templates\no_perm_info.html:15
msgid "Please contact your administrator."
msgstr "請聯繫你的管理員。"
#: .\cookbook\templates\no_perm_info.html:5
#: .\cookbook\templates\no_perm_info.html:12
msgid "No Permission"
msgstr "無權限"
#: .\cookbook\templates\no_perm_info.html:15
msgid ""
"You do not have the required permissions to view this page or perform this "
"action."
msgstr "你沒有查看此頁面或執行此操作所需的權限。"
#: .\cookbook\templates\offline.html:5
msgid "Offline"
msgstr "離線"
#: .\cookbook\templates\openid\login.html:27
#: .\cookbook\templates\socialaccount\authentication_error.html:27
msgid "Back"
msgstr "返回"
#: .\cookbook\templates\rest_framework\api.html:5
msgid "Recipe Home"
msgstr "食譜首頁"
#: .\cookbook\templates\rest_framework\api.html:11
msgid "API Documentation"
msgstr "API文檔"
#: .\cookbook\templates\search_info.html:5
#: .\cookbook\templates\search_info.html:9
msgid "Search Settings"
msgstr "搜索設置"
#: .\cookbook\templates\search_info.html:10
msgid ""
"\n"
" Creating the best search experience is complicated and weighs "
"heavily on your personal configuration. \n"
" Changing any of the search settings can have significant impact on "
"the speed and quality of the results.\n"
" Search Methods, Trigrams and Full Text Search configurations are "
"only available if you are using Postgres for your database.\n"
" "
msgstr ""
"\n"
" 創建最佳搜索體驗是複雜的,並且在很大程度上取決於你的個人配置。\n"
" 更改任何搜索設置都會對結果的速度和質量產生重大影響。\n"
" 搜索方法、三元組和全文搜索配置僅在你使用Postgres作為數據庫時可用。\n"
" "
#: .\cookbook\templates\search_info.html:19
msgid "Search Methods"
msgstr "搜索方法"
#: .\cookbook\templates\search_info.html:23
msgid ""
" \n"
" Full text searches attempt to normalize the words provided to "
"match common variants. For example: 'forked', 'forking', 'forks' will all "
"normalize to 'fork'.\n"
" There are several methods available, described below, that will "
"control how the search behavior should react when multiple words are "
"searched.\n"
" Full technical details on how these operate can be viewed on Postgresql's website.\n"
" "
msgstr ""
" \n"
" 全文搜索嘗試將提供的單詞標準化以匹配常見變體。例"
"如:'forked'、'forking'、'forks'都會標準化為'fork'。\n"
" 有幾種可用的方法,如下所述,將控制多個單詞搜索時的搜索行為。\n"
" 有關這些操作的完整技術細節可以在Postgresql的網站上查看。\n"
" "
#: .\cookbook\templates\search_info.html:29
msgid ""
" \n"
" Simple searches ignore punctuation and common words such as "
"'the', 'a', 'and'. And will treat separate words as required.\n"
" Searching for 'apple or flour' will return any recipe that "
"includes both 'apple' and 'flour' anywhere in the fields that have been "
"selected for a full text search.\n"
" "
msgstr ""
" \n"
" 簡單搜索會忽略標點符號和常見單詞,如'the'、'a'、'and'。並將根據需"
"要處理單獨的單詞。\n"
" 搜索'apple or flour'將返回在選擇進行全文搜索的字段中包"
"含'apple'和'flour'的任何食譜。\n"
" "
#: .\cookbook\templates\search_info.html:34
msgid ""
" \n"
" Phrase searches ignore punctuation, but will search for all of "
"the words in the exact order provided.\n"
" Searching for 'apple or flour' will only return a recipe that "
"includes the exact phrase 'apple or flour' in any of the fields that have "
"been selected for a full text search.\n"
" "
msgstr ""
" \n"
" 短語搜索會忽略標點符號,但會按提供的確切順序搜索所有單詞。\n"
" 搜索'apple or flour'將僅返回在選擇進行全文搜索的字段中包含確切短"
"語'apple or flour'的食譜。\n"
" "
#: .\cookbook\templates\search_info.html:39
msgid ""
" \n"
" Web searches simulate functionality found on many web search "
"sites supporting special syntax.\n"
" Placing quotes around several words will convert those words "
"into a phrase.\n"
" 'or' is recognized as searching for the word (or phrase) "
"immediately before 'or' OR the word (or phrase) directly after.\n"
" '-' is recognized as searching for recipes that do not include "
"the word (or phrase) that comes immediately after. \n"
" For example searching for 'apple pie' or cherry -butter will "
"return any recipe that includes the phrase 'apple pie' or the word "
"'cherry' \n"
" in any field included in the full text search but exclude any "
"recipe that has the word 'butter' in any field included.\n"
" "
msgstr ""
" \n"
" 網絡搜索模擬許多支持特殊語法的網絡搜索網站上的功能。\n"
" 在幾個單詞周圍加上引號將把這些單詞轉換為短語。\n"
" 'or'被認為是搜索'或'之前的單詞(或短語)或'或'之後的單詞(或短"
"語)。\n"
" '-'被認為是搜索不包含緊接其後的單詞(或短語)的食譜。\n"
" 例如,搜索'apple pie'或'cherry -butter'將返回在全文搜索中包含短"
"語'apple pie'或單詞'cherry'的任何食譜,\n"
" 但排除在任何字段中包含單詞'butter'的任何食譜。\n"
" "
#: .\cookbook\templates\search_info.html:48
msgid ""
" \n"
" Raw search is similar to Web except will take puncuation "
"operators such as '|', '&' and '()'\n"
" "
msgstr ""
" \n"
" 原始搜索類似於網絡搜索,但會接受標點符號運算符,"
"如'|'、'&'和'()'\n"
" "
#: .\cookbook\templates\search_info.html:59
msgid ""
" \n"
" Another approach to searching that also requires Postgresql is "
"fuzzy search or trigram similarity. A trigram is a group of three "
"consecutive characters.\n"
" For example searching for 'apple' will create x trigrams 'app', "
"'ppl', 'ple' and will create a score of how closely words match the "
"generated trigrams.\n"
" One benefit of searching trigams is that a search for 'sandwich' "
"will find misspelled words such as 'sandwhich' that would be missed by other "
"methods.\n"
" "
msgstr ""
" \n"
" 另一種搜索方法也需要Postgresql,即模糊搜索或三元組相似性。三元組"
"是一組三個連續的字符。\n"
" 例如,搜索'apple'將創建三元組'app'、'ppl'、'ple',並創建一個分數"
"來衡量單詞與生成的三元組的匹配程度。\n"
" 搜索三元組的一個好處是,搜索'sandwich'將找到拼寫錯誤的單詞,"
"如'sandwhich',這些單詞將被其他方法忽略。\n"
" "
#: .\cookbook\templates\search_info.html:69
msgid "Search Fields"
msgstr "搜索字段"
#: .\cookbook\templates\search_info.html:73
msgid ""
" \n"
" Unaccent is a special case in that it enables searching a field "
"'unaccented' for each search style attempting to ignore accented values. \n"
" For example when you enable unaccent for 'Name' any search "
"(starts with, contains, trigram) will attempt the search ignoring accented "
"characters.\n"
" \n"
" For the other options, you can enable search on any or all "
"fields and they will be combined together with an assumed 'OR'.\n"
" For example enabling 'Name' for Starts With, 'Name' and "
"'Description' for Partial Match and 'Ingredients' and 'Keywords' for Full "
"Search\n"
" and searching for 'apple' will generate a search that will "
"return recipes that have:\n"
" - A recipe name that starts with 'apple'\n"
" - OR a recipe name that contains 'apple'\n"
" - OR a recipe description that contains 'apple'\n"
" - OR a recipe that will have a full text search match ('apple' "
"or 'apples') in ingredients\n"
" - OR a recipe that will have a full text search match in "
"Keywords\n"
"\n"
" Combining too many fields in too many types of search can have a "
"negative impact on performance, create duplicate results or return "
"unexpected results.\n"
" For example, enabling fuzzy search or partial matches will "
"interfere with web search methods. \n"
" Searching for 'apple -pie' with fuzzy search and full text "
"search will return the recipe Apple Pie. Though it is not included in the "
"full text results, it does match the trigram results.\n"
" "
msgstr ""
" \n"
" Unaccent是一種特殊情況,它使每種搜索樣式都能在搜索字段時忽略重音"
"值。\n"
" 例如,當你為'Name'啟用Unaccent時,任何搜索(以...開始、包含、三元"
"組)都會嘗試忽略重音字符進行搜索。\n"
" \n"
" 對於其他選項,你可以啟用任何或所有字段的搜索,它們將與假設"
"的'OR'一起組合。\n"
" 例如,啟用'Name'的以...開始,啟用'Name'和'Description'的部分匹"
"配,啟用'Ingredients'和'Keywords'的全文搜索\n"
" 並搜索'apple'將生成一個搜索,返回包含以下內容的食譜:\n"
" - 以'apple'開頭的食譜名稱\n"
" - 或包含'apple'的食譜名稱\n"
" - 或包含'apple'的食譜描述\n"
" - 或在成分中進行全文搜索匹配('apple'或'apples')的食譜\n"
" - 或在關鍵詞中進行全文搜索匹配的食譜\n"
"\n"
" 在太多類型的搜索中組合太多字段會對性能產生負面影響,創建重複結果"
"或返回意外結果。\n"
" 例如,啟用模糊搜索或部分匹配會干擾網絡搜索方法。\n"
" 使用模糊搜索和全文搜索搜索'apple -pie'將返回食譜Apple Pie。雖然它"
"不包含在全文結果中,但它確實匹配三元組結果。\n"
" "
#: .\cookbook\templates\search_info.html:95
msgid "Search Index"
msgstr "搜索索引"
#: .\cookbook\templates\search_info.html:99
msgid ""
" \n"
" Trigram search and Full Text Search both rely on database "
"indexes to perform effectively. \n"
" You can rebuild the indexes on all fields in the Admin page for "
"Recipes and selecting all recipes and running 'rebuild index for selected "
"recipes'\n"
" You can also rebuild indexes at the command line by executing "
"the management command 'python manage.py rebuildindex'\n"
" "
msgstr ""
" \n"
" 三元組搜索和全文搜索都依賴於數據庫索引才能有效執行。\n"
" 你可以在食譜的管理頁面上重建所有字段的索引,選擇所有食譜並運行'為"
"選定的食譜重建索引'\n"
" 你也可以通過執行管理命令'python manage.py rebuildindex'來重建索"
"引\n"
" "
#: .\cookbook\templates\setup.html:6
msgid "Cookbook Setup"
msgstr "食譜設置"
#: .\cookbook\templates\setup.html:14
msgid "Setup"
msgstr "設置"
#: .\cookbook\templates\setup.html:15
msgid ""
"To start using this application you must first create a superuser account."
msgstr "要開始使用此應用程序,你必須首先創建一個超級用戶賬戶。"
#: .\cookbook\templates\setup.html:20
msgid "Create Superuser account"
msgstr "創建超級用戶賬戶"
#: .\cookbook\templates\socialaccount\authentication_error.html:7
#: .\cookbook\templates\socialaccount\authentication_error.html:23
msgid "Social Network Login Failure"
msgstr "社交網絡登錄失敗"
#: .\cookbook\templates\socialaccount\authentication_error.html:25
msgid ""
"An error occurred while attempting to login via your social network account."
msgstr "嘗試通過你的社交網絡賬戶登錄時發生錯誤。"
#: .\cookbook\templates\socialaccount\connections.html:4
#: .\cookbook\templates\socialaccount\connections.html:7
msgid "Account Connections"
msgstr "賬戶連接"
#: .\cookbook\templates\socialaccount\connections.html:10
msgid ""
"You can sign in to your account using any of the following third party\n"
" accounts:"
msgstr ""
"你可以使用以下任何第三方登錄您的帳戶\n"
" 賬戶:"
#: .\cookbook\templates\socialaccount\connections.html:44
msgid ""
"You currently have no social network accounts connected to this account."
msgstr "你目前沒有任何社交網絡賬戶連接到此賬戶。"
#: .\cookbook\templates\socialaccount\connections.html:47
msgid "Add a 3rd Party Account"
msgstr "添加第三方賬戶"
#: .\cookbook\templates\socialaccount\login.html:5
#: .\cookbook\templates\socialaccount\signup.html:5
msgid "Signup"
msgstr "註冊"
#: .\cookbook\templates\socialaccount\login.html:9
#, python-format
msgid "Connect %(provider)s"
msgstr "連接%(provider)s"
#: .\cookbook\templates\socialaccount\login.html:11
#, python-format
msgid "You are about to connect a new third party account from %(provider)s."
msgstr "你即將連接一個來自%(provider)s的新第三方賬戶。"
#: .\cookbook\templates\socialaccount\login.html:13
#, python-format
msgid "Sign In Via %(provider)s"
msgstr "通過%(provider)s登錄"
#: .\cookbook\templates\socialaccount\login.html:15
#, python-format
msgid "You are about to sign in using a third party account from %(provider)s."
msgstr "你即將使用來自%(provider)s的第三方賬戶登錄。"
#: .\cookbook\templates\socialaccount\login.html:20
msgid "Continue"
msgstr "繼續"
#: .\cookbook\templates\socialaccount\signup.html:10
#, python-format
msgid ""
"You are about to use your\n"
" %(provider_name)s account to login to\n"
" %(site_name)s. As a final step, please complete the following form:"
msgstr ""
"你即將使用你的\n"
" %(provider_name)s賬戶登錄到\n"
" %(site_name)s。作為最後一步,請完成以下表格:"
#: .\cookbook\templates\socialaccount\signup.html:32
#, fuzzy
#| msgid "I accept the follwoing"
msgid "I accept the following"
msgstr "我接受以下"
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:23
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:31
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:39
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:47
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:55
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:63
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:71
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:79
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:87
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:95
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:103
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:111
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:119
#: .\cookbook\templates\socialaccount\snippets\provider_list.html:127
msgid "Sign in using"
msgstr "使用以下方式登錄"
#: .\cookbook\templates\space_overview.html:6
msgid "Overview"
msgstr "概覽"
#: .\cookbook\templates\space_overview.html:13
msgid "Space"
msgstr "空間"
#: .\cookbook\templates\space_overview.html:17
msgid ""
"Recipes, foods, shopping lists and more are organized in spaces of one or "
"more people."
msgstr "食譜、食物、購物清單等由一個或多個人的空間組織。"
#: .\cookbook\templates\space_overview.html:18
msgid ""
"You can either be invited into an existing space or create your own one."
msgstr "你可以被邀請加入現有空間或創建自己的空間。"
#: .\cookbook\templates\space_overview.html:25
msgid "Your Spaces"
msgstr "你的空間"
#: .\cookbook\templates\space_overview.html:53
msgid "Owner"
msgstr "擁有者"
#: .\cookbook\templates\space_overview.html:73
#: .\cookbook\templates\space_overview.html:83
msgid "Join Space"
msgstr "加入空間"
#: .\cookbook\templates\space_overview.html:76
msgid "Join an existing space."
msgstr "加入現有空間。"
#: .\cookbook\templates\space_overview.html:78
msgid ""
"To join an existing space either enter your invite token or click on the "
"invite link the space owner send you."
msgstr "要加入現有空間,請輸入你的邀請令牌或點擊空間所有者發送給你的邀請鏈接。"
#: .\cookbook\templates\space_overview.html:91
#: .\cookbook\templates\space_overview.html:100
msgid "Create Space"
msgstr "創建空間"
#: .\cookbook\templates\space_overview.html:94
msgid "Create your own recipe space."
msgstr "創建你自己的食譜空間。"
#: .\cookbook\templates\space_overview.html:96
msgid "Start your own recipe space and invite other users to it."
msgstr "創建你自己的食譜空間並邀請其他用戶加入。"
#: .\cookbook\templates\system.html:23
msgid "System"
msgstr "系統"
#: .\cookbook\templates\system.html:24
#, fuzzy
#| msgid ""
#| "\n"
#| " Django Recipes is an open source free software application. It "
#| "can be found on\n"
#| " GitHub.\n"
#| " Changelogs can be found here.\n"
#| " "
msgid ""
"\n"
" Tandoor Recipes is an open source free software application. It can "
"be found on\n"
" GitHub.\n"
" Changelogs can be found here.\n"
" "
msgstr ""
"\n"
" Django Recipes是一個開源免費軟件應用程序。可以在\n"
" GitHub上找"
"到。\n"
" 變更日誌可以在這裡找到。\n"
" "
#: .\cookbook\templates\system.html:30
msgid "System Information"
msgstr "系統信息"
#: .\cookbook\templates\system.html:51
msgid ""
"\n"
" You need to execute version.py in your update "
"script to generate version information (done automatically in docker).\n"
" "
msgstr ""
"\n"
" 你需要在更新腳本中執行version.py來生成版本信息(在"
"docker中自動完成)。\n"
" "
#: .\cookbook\templates\system.html:56
msgid "Plugins"
msgstr ""
#: .\cookbook\templates\system.html:67
msgid "Media Serving"
msgstr "媒體服務"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:123 .\cookbook\templates\system.html:134
msgid "Warning"
msgstr "警告"
#: .\cookbook\templates\system.html:68 .\cookbook\templates\system.html:82
#: .\cookbook\templates\system.html:96 .\cookbook\templates\system.html:109
#: .\cookbook\templates\system.html:125 .\cookbook\templates\system.html:134
msgid "Ok"
msgstr "好的"
#: .\cookbook\templates\system.html:70
msgid ""
"Serving media files directly using gunicorn/python is not recommend!\n"
" Please follow the steps described\n"
" here to update\n"
" your installation.\n"
" "
msgstr ""
"不推薦 使用 gunicorn/python 提供媒體文件!\n"
" 請按照\n"
" 這裡 to update\n"
" 描述的步驟操作更新安裝。\n"
" "
#: .\cookbook\templates\system.html:76 .\cookbook\templates\system.html:91
#: .\cookbook\templates\system.html:104 .\cookbook\templates\system.html:115
#: .\cookbook\views\views.py:168
msgid "Everything is fine!"
msgstr "一切正常!"
#: .\cookbook\templates\system.html:80
msgid "Secret Key"
msgstr "密鑰"
#: .\cookbook\templates\system.html:84
msgid ""
"\n"
" You do not have a SECRET_KEY configured in your "
".env file. Django defaulted to the\n"
" standard key\n"
" provided with the installation which is publicly know and "
"insecure! Please set\n"
" SECRET_KEY int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" 你沒有在.env文件中配置SECRET_KEY。"
"Django默認使用\n"
" 安裝時提供的標準密鑰,這是公開且不安全的!\n"
" 請在.env配置文件中設置\n"
" SECRET_KEY。\n"
" "
#: .\cookbook\templates\system.html:94
msgid "Debug Mode"
msgstr "調試模式"
#: .\cookbook\templates\system.html:98
msgid ""
"\n"
" This application is still running in debug mode. This is most "
"likely not needed. Turn of debug mode by\n"
" setting\n"
" DEBUG=0 int the .env configuration "
"file.\n"
" "
msgstr ""
"\n"
" 此應用程序仍在調試模式下運行。這很可能是不需要的。通過\n"
" 設置\n"
" .env配置文件中的DEBUG=0來關閉調試模"
"式。\n"
" "
#: .\cookbook\templates\system.html:107
msgid "Allowed Hosts"
msgstr "允許的主機"
#: .\cookbook\templates\system.html:111
msgid ""
"\n"
" Your allowed hosts are configured to allow every host. This "
"might be ok in some setups but should be avoided. Please see the docs about "
"this.\n"
" "
msgstr ""
"\n"
" 你的允許主機配置為允許所有主機。在某些設置中這可能是可以的,但應"
"該避免。請參閱有關此的文檔。\n"
" "
#: .\cookbook\templates\system.html:118
msgid "Database"
msgstr "數據庫"
#: .\cookbook\templates\system.html:121
msgid "Info"
msgstr "信息"
#: .\cookbook\templates\system.html:131 .\cookbook\templates\system.html:148
msgid "Migrations"
msgstr "遷移"
#: .\cookbook\templates\system.html:137
msgid ""
"\n"
" Migrations should never fail!\n"
" Failed migrations will likely cause major parts of the app to "
"not function correctly.\n"
" If a migration fails make sure you are on the latest version and "
"if so please post the migration log and the overview below in a GitHub "
"issue.\n"
" "
msgstr ""
"\n"
" 遷移不應該失敗!\n"
" 遷移失敗可能會導致應用程序的主要部分無法正常運行。\n"
" 如果遷移失敗,請確保你使用的是最新版本,如果是,請在GitHub問題中"
"發布遷移日誌和下面的概述。\n"
" "
#: .\cookbook\templates\system.html:238
msgid "False"
msgstr "錯誤"
#: .\cookbook\templates\system.html:238
msgid "True"
msgstr "真"
#: .\cookbook\templates\system.html:268
msgid "Hide"
msgstr "隱藏"
#: .\cookbook\templates\system.html:271
msgid "Show"
msgstr "顯示"
#: .\cookbook\templates\test2.html:6
msgid "Export Recipes"
msgstr "導出食譜"
#: .\cookbook\templates\test2.html:14 .\cookbook\templates\test2.html:20
msgid "Export"
msgstr "導出"
#: .\cookbook\views\api.py:198 .\cookbook\views\api.py:326
msgid "Parameter updated_at incorrectly formatted"
msgstr "參數updated_at格式不正確"
#: .\cookbook\views\api.py:351 .\cookbook\views\api.py:484
#, python-brace-format
msgid "No {self.basename} with id {pk} exists"
msgstr "不存在id為{pk}的{self.basename}"
#: .\cookbook\views\api.py:355
msgid "Cannot merge with the same object!"
msgstr "不能與相同的對象合併!"
#: .\cookbook\views\api.py:362
#, python-brace-format
msgid "No {self.basename} with id {target} exists"
msgstr "不存在id為{target}的{self.basename}"
#: .\cookbook\views\api.py:367
msgid "Cannot merge with child object!"
msgstr "不能與子對象合併!"
#: .\cookbook\views\api.py:405
#, python-brace-format
msgid "{source.name} was merged successfully with {target.name}"
msgstr "{source.name}已成功與{target.name}合併"
#: .\cookbook\views\api.py:411
#, python-brace-format
msgid "An error occurred attempting to merge {source.name} with {target.name}"
msgstr "嘗試將{source.name}與{target.name}合併時發生錯誤"
#: .\cookbook\views\api.py:493
#, python-brace-format
msgid "{child.name} was moved successfully to the root."
msgstr "{child.name}已成功移動到根目錄。"
#: .\cookbook\views\api.py:496 .\cookbook\views\api.py:514
msgid "An error occurred attempting to move "
msgstr "嘗試移動時發生錯誤 "
#: .\cookbook\views\api.py:499
msgid "Cannot move an object to itself!"
msgstr "不能將對象移動到自身!"
#: .\cookbook\views\api.py:505
#, python-brace-format
msgid "No {self.basename} with id {parent} exists"
msgstr "不存在id為{parent}的{self.basename}"
#: .\cookbook\views\api.py:511
#, python-brace-format
msgid "{child.name} was moved successfully to parent {parent.name}"
msgstr "{child.name}已成功移動到父級{parent.name}"
#: .\cookbook\views\api.py:992
#, python-brace-format
msgid "{obj.name} was removed from the shopping list."
msgstr "{obj.name}已從購物清單中移除。"
#: .\cookbook\views\api.py:997 .\cookbook\views\api.py:1627
#, python-brace-format
msgid "{obj.name} was added to the shopping list."
msgstr "{obj.name}已添加到購物清單中。"
#: .\cookbook\views\api.py:1239
#, fuzzy
#| msgid "Filter meal plans from date (inclusive) in the format of YYYY-MM-DD."
msgid "Filter meal plans from date (inclusive)."
msgstr "按日期篩選膳食計劃(包括),格式為YYYY-MM-DD。"
#: .\cookbook\views\api.py:1241
#, fuzzy
#| msgid "Filter meal plans to date (inclusive) in the format of YYYY-MM-DD."
msgid "Filter meal plans to date (inclusive)."
msgstr "按日期篩選膳食計劃(包括),格式為YYYY-MM-DD。"
#: .\cookbook\views\api.py:1244
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
msgstr "按MealType ID篩選膳食計劃。對於多個重複參數。"
#: .\cookbook\views\api.py:1404
msgid "ID of recipe a step is part of. For multiple repeat parameter."
msgstr "步驟所屬的食譜ID。對於多個重複參數。"
#: .\cookbook\views\api.py:1406
msgid "Query string matched (fuzzy) against object name."
msgstr "查詢字符串與對象名稱模糊匹配。"
#: .\cookbook\views\api.py:1442
msgid ""
"Query string matched (fuzzy) against recipe name. In the future also "
"fulltext search."
msgstr "查詢字符串與食譜名稱模糊匹配。未來還將進行全文搜索。"
#: .\cookbook\views\api.py:1444
msgid ""
"ID of keyword a recipe should have. For multiple repeat parameter. "
"Equivalent to keywords_or"
msgstr "食譜應具有的關鍵詞ID。對於多個重複參數。相當於keywords_or"
#: .\cookbook\views\api.py:1445
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with any of the keywords"
msgstr "關鍵詞ID,多次重複。返回具有任何關鍵詞的食譜"
#: .\cookbook\views\api.py:1446
msgid ""
"Keyword IDs, repeat for multiple. Return recipes with all of the keywords."
msgstr "關鍵詞ID,多次重複。返回具有所有關鍵詞的食譜。"
#: .\cookbook\views\api.py:1447
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords."
msgstr "關鍵詞ID,多次重複。排除具有任何關鍵詞的食譜。"
#: .\cookbook\views\api.py:1448
msgid ""
"Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords."
msgstr "關鍵詞ID,多次重複。排除具有所有關鍵詞的食譜。"
#: .\cookbook\views\api.py:1450
msgid "ID of food a recipe should have. For multiple repeat parameter."
msgstr "食譜應具有的食物ID。對於多個重複參數。"
#: .\cookbook\views\api.py:1451
msgid "Food IDs, repeat for multiple. Return recipes with any of the foods"
msgstr "食物ID,多次重複。返回具有任何食物的食譜"
#: .\cookbook\views\api.py:1452
msgid "Food IDs, repeat for multiple. Return recipes with all of the foods."
msgstr "食物ID,多次重複。返回具有所有食物的食譜。"
#: .\cookbook\views\api.py:1453
msgid "Food IDs, repeat for multiple. Exclude recipes with any of the foods."
msgstr "食物ID,多次重複。排除具有任何食物的食譜。"
#: .\cookbook\views\api.py:1454
msgid "Food IDs, repeat for multiple. Exclude recipes with all of the foods."
msgstr "食物ID,多次重複。排除具有所有食物的食譜。"
#: .\cookbook\views\api.py:1456
msgid "ID of book a recipe should be in. For multiple repeat parameter."
msgstr "食譜應在的書籍ID。對於多個重複參數。"
#: .\cookbook\views\api.py:1457
msgid "Book IDs, repeat for multiple. Return recipes with any of the books"
msgstr "書籍ID,多次重複。返回具有任何書籍的食譜"
#: .\cookbook\views\api.py:1458
msgid "Book IDs, repeat for multiple. Return recipes with all of the books."
msgstr "書籍ID,多次重複。返回具有所有書籍的食譜。"
#: .\cookbook\views\api.py:1459
msgid "Book IDs, repeat for multiple. Exclude recipes with any of the books."
msgstr "書籍ID,多次重複。排除具有任何書籍的食譜。"
#: .\cookbook\views\api.py:1460
msgid "Book IDs, repeat for multiple. Exclude recipes with all of the books."
msgstr "書籍ID,多次重複。排除具有所有書籍的食譜。"
#: .\cookbook\views\api.py:1462
msgid "ID of unit a recipe should have."
msgstr "食譜應具有的單位ID。"
#: .\cookbook\views\api.py:1464
msgid "Exact rating of recipe"
msgstr ""
#: .\cookbook\views\api.py:1465
#, fuzzy
#| msgid "ID of unit a recipe should have."
msgid "Rating a recipe should have or greater."
msgstr "食譜應具有的單位ID。"
#: .\cookbook\views\api.py:1466
#, fuzzy
#| msgid "ID of unit a recipe should have."
msgid "Rating a recipe should have or smaller."
msgstr "食譜應具有的單位ID。"
#: .\cookbook\views\api.py:1468
msgid "Filter recipes cooked X times."
msgstr ""
#: .\cookbook\views\api.py:1469
#, fuzzy
#| msgid ""
#| "Filter recipes cooked X times or more. Negative values returns cooked "
#| "less than X times"
msgid "Filter recipes cooked X times or more."
msgstr "篩選烹飪X次或更多次的食譜。負值返回烹飪次數少於X次的食譜"
#: .\cookbook\views\api.py:1470
msgid "Filter recipes cooked X times or less."
msgstr ""
#: .\cookbook\views\api.py:1472
#, fuzzy
#| msgid "Filter for entries with the given recipe"
msgid "Filter recipes created on the given date."
msgstr "篩選具有給定食譜的條目"
#: .\cookbook\views\api.py:1473
#, fuzzy
#| msgid ""
#| "Filter recipes created on or after YYYY-MM-DD. Prepending - filters on or "
#| "before date."
msgid "Filter recipes created on the given date or after."
msgstr "篩選創建日期在YYYY-MM-DD或之後的食譜。前置-篩選在日期或之前的食譜。"
#: .\cookbook\views\api.py:1474
#, fuzzy
#| msgid ""
#| "Filter recipes created on or after YYYY-MM-DD. Prepending - filters on or "
#| "before date."
msgid "Filter recipes created on the given date or before."
msgstr "篩選創建日期在YYYY-MM-DD或之後的食譜。前置-篩選在日期或之前的食譜。"
#: .\cookbook\views\api.py:1476 .\cookbook\views\api.py:1477
#: .\cookbook\views\api.py:1478
#, fuzzy
#| msgid "Filter for entries with the given recipe"
msgid "Filter recipes updated on the given date."
msgstr "篩選具有給定食譜的條目"
#: .\cookbook\views\api.py:1480
#, fuzzy
#| msgid ""
#| "Filter recipes last cooked on or after YYYY-MM-DD. Prepending - filters "
#| "on or before date."
msgid "Filter recipes last cooked on the given date or after."
msgstr ""
"篩選最後烹飪日期在YYYY-MM-DD或之後的食譜。前置-篩選在日期或之前的食譜。"
#: .\cookbook\views\api.py:1481
#, fuzzy
#| msgid ""
#| "Filter recipes last cooked on or after YYYY-MM-DD. Prepending - filters "
#| "on or before date."
msgid "Filter recipes last cooked on the given date or before."
msgstr ""
"篩選最後烹飪日期在YYYY-MM-DD或之後的食譜。前置-篩選在日期或之前的食譜。"
#: .\cookbook\views\api.py:1483 .\cookbook\views\api.py:1484
#, fuzzy
#| msgid ""
#| "Filter recipes lasts viewed on or after YYYY-MM-DD. Prepending - filters "
#| "on or before date."
msgid "Filter recipes lasts viewed on the given date."
msgstr ""
"篩選最後查看日期在YYYY-MM-DD或之後的食譜。前置-篩選在日期或之前的食譜。"
#: .\cookbook\views\api.py:1486
#, fuzzy
#| msgid "Filter for entries with the given recipe"
msgid "Filter recipes for ones created by the given user ID"
msgstr "篩選具有給定食譜的條目"
#: .\cookbook\views\api.py:1487
msgid "If only internal recipes should be returned. [true/false]"
msgstr "是否僅返回內部食譜。[true/false]"
#: .\cookbook\views\api.py:1488
msgid "Returns the results in randomized order. [true/false]"
msgstr "以隨機順序返回結果。[true/false]"
#: .\cookbook\views\api.py:1490
msgid ""
"Determines the order of the results. Options are: score,-score,name,-name,"
"lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-"
"created_at,lastviewed,-lastviewed"
msgstr ""
#: .\cookbook\views\api.py:1492
msgid "Returns new results first in search results. [true/false]"
msgstr "在搜索結果中首先返回新結果。[true/false]"
#: .\cookbook\views\api.py:1493
msgid ""
"Returns the given number of recently viewed recipes before search results "
"(if given)"
msgstr ""
#: .\cookbook\views\api.py:1494
msgid "ID of a custom filter. Returns all recipes matched by that filter."
msgstr ""
#: .\cookbook\views\api.py:1495
msgid "Filter recipes that can be made with OnHand food. [true/false]"
msgstr "篩選可以用現有食物製作的食譜。[true/false]"
#: .\cookbook\views\api.py:1773
msgid ""
"Return the PropertyTypes matching the property category. Repeat for "
"multiple."
msgstr ""
#: .\cookbook\views\api.py:1804 .\cookbook\views\api.py:1860
#, fuzzy
#| msgid "Filter for entries with the given recipe"
msgid "Returns only entries associated with the given mealplan id"
msgstr "篩選具有給定食譜的條目"
#: .\cookbook\views\api.py:1858
msgid ""
"Returns only elements updated after the given timestamp in ISO 8601 format."
msgstr ""
#: .\cookbook\views\api.py:2031
#, fuzzy
#| msgid ""
#| "Return the Automations matching the automation type. Multiple values "
#| "allowed."
msgid ""
"Return the Automations matching the automation type. Repeat for multiple."
msgstr "返回與自動化類型匹配的自動化。允許多個值。"
#: .\cookbook\views\api.py:2048
msgid ""
"Text field to store data that gets carried over to the UserSpace created "
"from the InviteLink"
msgstr ""
#: .\cookbook\views\api.py:2049
msgid "Only return InviteLinks that have not been used yet."
msgstr ""
#: .\cookbook\views\api.py:2076
#, fuzzy
#| msgid ""
#| "Return the Automations matching the automation type. Multiple values "
#| "allowed."
msgid "Return the CustomFilters matching the model type. Repeat for multiple."
msgstr "返回與自動化類型匹配的自動化。允許多個值。"
#: .\cookbook\views\api.py:2176
msgid "Nothing to do."
msgstr "無事可做。"
#: .\cookbook\views\api.py:2222
msgid "Invalid Url"
msgstr "無效的URL"
#: .\cookbook\views\api.py:2228
msgid "Connection Refused."
msgstr "連接被拒絕。"
#: .\cookbook\views\api.py:2232
msgid "Bad URL Schema."
msgstr "錯誤的URL架構。"
#: .\cookbook\views\api.py:2257
msgid "No usable data could be found."
msgstr "找不到可用的數據。"
#: .\cookbook\views\api.py:2286 .\cookbook\views\api.py:2434
msgid "You must select an AI provider to perform your request."
msgstr ""
#: .\cookbook\views\api.py:2293 .\cookbook\views\api.py:2441
msgid ""
"You don't have any credits remaining to use AI or AI features are not "
"enabled for your space."
msgstr ""
#: .\cookbook\views\api.py:2499 .\cookbook\views\api.py:2667
msgid "File is above space limit"
msgstr "文件超過空間限制"
#: .\cookbook\views\api.py:2522 .\cookbook\views\api.py:2684
msgid "Importing is not implemented for this provider"
msgstr "此提供程序未實現導入"
#: .\cookbook\views\api.py:2548
msgid ""
"The PDF Exporter is not enabled on this instance as it is still in an "
"experimental state."
msgstr "此實例未啟用PDF導出器,因為它仍處於實驗階段。"
#: .\cookbook\views\api.py:2842
msgid "This feature is not yet available in the hosted version of tandoor!"
msgstr "此功能在 Tandoor 的托管版本中尚不可用!"
#: .\cookbook\views\api.py:2863
msgid "Sync successful!"
msgstr "同步成功!"
#: .\cookbook\views\api.py:2866
msgid "Error synchronizing with Storage"
msgstr "與儲存同步時出錯"
#: .\cookbook\views\views.py:89
msgid "This feature is not available in the demo version!"
msgstr "此功能在演示版本中不可用!"
#: .\cookbook\views\views.py:110
msgid ""
"You have successfully created your own recipe space. Start by adding some "
"recipes or invite other people to join you."
msgstr "你已成功創建自己的食譜空間。開始添加一些食譜或邀請其他人加入你。"
#: .\cookbook\views\views.py:171
#, python-format
msgid "PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!"
msgstr "PostgreSQL %(v)s已被棄用。升級到完全支持的版本!"
#: .\cookbook\views\views.py:174
#, python-format
msgid "You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended"
msgstr "你正在運行PostgreSQL %(v1)s。建議使用PostgreSQL %(v2)s"
#: .\cookbook\views\views.py:178
msgid "Unable to determine PostgreSQL version."
msgstr "無法確定PostgreSQL版本。"
#: .\cookbook\views\views.py:182
msgid ""
"This application is not running with a Postgres database backend. This is ok "
"but not recommended as some features only work with postgres databases."
msgstr ""
"此應用程序未使用Postgres數據庫後端運行。這是可以的,但不推薦,因為某些功能僅"
"適用於Postgres數據庫。"
#: .\cookbook\views\views.py:296
msgid ""
"The setup page can only be used to create the first "
"user! If you have forgotten your superuser credentials "
"please consult the django documentation on how to reset passwords."
msgstr ""
"設置頁面只能用於創建第一個用戶!如果你忘記了超級用戶憑據,請參閱django文檔了"
"解如何重置密碼。"
#: .\cookbook\views\views.py:304
msgid "Passwords dont match!"
msgstr "密碼不匹配!"
#: .\cookbook\views\views.py:312
msgid "User has been created, please login!"
msgstr "用戶已創建,請登錄!"
#: .\cookbook\views\views.py:352
msgid ""
"Reporting share links is not enabled for this instance. Please notify the "
"page administrator to report problems."
msgstr "此實例未啟用報告共享鏈接。請通知頁面管理員報告問題。"
#: .\cookbook\views\views.py:357
msgid ""
"Recipe sharing link has been disabled! For additional information please "
"contact the page administrator."
msgstr "食譜共享鏈接已被禁用!如需更多信息,請聯繫頁面管理員。"
#: .\cookbook\views\views.py:383
msgid "Manage recipes, shopping list, meal plans and more."
msgstr "管理食譜、購物清單、膳食計劃等。"
#: .\cookbook\views\views.py:397 .\cookbook\views\views.py:398
msgid "Plan"
msgstr "計劃"
#: .\cookbook\views\views.py:399
msgid "View your meal Plan"
msgstr "查看你的膳食計劃"
#: .\cookbook\views\views.py:418
msgid "View your shopping lists"
msgstr "查看你的購物清單"
#~ msgid ""
#~ "Both fields are optional. If none are given the username will be "
#~ "displayed instead"
#~ msgstr "這兩個欄位都是可選的。如果沒有輸入,將顯示用戶名"
#~ msgid "Name"
#~ msgstr "名字"
#~ msgid "Keywords"
#~ msgstr "關鍵字"
#~ msgid "Preparation time in minutes"
#~ msgstr "準備時間(分鐘)"
#~ msgid "Waiting time (cooking/baking) in minutes"
#~ msgstr "等候(烹飪、烘焙等)時間(分鐘)"
#~ msgid "Path"
#~ msgstr "路徑"
#~ msgid "Storage UID"
#~ msgstr "存儲ID"
#~ msgid "Add your comment: "
#~ msgstr "發表評論。 "
#~ msgid "Leave empty for dropbox and enter app password for nextcloud."
#~ msgstr "Dropbox 留空並輸入 Nextcloud 應用密碼。"
#~ msgid "Leave empty for nextcloud and enter api token for dropbox."
#~ msgstr "Nextcloud 留空並輸入 Dropbox API 令牌。"
#~ msgid ""
#~ "Leave empty for dropbox and enter only base url for nextcloud (/"
#~ "remote.php/webdav/ is added automatically)"
#~ msgstr ""
#~ "Dropbox 留空並輸入基礎 Nextcloud 網址(/remote.php/webdav/ 會"
#~ "自動添加)"
#~ msgid ""
#~ "Long Lived Access Token for your HomeAssistant instance"
#~ msgstr ""
#~ "您的HomeAssistant示例的長期訪問令牌"
#~ msgid "Something like http://homeassistant.local:8123/api"
#~ msgstr "形如 http://homeassistant.local:8123/api"
#~ msgid "http://homeassistant.local:8123/api for example"
#~ msgstr "例如 http://homeassistant.local:8123/api"
#~ msgid "Storage"
#~ msgstr "儲存"
#~ msgid "Active"
#~ msgstr "活躍"
#~ msgid "Search String"
#~ msgstr "搜索字符串"
#~ msgid "File ID"
#~ msgstr "文件編號"
#~ msgid ""
#~ "Determines how fuzzy a search is if it uses trigram similarity matching "
#~ "(e.g. low values mean more typos are ignored)."
#~ msgstr ""
#~ "決定使用三元組相似度匹配時搜索的模糊程度(例如,低值意味著更多的拼寫錯誤被"
#~ "忽略)。"
#~ msgid ""
#~ "Select type method of search. Click here "
#~ "for full description of choices."
#~ msgstr ""
#~ "選擇搜索類型方法。點擊這裡查看選項的完整描"
#~ "述。"
#~ msgid ""
#~ "Use fuzzy matching on units, keywords and ingredients when editing and "
#~ "importing recipes."
#~ msgstr "在編輯和導入食譜時對單位、關鍵詞和成分使用模糊匹配。"
#~ msgid ""
#~ "Fields to search ignoring accents. Selecting this option can improve or "
#~ "degrade search quality depending on language"
#~ msgstr "忽略搜索字段的重音。此選項會因語言差異導致搜索質量產生變化"
#~ msgid ""
#~ "Fields to search for partial matches. (e.g. searching for 'Pie' will "
#~ "return 'pie' and 'piece' and 'soapie')"
#~ msgstr ""
#~ "搜索部分匹配的字段。(例如,搜索'Pie'將返回'pie'、'piece'和'soapie')"
#~ msgid ""
#~ "Fields to search for beginning of word matches. (e.g. searching for 'sa' "
#~ "will return 'salad' and 'sandwich')"
#~ msgstr "搜索詞首匹配的字段。(例如,搜索'sa'將返回'salad'和'sandwich')"
#~ msgid ""
#~ "Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find "
#~ "'recipe'.) Note: this option will conflict with 'web' and 'raw' methods "
#~ "of search."
#~ msgstr ""
#~ "模糊搜索的字段。(例如,搜索'recpie'將找到'recipe'。)注意:此選項將"
#~ "與'web'和'raw'搜索方法衝突。"
#~ msgid ""
#~ "Fields to full text search. Note: 'web', 'phrase', and 'raw' search "
#~ "methods only function with fulltext fields."
#~ msgstr ""
#~ "全文搜索的字段。注意:'web'、'phrase'和'raw'搜索方法僅適用於全文字段。"
#~ msgid "Search Method"
#~ msgstr "搜索方法"
#~ msgid "Fuzzy Lookups"
#~ msgstr "模糊查找"
#~ msgid "Ignore Accent"
#~ msgstr "忽略重音"
#~ msgid "Partial Match"
#~ msgstr "部分匹配"
#~ msgid "Starts With"
#~ msgstr "以...開始"
#~ msgid "Fuzzy Search"
#~ msgstr "模糊搜索"
#~ msgid "Full Text"
#~ msgstr "全文"
#~ msgid " is part of a recipe step and cannot be deleted"
#~ msgstr " 是食譜步驟的一部分,無法刪除"
#~ msgid "Delete"
#~ msgstr "刪除"
#~ msgid "Settings"
#~ msgstr "設定"
#~ msgid "Email"
#~ msgstr "電子郵件"
#~ msgid "Password"
#~ msgstr "密碼"
#~ msgid "Foods"
#~ msgstr "食物"
#~ msgid "Units"
#~ msgstr "單位"
#~ msgid "Supermarket"
#~ msgstr "超市"
#~ msgid "Supermarket Category"
#~ msgstr "超市分類"
#~ msgid "Automations"
#~ msgstr "自動化"
#~ msgid "Files"
#~ msgstr "文件"
#~ msgid "Batch Edit"
#~ msgstr "批量編輯"
#~ msgid "History"
#~ msgstr "歷史"
#~ msgid "Ingredient Editor"
#~ msgstr "食材編輯器"
#~ msgid "Properties"
#~ msgstr "屬性"
#~ msgid "Unit Conversions"
#~ msgstr "單位換算"
#~ msgid "Create"
#~ msgstr "創建"
#~ msgid "External Recipes"
#~ msgstr "外部食譜"
#~ msgid "Space Settings"
#~ msgstr "空間設置"
#~ msgid "External Connectors"
#~ msgstr "外部連接器"
#~ msgid "Admin"
#~ msgstr "管理員"
#~ msgid "Markdown Guide"
#~ msgstr "Markdown指南"
#~ msgid "GitHub"
#~ msgstr "GitHub"
#~ msgid "Translate Tandoor"
#~ msgstr "翻譯Tandoor"
#~ msgid "API Browser"
#~ msgstr "API瀏覽器"
#~ msgid "Log out"
#~ msgstr "登出"
#~ msgid "You are using the free version of Tandor"
#~ msgstr "你正在使用Tandor的免費版本"
#~ msgid "Upgrade Now"
#~ msgstr "立即升級"
#~ msgid "Batch edit Category"
#~ msgstr "批量編輯類別"
#~ msgid "Batch edit Recipes"
#~ msgstr "批量編輯食譜"
#~ msgid "Add the specified keywords to all recipes containing a word"
#~ msgstr "將指定的關鍵詞添加到包含該詞的所有食譜中"
#~ msgid "Sync"
#~ msgstr "同步"
#~ msgid "Manage watched Folders"
#~ msgstr "管理監控文件夾"
#~ msgid ""
#~ "On this Page you can manage all storage folder locations that should be "
#~ "monitored and synced."
#~ msgstr "在此頁面上,你可以管理所有應該被監控和同步的存儲文件夾位置。"
#~ msgid "The path must be in the following format"
#~ msgstr "路徑必須是以下格式"
#~ msgid "Save"
#~ msgstr "保存"
#~ msgid "Manage External Storage"
#~ msgstr "管理外部存儲"
#~ msgid "Sync Now!"
#~ msgstr "立即同步!"
#~ msgid "Show Recipes"
#~ msgstr "顯示食譜"
#~ msgid "Show Log"
#~ msgstr "顯示日誌"
#~ msgid "Importing Recipes"
#~ msgstr "正在導入食譜"
#~ msgid ""
#~ "This can take a few minutes, depending on the number of recipes in sync, "
#~ "please wait."
#~ msgstr "這可能需要幾分鐘,具體取決於同步的食譜數量,請稍候。"
#~ msgid "Recipe Books"
#~ msgstr "食譜書"
#~ msgid "Import new Recipe"
#~ msgstr "導入新食譜"
#~ msgid "Edit Recipe"
#~ msgstr "編輯食譜"
#, python-format
#~ msgid "Are you sure you want to delete the %(title)s: %(object)s "
#~ msgstr "你確定要刪除%(title)s:%(object)s嗎 "
#~ msgid "This cannot be undone!"
#~ msgstr "此操作無法撤銷!"
#~ msgid "Protected"
#~ msgstr "受保護"
#~ msgid "Cascade"
#~ msgstr "級聯"
#~ msgid "Cancel"
#~ msgstr "取消"
#~ msgid "Edit"
#~ msgstr "編輯"
#~ msgid "View"
#~ msgstr "查看"
#~ msgid "Delete original file"
#~ msgstr "刪除原文件"
#~ msgid "List"
#~ msgstr "列表"
#~ msgid "Filter"
#~ msgstr "篩選"
#~ msgid "Import all"
#~ msgstr "全部導入"
#~ msgid "New"
#~ msgstr "新"
#~ msgid "previous"
#~ msgstr "上一頁"
#~ msgid "next"
#~ msgstr "下一頁"
#~ msgid "View Log"
#~ msgstr "查看日誌"
#~ msgid "Cook Log"
#~ msgstr "烹飪日誌"
#~ msgid "Import"
#~ msgstr "導入"
#~ msgid "Security Warning"
#~ msgstr "安全警告"
#~ msgid ""
#~ "\n"
#~ " The Password and Token field are stored as plain text"
#~ "b> inside the database.\n"
#~ " This is necessary because they are needed to make API requests, "
#~ "but it also increases the risk of\n"
#~ " someone stealing it.
\n"
#~ " To limit the possible damage tokens or accounts with limited "
#~ "access can be used.\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " 密碼和令牌字段以純文本形式存儲在數據庫中。\n"
#~ " 這是必要的,因為它們需要用於API請求,但這也增加了\n"
#~ " 被盜的風險。
\n"
#~ " 可以使用訪問受限的令牌或賬戶來限制可能的損害。\n"
#~ " "
#~ msgid "You are currently offline!"
#~ msgstr "你目前處於離線狀態!"
#~ msgid ""
#~ "The recipes listed below are available for offline viewing because you "
#~ "have recently viewed them. Keep in mind that data might be outdated."
#~ msgstr ""
#~ "以下列出的食譜可供離線查看,因為你最近查看過它們。請注意,數據可能已過時。"
#~ msgid "Property Editor"
#~ msgstr "屬性編輯器"
#~ msgid "Comments"
#~ msgstr "評論"
#~ msgid "by"
#~ msgstr "由"
#~ msgid "Comment"
#~ msgstr "評論"
#~ msgid ""
#~ "There are many options to configure the search depending on your personal "
#~ "preferences."
#~ msgstr "有許多選項可以根據你的個人喜好配置搜索。"
#~ msgid ""
#~ "Usually you do not need to configure any of them and can just "
#~ "stick with either the default or one of the following presets."
#~ msgstr ""
#~ "通常你不需要配置其中任何一個,只需堅持使用默認設置或以下預設之一。"
#~ msgid ""
#~ "If you do want to configure the search you can read about the different "
#~ "options here."
#~ msgstr ""
#~ "如果你確實想配置搜索,你可以在這裡閱讀不同選"
#~ "項。"
#~ msgid "Fuzzy"
#~ msgstr "模糊"
#~ msgid ""
#~ "Find what you need even if your search or the recipe contains typos. "
#~ "Might return more results than needed to make sure you find what you are "
#~ "looking for."
#~ msgstr ""
#~ "即使你的搜索或食譜包含拼寫錯誤,也能找到你需要的東西。可能會返回比需要更多"
#~ "的結果,以確保你找到你正在尋找的東西。"
#~ msgid "This is the default behavior"
#~ msgstr "這是默認行為"
#~ msgid "Apply"
#~ msgstr "應用"
#~ msgid "Precise"
#~ msgstr "精確"
#~ msgid ""
#~ "Allows fine control over search results but might not return results if "
#~ "too many spelling mistakes are made."
#~ msgstr "允許對搜索結果進行精細控制,但如果拼寫錯誤過多,可能不會返回結果。"
#~ msgid "Perfect for large Databases"
#~ msgstr "非常適合大型數據庫"
#~ msgid "Social"
#~ msgstr "社交"
#~ msgid "Space Management"
#~ msgstr "空間管理"
#~ msgid "Space:"
#~ msgstr "空間:"
#~ msgid "Manage Subscription"
#~ msgstr "管理訂閱"
#~ msgid "Leave Space"
#~ msgstr "離開空間"
#~ msgid "URL Import"
#~ msgstr "URL導入"
#~ msgid ""
#~ "Rating a recipe should have or greater. [0 - 5] Negative value filters "
#~ "rating less than."
#~ msgstr "食譜應具有或更高的評分。[0 - 5] 負值篩選評分低於。"
#~ msgid ""
#~ "Filter recipes updated on or after YYYY-MM-DD. Prepending - filters on or "
#~ "before date."
#~ msgstr "篩選更新日期在YYYY-MM-DD或之後的食譜。前置-篩選在日期或之前的食譜。"
#~ msgid ""
#~ "Returns the shopping list entry with a primary key of id. Multiple "
#~ "values allowed."
#~ msgstr "返回主鍵為id的購物清單條目。允許多個值。"
#~ msgid ""
#~ "Filter shopping list entries on checked. [true, false, both, recent"
#~ "b>]
- recent includes unchecked items and recently "
#~ "completed items."
#~ msgstr ""
#~ "按已選篩選購物清單條目。[true, false, both, recent"
#~ "b>]
- recent包括未選中的項目和最近完成的項目。"
#~ msgid ""
#~ "Returns the shopping list entries sorted by supermarket category order."
#~ msgstr "按超市類別順序返回購物清單條目。"
#, python-format
#~ msgid "Batch edit done. %(count)d recipe was updated."
#~ msgid_plural "Batch edit done. %(count)d Recipes where updated."
#~ msgstr[0] "批量編輯完成。%(count)d 個菜譜已更新。"
#~ msgid "Monitor"
#~ msgstr "監測"
#~ msgid "Storage Backend"
#~ msgstr "存儲後端"
#~ msgid ""
#~ "Could not delete this storage backend as it is used in at least one "
#~ "monitor."
#~ msgstr "無法刪除此存儲後端,因為它至少在一個監控中使用。"
#~ msgid "Connectors Config Backend"
#~ msgstr "連接器配置後端"
#~ msgid "Invite Link"
#~ msgstr "邀請鏈接"
#~ msgid "Space Membership"
#~ msgstr "空間成員資格"
#~ msgid "You cannot edit this storage!"
#~ msgstr "你不能編輯此存儲!"
#~ msgid "Storage saved!"
#~ msgstr "存儲已保存!"
#~ msgid "There was an error updating this storage backend!"
#~ msgstr "更新此存儲後端時出錯!"
#~ msgid "Config saved!"
#~ msgstr "配置已保存!"
#~ msgid "ConnectorConfig"
#~ msgstr "連接器配置"
#~ msgid "Changes saved!"
#~ msgstr "更改已保存!"
#~ msgid "Error saving changes!"
#~ msgstr "保存更改時出錯!"
#~ msgid "Import Log"
#~ msgstr "導入日誌"
#~ msgid "Discovery"
#~ msgstr "發現"
#~ msgid "Shopping List"
#~ msgstr "購物清單"
#~ msgid "Connector Config Backend"
#~ msgstr "連接器配置後端"
#~ msgid "Invite Links"
#~ msgstr "邀請鏈接"
#~ msgid "Supermarkets"
#~ msgstr "超市"
#~ msgid "Shopping Categories"
#~ msgstr "購物類別"
#~ msgid "Custom Filters"
#~ msgstr "自定義篩選器"
#~ msgid "Steps"
#~ msgstr "步驟"
#~ msgid "Property Types"
#~ msgstr "屬性類型"
#~ msgid "This feature is not enabled by the server admin!"
#~ msgstr "此功能未由服務器管理員啟用!"
#~ msgid "Imported new recipe!"
#~ msgstr "已導入新食譜!"
#~ msgid "There was an error importing this recipe!"
#~ msgstr "導入此食譜時出錯!"
#~ msgid "You do not have the required permissions to perform this action!"
#~ msgstr "你沒有執行此操作所需的權限!"
#~ msgid "Comment saved!"
#~ msgstr "評論已保存!"
#~ msgid "You must select at least one field to search!"
#~ msgstr "你必須選擇至少一個字段進行搜索!"
#~ msgid ""
#~ "To use this search method you must select at least one full text search "
#~ "field!"
#~ msgstr "要使用此搜索方法,你必須選擇至少一個全文搜索字段!"
#~ msgid "Fuzzy search is not compatible with this search method!"
#~ msgstr "模糊搜索與此搜索方法不兼容!"
#~ msgid "Malformed Invite Link supplied!"
#~ msgstr "提供的邀請鏈接格式錯誤!"
#~ msgid "Successfully joined space."
#~ msgstr "成功加入空間。"
#~ msgid "Invite Link not valid or already used!"
#~ msgstr "邀請鏈接無效或已被使用!"
#~ msgid "View your cookbooks"
#~ msgstr "查看你的食譜書"
#~ msgid ""
#~ "Color of the top navigation bar. Not all colors work with all themes, "
#~ "just try them out!"
#~ msgstr ""
#~ "頂部導航欄的顏色。並非所有的顏色都適用於所有的主題,只要試一試就可以了!"
#~ msgid ""
#~ "Default Unit to be used when inserting a new ingredient into a recipe."
#~ msgstr "在菜譜中插入新食材時使用的默認單位。"
#~ msgid ""
#~ "Enables support for fractions in ingredient amounts (e.g. convert "
#~ "decimals to fractions automatically)"
#~ msgstr "啟用對食材數量的分數支持(例如自動將小數轉換為分數)"
#~ msgid ""
#~ "Users with whom newly created meal plan/shopping list entries should be "
#~ "shared by default."
#~ msgstr "默認情況下,將自動與用戶共享新創建的膳食計劃。"
#~ msgid "Show recently viewed recipes on search page."
#~ msgstr "在搜索頁面上查看最近看過的食譜。"
#~ msgid "Number of decimals to round ingredients."
#~ msgstr "四舍五入食材的小數點數量。"
#~ msgid ""
#~ "If you want to be able to create and see comments underneath recipes."
#~ msgstr "如果你希望能夠在菜譜下面創建並看到評論。"
#~ msgid ""
#~ "Setting to 0 will disable auto sync. When viewing a shopping list the "
#~ "list is updated every set seconds to sync changes someone else might have "
#~ "made. Useful when shopping with multiple people but might use a little "
#~ "bit of mobile data. If lower than instance limit it is reset when saving."
#~ msgstr ""
#~ "設置為0將禁用自動同步。當查看購物清單時,清單會每隔幾秒鐘更新一次,以同步"
#~ "其他人可能做出的改變。在與多人一起購物時很有用,但可能會消耗一點移動數據。"
#~ "如果低於實例限制,它將在保存時被重置。"
#~ msgid "Makes the navbar stick to the top of the page."
#~ msgstr "使導航欄保持在頁面的頂部。"
#~ msgid "Old Unit"
#~ msgstr "舊單位"
#~ msgid "Unit that should be replaced."
#~ msgstr "該被替換的單位。"
#~ msgid "Old Food"
#~ msgstr "舊食物"
#~ msgid "Food that should be replaced."
#~ msgstr "該被替換的食物。"
#~ msgid "You must provide at least a recipe or a title."
#~ msgstr "你必須至少提供一份菜譜或一個標題。"
#~ msgid "You can list default users to share recipes with in the settings."
#~ msgstr "你可以在設置中列出默認用戶來分享菜譜。"
#~ msgid ""
#~ "You can use markdown to format this field. See the docs here"
#~ msgstr ""
#~ "可以使用 Markdown 設置此字段格式。查看文檔"
#~ msgid "Small"
#~ msgstr "小"
#~ msgid "Large"
#~ msgstr "大"
#~ msgid "Time"
#~ msgstr "時間"
#~ msgid "File"
#~ msgstr "文件"
#~ msgid "user"
#~ msgstr "用戶"
================================================
FILE: cookbook/management/commands/export.py
================================================
from django.core.management.commands.dumpdata import Command as DumpdataCommand
from django_scopes import scopes_disabled
class Command(DumpdataCommand):
def handle(self, *args, **options):
with scopes_disabled():
return super().handle(*args, **options)
================================================
FILE: cookbook/management/commands/fix_duplicate_properties.py
================================================
from django.conf import settings
from django.contrib.postgres.search import SearchVector
from django.core.management.base import BaseCommand
from django.db.models import Count
from django.utils import translation
from django.utils.translation import gettext_lazy as _
from django_scopes import scopes_disabled
from cookbook.managers import DICTIONARY
from cookbook.models import Recipe, Step, FoodProperty, Food
# can be executed at the command line with 'python manage.py rebuildindex'
class Command(BaseCommand):
help = _('Fixes foods with ')
def add_arguments(self, parser):
parser.add_argument('-d', '--dry-run', help='does not delete properties but instead prints them', action='store_true')
def handle(self, *args, **options):
with scopes_disabled():
foods_with_duplicate_properties = Food.objects.annotate(property_type_count=Count('foodproperty__property__property_type') - Count('foodproperty__property__property_type', distinct=True)).filter(property_type_count__gt=0).all()
for f in foods_with_duplicate_properties:
found_property_types = []
for fp in f.properties.all():
if fp.property_type.id in found_property_types:
if options['dry_run']:
print(f'Property id {fp.id} duplicate type {fp.property_type}({fp.property_type.id}) for food {f}({f.id})')
else:
print(f'DELETING property id {fp.id} duplicate type {fp.property_type}({fp.property_type.id}) for food {f}({f.id})')
fp.delete()
else:
found_property_types.append(fp.property_type.id)
================================================
FILE: cookbook/management/commands/import.py
================================================
from django.core.management.commands.loaddata import Command as LoaddataCommand
from django_scopes import scopes_disabled
class Command(LoaddataCommand):
def handle(self, *args, **options):
with scopes_disabled():
return super().handle(*args, **options)
================================================
FILE: cookbook/management/commands/rebuildindex.py
================================================
from django.conf import settings
from django.contrib.postgres.search import SearchVector
from django.core.management.base import BaseCommand
from django.utils import translation
from django.utils.translation import gettext_lazy as _
from django_scopes import scopes_disabled
from cookbook.managers import DICTIONARY
from cookbook.models import Recipe, Step
# can be executed at the command line with 'python manage.py rebuildindex'
class Command(BaseCommand):
help = _('Rebuilds full text search index on Recipe')
def handle(self, *args, **options):
if settings.DATABASES['default']['ENGINE'] != 'django.db.backends.postgresql':
self.stdout.write(self.style.WARNING(_('Only Postgresql databases use full text search, no index to rebuild')))
try:
language = DICTIONARY.get(translation.get_language(), 'simple')
with scopes_disabled():
Recipe.objects.all().update(
name_search_vector=SearchVector('name__unaccent', weight='A', config=language),
desc_search_vector=SearchVector('description__unaccent', weight='B', config=language)
)
Step.objects.all().update(search_vector=SearchVector('instruction__unaccent', weight='B', config=language))
self.stdout.write(self.style.SUCCESS(_('Recipe index rebuild complete.')))
except Exception:
self.stdout.write(self.style.ERROR(_('Recipe index rebuild failed.')))
================================================
FILE: cookbook/management/commands/seed_basic_data.py
================================================
from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.postgres.search import SearchVector
from django.core.management.base import BaseCommand
from django.utils import translation
from django.utils.translation import gettext_lazy as _
from django_scopes import scopes_disabled
from cookbook.managers import DICTIONARY
from cookbook.models import Recipe, Step, Space
class Command(BaseCommand):
help = 'Seeds some basic data (space, account, food)'
def handle(self, *args, **options):
with scopes_disabled():
user = User.objects.get_or_create(username='test')[0]
user.set_password('test')
user.save()
space = Space.objects.get_or_create(
name='Test Space',
created_by=user
)[0]
================================================
FILE: cookbook/managers.py
================================================
from django.contrib.postgres.aggregates import StringAgg
from django.contrib.postgres.search import SearchQuery, SearchRank, SearchVector
from django.db import models
from django.db.models import Q
from django.utils import translation
DICTIONARY = {
# TODO find custom dictionaries - maybe from here https://www.postgresql.org/message-id/CAF4Au4x6X_wSXFwsQYE8q5o0aQZANrvYjZJ8uOnsiHDnOVPPEg%40mail.gmail.com
# 'hy': 'Armenian',
# 'ca': 'Catalan',
# 'cs': 'Czech',
'nl': 'dutch',
'en': 'english',
'fr': 'french',
'de': 'german',
'it': 'italian',
# 'lv': 'Latvian',
'es': 'spanish',
'sv': 'swedish',
}
# TODO add schedule index rebuild
class RecipeSearchManager(models.Manager):
def search(self, search_text, space):
language = DICTIONARY.get(translation.get_language(), 'simple')
search_query = SearchQuery(
search_text,
config=language,
search_type="websearch"
)
search_vectors = (
SearchVector('search_vector')
+ SearchVector(StringAgg('steps__ingredients__food__name__unaccent', delimiter=' '), weight='B', config=language)
+ SearchVector(StringAgg('keywords__name__unaccent', delimiter=' '), weight='B', config=language))
search_rank = SearchRank(search_vectors, search_query)
return (
self.get_queryset()
.annotate(
search=search_vectors,
rank=search_rank,
)
.filter(
Q(search=search_query)
)
.order_by('-rank'))
================================================
FILE: cookbook/migrations/0001_initial.py
================================================
# Generated by Django 2.2.7 on 2019-11-19 18:43
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Keyword',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=64, unique=True)),
('icon', models.CharField(blank=True, max_length=1, null=True)),
('description', models.TextField(blank=True, default='')),
('created_by', models.IntegerField(default=0)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
),
migrations.CreateModel(
name='Recipe',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128)),
('instructions', models.TextField(blank=True)),
('file_uid', models.CharField(default='', max_length=256)),
('file_path', models.CharField(default='', max_length=512)),
('link', models.CharField(default='', max_length=512)),
('time', models.IntegerField(default=0)),
('internal', models.BooleanField(default=False)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
('keywords', models.ManyToManyField(blank=True, to='cookbook.Keyword')),
],
),
migrations.CreateModel(
name='Storage',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128)),
('method', models.CharField(choices=[('DB', 'Dropbox'), ('NEXTCLOUD', 'Nextcloud')], default='DB', max_length=128)),
('username', models.CharField(blank=True, max_length=128, null=True)),
('password', models.CharField(blank=True, max_length=128, null=True)),
('token', models.CharField(blank=True, max_length=512, null=True)),
('url', models.URLField(blank=True, null=True)),
],
),
migrations.CreateModel(
name='Sync',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('path', models.CharField(default='', max_length=512)),
('active', models.BooleanField(default=True)),
('last_checked', models.DateTimeField()),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('storage', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='cookbook.Storage')),
],
),
migrations.CreateModel(
name='SyncLog',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('status', models.CharField(max_length=32)),
('msg', models.TextField(default='')),
('created_at', models.DateTimeField(auto_now_add=True)),
('sync', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.Sync')),
],
),
migrations.CreateModel(
name='RecipeIngredients',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('unit', models.CharField(max_length=128)),
('amount', models.DecimalField(decimal_places=2, default=0, max_digits=16)),
('ingredient', models.CharField(max_length=128)),
('recipe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.Recipe')),
],
),
migrations.CreateModel(
name='RecipeImport',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128)),
('file_uid', models.CharField(default='', max_length=256)),
('file_path', models.CharField(default='', max_length=512)),
('created_at', models.DateTimeField(auto_now_add=True)),
('storage', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='cookbook.Storage')),
],
),
migrations.AddField(
model_name='recipe',
name='storage',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='cookbook.Storage'),
),
migrations.CreateModel(
name='Comment',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('text', models.TextField()),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('recipe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.Recipe')),
],
),
]
================================================
FILE: cookbook/migrations/0001_squashed_0227_space_ai_default_provider_and_more.py
================================================
# Generated by Django 5.2.6 on 2025-09-10 18:59
import annoying.fields
from django_scopes import scopes_disabled
import cookbook.models
import datetime
import django.contrib.postgres.indexes
import django.contrib.postgres.operations
import django.contrib.postgres.search
import django.core.validators
import django.db.models.deletion
import django.utils.timezone
import django_prometheus.models
import uuid
from django.conf import settings
from django.db import migrations, models
from cookbook.models import SearchFields
from django.contrib.postgres.operations import TrigramExtension, UnaccentExtension
def allSearchFields():
return list(SearchFields.objects.values_list('id', flat=True))
def nameSearchField():
return [SearchFields.objects.get(name='Name').id]
def create_default_groups(apps, schema_editor):
with scopes_disabled():
Group = apps.get_model('auth', 'Group')
Group.objects.bulk_create([
Group(name=u'guest'),
Group(name=u'user'),
Group(name=u'admin'),
])
def create_fields(apps, schema_editor):
SearchFields = apps.get_model('cookbook', 'SearchFields')
SearchFields.objects.create(name='Name', field='name')
SearchFields.objects.create(name='Description', field='description')
SearchFields.objects.create(name='Instructions', field='steps__instruction')
SearchFields.objects.create(name='Ingredients', field='steps__ingredients__food__name')
SearchFields.objects.create(name='Keywords', field='keywords__name')
SearchFields.objects.create(name='Units', field='steps__ingredients__unit__name')
FoodInheritField = apps.get_model('cookbook', 'FoodInheritField')
FoodInheritField.objects.create(name='Supermarket Category', field='supermarket_category')
FoodInheritField.objects.create(name='Ignore Shopping', field='ignore_shopping')
FoodInheritField.objects.create(name='Diet', field='diet')
FoodInheritField.objects.create(name='Substitute', field='substitute')
FoodInheritField.objects.create(name='Substitute Children', field='substitute_children')
FoodInheritField.objects.create(name='Substitute Siblings', field='substitute_siblings')
class Migration(migrations.Migration):
replaces = [('cookbook', '0001_initial'), ('cookbook', '0002_auto_20191119_2035'), ('cookbook', '0003_enable_pgtrm'), ('cookbook', '0004_storage_created_by'),
('cookbook', '0005_recipebook_recipebookentry'), ('cookbook', '0006_recipe_image'), ('cookbook', '0007_auto_20191226_0852'), ('cookbook', '0008_mealplan'),
('cookbook', '0009_auto_20200130_1056'), ('cookbook', '0010_auto_20200130_1059'), ('cookbook', '0011_remove_recipeingredients_unit'),
('cookbook', '0012_auto_20200130_1116'), ('cookbook', '0013_userpreference'), ('cookbook', '0014_auto_20200213_2332'), ('cookbook', '0015_auto_20200213_2334'),
('cookbook', '0016_auto_20200213_2335'), ('cookbook', '0017_auto_20200216_2257'), ('cookbook', '0018_auto_20200216_2303'), ('cookbook', '0019_ingredient'),
('cookbook', '0020_recipeingredient_ingredient'), ('cookbook', '0021_auto_20200216_2309'), ('cookbook', '0022_remove_recipeingredient_name'),
('cookbook', '0023_auto_20200216_2311'), ('cookbook', '0024_auto_20200216_2313'), ('cookbook', '0025_userpreference_nav_color'),
('cookbook', '0026_auto_20200219_1605'), ('cookbook', '0027_ingredient_recipe'), ('cookbook', '0028_auto_20200317_1901'), ('cookbook', '0029_auto_20200317_1901'),
('cookbook', '0030_recipeingredient_note'), ('cookbook', '0031_auto_20200407_1841'), ('cookbook', '0032_userpreference_default_unit'),
('cookbook', '0033_userpreference_default_page'), ('cookbook', '0034_auto_20200426_1614'), ('cookbook', '0035_auto_20200427_1637'),
('cookbook', '0036_auto_20200427_1800'), ('cookbook', '0037_userpreference_search_style'), ('cookbook', '0038_auto_20200502_1259'),
('cookbook', '0039_recipebook_shared'), ('cookbook', '0040_auto_20200502_1433'), ('cookbook', '0041_auto_20200502_1446'), ('cookbook', '0042_cooklog'),
('cookbook', '0043_auto_20200507_2302'), ('cookbook', '0044_viewlog'), ('cookbook', '0045_userpreference_show_recent'), ('cookbook', '0046_auto_20200602_1133'),
('cookbook', '0047_auto_20200602_1133'), ('cookbook', '0048_auto_20200602_1140'), ('cookbook', '0049_mealtype_created_by'), ('cookbook', '0050_auto_20200611_1509'),
('cookbook', '0051_auto_20200611_1518'), ('cookbook', '0052_userpreference_ingredient_decimals'), ('cookbook', '0053_auto_20200611_2217'),
('cookbook', '0054_sharelink'), ('cookbook', '0055_auto_20200616_1236'), ('cookbook', '0056_auto_20200625_2118'), ('cookbook', '0057_auto_20200625_2127'),
('cookbook', '0058_auto_20200625_2128'), ('cookbook', '0059_auto_20200625_2137'), ('cookbook', '0060_auto_20200625_2144'), ('cookbook', '0056_auto_20200625_2157'),
('cookbook', '0061_merge_20200625_2209'), ('cookbook', '0062_auto_20200625_2219'), ('cookbook', '0063_auto_20200625_2230'), ('cookbook', '0064_auto_20200625_2329'),
('cookbook', '0065_auto_20200626_1444'), ('cookbook', '0066_auto_20200626_1455'), ('cookbook', '0067_auto_20200629_1508'), ('cookbook', '0068_auto_20200629_2127'),
('cookbook', '0069_auto_20200629_2134'), ('cookbook', '0070_auto_20200701_2007'), ('cookbook', '0071_auto_20200701_2048'), ('cookbook', '0072_step_show_as_header'),
('cookbook', '0073_auto_20200708_2311'), ('cookbook', '0074_remove_keyword_created_by'), ('cookbook', '0075_shoppinglist_shoppinglistentry_shoppinglistrecipe'),
('cookbook', '0076_shoppinglist_entries'), ('cookbook', '0077_invitelink'), ('cookbook', '0078_invitelink_used_by'), ('cookbook', '0079_invitelink_group'),
('cookbook', '0080_auto_20200921_2331'), ('cookbook', '0081_auto_20200921_2349'), ('cookbook', '0082_auto_20200922_1143'), ('cookbook', '0083_space'),
('cookbook', '0084_auto_20200922_1233'), ('cookbook', '0085_auto_20200922_1235'), ('cookbook', '0086_auto_20200929_1143'), ('cookbook', '0087_auto_20200929_1152'),
('cookbook', '0088_shoppinglist_finished'), ('cookbook', '0089_auto_20201117_2222'), ('cookbook', '0090_auto_20201214_1359'),
('cookbook', '0091_auto_20201226_1551'), ('cookbook', '0092_recipe_servings'), ('cookbook', '0093_auto_20201231_1236'), ('cookbook', '0094_auto_20201231_1238'),
('cookbook', '0095_auto_20210107_1804'), ('cookbook', '0096_auto_20210109_2044'), ('cookbook', '0097_auto_20210113_1315'), ('cookbook', '0098_auto_20210113_1320'),
('cookbook', '0099_auto_20210113_1518'), ('cookbook', '0100_recipe_servings_text'), ('cookbook', '0101_storage_path'), ('cookbook', '0102_auto_20210125_1147'),
('cookbook', '0103_food_ignore_shopping'), ('cookbook', '0104_auto_20210125_2133'), ('cookbook', '0105_auto_20210126_1604'),
('cookbook', '0106_shoppinglist_supermarket'), ('cookbook', '0107_auto_20210128_1535'), ('cookbook', '0108_auto_20210219_1410'),
('cookbook', '0109_auto_20210221_1204'), ('cookbook', '0110_auto_20210221_1406'), ('cookbook', '0111_space_created_by'), ('cookbook', '0112_remove_synclog_space'),
('cookbook', '0113_auto_20210317_2017'), ('cookbook', '0114_importlog'), ('cookbook', '0115_telegrambot'), ('cookbook', '0116_auto_20210319_0012'),
('cookbook', '0117_space_max_recipes'), ('cookbook', '0118_auto_20210406_1805'), ('cookbook', '0119_auto_20210411_2101'), ('cookbook', '0120_bookmarklet'),
('cookbook', '0121_auto_20210518_1638'), ('cookbook', '0122_auto_20210527_1712'), ('cookbook', '0123_invitelink_email'),
('cookbook', '0124_alter_userpreference_theme'), ('cookbook', '0125_space_demo'), ('cookbook', '0126_alter_userpreference_theme'),
('cookbook', '0127_remove_invitelink_username'), ('cookbook', '0128_userfile'), ('cookbook', '0129_auto_20210608_1233'),
('cookbook', '0130_alter_userfile_file_size_kb'), ('cookbook', '0131_auto_20210608_1929'), ('cookbook', '0132_sharelink_request_count'),
('cookbook', '0133_sharelink_abuse_blocked'), ('cookbook', '0134_space_allow_sharing'), ('cookbook', '0135_auto_20210615_2210'),
('cookbook', '0136_auto_20210617_1343'), ('cookbook', '0137_auto_20210617_1501'), ('cookbook', '0138_auto_20210617_1602'), ('cookbook', '0139_space_created_at'),
('cookbook', '0140_userpreference_created_at'), ('cookbook', '0141_auto_20210713_1042'), ('cookbook', '0142_alter_userpreference_search_style'),
('cookbook', '0143_build_full_text_index'), ('cookbook', '0144_create_searchfields'), ('cookbook', '0145_alter_userpreference_search_style'),
('cookbook', '0146_alter_userpreference_use_fractions'), ('cookbook', '0147_keyword_to_tree'), ('cookbook', '0148_auto_20210813_1829'),
('cookbook', '0149_fix_leading_trailing_spaces'), ('cookbook', '0150_food_to_tree'), ('cookbook', '0151_auto_20210915_1037'), ('cookbook', '0152_automation'),
('cookbook', '0153_auto_20210915_2327'), ('cookbook', '0154_auto_20210922_1705'), ('cookbook', '0155_mealtype_default'),
('cookbook', '0156_searchpreference_trigram_threshold'), ('cookbook', '0157_alter_searchpreference_trigram'), ('cookbook', '0158_userpreference_use_kj'),
('cookbook', '0159_add_shoppinglistentry_fields'), ('cookbook', '0160_delete_shoppinglist_orphans'), ('cookbook', '0161_alter_shoppinglistentry_food'),
('cookbook', '0162_userpreference_csv_delim'), ('cookbook', '0163_auto_20220105_0758'), ('cookbook', '0164_space_show_facet_count'),
('cookbook', '0165_remove_step_type'), ('cookbook', '0166_alter_userpreference_shopping_add_onhand'), ('cookbook', '0167_userpreference_left_handed'),
('cookbook', '0168_add_unit_searchfields'), ('cookbook', '0169_exportlog'), ('cookbook', '0170_auto_20220207_1848'),
('cookbook', '0171_alter_searchpreference_trigram_threshold'), ('cookbook', '0172_ingredient_original_text'), ('cookbook', '0173_recipe_source_url'),
('cookbook', '0174_alter_food_substitute_userspace'), ('cookbook', '0175_remove_userpreference_space'),
('cookbook', '0176_alter_searchpreference_icontains_and_more'), ('cookbook', '0177_recipe_show_ingredient_overview'),
('cookbook', '0178_remove_userpreference_search_style_and_more'), ('cookbook', '0179_recipe_private_recipe_shared'), ('cookbook', '0180_invitelink_reusable'),
('cookbook', '0181_space_image'), ('cookbook', '0182_userpreference_image'), ('cookbook', '0183_alter_space_image'),
('cookbook', '0184_alter_userpreference_image'), ('cookbook', '0185_food_plural_name_ingredient_always_use_plural_food_and_more'),
('cookbook', '0186_automation_order_alter_automation_type'), ('cookbook', '0187_alter_space_use_plural'), ('cookbook', '0188_space_no_sharing_limit'),
('cookbook', '0189_property_propertytype_unitconversion_food_fdc_id_and_more'), ('cookbook', '0190_auto_20230525_1506'),
('cookbook', '0191_foodproperty_property_import_food_id_and_more'), ('cookbook', '0192_food_food_unique_open_data_slug_per_space_and_more'),
('cookbook', '0193_space_internal_note'), ('cookbook', '0194_alter_food_properties_food_amount'),
('cookbook', '0195_invitelink_internal_note_userspace_internal_note_and_more'), ('cookbook', '0196_food_url'),
('cookbook', '0197_step_show_ingredients_table_and_more'), ('cookbook', '0198_propertytype_order'),
('cookbook', '0199_alter_propertytype_options_alter_automation_type_and_more'), ('cookbook', '0200_alter_propertytype_options_remove_keyword_icon_and_more'),
('cookbook', '0201_rename_date_mealplan_from_date_mealplan_to_date'), ('cookbook', '0202_remove_space_show_facet_count'),
('cookbook', '0203_alter_unique_contstraints'), ('cookbook', '0204_propertytype_fdc_id'), ('cookbook', '0205_alter_food_fdc_id_alter_propertytype_fdc_id'),
('cookbook', '0206_rename_sticky_navbar_userpreference_nav_sticky_and_more'), ('cookbook', '0207_space_logo_color_128_space_logo_color_144_and_more'),
('cookbook', '0208_space_app_name_userpreference_max_owned_spaces'), ('cookbook', '0209_remove_space_use_plural'),
('cookbook', '0210_shoppinglistentry_updated_at'), ('cookbook', '0211_recipebook_order'), ('cookbook', '0212_alter_property_property_amount'),
('cookbook', '0213_remove_property_property_unique_import_food_per_space_and_more'), ('cookbook', '0214_cooklog_comment_cooklog_updated_at_and_more'),
('cookbook', '0215_connectorconfig'), ('cookbook', '0216_delete_shoppinglist'), ('cookbook', '0217_alter_userpreference_default_page'),
('cookbook', '0218_alter_mealplan_from_date_alter_mealplan_to_date'), ('cookbook', '0219_connectorconfig_supports_description_field'),
('cookbook', '0220_shoppinglistrecipe_created_by_and_more'), ('cookbook', '0221_migrate_shoppinglistrecipe_space_created_by'),
('cookbook', '0222_alter_shoppinglistrecipe_created_by_and_more'), ('cookbook', '0223_auto_20250831_1111'),
('cookbook', '0224_space_ai_credits_balance_space_ai_credits_monthly_and_more'), ('cookbook', '0225_space_ai_enabled'),
('cookbook', '0226_aiprovider_log_credit_cost_and_more'), ('cookbook', '0227_space_ai_default_provider_and_more')]
initial = True
dependencies = [
('auth', '0011_update_proxy_permissions'),
('auth', '0012_alter_user_first_name_max_length'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
TrigramExtension(),
UnaccentExtension(),
migrations.RunPython(create_default_groups),
migrations.CreateModel(
name='AiProvider',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128)),
('description', models.TextField(blank=True)),
('api_key', models.CharField(max_length=2048)),
('model_name', models.CharField(max_length=256)),
('url', models.CharField(blank=True, max_length=2048, null=True)),
('log_credit_cost', models.BooleanField(default=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
),
migrations.CreateModel(
name='FoodInheritField',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('field', models.CharField(max_length=32, unique=True)),
('name', models.CharField(max_length=64, unique=True)),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
migrations.CreateModel(
name='Keyword',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('path', models.CharField(max_length=255, unique=True)),
('depth', models.PositiveIntegerField()),
('numchild', models.PositiveIntegerField(default=0)),
('name', models.CharField(max_length=64)),
('description', models.TextField(blank=True, default='')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
bases=(django_prometheus.models.ExportModelOperationsMixin('keyword'), models.Model, cookbook.models.PermissionModelMixin),
),
migrations.CreateModel(
name='NutritionInformation',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('fats', models.DecimalField(decimal_places=16, default=0, max_digits=32)),
('carbohydrates', models.DecimalField(decimal_places=16, default=0, max_digits=32)),
('proteins', models.DecimalField(decimal_places=16, default=0, max_digits=32)),
('calories', models.DecimalField(decimal_places=16, default=0, max_digits=32)),
('source', models.CharField(blank=True, default='', max_length=512, null=True)),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
migrations.CreateModel(
name='Property',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('property_amount', models.DecimalField(decimal_places=4, default=None, max_digits=32, null=True)),
('open_data_food_slug', models.CharField(blank=True, default=None, max_length=128, null=True)),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
migrations.CreateModel(
name='PropertyType',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128)),
('unit', models.CharField(blank=True, max_length=64, null=True)),
('order', models.IntegerField(default=0)),
('description', models.CharField(blank=True, max_length=512, null=True)),
('category', models.CharField(blank=True, choices=[('NUTRITION', 'Nutrition'), ('ALLERGEN', 'Allergen'), ('PRICE', 'Price'), ('GOAL', 'Goal'), ('OTHER', 'Other')],
max_length=64, null=True)),
('open_data_slug', models.CharField(blank=True, default=None, max_length=128, null=True)),
('fdc_id', models.IntegerField(blank=True, default=None, null=True)),
],
options={
'ordering': ('order',),
},
bases=(models.Model, cookbook.models.PermissionModelMixin, cookbook.models.MergeModelMixin),
),
migrations.CreateModel(
name='SearchFields',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=32, unique=True)),
('field', models.CharField(max_length=64, unique=True)),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
migrations.CreateModel(
name='CustomFilter',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128)),
('type', models.CharField(choices=[('RECIPE', 'Recipe'), ('FOOD', 'Food'), ('KEYWORD', 'Keyword')], default=('RECIPE', 'Recipe'), max_length=128)),
('search', models.TextField()),
('created_at', models.DateTimeField(auto_now_add=True)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('shared', models.ManyToManyField(blank=True, related_name='f_shared_with', to=settings.AUTH_USER_MODEL)),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
migrations.CreateModel(
name='Food',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('path', models.CharField(max_length=255, unique=True)),
('depth', models.PositiveIntegerField()),
('numchild', models.PositiveIntegerField(default=0)),
('name', models.CharField(max_length=128, validators=[django.core.validators.MinLengthValidator(1)])),
('plural_name', models.CharField(blank=True, default=None, max_length=128, null=True)),
('url', models.CharField(blank=True, default='', max_length=1024, null=True)),
('ignore_shopping', models.BooleanField(default=False)),
('description', models.TextField(blank=True, default='')),
('substitute_siblings', models.BooleanField(default=False)),
('substitute_children', models.BooleanField(default=False)),
('properties_food_amount', models.DecimalField(blank=True, decimal_places=2, default=100, max_digits=16)),
('fdc_id', models.IntegerField(blank=True, default=None, null=True)),
('open_data_slug', models.CharField(blank=True, default=None, max_length=128, null=True)),
('onhand_users', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL)),
('substitute', models.ManyToManyField(blank=True, to='cookbook.food')),
('child_inherit_fields', models.ManyToManyField(blank=True, related_name='child_inherit', to='cookbook.foodinheritfield')),
('inherit_fields', models.ManyToManyField(blank=True, to='cookbook.foodinheritfield')),
],
bases=(django_prometheus.models.ExportModelOperationsMixin('food'), models.Model, cookbook.models.PermissionModelMixin),
),
migrations.CreateModel(
name='MealType',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128)),
('order', models.IntegerField(default=0)),
('color', models.CharField(blank=True, max_length=7, null=True)),
('time', models.TimeField(blank=True, null=True)),
('default', models.BooleanField(blank=True, default=False)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
migrations.CreateModel(
name='FoodProperty',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('food', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.food')),
('property', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.property')),
],
),
migrations.AddField(
model_name='food',
name='properties',
field=models.ManyToManyField(blank=True, through='cookbook.FoodProperty', to='cookbook.property'),
),
migrations.AddField(
model_name='property',
name='property_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='cookbook.propertytype'),
),
migrations.CreateModel(
name='Recipe',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128)),
('description', models.CharField(blank=True, max_length=512, null=True)),
('servings', models.IntegerField(default=1)),
('servings_text', models.CharField(blank=True, default='', max_length=32)),
('image', models.ImageField(blank=True, null=True, upload_to='recipes/')),
('file_uid', models.CharField(blank=True, default='', max_length=256)),
('file_path', models.CharField(blank=True, default='', max_length=512)),
('link', models.CharField(blank=True, max_length=512, null=True)),
('cors_link', models.CharField(blank=True, max_length=1024, null=True)),
('working_time', models.IntegerField(default=0)),
('waiting_time', models.IntegerField(default=0)),
('internal', models.BooleanField(default=False)),
('show_ingredient_overview', models.BooleanField(default=True)),
('private', models.BooleanField(default=False)),
('source_url', models.CharField(blank=True, default=None, max_length=1024, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('name_search_vector', django.contrib.postgres.search.SearchVectorField(null=True)),
('desc_search_vector', django.contrib.postgres.search.SearchVectorField(null=True)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
('keywords', models.ManyToManyField(blank=True, to='cookbook.keyword')),
('nutrition', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.nutritioninformation')),
('properties', models.ManyToManyField(blank=True, to='cookbook.property')),
('shared', models.ManyToManyField(blank=True, related_name='recipe_shared_with', to=settings.AUTH_USER_MODEL)),
],
bases=(django_prometheus.models.ExportModelOperationsMixin('recipe'), models.Model, cookbook.models.PermissionModelMixin),
),
migrations.CreateModel(
name='MealPlan',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('servings', models.DecimalField(decimal_places=4, default=1, max_digits=8)),
('title', models.CharField(blank=True, default='', max_length=64)),
('note', models.TextField(blank=True)),
('from_date', models.DateTimeField()),
('to_date', models.DateTimeField()),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('shared', models.ManyToManyField(blank=True, related_name='plan_share', to=settings.AUTH_USER_MODEL)),
('meal_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.mealtype')),
('recipe', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.recipe')),
],
bases=(django_prometheus.models.ExportModelOperationsMixin('meal_plan'), models.Model, cookbook.models.PermissionModelMixin),
),
migrations.AddField(
model_name='food',
name='recipe',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='cookbook.recipe'),
),
migrations.CreateModel(
name='Comment',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('text', models.TextField()),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('recipe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.recipe')),
],
bases=(django_prometheus.models.ExportModelOperationsMixin('comment'), models.Model, cookbook.models.PermissionModelMixin),
),
migrations.CreateModel(
name='RecipeBook',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128)),
('description', models.TextField(blank=True)),
('order', models.IntegerField(default=0)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('filter', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='cookbook.customfilter')),
('shared', models.ManyToManyField(blank=True, related_name='shared_with', to=settings.AUTH_USER_MODEL)),
],
bases=(django_prometheus.models.ExportModelOperationsMixin('book'), models.Model, cookbook.models.PermissionModelMixin),
),
migrations.CreateModel(
name='RecipeBookEntry',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('book', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.recipebook')),
('recipe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.recipe')),
],
bases=(django_prometheus.models.ExportModelOperationsMixin('book_entry'), models.Model, cookbook.models.PermissionModelMixin),
),
migrations.CreateModel(
name='SearchPreference',
fields=[
('user', annoying.fields.AutoOneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL)),
('search', models.CharField(choices=[('plain', 'Simple'), ('phrase', 'Phrase'), ('websearch', 'Web'), ('raw', 'Raw')], default='plain', max_length=32)),
('lookup', models.BooleanField(default=False)),
('trigram_threshold', models.DecimalField(decimal_places=2, default=0.2, max_digits=3)),
('fulltext', models.ManyToManyField(blank=True, related_name='fulltext_fields', to='cookbook.searchfields')),
('icontains', models.ManyToManyField(blank=True, related_name='icontains_fields', to='cookbook.searchfields')),
('istartswith', models.ManyToManyField(blank=True, related_name='istartswith_fields', to='cookbook.searchfields')),
('trigram', models.ManyToManyField(blank=True, related_name='trigram_fields', to='cookbook.searchfields')),
('unaccent', models.ManyToManyField(blank=True, related_name='unaccent_fields', to='cookbook.searchfields')),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
migrations.CreateModel(
name='Space',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(default='Default', max_length=128)),
('space_theme', models.CharField(
choices=[('BLANK', '-------'), ('TANDOOR', 'Tandoor'), ('BOOTSTRAP', 'Bootstrap'), ('DARKLY', 'Darkly'), ('FLATLY', 'Flatly'), ('SUPERHERO', 'Superhero'),
('TANDOOR_DARK', 'Tandoor Dark (INCOMPLETE)')], default='BLANK', max_length=128)),
('nav_bg_color', models.CharField(blank=True, default='', max_length=8)),
('nav_text_color', models.CharField(choices=[('BLANK', '-------'), ('LIGHT', 'Light'), ('DARK', 'Dark')], default='BLANK', max_length=16)),
('app_name', models.CharField(blank=True, max_length=40, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('message', models.CharField(blank=True, default='', max_length=512)),
('max_recipes', models.IntegerField(default=0)),
('max_file_storage_mb', models.IntegerField(default=0, help_text='Maximum file storage for space in MB. 0 for unlimited, -1 to disable file upload.')),
('max_users', models.IntegerField(default=0)),
('allow_sharing', models.BooleanField(default=True)),
('no_sharing_limit', models.BooleanField(default=False)),
('demo', models.BooleanField(default=False)),
('ai_enabled', models.BooleanField(default=True)),
('ai_credits_monthly', models.IntegerField(default=100)),
('ai_credits_balance', models.DecimalField(decimal_places=4, default=0, max_digits=16)),
('internal_note', models.TextField(blank=True, null=True)),
('ai_default_provider',
models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='space_ai_default_provider', to='cookbook.aiprovider')),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
('food_inherit', models.ManyToManyField(blank=True, to='cookbook.foodinheritfield')),
],
bases=(django_prometheus.models.ExportModelOperationsMixin('space'), models.Model),
),
migrations.CreateModel(
name='ShoppingListRecipe',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(blank=True, default='', max_length=32)),
('servings', models.DecimalField(decimal_places=4, default=1, max_digits=8)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('mealplan', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.mealplan')),
('recipe', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.recipe')),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(django_prometheus.models.ExportModelOperationsMixin('shopping_list_recipe'), models.Model, cookbook.models.PermissionModelMixin),
),
migrations.CreateModel(
name='ShareLink',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('uuid', models.UUIDField(default=uuid.uuid4)),
('request_count', models.IntegerField(default=0)),
('abuse_blocked', models.BooleanField(default=False)),
('created_at', models.DateTimeField(auto_now_add=True)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('recipe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.recipe')),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(django_prometheus.models.ExportModelOperationsMixin('share_link'), models.Model, cookbook.models.PermissionModelMixin),
),
migrations.AddField(
model_name='recipebook',
name='space',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
),
migrations.AddField(
model_name='recipe',
name='space',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
),
migrations.AddField(
model_name='propertytype',
name='space',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
),
migrations.AddField(
model_name='property',
name='space',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
),
migrations.AddField(
model_name='nutritioninformation',
name='space',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
),
migrations.AddField(
model_name='mealtype',
name='space',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
),
migrations.AddField(
model_name='mealplan',
name='space',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
),
migrations.AddField(
model_name='keyword',
name='space',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
),
migrations.CreateModel(
name='InviteLink',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('uuid', models.UUIDField(default=uuid.uuid4)),
('email', models.EmailField(blank=True, max_length=254)),
('valid_until', models.DateField(default=cookbook.models.default_valid_until)),
('reusable', models.BooleanField(default=False)),
('created_at', models.DateTimeField(auto_now_add=True)),
('internal_note', models.TextField(blank=True, null=True)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.group')),
('used_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='used_by', to=settings.AUTH_USER_MODEL)),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(django_prometheus.models.ExportModelOperationsMixin('invite_link'), models.Model, cookbook.models.PermissionModelMixin),
),
migrations.CreateModel(
name='Ingredient',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('amount', models.DecimalField(decimal_places=16, default=0, max_digits=32)),
('note', models.CharField(blank=True, max_length=256, null=True)),
('is_header', models.BooleanField(default=False)),
('no_amount', models.BooleanField(default=False)),
('always_use_plural_unit', models.BooleanField(default=False)),
('always_use_plural_food', models.BooleanField(default=False)),
('order', models.IntegerField(default=0)),
('original_text', models.CharField(blank=True, default=None, max_length=512, null=True)),
('food', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.food')),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
options={
'ordering': ['order', 'pk'],
},
bases=(django_prometheus.models.ExportModelOperationsMixin('ingredient'), models.Model, cookbook.models.PermissionModelMixin),
),
migrations.CreateModel(
name='ImportLog',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('type', models.CharField(max_length=32)),
('running', models.BooleanField(default=True)),
('msg', models.TextField(default='')),
('total_recipes', models.IntegerField(default=0)),
('imported_recipes', models.IntegerField(default=0)),
('created_at', models.DateTimeField(auto_now_add=True)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('keyword', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='cookbook.keyword')),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
migrations.AddField(
model_name='food',
name='space',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
),
migrations.CreateModel(
name='ExportLog',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('type', models.CharField(max_length=32)),
('running', models.BooleanField(default=True)),
('msg', models.TextField(default='')),
('total_recipes', models.IntegerField(default=0)),
('exported_recipes', models.IntegerField(default=0)),
('cache_duration', models.IntegerField(default=0)),
('possibly_not_expired', models.BooleanField(default=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
migrations.AddField(
model_name='customfilter',
name='space',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
),
migrations.CreateModel(
name='CookLog',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('rating', models.IntegerField(blank=True, null=True)),
('servings', models.IntegerField(blank=True, null=True)),
('comment', models.TextField(blank=True, null=True)),
('created_at', models.DateTimeField(default=django.utils.timezone.now)),
('updated_at', models.DateTimeField(auto_now=True)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('recipe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.recipe')),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(django_prometheus.models.ExportModelOperationsMixin('cook_log'), models.Model, cookbook.models.PermissionModelMixin),
),
migrations.CreateModel(
name='ConnectorConfig',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128, validators=[django.core.validators.MinLengthValidator(1)])),
('type', models.CharField(choices=[('HomeAssistant', 'HomeAssistant')], default='HomeAssistant', max_length=128)),
('enabled', models.BooleanField(default=True, help_text='Is Connector Enabled')),
('on_shopping_list_entry_created_enabled', models.BooleanField(default=False)),
('on_shopping_list_entry_updated_enabled', models.BooleanField(default=False)),
('on_shopping_list_entry_deleted_enabled', models.BooleanField(default=False)),
('supports_description_field', models.BooleanField(default=True, help_text='Does the todo entity support the description field')),
('url', models.URLField(blank=True, null=True)),
('token', models.CharField(blank=True, max_length=512, null=True)),
('todo_entity', models.CharField(blank=True, max_length=128, null=True)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
migrations.CreateModel(
name='BookmarkletImport',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('html', models.TextField()),
('url', models.CharField(blank=True, max_length=256, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(django_prometheus.models.ExportModelOperationsMixin('bookmarklet_import'), models.Model, cookbook.models.PermissionModelMixin),
),
migrations.CreateModel(
name='Automation',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('type', models.CharField(
choices=[('FOOD_ALIAS', 'Food Alias'), ('UNIT_ALIAS', 'Unit Alias'), ('KEYWORD_ALIAS', 'Keyword Alias'), ('DESCRIPTION_REPLACE', 'Description Replace'),
('INSTRUCTION_REPLACE', 'Instruction Replace'), ('NEVER_UNIT', 'Never Unit'), ('TRANSPOSE_WORDS', 'Transpose Words'), ('FOOD_REPLACE', 'Food Replace'),
('UNIT_REPLACE', 'Unit Replace'), ('NAME_REPLACE', 'Name Replace')], max_length=128)),
('name', models.CharField(default='', max_length=128)),
('description', models.TextField(blank=True, null=True)),
('param_1', models.CharField(blank=True, max_length=128, null=True)),
('param_2', models.CharField(blank=True, max_length=128, null=True)),
('param_3', models.CharField(blank=True, max_length=128, null=True)),
('order', models.IntegerField(default=1000)),
('disabled', models.BooleanField(default=False)),
('updated_at', models.DateTimeField(auto_now=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(django_prometheus.models.ExportModelOperationsMixin('automations'), models.Model, cookbook.models.PermissionModelMixin),
),
migrations.AddField(
model_name='aiprovider',
name='space',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
),
migrations.CreateModel(
name='AiLog',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('function', models.CharField(max_length=64)),
('credit_cost', models.DecimalField(decimal_places=4, max_digits=16)),
('credits_from_balance', models.BooleanField(default=False)),
('input_tokens', models.IntegerField(default=0)),
('output_tokens', models.IntegerField(default=0)),
('start_time', models.DateTimeField(null=True)),
('end_time', models.DateTimeField(null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
('ai_provider', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='cookbook.aiprovider')),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
migrations.CreateModel(
name='Step',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(blank=True, default='', max_length=128)),
('instruction', models.TextField(blank=True)),
('time', models.IntegerField(blank=True, default=0)),
('order', models.IntegerField(default=0)),
('show_as_header', models.BooleanField(default=True)),
('show_ingredients_table', models.BooleanField(default=True)),
('search_vector', django.contrib.postgres.search.SearchVectorField(null=True)),
('ingredients', models.ManyToManyField(blank=True, to='cookbook.ingredient')),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
('step_recipe', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, to='cookbook.recipe')),
],
options={
'ordering': ['order', 'pk'],
},
bases=(django_prometheus.models.ExportModelOperationsMixin('step'), models.Model, cookbook.models.PermissionModelMixin),
),
migrations.AddField(
model_name='recipe',
name='steps',
field=models.ManyToManyField(blank=True, to='cookbook.step'),
),
migrations.CreateModel(
name='Storage',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128)),
('method', models.CharField(choices=[('DB', 'Dropbox'), ('NEXTCLOUD', 'Nextcloud'), ('LOCAL', 'Local')], default='DB', max_length=128)),
('username', models.CharField(blank=True, max_length=128, null=True)),
('password', models.CharField(blank=True, max_length=128, null=True)),
('token', models.CharField(blank=True, max_length=512, null=True)),
('url', models.URLField(blank=True, null=True)),
('path', models.CharField(blank=True, default='', max_length=256)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
migrations.CreateModel(
name='RecipeImport',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128)),
('file_uid', models.CharField(default='', max_length=256)),
('file_path', models.CharField(default='', max_length=512)),
('created_at', models.DateTimeField(auto_now_add=True)),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
('storage', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='cookbook.storage')),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
migrations.AddField(
model_name='recipe',
name='storage',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='cookbook.storage'),
),
migrations.CreateModel(
name='Supermarket',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128, validators=[django.core.validators.MinLengthValidator(1)])),
('description', models.TextField(blank=True, null=True)),
('open_data_slug', models.CharField(blank=True, default=None, max_length=128, null=True)),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
migrations.CreateModel(
name='SupermarketCategory',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128, validators=[django.core.validators.MinLengthValidator(1)])),
('description', models.TextField(blank=True, null=True)),
('open_data_slug', models.CharField(blank=True, default=None, max_length=128, null=True)),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(models.Model, cookbook.models.PermissionModelMixin, cookbook.models.MergeModelMixin),
),
migrations.AddField(
model_name='food',
name='supermarket_category',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='cookbook.supermarketcategory'),
),
migrations.CreateModel(
name='SupermarketCategoryRelation',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('order', models.IntegerField(default=0)),
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='category_to_supermarket', to='cookbook.supermarketcategory')),
('supermarket', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='category_to_supermarket', to='cookbook.supermarket')),
],
options={
'ordering': ('order',),
},
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
migrations.AddField(
model_name='supermarket',
name='categories',
field=models.ManyToManyField(through='cookbook.SupermarketCategoryRelation', to='cookbook.supermarketcategory'),
),
migrations.CreateModel(
name='Sync',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('path', models.CharField(default='', max_length=512)),
('active', models.BooleanField(default=True)),
('last_checked', models.DateTimeField(null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
('storage', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='cookbook.storage')),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
migrations.CreateModel(
name='SyncLog',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('status', models.CharField(max_length=32)),
('msg', models.TextField(default='')),
('created_at', models.DateTimeField(auto_now_add=True)),
('sync', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.sync')),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
migrations.CreateModel(
name='TelegramBot',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('token', models.CharField(max_length=256)),
('name', models.CharField(blank=True, default='', max_length=128)),
('chat_id', models.CharField(blank=True, default='', max_length=128)),
('webhook_token', models.UUIDField(default=uuid.uuid4)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
migrations.CreateModel(
name='Unit',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128, validators=[django.core.validators.MinLengthValidator(1)])),
('plural_name', models.CharField(blank=True, default=None, max_length=128, null=True)),
('description', models.TextField(blank=True, null=True)),
('base_unit', models.TextField(blank=True, default=None, max_length=256, null=True)),
('open_data_slug', models.CharField(blank=True, default=None, max_length=128, null=True)),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(django_prometheus.models.ExportModelOperationsMixin('unit'), models.Model, cookbook.models.PermissionModelMixin, cookbook.models.MergeModelMixin),
),
migrations.CreateModel(
name='ShoppingListEntry',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('amount', models.DecimalField(decimal_places=16, default=0, max_digits=32)),
('order', models.IntegerField(default=0)),
('checked', models.BooleanField(default=False)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('completed_at', models.DateTimeField(blank=True, null=True)),
('delay_until', models.DateTimeField(blank=True, null=True)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('food', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='shopping_entries', to='cookbook.food')),
('ingredient', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.ingredient')),
('list_recipe', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='entries', to='cookbook.shoppinglistrecipe')),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
('unit', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='cookbook.unit')),
],
bases=(django_prometheus.models.ExportModelOperationsMixin('shopping_list_entry'), models.Model, cookbook.models.PermissionModelMixin),
),
migrations.AddField(
model_name='ingredient',
name='unit',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='cookbook.unit'),
),
migrations.AddField(
model_name='food',
name='preferred_shopping_unit',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='preferred_shopping_unit', to='cookbook.unit'),
),
migrations.AddField(
model_name='food',
name='preferred_unit',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='preferred_unit', to='cookbook.unit'),
),
migrations.AddField(
model_name='food',
name='properties_food_unit',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='cookbook.unit'),
),
migrations.CreateModel(
name='UnitConversion',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('base_amount', models.DecimalField(decimal_places=16, default=0, max_digits=32)),
('converted_amount', models.DecimalField(decimal_places=16, default=0, max_digits=32)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('open_data_slug', models.CharField(blank=True, default=None, max_length=128, null=True)),
('base_unit', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='unit_conversion_base_relation', to='cookbook.unit')),
('converted_unit', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='unit_conversion_converted_relation', to='cookbook.unit')),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
('food', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.food')),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(django_prometheus.models.ExportModelOperationsMixin('unit_conversion'), models.Model, cookbook.models.PermissionModelMixin),
),
migrations.CreateModel(
name='UserFile',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128)),
('file', models.FileField(upload_to='files/')),
('file_size_kb', models.IntegerField(blank=True, default=0)),
('created_at', models.DateTimeField(auto_now_add=True)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(django_prometheus.models.ExportModelOperationsMixin('user_files'), models.Model, cookbook.models.PermissionModelMixin),
),
migrations.AddField(
model_name='step',
name='file',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='cookbook.userfile'),
),
migrations.AddField(
model_name='space',
name='custom_space_theme',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='space_theme', to='cookbook.userfile'),
),
migrations.AddField(
model_name='space',
name='image',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='space_image', to='cookbook.userfile'),
),
migrations.AddField(
model_name='space',
name='logo_color_128',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='space_logo_color_128', to='cookbook.userfile'),
),
migrations.AddField(
model_name='space',
name='logo_color_144',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='space_logo_color_144', to='cookbook.userfile'),
),
migrations.AddField(
model_name='space',
name='logo_color_180',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='space_logo_color_180', to='cookbook.userfile'),
),
migrations.AddField(
model_name='space',
name='logo_color_192',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='space_logo_color_192', to='cookbook.userfile'),
),
migrations.AddField(
model_name='space',
name='logo_color_32',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='space_logo_color_32', to='cookbook.userfile'),
),
migrations.AddField(
model_name='space',
name='logo_color_512',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='space_logo_color_512', to='cookbook.userfile'),
),
migrations.AddField(
model_name='space',
name='logo_color_svg',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='space_logo_color_svg', to='cookbook.userfile'),
),
migrations.AddField(
model_name='space',
name='nav_logo',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='space_nav_logo', to='cookbook.userfile'),
),
migrations.CreateModel(
name='UserPreference',
fields=[
('user', annoying.fields.AutoOneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL)),
('theme', models.CharField(choices=[('TANDOOR', 'Tandoor'), ('BOOTSTRAP', 'Bootstrap'), ('DARKLY', 'Darkly'), ('FLATLY', 'Flatly'), ('SUPERHERO', 'Superhero'),
('TANDOOR_DARK', 'Tandoor Dark (INCOMPLETE)')], default='TANDOOR', max_length=128)),
('nav_bg_color', models.CharField(default='#ddbf86', max_length=8)),
('nav_text_color', models.CharField(choices=[('LIGHT', 'Light'), ('DARK', 'Dark')], default='DARK', max_length=16)),
('nav_show_logo', models.BooleanField(default=True)),
('nav_sticky', models.BooleanField(default=True)),
('max_owned_spaces', models.IntegerField(default=100)),
('default_unit', models.CharField(default='g', max_length=32)),
('use_fractions', models.BooleanField(default=False)),
('use_kj', models.BooleanField(default=False)),
('default_page',
models.CharField(choices=[('SEARCH', 'Search'), ('PLAN', 'Meal-Plan'), ('BOOKS', 'Books'), ('SHOPPING', 'Shopping')], default='SEARCH', max_length=64)),
('ingredient_decimals', models.IntegerField(default=2)),
('comments', models.BooleanField(default=True)),
('shopping_auto_sync', models.IntegerField(default=5)),
('mealplan_autoadd_shopping', models.BooleanField(default=False)),
('mealplan_autoexclude_onhand', models.BooleanField(default=True)),
('mealplan_autoinclude_related', models.BooleanField(default=True)),
('shopping_add_onhand', models.BooleanField(default=False)),
('filter_to_supermarket', models.BooleanField(default=False)),
('left_handed', models.BooleanField(default=False)),
('show_step_ingredients', models.BooleanField(default=True)),
('default_delay', models.DecimalField(decimal_places=4, default=4, max_digits=8)),
('shopping_recent_days', models.PositiveIntegerField(default=7)),
('csv_delim', models.CharField(default=',', max_length=2)),
('csv_prefix', models.CharField(blank=True, max_length=10)),
('created_at', models.DateTimeField(auto_now_add=True)),
('image', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='user_image', to='cookbook.userfile')),
('plan_share', models.ManyToManyField(blank=True, related_name='plan_share_default', to=settings.AUTH_USER_MODEL)),
('shopping_share', models.ManyToManyField(blank=True, related_name='shopping_share', to=settings.AUTH_USER_MODEL)),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
migrations.CreateModel(
name='UserSpace',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('active', models.BooleanField(default=False)),
('internal_note', models.TextField(blank=True, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('groups', models.ManyToManyField(to='auth.group')),
('invite_link', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='cookbook.invitelink')),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
migrations.CreateModel(
name='ViewLog',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('recipe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.recipe')),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(django_prometheus.models.ExportModelOperationsMixin('view_log'), models.Model, cookbook.models.PermissionModelMixin),
),
migrations.AddConstraint(
model_name='foodproperty',
constraint=models.UniqueConstraint(fields=('food', 'property'), name='property_unique_food'),
),
migrations.AddConstraint(
model_name='recipebookentry',
constraint=models.UniqueConstraint(fields=('recipe', 'book'), name='rbe_unique_name_per_space'),
),
migrations.AddIndex(
model_name='recipebook',
index=models.Index(fields=['name'], name='cookbook_re_name_94cc63_idx'),
),
migrations.AddConstraint(
model_name='propertytype',
constraint=models.UniqueConstraint(fields=('space', 'name'), name='property_type_unique_name_per_space'),
),
migrations.AddConstraint(
model_name='propertytype',
constraint=models.UniqueConstraint(fields=('space', 'open_data_slug'), name='property_type_unique_open_data_slug_per_space'),
),
migrations.AddConstraint(
model_name='property',
constraint=models.UniqueConstraint(fields=('space', 'property_type', 'open_data_food_slug'), name='property_unique_import_food_per_space'),
),
migrations.AddConstraint(
model_name='mealtype',
constraint=models.UniqueConstraint(fields=('space', 'name', 'created_by'), name='mt_unique_name_per_space'),
),
migrations.AddIndex(
model_name='keyword',
index=models.Index(fields=['id', 'name'], name='cookbook_ke_id_ebc03f_idx'),
),
migrations.AddConstraint(
model_name='keyword',
constraint=models.UniqueConstraint(fields=('space', 'name'), name='kw_unique_name_per_space'),
),
migrations.AddConstraint(
model_name='customfilter',
constraint=models.UniqueConstraint(fields=('space', 'name'), name='cf_unique_name_per_space'),
),
migrations.AddIndex(
model_name='cooklog',
index=models.Index(fields=['id'], name='cookbook_co_id_553a6d_idx'),
),
migrations.AddIndex(
model_name='cooklog',
index=models.Index(fields=['recipe'], name='cookbook_co_recipe__8ec719_idx'),
),
migrations.AddIndex(
model_name='cooklog',
index=models.Index(fields=['-created_at'], name='cookbook_co_created_f6e244_idx'),
),
migrations.AddIndex(
model_name='cooklog',
index=models.Index(fields=['rating'], name='cookbook_co_rating_aa7662_idx'),
),
migrations.AddIndex(
model_name='cooklog',
index=models.Index(fields=['created_by'], name='cookbook_co_created_7ea086_idx'),
),
migrations.AddIndex(
model_name='cooklog',
index=models.Index(fields=['created_by', 'rating'], name='cookbook_co_created_f5ccd7_idx'),
),
migrations.AddIndex(
model_name='recipe',
index=django.contrib.postgres.indexes.GinIndex(fields=['name_search_vector'], name='cookbook_re_name_se_5dbbd5_gin'),
),
migrations.AddIndex(
model_name='recipe',
index=django.contrib.postgres.indexes.GinIndex(fields=['desc_search_vector'], name='cookbook_re_desc_se_fdee30_gin'),
),
migrations.AddIndex(
model_name='recipe',
index=models.Index(fields=['id'], name='cookbook_re_id_b2bdcf_idx'),
),
migrations.AddIndex(
model_name='recipe',
index=models.Index(fields=['name'], name='cookbook_re_name_b8a027_idx'),
),
migrations.AddConstraint(
model_name='supermarketcategory',
constraint=models.UniqueConstraint(fields=('space', 'name'), name='smc_unique_name_per_space'),
),
migrations.AddConstraint(
model_name='supermarketcategory',
constraint=models.UniqueConstraint(fields=('space', 'open_data_slug'), name='supermarket_category_unique_open_data_slug_per_space'),
),
migrations.AddConstraint(
model_name='supermarketcategoryrelation',
constraint=models.UniqueConstraint(fields=('supermarket', 'category'), name='unique_sm_category_relation'),
),
migrations.AddConstraint(
model_name='supermarket',
constraint=models.UniqueConstraint(fields=('space', 'name'), name='sm_unique_name_per_space'),
),
migrations.AddConstraint(
model_name='supermarket',
constraint=models.UniqueConstraint(fields=('space', 'open_data_slug'), name='supermarket_unique_open_data_slug_per_space'),
),
migrations.AddConstraint(
model_name='unit',
constraint=models.UniqueConstraint(fields=('space', 'name'), name='u_unique_name_per_space'),
),
migrations.AddConstraint(
model_name='unit',
constraint=models.UniqueConstraint(fields=('space', 'open_data_slug'), name='unit_unique_open_data_slug_per_space'),
),
migrations.AddIndex(
model_name='ingredient',
index=models.Index(fields=['id'], name='cookbook_in_id_2c1f57_idx'),
),
migrations.AddIndex(
model_name='food',
index=models.Index(fields=['id'], name='cookbook_fo_id_3c379b_idx'),
),
migrations.AddIndex(
model_name='food',
index=models.Index(fields=['name'], name='cookbook_fo_name_c848b6_idx'),
),
migrations.AddConstraint(
model_name='food',
constraint=models.UniqueConstraint(fields=('space', 'name'), name='f_unique_name_per_space'),
),
migrations.AddConstraint(
model_name='food',
constraint=models.UniqueConstraint(fields=('space', 'open_data_slug'), name='food_unique_open_data_slug_per_space'),
),
migrations.AddConstraint(
model_name='unitconversion',
constraint=models.UniqueConstraint(fields=('space', 'base_unit', 'converted_unit', 'food'), name='f_unique_conversion_per_space'),
),
migrations.AddConstraint(
model_name='unitconversion',
constraint=models.UniqueConstraint(fields=('space', 'open_data_slug'), name='unit_conversion_unique_open_data_slug_per_space'),
),
migrations.AddIndex(
model_name='step',
index=django.contrib.postgres.indexes.GinIndex(fields=['search_vector'], name='cookbook_st_search__2ef7fa_gin'),
),
migrations.AddIndex(
model_name='viewlog',
index=models.Index(fields=['recipe'], name='cookbook_vi_recipe__ce995d_idx'),
),
migrations.AddIndex(
model_name='viewlog',
index=models.Index(fields=['-created_at'], name='cookbook_vi_created_bd2b5f_idx'),
),
migrations.AddIndex(
model_name='viewlog',
index=models.Index(fields=['created_by'], name='cookbook_vi_created_f9385c_idx'),
),
migrations.AddIndex(
model_name='viewlog',
index=models.Index(fields=['recipe', '-created_at', 'created_by'], name='cookbook_vi_recipe__1b051f_idx'),
),
migrations.RunPython(create_fields),
]
================================================
FILE: cookbook/migrations/0002_auto_20191119_2035.py
================================================
# Generated by Django 2.2.7 on 2019-11-19 19:35
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0001_initial'),
]
operations = [
migrations.RenameField(
model_name='recipeingredients',
old_name='ingredient',
new_name='name',
),
]
================================================
FILE: cookbook/migrations/0003_enable_pgtrm.py
================================================
from django.contrib.postgres.operations import TrigramExtension
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0002_auto_20191119_2035'),
]
operations = [
TrigramExtension(),
]
================================================
FILE: cookbook/migrations/0004_storage_created_by.py
================================================
# Generated by Django 3.0 on 2019-12-09 10:30
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0003_enable_pgtrm'),
]
operations = [
migrations.AddField(
model_name='storage',
name='created_by',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
preserve_default=False,
),
]
================================================
FILE: cookbook/migrations/0005_recipebook_recipebookentry.py
================================================
# Generated by Django 2.2.9 on 2019-12-24 11:14
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0004_storage_created_by'),
]
operations = [
migrations.CreateModel(
name='RecipeBook',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='RecipeBookEntry',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('book', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.RecipeBook')),
('recipe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.Recipe')),
],
),
]
================================================
FILE: cookbook/migrations/0006_recipe_image.py
================================================
# Generated by Django 3.0.1 on 2019-12-25 15:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0005_recipebook_recipebookentry'),
]
operations = [
migrations.AddField(
model_name='recipe',
name='image',
field=models.ImageField(blank=True, null=True, upload_to='recipes/'),
),
]
================================================
FILE: cookbook/migrations/0007_auto_20191226_0852.py
================================================
# Generated by Django 3.0.1 on 2019-12-26 07:52
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0006_recipe_image'),
]
operations = [
migrations.RenameField(
model_name='recipe',
old_name='time',
new_name='working_time',
),
migrations.AddField(
model_name='recipe',
name='waiting_time',
field=models.IntegerField(default=0),
),
]
================================================
FILE: cookbook/migrations/0008_mealplan.py
================================================
# Generated by Django 3.0.2 on 2020-01-17 14:55
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0007_auto_20191226_0852'),
]
operations = [
migrations.CreateModel(
name='MealPlan',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('meal', models.CharField(choices=[('BREAKFAST', 'Breakfast'), ('LUNCH', 'Lunch'), ('DINNER', 'Dinner'), ('OTHER', 'Other')], default='BREAKFAST', max_length=128)),
('note', models.TextField(blank=True)),
('date', models.DateField()),
('recipe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.Recipe')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]
================================================
FILE: cookbook/migrations/0009_auto_20200130_1056.py
================================================
# Generated by Django 3.0.2 on 2020-01-30 09:56
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0008_mealplan'),
]
operations = [
migrations.CreateModel(
name='Unit',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128, unique=True)),
('description', models.TextField(blank=True, null=True)),
],
),
migrations.AddField(
model_name='recipeingredients',
name='unit_key',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='cookbook.Unit'),
),
]
================================================
FILE: cookbook/migrations/0010_auto_20200130_1059.py
================================================
# Generated by Django 3.0.2 on 2020-01-30 09:59
from django.db import migrations
from django_scopes import scopes_disabled
def migrate_ingredient_units(apps, schema_editor):
with scopes_disabled():
Unit = apps.get_model('cookbook', 'Unit')
RecipeIngredients = apps.get_model('cookbook', 'RecipeIngredients')
for u in RecipeIngredients.objects.values('unit').distinct():
unit = Unit()
unit.name = u['unit']
unit.save()
for i in RecipeIngredients.objects.all():
i.unit_key = Unit.objects.get(name=i.unit)
i.save()
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0009_auto_20200130_1056'),
]
operations = [
migrations.RunPython(migrate_ingredient_units),
]
================================================
FILE: cookbook/migrations/0011_remove_recipeingredients_unit.py
================================================
# Generated by Django 3.0.2 on 2020-01-30 10:16
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0010_auto_20200130_1059'),
]
operations = [
migrations.RemoveField(
model_name='recipeingredients',
name='unit',
),
]
================================================
FILE: cookbook/migrations/0012_auto_20200130_1116.py
================================================
# Generated by Django 3.0.2 on 2020-01-30 10:16
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0011_remove_recipeingredients_unit'),
]
operations = [
migrations.RenameField(
model_name='recipeingredients',
old_name='unit_key',
new_name='unit',
),
]
================================================
FILE: cookbook/migrations/0013_userpreference.py
================================================
# Generated by Django 3.0.2 on 2020-02-13 22:15
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0012_auto_20200130_1116'),
]
operations = [
migrations.CreateModel(
name='UserPreference',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('theme', models.CharField(choices=[('BOOTSTRAP', 'Bootstrap'), ('DARKLY', 'Darkly'), ('FLATLY', 'Flatly')], default='BOOTSTRAP', max_length=128)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]
================================================
FILE: cookbook/migrations/0014_auto_20200213_2332.py
================================================
# Generated by Django 3.0.2 on 2020-02-13 22:32
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0013_userpreference'),
]
operations = [
migrations.AlterField(
model_name='userpreference',
name='theme',
field=models.CharField(choices=[('BOOTSTRAP', 'Bootstrap'), ('DARKLY', 'Darkly'), ('FLATLY', 'Flatly'), ('SUPERHERO', 'Superhero')], default='BOOTSTRAP', max_length=128),
),
migrations.AlterField(
model_name='userpreference',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, unique=True),
),
]
================================================
FILE: cookbook/migrations/0015_auto_20200213_2334.py
================================================
# Generated by Django 3.0.2 on 2020-02-13 22:34
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0014_auto_20200213_2332'),
]
operations = [
migrations.AlterField(
model_name='userpreference',
name='user',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
]
================================================
FILE: cookbook/migrations/0016_auto_20200213_2335.py
================================================
# Generated by Django 3.0.2 on 2020-02-13 22:35
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0015_auto_20200213_2334'),
]
operations = [
migrations.RemoveField(
model_name='userpreference',
name='id',
),
migrations.AlterField(
model_name='userpreference',
name='user',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL),
),
]
================================================
FILE: cookbook/migrations/0017_auto_20200216_2257.py
================================================
# Generated by Django 3.0.2 on 2020-02-16 21:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0016_auto_20200213_2335'),
]
operations = [
migrations.AlterField(
model_name='userpreference',
name='theme',
field=models.CharField(choices=[('BOOTSTRAP', 'Bootstrap'), ('DARKLY', 'Darkly'), ('FLATLY', 'Flatly'), ('SUPERHERO', 'Superhero')], default='FLATLY', max_length=128),
),
]
================================================
FILE: cookbook/migrations/0018_auto_20200216_2303.py
================================================
# Generated by Django 3.0.2 on 2020-02-16 22:03
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0017_auto_20200216_2257'),
]
operations = [
migrations.RenameModel(
old_name='RecipeIngredients',
new_name='RecipeIngredient',
),
]
================================================
FILE: cookbook/migrations/0019_ingredient.py
================================================
# Generated by Django 3.0.2 on 2020-02-16 22:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0018_auto_20200216_2303'),
]
operations = [
migrations.CreateModel(
name='Ingredient',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128, unique=True)),
],
),
]
================================================
FILE: cookbook/migrations/0020_recipeingredient_ingredient.py
================================================
# Generated by Django 3.0.2 on 2020-02-16 22:08
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0019_ingredient'),
]
operations = [
migrations.AddField(
model_name='recipeingredient',
name='ingredient',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='cookbook.Ingredient'),
),
]
================================================
FILE: cookbook/migrations/0021_auto_20200216_2309.py
================================================
# Generated by Django 3.0.2 on 2020-02-16 22:09
from django.db import migrations
from django_scopes import scopes_disabled
def migrate_ingredients(apps, schema_editor):
with scopes_disabled():
Ingredient = apps.get_model('cookbook', 'Ingredient')
RecipeIngredient = apps.get_model('cookbook', 'RecipeIngredient')
for u in RecipeIngredient.objects.values('name').distinct():
ingredient = Ingredient()
ingredient.name = u['name']
ingredient.save()
for i in RecipeIngredient.objects.all():
i.ingredient = Ingredient.objects.get(name=i.name)
i.save()
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0020_recipeingredient_ingredient'),
]
operations = [
migrations.RunPython(migrate_ingredients),
]
================================================
FILE: cookbook/migrations/0022_remove_recipeingredient_name.py
================================================
# Generated by Django 3.0.2 on 2020-02-16 22:11
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0021_auto_20200216_2309'),
]
operations = [
migrations.RemoveField(
model_name='recipeingredient',
name='name',
),
]
================================================
FILE: cookbook/migrations/0023_auto_20200216_2311.py
================================================
# Generated by Django 3.0.2 on 2020-02-16 22:11
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0022_remove_recipeingredient_name'),
]
operations = [
migrations.RenameField(
model_name='recipeingredient',
old_name='ingredient',
new_name='name',
),
]
================================================
FILE: cookbook/migrations/0024_auto_20200216_2313.py
================================================
# Generated by Django 3.0.2 on 2020-02-16 22:13
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0023_auto_20200216_2311'),
]
operations = [
migrations.RenameField(
model_name='recipeingredient',
old_name='name',
new_name='ingredient',
),
]
================================================
FILE: cookbook/migrations/0025_userpreference_nav_color.py
================================================
# Generated by Django 3.0.2 on 2020-02-16 23:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0024_auto_20200216_2313'),
]
operations = [
migrations.AddField(
model_name='userpreference',
name='nav_color',
field=models.CharField(choices=[('PRIMARY', 'Primary'), ('SECONDARY', 'Secondary'), ('SUCCESS', 'Success'), ('INFO', 'Info'), ('WARNING', 'Warning'), ('DANGER', 'Danger'), ('LIGHT', 'Light'), ('DARK', 'Dark')], default='PRIMARY', max_length=128),
),
]
================================================
FILE: cookbook/migrations/0026_auto_20200219_1605.py
================================================
# Generated by Django 3.0.2 on 2020-02-19 15:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0025_userpreference_nav_color'),
]
operations = [
migrations.AddField(
model_name='recipe',
name='cors_link',
field=models.CharField(blank=True, max_length=1024, null=True),
),
migrations.AlterField(
model_name='recipe',
name='link',
field=models.CharField(blank=True, max_length=512, null=True),
),
]
================================================
FILE: cookbook/migrations/0027_ingredient_recipe.py
================================================
# Generated by Django 3.0.4 on 2020-03-17 17:31
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0026_auto_20200219_1605'),
]
operations = [
migrations.AddField(
model_name='ingredient',
name='recipe',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='cookbook.Recipe'),
),
]
================================================
FILE: cookbook/migrations/0028_auto_20200317_1901.py
================================================
# Generated by Django 3.0.4 on 2020-03-17 18:01
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0027_ingredient_recipe'),
]
operations = [
migrations.AlterField(
model_name='recipeingredient',
name='ingredient',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='cookbook.Ingredient'),
),
]
================================================
FILE: cookbook/migrations/0029_auto_20200317_1901.py
================================================
# Generated by Django 3.0.4 on 2020-03-17 18:01
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0028_auto_20200317_1901'),
]
operations = [
migrations.AlterField(
model_name='recipeingredient',
name='unit',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='cookbook.Unit'),
),
]
================================================
FILE: cookbook/migrations/0030_recipeingredient_note.py
================================================
# Generated by Django 3.0.4 on 2020-03-17 18:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0029_auto_20200317_1901'),
]
operations = [
migrations.AddField(
model_name='recipeingredient',
name='note',
field=models.CharField(blank=True, max_length=64, null=True),
),
]
================================================
FILE: cookbook/migrations/0031_auto_20200407_1841.py
================================================
# Generated by Django 3.0.4 on 2020-04-07 16:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0030_recipeingredient_note'),
]
operations = [
migrations.AlterField(
model_name='keyword',
name='icon',
field=models.CharField(blank=True, max_length=16, null=True),
),
]
================================================
FILE: cookbook/migrations/0032_userpreference_default_unit.py
================================================
# Generated by Django 3.0.4 on 2020-04-13 20:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0031_auto_20200407_1841'),
]
operations = [
migrations.AddField(
model_name='userpreference',
name='default_unit',
field=models.CharField(default='g', max_length=32),
),
]
================================================
FILE: cookbook/migrations/0033_userpreference_default_page.py
================================================
# Generated by Django 3.0.4 on 2020-04-13 20:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0032_userpreference_default_unit'),
]
operations = [
migrations.AddField(
model_name='userpreference',
name='default_page',
field=models.CharField(choices=[('SEARCH', 'Search'), ('PLAN', 'Meal-Plan')], default='SEARCH', max_length=64),
),
]
================================================
FILE: cookbook/migrations/0034_auto_20200426_1614.py
================================================
# Generated by Django 3.0.5 on 2020-04-26 14:14
from django.db import migrations
from django_scopes import scopes_disabled
def apply_migration(apps, schema_editor):
with scopes_disabled():
Group = apps.get_model('auth', 'Group')
Group.objects.bulk_create([
Group(name=u'guest'),
Group(name=u'user'),
Group(name=u'admin'),
])
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0033_userpreference_default_page'),
]
operations = [
migrations.RunPython(apply_migration)
]
================================================
FILE: cookbook/migrations/0035_auto_20200427_1637.py
================================================
# Generated by Django 3.0.5 on 2020-04-27 14:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0034_auto_20200426_1614'),
]
operations = [
migrations.RenameField(
model_name='mealplan',
old_name='user',
new_name='created_by',
),
migrations.RenameField(
model_name='recipebook',
old_name='user',
new_name='created_by',
),
migrations.AlterField(
model_name='userpreference',
name='default_page',
field=models.CharField(choices=[('SEARCH', 'Search'), ('PLAN', 'Meal-Plan'), ('BOOKS', 'Books')], default='SEARCH', max_length=64),
),
]
================================================
FILE: cookbook/migrations/0036_auto_20200427_1800.py
================================================
# Generated by Django 3.0.5 on 2020-04-27 16:00
from django.db import migrations
from django_scopes import scopes_disabled
def apply_migration(apps, schema_editor):
with scopes_disabled():
Group = apps.get_model('auth', 'Group')
User = apps.get_model('auth', 'User')
for u in User.objects.all():
if u.groups.count() < 1:
u.groups.add(Group.objects.get(name='admin'))
u.save()
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0035_auto_20200427_1637'),
]
operations = [
migrations.RunPython(apply_migration)
]
================================================
FILE: cookbook/migrations/0037_userpreference_search_style.py
================================================
# Generated by Django 3.0.5 on 2020-05-02 10:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0036_auto_20200427_1800'),
]
operations = [
migrations.AddField(
model_name='userpreference',
name='search_style',
field=models.CharField(choices=[('SMALL', 'Small'), ('LARGE', 'Large')], default='LARGE', max_length=64),
),
]
================================================
FILE: cookbook/migrations/0038_auto_20200502_1259.py
================================================
# Generated by Django 3.0.5 on 2020-05-02 10:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0037_userpreference_search_style'),
]
operations = [
migrations.AddField(
model_name='recipebook',
name='description',
field=models.TextField(blank=True),
),
migrations.AddField(
model_name='recipebook',
name='icon',
field=models.CharField(blank=True, max_length=16, null=True),
),
]
================================================
FILE: cookbook/migrations/0039_recipebook_shared.py
================================================
# Generated by Django 3.0.5 on 2020-05-02 12:04
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0038_auto_20200502_1259'),
]
operations = [
migrations.AddField(
model_name='recipebook',
name='shared',
field=models.ManyToManyField(blank=True, related_name='shared_with', to=settings.AUTH_USER_MODEL),
),
]
================================================
FILE: cookbook/migrations/0040_auto_20200502_1433.py
================================================
# Generated by Django 3.0.5 on 2020-05-02 12:33
import annoying.fields
from django.conf import settings
from django.db import migrations
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0039_recipebook_shared'),
]
operations = [
migrations.AlterField(
model_name='userpreference',
name='user',
field=annoying.fields.AutoOneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL),
),
]
================================================
FILE: cookbook/migrations/0041_auto_20200502_1446.py
================================================
# Generated by Django 3.0.5 on 2020-05-02 12:46
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0040_auto_20200502_1433'),
]
operations = [
migrations.AddField(
model_name='mealplan',
name='title',
field=models.CharField(blank=True, default='', max_length=64),
),
migrations.AlterField(
model_name='mealplan',
name='recipe',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.Recipe'),
),
]
================================================
FILE: cookbook/migrations/0042_cooklog.py
================================================
# Generated by Django 3.0.5 on 2020-05-02 14:47
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0041_auto_20200502_1446'),
]
operations = [
migrations.CreateModel(
name='CookLog',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('rating', models.IntegerField(null=True)),
('servings', models.IntegerField(default=0)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('recipe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.Recipe')),
],
),
]
================================================
FILE: cookbook/migrations/0043_auto_20200507_2302.py
================================================
# Generated by Django 3.0.5 on 2020-05-07 21:02
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0042_cooklog'),
]
operations = [
migrations.AddField(
model_name='mealplan',
name='shared',
field=models.ManyToManyField(blank=True, related_name='plan_share', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='userpreference',
name='plan_share',
field=models.ManyToManyField(blank=True, related_name='plan_share_default', to=settings.AUTH_USER_MODEL),
),
]
================================================
FILE: cookbook/migrations/0044_viewlog.py
================================================
# Generated by Django 3.0.5 on 2020-05-11 10:21
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0043_auto_20200507_2302'),
]
operations = [
migrations.CreateModel(
name='ViewLog',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('recipe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.Recipe')),
],
),
]
================================================
FILE: cookbook/migrations/0045_userpreference_show_recent.py
================================================
# Generated by Django 3.0.5 on 2020-06-02 08:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0044_viewlog'),
]
operations = [
migrations.AddField(
model_name='userpreference',
name='show_recent',
field=models.BooleanField(default=True),
),
]
================================================
FILE: cookbook/migrations/0046_auto_20200602_1133.py
================================================
# Generated by Django 3.0.5 on 2020-06-02 09:33
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0045_userpreference_show_recent'),
]
operations = [
migrations.CreateModel(
name='MealType',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128)),
('order', models.IntegerField(default=0)),
],
),
migrations.AddField(
model_name='mealplan',
name='meal_type',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.MealType'),
),
]
================================================
FILE: cookbook/migrations/0047_auto_20200602_1133.py
================================================
# Generated by Django 3.0.5 on 2020-06-02 09:33
from django.db import migrations
from django.utils.translation import gettext as _
from django_scopes import scopes_disabled
def migrate_meal_types(apps, schema_editor):
with scopes_disabled():
MealPlan = apps.get_model('cookbook', 'MealPlan')
MealType = apps.get_model('cookbook', 'MealType')
breakfast = MealType.objects.create(
name=_('Breakfast'),
order=0,
)
lunch = MealType.objects.create(
name=_('Lunch'),
order=0,
)
dinner = MealType.objects.create(
name=_('Dinner'),
order=0,
)
other = MealType.objects.create(
name=_('Other'),
order=0,
)
for m in MealPlan.objects.all():
if m.meal == 'BREAKFAST':
m.meal_type = breakfast
if m.meal == 'LUNCH':
m.meal_type = lunch
if m.meal == 'DINNER':
m.meal_type = dinner
if m.meal == 'OTHER':
m.meal_type = other
m.save()
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0046_auto_20200602_1133'),
]
operations = [
migrations.RunPython(migrate_meal_types),
]
================================================
FILE: cookbook/migrations/0048_auto_20200602_1140.py
================================================
# Generated by Django 3.0.5 on 2020-06-02 09:40
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0047_auto_20200602_1133'),
]
operations = [
migrations.RemoveField(
model_name='mealplan',
name='meal',
),
migrations.AlterField(
model_name='mealplan',
name='meal_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.MealType'),
),
]
================================================
FILE: cookbook/migrations/0049_mealtype_created_by.py
================================================
# Generated by Django 3.0.7 on 2020-06-11 13:08
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0048_auto_20200602_1140'),
]
operations = [
migrations.AddField(
model_name='mealtype',
name='created_by',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
]
================================================
FILE: cookbook/migrations/0050_auto_20200611_1509.py
================================================
# Generated by Django 3.0.7 on 2020-06-11 13:09
from django.db import migrations
from django.db.models import Q
from django_scopes import scopes_disabled
def migrate_meal_types(apps, schema_editor):
with scopes_disabled():
MealPlan = apps.get_model('cookbook', 'MealPlan')
MealType = apps.get_model('cookbook', 'MealType')
User = apps.get_model('auth', 'User')
for u in User.objects.all():
for t in MealType.objects.filter(created_by=None).all():
user_type = MealType.objects.create(
name=t.name,
created_by=u,
)
MealPlan.objects.filter(Q(created_by=u) and Q(meal_type=t)).update(meal_type=user_type)
MealType.objects.filter(created_by=None).delete()
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0049_mealtype_created_by'),
]
operations = [
migrations.RunPython(migrate_meal_types),
]
================================================
FILE: cookbook/migrations/0051_auto_20200611_1518.py
================================================
# Generated by Django 3.0.7 on 2020-06-11 13:18
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0050_auto_20200611_1509'),
]
operations = [
migrations.AlterField(
model_name='mealtype',
name='created_by',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
]
================================================
FILE: cookbook/migrations/0052_userpreference_ingredient_decimals.py
================================================
# Generated by Django 3.0.7 on 2020-06-11 20:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0051_auto_20200611_1518'),
]
operations = [
migrations.AddField(
model_name='userpreference',
name='ingredient_decimals',
field=models.IntegerField(default=2),
),
]
================================================
FILE: cookbook/migrations/0053_auto_20200611_2217.py
================================================
# Generated by Django 3.0.7 on 2020-06-11 20:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0052_userpreference_ingredient_decimals'),
]
operations = [
migrations.AlterField(
model_name='recipeingredient',
name='amount',
field=models.DecimalField(decimal_places=16, default=0, max_digits=32),
),
]
================================================
FILE: cookbook/migrations/0054_sharelink.py
================================================
# Generated by Django 3.0.7 on 2020-06-16 08:57
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0053_auto_20200611_2217'),
]
operations = [
migrations.CreateModel(
name='ShareLink',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('uuid', models.UUIDField(default=uuid.UUID('dbbf5150-0795-4305-b9bd-3952dfa2264b'))),
('created_at', models.DateTimeField(auto_now_add=True)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('recipe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.Recipe')),
],
),
]
================================================
FILE: cookbook/migrations/0055_auto_20200616_1236.py
================================================
# Generated by Django 3.0.7 on 2020-06-16 10:36
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0054_sharelink'),
]
operations = [
migrations.AddField(
model_name='userpreference',
name='comments',
field=models.BooleanField(default=True),
),
migrations.AlterField(
model_name='sharelink',
name='uuid',
field=models.UUIDField(default=uuid.UUID('a6e8f192-cc03-4dd4-8a03-58d7ab6b7df7')),
),
]
================================================
FILE: cookbook/migrations/0056_auto_20200625_2118.py
================================================
# Generated by Django 3.0.7 on 2020-06-25 19:18
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
atomic = False
dependencies = [
('cookbook', '0055_auto_20200616_1236'),
]
operations = [
migrations.RenameModel(
old_name='Ingredient',
new_name='Food',
),
migrations.AddField(
model_name='recipe',
name='ingredients',
field=models.ManyToManyField(blank=True, related_name='tmp_ingredients', to='cookbook.RecipeIngredient'),
),
migrations.AlterField(
model_name='sharelink',
name='uuid',
field=models.UUIDField(default=uuid.UUID('a5f12617-9e4b-41e3-87ee-49db96090974')),
),
]
================================================
FILE: cookbook/migrations/0056_auto_20200625_2157.py
================================================
# Generated by Django 3.0.7 on 2020-06-25 19:57
from django.db import migrations, models
import uuid
from django_scopes import scopes_disabled
def invalidate_shares(apps, schema_editor):
with scopes_disabled():
ShareLink = apps.get_model('cookbook', 'ShareLink')
ShareLink.objects.all().delete()
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0055_auto_20200616_1236'),
]
operations = [
migrations.AlterField(
model_name='sharelink',
name='uuid',
field=models.UUIDField(default=uuid.uuid4),
),
migrations.RunPython(invalidate_shares)
]
================================================
FILE: cookbook/migrations/0057_auto_20200625_2127.py
================================================
# Generated by Django 3.0.7 on 2020-06-25 19:27
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0056_auto_20200625_2118'),
]
operations = [
migrations.RenameField(
model_name='recipeingredient',
old_name='ingredient',
new_name='food',
),
migrations.AlterField(
model_name='sharelink',
name='uuid',
field=models.UUIDField(default=uuid.UUID('4a604ec8-c158-4011-ab5d-ddad7604c1e3')),
),
]
================================================
FILE: cookbook/migrations/0058_auto_20200625_2128.py
================================================
# Generated by Django 3.0.7 on 2020-06-25 19:28
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
atomic = False
dependencies = [
('cookbook', '0057_auto_20200625_2127'),
]
operations = [
migrations.RenameModel(
old_name='RecipeIngredient',
new_name='Ingredient',
),
migrations.AlterField(
model_name='sharelink',
name='uuid',
field=models.UUIDField(default=uuid.UUID('27328011-54fb-46fa-8846-424d5828d858')),
),
]
================================================
FILE: cookbook/migrations/0059_auto_20200625_2137.py
================================================
# Generated by Django 3.0.7 on 2020-06-25 19:37
from django.db import migrations
from django_scopes import scopes_disabled
def migrate_ingredients(apps, schema_editor):
with scopes_disabled():
Recipe = apps.get_model('cookbook', 'Recipe')
Ingredient = apps.get_model('cookbook', 'Ingredient')
for r in Recipe.objects.all():
for i in Ingredient.objects.filter(recipe=r).all():
r.ingredients.add(i)
r.save()
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0058_auto_20200625_2128'),
]
operations = [
migrations.RunPython(migrate_ingredients),
]
================================================
FILE: cookbook/migrations/0060_auto_20200625_2144.py
================================================
# Generated by Django 3.0.7 on 2020-06-25 19:44
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0059_auto_20200625_2137'),
]
operations = [
migrations.RemoveField(
model_name='ingredient',
name='recipe',
),
migrations.AlterField(
model_name='sharelink',
name='uuid',
field=models.UUIDField(default=uuid.UUID('a7a91b2e-ad33-4159-a35e-828a5244ede9')),
),
]
================================================
FILE: cookbook/migrations/0061_merge_20200625_2209.py
================================================
# Generated by Django 3.0.7 on 2020-06-25 20:09
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0056_auto_20200625_2157'),
('cookbook', '0060_auto_20200625_2144'),
]
operations = [
]
================================================
FILE: cookbook/migrations/0062_auto_20200625_2219.py
================================================
# Generated by Django 3.0.7 on 2020-06-25 20:19
from django.db import migrations, models
from django_scopes import scopes_disabled
def create_default_step(apps, schema_editor):
with scopes_disabled():
Recipe = apps.get_model('cookbook', 'Recipe')
Step = apps.get_model('cookbook', 'Step')
for r in Recipe.objects.filter(internal=True).all():
s = Step.objects.create(
instruction=r.instructions
)
for i in r.ingredients.all():
s.ingredients.add(i)
s.save()
r.steps.add(s)
r.save()
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0061_merge_20200625_2209'),
]
operations = [
migrations.AlterField(
model_name='recipe',
name='ingredients',
field=models.ManyToManyField(blank=True, to='cookbook.Ingredient'),
),
migrations.CreateModel(
name='Step',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('kind', models.CharField(choices=[('TEXT', 'Text')], default='TEXT', max_length=16)),
('instruction', models.TextField(blank=True)),
('ingredients', models.ManyToManyField(blank=True, to='cookbook.Ingredient')),
],
),
migrations.AddField(
model_name='recipe',
name='steps',
field=models.ManyToManyField(blank=True, to='cookbook.Step'),
),
migrations.RunPython(create_default_step)
]
================================================
FILE: cookbook/migrations/0063_auto_20200625_2230.py
================================================
# Generated by Django 3.0.7 on 2020-06-25 20:30
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0062_auto_20200625_2219'),
]
operations = [
migrations.RemoveField(
model_name='recipe',
name='ingredients',
),
migrations.RemoveField(
model_name='recipe',
name='instructions',
),
]
================================================
FILE: cookbook/migrations/0064_auto_20200625_2329.py
================================================
# Generated by Django 3.0.7 on 2020-06-25 21:29
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0063_auto_20200625_2230'),
]
operations = [
migrations.AlterField(
model_name='recipe',
name='file_path',
field=models.CharField(blank=True, default='', max_length=512),
),
migrations.AlterField(
model_name='recipe',
name='file_uid',
field=models.CharField(blank=True, default='', max_length=256),
),
]
================================================
FILE: cookbook/migrations/0065_auto_20200626_1444.py
================================================
# Generated by Django 3.0.7 on 2020-06-26 12:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0064_auto_20200625_2329'),
]
operations = [
migrations.AddField(
model_name='ingredient',
name='order',
field=models.IntegerField(default=0),
),
migrations.AddField(
model_name='step',
name='order',
field=models.IntegerField(default=0),
),
]
================================================
FILE: cookbook/migrations/0066_auto_20200626_1455.py
================================================
# Generated by Django 3.0.7 on 2020-06-26 12:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0065_auto_20200626_1444'),
]
operations = [
migrations.AlterModelOptions(
name='ingredient',
options={'ordering': ['order', 'pk']},
),
migrations.AlterModelOptions(
name='step',
options={'ordering': ['order', 'pk']},
),
migrations.AddField(
model_name='step',
name='name',
field=models.CharField(blank=True, default='', max_length=128),
),
]
================================================
FILE: cookbook/migrations/0067_auto_20200629_1508.py
================================================
# Generated by Django 3.0.7 on 2020-06-29 13:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0066_auto_20200626_1455'),
]
operations = [
migrations.AlterField(
model_name='ingredient',
name='note',
field=models.CharField(blank=True, max_length=256, null=True),
),
]
================================================
FILE: cookbook/migrations/0068_auto_20200629_2127.py
================================================
# Generated by Django 3.0.7 on 2020-06-29 19:27
from django.db import migrations, models
import django.db.models.deletion
from django_scopes import scopes_disabled
def convert_old_specials(apps, schema_editor):
with scopes_disabled():
Ingredient = apps.get_model('cookbook', 'Ingredient')
Food = apps.get_model('cookbook', 'Food')
Unit = apps.get_model('cookbook', 'Unit')
for i in Ingredient.objects.all():
if i.amount == 0:
i.no_amount = True
if i.unit.name == 'Special:Header':
i.header = True
i.unit = None
i.food = None
i.save()
try:
Unit.objects.filter(name='Special:Header').delete()
Food.objects.filter(name='Header').delete()
except Exception:
pass
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0067_auto_20200629_1508'),
]
operations = [
migrations.AddField(
model_name='ingredient',
name='header',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='ingredient',
name='no_amount',
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name='ingredient',
name='food',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='cookbook.Food'),
),
migrations.AlterField(
model_name='ingredient',
name='unit',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='cookbook.Unit'),
),
migrations.RunPython(convert_old_specials)
]
================================================
FILE: cookbook/migrations/0069_auto_20200629_2134.py
================================================
# Generated by Django 3.0.7 on 2020-06-29 19:34
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0068_auto_20200629_2127'),
]
operations = [
migrations.RenameField(
model_name='ingredient',
old_name='header',
new_name='is_header',
),
]
================================================
FILE: cookbook/migrations/0070_auto_20200701_2007.py
================================================
# Generated by Django 3.0.7 on 2020-07-01 18:07
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0069_auto_20200629_2134'),
]
operations = [
migrations.AddField(
model_name='step',
name='time',
field=models.IntegerField(blank=True, default=0),
),
migrations.AlterField(
model_name='step',
name='kind',
field=models.CharField(choices=[('TEXT', 'Text'), ('TIME', 'Time')], default='TEXT', max_length=16),
),
]
================================================
FILE: cookbook/migrations/0071_auto_20200701_2048.py
================================================
# Generated by Django 3.0.7 on 2020-07-01 18:48
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0070_auto_20200701_2007'),
]
operations = [
migrations.RenameField(
model_name='step',
old_name='kind',
new_name='type',
),
]
================================================
FILE: cookbook/migrations/0072_step_show_as_header.py
================================================
# Generated by Django 3.0.7 on 2020-07-02 10:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0071_auto_20200701_2048'),
]
operations = [
migrations.AddField(
model_name='step',
name='show_as_header',
field=models.BooleanField(default=True),
),
]
================================================
FILE: cookbook/migrations/0073_auto_20200708_2311.py
================================================
# Generated by Django 3.0.7 on 2020-07-08 21:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0072_step_show_as_header'),
]
operations = [
migrations.AlterField(
model_name='sync',
name='last_checked',
field=models.DateTimeField(null=True),
),
]
================================================
FILE: cookbook/migrations/0074_remove_keyword_created_by.py
================================================
# Generated by Django 3.0.7 on 2020-07-09 19:54
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0073_auto_20200708_2311'),
]
operations = [
migrations.RemoveField(
model_name='keyword',
name='created_by',
),
]
================================================
FILE: cookbook/migrations/0075_shoppinglist_shoppinglistentry_shoppinglistrecipe.py
================================================
# Generated by Django 3.0.7 on 2020-08-11 10:14
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0074_remove_keyword_created_by'),
]
operations = [
migrations.CreateModel(
name='ShoppingListRecipe',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('multiplier', models.IntegerField(default=1)),
('recipe', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.Recipe')),
],
),
migrations.CreateModel(
name='ShoppingListEntry',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('amount', models.IntegerField(default=1)),
('order', models.IntegerField(default=0)),
('checked', models.BooleanField(default=False)),
('food', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.Food')),
('list_recipe', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.ShoppingListRecipe')),
('unit', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.Unit')),
],
),
migrations.CreateModel(
name='ShoppingList',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('uuid', models.UUIDField(default=uuid.uuid4)),
('note', models.TextField(blank=True, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('recipes', models.ManyToManyField(blank=True, to='cookbook.ShoppingListRecipe')),
('shared', models.ManyToManyField(blank=True, related_name='list_share', to=settings.AUTH_USER_MODEL)),
],
),
]
================================================
FILE: cookbook/migrations/0076_shoppinglist_entries.py
================================================
# Generated by Django 3.0.7 on 2020-08-26 18:46
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0075_shoppinglist_shoppinglistentry_shoppinglistrecipe'),
]
operations = [
migrations.AddField(
model_name='shoppinglist',
name='entries',
field=models.ManyToManyField(blank=True, to='cookbook.ShoppingListEntry'),
),
]
================================================
FILE: cookbook/migrations/0077_invitelink.py
================================================
# Generated by Django 3.0.7 on 2020-09-01 11:31
import datetime
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0076_shoppinglist_entries'),
]
operations = [
migrations.CreateModel(
name='InviteLink',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('uuid', models.UUIDField(default=uuid.uuid4)),
('username', models.CharField(blank=True, max_length=64)),
('valid_until', models.DateField(default=datetime.date(2020, 9, 15))),
('created_at', models.DateTimeField(auto_now_add=True)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]
================================================
FILE: cookbook/migrations/0078_invitelink_used_by.py
================================================
# Generated by Django 3.0.7 on 2020-09-01 11:39
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0077_invitelink'),
]
operations = [
migrations.AddField(
model_name='invitelink',
name='used_by',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='used_by', to=settings.AUTH_USER_MODEL),
),
]
================================================
FILE: cookbook/migrations/0079_invitelink_group.py
================================================
# Generated by Django 3.0.7 on 2020-09-01 12:54
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('auth', '0011_update_proxy_permissions'),
('cookbook', '0078_invitelink_used_by'),
]
operations = [
migrations.AddField(
model_name='invitelink',
name='group',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='auth.Group'),
preserve_default=False,
),
]
================================================
FILE: cookbook/migrations/0080_auto_20200921_2331.py
================================================
# Generated by Django 3.0.7 on 2020-09-21 21:31
import datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0079_invitelink_group'),
]
operations = [
migrations.AddField(
model_name='userpreference',
name='shopping_auto_sync',
field=models.BooleanField(default=True),
),
migrations.AlterField(
model_name='invitelink',
name='valid_until',
field=models.DateField(default=datetime.date(2020, 10, 5)),
),
]
================================================
FILE: cookbook/migrations/0081_auto_20200921_2349.py
================================================
# Generated by Django 3.0.7 on 2020-09-21 21:49
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0080_auto_20200921_2331'),
]
operations = [
migrations.AlterField(
model_name='userpreference',
name='shopping_auto_sync',
field=models.IntegerField(default=5),
),
]
================================================
FILE: cookbook/migrations/0082_auto_20200922_1143.py
================================================
# Generated by Django 3.0.7 on 2020-09-22 09:43
import datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0081_auto_20200921_2349'),
]
operations = [
migrations.AlterField(
model_name='invitelink',
name='valid_until',
field=models.DateField(default=datetime.date(2020, 10, 6)),
),
migrations.AlterField(
model_name='shoppinglistentry',
name='amount',
field=models.DecimalField(decimal_places=16, default=0, max_digits=32),
),
]
================================================
FILE: cookbook/migrations/0083_space.py
================================================
# Generated by Django 3.0.7 on 2020-09-22 10:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0082_auto_20200922_1143'),
]
operations = [
migrations.CreateModel(
name='Space',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(default='Default', max_length=128)),
('message', models.CharField(default='', max_length=512)),
],
),
]
================================================
FILE: cookbook/migrations/0084_auto_20200922_1233.py
================================================
# Generated by Django 3.0.7 on 2020-09-22 10:33
from django.db import migrations
def create_default_space(apps, schema_editor):
# Space = apps.get_model('cookbook', 'Space')
# Space.objects.create(
# name='Default',
# message=''
# )
pass # Beginning with the multi space tenancy version (~something around 1.3) a default space is no longer needed as the first user can create it after setup
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0083_space'),
]
operations = [
migrations.RunPython(create_default_space),
]
================================================
FILE: cookbook/migrations/0085_auto_20200922_1235.py
================================================
# Generated by Django 3.0.7 on 2020-09-22 10:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0084_auto_20200922_1233'),
]
operations = [
migrations.AlterField(
model_name='space',
name='message',
field=models.CharField(blank=True, default='', max_length=512),
),
]
================================================
FILE: cookbook/migrations/0086_auto_20200929_1143.py
================================================
# Generated by Django 3.0.7 on 2020-09-29 09:43
import datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0085_auto_20200922_1235'),
]
operations = [
migrations.AddField(
model_name='mealplan',
name='recipe_multiplier',
field=models.IntegerField(default=1),
),
migrations.AlterField(
model_name='invitelink',
name='valid_until',
field=models.DateField(default=datetime.date(2020, 10, 13)),
),
]
================================================
FILE: cookbook/migrations/0087_auto_20200929_1152.py
================================================
# Generated by Django 3.0.7 on 2020-09-29 09:52
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0086_auto_20200929_1143'),
]
operations = [
migrations.AlterField(
model_name='mealplan',
name='recipe_multiplier',
field=models.DecimalField(decimal_places=4, default=1, max_digits=8),
),
migrations.AlterField(
model_name='shoppinglistrecipe',
name='multiplier',
field=models.DecimalField(decimal_places=4, default=1, max_digits=8),
),
]
================================================
FILE: cookbook/migrations/0088_shoppinglist_finished.py
================================================
# Generated by Django 3.1.1 on 2020-09-29 11:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0087_auto_20200929_1152'),
]
operations = [
migrations.AddField(
model_name='shoppinglist',
name='finished',
field=models.BooleanField(default=False),
),
]
================================================
FILE: cookbook/migrations/0089_auto_20201117_2222.py
================================================
# Generated by Django 3.1.1 on 2020-11-17 21:22
import datetime
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0088_shoppinglist_finished'),
]
operations = [
migrations.CreateModel(
name='NutritionInformation',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('fats', models.DecimalField(decimal_places=16, default=0, max_digits=32)),
('carbohydrates', models.DecimalField(decimal_places=16, default=0, max_digits=32)),
('proteins', models.DecimalField(decimal_places=16, default=0, max_digits=32)),
('calories', models.DecimalField(decimal_places=16, default=0, max_digits=32)),
('source', models.CharField(blank=True, default='', max_length=512, null=True)),
],
),
migrations.AlterField(
model_name='invitelink',
name='valid_until',
field=models.DateField(default=datetime.date(2020, 12, 1)),
),
migrations.AddField(
model_name='recipe',
name='nutrition',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.nutritioninformation'),
),
]
================================================
FILE: cookbook/migrations/0090_auto_20201214_1359.py
================================================
# Generated by Django 3.1.3 on 2020-12-14 12:59
import datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0089_auto_20201117_2222'),
]
operations = [
migrations.AddField(
model_name='userpreference',
name='use_fractions',
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name='invitelink',
name='valid_until',
field=models.DateField(default=datetime.date(2020, 12, 28)),
),
]
================================================
FILE: cookbook/migrations/0091_auto_20201226_1551.py
================================================
# Generated by Django 3.1.4 on 2020-12-26 14:51
from django.db import migrations
def migrate_empty_units(apps, schema_editor):
Unit = apps.get_model('cookbook', 'Unit')
Ingredient = apps.get_model('cookbook', 'Ingredient')
empty_units = Unit.objects.filter(name='').all()
for x in empty_units:
for i in Ingredient.objects.all():
if i.unit == x:
i.unit = None
i.save()
x.delete()
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0090_auto_20201214_1359'),
]
operations = [
migrations.RunPython(migrate_empty_units),
]
================================================
FILE: cookbook/migrations/0092_recipe_servings.py
================================================
# Generated by Django 3.0.7 on 2020-08-30 13:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0091_auto_20201226_1551'),
]
operations = [
migrations.AddField(
model_name='recipe',
name='servings',
field=models.IntegerField(default=1),
),
]
================================================
FILE: cookbook/migrations/0093_auto_20201231_1236.py
================================================
# Generated by Django 3.1.4 on 2020-12-31 11:36
import datetime
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0092_recipe_servings'),
]
operations = [
migrations.RenameField(
model_name='mealplan',
old_name='recipe_multiplier',
new_name='servings',
),
migrations.AlterField(
model_name='invitelink',
name='valid_until',
field=models.DateField(default=datetime.date(2021, 1, 14)),
),
migrations.AlterField(
model_name='unit',
name='name',
field=models.CharField(max_length=128, unique=True, validators=[django.core.validators.MinLengthValidator(1)]),
),
]
================================================
FILE: cookbook/migrations/0094_auto_20201231_1238.py
================================================
# Generated by Django 3.1.4 on 2020-12-31 11:38
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0093_auto_20201231_1236'),
]
operations = [
migrations.RenameField(
model_name='shoppinglistrecipe',
old_name='multiplier',
new_name='servings',
),
]
================================================
FILE: cookbook/migrations/0095_auto_20210107_1804.py
================================================
# Generated by Django 3.1.5 on 2021-01-07 17:04
import datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0094_auto_20201231_1238'),
]
operations = [
migrations.AddField(
model_name='userpreference',
name='sticky_navbar',
field=models.BooleanField(default=True),
),
migrations.AlterField(
model_name='invitelink',
name='valid_until',
field=models.DateField(default=datetime.date(2021, 1, 21)),
),
]
================================================
FILE: cookbook/migrations/0096_auto_20210109_2044.py
================================================
# Generated by Django 3.1.5 on 2021-01-09 19:44
from django.db import migrations
def delete_duplicate_bookmarks(apps, schema_editor):
"""
In this migration, a unique constraint is set on the fields `recipe` and `book`.
If there are already duplicate entries, the migration will fail.
Therefore all duplicate entries are deleted beforehand.
"""
RecipeBookEntry = apps.get_model('cookbook', 'RecipeBookEntry')
for row in RecipeBookEntry.objects.all():
if RecipeBookEntry.objects.filter(recipe=row.recipe, book=row.book).count() > 1:
row.delete()
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0095_auto_20210107_1804'),
]
operations = [
# run function to delete duplicated bookmarks
migrations.RunPython(delete_duplicate_bookmarks),
migrations.AlterUniqueTogether(
name='recipebookentry',
unique_together={('recipe', 'book')},
),
]
================================================
FILE: cookbook/migrations/0097_auto_20210113_1315.py
================================================
# Generated by Django 3.1.5 on 2021-01-13 12:15
import datetime
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0096_auto_20210109_2044'),
]
operations = [
migrations.AddField(
model_name='recipe',
name='description',
field=models.CharField(blank=True, max_length=512, null=True),
),
migrations.AlterField(
model_name='food',
name='name',
field=models.CharField(max_length=128, unique=True, validators=[django.core.validators.MinLengthValidator(1)]),
),
]
================================================
FILE: cookbook/migrations/0098_auto_20210113_1320.py
================================================
# Generated by Django 3.1.5 on 2021-01-13 12:20
import cookbook.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0097_auto_20210113_1315'),
]
operations = [
migrations.AlterField(
model_name='invitelink',
name='valid_until',
field=models.DateField(default=cookbook.models.default_valid_until),
),
]
================================================
FILE: cookbook/migrations/0099_auto_20210113_1518.py
================================================
# Generated by Django 3.1.5 on 2021-01-13 14:18
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0098_auto_20210113_1320'),
]
operations = [
migrations.AlterField(
model_name='cooklog',
name='created_at',
field=models.DateTimeField(default=django.utils.timezone.now),
),
]
================================================
FILE: cookbook/migrations/0100_recipe_servings_text.py
================================================
# Generated by Django 3.1.5 on 2021-01-21 19:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0099_auto_20210113_1518'),
]
operations = [
migrations.AddField(
model_name='recipe',
name='servings_text',
field=models.CharField(blank=True, default='', max_length=32),
),
]
================================================
FILE: cookbook/migrations/0101_storage_path.py
================================================
# Generated by Django 3.1.5 on 2021-01-22 18:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0100_recipe_servings_text'),
]
operations = [
migrations.AddField(
model_name='storage',
name='path',
field=models.CharField(blank=True, default='', max_length=256),
),
]
================================================
FILE: cookbook/migrations/0102_auto_20210125_1147.py
================================================
# Generated by Django 3.1.5 on 2021-01-25 10:47
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0101_storage_path'),
]
operations = [
migrations.CreateModel(
name='Supermarket',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128, unique=True, validators=[django.core.validators.MinLengthValidator(1)])),
('description', models.TextField(blank=True, null=True)),
],
),
migrations.CreateModel(
name='SupermarketCategory',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128, unique=True, validators=[django.core.validators.MinLengthValidator(1)])),
('description', models.TextField(blank=True, null=True)),
],
),
migrations.AddField(
model_name='food',
name='description',
field=models.TextField(blank=True, default=''),
),
migrations.AlterField(
model_name='storage',
name='method',
field=models.CharField(choices=[('DB', 'Dropbox'), ('NEXTCLOUD', 'Nextcloud'), ('LOCAL', 'Local')], default='DB', max_length=128),
),
migrations.AddField(
model_name='food',
name='supermarket_category',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='cookbook.supermarketcategory'),
),
]
================================================
FILE: cookbook/migrations/0103_food_ignore_shopping.py
================================================
# Generated by Django 3.1.5 on 2021-01-25 13:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0102_auto_20210125_1147'),
]
operations = [
migrations.AddField(
model_name='food',
name='ignore_shopping',
field=models.BooleanField(default=False),
),
]
================================================
FILE: cookbook/migrations/0104_auto_20210125_2133.py
================================================
# Generated by Django 3.1.5 on 2021-01-25 20:33
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0103_food_ignore_shopping'),
]
operations = [
migrations.CreateModel(
name='SupermarketCategoryRelation',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('order', models.IntegerField(default=0)),
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.supermarketcategory')),
('supermarket', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.supermarket')),
],
),
migrations.AddField(
model_name='supermarket',
name='categories',
field=models.ManyToManyField(through='cookbook.SupermarketCategoryRelation', to='cookbook.SupermarketCategory'),
),
]
================================================
FILE: cookbook/migrations/0105_auto_20210126_1604.py
================================================
# Generated by Django 3.1.5 on 2021-01-26 15:04
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0104_auto_20210125_2133'),
]
operations = [
migrations.AlterField(
model_name='supermarketcategoryrelation',
name='category',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='category_to_supermarket', to='cookbook.supermarketcategory'),
),
migrations.AlterField(
model_name='supermarketcategoryrelation',
name='supermarket',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='category_to_supermarket', to='cookbook.supermarket'),
),
]
================================================
FILE: cookbook/migrations/0106_shoppinglist_supermarket.py
================================================
# Generated by Django 3.1.5 on 2021-01-26 15:21
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0105_auto_20210126_1604'),
]
operations = [
migrations.AddField(
model_name='shoppinglist',
name='supermarket',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='cookbook.supermarket'),
),
]
================================================
FILE: cookbook/migrations/0107_auto_20210128_1535.py
================================================
# Generated by Django 3.1.5 on 2021-01-28 14:35
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0106_shoppinglist_supermarket'),
]
operations = [
migrations.AlterModelOptions(
name='supermarketcategoryrelation',
options={'ordering': ('order',)},
),
]
================================================
FILE: cookbook/migrations/0108_auto_20210219_1410.py
================================================
# Generated by Django 3.1.6 on 2021-02-19 13:10
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0107_auto_20210128_1535'),
]
operations = [
migrations.AddField(
model_name='cooklog',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='food',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='invitelink',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='keyword',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='mealplan',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='mealtype',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='recipe',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='recipebook',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='recipebookentry',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='recipeimport',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='sharelink',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='shoppinglist',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='shoppinglistentry',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='shoppinglistrecipe',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='storage',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='supermarket',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='supermarketcategory',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='sync',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='synclog',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='unit',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='userpreference',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='viewlog',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
]
================================================
FILE: cookbook/migrations/0109_auto_20210221_1204.py
================================================
# Generated by Django 3.1.6 on 2021-02-21 11:04
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0108_auto_20210219_1410'),
]
operations = [
migrations.RemoveField(
model_name='recipebookentry',
name='space',
),
migrations.AlterField(
model_name='food',
name='name',
field=models.CharField(max_length=128, validators=[django.core.validators.MinLengthValidator(1)]),
),
migrations.AlterField(
model_name='keyword',
name='name',
field=models.CharField(max_length=64),
),
migrations.AlterField(
model_name='supermarket',
name='name',
field=models.CharField(max_length=128, validators=[django.core.validators.MinLengthValidator(1)]),
),
migrations.AlterField(
model_name='supermarketcategory',
name='name',
field=models.CharField(max_length=128, validators=[django.core.validators.MinLengthValidator(1)]),
),
migrations.AlterField(
model_name='unit',
name='name',
field=models.CharField(max_length=128, validators=[django.core.validators.MinLengthValidator(1)]),
),
migrations.AlterUniqueTogether(
name='food',
unique_together={('space', 'name')},
),
migrations.AlterUniqueTogether(
name='keyword',
unique_together={('space', 'name')},
),
migrations.AlterUniqueTogether(
name='supermarket',
unique_together={('space', 'name')},
),
migrations.AlterUniqueTogether(
name='supermarketcategory',
unique_together={('space', 'name')},
),
migrations.AlterUniqueTogether(
name='unit',
unique_together={('space', 'name')},
),
]
================================================
FILE: cookbook/migrations/0110_auto_20210221_1406.py
================================================
# Generated by Django 3.1.6 on 2021-02-21 13:06
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0109_auto_20210221_1204'),
]
operations = [
migrations.AlterField(
model_name='userpreference',
name='space',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
),
]
================================================
FILE: cookbook/migrations/0111_space_created_by.py
================================================
# Generated by Django 3.1.6 on 2021-02-21 13:19
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
from django_scopes import scopes_disabled
def set_default_owner(apps, schema_editor):
Space = apps.get_model('cookbook', 'Space')
User = apps.get_model('auth', 'user')
with scopes_disabled():
for x in Space.objects.all():
x.created_by = User.objects.filter(is_superuser=True).first()
x.save()
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0110_auto_20210221_1406'),
]
operations = [
migrations.AddField(
model_name='space',
name='created_by',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
),
migrations.RunPython(set_default_owner),
]
================================================
FILE: cookbook/migrations/0112_remove_synclog_space.py
================================================
# Generated by Django 3.1.7 on 2021-03-16 23:21
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0111_space_created_by'),
]
operations = [
migrations.RemoveField(
model_name='synclog',
name='space',
),
]
================================================
FILE: cookbook/migrations/0113_auto_20210317_2017.py
================================================
# Generated by Django 3.1.7 on 2021-03-17 19:17
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0112_remove_synclog_space'),
]
operations = [
migrations.RemoveField(
model_name='shoppinglistentry',
name='space',
),
migrations.RemoveField(
model_name='shoppinglistrecipe',
name='space',
),
]
================================================
FILE: cookbook/migrations/0114_importlog.py
================================================
# Generated by Django 3.1.7 on 2021-03-18 17:23
import cookbook.models
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0113_auto_20210317_2017'),
]
operations = [
migrations.CreateModel(
name='ImportLog',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('type', models.CharField(max_length=32)),
('running', models.BooleanField(default=True)),
('msg', models.TextField(default='')),
('created_at', models.DateTimeField(auto_now_add=True)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('keyword', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='cookbook.keyword')),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
]
================================================
FILE: cookbook/migrations/0115_telegrambot.py
================================================
# Generated by Django 3.1.7 on 2021-03-18 21:12
import cookbook.models
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0114_importlog'),
]
operations = [
migrations.CreateModel(
name='TelegramBot',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('token', models.CharField(max_length=256)),
('name', models.CharField(blank=True, default='', max_length=128)),
('chat_id', models.CharField(blank=True, default='', max_length=128)),
('webhook_token', models.UUIDField(default=uuid.uuid4)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
]
================================================
FILE: cookbook/migrations/0116_auto_20210319_0012.py
================================================
# Generated by Django 3.1.7 on 2021-03-18 23:12
from django.db import migrations
from django_scopes import scopes_disabled
def remove_empty_food_unit(apps, schema_editor):
with scopes_disabled():
Ingredient = apps.get_model('cookbook', 'Ingredient')
ShoppingListEntry = apps.get_model('cookbook', 'ShoppingListEntry')
Food = apps.get_model('cookbook', 'Food')
Unit = apps.get_model('cookbook', 'Unit')
for f in Food.objects.filter(name='').all():
for o in Ingredient.objects.filter(food=f):
o.food = None
o.save()
for o in ShoppingListEntry.objects.filter(food=f):
o.delete()
f.delete()
for u in Unit.objects.filter(name='').all():
for o in Ingredient.objects.filter(unit=u):
o.unit = None
o.save()
for o in ShoppingListEntry.objects.filter(unit=u):
o.unit = None
o.save()
u.delete()
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0115_telegrambot'),
]
operations = [
migrations.RunPython(remove_empty_food_unit),
]
================================================
FILE: cookbook/migrations/0117_space_max_recipes.py
================================================
# Generated by Django 3.1.7 on 2021-03-23 21:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0116_auto_20210319_0012'),
]
operations = [
migrations.AddField(
model_name='space',
name='max_recipes',
field=models.IntegerField(default=0),
),
]
================================================
FILE: cookbook/migrations/0118_auto_20210406_1805.py
================================================
# Generated by Django 3.1.7 on 2021-04-06 16:05
from django.db import migrations
from django_scopes import scopes_disabled
def migrate_no_group_superusers(apps, schema_editor):
with scopes_disabled():
User = apps.get_model('auth', 'User')
Groups = apps.get_model('auth', 'Group')
for u in User.objects.filter(is_superuser=True).all():
if u.groups.count() == 0:
u.groups.add(Groups.objects.get(name='admin'))
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0117_space_max_recipes'),
]
operations = [
migrations.RunPython(migrate_no_group_superusers),
]
================================================
FILE: cookbook/migrations/0119_auto_20210411_2101.py
================================================
# Generated by Django 3.2 on 2021-04-11 19:01
from django.contrib.postgres.operations import UnaccentExtension, TrigramExtension
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0118_auto_20210406_1805'),
]
operations = [
TrigramExtension(),
UnaccentExtension(),
]
================================================
FILE: cookbook/migrations/0120_bookmarklet.py
================================================
# Generated by Django 3.1.7 on 2021-03-29 11:05
import cookbook.models
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0119_auto_20210411_2101'),
]
operations = [
migrations.AlterField(
model_name='userpreference',
name='use_fractions',
field=models.BooleanField(default=True),
),
migrations.CreateModel(
name='BookmarkletImport',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('html', models.TextField()),
('url', models.CharField(blank=True, max_length=256, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
]
================================================
FILE: cookbook/migrations/0121_auto_20210518_1638.py
================================================
# Generated by Django 3.2.3 on 2021-05-18 14:38
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0120_bookmarklet'),
]
operations = [
migrations.AlterField(
model_name='userpreference',
name='search_style',
field=models.CharField(choices=[('SMALL', 'Small'), ('LARGE', 'Large'), ('NEW', 'New')], default='LARGE', max_length=64),
),
migrations.AlterField(
model_name='userpreference',
name='use_fractions',
field=models.BooleanField(default=True),
),
]
================================================
FILE: cookbook/migrations/0122_auto_20210527_1712.py
================================================
# Generated by Django 3.2.3 on 2021-05-27 15:12
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0121_auto_20210518_1638'),
]
operations = [
migrations.AddField(
model_name='space',
name='allow_files',
field=models.BooleanField(default=True),
),
migrations.AddField(
model_name='space',
name='max_users',
field=models.IntegerField(default=0),
),
]
================================================
FILE: cookbook/migrations/0123_invitelink_email.py
================================================
# Generated by Django 3.2.3 on 2021-05-28 12:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0122_auto_20210527_1712'),
]
operations = [
migrations.AddField(
model_name='invitelink',
name='email',
field=models.EmailField(blank=True, max_length=254),
),
]
================================================
FILE: cookbook/migrations/0124_alter_userpreference_theme.py
================================================
# Generated by Django 3.2.3 on 2021-05-30 15:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0123_invitelink_email'),
]
operations = [
migrations.AlterField(
model_name='userpreference',
name='theme',
field=models.CharField(choices=[('BOOTSTRAP', 'Bootstrap'), ('DARKLY', 'Darkly'), ('FLATLY', 'Flatly'), ('SUPERHERO', 'Superhero'), ('TANDOOR', 'Tandoor')], default='FLATLY', max_length=128),
),
]
================================================
FILE: cookbook/migrations/0125_space_demo.py
================================================
# Generated by Django 3.2.3 on 2021-06-04 14:52
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0124_alter_userpreference_theme'),
]
operations = [
migrations.AddField(
model_name='space',
name='demo',
field=models.BooleanField(default=False),
),
]
================================================
FILE: cookbook/migrations/0126_alter_userpreference_theme.py
================================================
# Generated by Django 3.2.3 on 2021-06-05 15:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0125_space_demo'),
]
operations = [
migrations.AlterField(
model_name='userpreference',
name='theme',
field=models.CharField(choices=[('TANDOOR', 'Tandoor'), ('BOOTSTRAP', 'Bootstrap'), ('DARKLY', 'Darkly'), ('FLATLY', 'Flatly'), ('SUPERHERO', 'Superhero')], default='TANDOOR', max_length=128),
),
]
================================================
FILE: cookbook/migrations/0127_remove_invitelink_username.py
================================================
# Generated by Django 3.2.3 on 2021-06-07 14:21
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0126_alter_userpreference_theme'),
]
operations = [
migrations.RemoveField(
model_name='invitelink',
name='username',
),
]
================================================
FILE: cookbook/migrations/0128_userfile.py
================================================
# Generated by Django 3.2.3 on 2021-06-08 10:23
import cookbook.models
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0127_remove_invitelink_username'),
]
operations = [
migrations.CreateModel(
name='UserFile',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128)),
('file', models.FileField(upload_to='files/')),
('file_size_kb', models.IntegerField()),
('created_at', models.DateTimeField(auto_now_add=True)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
]
================================================
FILE: cookbook/migrations/0129_auto_20210608_1233.py
================================================
# Generated by Django 3.2.3 on 2021-06-08 10:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0128_userfile'),
]
operations = [
migrations.RemoveField(
model_name='space',
name='allow_files',
),
migrations.AddField(
model_name='space',
name='max_file_storage_mb',
field=models.IntegerField(default=0, help_text='Maximum file storage for space in MB. 0 for unlimited, -1 to disable file upload.'),
),
]
================================================
FILE: cookbook/migrations/0130_alter_userfile_file_size_kb.py
================================================
# Generated by Django 3.2.3 on 2021-06-08 10:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0129_auto_20210608_1233'),
]
operations = [
migrations.AlterField(
model_name='userfile',
name='file_size_kb',
field=models.IntegerField(blank=True, default=0),
),
]
================================================
FILE: cookbook/migrations/0131_auto_20210608_1929.py
================================================
# Generated by Django 3.2.4 on 2021-06-08 17:29
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0130_alter_userfile_file_size_kb'),
]
operations = [
migrations.AddField(
model_name='step',
name='file',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='cookbook.userfile'),
),
migrations.AlterField(
model_name='step',
name='type',
field=models.CharField(choices=[('TEXT', 'Text'), ('TIME', 'Time'), ('FILE', 'File')], default='TEXT', max_length=16),
),
]
================================================
FILE: cookbook/migrations/0132_sharelink_request_count.py
================================================
# Generated by Django 3.2.4 on 2021-06-12 18:39
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0131_auto_20210608_1929'),
]
operations = [
migrations.AddField(
model_name='sharelink',
name='request_count',
field=models.IntegerField(default=0),
),
]
================================================
FILE: cookbook/migrations/0133_sharelink_abuse_blocked.py
================================================
# Generated by Django 3.2.4 on 2021-06-12 18:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0132_sharelink_request_count'),
]
operations = [
migrations.AddField(
model_name='sharelink',
name='abuse_blocked',
field=models.BooleanField(default=False),
),
]
================================================
FILE: cookbook/migrations/0134_space_allow_sharing.py
================================================
# Generated by Django 3.2.4 on 2021-06-15 19:07
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0133_sharelink_abuse_blocked'),
]
operations = [
migrations.AddField(
model_name='space',
name='allow_sharing',
field=models.BooleanField(default=True),
),
]
================================================
FILE: cookbook/migrations/0135_auto_20210615_2210.py
================================================
# Generated by Django 3.2.4 on 2021-06-15 20:10
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0134_space_allow_sharing'),
]
operations = [
migrations.AddField(
model_name='ingredient',
name='space',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
),
migrations.AddField(
model_name='nutritioninformation',
name='space',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
),
migrations.AddField(
model_name='step',
name='space',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
),
]
================================================
FILE: cookbook/migrations/0136_auto_20210617_1343.py
================================================
# Generated by Django 3.2.4 on 2021-06-17 11:43
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0135_auto_20210615_2210'),
]
operations = [
migrations.AddField(
model_name='importlog',
name='imported_recipes',
field=models.IntegerField(default=0),
),
migrations.AddField(
model_name='importlog',
name='total_recipes',
field=models.IntegerField(default=0),
),
]
================================================
FILE: cookbook/migrations/0137_auto_20210617_1501.py
================================================
# Generated by Django 3.2.4 on 2021-06-17 13:01
from django.db import migrations
from django.db.models import Subquery, OuterRef
from django_scopes import scopes_disabled
from django.db import migrations, models
import django.db.models.deletion
def migrate_spaces(apps, schema_editor):
with scopes_disabled():
Recipe = apps.get_model('cookbook', 'Recipe')
Step = apps.get_model('cookbook', 'Step')
Ingredient = apps.get_model('cookbook', 'Ingredient')
NutritionInformation = apps.get_model('cookbook', 'NutritionInformation')
Step.objects.filter(recipe__isnull=True).delete()
Ingredient.objects.filter(step__recipe__isnull=True).delete()
NutritionInformation.objects.filter(recipe__isnull=True).delete()
Step.objects.update(space=Subquery(Step.objects.filter(pk=OuterRef('pk')).values('recipe__space')[:1]))
Ingredient.objects.update(space=Subquery(Ingredient.objects.filter(pk=OuterRef('pk')).values('step__recipe__space')[:1]))
NutritionInformation.objects.update(space=Subquery(NutritionInformation.objects.filter(pk=OuterRef('pk')).values('recipe__space')[:1]))
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0136_auto_20210617_1343'),
]
operations = [
migrations.RunPython(migrate_spaces),
]
================================================
FILE: cookbook/migrations/0138_auto_20210617_1602.py
================================================
# Generated by Django 3.2.4 on 2021-06-17 14:02
from django.db import migrations
from django.db.models import Subquery, OuterRef
from django_scopes import scopes_disabled
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0137_auto_20210617_1501'),
]
operations = [
migrations.AlterField(
model_name='ingredient',
name='space',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
),
migrations.AlterField(
model_name='nutritioninformation',
name='space',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
),
migrations.AlterField(
model_name='step',
name='space',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
),
]
================================================
FILE: cookbook/migrations/0139_space_created_at.py
================================================
# Generated by Django 3.2.4 on 2021-06-22 16:14
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0138_auto_20210617_1602'),
]
operations = [
migrations.AddField(
model_name='space',
name='created_at',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
]
================================================
FILE: cookbook/migrations/0140_userpreference_created_at.py
================================================
# Generated by Django 3.2.4 on 2021-06-22 16:19
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0139_space_created_at'),
]
operations = [
migrations.AddField(
model_name='userpreference',
name='created_at',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
]
================================================
FILE: cookbook/migrations/0141_auto_20210713_1042.py
================================================
# Generated by Django 3.2.5 on 2021-07-13 08:42
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0140_userpreference_created_at'),
]
operations = [
migrations.AddField(
model_name='step',
name='step_recipe',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, to='cookbook.recipe'),
),
migrations.AlterField(
model_name='step',
name='type',
field=models.CharField(choices=[('TEXT', 'Text'), ('TIME', 'Time'), ('FILE', 'File'), ('RECIPE', 'Recipe')], default='TEXT', max_length=16),
),
]
================================================
FILE: cookbook/migrations/0142_alter_userpreference_search_style.py
================================================
# Generated by Django 3.2.5 on 2021-07-29 14:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0141_auto_20210713_1042'),
]
operations = [
migrations.AlterField(
model_name='userpreference',
name='search_style',
field=models.CharField(choices=[('SMALL', 'Small'), ('LARGE', 'Large'), ('NEW', 'New')], default='NEW', max_length=64),
),
]
================================================
FILE: cookbook/migrations/0143_build_full_text_index.py
================================================
# Generated by Django 3.1.7 on 2021-04-07 20:00
import annoying.fields
from django.conf import settings
from django.contrib.postgres.indexes import GinIndex
from django.contrib.postgres.search import SearchVector, SearchVectorField
from django.db import migrations, models
from django.db.models import deletion
from django.utils import translation
from django_scopes import scopes_disabled
from cookbook.managers import DICTIONARY
from cookbook.models import Index, PermissionModelMixin, Recipe, SearchFields, Step
def allSearchFields():
return list(SearchFields.objects.values_list('id', flat=True))
def nameSearchField():
return [SearchFields.objects.get(name='Name').id]
def set_default_search_vector(apps, schema_editor):
if settings.DATABASES['default']['ENGINE'] != 'django.db.backends.postgresql':
return
language = DICTIONARY.get(translation.get_language(), 'simple')
with scopes_disabled():
Recipe.objects.all().update(
name_search_vector=SearchVector('name__unaccent', weight='A', config=language),
desc_search_vector=SearchVector('description__unaccent', weight='B', config=language)
)
Step.objects.all().update(search_vector=SearchVector('instruction__unaccent', weight='B'))
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0142_alter_userpreference_search_style'),
]
operations = [
migrations.AddField(
model_name='recipe',
name='desc_search_vector',
field=SearchVectorField(null=True),
),
migrations.AddField(
model_name='recipe',
name='name_search_vector',
field=SearchVectorField(null=True),
),
migrations.AddIndex(
model_name='recipe',
index=GinIndex(fields=['name_search_vector', 'desc_search_vector'], name='cookbook_re_name_se_bdf3ca_gin'),
),
migrations.AddField(
model_name='step',
name='search_vector',
field=SearchVectorField(null=True),
),
migrations.AddIndex(
model_name='step',
index=GinIndex(fields=['search_vector'], name='cookbook_st_search__2ef7fa_gin'),
),
migrations.AddIndex(
model_name='cooklog',
index=Index(fields=['id', 'recipe', '-created_at', 'rating'], name='cookbook_co_id_37485a_idx'),
),
migrations.AddIndex(
model_name='food',
index=Index(fields=['id', 'name'], name='cookbook_fo_id_22b733_idx'),
),
migrations.AddIndex(
model_name='ingredient',
index=Index(fields=['id', 'food', 'unit'], name='cookbook_in_id_3368be_idx'),
),
migrations.AddIndex(
model_name='keyword',
index=Index(fields=['id', 'name'], name='cookbook_ke_id_ebc03f_idx'),
),
migrations.AddIndex(
model_name='recipe',
index=Index(fields=['id', 'name', 'description'], name='cookbook_re_id_e4c2d4_idx'),
),
migrations.AddIndex(
model_name='recipebook',
index=Index(fields=['name', 'description'], name='cookbook_re_name_bbe446_idx'),
),
migrations.AddIndex(
model_name='viewlog',
index=Index(fields=['recipe', '-created_at'], name='cookbook_vi_recipe__5cd178_idx'),
),
migrations.CreateModel(
name='SearchFields',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=32, unique=True)),
('field', models.CharField(max_length=64, unique=True)),
],
bases=(models.Model, PermissionModelMixin),
),
migrations.CreateModel(
name='SearchPreference',
fields=[
('user', annoying.fields.AutoOneToOneField(on_delete=deletion.CASCADE, primary_key=True, serialize=False, to='auth.user')),
('search', models.CharField(choices=[('plain', 'Simple'), ('phrase', 'Phrase'), ('websearch', 'Web'), ('raw', 'Raw')], default='plain', max_length=32)),
('lookup', models.BooleanField(default=False)),
('fulltext', models.ManyToManyField(blank=True, related_name='fulltext_fields', to='cookbook.SearchFields')),
('icontains', models.ManyToManyField(blank=True, default=nameSearchField, related_name='icontains_fields', to='cookbook.SearchFields')),
('istartswith', models.ManyToManyField(blank=True, related_name='istartswith_fields', to='cookbook.SearchFields')),
('trigram', models.ManyToManyField(blank=True, related_name='trigram_fields', to='cookbook.SearchFields')),
('unaccent', models.ManyToManyField(blank=True, default=allSearchFields, related_name='unaccent_fields', to='cookbook.SearchFields')),
],
bases=(models.Model, PermissionModelMixin),
),
migrations.RunPython(
set_default_search_vector
),
]
================================================
FILE: cookbook/migrations/0144_create_searchfields.py
================================================
from cookbook.models import SearchFields
from django.db import migrations
def create_searchfields(apps, schema_editor):
SearchFields.objects.create(name='Name', field='name')
SearchFields.objects.create(name='Description', field='description')
SearchFields.objects.create(name='Instructions', field='steps__instruction')
SearchFields.objects.create(name='Ingredients', field='steps__ingredients__food__name')
SearchFields.objects.create(name='Keywords', field='keywords__name')
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0143_build_full_text_index'),
]
operations = [
migrations.RunPython(
create_searchfields
),
]
================================================
FILE: cookbook/migrations/0145_alter_userpreference_search_style.py
================================================
# Generated by Django 3.2 on 2021-04-22 21:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0144_create_searchfields'),
]
operations = [
migrations.AlterField(
model_name='userpreference',
name='search_style',
field=models.CharField(choices=[('SMALL', 'Small'), ('LARGE', 'Large'), ('NEW', 'New')], default='LARGE', max_length=64),
),
]
================================================
FILE: cookbook/migrations/0146_alter_userpreference_use_fractions.py
================================================
# Generated by Django 3.2.4 on 2021-07-03 08:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0145_alter_userpreference_search_style'),
]
operations = [
migrations.AlterField(
model_name='userpreference',
name='use_fractions',
field=models.BooleanField(default=False),
),
]
================================================
FILE: cookbook/migrations/0147_keyword_to_tree.py
================================================
# Generated by Django 3.1.7 on 2021-03-30 19:42
from treebeard.mp_tree import MP_Node
from django.db import migrations, models
from django_scopes import scopes_disabled
# update if needed
steplen = MP_Node.steplen
alphabet = MP_Node.alphabet
node_order_by = ["name"]
def update_paths(apps, schema_editor):
with scopes_disabled():
Node = apps.get_model("cookbook", "Keyword")
nodes = Node.objects.all().order_by(*node_order_by)
for i, node in enumerate(nodes, 1):
# for default values, this resolves to: "{:04d}".format(i)
node.path = f"{{:{alphabet[0]}{steplen}d}}".format(i)
if nodes:
Node.objects.bulk_update(nodes, ["path"])
def backwards(apps, schema_editor):
"""nothing to do"""
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0146_alter_userpreference_use_fractions'),
]
operations = [
migrations.AddField(
model_name='keyword',
name='depth',
field=models.PositiveIntegerField(default=1),
preserve_default=False,
),
migrations.AddField(
model_name='keyword',
name='numchild',
field=models.PositiveIntegerField(default=0),
),
migrations.AddField(
model_name='keyword',
name='path',
field=models.CharField(default="", max_length=255, unique=False),
preserve_default=False,
),
migrations.AlterField(
model_name='userpreference',
name='use_fractions',
field=models.BooleanField(default=True),
),
migrations.RunPython(update_paths, backwards),
migrations.AlterField(
model_name="keyword",
name="path",
field=models.CharField(max_length=255, unique=True),
),
migrations.AlterUniqueTogether(
name='keyword',
unique_together=set(),
),
migrations.AddConstraint(
model_name='keyword',
constraint=models.UniqueConstraint(fields=('space', 'name'), name='unique_name_per_space'),
),
]
================================================
FILE: cookbook/migrations/0148_auto_20210813_1829.py
================================================
# Generated by Django 3.2.5 on 2021-08-13 16:29
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0147_keyword_to_tree'),
]
operations = [
migrations.RemoveConstraint(
model_name='keyword',
name='unique_name_per_space',
),
migrations.AlterField(
model_name='userpreference',
name='use_fractions',
field=models.BooleanField(default=False),
),
migrations.AlterUniqueTogether(
name='food',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='recipebookentry',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='supermarket',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='supermarketcategory',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='unit',
unique_together=set(),
),
migrations.AddConstraint(
model_name='food',
constraint=models.UniqueConstraint(fields=('space', 'name'), name='f_unique_name_per_space'),
),
migrations.AddConstraint(
model_name='keyword',
constraint=models.UniqueConstraint(fields=('space', 'name'), name='kw_unique_name_per_space'),
),
migrations.AddConstraint(
model_name='recipebookentry',
constraint=models.UniqueConstraint(fields=('recipe', 'book'), name='rbe_unique_name_per_space'),
),
migrations.AddConstraint(
model_name='supermarket',
constraint=models.UniqueConstraint(fields=('space', 'name'), name='sm_unique_name_per_space'),
),
migrations.AddConstraint(
model_name='supermarketcategory',
constraint=models.UniqueConstraint(fields=('space', 'name'), name='smc_unique_name_per_space'),
),
migrations.AddConstraint(
model_name='unit',
constraint=models.UniqueConstraint(fields=('space', 'name'), name='u_unique_name_per_space'),
),
]
================================================
FILE: cookbook/migrations/0149_fix_leading_trailing_spaces.py
================================================
from django.db import migrations, models
from django_scopes import scopes_disabled
models = ["Keyword", "Food", "Unit"]
def update_paths(apps, schema_editor):
with scopes_disabled():
for model in models:
Node = apps.get_model("cookbook", model)
nodes = Node.objects.all().filter(name__startswith=" ")
for i in nodes:
i.name = "_" + i.name
i.save()
nodes = Node.objects.all().filter(name__endswith=" ")
for i in nodes:
i.name = i.name + "_"
i.save()
def backwards(apps, schema_editor):
"""nothing to do"""
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0148_auto_20210813_1829'),
]
operations = [
migrations.RunPython(update_paths, backwards),
]
================================================
FILE: cookbook/migrations/0150_food_to_tree.py
================================================
# Generated by Django 3.2.5 on 2021-08-14 15:40
from treebeard.mp_tree import MP_Node
from django.db import migrations, models
from django_scopes import scopes_disabled
# update if needed
steplen = MP_Node.steplen
alphabet = MP_Node.alphabet
node_order_by = ["name"]
def update_paths(apps, schema_editor):
with scopes_disabled():
Node = apps.get_model("cookbook", "Food")
nodes = Node.objects.all().order_by(*node_order_by)
for i, node in enumerate(nodes, 1):
# for default values, this resolves to: "{:04d}".format(i)
node.path = f"{{:{alphabet[0]}{steplen}d}}".format(i)
if nodes:
Node.objects.bulk_update(nodes, ["path"])
def backwards(apps, schema_editor):
"""nothing to do"""
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0149_fix_leading_trailing_spaces'),
]
operations = [
migrations.AddField(
model_name='food',
name='depth',
field=models.PositiveIntegerField(default=1),
preserve_default=False,
),
migrations.AddField(
model_name='food',
name='numchild',
field=models.PositiveIntegerField(default=0),
),
migrations.AddField(
model_name='food',
name='path',
field=models.CharField(default=0, max_length=255, unique=False),
preserve_default=False,
),
migrations.RunPython(update_paths, backwards),
migrations.AlterField(
model_name="food",
name="path",
field=models.CharField(max_length=255, unique=True),
),
]
================================================
FILE: cookbook/migrations/0151_auto_20210915_1037.py
================================================
# Generated by Django 3.2.7 on 2021-09-15 08:37
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0150_food_to_tree'),
]
operations = [
migrations.RemoveIndex(
model_name='cooklog',
name='cookbook_co_id_37485a_idx',
),
migrations.RemoveIndex(
model_name='viewlog',
name='cookbook_vi_recipe__5cd178_idx',
),
migrations.AlterField(
model_name='ingredient',
name='food',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.food'),
),
migrations.AlterField(
model_name='userpreference',
name='search_style',
field=models.CharField(choices=[('SMALL', 'Small'), ('LARGE', 'Large'), ('NEW', 'New')], default='NEW', max_length=64),
),
migrations.AddIndex(
model_name='cooklog',
index=models.Index(fields=['id', 'recipe', '-created_at', 'rating', 'created_by'], name='cookbook_co_id_93d841_idx'),
),
migrations.AddIndex(
model_name='viewlog',
index=models.Index(fields=['recipe', '-created_at', 'created_by'], name='cookbook_vi_recipe__1b051f_idx'),
),
]
================================================
FILE: cookbook/migrations/0152_automation.py
================================================
# Generated by Django 3.2.7 on 2021-09-15 10:12
import cookbook.models
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0151_auto_20210915_1037'),
]
operations = [
migrations.CreateModel(
name='Automation',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('type', models.CharField(choices=[('FOOD_ALIAS', 'Food Alias'), ('UNIT_ALIAS', 'Unit Alias'), ('KEYWORD_ALIAS', 'Keyword Alias')], max_length=128)),
('name', models.CharField(default='', max_length=128)),
('description', models.TextField(blank=True, null=True)),
('param_1', models.CharField(blank=True, max_length=128, null=True)),
('param_2', models.CharField(blank=True, max_length=128, null=True)),
('param_3', models.CharField(blank=True, max_length=128, null=True)),
('disabled', models.BooleanField(default=False)),
('updated_at', models.DateTimeField(auto_now=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
]
================================================
FILE: cookbook/migrations/0153_auto_20210915_2327.py
================================================
# Generated by Django 3.2.7 on 2021-09-15 21:27
import django.contrib.postgres.indexes
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0152_automation'),
]
operations = [
migrations.RemoveIndex(
model_name='cooklog',
name='cookbook_co_id_93d841_idx',
),
migrations.RemoveIndex(
model_name='food',
name='cookbook_fo_id_22b733_idx',
),
migrations.RemoveIndex(
model_name='ingredient',
name='cookbook_in_id_3368be_idx',
),
migrations.RemoveIndex(
model_name='recipe',
name='cookbook_re_name_se_bdf3ca_gin',
),
migrations.RemoveIndex(
model_name='recipe',
name='cookbook_re_id_e4c2d4_idx',
),
migrations.RemoveIndex(
model_name='recipebook',
name='cookbook_re_name_bbe446_idx',
),
migrations.AddIndex(
model_name='cooklog',
index=models.Index(fields=['id'], name='cookbook_co_id_553a6d_idx'),
),
migrations.AddIndex(
model_name='cooklog',
index=models.Index(fields=['recipe'], name='cookbook_co_recipe__8ec719_idx'),
),
migrations.AddIndex(
model_name='cooklog',
index=models.Index(fields=['-created_at'], name='cookbook_co_created_f6e244_idx'),
),
migrations.AddIndex(
model_name='cooklog',
index=models.Index(fields=['rating'], name='cookbook_co_rating_aa7662_idx'),
),
migrations.AddIndex(
model_name='cooklog',
index=models.Index(fields=['created_by'], name='cookbook_co_created_7ea086_idx'),
),
migrations.AddIndex(
model_name='cooklog',
index=models.Index(fields=['created_by', 'rating'], name='cookbook_co_created_f5ccd7_idx'),
),
migrations.AddIndex(
model_name='food',
index=models.Index(fields=['id'], name='cookbook_fo_id_3c379b_idx'),
),
migrations.AddIndex(
model_name='food',
index=models.Index(fields=['name'], name='cookbook_fo_name_c848b6_idx'),
),
migrations.AddIndex(
model_name='ingredient',
index=models.Index(fields=['id'], name='cookbook_in_id_2c1f57_idx'),
),
migrations.AddIndex(
model_name='recipe',
index=django.contrib.postgres.indexes.GinIndex(fields=['name_search_vector'], name='cookbook_re_name_se_5dbbd5_gin'),
),
migrations.AddIndex(
model_name='recipe',
index=django.contrib.postgres.indexes.GinIndex(fields=['desc_search_vector'], name='cookbook_re_desc_se_fdee30_gin'),
),
migrations.AddIndex(
model_name='recipe',
index=models.Index(fields=['id'], name='cookbook_re_id_b2bdcf_idx'),
),
migrations.AddIndex(
model_name='recipe',
index=models.Index(fields=['name'], name='cookbook_re_name_b8a027_idx'),
),
migrations.AddIndex(
model_name='recipebook',
index=models.Index(fields=['name'], name='cookbook_re_name_94cc63_idx'),
),
migrations.AddIndex(
model_name='viewlog',
index=models.Index(fields=['recipe'], name='cookbook_vi_recipe__ce995d_idx'),
),
migrations.AddIndex(
model_name='viewlog',
index=models.Index(fields=['-created_at'], name='cookbook_vi_created_bd2b5f_idx'),
),
migrations.AddIndex(
model_name='viewlog',
index=models.Index(fields=['created_by'], name='cookbook_vi_created_f9385c_idx'),
),
]
================================================
FILE: cookbook/migrations/0154_auto_20210922_1705.py
================================================
# Generated by Django 3.2.7 on 2021-09-22 15:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0153_auto_20210915_2327'),
]
operations = [
migrations.AddField(
model_name='mealtype',
name='color',
field=models.CharField(blank=True, max_length=7, null=True),
),
migrations.AddField(
model_name='mealtype',
name='icon',
field=models.CharField(blank=True, max_length=16, null=True),
),
]
================================================
FILE: cookbook/migrations/0155_mealtype_default.py
================================================
# Generated by Django 3.2.7 on 2021-09-23 11:38
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0154_auto_20210922_1705'),
]
operations = [
migrations.AddField(
model_name='mealtype',
name='default',
field=models.BooleanField(blank=True, default=False),
),
]
================================================
FILE: cookbook/migrations/0156_searchpreference_trigram_threshold.py
================================================
# Generated by Django 3.2.7 on 2021-09-28 16:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0155_mealtype_default'),
]
operations = [
migrations.AddField(
model_name='searchpreference',
name='trigram_threshold',
field=models.DecimalField(decimal_places=2, default=0.1, max_digits=3),
),
]
================================================
FILE: cookbook/migrations/0157_alter_searchpreference_trigram.py
================================================
# Generated by Django 3.2.7 on 2021-09-29 06:37
from django_scopes import scopes_disabled
from django.db import migrations, models
from cookbook.models import SearchFields
def nameSearchField():
return [SearchFields.objects.get(name='Name').id]
def add_default_trigram(apps, schema_editor):
with scopes_disabled():
SearchFields = apps.get_model('cookbook', 'SearchFields')
SearchPreference = apps.get_model('cookbook', 'SearchPreference')
name_field = SearchFields.objects.get(name='Name')
for p in SearchPreference.objects.all():
if not p.trigram.all() and p.search == 'plain':
p.trigram.add(name_field)
p.save()
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0156_searchpreference_trigram_threshold'),
]
operations = [
migrations.AlterField(
model_name='searchpreference',
name='trigram',
field=models.ManyToManyField(blank=True, default=nameSearchField, related_name='trigram_fields', to='cookbook.SearchFields'),
),
migrations.RunPython(add_default_trigram),
]
================================================
FILE: cookbook/migrations/0158_userpreference_use_kj.py
================================================
# Generated by Django 3.2.7 on 2021-10-25 05:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0157_alter_searchpreference_trigram'),
]
operations = [
migrations.AddField(
model_name='userpreference',
name='use_kj',
field=models.BooleanField(default=False),
),
]
================================================
FILE: cookbook/migrations/0159_add_shoppinglistentry_fields.py
================================================
# Generated by Django 3.2.7 on 2021-10-01 20:52
import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
from django_scopes import scopes_disabled
from cookbook.models import PermissionModelMixin
def copy_values_to_sle(apps, schema_editor):
with scopes_disabled():
ShoppingListEntry = apps.get_model('cookbook', 'ShoppingListEntry')
entries = ShoppingListEntry.objects.all()
for entry in entries:
if entry.shoppinglist_set.first():
entry.created_by = entry.shoppinglist_set.first().created_by
entry.space = entry.shoppinglist_set.first().space
if entries:
ShoppingListEntry.objects.bulk_update(entries, ["created_by", "space", ])
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0158_userpreference_use_kj'),
]
operations = [
migrations.AddField(
model_name='shoppinglistentry',
name='completed_at',
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name='shoppinglistentry',
name='created_at',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='shoppinglistentry',
name='created_by',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='auth.user'),
preserve_default=False,
),
migrations.AddField(
model_name='userpreference',
name='shopping_share',
field=models.ManyToManyField(blank=True, related_name='shopping_share', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='shoppinglistentry',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='shoppinglistrecipe',
name='mealplan',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.mealplan'),
),
migrations.AddField(
model_name='shoppinglistrecipe',
name='name',
field=models.CharField(blank=True, default='', max_length=32),
),
migrations.AddField(
model_name='shoppinglistentry',
name='ingredient',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.ingredient'),
),
migrations.AlterField(
model_name='shoppinglistentry',
name='unit',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='cookbook.unit'),
),
migrations.AddField(
model_name='userpreference',
name='mealplan_autoadd_shopping',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='userpreference',
name='mealplan_autoexclude_onhand',
field=models.BooleanField(default=True),
),
migrations.AlterField(
model_name='shoppinglistentry',
name='list_recipe',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='entries', to='cookbook.shoppinglistrecipe'),
),
migrations.CreateModel(
name='FoodInheritField',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('field', models.CharField(max_length=32, unique=True)),
('name', models.CharField(max_length=64, unique=True)),
],
bases=(models.Model, PermissionModelMixin),
),
migrations.AddField(
model_name='userpreference',
name='mealplan_autoinclude_related',
field=models.BooleanField(default=True),
),
migrations.AddField(
model_name='food',
name='inherit_fields',
field=models.ManyToManyField(blank=True, to='cookbook.FoodInheritField'),
),
migrations.AddField(
model_name='space',
name='food_inherit',
field=models.ManyToManyField(blank=True, to='cookbook.FoodInheritField'),
),
migrations.AddField(
model_name='shoppinglistentry',
name='delay_until',
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name='userpreference',
name='default_delay',
field=models.DecimalField(decimal_places=4, default=4, max_digits=8),
),
migrations.AddField(
model_name='userpreference',
name='filter_to_supermarket',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='userpreference',
name='shopping_recent_days',
field=models.PositiveIntegerField(default=7),
),
migrations.RenameField(
model_name='food',
old_name='ignore_shopping',
new_name='food_onhand',
),
migrations.AddField(
model_name='space',
name='show_facet_count',
field=models.BooleanField(default=False),
),
migrations.RunPython(copy_values_to_sle),
]
================================================
FILE: cookbook/migrations/0160_delete_shoppinglist_orphans.py
================================================
# Generated by Django 3.2.7 on 2021-10-01 22:34
from datetime import timedelta
from django.conf import settings
from django.db import migrations
from django.utils import timezone
from django_scopes import scopes_disabled
def delete_orphaned_sle(apps, schema_editor):
ShoppingListEntry = apps.get_model('cookbook', 'ShoppingListEntry')
with scopes_disabled():
# shopping list entry is orphaned - delete it
ShoppingListEntry.objects.filter(shoppinglist=None).delete()
def create_inheritfields(apps, schema_editor):
FoodInheritField = apps.get_model('cookbook', 'FoodInheritField')
FoodInheritField.objects.create(name='Supermarket Category', field='supermarket_category')
FoodInheritField.objects.create(name='On Hand', field='food_onhand')
FoodInheritField.objects.create(name='Diet', field='diet')
FoodInheritField.objects.create(name='Substitute', field='substitute')
FoodInheritField.objects.create(name='Substitute Children', field='substitute_children')
FoodInheritField.objects.create(name='Substitute Siblings', field='substitute_siblings')
def set_completed_at(apps, schema_editor):
ShoppingListEntry = apps.get_model('cookbook', 'ShoppingListEntry')
today_start = timezone.now().replace(hour=0, minute=0, second=0)
# arbitrary - keeping all of the closed shopping list items out of the 'recent' view
month_ago = today_start - timedelta(days=30)
with scopes_disabled():
ShoppingListEntry.objects.filter(checked=True).update(completed_at=month_ago)
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0159_add_shoppinglistentry_fields'),
]
operations = [
migrations.RunPython(delete_orphaned_sle),
migrations.RunPython(create_inheritfields),
migrations.RunPython(set_completed_at),
]
================================================
FILE: cookbook/migrations/0161_alter_shoppinglistentry_food.py
================================================
# Generated by Django 3.2.8 on 2021-11-03 23:19
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0160_delete_shoppinglist_orphans'),
]
operations = [
migrations.AlterField(
model_name='shoppinglistentry',
name='food',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='shopping_entries', to='cookbook.food'),
),
]
================================================
FILE: cookbook/migrations/0162_userpreference_csv_delim.py
================================================
# Generated by Django 3.2.9 on 2021-11-30 22:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0161_alter_shoppinglistentry_food'),
]
operations = [
migrations.AddField(
model_name='userpreference',
name='csv_delim',
field=models.CharField(default=',', max_length=2),
),
migrations.AddField(
model_name='userpreference',
name='csv_prefix',
field=models.CharField(blank=True, max_length=10),
),
]
================================================
FILE: cookbook/migrations/0163_auto_20220105_0758.py
================================================
# Generated by Django 3.2.10 on 2022-01-05 13:58
from django.conf import settings
from django.db import migrations, models
from cookbook.models import FoodInheritField
def rename_inherit_field(apps, schema_editor):
x = FoodInheritField.objects.filter(name='On Hand', field='food_onhand').first()
if x:
x.name = "Ignore Shopping"
x.field = "ignore_shopping"
x.save()
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0162_userpreference_csv_delim'),
]
operations = [
migrations.AddField(
model_name='food',
name='onhand_users',
field=models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='userpreference',
name='shopping_add_onhand',
field=models.BooleanField(default=True),
),
migrations.RenameField(
model_name='food',
old_name='food_onhand',
new_name='ignore_shopping',
),
migrations.RunPython(rename_inherit_field),
]
================================================
FILE: cookbook/migrations/0164_space_show_facet_count.py
================================================
# Generated by Django 3.2.11 on 2022-01-17 22:23
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0163_auto_20220105_0758'),
]
operations = [
# migrations.AddField(
# model_name='space',
# name='show_facet_count',
# field=models.BooleanField(default=False),
# ),
# removed due to quick fix in 0159 migration to maintain correct order
]
================================================
FILE: cookbook/migrations/0165_remove_step_type.py
================================================
# Generated by Django 3.2.11 on 2022-01-18 19:19
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0164_space_show_facet_count'),
]
operations = [
migrations.RemoveField(
model_name='step',
name='type',
),
]
================================================
FILE: cookbook/migrations/0166_alter_userpreference_shopping_add_onhand.py
================================================
# Generated by Django 3.2.11 on 2022-01-20 14:39
from django.db import migrations, models
from django_scopes import scopes_disabled
def add_default_trigram(apps, schema_editor):
with scopes_disabled():
UserPreference = apps.get_model('cookbook', 'UserPreference')
UserPreference.objects.all().update(shopping_add_onhand=False)
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0165_remove_step_type'),
]
operations = [
migrations.AlterField(
model_name='userpreference',
name='shopping_add_onhand',
field=models.BooleanField(default=False),
),
migrations.RunPython(add_default_trigram),
]
================================================
FILE: cookbook/migrations/0167_userpreference_left_handed.py
================================================
# Generated by Django 3.2.11 on 2022-01-20 22:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0166_alter_userpreference_shopping_add_onhand'),
]
operations = [
migrations.AddField(
model_name='userpreference',
name='left_handed',
field=models.BooleanField(default=False),
),
]
================================================
FILE: cookbook/migrations/0168_add_unit_searchfields.py
================================================
from django.db import migrations
from cookbook.models import SearchFields
def create_searchfields(apps, schema_editor):
SearchFields.objects.create(name='Units', field='steps__ingredients__unit__name')
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0167_userpreference_left_handed'),
]
operations = [
migrations.RunPython(
create_searchfields
),
]
================================================
FILE: cookbook/migrations/0169_exportlog.py
================================================
# Generated by Django 3.2.11 on 2022-02-03 15:03
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
import cookbook.models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0168_add_unit_searchfields'),
]
operations = [
migrations.CreateModel(
name='ExportLog',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('type', models.CharField(max_length=32)),
('running', models.BooleanField(default=True)),
('msg', models.TextField(default='')),
('total_recipes', models.IntegerField(default=0)),
('exported_recipes', models.IntegerField(default=0)),
('cache_duration', models.IntegerField(default=0)),
('possibly_not_expired', models.BooleanField(default=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
]
================================================
FILE: cookbook/migrations/0170_auto_20220207_1848.py
================================================
# Generated by Django 3.2.11 on 2022-02-07 17:48
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
import cookbook.models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0169_exportlog'),
]
operations = [
migrations.AddField(
model_name='food',
name='child_inherit_fields',
field=models.ManyToManyField(blank=True, related_name='child_inherit', to='cookbook.FoodInheritField'),
),
migrations.AddField(
model_name='food',
name='substitute',
field=models.ManyToManyField(blank=True, related_name='_cookbook_food_substitute_+', to='cookbook.Food'),
),
migrations.AddField(
model_name='food',
name='substitute_children',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='food',
name='substitute_siblings',
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name='ingredient',
name='unit',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='cookbook.unit'),
),
migrations.CreateModel(
name='CustomFilter',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128)),
('type', models.CharField(choices=[('RECIPE', 'Recipe'), ('FOOD', 'Food'), ('KEYWORD', 'Keyword')], default=('RECIPE', 'Recipe'), max_length=128)),
('search', models.TextField()),
('created_at', models.DateTimeField(auto_now_add=True)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('shared', models.ManyToManyField(blank=True, related_name='f_shared_with', to=settings.AUTH_USER_MODEL)),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
migrations.AddField(
model_name='recipebook',
name='filter',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='cookbook.customfilter'),
),
migrations.AddConstraint(
model_name='customfilter',
constraint=models.UniqueConstraint(fields=('space', 'name'), name='cf_unique_name_per_space'),
),
]
================================================
FILE: cookbook/migrations/0171_alter_searchpreference_trigram_threshold.py
================================================
# Generated by Django 3.2.12 on 2022-02-15 00:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0170_auto_20220207_1848'),
]
operations = [
migrations.AlterField(
model_name='searchpreference',
name='trigram_threshold',
field=models.DecimalField(decimal_places=2, default=0.2, max_digits=3),
),
]
================================================
FILE: cookbook/migrations/0172_ingredient_original_text.py
================================================
# Generated by Django 3.2.12 on 2022-02-25 15:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0171_alter_searchpreference_trigram_threshold'),
]
operations = [
migrations.AddField(
model_name='ingredient',
name='original_text',
field=models.CharField(blank=True, default=None, max_length=512, null=True),
),
]
================================================
FILE: cookbook/migrations/0173_recipe_source_url.py
================================================
# Generated by Django 3.2.12 on 2022-03-04 13:39
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0172_ingredient_original_text'),
]
operations = [
migrations.AddField(
model_name='recipe',
name='source_url',
field=models.CharField(blank=True, default=None, max_length=1024, null=True),
),
]
================================================
FILE: cookbook/migrations/0174_alter_food_substitute_userspace.py
================================================
# Generated by Django 4.0.4 on 2022-05-31 14:10
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
from django_scopes import scopes_disabled
def migrate_space_permissions(apps, schema_editor):
with scopes_disabled():
UserPreference = apps.get_model('cookbook', 'UserPreference')
UserSpace = apps.get_model('cookbook', 'UserSpace')
for up in UserPreference.objects.exclude(space=None).all():
us = UserSpace.objects.create(user=up.user, space=up.space, active=True)
us.groups.set(up.user.groups.all())
class Migration(migrations.Migration):
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0173_recipe_source_url'),
]
operations = [
migrations.AlterField(
model_name='food',
name='substitute',
field=models.ManyToManyField(blank=True, to='cookbook.food'),
),
migrations.CreateModel(
name='UserSpace',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('active', models.BooleanField(default=False)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('groups', models.ManyToManyField(to='auth.group')),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.RunPython(migrate_space_permissions)
]
================================================
FILE: cookbook/migrations/0175_remove_userpreference_space.py
================================================
# Generated by Django 4.0.4 on 2022-05-31 14:56
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0174_alter_food_substitute_userspace'),
]
operations = [
migrations.RemoveField(
model_name='userpreference',
name='space',
),
]
================================================
FILE: cookbook/migrations/0176_alter_searchpreference_icontains_and_more.py
================================================
# Generated by Django 4.0.4 on 2022-06-14 14:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0175_remove_userpreference_space'),
]
operations = [
migrations.AlterField(
model_name='searchpreference',
name='icontains',
field=models.ManyToManyField(blank=True, related_name='icontains_fields', to='cookbook.searchfields'),
),
migrations.AlterField(
model_name='searchpreference',
name='trigram',
field=models.ManyToManyField(blank=True, related_name='trigram_fields', to='cookbook.searchfields'),
),
migrations.AlterField(
model_name='searchpreference',
name='unaccent',
field=models.ManyToManyField(blank=True, related_name='unaccent_fields', to='cookbook.searchfields'),
),
]
================================================
FILE: cookbook/migrations/0177_recipe_show_ingredient_overview.py
================================================
# Generated by Django 4.0.4 on 2022-06-26 10:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0176_alter_searchpreference_icontains_and_more'),
]
operations = [
migrations.AddField(
model_name='recipe',
name='show_ingredient_overview',
field=models.BooleanField(default=True),
),
]
================================================
FILE: cookbook/migrations/0178_remove_userpreference_search_style_and_more.py
================================================
# Generated by Django 4.0.6 on 2022-07-12 18:04
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0177_recipe_show_ingredient_overview'),
]
operations = [
migrations.RemoveField(
model_name='userpreference',
name='search_style',
),
migrations.RemoveField(
model_name='userpreference',
name='show_recent',
),
]
================================================
FILE: cookbook/migrations/0179_recipe_private_recipe_shared.py
================================================
# Generated by Django 4.0.6 on 2022-07-13 10:53
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0178_remove_userpreference_search_style_and_more'),
]
operations = [
migrations.AddField(
model_name='recipe',
name='private',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='recipe',
name='shared',
field=models.ManyToManyField(blank=True, related_name='recipe_shared_with', to=settings.AUTH_USER_MODEL),
),
]
================================================
FILE: cookbook/migrations/0180_invitelink_reusable.py
================================================
# Generated by Django 4.0.6 on 2022-07-14 09:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0179_recipe_private_recipe_shared'),
]
operations = [
migrations.AddField(
model_name='invitelink',
name='reusable',
field=models.BooleanField(default=False),
),
]
================================================
FILE: cookbook/migrations/0181_space_image.py
================================================
# Generated by Django 4.0.6 on 2022-07-14 11:14
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0180_invitelink_reusable'),
]
operations = [
migrations.AddField(
model_name='space',
name='image',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='space_image', to='cookbook.userfile'),
),
]
================================================
FILE: cookbook/migrations/0182_userpreference_image.py
================================================
# Generated by Django 4.0.6 on 2022-07-14 13:32
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0181_space_image'),
]
operations = [
migrations.AddField(
model_name='userpreference',
name='image',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='user_image', to='cookbook.userfile'),
),
]
================================================
FILE: cookbook/migrations/0183_alter_space_image.py
================================================
# Generated by Django 4.0.6 on 2022-08-04 16:46
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0182_userpreference_image'),
]
operations = [
migrations.AlterField(
model_name='space',
name='image',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='space_image', to='cookbook.userfile'),
),
]
================================================
FILE: cookbook/migrations/0184_alter_userpreference_image.py
================================================
# Generated by Django 4.0.7 on 2022-09-12 10:29
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0183_alter_space_image'),
]
operations = [
migrations.AlterField(
model_name='userpreference',
name='image',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='user_image', to='cookbook.userfile'),
),
]
================================================
FILE: cookbook/migrations/0185_food_plural_name_ingredient_always_use_plural_food_and_more.py
================================================
# Generated by Django 4.0.8 on 2022-11-22 06:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0184_alter_userpreference_image'),
]
operations = [
migrations.AddField(
model_name='food',
name='plural_name',
field=models.CharField(blank=True, default=None, max_length=128, null=True),
),
migrations.AddField(
model_name='ingredient',
name='always_use_plural_food',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='ingredient',
name='always_use_plural_unit',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='space',
name='use_plural',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='unit',
name='plural_name',
field=models.CharField(blank=True, default=None, max_length=128, null=True),
),
]
================================================
FILE: cookbook/migrations/0186_automation_order_alter_automation_type.py
================================================
# Generated by Django 4.1.4 on 2023-01-03 21:38
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0185_food_plural_name_ingredient_always_use_plural_food_and_more'),
]
operations = [
migrations.AddField(
model_name='automation',
name='order',
field=models.IntegerField(default=1000),
),
migrations.AlterField(
model_name='automation',
name='type',
field=models.CharField(choices=[('FOOD_ALIAS', 'Food Alias'), ('UNIT_ALIAS', 'Unit Alias'), ('KEYWORD_ALIAS', 'Keyword Alias'), ('DESCRIPTION_REPLACE', 'Description Replace'), ('INSTRUCTION_REPLACE', 'Instruction Replace')], max_length=128),
),
]
================================================
FILE: cookbook/migrations/0187_alter_space_use_plural.py
================================================
# Generated by Django 4.1.4 on 2023-01-20 09:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0186_automation_order_alter_automation_type'),
]
operations = [
migrations.AlterField(
model_name='space',
name='use_plural',
field=models.BooleanField(default=True),
),
]
================================================
FILE: cookbook/migrations/0188_space_no_sharing_limit.py
================================================
# Generated by Django 4.1.4 on 2023-02-12 16:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0187_alter_space_use_plural'),
]
operations = [
migrations.AddField(
model_name='space',
name='no_sharing_limit',
field=models.BooleanField(default=False),
),
]
================================================
FILE: cookbook/migrations/0189_property_propertytype_unitconversion_food_fdc_id_and_more.py
================================================
# Generated by Django 4.1.9 on 2023-05-25 13:05
import cookbook.models
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django_prometheus.models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0188_space_no_sharing_limit'),
]
operations = [
migrations.CreateModel(
name='Property',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('property_amount', models.DecimalField(decimal_places=4, default=0, max_digits=32)),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
migrations.CreateModel(
name='PropertyType',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128)),
('unit', models.CharField(blank=True, max_length=64, null=True)),
('icon', models.CharField(blank=True, max_length=16, null=True)),
('description', models.CharField(blank=True, max_length=512, null=True)),
('category', models.CharField(blank=True, choices=[('NUTRITION', 'Nutrition'), ('ALLERGEN', 'Allergen'), ('PRICE', 'Price'), ('GOAL', 'Goal'), ('OTHER', 'Other')], max_length=64, null=True)),
('open_data_slug', models.CharField(blank=True, default=None, max_length=128, null=True)),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
migrations.CreateModel(
name='UnitConversion',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('base_amount', models.DecimalField(decimal_places=16, default=0, max_digits=32)),
('converted_amount', models.DecimalField(decimal_places=16, default=0, max_digits=32)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('open_data_slug', models.CharField(blank=True, default=None, max_length=128, null=True)),
],
bases=(django_prometheus.models.ExportModelOperationsMixin('unit_conversion'), models.Model, cookbook.models.PermissionModelMixin),
),
migrations.AddField(
model_name='food',
name='fdc_id',
field=models.CharField(blank=True, default=None, max_length=128, null=True),
),
migrations.AddField(
model_name='food',
name='open_data_slug',
field=models.CharField(blank=True, default=None, max_length=128, null=True),
),
migrations.AddField(
model_name='food',
name='preferred_shopping_unit',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='preferred_shopping_unit', to='cookbook.unit'),
),
migrations.AddField(
model_name='food',
name='preferred_unit',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='preferred_unit', to='cookbook.unit'),
),
migrations.AddField(
model_name='food',
name='properties_food_amount',
field=models.IntegerField(blank=True, default=100),
),
migrations.AddField(
model_name='food',
name='properties_food_unit',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='cookbook.unit'),
),
migrations.AddField(
model_name='supermarket',
name='open_data_slug',
field=models.CharField(blank=True, default=None, max_length=128, null=True),
),
migrations.AddField(
model_name='supermarketcategory',
name='open_data_slug',
field=models.CharField(blank=True, default=None, max_length=128, null=True),
),
migrations.AddField(
model_name='unit',
name='base_unit',
field=models.TextField(blank=True, default=None, max_length=256, null=True),
),
migrations.AddField(
model_name='unit',
name='open_data_slug',
field=models.CharField(blank=True, default=None, max_length=128, null=True),
),
migrations.AddConstraint(
model_name='supermarketcategoryrelation',
constraint=models.UniqueConstraint(fields=('supermarket', 'category'), name='unique_sm_category_relation'),
),
migrations.AddField(
model_name='unitconversion',
name='base_unit',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='unit_conversion_base_relation', to='cookbook.unit'),
),
migrations.AddField(
model_name='unitconversion',
name='converted_unit',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='unit_conversion_converted_relation', to='cookbook.unit'),
),
migrations.AddField(
model_name='unitconversion',
name='created_by',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='unitconversion',
name='food',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.food'),
),
migrations.AddField(
model_name='unitconversion',
name='space',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
),
migrations.AddField(
model_name='propertytype',
name='space',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
),
migrations.AddField(
model_name='property',
name='property_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='cookbook.propertytype'),
),
migrations.AddField(
model_name='property',
name='space',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
),
migrations.AddField(
model_name='food',
name='properties',
field=models.ManyToManyField(blank=True, to='cookbook.property'),
),
migrations.AddField(
model_name='recipe',
name='properties',
field=models.ManyToManyField(blank=True, to='cookbook.property'),
),
migrations.AddConstraint(
model_name='unitconversion',
constraint=models.UniqueConstraint(fields=('space', 'base_unit', 'converted_unit', 'food'), name='f_unique_conversion_per_space'),
),
migrations.AddConstraint(
model_name='propertytype',
constraint=models.UniqueConstraint(fields=('space', 'name'), name='property_type_unique_name_per_space'),
),
]
================================================
FILE: cookbook/migrations/0190_auto_20230525_1506.py
================================================
# Generated by Django 4.1.9 on 2023-05-25 13:06
from django.db import migrations
from django_scopes import scopes_disabled
from gettext import gettext as _
def migrate_old_nutrition_data(apps, schema_editor):
print('Transforming nutrition information, this might take a while on large databases')
with scopes_disabled():
PropertyType = apps.get_model('cookbook', 'PropertyType')
RecipeProperty = apps.get_model('cookbook', 'Property')
Recipe = apps.get_model('cookbook', 'Recipe')
Space = apps.get_model('cookbook', 'Space')
# TODO respect space
for s in Space.objects.all():
property_fat = PropertyType.objects.get_or_create(name=_('Fat'), unit=_('g'), space=s, )[0]
property_carbohydrates = PropertyType.objects.get_or_create(name=_('Carbohydrates'), unit=_('g'), space=s, )[0]
property_proteins = PropertyType.objects.get_or_create(name=_('Proteins'), unit=_('g'), space=s, )[0]
property_calories = PropertyType.objects.get_or_create(name=_('Calories'), unit=_('kcal'), space=s, )[0]
for r in Recipe.objects.filter(nutrition__isnull=False, space=s).all():
rp_fat = RecipeProperty.objects.create(property_type=property_fat, property_amount=r.nutrition.fats, space=s)
rp_carbohydrates = RecipeProperty.objects.create(property_type=property_carbohydrates, property_amount=r.nutrition.carbohydrates, space=s)
rp_proteins = RecipeProperty.objects.create(property_type=property_proteins, property_amount=r.nutrition.proteins, space=s)
rp_calories = RecipeProperty.objects.create(property_type=property_calories, property_amount=r.nutrition.calories, space=s)
r.properties.add(rp_fat, rp_carbohydrates, rp_proteins, rp_calories)
r.nutrition = None
r.save()
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0189_property_propertytype_unitconversion_food_fdc_id_and_more'),
]
operations = [
migrations.RunPython(migrate_old_nutrition_data)
]
================================================
FILE: cookbook/migrations/0191_foodproperty_property_import_food_id_and_more.py
================================================
# Generated by Django 4.1.9 on 2023-06-20 13:07
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0190_auto_20230525_1506'),
]
operations = [
migrations.SeparateDatabaseAndState(
database_operations=[
migrations.RunSQL(
sql="ALTER TABLE cookbook_food_properties RENAME TO cookbook_foodproperty",
reverse_sql="ALTER TABLE cookbook_foodproperty RENAME TO cookbook_food_properties",
),
],
state_operations=[
migrations.CreateModel(
name='FoodProperty',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('food', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.food')),
('property', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.property')),
],
),
migrations.AlterField(
model_name='food',
name='properties',
field=models.ManyToManyField(blank=True, through='cookbook.FoodProperty', to='cookbook.property'),
),
]
),
migrations.AddConstraint(
model_name='foodproperty',
constraint=models.UniqueConstraint(fields=('food', 'property'), name='property_unique_food'),
),
migrations.AddField(
model_name='property',
name='import_food_id',
field=models.IntegerField(blank=True, null=True),
),
migrations.AddConstraint(
model_name='property',
constraint=models.UniqueConstraint(fields=('space', 'property_type', 'import_food_id'), name='property_unique_import_food_per_space'),
),
]
================================================
FILE: cookbook/migrations/0192_food_food_unique_open_data_slug_per_space_and_more.py
================================================
# Generated by Django 4.1.9 on 2023-06-20 13:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0191_foodproperty_property_import_food_id_and_more'),
]
operations = [
migrations.AddConstraint(
model_name='food',
constraint=models.UniqueConstraint(fields=('space', 'open_data_slug'), name='food_unique_open_data_slug_per_space'),
),
migrations.AddConstraint(
model_name='propertytype',
constraint=models.UniqueConstraint(fields=('space', 'open_data_slug'), name='property_type_unique_open_data_slug_per_space'),
),
migrations.AddConstraint(
model_name='supermarket',
constraint=models.UniqueConstraint(fields=('space', 'open_data_slug'), name='supermarket_unique_open_data_slug_per_space'),
),
migrations.AddConstraint(
model_name='supermarketcategory',
constraint=models.UniqueConstraint(fields=('space', 'open_data_slug'), name='supermarket_category_unique_open_data_slug_per_space'),
),
migrations.AddConstraint(
model_name='unit',
constraint=models.UniqueConstraint(fields=('space', 'open_data_slug'), name='unit_unique_open_data_slug_per_space'),
),
migrations.AddConstraint(
model_name='unitconversion',
constraint=models.UniqueConstraint(fields=('space', 'open_data_slug'), name='unit_conversion_unique_open_data_slug_per_space'),
),
]
================================================
FILE: cookbook/migrations/0193_space_internal_note.py
================================================
# Generated by Django 4.1.9 on 2023-06-21 13:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0192_food_food_unique_open_data_slug_per_space_and_more'),
]
operations = [
migrations.AddField(
model_name='space',
name='internal_note',
field=models.TextField(blank=True, null=True),
),
]
================================================
FILE: cookbook/migrations/0194_alter_food_properties_food_amount.py
================================================
# Generated by Django 4.1.9 on 2023-06-26 13:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0193_space_internal_note'),
]
operations = [
migrations.AlterField(
model_name='food',
name='properties_food_amount',
field=models.DecimalField(blank=True, decimal_places=2, default=100, max_digits=16),
),
]
================================================
FILE: cookbook/migrations/0195_invitelink_internal_note_userspace_internal_note_and_more.py
================================================
# Generated by Django 4.1.9 on 2023-06-30 20:34
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0194_alter_food_properties_food_amount'),
]
operations = [
migrations.AddField(
model_name='invitelink',
name='internal_note',
field=models.TextField(blank=True, null=True),
),
migrations.AddField(
model_name='userspace',
name='internal_note',
field=models.TextField(blank=True, null=True),
),
migrations.AddField(
model_name='userspace',
name='invite_link',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='cookbook.invitelink'),
),
migrations.AlterField(
model_name='userpreference',
name='theme',
field=models.CharField(choices=[('TANDOOR', 'Tandoor'), ('BOOTSTRAP', 'Bootstrap'), ('DARKLY', 'Darkly'), ('FLATLY', 'Flatly'), ('SUPERHERO', 'Superhero'), ('TANDOOR_DARK', 'Tandoor Dark (INCOMPLETE)')], default='TANDOOR', max_length=128),
),
]
================================================
FILE: cookbook/migrations/0196_food_url.py
================================================
# Generated by Django 4.1.10 on 2023-07-22 06:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0195_invitelink_internal_note_userspace_internal_note_and_more'),
]
operations = [
migrations.AddField(
model_name='food',
name='url',
field=models.CharField(blank=True, default='', max_length=1024, null=True),
),
]
================================================
FILE: cookbook/migrations/0197_step_show_ingredients_table_and_more.py
================================================
# Generated by Django 4.1.10 on 2023-08-24 08:43
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0196_food_url'),
]
operations = [
migrations.AddField(
model_name='step',
name='show_ingredients_table',
field=models.BooleanField(default=True),
),
migrations.AddField(
model_name='userpreference',
name='show_step_ingredients',
field=models.BooleanField(default=True),
),
]
================================================
FILE: cookbook/migrations/0198_propertytype_order.py
================================================
# Generated by Django 4.1.10 on 2023-08-24 09:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0197_step_show_ingredients_table_and_more'),
]
operations = [
migrations.AddField(
model_name='propertytype',
name='order',
field=models.IntegerField(default=0),
),
]
================================================
FILE: cookbook/migrations/0199_alter_propertytype_options_alter_automation_type_and_more.py
================================================
# Generated by Django 4.1.10 on 2023-09-01 17:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0198_propertytype_order'),
]
operations = [
migrations.AlterField(
model_name='automation',
name='type',
field=models.CharField(
choices=[
('FOOD_ALIAS', 'Food Alias'),
('UNIT_ALIAS', 'Unit Alias'),
('KEYWORD_ALIAS', 'Keyword Alias'),
('DESCRIPTION_REPLACE', 'Description Replace'),
('INSTRUCTION_REPLACE', 'Instruction Replace'),
('NEVER_UNIT', 'Never Unit'),
('TRANSPOSE_WORDS', 'Transpose Words'),
('FOOD_REPLACE', 'Food Replace'),
('UNIT_REPLACE', 'Unit Replace'),
('NAME_REPLACE', 'Name Replace')],
max_length=128),
),
]
================================================
FILE: cookbook/migrations/0200_alter_propertytype_options_remove_keyword_icon_and_more.py
================================================
# Generated by Django 4.1.10 on 2023-08-29 11:59
from django.db import migrations
from django.db.models import F, Value, Count
from django.db.models.functions import Concat
from django_scopes import scopes_disabled
def migrate_icons(apps, schema_editor):
with scopes_disabled():
MealType = apps.get_model('cookbook', 'MealType')
Keyword = apps.get_model('cookbook', 'Keyword')
PropertyType = apps.get_model('cookbook', 'PropertyType')
RecipeBook = apps.get_model('cookbook', 'RecipeBook')
duplicate_meal_types = MealType.objects.values('space_id', 'name').annotate(name_count=Count('name')).exclude(name_count=1).all()
if len(duplicate_meal_types) > 0:
raise RuntimeError(f'Duplicate MealTypes found, please remove/rename them and run migrations again/restart the container. {duplicate_meal_types}')
MealType.objects.update(name=Concat(F('icon'), Value(' '), F('name')))
duplicate_meal_types = Keyword.objects.values('space_id', 'name').annotate(name_count=Count('name')).exclude(name_count=1).all()
if len(duplicate_meal_types) > 0:
raise RuntimeError(f'Duplicate Keyword found, please remove/rename them and run migrations again/restart the container. {duplicate_meal_types}')
Keyword.objects.update(name=Concat(F('icon'), Value(' '), F('name')))
duplicate_meal_types = PropertyType.objects.values('space_id', 'name').annotate(name_count=Count('name')).exclude(name_count=1).all()
if len(duplicate_meal_types) > 0:
raise RuntimeError(f'Duplicate PropertyType found, please remove/rename them and run migrations again/restart the container. {duplicate_meal_types}')
PropertyType.objects.update(name=Concat(F('icon'), Value(' '), F('name')))
duplicate_meal_types = RecipeBook.objects.values('space_id', 'name').annotate(name_count=Count('name')).exclude(name_count=1).all()
if len(duplicate_meal_types) > 0:
raise RuntimeError(f'Duplicate RecipeBook found, please remove/rename them and run migrations again/restart the container. {duplicate_meal_types}')
RecipeBook.objects.update(name=Concat(F('icon'), Value(' '), F('name')))
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0199_alter_propertytype_options_alter_automation_type_and_more'),
]
operations = [
migrations.RunPython(migrate_icons),
migrations.AlterModelOptions(
name='propertytype',
options={'ordering': ('order',)},
),
migrations.RemoveField(
model_name='keyword',
name='icon',
),
migrations.RemoveField(
model_name='mealtype',
name='icon',
),
migrations.RemoveField(
model_name='propertytype',
name='icon',
),
migrations.RemoveField(
model_name='recipebook',
name='icon',
),
]
================================================
FILE: cookbook/migrations/0201_rename_date_mealplan_from_date_mealplan_to_date.py
================================================
# Generated by Django 4.1.10 on 2023-09-08 12:20
from django.db import migrations, models
from django.db.models import F
from django_scopes import scopes_disabled
def apply_migration(apps, schema_editor):
with scopes_disabled():
MealPlan = apps.get_model('cookbook', 'MealPlan')
MealPlan.objects.update(to_date=F('from_date'))
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0200_alter_propertytype_options_remove_keyword_icon_and_more'),
]
operations = [
migrations.RenameField(
model_name='mealplan',
old_name='date',
new_name='from_date',
),
migrations.AddField(
model_name='mealplan',
name='to_date',
field=models.DateField(blank=True, null=True),
),
migrations.RunPython(apply_migration),
migrations.AlterField(
model_name='mealplan',
name='to_date',
field=models.DateField(),
),
]
================================================
FILE: cookbook/migrations/0202_remove_space_show_facet_count.py
================================================
# Generated by Django 4.1.10 on 2023-09-12 13:37
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0201_rename_date_mealplan_from_date_mealplan_to_date'),
]
operations = [
migrations.RemoveField(
model_name='space',
name='show_facet_count',
),
]
================================================
FILE: cookbook/migrations/0203_alter_unique_contstraints.py
================================================
# Generated by Django 4.2.5 on 2023-09-14 12:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0202_remove_space_show_facet_count'),
]
operations = [
migrations.AddConstraint(
model_name='mealtype',
constraint=models.UniqueConstraint(fields=('space', 'name', 'created_by'), name='mt_unique_name_per_space'),
),
]
================================================
FILE: cookbook/migrations/0204_propertytype_fdc_id.py
================================================
# Generated by Django 4.2.7 on 2023-11-27 21:09
from django.db import migrations, models
from django_scopes import scopes_disabled
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0203_alter_unique_contstraints'),
]
operations = [
migrations.AddField(
model_name='propertytype',
name='fdc_id',
field=models.CharField(blank=True, default=None, max_length=128, null=True),
),
]
================================================
FILE: cookbook/migrations/0205_alter_food_fdc_id_alter_propertytype_fdc_id.py
================================================
# Generated by Django 4.2.7 on 2023-11-29 19:44
from django.db import migrations, models
from django_scopes import scopes_disabled
def fix_fdc_ids(apps, schema_editor):
with scopes_disabled():
# in case any food had a non digit fdc ID before this migration, remove it
Food = apps.get_model('cookbook', 'Food')
Food.objects.exclude(fdc_id__regex=r'^\d+$').exclude(fdc_id=None).update(fdc_id=None)
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0204_propertytype_fdc_id'),
]
operations = [
migrations.RunPython(fix_fdc_ids),
migrations.AlterField(
model_name='food',
name='fdc_id',
field=models.IntegerField(blank=True, default=None, null=True),
),
migrations.AlterField(
model_name='propertytype',
name='fdc_id',
field=models.IntegerField(blank=True, default=None, null=True),
),
]
================================================
FILE: cookbook/migrations/0206_rename_sticky_navbar_userpreference_nav_sticky_and_more.py
================================================
# Generated by Django 4.2.7 on 2024-01-01 18:44
import django
from django.db import migrations, models
from django_scopes import scopes_disabled
TANDOOR = 'TANDOOR'
TANDOOR_DARK = 'TANDOOR_DARK'
BOOTSTRAP = 'BOOTSTRAP'
DARKLY = 'DARKLY'
FLATLY = 'FLATLY'
SUPERHERO = 'SUPERHERO'
PRIMARY = 'PRIMARY'
SECONDARY = 'SECONDARY'
SUCCESS = 'SUCCESS'
INFO = 'INFO'
WARNING = 'WARNING'
DANGER = 'DANGER'
LIGHT = 'LIGHT'
DARK = 'DARK'
# ['light', 'warning', 'info', 'success'] --> light (theming_tags L45)
def get_nav_bg_color(theme, nav_color):
if theme == TANDOOR: # primary not actually primary color but override existed before update, same for dark
return {PRIMARY: '#ddbf86', SECONDARY: '#b55e4f', SUCCESS: '#82aa8b', INFO: '#385f84', WARNING: '#eaaa21', DANGER: '#a7240e', LIGHT: '#cfd5cd', DARK: '#221e1e'}[nav_color]
if theme == TANDOOR_DARK:
return {PRIMARY: '#ddbf86', SECONDARY: '#b55e4f', SUCCESS: '#82aa8b', INFO: '#385f84', WARNING: '#eaaa21', DANGER: '#a7240e', LIGHT: '#cfd5cd', DARK: '#221e1e'}[nav_color]
if theme == BOOTSTRAP:
return {PRIMARY: '#007bff', SECONDARY: '#6c757d', SUCCESS: '#28a745', INFO: '#17a2b8', WARNING: '#ffc107', DANGER: '#dc3545', LIGHT: '#f8f9fa', DARK: '#343a40'}[nav_color]
if theme == DARKLY:
return {PRIMARY: '#375a7f', SECONDARY: '#444', SUCCESS: '#00bc8c', INFO: '#3498DB', WARNING: '#F39C12', DANGER: '#E74C3C', LIGHT: '#999', DARK: '#303030'}[nav_color]
if theme == FLATLY:
return {PRIMARY: '#2C3E50', SECONDARY: '#95a5a6', SUCCESS: '#18BC9C', INFO: '#3498DB', WARNING: '#F39C12', DANGER: '#E74C3C', LIGHT: '#ecf0f1', DARK: '#7b8a8b'}[nav_color]
if theme == SUPERHERO:
return {PRIMARY: '#DF691A', SECONDARY: '#4E5D6C', SUCCESS: '#5cb85c', INFO: '#5bc0de', WARNING: '#f0ad4e', DANGER: '#d9534f', LIGHT: '#abb6c2', DARK: '#4E5D6C'}[nav_color]
def get_nav_text_color(theme, nav_color):
if theme == TANDOOR:
return {PRIMARY: DARK, SECONDARY: DARK, SUCCESS: DARK, INFO: DARK, WARNING: DARK, DANGER: DARK, LIGHT: DARK, DARK: DARK}[nav_color]
if theme == TANDOOR_DARK:
return {PRIMARY: DARK, SECONDARY: DARK, SUCCESS: DARK, INFO: DARK, WARNING: DARK, DANGER: DARK, LIGHT: DARK, DARK: DARK}[nav_color]
if theme == BOOTSTRAP:
return {PRIMARY: DARK, SECONDARY: DARK, SUCCESS: DARK, INFO: DARK, WARNING: DARK, DANGER: DARK, LIGHT: DARK, DARK: DARK}[nav_color]
if theme == DARKLY:
return {PRIMARY: DARK, SECONDARY: DARK, SUCCESS: DARK, INFO: DARK, WARNING: DARK, DANGER: DARK, LIGHT: DARK, DARK: DARK}[nav_color]
if theme == FLATLY:
return {PRIMARY: DARK, SECONDARY: DARK, SUCCESS: LIGHT, INFO: LIGHT, WARNING: LIGHT, DANGER: DARK, LIGHT: LIGHT, DARK: DARK}[nav_color]
if theme == SUPERHERO:
return {PRIMARY: DARK, SECONDARY: DARK, SUCCESS: LIGHT, INFO: LIGHT, WARNING: LIGHT, DANGER: DARK, LIGHT: LIGHT, DARK: DARK}[nav_color]
def get_current_colors(apps, schema_editor):
with scopes_disabled():
# in case any food had a non digit fdc ID before this migration, remove it
UserPreference = apps.get_model('cookbook', 'UserPreference')
update_ups = []
for up in UserPreference.objects.all():
if up.theme != TANDOOR or up.nav_color != PRIMARY:
up.nav_bg_color = get_nav_bg_color(up.theme, up.nav_color)
up.nav_text_color = get_nav_text_color(up.theme, up.nav_color)
up.nav_show_logo = (up.theme == TANDOOR or up.theme == TANDOOR_DARK)
update_ups.append(up)
UserPreference.objects.bulk_update(update_ups, ['nav_bg_color', 'nav_text_color', 'nav_show_logo'])
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0205_alter_food_fdc_id_alter_propertytype_fdc_id'),
]
operations = [
migrations.RenameField(
model_name='userpreference',
old_name='sticky_navbar',
new_name='nav_sticky',
),
migrations.AddField(
model_name='userpreference',
name='nav_bg_color',
field=models.CharField(default='#ddbf86', max_length=8),
),
migrations.AddField(
model_name='userpreference',
name='nav_text_color',
field=models.CharField(choices=[('LIGHT', 'Light'), ('DARK', 'Dark')], default='DARK', max_length=16),
),
migrations.AddField(
model_name='userpreference',
name='nav_show_logo',
field=models.BooleanField(default=True),
),
migrations.RunPython(get_current_colors),
migrations.RemoveField(
model_name='userpreference',
name='nav_color',
),
migrations.AddField(
model_name='space',
name='nav_bg_color',
field=models.CharField(blank=True, default='', max_length=8),
),
migrations.AddField(
model_name='space',
name='nav_logo',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='space_nav_logo', to='cookbook.userfile'),
),
migrations.AddField(
model_name='space',
name='nav_text_color',
field=models.CharField(choices=[('BLANK', '-------'), ('LIGHT', 'Light'), ('DARK', 'Dark')], default='BLANK', max_length=16),
),
migrations.AddField(
model_name='space',
name='space_theme',
field=models.CharField(choices=[('BLANK', '-------'), ('TANDOOR', 'Tandoor'), ('BOOTSTRAP', 'Bootstrap'), ('DARKLY', 'Darkly'), ('FLATLY', 'Flatly'), ('SUPERHERO', 'Superhero'), ('TANDOOR_DARK', 'Tandoor Dark (INCOMPLETE)')],
default='BLANK',
max_length=128),
),
migrations.AddField(
model_name='space',
name='custom_space_theme',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='space_theme', to='cookbook.userfile'),
),
]
================================================
FILE: cookbook/migrations/0207_space_logo_color_128_space_logo_color_144_and_more.py
================================================
# Generated by Django 4.2.7 on 2024-01-06 15:05
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0206_rename_sticky_navbar_userpreference_nav_sticky_and_more'),
]
operations = [
migrations.AddField(
model_name='space',
name='logo_color_128',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='space_logo_color_128', to='cookbook.userfile'),
),
migrations.AddField(
model_name='space',
name='logo_color_144',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='space_logo_color_144', to='cookbook.userfile'),
),
migrations.AddField(
model_name='space',
name='logo_color_180',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='space_logo_color_180', to='cookbook.userfile'),
),
migrations.AddField(
model_name='space',
name='logo_color_192',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='space_logo_color_192', to='cookbook.userfile'),
),
migrations.AddField(
model_name='space',
name='logo_color_32',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='space_logo_color_32', to='cookbook.userfile'),
),
migrations.AddField(
model_name='space',
name='logo_color_512',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='space_logo_color_512', to='cookbook.userfile'),
),
migrations.AddField(
model_name='space',
name='logo_color_svg',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='space_logo_color_svg', to='cookbook.userfile'),
),
]
================================================
FILE: cookbook/migrations/0208_space_app_name_userpreference_max_owned_spaces.py
================================================
# Generated by Django 4.2.7 on 2024-01-14 23:06
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0207_space_logo_color_128_space_logo_color_144_and_more'),
]
operations = [
migrations.AddField(
model_name='space',
name='app_name',
field=models.CharField(blank=True, max_length=40, null=True),
),
migrations.AddField(
model_name='userpreference',
name='max_owned_spaces',
field=models.IntegerField(default=100),
),
]
================================================
FILE: cookbook/migrations/0209_remove_space_use_plural.py
================================================
# Generated by Django 4.2.7 on 2024-01-28 07:42
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0208_space_app_name_userpreference_max_owned_spaces'),
]
operations = [
migrations.RemoveField(
model_name='space',
name='use_plural',
),
]
================================================
FILE: cookbook/migrations/0210_shoppinglistentry_updated_at.py
================================================
# Generated by Django 4.2.7 on 2024-01-28 10:06
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0209_remove_space_use_plural'),
]
operations = [
migrations.AddField(
model_name='shoppinglistentry',
name='updated_at',
field=models.DateTimeField(auto_now=True),
),
]
================================================
FILE: cookbook/migrations/0211_recipebook_order.py
================================================
# Generated by Django 4.2.7 on 2024-02-16 19:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0210_shoppinglistentry_updated_at'),
]
operations = [
migrations.AddField(
model_name='recipebook',
name='order',
field=models.IntegerField(default=0),
),
]
================================================
FILE: cookbook/migrations/0212_alter_property_property_amount.py
================================================
# Generated by Django 4.2.7 on 2024-02-18 07:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0211_recipebook_order'),
]
operations = [
migrations.AlterField(
model_name='property',
name='property_amount',
field=models.DecimalField(decimal_places=4, default=None, max_digits=32, null=True),
),
]
================================================
FILE: cookbook/migrations/0213_remove_property_property_unique_import_food_per_space_and_more.py
================================================
# Generated by Django 4.2.10 on 2024-02-19 13:48
from django.db import migrations, models
from django_scopes import scopes_disabled
def migrate_property_import_slug(apps, schema_editor):
with scopes_disabled():
Property = apps.get_model('cookbook', 'Property')
Food = apps.get_model('cookbook', 'Food')
id_slug_mapping = {}
with scopes_disabled():
for f in Food.objects.filter(open_data_slug__isnull=False).values('id', 'open_data_slug').all():
id_slug_mapping[f['id']] = f['open_data_slug']
property_update_list = []
for p in Property.objects.filter().values('id', 'import_food_id').all():
if p['import_food_id'] in id_slug_mapping:
property_update_list.append(Property(
id=p['id'],
open_data_food_slug=id_slug_mapping[p['import_food_id']]
))
Property.objects.bulk_update(property_update_list, ('open_data_food_slug',))
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0212_alter_property_property_amount'),
]
operations = [
migrations.AddField(
model_name='property',
name='open_data_food_slug',
field=models.CharField(blank=True, default=None, max_length=128, null=True),
),
migrations.RunPython(migrate_property_import_slug),
migrations.RemoveConstraint(
model_name='property',
name='property_unique_import_food_per_space',
),
migrations.RemoveField(
model_name='property',
name='import_food_id',
),
migrations.AddConstraint(
model_name='property',
constraint=models.UniqueConstraint(fields=('space', 'property_type', 'open_data_food_slug'), name='property_unique_import_food_per_space'),
),
]
================================================
FILE: cookbook/migrations/0214_cooklog_comment_cooklog_updated_at_and_more.py
================================================
# Generated by Django 4.2.7 on 2024-02-24 12:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0213_remove_property_property_unique_import_food_per_space_and_more'),
]
operations = [
migrations.AddField(
model_name='cooklog',
name='comment',
field=models.TextField(blank=True, null=True),
),
migrations.AddField(
model_name='cooklog',
name='updated_at',
field=models.DateTimeField(auto_now=True),
),
migrations.AlterField(
model_name='cooklog',
name='rating',
field=models.IntegerField(blank=True, null=True),
),
migrations.AlterField(
model_name='cooklog',
name='servings',
field=models.IntegerField(blank=True, null=True),
),
]
================================================
FILE: cookbook/migrations/0215_connectorconfig.py
================================================
# Generated by Django 4.2.10 on 2024-02-26 14:41
import cookbook.models
from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0214_cooklog_comment_cooklog_updated_at_and_more'),
]
operations = [
migrations.CreateModel(
name='ConnectorConfig',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128, validators=[django.core.validators.MinLengthValidator(1)])),
('type', models.CharField(choices=[('HomeAssistant', 'HomeAssistant')], default='HomeAssistant', max_length=128)),
('enabled', models.BooleanField(default=True, help_text='Is Connector Enabled')),
('on_shopping_list_entry_created_enabled', models.BooleanField(default=False)),
('on_shopping_list_entry_updated_enabled', models.BooleanField(default=False)),
('on_shopping_list_entry_deleted_enabled', models.BooleanField(default=False)),
('url', models.URLField(blank=True, null=True)),
('token', models.CharField(blank=True, max_length=512, null=True)),
('todo_entity', models.CharField(blank=True, max_length=128, null=True)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
]
================================================
FILE: cookbook/migrations/0216_delete_shoppinglist.py
================================================
# Generated by Django 4.2.10 on 2024-02-28 16:21
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0215_connectorconfig'),
]
operations = [
migrations.DeleteModel(
name='ShoppingList',
),
]
================================================
FILE: cookbook/migrations/0217_alter_userpreference_default_page.py
================================================
# Generated by Django 4.2.10 on 2024-03-09 06:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0216_delete_shoppinglist'),
]
operations = [
migrations.AlterField(
model_name='userpreference',
name='default_page',
field=models.CharField(choices=[('SEARCH', 'Search'), ('PLAN', 'Meal-Plan'), ('BOOKS', 'Books'), ('SHOPPING', 'Shopping')], default='SEARCH', max_length=64),
),
]
================================================
FILE: cookbook/migrations/0218_alter_mealplan_from_date_alter_mealplan_to_date.py
================================================
# Generated by Django 4.2.11 on 2024-05-01 10:56
from datetime import timedelta
from django.db import migrations, models
from django_scopes import scopes_disabled
def timezone_correction(apps, schema_editor):
# when converting from date to datetime the field becomes timezone aware and defaults to 00:00:00 UTC
# this will be converted to a local datetime on the client which, for all negative offset timezones,
# would mean that the plan item shows one day prior to the previous planning date
# by setting the time on the old entries to 11:00 this issue will be limited to a very small number of people (see https://en.wikipedia.org/wiki/Time_zone#/media/File:World_Time_Zones_Map.svg)
# the server timezone could be used to guess zone of most of the clients but that would also not be perfect so this much simpler alternative is chosen
with scopes_disabled():
MealPlan = apps.get_model('cookbook', 'MealPlan')
meal_plans = MealPlan.objects.all()
for mp in meal_plans:
mp.from_date += timedelta(hours=12)
mp.to_date += timedelta(hours=12)
MealPlan.objects.bulk_update(meal_plans, ['from_date', 'to_date'], batch_size=1000)
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0217_alter_userpreference_default_page'),
]
operations = [
migrations.AddField(
model_name='mealtype',
name='time',
field=models.TimeField(blank=True, null=True),
),
migrations.AlterField(
model_name='mealplan',
name='from_date',
field=models.DateTimeField(),
),
migrations.AlterField(
model_name='mealplan',
name='to_date',
field=models.DateTimeField(),
),
migrations.RunPython(timezone_correction)
]
================================================
FILE: cookbook/migrations/0219_connectorconfig_supports_description_field.py
================================================
# Generated by Django 4.2.15 on 2024-09-15 10:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0218_alter_mealplan_from_date_alter_mealplan_to_date'),
]
operations = [
migrations.AddField(
model_name='connectorconfig',
name='supports_description_field',
field=models.BooleanField(default=True, help_text='Does the todo entity support the description field'),
),
]
================================================
FILE: cookbook/migrations/0220_shoppinglistrecipe_created_by_and_more.py
================================================
# Generated by Django 4.2.18 on 2025-03-14 10:50
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0219_connectorconfig_supports_description_field'),
]
operations = [
migrations.AddField(
model_name='shoppinglistrecipe',
name='created_by',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='shoppinglistrecipe',
name='space',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
),
]
================================================
FILE: cookbook/migrations/0221_migrate_shoppinglistrecipe_space_created_by.py
================================================
# Generated by Django 4.2.18 on 2025-03-14 10:50
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
from django.db.models import F, Count
from django_scopes import scopes_disabled
def add_space_and_owner_to_shopping_list_recipe(apps, schema_editor):
print('migrating shopping list recipe space attribute, this might take a while ...')
with scopes_disabled():
ShoppingListRecipe = apps.get_model('cookbook', 'ShoppingListRecipe')
# delete all shopping list recipes that do not have entries as those are of no use anyway
ShoppingListRecipe.objects.annotate(entry_count=Count('entries')).filter(entry_count__lte=0).delete()
shopping_list_recipes = ShoppingListRecipe.objects.all().prefetch_related('entries')
update_list = []
for slr in shopping_list_recipes:
if entry := slr.entries.first():
if entry.space and entry.created_by:
slr.space = entry.space
slr.created_by = entry.created_by
update_list.append(slr)
else:
print(slr, 'missing data on entry')
else:
print(slr, 'missing entry')
ShoppingListRecipe.objects.bulk_update(update_list, ['space', 'created_by'], batch_size=500)
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0220_shoppinglistrecipe_created_by_and_more'),
]
operations = [
migrations.RunPython(add_space_and_owner_to_shopping_list_recipe),
]
================================================
FILE: cookbook/migrations/0222_alter_shoppinglistrecipe_created_by_and_more.py
================================================
# Generated by Django 4.2.18 on 2025-03-14 12:41
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0221_migrate_shoppinglistrecipe_space_created_by'),
]
operations = [
migrations.AlterField(
model_name='shoppinglistrecipe',
name='created_by',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='shoppinglistrecipe',
name='space',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
),
]
================================================
FILE: cookbook/migrations/0223_auto_20250831_1111.py
================================================
# Generated by Django 4.2.22 on 2025-08-31 09:11
from django.db import migrations
from django_scopes import scopes_disabled
def migrate_comments(apps, schema_editor):
with scopes_disabled():
Comment = apps.get_model('cookbook', 'Comment')
CookLog = apps.get_model('cookbook', 'CookLog')
cook_logs = []
for c in Comment.objects.all():
cook_logs.append(CookLog(
recipe=c.recipe,
created_by=c.created_by,
created_at=c.created_at,
comment=c.text,
space=c.recipe.space,
))
CookLog.objects.bulk_create(cook_logs, unique_fields=('recipe', 'comment', 'created_at', 'created_by'))
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0222_alter_shoppinglistrecipe_created_by_and_more'),
]
operations = [
migrations.RunPython(migrate_comments),
]
================================================
FILE: cookbook/migrations/0224_space_ai_credits_balance_space_ai_credits_monthly_and_more.py
================================================
# Generated by Django 4.2.22 on 2025-09-05 06:51
import cookbook.models
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0223_auto_20250831_1111'),
]
operations = [
migrations.AddField(
model_name='space',
name='ai_credits_balance',
field=models.IntegerField(default=0),
),
migrations.AddField(
model_name='space',
name='ai_credits_monthly',
field=models.IntegerField(default=100),
),
migrations.CreateModel(
name='AiProvider',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128)),
('description', models.TextField(blank=True)),
('api_key', models.CharField(max_length=2048)),
('model_name', models.CharField(max_length=256)),
('url', models.CharField(blank=True, max_length=2048, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('space', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
),
migrations.CreateModel(
name='AiLog',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('function', models.CharField(max_length=64)),
('credit_cost', models.DecimalField(decimal_places=4, max_digits=16)),
('credits_from_balance', models.BooleanField(default=False)),
('input_tokens', models.IntegerField(default=0)),
('output_tokens', models.IntegerField(default=0)),
('start_time', models.DateTimeField(null=True)),
('end_time', models.DateTimeField(null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('ai_provider', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='cookbook.aiprovider')),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
]
================================================
FILE: cookbook/migrations/0225_space_ai_enabled.py
================================================
# Generated by Django 4.2.22 on 2025-09-08 19:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0224_space_ai_credits_balance_space_ai_credits_monthly_and_more'),
]
operations = [
migrations.AddField(
model_name='space',
name='ai_enabled',
field=models.BooleanField(default=True),
),
]
================================================
FILE: cookbook/migrations/0226_aiprovider_log_credit_cost_and_more.py
================================================
# Generated by Django 4.2.22 on 2025-09-08 20:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0225_space_ai_enabled'),
]
operations = [
migrations.AddField(
model_name='aiprovider',
name='log_credit_cost',
field=models.BooleanField(default=True),
),
migrations.AlterField(
model_name='space',
name='ai_credits_monthly',
field=models.IntegerField(default=10000),
),
]
================================================
FILE: cookbook/migrations/0227_space_ai_default_provider_and_more.py
================================================
# Generated by Django 4.2.22 on 2025-09-09 11:40
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0226_aiprovider_log_credit_cost_and_more'),
]
operations = [
migrations.AddField(
model_name='space',
name='ai_default_provider',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='space_ai_default_provider', to='cookbook.aiprovider'),
),
migrations.AlterField(
model_name='space',
name='ai_credits_balance',
field=models.DecimalField(decimal_places=4, default=0, max_digits=16),
),
]
================================================
FILE: cookbook/migrations/0228_space_space_setup_completed.py
================================================
# Generated by Django 5.2.6 on 2025-09-10 20:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0001_squashed_0227_space_ai_default_provider_and_more'),
]
operations = [
migrations.AddField(
model_name='space',
name='space_setup_completed',
field=models.BooleanField(default=True),
),
]
================================================
FILE: cookbook/migrations/0229_alter_ailog_options_alter_aiprovider_options_and_more.py
================================================
# Generated by Django 5.2.6 on 2025-09-24 17:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0228_space_space_setup_completed'),
]
operations = [
migrations.AlterModelOptions(
name='ailog',
options={'ordering': ('-created_at',)},
),
migrations.AlterModelOptions(
name='aiprovider',
options={'ordering': ('id',)},
),
migrations.AlterField(
model_name='storage',
name='token',
field=models.CharField(blank=True, max_length=4098, null=True),
),
]
================================================
FILE: cookbook/migrations/0230_auto_20250925_2056.py
================================================
# Generated by Django 5.2.6 on 2025-09-25 18:56
from django.db import migrations
from django.contrib.postgres.operations import TrigramExtension, UnaccentExtension
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0229_alter_ailog_options_alter_aiprovider_options_and_more'),
]
operations = [
TrigramExtension(),
UnaccentExtension(),
]
================================================
FILE: cookbook/migrations/0231_alter_aiprovider_options_alter_automation_options_and_more.py
================================================
# Generated by Django 5.2.6 on 2025-09-30 18:47
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0230_auto_20250925_2056'),
]
operations = [
migrations.AlterModelOptions(
name='aiprovider',
options={'ordering': ('pk',)},
),
migrations.AlterModelOptions(
name='automation',
options={'ordering': ('pk',)},
),
migrations.AlterModelOptions(
name='bookmarkletimport',
options={'ordering': ('pk',)},
),
migrations.AlterModelOptions(
name='comment',
options={'ordering': ('pk',)},
),
migrations.AlterModelOptions(
name='connectorconfig',
options={'ordering': ('pk',)},
),
migrations.AlterModelOptions(
name='cooklog',
options={'ordering': ('pk',)},
),
migrations.AlterModelOptions(
name='customfilter',
options={'ordering': ('pk',)},
),
migrations.AlterModelOptions(
name='exportlog',
options={'ordering': ('pk',)},
),
migrations.AlterModelOptions(
name='food',
options={'ordering': ('name',)},
),
migrations.AlterModelOptions(
name='importlog',
options={'ordering': ('pk',)},
),
migrations.AlterModelOptions(
name='invitelink',
options={'ordering': ('pk',)},
),
migrations.AlterModelOptions(
name='keyword',
options={'ordering': ('name',)},
),
migrations.AlterModelOptions(
name='mealplan',
options={'ordering': ('pk',)},
),
migrations.AlterModelOptions(
name='mealtype',
options={'ordering': ('name',)},
),
migrations.AlterModelOptions(
name='recipe',
options={'ordering': ('name',)},
),
migrations.AlterModelOptions(
name='recipebook',
options={'ordering': ('name',)},
),
migrations.AlterModelOptions(
name='recipeimport',
options={'ordering': ('pk',)},
),
migrations.AlterModelOptions(
name='sharelink',
options={'ordering': ('pk',)},
),
migrations.AlterModelOptions(
name='shoppinglistentry',
options={'ordering': ('pk',)},
),
migrations.AlterModelOptions(
name='shoppinglistrecipe',
options={'ordering': ('pk',)},
),
migrations.AlterModelOptions(
name='space',
options={'ordering': ('pk',)},
),
migrations.AlterModelOptions(
name='storage',
options={'ordering': ('pk',)},
),
migrations.AlterModelOptions(
name='supermarket',
options={'ordering': ('name',)},
),
migrations.AlterModelOptions(
name='supermarketcategory',
options={'ordering': ('name',)},
),
migrations.AlterModelOptions(
name='sync',
options={'ordering': ('pk',)},
),
migrations.AlterModelOptions(
name='synclog',
options={'ordering': ('pk',)},
),
migrations.AlterModelOptions(
name='telegrambot',
options={'ordering': ('pk',)},
),
migrations.AlterModelOptions(
name='unit',
options={'ordering': ('name',)},
),
migrations.AlterModelOptions(
name='unitconversion',
options={'ordering': ('pk',)},
),
migrations.AlterModelOptions(
name='userfile',
options={'ordering': ('pk',)},
),
migrations.AlterModelOptions(
name='userspace',
options={'ordering': ('pk',)},
),
migrations.AlterModelOptions(
name='viewlog',
options={'ordering': ('pk',)},
),
]
================================================
FILE: cookbook/migrations/0232_shoppinglist.py
================================================
# Generated by Django 5.2.7 on 2025-11-30 10:19
import cookbook.models
import django.db.models.deletion
import django_prometheus.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0231_alter_aiprovider_options_alter_automation_options_and_more'),
]
operations = [
migrations.CreateModel(
name='ShoppingList',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(blank=True, default='', max_length=32)),
('description', models.TextField(blank=True)),
('color', models.CharField(blank=True, max_length=7, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(django_prometheus.models.ExportModelOperationsMixin('shopping_list'), models.Model, cookbook.models.PermissionModelMixin),
),
]
================================================
FILE: cookbook/migrations/0233_food_shopping_lists_shoppinglistentry_shopping_lists_and_more.py
================================================
# Generated by Django 5.2.7 on 2025-11-30 14:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0232_shoppinglist'),
]
operations = [
migrations.AddField(
model_name='food',
name='shopping_lists',
field=models.ManyToManyField(blank=True, to='cookbook.shoppinglist'),
),
migrations.AddField(
model_name='shoppinglistentry',
name='shopping_lists',
field=models.ManyToManyField(blank=True, to='cookbook.shoppinglist'),
),
migrations.AddField(
model_name='supermarket',
name='shopping_lists',
field=models.ManyToManyField(blank=True, to='cookbook.shoppinglist'),
),
]
================================================
FILE: cookbook/migrations/0234_alter_shoppinglist_options_and_more.py
================================================
# Generated by Django 5.2.8 on 2025-12-03 16:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0233_food_shopping_lists_shoppinglistentry_shopping_lists_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='shoppinglist',
options={'ordering': ('pk',)},
),
migrations.AddField(
model_name='userpreference',
name='shopping_update_food_lists',
field=models.BooleanField(default=True),
),
]
================================================
FILE: cookbook/migrations/0235_recipe_diameter_recipe_diameter_text.py
================================================
# Generated by Django 5.2.10 on 2026-01-28 20:13
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0234_alter_shoppinglist_options_and_more'),
]
operations = [
migrations.AddField(
model_name='recipe',
name='diameter',
field=models.IntegerField(default=0),
),
migrations.AddField(
model_name='recipe',
name='diameter_text',
field=models.CharField(blank=True, default='', max_length=32),
),
]
================================================
FILE: cookbook/migrations/0236_household_userspace_household_inventorylocation_and_more.py
================================================
# Generated by Django 5.2.10 on 2026-02-28 18:17
import cookbook.models
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0235_recipe_diameter_recipe_diameter_text'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Household',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
migrations.AddField(
model_name='userspace',
name='household',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='cookbook.household'),
),
migrations.CreateModel(
name='InventoryLocation',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=64)),
('is_freezer', models.BooleanField(default=False)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('household', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='cookbook.household')),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
migrations.CreateModel(
name='InventoryEntry',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('sub_location', models.CharField(blank=True, max_length=64, null=True)),
('code', models.CharField(blank=True, max_length=16, null=True)),
('amount', models.DecimalField(decimal_places=16, default=0, max_digits=32)),
('expires', models.DateField(blank=True, null=True)),
('note', models.CharField(blank=True, max_length=256, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('food', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.food')),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
('unit', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='cookbook.unit')),
('inventory_location', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.inventorylocation')),
],
options={
'ordering': ('id',),
},
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
migrations.CreateModel(
name='InventoryLog',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('booking_type', models.CharField(choices=[('add', 'Add'), ('remove', 'Remove'), ('move', 'Move')], default='add', max_length=10)),
('old_amount', models.DecimalField(decimal_places=16, default=0, max_digits=32)),
('new_amount', models.DecimalField(decimal_places=16, default=0, max_digits=32)),
('note', models.CharField(blank=True, max_length=256, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('entry', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.inventoryentry')),
('new_inventory_location', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='new_inventory_location', to='cookbook.inventorylocation')),
('old_inventory_location', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='old_inventory_location', to='cookbook.inventorylocation')),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
options={
'ordering': ('created_at',),
},
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
migrations.AddConstraint(
model_name='inventoryentry',
constraint=models.UniqueConstraint(fields=('space', 'code'), name='code_unique_per_space'),
),
]
================================================
FILE: cookbook/migrations/0237_remove_mealtype_mt_unique_name_per_space_and_more.py
================================================
# Generated by Django 5.2.10 on 2026-02-28 19:03
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
from django_scopes import scopes_disabled
from cookbook.models import UserPreference
def apply_migration(apps, schema_editor):
with scopes_disabled():
MealType = apps.get_model('cookbook', 'MealType')
Space = apps.get_model('cookbook', 'Space')
delete_mealtype_ids = []
update_userpreference_list = []
for space in Space.objects.all():
space_mealtypes = []
for mt in MealType.objects.filter(space=space):
replaced_by = None
# check if another mealtype has the same name and if so delete the duplicate
for sMt in space_mealtypes:
if mt.name.strip().lower() == sMt.name.strip().lower():
delete_mealtype_ids.append(mt.id)
replaced_by = sMt
if not replaced_by:
space_mealtypes.append(mt)
# migrate default to userpreference
if mt.default:
if replaced_by:
mt.created_by.userpreference.default_meal_type_id = replaced_by.id
else:
mt.created_by.userpreference.default_meal_type_id = mt.id
update_userpreference_list.append(mt.created_by.userpreference)
MealType.objects.filter(id__in=delete_mealtype_ids).delete()
UserPreference.objects.bulk_update(update_userpreference_list, ['default_meal_type_id'], batch_size=500)
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0236_household_userspace_household_inventorylocation_and_more'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddField(
model_name='userpreference',
name='default_meal_type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='cookbook.mealtype'),
),
migrations.RunPython(apply_migration),
]
================================================
FILE: cookbook/migrations/0238_auto_20260312_1920.py
================================================
# Generated by Django 5.2.10 on 2026-03-12 18:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0237_remove_mealtype_mt_unique_name_per_space_and_more'),
]
operations = [
migrations.RemoveConstraint(
model_name='mealtype',
name='mt_unique_name_per_space',
),
migrations.AddConstraint(
model_name='mealtype',
constraint=models.UniqueConstraint(fields=('space', 'name'), name='mt_unique_name_per_space'),
),
]
================================================
FILE: cookbook/migrations/__init__.py
================================================
================================================
FILE: cookbook/models.py
================================================
import operator
import pathlib
import re
import uuid
from datetime import date, timedelta
import oauth2_provider.models
from annoying.fields import AutoOneToOneField
from django.contrib import auth
from django.contrib.auth.models import Group, User
from django.contrib.postgres.indexes import GinIndex
from django.contrib.postgres.search import SearchVectorField
from django.core.files.uploadedfile import InMemoryUploadedFile, UploadedFile
from django.core.validators import MinLengthValidator
from django.db import IntegrityError, models
from django.db.models import Avg, Index, Max, ProtectedError, Q
from django.db.models.fields.related import ManyToManyField
from django.db.models.functions import Substr
from django.utils import timezone
from django.utils.translation import gettext as _
from django_prometheus.models import ExportModelOperationsMixin
from django_scopes import ScopedManager, scopes_disabled
from PIL import Image
from treebeard.mp_tree import MP_Node, MP_NodeManager
from recipes.settings import (COMMENT_PREF_DEFAULT, FRACTION_PREF_DEFAULT, KJ_PREF_DEFAULT,
SORT_TREE_BY_NAME, STICKY_NAV_PREF_DEFAULT, MAX_OWNED_SPACES_PREF_DEFAULT)
def get_user_display_name(self):
if not (name := f"{self.first_name} {self.last_name}") == " ":
return name
else:
return self.username
def get_active_space(self):
"""
Returns the active space of a user or in case no space is actives raises an *** exception
CAREFUL: cannot be used in django scopes with scope() function because passing None as a scope context means no space checking is enforced (at least I think)!!
:param self: user
:return: space currently active for user
"""
try:
return self.userspace_set.filter(active=True).first().space
except AttributeError:
return None
auth.models.User.add_to_class('get_user_display_name', get_user_display_name)
auth.models.User.add_to_class('get_active_space', get_active_space)
def oauth_token_get_owner(self):
return self.user
oauth2_provider.models.AccessToken.add_to_class('get_owner', oauth_token_get_owner)
def get_model_name(model):
return ('_'.join(re.findall('[A-Z][^A-Z]*', model.__name__))).lower()
class TreeManager(MP_NodeManager):
def create(self, *args, **kwargs):
return self.get_or_create(*args, **kwargs)[0]
# model.Manager get_or_create() is not compatible with MP_Tree
def get_or_create(self, *args, **kwargs):
kwargs['name'] = kwargs['name'].strip()
if hasattr(self, 'space'):
if obj := self.filter(name__iexact=kwargs['name'], space=kwargs['space']).first():
return obj, False
else:
if obj := self.filter(name__iexact=kwargs['name']).first():
return obj, False
with scopes_disabled():
try:
defaults = kwargs.pop('defaults', None)
if defaults:
kwargs = {**kwargs, **defaults}
# ManyToMany fields can't be set this way, so pop them out to save for later
fields = [field.name for field in self.model._meta.get_fields() if issubclass(type(field), ManyToManyField)]
many_to_many = {field: kwargs.pop(field) for field in list(kwargs) if field in fields}
obj = self.model.add_root(**kwargs)
for field in many_to_many:
field_model = getattr(obj, field).model
for related_obj in many_to_many[field]:
if isinstance(related_obj, User):
getattr(obj, field).add(field_model.objects.get(id=related_obj.id))
else:
getattr(obj, field).add(field_model.objects.get(**dict(related_obj)))
return obj, True
except IntegrityError as e:
if 'Key (path)' in e.args[0]:
self.model.fix_tree(fix_paths=True)
return self.model.add_root(**kwargs), True
raise e
class TreeModel(MP_Node):
_full_name_separator = ' > '
def __str__(self):
return f"{self.name}"
@property
def parent(self):
parent = self.get_parent()
if parent:
return self.get_parent().id
return None
@property
def full_name(self) -> str:
"""
Returns a string representation of a tree node and it's ancestors,
e.g. 'Cuisine > Asian > Chinese > Catonese'.
"""
names = [node.name for node in self.get_ancestors_and_self()]
return self._full_name_separator.join(names)
def get_ancestors_and_self(self):
"""
Gets ancestors and includes itself. Use treebeard's get_ancestors
if you don't want to include the node itself. It's a separate
function as it's commonly used in templates.
"""
if self.is_root():
return [self]
return list(self.get_ancestors()) + [self]
def get_descendants_and_self(self):
"""
Gets descendants and includes itself. Use treebeard's get_descendants
if you don't want to include the node itself. It's a separate
function as it's commonly used in templates.
"""
return self.get_tree(self)
def has_children(self):
return self.get_num_children() > 0
def get_num_children(self):
return self.get_children().count()
# use self.objects.get_or_create() instead
@classmethod
def add_root(self, **kwargs):
with scopes_disabled():
return super().add_root(**kwargs)
# i'm 99% sure there is a more idiomatic way to do this subclassing MP_NodeQuerySet
@staticmethod
def include_descendants(queryset=None, filter=None):
"""
:param queryset: Model Queryset to add descendants
:param filter: Filter (exclude) the descendants nodes with the provided Q filter
"""
descendants = Q()
# TODO filter the queryset nodes to exclude descendants of objects in the queryset
nodes = queryset.values('path', 'depth')
for node in nodes:
descendants |= Q(path__startswith=node['path'], depth__gt=node['depth'])
return queryset.model.objects.filter(Q(id__in=queryset.values_list('id')) | descendants)
def exclude_descendants(queryset=None, filter=None):
"""
:param queryset: Model Queryset to add descendants
:param filter: Filter (include) the descendants nodes with the provided Q filter
"""
descendants = Q()
nodes = queryset.values('path', 'depth')
for node in nodes:
descendants |= Q(path__startswith=node['path'], depth__gt=node['depth'])
return queryset.model.objects.filter(id__in=queryset.values_list('id')).exclude(descendants)
def include_ancestors(queryset=None):
"""
:param queryset: Model Queryset to add ancestors
:param filter: Filter (include) the ancestors nodes with the provided Q filter
"""
queryset = queryset.annotate(root=Substr('path', 1, queryset.model.steplen))
nodes = list(set(queryset.values_list('root', 'depth')))
ancestors = Q()
for node in nodes:
ancestors |= Q(path__startswith=node[0], depth__lt=node[1])
return queryset.model.objects.filter(Q(id__in=queryset.values_list('id')) | ancestors)
class Meta:
abstract = True
class MergeModelMixin:
def merge_into(self, target):
"""
very simple merge function that replaces the current instance with the target instance
:param target: target object
:return: target with data merged
"""
if self == target:
raise ValueError('Cannot merge an object with itself')
if getattr(self, 'space', 0) != getattr(target, 'space', 0):
raise RuntimeError('Cannot merge objects from different spaces')
if hasattr(self, 'get_descendants_and_self') and target in callable(getattr(self, 'get_descendants_and_self')):
raise RuntimeError('Cannot merge parent (source) with child (target) object')
# TODO copy field values
class PermissionModelMixin:
@staticmethod
def get_space_key():
return ('space',)
def get_space_kwarg(self):
return '__'.join(self.get_space_key())
def get_owner(self):
if getattr(self, 'created_by', None):
return self.created_by
if getattr(self, 'user', None):
return self.user
return None
def get_shared(self):
if getattr(self, 'shared', None):
return self.shared.all()
return []
def get_space(self):
p = '.'.join(self.get_space_key())
try:
if space := operator.attrgetter(p)(self):
return space
except AttributeError:
raise NotImplementedError('get space for method not implemented and standard fields not available')
class FoodInheritField(models.Model, PermissionModelMixin):
field = models.CharField(max_length=32, unique=True)
name = models.CharField(max_length=64, unique=True)
def __str__(self):
return _(self.name)
@staticmethod
def get_name(self):
return _(self.name)
class Space(ExportModelOperationsMixin('space'), models.Model):
# TODO remove redundant theming constants
# Themes
BLANK = 'BLANK'
TANDOOR = 'TANDOOR'
TANDOOR_DARK = 'TANDOOR_DARK'
BOOTSTRAP = 'BOOTSTRAP'
DARKLY = 'DARKLY'
FLATLY = 'FLATLY'
SUPERHERO = 'SUPERHERO'
THEMES = (
(BLANK, '-------'),
(TANDOOR, 'Tandoor'),
(BOOTSTRAP, 'Bootstrap'),
(DARKLY, 'Darkly'),
(FLATLY, 'Flatly'),
(SUPERHERO, 'Superhero'),
(TANDOOR_DARK, 'Tandoor Dark (INCOMPLETE)'),
)
LIGHT = 'LIGHT'
DARK = 'DARK'
NAV_TEXT_COLORS = (
(BLANK, '-------'),
(LIGHT, 'Light'),
(DARK, 'Dark')
)
name = models.CharField(max_length=128, default='Default')
image = models.ForeignKey("UserFile", on_delete=models.SET_NULL, null=True, blank=True, related_name='space_image')
space_theme = models.CharField(choices=THEMES, max_length=128, default=BLANK)
custom_space_theme = models.ForeignKey("UserFile", on_delete=models.SET_NULL, null=True, blank=True, related_name='space_theme')
nav_logo = models.ForeignKey("UserFile", on_delete=models.SET_NULL, null=True, blank=True, related_name='space_nav_logo')
nav_bg_color = models.CharField(max_length=8, default='', blank=True, )
nav_text_color = models.CharField(max_length=16, choices=NAV_TEXT_COLORS, default=BLANK)
app_name = models.CharField(max_length=40, null=True, blank=True, )
logo_color_32 = models.ForeignKey("UserFile", on_delete=models.SET_NULL, null=True, blank=True, related_name='space_logo_color_32')
logo_color_128 = models.ForeignKey("UserFile", on_delete=models.SET_NULL, null=True, blank=True, related_name='space_logo_color_128')
logo_color_144 = models.ForeignKey("UserFile", on_delete=models.SET_NULL, null=True, blank=True, related_name='space_logo_color_144')
logo_color_180 = models.ForeignKey("UserFile", on_delete=models.SET_NULL, null=True, blank=True, related_name='space_logo_color_180')
logo_color_192 = models.ForeignKey("UserFile", on_delete=models.SET_NULL, null=True, blank=True, related_name='space_logo_color_192')
logo_color_512 = models.ForeignKey("UserFile", on_delete=models.SET_NULL, null=True, blank=True, related_name='space_logo_color_512')
logo_color_svg = models.ForeignKey("UserFile", on_delete=models.SET_NULL, null=True, blank=True, related_name='space_logo_color_svg')
created_by = models.ForeignKey(User, on_delete=models.PROTECT, null=True)
created_at = models.DateTimeField(auto_now_add=True)
message = models.CharField(max_length=512, default='', blank=True)
max_recipes = models.IntegerField(default=0)
max_file_storage_mb = models.IntegerField(default=0, help_text=_('Maximum file storage for space in MB. 0 for unlimited, -1 to disable file upload.'))
max_users = models.IntegerField(default=0)
allow_sharing = models.BooleanField(default=True)
no_sharing_limit = models.BooleanField(default=False)
demo = models.BooleanField(default=False)
food_inherit = models.ManyToManyField(FoodInheritField, blank=True)
space_setup_completed = models.BooleanField(default=True)
ai_enabled = models.BooleanField(default=True)
ai_credits_monthly = models.IntegerField(default=100)
ai_credits_balance = models.DecimalField(default=0, max_digits=16, decimal_places=4)
ai_default_provider = models.ForeignKey("AiProvider", on_delete=models.SET_NULL, null=True, blank=True, related_name='space_ai_default_provider')
internal_note = models.TextField(blank=True, null=True)
def safe_delete(self):
"""
Safely deletes a space by deleting all objects belonging to the space first and then deleting the space itself
"""
CookLog.objects.filter(space=self).delete()
ViewLog.objects.filter(space=self).delete()
ImportLog.objects.filter(space=self).delete()
BookmarkletImport.objects.filter(space=self).delete()
CustomFilter.objects.filter(space=self).delete()
AiLog.objects.filter(space=self).delete()
AiProvider.objects.filter(space=self).delete()
Property.objects.filter(space=self).delete()
PropertyType.objects.filter(space=self).delete()
Comment.objects.filter(recipe__space=self).delete()
Ingredient.objects.filter(space=self).delete()
Keyword.objects.filter(space=self).delete()
# delete food in batches because treabeard might fail to delete otherwise
while Food.objects.filter(space=self).count() > 0:
pks = Food.objects.filter(space=self).values_list('pk')[:200]
Food.objects.filter(pk__in=pks).delete()
Unit.objects.filter(space=self).delete()
Step.objects.filter(space=self).delete()
NutritionInformation.objects.filter(space=self).delete()
RecipeBookEntry.objects.filter(book__space=self).delete()
RecipeBook.objects.filter(space=self).delete()
MealType.objects.filter(space=self).delete()
MealPlan.objects.filter(space=self).delete()
ShareLink.objects.filter(space=self).delete()
Recipe.objects.filter(space=self).delete()
RecipeImport.objects.filter(space=self).delete()
SyncLog.objects.filter(sync__space=self).delete()
Sync.objects.filter(space=self).delete()
Storage.objects.filter(space=self).delete()
ConnectorConfig.objects.filter(space=self).delete()
ShoppingListEntry.objects.filter(space=self).delete()
ShoppingListRecipe.objects.filter(recipe__space=self).delete()
SupermarketCategoryRelation.objects.filter(supermarket__space=self).delete()
SupermarketCategory.objects.filter(space=self).delete()
Supermarket.objects.filter(space=self).delete()
InventoryEntry.objects.filter(space=self).delete()
InventoryLocation.objects.filter(space=self).delete()
UserFile.objects.filter(space=self).delete()
UserSpace.objects.filter(space=self).delete()
Automation.objects.filter(space=self).delete()
InviteLink.objects.filter(space=self).delete()
TelegramBot.objects.filter(space=self).delete()
self.delete()
def get_owner(self):
return self.created_by
def get_space(self):
return self
def __str__(self):
return self.name
class Meta:
ordering = ('pk',)
class AiProvider(models.Model):
name = models.CharField(max_length=128)
description = models.TextField(blank=True)
# AiProviders can be global, so space=null is allowed (configurable by superusers)
space = models.ForeignKey(Space, on_delete=models.CASCADE, null=True)
api_key = models.CharField(max_length=2048)
model_name = models.CharField(max_length=256)
url = models.CharField(max_length=2048, blank=True, null=True)
log_credit_cost = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
class Meta:
ordering = ('pk',)
class AiLog(models.Model, PermissionModelMixin):
F_FILE_IMPORT = 'FILE_IMPORT'
F_STEP_SORT = 'STEP_SORT'
F_FOOD_PROPERTIES = 'FOOD_PROPERTIES'
F_RECIPE_PROPERTIES = 'RECIPE_PROPERTIES'
ai_provider = models.ForeignKey(AiProvider, on_delete=models.SET_NULL, null=True)
function = models.CharField(max_length=64)
credit_cost = models.DecimalField(max_digits=16, decimal_places=4)
# if credits from balance were used, else its from monthly quota
credits_from_balance = models.BooleanField(default=False)
input_tokens = models.IntegerField(default=0)
output_tokens = models.IntegerField(default=0)
start_time = models.DateTimeField(null=True)
end_time = models.DateTimeField(null=True)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return f"{self.function} {self.ai_provider.name} {self.created_at}"
class Meta:
ordering = ('-created_at',)
class ConnectorConfig(models.Model, PermissionModelMixin):
HOMEASSISTANT = 'HomeAssistant'
CONNECTER_TYPE = ((HOMEASSISTANT, 'HomeAssistant'),)
name = models.CharField(max_length=128, validators=[MinLengthValidator(1)])
type = models.CharField(
choices=CONNECTER_TYPE, max_length=128, default=HOMEASSISTANT
)
enabled = models.BooleanField(default=True, help_text="Is Connector Enabled")
on_shopping_list_entry_created_enabled = models.BooleanField(default=False)
on_shopping_list_entry_updated_enabled = models.BooleanField(default=False)
on_shopping_list_entry_deleted_enabled = models.BooleanField(default=False)
supports_description_field = models.BooleanField(default=True, help_text="Does the todo entity support the description field")
url = models.URLField(blank=True, null=True)
token = models.CharField(max_length=512, blank=True, null=True)
todo_entity = models.CharField(max_length=128, blank=True, null=True)
created_by = models.ForeignKey(User, on_delete=models.PROTECT)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
class Meta:
ordering = ('pk',)
class UserPreference(models.Model, PermissionModelMixin):
# Themes
BOOTSTRAP = 'BOOTSTRAP'
DARKLY = 'DARKLY'
FLATLY = 'FLATLY'
SUPERHERO = 'SUPERHERO'
TANDOOR = 'TANDOOR'
TANDOOR_DARK = 'TANDOOR_DARK'
THEMES = (
(TANDOOR, 'Tandoor'),
(BOOTSTRAP, 'Bootstrap'),
(DARKLY, 'Darkly'),
(FLATLY, 'Flatly'),
(SUPERHERO, 'Superhero'),
(TANDOOR_DARK, 'Tandoor Dark (INCOMPLETE)'),
)
# Nav colors
LIGHT = 'LIGHT'
DARK = 'DARK'
NAV_TEXT_COLORS = (
(LIGHT, 'Light'),
(DARK, 'Dark')
)
# Default Page
SEARCH = 'SEARCH'
PLAN = 'PLAN'
BOOKS = 'BOOKS'
SHOPPING = 'SHOPPING'
PAGES = (
(SEARCH, _('Search')),
(PLAN, _('Meal-Plan')),
(BOOKS, _('Books')),
(SHOPPING, _('Shopping')),
)
user = AutoOneToOneField(User, on_delete=models.CASCADE, primary_key=True)
image = models.ForeignKey("UserFile", on_delete=models.SET_NULL, null=True, blank=True, related_name='user_image')
theme = models.CharField(choices=THEMES, max_length=128, default=TANDOOR)
nav_bg_color = models.CharField(max_length=8, default='#ddbf86')
nav_text_color = models.CharField(max_length=16, choices=NAV_TEXT_COLORS, default=DARK)
nav_show_logo = models.BooleanField(default=True)
nav_sticky = models.BooleanField(default=STICKY_NAV_PREF_DEFAULT)
max_owned_spaces = models.IntegerField(default=MAX_OWNED_SPACES_PREF_DEFAULT)
default_unit = models.CharField(max_length=32, default='g')
use_fractions = models.BooleanField(default=FRACTION_PREF_DEFAULT)
use_kj = models.BooleanField(default=KJ_PREF_DEFAULT)
default_page = models.CharField(choices=PAGES, max_length=64, default=SEARCH)
plan_share = models.ManyToManyField(User, blank=True, related_name='plan_share_default')
shopping_share = models.ManyToManyField(User, blank=True, related_name='shopping_share')
ingredient_decimals = models.IntegerField(default=2)
comments = models.BooleanField(default=COMMENT_PREF_DEFAULT)
shopping_auto_sync = models.IntegerField(default=5)
mealplan_autoadd_shopping = models.BooleanField(default=False)
mealplan_autoexclude_onhand = models.BooleanField(default=True)
mealplan_autoinclude_related = models.BooleanField(default=True)
shopping_add_onhand = models.BooleanField(default=False)
filter_to_supermarket = models.BooleanField(default=False)
left_handed = models.BooleanField(default=False)
show_step_ingredients = models.BooleanField(default=True)
default_delay = models.DecimalField(default=4, max_digits=8, decimal_places=4)
shopping_recent_days = models.PositiveIntegerField(default=7)
shopping_update_food_lists = models.BooleanField(default=True)
csv_delim = models.CharField(max_length=2, default=",")
csv_prefix = models.CharField(max_length=10, blank=True, )
default_meal_type = models.ForeignKey("MealType", on_delete=models.SET_NULL, null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
objects = ScopedManager(space='space')
def save(self, *args, **kwargs):
if not self.pk:
self.max_owned_spaces = MAX_OWNED_SPACES_PREF_DEFAULT
self.comments = COMMENT_PREF_DEFAULT
self.nav_sticky = STICKY_NAV_PREF_DEFAULT
self.use_kj = KJ_PREF_DEFAULT
self.use_fractions = FRACTION_PREF_DEFAULT
return super().save(*args, **kwargs)
def __str__(self):
return str(self.user)
class Household(models.Model, PermissionModelMixin):
name = models.CharField(max_length=128)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class UserSpace(models.Model, PermissionModelMixin):
user = models.ForeignKey(User, on_delete=models.CASCADE)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
household = models.ForeignKey(Household, on_delete=models.PROTECT, null=True, blank=True)
groups = models.ManyToManyField(Group)
# there should always only be one active space although permission methods are written in such a way
# that having more than one active space should just break certain parts of the application and not leak any data
active = models.BooleanField(default=False)
invite_link = models.ForeignKey("InviteLink", on_delete=models.PROTECT, null=True, blank=True)
internal_note = models.TextField(blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ('pk',)
class Storage(models.Model, PermissionModelMixin):
DROPBOX = 'DB'
NEXTCLOUD = 'NEXTCLOUD'
LOCAL = 'LOCAL'
STORAGE_TYPES = ((DROPBOX, 'Dropbox'), (NEXTCLOUD, 'Nextcloud'), (LOCAL, 'Local'))
name = models.CharField(max_length=128)
method = models.CharField(
choices=STORAGE_TYPES, max_length=128, default=DROPBOX
)
username = models.CharField(max_length=128, blank=True, null=True)
password = models.CharField(max_length=128, blank=True, null=True)
token = models.CharField(max_length=4098, blank=True, null=True)
url = models.URLField(blank=True, null=True)
path = models.CharField(blank=True, default='', max_length=256)
created_by = models.ForeignKey(User, on_delete=models.PROTECT)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
def __str__(self):
return self.name
class Meta:
ordering = ('pk',)
class Sync(models.Model, PermissionModelMixin):
storage = models.ForeignKey(Storage, on_delete=models.PROTECT)
path = models.CharField(max_length=512, default="")
active = models.BooleanField(default=True)
last_checked = models.DateTimeField(null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
def __str__(self):
return self.path
class Meta:
ordering = ('pk',)
class SupermarketCategory(models.Model, PermissionModelMixin, MergeModelMixin):
name = models.CharField(max_length=128, validators=[MinLengthValidator(1)])
description = models.TextField(blank=True, null=True)
open_data_slug = models.CharField(max_length=128, null=True, blank=True, default=None)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
def __str__(self):
return self.name
def merge_into(self, target):
super().merge_into(target)
Food.objects.filter(supermarket_category=self).update(supermarket_category=target)
SupermarketCategoryRelation.objects.filter(category=self).update(category=target)
self.delete()
return target
class Meta:
constraints = [
models.UniqueConstraint(fields=['space', 'name'], name='smc_unique_name_per_space'),
models.UniqueConstraint(fields=['space', 'open_data_slug'], name='supermarket_category_unique_open_data_slug_per_space')
]
ordering = ('name',)
class Supermarket(models.Model, PermissionModelMixin):
name = models.CharField(max_length=128, validators=[MinLengthValidator(1)])
description = models.TextField(blank=True, null=True)
categories = models.ManyToManyField(SupermarketCategory, through='SupermarketCategoryRelation')
shopping_lists = models.ManyToManyField("ShoppingList", blank=True)
open_data_slug = models.CharField(max_length=128, null=True, blank=True, default=None)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
def __str__(self):
return self.name
class Meta:
constraints = [
models.UniqueConstraint(fields=['space', 'name'], name='sm_unique_name_per_space'),
models.UniqueConstraint(fields=['space', 'open_data_slug'], name='supermarket_unique_open_data_slug_per_space')
]
ordering = ('name',)
class SupermarketCategoryRelation(models.Model, PermissionModelMixin):
supermarket = models.ForeignKey(Supermarket, on_delete=models.CASCADE, related_name='category_to_supermarket')
category = models.ForeignKey(SupermarketCategory, on_delete=models.CASCADE, related_name='category_to_supermarket')
order = models.IntegerField(default=0)
objects = ScopedManager(space='supermarket__space')
@staticmethod
def get_space_key():
return 'supermarket', 'space'
class Meta:
constraints = [
models.UniqueConstraint(fields=['supermarket', 'category'], name='unique_sm_category_relation')
]
ordering = ('order',)
class SyncLog(models.Model, PermissionModelMixin):
sync = models.ForeignKey(Sync, on_delete=models.CASCADE)
status = models.CharField(max_length=32)
msg = models.TextField(default="")
created_at = models.DateTimeField(auto_now_add=True)
objects = ScopedManager(space='sync__space')
def __str__(self):
return f"{self.created_at}:{self.sync} - {self.status}"
class Meta:
ordering = ('pk',)
class Keyword(ExportModelOperationsMixin('keyword'), TreeModel, PermissionModelMixin):
if SORT_TREE_BY_NAME:
node_order_by = ['name']
name = models.CharField(max_length=64)
description = models.TextField(default="", blank=True)
created_at = models.DateTimeField(auto_now_add=True) # TODO deprecate
updated_at = models.DateTimeField(auto_now=True) # TODO deprecate
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space', _manager_class=TreeManager)
class Meta:
constraints = [
models.UniqueConstraint(fields=['space', 'name'], name='kw_unique_name_per_space')
]
indexes = (Index(fields=['id', 'name']),)
ordering = ('name',)
class Unit(ExportModelOperationsMixin('unit'), models.Model, PermissionModelMixin, MergeModelMixin):
name = models.CharField(max_length=128, validators=[MinLengthValidator(1)])
plural_name = models.CharField(max_length=128, null=True, blank=True, default=None)
description = models.TextField(blank=True, null=True)
base_unit = models.TextField(max_length=256, null=True, blank=True, default=None)
open_data_slug = models.CharField(max_length=128, null=True, blank=True, default=None)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
def merge_into(self, target):
super().merge_into(target)
Ingredient.objects.filter(unit=self).update(unit=target)
ShoppingListEntry.objects.filter(unit=self).update(unit=target)
Food.objects.filter(properties_food_unit=self).update(properties_food_unit=target)
Food.objects.filter(preferred_unit=self).update(preferred_unit=target)
Food.objects.filter(preferred_shopping_unit=self).update(preferred_shopping_unit=target)
self.delete()
return target
def __str__(self):
return self.name
class Meta:
constraints = [
models.UniqueConstraint(fields=['space', 'name'], name='u_unique_name_per_space'),
models.UniqueConstraint(fields=['space', 'open_data_slug'], name='unit_unique_open_data_slug_per_space')
]
ordering = ('name',)
class Food(ExportModelOperationsMixin('food'), TreeModel, PermissionModelMixin):
# TODO when savings a food as substitute children - assume children and descednants are also substitutes for siblings
# exclude fields not implemented yet
inheritable_fields = FoodInheritField.objects.exclude(field__in=['diet', 'substitute', ])
# TODO add inherit children_inherit, parent_inherit, Do Not Inherit
# WARNING: Food inheritance relies on post_save signals, avoid using UPDATE to update Food objects unless you intend to bypass those signals
if SORT_TREE_BY_NAME:
node_order_by = ['name']
name = models.CharField(max_length=128, validators=[MinLengthValidator(1)])
plural_name = models.CharField(max_length=128, null=True, blank=True, default=None)
recipe = models.ForeignKey('Recipe', null=True, blank=True, on_delete=models.SET_NULL)
url = models.CharField(max_length=1024, blank=True, null=True, default='')
supermarket_category = models.ForeignKey(SupermarketCategory, null=True, blank=True, on_delete=models.SET_NULL) # inherited field
shopping_lists = models.ManyToManyField("ShoppingList", blank=True)
ignore_shopping = models.BooleanField(default=False) # inherited field
onhand_users = models.ManyToManyField(User, blank=True)
description = models.TextField(default='', blank=True)
inherit_fields = models.ManyToManyField(FoodInheritField, blank=True)
substitute = models.ManyToManyField("self", blank=True)
substitute_siblings = models.BooleanField(default=False)
substitute_children = models.BooleanField(default=False)
child_inherit_fields = models.ManyToManyField(FoodInheritField, blank=True, related_name='child_inherit')
properties = models.ManyToManyField("Property", blank=True, through='FoodProperty')
properties_food_amount = models.DecimalField(default=100, max_digits=16, decimal_places=2, blank=True)
properties_food_unit = models.ForeignKey(Unit, on_delete=models.PROTECT, blank=True, null=True)
preferred_unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True, default=None, related_name='preferred_unit')
preferred_shopping_unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True, default=None, related_name='preferred_shopping_unit')
fdc_id = models.IntegerField(null=True, default=None, blank=True)
open_data_slug = models.CharField(max_length=128, null=True, blank=True, default=None)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space', _manager_class=TreeManager)
def __str__(self):
return self.name
def merge_into(self, target):
"""
very simple merge function that replaces the current food with the target food
also replaces a few attributes on the target field if they were empty before
:param target: target food object
:return: target with data merged
"""
if self == target:
raise ValueError('Cannot merge an object with itself')
if self.space != target.space:
raise RuntimeError('Cannot merge objects from different spaces')
try:
if target in self.get_descendants_and_self():
raise RuntimeError('Cannot merge parent (source) with child (target) object')
except AttributeError:
pass # AttributeError is raised when the object is not a tree and thus does not have the get_descendants_and_self() function
self.properties.all().delete()
self.properties.clear()
Ingredient.objects.filter(food=self).update(food=target)
ShoppingListEntry.objects.filter(food=self).update(food=target)
self.delete()
return target
# MP_Tree move uses raw SQL to execute move, override behavior to force a save triggering post_save signal
def move(self, *args, **kwargs):
super().move(*args, **kwargs)
# treebeard bypasses ORM, need to explicity save to trigger post save signals retrieve the object again to avoid writing previous state back to disk
obj = self.__class__.objects.get(id=self.id)
if parent := obj.get_parent():
# child should inherit what the parent defines it should inherit
fields = list(parent.child_inherit_fields.all() or parent.inherit_fields.all())
if len(fields) > 0:
obj.inherit_fields.set(fields)
obj.save()
@staticmethod
def reset_inheritance(space=None, food=None):
# resets inherited fields to the space defaults and updates all inherited fields to root object values
if food:
# if child inherit fields is preset children should be set to that, otherwise inherit this foods inherited fields
inherit = list((food.child_inherit_fields.all() or food.inherit_fields.all()).values('id', 'field'))
tree_filter = Q(path__startswith=food.path, space=space, depth=food.depth + 1)
else:
inherit = list(space.food_inherit.all().values('id', 'field'))
tree_filter = Q(space=space)
# remove all inherited fields from food
through = Food.inherit_fields.through
through.objects.all().delete()
# food is going to inherit attributes
if len(inherit) > 0:
# ManyToMany cannot be updated through an UPDATE operation
for i in inherit:
through.objects.bulk_create([
through(food_id=x, foodinheritfield_id=i['id'])
for x in Food.objects.filter(tree_filter).values_list('id', flat=True)
])
inherit = [x['field'] for x in inherit]
for field in ['ignore_shopping', 'substitute_children', 'substitute_siblings']:
if field in inherit:
if food and getattr(food, field, None):
food.get_descendants().update(**{f"{field}": True})
elif food and not getattr(food, field, True):
food.get_descendants().update(**{f"{field}": False})
else:
# get food at root that have children that need updated
Food.include_descendants(queryset=Food.objects.filter(depth=1, numchild__gt=0, **{f"{field}": True}, space=space)).update(**{f"{field}": True})
Food.include_descendants(queryset=Food.objects.filter(depth=1, numchild__gt=0, **{f"{field}": False}, space=space)).update(**{f"{field}": False})
if 'supermarket_category' in inherit:
# when supermarket_category is null or blank assuming it is not set and not intended to be blank for all descedants
if food and food.supermarket_category:
food.get_descendants().update(supermarket_category=food.supermarket_category)
elif food is None:
# find top node that has category set
category_roots = Food.exclude_descendants(queryset=Food.objects.filter(supermarket_category__isnull=False, numchild__gt=0, space=space))
for root in category_roots:
root.get_descendants().update(supermarket_category=root.supermarket_category)
class Meta:
constraints = [
models.UniqueConstraint(fields=['space', 'name'], name='f_unique_name_per_space'),
models.UniqueConstraint(fields=['space', 'open_data_slug'], name='food_unique_open_data_slug_per_space')
]
indexes = (
Index(fields=['id']),
Index(fields=['name']),
)
ordering = ('name',)
class UnitConversion(ExportModelOperationsMixin('unit_conversion'), models.Model, PermissionModelMixin):
base_amount = models.DecimalField(default=0, decimal_places=16, max_digits=32)
base_unit = models.ForeignKey('Unit', on_delete=models.CASCADE, related_name='unit_conversion_base_relation')
converted_amount = models.DecimalField(default=0, decimal_places=16, max_digits=32)
converted_unit = models.ForeignKey('Unit', on_delete=models.CASCADE, related_name='unit_conversion_converted_relation')
food = models.ForeignKey('Food', on_delete=models.CASCADE, null=True, blank=True)
created_by = models.ForeignKey(User, on_delete=models.PROTECT)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
open_data_slug = models.CharField(max_length=128, null=True, blank=True, default=None)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
def __str__(self):
return f'{self.base_amount} {self.base_unit} -> {self.converted_amount} {self.converted_unit} {self.food}'
class Meta:
constraints = [
models.UniqueConstraint(fields=['space', 'base_unit', 'converted_unit', 'food'], name='f_unique_conversion_per_space'),
models.UniqueConstraint(fields=['space', 'open_data_slug'], name='unit_conversion_unique_open_data_slug_per_space')
]
ordering = ('pk',)
class Ingredient(ExportModelOperationsMixin('ingredient'), models.Model, PermissionModelMixin):
# delete method on Food and Unit checks if they are part of a Recipe, if it is raises a ProtectedError instead of cascading the delete
food = models.ForeignKey(Food, on_delete=models.CASCADE, null=True, blank=True)
unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True)
amount = models.DecimalField(default=0, decimal_places=16, max_digits=32)
note = models.CharField(max_length=256, null=True, blank=True)
is_header = models.BooleanField(default=False)
no_amount = models.BooleanField(default=False)
always_use_plural_unit = models.BooleanField(default=False)
always_use_plural_food = models.BooleanField(default=False)
order = models.IntegerField(default=0)
original_text = models.CharField(max_length=512, null=True, blank=True, default=None)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
# def __str__(self):
# return f'{self.pk}: {self.amount} ' + (self.food.name if self.food else ' ') + (self.unit.name if self.unit else '')
class Meta:
ordering = ['order', 'pk']
indexes = (
Index(fields=['id']),
)
class Step(ExportModelOperationsMixin('step'), models.Model, PermissionModelMixin):
name = models.CharField(max_length=128, default='', blank=True)
instruction = models.TextField(blank=True)
ingredients = models.ManyToManyField(Ingredient, blank=True)
time = models.IntegerField(default=0, blank=True)
order = models.IntegerField(default=0)
file = models.ForeignKey('UserFile', on_delete=models.PROTECT, null=True, blank=True)
show_as_header = models.BooleanField(default=True)
show_ingredients_table = models.BooleanField(default=True)
search_vector = SearchVectorField(null=True)
step_recipe = models.ForeignKey('Recipe', default=None, blank=True, null=True, on_delete=models.PROTECT)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
def get_instruction_render(self):
from cookbook.helper.template_helper import render_instructions
return render_instructions(self)
def __str__(self):
if not self.recipe_set.exists():
return f"{self.pk}: {_('Orphaned Step')}"
return f"{self.pk}: {self.name}" if self.name else f"Step: {self.pk}"
class Meta:
ordering = ['order', 'pk']
indexes = (GinIndex(fields=["search_vector"]),)
class PropertyType(models.Model, PermissionModelMixin, MergeModelMixin):
NUTRITION = 'NUTRITION'
ALLERGEN = 'ALLERGEN'
PRICE = 'PRICE'
GOAL = 'GOAL'
OTHER = 'OTHER'
CHOICES = (
(NUTRITION, _('Nutrition')),
(ALLERGEN, _('Allergen')),
(PRICE, _('Price')),
(GOAL, _('Goal')),
(OTHER, _('Other')),
)
name = models.CharField(max_length=128)
unit = models.CharField(max_length=64, blank=True, null=True)
order = models.IntegerField(default=0)
description = models.CharField(max_length=512, blank=True, null=True)
category = models.CharField(max_length=64, choices=CHOICES, null=True, blank=True)
open_data_slug = models.CharField(max_length=128, null=True, blank=True, default=None)
fdc_id = models.IntegerField(null=True, default=None, blank=True)
# TODO show if empty property?
# TODO formatting property?
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
def __str__(self):
return f'{self.name}'
def merge_into(self, target):
super().merge_into(target)
Property.objects.filter(property_type=self).update(property_type=target)
self.delete()
return target
class Meta:
constraints = [
models.UniqueConstraint(fields=['space', 'name'], name='property_type_unique_name_per_space'),
models.UniqueConstraint(fields=['space', 'open_data_slug'], name='property_type_unique_open_data_slug_per_space')
]
ordering = ('order',)
class Property(models.Model, PermissionModelMixin):
property_amount = models.DecimalField(default=None, null=True, decimal_places=4, max_digits=32)
property_type = models.ForeignKey(PropertyType, on_delete=models.PROTECT)
open_data_food_slug = models.CharField(max_length=128, null=True, blank=True, default=None) # field to hold food id when importing properties from the open data project
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
def __str__(self):
return f'{self.property_amount} {self.property_type.unit} {self.property_type.name}'
class Meta:
constraints = [
models.UniqueConstraint(fields=['space', 'property_type', 'open_data_food_slug'], name='property_unique_import_food_per_space')
]
class FoodProperty(models.Model):
food = models.ForeignKey(Food, on_delete=models.CASCADE)
property = models.ForeignKey(Property, on_delete=models.CASCADE)
class Meta:
constraints = [
models.UniqueConstraint(fields=['food', 'property'], name='property_unique_food'),
]
class NutritionInformation(models.Model, PermissionModelMixin):
fats = models.DecimalField(default=0, decimal_places=16, max_digits=32)
carbohydrates = models.DecimalField(
default=0, decimal_places=16, max_digits=32
)
proteins = models.DecimalField(default=0, decimal_places=16, max_digits=32)
calories = models.DecimalField(default=0, decimal_places=16, max_digits=32)
source = models.CharField(max_length=512, default="", null=True, blank=True)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
def __str__(self):
return f'Nutrition {self.pk}'
class RecipeManager(models.Manager.from_queryset(models.QuerySet)):
def get_queryset(self):
return super(RecipeManager, self).get_queryset().annotate(rating=Avg('cooklog__rating')).annotate(last_cooked=Max('cooklog__created_at'))
class Recipe(ExportModelOperationsMixin('recipe'), models.Model, PermissionModelMixin):
name = models.CharField(max_length=128)
description = models.CharField(max_length=512, blank=True, null=True)
servings = models.IntegerField(default=1)
servings_text = models.CharField(default='', blank=True, max_length=32)
diameter = models.IntegerField(default=0)
diameter_text = models.CharField(default='', blank=True, max_length=32)
image = models.ImageField(upload_to='recipes/', blank=True, null=True)
storage = models.ForeignKey(Storage, on_delete=models.PROTECT, blank=True, null=True)
file_uid = models.CharField(max_length=256, default="", blank=True)
file_path = models.CharField(max_length=512, default="", blank=True)
link = models.CharField(max_length=512, null=True, blank=True)
cors_link = models.CharField(max_length=1024, null=True, blank=True)
keywords = models.ManyToManyField(Keyword, blank=True)
steps = models.ManyToManyField(Step, blank=True)
working_time = models.IntegerField(default=0)
waiting_time = models.IntegerField(default=0)
internal = models.BooleanField(default=False)
nutrition = models.ForeignKey(NutritionInformation, blank=True, null=True, on_delete=models.CASCADE)
properties = models.ManyToManyField(Property, blank=True)
show_ingredient_overview = models.BooleanField(default=True)
private = models.BooleanField(default=False)
shared = models.ManyToManyField(User, blank=True, related_name='recipe_shared_with')
source_url = models.CharField(max_length=1024, default=None, blank=True, null=True)
created_by = models.ForeignKey(User, on_delete=models.PROTECT)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
name_search_vector = SearchVectorField(null=True)
desc_search_vector = SearchVectorField(null=True)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space', _manager_class=RecipeManager)
def __str__(self):
return self.name
def get_related_recipes(self, levels=1):
# recipes for step recipe
step_recipes = Q(id__in=self.steps.exclude(step_recipe=None).values_list('step_recipe'))
# recipes for foods
food_recipes = Q(id__in=Food.objects.filter(ingredient__step__recipe=self).exclude(recipe=None).values_list('recipe'))
related_recipes = Recipe.objects.filter(step_recipes | food_recipes)
if levels == 1:
return related_recipes
# this can loop over multiple levels if you update the value of related_recipes at each step (maybe an array?)
# for now keeping it at 2 levels max, should be sufficient in 99.9% of scenarios
sub_step_recipes = Q(id__in=Step.objects.filter(recipe__in=related_recipes.values_list('steps')).exclude(step_recipe=None).values_list('step_recipe'))
sub_food_recipes = Q(id__in=Food.objects.filter(ingredient__step__recipe__in=related_recipes).exclude(recipe=None).values_list('recipe'))
return Recipe.objects.filter(Q(id__in=related_recipes.values_list('id')) | sub_step_recipes | sub_food_recipes)
class Meta:
indexes = (
GinIndex(fields=["name_search_vector"]),
GinIndex(fields=["desc_search_vector"]),
Index(fields=['id']),
Index(fields=['name']),
)
ordering = ('name',)
class Comment(ExportModelOperationsMixin('comment'), models.Model, PermissionModelMixin):
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
text = models.TextField()
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
objects = ScopedManager(space='recipe__space')
@staticmethod
def get_space_key():
return 'recipe', 'space'
def get_space(self):
return self.recipe.space
def __str__(self):
return self.text
class Meta:
ordering = ('pk',)
class RecipeImport(models.Model, PermissionModelMixin):
name = models.CharField(max_length=128)
storage = models.ForeignKey(Storage, on_delete=models.PROTECT)
file_uid = models.CharField(max_length=256, default="")
file_path = models.CharField(max_length=512, default="")
created_at = models.DateTimeField(auto_now_add=True)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
def __str__(self):
return self.name
def convert_to_recipe(self, user):
recipe = Recipe(
name=self.name,
file_path=self.file_path,
storage=self.storage,
file_uid=self.file_uid,
created_by=user,
space=self.space
)
recipe.save()
self.delete()
return recipe
class Meta:
ordering = ('pk',)
class RecipeBook(ExportModelOperationsMixin('book'), models.Model, PermissionModelMixin):
name = models.CharField(max_length=128)
description = models.TextField(blank=True)
shared = models.ManyToManyField(User, blank=True, related_name='shared_with')
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
filter = models.ForeignKey('cookbook.CustomFilter', null=True, blank=True, on_delete=models.SET_NULL)
order = models.IntegerField(default=0)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
def __str__(self):
return self.name
class Meta():
indexes = (Index(fields=['name']),)
ordering = ('name',)
class RecipeBookEntry(ExportModelOperationsMixin('book_entry'), models.Model, PermissionModelMixin):
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
book = models.ForeignKey(RecipeBook, on_delete=models.CASCADE)
objects = ScopedManager(space='book__space')
@staticmethod
def get_space_key():
return 'book', 'space'
def __str__(self):
return self.recipe.name
def get_owner(self):
try:
return self.book.created_by
except AttributeError:
return None
class Meta:
constraints = [
models.UniqueConstraint(fields=['recipe', 'book'], name='rbe_unique_name_per_space')
]
class MealType(models.Model, PermissionModelMixin):
name = models.CharField(max_length=128)
order = models.IntegerField(default=0)
color = models.CharField(max_length=7, blank=True, null=True)
time = models.TimeField(null=True, blank=True)
default = models.BooleanField(default=False, blank=True)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
def __str__(self):
return self.name
class Meta:
constraints = [
models.UniqueConstraint(fields=['space', 'name'], name='mt_unique_name_per_space'),
]
ordering = ('name',)
class MealPlan(ExportModelOperationsMixin('meal_plan'), models.Model, PermissionModelMixin):
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, blank=True, null=True)
servings = models.DecimalField(default=1, max_digits=8, decimal_places=4)
title = models.CharField(max_length=64, blank=True, default='')
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
shared = models.ManyToManyField(User, blank=True, related_name='plan_share')
meal_type = models.ForeignKey(MealType, on_delete=models.CASCADE)
note = models.TextField(blank=True)
from_date = models.DateTimeField()
to_date = models.DateTimeField()
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
def get_label(self):
if self.title:
return self.title
return str(self.recipe)
def get_meal_name(self):
return self.meal_type.name
def __str__(self):
return f'{self.get_label()} - {self.from_date} - {self.meal_type.name}'
class Meta:
ordering = ('pk',)
class ShoppingListRecipe(ExportModelOperationsMixin('shopping_list_recipe'), models.Model, PermissionModelMixin):
name = models.CharField(max_length=32, blank=True, default='')
servings = models.DecimalField(default=1, max_digits=8, decimal_places=4)
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, null=True, blank=True)
mealplan = models.ForeignKey(MealPlan, on_delete=models.CASCADE, null=True, blank=True)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
# def __str__(self):
# return f'Shopping list recipe {self.id} - {self.recipe}'
class Meta:
ordering = ('pk',)
class ShoppingList(ExportModelOperationsMixin('shopping_list'), models.Model, PermissionModelMixin):
name = models.CharField(max_length=32, blank=True, default='')
description = models.TextField(blank=True)
color = models.CharField(max_length=7, blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
class Meta:
ordering = ('pk',)
class ShoppingListEntry(ExportModelOperationsMixin('shopping_list_entry'), models.Model, PermissionModelMixin):
shopping_lists = models.ManyToManyField(ShoppingList, blank=True)
list_recipe = models.ForeignKey(ShoppingListRecipe, on_delete=models.CASCADE, null=True, blank=True, related_name='entries')
food = models.ForeignKey(Food, on_delete=models.CASCADE, related_name='shopping_entries')
unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True)
ingredient = models.ForeignKey(Ingredient, on_delete=models.CASCADE, null=True, blank=True)
amount = models.DecimalField(default=0, decimal_places=16, max_digits=32)
order = models.IntegerField(default=0)
checked = models.BooleanField(default=False)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
completed_at = models.DateTimeField(null=True, blank=True)
delay_until = models.DateTimeField(null=True, blank=True)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
def __str__(self):
return f'Shopping list entry {self.id}'
def get_owner(self):
try:
return self.created_by
except AttributeError:
return None
class Meta:
ordering = ('pk',)
class InventoryLocation(models.Model, PermissionModelMixin):
name = models.CharField(max_length=64)
is_freezer = models.BooleanField(default=False)
household = models.ForeignKey(Household, on_delete=models.PROTECT)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
class InventoryEntry(models.Model, PermissionModelMixin):
inventory_location = models.ForeignKey(InventoryLocation, on_delete=models.CASCADE)
sub_location = models.CharField(max_length=64, blank=True, null=True)
code = models.CharField(max_length=16, null=True, blank=True)
amount = models.DecimalField(default=0, decimal_places=16, max_digits=32)
unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True)
food = models.ForeignKey(Food, on_delete=models.CASCADE, null=True, blank=True)
expires = models.DateField(null=True, blank=True)
note = models.CharField(max_length=256, null=True, blank=True)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
class Meta:
constraints = [
models.UniqueConstraint(fields=['space', 'code'], name='code_unique_per_space'),
]
ordering = ('id',)
class InventoryLog(models.Model, PermissionModelMixin):
B_ADD = 'add'
B_REMOVE = 'remove'
B_MOVE = 'move'
BOOKING_TYPES = [
(B_ADD, _('Add')),
(B_REMOVE, _('Remove')),
(B_MOVE, _('Move')),
]
entry = models.ForeignKey(InventoryEntry, on_delete=models.CASCADE)
booking_type = models.CharField(max_length=10, choices=BOOKING_TYPES, default=B_ADD)
old_amount = models.DecimalField(default=0, decimal_places=16, max_digits=32)
new_amount = models.DecimalField(default=0, decimal_places=16, max_digits=32)
old_inventory_location = models.ForeignKey(InventoryLocation, on_delete=models.CASCADE, related_name='old_inventory_location')
new_inventory_location = models.ForeignKey(InventoryLocation, on_delete=models.CASCADE, related_name='new_inventory_location')
note = models.CharField(max_length=256, null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
class Meta:
ordering = ('created_at',)
class ShareLink(ExportModelOperationsMixin('share_link'), models.Model, PermissionModelMixin):
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
uuid = models.UUIDField(default=uuid.uuid4)
request_count = models.IntegerField(default=0)
abuse_blocked = models.BooleanField(default=False)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
def __str__(self):
return f'{self.recipe} - {self.uuid}'
class Meta:
ordering = ('pk',)
def default_valid_until():
return date.today() + timedelta(days=14)
class InviteLink(ExportModelOperationsMixin('invite_link'), models.Model, PermissionModelMixin):
uuid = models.UUIDField(default=uuid.uuid4)
email = models.EmailField(blank=True)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
valid_until = models.DateField(default=default_valid_until)
used_by = models.ForeignKey(User, null=True, on_delete=models.CASCADE, related_name='used_by')
reusable = models.BooleanField(default=False)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
internal_note = models.TextField(blank=True, null=True)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
def __str__(self):
return f'{self.uuid}'
class Meta:
ordering = ('pk',)
class TelegramBot(models.Model, PermissionModelMixin):
token = models.CharField(max_length=256)
name = models.CharField(max_length=128, default='', blank=True)
chat_id = models.CharField(max_length=128, default='', blank=True)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
webhook_token = models.UUIDField(default=uuid.uuid4)
objects = ScopedManager(space='space')
space = models.ForeignKey(Space, on_delete=models.CASCADE)
def __str__(self):
return f"{self.name}"
class Meta:
ordering = ('pk',)
class CookLog(ExportModelOperationsMixin('cook_log'), models.Model, PermissionModelMixin):
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
rating = models.IntegerField(null=True, blank=True)
servings = models.IntegerField(null=True, blank=True)
comment = models.TextField(null=True, blank=True)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(default=timezone.now)
updated_at = models.DateTimeField(auto_now=True)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
def __str__(self):
return self.recipe.name
class Meta:
indexes = (
Index(fields=['id']),
Index(fields=['recipe']),
Index(fields=['-created_at']),
Index(fields=['rating']),
Index(fields=['created_by']),
Index(fields=['created_by', 'rating']),
)
ordering = ('pk',)
class ViewLog(ExportModelOperationsMixin('view_log'), models.Model, PermissionModelMixin):
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
def __str__(self):
return self.recipe.name
class Meta:
indexes = (
Index(fields=['recipe']),
Index(fields=['-created_at']),
Index(fields=['created_by']),
Index(fields=['recipe', '-created_at', 'created_by']),
)
ordering = ('pk',)
class ImportLog(models.Model, PermissionModelMixin):
type = models.CharField(max_length=32)
running = models.BooleanField(default=True)
msg = models.TextField(default="")
keyword = models.ForeignKey(Keyword, null=True, blank=True, on_delete=models.SET_NULL)
total_recipes = models.IntegerField(default=0)
imported_recipes = models.IntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
space = models.ForeignKey(Space, on_delete=models.CASCADE)
def __str__(self):
return f"{self.created_at}:{self.type}"
class Meta:
ordering = ('pk',)
class ExportLog(models.Model, PermissionModelMixin):
type = models.CharField(max_length=32)
running = models.BooleanField(default=True)
msg = models.TextField(default="")
total_recipes = models.IntegerField(default=0)
exported_recipes = models.IntegerField(default=0)
cache_duration = models.IntegerField(default=0)
possibly_not_expired = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
space = models.ForeignKey(Space, on_delete=models.CASCADE)
def __str__(self):
return f"{self.created_at}:{self.type}"
class Meta:
ordering = ('pk',)
class BookmarkletImport(ExportModelOperationsMixin('bookmarklet_import'), models.Model, PermissionModelMixin):
html = models.TextField()
url = models.CharField(max_length=256, null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
space = models.ForeignKey(Space, on_delete=models.CASCADE)
class Meta:
ordering = ('pk',)
# field names used to configure search behavior - all data populated during data migration
# other option is to use a MultiSelectField from https://github.com/goinnn/django-multiselectfield
class SearchFields(models.Model, PermissionModelMixin):
name = models.CharField(max_length=32, unique=True)
field = models.CharField(max_length=64, unique=True)
def __str__(self):
return _(self.name)
@staticmethod
def get_name(self):
return _(self.name)
class SearchPreference(models.Model, PermissionModelMixin):
# Search Style (validation parsleyjs.org)
# phrase or plain or raw (websearch and trigrams are mutually exclusive)
SIMPLE = 'plain'
PHRASE = 'phrase'
WEB = 'websearch'
RAW = 'raw'
SEARCH_STYLE = (
(SIMPLE, _('Simple')),
(PHRASE, _('Phrase')),
(WEB, _('Web')),
(RAW, _('Raw'))
)
user = AutoOneToOneField(User, on_delete=models.CASCADE, primary_key=True)
search = models.CharField(choices=SEARCH_STYLE, max_length=32, default=SIMPLE)
lookup = models.BooleanField(default=False)
unaccent = models.ManyToManyField(SearchFields, related_name="unaccent_fields", blank=True)
icontains = models.ManyToManyField(SearchFields, related_name="icontains_fields", blank=True)
istartswith = models.ManyToManyField(SearchFields, related_name="istartswith_fields", blank=True)
trigram = models.ManyToManyField(SearchFields, related_name="trigram_fields", blank=True)
fulltext = models.ManyToManyField(SearchFields, related_name="fulltext_fields", blank=True)
trigram_threshold = models.DecimalField(default=0.2, decimal_places=2, max_digits=3)
class UserFile(ExportModelOperationsMixin('user_files'), models.Model, PermissionModelMixin):
name = models.CharField(max_length=128)
file = models.FileField(upload_to='files/')
file_size_kb = models.IntegerField(default=0, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
space = models.ForeignKey(Space, on_delete=models.CASCADE)
def is_image(self):
try:
Image.open(self.file.file.file)
return True
except Exception:
return False
def save(self, *args, **kwargs):
if hasattr(self.file, 'file') and isinstance(self.file.file, UploadedFile) or isinstance(self.file.file, InMemoryUploadedFile):
self.file.name = f'{uuid.uuid4()}' + pathlib.Path(self.file.name).suffix
self.file_size_kb = round(self.file.size / 1000)
super(UserFile, self).save(*args, **kwargs)
def __str__(self):
return f'{self.name} (#{self.id})'
class Meta:
ordering = ('pk',)
class Automation(ExportModelOperationsMixin('automations'), models.Model, PermissionModelMixin):
FOOD_ALIAS = 'FOOD_ALIAS'
UNIT_ALIAS = 'UNIT_ALIAS'
KEYWORD_ALIAS = 'KEYWORD_ALIAS'
DESCRIPTION_REPLACE = 'DESCRIPTION_REPLACE'
INSTRUCTION_REPLACE = 'INSTRUCTION_REPLACE'
NEVER_UNIT = 'NEVER_UNIT'
TRANSPOSE_WORDS = 'TRANSPOSE_WORDS'
FOOD_REPLACE = 'FOOD_REPLACE'
UNIT_REPLACE = 'UNIT_REPLACE'
NAME_REPLACE = 'NAME_REPLACE'
automation_types = (
(FOOD_ALIAS, _('Food Alias')),
(UNIT_ALIAS, _('Unit Alias')),
(KEYWORD_ALIAS, _('Keyword Alias')),
(DESCRIPTION_REPLACE, _('Description Replace')),
(INSTRUCTION_REPLACE, _('Instruction Replace')),
(NEVER_UNIT, _('Never Unit')),
(TRANSPOSE_WORDS, _('Transpose Words')),
(FOOD_REPLACE, _('Food Replace')),
(UNIT_REPLACE, _('Unit Replace')),
(NAME_REPLACE, _('Name Replace')),
)
type = models.CharField(max_length=128,
choices=automation_types)
name = models.CharField(max_length=128, default='')
description = models.TextField(blank=True, null=True)
param_1 = models.CharField(max_length=128, blank=True, null=True)
param_2 = models.CharField(max_length=128, blank=True, null=True)
param_3 = models.CharField(max_length=128, blank=True, null=True)
order = models.IntegerField(default=1000)
disabled = models.BooleanField(default=False)
updated_at = models.DateTimeField(auto_now=True)
created_at = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
space = models.ForeignKey(Space, on_delete=models.CASCADE)
class Meta:
ordering = ('pk',)
class CustomFilter(models.Model, PermissionModelMixin):
RECIPE = 'RECIPE'
FOOD = 'FOOD'
KEYWORD = 'KEYWORD'
MODELS = (
(RECIPE, _('Recipe')),
(FOOD, _('Food')),
(KEYWORD, _('Keyword')),
)
name = models.CharField(max_length=128, null=False, blank=False)
type = models.CharField(max_length=128, choices=(MODELS), default=MODELS[0])
# could use JSONField, but requires installing extension on SQLite, don't need to search the objects, so seems unecessary
search = models.TextField(blank=False, null=False)
created_at = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
shared = models.ManyToManyField(User, blank=True, related_name='f_shared_with')
objects = ScopedManager(space='space')
space = models.ForeignKey(Space, on_delete=models.CASCADE)
def __str__(self):
return self.name
class Meta:
constraints = [
models.UniqueConstraint(fields=['space', 'name'], name='cf_unique_name_per_space')
]
ordering = ('pk',)
================================================
FILE: cookbook/provider/__init__.py
================================================
================================================
FILE: cookbook/provider/dropbox.py
================================================
import io
import json
import os
from django.utils import timezone
from cookbook.helper.HelperFunctions import safe_request
from cookbook.models import Recipe, RecipeImport, SyncLog
from cookbook.provider.provider import Provider
class Dropbox(Provider):
@staticmethod
def import_all(monitor):
url = "https://api.dropboxapi.com/2/files/list_folder"
headers = {
"Authorization": "Bearer " + monitor.storage.token,
"Content-Type": "application/json"
}
data = {
"path": monitor.path
}
r = safe_request('POST', url, headers=headers, data=json.dumps(data))
try:
recipes = r.json()
except ValueError:
log_entry = SyncLog(status='ERROR', msg=str(r), sync=monitor)
log_entry.save()
return log_entry
import_count = 0
# TODO check if has_more is set and import that as well
for recipe in recipes['entries']:
path = recipe['path_lower']
if not Recipe.objects.filter(file_path__iexact=path, space=monitor.space).exists() and not RecipeImport.objects.filter(file_path=path, space=monitor.space).exists():
name = os.path.splitext(recipe['name'])[0]
new_recipe = RecipeImport(
name=name,
file_path=path,
storage=monitor.storage,
file_uid=recipe['id'],
space=monitor.space,
)
new_recipe.save()
import_count += 1
log_entry = SyncLog(
status='SUCCESS',
msg='Imported ' + str(import_count) + ' recipes',
sync=monitor,
)
log_entry.save()
monitor.last_checked = timezone.now()
monitor.save()
return log_entry
@staticmethod
def create_share_link(recipe):
url = "https://api.dropboxapi.com/2/sharing/create_shared_link_with_settings" # noqa: E501
headers = {
"Authorization": "Bearer " + recipe.storage.token,
"Content-Type": "application/json"
}
data = {
"path": recipe.file_uid
}
r = safe_request('POST', url, headers=headers, data=json.dumps(data))
return r.json()
@staticmethod
def get_share_link(recipe):
url = "https://api.dropboxapi.com/2/sharing/list_shared_links"
headers = {
"Authorization": "Bearer " + recipe.storage.token,
"Content-Type": "application/json"
}
data = {
"path": recipe.file_path,
}
r = safe_request('POST', url, headers=headers, data=json.dumps(data))
p = r.json()
for link in p['links']:
return link['url']
response = Dropbox.create_share_link(recipe)
return response['url']
@staticmethod
def get_file(recipe):
if not recipe.link:
recipe.link = Dropbox.get_share_link(recipe)
recipe.save()
url = recipe.link.replace('www.dropbox.', 'dl.dropboxusercontent.')
response = safe_request('GET', url)
return io.BytesIO(response.content)
@staticmethod
def rename_file(recipe, new_name):
url = "https://api.dropboxapi.com/2/files/move_v2"
headers = {
"Authorization": "Bearer " + recipe.storage.token,
"Content-Type": "application/json"
}
data = {
"from_path": recipe.file_path,
"to_path": "%s/%s%s" % (
os.path.dirname(recipe.file_path),
new_name,
os.path.splitext(recipe.file_path)[1]
)
}
r = safe_request('POST', url, headers=headers, data=json.dumps(data))
return r.json()
@staticmethod
def delete_file(recipe):
url = "https://api.dropboxapi.com/2/files/delete_v2"
headers = {
"Authorization": "Bearer " + recipe.storage.token,
"Content-Type": "application/json"
}
data = {
"path": recipe.file_path
}
r = safe_request('POST', url, headers=headers, data=json.dumps(data))
return r.json()
================================================
FILE: cookbook/provider/local.py
================================================
import io
import os
from django.conf import settings
from django.utils import timezone
from os import listdir
from os.path import isfile, join
from cookbook.models import Recipe, RecipeImport, SyncLog
from cookbook.provider.provider import Provider
class Local(Provider):
@staticmethod
def import_all(monitor):
if not Local.is_path_allowed(monitor.path):
return False
files = [f for f in listdir(monitor.path) if isfile(join(monitor.path, f))]
import_count = 0
for file in files:
if file.endswith('.pdf') or file.endswith('.png') or file.endswith('.jpg') or file.endswith('.jpeg') or file.endswith('.gif'):
path = monitor.path + '/' + file
if not Recipe.objects.filter(file_path__iexact=path, space=monitor.space).exists() and not RecipeImport.objects.filter(file_path=path, space=monitor.space).exists():
name = os.path.splitext(file)[0]
new_recipe = RecipeImport(
name=name,
file_path=path,
storage=monitor.storage,
space=monitor.space,
)
new_recipe.save()
import_count += 1
log_entry = SyncLog(
status='SUCCESS',
msg='Imported ' + str(import_count) + ' recipes',
sync=monitor,
)
log_entry.save()
monitor.last_checked = timezone.now()
monitor.save()
return log_entry
@staticmethod
def get_file(recipe):
if not Local.is_path_allowed(recipe.file_path):
raise Exception('Path not allowed')
file = io.BytesIO(open(recipe.file_path, 'rb').read())
return file
@staticmethod
def is_path_allowed(path):
normalized_path = os.path.normpath(os.path.abspath(path))
for allowed_path in settings.LOCAL_STORAGE_PATHS:
normalized_allowed_path = os.path.normpath(os.path.abspath(allowed_path))
if normalized_path.startswith(normalized_allowed_path + os.sep) or normalized_path == normalized_allowed_path:
return True
return False
@staticmethod
def rename_file(recipe, new_name):
if not Local.is_path_allowed(recipe.file_path):
raise Exception('Path not allowed')
os.rename(recipe.file_path, os.path.join(os.path.dirname(recipe.file_path), (new_name + os.path.splitext(recipe.file_path)[1])))
return True
@staticmethod
def delete_file(recipe):
if not Local.is_path_allowed(recipe.file_path):
raise Exception('Path not allowed')
os.remove(recipe.file_path)
return True
================================================
FILE: cookbook/provider/nextcloud.py
================================================
import io
import os
import tempfile
from django.utils import timezone
import webdav3.client as wc
from cookbook.helper.HelperFunctions import safe_request
from cookbook.models import Recipe, RecipeImport, SyncLog
from cookbook.provider.provider import Provider
from requests.auth import HTTPBasicAuth
from recipes.settings import DEBUG
class Nextcloud(Provider):
@staticmethod
def get_client(storage):
options = {
'webdav_hostname': storage.url,
'webdav_login': storage.username,
'webdav_password': storage.password,
'webdav_root': '/remote.php/dav/files/' + storage.username
}
if storage.path != '':
options['webdav_root'] = storage.path
return wc.Client(options)
@staticmethod
def import_all(monitor):
client = Nextcloud.get_client(monitor.storage)
if DEBUG:
print(f'TANDOOR_PROVIDER_DEBUG checking path {monitor.path} with client {client}')
files = client.list(monitor.path)
if DEBUG:
print(f'TANDOOR_PROVIDER_DEBUG file list {files}')
import_count = 0
for file in files:
if DEBUG:
print(f'TANDOOR_PROVIDER_DEBUG importing file {file}')
path = monitor.path + '/' + file
if not Recipe.objects.filter(file_path__iexact=path, space=monitor.space).exists() and not RecipeImport.objects.filter(file_path=path, space=monitor.space).exists():
name = os.path.splitext(file)[0]
new_recipe = RecipeImport(
name=name,
file_path=path,
storage=monitor.storage,
space=monitor.space,
)
new_recipe.save()
import_count += 1
log_entry = SyncLog(
status='SUCCESS',
msg='Imported ' + str(import_count) + ' recipes',
sync=monitor,
)
log_entry.save()
monitor.last_checked = timezone.now()
monitor.save()
return log_entry
@staticmethod
def create_share_link(recipe):
url = recipe.storage.url + '/ocs/v2.php/apps/files_sharing/api/v1/shares?format=json' # noqa: E501
headers = {
"OCS-APIRequest": "true",
"Content-Type": "application/x-www-form-urlencoded"
}
data = {'path': recipe.file_path, 'shareType': 3}
r = safe_request('POST', url, headers=headers, auth=HTTPBasicAuth(recipe.storage.username, recipe.storage.password), data=data)
response_json = r.json()
return response_json['ocs']['data']['url']
@staticmethod
def get_share_link(recipe):
url = recipe.storage.url + '/ocs/v2.php/apps/files_sharing/api/v1/shares?format=json&path=' + recipe.file_path # noqa: E501
headers = {
"OCS-APIRequest": "true",
"Content-Type": "application/json"
}
r = safe_request('GET',
url,
headers=headers,
auth=HTTPBasicAuth(
recipe.storage.username, recipe.storage.password
)
)
response_json = r.json()
for element in response_json['ocs']['data']:
if element['share_type'] == '3':
return element['url']
return Nextcloud.create_share_link(recipe)
@staticmethod
def get_file(recipe):
client = Nextcloud.get_client(recipe.storage)
tmp_file_path = tempfile.gettempdir() + '/' + recipe.name + '.pdf'
client.download_file(
remote_path=recipe.file_path,
local_path=tmp_file_path
)
file = io.BytesIO(open(tmp_file_path, 'rb').read())
os.remove(tmp_file_path)
return file
@staticmethod
def rename_file(recipe, new_name):
client = Nextcloud.get_client(recipe.storage)
client.move(
recipe.file_path,
"%s/%s%s" % (
os.path.dirname(recipe.file_path),
new_name,
os.path.splitext(recipe.file_path)[1]
)
)
return True
@staticmethod
def delete_file(recipe):
client = Nextcloud.get_client(recipe.storage)
client.clean(recipe.file_path)
return True
================================================
FILE: cookbook/provider/provider.py
================================================
class Provider:
@staticmethod
def import_all(monitor):
raise Exception('Method not implemented in storage provider')
@staticmethod
def create_share_link(recipe):
raise Exception('Method not implemented in storage provider')
@staticmethod
def get_share_link(recipe):
raise Exception('Method not implemented in storage provider')
@staticmethod
def get_file(recipe):
raise Exception('Method not implemented in storage provider')
@staticmethod
def rename_file(recipe, new_name):
raise Exception('Method not implemented in storage provider')
@staticmethod
def delete_file(recipe):
raise Exception('Method not implemented in storage provider')
================================================
FILE: cookbook/serializer.py
================================================
import traceback
import uuid
from datetime import timedelta
from decimal import Decimal
from gettext import gettext as _
from html import escape
from smtplib import SMTPException
from drf_spectacular.utils import extend_schema_field
from django.forms.models import model_to_dict
from django.contrib.auth.models import AnonymousUser, Group, User
from django.core.cache import caches
from django.core.mail import send_mail
from django.db.models import Q, QuerySet, Sum
from django.http import BadHeaderError
from django.urls import reverse
from django.utils import timezone
from django_scopes import scopes_disabled
from drf_writable_nested import UniqueFieldsMixin
from drf_writable_nested import WritableNestedModelSerializer as WNMS
from oauth2_provider.models import AccessToken
from PIL import Image
from rest_framework import serializers
from rest_framework.exceptions import NotFound, ValidationError
from rest_framework.fields import IntegerField
from cookbook.helper.CustomStorageClass import CachedS3Boto3Storage
from cookbook.helper.HelperFunctions import str2bool
from cookbook.helper.ai_helper import get_monthly_token_usage
from cookbook.helper.image_processing import is_file_type_allowed
from cookbook.helper.permission_helper import above_space_limit, create_space_for_user
from cookbook.helper.property_helper import FoodPropertyHelper
from cookbook.helper.shopping_helper import RecipeShoppingEditor
from cookbook.helper.unit_conversion_helper import UnitConversionHelper
from cookbook.models import (Automation, BookmarkletImport, Comment, CookLog, CustomFilter,
ExportLog, Food, FoodInheritField, ImportLog, Ingredient, InviteLink,
Keyword, MealPlan, MealType, NutritionInformation, Property,
PropertyType, Recipe, RecipeBook, RecipeBookEntry, RecipeImport,
ShareLink, ShoppingListEntry, ShoppingListRecipe, Space,
Step, Storage, Supermarket, SupermarketCategory,
SupermarketCategoryRelation, Sync, SyncLog, Unit, UnitConversion,
UserFile, UserPreference, UserSpace, ViewLog, ConnectorConfig, SearchPreference, SearchFields, AiLog, AiProvider, ShoppingList,
InventoryLocation, InventoryEntry, InventoryLog, Household)
from cookbook.templatetags.custom_tags import markdown
from recipes.settings import AWS_ENABLED, MEDIA_URL, EMAIL_HOST
class WritableNestedModelSerializer(WNMS):
# overload to_internal_value to allow using PK only on nested object
def to_internal_value(self, data):
# iterate through every field on the posted object
for f in list(data):
if f not in self.fields:
continue
elif issubclass(self.fields[f].__class__, serializers.Serializer):
# if the field is a serializer and an integer, assume its an ID of an existing object
if isinstance(data[f], int):
# only retrieve serializer required fields
required_fields = ['id'] + [field_name for field_name, field in self.fields[f].__class__().fields.items() if field.required]
data[f] = model_to_dict(self.fields[f].Meta.model.objects.get(id=data[f]), fields=required_fields)
elif issubclass(self.fields[f].__class__, serializers.ListSerializer):
# if the field is a ListSerializer get dict values of PKs provided
if any(isinstance(x, int) for x in data[f]):
# only retrieve serializer required fields
required_fields = ['id'] + [field_name for field_name, field in self.fields[f].child.__class__().fields.items() if field.required]
# filter values to integer values
pk_data = [x for x in data[f] if isinstance(x, int)]
# merge non-pk values with retrieved values
data[f] = [x for x in data[f] if not isinstance(x, int)] \
+ list(self.fields[f].child.Meta.model.objects.filter(id__in=pk_data).values(*required_fields))
return super().to_internal_value(data)
class ExtendedRecipeMixin(serializers.ModelSerializer):
# adds image and recipe count to serializer when query param extended=1
# ORM path to this object from Recipe
recipe_filter = None
# list of ORM paths to any image
images = None
image = serializers.SerializerMethodField('get_image')
numrecipe = serializers.IntegerField(source='recipe_count', read_only=True)
def get_fields(self, *args, **kwargs):
fields = super().get_fields(*args, **kwargs)
try:
api_serializer = self.context['view'].serializer_class
except KeyError:
api_serializer = None
# extended values are computationally expensive and not needed in normal circumstances
try:
if str2bool(self.context['request'].query_params.get('extended', False)) and self.__class__ == api_serializer:
return fields
except (AttributeError, KeyError):
pass
try:
del fields['image']
del fields['numrecipe']
except KeyError:
pass
return fields
def get_image(self, obj):
if obj.recipe_image:
if AWS_ENABLED:
storage = CachedS3Boto3Storage()
path = storage.url(obj.recipe_image)
else:
path = MEDIA_URL + obj.recipe_image
return path
class OpenDataModelMixin(serializers.ModelSerializer):
def create(self, validated_data):
if 'open_data_slug' in validated_data and validated_data['open_data_slug'] is not None and validated_data['open_data_slug'].strip() == '':
validated_data['open_data_slug'] = None
return super().create(validated_data)
def update(self, instance, validated_data):
if 'open_data_slug' in validated_data and validated_data['open_data_slug'] is not None and validated_data['open_data_slug'].strip() == '':
validated_data['open_data_slug'] = None
return super().update(instance, validated_data)
@extend_schema_field(float)
class CustomDecimalField(serializers.Field):
"""
Custom decimal field to normalize useless decimal places
and allow commas as decimal separators
"""
def to_representation(self, value):
if not isinstance(value, Decimal):
value = Decimal(value)
return round(value, 4).normalize()
def to_internal_value(self, data):
if isinstance(data, int) or isinstance(data, float):
return data
elif isinstance(data, str):
if data == '':
return 0
try:
return float(data.replace(',', '.'))
except ValueError:
raise ValidationError('A valid number is required')
@extend_schema_field(bool)
class CustomOnHandField(serializers.Field):
def get_attribute(self, instance):
return instance
def to_representation(self, obj):
try:
if not self.context["request"].user.is_authenticated:
return []
shared_users = []
if c := caches['default'].get(f'shopping_shared_users_{self.context["request"].space.id}_{self.context["request"].user.id}', None):
shared_users = c
else:
try:
shared_users = self.context["request"].user_space.household.values_list('user_id', flat=True)
caches['default'].set(f'shopping_shared_users_{self.context["request"].space.id}_{self.context["request"].user.id}', shared_users, timeout=5 * 60)
# TODO ugly hack that improves API performance significantly, should be done properly
except AttributeError: # Anonymous users (using share links) don't have shared users
pass
return obj.onhand_users.filter(id__in=shared_users).exists()
except AttributeError:
return []
def to_internal_value(self, data):
return data
class SpaceFilterSerializer(serializers.ListSerializer):
def to_representation(self, data):
if self.context.get('request', None) is None:
return
if (isinstance(data, QuerySet) and data.query.is_sliced):
# if query is sliced it came from api request not nested serializer
return super().to_representation(data)
if self.child.Meta.model == User:
# Don't return User details to anonymous users
if isinstance(self.context['request'].user, AnonymousUser):
data = []
else:
iterable = data.all() if hasattr(data, 'all') else data
if isinstance(iterable, list) or (isinstance(iterable, QuerySet) and getattr(iterable, '_result_cache', None) is not None):
try:
new_data = []
for u in iterable:
for us in u.userspace_set.all():
if us.space.id == self.context['request'].space.id:
new_data.append(u)
data = new_data
except Exception:
traceback.print_exc()
data = data.filter(userspace__space=self.context['request'].user.get_active_space()).all()
else:
if hasattr(self.context['request'], 'space'):
data = data.filter(userspace__space=self.context['request'].space).all()
else:
# not sure why but this branch can be hit (just normal page load, need to see why)
data = data.filter(userspace__space=self.context['request'].user.get_active_space()).all()
elif isinstance(data, list):
data = [d for d in data if getattr(d, self.child.Meta.model.get_space_key()[0]) == self.context['request'].space]
else:
iterable = data.all() if hasattr(data, 'all') else data
if isinstance(iterable, list) or (isinstance(iterable, QuerySet) and getattr(iterable, '_result_cache', None) is not None):
keys = self.child.Meta.model.get_space_key()
if keys == ('space',):
data = [d for d in iterable if getattr(d, 'space_id') == self.context['request'].space.id]
else:
# use cached results here too, just dont have time to test this now, probably obj.get_space()
data = data.filter(**{'__'.join(self.child.Meta.model.get_space_key()): self.context['request'].space})
else:
data = data.filter(**{'__'.join(self.child.Meta.model.get_space_key()): self.context['request'].space})
return super().to_representation(data)
class UserSerializer(WritableNestedModelSerializer):
display_name = serializers.SerializerMethodField('get_user_label')
@extend_schema_field(str)
def get_user_label(self, obj):
return obj.get_user_display_name()
class Meta:
list_serializer_class = SpaceFilterSerializer
model = User
fields = ('id', 'username', 'first_name', 'last_name', 'display_name', 'is_staff', 'is_superuser', 'is_active')
read_only_fields = ('id', 'username', 'display_name', 'is_staff', 'is_superuser', 'is_active')
class GroupSerializer(UniqueFieldsMixin, WritableNestedModelSerializer):
def create(self, validated_data):
raise ValidationError('Cannot create using this endpoint')
def update(self, instance, validated_data):
return instance # cannot update group
class Meta:
model = Group
fields = ('id', 'name')
read_only_fields = ('id', 'name')
class FoodInheritFieldSerializer(UniqueFieldsMixin, WritableNestedModelSerializer):
name = serializers.CharField(allow_null=True, allow_blank=True, required=False)
field = serializers.CharField(allow_null=True, allow_blank=True, required=False)
def create(self, validated_data):
raise ValidationError('Cannot create using this endpoint')
def update(self, instance, validated_data):
return instance
class Meta:
model = FoodInheritField
fields = ('id', 'name', 'field',)
read_only_fields = ['id']
class UserFileSerializer(serializers.ModelSerializer):
created_by = UserSerializer(read_only=True)
file = serializers.FileField(write_only=True, required=False)
file_download = serializers.SerializerMethodField('get_download_link')
preview = serializers.SerializerMethodField('get_preview_link')
@extend_schema_field(serializers.CharField(read_only=True))
def get_download_link(self, obj):
return self.context['request'].build_absolute_uri(reverse('api_download_file', args={obj.pk}))
@extend_schema_field(serializers.CharField(read_only=True))
def get_preview_link(self, obj):
try:
Image.open(obj.file.file.file)
return self.context['request'].build_absolute_uri(obj.file.url)
except Exception:
# traceback.print_exc()
return ""
def check_file_limit(self, validated_data):
if 'file' in validated_data:
if self.context['request'].space.max_file_storage_mb == -1:
raise ValidationError(_('File uploads are not enabled for this Space.'))
try:
current_file_size_mb = \
UserFile.objects.filter(space=self.context['request'].space).aggregate(Sum('file_size_kb'))[
'file_size_kb__sum'] / 1000
except TypeError:
current_file_size_mb = 0
if ((validated_data['file'].size / 1000 / 1000 + current_file_size_mb - 5)
> self.context['request'].space.max_file_storage_mb != 0):
raise ValidationError(_('You have reached your file upload limit.'))
def check_file_type(self, validated_data):
print('checking file type')
if 'file' in validated_data:
print('filke present in data')
if not is_file_type_allowed(validated_data['file'].name, image_only=False):
print('is not allowed')
raise ValidationError(_('The given file type is not allowed.'))
def create(self, validated_data):
self.check_file_limit(validated_data)
self.check_file_type(validated_data)
validated_data['created_by'] = self.context['request'].user
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
def update(self, instance, validated_data):
self.check_file_limit(validated_data)
self.check_file_type(validated_data)
return super().update(instance, validated_data)
class Meta:
model = UserFile
fields = ('id', 'name', 'file', 'file_download', 'preview', 'file_size_kb', 'created_by', 'created_at')
read_only_fields = ('id', 'file_download', 'preview', 'file_size_kb', 'created_by', 'created_at')
extra_kwargs = {"file": {"required": False, }}
class UserFileViewSerializer(serializers.ModelSerializer):
created_by = UserSerializer(read_only=True)
file_download = serializers.SerializerMethodField('get_download_link')
preview = serializers.SerializerMethodField('get_preview_link')
@extend_schema_field(str)
def get_download_link(self, obj):
return self.context['request'].build_absolute_uri(reverse('api_download_file', args={obj.pk}))
@extend_schema_field(str)
def get_preview_link(self, obj):
try:
Image.open(obj.file.file.file)
return self.context['request'].build_absolute_uri(obj.file.url)
except Exception:
# traceback.print_exc()
return ""
def create(self, validated_data):
raise ValidationError('Cannot create File over this view')
def update(self, instance, validated_data):
return instance
class Meta:
model = UserFile
fields = ('id', 'name', 'file_download', 'preview', 'file_size_kb', 'created_by', 'created_at')
read_only_fields = ('id', 'file', 'file_download', 'file_size_kb', 'preview', 'created_by', 'created_at')
class AiProviderSerializer(serializers.ModelSerializer):
api_key = serializers.CharField(required=False, write_only=True)
def create(self, validated_data):
validated_data = self.handle_global_space_logic(validated_data)
return super().create(validated_data)
def update(self, instance, validated_data):
validated_data = self.handle_global_space_logic(validated_data, instance=instance)
return super().update(instance, validated_data)
def handle_global_space_logic(self, validated_data, instance=None):
"""
allow superusers to create AI providers without a space but make sure everyone else only uses their own space
"""
if self.context['request'].user.is_superuser:
if ('space' not in validated_data or not validated_data['space']):
validated_data['space'] = None
else:
validated_data['space'] = self.context['request'].space
else:
if instance:
validated_data['space'] = instance.space
else:
validated_data['space'] = self.context['request'].space
if 'log_credit_cost' in validated_data and not self.context['request'].user.is_superuser:
del validated_data['log_credit_cost']
return validated_data
class Meta:
model = AiProvider
fields = ('id', 'name', 'description', 'api_key', 'model_name', 'url', 'log_credit_cost', 'space', 'created_at', 'updated_at')
read_only_fields = ('created_at', 'updated_at',)
class AiLogSerializer(serializers.ModelSerializer):
ai_provider = AiProviderSerializer(read_only=True)
class Meta:
model = AiLog
fields = ('id', 'ai_provider', 'function', 'credit_cost', 'credits_from_balance', 'input_tokens', 'output_tokens', 'start_time', 'end_time', 'created_by', 'created_at',
'updated_at')
read_only_fields = ('__all__',)
class SpaceSerializer(WritableNestedModelSerializer):
created_by = UserSerializer(read_only=True)
user_count = serializers.SerializerMethodField('get_user_count', read_only=True)
recipe_count = serializers.SerializerMethodField('get_recipe_count', read_only=True)
file_size_mb = serializers.SerializerMethodField('get_file_size_mb', read_only=True)
ai_monthly_credits_used = serializers.SerializerMethodField('get_ai_monthly_credits_used', read_only=True)
ai_default_provider = AiProviderSerializer(required=False, allow_null=True)
food_inherit = FoodInheritFieldSerializer(many=True, required=False)
image = UserFileViewSerializer(required=False, many=False, allow_null=True)
nav_logo = UserFileViewSerializer(required=False, many=False, allow_null=True)
custom_space_theme = UserFileViewSerializer(required=False, many=False, allow_null=True)
logo_color_32 = UserFileViewSerializer(required=False, many=False, allow_null=True)
logo_color_128 = UserFileViewSerializer(required=False, many=False, allow_null=True)
logo_color_144 = UserFileViewSerializer(required=False, many=False, allow_null=True)
logo_color_180 = UserFileViewSerializer(required=False, many=False, allow_null=True)
logo_color_192 = UserFileViewSerializer(required=False, many=False, allow_null=True)
logo_color_512 = UserFileViewSerializer(required=False, many=False, allow_null=True)
logo_color_svg = UserFileViewSerializer(required=False, many=False, allow_null=True)
@extend_schema_field(int)
def get_user_count(self, obj):
return UserSpace.objects.filter(space=obj).count()
@extend_schema_field(int)
def get_recipe_count(self, obj):
return Recipe.objects.filter(space=obj).count()
@extend_schema_field(int)
def get_ai_monthly_credits_used(self, obj):
return get_monthly_token_usage(obj)
@extend_schema_field(float)
def get_file_size_mb(self, obj):
try:
return UserFile.objects.filter(space=obj).aggregate(Sum('file_size_kb'))['file_size_kb__sum'] / 1000
except TypeError:
return 0
def create(self, validated_data):
if Space.objects.filter(created_by=self.context['request'].user).count() >= self.context['request'].user.userpreference.max_owned_spaces:
raise serializers.ValidationError(
_('You have the reached the maximum amount of spaces that can be owned by you.') + f' ({self.context['request'].user.userpreference.max_owned_spaces})')
name = None
if 'name' in validated_data:
name = validated_data['name']
user_space = create_space_for_user(self.context['request'].user, name)
return user_space.space
def update(self, instance, validated_data):
validated_data = self.filter_superuser_parameters(validated_data)
if 'name' in validated_data:
if Space.objects.filter(Q(name=validated_data['name']), ~Q(pk=instance.pk)).exists():
raise ValidationError(_('Space Name must be unique.'))
return super().update(instance, validated_data)
def filter_superuser_parameters(self, validated_data):
if 'ai_enabled' in validated_data and not self.context['request'].user.is_superuser:
del validated_data['ai_enabled']
if 'ai_credits_monthly' in validated_data and not self.context['request'].user.is_superuser:
del validated_data['ai_credits_monthly']
if 'ai_credits_balance' in validated_data and not self.context['request'].user.is_superuser:
del validated_data['ai_credits_balance']
return validated_data
class Meta:
model = Space
fields = (
'id', 'name', 'created_by', 'created_at', 'message', 'max_recipes', 'max_file_storage_mb', 'max_users',
'allow_sharing', 'demo', 'food_inherit', 'user_count', 'recipe_count', 'file_size_mb',
'image', 'nav_logo', 'space_theme', 'custom_space_theme', 'nav_bg_color', 'nav_text_color',
'logo_color_32', 'logo_color_128', 'logo_color_144', 'logo_color_180', 'logo_color_192', 'logo_color_512', 'logo_color_svg', 'ai_credits_monthly',
'ai_credits_balance', 'ai_monthly_credits_used', 'ai_enabled', 'ai_default_provider', 'space_setup_completed')
read_only_fields = (
'id', 'created_by', 'created_at', 'max_recipes', 'max_file_storage_mb', 'max_users', 'allow_sharing',
'demo', 'ai_monthly_credits_used')
class HouseholdSerializer(WritableNestedModelSerializer):
def create(self, validated_data):
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
class Meta:
model = Household
fields = ('id', 'name', 'created_at', 'updated_at')
read_only_fields = ('id', 'created_at', 'updated_at',)
class UserSpaceSerializer(WritableNestedModelSerializer):
user = UserSerializer(read_only=True)
groups = GroupSerializer(many=True)
household = HouseholdSerializer(allow_null=True, required=False)
def create(self, validated_data):
raise ValidationError('Cannot create using this endpoint')
class Meta:
model = UserSpace
fields = ('id', 'user', 'space', 'groups', 'household','active', 'internal_note', 'invite_link', 'created_at', 'updated_at',)
read_only_fields = ('id', 'invite_link', 'created_at', 'updated_at', 'space')
class SpacedModelSerializer(serializers.ModelSerializer):
def create(self, validated_data):
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
class ShoppingListSerializer(SpacedModelSerializer, WritableNestedModelSerializer):
def create(self, validated_data):
validated_data['name'] = validated_data['name'].strip()
space = validated_data.pop('space', self.context['request'].space)
obj, created = ShoppingList.objects.get_or_create(name__iexact=validated_data['name'], space=space, defaults=validated_data)
return obj
class Meta:
model = ShoppingList
fields = ('id', 'name', 'description', 'color',)
read_only_fields = ('id',)
class MealTypeSerializer(SpacedModelSerializer, WritableNestedModelSerializer):
def create(self, validated_data):
validated_data['name'] = validated_data['name'].strip()
space = validated_data.pop('space', self.context['request'].space)
validated_data['created_by'] = self.context['request'].user
obj, created = MealType.objects.get_or_create(name__iexact=validated_data['name'], space=space, created_by=self.context['request'].user, defaults=validated_data)
return obj
class Meta:
list_serializer_class = SpaceFilterSerializer
model = MealType
fields = ('id', 'name', 'order', 'time', 'color', 'created_by')
read_only_fields = ('created_by',)
class UserPreferenceSerializer(WritableNestedModelSerializer):
user = UserSerializer(read_only=True)
food_inherit_default = serializers.SerializerMethodField('get_food_inherit_defaults')
plan_share = UserSerializer(many=True, allow_null=True, required=False)
shopping_share = UserSerializer(many=True, allow_null=True, required=False)
default_meal_type = MealTypeSerializer(required=False, allow_null=True)
food_children_exist = serializers.SerializerMethodField('get_food_children_exist')
image = UserFileViewSerializer(required=False, allow_null=True, many=False)
@extend_schema_field(FoodInheritFieldSerializer)
def get_food_inherit_defaults(self, obj):
return FoodInheritFieldSerializer(obj.user.get_active_space().food_inherit.all(), many=True).data
@extend_schema_field(bool)
def get_food_children_exist(self, obj):
space = getattr(self.context.get('request', None), 'space', None)
return Food.objects.filter(depth__gt=0, space=space).exists()
def update(self, instance, validated_data):
with scopes_disabled():
return super().update(instance, validated_data)
def create(self, validated_data):
raise ValidationError('Cannot create using this endpoint')
class Meta:
model = UserPreference
fields = (
'user', 'image', 'theme', 'nav_bg_color', 'nav_text_color', 'nav_show_logo', 'default_unit', 'default_page',
'use_fractions', 'use_kj',
'plan_share', 'nav_sticky',
'ingredient_decimals', 'comments', 'shopping_auto_sync', 'mealplan_autoadd_shopping',
'food_inherit_default', 'default_delay',
'mealplan_autoinclude_related', 'mealplan_autoexclude_onhand', 'shopping_share', 'shopping_recent_days',
'csv_delim', 'csv_prefix', 'shopping_update_food_lists','default_meal_type',
'filter_to_supermarket', 'shopping_add_onhand', 'left_handed', 'show_step_ingredients',
'food_children_exist'
)
read_only_fields = ('user',)
class SearchFieldsSerializer(UniqueFieldsMixin, WritableNestedModelSerializer):
name = serializers.CharField(allow_null=True, allow_blank=True, required=False)
field = serializers.CharField(allow_null=True, allow_blank=True, required=False)
def create(self, validated_data):
raise ValidationError('Cannot create using this endpoint')
def update(self, instance, validated_data):
return instance
class Meta:
model = SearchFields
fields = ('id', 'name', 'field',)
read_only_fields = ('id',)
class SearchPreferenceSerializer(WritableNestedModelSerializer):
user = UserSerializer(read_only=True)
unaccent = SearchFieldsSerializer(many=True, allow_null=True, required=False)
icontains = SearchFieldsSerializer(many=True, allow_null=True, required=False)
istartswith = SearchFieldsSerializer(many=True, allow_null=True, required=False)
trigram = SearchFieldsSerializer(many=True, allow_null=True, required=False)
fulltext = SearchFieldsSerializer(many=True, allow_null=True, required=False)
def create(self, validated_data):
raise ValidationError('Cannot create using this endpoint')
class Meta:
model = SearchPreference
fields = ('user', 'search', 'lookup', 'unaccent', 'icontains', 'istartswith', 'trigram', 'fulltext', 'trigram_threshold')
read_only_fields = ('user',)
class ConnectorConfigSerializer(SpacedModelSerializer):
def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user
return super().create(validated_data)
class Meta:
model = ConnectorConfig
fields = (
'id', 'name', 'type', 'url', 'token', 'todo_entity', 'enabled',
'on_shopping_list_entry_created_enabled', 'on_shopping_list_entry_updated_enabled',
'on_shopping_list_entry_deleted_enabled', 'supports_description_field', 'created_by'
)
read_only_fields = ('created_by',)
extra_kwargs = {
'token': {'write_only': True},
}
class StorageSerializer(WritableNestedModelSerializer, SpacedModelSerializer):
def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user
return super().create(validated_data)
class Meta:
model = Storage
fields = (
'id', 'name', 'method', 'username', 'password',
'token', 'url', 'path', 'created_by'
)
read_only_fields = ('id', 'created_by',)
extra_kwargs = {
'password': {'write_only': True},
'token': {'write_only': True},
}
class RecipeImportSerializer(WritableNestedModelSerializer, SpacedModelSerializer):
storage = StorageSerializer()
class Meta:
model = RecipeImport
fields = ('id', 'storage', 'name', 'file_uid', 'file_path', 'created_at')
class SyncSerializer(WritableNestedModelSerializer, SpacedModelSerializer):
storage = StorageSerializer()
class Meta:
model = Sync
fields = (
'id', 'storage', 'path', 'active', 'last_checked',
'created_at', 'updated_at'
)
class SyncLogSerializer(SpacedModelSerializer):
sync = SyncSerializer(read_only=True)
class Meta:
model = SyncLog
fields = ('id', 'sync', 'status', 'msg', 'created_at')
class KeywordLabelSerializer(serializers.ModelSerializer):
label = serializers.SerializerMethodField('get_label')
@extend_schema_field(str)
def get_label(self, obj):
return obj.name
class Meta:
list_serializer_class = SpaceFilterSerializer
model = Keyword
fields = ('id', 'label')
read_only_fields = ('id', 'label')
class KeywordSerializer(UniqueFieldsMixin, ExtendedRecipeMixin):
label = serializers.SerializerMethodField('get_label', allow_null=False)
parent = IntegerField(read_only=True)
recipe_filter = 'keywords'
@extend_schema_field(str)
def get_label(self, obj):
return obj.name
def create(self, validated_data):
# since multi select tags dont have id's
# duplicate names might be routed to create
name = validated_data.pop('name').strip()
space = validated_data.pop('space', self.context['request'].space)
obj, created = Keyword.objects.get_or_create(name=name, space=space, defaults=validated_data)
return obj
class Meta:
model = Keyword
fields = (
'id', 'name', 'label', 'description', 'image', 'parent', 'numchild', 'numrecipe', 'created_at',
'updated_at', 'full_name')
read_only_fields = ('id', 'label', 'numchild', 'numrecipe', 'parent', 'image')
class UnitSerializer(UniqueFieldsMixin, ExtendedRecipeMixin, OpenDataModelMixin):
recipe_filter = 'steps__ingredients__unit'
def create(self, validated_data):
# get_or_create drops any field that contains '__' when creating so values must be included in validated data
space = validated_data.pop('space', self.context['request'].space)
if x := validated_data.get('name', None):
validated_data['name'] = x.strip()
if x := validated_data.get('name', None):
validated_data['plural_name'] = x.strip()
if unit := Unit.objects.filter(
Q(name__iexact=validated_data['name']) | Q(plural_name__iexact=validated_data['name']),
space=space).first():
return unit
obj, created = Unit.objects.get_or_create(name__iexact=validated_data['name'], space=space,
defaults=validated_data)
return obj
def update(self, instance, validated_data):
validated_data['name'] = validated_data['name'].strip()
if plural_name := validated_data.get('plural_name', None):
validated_data['plural_name'] = plural_name.strip()
return super(UnitSerializer, self).update(instance, validated_data)
class Meta:
model = Unit
fields = ('id', 'name', 'plural_name', 'description', 'base_unit', 'numrecipe', 'image', 'open_data_slug')
read_only_fields = ('id', 'numrecipe', 'image')
class SupermarketCategorySerializer(UniqueFieldsMixin, WritableNestedModelSerializer, OpenDataModelMixin):
def create(self, validated_data):
validated_data['name'] = validated_data['name'].strip()
space = validated_data.pop('space', self.context['request'].space)
obj, created = SupermarketCategory.objects.get_or_create(name__iexact=validated_data['name'], space=space,
defaults=validated_data)
return obj
def update(self, instance, validated_data):
return super(SupermarketCategorySerializer, self).update(instance, validated_data)
class Meta:
model = SupermarketCategory
fields = ('id', 'name', 'description', 'open_data_slug')
class SupermarketCategoryRelationSerializer(WritableNestedModelSerializer):
category = SupermarketCategorySerializer()
class Meta:
model = SupermarketCategoryRelation
fields = ('id', 'category', 'supermarket', 'order')
class SupermarketSerializer(UniqueFieldsMixin, SpacedModelSerializer, WritableNestedModelSerializer, OpenDataModelMixin):
category_to_supermarket = SupermarketCategoryRelationSerializer(many=True, read_only=True)
shopping_lists = ShoppingListSerializer(many=True, required=False)
def create(self, validated_data):
validated_data['name'] = validated_data['name'].strip()
space = validated_data.pop('space', self.context['request'].space)
obj, created = Supermarket.objects.get_or_create(name__iexact=validated_data['name'], space=space,
defaults=validated_data)
return obj
class Meta:
model = Supermarket
fields = ('id', 'name', 'description', 'shopping_lists', 'category_to_supermarket', 'open_data_slug')
class PropertyTypeSerializer(OpenDataModelMixin, WritableNestedModelSerializer, UniqueFieldsMixin):
id = serializers.IntegerField(required=False)
order = IntegerField(default=0, required=False)
def create(self, validated_data):
validated_data['name'] = validated_data['name'].strip()
space = validated_data.pop('space', self.context['request'].space)
obj, created = PropertyType.objects.get_or_create(name__iexact=validated_data['name'], space=space,
defaults=validated_data)
return obj
class Meta:
model = PropertyType
fields = ('id', 'name', 'unit', 'description', 'order', 'open_data_slug', 'fdc_id',)
class PropertySerializer(UniqueFieldsMixin, WritableNestedModelSerializer):
property_type = PropertyTypeSerializer()
property_amount = CustomDecimalField(allow_null=True)
def create(self, validated_data):
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
class Meta:
model = Property
fields = ('id', 'property_amount', 'property_type')
class RecipeSimpleSerializer(WritableNestedModelSerializer):
url = serializers.SerializerMethodField('get_url')
@extend_schema_field(str)
def get_url(self, obj):
return f'recipe/{obj.pk}'
def create(self, validated_data):
# don't allow writing to Recipe via this API
return Recipe.objects.get(**validated_data)
def update(self, instance, validated_data):
# don't allow writing to Recipe via this API
return instance
class Meta:
model = Recipe
fields = ('id', 'name', 'url')
class RecipeFlatSerializer(WritableNestedModelSerializer):
def create(self, validated_data):
# don't allow writing to Recipe via this API
return Recipe.objects.get(**validated_data)
def update(self, instance, validated_data):
# don't allow writing to Recipe via this API
return Recipe.objects.get(**validated_data)
class Meta:
model = Recipe
fields = ('id', 'name', 'image')
read_only_fields = ('id', 'name', 'image')
class FoodSimpleSerializer(serializers.ModelSerializer):
class Meta:
model = Food
fields = ('id', 'name', 'plural_name')
class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedRecipeMixin, OpenDataModelMixin):
supermarket_category = SupermarketCategorySerializer(allow_null=True, required=False)
recipe = RecipeSimpleSerializer(allow_null=True, required=False)
shopping = serializers.CharField(source='shopping_status', read_only=True)
inherit_fields = FoodInheritFieldSerializer(many=True, allow_null=True, required=False)
child_inherit_fields = FoodInheritFieldSerializer(many=True, allow_null=True, required=False)
food_onhand = CustomOnHandField(required=False, allow_null=True)
substitute_onhand = serializers.SerializerMethodField('get_substitute_onhand')
substitute = FoodSimpleSerializer(many=True, allow_null=True, required=False)
parent = IntegerField(read_only=True)
shopping_lists = ShoppingListSerializer(many=True, required=False)
properties = PropertySerializer(many=True, allow_null=True, required=False)
properties_food_unit = UnitSerializer(allow_null=True, required=False)
properties_food_amount = CustomDecimalField(required=False)
recipe_filter = 'steps__ingredients__food'
images = ['recipe__image']
@extend_schema_field(bool)
def get_substitute_onhand(self, obj):
try:
if not self.context["request"].user.is_authenticated:
return []
shared_users = []
if c := caches['default'].get(
f'shopping_shared_users_{self.context["request"].space.id}_{self.context["request"].user.id}', None):
shared_users = c
else:
try:
shared_users = self.context["request"].user_space.household.values_list('user_id', flat=True)
caches['default'].set(
f'shopping_shared_users_{self.context["request"].space.id}_{self.context["request"].user.id}',
shared_users, timeout=5 * 60)
# TODO ugly hack that improves API performance significantly, should be done properly
except AttributeError: # Anonymous users (using share links) don't have shared users
pass
filter = Q(id__in=obj.substitute.all())
if obj.substitute_siblings:
filter |= Q(path__startswith=obj.path[:Food.steplen * (obj.depth - 1)], depth=obj.depth)
if obj.substitute_children:
filter |= Q(path__startswith=obj.path, depth__gt=obj.depth)
return Food.objects.filter(filter).filter(onhand_users__id__in=shared_users).exists()
except AttributeError:
return []
def create(self, validated_data):
name = validated_data['name'].strip()
if plural_name := validated_data.pop('plural_name', None):
plural_name = plural_name.strip()
if food := Food.objects.filter(Q(name__iexact=name) | Q(plural_name__iexact=name)).first():
return food
space = validated_data.pop('space', self.context['request'].space)
# supermarket category needs to be handled manually as food.get or create does not create nested serializers unlike a super.create of serializer
if 'supermarket_category' in validated_data and validated_data['supermarket_category']:
sm_category = validated_data['supermarket_category']
sc_name = sm_category.pop('name', None)
validated_data['supermarket_category'], sc_created = SupermarketCategory.objects.get_or_create(
name=sc_name,
space=space, defaults=sm_category)
onhand = validated_data.pop('food_onhand', None)
if recipe := validated_data.get('recipe', None):
validated_data['recipe'] = Recipe.objects.get(**recipe)
# assuming if on hand for user also onhand for shopping_share users
if onhand is not None:
shared_users = [user := self.context['request'].user] + list(user.userpreference.shopping_share.all())
if self.instance:
onhand_users = self.instance.onhand_users.all()
else:
onhand_users = []
if onhand:
validated_data['onhand_users'] = list(onhand_users) + shared_users
else:
validated_data['onhand_users'] = list(set(onhand_users) - set(shared_users))
if properties_food_unit := validated_data.pop('properties_food_unit', None):
properties_food_unit = Unit.objects.filter(name=properties_food_unit['name']).first()
properties = validated_data.pop('properties', None)
obj, created = Food.objects.get_or_create(name=name, plural_name=plural_name, space=space,
properties_food_unit=properties_food_unit,
defaults=validated_data)
if properties and len(properties) > 0:
for p in properties:
obj.properties.add(Property.objects.create(property_type_id=p['property_type']['id'],
property_amount=p['property_amount'], space=space))
return obj
def update(self, instance, validated_data):
if name := validated_data.get('name', None):
validated_data['name'] = name.strip()
if plural_name := validated_data.get('plural_name', None):
validated_data['plural_name'] = plural_name.strip()
# assuming if on hand for user also onhand for shopping_share users
onhand = validated_data.get('food_onhand', None)
reset_inherit = self.initial_data.get('reset_inherit', False)
if onhand is not None:
shared_users = []
if self.context["request"].user_space.household:
shared_users = self.context["request"].user_space.household.userspace_set.values_list('user_id', flat=True)
if len(shared_users) == 0:
shared_users = [user := self.context['request'].user]
if onhand:
validated_data['onhand_users'] = list(self.instance.onhand_users.all()) + shared_users
else:
validated_data['onhand_users'] = list(set(self.instance.onhand_users.all()) - set(shared_users))
# update before resetting inheritance
saved_instance = super(FoodSerializer, self).update(instance, validated_data)
if reset_inherit and (r := self.context.get('request', None)):
Food.reset_inheritance(food=saved_instance, space=r.space)
return saved_instance
class Meta:
model = Food
fields = (
'id', 'name', 'plural_name', 'description', 'shopping', 'recipe', 'url', 'properties', 'properties_food_amount', 'properties_food_unit', 'fdc_id',
'food_onhand', 'supermarket_category', 'image', 'parent', 'numchild', 'numrecipe', 'inherit_fields', 'full_name', 'ignore_shopping',
'substitute', 'substitute_siblings', 'substitute_children', 'substitute_onhand', 'child_inherit_fields', 'open_data_slug', 'shopping_lists',
)
read_only_fields = ('id', 'numchild', 'parent', 'image', 'numrecipe')
class IngredientSimpleSerializer(WritableNestedModelSerializer):
food = FoodSimpleSerializer(allow_null=True)
unit = UnitSerializer(allow_null=True)
amount = CustomDecimalField()
checked = serializers.BooleanField(read_only=True, default=False, help_text='Just laziness to have a checked field on the frontend API client')
def create(self, validated_data):
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
def update(self, instance, validated_data):
validated_data.pop('original_text', None)
return super().update(instance, validated_data)
class Meta:
model = Ingredient
fields = (
'id', 'food', 'unit', 'amount', 'note', 'order',
'is_header', 'no_amount', 'original_text', 'checked',
'always_use_plural_unit', 'always_use_plural_food',
)
class IngredientSerializer(IngredientSimpleSerializer):
food = FoodSerializer(allow_null=True)
used_in_recipes = serializers.SerializerMethodField('get_used_in_recipes')
conversions = serializers.SerializerMethodField('get_conversions')
@extend_schema_field(list)
def get_used_in_recipes(self, obj):
used_in = []
for s in obj.step_set.all():
for r in s.recipe_set.all():
used_in.append({'id': r.id, 'name': r.name})
return used_in
@extend_schema_field(list)
def get_conversions(self, obj):
if obj.unit and obj.food:
uch = UnitConversionHelper(self.context['request'].space)
conversions = []
for c in uch.get_conversions(obj):
conversions.append(
{'food': c.food.name, 'unit': c.unit.name, 'amount': c.amount}) # TODO do formatting in helper
return conversions
else:
return []
class Meta:
model = Ingredient
fields = (
'id', 'food', 'unit', 'amount', 'conversions', 'note', 'order',
'is_header', 'no_amount', 'original_text', 'used_in_recipes',
'always_use_plural_unit', 'always_use_plural_food', 'checked',
)
read_only_fields = ['conversions', ]
class StepSerializer(WritableNestedModelSerializer, ExtendedRecipeMixin):
ingredients = IngredientSerializer(many=True)
instructions_markdown = serializers.SerializerMethodField('get_instructions_markdown')
file = UserFileViewSerializer(allow_null=True, required=False)
step_recipe_data = serializers.SerializerMethodField('get_step_recipe_data')
recipe_filter = 'steps'
def create(self, validated_data):
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
@extend_schema_field(str)
def get_instructions_markdown(self, obj):
return obj.get_instruction_render()
@extend_schema_field(serializers.ListField)
def get_step_recipes(self, obj):
return list(obj.recipe_set.values_list('id', flat=True).all())
# couldn't set proper serializer StepRecipeSerializer because of circular reference
@extend_schema_field(serializers.JSONField)
def get_step_recipe_data(self, obj):
# check if root type is recipe to prevent infinite recursion
# can be improved later to allow multi level embedding
if obj.step_recipe and isinstance(self.parent.root, RecipeSerializer):
return StepRecipeSerializer(obj.step_recipe, context={'request': self.context['request']}).data
class Meta:
model = Step
fields = (
'id', 'name', 'instruction', 'ingredients', 'instructions_markdown', 'time', 'order', 'show_as_header', 'file', 'step_recipe',
'step_recipe_data', 'numrecipe', 'show_ingredients_table'
)
class StepRecipeSerializer(WritableNestedModelSerializer):
steps = StepSerializer(many=True)
class Meta:
model = Recipe
fields = ('id', 'name', 'steps')
class UnitConversionSerializer(WritableNestedModelSerializer, OpenDataModelMixin):
name = serializers.SerializerMethodField('get_conversion_name')
base_unit = UnitSerializer()
converted_unit = UnitSerializer()
food = FoodSerializer(allow_null=True, required=False)
base_amount = CustomDecimalField()
converted_amount = CustomDecimalField()
@extend_schema_field(str)
def get_conversion_name(self, obj):
text = f'{round(obj.base_amount)} {obj.base_unit} '
if obj.food:
text += f' {obj.food}'
return text + f' = {round(obj.converted_amount)} {obj.converted_unit}'
def create(self, validated_data):
validated_data['space'] = validated_data.pop('space', self.context['request'].space)
try:
return UnitConversion.objects.get(
food__name__iexact=validated_data.get('food', {}).get('name', None),
base_unit__name__iexact=validated_data.get('base_unit', {}).get('name', None),
converted_unit__name__iexact=validated_data.get('converted_unit', {}).get('name', None),
space=validated_data['space']
)
except UnitConversion.DoesNotExist:
validated_data['created_by'] = self.context['request'].user
return super().create(validated_data)
class Meta:
model = UnitConversion
fields = ('id', 'name', 'base_amount', 'base_unit', 'converted_amount', 'converted_unit', 'food', 'open_data_slug')
class NutritionInformationSerializer(serializers.ModelSerializer):
carbohydrates = CustomDecimalField()
fats = CustomDecimalField()
proteins = CustomDecimalField()
calories = CustomDecimalField()
def create(self, validated_data):
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
class Meta:
model = NutritionInformation
fields = ('id', 'carbohydrates', 'fats', 'proteins', 'calories', 'source')
class RecipeBaseSerializer(WritableNestedModelSerializer):
# TODO make days of new recipe a setting
@extend_schema_field(bool)
def is_recipe_new(self, obj):
if getattr(obj, 'new_recipe', None) or obj.created_at > (timezone.now() - timedelta(days=7)):
return True
else:
return False
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = '__all__'
read_only_fields = ['id', 'created_at', 'created_by', 'updated_at', ]
class RecipeOverviewSerializer(RecipeBaseSerializer):
keywords = KeywordLabelSerializer(many=True, read_only=True)
new = serializers.SerializerMethodField('is_recipe_new', read_only=True)
recent = serializers.CharField(read_only=True)
rating = CustomDecimalField(required=False, allow_null=True, read_only=True)
last_cooked = serializers.DateTimeField(required=False, allow_null=True, read_only=True)
created_by = UserSerializer(read_only=True)
def create(self, validated_data):
pass
def update(self, instance, validated_data):
return instance
class Meta:
model = Recipe
fields = (
'id', 'name', 'description', 'image', 'keywords', 'working_time',
'waiting_time', 'created_by', 'created_at', 'updated_at',
'internal', 'private', 'servings', 'servings_text', 'rating', 'last_cooked', 'new', 'recent'
)
# TODO having these readonly fields makes "RecipeOverview.ts" (API Client) not generate the RecipeOverviewToJSON second else block which leads to errors when using the api
# TODO find a solution (custom schema?) to have these fields readonly (to save performance) and generate a proper client (two serializers would probably do the trick)
# read_only_fields = ['id', 'name', 'description', 'image', 'keywords', 'working_time',
# 'waiting_time', 'created_by', 'created_at', 'updated_at',
# 'internal', 'servings', 'servings_text', 'rating', 'last_cooked', 'new', 'recent']
read_only_fields = ['image', 'keywords', 'working_time',
'waiting_time', 'created_by', 'created_at', 'updated_at',
'internal', 'servings', 'servings_text', 'diameter', 'diameter_text', 'rating', 'last_cooked', 'new', 'recent']
class RecipeSerializer(RecipeBaseSerializer):
nutrition = NutritionInformationSerializer(allow_null=True, required=False)
properties = PropertySerializer(many=True, required=False)
steps = StepSerializer(many=True)
keywords = KeywordSerializer(many=True, required=False)
shared = UserSerializer(many=True, required=False)
rating = CustomDecimalField(required=False, allow_null=True, read_only=True)
last_cooked = serializers.DateTimeField(required=False, allow_null=True, read_only=True)
food_properties = serializers.SerializerMethodField('get_food_properties')
created_by = UserSerializer(read_only=True)
@extend_schema_field(serializers.JSONField)
def get_food_properties(self, obj):
# Skip expensive computation on CREATE - UI doesn't display it after create
# User navigates to view page which triggers GET with full data
# Fixes issue #4356 - N+1 queries causing gunicorn worker timeout
view = self.context.get('view')
if view and getattr(view, 'action', None) == 'create':
return {}
fph = FoodPropertyHelper(obj.space) # initialize with object space since recipes might be viewed anonymously
return fph.calculate_recipe_properties(obj)
class Meta:
model = Recipe
fields = (
'id', 'name', 'description', 'image', 'keywords', 'steps', 'working_time', 'waiting_time', 'created_by', 'created_at', 'updated_at', 'source_url',
'internal', 'show_ingredient_overview', 'nutrition', 'properties', 'food_properties', 'servings', 'file_path', 'servings_text', 'diameter', 'diameter_text', 'rating',
'last_cooked', 'private', 'shared'
)
read_only_fields = ['image', 'created_by', 'created_at', 'food_properties']
def validate(self, data):
above_limit, msg = above_space_limit(self.context['request'].space)
if above_limit:
raise serializers.ValidationError(msg)
return super().validate(data)
def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
class RecipeImageSerializer(WritableNestedModelSerializer):
image = serializers.ImageField(required=False, allow_null=True)
image_url = serializers.CharField(max_length=4096, required=False, allow_null=True)
def create(self, validated_data):
if 'image' in validated_data and not is_file_type_allowed(validated_data['image'].name, image_only=True):
return None
return super().create(validated_data)
def update(self, instance, validated_data):
if 'image' in validated_data and not is_file_type_allowed(validated_data['image'].name, image_only=True):
return None
return super().update(instance, validated_data)
class Meta:
model = Recipe
fields = ['image', 'image_url', ]
class RecipeBatchUpdateSerializer(serializers.Serializer):
recipes = serializers.ListField(child=serializers.IntegerField())
keywords_add = serializers.ListField(child=serializers.IntegerField())
keywords_remove = serializers.ListField(child=serializers.IntegerField())
keywords_set = serializers.ListField(child=serializers.IntegerField())
keywords_remove_all = serializers.BooleanField(default=False)
working_time = serializers.IntegerField(required=False, allow_null=True)
waiting_time = serializers.IntegerField(required=False, allow_null=True)
servings = serializers.IntegerField(required=False, allow_null=True)
servings_text = serializers.CharField(required=False, allow_null=True, allow_blank=True)
private = serializers.BooleanField(required=False, allow_null=True)
shared_add = serializers.ListField(child=serializers.IntegerField())
shared_remove = serializers.ListField(child=serializers.IntegerField())
shared_set = serializers.ListField(child=serializers.IntegerField())
shared_remove_all = serializers.BooleanField(default=False)
show_ingredient_overview = serializers.BooleanField(required=False, allow_null=True)
clear_description = serializers.BooleanField(required=False, allow_null=True)
class FoodBatchUpdateSerializer(serializers.Serializer):
foods = serializers.ListField(child=serializers.IntegerField())
category = serializers.IntegerField(required=False, allow_null=True)
substitute_add = serializers.ListField(child=serializers.IntegerField())
substitute_remove = serializers.ListField(child=serializers.IntegerField())
substitute_set = serializers.ListField(child=serializers.IntegerField())
substitute_remove_all = serializers.BooleanField(default=False)
inherit_fields_add = serializers.ListField(child=serializers.IntegerField())
inherit_fields_remove = serializers.ListField(child=serializers.IntegerField())
inherit_fields_set = serializers.ListField(child=serializers.IntegerField())
inherit_fields_remove_all = serializers.BooleanField(default=False)
child_inherit_fields_add = serializers.ListField(child=serializers.IntegerField())
child_inherit_fields_remove = serializers.ListField(child=serializers.IntegerField())
child_inherit_fields_set = serializers.ListField(child=serializers.IntegerField())
child_inherit_fields_remove_all = serializers.BooleanField(default=False)
shopping_lists_add = serializers.ListField(child=serializers.IntegerField(), required=False)
shopping_lists_remove = serializers.ListField(child=serializers.IntegerField(), required=False)
shopping_lists_set = serializers.ListField(child=serializers.IntegerField(), required=False)
shopping_lists_remove_all = serializers.BooleanField(default=False)
substitute_children = serializers.BooleanField(required=False, allow_null=True)
substitute_siblings = serializers.BooleanField(required=False, allow_null=True)
ignore_shopping = serializers.BooleanField(required=False, allow_null=True)
on_hand = serializers.BooleanField(required=False, allow_null=True)
parent_remove = serializers.BooleanField(required=False, allow_null=True)
parent_set = serializers.IntegerField(required=False, allow_null=True)
class CustomFilterSerializer(SpacedModelSerializer, WritableNestedModelSerializer):
shared = UserSerializer(many=True, required=False)
def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user
return super().create(validated_data)
class Meta:
model = CustomFilter
fields = ('id', 'name', 'search', 'shared', 'created_by')
read_only_fields = ('created_by',)
class RecipeBookSerializer(SpacedModelSerializer, WritableNestedModelSerializer):
created_by = UserSerializer(read_only=True)
shared = UserSerializer(many=True)
filter = CustomFilterSerializer(allow_null=True, required=False)
def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user
return super().create(validated_data)
class Meta:
model = RecipeBook
fields = ('id', 'name', 'description', 'shared', 'created_by', 'filter', 'order')
read_only_fields = ('created_by',)
class RecipeBookEntrySerializer(serializers.ModelSerializer):
book_content = serializers.SerializerMethodField(method_name='get_book_content', read_only=True)
recipe_content = serializers.SerializerMethodField(method_name='get_recipe_content', read_only=True)
@extend_schema_field(RecipeBookSerializer)
def get_book_content(self, obj):
return RecipeBookSerializer(context={'request': self.context['request']}).to_representation(obj.book)
@extend_schema_field(RecipeOverviewSerializer)
def get_recipe_content(self, obj):
return RecipeOverviewSerializer(context={'request': self.context['request']}).to_representation(obj.recipe)
def create(self, validated_data):
book = validated_data['book']
recipe = validated_data['recipe']
if not book.get_owner() == self.context['request'].user and not self.context['request'].user in book.get_shared():
raise NotFound(detail=None, code=None)
obj, created = RecipeBookEntry.objects.get_or_create(book=book, recipe=recipe)
return obj
class Meta:
model = RecipeBookEntry
fields = ('id', 'book', 'book_content', 'recipe', 'recipe_content',)
class MealPlanSerializer(SpacedModelSerializer, WritableNestedModelSerializer):
recipe = RecipeOverviewSerializer(required=False, allow_null=True)
recipe_name = serializers.CharField(source='recipe.name', read_only=True)
meal_type = MealTypeSerializer()
meal_type_name = serializers.CharField(source='meal_type.name', read_only=True) # TODO deprecate once old meal plan was removed
note_markdown = serializers.SerializerMethodField('get_note_markdown')
servings = CustomDecimalField()
shared = UserSerializer(many=True, required=False, allow_null=True)
shopping = serializers.SerializerMethodField('in_shopping')
addshopping = serializers.BooleanField(write_only=True, required=False)
to_date = serializers.DateTimeField(required=False)
@extend_schema_field(str)
def get_note_markdown(self, obj):
return markdown(obj.note)
@extend_schema_field(bool)
def in_shopping(self, obj):
return obj.shoppinglistrecipe_set.count() > 0
@staticmethod
def _apply_default_time(dt, meal_type_obj):
"""Apply default time to a datetime that has no explicit time (midnight local).
Priority: explicit time > meal_type.time > noon fallback.
Returns the datetime unchanged if it already has a non-midnight local time.
"""
local_dt = timezone.localtime(dt)
if local_dt.hour != 0 or local_dt.minute != 0 or local_dt.second != 0:
return local_dt
if meal_type_obj and meal_type_obj.time:
return local_dt.replace(hour=meal_type_obj.time.hour, minute=meal_type_obj.time.minute, second=0)
return local_dt.replace(hour=12, minute=0, second=0)
def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user
meal_type_obj = None
meal_type_data = self.context['request'].data.get('meal_type')
if isinstance(meal_type_data, dict):
meal_type_id = meal_type_data.get('id')
else:
meal_type_id = meal_type_data
if meal_type_id:
meal_type_obj = MealType.objects.filter(pk=meal_type_id, space=self.context['request'].space).first()
validated_data['from_date'] = self._apply_default_time(validated_data['from_date'], meal_type_obj)
if 'to_date' not in validated_data or validated_data['to_date'] is None:
validated_data['to_date'] = validated_data['from_date']
else:
validated_data['to_date'] = self._apply_default_time(validated_data['to_date'], meal_type_obj)
add_to_shopping = False
try:
add_to_shopping = validated_data.pop('addshopping', False)
except KeyError:
pass
mealplan = super().create(validated_data)
if add_to_shopping and self.context['request'].data.get('recipe', None):
SLR = RecipeShoppingEditor(user=validated_data['created_by'], space=validated_data['space'])
SLR.create(mealplan=mealplan, servings=validated_data['servings'])
return mealplan
def update(self, obj, validated_data):
if sr := ShoppingListRecipe.objects.filter(mealplan=obj.id).first():
SLR = RecipeShoppingEditor(user=obj.created_by, space=obj.space, id=sr.id)
SLR.edit(mealplan=obj, servings=validated_data['servings'])
return super().update(obj, validated_data)
class Meta:
model = MealPlan
fields = (
'id', 'title', 'recipe', 'servings', 'note', 'note_markdown',
'from_date', 'to_date', 'meal_type', 'created_by', 'shared', 'recipe_name',
'meal_type_name', 'shopping', 'addshopping'
)
read_only_fields = ('created_by',)
class AutoMealPlanSerializer(serializers.Serializer):
start_date = serializers.DateTimeField()
end_date = serializers.DateTimeField()
meal_type_id = serializers.IntegerField()
keyword_ids = serializers.ListField()
servings = CustomDecimalField()
shared = UserSerializer(many=True, required=False, allow_null=True)
addshopping = serializers.BooleanField()
class ShoppingListRecipeSerializer(serializers.ModelSerializer):
recipe_data = RecipeOverviewSerializer(source='recipe', read_only=True, required=False)
meal_plan_data = MealPlanSerializer(source='mealplan', read_only=True, required=False)
servings = CustomDecimalField()
created_by = UserSerializer(read_only=True)
def create(self, validated_data):
validated_data['space'] = self.context['request'].space
validated_data['created_by'] = self.context['request'].user
return super().create(validated_data)
def update(self, instance, validated_data):
if 'servings' in validated_data and self.context.get('view', None).__class__.__name__ != 'ShoppingListViewSet':
SLR = RecipeShoppingEditor(user=self.context['request'].user, space=self.context['request'].space)
SLR.edit_servings(servings=validated_data['servings'], id=instance.id)
return super().update(instance, validated_data)
class Meta:
model = ShoppingListRecipe
fields = ('id', 'name', 'recipe', 'recipe_data', 'meal_plan_data', 'mealplan', 'servings', 'created_by',)
read_only_fields = ('id', 'created_by',)
class FoodShoppingSerializer(serializers.ModelSerializer):
supermarket_category = SupermarketCategorySerializer(read_only=True)
shopping_lists = ShoppingListSerializer(read_only=True, many=True)
# TODO duplicate code with FoodSerializer, merge into one or use proper function
def create(self, validated_data):
name = validated_data['name'].strip()
if plural_name := validated_data.pop('plural_name', None):
plural_name = plural_name.strip()
if food := Food.objects.filter(Q(name__iexact=name) | Q(plural_name__iexact=name)).first():
return food
space = validated_data.pop('space', self.context['request'].space)
# supermarket category needs to be handled manually as food.get or create does not create nested serializers unlike a super.create of serializer
if 'supermarket_category' in validated_data and validated_data['supermarket_category']:
sm_category = validated_data['supermarket_category']
sc_name = sm_category.pop('name', None)
validated_data['supermarket_category'], sc_created = SupermarketCategory.objects.get_or_create(
name=sc_name,
space=space, defaults=sm_category)
if properties_food_unit := validated_data.pop('properties_food_unit', None):
properties_food_unit = Unit.objects.filter(name=properties_food_unit['name']).first()
obj, created = Food.objects.get_or_create(name=name, plural_name=plural_name, space=space,
properties_food_unit=properties_food_unit,
defaults=validated_data)
return obj
class Meta:
model = Food
fields = ('id', 'name', 'plural_name', 'supermarket_category', 'shopping_lists')
class ShoppingListEntrySerializer(WritableNestedModelSerializer):
food = FoodShoppingSerializer(allow_null=True)
unit = UnitSerializer(allow_null=True, required=False)
shopping_lists = ShoppingListSerializer(many=True, required=False)
list_recipe_data = ShoppingListRecipeSerializer(source='list_recipe', read_only=True)
amount = CustomDecimalField()
created_by = UserSerializer(read_only=True)
completed_at = serializers.DateTimeField(allow_null=True, required=False)
mealplan_id = serializers.IntegerField(required=False, write_only=True,
help_text='If a mealplan id is given try to find existing or create new ShoppingListRecipe with that meal plan and link entry to it')
def get_fields(self, *args, **kwargs):
fields = super().get_fields(*args, **kwargs)
# autosync values are only needed for frequent 'checked' value updating
if self.context['request'] and bool(int(self.context['request'].query_params.get('autosync', False))):
for f in list(set(fields) - set(['id', 'checked', 'updated_at', ])):
del fields[f]
return fields
def run_validation(self, data):
if self.root.instance.__class__.__name__ == 'ShoppingListEntry':
if (
data.get('checked', False)
and self.root.instance
and not self.root.instance.checked
):
# if checked flips from false to true set completed datetime
data['completed_at'] = timezone.now()
elif not data.get('checked', False):
# if not checked set completed to None
data['completed_at'] = None
else:
# otherwise don't write anything
if 'completed_at' in data:
del data['completed_at']
return super().run_validation(data)
def create(self, validated_data):
validated_data['space'] = self.context['request'].space
validated_data['created_by'] = self.context['request'].user
if 'mealplan_id' in validated_data:
if existing_slr := ShoppingListRecipe.objects.filter(mealplan_id=validated_data['mealplan_id'], space=self.context['request'].space).first():
validated_data['list_recipe'] = existing_slr
else:
validated_data['list_recipe'] = ShoppingListRecipe.objects.create(mealplan_id=validated_data['mealplan_id'], space=self.context['request'].space,
created_by=self.context['request'].user)
del validated_data['mealplan_id']
obj = super().create(validated_data)
if self.context['request'].user.userpreference.shopping_update_food_lists and obj.shopping_lists.count() == 0:
obj.shopping_lists.clear()
obj.shopping_lists.set(obj.food.shopping_lists.all())
return obj
def update(self, instance, validated_data):
user = self.context['request'].user
if 'mealplan_id' in validated_data:
del validated_data['mealplan_id']
# update the onhand for food if shopping_add_onhand is True
if user.userpreference.shopping_add_onhand:
if checked := validated_data.get('checked', None):
validated_data['completed_at'] = timezone.now()
instance.food.onhand_users.add(*user.userpreference.shopping_share.all(), user)
elif not checked:
instance.food.onhand_users.remove(*user.userpreference.shopping_share.all(), user)
return super().update(instance, validated_data)
class Meta:
model = ShoppingListEntry
fields = (
'id', 'list_recipe', 'shopping_lists', 'food', 'unit', 'amount', 'order', 'checked', 'ingredient',
'list_recipe_data', 'created_by', 'created_at', 'updated_at', 'completed_at', 'delay_until', 'mealplan_id'
)
read_only_fields = ('id', 'created_by', 'created_at')
class ShoppingListEntrySimpleCreateSerializer(serializers.Serializer):
amount = CustomDecimalField()
unit_id = serializers.IntegerField(allow_null=True)
food_id = serializers.IntegerField(allow_null=True)
ingredient_id = serializers.IntegerField(allow_null=True)
class ShoppingListEntryBulkCreateSerializer(serializers.Serializer):
entries = serializers.ListField(child=ShoppingListEntrySimpleCreateSerializer())
shopping_lists_ids = serializers.ListField(child=serializers.IntegerField(), required=False)
class ShoppingListEntryBulkSerializer(serializers.Serializer):
ids = serializers.ListField()
checked = serializers.BooleanField(required=False, allow_null=True)
timestamp = serializers.DateTimeField(read_only=True, required=False)
shopping_lists_add = serializers.ListField(child=serializers.IntegerField(), required=False)
shopping_lists_remove = serializers.ListField(child=serializers.IntegerField(), required=False)
shopping_lists_set = serializers.ListField(child=serializers.IntegerField(), required=False)
shopping_lists_remove_all = serializers.BooleanField(default=False)
# TODO deprecate
class ShoppingListEntryCheckedSerializer(serializers.ModelSerializer):
class Meta:
model = ShoppingListEntry
fields = ('id', 'checked')
class ShareLinkSerializer(SpacedModelSerializer):
class Meta:
model = ShareLink
fields = '__all__'
class CookLogSerializer(serializers.ModelSerializer):
created_by = UserSerializer(read_only=True)
def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
class Meta:
model = CookLog
fields = ('id', 'recipe', 'servings', 'rating', 'comment', 'created_by', 'created_at', 'updated_at')
read_only_fields = ('id', 'created_by')
class ViewLogSerializer(serializers.ModelSerializer):
def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user
validated_data['space'] = self.context['request'].space
view_log = ViewLog.objects.filter(recipe=validated_data['recipe'], created_by=self.context['request'].user, created_at__gt=(timezone.now() - timezone.timedelta(minutes=5)),
space=self.context['request'].space).first()
if not view_log:
view_log = ViewLog.objects.create(recipe=validated_data['recipe'], created_by=self.context['request'].user, space=self.context['request'].space)
return view_log
class Meta:
model = ViewLog
fields = ('id', 'recipe', 'created_by', 'created_at')
read_only_fields = ('created_by',)
class ImportLogSerializer(serializers.ModelSerializer):
keyword = KeywordSerializer(read_only=True)
def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
class Meta:
model = ImportLog
fields = (
'id', 'type', 'msg', 'running', 'keyword', 'total_recipes', 'imported_recipes', 'created_by', 'created_at')
read_only_fields = ('created_by',)
class ExportLogSerializer(serializers.ModelSerializer):
def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
class Meta:
model = ExportLog
fields = (
'id', 'type', 'msg', 'running', 'total_recipes', 'exported_recipes', 'cache_duration',
'possibly_not_expired',
'created_by', 'created_at')
read_only_fields = ('created_by',)
class AutomationSerializer(serializers.ModelSerializer):
def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
class Meta:
model = Automation
fields = (
'id', 'type', 'name', 'description', 'param_1', 'param_2', 'param_3', 'order', 'disabled', 'created_by',)
read_only_fields = ('created_by',)
class InventoryLocationSerializer(UniqueFieldsMixin, SpacedModelSerializer, WritableNestedModelSerializer):
household = HouseholdSerializer()
def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
class Meta:
model = InventoryLocation
fields = ('id', 'name', 'is_freezer', 'household')
class InventoryEntrySerializer(SpacedModelSerializer, WritableNestedModelSerializer):
inventory_location = InventoryLocationSerializer()
food = FoodSerializer()
unit = UnitSerializer()
label = serializers.SerializerMethodField('get_label')
def get_label(self, obj):
text = f'#{obj.code} - {round(obj.amount, 2)}'
if obj.unit:
text += f' ({obj.unit})'
text += f' {obj.food.name}'
return text
def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user
validated_data['space'] = self.context['request'].space
instance = super().create(validated_data)
if not instance.code:
instance.code = hex(instance.id)[2:].upper()
instance.save()
InventoryLog.objects.create(
space=instance.space,
entry=instance,
booking_type=InventoryLog.B_ADD,
old_amount=0,
new_amount=instance.amount,
old_inventory_location=instance.inventory_location,
new_inventory_location=instance.inventory_location,
)
return instance
def update(self, instance, validated_data):
old_amount = instance.amount
old_inventory_location = instance.inventory_location
instance = super().update(instance, validated_data)
if old_amount != instance.amount or old_inventory_location != instance.inventory_location:
booking_type = InventoryLog.B_MOVE if old_inventory_location != instance.inventory_location else InventoryLog.B_REMOVE
InventoryLog.objects.create(
space=instance.space,
entry=instance,
booking_type=booking_type,
old_amount=old_amount,
new_amount=instance.amount,
old_inventory_location=old_inventory_location,
new_inventory_location=instance.inventory_location,
)
return instance
class Meta:
model = InventoryEntry
fields = (
'id', 'inventory_location', 'sub_location', 'code',
'food', 'unit', 'amount', 'expires', 'note', 'label', 'created_at', 'created_by'
)
read_only_fields = ('id', 'created_at', 'created_by')
class InventoryLogSerializer(SpacedModelSerializer):
old_inventory_location = InventoryLocationSerializer()
new_inventory_location = InventoryLocationSerializer()
entry = InventoryEntrySerializer()
def create(self, validated_data):
raise ValidationError('Cannot create using this endpoint')
def update(self, instance, validated_data):
raise ValidationError('Cannot update using this endpoint')
class Meta:
model = InventoryLog
fields = ('id', 'entry', 'booking_type', 'old_amount', 'new_amount', 'old_inventory_location', 'new_inventory_location', 'note', 'created_at')
class InviteLinkSerializer(WritableNestedModelSerializer):
group = GroupSerializer()
email_sent = serializers.SerializerMethodField()
@extend_schema_field(bool)
def get_email_sent(self, obj):
"""Return whether the invite email was successfully sent."""
return getattr(obj, '_email_sent', False)
def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user
validated_data['space'] = self.context['request'].space
obj = super().create(validated_data)
# Track email status - default to False
obj._email_sent = False
if obj.email and EMAIL_HOST != '':
try:
if InviteLink.objects.filter(space=self.context['request'].space,
created_at__gte=timezone.now() - timedelta(hours=4)).count() < 20:
message = _('Hello') + '!\n\n' + _('You have been invited by ') + escape(
self.context['request'].user.get_user_display_name())
message += _(' to join their Tandoor Recipes space ') + escape(
self.context['request'].space.name) + '.\n\n'
message += _('Click the following link to activate your account: ') + self.context[
'request'].build_absolute_uri(reverse('view_invite', args=[str(obj.uuid)])) + '\n\n'
message += _('If the link does not work use the following code to manually join the space: ') + str(
obj.uuid) + '\n\n'
message += _('The invitation is valid until ') + str(obj.valid_until) + '\n\n'
message += _(
'Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub ') + 'https://github.com/vabene1111/recipes/'
send_mail(
_('Tandoor Recipes Invite'),
message,
None,
[obj.email],
fail_silently=False,
)
obj._email_sent = True
except (SMTPException, BadHeaderError, TimeoutError, OSError) as e:
print(f"Failed to send invite email to {obj.email}: {type(e).__name__}: {e}")
obj._email_sent = False
return obj
class Meta:
model = InviteLink
fields = (
'id', 'uuid', 'email', 'group', 'valid_until', 'used_by', 'reusable', 'internal_note', 'created_by',
'created_at', 'email_sent',)
read_only_fields = ('id', 'uuid', 'used_by', 'created_by', 'created_at', 'email_sent',)
# CORS, REST and Scopes aren't currently working
# Scopes are evaluating before REST has authenticated the user assigning a None space
# I've made the change below to fix the bookmarklet, other serializers likely need a similar/better fix
class BookmarkletImportListSerializer(serializers.ModelSerializer):
def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
class Meta:
model = BookmarkletImport
fields = ('id', 'url', 'created_by', 'created_at')
read_only_fields = ('created_by', 'space')
class BookmarkletImportSerializer(BookmarkletImportListSerializer):
class Meta:
model = BookmarkletImport
fields = ('id', 'url', 'html', 'created_by', 'created_at')
read_only_fields = ('created_by', 'space')
# OAuth / Auth Token related Serializers
class AccessTokenSerializer(serializers.ModelSerializer):
token = serializers.SerializerMethodField('get_token')
def create(self, validated_data):
validated_data['token'] = f'tda_{str(uuid.uuid4()).replace("-", "_")}'
validated_data['user'] = self.context['request'].user
return super().create(validated_data)
@extend_schema_field(str)
def get_token(self, obj):
if (timezone.now() - obj.created).seconds < 15:
return obj.token
if obj.scope == 'bookmarklet' or obj.scope == 'mealplan':
# bookmarklet and mealplan only tokens are always returned because they have very limited access and are needed for the bookmarklet function to work
return obj.token
return f'tda_************_******_***********{obj.token[len(obj.token) - 4:]}'
class Meta:
model = AccessToken
fields = ('id', 'token', 'expires', 'scope', 'created', 'updated')
read_only_fields = ('id', 'token',)
class LocalizationSerializer(serializers.Serializer):
code = serializers.CharField(max_length=8, read_only=True)
language = serializers.CharField(read_only=True)
class Meta:
fields = '__ALL__'
class ServerSettingsSerializer(serializers.Serializer):
# TODO add all other relevant settings including path/url related ones?
shopping_min_autosync_interval = serializers.CharField()
enable_pdf_export = serializers.BooleanField()
disable_external_connectors = serializers.BooleanField()
terms_url = serializers.CharField()
privacy_url = serializers.CharField()
imprint_url = serializers.CharField()
hosted = serializers.BooleanField()
debug = serializers.BooleanField()
version = serializers.CharField()
unauthenticated_theme_from_space = serializers.IntegerField()
force_theme_from_space = serializers.IntegerField()
logo_color_32 = serializers.ImageField(default=None)
logo_color_128 = serializers.CharField(default=None)
logo_color_144 = serializers.CharField(default=None)
logo_color_180 = serializers.CharField(default=None)
logo_color_192 = serializers.CharField(default=None)
logo_color_512 = serializers.CharField(default=None)
logo_color_svg = serializers.CharField(default=None)
custom_space_theme = serializers.CharField(default=None)
nav_logo = serializers.CharField(default=None)
nav_bg_color = serializers.CharField(default=None)
class Meta:
fields = '__ALL__'
read_only_fields = '__ALL__'
class FdcQueryFoodsSerializer(serializers.Serializer):
fdcId = serializers.IntegerField()
description = serializers.CharField()
dataType = serializers.CharField()
class FdcQuerySerializer(serializers.Serializer):
totalHits = serializers.IntegerField()
currentPage = serializers.IntegerField()
totalPages = serializers.IntegerField()
foods = FdcQueryFoodsSerializer(many=True)
class GenericModelReferenceSerializer(serializers.Serializer):
id = serializers.IntegerField()
model = serializers.CharField()
name = serializers.CharField()
# Export/Import Serializers
class KeywordExportSerializer(KeywordSerializer):
class Meta:
model = Keyword
fields = ('name', 'description', 'created_at', 'updated_at')
class NutritionInformationExportSerializer(NutritionInformationSerializer):
class Meta:
model = NutritionInformation
fields = ('carbohydrates', 'fats', 'proteins', 'calories', 'source')
class SupermarketCategoryExportSerializer(SupermarketCategorySerializer):
class Meta:
model = SupermarketCategory
fields = ('name',)
class UnitExportSerializer(UnitSerializer):
class Meta:
model = Unit
fields = ('name', 'plural_name', 'description')
class FoodExportSerializer(FoodSerializer):
supermarket_category = SupermarketCategoryExportSerializer(allow_null=True, required=False)
class Meta:
model = Food
fields = ('name', 'plural_name', 'ignore_shopping', 'supermarket_category',)
class IngredientExportSerializer(WritableNestedModelSerializer):
food = FoodExportSerializer(allow_null=True)
unit = UnitExportSerializer(allow_null=True)
amount = CustomDecimalField()
def create(self, validated_data):
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
class Meta:
model = Ingredient
fields = ('food', 'unit', 'amount', 'note', 'order', 'is_header', 'no_amount', 'always_use_plural_unit',
'always_use_plural_food')
class StepExportSerializer(WritableNestedModelSerializer):
ingredients = IngredientExportSerializer(many=True)
def create(self, validated_data):
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
class Meta:
model = Step
fields = ('name', 'instruction', 'ingredients', 'time', 'order', 'show_as_header', 'show_ingredients_table')
class RecipeExportSerializer(WritableNestedModelSerializer):
nutrition = NutritionInformationSerializer(allow_null=True, required=False)
steps = StepExportSerializer(many=True)
keywords = KeywordExportSerializer(many=True)
class Meta:
model = Recipe
fields = (
'name', 'description', 'keywords', 'steps', 'working_time',
'waiting_time', 'internal', 'nutrition', 'servings', 'servings_text', 'source_url',
)
def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
class RecipeShoppingUpdateSerializer(serializers.ModelSerializer):
list_recipe = serializers.IntegerField(write_only=True, allow_null=True, required=False,
help_text=_("Existing shopping list to update"))
ingredients = serializers.ListField(child=serializers.IntegerField(write_only=True, allow_null=True, required=False, help_text=_(
"List of ingredient IDs from the recipe to add, if not provided all ingredients will be added.")))
servings = serializers.IntegerField(default=1, write_only=True, allow_null=True, required=False, help_text=_(
"Providing a list_recipe ID and servings of 0 will delete that shopping list."))
class Meta:
model = Recipe
fields = ['id', 'list_recipe', 'ingredients', 'servings', ]
class FoodShoppingUpdateSerializer(serializers.ModelSerializer):
amount = serializers.IntegerField(write_only=True, allow_null=True, required=False,
help_text=_("Amount of food to add to the shopping list"))
unit = serializers.IntegerField(write_only=True, allow_null=True, required=False,
help_text=_("ID of unit to use for the shopping list"))
delete = serializers.ChoiceField(choices=['true'], write_only=True, allow_null=True, allow_blank=True,
help_text=_("When set to true will delete all food from active shopping lists."))
class Meta:
model = Recipe
fields = ['id', 'amount', 'unit', 'delete', ]
# non model serializers
class RecipeFromSourceSerializer(serializers.Serializer):
url = serializers.CharField(max_length=4096, required=False, allow_null=True, allow_blank=True)
data = serializers.CharField(required=False, allow_null=True, allow_blank=True)
bookmarklet = serializers.IntegerField(required=False, allow_null=True, )
class SourceImportFoodSerializer(serializers.Serializer):
name = serializers.CharField()
class SourceImportUnitSerializer(serializers.Serializer):
name = serializers.CharField()
class SourceImportIngredientSerializer(serializers.Serializer):
amount = serializers.FloatField()
food = SourceImportFoodSerializer()
unit = SourceImportUnitSerializer()
note = serializers.CharField(required=False)
original_text = serializers.CharField()
class SourceImportStepSerializer(serializers.Serializer):
instruction = serializers.CharField()
ingredients = SourceImportIngredientSerializer(many=True)
show_ingredients_table = serializers.BooleanField(default=True)
class SourceImportKeywordSerializer(serializers.Serializer):
id = serializers.IntegerField(allow_null=True)
label = serializers.CharField()
name = serializers.CharField()
import_keyword = serializers.BooleanField(default=True)
class SourceImportPropertyTypeSerializer(serializers.Serializer):
id = serializers.IntegerField()
name = serializers.CharField()
class SourceImportPropertySerializer(serializers.Serializer):
property_type = SourceImportPropertyTypeSerializer(many=False)
property_amount = serializers.FloatField()
class SourceImportRecipeSerializer(serializers.Serializer):
steps = SourceImportStepSerializer(many=True)
internal = serializers.BooleanField(default=True)
source_url = serializers.URLField()
name = serializers.CharField()
description = serializers.CharField(default=None)
servings = serializers.IntegerField(default=1)
servings_text = serializers.CharField(default='')
working_time = serializers.IntegerField(default=0)
waiting_time = serializers.IntegerField(default=0)
image_url = serializers.URLField(default=None)
keywords = SourceImportKeywordSerializer(many=True, default=[])
properties = serializers.ListField(child=SourceImportPropertySerializer(), default=[])
class SourceImportDuplicateSerializer(serializers.Serializer):
id = serializers.IntegerField()
name = serializers.CharField()
class RecipeFromSourceResponseSerializer(serializers.Serializer):
recipe = SourceImportRecipeSerializer(default=None)
recipe_id = serializers.IntegerField(default=None)
images = serializers.ListField(child=serializers.CharField(), default=[], allow_null=False)
error = serializers.BooleanField(default=False)
msg = serializers.CharField(max_length=1024, default='')
duplicates = serializers.ListField(child=SourceImportDuplicateSerializer(), default=[], allow_null=False)
class AiImportSerializer(serializers.Serializer):
ai_provider_id = serializers.IntegerField()
file = serializers.FileField(allow_null=True)
text = serializers.CharField(allow_null=True, allow_blank=True)
recipe_id = serializers.CharField(allow_null=True, allow_blank=True)
class ExportRequestSerializer(serializers.Serializer):
type = serializers.CharField()
all = serializers.BooleanField(default=False)
recipes = RecipeSimpleSerializer(many=True, default=[])
custom_filter = CustomFilterSerializer(many=False, default=None, allow_null=True)
class ImportOpenDataSerializer(serializers.Serializer):
selected_version = serializers.CharField()
selected_datatypes = serializers.ListField(child=serializers.CharField())
update_existing = serializers.BooleanField(default=True)
use_metric = serializers.BooleanField(default=True)
class ImportOpenDataResponseDetailSerializer(serializers.Serializer):
total_created = serializers.IntegerField(default=0)
total_updated = serializers.IntegerField(default=0)
total_untouched = serializers.IntegerField(default=0)
total_errored = serializers.IntegerField(default=0)
class ImportOpenDataResponseSerializer(serializers.Serializer):
food = ImportOpenDataResponseDetailSerializer(required=False)
unit = ImportOpenDataResponseDetailSerializer(required=False)
category = ImportOpenDataResponseDetailSerializer(required=False)
property = ImportOpenDataResponseDetailSerializer(required=False)
store = ImportOpenDataResponseDetailSerializer(required=False)
conversion = ImportOpenDataResponseDetailSerializer(required=False)
class ImportOpenDataVersionMetaDataSerializer(serializers.Serializer):
food = serializers.IntegerField()
unit = serializers.IntegerField()
category = serializers.IntegerField()
property = serializers.IntegerField()
store = serializers.IntegerField()
conversion = serializers.IntegerField()
class ImportOpenDataMetaDataSerializer(serializers.Serializer):
versions = serializers.ListField(child=serializers.CharField())
datatypes = serializers.ListField(child=serializers.CharField())
base = ImportOpenDataVersionMetaDataSerializer()
cs = ImportOpenDataVersionMetaDataSerializer()
da = ImportOpenDataVersionMetaDataSerializer()
de = ImportOpenDataVersionMetaDataSerializer()
el = ImportOpenDataVersionMetaDataSerializer()
en = ImportOpenDataVersionMetaDataSerializer()
es = ImportOpenDataVersionMetaDataSerializer()
fr = ImportOpenDataVersionMetaDataSerializer()
hu = ImportOpenDataVersionMetaDataSerializer()
it = ImportOpenDataVersionMetaDataSerializer()
nb_NO = ImportOpenDataVersionMetaDataSerializer()
nl = ImportOpenDataVersionMetaDataSerializer()
pl = ImportOpenDataVersionMetaDataSerializer()
pt = ImportOpenDataVersionMetaDataSerializer()
pt_BR = ImportOpenDataVersionMetaDataSerializer()
sk = ImportOpenDataVersionMetaDataSerializer()
sl = ImportOpenDataVersionMetaDataSerializer()
zh_Hans = ImportOpenDataVersionMetaDataSerializer()
class IngredientParserRequestSerializer(serializers.Serializer):
ingredient = serializers.CharField(required=False)
ingredients = serializers.ListField(child=serializers.CharField(allow_blank=True), required=False)
class IngredientParserResponseSerializer(serializers.Serializer):
ingredient = IngredientSimpleSerializer(many=False, allow_null=True)
ingredients = IngredientSimpleSerializer(many=True)
================================================
FILE: cookbook/signals.py
================================================
from functools import wraps
from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.postgres.search import SearchVector
from django.core.cache import caches
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils import translation
from django_scopes import scope, scopes_disabled
from cookbook.helper.cache_helper import CacheHelper
from cookbook.helper.shopping_helper import RecipeShoppingEditor
from cookbook.helper.unit_conversion_helper import UnitConversionHelper
from cookbook.managers import DICTIONARY
from cookbook.models import (Food, MealPlan, PropertyType, Recipe, SearchFields, SearchPreference,
Step, Unit, UserPreference)
SQLITE = True
if settings.DATABASES['default']['ENGINE'] == 'django.db.backends.postgresql':
SQLITE = False
# wraps a signal with the ability to set 'skip_signal' to avoid creating recursive signals
def skip_signal(signal_func):
@wraps(signal_func)
def _decorator(sender, instance, **kwargs):
if not instance:
return None
if hasattr(instance, 'skip_signal'):
return None
return signal_func(sender, instance, **kwargs)
return _decorator
@receiver(post_save, sender=User)
def create_user_preference(sender, instance=None, created=False, **kwargs):
if created:
with scopes_disabled():
UserPreference.objects.get_or_create(user=instance)
@receiver(post_save, sender=SearchPreference)
def create_search_preference(sender, instance=None, created=False, **kwargs):
if created:
with scopes_disabled():
instance.unaccent.add(SearchFields.objects.get(name='Name'))
instance.icontains.add(SearchFields.objects.get(name='Name'))
instance.trigram.add(SearchFields.objects.get(name='Name'))
@receiver(post_save, sender=Recipe)
@skip_signal
def update_recipe_search_vector(sender, instance=None, created=False, **kwargs):
if SQLITE:
return
language = DICTIONARY.get(translation.get_language(), 'simple')
# these indexed fields are space wide, reading user preferences would lead to inconsistent behavior
instance.name_search_vector = SearchVector('name__unaccent', weight='A', config=language)
instance.desc_search_vector = SearchVector('description__unaccent', weight='C', config=language)
try:
instance.skip_signal = True
instance.save()
finally:
del instance.skip_signal
@receiver(post_save, sender=Step)
@skip_signal
def update_step_search_vector(sender, instance=None, created=False, **kwargs):
if SQLITE:
return
language = DICTIONARY.get(translation.get_language(), 'simple')
instance.search_vector = SearchVector('instruction__unaccent', weight='B', config=language)
try:
instance.skip_signal = True
instance.save()
finally:
del instance.skip_signal
@receiver(post_save, sender=Food)
@skip_signal
def update_food_inheritance(sender, instance=None, created=False, **kwargs):
if not instance:
return
inherit = instance.inherit_fields.all()
# nothing to apply from parent and nothing to apply to children
if (not instance.parent or inherit.count() == 0) and instance.numchild == 0:
return
inherit = inherit.values_list('field', flat=True)
# apply changes from parent to instance for each inherited field
if instance.parent and inherit.count() > 0:
parent = instance.get_parent()
for field in ['ignore_shopping', 'substitute_children', 'substitute_siblings']:
if field in inherit:
setattr(instance, field, getattr(parent, field, None))
# if supermarket_category is not set, do not cascade - if this becomes non-intuitive can change
if 'supermarket_category' in inherit and parent.supermarket_category:
instance.supermarket_category = parent.supermarket_category
try:
instance.skip_signal = True
instance.save()
finally:
del instance.skip_signal
# apply changes to direct children - depend on save signals for those objects to cascade inheritance down
for child in instance.get_children().filter(inherit_fields__in=Food.inheritable_fields):
# set inherited field values
for field in (inherit_fields := ['ignore_shopping', 'substitute_children', 'substitute_siblings']):
if field in instance.inherit_fields.values_list('field', flat=True):
setattr(child, field, getattr(instance, field, None))
# don't cascade empty supermarket category
if instance.supermarket_category and 'supermarket_category' in inherit_fields:
setattr(child, 'supermarket_category', getattr(instance, 'supermarket_category', None))
child.save()
@receiver(post_save, sender=Unit)
def clear_unit_cache(sender, instance=None, created=False, **kwargs):
if instance:
caches['default'].delete(CacheHelper(instance.space).BASE_UNITS_CACHE_KEY)
# Also clear class-level cache used by UnitConversionHelper
UnitConversionHelper._base_units_cache.pop(instance.space.id, None)
@receiver(post_save, sender=PropertyType)
def clear_property_type_cache(sender, instance=None, created=False, **kwargs):
if instance:
caches['default'].delete(CacheHelper(instance.space).PROPERTY_TYPE_CACHE_KEY)
================================================
FILE: cookbook/static/custom/css/markdown_blockquote.css
================================================
/* css classes needed to render markdown blockquotes */
blockquote {
background: #f9f9f9;
border-left: 4px solid #ccc;
margin: 1.5em 10px;
padding: .5em 10px;
quotes: none;
}
blockquote:before {
color: #ccc;
content: open-quote;
font-size: 4em;
line-height: .1em;
margin-right: .25em;
vertical-align: -.4em;
}
blockquote p {
display: inline;
}
================================================
FILE: cookbook/static/pdfjs/LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
================================================
FILE: cookbook/static/pdfjs/web/cmaps/CNS2-V.bcmap
================================================
RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSECNS2-H
================================================
FILE: cookbook/static/pdfjs/web/cmaps/ETenms-B5-H.bcmap
================================================
RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE ETen-B5-H` ^
================================================
FILE: cookbook/static/pdfjs/web/cmaps/GB-H.bcmap
================================================
RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE!!]aX!!]`21> pz$]"Rd-U7*
4%+ Z {/%<9Kb1]."`],"]
"]h"]F"]$"]"]`"]>"]"]z"]X"]6"]"]r"]P"]."]"]j"]H"]&"]"]b"]@"]"]|"]Z"]8"]"]t"]R"]0"]"]l"]J"]("]"]d"]B"] "X~']W"]5"]"]q"]O"]-"]"]i"]G"]%"]"]a"]?"]"]{"]Y"]7"]"]s"]Q"]/"]
"]k"]I"]'"]"]c"]A"]"]}"]["]9
================================================
FILE: cookbook/static/pdfjs/web/cmaps/LICENSE
================================================
%%Copyright: -----------------------------------------------------------
%%Copyright: Copyright 1990-2009 Adobe Systems Incorporated.
%%Copyright: All rights reserved.
%%Copyright:
%%Copyright: Redistribution and use in source and binary forms, with or
%%Copyright: without modification, are permitted provided that the
%%Copyright: following conditions are met:
%%Copyright:
%%Copyright: Redistributions of source code must retain the above
%%Copyright: copyright notice, this list of conditions and the following
%%Copyright: disclaimer.
%%Copyright:
%%Copyright: Redistributions in binary form must reproduce the above
%%Copyright: copyright notice, this list of conditions and the following
%%Copyright: disclaimer in the documentation and/or other materials
%%Copyright: provided with the distribution.
%%Copyright:
%%Copyright: Neither the name of Adobe Systems Incorporated nor the names
%%Copyright: of its contributors may be used to endorse or promote
%%Copyright: products derived from this software without specific prior
%%Copyright: written permission.
%%Copyright:
%%Copyright: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
%%Copyright: CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
%%Copyright: INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
%%Copyright: MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
%%Copyright: DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
%%Copyright: CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
%%Copyright: SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
%%Copyright: NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
%%Copyright: LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
%%Copyright: HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
%%Copyright: CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
%%Copyright: OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
%%Copyright: SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
%%Copyright: -----------------------------------------------------------
================================================
FILE: cookbook/static/pdfjs/web/debugger.css
================================================
/* Copyright 2014 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
:root {
--panel-width: 300px;
}
#PDFBug,
#PDFBug :is(input, button, select) {
font: message-box;
}
#PDFBug {
background-color: rgb(255 255 255);
border: 1px solid rgb(102 102 102);
position: fixed;
top: 32px;
right: 0;
bottom: 0;
font-size: 10px;
padding: 0;
width: var(--panel-width);
}
#PDFBug .controls {
background: rgb(238 238 238);
border-bottom: 1px solid rgb(102 102 102);
padding: 3px;
}
#PDFBug .panels {
inset: 27px 0 0;
overflow: auto;
position: absolute;
}
#PDFBug .panels > div {
padding: 5px;
}
#PDFBug button.active {
font-weight: bold;
}
.debuggerShowText,
.debuggerHideText:hover {
background-color: rgb(255 255 0 / 0.25);
}
#PDFBug .stats {
font-family: courier;
font-size: 10px;
white-space: pre;
}
#PDFBug .stats .title {
font-weight: bold;
}
#PDFBug table {
font-size: 10px;
white-space: pre;
}
#PDFBug table.showText {
border-collapse: collapse;
text-align: center;
}
#PDFBug table.showText,
#PDFBug table.showText :is(tr, td) {
border: 1px solid black;
padding: 1px;
}
#PDFBug table.showText td.advance {
color: grey;
}
#viewer.textLayer-visible .textLayer {
opacity: 1;
}
#viewer.textLayer-visible .canvasWrapper {
background-color: rgb(128 255 128);
}
#viewer.textLayer-visible .canvasWrapper canvas {
mix-blend-mode: screen;
}
#viewer.textLayer-visible .textLayer span {
background-color: rgb(255 255 0 / 0.1);
color: rgb(0 0 0);
border: solid 1px rgb(255 0 0 / 0.5);
box-sizing: border-box;
}
#viewer.textLayer-visible .textLayer span[aria-owns] {
background-color: rgb(255 0 0 / 0.3);
}
#viewer.textLayer-hover .textLayer span:hover {
background-color: rgb(255 255 255);
color: rgb(0 0 0);
}
#viewer.textLayer-shadow .textLayer span {
background-color: rgb(255 255 255 / 0.6);
color: rgb(0 0 0);
}
================================================
FILE: cookbook/static/pdfjs/web/debugger.mjs
================================================
/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const { OPS } = globalThis.pdfjsLib || (await import("pdfjs-lib"));
const opMap = Object.create(null);
for (const key in OPS) {
opMap[OPS[key]] = key;
}
const FontInspector = (function FontInspectorClosure() {
let fonts;
let active = false;
const fontAttribute = "data-font-name";
function removeSelection() {
const divs = document.querySelectorAll(`span[${fontAttribute}]`);
for (const div of divs) {
div.className = "";
}
}
function resetSelection() {
const divs = document.querySelectorAll(`span[${fontAttribute}]`);
for (const div of divs) {
div.className = "debuggerHideText";
}
}
function selectFont(fontName, show) {
const divs = document.querySelectorAll(
`span[${fontAttribute}=${fontName}]`
);
for (const div of divs) {
div.className = show ? "debuggerShowText" : "debuggerHideText";
}
}
function textLayerClick(e) {
if (
!e.target.dataset.fontName ||
e.target.tagName.toUpperCase() !== "SPAN"
) {
return;
}
const fontName = e.target.dataset.fontName;
const selects = document.getElementsByTagName("input");
for (const select of selects) {
if (select.dataset.fontName !== fontName) {
continue;
}
select.checked = !select.checked;
selectFont(fontName, select.checked);
select.scrollIntoView();
}
}
return {
// Properties/functions needed by PDFBug.
id: "FontInspector",
name: "Font Inspector",
panel: null,
manager: null,
init() {
const panel = this.panel;
const tmp = document.createElement("button");
tmp.addEventListener("click", resetSelection);
tmp.textContent = "Refresh";
panel.append(tmp);
fonts = document.createElement("div");
panel.append(fonts);
},
cleanup() {
fonts.textContent = "";
},
enabled: false,
get active() {
return active;
},
set active(value) {
active = value;
if (active) {
document.body.addEventListener("click", textLayerClick, true);
resetSelection();
} else {
document.body.removeEventListener("click", textLayerClick, true);
removeSelection();
}
},
// FontInspector specific functions.
fontAdded(fontObj, url) {
function properties(obj, list) {
const moreInfo = document.createElement("table");
for (const entry of list) {
const tr = document.createElement("tr");
const td1 = document.createElement("td");
td1.textContent = entry;
tr.append(td1);
const td2 = document.createElement("td");
td2.textContent = obj[entry].toString();
tr.append(td2);
moreInfo.append(tr);
}
return moreInfo;
}
const moreInfo = fontObj.css
? properties(fontObj, ["baseFontName"])
: properties(fontObj, ["name", "type"]);
const fontName = fontObj.loadedName;
const font = document.createElement("div");
const name = document.createElement("span");
name.textContent = fontName;
let download;
if (!fontObj.css) {
download = document.createElement("a");
if (url) {
url = /url\(['"]?([^)"']+)/.exec(url);
download.href = url[1];
} else if (fontObj.data) {
download.href = URL.createObjectURL(
new Blob([fontObj.data], { type: fontObj.mimetype })
);
}
download.textContent = "Download";
}
const logIt = document.createElement("a");
logIt.href = "";
logIt.textContent = "Log";
logIt.addEventListener("click", function (event) {
event.preventDefault();
console.log(fontObj);
});
const select = document.createElement("input");
select.setAttribute("type", "checkbox");
select.dataset.fontName = fontName;
select.addEventListener("click", function () {
selectFont(fontName, select.checked);
});
if (download) {
font.append(select, name, " ", download, " ", logIt, moreInfo);
} else {
font.append(select, name, " ", logIt, moreInfo);
}
fonts.append(font);
// Somewhat of a hack, should probably add a hook for when the text layer
// is done rendering.
setTimeout(() => {
if (this.active) {
resetSelection();
}
}, 2000);
},
};
})();
// Manages all the page steppers.
const StepperManager = (function StepperManagerClosure() {
let steppers = [];
let stepperDiv = null;
let stepperControls = null;
let stepperChooser = null;
let breakPoints = Object.create(null);
return {
// Properties/functions needed by PDFBug.
id: "Stepper",
name: "Stepper",
panel: null,
manager: null,
init() {
const self = this;
stepperControls = document.createElement("div");
stepperChooser = document.createElement("select");
stepperChooser.addEventListener("change", function (event) {
self.selectStepper(this.value);
});
stepperControls.append(stepperChooser);
stepperDiv = document.createElement("div");
this.panel.append(stepperControls, stepperDiv);
if (sessionStorage.getItem("pdfjsBreakPoints")) {
breakPoints = JSON.parse(sessionStorage.getItem("pdfjsBreakPoints"));
}
},
cleanup() {
stepperChooser.textContent = "";
stepperDiv.textContent = "";
steppers = [];
},
enabled: false,
active: false,
// Stepper specific functions.
create(pageIndex) {
const debug = document.createElement("div");
debug.id = "stepper" + pageIndex;
debug.hidden = true;
debug.className = "stepper";
stepperDiv.append(debug);
const b = document.createElement("option");
b.textContent = "Page " + (pageIndex + 1);
b.value = pageIndex;
stepperChooser.append(b);
const initBreakPoints = breakPoints[pageIndex] || [];
const stepper = new Stepper(debug, pageIndex, initBreakPoints);
steppers.push(stepper);
if (steppers.length === 1) {
this.selectStepper(pageIndex, false);
}
return stepper;
},
selectStepper(pageIndex, selectPanel) {
pageIndex |= 0;
if (selectPanel) {
this.manager.selectPanel(this);
}
for (const stepper of steppers) {
stepper.panel.hidden = stepper.pageIndex !== pageIndex;
}
for (const option of stepperChooser.options) {
option.selected = (option.value | 0) === pageIndex;
}
},
saveBreakPoints(pageIndex, bps) {
breakPoints[pageIndex] = bps;
sessionStorage.setItem("pdfjsBreakPoints", JSON.stringify(breakPoints));
},
};
})();
// The stepper for each page's operatorList.
class Stepper {
// Shorter way to create element and optionally set textContent.
#c(tag, textContent) {
const d = document.createElement(tag);
if (textContent) {
d.textContent = textContent;
}
return d;
}
#simplifyArgs(args) {
if (typeof args === "string") {
const MAX_STRING_LENGTH = 75;
return args.length <= MAX_STRING_LENGTH
? args
: args.substring(0, MAX_STRING_LENGTH) + "...";
}
if (typeof args !== "object" || args === null) {
return args;
}
if ("length" in args) {
// array
const MAX_ITEMS = 10,
simpleArgs = [];
let i, ii;
for (i = 0, ii = Math.min(MAX_ITEMS, args.length); i < ii; i++) {
simpleArgs.push(this.#simplifyArgs(args[i]));
}
if (i < args.length) {
simpleArgs.push("...");
}
return simpleArgs;
}
const simpleObj = {};
for (const key in args) {
simpleObj[key] = this.#simplifyArgs(args[key]);
}
return simpleObj;
}
constructor(panel, pageIndex, initialBreakPoints) {
this.panel = panel;
this.breakPoint = 0;
this.nextBreakPoint = null;
this.pageIndex = pageIndex;
this.breakPoints = initialBreakPoints;
this.currentIdx = -1;
this.operatorListIdx = 0;
this.indentLevel = 0;
}
init(operatorList) {
const panel = this.panel;
const content = this.#c("div", "c=continue, s=step");
const table = this.#c("table");
content.append(table);
table.cellSpacing = 0;
const headerRow = this.#c("tr");
table.append(headerRow);
headerRow.append(
this.#c("th", "Break"),
this.#c("th", "Idx"),
this.#c("th", "fn"),
this.#c("th", "args")
);
panel.append(content);
this.table = table;
this.updateOperatorList(operatorList);
}
updateOperatorList(operatorList) {
const self = this;
function cboxOnClick() {
const x = +this.dataset.idx;
if (this.checked) {
self.breakPoints.push(x);
} else {
self.breakPoints.splice(self.breakPoints.indexOf(x), 1);
}
StepperManager.saveBreakPoints(self.pageIndex, self.breakPoints);
}
const MAX_OPERATORS_COUNT = 15000;
if (this.operatorListIdx > MAX_OPERATORS_COUNT) {
return;
}
const chunk = document.createDocumentFragment();
const operatorsToDisplay = Math.min(
MAX_OPERATORS_COUNT,
operatorList.fnArray.length
);
for (let i = this.operatorListIdx; i < operatorsToDisplay; i++) {
const line = this.#c("tr");
line.className = "line";
line.dataset.idx = i;
chunk.append(line);
const checked = this.breakPoints.includes(i);
const args = operatorList.argsArray[i] || [];
const breakCell = this.#c("td");
const cbox = this.#c("input");
cbox.type = "checkbox";
cbox.className = "points";
cbox.checked = checked;
cbox.dataset.idx = i;
cbox.onclick = cboxOnClick;
breakCell.append(cbox);
line.append(breakCell, this.#c("td", i.toString()));
const fn = opMap[operatorList.fnArray[i]];
let decArgs = args;
if (fn === "showText") {
const glyphs = args[0];
const charCodeRow = this.#c("tr");
const fontCharRow = this.#c("tr");
const unicodeRow = this.#c("tr");
for (const glyph of glyphs) {
if (typeof glyph === "object" && glyph !== null) {
charCodeRow.append(this.#c("td", glyph.originalCharCode));
fontCharRow.append(this.#c("td", glyph.fontChar));
unicodeRow.append(this.#c("td", glyph.unicode));
} else {
// null or number
const advanceEl = this.#c("td", glyph);
advanceEl.classList.add("advance");
charCodeRow.append(advanceEl);
fontCharRow.append(this.#c("td"));
unicodeRow.append(this.#c("td"));
}
}
decArgs = this.#c("td");
const table = this.#c("table");
table.classList.add("showText");
decArgs.append(table);
table.append(charCodeRow, fontCharRow, unicodeRow);
} else if (fn === "restore" && this.indentLevel > 0) {
this.indentLevel--;
}
line.append(this.#c("td", " ".repeat(this.indentLevel * 2) + fn));
if (fn === "save") {
this.indentLevel++;
}
if (decArgs instanceof HTMLElement) {
line.append(decArgs);
} else {
line.append(this.#c("td", JSON.stringify(this.#simplifyArgs(decArgs))));
}
}
if (operatorsToDisplay < operatorList.fnArray.length) {
const lastCell = this.#c("td", "...");
lastCell.colspan = 4;
chunk.append(lastCell);
}
this.operatorListIdx = operatorList.fnArray.length;
this.table.append(chunk);
}
getNextBreakPoint() {
this.breakPoints.sort((a, b) => a - b);
for (const breakPoint of this.breakPoints) {
if (breakPoint > this.currentIdx) {
return breakPoint;
}
}
return null;
}
breakIt(idx, callback) {
StepperManager.selectStepper(this.pageIndex, true);
this.currentIdx = idx;
const listener = evt => {
switch (evt.keyCode) {
case 83: // step
document.removeEventListener("keydown", listener);
this.nextBreakPoint = this.currentIdx + 1;
this.goTo(-1);
callback();
break;
case 67: // continue
document.removeEventListener("keydown", listener);
this.nextBreakPoint = this.getNextBreakPoint();
this.goTo(-1);
callback();
break;
}
};
document.addEventListener("keydown", listener);
this.goTo(idx);
}
goTo(idx) {
const allRows = this.panel.getElementsByClassName("line");
for (const row of allRows) {
if ((row.dataset.idx | 0) === idx) {
row.style.backgroundColor = "rgb(251,250,207)";
row.scrollIntoView();
} else {
row.style.backgroundColor = null;
}
}
}
}
const Stats = (function Stats() {
let stats = [];
function clear(node) {
node.textContent = ""; // Remove any `node` contents from the DOM.
}
function getStatIndex(pageNumber) {
for (const [i, stat] of stats.entries()) {
if (stat.pageNumber === pageNumber) {
return i;
}
}
return false;
}
return {
// Properties/functions needed by PDFBug.
id: "Stats",
name: "Stats",
panel: null,
manager: null,
init() {},
enabled: false,
active: false,
// Stats specific functions.
add(pageNumber, stat) {
if (!stat) {
return;
}
const statsIndex = getStatIndex(pageNumber);
if (statsIndex !== false) {
stats[statsIndex].div.remove();
stats.splice(statsIndex, 1);
}
const wrapper = document.createElement("div");
wrapper.className = "stats";
const title = document.createElement("div");
title.className = "title";
title.textContent = "Page: " + pageNumber;
const statsDiv = document.createElement("div");
statsDiv.textContent = stat.toString();
wrapper.append(title, statsDiv);
stats.push({ pageNumber, div: wrapper });
stats.sort((a, b) => a.pageNumber - b.pageNumber);
clear(this.panel);
for (const entry of stats) {
this.panel.append(entry.div);
}
},
cleanup() {
stats = [];
clear(this.panel);
},
};
})();
// Manages all the debugging tools.
class PDFBug {
static #buttons = [];
static #activePanel = null;
static tools = [FontInspector, StepperManager, Stats];
static enable(ids) {
const all = ids.length === 1 && ids[0] === "all";
const tools = this.tools;
for (const tool of tools) {
if (all || ids.includes(tool.id)) {
tool.enabled = true;
}
}
if (!all) {
// Sort the tools by the order they are enabled.
tools.sort(function (a, b) {
let indexA = ids.indexOf(a.id);
indexA = indexA < 0 ? tools.length : indexA;
let indexB = ids.indexOf(b.id);
indexB = indexB < 0 ? tools.length : indexB;
return indexA - indexB;
});
}
}
static init(container, ids) {
this.loadCSS();
this.enable(ids);
/*
* Basic Layout:
* PDFBug
* Controls
* Panels
* Panel
* Panel
* ...
*/
const ui = document.createElement("div");
ui.id = "PDFBug";
const controls = document.createElement("div");
controls.setAttribute("class", "controls");
ui.append(controls);
const panels = document.createElement("div");
panels.setAttribute("class", "panels");
ui.append(panels);
container.append(ui);
container.style.right = "var(--panel-width)";
// Initialize all the debugging tools.
for (const tool of this.tools) {
const panel = document.createElement("div");
const panelButton = document.createElement("button");
panelButton.textContent = tool.name;
panelButton.addEventListener("click", event => {
event.preventDefault();
this.selectPanel(tool);
});
controls.append(panelButton);
panels.append(panel);
tool.panel = panel;
tool.manager = this;
if (tool.enabled) {
tool.init();
} else {
panel.textContent =
`${tool.name} is disabled. To enable add "${tool.id}" to ` +
"the pdfBug parameter and refresh (separate multiple by commas).";
}
this.#buttons.push(panelButton);
}
this.selectPanel(0);
}
static loadCSS() {
const { url } = import.meta;
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = url.replace(/\.mjs$/, ".css");
document.head.append(link);
}
static cleanup() {
for (const tool of this.tools) {
if (tool.enabled) {
tool.cleanup();
}
}
}
static selectPanel(index) {
if (typeof index !== "number") {
index = this.tools.indexOf(index);
}
if (index === this.#activePanel) {
return;
}
this.#activePanel = index;
for (const [j, tool] of this.tools.entries()) {
const isActive = j === index;
this.#buttons[j].classList.toggle("active", isActive);
tool.active = isActive;
tool.panel.hidden = !isActive;
}
}
}
globalThis.FontInspector = FontInspector;
globalThis.StepperManager = StepperManager;
globalThis.Stats = Stats;
export { PDFBug };
================================================
FILE: cookbook/static/pdfjs/web/iccs/LICENSE
================================================
CC0 1.0 Universal
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator and
subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for the
purpose of contributing to a commons of creative, cultural and scientific
works ("Commons") that the public can reliably and without fear of later
claims of infringement build upon, modify, incorporate in other works, reuse
and redistribute as freely as possible in any form whatsoever and for any
purposes, including without limitation commercial purposes. These owners may
contribute to the Commons to promote the ideal of a free culture and the
further production of creative, cultural and scientific works, or to gain
reputation or greater distribution for their Work in part through the use and
efforts of others.
For these and/or other purposes and motivations, and without any expectation
of additional consideration or compensation, the person associating CC0 with a
Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
and publicly distribute the Work under its terms, with knowledge of his or her
Copyright and Related Rights in the Work and the meaning and intended legal
effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not limited
to, the following:
i. the right to reproduce, adapt, distribute, perform, display, communicate,
and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or likeness
depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data in
a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation thereof,
including any amended or successor version of such directive); and
vii. other similar, equivalent or corresponding rights throughout the world
based on applicable law or treaty, and any national implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention of,
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
and Related Rights and associated claims and causes of action, whether now
known or unknown (including existing as well as future claims and causes of
action), in the Work (i) in all territories worldwide, (ii) for the maximum
duration provided by applicable law or treaty (including future time
extensions), (iii) in any current or future medium and for any number of
copies, and (iv) for any purpose whatsoever, including without limitation
commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
the Waiver for the benefit of each member of the public at large and to the
detriment of Affirmer's heirs and successors, fully intending that such Waiver
shall not be subject to revocation, rescission, cancellation, termination, or
any other legal or equitable action to disrupt the quiet enjoyment of the Work
by the public as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason be
judged legally invalid or ineffective under applicable law, then the Waiver
shall be preserved to the maximum extent permitted taking into account
Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
is so judged Affirmer hereby grants to each affected person a royalty-free,
non transferable, non sublicensable, non exclusive, irrevocable and
unconditional license to exercise Affirmer's Copyright and Related Rights in
the Work (i) in all territories worldwide, (ii) for the maximum duration
provided by applicable law or treaty (including future time extensions), (iii)
in any current or future medium and for any number of copies, and (iv) for any
purpose whatsoever, including without limitation commercial, advertising or
promotional purposes (the "License"). The License shall be deemed effective as
of the date CC0 was applied by Affirmer to the Work. Should any part of the
License for any reason be judged legally invalid or ineffective under
applicable law, such partial invalidity or ineffectiveness shall not
invalidate the remainder of the License, and in such case Affirmer hereby
affirms that he or she will not (i) exercise any of his or her remaining
Copyright and Related Rights in the Work or (ii) assert any associated claims
and causes of action with respect to the Work, in either case contrary to
Affirmer's express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or warranties
of any kind concerning the Work, express, implied, statutory or otherwise,
including without limitation warranties of title, merchantability, fitness
for a particular purpose, non infringement, or the absence of latent or
other defects, accuracy, or the present or absence of errors, whether or not
discoverable, all to the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without limitation
any person's Copyright and Related Rights in the Work. Further, Affirmer
disclaims responsibility for obtaining any necessary consents, permissions
or other rights required for any use of the Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to this
CC0 or use of the Work.
For more information, please see
http://creativecommons.org/publicdomain/zero/1.0/
================================================
FILE: cookbook/static/pdfjs/web/locale/ach/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Pot buk mukato
pdfjs-previous-button-label = Mukato
pdfjs-next-button =
.title = Pot buk malubo
pdfjs-next-button-label = Malubo
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Pot buk
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = pi { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } me { $pagesCount })
pdfjs-zoom-out-button =
.title = Jwik Matidi
pdfjs-zoom-out-button-label = Jwik Matidi
pdfjs-zoom-in-button =
.title = Kwot Madit
pdfjs-zoom-in-button-label = Kwot Madit
pdfjs-zoom-select =
.title = Kwoti
pdfjs-presentation-mode-button =
.title = Lokke i kit me tyer
pdfjs-presentation-mode-button-label = Kit me tyer
pdfjs-open-file-button =
.title = Yab Pwail
pdfjs-open-file-button-label = Yab
pdfjs-print-button =
.title = Go
pdfjs-print-button-label = Go
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Gintic
pdfjs-tools-button-label = Gintic
pdfjs-first-page-button =
.title = Cit i pot buk mukwongo
pdfjs-first-page-button-label = Cit i pot buk mukwongo
pdfjs-last-page-button =
.title = Cit i pot buk magiko
pdfjs-last-page-button-label = Cit i pot buk magiko
pdfjs-page-rotate-cw-button =
.title = Wire i tung lacuc
pdfjs-page-rotate-cw-button-label = Wire i tung lacuc
pdfjs-page-rotate-ccw-button =
.title = Wire i tung lacam
pdfjs-page-rotate-ccw-button-label = Wire i tung lacam
pdfjs-cursor-text-select-tool-button =
.title = Cak gitic me yero coc
pdfjs-cursor-text-select-tool-button-label = Gitic me yero coc
pdfjs-cursor-hand-tool-button =
.title = Cak gitic me cing
pdfjs-cursor-hand-tool-button-label = Gitic cing
## Document properties dialog
pdfjs-document-properties-button =
.title = Jami me gin acoya…
pdfjs-document-properties-button-label = Jami me gin acoya…
pdfjs-document-properties-file-name = Nying pwail:
pdfjs-document-properties-file-size = Dit pa pwail:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = Wiye:
pdfjs-document-properties-author = Ngat mucoyo:
pdfjs-document-properties-subject = Subjek:
pdfjs-document-properties-keywords = Lok mapire tek:
pdfjs-document-properties-creation-date = Nino dwe me cwec:
pdfjs-document-properties-modification-date = Nino dwe me yub:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Lacwec:
pdfjs-document-properties-producer = Layub PDF:
pdfjs-document-properties-version = Kit PDF:
pdfjs-document-properties-page-count = Kwan me pot buk:
pdfjs-document-properties-page-size = Dit pa potbuk:
pdfjs-document-properties-page-size-unit-inches = i
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = atir
pdfjs-document-properties-page-size-orientation-landscape = arii
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Waraga
pdfjs-document-properties-page-size-name-legal = Cik
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
pdfjs-document-properties-linearized-yes = Eyo
pdfjs-document-properties-linearized-no = Pe
pdfjs-document-properties-close-button = Lor
## Print
pdfjs-print-progress-message = Yubo coc me agoya…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Juki
pdfjs-printing-not-supported = Ciko: Layeny ma pe teno goyo liweng.
pdfjs-printing-not-ready = Ciko: PDF pe ocane weng me agoya.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Lok gintic ma inget
pdfjs-toggle-sidebar-button-label = Lok gintic ma inget
pdfjs-document-outline-button =
.title = Nyut Wiyewiye me Gin acoya (dii-kiryo me yaro/kano jami weng)
pdfjs-document-outline-button-label = Pek pa gin acoya
pdfjs-attachments-button =
.title = Nyut twec
pdfjs-attachments-button-label = Twec
pdfjs-thumbs-button =
.title = Nyut cal
pdfjs-thumbs-button-label = Cal
pdfjs-findbar-button =
.title = Nong iye gin acoya
pdfjs-findbar-button-label = Nong
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Pot buk { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Cal me pot buk { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Nong
.placeholder = Nong i dokumen…
pdfjs-find-previous-button =
.title = Nong timme pa lok mukato
pdfjs-find-previous-button-label = Mukato
pdfjs-find-next-button =
.title = Nong timme pa lok malubo
pdfjs-find-next-button-label = Malubo
pdfjs-find-highlight-checkbox = Ket Lanyut I Weng
pdfjs-find-match-case-checkbox-label = Lok marwate
pdfjs-find-reached-top = Oo iwi gin acoya, omede ki i tere
pdfjs-find-reached-bottom = Oo i agiki me gin acoya, omede ki iwiye
pdfjs-find-not-found = Lok pe ononge
## Predefined zoom values
pdfjs-page-scale-width = Lac me iye pot buk
pdfjs-page-scale-fit = Porre me pot buk
pdfjs-page-scale-auto = Kwot pire kene
pdfjs-page-scale-actual = Dite kikome
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
## Loading indicator messages
pdfjs-loading-error = Bal otime kun cano PDF.
pdfjs-invalid-file-error = Pwail me PDF ma pe atir onyo obale woko.
pdfjs-missing-file-error = Pwail me PDF tye ka rem.
pdfjs-unexpected-response-error = Lagam mape kigeno pa lapok tic.
pdfjs-rendering-error = Bal otime i kare me nyuto pot buk.
## Annotations
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } Lok angea manok]
## Password
pdfjs-password-label = Ket mung me donyo me yabo pwail me PDF man.
pdfjs-password-invalid = Mung me donyo pe atir. Tim ber i tem doki.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Juki
pdfjs-web-fonts-disabled = Kijuko dit pa coc me kakube woko: pe romo tic ki dit pa coc me PDF ma kiketo i kine.
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/af/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Vorige bladsy
pdfjs-previous-button-label = Vorige
pdfjs-next-button =
.title = Volgende bladsy
pdfjs-next-button-label = Volgende
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Bladsy
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = van { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } van { $pagesCount })
pdfjs-zoom-out-button =
.title = Zoem uit
pdfjs-zoom-out-button-label = Zoem uit
pdfjs-zoom-in-button =
.title = Zoem in
pdfjs-zoom-in-button-label = Zoem in
pdfjs-zoom-select =
.title = Zoem
pdfjs-presentation-mode-button =
.title = Wissel na voorleggingsmodus
pdfjs-presentation-mode-button-label = Voorleggingsmodus
pdfjs-open-file-button =
.title = Open lêer
pdfjs-open-file-button-label = Open
pdfjs-print-button =
.title = Druk
pdfjs-print-button-label = Druk
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Nutsgoed
pdfjs-tools-button-label = Nutsgoed
pdfjs-first-page-button =
.title = Gaan na eerste bladsy
pdfjs-first-page-button-label = Gaan na eerste bladsy
pdfjs-last-page-button =
.title = Gaan na laaste bladsy
pdfjs-last-page-button-label = Gaan na laaste bladsy
pdfjs-page-rotate-cw-button =
.title = Roteer kloksgewys
pdfjs-page-rotate-cw-button-label = Roteer kloksgewys
pdfjs-page-rotate-ccw-button =
.title = Roteer anti-kloksgewys
pdfjs-page-rotate-ccw-button-label = Roteer anti-kloksgewys
pdfjs-cursor-text-select-tool-button =
.title = Aktiveer gereedskap om teks te merk
pdfjs-cursor-text-select-tool-button-label = Teksmerkgereedskap
pdfjs-cursor-hand-tool-button =
.title = Aktiveer handjie
pdfjs-cursor-hand-tool-button-label = Handjie
## Document properties dialog
pdfjs-document-properties-button =
.title = Dokumenteienskappe…
pdfjs-document-properties-button-label = Dokumenteienskappe…
pdfjs-document-properties-file-name = Lêernaam:
pdfjs-document-properties-file-size = Lêergrootte:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } kG ({ $size_b } grepe)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MG ({ $size_b } grepe)
pdfjs-document-properties-title = Titel:
pdfjs-document-properties-author = Outeur:
pdfjs-document-properties-subject = Onderwerp:
pdfjs-document-properties-keywords = Sleutelwoorde:
pdfjs-document-properties-creation-date = Skeppingsdatum:
pdfjs-document-properties-modification-date = Wysigingsdatum:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Skepper:
pdfjs-document-properties-producer = PDF-vervaardiger:
pdfjs-document-properties-version = PDF-weergawe:
pdfjs-document-properties-page-count = Aantal bladsye:
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
##
pdfjs-document-properties-close-button = Sluit
## Print
pdfjs-print-progress-message = Berei tans dokument voor om te druk…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Kanselleer
pdfjs-printing-not-supported = Waarskuwing: Dié blaaier ondersteun nie drukwerk ten volle nie.
pdfjs-printing-not-ready = Waarskuwing: Die PDF is nog nie volledig gelaai vir drukwerk nie.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Sypaneel aan/af
pdfjs-toggle-sidebar-button-label = Sypaneel aan/af
pdfjs-document-outline-button =
.title = Wys dokumentskema (dubbelklik om alle items oop/toe te vou)
pdfjs-document-outline-button-label = Dokumentoorsig
pdfjs-attachments-button =
.title = Wys aanhegsels
pdfjs-attachments-button-label = Aanhegsels
pdfjs-thumbs-button =
.title = Wys duimnaels
pdfjs-thumbs-button-label = Duimnaels
pdfjs-findbar-button =
.title = Soek in dokument
pdfjs-findbar-button-label = Vind
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Bladsy { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Duimnael van bladsy { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Vind
.placeholder = Soek in dokument…
pdfjs-find-previous-button =
.title = Vind die vorige voorkoms van die frase
pdfjs-find-previous-button-label = Vorige
pdfjs-find-next-button =
.title = Vind die volgende voorkoms van die frase
pdfjs-find-next-button-label = Volgende
pdfjs-find-highlight-checkbox = Verlig almal
pdfjs-find-match-case-checkbox-label = Kassensitief
pdfjs-find-reached-top = Bokant van dokument is bereik; gaan voort van onder af
pdfjs-find-reached-bottom = Einde van dokument is bereik; gaan voort van bo af
pdfjs-find-not-found = Frase nie gevind nie
## Predefined zoom values
pdfjs-page-scale-width = Bladsywydte
pdfjs-page-scale-fit = Pas bladsy
pdfjs-page-scale-auto = Outomatiese zoem
pdfjs-page-scale-actual = Werklike grootte
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
## Loading indicator messages
pdfjs-loading-error = 'n Fout het voorgekom met die laai van die PDF.
pdfjs-invalid-file-error = Ongeldige of korrupte PDF-lêer.
pdfjs-missing-file-error = PDF-lêer is weg.
pdfjs-unexpected-response-error = Onverwagse antwoord van bediener.
pdfjs-rendering-error = 'n Fout het voorgekom toe die bladsy weergegee is.
## Annotations
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type }-annotasie]
## Password
pdfjs-password-label = Gee die wagwoord om dié PDF-lêer mee te open.
pdfjs-password-invalid = Ongeldige wagwoord. Probeer gerus weer.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Kanselleer
pdfjs-web-fonts-disabled = Webfonte is gedeaktiveer: kan nie PDF-fonte wat ingebed is, gebruik nie.
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/an/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Pachina anterior
pdfjs-previous-button-label = Anterior
pdfjs-next-button =
.title = Pachina siguient
pdfjs-next-button-label = Siguient
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Pachina
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = de { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } de { $pagesCount })
pdfjs-zoom-out-button =
.title = Achiquir
pdfjs-zoom-out-button-label = Achiquir
pdfjs-zoom-in-button =
.title = Agrandir
pdfjs-zoom-in-button-label = Agrandir
pdfjs-zoom-select =
.title = Grandaria
pdfjs-presentation-mode-button =
.title = Cambear t'o modo de presentación
pdfjs-presentation-mode-button-label = Modo de presentación
pdfjs-open-file-button =
.title = Ubrir o fichero
pdfjs-open-file-button-label = Ubrir
pdfjs-print-button =
.title = Imprentar
pdfjs-print-button-label = Imprentar
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Ferramientas
pdfjs-tools-button-label = Ferramientas
pdfjs-first-page-button =
.title = Ir ta la primer pachina
pdfjs-first-page-button-label = Ir ta la primer pachina
pdfjs-last-page-button =
.title = Ir ta la zaguer pachina
pdfjs-last-page-button-label = Ir ta la zaguer pachina
pdfjs-page-rotate-cw-button =
.title = Chirar enta la dreita
pdfjs-page-rotate-cw-button-label = Chira enta la dreita
pdfjs-page-rotate-ccw-button =
.title = Chirar enta la zurda
pdfjs-page-rotate-ccw-button-label = Chirar enta la zurda
pdfjs-cursor-text-select-tool-button =
.title = Activar la ferramienta de selección de texto
pdfjs-cursor-text-select-tool-button-label = Ferramienta de selección de texto
pdfjs-cursor-hand-tool-button =
.title = Activar la ferramienta man
pdfjs-cursor-hand-tool-button-label = Ferramienta man
pdfjs-scroll-vertical-button =
.title = Usar lo desplazamiento vertical
pdfjs-scroll-vertical-button-label = Desplazamiento vertical
pdfjs-scroll-horizontal-button =
.title = Usar lo desplazamiento horizontal
pdfjs-scroll-horizontal-button-label = Desplazamiento horizontal
pdfjs-scroll-wrapped-button =
.title = Activaar lo desplazamiento contino
pdfjs-scroll-wrapped-button-label = Desplazamiento contino
pdfjs-spread-none-button =
.title = No unir vistas de pachinas
pdfjs-spread-none-button-label = Una pachina nomás
pdfjs-spread-odd-button =
.title = Mostrar vista de pachinas, con as impars a la zurda
pdfjs-spread-odd-button-label = Doble pachina, impar a la zurda
pdfjs-spread-even-button =
.title = Amostrar vista de pachinas, con as pars a la zurda
pdfjs-spread-even-button-label = Doble pachina, para a la zurda
## Document properties dialog
pdfjs-document-properties-button =
.title = Propiedatz d'o documento...
pdfjs-document-properties-button-label = Propiedatz d'o documento...
pdfjs-document-properties-file-name = Nombre de fichero:
pdfjs-document-properties-file-size = Grandaria d'o fichero:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = Titol:
pdfjs-document-properties-author = Autor:
pdfjs-document-properties-subject = Afer:
pdfjs-document-properties-keywords = Parolas clau:
pdfjs-document-properties-creation-date = Calendata de creyación:
pdfjs-document-properties-modification-date = Calendata de modificación:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Creyador:
pdfjs-document-properties-producer = Creyador de PDF:
pdfjs-document-properties-version = Versión de PDF:
pdfjs-document-properties-page-count = Numero de pachinas:
pdfjs-document-properties-page-size = Mida de pachina:
pdfjs-document-properties-page-size-unit-inches = pulgadas
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = vertical
pdfjs-document-properties-page-size-orientation-landscape = horizontal
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Carta
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } x { $height } { $unit } { $orientation }
pdfjs-document-properties-page-size-dimension-name-string = { $width } x { $height } { $unit } { $name }, { $orientation }
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Vista web rapida:
pdfjs-document-properties-linearized-yes = Sí
pdfjs-document-properties-linearized-no = No
pdfjs-document-properties-close-button = Zarrar
## Print
pdfjs-print-progress-message = Se ye preparando la documentación pa imprentar…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Cancelar
pdfjs-printing-not-supported = Pare cuenta: Iste navegador no maneya totalment as impresions.
pdfjs-printing-not-ready = Aviso: Encara no se ha cargau completament o PDF ta imprentar-lo.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Amostrar u amagar a barra lateral
pdfjs-toggle-sidebar-notification-button =
.title = Cambiar barra lateral (lo documento contiene esquema/adchuntos/capas)
pdfjs-toggle-sidebar-button-label = Amostrar a barra lateral
pdfjs-document-outline-button =
.title = Amostrar esquema d'o documento (fer doble clic pa expandir/compactar totz los items)
pdfjs-document-outline-button-label = Esquema d'o documento
pdfjs-attachments-button =
.title = Amostrar os adchuntos
pdfjs-attachments-button-label = Adchuntos
pdfjs-layers-button =
.title = Amostrar capas (doble clic para reiniciar totas las capas a lo estau per defecto)
pdfjs-layers-button-label = Capas
pdfjs-thumbs-button =
.title = Amostrar as miniaturas
pdfjs-thumbs-button-label = Miniaturas
pdfjs-findbar-button =
.title = Trobar en o documento
pdfjs-findbar-button-label = Trobar
pdfjs-additional-layers = Capas adicionals
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Pachina { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Miniatura d'a pachina { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Trobar
.placeholder = Trobar en o documento…
pdfjs-find-previous-button =
.title = Trobar l'anterior coincidencia d'a frase
pdfjs-find-previous-button-label = Anterior
pdfjs-find-next-button =
.title = Trobar a siguient coincidencia d'a frase
pdfjs-find-next-button-label = Siguient
pdfjs-find-highlight-checkbox = Resaltar-lo tot
pdfjs-find-match-case-checkbox-label = Coincidencia de mayusclas/minusclas
pdfjs-find-entire-word-checkbox-label = Parolas completas
pdfjs-find-reached-top = S'ha plegau a l'inicio d'o documento, se contina dende baixo
pdfjs-find-reached-bottom = S'ha plegau a la fin d'o documento, se contina dende alto
pdfjs-find-not-found = No s'ha trobau a frase
## Predefined zoom values
pdfjs-page-scale-width = Amplaria d'a pachina
pdfjs-page-scale-fit = Achuste d'a pachina
pdfjs-page-scale-auto = Grandaria automatica
pdfjs-page-scale-actual = Grandaria actual
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
## Loading indicator messages
pdfjs-loading-error = S'ha produciu una error en cargar o PDF.
pdfjs-invalid-file-error = O PDF no ye valido u ye estorbau.
pdfjs-missing-file-error = No i ha fichero PDF.
pdfjs-unexpected-response-error = Respuesta a lo servicio inasperada.
pdfjs-rendering-error = Ha ocurriu una error en renderizar a pachina.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [Anotación { $type }]
## Password
pdfjs-password-label = Introduzca a clau ta ubrir iste fichero PDF.
pdfjs-password-invalid = Clau invalida. Torna a intentar-lo.
pdfjs-password-ok-button = Acceptar
pdfjs-password-cancel-button = Cancelar
pdfjs-web-fonts-disabled = As fuents web son desactivadas: no se puet incrustar fichers PDF.
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/ar/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = الصفحة السابقة
pdfjs-previous-button-label = السابقة
pdfjs-next-button =
.title = الصفحة التالية
pdfjs-next-button-label = التالية
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = صفحة
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = من { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } من { $pagesCount })
pdfjs-zoom-out-button =
.title = بعّد
pdfjs-zoom-out-button-label = بعّد
pdfjs-zoom-in-button =
.title = قرّب
pdfjs-zoom-in-button-label = قرّب
pdfjs-zoom-select =
.title = التقريب
pdfjs-presentation-mode-button =
.title = انتقل لوضع العرض التقديمي
pdfjs-presentation-mode-button-label = وضع العرض التقديمي
pdfjs-open-file-button =
.title = افتح ملفًا
pdfjs-open-file-button-label = افتح
pdfjs-print-button =
.title = اطبع
pdfjs-print-button-label = اطبع
pdfjs-save-button =
.title = احفظ
pdfjs-save-button-label = احفظ
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = نزّل
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = نزّل
pdfjs-bookmark-button =
.title = الصفحة الحالية (عرض URL من الصفحة الحالية)
pdfjs-bookmark-button-label = الصفحة الحالية
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = الأدوات
pdfjs-tools-button-label = الأدوات
pdfjs-first-page-button =
.title = انتقل إلى الصفحة الأولى
pdfjs-first-page-button-label = انتقل إلى الصفحة الأولى
pdfjs-last-page-button =
.title = انتقل إلى الصفحة الأخيرة
pdfjs-last-page-button-label = انتقل إلى الصفحة الأخيرة
pdfjs-page-rotate-cw-button =
.title = أدر باتجاه عقارب الساعة
pdfjs-page-rotate-cw-button-label = أدر باتجاه عقارب الساعة
pdfjs-page-rotate-ccw-button =
.title = أدر بعكس اتجاه عقارب الساعة
pdfjs-page-rotate-ccw-button-label = أدر بعكس اتجاه عقارب الساعة
pdfjs-cursor-text-select-tool-button =
.title = فعّل أداة اختيار النص
pdfjs-cursor-text-select-tool-button-label = أداة اختيار النص
pdfjs-cursor-hand-tool-button =
.title = فعّل أداة اليد
pdfjs-cursor-hand-tool-button-label = أداة اليد
pdfjs-scroll-page-button =
.title = استخدم تمرير الصفحة
pdfjs-scroll-page-button-label = تمرير الصفحة
pdfjs-scroll-vertical-button =
.title = استخدم التمرير الرأسي
pdfjs-scroll-vertical-button-label = التمرير الرأسي
pdfjs-scroll-horizontal-button =
.title = استخدم التمرير الأفقي
pdfjs-scroll-horizontal-button-label = التمرير الأفقي
pdfjs-scroll-wrapped-button =
.title = استخدم التمرير الملتف
pdfjs-scroll-wrapped-button-label = التمرير الملتف
pdfjs-spread-none-button =
.title = لا تدمج هوامش الصفحات مع بعضها البعض
pdfjs-spread-none-button-label = بلا هوامش
pdfjs-spread-odd-button =
.title = ادمج هوامش الصفحات الفردية
pdfjs-spread-odd-button-label = هوامش الصفحات الفردية
pdfjs-spread-even-button =
.title = ادمج هوامش الصفحات الزوجية
pdfjs-spread-even-button-label = هوامش الصفحات الزوجية
## Document properties dialog
pdfjs-document-properties-button =
.title = خصائص المستند…
pdfjs-document-properties-button-label = خصائص المستند…
pdfjs-document-properties-file-name = اسم الملف:
pdfjs-document-properties-file-size = حجم الملف:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } ك.بايت ({ $b } بايتات)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } م.بايت ({ $b } بايتات)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } ك.بايت ({ $size_b } بايت)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } م.بايت ({ $size_b } بايت)
pdfjs-document-properties-title = العنوان:
pdfjs-document-properties-author = المؤلف:
pdfjs-document-properties-subject = الموضوع:
pdfjs-document-properties-keywords = الكلمات الأساسية:
pdfjs-document-properties-creation-date = تاريخ الإنشاء:
pdfjs-document-properties-modification-date = تاريخ التعديل:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }، { $time }
pdfjs-document-properties-creator = المنشئ:
pdfjs-document-properties-producer = منتج PDF:
pdfjs-document-properties-version = إصدارة PDF:
pdfjs-document-properties-page-count = عدد الصفحات:
pdfjs-document-properties-page-size = مقاس الورقة:
pdfjs-document-properties-page-size-unit-inches = بوصة
pdfjs-document-properties-page-size-unit-millimeters = ملم
pdfjs-document-properties-page-size-orientation-portrait = طوليّ
pdfjs-document-properties-page-size-orientation-landscape = عرضيّ
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = خطاب
pdfjs-document-properties-page-size-name-legal = قانونيّ
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }، { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = العرض السريع عبر الوِب:
pdfjs-document-properties-linearized-yes = نعم
pdfjs-document-properties-linearized-no = لا
pdfjs-document-properties-close-button = أغلق
## Print
pdfjs-print-progress-message = يُحضّر المستند للطباعة…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }٪
pdfjs-print-progress-close-button = ألغِ
pdfjs-printing-not-supported = تحذير: لا يدعم هذا المتصفح الطباعة بشكل كامل.
pdfjs-printing-not-ready = تحذير: ملف PDF لم يُحمّل كاملًا للطباعة.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = بدّل ظهور الشريط الجانبي
pdfjs-toggle-sidebar-notification-button =
.title = بدّل ظهور الشريط الجانبي (يحتوي المستند على مخطط أو مرفقات أو طبقات)
pdfjs-toggle-sidebar-button-label = بدّل ظهور الشريط الجانبي
pdfjs-document-outline-button =
.title = اعرض فهرس المستند (نقر مزدوج لتمديد أو تقليص كل العناصر)
pdfjs-document-outline-button-label = مخطط المستند
pdfjs-attachments-button =
.title = اعرض المرفقات
pdfjs-attachments-button-label = المُرفقات
pdfjs-layers-button =
.title = اعرض الطبقات (انقر مرتين لتصفير كل الطبقات إلى الحالة المبدئية)
pdfjs-layers-button-label = الطبقات
pdfjs-thumbs-button =
.title = اعرض مُصغرات
pdfjs-thumbs-button-label = مُصغّرات
pdfjs-current-outline-item-button =
.title = ابحث عن عنصر المخطّط التفصيلي الحالي
pdfjs-current-outline-item-button-label = عنصر المخطّط التفصيلي الحالي
pdfjs-findbar-button =
.title = ابحث في المستند
pdfjs-findbar-button-label = ابحث
pdfjs-additional-layers = الطبقات الإضافية
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = صفحة { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = مصغّرة صفحة { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = ابحث
.placeholder = ابحث في المستند…
pdfjs-find-previous-button =
.title = ابحث عن التّواجد السّابق للعبارة
pdfjs-find-previous-button-label = السابق
pdfjs-find-next-button =
.title = ابحث عن التّواجد التّالي للعبارة
pdfjs-find-next-button-label = التالي
pdfjs-find-highlight-checkbox = أبرِز الكل
pdfjs-find-match-case-checkbox-label = طابق حالة الأحرف
pdfjs-find-match-diacritics-checkbox-label = طابِق التشكيل
pdfjs-find-entire-word-checkbox-label = كلمات كاملة
pdfjs-find-reached-top = تابعت من الأسفل بعدما وصلت إلى بداية المستند
pdfjs-find-reached-bottom = تابعت من الأعلى بعدما وصلت إلى نهاية المستند
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[zero] لا مطابقة
[one] { $current } من أصل { $total } مطابقة
[two] { $current } من أصل { $total } مطابقة
[few] { $current } من أصل { $total } مطابقة
[many] { $current } من أصل { $total } مطابقة
*[other] { $current } من أصل { $total } مطابقة
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[zero] { $limit } مطابقة
[one] أكثر من { $limit } مطابقة
[two] أكثر من { $limit } مطابقة
[few] أكثر من { $limit } مطابقة
[many] أكثر من { $limit } مطابقة
*[other] أكثر من { $limit } مطابقات
}
pdfjs-find-not-found = لا وجود للعبارة
## Predefined zoom values
pdfjs-page-scale-width = عرض الصفحة
pdfjs-page-scale-fit = ملائمة الصفحة
pdfjs-page-scale-auto = تقريب تلقائي
pdfjs-page-scale-actual = الحجم الفعلي
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }٪
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = صفحة { $page }
## Loading indicator messages
pdfjs-loading-error = حدث عطل أثناء تحميل ملف PDF.
pdfjs-invalid-file-error = ملف PDF تالف أو غير صحيح.
pdfjs-missing-file-error = ملف PDF غير موجود.
pdfjs-unexpected-response-error = استجابة خادوم غير متوقعة.
pdfjs-rendering-error = حدث خطأ أثناء عرض الصفحة.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }، { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [تعليق { $type }]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = أدخل لكلمة السر لفتح هذا الملف.
pdfjs-password-invalid = كلمة سر خطأ. من فضلك أعد المحاولة.
pdfjs-password-ok-button = حسنا
pdfjs-password-cancel-button = ألغِ
pdfjs-web-fonts-disabled = خطوط الوب مُعطّلة: تعذّر استخدام خطوط PDF المُضمّنة.
## Editing
pdfjs-editor-free-text-button =
.title = نص
pdfjs-editor-free-text-button-label = نص
pdfjs-editor-ink-button =
.title = ارسم
pdfjs-editor-ink-button-label = ارسم
pdfjs-editor-stamp-button =
.title = أضِف أو حرّر الصور
pdfjs-editor-stamp-button-label = أضِف أو حرّر الصور
pdfjs-editor-highlight-button =
.title = أبرِز
pdfjs-editor-highlight-button-label = أبرِز
pdfjs-highlight-floating-button1 =
.title = أبرِز
.aria-label = أبرِز
pdfjs-highlight-floating-button-label = أبرِز
## Default editor aria labels
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = أزِل الرسم
pdfjs-editor-remove-freetext-button =
.title = أزِل النص
pdfjs-editor-remove-stamp-button =
.title = أزِل الصورة
pdfjs-editor-remove-highlight-button =
.title = أزِل الإبراز
pdfjs-editor-remove-signature-button =
.title = أزِل التوقيع
##
# Editor Parameters
pdfjs-editor-free-text-color-input = اللون
pdfjs-editor-free-text-size-input = الحجم
pdfjs-editor-ink-color-input = اللون
pdfjs-editor-ink-thickness-input = السماكة
pdfjs-editor-ink-opacity-input = العتامة
pdfjs-editor-stamp-add-image-button =
.title = أضِف صورة
pdfjs-editor-stamp-add-image-button-label = أضِف صورة
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = السماكة
pdfjs-editor-free-highlight-thickness-title =
.title = غيّر السُمك عند إبراز عناصر أُخرى غير النص
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = محرِّر النص
.default-content = ابدأ في كتابة…
pdfjs-free-text =
.aria-label = محرِّر النص
pdfjs-free-text-default-content = ابدأ الكتابة…
pdfjs-ink =
.aria-label = محرِّر الرسم
pdfjs-ink-canvas =
.aria-label = صورة أنشأها المستخدم
## Alt-text dialog
pdfjs-editor-alt-text-button-label = نص بديل
pdfjs-editor-alt-text-edit-button =
.aria-label = حرّر النص البديل
pdfjs-editor-alt-text-edit-button-label = تحرير النص البديل
pdfjs-editor-alt-text-dialog-label = اختر خيار
pdfjs-editor-alt-text-dialog-description = يساعد النص البديل عندما لا يتمكن الأشخاص من رؤية الصورة أو عندما لا يتم تحميلها.
pdfjs-editor-alt-text-add-description-label = أضِف وصف
pdfjs-editor-alt-text-add-description-description = استهدف جملتين تصفان الموضوع أو الإعداد أو الإجراءات.
pdfjs-editor-alt-text-mark-decorative-label = علّمها على أنها زخرفية
pdfjs-editor-alt-text-mark-decorative-description = يُستخدم هذا في الصور المزخرفة، مثل الحدود أو العلامات المائية.
pdfjs-editor-alt-text-cancel-button = ألغِ
pdfjs-editor-alt-text-save-button = احفظ
pdfjs-editor-alt-text-decorative-tooltip = عُلّمت على أنها زخرفية
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = على سبيل المثال، "يجلس شاب على الطاولة لتناول وجبة"
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = نص بديل
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = الزاوية اليُسرى العُليا — غيّر الحجم
pdfjs-editor-resizer-label-top-middle = أعلى الوسط - غيّر الحجم
pdfjs-editor-resizer-label-top-right = الزاوية اليُمنى العُليا - غيّر الحجم
pdfjs-editor-resizer-label-middle-right = اليمين الأوسط - غيّر الحجم
pdfjs-editor-resizer-label-bottom-right = الزاوية اليُمنى السُفلى - غيّر الحجم
pdfjs-editor-resizer-label-bottom-middle = أسفل الوسط - غيّر الحجم
pdfjs-editor-resizer-label-bottom-left = الزاوية اليُسرى السُفلية - غيّر الحجم
pdfjs-editor-resizer-label-middle-left = مُنتصف اليسار - غيّر الحجم
pdfjs-editor-resizer-top-left =
.aria-label = الزاوية اليُسرى العُليا — غيّر الحجم
pdfjs-editor-resizer-top-middle =
.aria-label = أعلى الوسط - غيّر الحجم
pdfjs-editor-resizer-top-right =
.aria-label = الزاوية اليُمنى العُليا - غيّر الحجم
pdfjs-editor-resizer-middle-right =
.aria-label = اليمين الأوسط - غيّر الحجم
pdfjs-editor-resizer-bottom-right =
.aria-label = الزاوية اليُمنى السُفلى - غيّر الحجم
pdfjs-editor-resizer-bottom-middle =
.aria-label = أسفل الوسط - غيّر الحجم
pdfjs-editor-resizer-bottom-left =
.aria-label = الزاوية اليُسرى السُفلية - غيّر الحجم
pdfjs-editor-resizer-middle-left =
.aria-label = مُنتصف اليسار - غيّر الحجم
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = أبرِز اللون
pdfjs-editor-colorpicker-button =
.title = غيّر اللون
pdfjs-editor-colorpicker-dropdown =
.aria-label = اختيارات الألوان
pdfjs-editor-colorpicker-yellow =
.title = أصفر
pdfjs-editor-colorpicker-green =
.title = أخضر
pdfjs-editor-colorpicker-blue =
.title = أزرق
pdfjs-editor-colorpicker-pink =
.title = وردي
pdfjs-editor-colorpicker-red =
.title = أحمر
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = أظهِر الكل
pdfjs-editor-highlight-show-all-button =
.title = أظهِر الكل
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = حرّر النص البديل (وصف الصورة)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = أضِف النص البديل (وصف الصورة)
pdfjs-editor-new-alt-text-textarea =
.placeholder = اكتب وصفك هنا…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = وصف مختصر للأشخاص الذين لا يستطيعون رؤية الصورة أو عندما لا يتم تحميل الصورة.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = أُنشئ هذا النص البديل تلقائيًا وقد يكون غير دقيق.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = اطّلع على المزيد
pdfjs-editor-new-alt-text-create-automatically-button-label = أنشئ نص بديل تلقائيًا
pdfjs-editor-new-alt-text-not-now-button = ليس الآن
pdfjs-editor-new-alt-text-error-title = لم يتمكن من إنشاء نص بديل تلقائيًا
pdfjs-editor-new-alt-text-error-description = يُرجى كتابة نص بديلك أو المحاولة مرة أخرى لاحقًا.
pdfjs-editor-new-alt-text-error-close-button = أغلق
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = يُنزّل نموذج الذكاء الاصطناعي للنص البديل ({ $downloadedSize } من { $totalSize } م.بايت)
.aria-valuetext = يُنزّل نموذج الذكاء الاصطناعي للنص البديل ({ $downloadedSize } من { $totalSize } م.بايت)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = أُضِيف نص بديل
pdfjs-editor-new-alt-text-added-button-label = أُضِيف نص بديل
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = نص بديل مفقود
pdfjs-editor-new-alt-text-missing-button-label = نص بديل مفقود
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = راجع النص البديل
pdfjs-editor-new-alt-text-to-review-button-label = راجع النص البديل
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = أُنشئ تلقائيًا: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = إعدادات النص البديل للصورة
pdfjs-image-alt-text-settings-button-label = إعدادات النص البديل للصورة
pdfjs-editor-alt-text-settings-dialog-label = إعدادات النص البديل للصورة
pdfjs-editor-alt-text-settings-automatic-title = نص بديل تلقائي
pdfjs-editor-alt-text-settings-create-model-button-label = أنشئ نص بديل تلقائيًا
pdfjs-editor-alt-text-settings-create-model-description = يقترح أوصافًا لمساعدة الأشخاص الذين لا يستطيعون رؤية الصورة أو عندما لا يتم تحميل الصورة.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = نموذج الذكاء الاصطناعي للنص البديل ({ $totalSize } م.بايت)
pdfjs-editor-alt-text-settings-ai-model-description = يتم تشغيله محليًا على جهازك حتى تظل بياناتك خاصة. مطلوب للنص البديل التلقائي.
pdfjs-editor-alt-text-settings-delete-model-button = احذف
pdfjs-editor-alt-text-settings-download-model-button = نزّل
pdfjs-editor-alt-text-settings-downloading-model-button = يُنزل…
pdfjs-editor-alt-text-settings-editor-title = مُحرِّر النص البديل
pdfjs-editor-alt-text-settings-show-dialog-button-label = أظهِر مُحرِّر النص البديل على الفور عند إضافة صورة
pdfjs-editor-alt-text-settings-show-dialog-description = يساعدك على التأكد من أن جميع صورك تحتوي على نص بديل.
pdfjs-editor-alt-text-settings-close-button = أغلق
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = أُزِيل البرز
pdfjs-editor-undo-bar-message-freetext = أُزيل النص
pdfjs-editor-undo-bar-message-ink = أُزِيلت الرسمة
pdfjs-editor-undo-bar-message-stamp = أُزيلت الصورة
pdfjs-editor-undo-bar-message-signature = أُزيل التوقيع
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[zero] أُزيل لا تعليق
[one] أُزيل تعليق
[two] أُزيل تعليقين
[few] أُزيلت { $count } تعليقات
[many] أُزيل { $count } تعليق
*[other] أُزيل { $count } تعليق
}
pdfjs-editor-undo-bar-undo-button =
.title = تراجع
pdfjs-editor-undo-bar-undo-button-label = تراجع
pdfjs-editor-undo-bar-close-button =
.title = أغلق
pdfjs-editor-undo-bar-close-button-label = أغلق
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = يتيح هذا النموذج للمستخدم إنشاء توقيع لإضافته إلى مستند PDF. ويمكن للمستخدم تحرير الاسم (الذي يعمل أيضًا كنص بديل)، وحفظ التوقيع بشكل اختياري للاستخدام المتكرر.
pdfjs-editor-add-signature-dialog-title = أضِف توقيعا
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = اكتب
.title = اكتب
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = ارسم
.title = ارسم
pdfjs-editor-add-signature-image-button = صورة
.title = صورة
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = اكتب توقيعك
.placeholder = اكتب توقيعك
pdfjs-editor-add-signature-draw-placeholder = ارسم توقيعك
pdfjs-editor-add-signature-draw-thickness-range-label = السماكة
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = سمك الرسم: { $thickness }
pdfjs-editor-add-signature-image-placeholder = اسحب الملف هنا لرفعه
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] أو اختر ملفات الصور
*[other] أو تصفح ملفات الصور
}
## Controls
pdfjs-editor-add-signature-description-label = الوصف (نص بديل)
pdfjs-editor-add-signature-description-input =
.title = الوصف (نص بديل)
pdfjs-editor-add-signature-description-default-when-drawing = توقيع
pdfjs-editor-add-signature-clear-button-label = امحُ التوقيع
pdfjs-editor-add-signature-clear-button =
.title = امحُ التوقيع
pdfjs-editor-add-signature-save-checkbox = احفظ التوقيع
pdfjs-editor-add-signature-save-warning-message = لقد وصلت إلى الحد الأقصى وهو 5 توقيعات محفوظة. أزِل توقيع واحد لحفظ المزيد.
pdfjs-editor-add-signature-image-upload-error-title = تعذر رفع الصورة.
pdfjs-editor-add-signature-image-upload-error-description = تحقق من اتصال الشبكة لديك أو جرّب صورة أخرى.
pdfjs-editor-add-signature-error-close-button = أغلق
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = ألغِ
pdfjs-editor-add-signature-add-button = أضِف
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/ast/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Páxina anterior
pdfjs-previous-button-label = Anterior
pdfjs-next-button =
.title = Páxina siguiente
pdfjs-next-button-label = Siguiente
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Páxina
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = de { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } de { $pagesCount })
pdfjs-zoom-out-button =
.title = Alloñar
pdfjs-zoom-out-button-label = Alloña
pdfjs-zoom-in-button =
.title = Averar
pdfjs-zoom-in-button-label = Avera
pdfjs-zoom-select =
.title = Zoom
pdfjs-presentation-mode-button =
.title = Cambiar al mou de presentación
pdfjs-presentation-mode-button-label = Mou de presentación
pdfjs-open-file-button-label = Abrir
pdfjs-print-button =
.title = Imprentar
pdfjs-print-button-label = Imprentar
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Ferramientes
pdfjs-tools-button-label = Ferramientes
pdfjs-first-page-button-label = Dir a la primer páxina
pdfjs-last-page-button-label = Dir a la última páxina
pdfjs-page-rotate-cw-button =
.title = Voltia a la derecha
pdfjs-page-rotate-cw-button-label = Voltiar a la derecha
pdfjs-page-rotate-ccw-button =
.title = Voltia a la esquierda
pdfjs-page-rotate-ccw-button-label = Voltiar a la esquierda
pdfjs-cursor-text-select-tool-button =
.title = Activa la ferramienta d'esbilla de testu
pdfjs-cursor-text-select-tool-button-label = Ferramienta d'esbilla de testu
pdfjs-cursor-hand-tool-button =
.title = Activa la ferramienta de mano
pdfjs-cursor-hand-tool-button-label = Ferramienta de mano
pdfjs-scroll-vertical-button =
.title = Usa'l desplazamientu vertical
pdfjs-scroll-vertical-button-label = Desplazamientu vertical
pdfjs-scroll-horizontal-button =
.title = Usa'l desplazamientu horizontal
pdfjs-scroll-horizontal-button-label = Desplazamientu horizontal
pdfjs-scroll-wrapped-button =
.title = Usa'l desplazamientu continuu
pdfjs-scroll-wrapped-button-label = Desplazamientu continuu
pdfjs-spread-none-button-label = Fueyes individuales
pdfjs-spread-odd-button-label = Fueyes pares
pdfjs-spread-even-button-label = Fueyes impares
## Document properties dialog
pdfjs-document-properties-button =
.title = Propiedaes del documentu…
pdfjs-document-properties-button-label = Propiedaes del documentu…
pdfjs-document-properties-file-name = Nome del ficheru:
pdfjs-document-properties-file-size = Tamañu del ficheru:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = Títulu:
pdfjs-document-properties-keywords = Pallabres clave:
pdfjs-document-properties-creation-date = Data de creación:
pdfjs-document-properties-modification-date = Data de modificación:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-producer = Productor del PDF:
pdfjs-document-properties-version = Versión del PDF:
pdfjs-document-properties-page-count = Númberu de páxines:
pdfjs-document-properties-page-size = Tamañu de páxina:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = vertical
pdfjs-document-properties-page-size-orientation-landscape = horizontal
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Vista web rápida:
pdfjs-document-properties-linearized-yes = Sí
pdfjs-document-properties-linearized-no = Non
pdfjs-document-properties-close-button = Zarrar
## Print
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Encaboxar
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Alternar la barra llateral
pdfjs-attachments-button =
.title = Amosar los axuntos
pdfjs-attachments-button-label = Axuntos
pdfjs-layers-button-label = Capes
pdfjs-thumbs-button =
.title = Amosar les miniatures
pdfjs-thumbs-button-label = Miniatures
pdfjs-findbar-button-label = Atopar
pdfjs-additional-layers = Capes adicionales
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Páxina { $page }
## Find panel button title and messages
pdfjs-find-previous-button-label = Anterior
pdfjs-find-next-button-label = Siguiente
pdfjs-find-entire-word-checkbox-label = Pallabres completes
pdfjs-find-reached-top = Algamóse'l comienzu de la páxina, síguese dende abaxo
pdfjs-find-reached-bottom = Algamóse la fin del documentu, síguese dende arriba
## Predefined zoom values
pdfjs-page-scale-auto = Zoom automáticu
pdfjs-page-scale-actual = Tamañu real
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Páxina { $page }
## Loading indicator messages
pdfjs-loading-error = Asocedió un fallu mentanto se cargaba'l PDF.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
## Password
pdfjs-password-ok-button = Aceptar
pdfjs-password-cancel-button = Encaboxar
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/az/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Əvvəlki səhifə
pdfjs-previous-button-label = Əvvəlkini tap
pdfjs-next-button =
.title = Növbəti səhifə
pdfjs-next-button-label = İrəli
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Səhifə
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = / { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } / { $pagesCount })
pdfjs-zoom-out-button =
.title = Uzaqlaş
pdfjs-zoom-out-button-label = Uzaqlaş
pdfjs-zoom-in-button =
.title = Yaxınlaş
pdfjs-zoom-in-button-label = Yaxınlaş
pdfjs-zoom-select =
.title = Yaxınlaşdırma
pdfjs-presentation-mode-button =
.title = Təqdimat Rejiminə Keç
pdfjs-presentation-mode-button-label = Təqdimat Rejimi
pdfjs-open-file-button =
.title = Fayl Aç
pdfjs-open-file-button-label = Aç
pdfjs-print-button =
.title = Yazdır
pdfjs-print-button-label = Yazdır
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Alətlər
pdfjs-tools-button-label = Alətlər
pdfjs-first-page-button =
.title = İlk Səhifəyə get
pdfjs-first-page-button-label = İlk Səhifəyə get
pdfjs-last-page-button =
.title = Son Səhifəyə get
pdfjs-last-page-button-label = Son Səhifəyə get
pdfjs-page-rotate-cw-button =
.title = Saat İstiqamətində Fırlat
pdfjs-page-rotate-cw-button-label = Saat İstiqamətində Fırlat
pdfjs-page-rotate-ccw-button =
.title = Saat İstiqamətinin Əksinə Fırlat
pdfjs-page-rotate-ccw-button-label = Saat İstiqamətinin Əksinə Fırlat
pdfjs-cursor-text-select-tool-button =
.title = Yazı seçmə alətini aktivləşdir
pdfjs-cursor-text-select-tool-button-label = Yazı seçmə aləti
pdfjs-cursor-hand-tool-button =
.title = Əl alətini aktivləşdir
pdfjs-cursor-hand-tool-button-label = Əl aləti
pdfjs-scroll-vertical-button =
.title = Şaquli sürüşdürmə işlət
pdfjs-scroll-vertical-button-label = Şaquli sürüşdürmə
pdfjs-scroll-horizontal-button =
.title = Üfüqi sürüşdürmə işlət
pdfjs-scroll-horizontal-button-label = Üfüqi sürüşdürmə
pdfjs-scroll-wrapped-button =
.title = Bükülü sürüşdürmə işlət
pdfjs-scroll-wrapped-button-label = Bükülü sürüşdürmə
pdfjs-spread-none-button =
.title = Yan-yana birləşdirilmiş səhifələri işlətmə
pdfjs-spread-none-button-label = Birləşdirmə
pdfjs-spread-odd-button =
.title = Yan-yana birləşdirilmiş səhifələri tək nömrəli səhifələrdən başlat
pdfjs-spread-odd-button-label = Tək nömrəli
pdfjs-spread-even-button =
.title = Yan-yana birləşdirilmiş səhifələri cüt nömrəli səhifələrdən başlat
pdfjs-spread-even-button-label = Cüt nömrəli
## Document properties dialog
pdfjs-document-properties-button =
.title = Sənəd xüsusiyyətləri…
pdfjs-document-properties-button-label = Sənəd xüsusiyyətləri…
pdfjs-document-properties-file-name = Fayl adı:
pdfjs-document-properties-file-size = Fayl ölçüsü:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bayt)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bayt)
pdfjs-document-properties-title = Başlık:
pdfjs-document-properties-author = Müəllif:
pdfjs-document-properties-subject = Mövzu:
pdfjs-document-properties-keywords = Açar sözlər:
pdfjs-document-properties-creation-date = Yaradılış Tarixi :
pdfjs-document-properties-modification-date = Dəyişdirilmə Tarixi :
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Yaradan:
pdfjs-document-properties-producer = PDF yaradıcısı:
pdfjs-document-properties-version = PDF versiyası:
pdfjs-document-properties-page-count = Səhifə sayı:
pdfjs-document-properties-page-size = Səhifə Ölçüsü:
pdfjs-document-properties-page-size-unit-inches = inç
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = portret
pdfjs-document-properties-page-size-orientation-landscape = albom
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Məktub
pdfjs-document-properties-page-size-name-legal = Hüquqi
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Fast Web View:
pdfjs-document-properties-linearized-yes = Bəli
pdfjs-document-properties-linearized-no = Xeyr
pdfjs-document-properties-close-button = Qapat
## Print
pdfjs-print-progress-message = Sənəd çap üçün hazırlanır…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Ləğv et
pdfjs-printing-not-supported = Xəbərdarlıq: Çap bu səyyah tərəfindən tam olaraq dəstəklənmir.
pdfjs-printing-not-ready = Xəbərdarlıq: PDF çap üçün tam yüklənməyib.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Yan Paneli Aç/Bağla
pdfjs-toggle-sidebar-notification-button =
.title = Yan paneli çevir (sənəddə icmal/bağlamalar/laylar mövcuddur)
pdfjs-toggle-sidebar-button-label = Yan Paneli Aç/Bağla
pdfjs-document-outline-button =
.title = Sənədin eskizini göstər (bütün bəndləri açmaq/yığmaq üçün iki dəfə klikləyin)
pdfjs-document-outline-button-label = Sənəd strukturu
pdfjs-attachments-button =
.title = Bağlamaları göstər
pdfjs-attachments-button-label = Bağlamalar
pdfjs-layers-button =
.title = Layları göstər (bütün layları ilkin halına sıfırlamaq üçün iki dəfə klikləyin)
pdfjs-layers-button-label = Laylar
pdfjs-thumbs-button =
.title = Kiçik şəkilləri göstər
pdfjs-thumbs-button-label = Kiçik şəkillər
pdfjs-findbar-button =
.title = Sənəddə Tap
pdfjs-findbar-button-label = Tap
pdfjs-additional-layers = Əlavə laylar
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Səhifə{ $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = { $page } səhifəsinin kiçik vəziyyəti
## Find panel button title and messages
pdfjs-find-input =
.title = Tap
.placeholder = Sənəddə tap…
pdfjs-find-previous-button =
.title = Bir öncəki uyğun gələn sözü tapır
pdfjs-find-previous-button-label = Geri
pdfjs-find-next-button =
.title = Bir sonrakı uyğun gələn sözü tapır
pdfjs-find-next-button-label = İrəli
pdfjs-find-highlight-checkbox = İşarələ
pdfjs-find-match-case-checkbox-label = Böyük/kiçik hərfə həssaslıq
pdfjs-find-entire-word-checkbox-label = Tam sözlər
pdfjs-find-reached-top = Sənədin yuxarısına çatdı, aşağıdan davam edir
pdfjs-find-reached-bottom = Sənədin sonuna çatdı, yuxarıdan davam edir
pdfjs-find-not-found = Uyğunlaşma tapılmadı
## Predefined zoom values
pdfjs-page-scale-width = Səhifə genişliyi
pdfjs-page-scale-fit = Səhifəni sığdır
pdfjs-page-scale-auto = Avtomatik yaxınlaşdır
pdfjs-page-scale-actual = Hazırkı Həcm
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
## Loading indicator messages
pdfjs-loading-error = PDF yüklenərkən bir səhv yarandı.
pdfjs-invalid-file-error = Səhv və ya zədələnmiş olmuş PDF fayl.
pdfjs-missing-file-error = PDF fayl yoxdur.
pdfjs-unexpected-response-error = Gözlənilməz server cavabı.
pdfjs-rendering-error = Səhifə göstərilərkən səhv yarandı.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } Annotasiyası]
## Password
pdfjs-password-label = Bu PDF faylı açmaq üçün parolu daxil edin.
pdfjs-password-invalid = Parol səhvdir. Bir daha yoxlayın.
pdfjs-password-ok-button = Tamam
pdfjs-password-cancel-button = Ləğv et
pdfjs-web-fonts-disabled = Web Şriftlər söndürülüb: yerləşdirilmiş PDF şriftlərini istifadə etmək mümkün deyil.
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/be/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Папярэдняя старонка
pdfjs-previous-button-label = Папярэдняя
pdfjs-next-button =
.title = Наступная старонка
pdfjs-next-button-label = Наступная
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Старонка
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = з { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } з { $pagesCount })
pdfjs-zoom-out-button =
.title = Паменшыць
pdfjs-zoom-out-button-label = Паменшыць
pdfjs-zoom-in-button =
.title = Павялічыць
pdfjs-zoom-in-button-label = Павялічыць
pdfjs-zoom-select =
.title = Павялічэнне тэксту
pdfjs-presentation-mode-button =
.title = Пераключыцца ў рэжым паказу
pdfjs-presentation-mode-button-label = Рэжым паказу
pdfjs-open-file-button =
.title = Адкрыць файл
pdfjs-open-file-button-label = Адкрыць
pdfjs-print-button =
.title = Друкаваць
pdfjs-print-button-label = Друкаваць
pdfjs-save-button =
.title = Захаваць
pdfjs-save-button-label = Захаваць
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Сцягнуць
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Сцягнуць
pdfjs-bookmark-button =
.title = Дзейная старонка (паглядзець URL-адрас з дзейнай старонкі)
pdfjs-bookmark-button-label = Цяперашняя старонка
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Прылады
pdfjs-tools-button-label = Прылады
pdfjs-first-page-button =
.title = Перайсці на першую старонку
pdfjs-first-page-button-label = Перайсці на першую старонку
pdfjs-last-page-button =
.title = Перайсці на апошнюю старонку
pdfjs-last-page-button-label = Перайсці на апошнюю старонку
pdfjs-page-rotate-cw-button =
.title = Павярнуць па сонцу
pdfjs-page-rotate-cw-button-label = Павярнуць па сонцу
pdfjs-page-rotate-ccw-button =
.title = Павярнуць супраць сонца
pdfjs-page-rotate-ccw-button-label = Павярнуць супраць сонца
pdfjs-cursor-text-select-tool-button =
.title = Уключыць прыладу выбару тэксту
pdfjs-cursor-text-select-tool-button-label = Прылада выбару тэксту
pdfjs-cursor-hand-tool-button =
.title = Уключыць ручную прыладу
pdfjs-cursor-hand-tool-button-label = Ручная прылада
pdfjs-scroll-page-button =
.title = Выкарыстоўваць пракрутку старонкi
pdfjs-scroll-page-button-label = Пракрутка старонкi
pdfjs-scroll-vertical-button =
.title = Ужываць вертыкальную пракрутку
pdfjs-scroll-vertical-button-label = Вертыкальная пракрутка
pdfjs-scroll-horizontal-button =
.title = Ужываць гарызантальную пракрутку
pdfjs-scroll-horizontal-button-label = Гарызантальная пракрутка
pdfjs-scroll-wrapped-button =
.title = Ужываць маштабавальную пракрутку
pdfjs-scroll-wrapped-button-label = Маштабавальная пракрутка
pdfjs-spread-none-button =
.title = Не выкарыстоўваць разгорнутыя старонкі
pdfjs-spread-none-button-label = Без разгорнутых старонак
pdfjs-spread-odd-button =
.title = Разгорнутыя старонкі пачынаючы з няцотных нумароў
pdfjs-spread-odd-button-label = Няцотныя старонкі злева
pdfjs-spread-even-button =
.title = Разгорнутыя старонкі пачынаючы з цотных нумароў
pdfjs-spread-even-button-label = Цотныя старонкі злева
## Document properties dialog
pdfjs-document-properties-button =
.title = Уласцівасці дакумента…
pdfjs-document-properties-button-label = Уласцівасці дакумента…
pdfjs-document-properties-file-name = Назва файла:
pdfjs-document-properties-file-size = Памер файла:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } КБ ({ $b } байтаў)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } МБ ({ $b } байтаў)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } КБ ({ $size_b } байт)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } МБ ({ $size_b } байт)
pdfjs-document-properties-title = Загаловак:
pdfjs-document-properties-author = Аўтар:
pdfjs-document-properties-subject = Тэма:
pdfjs-document-properties-keywords = Ключавыя словы:
pdfjs-document-properties-creation-date = Дата стварэння:
pdfjs-document-properties-modification-date = Дата змянення:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Стваральнік:
pdfjs-document-properties-producer = Вырабнік PDF:
pdfjs-document-properties-version = Версія PDF:
pdfjs-document-properties-page-count = Колькасць старонак:
pdfjs-document-properties-page-size = Памер старонкі:
pdfjs-document-properties-page-size-unit-inches = цаляў
pdfjs-document-properties-page-size-unit-millimeters = мм
pdfjs-document-properties-page-size-orientation-portrait = кніжная
pdfjs-document-properties-page-size-orientation-landscape = альбомная
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letter
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Хуткі прагляд у Інтэрнэце:
pdfjs-document-properties-linearized-yes = Так
pdfjs-document-properties-linearized-no = Не
pdfjs-document-properties-close-button = Закрыць
## Print
pdfjs-print-progress-message = Падрыхтоўка дакумента да друку…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Скасаваць
pdfjs-printing-not-supported = Папярэджанне: друк не падтрымліваецца цалкам гэтым браўзерам.
pdfjs-printing-not-ready = Увага: PDF не сцягнуты цалкам для друкавання.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Паказаць/схаваць бакавую панэль
pdfjs-toggle-sidebar-notification-button =
.title = Паказаць/схаваць бакавую панэль (дакумент мае змест/укладанні/пласты)
pdfjs-toggle-sidebar-button-label = Паказаць/схаваць бакавую панэль
pdfjs-document-outline-button =
.title = Паказаць структуру дакумента (двайная пстрычка, каб разгарнуць /згарнуць усе элементы)
pdfjs-document-outline-button-label = Структура дакумента
pdfjs-attachments-button =
.title = Паказаць далучэнні
pdfjs-attachments-button-label = Далучэнні
pdfjs-layers-button =
.title = Паказаць пласты (націсніце двойчы, каб скінуць усе пласты да прадвызначанага стану)
pdfjs-layers-button-label = Пласты
pdfjs-thumbs-button =
.title = Паказ мініяцюр
pdfjs-thumbs-button-label = Мініяцюры
pdfjs-current-outline-item-button =
.title = Знайсці бягучы элемент структуры
pdfjs-current-outline-item-button-label = Бягучы элемент структуры
pdfjs-findbar-button =
.title = Пошук у дакуменце
pdfjs-findbar-button-label = Знайсці
pdfjs-additional-layers = Дадатковыя пласты
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Старонка { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Мініяцюра старонкі { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Шукаць
.placeholder = Шукаць у дакуменце…
pdfjs-find-previous-button =
.title = Знайсці папярэдні выпадак выразу
pdfjs-find-previous-button-label = Папярэдні
pdfjs-find-next-button =
.title = Знайсці наступны выпадак выразу
pdfjs-find-next-button-label = Наступны
pdfjs-find-highlight-checkbox = Падфарбаваць усе
pdfjs-find-match-case-checkbox-label = Адрозніваць вялікія/малыя літары
pdfjs-find-match-diacritics-checkbox-label = З улікам дыякрытык
pdfjs-find-entire-word-checkbox-label = Словы цалкам
pdfjs-find-reached-top = Дасягнуты пачатак дакумента, працяг з канца
pdfjs-find-reached-bottom = Дасягнуты канец дакумента, працяг з пачатку
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } з { $total } супадзенняў
[few] { $current } з { $total } супадзенняў
*[many] { $current } з { $total } супадзенняў
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Больш за { $limit } супадзенне
[few] Больш за { $limit } супадзенні
*[many] Больш за { $limit } супадзенняў
}
pdfjs-find-not-found = Выраз не знойдзены
## Predefined zoom values
pdfjs-page-scale-width = Шырыня старонкі
pdfjs-page-scale-fit = Уцісненне старонкі
pdfjs-page-scale-auto = Аўтаматычнае павелічэнне
pdfjs-page-scale-actual = Сапраўдны памер
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Старонка { $page }
## Loading indicator messages
pdfjs-loading-error = Здарылася памылка ў часе загрузкі PDF.
pdfjs-invalid-file-error = Няспраўны або пашкоджаны файл PDF.
pdfjs-missing-file-error = Адсутны файл PDF.
pdfjs-unexpected-response-error = Нечаканы адказ сервера.
pdfjs-rendering-error = Здарылася памылка падчас адлюстравання старонкі.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } Annotation]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Увядзіце пароль, каб адкрыць гэты файл PDF.
pdfjs-password-invalid = Нядзейсны пароль. Паспрабуйце зноў.
pdfjs-password-ok-button = Добра
pdfjs-password-cancel-button = Скасаваць
pdfjs-web-fonts-disabled = Шрыфты Сеціва забаронены: немагчыма ўжываць укладзеныя шрыфты PDF.
## Editing
pdfjs-editor-free-text-button =
.title = Тэкст
pdfjs-editor-free-text-button-label = Тэкст
pdfjs-editor-ink-button =
.title = Маляваць
pdfjs-editor-ink-button-label = Маляваць
pdfjs-editor-stamp-button =
.title = Дадаць або змяніць выявы
pdfjs-editor-stamp-button-label = Дадаць або змяніць выявы
pdfjs-editor-highlight-button =
.title = Вылучэнне
pdfjs-editor-highlight-button-label = Вылучэнне
pdfjs-highlight-floating-button1 =
.title = Падфарбаваць
.aria-label = Падфарбаваць
pdfjs-highlight-floating-button-label = Падфарбаваць
pdfjs-editor-signature-button =
.title = Дадаць подпіс
pdfjs-editor-signature-button-label = Дадаць подпіс
## Default editor aria labels
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Выдаліць малюнак
pdfjs-editor-remove-freetext-button =
.title = Выдаліць тэкст
pdfjs-editor-remove-stamp-button =
.title = Выдаліць выяву
pdfjs-editor-remove-highlight-button =
.title = Выдаліць падфарбоўку
pdfjs-editor-remove-signature-button =
.title = Выдаліць подпіс
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Колер
pdfjs-editor-free-text-size-input = Памер
pdfjs-editor-ink-color-input = Колер
pdfjs-editor-ink-thickness-input = Таўшчыня
pdfjs-editor-ink-opacity-input = Непразрыстасць
pdfjs-editor-stamp-add-image-button =
.title = Дадаць выяву
pdfjs-editor-stamp-add-image-button-label = Дадаць выяву
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Таўшчыня
pdfjs-editor-free-highlight-thickness-title =
.title = Змяняць таўшчыню пры вылучэнні іншых элементаў, акрамя тэксту
pdfjs-editor-signature-add-signature-button =
.title = Дадаць новы подпіс
pdfjs-editor-signature-add-signature-button-label = Дадаць новы подпіс
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Тэкставы рэдактар
.default-content = Пачніце ўводзіць…
pdfjs-free-text =
.aria-label = Тэкставы рэдактар
pdfjs-free-text-default-content = Пачніце набор тэксту…
pdfjs-ink =
.aria-label = Графічны рэдактар
pdfjs-ink-canvas =
.aria-label = Выява, створаная карыстальнікам
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Альтэрнатыўны тэкст
pdfjs-editor-alt-text-edit-button =
.aria-label = Змяніць альтэрнатыўны тэкст
pdfjs-editor-alt-text-edit-button-label = Змяніць альтэрнатыўны тэкст
pdfjs-editor-alt-text-dialog-label = Выберыце варыянт
pdfjs-editor-alt-text-dialog-description = Альтэрнатыўны тэкст дапамагае, калі людзі не бачаць выяву або калі яна не загружаецца.
pdfjs-editor-alt-text-add-description-label = Дадаць апісанне
pdfjs-editor-alt-text-add-description-description = Старайцеся скласці 1-2 сказы, якія апісваюць прадмет, абстаноўку або дзеянні.
pdfjs-editor-alt-text-mark-decorative-label = Пазначыць як дэкаратыўны
pdfjs-editor-alt-text-mark-decorative-description = Выкарыстоўваецца для дэкаратыўных выяваў, такіх як рамкі або вадзяныя знакі.
pdfjs-editor-alt-text-cancel-button = Скасаваць
pdfjs-editor-alt-text-save-button = Захаваць
pdfjs-editor-alt-text-decorative-tooltip = Пазначаны як дэкаратыўны
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Напрыклад, «Малады чалавек садзіцца за стол есці»
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Альтэрнатыўны тэкст
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Верхні левы кут — змяніць памер
pdfjs-editor-resizer-label-top-middle = Уверсе пасярэдзіне — змяніць памер
pdfjs-editor-resizer-label-top-right = Верхні правы кут — змяніць памер
pdfjs-editor-resizer-label-middle-right = Пасярэдзіне справа — змяніць памер
pdfjs-editor-resizer-label-bottom-right = Правы ніжні кут — змяніць памер
pdfjs-editor-resizer-label-bottom-middle = Пасярэдзіне ўнізе — змяніць памер
pdfjs-editor-resizer-label-bottom-left = Левы ніжні кут — змяніць памер
pdfjs-editor-resizer-label-middle-left = Пасярэдзіне злева — змяніць памер
pdfjs-editor-resizer-top-left =
.aria-label = Верхні левы кут — змяніць памер
pdfjs-editor-resizer-top-middle =
.aria-label = Уверсе пасярэдзіне — змяніць памер
pdfjs-editor-resizer-top-right =
.aria-label = Верхні правы кут — змяніць памер
pdfjs-editor-resizer-middle-right =
.aria-label = Пасярэдзіне справа — змяніць памер
pdfjs-editor-resizer-bottom-right =
.aria-label = Правы ніжні кут — змяніць памер
pdfjs-editor-resizer-bottom-middle =
.aria-label = Пасярэдзіне ўнізе — змяніць памер
pdfjs-editor-resizer-bottom-left =
.aria-label = Левы ніжні кут — змяніць памер
pdfjs-editor-resizer-middle-left =
.aria-label = Пасярэдзіне злева — змяніць памер
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Колер падфарбоўкі
pdfjs-editor-colorpicker-button =
.title = Змяніць колер
pdfjs-editor-colorpicker-dropdown =
.aria-label = Выбар колеру
pdfjs-editor-colorpicker-yellow =
.title = Жоўты
pdfjs-editor-colorpicker-green =
.title = Зялёны
pdfjs-editor-colorpicker-blue =
.title = Блакітны
pdfjs-editor-colorpicker-pink =
.title = Ружовы
pdfjs-editor-colorpicker-red =
.title = Чырвоны
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Паказаць усе
pdfjs-editor-highlight-show-all-button =
.title = Паказаць усе
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Рэдагаваць тэкст для атрыбута alt (апісанне выявы)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Дадаць тэкст для атрыбута alt (апісанне выявы)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Напішыце сваё апісанне тут…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Кароткае апісанне для людзей, якія не бачаць выяву, ці калі выява не загружаецца.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Гэты тэкст для атрыбута alt быў створаны аўтаматычна і можа быць недакладным
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Даведацца больш
pdfjs-editor-new-alt-text-create-automatically-button-label = Ствараць тэкст для атрыбута alt аўтаматычна
pdfjs-editor-new-alt-text-not-now-button = Не зараз
pdfjs-editor-new-alt-text-error-title = Не ўдалося аўтаматычна стварыць тэкст для атрыбута alt
pdfjs-editor-new-alt-text-error-description = Калі ласка, напішыце ўласны тэкст для атрыбута alt або паўтарыце спробу пазней.
pdfjs-editor-new-alt-text-error-close-button = Закрыць
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Сцягванне мадэлі ШІ для тэксту для атрыбута alt ({ $downloadedSize } з { $totalSize } МБ)
.aria-valuetext = Сцягванне мадэлі ШІ для тэксту для атрыбута alt ({ $downloadedSize } з { $totalSize } МБ)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Тэкст для атрыбута alt дададзены
pdfjs-editor-new-alt-text-added-button-label = Тэкст для атрыбута alt дададзены
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Адсутнічае тэкст для атрыбута alt
pdfjs-editor-new-alt-text-missing-button-label = Адсутнічае тэкст для атрыбута alt
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Водгук на тэкст для атрыбута alt
pdfjs-editor-new-alt-text-to-review-button-label = Водгук на тэкст для атрыбута alt
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Створаны аўтаматычна: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Налады альтэрнатыўнага тэксту для выявы
pdfjs-image-alt-text-settings-button-label = Налады альтэрнатыўнага тэксту для выявы
pdfjs-editor-alt-text-settings-dialog-label = Налады альтэрнатыўнага тэксту для выявы
pdfjs-editor-alt-text-settings-automatic-title = Аўтаматычны тэкст для атрыбута alt
pdfjs-editor-alt-text-settings-create-model-button-label = Ствараць тэкст для атрыбута alt аўтаматычна
pdfjs-editor-alt-text-settings-create-model-description = Прапануе апісанні, каб дапамагчы людзям, якія не бачаць выяву, ці калі выява не загружаецца.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Мадэль ШІ для тэксту для атрыбута alt ({ $totalSize } МБ)
pdfjs-editor-alt-text-settings-ai-model-description = Працуе лакальна на вашай прыладзе, таму вашы звесткі застаюцца прыватнымі. Патрабуецца для аўтаматычнага альтэрнатыўнага тэксту.
pdfjs-editor-alt-text-settings-delete-model-button = Выдаліць
pdfjs-editor-alt-text-settings-download-model-button = Сцягнуць
pdfjs-editor-alt-text-settings-downloading-model-button = Сцягванне…
pdfjs-editor-alt-text-settings-editor-title = Рэдактар тэксту для атрыбута alt
pdfjs-editor-alt-text-settings-show-dialog-button-label = Адразу паказваць рэдактар тэксту для атрыбута alt пры даданні выявы
pdfjs-editor-alt-text-settings-show-dialog-description = Дапамагае пераканацца, што ўсе вашы выявы маюць альтэрнатыўны тэкст.
pdfjs-editor-alt-text-settings-close-button = Закрыць
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Падсвятленне выдалена
pdfjs-editor-undo-bar-message-freetext = Тэкст выдалены
pdfjs-editor-undo-bar-message-ink = Малюнак выдалены
pdfjs-editor-undo-bar-message-stamp = Відарыс выдалены
pdfjs-editor-undo-bar-message-signature = Подпіс выдалены
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } анатацыя выдалена
[few] { $count } анатацыі выдалена
*[many] { $count } анатацый выдалена
}
pdfjs-editor-undo-bar-undo-button =
.title = Адмяніць
pdfjs-editor-undo-bar-undo-button-label = Адмяніць
pdfjs-editor-undo-bar-close-button =
.title = Закрыць
pdfjs-editor-undo-bar-close-button-label = Закрыць
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = Гэты рэжым дазваляе карыстальніку ствараць подпіс для дадання ў дакумент PDF. Карыстальнік можа рэдагаваць імя (якое таксама служыць альтэрнатыўным тэкстам) і пры жаданні захаваць подпіс для паўторнага выкарыстання.
pdfjs-editor-add-signature-dialog-title = Дадаць подпіс
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Увод
.title = Увод
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Маляваць
.title = Маляваць
pdfjs-editor-add-signature-image-button = Выява
.title = Выява
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Увядзіце свой подпіс
.placeholder = Увядзіце свой подпіс
pdfjs-editor-add-signature-draw-placeholder = Намалюйце свой подпіс
pdfjs-editor-add-signature-draw-thickness-range-label = Таўшчыня
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Таўшчыня малюнка: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Перацягнуць файл сюды, каб загрузіць
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Або праглядайце файлы малюнкаў
*[other] Або праглядайце файлы малюнкаў
}
## Controls
pdfjs-editor-add-signature-description-label = Апісанне (альтэрнатыўны тэкст)
pdfjs-editor-add-signature-description-input =
.title = Апісанне (альтэрнатыўны тэкст)
pdfjs-editor-add-signature-description-default-when-drawing = Подпіс
pdfjs-editor-add-signature-clear-button-label = Выдаліць подпіс
pdfjs-editor-add-signature-clear-button =
.title = Выдаліць подпіс
pdfjs-editor-add-signature-save-checkbox = Захаваць подпіс
pdfjs-editor-add-signature-save-warning-message = Вы дасягнулі ліміту ў 5 захаваных подпісаў. Выдаліце адзін, каб захаваць іншы.
pdfjs-editor-add-signature-image-upload-error-title = Не ўдалося загрузіць выяву
pdfjs-editor-add-signature-image-upload-error-description = Праверце падключэнне да сеткі ці паспрабуйце іншую выяву.
pdfjs-editor-add-signature-error-close-button = Закрыць
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Скасаваць
pdfjs-editor-add-signature-add-button = Дадаць
pdfjs-editor-edit-signature-update-button = Абнавіць
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Выдаліць подпіс
pdfjs-editor-delete-signature-button-label = Выдаліць подпіс
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Рэдагаваць апісанне
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Рэдагаваць апісанне
================================================
FILE: cookbook/static/pdfjs/web/locale/bg/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Предишна страница
pdfjs-previous-button-label = Предишна
pdfjs-next-button =
.title = Следваща страница
pdfjs-next-button-label = Следваща
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Страница
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = от { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } от { $pagesCount })
pdfjs-zoom-out-button =
.title = Намаляване
pdfjs-zoom-out-button-label = Намаляване
pdfjs-zoom-in-button =
.title = Увеличаване
pdfjs-zoom-in-button-label = Увеличаване
pdfjs-zoom-select =
.title = Мащабиране
pdfjs-presentation-mode-button =
.title = Превключване към режим на представяне
pdfjs-presentation-mode-button-label = Режим на представяне
pdfjs-open-file-button =
.title = Отваряне на файл
pdfjs-open-file-button-label = Отваряне
pdfjs-print-button =
.title = Отпечатване
pdfjs-print-button-label = Отпечатване
pdfjs-save-button =
.title = Запазване
pdfjs-save-button-label = Запазване
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Изтегляне
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Изтегляне
pdfjs-bookmark-button =
.title = Текуща страница (преглед на адреса на страницата)
pdfjs-bookmark-button-label = Текуща страница
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Инструменти
pdfjs-tools-button-label = Инструменти
pdfjs-first-page-button =
.title = Към първата страница
pdfjs-first-page-button-label = Към първата страница
pdfjs-last-page-button =
.title = Към последната страница
pdfjs-last-page-button-label = Към последната страница
pdfjs-page-rotate-cw-button =
.title = Завъртане по час. стрелка
pdfjs-page-rotate-cw-button-label = Завъртане по часовниковата стрелка
pdfjs-page-rotate-ccw-button =
.title = Завъртане обратно на час. стрелка
pdfjs-page-rotate-ccw-button-label = Завъртане обратно на часовниковата стрелка
pdfjs-cursor-text-select-tool-button =
.title = Включване на инструмента за избор на текст
pdfjs-cursor-text-select-tool-button-label = Инструмент за избор на текст
pdfjs-cursor-hand-tool-button =
.title = Включване на инструмента ръка
pdfjs-cursor-hand-tool-button-label = Инструмент ръка
pdfjs-scroll-page-button =
.title = Използване на плъзгане на страници
pdfjs-scroll-page-button-label = Плъзгане на страници
pdfjs-scroll-vertical-button =
.title = Използване на вертикално плъзгане
pdfjs-scroll-vertical-button-label = Вертикално плъзгане
pdfjs-scroll-horizontal-button =
.title = Използване на хоризонтално
pdfjs-scroll-horizontal-button-label = Хоризонтално плъзгане
pdfjs-scroll-wrapped-button =
.title = Използване на мащабируемо плъзгане
pdfjs-scroll-wrapped-button-label = Мащабируемо плъзгане
pdfjs-spread-none-button =
.title = Режимът на сдвояване е изключен
pdfjs-spread-none-button-label = Без сдвояване
pdfjs-spread-odd-button =
.title = Сдвояване, започвайки от нечетните страници
pdfjs-spread-odd-button-label = Нечетните отляво
pdfjs-spread-even-button =
.title = Сдвояване, започвайки от четните страници
pdfjs-spread-even-button-label = Четните отляво
## Document properties dialog
pdfjs-document-properties-button =
.title = Свойства на документа…
pdfjs-document-properties-button-label = Свойства на документа…
pdfjs-document-properties-file-name = Име на файл:
pdfjs-document-properties-file-size = Големина на файл:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } КБ ({ $b } байта)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } МБ ({ $b } байта)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } КБ ({ $size_b } байта)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } МБ ({ $size_b } байта)
pdfjs-document-properties-title = Заглавие:
pdfjs-document-properties-author = Автор:
pdfjs-document-properties-subject = Тема:
pdfjs-document-properties-keywords = Ключови думи:
pdfjs-document-properties-creation-date = Дата на създаване:
pdfjs-document-properties-modification-date = Дата на промяна:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Създател:
pdfjs-document-properties-producer = PDF произведен от:
pdfjs-document-properties-version = Издание на PDF:
pdfjs-document-properties-page-count = Брой страници:
pdfjs-document-properties-page-size = Размер на страницата:
pdfjs-document-properties-page-size-unit-inches = инч
pdfjs-document-properties-page-size-unit-millimeters = мм
pdfjs-document-properties-page-size-orientation-portrait = портрет
pdfjs-document-properties-page-size-orientation-landscape = пейзаж
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letter
pdfjs-document-properties-page-size-name-legal = Правни въпроси
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Бърз преглед:
pdfjs-document-properties-linearized-yes = Да
pdfjs-document-properties-linearized-no = Не
pdfjs-document-properties-close-button = Затваряне
## Print
pdfjs-print-progress-message = Подготвяне на документа за отпечатване…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Отказ
pdfjs-printing-not-supported = Внимание: Този четец няма пълна поддръжка на отпечатване.
pdfjs-printing-not-ready = Внимание: Този PDF файл не е напълно зареден за печат.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Превключване на страничната лента
pdfjs-toggle-sidebar-notification-button =
.title = Превключване на страничната лента (документът има структура/прикачени файлове/слоеве)
pdfjs-toggle-sidebar-button-label = Превключване на страничната лента
pdfjs-document-outline-button =
.title = Показване на структурата на документа (двукратно щракване за свиване/разгъване на всичко)
pdfjs-document-outline-button-label = Структура на документа
pdfjs-attachments-button =
.title = Показване на притурките
pdfjs-attachments-button-label = Притурки
pdfjs-layers-button =
.title = Показване на слоевете (двукратно щракване за възстановяване на всички слоеве към състоянието по подразбиране)
pdfjs-layers-button-label = Слоеве
pdfjs-thumbs-button =
.title = Показване на миниатюрите
pdfjs-thumbs-button-label = Миниатюри
pdfjs-current-outline-item-button =
.title = Намиране на текущия елемент от структурата
pdfjs-current-outline-item-button-label = Текущ елемент от структурата
pdfjs-findbar-button =
.title = Намиране в документа
pdfjs-findbar-button-label = Търсене
pdfjs-additional-layers = Допълнителни слоеве
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Страница { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Миниатюра на страница { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Търсене
.placeholder = Търсене в документа…
pdfjs-find-previous-button =
.title = Намиране на предишно съвпадение на фразата
pdfjs-find-previous-button-label = Предишна
pdfjs-find-next-button =
.title = Намиране на следващо съвпадение на фразата
pdfjs-find-next-button-label = Следваща
pdfjs-find-highlight-checkbox = Открояване на всички
pdfjs-find-match-case-checkbox-label = Съвпадение на регистъра
pdfjs-find-match-diacritics-checkbox-label = Без производни букви
pdfjs-find-entire-word-checkbox-label = Цели думи
pdfjs-find-reached-top = Достигнато е началото на документа, продължаване от края
pdfjs-find-reached-bottom = Достигнат е краят на документа, продължаване от началото
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } от { $total } съвпадение
*[other] { $current } от { $total } съвпадения
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Повече от { $limit } съвпадение
*[other] Повече от { $limit } съвпадения
}
pdfjs-find-not-found = Фразата не е намерена
## Predefined zoom values
pdfjs-page-scale-width = Ширина на страницата
pdfjs-page-scale-fit = Вместване в страницата
pdfjs-page-scale-auto = Автоматично мащабиране
pdfjs-page-scale-actual = Действителен размер
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Страница { $page }
## Loading indicator messages
pdfjs-loading-error = Получи се грешка при зареждане на PDF-а.
pdfjs-invalid-file-error = Невалиден или повреден PDF файл.
pdfjs-missing-file-error = Липсващ PDF файл.
pdfjs-unexpected-response-error = Неочакван отговор от сървъра.
pdfjs-rendering-error = Грешка при изчертаване на страницата.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [Анотация { $type }]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Въведете парола за отваряне на този PDF файл.
pdfjs-password-invalid = Невалидна парола. Моля, опитайте отново.
pdfjs-password-ok-button = Добре
pdfjs-password-cancel-button = Отказ
pdfjs-web-fonts-disabled = Уеб-шрифтовете са забранени: разрешаване на използването на вградените PDF шрифтове.
## Editing
pdfjs-editor-free-text-button =
.title = Текст
pdfjs-editor-free-text-button-label = Текст
pdfjs-editor-ink-button =
.title = Рисуване
pdfjs-editor-ink-button-label = Рисуване
pdfjs-editor-stamp-button =
.title = Добавяне или променяне на изображения
pdfjs-editor-stamp-button-label = Добавяне или променяне на изображения
## Default editor aria labels
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Премахване на рисунката
pdfjs-editor-remove-freetext-button =
.title = Премахване на текста
pdfjs-editor-remove-stamp-button =
.title = Пермахване на изображението
pdfjs-editor-remove-highlight-button =
.title = Премахване на открояването
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Цвят
pdfjs-editor-free-text-size-input = Размер
pdfjs-editor-ink-color-input = Цвят
pdfjs-editor-ink-thickness-input = Дебелина
pdfjs-editor-ink-opacity-input = Прозрачност
pdfjs-editor-stamp-add-image-button =
.title = Добавяне на изображение
pdfjs-editor-stamp-add-image-button-label = Добавяне на изображение
pdfjs-free-text =
.aria-label = Текстов редактор
pdfjs-free-text-default-content = Започнете да пишете…
pdfjs-ink =
.aria-label = Промяна на рисунка
pdfjs-ink-canvas =
.aria-label = Изображение, създадено от потребител
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Алтернативен текст
pdfjs-editor-alt-text-edit-button-label = Промяна на алтернативния текст
pdfjs-editor-alt-text-dialog-label = Изберете от възможностите
pdfjs-editor-alt-text-dialog-description = Алтернативният текст помага на потребителите, когато не могат да видят изображението или то не се зарежда.
pdfjs-editor-alt-text-add-description-label = Добавяне на описание
pdfjs-editor-alt-text-add-description-description = Стремете се към 1-2 изречения, описващи предмета, настройката или действията.
pdfjs-editor-alt-text-mark-decorative-label = Отбелязване като декоративно
pdfjs-editor-alt-text-mark-decorative-description = Използва се за орнаменти или декоративни изображения, като контури и водни знаци.
pdfjs-editor-alt-text-cancel-button = Отказ
pdfjs-editor-alt-text-save-button = Запазване
pdfjs-editor-alt-text-decorative-tooltip = Отбелязване като декоративно
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Например, „Млад мъж седи на маса и се храни“
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Горен ляв ъгъл — преоразмеряване
pdfjs-editor-resizer-label-top-middle = Горе в средата — преоразмеряване
pdfjs-editor-resizer-label-top-right = Горен десен ъгъл — преоразмеряване
pdfjs-editor-resizer-label-middle-right = Дясно в средата — преоразмеряване
pdfjs-editor-resizer-label-bottom-right = Долен десен ъгъл — преоразмеряване
pdfjs-editor-resizer-label-bottom-middle = Долу в средата — преоразмеряване
pdfjs-editor-resizer-label-bottom-left = Долен ляв ъгъл — преоразмеряване
pdfjs-editor-resizer-label-middle-left = Ляво в средата — преоразмеряване
pdfjs-editor-resizer-top-left =
.aria-label = Горен ляв ъгъл — преоразмеряване
pdfjs-editor-resizer-top-middle =
.aria-label = Горе в средата — преоразмеряване
pdfjs-editor-resizer-top-right =
.aria-label = Горен десен ъгъл — преоразмеряване
pdfjs-editor-resizer-middle-right =
.aria-label = Дясно в средата — преоразмеряване
pdfjs-editor-resizer-bottom-right =
.aria-label = Долен десен ъгъл — преоразмеряване
pdfjs-editor-resizer-bottom-middle =
.aria-label = Долу в средата — преоразмеряване
pdfjs-editor-resizer-bottom-left =
.aria-label = Долен ляв ъгъл — преоразмеряване
pdfjs-editor-resizer-middle-left =
.aria-label = Ляво в средата — преоразмеряване
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Цвят на открояване
pdfjs-editor-colorpicker-button =
.title = Промяна на цвят
pdfjs-editor-colorpicker-dropdown =
.aria-label = Избор на цвят
pdfjs-editor-colorpicker-yellow =
.title = Жълто
pdfjs-editor-colorpicker-green =
.title = Зелено
pdfjs-editor-colorpicker-blue =
.title = Синьо
pdfjs-editor-colorpicker-pink =
.title = Розово
pdfjs-editor-colorpicker-red =
.title = Червено
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
pdfjs-editor-new-alt-text-not-now-button = Не сега
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/bn/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = পূর্ববর্তী পাতা
pdfjs-previous-button-label = পূর্ববর্তী
pdfjs-next-button =
.title = পরবর্তী পাতা
pdfjs-next-button-label = পরবর্তী
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = পাতা
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = { $pagesCount } এর
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pagesCount } এর { $pageNumber })
pdfjs-zoom-out-button =
.title = ছোট আকারে প্রদর্শন
pdfjs-zoom-out-button-label = ছোট আকারে প্রদর্শন
pdfjs-zoom-in-button =
.title = বড় আকারে প্রদর্শন
pdfjs-zoom-in-button-label = বড় আকারে প্রদর্শন
pdfjs-zoom-select =
.title = বড় আকারে প্রদর্শন
pdfjs-presentation-mode-button =
.title = উপস্থাপনা মোডে স্যুইচ করুন
pdfjs-presentation-mode-button-label = উপস্থাপনা মোড
pdfjs-open-file-button =
.title = ফাইল খুলুন
pdfjs-open-file-button-label = খুলুন
pdfjs-print-button =
.title = মুদ্রণ
pdfjs-print-button-label = মুদ্রণ
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = টুল
pdfjs-tools-button-label = টুল
pdfjs-first-page-button =
.title = প্রথম পাতায় যাও
pdfjs-first-page-button-label = প্রথম পাতায় যাও
pdfjs-last-page-button =
.title = শেষ পাতায় যাও
pdfjs-last-page-button-label = শেষ পাতায় যাও
pdfjs-page-rotate-cw-button =
.title = ঘড়ির কাঁটার দিকে ঘোরাও
pdfjs-page-rotate-cw-button-label = ঘড়ির কাঁটার দিকে ঘোরাও
pdfjs-page-rotate-ccw-button =
.title = ঘড়ির কাঁটার বিপরীতে ঘোরাও
pdfjs-page-rotate-ccw-button-label = ঘড়ির কাঁটার বিপরীতে ঘোরাও
pdfjs-cursor-text-select-tool-button =
.title = লেখা নির্বাচক টুল সক্রিয় করুন
pdfjs-cursor-text-select-tool-button-label = লেখা নির্বাচক টুল
pdfjs-cursor-hand-tool-button =
.title = হ্যান্ড টুল সক্রিয় করুন
pdfjs-cursor-hand-tool-button-label = হ্যান্ড টুল
pdfjs-scroll-vertical-button =
.title = উলম্ব স্ক্রলিং ব্যবহার করুন
pdfjs-scroll-vertical-button-label = উলম্ব স্ক্রলিং
pdfjs-scroll-horizontal-button =
.title = অনুভূমিক স্ক্রলিং ব্যবহার করুন
pdfjs-scroll-horizontal-button-label = অনুভূমিক স্ক্রলিং
pdfjs-scroll-wrapped-button =
.title = Wrapped স্ক্রোলিং ব্যবহার করুন
pdfjs-scroll-wrapped-button-label = Wrapped স্ক্রোলিং
pdfjs-spread-none-button =
.title = পেজ স্প্রেডগুলোতে যোগদান করবেন না
pdfjs-spread-none-button-label = Spreads নেই
pdfjs-spread-odd-button-label = বিজোড় Spreads
pdfjs-spread-even-button-label = জোড় Spreads
## Document properties dialog
pdfjs-document-properties-button =
.title = নথি বৈশিষ্ট্য…
pdfjs-document-properties-button-label = নথি বৈশিষ্ট্য…
pdfjs-document-properties-file-name = ফাইলের নাম:
pdfjs-document-properties-file-size = ফাইলের আকার:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } কেবি ({ $size_b } বাইট)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } এমবি ({ $size_b } বাইট)
pdfjs-document-properties-title = শিরোনাম:
pdfjs-document-properties-author = লেখক:
pdfjs-document-properties-subject = বিষয়:
pdfjs-document-properties-keywords = কীওয়ার্ড:
pdfjs-document-properties-creation-date = তৈরির তারিখ:
pdfjs-document-properties-modification-date = পরিবর্তনের তারিখ:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = প্রস্তুতকারক:
pdfjs-document-properties-producer = পিডিএফ প্রস্তুতকারক:
pdfjs-document-properties-version = পিডিএফ সংষ্করণ:
pdfjs-document-properties-page-count = মোট পাতা:
pdfjs-document-properties-page-size = পাতার সাইজ:
pdfjs-document-properties-page-size-unit-inches = এর মধ্যে
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = উলম্ব
pdfjs-document-properties-page-size-orientation-landscape = অনুভূমিক
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = লেটার
pdfjs-document-properties-page-size-name-legal = লীগাল
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Fast Web View:
pdfjs-document-properties-linearized-yes = হ্যাঁ
pdfjs-document-properties-linearized-no = না
pdfjs-document-properties-close-button = বন্ধ
## Print
pdfjs-print-progress-message = মুদ্রণের জন্য নথি প্রস্তুত করা হচ্ছে…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = বাতিল
pdfjs-printing-not-supported = সতর্কতা: এই ব্রাউজারে মুদ্রণ সম্পূর্ণভাবে সমর্থিত নয়।
pdfjs-printing-not-ready = সতর্কীকরণ: পিডিএফটি মুদ্রণের জন্য সম্পূর্ণ লোড হয়নি।
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = সাইডবার টগল করুন
pdfjs-toggle-sidebar-button-label = সাইডবার টগল করুন
pdfjs-document-outline-button =
.title = নথির আউটলাইন দেখাও (সব আইটেম প্রসারিত/সঙ্কুচিত করতে ডবল ক্লিক করুন)
pdfjs-document-outline-button-label = নথির রূপরেখা
pdfjs-attachments-button =
.title = সংযুক্তি দেখাও
pdfjs-attachments-button-label = সংযুক্তি
pdfjs-thumbs-button =
.title = থাম্বনেইল সমূহ প্রদর্শন করুন
pdfjs-thumbs-button-label = থাম্বনেইল সমূহ
pdfjs-findbar-button =
.title = নথির মধ্যে খুঁজুন
pdfjs-findbar-button-label = খুঁজুন
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = পাতা { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = { $page } পাতার থাম্বনেইল
## Find panel button title and messages
pdfjs-find-input =
.title = খুঁজুন
.placeholder = নথির মধ্যে খুঁজুন…
pdfjs-find-previous-button =
.title = বাক্যাংশের পূর্ববর্তী উপস্থিতি অনুসন্ধান
pdfjs-find-previous-button-label = পূর্ববর্তী
pdfjs-find-next-button =
.title = বাক্যাংশের পরবর্তী উপস্থিতি অনুসন্ধান
pdfjs-find-next-button-label = পরবর্তী
pdfjs-find-highlight-checkbox = সব হাইলাইট করুন
pdfjs-find-match-case-checkbox-label = অক্ষরের ছাঁদ মেলানো
pdfjs-find-entire-word-checkbox-label = সম্পূর্ণ শব্দ
pdfjs-find-reached-top = পাতার শুরুতে পৌছে গেছে, নীচ থেকে আরম্ভ করা হয়েছে
pdfjs-find-reached-bottom = পাতার শেষে পৌছে গেছে, উপর থেকে আরম্ভ করা হয়েছে
pdfjs-find-not-found = বাক্যাংশ পাওয়া যায়নি
## Predefined zoom values
pdfjs-page-scale-width = পাতার প্রস্থ
pdfjs-page-scale-fit = পাতা ফিট করুন
pdfjs-page-scale-auto = স্বয়ংক্রিয় জুম
pdfjs-page-scale-actual = প্রকৃত আকার
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
## Loading indicator messages
pdfjs-loading-error = পিডিএফ লোড করার সময় ত্রুটি দেখা দিয়েছে।
pdfjs-invalid-file-error = অকার্যকর অথবা ক্ষতিগ্রস্ত পিডিএফ ফাইল।
pdfjs-missing-file-error = নিখোঁজ PDF ফাইল।
pdfjs-unexpected-response-error = অপ্রত্যাশীত সার্ভার প্রতিক্রিয়া।
pdfjs-rendering-error = পাতা উপস্থাপনার সময় ত্রুটি দেখা দিয়েছে।
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } টীকা]
## Password
pdfjs-password-label = পিডিএফ ফাইলটি ওপেন করতে পাসওয়ার্ড দিন।
pdfjs-password-invalid = ভুল পাসওয়ার্ড। অনুগ্রহ করে আবার চেষ্টা করুন।
pdfjs-password-ok-button = ঠিক আছে
pdfjs-password-cancel-button = বাতিল
pdfjs-web-fonts-disabled = ওয়েব ফন্ট নিষ্ক্রিয়: সংযুক্ত পিডিএফ ফন্ট ব্যবহার করা যাচ্ছে না।
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/bo/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = དྲ་ངོས་སྔོན་མ
pdfjs-previous-button-label = སྔོན་མ
pdfjs-next-button =
.title = དྲ་ངོས་རྗེས་མ
pdfjs-next-button-label = རྗེས་མ
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = ཤོག་ངོས
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = of { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } of { $pagesCount })
pdfjs-zoom-out-button =
.title = Zoom Out
pdfjs-zoom-out-button-label = Zoom Out
pdfjs-zoom-in-button =
.title = Zoom In
pdfjs-zoom-in-button-label = Zoom In
pdfjs-zoom-select =
.title = Zoom
pdfjs-presentation-mode-button =
.title = Switch to Presentation Mode
pdfjs-presentation-mode-button-label = Presentation Mode
pdfjs-open-file-button =
.title = Open File
pdfjs-open-file-button-label = Open
pdfjs-print-button =
.title = Print
pdfjs-print-button-label = Print
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Tools
pdfjs-tools-button-label = Tools
pdfjs-first-page-button =
.title = Go to First Page
pdfjs-first-page-button-label = Go to First Page
pdfjs-last-page-button =
.title = Go to Last Page
pdfjs-last-page-button-label = Go to Last Page
pdfjs-page-rotate-cw-button =
.title = Rotate Clockwise
pdfjs-page-rotate-cw-button-label = Rotate Clockwise
pdfjs-page-rotate-ccw-button =
.title = Rotate Counterclockwise
pdfjs-page-rotate-ccw-button-label = Rotate Counterclockwise
pdfjs-cursor-text-select-tool-button =
.title = Enable Text Selection Tool
pdfjs-cursor-text-select-tool-button-label = Text Selection Tool
pdfjs-cursor-hand-tool-button =
.title = Enable Hand Tool
pdfjs-cursor-hand-tool-button-label = Hand Tool
pdfjs-scroll-vertical-button =
.title = Use Vertical Scrolling
pdfjs-scroll-vertical-button-label = Vertical Scrolling
pdfjs-scroll-horizontal-button =
.title = Use Horizontal Scrolling
pdfjs-scroll-horizontal-button-label = Horizontal Scrolling
pdfjs-scroll-wrapped-button =
.title = Use Wrapped Scrolling
pdfjs-scroll-wrapped-button-label = Wrapped Scrolling
pdfjs-spread-none-button =
.title = Do not join page spreads
pdfjs-spread-none-button-label = No Spreads
pdfjs-spread-odd-button =
.title = Join page spreads starting with odd-numbered pages
pdfjs-spread-odd-button-label = Odd Spreads
pdfjs-spread-even-button =
.title = Join page spreads starting with even-numbered pages
pdfjs-spread-even-button-label = Even Spreads
## Document properties dialog
pdfjs-document-properties-button =
.title = Document Properties…
pdfjs-document-properties-button-label = Document Properties…
pdfjs-document-properties-file-name = File name:
pdfjs-document-properties-file-size = File size:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = Title:
pdfjs-document-properties-author = Author:
pdfjs-document-properties-subject = Subject:
pdfjs-document-properties-keywords = Keywords:
pdfjs-document-properties-creation-date = Creation Date:
pdfjs-document-properties-modification-date = Modification Date:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Creator:
pdfjs-document-properties-producer = PDF Producer:
pdfjs-document-properties-version = PDF Version:
pdfjs-document-properties-page-count = Page Count:
pdfjs-document-properties-page-size = Page Size:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = portrait
pdfjs-document-properties-page-size-orientation-landscape = landscape
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letter
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Fast Web View:
pdfjs-document-properties-linearized-yes = Yes
pdfjs-document-properties-linearized-no = No
pdfjs-document-properties-close-button = Close
## Print
pdfjs-print-progress-message = Preparing document for printing…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Cancel
pdfjs-printing-not-supported = Warning: Printing is not fully supported by this browser.
pdfjs-printing-not-ready = Warning: The PDF is not fully loaded for printing.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Toggle Sidebar
pdfjs-toggle-sidebar-button-label = Toggle Sidebar
pdfjs-document-outline-button =
.title = Show Document Outline (double-click to expand/collapse all items)
pdfjs-document-outline-button-label = Document Outline
pdfjs-attachments-button =
.title = Show Attachments
pdfjs-attachments-button-label = Attachments
pdfjs-thumbs-button =
.title = Show Thumbnails
pdfjs-thumbs-button-label = Thumbnails
pdfjs-findbar-button =
.title = Find in Document
pdfjs-findbar-button-label = Find
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Page { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Thumbnail of Page { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Find
.placeholder = Find in document…
pdfjs-find-previous-button =
.title = Find the previous occurrence of the phrase
pdfjs-find-previous-button-label = Previous
pdfjs-find-next-button =
.title = Find the next occurrence of the phrase
pdfjs-find-next-button-label = Next
pdfjs-find-highlight-checkbox = Highlight all
pdfjs-find-match-case-checkbox-label = Match case
pdfjs-find-entire-word-checkbox-label = Whole words
pdfjs-find-reached-top = Reached top of document, continued from bottom
pdfjs-find-reached-bottom = Reached end of document, continued from top
pdfjs-find-not-found = Phrase not found
## Predefined zoom values
pdfjs-page-scale-width = Page Width
pdfjs-page-scale-fit = Page Fit
pdfjs-page-scale-auto = Automatic Zoom
pdfjs-page-scale-actual = Actual Size
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
## Loading indicator messages
pdfjs-loading-error = An error occurred while loading the PDF.
pdfjs-invalid-file-error = Invalid or corrupted PDF file.
pdfjs-missing-file-error = Missing PDF file.
pdfjs-unexpected-response-error = Unexpected server response.
pdfjs-rendering-error = An error occurred while rendering the page.
## Annotations
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } Annotation]
## Password
pdfjs-password-label = Enter the password to open this PDF file.
pdfjs-password-invalid = Invalid password. Please try again.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Cancel
pdfjs-web-fonts-disabled = Web fonts are disabled: unable to use embedded PDF fonts.
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/br/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Pajenn a-raok
pdfjs-previous-button-label = A-raok
pdfjs-next-button =
.title = Pajenn war-lerc'h
pdfjs-next-button-label = War-lerc'h
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Pajenn
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = eus { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } war { $pagesCount })
pdfjs-zoom-out-button =
.title = Zoum bihanaat
pdfjs-zoom-out-button-label = Zoum bihanaat
pdfjs-zoom-in-button =
.title = Zoum brasaat
pdfjs-zoom-in-button-label = Zoum brasaat
pdfjs-zoom-select =
.title = Zoum
pdfjs-presentation-mode-button =
.title = Trec'haoliñ etrezek ar mod kinnigadenn
pdfjs-presentation-mode-button-label = Mod kinnigadenn
pdfjs-open-file-button =
.title = Digeriñ ur restr
pdfjs-open-file-button-label = Digeriñ ur restr
pdfjs-print-button =
.title = Moullañ
pdfjs-print-button-label = Moullañ
pdfjs-save-button =
.title = Enrollañ
pdfjs-save-button-label = Enrollañ
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Pellgargañ
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Pellgargañ
pdfjs-bookmark-button-label = Pajenn a-vremañ
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Ostilhoù
pdfjs-tools-button-label = Ostilhoù
pdfjs-first-page-button =
.title = Mont d'ar bajenn gentañ
pdfjs-first-page-button-label = Mont d'ar bajenn gentañ
pdfjs-last-page-button =
.title = Mont d'ar bajenn diwezhañ
pdfjs-last-page-button-label = Mont d'ar bajenn diwezhañ
pdfjs-page-rotate-cw-button =
.title = C'hwelañ gant roud ar bizied
pdfjs-page-rotate-cw-button-label = C'hwelañ gant roud ar bizied
pdfjs-page-rotate-ccw-button =
.title = C'hwelañ gant roud gin ar bizied
pdfjs-page-rotate-ccw-button-label = C'hwelañ gant roud gin ar bizied
pdfjs-cursor-text-select-tool-button =
.title = Gweredekaat an ostilh diuzañ testenn
pdfjs-cursor-text-select-tool-button-label = Ostilh diuzañ testenn
pdfjs-cursor-hand-tool-button =
.title = Gweredekaat an ostilh dorn
pdfjs-cursor-hand-tool-button-label = Ostilh dorn
pdfjs-scroll-vertical-button =
.title = Arverañ an dibunañ a-blom
pdfjs-scroll-vertical-button-label = Dibunañ a-serzh
pdfjs-scroll-horizontal-button =
.title = Arverañ an dibunañ a-blaen
pdfjs-scroll-horizontal-button-label = Dibunañ a-blaen
pdfjs-scroll-wrapped-button =
.title = Arverañ an dibunañ paket
pdfjs-scroll-wrapped-button-label = Dibunañ paket
pdfjs-spread-none-button =
.title = Chom hep stagañ ar skignadurioù
pdfjs-spread-none-button-label = Skignadenn ebet
pdfjs-spread-odd-button =
.title = Lakaat ar pajennadoù en ur gregiñ gant ar pajennoù ampar
pdfjs-spread-odd-button-label = Pajennoù ampar
pdfjs-spread-even-button =
.title = Lakaat ar pajennadoù en ur gregiñ gant ar pajennoù par
pdfjs-spread-even-button-label = Pajennoù par
## Document properties dialog
pdfjs-document-properties-button =
.title = Perzhioù an teul…
pdfjs-document-properties-button-label = Perzhioù an teul…
pdfjs-document-properties-file-name = Anv restr:
pdfjs-document-properties-file-size = Ment ar restr:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } Ke ({ $size_b } eizhbit)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } Me ({ $size_b } eizhbit)
pdfjs-document-properties-title = Titl:
pdfjs-document-properties-author = Aozer:
pdfjs-document-properties-subject = Danvez:
pdfjs-document-properties-keywords = Gerioù-alc'hwez:
pdfjs-document-properties-creation-date = Deiziad krouiñ:
pdfjs-document-properties-modification-date = Deiziad kemmañ:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Krouer:
pdfjs-document-properties-producer = Kenderc'her PDF:
pdfjs-document-properties-version = Handelv PDF:
pdfjs-document-properties-page-count = Niver a bajennoù:
pdfjs-document-properties-page-size = Ment ar bajenn:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = poltred
pdfjs-document-properties-page-size-orientation-landscape = gweledva
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Lizher
pdfjs-document-properties-page-size-name-legal = Lezennel
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Gwel Web Herrek:
pdfjs-document-properties-linearized-yes = Ya
pdfjs-document-properties-linearized-no = Ket
pdfjs-document-properties-close-button = Serriñ
## Print
pdfjs-print-progress-message = O prientiñ an teul evit moullañ...
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Nullañ
pdfjs-printing-not-supported = Kemenn: N'eo ket skoret penn-da-benn ar moullañ gant ar merdeer-mañ.
pdfjs-printing-not-ready = Kemenn: N'hall ket bezañ moullet ar restr PDF rak n'eo ket karget penn-da-benn.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Diskouez/kuzhat ar varrenn gostez
pdfjs-toggle-sidebar-notification-button =
.title = Trec'haoliñ ar varrenn-gostez (ur steuñv pe stagadennoù a zo en teul)
pdfjs-toggle-sidebar-button-label = Diskouez/kuzhat ar varrenn gostez
pdfjs-document-outline-button =
.title = Diskouez steuñv an teul (daouglikit evit brasaat/bihanaat an holl elfennoù)
pdfjs-document-outline-button-label = Sinedoù an teuliad
pdfjs-attachments-button =
.title = Diskouez ar c'henstagadurioù
pdfjs-attachments-button-label = Kenstagadurioù
pdfjs-layers-button =
.title = Diskouez ar gwiskadoù (daou-glikañ evit adderaouekaat an holl gwiskadoù d'o stad dre ziouer)
pdfjs-layers-button-label = Gwiskadoù
pdfjs-thumbs-button =
.title = Diskouez ar melvennoù
pdfjs-thumbs-button-label = Melvennoù
pdfjs-findbar-button =
.title = Klask e-barzh an teuliad
pdfjs-findbar-button-label = Klask
pdfjs-additional-layers = Gwiskadoù ouzhpenn
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Pajenn { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Melvenn ar bajenn { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Klask
.placeholder = Klask e-barzh an teuliad
pdfjs-find-previous-button =
.title = Kavout an tamm frazenn kent o klotañ ganti
pdfjs-find-previous-button-label = Kent
pdfjs-find-next-button =
.title = Kavout an tamm frazenn war-lerc'h o klotañ ganti
pdfjs-find-next-button-label = War-lerc'h
pdfjs-find-highlight-checkbox = Usskediñ pep tra
pdfjs-find-match-case-checkbox-label = Teurel evezh ouzh ar pennlizherennoù
pdfjs-find-match-diacritics-checkbox-label = Doujañ d’an tiredoù
pdfjs-find-entire-word-checkbox-label = Gerioù a-bezh
pdfjs-find-reached-top = Tizhet eo bet derou ar bajenn, kenderc'hel diouzh an diaz
pdfjs-find-reached-bottom = Tizhet eo bet dibenn ar bajenn, kenderc'hel diouzh ar c'hrec'h
pdfjs-find-not-found = N'haller ket kavout ar frazenn
## Predefined zoom values
pdfjs-page-scale-width = Led ar bajenn
pdfjs-page-scale-fit = Pajenn a-bezh
pdfjs-page-scale-auto = Zoum emgefreek
pdfjs-page-scale-actual = Ment wir
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Pajenn { $page }
## Loading indicator messages
pdfjs-loading-error = Degouezhet ez eus bet ur fazi e-pad kargañ ar PDF.
pdfjs-invalid-file-error = Restr PDF didalvoudek pe kontronet.
pdfjs-missing-file-error = Restr PDF o vankout.
pdfjs-unexpected-response-error = Respont dic'hortoz a-berzh an dafariad
pdfjs-rendering-error = Degouezhet ez eus bet ur fazi e-pad skrammañ ar bajennad.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } Notennañ]
## Password
pdfjs-password-label = Enankit ar ger-tremen evit digeriñ ar restr PDF-mañ.
pdfjs-password-invalid = Ger-tremen didalvoudek. Klaskit en-dro mar plij.
pdfjs-password-ok-button = Mat eo
pdfjs-password-cancel-button = Nullañ
pdfjs-web-fonts-disabled = Diweredekaet eo an nodrezhoù web: n'haller ket arverañ an nodrezhoù PDF enframmet.
## Editing
pdfjs-editor-free-text-button =
.title = Testenn
pdfjs-editor-free-text-button-label = Testenn
pdfjs-editor-ink-button =
.title = Tresañ
pdfjs-editor-ink-button-label = Tresañ
pdfjs-editor-stamp-button =
.title = Ouzhpennañ pe aozañ skeudennoù
pdfjs-editor-stamp-button-label = Ouzhpennañ pe aozañ skeudennoù
## Default editor aria labels
## Remove button for the various kind of editor.
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Liv
pdfjs-editor-free-text-size-input = Ment
pdfjs-editor-ink-color-input = Liv
pdfjs-editor-ink-thickness-input = Tevder
pdfjs-editor-ink-opacity-input = Boullder
pdfjs-editor-stamp-add-image-button =
.title = Ouzhpennañ ur skeudenn
pdfjs-editor-stamp-add-image-button-label = Ouzhpennañ ur skeudenn
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Tevded
pdfjs-free-text =
.aria-label = Aozer testennoù
pdfjs-ink =
.aria-label = Aozer tresoù
pdfjs-ink-canvas =
.aria-label = Skeudenn bet krouet gant an implijer·ez
## Alt-text dialog
pdfjs-editor-alt-text-add-description-label = Ouzhpennañ un deskrivadur
pdfjs-editor-alt-text-cancel-button = Nullañ
pdfjs-editor-alt-text-save-button = Enrollañ
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
pdfjs-editor-colorpicker-button =
.title = Cheñch liv
pdfjs-editor-colorpicker-yellow =
.title = Melen
pdfjs-editor-colorpicker-blue =
.title = Glas
pdfjs-editor-colorpicker-pink =
.title = Roz
pdfjs-editor-colorpicker-red =
.title = Ruz
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Diskouez pep tra
pdfjs-editor-highlight-show-all-button =
.title = Diskouez pep tra
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Gouzout hiroc’h
pdfjs-editor-new-alt-text-error-close-button = Serriñ
## Image alt-text settings
pdfjs-editor-alt-text-settings-delete-model-button = Dilemel
pdfjs-editor-alt-text-settings-download-model-button = Pellgargañ
pdfjs-editor-alt-text-settings-downloading-model-button = O pellgargañ…
pdfjs-editor-alt-text-settings-close-button = Serriñ
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/brx/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = आगोलनि बिलाइ
pdfjs-previous-button-label = आगोलनि
pdfjs-next-button =
.title = उननि बिलाइ
pdfjs-next-button-label = उननि
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = बिलाइ
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = { $pagesCount } नि
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pagesCount } नि { $pageNumber })
pdfjs-zoom-out-button =
.title = फिसायै जुम खालाम
pdfjs-zoom-out-button-label = फिसायै जुम खालाम
pdfjs-zoom-in-button =
.title = गेदेरै जुम खालाम
pdfjs-zoom-in-button-label = गेदेरै जुम खालाम
pdfjs-zoom-select =
.title = जुम खालाम
pdfjs-presentation-mode-button =
.title = दिन्थिफुंनाय म'डआव थां
pdfjs-presentation-mode-button-label = दिन्थिफुंनाय म'ड
pdfjs-open-file-button =
.title = फाइलखौ खेव
pdfjs-open-file-button-label = खेव
pdfjs-print-button =
.title = साफाय
pdfjs-print-button-label = साफाय
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = टुल
pdfjs-tools-button-label = टुल
pdfjs-first-page-button =
.title = गिबि बिलाइआव थां
pdfjs-first-page-button-label = गिबि बिलाइआव थां
pdfjs-last-page-button =
.title = जोबथा बिलाइआव थां
pdfjs-last-page-button-label = जोबथा बिलाइआव थां
pdfjs-page-rotate-cw-button =
.title = घरि गिदिंनाय फार्से फिदिं
pdfjs-page-rotate-cw-button-label = घरि गिदिंनाय फार्से फिदिं
pdfjs-page-rotate-ccw-button =
.title = घरि गिदिंनाय उल्था फार्से फिदिं
pdfjs-page-rotate-ccw-button-label = घरि गिदिंनाय उल्था फार्से फिदिं
## Document properties dialog
pdfjs-document-properties-button =
.title = फोरमान बिलाइनि आखुथाय...
pdfjs-document-properties-button-label = फोरमान बिलाइनि आखुथाय...
pdfjs-document-properties-file-name = फाइलनि मुं:
pdfjs-document-properties-file-size = फाइलनि महर:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } बाइट)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } बाइट)
pdfjs-document-properties-title = बिमुं:
pdfjs-document-properties-author = लिरगिरि:
pdfjs-document-properties-subject = आयदा:
pdfjs-document-properties-keywords = गाहाय सोदोब:
pdfjs-document-properties-creation-date = सोरजिनाय अक्ट':
pdfjs-document-properties-modification-date = सुद्रायनाय अक्ट':
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = सोरजिग्रा:
pdfjs-document-properties-producer = PDF दिहुनग्रा:
pdfjs-document-properties-version = PDF बिसान:
pdfjs-document-properties-page-count = बिलाइनि हिसाब:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = प'र्ट्रेट
pdfjs-document-properties-page-size-orientation-landscape = लेण्डस्केप
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = लायजाम
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
pdfjs-document-properties-linearized-yes = नंगौ
pdfjs-document-properties-linearized-no = नङा
pdfjs-document-properties-close-button = बन्द खालाम
## Print
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = नेवसि
pdfjs-printing-not-supported = सांग्रांथि: साफायनाया बे ब्राउजारजों आबुङै हेफाजाब होजाया।
pdfjs-printing-not-ready = सांग्रांथि: PDF खौ साफायनायनि थाखाय फुरायै ल'ड खालामाखै।
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = टग्गल साइडबार
pdfjs-toggle-sidebar-button-label = टग्गल साइडबार
pdfjs-document-outline-button-label = फोरमान बिलाइ सिमा हांखो
pdfjs-attachments-button =
.title = नांजाब होनायखौ दिन्थि
pdfjs-attachments-button-label = नांजाब होनाय
pdfjs-thumbs-button =
.title = थामनेइलखौ दिन्थि
pdfjs-thumbs-button-label = थामनेइल
pdfjs-findbar-button =
.title = फोरमान बिलाइआव नागिरना दिहुन
pdfjs-findbar-button-label = नायगिरना दिहुन
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = बिलाइ { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = बिलाइ { $page } नि थामनेइल
## Find panel button title and messages
pdfjs-find-input =
.title = नायगिरना दिहुन
.placeholder = फोरमान बिलाइआव नागिरना दिहुन...
pdfjs-find-previous-button =
.title = बाथ्रा खोन्दोबनि सिगांनि नुजाथिनायखौ नागिर
pdfjs-find-previous-button-label = आगोलनि
pdfjs-find-next-button =
.title = बाथ्रा खोन्दोबनि उननि नुजाथिनायखौ नागिर
pdfjs-find-next-button-label = उननि
pdfjs-find-highlight-checkbox = गासैखौबो हाइलाइट खालाम
pdfjs-find-match-case-checkbox-label = गोरोबनाय केस
pdfjs-find-reached-top = थालो निफ्राय जागायनानै फोरमान बिलाइनि बिजौआव सौहैबाय
pdfjs-find-reached-bottom = बिजौ निफ्राय जागायनानै फोरमान बिलाइनि बिजौआव सौहैबाय
pdfjs-find-not-found = बाथ्रा खोन्दोब मोनाखै
## Predefined zoom values
pdfjs-page-scale-width = बिलाइनि गुवार
pdfjs-page-scale-fit = बिलाइ गोरोबनाय
pdfjs-page-scale-auto = गावनोगाव जुम
pdfjs-page-scale-actual = थार महर
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
## Loading indicator messages
pdfjs-loading-error = PDF ल'ड खालामनाय समाव मोनसे गोरोन्थि जाबाय।
pdfjs-invalid-file-error = बाहायजायै एबा गाज्रि जानाय PDF फाइल
pdfjs-missing-file-error = गोमानाय PDF फाइल
pdfjs-unexpected-response-error = मिजिंथियै सार्भार फिननाय।
pdfjs-rendering-error = बिलाइखौ राव सोलायनाय समाव मोनसे गोरोन्थि जादों।
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } सोदोब बेखेवनाय]
## Password
pdfjs-password-label = बे PDF फाइलखौ खेवनो पासवार्ड हाबहो।
pdfjs-password-invalid = बाहायजायै पासवार्ड। अननानै फिन नाजा।
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = नेवसि
pdfjs-web-fonts-disabled = वेब फन्टखौ लोरबां खालामबाय: अरजाबहोनाय PDF फन्टखौ बाहायनो हायाखै।
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/bs/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Prethodna strana
pdfjs-previous-button-label = Prethodna
pdfjs-next-button =
.title = Sljedeća strna
pdfjs-next-button-label = Sljedeća
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Strana
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = od { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } od { $pagesCount })
pdfjs-zoom-out-button =
.title = Umanji
pdfjs-zoom-out-button-label = Umanji
pdfjs-zoom-in-button =
.title = Uvećaj
pdfjs-zoom-in-button-label = Uvećaj
pdfjs-zoom-select =
.title = Uvećanje
pdfjs-presentation-mode-button =
.title = Prebaci se u prezentacijski režim
pdfjs-presentation-mode-button-label = Prezentacijski režim
pdfjs-open-file-button =
.title = Otvori fajl
pdfjs-open-file-button-label = Otvori
pdfjs-print-button =
.title = Štampaj
pdfjs-print-button-label = Štampaj
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Alati
pdfjs-tools-button-label = Alati
pdfjs-first-page-button =
.title = Idi na prvu stranu
pdfjs-first-page-button-label = Idi na prvu stranu
pdfjs-last-page-button =
.title = Idi na zadnju stranu
pdfjs-last-page-button-label = Idi na zadnju stranu
pdfjs-page-rotate-cw-button =
.title = Rotiraj u smjeru kazaljke na satu
pdfjs-page-rotate-cw-button-label = Rotiraj u smjeru kazaljke na satu
pdfjs-page-rotate-ccw-button =
.title = Rotiraj suprotno smjeru kazaljke na satu
pdfjs-page-rotate-ccw-button-label = Rotiraj suprotno smjeru kazaljke na satu
pdfjs-cursor-text-select-tool-button =
.title = Omogući alat za označavanje teksta
pdfjs-cursor-text-select-tool-button-label = Alat za označavanje teksta
pdfjs-cursor-hand-tool-button =
.title = Omogući ručni alat
pdfjs-cursor-hand-tool-button-label = Ručni alat
## Document properties dialog
pdfjs-document-properties-button =
.title = Svojstva dokumenta...
pdfjs-document-properties-button-label = Svojstva dokumenta...
pdfjs-document-properties-file-name = Naziv fajla:
pdfjs-document-properties-file-size = Veličina fajla:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bajta)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bajta)
pdfjs-document-properties-title = Naslov:
pdfjs-document-properties-author = Autor:
pdfjs-document-properties-subject = Predmet:
pdfjs-document-properties-keywords = Ključne riječi:
pdfjs-document-properties-creation-date = Datum kreiranja:
pdfjs-document-properties-modification-date = Datum promjene:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Kreator:
pdfjs-document-properties-producer = PDF stvaratelj:
pdfjs-document-properties-version = PDF verzija:
pdfjs-document-properties-page-count = Broj stranica:
pdfjs-document-properties-page-size = Veličina stranice:
pdfjs-document-properties-page-size-unit-inches = u
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = uspravno
pdfjs-document-properties-page-size-orientation-landscape = vodoravno
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Pismo
pdfjs-document-properties-page-size-name-legal = Pravni
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
pdfjs-document-properties-close-button = Zatvori
## Print
pdfjs-print-progress-message = Pripremam dokument za štampu…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Otkaži
pdfjs-printing-not-supported = Upozorenje: Štampanje nije u potpunosti podržano u ovom browseru.
pdfjs-printing-not-ready = Upozorenje: PDF nije u potpunosti učitan za štampanje.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Uključi/isključi bočnu traku
pdfjs-toggle-sidebar-button-label = Uključi/isključi bočnu traku
pdfjs-document-outline-button =
.title = Prikaži outline dokumenta (dvoklik za skupljanje/širenje svih stavki)
pdfjs-document-outline-button-label = Konture dokumenta
pdfjs-attachments-button =
.title = Prikaži priloge
pdfjs-attachments-button-label = Prilozi
pdfjs-thumbs-button =
.title = Prikaži thumbnailove
pdfjs-thumbs-button-label = Thumbnailovi
pdfjs-findbar-button =
.title = Pronađi u dokumentu
pdfjs-findbar-button-label = Pronađi
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Strana { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Thumbnail strane { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Pronađi
.placeholder = Pronađi u dokumentu…
pdfjs-find-previous-button =
.title = Pronađi prethodno pojavljivanje fraze
pdfjs-find-previous-button-label = Prethodno
pdfjs-find-next-button =
.title = Pronađi sljedeće pojavljivanje fraze
pdfjs-find-next-button-label = Sljedeće
pdfjs-find-highlight-checkbox = Označi sve
pdfjs-find-match-case-checkbox-label = Osjetljivost na karaktere
pdfjs-find-reached-top = Dostigao sam vrh dokumenta, nastavljam sa dna
pdfjs-find-reached-bottom = Dostigao sam kraj dokumenta, nastavljam sa vrha
pdfjs-find-not-found = Fraza nije pronađena
## Predefined zoom values
pdfjs-page-scale-width = Širina strane
pdfjs-page-scale-fit = Uklopi stranu
pdfjs-page-scale-auto = Automatsko uvećanje
pdfjs-page-scale-actual = Stvarna veličina
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
## Loading indicator messages
pdfjs-loading-error = Došlo je do greške prilikom učitavanja PDF-a.
pdfjs-invalid-file-error = Neispravan ili oštećen PDF fajl.
pdfjs-missing-file-error = Nedostaje PDF fajl.
pdfjs-unexpected-response-error = Neočekivani odgovor servera.
pdfjs-rendering-error = Došlo je do greške prilikom renderiranja strane.
## Annotations
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } pribilješka]
## Password
pdfjs-password-label = Upišite lozinku da biste otvorili ovaj PDF fajl.
pdfjs-password-invalid = Pogrešna lozinka. Pokušajte ponovo.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Otkaži
pdfjs-web-fonts-disabled = Web fontovi su onemogućeni: nemoguće koristiti ubačene PDF fontove.
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/ca/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Pàgina anterior
pdfjs-previous-button-label = Anterior
pdfjs-next-button =
.title = Pàgina següent
pdfjs-next-button-label = Següent
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Pàgina
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = de { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } de { $pagesCount })
pdfjs-zoom-out-button =
.title = Redueix
pdfjs-zoom-out-button-label = Redueix
pdfjs-zoom-in-button =
.title = Amplia
pdfjs-zoom-in-button-label = Amplia
pdfjs-zoom-select =
.title = Escala
pdfjs-presentation-mode-button =
.title = Canvia al mode de presentació
pdfjs-presentation-mode-button-label = Mode de presentació
pdfjs-open-file-button =
.title = Obre el fitxer
pdfjs-open-file-button-label = Obre
pdfjs-print-button =
.title = Imprimeix
pdfjs-print-button-label = Imprimeix
pdfjs-save-button =
.title = Desa
pdfjs-save-button-label = Desa
pdfjs-bookmark-button =
.title = Pàgina actual (mostra l'URL de la pàgina actual)
pdfjs-bookmark-button-label = Pàgina actual
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Eines
pdfjs-tools-button-label = Eines
pdfjs-first-page-button =
.title = Vés a la primera pàgina
pdfjs-first-page-button-label = Vés a la primera pàgina
pdfjs-last-page-button =
.title = Vés a l'última pàgina
pdfjs-last-page-button-label = Vés a l'última pàgina
pdfjs-page-rotate-cw-button =
.title = Gira cap a la dreta
pdfjs-page-rotate-cw-button-label = Gira cap a la dreta
pdfjs-page-rotate-ccw-button =
.title = Gira cap a l'esquerra
pdfjs-page-rotate-ccw-button-label = Gira cap a l'esquerra
pdfjs-cursor-text-select-tool-button =
.title = Habilita l'eina de selecció de text
pdfjs-cursor-text-select-tool-button-label = Eina de selecció de text
pdfjs-cursor-hand-tool-button =
.title = Habilita l'eina de mà
pdfjs-cursor-hand-tool-button-label = Eina de mà
pdfjs-scroll-page-button =
.title = Usa el desplaçament de pàgina
pdfjs-scroll-page-button-label = Desplaçament de pàgina
pdfjs-scroll-vertical-button =
.title = Utilitza el desplaçament vertical
pdfjs-scroll-vertical-button-label = Desplaçament vertical
pdfjs-scroll-horizontal-button =
.title = Utilitza el desplaçament horitzontal
pdfjs-scroll-horizontal-button-label = Desplaçament horitzontal
pdfjs-scroll-wrapped-button =
.title = Activa el desplaçament continu
pdfjs-scroll-wrapped-button-label = Desplaçament continu
pdfjs-spread-none-button =
.title = No agrupis les pàgines de dues en dues
pdfjs-spread-none-button-label = Una sola pàgina
pdfjs-spread-odd-button =
.title = Mostra dues pàgines començant per les pàgines de numeració senar
pdfjs-spread-odd-button-label = Doble pàgina (senar)
pdfjs-spread-even-button =
.title = Mostra dues pàgines començant per les pàgines de numeració parell
pdfjs-spread-even-button-label = Doble pàgina (parell)
## Document properties dialog
pdfjs-document-properties-button =
.title = Propietats del document…
pdfjs-document-properties-button-label = Propietats del document…
pdfjs-document-properties-file-name = Nom del fitxer:
pdfjs-document-properties-file-size = Mida del fitxer:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = Títol:
pdfjs-document-properties-author = Autor:
pdfjs-document-properties-subject = Assumpte:
pdfjs-document-properties-keywords = Paraules clau:
pdfjs-document-properties-creation-date = Data de creació:
pdfjs-document-properties-modification-date = Data de modificació:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Creador:
pdfjs-document-properties-producer = Generador de PDF:
pdfjs-document-properties-version = Versió de PDF:
pdfjs-document-properties-page-count = Nombre de pàgines:
pdfjs-document-properties-page-size = Mida de la pàgina:
pdfjs-document-properties-page-size-unit-inches = polzades
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = vertical
pdfjs-document-properties-page-size-orientation-landscape = apaïsat
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Carta
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Vista web ràpida:
pdfjs-document-properties-linearized-yes = Sí
pdfjs-document-properties-linearized-no = No
pdfjs-document-properties-close-button = Tanca
## Print
pdfjs-print-progress-message = S'està preparant la impressió del document…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Cancel·la
pdfjs-printing-not-supported = Avís: la impressió no és plenament funcional en aquest navegador.
pdfjs-printing-not-ready = Atenció: el PDF no s'ha acabat de carregar per imprimir-lo.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Mostra/amaga la barra lateral
pdfjs-toggle-sidebar-notification-button =
.title = Mostra/amaga la barra lateral (el document conté un esquema, adjuncions o capes)
pdfjs-toggle-sidebar-button-label = Mostra/amaga la barra lateral
pdfjs-document-outline-button =
.title = Mostra l'esquema del document (doble clic per ampliar/reduir tots els elements)
pdfjs-document-outline-button-label = Esquema del document
pdfjs-attachments-button =
.title = Mostra les adjuncions
pdfjs-attachments-button-label = Adjuncions
pdfjs-layers-button =
.title = Mostra les capes (doble clic per restablir totes les capes al seu estat per defecte)
pdfjs-layers-button-label = Capes
pdfjs-thumbs-button =
.title = Mostra les miniatures
pdfjs-thumbs-button-label = Miniatures
pdfjs-current-outline-item-button =
.title = Cerca l'element d'esquema actual
pdfjs-current-outline-item-button-label = Element d'esquema actual
pdfjs-findbar-button =
.title = Cerca al document
pdfjs-findbar-button-label = Cerca
pdfjs-additional-layers = Capes addicionals
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Pàgina { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Miniatura de la pàgina { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Cerca
.placeholder = Cerca al document…
pdfjs-find-previous-button =
.title = Cerca l'anterior coincidència de l'expressió
pdfjs-find-previous-button-label = Anterior
pdfjs-find-next-button =
.title = Cerca la següent coincidència de l'expressió
pdfjs-find-next-button-label = Següent
pdfjs-find-highlight-checkbox = Ressalta-ho tot
pdfjs-find-match-case-checkbox-label = Distingeix entre majúscules i minúscules
pdfjs-find-match-diacritics-checkbox-label = Respecta els diacrítics
pdfjs-find-entire-word-checkbox-label = Paraules senceres
pdfjs-find-reached-top = S'ha arribat al principi del document, es continua pel final
pdfjs-find-reached-bottom = S'ha arribat al final del document, es continua pel principi
pdfjs-find-not-found = No s'ha trobat l'expressió
## Predefined zoom values
pdfjs-page-scale-width = Amplada de la pàgina
pdfjs-page-scale-fit = Ajusta la pàgina
pdfjs-page-scale-auto = Zoom automàtic
pdfjs-page-scale-actual = Mida real
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Pàgina { $page }
## Loading indicator messages
pdfjs-loading-error = S'ha produït un error en carregar el PDF.
pdfjs-invalid-file-error = El fitxer PDF no és vàlid o està malmès.
pdfjs-missing-file-error = Falta el fitxer PDF.
pdfjs-unexpected-response-error = Resposta inesperada del servidor.
pdfjs-rendering-error = S'ha produït un error mentre es renderitzava la pàgina.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [Anotació { $type }]
## Password
pdfjs-password-label = Introduïu la contrasenya per obrir aquest fitxer PDF.
pdfjs-password-invalid = La contrasenya no és vàlida. Torneu-ho a provar.
pdfjs-password-ok-button = D'acord
pdfjs-password-cancel-button = Cancel·la
pdfjs-web-fonts-disabled = Els tipus de lletra web estan desactivats: no es poden utilitzar els tipus de lletra incrustats al PDF.
## Editing
pdfjs-editor-free-text-button =
.title = Text
pdfjs-editor-free-text-button-label = Text
pdfjs-editor-ink-button =
.title = Dibuixa
pdfjs-editor-ink-button-label = Dibuixa
## Default editor aria labels
## Remove button for the various kind of editor.
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Color
pdfjs-editor-free-text-size-input = Mida
pdfjs-editor-ink-color-input = Color
pdfjs-editor-ink-thickness-input = Gruix
pdfjs-editor-ink-opacity-input = Opacitat
pdfjs-free-text =
.aria-label = Editor de text
pdfjs-free-text-default-content = Escriviu…
pdfjs-ink =
.aria-label = Editor de dibuix
pdfjs-ink-canvas =
.aria-label = Imatge creada per l'usuari
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/cak/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Jun kan ruxaq
pdfjs-previous-button-label = Jun kan
pdfjs-next-button =
.title = Jun chik ruxaq
pdfjs-next-button-label = Jun chik
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Ruxaq
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = richin { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } richin { $pagesCount })
pdfjs-zoom-out-button =
.title = Tich'utinirisäx
pdfjs-zoom-out-button-label = Tich'utinirisäx
pdfjs-zoom-in-button =
.title = Tinimirisäx
pdfjs-zoom-in-button-label = Tinimirisäx
pdfjs-zoom-select =
.title = Sum
pdfjs-presentation-mode-button =
.title = Tijal ri rub'anikil niwachin
pdfjs-presentation-mode-button-label = Pa rub'eyal niwachin
pdfjs-open-file-button =
.title = Tijaq Yakb'äl
pdfjs-open-file-button-label = Tijaq
pdfjs-print-button =
.title = Titz'ajb'äx
pdfjs-print-button-label = Titz'ajb'äx
pdfjs-save-button =
.title = Tiyak
pdfjs-save-button-label = Tiyak
pdfjs-bookmark-button-label = Ruxaq k'o wakami
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Samajib'äl
pdfjs-tools-button-label = Samajib'äl
pdfjs-first-page-button =
.title = Tib'e pa nab'ey ruxaq
pdfjs-first-page-button-label = Tib'e pa nab'ey ruxaq
pdfjs-last-page-button =
.title = Tib'e pa ruk'isib'äl ruxaq
pdfjs-last-page-button-label = Tib'e pa ruk'isib'äl ruxaq
pdfjs-page-rotate-cw-button =
.title = Tisutïx pan ajkiq'a'
pdfjs-page-rotate-cw-button-label = Tisutïx pan ajkiq'a'
pdfjs-page-rotate-ccw-button =
.title = Tisutïx pan ajxokon
pdfjs-page-rotate-ccw-button-label = Tisutïx pan ajxokon
pdfjs-cursor-text-select-tool-button =
.title = Titzij ri rusamajib'al Rucha'ik Rucholajem Tzij
pdfjs-cursor-text-select-tool-button-label = Rusamajib'al Rucha'ik Rucholajem Tzij
pdfjs-cursor-hand-tool-button =
.title = Titzij ri q'ab'aj samajib'äl
pdfjs-cursor-hand-tool-button-label = Q'ab'aj Samajib'äl
pdfjs-scroll-page-button =
.title = Tokisäx Ruxaq Q'axanem
pdfjs-scroll-page-button-label = Ruxaq Q'axanem
pdfjs-scroll-vertical-button =
.title = Tokisäx Pa'äl Q'axanem
pdfjs-scroll-vertical-button-label = Pa'äl Q'axanem
pdfjs-scroll-horizontal-button =
.title = Tokisäx Kotz'öl Q'axanem
pdfjs-scroll-horizontal-button-label = Kotz'öl Q'axanem
pdfjs-scroll-wrapped-button =
.title = Tokisäx Tzub'aj Q'axanem
pdfjs-scroll-wrapped-button-label = Tzub'aj Q'axanem
pdfjs-spread-none-button =
.title = Man ketun taq ruxaq pa rub'eyal wuj
pdfjs-spread-none-button-label = Majun Rub'eyal
pdfjs-spread-odd-button =
.title = Ke'atunu' ri taq ruxaq rik'in natikirisaj rik'in jun man k'ulaj ta rajilab'al
pdfjs-spread-odd-button-label = Man K'ulaj Ta Rub'eyal
pdfjs-spread-even-button =
.title = Ke'atunu' ri taq ruxaq rik'in natikirisaj rik'in jun k'ulaj rajilab'al
pdfjs-spread-even-button-label = K'ulaj Rub'eyal
## Document properties dialog
pdfjs-document-properties-button =
.title = Taq richinil wuj…
pdfjs-document-properties-button-label = Taq richinil wuj…
pdfjs-document-properties-file-name = Rub'i' yakb'äl:
pdfjs-document-properties-file-size = Runimilem yakb'äl:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = B'i'aj:
pdfjs-document-properties-author = B'anel:
pdfjs-document-properties-subject = Taqikil:
pdfjs-document-properties-keywords = Kixe'el taq tzij:
pdfjs-document-properties-creation-date = Ruq'ijul xtz'uk:
pdfjs-document-properties-modification-date = Ruq'ijul xjalwachïx:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Q'inonel:
pdfjs-document-properties-producer = PDF b'anöy:
pdfjs-document-properties-version = PDF ruwäch:
pdfjs-document-properties-page-count = Jarupe' ruxaq:
pdfjs-document-properties-page-size = Runimilem ri Ruxaq:
pdfjs-document-properties-page-size-unit-inches = pa
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = rupalem
pdfjs-document-properties-page-size-orientation-landscape = rukotz'olem
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Loman wuj
pdfjs-document-properties-page-size-name-legal = Taqanel tzijol
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Anin Rutz'etik Ajk'amaya'l:
pdfjs-document-properties-linearized-yes = Ja'
pdfjs-document-properties-linearized-no = Mani
pdfjs-document-properties-close-button = Titz'apïx
## Print
pdfjs-print-progress-message = Ruchojmirisaxik wuj richin nitz'ajb'äx…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Tiq'at
pdfjs-printing-not-supported = Rutzijol k'ayewal: Ri rutz'ajb'axik man koch'el ta ronojel pa re okik'amaya'l re'.
pdfjs-printing-not-ready = Rutzijol k'ayewal: Ri PDF man xusamajij ta ronojel richin nitz'ajb'äx.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Tijal ri ajxikin kajtz'ik
pdfjs-toggle-sidebar-notification-button =
.title = Tik'ex ri ajxikin yuqkajtz'ik (ri wuj eruk'wan taq ruchi'/taqo/kuchuj)
pdfjs-toggle-sidebar-button-label = Tijal ri ajxikin kajtz'ik
pdfjs-document-outline-button =
.title = Tik'ut pe ruch'akulal wuj (kamul-pitz'oj richin nirik'/nich'utinirisäx ronojel ruch'akulal)
pdfjs-document-outline-button-label = Ruch'akulal wuj
pdfjs-attachments-button =
.title = Kek'ut pe ri taq taqoj
pdfjs-attachments-button-label = Taq taqoj
pdfjs-layers-button =
.title = Kek'ut taq Kuchuj (ka'i'-pitz' richin yetzolïx ronojel ri taq kuchuj e k'o wi)
pdfjs-layers-button-label = Taq kuchuj
pdfjs-thumbs-button =
.title = Kek'ut pe taq ch'utiq
pdfjs-thumbs-button-label = Koköj
pdfjs-current-outline-item-button =
.title = Kekanöx Taq Ch'akulal Kik'wan Chib'äl
pdfjs-current-outline-item-button-label = Taq Ch'akulal Kik'wan Chib'äl
pdfjs-findbar-button =
.title = Tikanöx chupam ri wuj
pdfjs-findbar-button-label = Tikanöx
pdfjs-additional-layers = Tz'aqat ta Kuchuj
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Ruxaq { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Ruch'utinirisaxik ruxaq { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Tikanöx
.placeholder = Tikanöx pa wuj…
pdfjs-find-previous-button =
.title = Tib'an b'enam pa ri jun kan q'aptzij xilitäj
pdfjs-find-previous-button-label = Jun kan
pdfjs-find-next-button =
.title = Tib'e pa ri jun chik pajtzij xilitäj
pdfjs-find-next-button-label = Jun chik
pdfjs-find-highlight-checkbox = Tiya' retal ronojel
pdfjs-find-match-case-checkbox-label = Tuk'äm ri' kik'in taq nimatz'ib' chuqa' taq ch'utitz'ib'
pdfjs-find-match-diacritics-checkbox-label = Tiya' Kikojol Tz'aqat taq Tz'ib'
pdfjs-find-entire-word-checkbox-label = Tz'aqät taq tzij
pdfjs-find-reached-top = Xb'eq'i' ri rutikirib'al wuj, xtikanöx k'a pa ruk'isib'äl
pdfjs-find-reached-bottom = Xb'eq'i' ri ruk'isib'äl wuj, xtikanöx pa rutikirib'al
pdfjs-find-not-found = Man xilitäj ta ri pajtzij
## Predefined zoom values
pdfjs-page-scale-width = Ruwa ruxaq
pdfjs-page-scale-fit = Tinuk' ruxaq
pdfjs-page-scale-auto = Yonil chi nimilem
pdfjs-page-scale-actual = Runimilem Wakami
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Ruxaq { $page }
## Loading indicator messages
pdfjs-loading-error = Xk'ulwachitäj jun sach'oj toq xnuk'ux ri PDF .
pdfjs-invalid-file-error = Man oke ta o yujtajinäq ri PDF yakb'äl.
pdfjs-missing-file-error = Man xilitäj ta ri PDF yakb'äl.
pdfjs-unexpected-response-error = Man oyob'en ta tz'olin rutzij ruk'u'x samaj.
pdfjs-rendering-error = Xk'ulwachitäj jun sachoj toq ninuk'wachij ri ruxaq.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } Tz'ib'anïk]
## Password
pdfjs-password-label = Tatz'ib'aj ri ewan tzij richin najäq re yakb'äl re' pa PDF.
pdfjs-password-invalid = Man okel ta ri ewan tzij: Tatojtob'ej chik.
pdfjs-password-ok-button = Ütz
pdfjs-password-cancel-button = Tiq'at
pdfjs-web-fonts-disabled = E chupül ri taq ajk'amaya'l tz'ib': man tikirel ta nokisäx ri taq tz'ib' PDF pa ch'ikenïk
## Editing
pdfjs-editor-free-text-button =
.title = Rucholajem tz'ib'
pdfjs-editor-free-text-button-label = Rucholajem tz'ib'
pdfjs-editor-ink-button =
.title = Tiwachib'ëx
pdfjs-editor-ink-button-label = Tiwachib'ëx
## Default editor aria labels
## Remove button for the various kind of editor.
##
# Editor Parameters
pdfjs-editor-free-text-color-input = B'onil
pdfjs-editor-free-text-size-input = Nimilem
pdfjs-editor-ink-color-input = B'onil
pdfjs-editor-ink-thickness-input = Rupimil
pdfjs-editor-ink-opacity-input = Q'equmal
pdfjs-free-text =
.aria-label = Nuk'unel tz'ib'atzij
pdfjs-free-text-default-content = Titikitisäx rutz'ib'axik…
pdfjs-ink =
.aria-label = Nuk'unel wachib'äl
pdfjs-ink-canvas =
.aria-label = Wachib'äl nuk'un ruma okisaxel
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/ckb/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = پەڕەی پێشوو
pdfjs-previous-button-label = پێشوو
pdfjs-next-button =
.title = پەڕەی دوواتر
pdfjs-next-button-label = دوواتر
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = پەرە
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = لە { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } لە { $pagesCount })
pdfjs-zoom-out-button =
.title = ڕۆچوونی
pdfjs-zoom-out-button-label = ڕۆچوونی
pdfjs-zoom-in-button =
.title = هێنانەپێش
pdfjs-zoom-in-button-label = هێنانەپێش
pdfjs-zoom-select =
.title = زووم
pdfjs-presentation-mode-button =
.title = گۆڕین بۆ دۆخی پێشکەشکردن
pdfjs-presentation-mode-button-label = دۆخی پێشکەشکردن
pdfjs-open-file-button =
.title = پەڕگە بکەرەوە
pdfjs-open-file-button-label = کردنەوە
pdfjs-print-button =
.title = چاپکردن
pdfjs-print-button-label = چاپکردن
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = ئامرازەکان
pdfjs-tools-button-label = ئامرازەکان
pdfjs-first-page-button =
.title = برۆ بۆ یەکەم پەڕە
pdfjs-first-page-button-label = بڕۆ بۆ یەکەم پەڕە
pdfjs-last-page-button =
.title = بڕۆ بۆ کۆتا پەڕە
pdfjs-last-page-button-label = بڕۆ بۆ کۆتا پەڕە
pdfjs-page-rotate-cw-button =
.title = ئاڕاستەی میلی کاتژمێر
pdfjs-page-rotate-cw-button-label = ئاڕاستەی میلی کاتژمێر
pdfjs-page-rotate-ccw-button =
.title = پێچەوانەی میلی کاتژمێر
pdfjs-page-rotate-ccw-button-label = پێچەوانەی میلی کاتژمێر
pdfjs-cursor-text-select-tool-button =
.title = توڵامرازی نیشانکەری دەق چالاک بکە
pdfjs-cursor-text-select-tool-button-label = توڵامرازی نیشانکەری دەق
pdfjs-cursor-hand-tool-button =
.title = توڵامرازی دەستی چالاک بکە
pdfjs-cursor-hand-tool-button-label = توڵامرازی دەستی
pdfjs-scroll-vertical-button =
.title = ناردنی ئەستوونی بەکاربێنە
pdfjs-scroll-vertical-button-label = ناردنی ئەستوونی
pdfjs-scroll-horizontal-button =
.title = ناردنی ئاسۆیی بەکاربێنە
pdfjs-scroll-horizontal-button-label = ناردنی ئاسۆیی
pdfjs-scroll-wrapped-button =
.title = ناردنی لوولکراو بەکاربێنە
pdfjs-scroll-wrapped-button-label = ناردنی لوولکراو
## Document properties dialog
pdfjs-document-properties-button =
.title = تایبەتمەندییەکانی بەڵگەنامە...
pdfjs-document-properties-button-label = تایبەتمەندییەکانی بەڵگەنامە...
pdfjs-document-properties-file-name = ناوی پەڕگە:
pdfjs-document-properties-file-size = قەبارەی پەڕگە:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } کب ({ $size_b } بایت)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } مب ({ $size_b } بایت)
pdfjs-document-properties-title = سەردێڕ:
pdfjs-document-properties-author = نووسەر
pdfjs-document-properties-subject = بابەت:
pdfjs-document-properties-keywords = کلیلەوشە:
pdfjs-document-properties-creation-date = بەرواری درووستکردن:
pdfjs-document-properties-modification-date = بەرواری دەستکاریکردن:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = درووستکەر:
pdfjs-document-properties-producer = بەرهەمهێنەری PDF:
pdfjs-document-properties-version = وەشانی PDF:
pdfjs-document-properties-page-count = ژمارەی پەرەکان:
pdfjs-document-properties-page-size = قەبارەی پەڕە:
pdfjs-document-properties-page-size-unit-inches = ئینچ
pdfjs-document-properties-page-size-unit-millimeters = ملم
pdfjs-document-properties-page-size-orientation-portrait = پۆرترەیت(درێژ)
pdfjs-document-properties-page-size-orientation-landscape = پانیی
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = نامە
pdfjs-document-properties-page-size-name-legal = یاسایی
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = پیشاندانی وێبی خێرا:
pdfjs-document-properties-linearized-yes = بەڵێ
pdfjs-document-properties-linearized-no = نەخێر
pdfjs-document-properties-close-button = داخستن
## Print
pdfjs-print-progress-message = بەڵگەنامە ئامادەدەکرێت بۆ چاپکردن...
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = پاشگەزبوونەوە
pdfjs-printing-not-supported = ئاگاداربە: چاپکردن بە تەواوی پشتگیر ناکرێت لەم وێبگەڕە.
pdfjs-printing-not-ready = ئاگاداربە: PDF بە تەواوی بارنەبووە بۆ چاپکردن.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = لاتەنیشت پیشاندان/شاردنەوە
pdfjs-toggle-sidebar-button-label = لاتەنیشت پیشاندان/شاردنەوە
pdfjs-document-outline-button-label = سنووری چوارچێوە
pdfjs-attachments-button =
.title = پاشکۆکان پیشان بدە
pdfjs-attachments-button-label = پاشکۆکان
pdfjs-layers-button-label = چینەکان
pdfjs-thumbs-button =
.title = وێنۆچکە پیشان بدە
pdfjs-thumbs-button-label = وێنۆچکە
pdfjs-findbar-button =
.title = لە بەڵگەنامە بگەرێ
pdfjs-findbar-button-label = دۆزینەوە
pdfjs-additional-layers = چینی زیاتر
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = پەڕەی { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = وێنۆچکەی پەڕەی { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = دۆزینەوە
.placeholder = لە بەڵگەنامە بگەرێ...
pdfjs-find-previous-button =
.title = هەبوونی پێشوو بدۆزرەوە لە ڕستەکەدا
pdfjs-find-previous-button-label = پێشوو
pdfjs-find-next-button =
.title = هەبوونی داهاتوو بدۆزەرەوە لە ڕستەکەدا
pdfjs-find-next-button-label = دوواتر
pdfjs-find-highlight-checkbox = هەمووی نیشانە بکە
pdfjs-find-match-case-checkbox-label = دۆخی لەیەکچوون
pdfjs-find-entire-word-checkbox-label = هەموو وشەکان
pdfjs-find-reached-top = گەشتیتە سەرەوەی بەڵگەنامە، لە خوارەوە دەستت پێکرد
pdfjs-find-reached-bottom = گەشتیتە کۆتایی بەڵگەنامە. لەسەرەوە دەستت پێکرد
pdfjs-find-not-found = نووسین نەدۆزرایەوە
## Predefined zoom values
pdfjs-page-scale-width = پانی پەڕە
pdfjs-page-scale-fit = پڕبوونی پەڕە
pdfjs-page-scale-auto = زوومی خۆکار
pdfjs-page-scale-actual = قەبارەی ڕاستی
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
## Loading indicator messages
pdfjs-loading-error = هەڵەیەک ڕوویدا لە کاتی بارکردنی PDF.
pdfjs-invalid-file-error = پەڕگەی pdf تێکچووە یان نەگونجاوە.
pdfjs-missing-file-error = پەڕگەی pdf بوونی نیە.
pdfjs-unexpected-response-error = وەڵامی ڕاژەخوازی نەخوازراو.
pdfjs-rendering-error = هەڵەیەک ڕوویدا لە کاتی پوختەکردنی (ڕێندەر) پەڕە.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } سەرنج]
## Password
pdfjs-password-label = وشەی تێپەڕ بنووسە بۆ کردنەوەی پەڕگەی pdf.
pdfjs-password-invalid = وشەی تێپەڕ هەڵەیە. تکایە دووبارە هەوڵ بدەرەوە.
pdfjs-password-ok-button = باشە
pdfjs-password-cancel-button = پاشگەزبوونەوە
pdfjs-web-fonts-disabled = جۆرەپیتی وێب ناچالاکە: نەتوانی جۆرەپیتی تێخراوی ناو pdfـەکە بەکاربێت.
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/cs/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Přejde na předchozí stránku
pdfjs-previous-button-label = Předchozí
pdfjs-next-button =
.title = Přejde na následující stránku
pdfjs-next-button-label = Další
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Stránka
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = z { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } z { $pagesCount })
pdfjs-zoom-out-button =
.title = Zmenší velikost
pdfjs-zoom-out-button-label = Zmenšit
pdfjs-zoom-in-button =
.title = Zvětší velikost
pdfjs-zoom-in-button-label = Zvětšit
pdfjs-zoom-select =
.title = Nastaví velikost
pdfjs-presentation-mode-button =
.title = Přepne do režimu prezentace
pdfjs-presentation-mode-button-label = Režim prezentace
pdfjs-open-file-button =
.title = Otevře soubor
pdfjs-open-file-button-label = Otevřít
pdfjs-print-button =
.title = Vytiskne dokument
pdfjs-print-button-label = Vytisknout
pdfjs-save-button =
.title = Uložit
pdfjs-save-button-label = Uložit
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Stáhnout
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Stáhnout
pdfjs-bookmark-button =
.title = Aktuální stránka (zobrazit URL od aktuální stránky)
pdfjs-bookmark-button-label = Aktuální stránka
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Nástroje
pdfjs-tools-button-label = Nástroje
pdfjs-first-page-button =
.title = Přejde na první stránku
pdfjs-first-page-button-label = Přejít na první stránku
pdfjs-last-page-button =
.title = Přejde na poslední stránku
pdfjs-last-page-button-label = Přejít na poslední stránku
pdfjs-page-rotate-cw-button =
.title = Otočí po směru hodin
pdfjs-page-rotate-cw-button-label = Otočit po směru hodin
pdfjs-page-rotate-ccw-button =
.title = Otočí proti směru hodin
pdfjs-page-rotate-ccw-button-label = Otočit proti směru hodin
pdfjs-cursor-text-select-tool-button =
.title = Povolí výběr textu
pdfjs-cursor-text-select-tool-button-label = Výběr textu
pdfjs-cursor-hand-tool-button =
.title = Povolí nástroj ručička
pdfjs-cursor-hand-tool-button-label = Nástroj ručička
pdfjs-scroll-page-button =
.title = Posouvat po stránkách
pdfjs-scroll-page-button-label = Posouvání po stránkách
pdfjs-scroll-vertical-button =
.title = Použít svislé posouvání
pdfjs-scroll-vertical-button-label = Svislé posouvání
pdfjs-scroll-horizontal-button =
.title = Použít vodorovné posouvání
pdfjs-scroll-horizontal-button-label = Vodorovné posouvání
pdfjs-scroll-wrapped-button =
.title = Použít postupné posouvání
pdfjs-scroll-wrapped-button-label = Postupné posouvání
pdfjs-spread-none-button =
.title = Nesdružovat stránky
pdfjs-spread-none-button-label = Žádné sdružení
pdfjs-spread-odd-button =
.title = Sdruží stránky s umístěním lichých vlevo
pdfjs-spread-odd-button-label = Sdružení stránek (liché vlevo)
pdfjs-spread-even-button =
.title = Sdruží stránky s umístěním sudých vlevo
pdfjs-spread-even-button-label = Sdružení stránek (sudé vlevo)
## Document properties dialog
pdfjs-document-properties-button =
.title = Vlastnosti dokumentu…
pdfjs-document-properties-button-label = Vlastnosti dokumentu…
pdfjs-document-properties-file-name = Název souboru:
pdfjs-document-properties-file-size = Velikost souboru:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } kB ({ $b } bajtů)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bajtů)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bajtů)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bajtů)
pdfjs-document-properties-title = Název stránky:
pdfjs-document-properties-author = Autor:
pdfjs-document-properties-subject = Předmět:
pdfjs-document-properties-keywords = Klíčová slova:
pdfjs-document-properties-creation-date = Datum vytvoření:
pdfjs-document-properties-modification-date = Datum úpravy:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Vytvořil:
pdfjs-document-properties-producer = Tvůrce PDF:
pdfjs-document-properties-version = Verze PDF:
pdfjs-document-properties-page-count = Počet stránek:
pdfjs-document-properties-page-size = Velikost stránky:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = na výšku
pdfjs-document-properties-page-size-orientation-landscape = na šířku
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Dopis
pdfjs-document-properties-page-size-name-legal = Právní dokument
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Rychlé zobrazování z webu:
pdfjs-document-properties-linearized-yes = Ano
pdfjs-document-properties-linearized-no = Ne
pdfjs-document-properties-close-button = Zavřít
## Print
pdfjs-print-progress-message = Příprava dokumentu pro tisk…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress } %
pdfjs-print-progress-close-button = Zrušit
pdfjs-printing-not-supported = Upozornění: Tisk není v tomto prohlížeči plně podporován.
pdfjs-printing-not-ready = Upozornění: Dokument PDF není kompletně načten.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Postranní lišta
pdfjs-toggle-sidebar-notification-button =
.title = Přepnout postranní lištu (dokument obsahuje osnovu/přílohy/vrstvy)
pdfjs-toggle-sidebar-button-label = Postranní lišta
pdfjs-document-outline-button =
.title = Zobrazí osnovu dokumentu (poklepání přepne zobrazení všech položek)
pdfjs-document-outline-button-label = Osnova dokumentu
pdfjs-attachments-button =
.title = Zobrazí přílohy
pdfjs-attachments-button-label = Přílohy
pdfjs-layers-button =
.title = Zobrazit vrstvy (poklepáním obnovíte všechny vrstvy do výchozího stavu)
pdfjs-layers-button-label = Vrstvy
pdfjs-thumbs-button =
.title = Zobrazí náhledy
pdfjs-thumbs-button-label = Náhledy
pdfjs-current-outline-item-button =
.title = Najít aktuální položku v osnově
pdfjs-current-outline-item-button-label = Aktuální položka v osnově
pdfjs-findbar-button =
.title = Najde v dokumentu
pdfjs-findbar-button-label = Najít
pdfjs-additional-layers = Další vrstvy
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Strana { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Náhled strany { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Najít
.placeholder = Najít v dokumentu…
pdfjs-find-previous-button =
.title = Najde předchozí výskyt hledaného textu
pdfjs-find-previous-button-label = Předchozí
pdfjs-find-next-button =
.title = Najde další výskyt hledaného textu
pdfjs-find-next-button-label = Další
pdfjs-find-highlight-checkbox = Zvýraznit
pdfjs-find-match-case-checkbox-label = Rozlišovat velikost
pdfjs-find-match-diacritics-checkbox-label = Rozlišovat diakritiku
pdfjs-find-entire-word-checkbox-label = Celá slova
pdfjs-find-reached-top = Dosažen začátek dokumentu, pokračuje se od konce
pdfjs-find-reached-bottom = Dosažen konec dokumentu, pokračuje se od začátku
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current }. z { $total } výskytu
[few] { $current }. z { $total } výskytů
[many] { $current }. z { $total } výskytů
*[other] { $current }. z { $total } výskytů
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Více než { $limit } výskyt
[few] Více než { $limit } výskyty
[many] Více než { $limit } výskytů
*[other] Více než { $limit } výskytů
}
pdfjs-find-not-found = Hledaný text nenalezen
## Predefined zoom values
pdfjs-page-scale-width = Podle šířky
pdfjs-page-scale-fit = Podle výšky
pdfjs-page-scale-auto = Automatická velikost
pdfjs-page-scale-actual = Skutečná velikost
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale } %
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Strana { $page }
## Loading indicator messages
pdfjs-loading-error = Při nahrávání PDF nastala chyba.
pdfjs-invalid-file-error = Neplatný nebo chybný soubor PDF.
pdfjs-missing-file-error = Chybí soubor PDF.
pdfjs-unexpected-response-error = Neočekávaná odpověď serveru.
pdfjs-rendering-error = Při vykreslování stránky nastala chyba.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [Anotace typu { $type }]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Pro otevření PDF souboru vložte heslo.
pdfjs-password-invalid = Neplatné heslo. Zkuste to znovu.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Zrušit
pdfjs-web-fonts-disabled = Webová písma jsou zakázána, proto není možné použít vložená písma PDF.
## Editing
pdfjs-editor-free-text-button =
.title = Text
pdfjs-editor-free-text-button-label = Text
pdfjs-editor-ink-button =
.title = Kreslení
pdfjs-editor-ink-button-label = Kreslení
pdfjs-editor-stamp-button =
.title = Přidání či úprava obrázků
pdfjs-editor-stamp-button-label = Přidání či úprava obrázků
pdfjs-editor-highlight-button =
.title = Zvýraznění
pdfjs-editor-highlight-button-label = Zvýraznění
pdfjs-highlight-floating-button1 =
.title = Zvýraznit
.aria-label = Zvýraznit
pdfjs-highlight-floating-button-label = Zvýraznit
pdfjs-editor-signature-button =
.title = Přidat podpis
pdfjs-editor-signature-button-label = Přidat podpis
## Default editor aria labels
# “Highlight” is a noun, the string is used on the editor for highlights.
pdfjs-editor-highlight-editor =
.aria-label = Editor zvýraznění
# “Drawing” is a noun, the string is used on the editor for drawings.
pdfjs-editor-ink-editor =
.aria-label = Editor kresby
pdfjs-editor-signature-editor =
.aria-label = Editor podpisu
pdfjs-editor-stamp-editor =
.aria-label = Editor obrázků
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Odebrat kresbu
pdfjs-editor-remove-freetext-button =
.title = Odebrat text
pdfjs-editor-remove-stamp-button =
.title = Odebrat obrázek
pdfjs-editor-remove-highlight-button =
.title = Odebrat zvýraznění
pdfjs-editor-remove-signature-button =
.title = Odebrat podpis
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Barva
pdfjs-editor-free-text-size-input = Velikost
pdfjs-editor-ink-color-input = Barva
pdfjs-editor-ink-thickness-input = Tloušťka
pdfjs-editor-ink-opacity-input = Průhlednost
pdfjs-editor-stamp-add-image-button =
.title = Přidat obrázek
pdfjs-editor-stamp-add-image-button-label = Přidat obrázek
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Tloušťka
pdfjs-editor-free-highlight-thickness-title =
.title = Změna tloušťky při zvýrazňování jiných položek než textu
pdfjs-editor-add-signature-container =
.aria-label = Ovládací prvky pro podpisy a uložené podpisy
pdfjs-editor-signature-add-signature-button =
.title = Přidat nový podpis
pdfjs-editor-signature-add-signature-button-label = Přidat nový podpis
# Used on the button to use an already saved signature.
# Variables:
# $description (String) - a string describing/labeling the signature.
pdfjs-editor-add-saved-signature-button =
.title = Uložený podpis: { $description }
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Textový editor
.default-content = Začněte psát...
pdfjs-free-text =
.aria-label = Textový editor
pdfjs-free-text-default-content = Začněte psát…
pdfjs-ink =
.aria-label = Editor kreslení
pdfjs-ink-canvas =
.aria-label = Uživatelem vytvořený obrázek
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Náhradní popis
pdfjs-editor-alt-text-edit-button =
.aria-label = Upravit alternativní text
pdfjs-editor-alt-text-edit-button-label = Upravit náhradní popis
pdfjs-editor-alt-text-dialog-label = Vyberte možnost
pdfjs-editor-alt-text-dialog-description = Náhradní popis pomáhá, když lidé obrázek nevidí nebo když se nenačítá.
pdfjs-editor-alt-text-add-description-label = Přidat popis
pdfjs-editor-alt-text-add-description-description = Snažte se o 1-2 věty, které popisují předmět, prostředí nebo činnosti.
pdfjs-editor-alt-text-mark-decorative-label = Označit jako dekorativní
pdfjs-editor-alt-text-mark-decorative-description = Používá se pro okrasné obrázky, jako jsou rámečky nebo vodoznaky.
pdfjs-editor-alt-text-cancel-button = Zrušit
pdfjs-editor-alt-text-save-button = Uložit
pdfjs-editor-alt-text-decorative-tooltip = Označen jako dekorativní
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Například: “Mladý muž si sedá ke stolu, aby se najedl.”
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Alternativní text
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Levý horní roh — změna velikosti
pdfjs-editor-resizer-label-top-middle = Horní střed — změna velikosti
pdfjs-editor-resizer-label-top-right = Pravý horní roh — změna velikosti
pdfjs-editor-resizer-label-middle-right = Vpravo uprostřed — změna velikosti
pdfjs-editor-resizer-label-bottom-right = Pravý dolní roh — změna velikosti
pdfjs-editor-resizer-label-bottom-middle = Střed dole — změna velikosti
pdfjs-editor-resizer-label-bottom-left = Levý dolní roh — změna velikosti
pdfjs-editor-resizer-label-middle-left = Vlevo uprostřed — změna velikosti
pdfjs-editor-resizer-top-left =
.aria-label = Levý horní roh — změna velikosti
pdfjs-editor-resizer-top-middle =
.aria-label = Horní střed — změna velikosti
pdfjs-editor-resizer-top-right =
.aria-label = Pravý horní roh — změna velikosti
pdfjs-editor-resizer-middle-right =
.aria-label = Vpravo uprostřed — změna velikosti
pdfjs-editor-resizer-bottom-right =
.aria-label = Pravý dolní roh — změna velikosti
pdfjs-editor-resizer-bottom-middle =
.aria-label = Střed dole — změna velikosti
pdfjs-editor-resizer-bottom-left =
.aria-label = Levý dolní roh — změna velikosti
pdfjs-editor-resizer-middle-left =
.aria-label = Vlevo uprostřed — změna velikosti
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Barva zvýraznění
pdfjs-editor-colorpicker-button =
.title = Změna barvy
pdfjs-editor-colorpicker-dropdown =
.aria-label = Výběr barev
pdfjs-editor-colorpicker-yellow =
.title = Žlutá
pdfjs-editor-colorpicker-green =
.title = Zelená
pdfjs-editor-colorpicker-blue =
.title = Modrá
pdfjs-editor-colorpicker-pink =
.title = Růžová
pdfjs-editor-colorpicker-red =
.title = Červená
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Zobrazit vše
pdfjs-editor-highlight-show-all-button =
.title = Zobrazit vše
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Upravit alternativní text (popis obrázku)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Přidat alternativní text (popis obrázku)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Sem napište svůj popis…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Krátký popis pro lidi, kteří neuvidí obrázek nebo když se obrázek nenačítá.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Tento alternativní text byl vytvořen automaticky a může být nepřesný.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Více informací
pdfjs-editor-new-alt-text-create-automatically-button-label = Vytvořit alternativní text automaticky
pdfjs-editor-new-alt-text-not-now-button = Teď ne
pdfjs-editor-new-alt-text-error-title = Nepodařilo se automaticky vytvořit alternativní text
pdfjs-editor-new-alt-text-error-description = Napište prosím vlastní alternativní text nebo to zkuste znovu později.
pdfjs-editor-new-alt-text-error-close-button = Zavřít
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Stahuje se model AI pro alternativní texty ({ $downloadedSize } z { $totalSize } MB)
.aria-valuetext = Stahuje se model AI pro alternativní texty ({ $downloadedSize } z { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Alternativní text byl přidán
pdfjs-editor-new-alt-text-added-button-label = Alternativní text byl přidán
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Chybí alternativní text
pdfjs-editor-new-alt-text-missing-button-label = Chybí alternativní text
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Zkontrolovat alternativní text
pdfjs-editor-new-alt-text-to-review-button-label = Zkontrolovat alternativní text
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Vytvořeno automaticky: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Nastavení alternativního textu obrázku
pdfjs-image-alt-text-settings-button-label = Nastavení alternativního textu obrázku
pdfjs-editor-alt-text-settings-dialog-label = Nastavení alternativního textu obrázku
pdfjs-editor-alt-text-settings-automatic-title = Automatický alternativní text
pdfjs-editor-alt-text-settings-create-model-button-label = Vytvořit alternativní text automaticky
pdfjs-editor-alt-text-settings-create-model-description = Navrhuje popisy, které pomohou lidem, kteří nevidí obrázek nebo když se obrázek nenačte.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Model AI pro alternativní text ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Běží lokálně na vašem zařízení, takže vaše data zůstávají v bezpečí. Vyžadováno pro automatický alternativní text.
pdfjs-editor-alt-text-settings-delete-model-button = Smazat
pdfjs-editor-alt-text-settings-download-model-button = Stáhnout
pdfjs-editor-alt-text-settings-downloading-model-button = Probíhá stahování...
pdfjs-editor-alt-text-settings-editor-title = Editor alternativního textu
pdfjs-editor-alt-text-settings-show-dialog-button-label = Při přidávání obrázku hned zobrazit editor alternativního textu
pdfjs-editor-alt-text-settings-show-dialog-description = Pomůže vám zajistit, aby všechny vaše obrázky obsahovaly alternativní text.
pdfjs-editor-alt-text-settings-close-button = Zavřít
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Zvýraznění odebráno
pdfjs-editor-undo-bar-message-freetext = Text odstraněn
pdfjs-editor-undo-bar-message-ink = Kresba odstraněna
pdfjs-editor-undo-bar-message-stamp = Obrázek odebrán
pdfjs-editor-undo-bar-message-signature = Podpis odebrán
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } anotace odebrána
[few] { $count } anotace odebrány
[many] { $count } anotací odebráno
*[other] { $count } anotací odebráno
}
pdfjs-editor-undo-bar-undo-button =
.title = Zpět
pdfjs-editor-undo-bar-undo-button-label = Zpět
pdfjs-editor-undo-bar-close-button =
.title = Zavřít
pdfjs-editor-undo-bar-close-button-label = Zavřít
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = Tento způsob umožňuje uživateli vytvořit podpis, který se přidá do dokumentu PDF. Uživatel může upravit jméno (které slouží zároveň jako alternativní text) a podpis uložit pro pozdější použití.
pdfjs-editor-add-signature-dialog-title = Přidat podpis
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Typ
.title = Typ
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Kreslit
.title = Kreslit
pdfjs-editor-add-signature-image-button = Obrázek
.title = Obrázek
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Zadejte svůj podpis
.placeholder = Zadejte svůj podpis
pdfjs-editor-add-signature-draw-placeholder = Nakreslete svůj podpis
pdfjs-editor-add-signature-draw-thickness-range-label = Tloušťka
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Tloušťka kresby: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Pro nahrání přetáhněte soubor sem
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Nebo vyberte soubory s obrázky
*[other] Nebo vyberte soubory s obrázky
}
## Controls
pdfjs-editor-add-signature-description-label = Popis (alternativní text)
pdfjs-editor-add-signature-description-input =
.title = Popis (alternativní text)
pdfjs-editor-add-signature-description-default-when-drawing = Podpis
pdfjs-editor-add-signature-clear-button-label = Vymazání podpisu
pdfjs-editor-add-signature-clear-button =
.title = Vymazání podpisu
pdfjs-editor-add-signature-save-checkbox = Uložit podpis
pdfjs-editor-add-signature-save-warning-message = Dosáhli jste limitu 5 uložených podpisů. Odstraňte jeden a uložte další.
pdfjs-editor-add-signature-image-upload-error-title = Obrázek se nepodařilo nahrát
pdfjs-editor-add-signature-image-upload-error-description = Zkontrolujte připojení k síti nebo zkuste jiný obrázek.
pdfjs-editor-add-signature-error-close-button = Zavřít
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Zrušit
pdfjs-editor-add-signature-add-button = Přidat
pdfjs-editor-edit-signature-update-button = Aktualizovat
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Odebrat podpis
pdfjs-editor-delete-signature-button-label = Odebrat podpis
pdfjs-editor-delete-signature-button1 =
.title = Odebrat uložený podpis
pdfjs-editor-delete-signature-button-label1 = Odebrat uložený podpis
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Upravit popis
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Upravit popis
================================================
FILE: cookbook/static/pdfjs/web/locale/cy/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Tudalen Flaenorol
pdfjs-previous-button-label = Blaenorol
pdfjs-next-button =
.title = Tudalen Nesaf
pdfjs-next-button-label = Nesaf
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Tudalen
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = o { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } o { $pagesCount })
pdfjs-zoom-out-button =
.title = Lleihau
pdfjs-zoom-out-button-label = Lleihau
pdfjs-zoom-in-button =
.title = Cynyddu
pdfjs-zoom-in-button-label = Cynyddu
pdfjs-zoom-select =
.title = Chwyddo
pdfjs-presentation-mode-button =
.title = Newid i'r Modd Cyflwyno
pdfjs-presentation-mode-button-label = Modd Cyflwyno
pdfjs-open-file-button =
.title = Agor Ffeil
pdfjs-open-file-button-label = Agor
pdfjs-print-button =
.title = Argraffu
pdfjs-print-button-label = Argraffu
pdfjs-save-button =
.title = Cadw
pdfjs-save-button-label = Cadw
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Llwytho i lawr
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Llwytho i lawr
pdfjs-bookmark-button =
.title = Tudalen Gyfredol (Gweld URL o'r Dudalen Gyfredol)
pdfjs-bookmark-button-label = Tudalen Gyfredol
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Offer
pdfjs-tools-button-label = Offer
pdfjs-first-page-button =
.title = Mynd i'r Dudalen Gyntaf
pdfjs-first-page-button-label = Mynd i'r Dudalen Gyntaf
pdfjs-last-page-button =
.title = Mynd i'r Dudalen Olaf
pdfjs-last-page-button-label = Mynd i'r Dudalen Olaf
pdfjs-page-rotate-cw-button =
.title = Cylchdroi Clocwedd
pdfjs-page-rotate-cw-button-label = Cylchdroi Clocwedd
pdfjs-page-rotate-ccw-button =
.title = Cylchdroi Gwrthglocwedd
pdfjs-page-rotate-ccw-button-label = Cylchdroi Gwrthglocwedd
pdfjs-cursor-text-select-tool-button =
.title = Galluogi Dewis Offeryn Testun
pdfjs-cursor-text-select-tool-button-label = Offeryn Dewis Testun
pdfjs-cursor-hand-tool-button =
.title = Galluogi Offeryn Llaw
pdfjs-cursor-hand-tool-button-label = Offeryn Llaw
pdfjs-scroll-page-button =
.title = Defnyddio Sgrolio Tudalen
pdfjs-scroll-page-button-label = Sgrolio Tudalen
pdfjs-scroll-vertical-button =
.title = Defnyddio Sgrolio Fertigol
pdfjs-scroll-vertical-button-label = Sgrolio Fertigol
pdfjs-scroll-horizontal-button =
.title = Defnyddio Sgrolio Llorweddol
pdfjs-scroll-horizontal-button-label = Sgrolio Llorweddol
pdfjs-scroll-wrapped-button =
.title = Defnyddio Sgrolio Amlapio
pdfjs-scroll-wrapped-button-label = Sgrolio Amlapio
pdfjs-spread-none-button =
.title = Peidio uno trawsdaleniadau
pdfjs-spread-none-button-label = Dim Trawsdaleniadau
pdfjs-spread-odd-button =
.title = Uno trawsdaleniadau gan gychwyn gyda thudalennau odrif
pdfjs-spread-odd-button-label = Trawsdaleniadau Odrif
pdfjs-spread-even-button =
.title = Uno trawsdaleniadau gan gychwyn gyda thudalennau eilrif
pdfjs-spread-even-button-label = Trawsdaleniadau Eilrif
## Document properties dialog
pdfjs-document-properties-button =
.title = Priodweddau Dogfen…
pdfjs-document-properties-button-label = Priodweddau Dogfen…
pdfjs-document-properties-file-name = Enw ffeil:
pdfjs-document-properties-file-size = Maint ffeil:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } beit)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } beit)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } beit)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } beit)
pdfjs-document-properties-title = Teitl:
pdfjs-document-properties-author = Awdur:
pdfjs-document-properties-subject = Pwnc:
pdfjs-document-properties-keywords = Allweddair:
pdfjs-document-properties-creation-date = Dyddiad Creu:
pdfjs-document-properties-modification-date = Dyddiad Addasu:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Crewr:
pdfjs-document-properties-producer = Cynhyrchydd PDF:
pdfjs-document-properties-version = Fersiwn PDF:
pdfjs-document-properties-page-count = Cyfrif Tudalen:
pdfjs-document-properties-page-size = Maint Tudalen:
pdfjs-document-properties-page-size-unit-inches = o fewn
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = portread
pdfjs-document-properties-page-size-orientation-landscape = tirlun
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Llythyr
pdfjs-document-properties-page-size-name-legal = Cyfreithiol
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Golwg Gwe Cyflym:
pdfjs-document-properties-linearized-yes = Iawn
pdfjs-document-properties-linearized-no = Na
pdfjs-document-properties-close-button = Cau
## Print
pdfjs-print-progress-message = Paratoi dogfen ar gyfer ei hargraffu…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Diddymu
pdfjs-printing-not-supported = Rhybudd: Nid yw argraffu yn cael ei gynnal yn llawn gan y porwr.
pdfjs-printing-not-ready = Rhybudd: Nid yw'r PDF wedi ei lwytho'n llawn ar gyfer argraffu.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Toglo'r Bar Ochr
pdfjs-toggle-sidebar-notification-button =
.title = Toglo'r Bar Ochr (mae'r ddogfen yn cynnwys amlinelliadau/atodiadau/haenau)
pdfjs-toggle-sidebar-button-label = Toglo'r Bar Ochr
pdfjs-document-outline-button =
.title = Dangos Amlinell Dogfen (clic dwbl i ymestyn/cau pob eitem)
pdfjs-document-outline-button-label = Amlinelliad Dogfen
pdfjs-attachments-button =
.title = Dangos Atodiadau
pdfjs-attachments-button-label = Atodiadau
pdfjs-layers-button =
.title = Dangos Haenau (cliciwch ddwywaith i ailosod yr holl haenau i'r cyflwr rhagosodedig)
pdfjs-layers-button-label = Haenau
pdfjs-thumbs-button =
.title = Dangos Lluniau Bach
pdfjs-thumbs-button-label = Lluniau Bach
pdfjs-current-outline-item-button =
.title = Canfod yr Eitem Amlinellol Gyfredol
pdfjs-current-outline-item-button-label = Yr Eitem Amlinellol Gyfredol
pdfjs-findbar-button =
.title = Canfod yn y Ddogfen
pdfjs-findbar-button-label = Canfod
pdfjs-additional-layers = Haenau Ychwanegol
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Tudalen { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Llun Bach Tudalen { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Canfod
.placeholder = Canfod yn y ddogfen…
pdfjs-find-previous-button =
.title = Canfod enghraifft flaenorol o'r ymadrodd
pdfjs-find-previous-button-label = Blaenorol
pdfjs-find-next-button =
.title = Canfod enghraifft nesaf yr ymadrodd
pdfjs-find-next-button-label = Nesaf
pdfjs-find-highlight-checkbox = Amlygu Popeth
pdfjs-find-match-case-checkbox-label = Cydweddu Maint
pdfjs-find-match-diacritics-checkbox-label = Diacritigau Cyfatebol
pdfjs-find-entire-word-checkbox-label = Geiriau Cyfan
pdfjs-find-reached-top = Wedi cyrraedd brig y dudalen, parhau o'r gwaelod
pdfjs-find-reached-bottom = Wedi cyrraedd diwedd y dudalen, parhau o'r brig
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[zero] { $current } o { $total } cydweddiadau
[one] { $current } o { $total } cydweddiad
[two] { $current } o { $total } gydweddiad
[few] { $current } o { $total } cydweddiad
[many] { $current } o { $total } chydweddiad
*[other] { $current } o { $total } cydweddiad
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[zero] Mwy nag { $limit } cydweddiadau
[one] Mwy nag { $limit } cydweddiad
[two] Mwy nag { $limit } gydweddiad
[few] Mwy nag { $limit } cydweddiad
[many] Mwy nag { $limit } chydweddiad
*[other] Mwy nag { $limit } cydweddiad
}
pdfjs-find-not-found = Heb ganfod ymadrodd
## Predefined zoom values
pdfjs-page-scale-width = Lled Tudalen
pdfjs-page-scale-fit = Ffit Tudalen
pdfjs-page-scale-auto = Chwyddo Awtomatig
pdfjs-page-scale-actual = Maint Gwirioneddol
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Tudalen { $page }
## Loading indicator messages
pdfjs-loading-error = Digwyddodd gwall wrth lwytho'r PDF.
pdfjs-invalid-file-error = Ffeil PDF annilys neu llwgr.
pdfjs-missing-file-error = Ffeil PDF coll.
pdfjs-unexpected-response-error = Ymateb annisgwyl gan y gweinydd.
pdfjs-rendering-error = Digwyddodd gwall wrth adeiladu'r dudalen.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [Anodiad { $type } ]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Rhowch gyfrinair i agor y PDF.
pdfjs-password-invalid = Cyfrinair annilys. Ceisiwch eto.
pdfjs-password-ok-button = Iawn
pdfjs-password-cancel-button = Diddymu
pdfjs-web-fonts-disabled = Ffontiau gwe wedi eu hanalluogi: methu defnyddio ffontiau PDF mewnblanedig.
## Editing
pdfjs-editor-free-text-button =
.title = Testun
pdfjs-editor-free-text-button-label = Testun
pdfjs-editor-ink-button =
.title = Lluniadu
pdfjs-editor-ink-button-label = Lluniadu
pdfjs-editor-stamp-button =
.title = Ychwanegu neu olygu delweddau
pdfjs-editor-stamp-button-label = Ychwanegu neu olygu delweddau
pdfjs-editor-highlight-button =
.title = Amlygu
pdfjs-editor-highlight-button-label = Amlygu
pdfjs-highlight-floating-button1 =
.title = Amlygu
.aria-label = Amlygu
pdfjs-highlight-floating-button-label = Amlygu
pdfjs-editor-signature-button =
.title = Ychwanegu llofnod
pdfjs-editor-signature-button-label = Ychwanegu llofnod
## Default editor aria labels
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Dileu lluniad
pdfjs-editor-remove-freetext-button =
.title = Dileu testun
pdfjs-editor-remove-stamp-button =
.title = Dileu delwedd
pdfjs-editor-remove-highlight-button =
.title = Tynnu amlygiad
pdfjs-editor-remove-signature-button =
.title = Dileu llofnod
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Lliw
pdfjs-editor-free-text-size-input = Maint
pdfjs-editor-ink-color-input = Lliw
pdfjs-editor-ink-thickness-input = Trwch
pdfjs-editor-ink-opacity-input = Didreiddedd
pdfjs-editor-stamp-add-image-button =
.title = Ychwanegu delwedd
pdfjs-editor-stamp-add-image-button-label = Ychwanegu delwedd
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Trwch
pdfjs-editor-free-highlight-thickness-title =
.title = Newid trwch wrth amlygu eitemau heblaw testun
pdfjs-editor-signature-add-signature-button =
.title = Ychwanegu llofnod newydd
pdfjs-editor-signature-add-signature-button-label = Ychwanegu llofnod newydd
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Golygydd Testun
.default-content = Cychwyn teipio…
pdfjs-free-text =
.aria-label = Golygydd Testun
pdfjs-free-text-default-content = Cychwyn teipio…
pdfjs-ink =
.aria-label = Golygydd Lluniadu
pdfjs-ink-canvas =
.aria-label = Delwedd wedi'i chreu gan ddefnyddwyr
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Testun amgen (alt)
pdfjs-editor-alt-text-edit-button =
.aria-label = Golygu testun amgen
pdfjs-editor-alt-text-edit-button-label = Golygu testun amgen
pdfjs-editor-alt-text-dialog-label = Dewisiadau
pdfjs-editor-alt-text-dialog-description = Mae testun amgen (testun alt) yn helpu pan na all pobl weld y ddelwedd neu pan nad yw'n llwytho.
pdfjs-editor-alt-text-add-description-label = Ychwanegu disgrifiad
pdfjs-editor-alt-text-add-description-description = Anelwch at 1-2 frawddeg sy'n disgrifio'r pwnc, y cefndir neu'r gweithredoedd.
pdfjs-editor-alt-text-mark-decorative-label = Marcio fel addurniadol
pdfjs-editor-alt-text-mark-decorative-description = Mae'n cael ei ddefnyddio ar gyfer delweddau addurniadol, fel borderi neu farciau dŵr.
pdfjs-editor-alt-text-cancel-button = Diddymu
pdfjs-editor-alt-text-save-button = Cadw
pdfjs-editor-alt-text-decorative-tooltip = Marcio fel addurniadol
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Er enghraifft, “Mae dyn ifanc yn eistedd wrth fwrdd i fwyta pryd bwyd”
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Testun amgen (alt)
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Y gornel chwith uchaf — newid maint
pdfjs-editor-resizer-label-top-middle = Canol uchaf - newid maint
pdfjs-editor-resizer-label-top-right = Y gornel dde uchaf - newid maint
pdfjs-editor-resizer-label-middle-right = De canol - newid maint
pdfjs-editor-resizer-label-bottom-right = Y gornel dde isaf — newid maint
pdfjs-editor-resizer-label-bottom-middle = Canol gwaelod — newid maint
pdfjs-editor-resizer-label-bottom-left = Y gornel chwith isaf — newid maint
pdfjs-editor-resizer-label-middle-left = Chwith canol — newid maint
pdfjs-editor-resizer-top-left =
.aria-label = Y gornel chwith uchaf — newid maint
pdfjs-editor-resizer-top-middle =
.aria-label = Canol uchaf - newid maint
pdfjs-editor-resizer-top-right =
.aria-label = Y gornel dde uchaf - newid maint
pdfjs-editor-resizer-middle-right =
.aria-label = De canol - newid maint
pdfjs-editor-resizer-bottom-right =
.aria-label = Y gornel dde isaf — newid maint
pdfjs-editor-resizer-bottom-middle =
.aria-label = Canol gwaelod — newid maint
pdfjs-editor-resizer-bottom-left =
.aria-label = Y gornel chwith isaf — newid maint
pdfjs-editor-resizer-middle-left =
.aria-label = Chwith canol — newid maint
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Lliw amlygu
pdfjs-editor-colorpicker-button =
.title = Newid lliw
pdfjs-editor-colorpicker-dropdown =
.aria-label = Dewisiadau lliw
pdfjs-editor-colorpicker-yellow =
.title = Melyn
pdfjs-editor-colorpicker-green =
.title = Gwyrdd
pdfjs-editor-colorpicker-blue =
.title = Glas
pdfjs-editor-colorpicker-pink =
.title = Pinc
pdfjs-editor-colorpicker-red =
.title = Coch
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Dangos y cyfan
pdfjs-editor-highlight-show-all-button =
.title = Dangos y cyfan
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Golygu testun amgen (disgrifiad o ddelwedd)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Ychwanegwch destun amgen (disgrifiad delwedd)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Ysgrifennwch eich disgrifiad yma…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Disgrifiad byr ar gyfer pobl sydd ddim yn gallu gweld y ddelwedd neu pan nad yw'r ddelwedd yn llwytho.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Cafodd y testun amgen hwn ei greu'n awtomatig a gall fod yn anghywir.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Rhagor
pdfjs-editor-new-alt-text-create-automatically-button-label = Creu testun amgen yn awtomatig
pdfjs-editor-new-alt-text-not-now-button = Nid nawr
pdfjs-editor-new-alt-text-error-title = Methu â chreu testun amgen yn awtomatig
pdfjs-editor-new-alt-text-error-description = Ysgrifennwch eich testun amgen eich hun neu ceisiwch eto yn nes ymlaen.
pdfjs-editor-new-alt-text-error-close-button = Cau
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Wrthi'n llwytho i lawr model AI testun amgen ( { $downloadedSize } o { $totalSize } MB)
.aria-valuetext = Wrthi'n llwytho i lawr model AI testun amgen ( { $downloadedSize } o { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Ychwanegwyd testun amgen
pdfjs-editor-new-alt-text-added-button-label = Ychwanegwyd testun amgen
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Testun amgen coll
pdfjs-editor-new-alt-text-missing-button-label = Testun amgen coll
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Adolygu'r testun amgen
pdfjs-editor-new-alt-text-to-review-button-label = Adolygu'r testun amgen
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Crëwyd yn awtomatig: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Gosodiadau testun amgen delwedd
pdfjs-image-alt-text-settings-button-label = Gosodiadau testun amgen delwedd
pdfjs-editor-alt-text-settings-dialog-label = Gosodiadau testun amgen delwedd
pdfjs-editor-alt-text-settings-automatic-title = Testun amgen awtomatig
pdfjs-editor-alt-text-settings-create-model-button-label = Creu testun amgen yn awtomatig
pdfjs-editor-alt-text-settings-create-model-description = Yn awgrymu disgrifiadau i helpu pobl sydd ddim yn gallu gweld y ddelwedd neu pan nad yw'r ddelwedd yn llwytho.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Model AI testun amgen ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Yn rhedeg yn lleol ar eich dyfais fel bod eich data'n aros yn breifat. Yn ofynnol ar gyfer testun amgen awtomatig.
pdfjs-editor-alt-text-settings-delete-model-button = Dileu
pdfjs-editor-alt-text-settings-download-model-button = Llwytho i Lawr
pdfjs-editor-alt-text-settings-downloading-model-button = Wrthi'n llwytho i lawr…
pdfjs-editor-alt-text-settings-editor-title = Golygydd testun amgen
pdfjs-editor-alt-text-settings-show-dialog-button-label = Dangoswch y golygydd testun amgen yn syth wrth ychwanegu delwedd
pdfjs-editor-alt-text-settings-show-dialog-description = Yn eich helpu i wneud yn siŵr bod gan eich holl ddelweddau destun amgen.
pdfjs-editor-alt-text-settings-close-button = Cau
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Tynnwyd yr amlygu
pdfjs-editor-undo-bar-message-freetext = Tynnwyd y testun
pdfjs-editor-undo-bar-message-ink = Tynnwyd y lluniad
pdfjs-editor-undo-bar-message-stamp = Tynnwyd y ddelwedd
pdfjs-editor-undo-bar-message-signature = Llofnod wedi'i dynnu
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[zero] { $count } anodiad wedi'u tynnu
[one] { $count } anodiad wedi'i dynnu
[two] { $count } anodiad wedi'u tynnu
[few] { $count } anodiad wedi'u tynnu
[many] { $count } anodiad wedi'u tynnu
*[other] { $count } anodiad wedi'u tynnu
}
pdfjs-editor-undo-bar-undo-button =
.title = Dadwneud
pdfjs-editor-undo-bar-undo-button-label = Dadwneud
pdfjs-editor-undo-bar-close-button =
.title = Cau
pdfjs-editor-undo-bar-close-button-label = Cau
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = Mae'r modd hwn yn caniatáu i'r defnyddiwr greu llofnod i'w ychwanegu at ddogfen PDF. Gall y defnyddiwr olygu'r enw (sydd hefyd yn gweithredu fel y testun amgen), ac yn ddewisol cadw'r llofnod i'w ddefnyddio dro ar ôl tro.
pdfjs-editor-add-signature-dialog-title = Ychwanegu llofnod
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Math
.title = Math
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Lluniadu
.title = Lluniadu
pdfjs-editor-add-signature-image-button = Delwedd
.title = Delwedd
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Teipiwch eich llofnod
.placeholder = Teipiwch eich llofnod
pdfjs-editor-add-signature-draw-placeholder = Lluniwch eich llofnod
pdfjs-editor-add-signature-draw-thickness-range-label = Trwch
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Trwch y llinell: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Llusgwch ffeil yma i'w llwytho
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Neu ddewis ffeiliau delwedd
*[other] Neu bori ffeiliau delwedd
}
## Controls
pdfjs-editor-add-signature-description-label = Disgrifiad (testun amgen)
pdfjs-editor-add-signature-description-input =
.title = Disgrifiad (testun amgen)
pdfjs-editor-add-signature-description-default-when-drawing = Llofnod
pdfjs-editor-add-signature-clear-button-label = Diddymu llofnod
pdfjs-editor-add-signature-clear-button =
.title = Diddymu llofnod
pdfjs-editor-add-signature-save-checkbox = Cadw llofnod
pdfjs-editor-add-signature-save-warning-message = Rydych chi wedi cyrraedd y terfyn o 5 llofnod sydd wedi'u cadw. Tynnwch un i gadw rhagor
pdfjs-editor-add-signature-image-upload-error-title = Methu llwytho'r ddelwedd.
pdfjs-editor-add-signature-image-upload-error-description = Gwiriwch eich cysylltiad rhwydwaith neu rhowch gynnig ar ddelwedd arall.
pdfjs-editor-add-signature-error-close-button = Cau
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Diddymu
pdfjs-editor-add-signature-add-button = Ychwanegu
pdfjs-editor-edit-signature-update-button = Diweddaru
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Dileu llofnod
pdfjs-editor-delete-signature-button-label = Dileu llofnod
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Golygu disgrifiad
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Golygu disgrifiad
================================================
FILE: cookbook/static/pdfjs/web/locale/da/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Forrige side
pdfjs-previous-button-label = Forrige
pdfjs-next-button =
.title = Næste side
pdfjs-next-button-label = Næste
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Side
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = af { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } af { $pagesCount })
pdfjs-zoom-out-button =
.title = Zoom ud
pdfjs-zoom-out-button-label = Zoom ud
pdfjs-zoom-in-button =
.title = Zoom ind
pdfjs-zoom-in-button-label = Zoom ind
pdfjs-zoom-select =
.title = Zoom
pdfjs-presentation-mode-button =
.title = Skift til fuldskærmsvisning
pdfjs-presentation-mode-button-label = Fuldskærmsvisning
pdfjs-open-file-button =
.title = Åbn fil
pdfjs-open-file-button-label = Åbn
pdfjs-print-button =
.title = Udskriv
pdfjs-print-button-label = Udskriv
pdfjs-save-button =
.title = Gem
pdfjs-save-button-label = Gem
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Hent
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Hent
pdfjs-bookmark-button =
.title = Aktuel side (vis URL fra den aktuelle side)
pdfjs-bookmark-button-label = Aktuel side
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Funktioner
pdfjs-tools-button-label = Funktioner
pdfjs-first-page-button =
.title = Gå til første side
pdfjs-first-page-button-label = Gå til første side
pdfjs-last-page-button =
.title = Gå til sidste side
pdfjs-last-page-button-label = Gå til sidste side
pdfjs-page-rotate-cw-button =
.title = Roter med uret
pdfjs-page-rotate-cw-button-label = Roter med uret
pdfjs-page-rotate-ccw-button =
.title = Roter mod uret
pdfjs-page-rotate-ccw-button-label = Roter mod uret
pdfjs-cursor-text-select-tool-button =
.title = Aktiver markeringsværktøj
pdfjs-cursor-text-select-tool-button-label = Markeringsværktøj
pdfjs-cursor-hand-tool-button =
.title = Aktiver håndværktøj
pdfjs-cursor-hand-tool-button-label = Håndværktøj
pdfjs-scroll-page-button =
.title = Brug sidescrolling
pdfjs-scroll-page-button-label = Sidescrolling
pdfjs-scroll-vertical-button =
.title = Brug vertikal scrolling
pdfjs-scroll-vertical-button-label = Vertikal scrolling
pdfjs-scroll-horizontal-button =
.title = Brug horisontal scrolling
pdfjs-scroll-horizontal-button-label = Horisontal scrolling
pdfjs-scroll-wrapped-button =
.title = Brug ombrudt scrolling
pdfjs-scroll-wrapped-button-label = Ombrudt scrolling
pdfjs-spread-none-button =
.title = Vis enkeltsider
pdfjs-spread-none-button-label = Enkeltsider
pdfjs-spread-odd-button =
.title = Vis opslag med ulige sidenumre til venstre
pdfjs-spread-odd-button-label = Opslag med forside
pdfjs-spread-even-button =
.title = Vis opslag med lige sidenumre til venstre
pdfjs-spread-even-button-label = Opslag uden forside
## Document properties dialog
pdfjs-document-properties-button =
.title = Dokumentegenskaber…
pdfjs-document-properties-button-label = Dokumentegenskaber…
pdfjs-document-properties-file-name = Filnavn:
pdfjs-document-properties-file-size = Filstørrelse:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = Titel:
pdfjs-document-properties-author = Forfatter:
pdfjs-document-properties-subject = Emne:
pdfjs-document-properties-keywords = Nøgleord:
pdfjs-document-properties-creation-date = Oprettet:
pdfjs-document-properties-modification-date = Redigeret:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Program:
pdfjs-document-properties-producer = PDF-producent:
pdfjs-document-properties-version = PDF-version:
pdfjs-document-properties-page-count = Antal sider:
pdfjs-document-properties-page-size = Sidestørrelse:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = stående
pdfjs-document-properties-page-size-orientation-landscape = liggende
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letter
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Hurtig web-visning:
pdfjs-document-properties-linearized-yes = Ja
pdfjs-document-properties-linearized-no = Nej
pdfjs-document-properties-close-button = Luk
## Print
pdfjs-print-progress-message = Forbereder dokument til udskrivning…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Annuller
pdfjs-printing-not-supported = Advarsel: Udskrivning er ikke fuldt understøttet af browseren.
pdfjs-printing-not-ready = Advarsel: PDF-filen er ikke fuldt indlæst til udskrivning.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Slå sidepanel til eller fra
pdfjs-toggle-sidebar-notification-button =
.title = Slå sidepanel til eller fra (dokumentet indeholder disposition/vedhæftede filer/lag)
pdfjs-toggle-sidebar-button-label = Slå sidepanel til eller fra
pdfjs-document-outline-button =
.title = Vis dokumentets disposition (dobbeltklik for at udvide/sammenfolde alle elementer)
pdfjs-document-outline-button-label = Dokument-disposition
pdfjs-attachments-button =
.title = Vis vedhæftede filer
pdfjs-attachments-button-label = Vedhæftede filer
pdfjs-layers-button =
.title = Vis lag (dobbeltklik for at nulstille alle lag til standard-tilstanden)
pdfjs-layers-button-label = Lag
pdfjs-thumbs-button =
.title = Vis miniaturer
pdfjs-thumbs-button-label = Miniaturer
pdfjs-current-outline-item-button =
.title = Find det aktuelle dispositions-element
pdfjs-current-outline-item-button-label = Aktuelt dispositions-element
pdfjs-findbar-button =
.title = Find i dokument
pdfjs-findbar-button-label = Find
pdfjs-additional-layers = Yderligere lag
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Side { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Miniature af side { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Find
.placeholder = Find i dokument…
pdfjs-find-previous-button =
.title = Find den forrige forekomst
pdfjs-find-previous-button-label = Forrige
pdfjs-find-next-button =
.title = Find den næste forekomst
pdfjs-find-next-button-label = Næste
pdfjs-find-highlight-checkbox = Fremhæv alle
pdfjs-find-match-case-checkbox-label = Forskel på store og små bogstaver
pdfjs-find-match-diacritics-checkbox-label = Diakritiske tegn
pdfjs-find-entire-word-checkbox-label = Hele ord
pdfjs-find-reached-top = Toppen af siden blev nået, fortsatte fra bunden
pdfjs-find-reached-bottom = Bunden af siden blev nået, fortsatte fra toppen
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } af { $total } forekomst
*[other] { $current } af { $total } forekomster
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Mere end { $limit } forekomst
*[other] Mere end { $limit } forekomster
}
pdfjs-find-not-found = Der blev ikke fundet noget
## Predefined zoom values
pdfjs-page-scale-width = Sidebredde
pdfjs-page-scale-fit = Tilpas til side
pdfjs-page-scale-auto = Automatisk zoom
pdfjs-page-scale-actual = Faktisk størrelse
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Side { $page }
## Loading indicator messages
pdfjs-loading-error = Der opstod en fejl ved indlæsning af PDF-filen.
pdfjs-invalid-file-error = PDF-filen er ugyldig eller ødelagt.
pdfjs-missing-file-error = Manglende PDF-fil.
pdfjs-unexpected-response-error = Uventet svar fra serveren.
pdfjs-rendering-error = Der opstod en fejl ved generering af siden.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type }kommentar]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Angiv adgangskode til at åbne denne PDF-fil.
pdfjs-password-invalid = Ugyldig adgangskode. Prøv igen.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Fortryd
pdfjs-web-fonts-disabled = Webskrifttyper er deaktiverede. De indlejrede skrifttyper i PDF-filen kan ikke anvendes.
## Editing
pdfjs-editor-free-text-button =
.title = Tekst
pdfjs-editor-free-text-button-label = Tekst
pdfjs-editor-ink-button =
.title = Tegn
pdfjs-editor-ink-button-label = Tegn
pdfjs-editor-stamp-button =
.title = Tilføj eller rediger billeder
pdfjs-editor-stamp-button-label = Tilføj eller rediger billeder
pdfjs-editor-highlight-button =
.title = Fremhæv
pdfjs-editor-highlight-button-label = Fremhæv
pdfjs-highlight-floating-button1 =
.title = Fremhæv
.aria-label = Fremhæv
pdfjs-highlight-floating-button-label = Fremhæv
pdfjs-editor-signature-button =
.title = Tilføj underskrift
pdfjs-editor-signature-button-label = Tilføj underskrift
## Default editor aria labels
# “Highlight” is a noun, the string is used on the editor for highlights.
pdfjs-editor-highlight-editor =
.aria-label = Redigering af fremhævning
# “Drawing” is a noun, the string is used on the editor for drawings.
pdfjs-editor-ink-editor =
.aria-label = Redigering af tegninger
pdfjs-editor-signature-editor =
.aria-label = Redigering af underskrifter
pdfjs-editor-stamp-editor =
.aria-label = Redigering af billeder
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Fjern tegning
pdfjs-editor-remove-freetext-button =
.title = Fjern tekst
pdfjs-editor-remove-stamp-button =
.title = Fjern billede
pdfjs-editor-remove-highlight-button =
.title = Fjern fremhævning
pdfjs-editor-remove-signature-button =
.title = Fjern underskrift
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Farve
pdfjs-editor-free-text-size-input = Størrelse
pdfjs-editor-ink-color-input = Farve
pdfjs-editor-ink-thickness-input = Tykkelse
pdfjs-editor-ink-opacity-input = Uigennemsigtighed
pdfjs-editor-stamp-add-image-button =
.title = Tilføj billede
pdfjs-editor-stamp-add-image-button-label = Tilføj billede
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Tykkelse
pdfjs-editor-free-highlight-thickness-title =
.title = Ændr tykkelse, når andre elementer end tekst fremhæves
pdfjs-editor-signature-add-signature-button =
.title = Tilføj ny underskrift
pdfjs-editor-signature-add-signature-button-label = Tilføj ny underskrift
# Used on the button to use an already saved signature.
# Variables:
# $description (String) - a string describing/labeling the signature.
pdfjs-editor-add-saved-signature-button =
.title = Gemt underskrift: { $description }
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Teksteditor
.default-content = Begynd at skrive…
pdfjs-free-text =
.aria-label = Teksteditor
pdfjs-free-text-default-content = Begynd at skrive…
pdfjs-ink =
.aria-label = Tegnings-editor
pdfjs-ink-canvas =
.aria-label = Brugeroprettet billede
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Alternativ tekst
pdfjs-editor-alt-text-edit-button =
.aria-label = Rediger alternativ tekst
pdfjs-editor-alt-text-edit-button-label = Rediger alternativ tekst
pdfjs-editor-alt-text-dialog-label = Vælg en indstilling
pdfjs-editor-alt-text-dialog-description = Alternativ tekst hjælper folk, som ikke kan se billedet eller når det ikke indlæses.
pdfjs-editor-alt-text-add-description-label = Tilføj en beskrivelse
pdfjs-editor-alt-text-add-description-description = Sigt efter en eller to sætninger, der beskriver emnet, omgivelserne eller handlinger.
pdfjs-editor-alt-text-mark-decorative-label = Marker som dekorativ
pdfjs-editor-alt-text-mark-decorative-description = Dette bruges for dekorative billeder som rammer eller vandmærker.
pdfjs-editor-alt-text-cancel-button = Annuller
pdfjs-editor-alt-text-save-button = Gem
pdfjs-editor-alt-text-decorative-tooltip = Markeret som dekorativ
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = For eksempel: "En ung mand sætter sig ved et bord for at spise et måltid mad"
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Alternativ tekst
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Øverste venstre hjørne — tilpas størrelse
pdfjs-editor-resizer-label-top-middle = Øverste i midten — tilpas størrelse
pdfjs-editor-resizer-label-top-right = Øverste højre hjørne — tilpas størrelse
pdfjs-editor-resizer-label-middle-right = Midten til højre — tilpas størrelse
pdfjs-editor-resizer-label-bottom-right = Nederste højre hjørne - tilpas størrelse
pdfjs-editor-resizer-label-bottom-middle = Nederst i midten - tilpas størrelse
pdfjs-editor-resizer-label-bottom-left = Nederste venstre hjørne - tilpas størrelse
pdfjs-editor-resizer-label-middle-left = Midten til venstre — tilpas størrelse
pdfjs-editor-resizer-top-left =
.aria-label = Øverste venstre hjørne — tilpas størrelse
pdfjs-editor-resizer-top-middle =
.aria-label = Øverste i midten — tilpas størrelse
pdfjs-editor-resizer-top-right =
.aria-label = Øverste højre hjørne — tilpas størrelse
pdfjs-editor-resizer-middle-right =
.aria-label = Midten til højre — tilpas størrelse
pdfjs-editor-resizer-bottom-right =
.aria-label = Nederste højre hjørne - tilpas størrelse
pdfjs-editor-resizer-bottom-middle =
.aria-label = Nederst i midten - tilpas størrelse
pdfjs-editor-resizer-bottom-left =
.aria-label = Nederste venstre hjørne - tilpas størrelse
pdfjs-editor-resizer-middle-left =
.aria-label = Midten til venstre — tilpas størrelse
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Fremhævningsfarve
pdfjs-editor-colorpicker-button =
.title = Skift farve
pdfjs-editor-colorpicker-dropdown =
.aria-label = Farvevalg
pdfjs-editor-colorpicker-yellow =
.title = Gul
pdfjs-editor-colorpicker-green =
.title = Grøn
pdfjs-editor-colorpicker-blue =
.title = Blå
pdfjs-editor-colorpicker-pink =
.title = Lyserød
pdfjs-editor-colorpicker-red =
.title = Rød
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Vis alle
pdfjs-editor-highlight-show-all-button =
.title = Vis alle
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Rediger alternativ tekst (billedbeskrivelse)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Tilføj alternativ tekst (billedbeskrivelse)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Skriv din beskrivelse her...
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Kort beskrivelse til personer, der ikke kan se billedet, eller når billedet ikke indlæses.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Denne alternative tekst blev oprettet automatisk og kan være upræcis.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Læs mere
pdfjs-editor-new-alt-text-create-automatically-button-label = Opret alternativ tekst automatisk
pdfjs-editor-new-alt-text-not-now-button = Ikke nu
pdfjs-editor-new-alt-text-error-title = Kunne ikke oprette alternativ tekst automatisk
pdfjs-editor-new-alt-text-error-description = Skriv din egen alternative tekst, eller prøv igen senere.
pdfjs-editor-new-alt-text-error-close-button = Luk
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Henter alternativ tekst AI-model ({ $downloadedSize } af { $totalSize } MB)
.aria-valuetext = Henter alternativ tekst AI-model ({ $downloadedSize } af { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Alternativ tekst tilføjet
pdfjs-editor-new-alt-text-added-button-label = Alternativ tekst tilføjet
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Mangler alternativ tekst
pdfjs-editor-new-alt-text-missing-button-label = Mangler alternativ tekst
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Gennemgå alternativ tekst
pdfjs-editor-new-alt-text-to-review-button-label = Gennemgå alternativ tekst
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Oprettet automatisk: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Indstillinger for alternativ tekst til billeder
pdfjs-image-alt-text-settings-button-label = Indstillinger for alternativ tekst til billeder
pdfjs-editor-alt-text-settings-dialog-label = Indstillinger for alternativ tekst til billeder
pdfjs-editor-alt-text-settings-automatic-title = Automatisk alternativ tekst
pdfjs-editor-alt-text-settings-create-model-button-label = Opret alternativ tekst automatisk
pdfjs-editor-alt-text-settings-create-model-description = Foreslår beskrivelser for at hjælpe folk, der ikke kan se billedet, eller når billedet ikke indlæses.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = AI-model til at oprette alternative tekster ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Kører lokalt på din enhed, så dine data forbliver private. Påkrævet for at anvende automatisk alternativ tekst.
pdfjs-editor-alt-text-settings-delete-model-button = Slet
pdfjs-editor-alt-text-settings-download-model-button = Hent
pdfjs-editor-alt-text-settings-downloading-model-button = Henter…
pdfjs-editor-alt-text-settings-editor-title = Redigering af alternativ tekst
pdfjs-editor-alt-text-settings-show-dialog-button-label = Vis redigering af alternativ tekst med det samme, når et billede tilføjes
pdfjs-editor-alt-text-settings-show-dialog-description = Hjælper dig med at sikre, at alle dine billeder har alternativ tekst.
pdfjs-editor-alt-text-settings-close-button = Luk
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Fremhævning fjernet
pdfjs-editor-undo-bar-message-freetext = Tekst fjernet
pdfjs-editor-undo-bar-message-ink = Tegning fjernet
pdfjs-editor-undo-bar-message-stamp = Billede fjernet
pdfjs-editor-undo-bar-message-signature = Underskrift fjernet
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } kommentar fjernet
*[other] { $count } kommentarer fjernet
}
pdfjs-editor-undo-bar-undo-button =
.title = Fortryd
pdfjs-editor-undo-bar-undo-button-label = Fortryd
pdfjs-editor-undo-bar-close-button =
.title = Luk
pdfjs-editor-undo-bar-close-button-label = Luk
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = Modal-vinduet gør det muligt for brugeren at oprette en underskrift, som kan føjes til PDF-dokumenter. Brugeren kan redigere navnet (der også fungerer som alternativ tekst) og eventuelt gemme signaturen, så den kan bruges igen.
pdfjs-editor-add-signature-dialog-title = Tilføj en underskrift
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Indtast
.title = Indtast
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Tegn
.title = Tegn
pdfjs-editor-add-signature-image-button = Billede
.title = Billede
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Indtast din underskrift
.placeholder = Indtast din underskrift
pdfjs-editor-add-signature-draw-placeholder = Tegn din underskrift
pdfjs-editor-add-signature-draw-thickness-range-label = Tykkelse
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Linjetykkelse: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Træk en fil herhen for at uploade den
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Eller vælg billedfiler
*[other] Eller vælg billedfiler
}
## Controls
pdfjs-editor-add-signature-description-label = Beskrivelse (alternativ tekst)
pdfjs-editor-add-signature-description-input =
.title = Beskrivelse (alternativ tekst)
pdfjs-editor-add-signature-description-default-when-drawing = Underskrift
pdfjs-editor-add-signature-clear-button-label = Ryd underskrift
pdfjs-editor-add-signature-clear-button =
.title = Ryd underskrift
pdfjs-editor-add-signature-save-checkbox = Gem underskrift
pdfjs-editor-add-signature-save-warning-message = Du har nået grænsen på 5 gemte underskrifter. Fjern en for at tilføje en ny.
pdfjs-editor-add-signature-image-upload-error-title = Kunne ikke uploade billede
pdfjs-editor-add-signature-image-upload-error-description = Kontroller din netværksforbindelse eller prøv med et andet billede.
pdfjs-editor-add-signature-error-close-button = Luk
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Annuller
pdfjs-editor-add-signature-add-button = Tilføj
pdfjs-editor-edit-signature-update-button = Opdater
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Fjern underskrift
pdfjs-editor-delete-signature-button-label = Fjern underskrift
pdfjs-editor-delete-signature-button1 =
.title = Fjern gemt underskrift
pdfjs-editor-delete-signature-button-label1 = Fjern gemt underskrift
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Rediger beskrivelse
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Rediger beskrivelse
================================================
FILE: cookbook/static/pdfjs/web/locale/de/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Eine Seite zurück
pdfjs-previous-button-label = Zurück
pdfjs-next-button =
.title = Eine Seite vor
pdfjs-next-button-label = Vor
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Seite
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = von { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } von { $pagesCount })
pdfjs-zoom-out-button =
.title = Verkleinern
pdfjs-zoom-out-button-label = Verkleinern
pdfjs-zoom-in-button =
.title = Vergrößern
pdfjs-zoom-in-button-label = Vergrößern
pdfjs-zoom-select =
.title = Zoom
pdfjs-presentation-mode-button =
.title = In Präsentationsmodus wechseln
pdfjs-presentation-mode-button-label = Präsentationsmodus
pdfjs-open-file-button =
.title = Datei öffnen
pdfjs-open-file-button-label = Öffnen
pdfjs-print-button =
.title = Drucken
pdfjs-print-button-label = Drucken
pdfjs-save-button =
.title = Speichern
pdfjs-save-button-label = Speichern
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Herunterladen
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Herunterladen
pdfjs-bookmark-button =
.title = Aktuelle Seite (URL von aktueller Seite anzeigen)
pdfjs-bookmark-button-label = Aktuelle Seite
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Werkzeuge
pdfjs-tools-button-label = Werkzeuge
pdfjs-first-page-button =
.title = Erste Seite anzeigen
pdfjs-first-page-button-label = Erste Seite anzeigen
pdfjs-last-page-button =
.title = Letzte Seite anzeigen
pdfjs-last-page-button-label = Letzte Seite anzeigen
pdfjs-page-rotate-cw-button =
.title = Im Uhrzeigersinn drehen
pdfjs-page-rotate-cw-button-label = Im Uhrzeigersinn drehen
pdfjs-page-rotate-ccw-button =
.title = Gegen Uhrzeigersinn drehen
pdfjs-page-rotate-ccw-button-label = Gegen Uhrzeigersinn drehen
pdfjs-cursor-text-select-tool-button =
.title = Textauswahl-Werkzeug aktivieren
pdfjs-cursor-text-select-tool-button-label = Textauswahl-Werkzeug
pdfjs-cursor-hand-tool-button =
.title = Hand-Werkzeug aktivieren
pdfjs-cursor-hand-tool-button-label = Hand-Werkzeug
pdfjs-scroll-page-button =
.title = Seiten einzeln anordnen
pdfjs-scroll-page-button-label = Einzelseitenanordnung
pdfjs-scroll-vertical-button =
.title = Seiten übereinander anordnen
pdfjs-scroll-vertical-button-label = Vertikale Seitenanordnung
pdfjs-scroll-horizontal-button =
.title = Seiten nebeneinander anordnen
pdfjs-scroll-horizontal-button-label = Horizontale Seitenanordnung
pdfjs-scroll-wrapped-button =
.title = Seiten neben- und übereinander anordnen, abhängig vom Platz
pdfjs-scroll-wrapped-button-label = Kombinierte Seitenanordnung
pdfjs-spread-none-button =
.title = Seiten nicht nebeneinander anzeigen
pdfjs-spread-none-button-label = Einzelne Seiten
pdfjs-spread-odd-button =
.title = Jeweils eine ungerade und eine gerade Seite nebeneinander anzeigen
pdfjs-spread-odd-button-label = Ungerade + gerade Seite
pdfjs-spread-even-button =
.title = Jeweils eine gerade und eine ungerade Seite nebeneinander anzeigen
pdfjs-spread-even-button-label = Gerade + ungerade Seite
## Document properties dialog
pdfjs-document-properties-button =
.title = Dokumenteigenschaften
pdfjs-document-properties-button-label = Dokumenteigenschaften…
pdfjs-document-properties-file-name = Dateiname:
pdfjs-document-properties-file-size = Dateigröße:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } Bytes)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } Bytes)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } Bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } Bytes)
pdfjs-document-properties-title = Titel:
pdfjs-document-properties-author = Autor:
pdfjs-document-properties-subject = Thema:
pdfjs-document-properties-keywords = Stichwörter:
pdfjs-document-properties-creation-date = Erstelldatum:
pdfjs-document-properties-modification-date = Bearbeitungsdatum:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date } { $time }
pdfjs-document-properties-creator = Anwendung:
pdfjs-document-properties-producer = PDF erstellt mit:
pdfjs-document-properties-version = PDF-Version:
pdfjs-document-properties-page-count = Seitenzahl:
pdfjs-document-properties-page-size = Seitengröße:
pdfjs-document-properties-page-size-unit-inches = Zoll
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = Hochformat
pdfjs-document-properties-page-size-orientation-landscape = Querformat
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letter
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Schnelle Webanzeige:
pdfjs-document-properties-linearized-yes = Ja
pdfjs-document-properties-linearized-no = Nein
pdfjs-document-properties-close-button = Schließen
## Print
pdfjs-print-progress-message = Dokument wird für Drucken vorbereitet…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress } %
pdfjs-print-progress-close-button = Abbrechen
pdfjs-printing-not-supported = Warnung: Die Drucken-Funktion wird durch diesen Browser nicht vollständig unterstützt.
pdfjs-printing-not-ready = Warnung: Die PDF-Datei ist nicht vollständig geladen, dies ist für das Drucken aber empfohlen.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Sidebar umschalten
pdfjs-toggle-sidebar-notification-button =
.title = Sidebar umschalten (Dokument enthält Dokumentstruktur/Anhänge/Ebenen)
pdfjs-toggle-sidebar-button-label = Sidebar umschalten
pdfjs-document-outline-button =
.title = Dokumentstruktur anzeigen (Doppelklicken, um alle Einträge aus- bzw. einzuklappen)
pdfjs-document-outline-button-label = Dokumentstruktur
pdfjs-attachments-button =
.title = Anhänge anzeigen
pdfjs-attachments-button-label = Anhänge
pdfjs-layers-button =
.title = Ebenen anzeigen (Doppelklicken, um alle Ebenen auf den Standardzustand zurückzusetzen)
pdfjs-layers-button-label = Ebenen
pdfjs-thumbs-button =
.title = Miniaturansichten anzeigen
pdfjs-thumbs-button-label = Miniaturansichten
pdfjs-current-outline-item-button =
.title = Aktuelles Struktur-Element finden
pdfjs-current-outline-item-button-label = Aktuelles Struktur-Element
pdfjs-findbar-button =
.title = Dokument durchsuchen
pdfjs-findbar-button-label = Suchen
pdfjs-additional-layers = Zusätzliche Ebenen
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Seite { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Miniaturansicht von Seite { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Suchen
.placeholder = Dokument durchsuchen…
pdfjs-find-previous-button =
.title = Vorheriges Vorkommen des Suchbegriffs finden
pdfjs-find-previous-button-label = Zurück
pdfjs-find-next-button =
.title = Nächstes Vorkommen des Suchbegriffs finden
pdfjs-find-next-button-label = Weiter
pdfjs-find-highlight-checkbox = Alle hervorheben
pdfjs-find-match-case-checkbox-label = Groß-/Kleinschreibung beachten
pdfjs-find-match-diacritics-checkbox-label = Akzente
pdfjs-find-entire-word-checkbox-label = Ganze Wörter
pdfjs-find-reached-top = Anfang des Dokuments erreicht, fahre am Ende fort
pdfjs-find-reached-bottom = Ende des Dokuments erreicht, fahre am Anfang fort
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } von { $total } Übereinstimmung
*[other] { $current } von { $total } Übereinstimmungen
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Mehr als { $limit } Übereinstimmung
*[other] Mehr als { $limit } Übereinstimmungen
}
pdfjs-find-not-found = Suchbegriff nicht gefunden
## Predefined zoom values
pdfjs-page-scale-width = Seitenbreite
pdfjs-page-scale-fit = Seitengröße
pdfjs-page-scale-auto = Automatischer Zoom
pdfjs-page-scale-actual = Originalgröße
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale } %
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Seite { $page }
## Loading indicator messages
pdfjs-loading-error = Beim Laden der PDF-Datei trat ein Fehler auf.
pdfjs-invalid-file-error = Ungültige oder beschädigte PDF-Datei
pdfjs-missing-file-error = Fehlende PDF-Datei
pdfjs-unexpected-response-error = Unerwartete Antwort des Servers
pdfjs-rendering-error = Beim Darstellen der Seite trat ein Fehler auf.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [Anlage: { $type }]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Geben Sie zum Öffnen der PDF-Datei deren Passwort ein.
pdfjs-password-invalid = Falsches Passwort. Bitte versuchen Sie es erneut.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Abbrechen
pdfjs-web-fonts-disabled = Web-Schriftarten sind deaktiviert: Eingebettete PDF-Schriftarten konnten nicht geladen werden.
## Editing
pdfjs-editor-free-text-button =
.title = Text
pdfjs-editor-free-text-button-label = Text
pdfjs-editor-ink-button =
.title = Zeichnen
pdfjs-editor-ink-button-label = Zeichnen
pdfjs-editor-stamp-button =
.title = Grafiken hinzufügen oder bearbeiten
pdfjs-editor-stamp-button-label = Grafiken hinzufügen oder bearbeiten
pdfjs-editor-highlight-button =
.title = Hervorheben
pdfjs-editor-highlight-button-label = Hervorheben
pdfjs-highlight-floating-button1 =
.title = Hervorheben
.aria-label = Hervorheben
pdfjs-highlight-floating-button-label = Hervorheben
pdfjs-editor-signature-button =
.title = Unterschrift hinzufügen
pdfjs-editor-signature-button-label = Unterschrift hinzufügen
## Default editor aria labels
# “Highlight” is a noun, the string is used on the editor for highlights.
pdfjs-editor-highlight-editor =
.aria-label = Hervorhebungs-Editor
# “Drawing” is a noun, the string is used on the editor for drawings.
pdfjs-editor-ink-editor =
.aria-label = Zeichnungseditor
pdfjs-editor-signature-editor =
.aria-label = Signatur-Editor
pdfjs-editor-stamp-editor =
.aria-label = Grafik-Editor
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Zeichnung entfernen
pdfjs-editor-remove-freetext-button =
.title = Text entfernen
pdfjs-editor-remove-stamp-button =
.title = Grafik entfernen
pdfjs-editor-remove-highlight-button =
.title = Hervorhebung entfernen
pdfjs-editor-remove-signature-button =
.title = Unterschrift entfernen
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Farbe
pdfjs-editor-free-text-size-input = Größe
pdfjs-editor-ink-color-input = Farbe
pdfjs-editor-ink-thickness-input = Linienstärke
pdfjs-editor-ink-opacity-input = Deckkraft
pdfjs-editor-stamp-add-image-button =
.title = Grafik hinzufügen
pdfjs-editor-stamp-add-image-button-label = Grafik hinzufügen
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Linienstärke
pdfjs-editor-free-highlight-thickness-title =
.title = Linienstärke beim Hervorheben anderer Elemente als Text ändern
pdfjs-editor-add-signature-container =
.aria-label = Signaturkontrollen und gespeicherte Signaturen
pdfjs-editor-signature-add-signature-button =
.title = Neue Unterschrift hinzufügen
pdfjs-editor-signature-add-signature-button-label = Neue Unterschrift hinzufügen
# Used on the button to use an already saved signature.
# Variables:
# $description (String) - a string describing/labeling the signature.
pdfjs-editor-add-saved-signature-button =
.title = Gespeicherte Signatur: { $description }
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Texteditor
.default-content = Schreiben beginnen…
pdfjs-free-text =
.aria-label = Texteditor
pdfjs-free-text-default-content = Schreiben beginnen…
pdfjs-ink =
.aria-label = Zeichnungseditor
pdfjs-ink-canvas =
.aria-label = Vom Benutzer erstelltes Bild
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Alternativ-Text
pdfjs-editor-alt-text-edit-button =
.aria-label = Alternativ-Text bearbeiten
pdfjs-editor-alt-text-edit-button-label = Alternativ-Text bearbeiten
pdfjs-editor-alt-text-dialog-label = Option wählen
pdfjs-editor-alt-text-dialog-description = Alt-Text (Alternativtext) hilft, wenn Personen die Grafik nicht sehen können oder wenn sie nicht geladen wird.
pdfjs-editor-alt-text-add-description-label = Beschreibung hinzufügen
pdfjs-editor-alt-text-add-description-description = Ziel sind 1-2 Sätze, die das Thema, das Szenario oder Aktionen beschreiben.
pdfjs-editor-alt-text-mark-decorative-label = Als dekorativ markieren
pdfjs-editor-alt-text-mark-decorative-description = Dies wird für Ziergrafiken wie Ränder oder Wasserzeichen verwendet.
pdfjs-editor-alt-text-cancel-button = Abbrechen
pdfjs-editor-alt-text-save-button = Speichern
pdfjs-editor-alt-text-decorative-tooltip = Als dekorativ markiert
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Zum Beispiel: "Ein junger Mann setzt sich an einen Tisch, um zu essen."
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Alternativ-Text
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Linke obere Ecke - Größe ändern
pdfjs-editor-resizer-label-top-middle = Oben mittig - Größe ändern
pdfjs-editor-resizer-label-top-right = Rechts oben - Größe ändern
pdfjs-editor-resizer-label-middle-right = Mitte rechts - Größe ändern
pdfjs-editor-resizer-label-bottom-right = Rechte untere Ecke - Größe ändern
pdfjs-editor-resizer-label-bottom-middle = Unten mittig - Größe ändern
pdfjs-editor-resizer-label-bottom-left = Linke untere Ecke - Größe ändern
pdfjs-editor-resizer-label-middle-left = Mitte links - Größe ändern
pdfjs-editor-resizer-top-left =
.aria-label = Linke obere Ecke - Größe ändern
pdfjs-editor-resizer-top-middle =
.aria-label = Oben mittig - Größe ändern
pdfjs-editor-resizer-top-right =
.aria-label = Rechts oben - Größe ändern
pdfjs-editor-resizer-middle-right =
.aria-label = Mitte rechts - Größe ändern
pdfjs-editor-resizer-bottom-right =
.aria-label = Rechte untere Ecke - Größe ändern
pdfjs-editor-resizer-bottom-middle =
.aria-label = Unten mittig - Größe ändern
pdfjs-editor-resizer-bottom-left =
.aria-label = Linke untere Ecke - Größe ändern
pdfjs-editor-resizer-middle-left =
.aria-label = Mitte links - Größe ändern
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Hervorhebungsfarbe
pdfjs-editor-colorpicker-button =
.title = Farbe ändern
pdfjs-editor-colorpicker-dropdown =
.aria-label = Farbauswahl
pdfjs-editor-colorpicker-yellow =
.title = Gelb
pdfjs-editor-colorpicker-green =
.title = Grün
pdfjs-editor-colorpicker-blue =
.title = Blau
pdfjs-editor-colorpicker-pink =
.title = Pink
pdfjs-editor-colorpicker-red =
.title = Rot
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Alle anzeigen
pdfjs-editor-highlight-show-all-button =
.title = Alle anzeigen
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Alternativ-Text (Grafikbeschreibung) bearbeiten
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Alternativ-Text (Grafikbeschreibung) hinzufügen
pdfjs-editor-new-alt-text-textarea =
.placeholder = Schreiben Sie Ihre Beschreibung hier…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Kurze Beschreibung für Personen, die die Grafik nicht sehen können, oder wenn die Grafik nicht geladen wird.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Dieser Alternativ-Text wurde automatisch erstellt und könnte ungenau sein.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Weitere Informationen
pdfjs-editor-new-alt-text-create-automatically-button-label = Alternativ-Text automatisch erstellen
pdfjs-editor-new-alt-text-not-now-button = Nicht jetzt
pdfjs-editor-new-alt-text-error-title = Alternativ-Text konnte nicht automatisch erstellt werden
pdfjs-editor-new-alt-text-error-description = Bitte schreiben Sie Ihren eigenen Alternativ-Text oder versuchen Sie es später erneut.
pdfjs-editor-new-alt-text-error-close-button = Schließen
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Alternativ-Text-KI-Modell wird heruntergeladen ({ $downloadedSize } von { $totalSize } MB)
.aria-valuetext = Alternativ-Text-KI-Modell wird heruntergeladen ({ $downloadedSize } von { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Alternativ-Text hinzugefügt
pdfjs-editor-new-alt-text-added-button-label = Alternativ-Text hinzugefügt
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Fehlender Alternativ-Text
pdfjs-editor-new-alt-text-missing-button-label = Fehlender Alternativ-Text
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Alternativ-Text überprüfen
pdfjs-editor-new-alt-text-to-review-button-label = Alternativ-Text überprüfen
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Automatisch erstellt: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Alternativ-Text-Einstellungen für Grafiken
pdfjs-image-alt-text-settings-button-label = Alternativ-Text-Einstellungen für Grafiken
pdfjs-editor-alt-text-settings-dialog-label = Alternativ-Text-Einstellungen für Grafiken
pdfjs-editor-alt-text-settings-automatic-title = Automatischer Alternativ-Text
pdfjs-editor-alt-text-settings-create-model-button-label = Alternativ-Text automatisch erstellen
pdfjs-editor-alt-text-settings-create-model-description = Schlägt Beschreibungen vor, um Personen zu helfen, die die Grafik nicht sehen können, oder wenn die Grafik nicht geladen wird.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Alternativ-Text-KI-Modell ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Wird lokal auf Ihrem Gerät ausgeführt, sodass Ihre Daten privat bleiben. Erforderlich für automatischen Alternativ-Text.
pdfjs-editor-alt-text-settings-delete-model-button = Löschen
pdfjs-editor-alt-text-settings-download-model-button = Herunterladen
pdfjs-editor-alt-text-settings-downloading-model-button = Wird heruntergeladen…
pdfjs-editor-alt-text-settings-editor-title = Alternativ-Texteditor
pdfjs-editor-alt-text-settings-show-dialog-button-label = Alternativ-Texteditor beim Hinzufügen einer Grafik anzeigen
pdfjs-editor-alt-text-settings-show-dialog-description = Hilft Ihnen, sicherzustellen, dass alle Ihre Grafiken Alternativ-Text haben.
pdfjs-editor-alt-text-settings-close-button = Schließen
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Hervorhebung entfernt
pdfjs-editor-undo-bar-message-freetext = Text entfernt
pdfjs-editor-undo-bar-message-ink = Zeichnung entfernt
pdfjs-editor-undo-bar-message-stamp = Grafik entfernt
pdfjs-editor-undo-bar-message-signature = Unterschrift entfernt
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } Anmerkung entfernt
*[other] { $count } Anmerkungen entfernt
}
pdfjs-editor-undo-bar-undo-button =
.title = Rückgängig
pdfjs-editor-undo-bar-undo-button-label = Rückgängig
pdfjs-editor-undo-bar-close-button =
.title = Schließen
pdfjs-editor-undo-bar-close-button-label = Schließen
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = Dieses Modal ermöglicht es dem Benutzer, eine Unterschrift zu erstellen, um sie zu einem PDF-Dokument hinzuzufügen. Der Benutzer kann den Namen bearbeiten (der auch als Alt-Text dient) und optional die Unterschrift zur wiederholten Verwendung speichern.
pdfjs-editor-add-signature-dialog-title = Unterschrift hinzufügen
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Eintippen
.title = Eintippen
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Zeichnen
.title = Zeichnen
pdfjs-editor-add-signature-image-button = Grafik
.title = Grafik
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Tippen Sie Ihre Unterschrift ein
.placeholder = Tippen Sie Ihre Unterschrift ein
pdfjs-editor-add-signature-draw-placeholder = Ihre Unterschrift zeichnen
pdfjs-editor-add-signature-draw-thickness-range-label = Linienstärke
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Zeichnungsstärke: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Datei zum Hochladen hierher ziehen
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Oder Grafikdateien wählen
*[other] Oder Bilddateien durchsuchen
}
## Controls
pdfjs-editor-add-signature-description-label = Beschreibung (alternativer Text)
pdfjs-editor-add-signature-description-input =
.title = Beschreibung (alternativer Text)
pdfjs-editor-add-signature-description-default-when-drawing = Unterschrift
pdfjs-editor-add-signature-clear-button-label = Unterschrift löschen
pdfjs-editor-add-signature-clear-button =
.title = Unterschrift löschen
pdfjs-editor-add-signature-save-checkbox = Unterschrift speichern
pdfjs-editor-add-signature-save-warning-message = Sie haben die Grenze von 5 gespeicherten Unterschriften erreicht. Entfernen Sie eine, um weitere zu speichern.
pdfjs-editor-add-signature-image-upload-error-title = Grafik konnte nicht hochgeladen werden
pdfjs-editor-add-signature-image-upload-error-description = Überprüfen Sie Ihre Netzwerkverbindung, oder versuchen Sie es mit einer anderen Grafik.
pdfjs-editor-add-signature-error-close-button = Schließen
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Abbrechen
pdfjs-editor-add-signature-add-button = Hinzufügen
pdfjs-editor-edit-signature-update-button = Aktualisieren
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Unterschrift entfernen
pdfjs-editor-delete-signature-button-label = Unterschrift entfernen
pdfjs-editor-delete-signature-button1 =
.title = Gespeicherte Signatur entfernen
pdfjs-editor-delete-signature-button-label1 = Gespeicherte Signatur entfernen
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Beschreibung bearbeiten
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Beschreibung bearbeiten
================================================
FILE: cookbook/static/pdfjs/web/locale/dsb/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Pjerwjejšny bok
pdfjs-previous-button-label = Slědk
pdfjs-next-button =
.title = Pśiducy bok
pdfjs-next-button-label = Dalej
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Bok
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = z { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } z { $pagesCount })
pdfjs-zoom-out-button =
.title = Pómjeńšyś
pdfjs-zoom-out-button-label = Pómjeńšyś
pdfjs-zoom-in-button =
.title = Pówětšyś
pdfjs-zoom-in-button-label = Pówětšyś
pdfjs-zoom-select =
.title = Skalěrowanje
pdfjs-presentation-mode-button =
.title = Do prezentaciskego modusa pśejś
pdfjs-presentation-mode-button-label = Prezentaciski modus
pdfjs-open-file-button =
.title = Dataju wócyniś
pdfjs-open-file-button-label = Wócyniś
pdfjs-print-button =
.title = Śišćaś
pdfjs-print-button-label = Śišćaś
pdfjs-save-button =
.title = Składowaś
pdfjs-save-button-label = Składowaś
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Ześěgnuś
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Ześěgnuś
pdfjs-bookmark-button =
.title = Aktualny bok (URL z aktualnego boka pokazaś)
pdfjs-bookmark-button-label = Aktualny bok
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Rědy
pdfjs-tools-button-label = Rědy
pdfjs-first-page-button =
.title = K prědnemu bokoju
pdfjs-first-page-button-label = K prědnemu bokoju
pdfjs-last-page-button =
.title = K slědnemu bokoju
pdfjs-last-page-button-label = K slědnemu bokoju
pdfjs-page-rotate-cw-button =
.title = Wobwjertnuś ako špěra źo
pdfjs-page-rotate-cw-button-label = Wobwjertnuś ako špěra źo
pdfjs-page-rotate-ccw-button =
.title = Wobwjertnuś nawopaki ako špěra źo
pdfjs-page-rotate-ccw-button-label = Wobwjertnuś nawopaki ako špěra źo
pdfjs-cursor-text-select-tool-button =
.title = Rěd za wuběranje teksta zmóžniś
pdfjs-cursor-text-select-tool-button-label = Rěd za wuběranje teksta
pdfjs-cursor-hand-tool-button =
.title = Rucny rěd zmóžniś
pdfjs-cursor-hand-tool-button-label = Rucny rěd
pdfjs-scroll-page-button =
.title = Kulanje boka wužywaś
pdfjs-scroll-page-button-label = Kulanje boka
pdfjs-scroll-vertical-button =
.title = Wertikalne suwanje wužywaś
pdfjs-scroll-vertical-button-label = Wertikalne suwanje
pdfjs-scroll-horizontal-button =
.title = Horicontalne suwanje wužywaś
pdfjs-scroll-horizontal-button-label = Horicontalne suwanje
pdfjs-scroll-wrapped-button =
.title = Pózlažke suwanje wužywaś
pdfjs-scroll-wrapped-button-label = Pózlažke suwanje
pdfjs-spread-none-button =
.title = Boki njezwězaś
pdfjs-spread-none-button-label = Žeden dwójny bok
pdfjs-spread-odd-button =
.title = Boki zachopinajucy z njerownymi bokami zwězaś
pdfjs-spread-odd-button-label = Njerowne boki
pdfjs-spread-even-button =
.title = Boki zachopinajucy z rownymi bokami zwězaś
pdfjs-spread-even-button-label = Rowne boki
## Document properties dialog
pdfjs-document-properties-button =
.title = Dokumentowe kakosći…
pdfjs-document-properties-button-label = Dokumentowe kakosći…
pdfjs-document-properties-file-name = Mě dataje:
pdfjs-document-properties-file-size = Wjelikosć dataje:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bajtow)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bajtow)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bajtow)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bajtow)
pdfjs-document-properties-title = Titel:
pdfjs-document-properties-author = Awtor:
pdfjs-document-properties-subject = Tema:
pdfjs-document-properties-keywords = Klucowe słowa:
pdfjs-document-properties-creation-date = Datum napóranja:
pdfjs-document-properties-modification-date = Datum změny:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Awtor:
pdfjs-document-properties-producer = PDF-gótowaŕ:
pdfjs-document-properties-version = PDF-wersija:
pdfjs-document-properties-page-count = Licba bokow:
pdfjs-document-properties-page-size = Wjelikosć boka:
pdfjs-document-properties-page-size-unit-inches = col
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = wusoki format
pdfjs-document-properties-page-size-orientation-landscape = prěcny format
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letter
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Fast Web View:
pdfjs-document-properties-linearized-yes = Jo
pdfjs-document-properties-linearized-no = Ně
pdfjs-document-properties-close-button = Zacyniś
## Print
pdfjs-print-progress-message = Dokument pśigótujo se za śišćanje…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Pśetergnuś
pdfjs-printing-not-supported = Warnowanje: Śišćanje njepódpěra se połnje pśez toś ten wobglědowak.
pdfjs-printing-not-ready = Warnowanje: PDF njejo se za śišćanje dopołnje zacytał.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Bócnicu pokazaś/schowaś
pdfjs-toggle-sidebar-notification-button =
.title = Bocnicu pśešaltowaś (dokument rozrědowanje/pśipiski/warstwy wopśimujo)
pdfjs-toggle-sidebar-button-label = Bócnicu pokazaś/schowaś
pdfjs-document-outline-button =
.title = Dokumentowe naraźenje pokazaś (dwójne kliknjenje, aby se wšykne zapiski pokazali/schowali)
pdfjs-document-outline-button-label = Dokumentowa struktura
pdfjs-attachments-button =
.title = Pśidanki pokazaś
pdfjs-attachments-button-label = Pśidanki
pdfjs-layers-button =
.title = Warstwy pokazaś (klikniśo dwójcy, aby wšykne warstwy na standardny staw slědk stajił)
pdfjs-layers-button-label = Warstwy
pdfjs-thumbs-button =
.title = Miniatury pokazaś
pdfjs-thumbs-button-label = Miniatury
pdfjs-current-outline-item-button =
.title = Aktualny rozrědowański zapisk pytaś
pdfjs-current-outline-item-button-label = Aktualny rozrědowański zapisk
pdfjs-findbar-button =
.title = W dokumenśe pytaś
pdfjs-findbar-button-label = Pytaś
pdfjs-additional-layers = Dalšne warstwy
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Bok { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Miniatura boka { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Pytaś
.placeholder = W dokumenśe pytaś…
pdfjs-find-previous-button =
.title = Pjerwjejšne wustupowanje pytańskego wuraza pytaś
pdfjs-find-previous-button-label = Slědk
pdfjs-find-next-button =
.title = Pśidujuce wustupowanje pytańskego wuraza pytaś
pdfjs-find-next-button-label = Dalej
pdfjs-find-highlight-checkbox = Wšykne wuzwignuś
pdfjs-find-match-case-checkbox-label = Na wjelikopisanje źiwaś
pdfjs-find-match-diacritics-checkbox-label = Diakritiske znamuška wužywaś
pdfjs-find-entire-word-checkbox-label = Cełe słowa
pdfjs-find-reached-top = Zachopjeńk dokumenta dostany, pókšacujo se z kóńcom
pdfjs-find-reached-bottom = Kóńc dokumenta dostany, pókšacujo se ze zachopjeńkom
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } z { $total } wótpowědnika
[two] { $current } z { $total } wótpowědnikowu
[few] { $current } z { $total } wótpowědnikow
*[other] { $current } z { $total } wótpowědnikow
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Wušej { $limit } wótpowědnik
[two] Wušej { $limit } wótpowědnika
[few] Wušej { $limit } wótpowědniki
*[other] Wušej { $limit } wótpowědniki
}
pdfjs-find-not-found = Pytański wuraz njejo se namakał
## Predefined zoom values
pdfjs-page-scale-width = Šyrokosć boka
pdfjs-page-scale-fit = Wjelikosć boka
pdfjs-page-scale-auto = Awtomatiske skalěrowanje
pdfjs-page-scale-actual = Aktualna wjelikosć
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Bok { $page }
## Loading indicator messages
pdfjs-loading-error = Pśi zacytowanju PDF jo zmólka nastała.
pdfjs-invalid-file-error = Njepłaśiwa abo wobškóźona PDF-dataja.
pdfjs-missing-file-error = Felujuca PDF-dataja.
pdfjs-unexpected-response-error = Njewócakane serwerowe wótegrono.
pdfjs-rendering-error = Pśi zwobraznjanju boka jo zmólka nastała.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [Typ pśipiskow: { $type }]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Zapódajśo gronidło, aby PDF-dataju wócynił.
pdfjs-password-invalid = Njepłaśiwe gronidło. Pšosym wopytajśo hyšći raz.
pdfjs-password-ok-button = W pórěźe
pdfjs-password-cancel-button = Pśetergnuś
pdfjs-web-fonts-disabled = Webpisma su znjemóžnjone: njejo móžno, zasajźone PDF-pisma wužywaś.
## Editing
pdfjs-editor-free-text-button =
.title = Tekst
pdfjs-editor-free-text-button-label = Tekst
pdfjs-editor-ink-button =
.title = Kresliś
pdfjs-editor-ink-button-label = Kresliś
pdfjs-editor-stamp-button =
.title = Wobraze pśidaś abo wobźěłaś
pdfjs-editor-stamp-button-label = Wobraze pśidaś abo wobźěłaś
pdfjs-editor-highlight-button =
.title = Wuzwignuś
pdfjs-editor-highlight-button-label = Wuzwignuś
pdfjs-highlight-floating-button1 =
.title = Wuzwignuś
.aria-label = Wuzwignuś
pdfjs-highlight-floating-button-label = Wuzwignuś
pdfjs-editor-signature-button =
.title = Signaturu pśidaś
pdfjs-editor-signature-button-label = Signaturu pśidaś
## Default editor aria labels
# “Highlight” is a noun, the string is used on the editor for highlights.
pdfjs-editor-highlight-editor =
.aria-label = Editor wuzwignjenja
# “Drawing” is a noun, the string is used on the editor for drawings.
pdfjs-editor-ink-editor =
.aria-label = Kresleński editor
pdfjs-editor-signature-editor =
.aria-label = Editor signaturow
pdfjs-editor-stamp-editor =
.aria-label = Wobrazowy editor
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Kreslanku wótwónoźeś
pdfjs-editor-remove-freetext-button =
.title = Tekst wótwónoźeś
pdfjs-editor-remove-stamp-button =
.title = Wobraz wótwónoźeś
pdfjs-editor-remove-highlight-button =
.title = Wuzwignjenje wótpóraś
pdfjs-editor-remove-signature-button =
.title = Signaturu wótwónoźeś
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Barwa
pdfjs-editor-free-text-size-input = Wjelikosć
pdfjs-editor-ink-color-input = Barwa
pdfjs-editor-ink-thickness-input = Tłustosć
pdfjs-editor-ink-opacity-input = Opacita
pdfjs-editor-stamp-add-image-button =
.title = Wobraz pśidaś
pdfjs-editor-stamp-add-image-button-label = Wobraz pśidaś
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Tłustosć
pdfjs-editor-free-highlight-thickness-title =
.title = Tłustosć změniś, gaž se zapiski wuzwiguju, kótarež tekst njejsu
pdfjs-editor-add-signature-container =
.aria-label = Wóźeńske elementy signaturow a skłaźone signatury
pdfjs-editor-signature-add-signature-button =
.title = Nowu signaturu pśidaś
pdfjs-editor-signature-add-signature-button-label = Nowu signaturu pśidaś
# Used on the button to use an already saved signature.
# Variables:
# $description (String) - a string describing/labeling the signature.
pdfjs-editor-add-saved-signature-button =
.title = Skłaźona signatura: { $description }
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Tekstowy editor
.default-content = Zachopśo pisaś …
pdfjs-free-text =
.aria-label = Tekstowy editor
pdfjs-free-text-default-content = Zachopśo pisaś…
pdfjs-ink =
.aria-label = Kresleński editor
pdfjs-ink-canvas =
.aria-label = Wobraz napórany wót wužywarja
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Alternatiwny tekst
pdfjs-editor-alt-text-edit-button =
.aria-label = Alternatiwny tekst wobźěłaś
pdfjs-editor-alt-text-edit-button-label = Alternatiwny tekst wobźěłaś
pdfjs-editor-alt-text-dialog-label = Nastajenje wubraś
pdfjs-editor-alt-text-dialog-description = Alternatiwny tekst pomaga, gaž luźe njamógu wobraz wiźeś abo gaž se wobraz njezacytajo.
pdfjs-editor-alt-text-add-description-label = Wopisanje pśidaś
pdfjs-editor-alt-text-add-description-description = Pišćo 1 sadu abo 2 saźe, kótarejž temu, nastajenje abo akcije wopisujotej.
pdfjs-editor-alt-text-mark-decorative-label = Ako dekoratiwny markěrowaś
pdfjs-editor-alt-text-mark-decorative-description = To se za pyšnjece wobraze wužywa, na pśikład ramiki abo wódowe znamjenja.
pdfjs-editor-alt-text-cancel-button = Pśetergnuś
pdfjs-editor-alt-text-save-button = Składowaś
pdfjs-editor-alt-text-decorative-tooltip = Ako dekoratiwny markěrowany
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Na pśikład, „Młody muski za blidom sejźi, aby jěź jědł“
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Alternatiwny tekst
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Górjejce nalěwo – wjelikosć změniś
pdfjs-editor-resizer-label-top-middle = Górjejce wesrjejź – wjelikosć změniś
pdfjs-editor-resizer-label-top-right = Górjejce napšawo – wjelikosć změniś
pdfjs-editor-resizer-label-middle-right = Wesrjejź napšawo – wjelikosć změniś
pdfjs-editor-resizer-label-bottom-right = Dołojce napšawo – wjelikosć změniś
pdfjs-editor-resizer-label-bottom-middle = Dołojce wesrjejź – wjelikosć změniś
pdfjs-editor-resizer-label-bottom-left = Dołojce nalěwo – wjelikosć změniś
pdfjs-editor-resizer-label-middle-left = Wesrjejź nalěwo – wjelikosć změniś
pdfjs-editor-resizer-top-left =
.aria-label = Górjejce nalěwo – wjelikosć změniś
pdfjs-editor-resizer-top-middle =
.aria-label = Górjejce wesrjejź – wjelikosć změniś
pdfjs-editor-resizer-top-right =
.aria-label = Górjejce napšawo – wjelikosć změniś
pdfjs-editor-resizer-middle-right =
.aria-label = Wesrjejź napšawo – wjelikosć změniś
pdfjs-editor-resizer-bottom-right =
.aria-label = Dołojce napšawo – wjelikosć změniś
pdfjs-editor-resizer-bottom-middle =
.aria-label = Dołojce wesrjejź – wjelikosć změniś
pdfjs-editor-resizer-bottom-left =
.aria-label = Dołojce nalěwo – wjelikosć změniś
pdfjs-editor-resizer-middle-left =
.aria-label = Wesrjejź nalěwo – wjelikosć změniś
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Barwa wuzwignjenja
pdfjs-editor-colorpicker-button =
.title = Barwu změniś
pdfjs-editor-colorpicker-dropdown =
.aria-label = Wuběrk barwow
pdfjs-editor-colorpicker-yellow =
.title = Žołty
pdfjs-editor-colorpicker-green =
.title = Zeleny
pdfjs-editor-colorpicker-blue =
.title = Módry
pdfjs-editor-colorpicker-pink =
.title = Pink
pdfjs-editor-colorpicker-red =
.title = Cerwjeny
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Wšykne pokazaś
pdfjs-editor-highlight-show-all-button =
.title = Wšykne pokazaś
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Alternatiwny tekst wobźěłaś (wobrazowe wopisanje)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Alternatiwny tekst pśidaś (wobrazowe wopisanje)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Pišćo how swójo wopisanje…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Krotke wopisanje za luźe, kótarež njamóžośo wobraz wiźeś abo gaž se wobraz njezacytajo.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Toś ten alternatiwny tekst jo se awtomatiski napórał a jo snaź njedokradny.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Dalšne informacije
pdfjs-editor-new-alt-text-create-automatically-button-label = Alternatiwny tekst awtomatiski napóraś
pdfjs-editor-new-alt-text-not-now-button = Nic něnto
pdfjs-editor-new-alt-text-error-title = Alternatiwny tekst njedajo se awtomatiski napóraś
pdfjs-editor-new-alt-text-error-description = Pšosym pišćo swój alternatiwny tekst abo wopytajśo pózdźej hyšći raz.
pdfjs-editor-new-alt-text-error-close-button = Zacyniś
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Model KI za alternatiwny tekst se ześěgujo ({ $downloadedSize } z { $totalSize } MB)
.aria-valuetext = Model KI za alternatiwny tekst se ześěgujo ({ $downloadedSize } z { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Alternatiwny tekst jo se pśidał
pdfjs-editor-new-alt-text-added-button-label = Alternatiwny tekst jo se pśidał
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Alternatiwny tekst felujo
pdfjs-editor-new-alt-text-missing-button-label = Alternatiwny tekst felujo
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Alternatiwny tekst pśeglědowaś
pdfjs-editor-new-alt-text-to-review-button-label = Alternatiwny tekst pśeglědowaś
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Awtomatiski napórany: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Nastajenja alternatiwnego wobrazowego teksta
pdfjs-image-alt-text-settings-button-label = Nastajenja alternatiwnego wobrazowego teksta
pdfjs-editor-alt-text-settings-dialog-label = Nastajenja alternatiwnego wobrazowego teksta
pdfjs-editor-alt-text-settings-automatic-title = Awtomatiski alternatiwny tekst
pdfjs-editor-alt-text-settings-create-model-button-label = Alternatiwny tekst awtomatiski napóraś
pdfjs-editor-alt-text-settings-create-model-description = Naraźujo wopisanja, aby pomagał ludam, kótarež njamóžośo wobraz wiźeś abo gaž se wobraz njezacytajo.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Model KI alternatiwnego teksta ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Běžy lokalnje na wašom rěźe, aby waše daty priwatne wóstali. Za awtomatiski alternatiwny tekst trjebny.
pdfjs-editor-alt-text-settings-delete-model-button = Lašowaś
pdfjs-editor-alt-text-settings-download-model-button = Ześěgnuś
pdfjs-editor-alt-text-settings-downloading-model-button = Ześěgujo se…
pdfjs-editor-alt-text-settings-editor-title = Editor za alternatiwny tekst
pdfjs-editor-alt-text-settings-show-dialog-button-label = Editor alternatiwnego teksta ned pokazaś, gaž se wobraz pśidawa
pdfjs-editor-alt-text-settings-show-dialog-description = Pomaga, wam wšym swójim wobrazam alternatiwny tekst pśidaś.
pdfjs-editor-alt-text-settings-close-button = Zacyniś
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Wótwónoźone wuzwignuś
pdfjs-editor-undo-bar-message-freetext = Tekst jo se wótwónoźeł
pdfjs-editor-undo-bar-message-ink = Kreslanka jo se wótwónoźeła
pdfjs-editor-undo-bar-message-stamp = Wobraz jo se wótwónoźeł
pdfjs-editor-undo-bar-message-signature = Signatura jo se wótwónoźeła
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } pśipisk jo se wótwónoźeł
[two] { $count } pśipiska stej se wótwónoźełej
[few] { $count } pśipiski su se wótwónoźeli
*[other] { $count } pśipiskow jo se wótwónoźeło
}
pdfjs-editor-undo-bar-undo-button =
.title = Anulěrowaś
pdfjs-editor-undo-bar-undo-button-label = Anulěrowaś
pdfjs-editor-undo-bar-close-button =
.title = Zacyniś
pdfjs-editor-undo-bar-close-button-label = Zacyniś
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = Toś ten modalny dialog wužywarjeju zmóžnja, signaturu napóraś, aby PDF-dokument pśidał. Wužywaŕ móžo mě wobźěłaś (kótarež teke ako alternatiwny tekst słužy) a pó žycenju signaturu za wóspjetne wužywanje składowaś.
pdfjs-editor-add-signature-dialog-title = Signaturu pśidaś
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Typ
.title = Typ
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Kresliś
.title = Kresliś
pdfjs-editor-add-signature-image-button = Wobraz
.title = Wobraz
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Zapódajśo swóju signaturu
.placeholder = Zapódajśo swóju signaturu
pdfjs-editor-add-signature-draw-placeholder = Kresliśo swóju signaturu
pdfjs-editor-add-signature-draw-thickness-range-label = Tłustosć
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Tłustosć kreslanki: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Śěgniśo dataju sem, aby ju nagrał
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Abo wubjeŕśo wobrazowe dataje
*[other] Abo pśepytajśo wobrazowe dataje
}
## Controls
pdfjs-editor-add-signature-description-label = Wopisanje (alternatiwny tekst)
pdfjs-editor-add-signature-description-input =
.title = Wopisanje (alternatiwny tekst)
pdfjs-editor-add-signature-description-default-when-drawing = Signatura
pdfjs-editor-add-signature-clear-button-label = Signaturu lašowaś
pdfjs-editor-add-signature-clear-button =
.title = Signaturu lašowaś
pdfjs-editor-add-signature-save-checkbox = Signaturu składowaś
pdfjs-editor-add-signature-save-warning-message = Sćo dojśpił limit 5 skłaźonych signaturow. Wótwónoźćo jadnu, aby wěcej składował.
pdfjs-editor-add-signature-image-upload-error-title = Wobraz njedajo se nagraś
pdfjs-editor-add-signature-image-upload-error-description = Pśeglědajśo swój seśowy zwisk abo wopytajśo drugi wobraz.
pdfjs-editor-add-signature-error-close-button = Zacyniś
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Pśetergnuś
pdfjs-editor-add-signature-add-button = Pśidaś
pdfjs-editor-edit-signature-update-button = Aktualizěrowaś
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Signaturu wótwónoźeś
pdfjs-editor-delete-signature-button-label = Signaturu wótwónoźeś
pdfjs-editor-delete-signature-button1 =
.title = Skłaźonu signaturu wótwónoźeś
pdfjs-editor-delete-signature-button-label1 = Skłaźonu signaturu wótwónoźeś
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Wopisanje wobźěłaś
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Wopisanje wobźěłaś
================================================
FILE: cookbook/static/pdfjs/web/locale/el/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Προηγούμενη σελίδα
pdfjs-previous-button-label = Προηγούμενη
pdfjs-next-button =
.title = Επόμενη σελίδα
pdfjs-next-button-label = Επόμενη
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Σελίδα
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = από { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } από { $pagesCount })
pdfjs-zoom-out-button =
.title = Σμίκρυνση
pdfjs-zoom-out-button-label = Σμίκρυνση
pdfjs-zoom-in-button =
.title = Μεγέθυνση
pdfjs-zoom-in-button-label = Μεγέθυνση
pdfjs-zoom-select =
.title = Ζουμ
pdfjs-presentation-mode-button =
.title = Εναλλαγή σε λειτουργία παρουσίασης
pdfjs-presentation-mode-button-label = Λειτουργία παρουσίασης
pdfjs-open-file-button =
.title = Άνοιγμα αρχείου
pdfjs-open-file-button-label = Άνοιγμα
pdfjs-print-button =
.title = Εκτύπωση
pdfjs-print-button-label = Εκτύπωση
pdfjs-save-button =
.title = Αποθήκευση
pdfjs-save-button-label = Αποθήκευση
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Λήψη
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Λήψη
pdfjs-bookmark-button =
.title = Τρέχουσα σελίδα (Προβολή URL από τρέχουσα σελίδα)
pdfjs-bookmark-button-label = Τρέχουσα σελίδα
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Εργαλεία
pdfjs-tools-button-label = Εργαλεία
pdfjs-first-page-button =
.title = Μετάβαση στην πρώτη σελίδα
pdfjs-first-page-button-label = Μετάβαση στην πρώτη σελίδα
pdfjs-last-page-button =
.title = Μετάβαση στην τελευταία σελίδα
pdfjs-last-page-button-label = Μετάβαση στην τελευταία σελίδα
pdfjs-page-rotate-cw-button =
.title = Δεξιόστροφη περιστροφή
pdfjs-page-rotate-cw-button-label = Δεξιόστροφη περιστροφή
pdfjs-page-rotate-ccw-button =
.title = Αριστερόστροφη περιστροφή
pdfjs-page-rotate-ccw-button-label = Αριστερόστροφη περιστροφή
pdfjs-cursor-text-select-tool-button =
.title = Ενεργοποίηση εργαλείου επιλογής κειμένου
pdfjs-cursor-text-select-tool-button-label = Εργαλείο επιλογής κειμένου
pdfjs-cursor-hand-tool-button =
.title = Ενεργοποίηση εργαλείου χεριού
pdfjs-cursor-hand-tool-button-label = Εργαλείο χεριού
pdfjs-scroll-page-button =
.title = Χρήση κύλισης σελίδας
pdfjs-scroll-page-button-label = Κύλιση σελίδας
pdfjs-scroll-vertical-button =
.title = Χρήση κάθετης κύλισης
pdfjs-scroll-vertical-button-label = Κάθετη κύλιση
pdfjs-scroll-horizontal-button =
.title = Χρήση οριζόντιας κύλισης
pdfjs-scroll-horizontal-button-label = Οριζόντια κύλιση
pdfjs-scroll-wrapped-button =
.title = Χρήση κυκλικής κύλισης
pdfjs-scroll-wrapped-button-label = Κυκλική κύλιση
pdfjs-spread-none-button =
.title = Να μη γίνει σύνδεση επεκτάσεων σελίδων
pdfjs-spread-none-button-label = Χωρίς επεκτάσεις
pdfjs-spread-odd-button =
.title = Σύνδεση επεκτάσεων σελίδων ξεκινώντας από τις μονές σελίδες
pdfjs-spread-odd-button-label = Μονές επεκτάσεις
pdfjs-spread-even-button =
.title = Σύνδεση επεκτάσεων σελίδων ξεκινώντας από τις ζυγές σελίδες
pdfjs-spread-even-button-label = Ζυγές επεκτάσεις
## Document properties dialog
pdfjs-document-properties-button =
.title = Ιδιότητες εγγράφου…
pdfjs-document-properties-button-label = Ιδιότητες εγγράφου…
pdfjs-document-properties-file-name = Όνομα αρχείου:
pdfjs-document-properties-file-size = Μέγεθος αρχείου:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = Τίτλος:
pdfjs-document-properties-author = Συγγραφέας:
pdfjs-document-properties-subject = Θέμα:
pdfjs-document-properties-keywords = Λέξεις-κλειδιά:
pdfjs-document-properties-creation-date = Ημερομηνία δημιουργίας:
pdfjs-document-properties-modification-date = Ημερομηνία τροποποίησης:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Δημιουργός:
pdfjs-document-properties-producer = Παραγωγός PDF:
pdfjs-document-properties-version = Έκδοση PDF:
pdfjs-document-properties-page-count = Αριθμός σελίδων:
pdfjs-document-properties-page-size = Μέγεθος σελίδας:
pdfjs-document-properties-page-size-unit-inches = ίντσες
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = κατακόρυφα
pdfjs-document-properties-page-size-orientation-landscape = οριζόντια
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Επιστολή
pdfjs-document-properties-page-size-name-legal = Τύπου Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Ταχεία προβολή ιστού:
pdfjs-document-properties-linearized-yes = Ναι
pdfjs-document-properties-linearized-no = Όχι
pdfjs-document-properties-close-button = Κλείσιμο
## Print
pdfjs-print-progress-message = Προετοιμασία του εγγράφου για εκτύπωση…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Ακύρωση
pdfjs-printing-not-supported = Προειδοποίηση: Η εκτύπωση δεν υποστηρίζεται πλήρως από το πρόγραμμα περιήγησης.
pdfjs-printing-not-ready = Προειδοποίηση: Το PDF δεν φορτώθηκε πλήρως για εκτύπωση.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = (Απ)ενεργοποίηση πλαϊνής γραμμής
pdfjs-toggle-sidebar-notification-button =
.title = (Απ)ενεργοποίηση πλαϊνής γραμμής (το έγγραφο περιέχει περίγραμμα/συνημμένα/επίπεδα)
pdfjs-toggle-sidebar-button-label = (Απ)ενεργοποίηση πλαϊνής γραμμής
pdfjs-document-outline-button =
.title = Εμφάνιση διάρθρωσης εγγράφου (διπλό κλικ για ανάπτυξη/σύμπτυξη όλων των στοιχείων)
pdfjs-document-outline-button-label = Διάρθρωση εγγράφου
pdfjs-attachments-button =
.title = Εμφάνιση συνημμένων
pdfjs-attachments-button-label = Συνημμένα
pdfjs-layers-button =
.title = Εμφάνιση επιπέδων (διπλό κλικ για επαναφορά όλων των επιπέδων στην προεπιλεγμένη κατάσταση)
pdfjs-layers-button-label = Επίπεδα
pdfjs-thumbs-button =
.title = Εμφάνιση μικρογραφιών
pdfjs-thumbs-button-label = Μικρογραφίες
pdfjs-current-outline-item-button =
.title = Εύρεση τρέχοντος στοιχείου διάρθρωσης
pdfjs-current-outline-item-button-label = Τρέχον στοιχείο διάρθρωσης
pdfjs-findbar-button =
.title = Εύρεση στο έγγραφο
pdfjs-findbar-button-label = Εύρεση
pdfjs-additional-layers = Επιπρόσθετα επίπεδα
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Σελίδα { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Μικρογραφία σελίδας { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Εύρεση
.placeholder = Εύρεση στο έγγραφο…
pdfjs-find-previous-button =
.title = Εύρεση της προηγούμενης εμφάνισης της φράσης
pdfjs-find-previous-button-label = Προηγούμενο
pdfjs-find-next-button =
.title = Εύρεση της επόμενης εμφάνισης της φράσης
pdfjs-find-next-button-label = Επόμενο
pdfjs-find-highlight-checkbox = Επισήμανση όλων
pdfjs-find-match-case-checkbox-label = Συμφωνία πεζών/κεφαλαίων
pdfjs-find-match-diacritics-checkbox-label = Αντιστοίχιση διακριτικών
pdfjs-find-entire-word-checkbox-label = Ολόκληρες λέξεις
pdfjs-find-reached-top = Φτάσατε στην αρχή του εγγράφου, συνέχεια από το τέλος
pdfjs-find-reached-bottom = Φτάσατε στο τέλος του εγγράφου, συνέχεια από την αρχή
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } από { $total } αντιστοιχία
*[other] { $current } από { $total } αντιστοιχίες
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Περισσότερες από { $limit } αντιστοιχία
*[other] Περισσότερες από { $limit } αντιστοιχίες
}
pdfjs-find-not-found = Η φράση δεν βρέθηκε
## Predefined zoom values
pdfjs-page-scale-width = Πλάτος σελίδας
pdfjs-page-scale-fit = Μέγεθος σελίδας
pdfjs-page-scale-auto = Αυτόματο ζουμ
pdfjs-page-scale-actual = Πραγματικό μέγεθος
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Σελίδα { $page }
## Loading indicator messages
pdfjs-loading-error = Προέκυψε σφάλμα κατά τη φόρτωση του PDF.
pdfjs-invalid-file-error = Μη έγκυρο ή κατεστραμμένο αρχείο PDF.
pdfjs-missing-file-error = Λείπει αρχείο PDF.
pdfjs-unexpected-response-error = Μη αναμενόμενη απόκριση από το διακομιστή.
pdfjs-rendering-error = Προέκυψε σφάλμα κατά την εμφάνιση της σελίδας.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [Σχόλιο «{ $type }»]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Εισαγάγετε τον κωδικό πρόσβασης για να ανοίξετε αυτό το αρχείο PDF.
pdfjs-password-invalid = Μη έγκυρος κωδικός πρόσβασης. Δοκιμάστε ξανά.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Ακύρωση
pdfjs-web-fonts-disabled = Οι γραμματοσειρές ιστού είναι ανενεργές: δεν είναι δυνατή η χρήση των ενσωματωμένων γραμματοσειρών PDF.
## Editing
pdfjs-editor-free-text-button =
.title = Κείμενο
pdfjs-editor-free-text-button-label = Κείμενο
pdfjs-editor-ink-button =
.title = Σχέδιο
pdfjs-editor-ink-button-label = Σχέδιο
pdfjs-editor-stamp-button =
.title = Προσθήκη ή επεξεργασία εικόνων
pdfjs-editor-stamp-button-label = Προσθήκη ή επεξεργασία εικόνων
pdfjs-editor-highlight-button =
.title = Επισήμανση
pdfjs-editor-highlight-button-label = Επισήμανση
pdfjs-highlight-floating-button1 =
.title = Επισήμανση
.aria-label = Επισήμανση
pdfjs-highlight-floating-button-label = Επισήμανση
pdfjs-editor-signature-button =
.title = Προσθήκη υπογραφής
pdfjs-editor-signature-button-label = Προσθήκη υπογραφής
## Default editor aria labels
# “Highlight” is a noun, the string is used on the editor for highlights.
pdfjs-editor-highlight-editor =
.aria-label = Επεξεργασία επισήμανσης
# “Drawing” is a noun, the string is used on the editor for drawings.
pdfjs-editor-ink-editor =
.aria-label = Επεξεργασία σχεδίου
pdfjs-editor-signature-editor =
.aria-label = Επεξεργασία υπογραφής
pdfjs-editor-stamp-editor =
.aria-label = Επεξεργασία εικόνας
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Αφαίρεση σχεδίου
pdfjs-editor-remove-freetext-button =
.title = Αφαίρεση κειμένου
pdfjs-editor-remove-stamp-button =
.title = Αφαίρεση εικόνας
pdfjs-editor-remove-highlight-button =
.title = Αφαίρεση επισήμανσης
pdfjs-editor-remove-signature-button =
.title = Αφαίρεση υπογραφής
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Χρώμα
pdfjs-editor-free-text-size-input = Μέγεθος
pdfjs-editor-ink-color-input = Χρώμα
pdfjs-editor-ink-thickness-input = Πάχος
pdfjs-editor-ink-opacity-input = Αδιαφάνεια
pdfjs-editor-stamp-add-image-button =
.title = Προσθήκη εικόνας
pdfjs-editor-stamp-add-image-button-label = Προσθήκη εικόνας
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Πάχος
pdfjs-editor-free-highlight-thickness-title =
.title = Αλλαγή πάχους κατά την επισήμανση στοιχείων εκτός κειμένου
pdfjs-editor-add-signature-container =
.aria-label = Στοιχεία ελέγχου υπογραφής και αποθηκευμένες υπογραφές
pdfjs-editor-signature-add-signature-button =
.title = Προσθήκη νέας υπογραφής
pdfjs-editor-signature-add-signature-button-label = Προσθήκη νέας υπογραφής
# Used on the button to use an already saved signature.
# Variables:
# $description (String) - a string describing/labeling the signature.
pdfjs-editor-add-saved-signature-button =
.title = Αποθηκευμένη υπογραφή: { $description }
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Επεξεργασία κειμένου
.default-content = Ξεκινήστε να πληκτρολογείτε…
pdfjs-free-text =
.aria-label = Επεξεργασία κειμένου
pdfjs-free-text-default-content = Ξεκινήστε να πληκτρολογείτε…
pdfjs-ink =
.aria-label = Επεξεργασία σχεδίων
pdfjs-ink-canvas =
.aria-label = Εικόνα από τον χρήστη
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Εναλλακτικό κείμενο
pdfjs-editor-alt-text-edit-button =
.aria-label = Επεξεργασία εναλλακτικού κειμένου
pdfjs-editor-alt-text-edit-button-label = Επεξεργασία εναλλακτικού κειμένου
pdfjs-editor-alt-text-dialog-label = Διαλέξτε μια επιλογή
pdfjs-editor-alt-text-dialog-description = Το εναλλακτικό κείμενο είναι χρήσιμο όταν οι άνθρωποι δεν μπορούν να δουν την εικόνα ή όταν αυτή δεν φορτώνεται.
pdfjs-editor-alt-text-add-description-label = Προσθήκη περιγραφής
pdfjs-editor-alt-text-add-description-description = Στοχεύστε σε μία ή δύο προτάσεις που περιγράφουν το θέμα, τη ρύθμιση ή τις ενέργειες.
pdfjs-editor-alt-text-mark-decorative-label = Επισήμανση ως διακοσμητικό
pdfjs-editor-alt-text-mark-decorative-description = Χρησιμοποιείται για διακοσμητικές εικόνες, όπως περιγράμματα ή υδατογραφήματα.
pdfjs-editor-alt-text-cancel-button = Ακύρωση
pdfjs-editor-alt-text-save-button = Αποθήκευση
pdfjs-editor-alt-text-decorative-tooltip = Επισημασμένο ως διακοσμητικό
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Για παράδειγμα, «Ένας νεαρός άνδρας κάθεται σε ένα τραπέζι για να φάει ένα γεύμα»
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Εναλλακτικό κείμενο
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Επάνω αριστερή γωνία — αλλαγή μεγέθους
pdfjs-editor-resizer-label-top-middle = Μέσο επάνω πλευράς — αλλαγή μεγέθους
pdfjs-editor-resizer-label-top-right = Επάνω δεξιά γωνία — αλλαγή μεγέθους
pdfjs-editor-resizer-label-middle-right = Μέσο δεξιάς πλευράς — αλλαγή μεγέθους
pdfjs-editor-resizer-label-bottom-right = Κάτω δεξιά γωνία — αλλαγή μεγέθους
pdfjs-editor-resizer-label-bottom-middle = Μέσο κάτω πλευράς — αλλαγή μεγέθους
pdfjs-editor-resizer-label-bottom-left = Κάτω αριστερή γωνία — αλλαγή μεγέθους
pdfjs-editor-resizer-label-middle-left = Μέσο αριστερής πλευράς — αλλαγή μεγέθους
pdfjs-editor-resizer-top-left =
.aria-label = Επάνω αριστερή γωνία — αλλαγή μεγέθους
pdfjs-editor-resizer-top-middle =
.aria-label = Μέσο επάνω πλευράς — αλλαγή μεγέθους
pdfjs-editor-resizer-top-right =
.aria-label = Επάνω δεξιά γωνία — αλλαγή μεγέθους
pdfjs-editor-resizer-middle-right =
.aria-label = Μέσο δεξιάς πλευράς — αλλαγή μεγέθους
pdfjs-editor-resizer-bottom-right =
.aria-label = Κάτω δεξιά γωνία — αλλαγή μεγέθους
pdfjs-editor-resizer-bottom-middle =
.aria-label = Μέσο κάτω πλευράς — αλλαγή μεγέθους
pdfjs-editor-resizer-bottom-left =
.aria-label = Κάτω αριστερή γωνία — αλλαγή μεγέθους
pdfjs-editor-resizer-middle-left =
.aria-label = Μέσο αριστερής πλευράς — αλλαγή μεγέθους
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Χρώμα επισήμανσης
pdfjs-editor-colorpicker-button =
.title = Αλλαγή χρώματος
pdfjs-editor-colorpicker-dropdown =
.aria-label = Επιλογές χρωμάτων
pdfjs-editor-colorpicker-yellow =
.title = Κίτρινο
pdfjs-editor-colorpicker-green =
.title = Πράσινο
pdfjs-editor-colorpicker-blue =
.title = Μπλε
pdfjs-editor-colorpicker-pink =
.title = Ροζ
pdfjs-editor-colorpicker-red =
.title = Κόκκινο
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Εμφάνιση όλων
pdfjs-editor-highlight-show-all-button =
.title = Εμφάνιση όλων
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Επεξεργασία εναλλακτικού κειμένου (περιγραφή εικόνας)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Προσθήκη εναλλακτικού κειμένου (περιγραφή εικόνας)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Γράψτε την περιγραφή σας εδώ…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Σύντομη περιγραφή για άτομα που δεν μπορούν να δουν την εικόνα ή όταν η εικόνα δεν φορτώνεται.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Αυτό το εναλλακτικό κείμενο δημιουργήθηκε αυτόματα και ενδέχεται να είναι ανακριβές.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Μάθετε περισσότερα
pdfjs-editor-new-alt-text-create-automatically-button-label = Αυτόματη δημιουργία εναλλακτικού κειμένου
pdfjs-editor-new-alt-text-not-now-button = Όχι τώρα
pdfjs-editor-new-alt-text-error-title = Δεν ήταν δυνατή η αυτόματη δημιουργία εναλλακτικού κειμένου
pdfjs-editor-new-alt-text-error-description = Γράψτε το δικό σας εναλλακτικό κείμενο ή δοκιμάστε ξανά αργότερα.
pdfjs-editor-new-alt-text-error-close-button = Κλείσιμο
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Λήψη μοντέλου ΤΝ εναλλακτικού κειμένου ({ $downloadedSize } από { $totalSize } MB)
.aria-valuetext = Λήψη μοντέλου ΤΝ εναλλακτικού κειμένου ({ $downloadedSize } από { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Προστέθηκε εναλλακτικό κείμενο
pdfjs-editor-new-alt-text-added-button-label = Προστέθηκε εναλλακτικό κείμενο
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Απουσία εναλλακτικού κειμένου
pdfjs-editor-new-alt-text-missing-button-label = Απουσία εναλλακτικού κειμένου
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Έλεγχος εναλλακτικού κειμένου
pdfjs-editor-new-alt-text-to-review-button-label = Έλεγχος εναλλακτικού κειμένου
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Αυτόματη δημιουργία: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Ρυθμίσεις εναλλακτικού κειμένου εικόνας
pdfjs-image-alt-text-settings-button-label = Ρυθμίσεις εναλλακτικού κειμένου εικόνας
pdfjs-editor-alt-text-settings-dialog-label = Ρυθμίσεις εναλλακτικού κειμένου εικόνας
pdfjs-editor-alt-text-settings-automatic-title = Αυτόματο εναλλακτικό κείμενο
pdfjs-editor-alt-text-settings-create-model-button-label = Αυτόματη δημιουργία εναλλακτικού κειμένου
pdfjs-editor-alt-text-settings-create-model-description = Προτείνει περιγραφές για άτομα που δεν μπορούν να δουν την εικόνα ή όταν η εικόνα δεν φορτώνεται.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Μοντέλο ΤΝ εναλλακτικού κειμένου ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Εκτελείται τοπικά στη συσκευή σας, ώστε τα δεδομένα σας να παραμένουν ιδιωτικά. Απαιτείται για τη δημιουργία του αυτόματου εναλλακτικού κειμένου.
pdfjs-editor-alt-text-settings-delete-model-button = Διαγραφή
pdfjs-editor-alt-text-settings-download-model-button = Λήψη
pdfjs-editor-alt-text-settings-downloading-model-button = Λήψη…
pdfjs-editor-alt-text-settings-editor-title = Επεξεργασία εναλλακτικού κειμένου
pdfjs-editor-alt-text-settings-show-dialog-button-label = Άμεση εμφάνιση της επεξεργασίας εναλλακτικού κειμένου κατά την προσθήκη εικόνας
pdfjs-editor-alt-text-settings-show-dialog-description = Σας βοηθά να βεβαιωθείτε ότι όλες οι εικόνες σας έχουν εναλλακτικό κείμενο.
pdfjs-editor-alt-text-settings-close-button = Κλείσιμο
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Η επισήμανση αφαιρέθηκε
pdfjs-editor-undo-bar-message-freetext = Το κείμενο αφαιρέθηκε
pdfjs-editor-undo-bar-message-ink = Το σχέδιο αφαιρέθηκε
pdfjs-editor-undo-bar-message-stamp = Η εικόνα αφαιρέθηκε
pdfjs-editor-undo-bar-message-signature = Η υπογραφή αφαιρέθηκε
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] Αφαιρέθηκε { $count } σχολιασμός
*[other] Αφαιρέθηκαν { $count } σχολιασμοί
}
pdfjs-editor-undo-bar-undo-button =
.title = Αναίρεση
pdfjs-editor-undo-bar-undo-button-label = Αναίρεση
pdfjs-editor-undo-bar-close-button =
.title = Κλείσιμο
pdfjs-editor-undo-bar-close-button-label = Κλείσιμο
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = Αυτό το παράθυρο διαλόγου επιτρέπει στον χρήστη να δημιουργήσει μια υπογραφή για να την προσθέσει σε ένα έγγραφο PDF. Ο χρήστης μπορεί να επεξεργαστεί το όνομα (το οποίο χρησιμεύει και ως εναλλακτικό κείμενο) και, προαιρετικά, να αποθηκεύσει την υπογραφή για επαναλαμβανόμενη χρήση.
pdfjs-editor-add-signature-dialog-title = Προσθήκη υπογραφής
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Πληκτρολόγηση
.title = Πληκτρολόγηση
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Σχέδιο
.title = Σχέδιο
pdfjs-editor-add-signature-image-button = Εικόνα
.title = Εικόνα
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Πληκτρολογήστε την υπογραφή σας
.placeholder = Πληκτρολογήστε την υπογραφή σας
pdfjs-editor-add-signature-draw-placeholder = Σχεδιάστε την υπογραφή σας
pdfjs-editor-add-signature-draw-thickness-range-label = Πάχος
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Πάχος σχεδίου: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Σύρετε ένα αρχείο εδώ για μεταφόρτωση
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Ή επιλέξτε αρχεία εικόνας
*[other] Ή περιηγηθείτε σε αρχεία εικόνας
}
## Controls
pdfjs-editor-add-signature-description-label = Περιγραφή (εναλλακτικό κείμενο)
pdfjs-editor-add-signature-description-input =
.title = Περιγραφή (εναλλακτικό κείμενο)
pdfjs-editor-add-signature-description-default-when-drawing = Υπογραφή
pdfjs-editor-add-signature-clear-button-label = Απαλοιφή υπογραφής
pdfjs-editor-add-signature-clear-button =
.title = Απαλοιφή υπογραφής
pdfjs-editor-add-signature-save-checkbox = Αποθήκευση υπογραφής
pdfjs-editor-add-signature-save-warning-message = Έχετε φτάσει το όριο των 5 αποθηκευμένων υπογραφών. Αφαιρέστε μία για να αποθηκεύσετε περισσότερες.
pdfjs-editor-add-signature-image-upload-error-title = Δεν ήταν δυνατή η μεταφόρτωση της εικόνας
pdfjs-editor-add-signature-image-upload-error-description = Ελέγξτε τη σύνδεση δικτύου σας ή δοκιμάστε μια άλλη εικόνα.
pdfjs-editor-add-signature-error-close-button = Κλείσιμο
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Ακύρωση
pdfjs-editor-add-signature-add-button = Προσθήκη
pdfjs-editor-edit-signature-update-button = Ενημέρωση
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Αφαίρεση υπογραφής
pdfjs-editor-delete-signature-button-label = Αφαίρεση υπογραφής
pdfjs-editor-delete-signature-button1 =
.title = Αφαίρεση αποθηκευμένης υπογραφής
pdfjs-editor-delete-signature-button-label1 = Αφαίρεση αποθηκευμένης υπογραφής
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Επεξεργασία περιγραφής
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Επεξεργασία περιγραφής
================================================
FILE: cookbook/static/pdfjs/web/locale/en-CA/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Previous Page
pdfjs-previous-button-label = Previous
pdfjs-next-button =
.title = Next Page
pdfjs-next-button-label = Next
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Page
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = of { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } of { $pagesCount })
pdfjs-zoom-out-button =
.title = Zoom Out
pdfjs-zoom-out-button-label = Zoom Out
pdfjs-zoom-in-button =
.title = Zoom In
pdfjs-zoom-in-button-label = Zoom In
pdfjs-zoom-select =
.title = Zoom
pdfjs-presentation-mode-button =
.title = Switch to Presentation Mode
pdfjs-presentation-mode-button-label = Presentation Mode
pdfjs-open-file-button =
.title = Open File
pdfjs-open-file-button-label = Open
pdfjs-print-button =
.title = Print
pdfjs-print-button-label = Print
pdfjs-save-button =
.title = Save
pdfjs-save-button-label = Save
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Download
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Download
pdfjs-bookmark-button =
.title = Current Page (View URL from Current Page)
pdfjs-bookmark-button-label = Current Page
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Tools
pdfjs-tools-button-label = Tools
pdfjs-first-page-button =
.title = Go to First Page
pdfjs-first-page-button-label = Go to First Page
pdfjs-last-page-button =
.title = Go to Last Page
pdfjs-last-page-button-label = Go to Last Page
pdfjs-page-rotate-cw-button =
.title = Rotate Clockwise
pdfjs-page-rotate-cw-button-label = Rotate Clockwise
pdfjs-page-rotate-ccw-button =
.title = Rotate Counterclockwise
pdfjs-page-rotate-ccw-button-label = Rotate Counterclockwise
pdfjs-cursor-text-select-tool-button =
.title = Enable Text Selection Tool
pdfjs-cursor-text-select-tool-button-label = Text Selection Tool
pdfjs-cursor-hand-tool-button =
.title = Enable Hand Tool
pdfjs-cursor-hand-tool-button-label = Hand Tool
pdfjs-scroll-page-button =
.title = Use Page Scrolling
pdfjs-scroll-page-button-label = Page Scrolling
pdfjs-scroll-vertical-button =
.title = Use Vertical Scrolling
pdfjs-scroll-vertical-button-label = Vertical Scrolling
pdfjs-scroll-horizontal-button =
.title = Use Horizontal Scrolling
pdfjs-scroll-horizontal-button-label = Horizontal Scrolling
pdfjs-scroll-wrapped-button =
.title = Use Wrapped Scrolling
pdfjs-scroll-wrapped-button-label = Wrapped Scrolling
pdfjs-spread-none-button =
.title = Do not join page spreads
pdfjs-spread-none-button-label = No Spreads
pdfjs-spread-odd-button =
.title = Join page spreads starting with odd-numbered pages
pdfjs-spread-odd-button-label = Odd Spreads
pdfjs-spread-even-button =
.title = Join page spreads starting with even-numbered pages
pdfjs-spread-even-button-label = Even Spreads
## Document properties dialog
pdfjs-document-properties-button =
.title = Document Properties…
pdfjs-document-properties-button-label = Document Properties…
pdfjs-document-properties-file-name = File name:
pdfjs-document-properties-file-size = File size:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } kB ({ $size_b } bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = Title:
pdfjs-document-properties-author = Author:
pdfjs-document-properties-subject = Subject:
pdfjs-document-properties-keywords = Keywords:
pdfjs-document-properties-creation-date = Creation Date:
pdfjs-document-properties-modification-date = Modification Date:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Creator:
pdfjs-document-properties-producer = PDF Producer:
pdfjs-document-properties-version = PDF Version:
pdfjs-document-properties-page-count = Page Count:
pdfjs-document-properties-page-size = Page Size:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = portrait
pdfjs-document-properties-page-size-orientation-landscape = landscape
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letter
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Fast Web View:
pdfjs-document-properties-linearized-yes = Yes
pdfjs-document-properties-linearized-no = No
pdfjs-document-properties-close-button = Close
## Print
pdfjs-print-progress-message = Preparing document for printing…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Cancel
pdfjs-printing-not-supported = Warning: Printing is not fully supported by this browser.
pdfjs-printing-not-ready = Warning: The PDF is not fully loaded for printing.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Toggle Sidebar
pdfjs-toggle-sidebar-notification-button =
.title = Toggle Sidebar (document contains outline/attachments/layers)
pdfjs-toggle-sidebar-button-label = Toggle Sidebar
pdfjs-document-outline-button =
.title = Show Document Outline (double-click to expand/collapse all items)
pdfjs-document-outline-button-label = Document Outline
pdfjs-attachments-button =
.title = Show Attachments
pdfjs-attachments-button-label = Attachments
pdfjs-layers-button =
.title = Show Layers (double-click to reset all layers to the default state)
pdfjs-layers-button-label = Layers
pdfjs-thumbs-button =
.title = Show Thumbnails
pdfjs-thumbs-button-label = Thumbnails
pdfjs-current-outline-item-button =
.title = Find Current Outline Item
pdfjs-current-outline-item-button-label = Current Outline Item
pdfjs-findbar-button =
.title = Find in Document
pdfjs-findbar-button-label = Find
pdfjs-additional-layers = Additional Layers
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Page { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Thumbnail of Page { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Find
.placeholder = Find in document…
pdfjs-find-previous-button =
.title = Find the previous occurrence of the phrase
pdfjs-find-previous-button-label = Previous
pdfjs-find-next-button =
.title = Find the next occurrence of the phrase
pdfjs-find-next-button-label = Next
pdfjs-find-highlight-checkbox = Highlight All
pdfjs-find-match-case-checkbox-label = Match Case
pdfjs-find-match-diacritics-checkbox-label = Match Diacritics
pdfjs-find-entire-word-checkbox-label = Whole Words
pdfjs-find-reached-top = Reached top of document, continued from bottom
pdfjs-find-reached-bottom = Reached end of document, continued from top
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } of { $total } match
*[other] { $current } of { $total } matches
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] More than { $limit } match
*[other] More than { $limit } matches
}
pdfjs-find-not-found = Phrase not found
## Predefined zoom values
pdfjs-page-scale-width = Page Width
pdfjs-page-scale-fit = Page Fit
pdfjs-page-scale-auto = Automatic Zoom
pdfjs-page-scale-actual = Actual Size
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Page { $page }
## Loading indicator messages
pdfjs-loading-error = An error occurred while loading the PDF.
pdfjs-invalid-file-error = Invalid or corrupted PDF file.
pdfjs-missing-file-error = Missing PDF file.
pdfjs-unexpected-response-error = Unexpected server response.
pdfjs-rendering-error = An error occurred while rendering the page.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } Annotation]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Enter the password to open this PDF file.
pdfjs-password-invalid = Invalid password. Please try again.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Cancel
pdfjs-web-fonts-disabled = Web fonts are disabled: unable to use embedded PDF fonts.
## Editing
pdfjs-editor-free-text-button =
.title = Text
pdfjs-editor-free-text-button-label = Text
pdfjs-editor-ink-button =
.title = Draw
pdfjs-editor-ink-button-label = Draw
pdfjs-editor-stamp-button =
.title = Add or edit images
pdfjs-editor-stamp-button-label = Add or edit images
pdfjs-editor-highlight-button =
.title = Highlight
pdfjs-editor-highlight-button-label = Highlight
pdfjs-highlight-floating-button1 =
.title = Highlight
.aria-label = Highlight
pdfjs-highlight-floating-button-label = Highlight
pdfjs-editor-signature-button =
.title = Add signature
pdfjs-editor-signature-button-label = Add signature
## Default editor aria labels
# “Highlight” is a noun, the string is used on the editor for highlights.
pdfjs-editor-highlight-editor =
.aria-label = Highlight editor
# “Drawing” is a noun, the string is used on the editor for drawings.
pdfjs-editor-ink-editor =
.aria-label = Drawing editor
pdfjs-editor-signature-editor =
.aria-label = Signature editor
pdfjs-editor-stamp-editor =
.aria-label = Image editor
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Remove drawing
pdfjs-editor-remove-freetext-button =
.title = Remove text
pdfjs-editor-remove-stamp-button =
.title = Remove image
pdfjs-editor-remove-highlight-button =
.title = Remove highlight
pdfjs-editor-remove-signature-button =
.title = Remove signature
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Colour
pdfjs-editor-free-text-size-input = Size
pdfjs-editor-ink-color-input = Colour
pdfjs-editor-ink-thickness-input = Thickness
pdfjs-editor-ink-opacity-input = Opacity
pdfjs-editor-stamp-add-image-button =
.title = Add image
pdfjs-editor-stamp-add-image-button-label = Add image
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Thickness
pdfjs-editor-free-highlight-thickness-title =
.title = Change thickness when highlighting items other than text
pdfjs-editor-add-signature-container =
.aria-label = Signature controls and saved signatures
pdfjs-editor-signature-add-signature-button =
.title = Add new signature
pdfjs-editor-signature-add-signature-button-label = Add new signature
# Used on the button to use an already saved signature.
# Variables:
# $description (String) - a string describing/labeling the signature.
pdfjs-editor-add-saved-signature-button =
.title = Saved signature: { $description }
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Text Editor
.default-content = Start typing…
pdfjs-free-text =
.aria-label = Text Editor
pdfjs-free-text-default-content = Start typing…
pdfjs-ink =
.aria-label = Draw Editor
pdfjs-ink-canvas =
.aria-label = User-created image
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Alt text
pdfjs-editor-alt-text-edit-button =
.aria-label = Edit alt text
pdfjs-editor-alt-text-edit-button-label = Edit alt text
pdfjs-editor-alt-text-dialog-label = Choose an option
pdfjs-editor-alt-text-dialog-description = Alt text (alternative text) helps when people can’t see the image or when it doesn’t load.
pdfjs-editor-alt-text-add-description-label = Add a description
pdfjs-editor-alt-text-add-description-description = Aim for 1-2 sentences that describe the subject, setting, or actions.
pdfjs-editor-alt-text-mark-decorative-label = Mark as decorative
pdfjs-editor-alt-text-mark-decorative-description = This is used for ornamental images, like borders or watermarks.
pdfjs-editor-alt-text-cancel-button = Cancel
pdfjs-editor-alt-text-save-button = Save
pdfjs-editor-alt-text-decorative-tooltip = Marked as decorative
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = For example, “A young man sits down at a table to eat a meal”
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Alt text
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Top left corner — resize
pdfjs-editor-resizer-label-top-middle = Top middle — resize
pdfjs-editor-resizer-label-top-right = Top right corner — resize
pdfjs-editor-resizer-label-middle-right = Middle right — resize
pdfjs-editor-resizer-label-bottom-right = Bottom right corner — resize
pdfjs-editor-resizer-label-bottom-middle = Bottom middle — resize
pdfjs-editor-resizer-label-bottom-left = Bottom left corner — resize
pdfjs-editor-resizer-label-middle-left = Middle left — resize
pdfjs-editor-resizer-top-left =
.aria-label = Top left corner — resize
pdfjs-editor-resizer-top-middle =
.aria-label = Top middle — resize
pdfjs-editor-resizer-top-right =
.aria-label = Top right corner — resize
pdfjs-editor-resizer-middle-right =
.aria-label = Middle right — resize
pdfjs-editor-resizer-bottom-right =
.aria-label = Bottom right corner — resize
pdfjs-editor-resizer-bottom-middle =
.aria-label = Bottom middle — resize
pdfjs-editor-resizer-bottom-left =
.aria-label = Bottom left corner — resize
pdfjs-editor-resizer-middle-left =
.aria-label = Middle left — resize
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Highlight colour
pdfjs-editor-colorpicker-button =
.title = Change colour
pdfjs-editor-colorpicker-dropdown =
.aria-label = Colour choices
pdfjs-editor-colorpicker-yellow =
.title = Yellow
pdfjs-editor-colorpicker-green =
.title = Green
pdfjs-editor-colorpicker-blue =
.title = Blue
pdfjs-editor-colorpicker-pink =
.title = Pink
pdfjs-editor-colorpicker-red =
.title = Red
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Show all
pdfjs-editor-highlight-show-all-button =
.title = Show all
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Edit alt text (image description)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Add alt text (image description)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Write your description here…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Short description for people who can’t see the image or when the image doesn’t load.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = This alt text was created automatically and may be inaccurate.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Learn more
pdfjs-editor-new-alt-text-create-automatically-button-label = Create alt text automatically
pdfjs-editor-new-alt-text-not-now-button = Not now
pdfjs-editor-new-alt-text-error-title = Couldn’t create alt text automatically
pdfjs-editor-new-alt-text-error-description = Please write your own alt text or try again later.
pdfjs-editor-new-alt-text-error-close-button = Close
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Downloading alt text AI model ({ $downloadedSize } of { $totalSize } MB)
.aria-valuetext = Downloading alt text AI model ({ $downloadedSize } of { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Alt text added
pdfjs-editor-new-alt-text-added-button-label = Alt text added
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Missing alt text
pdfjs-editor-new-alt-text-missing-button-label = Missing alt text
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Review alt text
pdfjs-editor-new-alt-text-to-review-button-label = Review alt text
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Created automatically: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Image alt text settings
pdfjs-image-alt-text-settings-button-label = Image alt text settings
pdfjs-editor-alt-text-settings-dialog-label = Image alt text settings
pdfjs-editor-alt-text-settings-automatic-title = Automatic alt text
pdfjs-editor-alt-text-settings-create-model-button-label = Create alt text automatically
pdfjs-editor-alt-text-settings-create-model-description = Suggests descriptions to help people who can’t see the image or when the image doesn’t load.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Alt text AI model ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Runs locally on your device so your data stays private. Required for automatic alt text.
pdfjs-editor-alt-text-settings-delete-model-button = Delete
pdfjs-editor-alt-text-settings-download-model-button = Download
pdfjs-editor-alt-text-settings-downloading-model-button = Downloading…
pdfjs-editor-alt-text-settings-editor-title = Alt text editor
pdfjs-editor-alt-text-settings-show-dialog-button-label = Show alt text editor right away when adding an image
pdfjs-editor-alt-text-settings-show-dialog-description = Helps you make sure all your images have alt text.
pdfjs-editor-alt-text-settings-close-button = Close
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Highlight removed
pdfjs-editor-undo-bar-message-freetext = Text removed
pdfjs-editor-undo-bar-message-ink = Drawing removed
pdfjs-editor-undo-bar-message-stamp = Image removed
pdfjs-editor-undo-bar-message-signature = Signature removed
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } annotation removed
*[other] { $count } annotations removed
}
pdfjs-editor-undo-bar-undo-button =
.title = Undo
pdfjs-editor-undo-bar-undo-button-label = Undo
pdfjs-editor-undo-bar-close-button =
.title = Close
pdfjs-editor-undo-bar-close-button-label = Close
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = This modal allows the user to create a signature to add to a PDF document. The user can edit the name (which also serves as the alt text), and optionally save the signature for repeated use.
pdfjs-editor-add-signature-dialog-title = Add a signature
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Type
.title = Type
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Draw
.title = Draw
pdfjs-editor-add-signature-image-button = Image
.title = Image
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Type your signature
.placeholder = Type your signature
pdfjs-editor-add-signature-draw-placeholder = Draw your signature
pdfjs-editor-add-signature-draw-thickness-range-label = Thickness
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Drawing thickness: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Drag a file here to upload
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Or choose image files
*[other] Or browse image files
}
## Controls
pdfjs-editor-add-signature-description-label = Description (alt text)
pdfjs-editor-add-signature-description-input =
.title = Description (alt text)
pdfjs-editor-add-signature-description-default-when-drawing = Signature
pdfjs-editor-add-signature-clear-button-label = Clear signature
pdfjs-editor-add-signature-clear-button =
.title = Clear signature
pdfjs-editor-add-signature-save-checkbox = Save signature
pdfjs-editor-add-signature-save-warning-message = You’ve reached the limit of 5 saved signatures. Remove one to save more.
pdfjs-editor-add-signature-image-upload-error-title = Couldn’t upload image
pdfjs-editor-add-signature-image-upload-error-description = Check your network connection or try another image.
pdfjs-editor-add-signature-error-close-button = Close
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Cancel
pdfjs-editor-add-signature-add-button = Add
pdfjs-editor-edit-signature-update-button = Update
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Remove signature
pdfjs-editor-delete-signature-button-label = Remove signature
pdfjs-editor-delete-signature-button1 =
.title = Remove saved signature
pdfjs-editor-delete-signature-button-label1 = Remove saved signature
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Edit description
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Edit description
================================================
FILE: cookbook/static/pdfjs/web/locale/en-GB/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Previous Page
pdfjs-previous-button-label = Previous
pdfjs-next-button =
.title = Next Page
pdfjs-next-button-label = Next
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Page
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = of { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } of { $pagesCount })
pdfjs-zoom-out-button =
.title = Zoom Out
pdfjs-zoom-out-button-label = Zoom Out
pdfjs-zoom-in-button =
.title = Zoom In
pdfjs-zoom-in-button-label = Zoom In
pdfjs-zoom-select =
.title = Zoom
pdfjs-presentation-mode-button =
.title = Switch to Presentation Mode
pdfjs-presentation-mode-button-label = Presentation Mode
pdfjs-open-file-button =
.title = Open File
pdfjs-open-file-button-label = Open
pdfjs-print-button =
.title = Print
pdfjs-print-button-label = Print
pdfjs-save-button =
.title = Save
pdfjs-save-button-label = Save
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Download
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Download
pdfjs-bookmark-button =
.title = Current Page (View URL from Current Page)
pdfjs-bookmark-button-label = Current Page
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Tools
pdfjs-tools-button-label = Tools
pdfjs-first-page-button =
.title = Go to First Page
pdfjs-first-page-button-label = Go to First Page
pdfjs-last-page-button =
.title = Go to Last Page
pdfjs-last-page-button-label = Go to Last Page
pdfjs-page-rotate-cw-button =
.title = Rotate Clockwise
pdfjs-page-rotate-cw-button-label = Rotate Clockwise
pdfjs-page-rotate-ccw-button =
.title = Rotate Anti-Clockwise
pdfjs-page-rotate-ccw-button-label = Rotate Anti-Clockwise
pdfjs-cursor-text-select-tool-button =
.title = Enable Text Selection Tool
pdfjs-cursor-text-select-tool-button-label = Text Selection Tool
pdfjs-cursor-hand-tool-button =
.title = Enable Hand Tool
pdfjs-cursor-hand-tool-button-label = Hand Tool
pdfjs-scroll-page-button =
.title = Use Page Scrolling
pdfjs-scroll-page-button-label = Page Scrolling
pdfjs-scroll-vertical-button =
.title = Use Vertical Scrolling
pdfjs-scroll-vertical-button-label = Vertical Scrolling
pdfjs-scroll-horizontal-button =
.title = Use Horizontal Scrolling
pdfjs-scroll-horizontal-button-label = Horizontal Scrolling
pdfjs-scroll-wrapped-button =
.title = Use Wrapped Scrolling
pdfjs-scroll-wrapped-button-label = Wrapped Scrolling
pdfjs-spread-none-button =
.title = Do not join page spreads
pdfjs-spread-none-button-label = No Spreads
pdfjs-spread-odd-button =
.title = Join page spreads starting with odd-numbered pages
pdfjs-spread-odd-button-label = Odd Spreads
pdfjs-spread-even-button =
.title = Join page spreads starting with even-numbered pages
pdfjs-spread-even-button-label = Even Spreads
## Document properties dialog
pdfjs-document-properties-button =
.title = Document Properties…
pdfjs-document-properties-button-label = Document Properties…
pdfjs-document-properties-file-name = File name:
pdfjs-document-properties-file-size = File size:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } kB ({ $b } bytes)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } kB ({ $size_b } bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = Title:
pdfjs-document-properties-author = Author:
pdfjs-document-properties-subject = Subject:
pdfjs-document-properties-keywords = Keywords:
pdfjs-document-properties-creation-date = Creation Date:
pdfjs-document-properties-modification-date = Modification Date:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Creator:
pdfjs-document-properties-producer = PDF Producer:
pdfjs-document-properties-version = PDF Version:
pdfjs-document-properties-page-count = Page Count:
pdfjs-document-properties-page-size = Page Size:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = portrait
pdfjs-document-properties-page-size-orientation-landscape = landscape
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letter
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Fast Web View:
pdfjs-document-properties-linearized-yes = Yes
pdfjs-document-properties-linearized-no = No
pdfjs-document-properties-close-button = Close
## Print
pdfjs-print-progress-message = Preparing document for printing…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Cancel
pdfjs-printing-not-supported = Warning: Printing is not fully supported by this browser.
pdfjs-printing-not-ready = Warning: The PDF is not fully loaded for printing.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Toggle Sidebar
pdfjs-toggle-sidebar-notification-button =
.title = Toggle Sidebar (document contains outline/attachments/layers)
pdfjs-toggle-sidebar-button-label = Toggle Sidebar
pdfjs-document-outline-button =
.title = Show Document Outline (double-click to expand/collapse all items)
pdfjs-document-outline-button-label = Document Outline
pdfjs-attachments-button =
.title = Show Attachments
pdfjs-attachments-button-label = Attachments
pdfjs-layers-button =
.title = Show Layers (double-click to reset all layers to the default state)
pdfjs-layers-button-label = Layers
pdfjs-thumbs-button =
.title = Show Thumbnails
pdfjs-thumbs-button-label = Thumbnails
pdfjs-current-outline-item-button =
.title = Find Current Outline Item
pdfjs-current-outline-item-button-label = Current Outline Item
pdfjs-findbar-button =
.title = Find in Document
pdfjs-findbar-button-label = Find
pdfjs-additional-layers = Additional Layers
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Page { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Thumbnail of Page { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Find
.placeholder = Find in document…
pdfjs-find-previous-button =
.title = Find the previous occurrence of the phrase
pdfjs-find-previous-button-label = Previous
pdfjs-find-next-button =
.title = Find the next occurrence of the phrase
pdfjs-find-next-button-label = Next
pdfjs-find-highlight-checkbox = Highlight All
pdfjs-find-match-case-checkbox-label = Match Case
pdfjs-find-match-diacritics-checkbox-label = Match Diacritics
pdfjs-find-entire-word-checkbox-label = Whole Words
pdfjs-find-reached-top = Reached top of document, continued from bottom
pdfjs-find-reached-bottom = Reached end of document, continued from top
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } of { $total } match
*[other] { $current } of { $total } matches
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] More than { $limit } match
*[other] More than { $limit } matches
}
pdfjs-find-not-found = Phrase not found
## Predefined zoom values
pdfjs-page-scale-width = Page Width
pdfjs-page-scale-fit = Page Fit
pdfjs-page-scale-auto = Automatic Zoom
pdfjs-page-scale-actual = Actual Size
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Page { $page }
## Loading indicator messages
pdfjs-loading-error = An error occurred while loading the PDF.
pdfjs-invalid-file-error = Invalid or corrupted PDF file.
pdfjs-missing-file-error = Missing PDF file.
pdfjs-unexpected-response-error = Unexpected server response.
pdfjs-rendering-error = An error occurred while rendering the page.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } Annotation]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Enter the password to open this PDF file.
pdfjs-password-invalid = Invalid password. Please try again.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Cancel
pdfjs-web-fonts-disabled = Web fonts are disabled: unable to use embedded PDF fonts.
## Editing
pdfjs-editor-free-text-button =
.title = Text
pdfjs-editor-free-text-button-label = Text
pdfjs-editor-ink-button =
.title = Draw
pdfjs-editor-ink-button-label = Draw
pdfjs-editor-stamp-button =
.title = Add or edit images
pdfjs-editor-stamp-button-label = Add or edit images
pdfjs-editor-highlight-button =
.title = Highlight
pdfjs-editor-highlight-button-label = Highlight
pdfjs-highlight-floating-button1 =
.title = Highlight
.aria-label = Highlight
pdfjs-highlight-floating-button-label = Highlight
pdfjs-editor-signature-button =
.title = Add signature
pdfjs-editor-signature-button-label = Add signature
## Default editor aria labels
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Remove drawing
pdfjs-editor-remove-freetext-button =
.title = Remove text
pdfjs-editor-remove-stamp-button =
.title = Remove image
pdfjs-editor-remove-highlight-button =
.title = Remove highlight
pdfjs-editor-remove-signature-button =
.title = Remove signature
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Colour
pdfjs-editor-free-text-size-input = Size
pdfjs-editor-ink-color-input = Colour
pdfjs-editor-ink-thickness-input = Thickness
pdfjs-editor-ink-opacity-input = Opacity
pdfjs-editor-stamp-add-image-button =
.title = Add image
pdfjs-editor-stamp-add-image-button-label = Add image
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Thickness
pdfjs-editor-free-highlight-thickness-title =
.title = Change thickness when highlighting items other than text
pdfjs-editor-signature-add-signature-button =
.title = Add new signature
pdfjs-editor-signature-add-signature-button-label = Add new signature
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Text Editor
.default-content = Start typing…
pdfjs-free-text =
.aria-label = Text Editor
pdfjs-free-text-default-content = Start typing…
pdfjs-ink =
.aria-label = Draw Editor
pdfjs-ink-canvas =
.aria-label = User-created image
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Alt text
pdfjs-editor-alt-text-edit-button =
.aria-label = Edit alt text
pdfjs-editor-alt-text-edit-button-label = Edit alt text
pdfjs-editor-alt-text-dialog-label = Choose an option
pdfjs-editor-alt-text-dialog-description = Alt text (alternative text) helps when people can’t see the image or when it doesn’t load.
pdfjs-editor-alt-text-add-description-label = Add a description
pdfjs-editor-alt-text-add-description-description = Aim for 1-2 sentences that describe the subject, setting, or actions.
pdfjs-editor-alt-text-mark-decorative-label = Mark as decorative
pdfjs-editor-alt-text-mark-decorative-description = This is used for ornamental images, like borders or watermarks.
pdfjs-editor-alt-text-cancel-button = Cancel
pdfjs-editor-alt-text-save-button = Save
pdfjs-editor-alt-text-decorative-tooltip = Marked as decorative
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = For example, “A young man sits down at a table to eat a meal”
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Alt text
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Top left corner — resize
pdfjs-editor-resizer-label-top-middle = Top middle — resize
pdfjs-editor-resizer-label-top-right = Top right corner — resize
pdfjs-editor-resizer-label-middle-right = Middle right — resize
pdfjs-editor-resizer-label-bottom-right = Bottom right corner — resize
pdfjs-editor-resizer-label-bottom-middle = Bottom middle — resize
pdfjs-editor-resizer-label-bottom-left = Bottom left corner — resize
pdfjs-editor-resizer-label-middle-left = Middle left — resize
pdfjs-editor-resizer-top-left =
.aria-label = Top left corner — resize
pdfjs-editor-resizer-top-middle =
.aria-label = Top middle — resize
pdfjs-editor-resizer-top-right =
.aria-label = Top right corner — resize
pdfjs-editor-resizer-middle-right =
.aria-label = Middle right — resize
pdfjs-editor-resizer-bottom-right =
.aria-label = Bottom right corner — resize
pdfjs-editor-resizer-bottom-middle =
.aria-label = Bottom middle — resize
pdfjs-editor-resizer-bottom-left =
.aria-label = Bottom left corner — resize
pdfjs-editor-resizer-middle-left =
.aria-label = Middle left — resize
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Highlight colour
pdfjs-editor-colorpicker-button =
.title = Change colour
pdfjs-editor-colorpicker-dropdown =
.aria-label = Colour choices
pdfjs-editor-colorpicker-yellow =
.title = Yellow
pdfjs-editor-colorpicker-green =
.title = Green
pdfjs-editor-colorpicker-blue =
.title = Blue
pdfjs-editor-colorpicker-pink =
.title = Pink
pdfjs-editor-colorpicker-red =
.title = Red
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Show all
pdfjs-editor-highlight-show-all-button =
.title = Show all
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Edit alt text (image description)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Add alt text (image description)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Write your description here…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Short description for people who can’t see the image or when the image doesn’t load.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = This alt text was created automatically and may be inaccurate.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Learn more
pdfjs-editor-new-alt-text-create-automatically-button-label = Create alt text automatically
pdfjs-editor-new-alt-text-not-now-button = Not now
pdfjs-editor-new-alt-text-error-title = Couldn’t create alt text automatically
pdfjs-editor-new-alt-text-error-description = Please write your own alt text or try again later.
pdfjs-editor-new-alt-text-error-close-button = Close
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Downloading alt text AI model ({ $downloadedSize } of { $totalSize } MB)
.aria-valuetext = Downloading alt text AI model ({ $downloadedSize } of { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Alt text added
pdfjs-editor-new-alt-text-added-button-label = Alt text added
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Missing alt text
pdfjs-editor-new-alt-text-missing-button-label = Missing alt text
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Review alt text
pdfjs-editor-new-alt-text-to-review-button-label = Review alt text
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Created automatically: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Image alt text settings
pdfjs-image-alt-text-settings-button-label = Image alt text settings
pdfjs-editor-alt-text-settings-dialog-label = Image alt text settings
pdfjs-editor-alt-text-settings-automatic-title = Automatic alt text
pdfjs-editor-alt-text-settings-create-model-button-label = Create alt text automatically
pdfjs-editor-alt-text-settings-create-model-description = Suggests descriptions to help people who can’t see the image or when the image doesn’t load.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Alt text AI model ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Runs locally on your device so your data stays private. Required for automatic alt text.
pdfjs-editor-alt-text-settings-delete-model-button = Delete
pdfjs-editor-alt-text-settings-download-model-button = Download
pdfjs-editor-alt-text-settings-downloading-model-button = Downloading…
pdfjs-editor-alt-text-settings-editor-title = Alt text editor
pdfjs-editor-alt-text-settings-show-dialog-button-label = Show alt text editor right away when adding an image
pdfjs-editor-alt-text-settings-show-dialog-description = Helps you make sure all your images have alt text.
pdfjs-editor-alt-text-settings-close-button = Close
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Highlight removed
pdfjs-editor-undo-bar-message-freetext = Text removed
pdfjs-editor-undo-bar-message-ink = Drawing removed
pdfjs-editor-undo-bar-message-stamp = Image removed
pdfjs-editor-undo-bar-message-signature = Signature removed
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } annotation removed
*[other] { $count } annotations removed
}
pdfjs-editor-undo-bar-undo-button =
.title = Undo
pdfjs-editor-undo-bar-undo-button-label = Undo
pdfjs-editor-undo-bar-close-button =
.title = Close
pdfjs-editor-undo-bar-close-button-label = Close
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = This modal allows the user to create a signature to add to a PDF document. The user can edit the name (which also serves as the alt text), and optionally save the signature for repeated use.
pdfjs-editor-add-signature-dialog-title = Add a signature
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Type
.title = Type
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Draw
.title = Draw
pdfjs-editor-add-signature-image-button = Image
.title = Image
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Type your signature
.placeholder = Type your signature
pdfjs-editor-add-signature-draw-placeholder = Draw your signature
pdfjs-editor-add-signature-draw-thickness-range-label = Thickness
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Drawing thickness: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Drag a file here to upload
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Or choose image files
*[other] Or browse image files
}
## Controls
pdfjs-editor-add-signature-description-label = Description (alt text)
pdfjs-editor-add-signature-description-input =
.title = Description (alt text)
pdfjs-editor-add-signature-description-default-when-drawing = Signature
pdfjs-editor-add-signature-clear-button-label = Clear signature
pdfjs-editor-add-signature-clear-button =
.title = Clear signature
pdfjs-editor-add-signature-save-checkbox = Save signature
pdfjs-editor-add-signature-save-warning-message = You’ve reached the limit of 5 saved signatures. Remove one to save more.
pdfjs-editor-add-signature-image-upload-error-title = Couldn’t upload image
pdfjs-editor-add-signature-image-upload-error-description = Check your network connection or try another image.
pdfjs-editor-add-signature-error-close-button = Close
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Cancel
pdfjs-editor-add-signature-add-button = Add
pdfjs-editor-edit-signature-update-button = Update
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Remove signature
pdfjs-editor-delete-signature-button-label = Remove signature
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Edit description
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Edit description
================================================
FILE: cookbook/static/pdfjs/web/locale/en-US/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Previous Page
pdfjs-previous-button-label = Previous
pdfjs-next-button =
.title = Next Page
pdfjs-next-button-label = Next
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Page
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = of { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } of { $pagesCount })
pdfjs-zoom-out-button =
.title = Zoom Out
pdfjs-zoom-out-button-label = Zoom Out
pdfjs-zoom-in-button =
.title = Zoom In
pdfjs-zoom-in-button-label = Zoom In
pdfjs-zoom-select =
.title = Zoom
pdfjs-presentation-mode-button =
.title = Switch to Presentation Mode
pdfjs-presentation-mode-button-label = Presentation Mode
pdfjs-open-file-button =
.title = Open File
pdfjs-open-file-button-label = Open
pdfjs-print-button =
.title = Print
pdfjs-print-button-label = Print
pdfjs-save-button =
.title = Save
pdfjs-save-button-label = Save
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Download
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Download
pdfjs-bookmark-button =
.title = Current Page (View URL from Current Page)
pdfjs-bookmark-button-label = Current Page
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Tools
pdfjs-tools-button-label = Tools
pdfjs-first-page-button =
.title = Go to First Page
pdfjs-first-page-button-label = Go to First Page
pdfjs-last-page-button =
.title = Go to Last Page
pdfjs-last-page-button-label = Go to Last Page
pdfjs-page-rotate-cw-button =
.title = Rotate Clockwise
pdfjs-page-rotate-cw-button-label = Rotate Clockwise
pdfjs-page-rotate-ccw-button =
.title = Rotate Counterclockwise
pdfjs-page-rotate-ccw-button-label = Rotate Counterclockwise
pdfjs-cursor-text-select-tool-button =
.title = Enable Text Selection Tool
pdfjs-cursor-text-select-tool-button-label = Text Selection Tool
pdfjs-cursor-hand-tool-button =
.title = Enable Hand Tool
pdfjs-cursor-hand-tool-button-label = Hand Tool
pdfjs-scroll-page-button =
.title = Use Page Scrolling
pdfjs-scroll-page-button-label = Page Scrolling
pdfjs-scroll-vertical-button =
.title = Use Vertical Scrolling
pdfjs-scroll-vertical-button-label = Vertical Scrolling
pdfjs-scroll-horizontal-button =
.title = Use Horizontal Scrolling
pdfjs-scroll-horizontal-button-label = Horizontal Scrolling
pdfjs-scroll-wrapped-button =
.title = Use Wrapped Scrolling
pdfjs-scroll-wrapped-button-label = Wrapped Scrolling
pdfjs-spread-none-button =
.title = Do not join page spreads
pdfjs-spread-none-button-label = No Spreads
pdfjs-spread-odd-button =
.title = Join page spreads starting with odd-numbered pages
pdfjs-spread-odd-button-label = Odd Spreads
pdfjs-spread-even-button =
.title = Join page spreads starting with even-numbered pages
pdfjs-spread-even-button-label = Even Spreads
## Document properties dialog
pdfjs-document-properties-button =
.title = Document Properties…
pdfjs-document-properties-button-label = Document Properties…
pdfjs-document-properties-file-name = File name:
pdfjs-document-properties-file-size = File size:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes)
pdfjs-document-properties-title = Title:
pdfjs-document-properties-author = Author:
pdfjs-document-properties-subject = Subject:
pdfjs-document-properties-keywords = Keywords:
pdfjs-document-properties-creation-date = Creation Date:
pdfjs-document-properties-modification-date = Modification Date:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
pdfjs-document-properties-creator = Creator:
pdfjs-document-properties-producer = PDF Producer:
pdfjs-document-properties-version = PDF Version:
pdfjs-document-properties-page-count = Page Count:
pdfjs-document-properties-page-size = Page Size:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = portrait
pdfjs-document-properties-page-size-orientation-landscape = landscape
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letter
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Fast Web View:
pdfjs-document-properties-linearized-yes = Yes
pdfjs-document-properties-linearized-no = No
pdfjs-document-properties-close-button = Close
## Print
pdfjs-print-progress-message = Preparing document for printing…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Cancel
pdfjs-printing-not-supported = Warning: Printing is not fully supported by this browser.
pdfjs-printing-not-ready = Warning: The PDF is not fully loaded for printing.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Toggle Sidebar
pdfjs-toggle-sidebar-notification-button =
.title = Toggle Sidebar (document contains outline/attachments/layers)
pdfjs-toggle-sidebar-button-label = Toggle Sidebar
pdfjs-document-outline-button =
.title = Show Document Outline (double-click to expand/collapse all items)
pdfjs-document-outline-button-label = Document Outline
pdfjs-attachments-button =
.title = Show Attachments
pdfjs-attachments-button-label = Attachments
pdfjs-layers-button =
.title = Show Layers (double-click to reset all layers to the default state)
pdfjs-layers-button-label = Layers
pdfjs-thumbs-button =
.title = Show Thumbnails
pdfjs-thumbs-button-label = Thumbnails
pdfjs-current-outline-item-button =
.title = Find Current Outline Item
pdfjs-current-outline-item-button-label = Current Outline Item
pdfjs-findbar-button =
.title = Find in Document
pdfjs-findbar-button-label = Find
pdfjs-additional-layers = Additional Layers
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Page { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Thumbnail of Page { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Find
.placeholder = Find in document…
pdfjs-find-previous-button =
.title = Find the previous occurrence of the phrase
pdfjs-find-previous-button-label = Previous
pdfjs-find-next-button =
.title = Find the next occurrence of the phrase
pdfjs-find-next-button-label = Next
pdfjs-find-highlight-checkbox = Highlight All
pdfjs-find-match-case-checkbox-label = Match Case
pdfjs-find-match-diacritics-checkbox-label = Match Diacritics
pdfjs-find-entire-word-checkbox-label = Whole Words
pdfjs-find-reached-top = Reached top of document, continued from bottom
pdfjs-find-reached-bottom = Reached end of document, continued from top
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } of { $total } match
*[other] { $current } of { $total } matches
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] More than { $limit } match
*[other] More than { $limit } matches
}
pdfjs-find-not-found = Phrase not found
## Predefined zoom values
pdfjs-page-scale-width = Page Width
pdfjs-page-scale-fit = Page Fit
pdfjs-page-scale-auto = Automatic Zoom
pdfjs-page-scale-actual = Actual Size
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Page { $page }
## Loading indicator messages
pdfjs-loading-error = An error occurred while loading the PDF.
pdfjs-invalid-file-error = Invalid or corrupted PDF file.
pdfjs-missing-file-error = Missing PDF file.
pdfjs-unexpected-response-error = Unexpected server response.
pdfjs-rendering-error = An error occurred while rendering the page.
## Annotations
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } Annotation]
## Password
pdfjs-password-label = Enter the password to open this PDF file.
pdfjs-password-invalid = Invalid password. Please try again.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Cancel
pdfjs-web-fonts-disabled = Web fonts are disabled: unable to use embedded PDF fonts.
## Editing
pdfjs-editor-free-text-button =
.title = Text
pdfjs-editor-free-text-button-label = Text
pdfjs-editor-ink-button =
.title = Draw
pdfjs-editor-ink-button-label = Draw
pdfjs-editor-stamp-button =
.title = Add or edit images
pdfjs-editor-stamp-button-label = Add or edit images
pdfjs-editor-highlight-button =
.title = Highlight
pdfjs-editor-highlight-button-label = Highlight
pdfjs-highlight-floating-button1 =
.title = Highlight
.aria-label = Highlight
pdfjs-highlight-floating-button-label = Highlight
pdfjs-editor-signature-button =
.title = Add signature
pdfjs-editor-signature-button-label = Add signature
## Default editor aria labels
# “Highlight” is a noun, the string is used on the editor for highlights.
pdfjs-editor-highlight-editor =
.aria-label = Highlight editor
# “Drawing” is a noun, the string is used on the editor for drawings.
pdfjs-editor-ink-editor =
.aria-label = Drawing editor
# Used when a signature editor is selected/hovered.
# Variables:
# $description (String) - a string describing/labeling the signature.
pdfjs-editor-signature-editor1 =
.aria-description = Signature editor: { $description }
pdfjs-editor-stamp-editor =
.aria-label = Image editor
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Remove drawing
pdfjs-editor-remove-freetext-button =
.title = Remove text
pdfjs-editor-remove-stamp-button =
.title = Remove image
pdfjs-editor-remove-highlight-button =
.title = Remove highlight
pdfjs-editor-remove-signature-button =
.title = Remove signature
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Color
pdfjs-editor-free-text-size-input = Size
pdfjs-editor-ink-color-input = Color
pdfjs-editor-ink-thickness-input = Thickness
pdfjs-editor-ink-opacity-input = Opacity
pdfjs-editor-stamp-add-image-button =
.title = Add image
pdfjs-editor-stamp-add-image-button-label = Add image
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Thickness
pdfjs-editor-free-highlight-thickness-title =
.title = Change thickness when highlighting items other than text
pdfjs-editor-add-signature-container =
.aria-label = Signature controls and saved signatures
pdfjs-editor-signature-add-signature-button =
.title = Add new signature
pdfjs-editor-signature-add-signature-button-label = Add new signature
# Used on the button to use an already saved signature.
# Variables:
# $description (String) - a string describing/labeling the signature.
pdfjs-editor-add-saved-signature-button =
.title = Saved signature: { $description }
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Text Editor
.default-content = Start typing…
## Alt-text dialog
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Alt text
pdfjs-editor-alt-text-button-label = Alt text
pdfjs-editor-alt-text-edit-button =
.aria-label = Edit alt text
pdfjs-editor-alt-text-dialog-label = Choose an option
pdfjs-editor-alt-text-dialog-description = Alt text (alternative text) helps when people can’t see the image or when it doesn’t load.
pdfjs-editor-alt-text-add-description-label = Add a description
pdfjs-editor-alt-text-add-description-description = Aim for 1-2 sentences that describe the subject, setting, or actions.
pdfjs-editor-alt-text-mark-decorative-label = Mark as decorative
pdfjs-editor-alt-text-mark-decorative-description = This is used for ornamental images, like borders or watermarks.
pdfjs-editor-alt-text-cancel-button = Cancel
pdfjs-editor-alt-text-save-button = Save
pdfjs-editor-alt-text-decorative-tooltip = Marked as decorative
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = For example, “A young man sits down at a table to eat a meal”
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-top-left =
.aria-label = Top left corner — resize
pdfjs-editor-resizer-top-middle =
.aria-label = Top middle — resize
pdfjs-editor-resizer-top-right =
.aria-label = Top right corner — resize
pdfjs-editor-resizer-middle-right =
.aria-label = Middle right — resize
pdfjs-editor-resizer-bottom-right =
.aria-label = Bottom right corner — resize
pdfjs-editor-resizer-bottom-middle =
.aria-label = Bottom middle — resize
pdfjs-editor-resizer-bottom-left =
.aria-label = Bottom left corner — resize
pdfjs-editor-resizer-middle-left =
.aria-label = Middle left — resize
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Highlight color
pdfjs-editor-colorpicker-button =
.title = Change color
pdfjs-editor-colorpicker-dropdown =
.aria-label = Color choices
pdfjs-editor-colorpicker-yellow =
.title = Yellow
pdfjs-editor-colorpicker-green =
.title = Green
pdfjs-editor-colorpicker-blue =
.title = Blue
pdfjs-editor-colorpicker-pink =
.title = Pink
pdfjs-editor-colorpicker-red =
.title = Red
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Show all
pdfjs-editor-highlight-show-all-button =
.title = Show all
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Edit alt text (image description)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Add alt text (image description)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Write your description here…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Short description for people who can’t see the image or when the image doesn’t load.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = This alt text was created automatically and may be inaccurate.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Learn more
pdfjs-editor-new-alt-text-create-automatically-button-label = Create alt text automatically
pdfjs-editor-new-alt-text-not-now-button = Not now
pdfjs-editor-new-alt-text-error-title = Couldn’t create alt text automatically
pdfjs-editor-new-alt-text-error-description = Please write your own alt text or try again later.
pdfjs-editor-new-alt-text-error-close-button = Close
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Downloading alt text AI model ({ $downloadedSize } of { $totalSize } MB)
.aria-valuetext = Downloading alt text AI model ({ $downloadedSize } of { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Alt text added
pdfjs-editor-new-alt-text-added-button-label = Alt text added
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Missing alt text
pdfjs-editor-new-alt-text-missing-button-label = Missing alt text
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Review alt text
pdfjs-editor-new-alt-text-to-review-button-label = Review alt text
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Created automatically: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Image alt text settings
pdfjs-image-alt-text-settings-button-label = Image alt text settings
pdfjs-editor-alt-text-settings-dialog-label = Image alt text settings
pdfjs-editor-alt-text-settings-automatic-title = Automatic alt text
pdfjs-editor-alt-text-settings-create-model-button-label = Create alt text automatically
pdfjs-editor-alt-text-settings-create-model-description = Suggests descriptions to help people who can’t see the image or when the image doesn’t load.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Alt text AI model ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Runs locally on your device so your data stays private. Required for automatic alt text.
pdfjs-editor-alt-text-settings-delete-model-button = Delete
pdfjs-editor-alt-text-settings-download-model-button = Download
pdfjs-editor-alt-text-settings-downloading-model-button = Downloading…
pdfjs-editor-alt-text-settings-editor-title = Alt text editor
pdfjs-editor-alt-text-settings-show-dialog-button-label = Show alt text editor right away when adding an image
pdfjs-editor-alt-text-settings-show-dialog-description = Helps you make sure all your images have alt text.
pdfjs-editor-alt-text-settings-close-button = Close
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Highlight removed
pdfjs-editor-undo-bar-message-freetext = Text removed
pdfjs-editor-undo-bar-message-ink = Drawing removed
pdfjs-editor-undo-bar-message-stamp = Image removed
pdfjs-editor-undo-bar-message-signature = Signature removed
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } annotation removed
*[other] { $count } annotations removed
}
pdfjs-editor-undo-bar-undo-button =
.title = Undo
pdfjs-editor-undo-bar-undo-button-label = Undo
pdfjs-editor-undo-bar-close-button =
.title = Close
pdfjs-editor-undo-bar-close-button-label = Close
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = This modal allows the user to create a signature to add to a PDF document. The user can edit the name (which also serves as the alt text), and optionally save the signature for repeated use.
pdfjs-editor-add-signature-dialog-title = Add a signature
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Type
.title = Type
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Draw
.title = Draw
pdfjs-editor-add-signature-image-button = Image
.title = Image
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Type your signature
.placeholder = Type your signature
pdfjs-editor-add-signature-draw-placeholder = Draw your signature
pdfjs-editor-add-signature-draw-thickness-range-label = Thickness
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Drawing thickness: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Drag a file here to upload
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Or choose image files
*[other] Or browse image files
}
## Controls
pdfjs-editor-add-signature-description-label = Description (alt text)
pdfjs-editor-add-signature-description-input =
.title = Description (alt text)
pdfjs-editor-add-signature-description-default-when-drawing = Signature
pdfjs-editor-add-signature-clear-button-label = Clear signature
pdfjs-editor-add-signature-clear-button =
.title = Clear signature
pdfjs-editor-add-signature-save-checkbox = Save signature
pdfjs-editor-add-signature-save-warning-message = You’ve reached the limit of 5 saved signatures. Remove one to save more.
pdfjs-editor-add-signature-image-upload-error-title = Couldn’t upload image
pdfjs-editor-add-signature-image-upload-error-description = Check your network connection or try another image.
pdfjs-editor-add-signature-error-close-button = Close
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Cancel
pdfjs-editor-add-signature-add-button = Add
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button1 =
.title = Remove saved signature
pdfjs-editor-delete-signature-button-label1 = Remove saved signature
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Edit description
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Edit description
## Dialog buttons
pdfjs-editor-edit-signature-update-button = Update
================================================
FILE: cookbook/static/pdfjs/web/locale/eo/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Antaŭa paĝo
pdfjs-previous-button-label = Malantaŭen
pdfjs-next-button =
.title = Venonta paĝo
pdfjs-next-button-label = Antaŭen
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Paĝo
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = el { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } el { $pagesCount })
pdfjs-zoom-out-button =
.title = Malpligrandigi
pdfjs-zoom-out-button-label = Malpligrandigi
pdfjs-zoom-in-button =
.title = Pligrandigi
pdfjs-zoom-in-button-label = Pligrandigi
pdfjs-zoom-select =
.title = Pligrandigilo
pdfjs-presentation-mode-button =
.title = Iri al prezenta reĝimo
pdfjs-presentation-mode-button-label = Prezenta reĝimo
pdfjs-open-file-button =
.title = Malfermi dosieron
pdfjs-open-file-button-label = Malfermi
pdfjs-print-button =
.title = Presi
pdfjs-print-button-label = Presi
pdfjs-save-button =
.title = Konservi
pdfjs-save-button-label = Konservi
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Elŝuti
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Elŝuti
pdfjs-bookmark-button =
.title = Nuna paĝo (Montri adreson de la nuna paĝo)
pdfjs-bookmark-button-label = Nuna paĝo
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Iloj
pdfjs-tools-button-label = Iloj
pdfjs-first-page-button =
.title = Iri al la unua paĝo
pdfjs-first-page-button-label = Iri al la unua paĝo
pdfjs-last-page-button =
.title = Iri al la lasta paĝo
pdfjs-last-page-button-label = Iri al la lasta paĝo
pdfjs-page-rotate-cw-button =
.title = Rotaciigi dekstrume
pdfjs-page-rotate-cw-button-label = Rotaciigi dekstrume
pdfjs-page-rotate-ccw-button =
.title = Rotaciigi maldekstrume
pdfjs-page-rotate-ccw-button-label = Rotaciigi maldekstrume
pdfjs-cursor-text-select-tool-button =
.title = Aktivigi tekstan elektilon
pdfjs-cursor-text-select-tool-button-label = Teksta elektilo
pdfjs-cursor-hand-tool-button =
.title = Aktivigi ilon de mano
pdfjs-cursor-hand-tool-button-label = Ilo de mano
pdfjs-scroll-page-button =
.title = Uzi rulumon de paĝo
pdfjs-scroll-page-button-label = Rulumo de paĝo
pdfjs-scroll-vertical-button =
.title = Uzi vertikalan rulumon
pdfjs-scroll-vertical-button-label = Vertikala rulumo
pdfjs-scroll-horizontal-button =
.title = Uzi horizontalan rulumon
pdfjs-scroll-horizontal-button-label = Horizontala rulumo
pdfjs-scroll-wrapped-button =
.title = Uzi ambaŭdirektan rulumon
pdfjs-scroll-wrapped-button-label = Ambaŭdirekta rulumo
pdfjs-spread-none-button =
.title = Ne montri paĝojn po du
pdfjs-spread-none-button-label = Unupaĝa vido
pdfjs-spread-odd-button =
.title = Kunigi paĝojn komencante per nepara paĝo
pdfjs-spread-odd-button-label = Po du paĝoj, neparaj maldekstre
pdfjs-spread-even-button =
.title = Kunigi paĝojn komencante per para paĝo
pdfjs-spread-even-button-label = Po du paĝoj, paraj maldekstre
## Document properties dialog
pdfjs-document-properties-button =
.title = Atributoj de dokumento…
pdfjs-document-properties-button-label = Atributoj de dokumento…
pdfjs-document-properties-file-name = Nomo de dosiero:
pdfjs-document-properties-file-size = Grando de dosiero:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KO ({ $b } oktetoj)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } Mo ({ $b } oktetoj)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KO ({ $size_b } oktetoj)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MO ({ $size_b } oktetoj)
pdfjs-document-properties-title = Titolo:
pdfjs-document-properties-author = Aŭtoro:
pdfjs-document-properties-subject = Temo:
pdfjs-document-properties-keywords = Ŝlosilvorto:
pdfjs-document-properties-creation-date = Dato de kreado:
pdfjs-document-properties-modification-date = Dato de modifo:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Kreinto:
pdfjs-document-properties-producer = Produktinto de PDF:
pdfjs-document-properties-version = Versio de PDF:
pdfjs-document-properties-page-count = Nombro de paĝoj:
pdfjs-document-properties-page-size = Grando de paĝo:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = vertikala
pdfjs-document-properties-page-size-orientation-landscape = horizontala
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letera
pdfjs-document-properties-page-size-name-legal = Jura
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Rapida tekstaĵa vido:
pdfjs-document-properties-linearized-yes = Jes
pdfjs-document-properties-linearized-no = Ne
pdfjs-document-properties-close-button = Fermi
## Print
pdfjs-print-progress-message = Preparo de dokumento por presi ĝin …
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Nuligi
pdfjs-printing-not-supported = Averto: tiu ĉi retumilo ne plene subtenas presadon.
pdfjs-printing-not-ready = Averto: la PDF dosiero ne estas plene ŝargita por presado.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Montri/kaŝi flankan strion
pdfjs-toggle-sidebar-notification-button =
.title = Montri/kaŝi flankan strion (la dokumento enhavas konturon/kunsendaĵojn/tavolojn)
pdfjs-toggle-sidebar-button-label = Montri/kaŝi flankan strion
pdfjs-document-outline-button =
.title = Montri la konturon de dokumento (alklaku duoble por faldi/malfaldi ĉiujn elementojn)
pdfjs-document-outline-button-label = Konturo de dokumento
pdfjs-attachments-button =
.title = Montri kunsendaĵojn
pdfjs-attachments-button-label = Kunsendaĵojn
pdfjs-layers-button =
.title = Montri tavolojn (duoble alklaku por remeti ĉiujn tavolojn en la norman staton)
pdfjs-layers-button-label = Tavoloj
pdfjs-thumbs-button =
.title = Montri miniaturojn
pdfjs-thumbs-button-label = Miniaturoj
pdfjs-current-outline-item-button =
.title = Trovi nunan konturan elementon
pdfjs-current-outline-item-button-label = Nuna kontura elemento
pdfjs-findbar-button =
.title = Serĉi en dokumento
pdfjs-findbar-button-label = Serĉi
pdfjs-additional-layers = Aldonaj tavoloj
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Paĝo { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Miniaturo de paĝo { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Serĉi
.placeholder = Serĉi en dokumento…
pdfjs-find-previous-button =
.title = Serĉi la antaŭan aperon de la frazo
pdfjs-find-previous-button-label = Malantaŭen
pdfjs-find-next-button =
.title = Serĉi la venontan aperon de la frazo
pdfjs-find-next-button-label = Antaŭen
pdfjs-find-highlight-checkbox = Elstarigi ĉiujn
pdfjs-find-match-case-checkbox-label = Distingi inter majuskloj kaj minuskloj
pdfjs-find-match-diacritics-checkbox-label = Respekti supersignojn
pdfjs-find-entire-word-checkbox-label = Tutaj vortoj
pdfjs-find-reached-top = Komenco de la dokumento atingita, daŭrigado ekde la fino
pdfjs-find-reached-bottom = Fino de la dokumento atingita, daŭrigado ekde la komenco
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } el { $total } kongruo
*[other] { $current } el { $total } kongruoj
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Pli ol { $limit } kongruo
*[other] Pli ol { $limit } kongruoj
}
pdfjs-find-not-found = Frazo ne trovita
## Predefined zoom values
pdfjs-page-scale-width = Larĝo de paĝo
pdfjs-page-scale-fit = Adapti paĝon
pdfjs-page-scale-auto = Aŭtomata skalo
pdfjs-page-scale-actual = Reala grando
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Paĝo { $page }
## Loading indicator messages
pdfjs-loading-error = Okazis eraro dum la ŝargado de la PDF dosiero.
pdfjs-invalid-file-error = Nevalida aŭ difektita PDF dosiero.
pdfjs-missing-file-error = Mankas dosiero PDF.
pdfjs-unexpected-response-error = Neatendita respondo de servilo.
pdfjs-rendering-error = Okazis eraro dum la montro de la paĝo.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [Prinoto: { $type }]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Tajpu pasvorton por malfermi tiun ĉi dosieron PDF.
pdfjs-password-invalid = Nevalida pasvorto. Bonvolu provi denove.
pdfjs-password-ok-button = Akcepti
pdfjs-password-cancel-button = Nuligi
pdfjs-web-fonts-disabled = Neaktivaj teksaĵaj tiparoj: ne elbas uzi enmetitajn tiparojn de PDF.
## Editing
pdfjs-editor-free-text-button =
.title = Teksto
pdfjs-editor-free-text-button-label = Teksto
pdfjs-editor-ink-button =
.title = Desegni
pdfjs-editor-ink-button-label = Desegni
pdfjs-editor-stamp-button =
.title = Aldoni aŭ modifi bildojn
pdfjs-editor-stamp-button-label = Aldoni aŭ modifi bildojn
pdfjs-editor-highlight-button =
.title = Elstarigi
pdfjs-editor-highlight-button-label = Elstarigi
pdfjs-highlight-floating-button1 =
.title = Elstarigi
.aria-label = Elstarigi
pdfjs-highlight-floating-button-label = Elstarigi
pdfjs-editor-signature-button =
.title = Aldoni subskribon
pdfjs-editor-signature-button-label = Aldoni subskribon
## Default editor aria labels
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Forigi desegnon
pdfjs-editor-remove-freetext-button =
.title = Forigi tekston
pdfjs-editor-remove-stamp-button =
.title = Forigi bildon
pdfjs-editor-remove-highlight-button =
.title = Forigi elstaraĵon
pdfjs-editor-remove-signature-button =
.title = Forigi subskribon
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Koloro
pdfjs-editor-free-text-size-input = Grando
pdfjs-editor-ink-color-input = Koloro
pdfjs-editor-ink-thickness-input = Dikeco
pdfjs-editor-ink-opacity-input = Maldiafaneco
pdfjs-editor-stamp-add-image-button =
.title = Aldoni bildon
pdfjs-editor-stamp-add-image-button-label = Aldoni bildon
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Dikeco
pdfjs-editor-free-highlight-thickness-title =
.title = Ŝanĝi dikecon dum elstarigo de netekstaj elementoj
pdfjs-editor-signature-add-signature-button =
.title = Aldoni novan subskribon
pdfjs-editor-signature-add-signature-button-label = Aldoni novan subskribon
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Teksta redaktilo
.default-content = Komencu tajpi…
pdfjs-free-text =
.aria-label = Teksta redaktilo
pdfjs-free-text-default-content = Ektajpi…
pdfjs-ink =
.aria-label = Desegnan redaktilon
pdfjs-ink-canvas =
.aria-label = Bildo kreita de uzanto
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Alternativa teksto
pdfjs-editor-alt-text-edit-button =
.aria-label = Redakti alternativan tekston
pdfjs-editor-alt-text-edit-button-label = Redakti alternativan tekston
pdfjs-editor-alt-text-dialog-label = Elektu eblon
pdfjs-editor-alt-text-dialog-description = Alternativa teksto helpas personojn, en la okazoj kiam ili ne povas vidi aŭ ŝargi la bildon.
pdfjs-editor-alt-text-add-description-label = Aldoni priskribon
pdfjs-editor-alt-text-add-description-description = La celo estas unu aŭ du frazoj, kiuj priskribas la temon, etoson aŭ agojn.
pdfjs-editor-alt-text-mark-decorative-label = Marki kiel ornaman
pdfjs-editor-alt-text-mark-decorative-description = Tio ĉi estas uzita por ornamaj bildoj, kiel randoj aŭ fonaj bildoj.
pdfjs-editor-alt-text-cancel-button = Nuligi
pdfjs-editor-alt-text-save-button = Konservi
pdfjs-editor-alt-text-decorative-tooltip = Markita kiel ornama
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Ekzemple: “Juna persono sidiĝas ĉetable por ekmanĝi”
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Alternativa teksto
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Supra maldekstra angulo — ŝangi grandon
pdfjs-editor-resizer-label-top-middle = Supra mezo — ŝanĝi grandon
pdfjs-editor-resizer-label-top-right = Supran dekstran angulon — ŝanĝi grandon
pdfjs-editor-resizer-label-middle-right = Dekstra mezo — ŝanĝi grandon
pdfjs-editor-resizer-label-bottom-right = Malsupra deksta angulo — ŝanĝi grandon
pdfjs-editor-resizer-label-bottom-middle = Malsupra mezo — ŝanĝi grandon
pdfjs-editor-resizer-label-bottom-left = Malsupra maldekstra angulo — ŝanĝi grandon
pdfjs-editor-resizer-label-middle-left = Maldekstra mezo — ŝanĝi grandon
pdfjs-editor-resizer-top-left =
.aria-label = Supra maldekstra angulo — ŝangi grandon
pdfjs-editor-resizer-top-middle =
.aria-label = Supra mezo — ŝanĝi grandon
pdfjs-editor-resizer-top-right =
.aria-label = Supran dekstran angulon — ŝanĝi grandon
pdfjs-editor-resizer-middle-right =
.aria-label = Dekstra mezo — ŝanĝi grandon
pdfjs-editor-resizer-bottom-right =
.aria-label = Malsupra deksta angulo — ŝanĝi grandon
pdfjs-editor-resizer-bottom-middle =
.aria-label = Malsupra mezo — ŝanĝi grandon
pdfjs-editor-resizer-bottom-left =
.aria-label = Malsupra maldekstra angulo — ŝanĝi grandon
pdfjs-editor-resizer-middle-left =
.aria-label = Maldekstra mezo — ŝanĝi grandon
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Elstarigi koloron
pdfjs-editor-colorpicker-button =
.title = Ŝanĝi koloron
pdfjs-editor-colorpicker-dropdown =
.aria-label = Elekto de koloroj
pdfjs-editor-colorpicker-yellow =
.title = Flava
pdfjs-editor-colorpicker-green =
.title = Verda
pdfjs-editor-colorpicker-blue =
.title = Blua
pdfjs-editor-colorpicker-pink =
.title = Roza
pdfjs-editor-colorpicker-red =
.title = Ruĝa
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Montri ĉiujn
pdfjs-editor-highlight-show-all-button =
.title = Montri ĉiujn
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Modifi alternativan tekston (priskribo de bildo)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Aldoni alternativan tekston (priskribo de bildo)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Skribu vian priskribon ĉi tie…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Mallonga priskribo por personoj kiuj ne povas vidi la bildon kaj por montri kiam la bildo ne ŝargeblas.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Tiu ĉi alternativa teksto estis aŭtomate kreita kaj povus esti malĝusta.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Pli da informo
pdfjs-editor-new-alt-text-create-automatically-button-label = Aŭtomate krei alternativan tekston
pdfjs-editor-new-alt-text-not-now-button = Ne nun
pdfjs-editor-new-alt-text-error-title = Ne eblis aŭtomate krei alternativan tekston
pdfjs-editor-new-alt-text-error-description = Bonvolu skribi vian propran alternativan tekston aŭ provi denove poste.
pdfjs-editor-new-alt-text-error-close-button = Fermi
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Elŝuto de modelo de artefarita intelekto por alternativa teksto ({ $downloadedSize } el { $totalSize } MO)
.aria-valuetext = Elŝuto de modelo de artefarita intelekto por alternativa teksto ({ $downloadedSize } el { $totalSize } MO)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Alternativa teksto aldonita
pdfjs-editor-new-alt-text-added-button-label = Alternativa teksto aldonita
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Mankas alternativa teksto
pdfjs-editor-new-alt-text-missing-button-label = Mankas alternativa teksto
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Kontroli alternativan tekston
pdfjs-editor-new-alt-text-to-review-button-label = Kontroli alternativan tekston
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Aŭtomate kreita: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Agordoj por alternativa teksto de bildoj
pdfjs-image-alt-text-settings-button-label = Agordoj por alternativa teksto de bildoj
pdfjs-editor-alt-text-settings-dialog-label = Agordoj por alternativa teksto de bildoj
pdfjs-editor-alt-text-settings-automatic-title = Aŭtomata alternativa teksto
pdfjs-editor-alt-text-settings-create-model-button-label = Aŭtomate krei alternativan tekston
pdfjs-editor-alt-text-settings-create-model-description = Tio ĉi sugestas priskribojn por helpi personojn kiuj ne povas vidi aŭ ŝargi la bildon.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Modelo de artefarita intelekto por alternativa teksto ({ $totalSize } MO)
pdfjs-editor-alt-text-settings-ai-model-description = Ĝi funkcias en via aparato, do viaj datumoj restas privataj. Ĝi estas postulata por aŭtomata kreado de alternativa teksto.
pdfjs-editor-alt-text-settings-delete-model-button = Forigi
pdfjs-editor-alt-text-settings-download-model-button = Elŝuti
pdfjs-editor-alt-text-settings-downloading-model-button = Elŝuto…
pdfjs-editor-alt-text-settings-editor-title = Redaktilo de alternativa teksto
pdfjs-editor-alt-text-settings-show-dialog-button-label = Montri redaktilon de alternativa teksto tuj post aldono de bildo
pdfjs-editor-alt-text-settings-show-dialog-description = Tio ĉi helpas vin kontroli ĉu ĉiuj bildoj havas alternativan tekston.
pdfjs-editor-alt-text-settings-close-button = Fermi
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Elstaraĵo forigita
pdfjs-editor-undo-bar-message-freetext = Teksto forigita
pdfjs-editor-undo-bar-message-ink = Desegno forigita
pdfjs-editor-undo-bar-message-stamp = Bildo forigita
pdfjs-editor-undo-bar-message-signature = Subskribo forigita
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] unu prinoto forigita
*[other] { $count } prinotoj forigitaj
}
pdfjs-editor-undo-bar-undo-button =
.title = Malfari
pdfjs-editor-undo-bar-undo-button-label = Malfari
pdfjs-editor-undo-bar-close-button =
.title = Fermi
pdfjs-editor-undo-bar-close-button-label = Fermi
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = Tiu ĉi fenestro permesas al la uzanto krei subskribon por aldoni al dokumento PDF. La uzanto povas modifi la nomon (kiu estas cetere la alternativa teksto) kaj havas la eblon konservi la subskribon por posta uzo.
pdfjs-editor-add-signature-dialog-title = Aldoni subskribon
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Tajpi
.title = Tajpi
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Desegni
.title = Desegni
pdfjs-editor-add-signature-image-button = Bildo
.title = Bildo
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Tajpu vian subskribon
.placeholder = Tajpu vian subskribon
pdfjs-editor-add-signature-draw-placeholder = Desegni vian subskribon
pdfjs-editor-add-signature-draw-thickness-range-label = Dikeco
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Dikeco de desegno: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Trenu dosieron ĉi tien por alŝuti ĝin
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Aŭ elektu bildan dosieron
*[other] Aŭ elektu bildan dosieron
}
## Controls
pdfjs-editor-add-signature-description-label = Priskribo (alternativa teksto)
pdfjs-editor-add-signature-description-input =
.title = Priskribo (alternativa teksto)
pdfjs-editor-add-signature-description-default-when-drawing = Subskribo
pdfjs-editor-add-signature-clear-button-label = Viŝi subskribon
pdfjs-editor-add-signature-clear-button =
.title = Viŝi subskribon
pdfjs-editor-add-signature-save-checkbox = Konservi subskribon
pdfjs-editor-add-signature-save-warning-message = Vi atingis la limon de kvin konservitaj subskriboj. Forigi unu por povi konservi pli da.
pdfjs-editor-add-signature-image-upload-error-title = Ne eblis alŝuti bildon
pdfjs-editor-add-signature-image-upload-error-description = Kontrolu vian retaliron aŭ provu alŝuti alian bildon.
pdfjs-editor-add-signature-error-close-button = Fermi
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Nuligi
pdfjs-editor-add-signature-add-button = Aldoni
pdfjs-editor-edit-signature-update-button = Ĝisdatigi
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Forigi subskribon
pdfjs-editor-delete-signature-button-label = Forigi subskribon
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Modifi priskribon
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Modifi priskribon
================================================
FILE: cookbook/static/pdfjs/web/locale/es-AR/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Página anterior
pdfjs-previous-button-label = Anterior
pdfjs-next-button =
.title = Página siguiente
pdfjs-next-button-label = Siguiente
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Página
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = de { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ( { $pageNumber } de { $pagesCount } )
pdfjs-zoom-out-button =
.title = Alejar
pdfjs-zoom-out-button-label = Alejar
pdfjs-zoom-in-button =
.title = Acercar
pdfjs-zoom-in-button-label = Acercar
pdfjs-zoom-select =
.title = Zoom
pdfjs-presentation-mode-button =
.title = Cambiar a modo presentación
pdfjs-presentation-mode-button-label = Modo presentación
pdfjs-open-file-button =
.title = Abrir archivo
pdfjs-open-file-button-label = Abrir
pdfjs-print-button =
.title = Imprimir
pdfjs-print-button-label = Imprimir
pdfjs-save-button =
.title = Guardar
pdfjs-save-button-label = Guardar
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Descargar
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Descargar
pdfjs-bookmark-button =
.title = Página actual (Ver URL de la página actual)
pdfjs-bookmark-button-label = Página actual
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Herramientas
pdfjs-tools-button-label = Herramientas
pdfjs-first-page-button =
.title = Ir a primera página
pdfjs-first-page-button-label = Ir a primera página
pdfjs-last-page-button =
.title = Ir a última página
pdfjs-last-page-button-label = Ir a última página
pdfjs-page-rotate-cw-button =
.title = Rotar horario
pdfjs-page-rotate-cw-button-label = Rotar horario
pdfjs-page-rotate-ccw-button =
.title = Rotar antihorario
pdfjs-page-rotate-ccw-button-label = Rotar antihorario
pdfjs-cursor-text-select-tool-button =
.title = Habilitar herramienta de selección de texto
pdfjs-cursor-text-select-tool-button-label = Herramienta de selección de texto
pdfjs-cursor-hand-tool-button =
.title = Habilitar herramienta mano
pdfjs-cursor-hand-tool-button-label = Herramienta mano
pdfjs-scroll-page-button =
.title = Usar desplazamiento de página
pdfjs-scroll-page-button-label = Desplazamiento de página
pdfjs-scroll-vertical-button =
.title = Usar desplazamiento vertical
pdfjs-scroll-vertical-button-label = Desplazamiento vertical
pdfjs-scroll-horizontal-button =
.title = Usar desplazamiento vertical
pdfjs-scroll-horizontal-button-label = Desplazamiento horizontal
pdfjs-scroll-wrapped-button =
.title = Usar desplazamiento encapsulado
pdfjs-scroll-wrapped-button-label = Desplazamiento encapsulado
pdfjs-spread-none-button =
.title = No unir páginas dobles
pdfjs-spread-none-button-label = Sin dobles
pdfjs-spread-odd-button =
.title = Unir páginas dobles comenzando con las impares
pdfjs-spread-odd-button-label = Dobles impares
pdfjs-spread-even-button =
.title = Unir páginas dobles comenzando con las pares
pdfjs-spread-even-button-label = Dobles pares
## Document properties dialog
pdfjs-document-properties-button =
.title = Propiedades del documento…
pdfjs-document-properties-button-label = Propiedades del documento…
pdfjs-document-properties-file-name = Nombre de archivo:
pdfjs-document-properties-file-size = Tamaño de archovo:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = Título:
pdfjs-document-properties-author = Autor:
pdfjs-document-properties-subject = Asunto:
pdfjs-document-properties-keywords = Palabras clave:
pdfjs-document-properties-creation-date = Fecha de creación:
pdfjs-document-properties-modification-date = Fecha de modificación:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Creador:
pdfjs-document-properties-producer = PDF Productor:
pdfjs-document-properties-version = Versión de PDF:
pdfjs-document-properties-page-count = Cantidad de páginas:
pdfjs-document-properties-page-size = Tamaño de página:
pdfjs-document-properties-page-size-unit-inches = en
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = normal
pdfjs-document-properties-page-size-orientation-landscape = apaisado
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Carta
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Vista rápida de la Web:
pdfjs-document-properties-linearized-yes = Sí
pdfjs-document-properties-linearized-no = No
pdfjs-document-properties-close-button = Cerrar
## Print
pdfjs-print-progress-message = Preparando documento para imprimir…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Cancelar
pdfjs-printing-not-supported = Advertencia: La impresión no está totalmente soportada por este navegador.
pdfjs-printing-not-ready = Advertencia: El PDF no está completamente cargado para impresión.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Alternar barra lateral
pdfjs-toggle-sidebar-notification-button =
.title = Alternar barra lateral (el documento contiene esquemas/adjuntos/capas)
pdfjs-toggle-sidebar-button-label = Alternar barra lateral
pdfjs-document-outline-button =
.title = Mostrar esquema del documento (doble clic para expandir/colapsar todos los ítems)
pdfjs-document-outline-button-label = Esquema del documento
pdfjs-attachments-button =
.title = Mostrar adjuntos
pdfjs-attachments-button-label = Adjuntos
pdfjs-layers-button =
.title = Mostrar capas (doble clic para restablecer todas las capas al estado predeterminado)
pdfjs-layers-button-label = Capas
pdfjs-thumbs-button =
.title = Mostrar miniaturas
pdfjs-thumbs-button-label = Miniaturas
pdfjs-current-outline-item-button =
.title = Buscar elemento de esquema actual
pdfjs-current-outline-item-button-label = Elemento de esquema actual
pdfjs-findbar-button =
.title = Buscar en documento
pdfjs-findbar-button-label = Buscar
pdfjs-additional-layers = Capas adicionales
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Página { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Miniatura de página { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Buscar
.placeholder = Buscar en documento…
pdfjs-find-previous-button =
.title = Buscar la aparición anterior de la frase
pdfjs-find-previous-button-label = Anterior
pdfjs-find-next-button =
.title = Buscar la siguiente aparición de la frase
pdfjs-find-next-button-label = Siguiente
pdfjs-find-highlight-checkbox = Resaltar todo
pdfjs-find-match-case-checkbox-label = Coincidir mayúsculas
pdfjs-find-match-diacritics-checkbox-label = Coincidir diacríticos
pdfjs-find-entire-word-checkbox-label = Palabras completas
pdfjs-find-reached-top = Inicio de documento alcanzado, continuando desde abajo
pdfjs-find-reached-bottom = Fin de documento alcanzando, continuando desde arriba
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } de { $total } coincidencia
*[other] { $current } de { $total } coincidencias
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Más de { $limit } coincidencia
*[other] Más de { $limit } coincidencias
}
pdfjs-find-not-found = Frase no encontrada
## Predefined zoom values
pdfjs-page-scale-width = Ancho de página
pdfjs-page-scale-fit = Ajustar página
pdfjs-page-scale-auto = Zoom automático
pdfjs-page-scale-actual = Tamaño real
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Página { $page }
## Loading indicator messages
pdfjs-loading-error = Ocurrió un error al cargar el PDF.
pdfjs-invalid-file-error = Archivo PDF no válido o cocrrupto.
pdfjs-missing-file-error = Archivo PDF faltante.
pdfjs-unexpected-response-error = Respuesta del servidor inesperada.
pdfjs-rendering-error = Ocurrió un error al dibujar la página.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } Anotación]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Ingrese la contraseña para abrir este archivo PDF
pdfjs-password-invalid = Contraseña inválida. Intente nuevamente.
pdfjs-password-ok-button = Aceptar
pdfjs-password-cancel-button = Cancelar
pdfjs-web-fonts-disabled = Tipografía web deshabilitada: no se pueden usar tipos incrustados en PDF.
## Editing
pdfjs-editor-free-text-button =
.title = Texto
pdfjs-editor-free-text-button-label = Texto
pdfjs-editor-ink-button =
.title = Dibujar
pdfjs-editor-ink-button-label = Dibujar
pdfjs-editor-stamp-button =
.title = Agregar o editar imágenes
pdfjs-editor-stamp-button-label = Agregar o editar imágenes
pdfjs-editor-highlight-button =
.title = Resaltar
pdfjs-editor-highlight-button-label = Resaltar
pdfjs-highlight-floating-button1 =
.title = Resaltar
.aria-label = Resaltar
pdfjs-highlight-floating-button-label = Resaltar
pdfjs-editor-signature-button =
.title = Agregar firma
pdfjs-editor-signature-button-label = Agregar firma
## Default editor aria labels
# “Drawing” is a noun, the string is used on the editor for drawings.
pdfjs-editor-ink-editor =
.aria-label = Editor de dibujos
pdfjs-editor-signature-editor =
.aria-label = Editor de firmas
pdfjs-editor-stamp-editor =
.aria-label = Editor de imágenes
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Eliminar dibujo
pdfjs-editor-remove-freetext-button =
.title = Eliminar texto
pdfjs-editor-remove-stamp-button =
.title = Eliminar imagen
pdfjs-editor-remove-highlight-button =
.title = Eliminar resaltado
pdfjs-editor-remove-signature-button =
.title = Eliminar firma
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Color
pdfjs-editor-free-text-size-input = Tamaño
pdfjs-editor-ink-color-input = Color
pdfjs-editor-ink-thickness-input = Espesor
pdfjs-editor-ink-opacity-input = Opacidad
pdfjs-editor-stamp-add-image-button =
.title = Agregar una imagen
pdfjs-editor-stamp-add-image-button-label = Agregar una imagen
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Grosor
pdfjs-editor-free-highlight-thickness-title =
.title = Cambiar el grosor al resaltar elementos que no sean texto
pdfjs-editor-add-signature-container =
.aria-label = Controles de firma y firmas guardadas
pdfjs-editor-signature-add-signature-button =
.title = Agregar nueva firma
pdfjs-editor-signature-add-signature-button-label = Agregar nueva firma
# Used on the button to use an already saved signature.
# Variables:
# $description (String) - a string describing/labeling the signature.
pdfjs-editor-add-saved-signature-button =
.title = Firma guardada: { $description }
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Editor de texto
.default-content = Comenzar a tipear…
pdfjs-free-text =
.aria-label = Editor de texto
pdfjs-free-text-default-content = Empezar a tipear…
pdfjs-ink =
.aria-label = Editor de dibujos
pdfjs-ink-canvas =
.aria-label = Imagen creada por el usuario
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Texto alternativo
pdfjs-editor-alt-text-edit-button =
.aria-label = Editar texto alternativo
pdfjs-editor-alt-text-edit-button-label = Editar el texto alternativo
pdfjs-editor-alt-text-dialog-label = Eligir una opción
pdfjs-editor-alt-text-dialog-description = El texto alternativo (texto alternativo) ayuda cuando las personas no pueden ver la imagen o cuando no se carga.
pdfjs-editor-alt-text-add-description-label = Agregar una descripción
pdfjs-editor-alt-text-add-description-description = Intente escribir 1 o 2 oraciones que describan el tema, el entorno o las acciones.
pdfjs-editor-alt-text-mark-decorative-label = Marcar como decorativo
pdfjs-editor-alt-text-mark-decorative-description = Esto se usa para imágenes ornamentales, como bordes o marcas de agua.
pdfjs-editor-alt-text-cancel-button = Cancelar
pdfjs-editor-alt-text-save-button = Guardar
pdfjs-editor-alt-text-decorative-tooltip = Marcado como decorativo
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Por ejemplo: “Un joven se sienta a la mesa a comer”
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Texto alternativo
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Esquina superior izquierda — cambiar el tamaño
pdfjs-editor-resizer-label-top-middle = Arriba en el medio — cambiar el tamaño
pdfjs-editor-resizer-label-top-right = Esquina superior derecha — cambiar el tamaño
pdfjs-editor-resizer-label-middle-right = Al centro a la derecha — cambiar el tamaño
pdfjs-editor-resizer-label-bottom-right = Esquina inferior derecha — cambiar el tamaño
pdfjs-editor-resizer-label-bottom-middle = Abajo en el medio — cambiar el tamaño
pdfjs-editor-resizer-label-bottom-left = Esquina inferior izquierda — cambiar el tamaño
pdfjs-editor-resizer-label-middle-left = Al centro a la izquierda — cambiar el tamaño
pdfjs-editor-resizer-top-left =
.aria-label = Esquina superior izquierda — cambiar el tamaño
pdfjs-editor-resizer-top-middle =
.aria-label = Arriba en el medio — cambiar el tamaño
pdfjs-editor-resizer-top-right =
.aria-label = Esquina superior derecha — cambiar el tamaño
pdfjs-editor-resizer-middle-right =
.aria-label = Al centro a la derecha — cambiar el tamaño
pdfjs-editor-resizer-bottom-right =
.aria-label = Esquina inferior derecha — cambiar el tamaño
pdfjs-editor-resizer-bottom-middle =
.aria-label = Abajo en el medio — cambiar el tamaño
pdfjs-editor-resizer-bottom-left =
.aria-label = Esquina inferior izquierda — cambiar el tamaño
pdfjs-editor-resizer-middle-left =
.aria-label = Al centro a la izquierda — cambiar el tamaño
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Color de resaltado
pdfjs-editor-colorpicker-button =
.title = Cambiar el color
pdfjs-editor-colorpicker-dropdown =
.aria-label = Opciones de color
pdfjs-editor-colorpicker-yellow =
.title = Amarillo
pdfjs-editor-colorpicker-green =
.title = Verde
pdfjs-editor-colorpicker-blue =
.title = Azul
pdfjs-editor-colorpicker-pink =
.title = Rosado
pdfjs-editor-colorpicker-red =
.title = Rojo
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Mostrar todo
pdfjs-editor-highlight-show-all-button =
.title = Mostrar todo
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Editar texto alternativo (descripción de la imagen)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Agregar texto alternativo (descripción de la imagen)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Escribir la descripción aquí…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Descripción corta para las personas que no pueden ver la imagen o cuando la imagen no se carga.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Este texto alternativo fue creado automáticamente y puede ser incorrecto.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Conocer más
pdfjs-editor-new-alt-text-create-automatically-button-label = Crear texto alternativo automáticamente
pdfjs-editor-new-alt-text-not-now-button = No ahora
pdfjs-editor-new-alt-text-error-title = No se pudo crear el texto alternativo automáticamente
pdfjs-editor-new-alt-text-error-description = Escriba su propio texto alternativo o pruebe nuevamente más tarde.
pdfjs-editor-new-alt-text-error-close-button = Cerrar
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Descargando modelo de IA de texto alternativo ({ $downloadedSize } de { $totalSize } MB)
.aria-valuetext = Descargando modelo de IA de texto alternativo ({ $downloadedSize } de { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Texto alternativo agregado
pdfjs-editor-new-alt-text-added-button-label = Texto alternativo agregado
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Falta el texto alternativo
pdfjs-editor-new-alt-text-missing-button-label = Falta el texto alternativo
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Calificar el texto alternativo
pdfjs-editor-new-alt-text-to-review-button-label = Revisar el texto alternativo
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Creado automáticamente: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Configuración de texto alternativo de la imagen
pdfjs-image-alt-text-settings-button-label = Configuración de texto alternativo de la imagen
pdfjs-editor-alt-text-settings-dialog-label = Configuración de texto alternativo de la imagen
pdfjs-editor-alt-text-settings-automatic-title = Texto alternativo automático
pdfjs-editor-alt-text-settings-create-model-button-label = Crear texto alternativo automáticamente
pdfjs-editor-alt-text-settings-create-model-description = Sugiere descripciones para ayudar a las personas que no pueden ver la imagen o cuando la imagen no se carga.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Modelo de IA de texto alternativo ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Se ejecuta localmente en el dispositivo para que los datos se mantengan privados. Requerido para texto alternativo automático.
pdfjs-editor-alt-text-settings-delete-model-button = Borrar
pdfjs-editor-alt-text-settings-download-model-button = Descargar
pdfjs-editor-alt-text-settings-downloading-model-button = Descargando…
pdfjs-editor-alt-text-settings-editor-title = Editor de texto alternativo
pdfjs-editor-alt-text-settings-show-dialog-button-label = Mostrar el editor de texto alternativo inmediatamente al agregar una imagen
pdfjs-editor-alt-text-settings-show-dialog-description = Te ayuda a asegurarse de que todas las imágenes tengan texto alternativo.
pdfjs-editor-alt-text-settings-close-button = Cerrar
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Resaltado eliminado
pdfjs-editor-undo-bar-message-freetext = Texto eliminado
pdfjs-editor-undo-bar-message-ink = Dibujo eliminado
pdfjs-editor-undo-bar-message-stamp = Imagen eliminado
pdfjs-editor-undo-bar-message-signature = Firma eliminada
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } anotación eliminada
*[other] { $count } anotaciones eliminadas
}
pdfjs-editor-undo-bar-undo-button =
.title = Deshacer
pdfjs-editor-undo-bar-undo-button-label = Deshacer
pdfjs-editor-undo-bar-close-button =
.title = Cerrar
pdfjs-editor-undo-bar-close-button-label = Cerrar
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = Este modal permite al usuario crear una firma para agregar a un documento PDF. El usuario puede editar el nombre (que también sirve como texto alternativo) y opcionalmente guardar la firma para un uso repetido.
pdfjs-editor-add-signature-dialog-title = Agregar una firma
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Tipear
.title = Tipear
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Dibujar
.title = Dibujar
pdfjs-editor-add-signature-image-button = Imagen
.title = Imagen
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Escribir la firma
.placeholder = Escribir la firma
pdfjs-editor-add-signature-draw-placeholder = Dibujar la firma
pdfjs-editor-add-signature-draw-thickness-range-label = Grosor
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Grosor del dibujo: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Arrastrar un archivo acá para subirlo
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] O seleccionar archivos de imágenes
*[other] O seleccionar archivos de imágenes
}
## Controls
pdfjs-editor-add-signature-description-label = Descripción (texto alternativo)
pdfjs-editor-add-signature-description-input =
.title = Descripción (texto alternativo)
pdfjs-editor-add-signature-description-default-when-drawing = Firma
pdfjs-editor-add-signature-clear-button-label = Borrar firma
pdfjs-editor-add-signature-clear-button =
.title = Borrar firma
pdfjs-editor-add-signature-save-checkbox = Guardar firma
pdfjs-editor-add-signature-save-warning-message = Se alcanzó el límite de 5 firmas guardadas. Elimine una para guardar más.
pdfjs-editor-add-signature-image-upload-error-title = No se pudo subir la imagen
pdfjs-editor-add-signature-image-upload-error-description = Verifique la conexión de red o pruebe con otra imagen.
pdfjs-editor-add-signature-error-close-button = Cerrar
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Cancelar
pdfjs-editor-add-signature-add-button = Agregar
pdfjs-editor-edit-signature-update-button = Actualizar
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Eliminar firma
pdfjs-editor-delete-signature-button-label = Eliminar firma
pdfjs-editor-delete-signature-button1 =
.title = Eliminar firma guardada
pdfjs-editor-delete-signature-button-label1 = Eliminar firma guardada
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Editar descripción
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Editar descripción
================================================
FILE: cookbook/static/pdfjs/web/locale/es-CL/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Página anterior
pdfjs-previous-button-label = Anterior
pdfjs-next-button =
.title = Página siguiente
pdfjs-next-button-label = Siguiente
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Página
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = de { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } de { $pagesCount })
pdfjs-zoom-out-button =
.title = Alejar
pdfjs-zoom-out-button-label = Alejar
pdfjs-zoom-in-button =
.title = Acercar
pdfjs-zoom-in-button-label = Acercar
pdfjs-zoom-select =
.title = Ampliación
pdfjs-presentation-mode-button =
.title = Cambiar al modo de presentación
pdfjs-presentation-mode-button-label = Modo de presentación
pdfjs-open-file-button =
.title = Abrir archivo
pdfjs-open-file-button-label = Abrir
pdfjs-print-button =
.title = Imprimir
pdfjs-print-button-label = Imprimir
pdfjs-save-button =
.title = Guardar
pdfjs-save-button-label = Guardar
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Descargar
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Descargar
pdfjs-bookmark-button =
.title = Página actual (Ver URL de la página actual)
pdfjs-bookmark-button-label = Página actual
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Herramientas
pdfjs-tools-button-label = Herramientas
pdfjs-first-page-button =
.title = Ir a la primera página
pdfjs-first-page-button-label = Ir a la primera página
pdfjs-last-page-button =
.title = Ir a la última página
pdfjs-last-page-button-label = Ir a la última página
pdfjs-page-rotate-cw-button =
.title = Girar a la derecha
pdfjs-page-rotate-cw-button-label = Girar a la derecha
pdfjs-page-rotate-ccw-button =
.title = Girar a la izquierda
pdfjs-page-rotate-ccw-button-label = Girar a la izquierda
pdfjs-cursor-text-select-tool-button =
.title = Activar la herramienta de selección de texto
pdfjs-cursor-text-select-tool-button-label = Herramienta de selección de texto
pdfjs-cursor-hand-tool-button =
.title = Activar la herramienta de mano
pdfjs-cursor-hand-tool-button-label = Herramienta de mano
pdfjs-scroll-page-button =
.title = Usar desplazamiento de página
pdfjs-scroll-page-button-label = Desplazamiento de página
pdfjs-scroll-vertical-button =
.title = Usar desplazamiento vertical
pdfjs-scroll-vertical-button-label = Desplazamiento vertical
pdfjs-scroll-horizontal-button =
.title = Usar desplazamiento horizontal
pdfjs-scroll-horizontal-button-label = Desplazamiento horizontal
pdfjs-scroll-wrapped-button =
.title = Usar desplazamiento en bloque
pdfjs-scroll-wrapped-button-label = Desplazamiento en bloque
pdfjs-spread-none-button =
.title = No juntar páginas a modo de libro
pdfjs-spread-none-button-label = Vista de una página
pdfjs-spread-odd-button =
.title = Junta las páginas partiendo con una de número impar
pdfjs-spread-odd-button-label = Vista de libro impar
pdfjs-spread-even-button =
.title = Junta las páginas partiendo con una de número par
pdfjs-spread-even-button-label = Vista de libro par
## Document properties dialog
pdfjs-document-properties-button =
.title = Propiedades del documento…
pdfjs-document-properties-button-label = Propiedades del documento…
pdfjs-document-properties-file-name = Nombre de archivo:
pdfjs-document-properties-file-size = Tamaño del archivo:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = Título:
pdfjs-document-properties-author = Autor:
pdfjs-document-properties-subject = Asunto:
pdfjs-document-properties-keywords = Palabras clave:
pdfjs-document-properties-creation-date = Fecha de creación:
pdfjs-document-properties-modification-date = Fecha de modificación:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Creador:
pdfjs-document-properties-producer = Productor del PDF:
pdfjs-document-properties-version = Versión de PDF:
pdfjs-document-properties-page-count = Cantidad de páginas:
pdfjs-document-properties-page-size = Tamaño de la página:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = vertical
pdfjs-document-properties-page-size-orientation-landscape = horizontal
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Carta
pdfjs-document-properties-page-size-name-legal = Oficio
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Vista rápida en Web:
pdfjs-document-properties-linearized-yes = Sí
pdfjs-document-properties-linearized-no = No
pdfjs-document-properties-close-button = Cerrar
## Print
pdfjs-print-progress-message = Preparando documento para impresión…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Cancelar
pdfjs-printing-not-supported = Advertencia: Imprimir no está soportado completamente por este navegador.
pdfjs-printing-not-ready = Advertencia: El PDF no está completamente cargado para ser impreso.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Barra lateral
pdfjs-toggle-sidebar-notification-button =
.title = Cambiar barra lateral (índice de contenidos del documento/adjuntos/capas)
pdfjs-toggle-sidebar-button-label = Mostrar u ocultar la barra lateral
pdfjs-document-outline-button =
.title = Mostrar esquema del documento (doble clic para expandir/contraer todos los elementos)
pdfjs-document-outline-button-label = Esquema del documento
pdfjs-attachments-button =
.title = Mostrar adjuntos
pdfjs-attachments-button-label = Adjuntos
pdfjs-layers-button =
.title = Mostrar capas (doble clic para restablecer todas las capas al estado predeterminado)
pdfjs-layers-button-label = Capas
pdfjs-thumbs-button =
.title = Mostrar miniaturas
pdfjs-thumbs-button-label = Miniaturas
pdfjs-current-outline-item-button =
.title = Buscar elemento de esquema actual
pdfjs-current-outline-item-button-label = Elemento de esquema actual
pdfjs-findbar-button =
.title = Buscar en el documento
pdfjs-findbar-button-label = Buscar
pdfjs-additional-layers = Capas adicionales
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Página { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Miniatura de la página { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Encontrar
.placeholder = Encontrar en el documento…
pdfjs-find-previous-button =
.title = Buscar la aparición anterior de la frase
pdfjs-find-previous-button-label = Previo
pdfjs-find-next-button =
.title = Buscar la siguiente aparición de la frase
pdfjs-find-next-button-label = Siguiente
pdfjs-find-highlight-checkbox = Destacar todos
pdfjs-find-match-case-checkbox-label = Coincidir mayús./minús.
pdfjs-find-match-diacritics-checkbox-label = Coincidir diacríticos
pdfjs-find-entire-word-checkbox-label = Palabras completas
pdfjs-find-reached-top = Se alcanzó el inicio del documento, continuando desde el final
pdfjs-find-reached-bottom = Se alcanzó el final del documento, continuando desde el inicio
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] Coincidencia { $current } de { $total }
*[other] Coincidencia { $current } de { $total }
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Más de { $limit } coincidencia
*[other] Más de { $limit } coincidencias
}
pdfjs-find-not-found = Frase no encontrada
## Predefined zoom values
pdfjs-page-scale-width = Ancho de página
pdfjs-page-scale-fit = Ajuste de página
pdfjs-page-scale-auto = Aumento automático
pdfjs-page-scale-actual = Tamaño actual
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Página { $page }
## Loading indicator messages
pdfjs-loading-error = Ocurrió un error al cargar el PDF.
pdfjs-invalid-file-error = Archivo PDF inválido o corrupto.
pdfjs-missing-file-error = Falta el archivo PDF.
pdfjs-unexpected-response-error = Respuesta del servidor inesperada.
pdfjs-rendering-error = Ocurrió un error al renderizar la página.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } Anotación]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Ingresa la contraseña para abrir este archivo PDF.
pdfjs-password-invalid = Contraseña inválida. Por favor, vuelve a intentarlo.
pdfjs-password-ok-button = Aceptar
pdfjs-password-cancel-button = Cancelar
pdfjs-web-fonts-disabled = Las tipografías web están desactivadas: imposible usar las fuentes PDF embebidas.
## Editing
pdfjs-editor-free-text-button =
.title = Texto
pdfjs-editor-free-text-button-label = Texto
pdfjs-editor-ink-button =
.title = Dibujar
pdfjs-editor-ink-button-label = Dibujar
pdfjs-editor-stamp-button =
.title = Añadir o editar imágenes
pdfjs-editor-stamp-button-label = Añadir o editar imágenes
pdfjs-editor-highlight-button =
.title = Destacar
pdfjs-editor-highlight-button-label = Destacar
pdfjs-highlight-floating-button1 =
.title = Destacar
.aria-label = Destacar
pdfjs-highlight-floating-button-label = Destacar
pdfjs-editor-signature-button =
.title = Añadir firma
pdfjs-editor-signature-button-label = Añadir firma
## Default editor aria labels
# “Highlight” is a noun, the string is used on the editor for highlights.
pdfjs-editor-highlight-editor =
.aria-label = Editor de destacados
# “Drawing” is a noun, the string is used on the editor for drawings.
pdfjs-editor-ink-editor =
.aria-label = Editor de dibujos
pdfjs-editor-signature-editor =
.aria-label = Editor de firmas
pdfjs-editor-stamp-editor =
.aria-label = Editor de imágenes
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Eliminar dibujo
pdfjs-editor-remove-freetext-button =
.title = Eliminar texto
pdfjs-editor-remove-stamp-button =
.title = Eliminar imagen
pdfjs-editor-remove-highlight-button =
.title = Quitar resaltado
pdfjs-editor-remove-signature-button =
.title = Eliminar firma
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Color
pdfjs-editor-free-text-size-input = Tamaño
pdfjs-editor-ink-color-input = Color
pdfjs-editor-ink-thickness-input = Grosor
pdfjs-editor-ink-opacity-input = Opacidad
pdfjs-editor-stamp-add-image-button =
.title = Añadir imagen
pdfjs-editor-stamp-add-image-button-label = Añadir imagen
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Grosor
pdfjs-editor-free-highlight-thickness-title =
.title = Cambia el grosor al resaltar elementos que no sean texto
pdfjs-editor-add-signature-container =
.aria-label = Controles de firma y firmas guardadas
pdfjs-editor-signature-add-signature-button =
.title = Añadir nueva firma
pdfjs-editor-signature-add-signature-button-label = Añadir nueva firma
# Used on the button to use an already saved signature.
# Variables:
# $description (String) - a string describing/labeling the signature.
pdfjs-editor-add-saved-signature-button =
.title = Firma guardada: { $description }
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Editor de texto
.default-content = Empieza a escribir…
pdfjs-free-text =
.aria-label = Editor de texto
pdfjs-free-text-default-content = Empieza a escribir…
pdfjs-ink =
.aria-label = Editor de dibujos
pdfjs-ink-canvas =
.aria-label = Imagen creada por el usuario
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Texto alternativo
pdfjs-editor-alt-text-edit-button =
.aria-label = Editar texto alternativo
pdfjs-editor-alt-text-edit-button-label = Editar texto alternativo
pdfjs-editor-alt-text-dialog-label = Elige una opción
pdfjs-editor-alt-text-dialog-description = El texto alternativo (alt text) ayuda cuando las personas no pueden ver la imagen o cuando no se carga.
pdfjs-editor-alt-text-add-description-label = Añade una descripción
pdfjs-editor-alt-text-add-description-description = Intenta escribir 1 o 2 oraciones que describan el tema, el ambiente o las acciones.
pdfjs-editor-alt-text-mark-decorative-label = Marcar como decorativa
pdfjs-editor-alt-text-mark-decorative-description = Se utiliza para imágenes ornamentales, como bordes o marcas de agua.
pdfjs-editor-alt-text-cancel-button = Cancelar
pdfjs-editor-alt-text-save-button = Guardar
pdfjs-editor-alt-text-decorative-tooltip = Marcada como decorativa
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Por ejemplo: “Un joven se sienta a la mesa a comer”
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Texto alternativo
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Esquina superior izquierda — cambiar el tamaño
pdfjs-editor-resizer-label-top-middle = Borde superior en el medio — cambiar el tamaño
pdfjs-editor-resizer-label-top-right = Esquina superior derecha — cambiar el tamaño
pdfjs-editor-resizer-label-middle-right = Borde derecho en el medio — cambiar el tamaño
pdfjs-editor-resizer-label-bottom-right = Esquina inferior derecha — cambiar el tamaño
pdfjs-editor-resizer-label-bottom-middle = Borde inferior en el medio — cambiar el tamaño
pdfjs-editor-resizer-label-bottom-left = Esquina inferior izquierda — cambiar el tamaño
pdfjs-editor-resizer-label-middle-left = Borde izquierdo en el medio — cambiar el tamaño
pdfjs-editor-resizer-top-left =
.aria-label = Esquina superior izquierda — cambiar el tamaño
pdfjs-editor-resizer-top-middle =
.aria-label = Borde superior en el medio — cambiar el tamaño
pdfjs-editor-resizer-top-right =
.aria-label = Esquina superior derecha — cambiar el tamaño
pdfjs-editor-resizer-middle-right =
.aria-label = Borde derecho en el medio — cambiar el tamaño
pdfjs-editor-resizer-bottom-right =
.aria-label = Esquina inferior derecha — cambiar el tamaño
pdfjs-editor-resizer-bottom-middle =
.aria-label = Borde inferior en el medio — cambiar el tamaño
pdfjs-editor-resizer-bottom-left =
.aria-label = Esquina inferior izquierda — cambiar el tamaño
pdfjs-editor-resizer-middle-left =
.aria-label = Borde izquierdo en el medio — cambiar el tamaño
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Color de resaltado
pdfjs-editor-colorpicker-button =
.title = Cambiar color
pdfjs-editor-colorpicker-dropdown =
.aria-label = Opciones de color
pdfjs-editor-colorpicker-yellow =
.title = Amarillo
pdfjs-editor-colorpicker-green =
.title = Verde
pdfjs-editor-colorpicker-blue =
.title = Azul
pdfjs-editor-colorpicker-pink =
.title = Rosa
pdfjs-editor-colorpicker-red =
.title = Rojo
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Mostrar todo
pdfjs-editor-highlight-show-all-button =
.title = Mostrar todo
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Editar texto alternativo (descripción de la imagen)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Añadir texto alternativo (descripción de la imagen)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Escribe tu descripción aquí…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Breve descripción para las personas que no pueden ver la imagen o cuando la imagen no se carga.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Este texto alternativo fue creado automáticamente y puede ser incorrecto.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Aprender más
pdfjs-editor-new-alt-text-create-automatically-button-label = Crear texto alternativo automáticamente
pdfjs-editor-new-alt-text-not-now-button = Ahora no
pdfjs-editor-new-alt-text-error-title = No se pudo crear el texto alternativo automáticamente
pdfjs-editor-new-alt-text-error-description = Escribe tu propio texto alternativo o vuelve a intentarlo más tarde.
pdfjs-editor-new-alt-text-error-close-button = Cerrar
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Descargando el modelo de IA de texto alternativo ({ $downloadedSize } de { $totalSize } MB)
.aria-valuetext = Descargando el modelo de IA de texto alternativo ({ $downloadedSize } de { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Se añadió el texto alternativo
pdfjs-editor-new-alt-text-added-button-label = Se añadió el texto alternativo
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Falta el texto alternativo
pdfjs-editor-new-alt-text-missing-button-label = Falta el texto alternativo
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Revisar el texto alternativo
pdfjs-editor-new-alt-text-to-review-button-label = Revisar el texto alternativo
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Creado automáticamente: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Ajustes del texto alternativo de la imagen
pdfjs-image-alt-text-settings-button-label = Ajustes del texto alternativo de la imagen
pdfjs-editor-alt-text-settings-dialog-label = Ajustes del texto alternativo de la imagen
pdfjs-editor-alt-text-settings-automatic-title = Texto alternativo automático
pdfjs-editor-alt-text-settings-create-model-button-label = Crear texto alternativo automáticamente
pdfjs-editor-alt-text-settings-create-model-description = Sugiere descripciones para ayudar a las personas que no pueden ver la imagen o cuando la imagen no se carga.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Modelo de IA de texto alternativo ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Se ejecuta localmente en tu dispositivo para que tus datos permanezcan privados. Necesario para el texto alternativo automático.
pdfjs-editor-alt-text-settings-delete-model-button = Eliminar
pdfjs-editor-alt-text-settings-download-model-button = Descargar
pdfjs-editor-alt-text-settings-downloading-model-button = Bajando…
pdfjs-editor-alt-text-settings-editor-title = Editor de texto alternativo
pdfjs-editor-alt-text-settings-show-dialog-button-label = Mostrar el editor de texto alternativo inmediatamente al añadir una imagen
pdfjs-editor-alt-text-settings-show-dialog-description = Te ayuda a asegurarte de que todas tus imágenes tengan texto alternativo.
pdfjs-editor-alt-text-settings-close-button = Cerrar
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Resaltado eliminado
pdfjs-editor-undo-bar-message-freetext = Texto eliminado
pdfjs-editor-undo-bar-message-ink = Dibujo eliminado
pdfjs-editor-undo-bar-message-stamp = Imagen eliminada
pdfjs-editor-undo-bar-message-signature = Firma eliminada
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } anotación eliminada
*[other] { $count } anotaciones eliminadas
}
pdfjs-editor-undo-bar-undo-button =
.title = Deshacer
pdfjs-editor-undo-bar-undo-button-label = Deshacer
pdfjs-editor-undo-bar-close-button =
.title = Cerrar
pdfjs-editor-undo-bar-close-button-label = Cerrar
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = Este modal permite al usuario crear una firma para agregarla a un documento PDF. El usuario puede editar el nombre (que también sirve como texto alternativo) y, opcionalmente, guardar la firma para usarla nuevamente.
pdfjs-editor-add-signature-dialog-title = Añadir una firma
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Escribir
.title = Escribir
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Dibujar
.title = Dibujar
pdfjs-editor-add-signature-image-button = Imagen
.title = Imagen
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Escribe tu firma
.placeholder = Escribe tu firma
pdfjs-editor-add-signature-draw-placeholder = Dibuja tu firma
pdfjs-editor-add-signature-draw-thickness-range-label = Grosor
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Grosor del dibujo: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Arrastre un archivo aquí para cargarlo
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] O elige archivos de imagen
*[other] O busca archivos de imagen
}
## Controls
pdfjs-editor-add-signature-description-label = Descripción (texto alternativo)
pdfjs-editor-add-signature-description-input =
.title = Descripción (texto alternativo)
pdfjs-editor-add-signature-description-default-when-drawing = Firma
pdfjs-editor-add-signature-clear-button-label = Limpiar firma
pdfjs-editor-add-signature-clear-button =
.title = Limpiar firma
pdfjs-editor-add-signature-save-checkbox = Guardar firma
pdfjs-editor-add-signature-save-warning-message = Has alcanzado el límite de 5 firmas guardadas. Elimina una para guardar más.
pdfjs-editor-add-signature-image-upload-error-title = No se pudo subir la imagen
pdfjs-editor-add-signature-image-upload-error-description = Verifica tu conexión de red o prueba con otra imagen.
pdfjs-editor-add-signature-error-close-button = Cerrar
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Cancelar
pdfjs-editor-add-signature-add-button = Añadir
pdfjs-editor-edit-signature-update-button = Actualizar
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Eliminar firma
pdfjs-editor-delete-signature-button-label = Eliminar firma
pdfjs-editor-delete-signature-button1 =
.title = Eliminar firma guardada
pdfjs-editor-delete-signature-button-label1 = Eliminar firma guardada
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Editar descripción
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Editar descripción
================================================
FILE: cookbook/static/pdfjs/web/locale/es-ES/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Página anterior
pdfjs-previous-button-label = Anterior
pdfjs-next-button =
.title = Página siguiente
pdfjs-next-button-label = Siguiente
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Página
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = de { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } de { $pagesCount })
pdfjs-zoom-out-button =
.title = Reducir
pdfjs-zoom-out-button-label = Reducir
pdfjs-zoom-in-button =
.title = Aumentar
pdfjs-zoom-in-button-label = Aumentar
pdfjs-zoom-select =
.title = Tamaño
pdfjs-presentation-mode-button =
.title = Cambiar al modo presentación
pdfjs-presentation-mode-button-label = Modo presentación
pdfjs-open-file-button =
.title = Abrir archivo
pdfjs-open-file-button-label = Abrir
pdfjs-print-button =
.title = Imprimir
pdfjs-print-button-label = Imprimir
pdfjs-save-button =
.title = Guardar
pdfjs-save-button-label = Guardar
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Descargar
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Descargar
pdfjs-bookmark-button =
.title = Página actual (Ver URL de la página actual)
pdfjs-bookmark-button-label = Página actual
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Herramientas
pdfjs-tools-button-label = Herramientas
pdfjs-first-page-button =
.title = Ir a la primera página
pdfjs-first-page-button-label = Ir a la primera página
pdfjs-last-page-button =
.title = Ir a la última página
pdfjs-last-page-button-label = Ir a la última página
pdfjs-page-rotate-cw-button =
.title = Rotar en sentido horario
pdfjs-page-rotate-cw-button-label = Rotar en sentido horario
pdfjs-page-rotate-ccw-button =
.title = Rotar en sentido antihorario
pdfjs-page-rotate-ccw-button-label = Rotar en sentido antihorario
pdfjs-cursor-text-select-tool-button =
.title = Activar herramienta de selección de texto
pdfjs-cursor-text-select-tool-button-label = Herramienta de selección de texto
pdfjs-cursor-hand-tool-button =
.title = Activar herramienta de mano
pdfjs-cursor-hand-tool-button-label = Herramienta de mano
pdfjs-scroll-page-button =
.title = Usar desplazamiento de página
pdfjs-scroll-page-button-label = Desplazamiento de página
pdfjs-scroll-vertical-button =
.title = Usar desplazamiento vertical
pdfjs-scroll-vertical-button-label = Desplazamiento vertical
pdfjs-scroll-horizontal-button =
.title = Usar desplazamiento horizontal
pdfjs-scroll-horizontal-button-label = Desplazamiento horizontal
pdfjs-scroll-wrapped-button =
.title = Usar desplazamiento en bloque
pdfjs-scroll-wrapped-button-label = Desplazamiento en bloque
pdfjs-spread-none-button =
.title = No juntar páginas en vista de libro
pdfjs-spread-none-button-label = Vista de libro
pdfjs-spread-odd-button =
.title = Juntar las páginas partiendo de una con número impar
pdfjs-spread-odd-button-label = Vista de libro impar
pdfjs-spread-even-button =
.title = Juntar las páginas partiendo de una con número par
pdfjs-spread-even-button-label = Vista de libro par
## Document properties dialog
pdfjs-document-properties-button =
.title = Propiedades del documento…
pdfjs-document-properties-button-label = Propiedades del documento…
pdfjs-document-properties-file-name = Nombre de archivo:
pdfjs-document-properties-file-size = Tamaño de archivo:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = Título:
pdfjs-document-properties-author = Autor:
pdfjs-document-properties-subject = Asunto:
pdfjs-document-properties-keywords = Palabras clave:
pdfjs-document-properties-creation-date = Fecha de creación:
pdfjs-document-properties-modification-date = Fecha de modificación:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Creador:
pdfjs-document-properties-producer = Productor PDF:
pdfjs-document-properties-version = Versión PDF:
pdfjs-document-properties-page-count = Número de páginas:
pdfjs-document-properties-page-size = Tamaño de la página:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = vertical
pdfjs-document-properties-page-size-orientation-landscape = horizontal
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Carta
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Vista rápida de la web:
pdfjs-document-properties-linearized-yes = Sí
pdfjs-document-properties-linearized-no = No
pdfjs-document-properties-close-button = Cerrar
## Print
pdfjs-print-progress-message = Preparando documento para impresión…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Cancelar
pdfjs-printing-not-supported = Advertencia: Imprimir no está totalmente soportado por este navegador.
pdfjs-printing-not-ready = Advertencia: Este PDF no se ha cargado completamente para poder imprimirse.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Cambiar barra lateral
pdfjs-toggle-sidebar-notification-button =
.title = Alternar barra lateral (el documento contiene esquemas/adjuntos/capas)
pdfjs-toggle-sidebar-button-label = Cambiar barra lateral
pdfjs-document-outline-button =
.title = Mostrar resumen del documento (doble clic para expandir/contraer todos los elementos)
pdfjs-document-outline-button-label = Resumen de documento
pdfjs-attachments-button =
.title = Mostrar adjuntos
pdfjs-attachments-button-label = Adjuntos
pdfjs-layers-button =
.title = Mostrar capas (doble clic para restablecer todas las capas al estado predeterminado)
pdfjs-layers-button-label = Capas
pdfjs-thumbs-button =
.title = Mostrar miniaturas
pdfjs-thumbs-button-label = Miniaturas
pdfjs-current-outline-item-button =
.title = Encontrar elemento de esquema actual
pdfjs-current-outline-item-button-label = Elemento de esquema actual
pdfjs-findbar-button =
.title = Buscar en el documento
pdfjs-findbar-button-label = Buscar
pdfjs-additional-layers = Capas adicionales
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Página { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Miniatura de la página { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Buscar
.placeholder = Buscar en el documento…
pdfjs-find-previous-button =
.title = Encontrar la anterior aparición de la frase
pdfjs-find-previous-button-label = Anterior
pdfjs-find-next-button =
.title = Encontrar la siguiente aparición de esta frase
pdfjs-find-next-button-label = Siguiente
pdfjs-find-highlight-checkbox = Resaltar todos
pdfjs-find-match-case-checkbox-label = Coincidencia de mayús./minús.
pdfjs-find-match-diacritics-checkbox-label = Coincidir diacríticos
pdfjs-find-entire-word-checkbox-label = Palabras completas
pdfjs-find-reached-top = Se alcanzó el inicio del documento, se continúa desde el final
pdfjs-find-reached-bottom = Se alcanzó el final del documento, se continúa desde el inicio
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } de { $total } coincidencia
*[other] { $current } de { $total } coincidencias
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Más de { $limit } coincidencia
*[other] Más de { $limit } coincidencias
}
pdfjs-find-not-found = Frase no encontrada
## Predefined zoom values
pdfjs-page-scale-width = Anchura de la página
pdfjs-page-scale-fit = Ajuste de la página
pdfjs-page-scale-auto = Tamaño automático
pdfjs-page-scale-actual = Tamaño real
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Página { $page }
## Loading indicator messages
pdfjs-loading-error = Ocurrió un error al cargar el PDF.
pdfjs-invalid-file-error = Fichero PDF no válido o corrupto.
pdfjs-missing-file-error = No hay fichero PDF.
pdfjs-unexpected-response-error = Respuesta inesperada del servidor.
pdfjs-rendering-error = Ocurrió un error al renderizar la página.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [Anotación { $type }]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Introduzca la contraseña para abrir este archivo PDF.
pdfjs-password-invalid = Contraseña no válida. Vuelva a intentarlo.
pdfjs-password-ok-button = Aceptar
pdfjs-password-cancel-button = Cancelar
pdfjs-web-fonts-disabled = Las tipografías web están desactivadas: es imposible usar las tipografías PDF embebidas.
## Editing
pdfjs-editor-free-text-button =
.title = Texto
pdfjs-editor-free-text-button-label = Texto
pdfjs-editor-ink-button =
.title = Dibujar
pdfjs-editor-ink-button-label = Dibujar
pdfjs-editor-stamp-button =
.title = Añadir o editar imágenes
pdfjs-editor-stamp-button-label = Añadir o editar imágenes
pdfjs-editor-highlight-button =
.title = Resaltar
pdfjs-editor-highlight-button-label = Resaltar
pdfjs-highlight-floating-button1 =
.title = Resaltar
.aria-label = Resaltar
pdfjs-highlight-floating-button-label = Resaltar
pdfjs-editor-signature-button =
.title = Añadir firma
pdfjs-editor-signature-button-label = Añadir firma
## Default editor aria labels
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Eliminar dibujo
pdfjs-editor-remove-freetext-button =
.title = Eliminar texto
pdfjs-editor-remove-stamp-button =
.title = Eliminar imagen
pdfjs-editor-remove-highlight-button =
.title = Quitar resaltado
pdfjs-editor-remove-signature-button =
.title = Eliminar firma
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Color
pdfjs-editor-free-text-size-input = Tamaño
pdfjs-editor-ink-color-input = Color
pdfjs-editor-ink-thickness-input = Grosor
pdfjs-editor-ink-opacity-input = Opacidad
pdfjs-editor-stamp-add-image-button =
.title = Añadir imagen
pdfjs-editor-stamp-add-image-button-label = Añadir imagen
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Grosor
pdfjs-editor-free-highlight-thickness-title =
.title = Cambiar el grosor al resaltar elementos que no sean texto
pdfjs-editor-signature-add-signature-button =
.title = Añadir nueva firma
pdfjs-editor-signature-add-signature-button-label = Añadir nueva firma
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Editor de texto
.default-content = Empiece a escribir…
pdfjs-free-text =
.aria-label = Editor de texto
pdfjs-free-text-default-content = Empezar a escribir…
pdfjs-ink =
.aria-label = Editor de dibujos
pdfjs-ink-canvas =
.aria-label = Imagen creada por el usuario
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Texto alternativo
pdfjs-editor-alt-text-edit-button =
.aria-label = Editar el texto alternativo
pdfjs-editor-alt-text-edit-button-label = Editar el texto alternativo
pdfjs-editor-alt-text-dialog-label = Eligir una opción
pdfjs-editor-alt-text-dialog-description = El texto alternativo (texto alternativo) ayuda cuando las personas no pueden ver la imagen o cuando no se carga.
pdfjs-editor-alt-text-add-description-label = Añadir una descripción
pdfjs-editor-alt-text-add-description-description = Intente escribir 1 o 2 frases que describan el tema, el entorno o las acciones.
pdfjs-editor-alt-text-mark-decorative-label = Marcar como decorativa
pdfjs-editor-alt-text-mark-decorative-description = Se utiliza para imágenes ornamentales, como bordes o marcas de agua.
pdfjs-editor-alt-text-cancel-button = Cancelar
pdfjs-editor-alt-text-save-button = Guardar
pdfjs-editor-alt-text-decorative-tooltip = Marcada como decorativa
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Por ejemplo: “Un joven se sienta a la mesa a comer”
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Texto alternativo
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Esquina superior izquierda — redimensionar
pdfjs-editor-resizer-label-top-middle = Borde superior en el medio — redimensionar
pdfjs-editor-resizer-label-top-right = Esquina superior derecha — redimensionar
pdfjs-editor-resizer-label-middle-right = Borde derecho en el medio — redimensionar
pdfjs-editor-resizer-label-bottom-right = Esquina inferior derecha — redimensionar
pdfjs-editor-resizer-label-bottom-middle = Borde inferior en el medio — redimensionar
pdfjs-editor-resizer-label-bottom-left = Esquina inferior izquierda — redimensionar
pdfjs-editor-resizer-label-middle-left = Borde izquierdo en el medio — redimensionar
pdfjs-editor-resizer-top-left =
.aria-label = Esquina superior izquierda — redimensionar
pdfjs-editor-resizer-top-middle =
.aria-label = Borde superior en el medio — redimensionar
pdfjs-editor-resizer-top-right =
.aria-label = Esquina superior derecha — redimensionar
pdfjs-editor-resizer-middle-right =
.aria-label = Borde derecho en el medio — redimensionar
pdfjs-editor-resizer-bottom-right =
.aria-label = Esquina inferior derecha — redimensionar
pdfjs-editor-resizer-bottom-middle =
.aria-label = Borde inferior en el medio — redimensionar
pdfjs-editor-resizer-bottom-left =
.aria-label = Esquina inferior izquierda — redimensionar
pdfjs-editor-resizer-middle-left =
.aria-label = Borde izquierdo en el medio — redimensionar
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Color de resaltado
pdfjs-editor-colorpicker-button =
.title = Cambiar color
pdfjs-editor-colorpicker-dropdown =
.aria-label = Opciones de color
pdfjs-editor-colorpicker-yellow =
.title = Amarillo
pdfjs-editor-colorpicker-green =
.title = Verde
pdfjs-editor-colorpicker-blue =
.title = Azul
pdfjs-editor-colorpicker-pink =
.title = Rosa
pdfjs-editor-colorpicker-red =
.title = Rojo
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Mostrar todo
pdfjs-editor-highlight-show-all-button =
.title = Mostrar todo
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Editar texto alternativo (descripción de la imagen)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Añadir texto alternativo (descripción de la imagen)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Escribir la descripción aquí…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Breve descripción para las personas que no pueden ver la imagen o cuando la imagen no se carga.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Este texto alternativo fue creado automáticamente y puede ser inexacto.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Saber más
pdfjs-editor-new-alt-text-create-automatically-button-label = Crear texto alternativo automáticamente
pdfjs-editor-new-alt-text-not-now-button = Ahora no
pdfjs-editor-new-alt-text-error-title = No se ha podido crear el texto alternativo automáticamente
pdfjs-editor-new-alt-text-error-description = Escriba su propio texto alternativo o inténtelo de nuevo más tarde.
pdfjs-editor-new-alt-text-error-close-button = Cerrar
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Descargando el modelo de IA de texto alternativo ({ $downloadedSize } de { $totalSize } MB)
.aria-valuetext = Descargando el modelo de IA de texto alternativo ({ $downloadedSize } de { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Se añadió el texto alternativo
pdfjs-editor-new-alt-text-added-button-label = Se añadió el texto alternativo
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Falta el texto alternativo
pdfjs-editor-new-alt-text-missing-button-label = Falta el texto alternativo
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Revisar el texto alternativo
pdfjs-editor-new-alt-text-to-review-button-label = Revisar el texto alternativo
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Creado automáticamente: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Ajustes del texto alternativo de la imagen
pdfjs-image-alt-text-settings-button-label = Ajustes del texto alternativo de la imagen
pdfjs-editor-alt-text-settings-dialog-label = Ajustes del texto alternativo de la imagen
pdfjs-editor-alt-text-settings-automatic-title = Texto alternativo automático
pdfjs-editor-alt-text-settings-create-model-button-label = Crear texto alternativo automáticamente
pdfjs-editor-alt-text-settings-create-model-description = Sugiere descripciones para ayudar a las personas que no pueden ver la imagen o cuando la imagen no se carga.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Modelo de IA de texto alternativo ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Se ejecuta localmente en el dispositivo para que los datos se mantengan privados. Requerido para texto alternativo automático.
pdfjs-editor-alt-text-settings-delete-model-button = Eliminar
pdfjs-editor-alt-text-settings-download-model-button = Descargar
pdfjs-editor-alt-text-settings-downloading-model-button = Descargando…
pdfjs-editor-alt-text-settings-editor-title = Editor de texto alternativo
pdfjs-editor-alt-text-settings-show-dialog-button-label = Mostrar el editor de texto alternativo inmediatamente al añadir una imagen
pdfjs-editor-alt-text-settings-show-dialog-description = Le ayuda a asegurarse de que todas sus imágenes tengan texto alternativo.
pdfjs-editor-alt-text-settings-close-button = Cerrar
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Resaltado eliminado
pdfjs-editor-undo-bar-message-freetext = Texto eliminado
pdfjs-editor-undo-bar-message-ink = Dibujo eliminado
pdfjs-editor-undo-bar-message-stamp = Imagen eliminada
pdfjs-editor-undo-bar-message-signature = Firma eliminada
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } anotación eliminada
*[other] { $count } anotaciones eliminadas
}
pdfjs-editor-undo-bar-undo-button =
.title = Deshacer
pdfjs-editor-undo-bar-undo-button-label = Deshacer
pdfjs-editor-undo-bar-close-button =
.title = Cerrar
pdfjs-editor-undo-bar-close-button-label = Cerrar
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = Este modal permite al usuario crear una firma para añadirla a un documento PDF. El usuario puede editar el nombre (que también sirve como texto alternativo) y, opcionalmente, guardar la firma para usarla nuevamente.
pdfjs-editor-add-signature-dialog-title = Añadir una firma
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Escribir
.title = Escribir
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Dibujar
.title = Dibujar
pdfjs-editor-add-signature-image-button = Imagen
.title = Imagen
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Escriba su firma
.placeholder = Escriba su firma
pdfjs-editor-add-signature-draw-placeholder = Dibuje su firma
pdfjs-editor-add-signature-draw-thickness-range-label = Grosor
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Grosor del dibujo: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Arrastre un archivo aquí para cargarlo
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] O seleccione archivos de imágenes
*[other] O seleccione archivos de imágenes
}
## Controls
pdfjs-editor-add-signature-description-label = Descripción (texto alternativo)
pdfjs-editor-add-signature-description-input =
.title = Descripción (texto alternativo)
pdfjs-editor-add-signature-description-default-when-drawing = Firma
pdfjs-editor-add-signature-clear-button-label = Borrar firma
pdfjs-editor-add-signature-clear-button =
.title = Borrar firma
pdfjs-editor-add-signature-save-checkbox = Guardar firma
pdfjs-editor-add-signature-save-warning-message = Ha alcanzado el límite de 5 firmas guardadas. Elimine una para guardar más.
pdfjs-editor-add-signature-image-upload-error-title = No se ha podido subir la imagen
pdfjs-editor-add-signature-image-upload-error-description = Compruebe su conexión de red o pruebe con otra imagen.
pdfjs-editor-add-signature-error-close-button = Cerrar
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Cancelar
pdfjs-editor-add-signature-add-button = Añadir
pdfjs-editor-edit-signature-update-button = Actualizar
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Eliminar firma
pdfjs-editor-delete-signature-button-label = Eliminar firma
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Editar descripción
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Editar descripción
================================================
FILE: cookbook/static/pdfjs/web/locale/es-MX/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Página anterior
pdfjs-previous-button-label = Anterior
pdfjs-next-button =
.title = Página siguiente
pdfjs-next-button-label = Siguiente
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Página
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = de { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } de { $pagesCount })
pdfjs-zoom-out-button =
.title = Reducir
pdfjs-zoom-out-button-label = Reducir
pdfjs-zoom-in-button =
.title = Aumentar
pdfjs-zoom-in-button-label = Aumentar
pdfjs-zoom-select =
.title = Zoom
pdfjs-presentation-mode-button =
.title = Cambiar al modo presentación
pdfjs-presentation-mode-button-label = Modo presentación
pdfjs-open-file-button =
.title = Abrir archivo
pdfjs-open-file-button-label = Abrir
pdfjs-print-button =
.title = Imprimir
pdfjs-print-button-label = Imprimir
pdfjs-save-button =
.title = Guardar
pdfjs-save-button-label = Guardar
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Descargar
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Descargar
pdfjs-bookmark-button =
.title = Página actual (Ver URL de la página actual)
pdfjs-bookmark-button-label = Página actual
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Herramientas
pdfjs-tools-button-label = Herramientas
pdfjs-first-page-button =
.title = Ir a la primera página
pdfjs-first-page-button-label = Ir a la primera página
pdfjs-last-page-button =
.title = Ir a la última página
pdfjs-last-page-button-label = Ir a la última página
pdfjs-page-rotate-cw-button =
.title = Girar a la derecha
pdfjs-page-rotate-cw-button-label = Girar a la derecha
pdfjs-page-rotate-ccw-button =
.title = Girar a la izquierda
pdfjs-page-rotate-ccw-button-label = Girar a la izquierda
pdfjs-cursor-text-select-tool-button =
.title = Activar la herramienta de selección de texto
pdfjs-cursor-text-select-tool-button-label = Herramienta de selección de texto
pdfjs-cursor-hand-tool-button =
.title = Activar la herramienta de mano
pdfjs-cursor-hand-tool-button-label = Herramienta de mano
pdfjs-scroll-page-button =
.title = Usar desplazamiento de página
pdfjs-scroll-page-button-label = Desplazamiento de página
pdfjs-scroll-vertical-button =
.title = Usar desplazamiento vertical
pdfjs-scroll-vertical-button-label = Desplazamiento vertical
pdfjs-scroll-horizontal-button =
.title = Usar desplazamiento horizontal
pdfjs-scroll-horizontal-button-label = Desplazamiento horizontal
pdfjs-scroll-wrapped-button =
.title = Usar desplazamiento encapsulado
pdfjs-scroll-wrapped-button-label = Desplazamiento encapsulado
pdfjs-spread-none-button =
.title = No unir páginas separadas
pdfjs-spread-none-button-label = Vista de una página
pdfjs-spread-odd-button =
.title = Unir las páginas partiendo con una de número impar
pdfjs-spread-odd-button-label = Vista de libro impar
pdfjs-spread-even-button =
.title = Juntar las páginas partiendo con una de número par
pdfjs-spread-even-button-label = Vista de libro par
## Document properties dialog
pdfjs-document-properties-button =
.title = Propiedades del documento…
pdfjs-document-properties-button-label = Propiedades del documento…
pdfjs-document-properties-file-name = Nombre del archivo:
pdfjs-document-properties-file-size = Tamaño del archivo:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = Título:
pdfjs-document-properties-author = Autor:
pdfjs-document-properties-subject = Asunto:
pdfjs-document-properties-keywords = Palabras claves:
pdfjs-document-properties-creation-date = Fecha de creación:
pdfjs-document-properties-modification-date = Fecha de modificación:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Creador:
pdfjs-document-properties-producer = Productor PDF:
pdfjs-document-properties-version = Versión PDF:
pdfjs-document-properties-page-count = Número de páginas:
pdfjs-document-properties-page-size = Tamaño de la página:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = vertical
pdfjs-document-properties-page-size-orientation-landscape = horizontal
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Carta
pdfjs-document-properties-page-size-name-legal = Oficio
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Vista rápida de la web:
pdfjs-document-properties-linearized-yes = Sí
pdfjs-document-properties-linearized-no = No
pdfjs-document-properties-close-button = Cerrar
## Print
pdfjs-print-progress-message = Preparando documento para impresión…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Cancelar
pdfjs-printing-not-supported = Advertencia: La impresión no esta completamente soportada por este navegador.
pdfjs-printing-not-ready = Advertencia: El PDF no cargo completamente para impresión.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Cambiar barra lateral
pdfjs-toggle-sidebar-notification-button =
.title = Alternar barra lateral (el documento contiene esquemas/adjuntos/capas)
pdfjs-toggle-sidebar-button-label = Cambiar barra lateral
pdfjs-document-outline-button =
.title = Mostrar esquema del documento (doble clic para expandir/contraer todos los elementos)
pdfjs-document-outline-button-label = Esquema del documento
pdfjs-attachments-button =
.title = Mostrar adjuntos
pdfjs-attachments-button-label = Adjuntos
pdfjs-layers-button =
.title = Mostrar capas (doble clic para restablecer todas las capas al estado predeterminado)
pdfjs-layers-button-label = Capas
pdfjs-thumbs-button =
.title = Mostrar miniaturas
pdfjs-thumbs-button-label = Miniaturas
pdfjs-current-outline-item-button =
.title = Buscar elemento de esquema actual
pdfjs-current-outline-item-button-label = Elemento de esquema actual
pdfjs-findbar-button =
.title = Buscar en el documento
pdfjs-findbar-button-label = Buscar
pdfjs-additional-layers = Capas adicionales
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Página { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Miniatura de la página { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Buscar
.placeholder = Buscar en el documento…
pdfjs-find-previous-button =
.title = Ir a la anterior frase encontrada
pdfjs-find-previous-button-label = Anterior
pdfjs-find-next-button =
.title = Ir a la siguiente frase encontrada
pdfjs-find-next-button-label = Siguiente
pdfjs-find-highlight-checkbox = Resaltar todo
pdfjs-find-match-case-checkbox-label = Coincidir con mayúsculas y minúsculas
pdfjs-find-match-diacritics-checkbox-label = Coincidir diacríticos
pdfjs-find-entire-word-checkbox-label = Palabras completas
pdfjs-find-reached-top = Se alcanzó el inicio del documento, se buscará al final
pdfjs-find-reached-bottom = Se alcanzó el final del documento, se buscará al inicio
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } de { $total } coincidencia
*[other] { $current } de { $total } coincidencias
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Más de { $limit } coincidencia
*[other] Más de { $limit } coincidencias
}
pdfjs-find-not-found = No se encontró la frase
## Predefined zoom values
pdfjs-page-scale-width = Ancho de página
pdfjs-page-scale-fit = Ajustar página
pdfjs-page-scale-auto = Zoom automático
pdfjs-page-scale-actual = Tamaño real
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Página { $page }
## Loading indicator messages
pdfjs-loading-error = Un error ocurrió al cargar el PDF.
pdfjs-invalid-file-error = Archivo PDF invalido o dañado.
pdfjs-missing-file-error = Archivo PDF no encontrado.
pdfjs-unexpected-response-error = Respuesta inesperada del servidor.
pdfjs-rendering-error = Un error ocurrió al renderizar la página.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } anotación]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Ingresa la contraseña para abrir este archivo PDF.
pdfjs-password-invalid = Contraseña inválida. Por favor intenta de nuevo.
pdfjs-password-ok-button = Aceptar
pdfjs-password-cancel-button = Cancelar
pdfjs-web-fonts-disabled = Las fuentes web están desactivadas: es imposible usar las fuentes PDF embebidas.
## Editing
pdfjs-editor-free-text-button =
.title = Texto
pdfjs-editor-free-text-button-label = Texto
pdfjs-editor-ink-button =
.title = Dibujar
pdfjs-editor-ink-button-label = Dibujar
pdfjs-editor-stamp-button =
.title = Agregar o editar imágenes
pdfjs-editor-stamp-button-label = Agregar o editar imágenes
pdfjs-editor-highlight-button =
.title = Destacar
pdfjs-editor-highlight-button-label = Destacar
pdfjs-highlight-floating-button1 =
.title = Destacados
.aria-label = Destacados
pdfjs-highlight-floating-button-label = Destacados
pdfjs-editor-signature-button =
.title = Agregar firma
pdfjs-editor-signature-button-label = Añadir firma
## Default editor aria labels
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Eliminar dibujo
pdfjs-editor-remove-freetext-button =
.title = Eliminar texto
pdfjs-editor-remove-stamp-button =
.title = Eliminar imagen
pdfjs-editor-remove-highlight-button =
.title = Eliminar destacado
pdfjs-editor-remove-signature-button =
.title = Eliminar firma
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Color
pdfjs-editor-free-text-size-input = Tamaño
pdfjs-editor-ink-color-input = Color
pdfjs-editor-ink-thickness-input = Grossor
pdfjs-editor-ink-opacity-input = Opacidad
pdfjs-editor-stamp-add-image-button =
.title = Agregar imagen
pdfjs-editor-stamp-add-image-button-label = Agregar imagen
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Espesor
pdfjs-editor-free-highlight-thickness-title =
.title = Cambiar el grosor al resaltar elementos que no sean texto
pdfjs-editor-signature-add-signature-button =
.title = Agregar nueva firma
pdfjs-editor-signature-add-signature-button-label = Agregar nueva firma
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Editor de texto
.default-content = Comenzar a escribir…
pdfjs-free-text =
.aria-label = Editor de texto
pdfjs-free-text-default-content = Empieza a escribir…
pdfjs-ink =
.aria-label = Editor de dibujo
pdfjs-ink-canvas =
.aria-label = Imagen creada por el usuario
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Texto alternativo
pdfjs-editor-alt-text-edit-button =
.aria-label = Editar texto alternativo
pdfjs-editor-alt-text-edit-button-label = Editar texto alternativo
pdfjs-editor-alt-text-dialog-label = Elige una opción
pdfjs-editor-alt-text-dialog-description = El texto alternativo (texto alternativo) ayuda cuando las personas no pueden ver la imagen o cuando no se carga.
pdfjs-editor-alt-text-add-description-label = Añadir una descripción
pdfjs-editor-alt-text-add-description-description = Intente escribir 1 o 2 oraciones que describan el tema, el entorno o las acciones.
pdfjs-editor-alt-text-mark-decorative-label = Marcar como decorativo
pdfjs-editor-alt-text-mark-decorative-description = Se utiliza para imágenes ornamentales, como bordes o marcas de agua.
pdfjs-editor-alt-text-cancel-button = Cancelar
pdfjs-editor-alt-text-save-button = Guardar
pdfjs-editor-alt-text-decorative-tooltip = Marcado como decorativo
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Por ejemplo: “Un joven se sienta a la mesa a comer”
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Texto alternativo
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Esquina superior izquierda: cambiar el tamaño
pdfjs-editor-resizer-label-top-middle = Arriba en el medio: cambiar el tamaño
pdfjs-editor-resizer-label-top-right = Esquina superior derecha: cambiar el tamaño
pdfjs-editor-resizer-label-middle-right = Centro derecha: cambiar el tamaño
pdfjs-editor-resizer-label-bottom-right = Esquina inferior derecha: cambiar el tamaño
pdfjs-editor-resizer-label-bottom-middle = Abajo en el medio: cambiar el tamaño
pdfjs-editor-resizer-label-bottom-left = Esquina inferior izquierda: cambiar el tamaño
pdfjs-editor-resizer-label-middle-left = Centro izquierda: cambiar el tamaño
pdfjs-editor-resizer-top-left =
.aria-label = Esquina superior izquierda — redimensionar
pdfjs-editor-resizer-top-middle =
.aria-label = Borde superior en el medio — redimensionar
pdfjs-editor-resizer-top-right =
.aria-label = Esquina superior derecha — redimensionar
pdfjs-editor-resizer-middle-right =
.aria-label = Borde derecho en el medio — redimensionar
pdfjs-editor-resizer-bottom-right =
.aria-label = Esquina inferior derecha — redimensionar
pdfjs-editor-resizer-bottom-middle =
.aria-label = Borde inferior en el medio — redimensionar
pdfjs-editor-resizer-bottom-left =
.aria-label = Esquina inferior izquierda — redimensionar
pdfjs-editor-resizer-middle-left =
.aria-label = Borde izquierdo en el medio — redimensionar
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Color de resaltado
pdfjs-editor-colorpicker-button =
.title = Cambiar color
pdfjs-editor-colorpicker-dropdown =
.aria-label = Opciones de color
pdfjs-editor-colorpicker-yellow =
.title = Amarillo
pdfjs-editor-colorpicker-green =
.title = Verde
pdfjs-editor-colorpicker-blue =
.title = Azul
pdfjs-editor-colorpicker-pink =
.title = Rosa
pdfjs-editor-colorpicker-red =
.title = Rojo
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Mostrar todo
pdfjs-editor-highlight-show-all-button =
.title = Mostrar todo
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Editar texto alternativo (descripción de la imagen)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Agregar texto alternativo (descripción de la imagen)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Escribe tu descripción aquí…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Breve descripción para las personas que no pueden ver la imagen o cuando la imagen no se carga.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Este texto alternativo fue creado automáticamente y puede ser inexacto.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Saber más
pdfjs-editor-new-alt-text-create-automatically-button-label = Crear texto alternativo automáticamente
pdfjs-editor-new-alt-text-not-now-button = Ahora no
pdfjs-editor-new-alt-text-error-title = No se pudo crear el texto alternativo automáticamente
pdfjs-editor-new-alt-text-error-description = Escribe tu propio texto alternativo o inténtalo de nuevo más tarde.
pdfjs-editor-new-alt-text-error-close-button = Cerrar
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Descargando el modelo de IA de texto alternativo ({ $downloadedSize } de { $totalSize } MB)
.aria-valuetext = Descargando el modelo de IA de texto alternativo ({ $downloadedSize } de { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Se agregó el texto alternativo
pdfjs-editor-new-alt-text-added-button-label = Se agregó el texto alternativo
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Falta el texto alternativo
pdfjs-editor-new-alt-text-missing-button-label = Falta texto alternativo
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Revisar el texto alternativo
pdfjs-editor-new-alt-text-to-review-button-label = Revisar el texto alternativo
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Creado automáticamente: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Ajustes del texto alternativo de la imagen
pdfjs-image-alt-text-settings-button-label = Ajustes del texto alternativo de la imagen
pdfjs-editor-alt-text-settings-dialog-label = Ajustes del texto alternativo de la imagen
pdfjs-editor-alt-text-settings-automatic-title = Texto alternativo automático
pdfjs-editor-alt-text-settings-create-model-button-label = Crear texto alternativo automáticamente
pdfjs-editor-alt-text-settings-create-model-description = Sugiere descripciones para ayudar a las personas que no pueden ver la imagen o cuando la imagen no se carga.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Modelo de IA de texto alternativo ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Se ejecuta localmente en el dispositivo para que los datos se mantengan privados. Requerido para texto alternativo automático.
pdfjs-editor-alt-text-settings-delete-model-button = Eliminar
pdfjs-editor-alt-text-settings-download-model-button = Descargar
pdfjs-editor-alt-text-settings-downloading-model-button = Descargando…
pdfjs-editor-alt-text-settings-editor-title = Editor de texto alternativo
pdfjs-editor-alt-text-settings-show-dialog-button-label = Mostrar el editor de texto alternativo inmediatamente al añadir una imagen
pdfjs-editor-alt-text-settings-show-dialog-description = Te ayuda a asegurarte de que todas tus imágenes tengan texto alternativo.
pdfjs-editor-alt-text-settings-close-button = Cerrar
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Resaltado eliminado
pdfjs-editor-undo-bar-message-freetext = Texto eliminado
pdfjs-editor-undo-bar-message-ink = Dibujo eliminado
pdfjs-editor-undo-bar-message-stamp = Imagen eliminada
pdfjs-editor-undo-bar-message-signature = Firma eliminada
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } anotación eliminada
*[other] { $count } anotaciones eliminadas
}
pdfjs-editor-undo-bar-undo-button =
.title = Deshacer
pdfjs-editor-undo-bar-undo-button-label = Deshacer
pdfjs-editor-undo-bar-close-button =
.title = Cerrar
pdfjs-editor-undo-bar-close-button-label = Cerrar
## Add a signature dialog
pdfjs-editor-add-signature-dialog-title = Agregar una firma
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Tipo
.title = Tipo
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Dibujar
.title = Dibujar
pdfjs-editor-add-signature-image-button = Imagen
.title = Imagen
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Escribe tu firma
.placeholder = Escribe tu firma
pdfjs-editor-add-signature-draw-placeholder = Dibuja tu firma
pdfjs-editor-add-signature-draw-thickness-range-label = Grossor
## Controls
pdfjs-editor-add-signature-description-label = Descripción (texto alternativo)
pdfjs-editor-add-signature-description-input =
.title = Descripción (texto alternativo)
pdfjs-editor-add-signature-description-default-when-drawing = Firma
pdfjs-editor-add-signature-clear-button-label = Limpiar firma
pdfjs-editor-add-signature-clear-button =
.title = Limpiar firma
pdfjs-editor-add-signature-save-checkbox = Guardar firma
pdfjs-editor-add-signature-save-warning-message = Has alcanzado el límite de 5 firmas guardadas. Elimina una para guardar más.
pdfjs-editor-add-signature-image-upload-error-title = No se pudo cargar la imagen
pdfjs-editor-add-signature-image-upload-error-description = Verifica tu conexión de red o prueba con otra imagen.
pdfjs-editor-add-signature-error-close-button = Cerrar
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Cancelar
pdfjs-editor-add-signature-add-button = Agregar
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Eliminar firma
pdfjs-editor-delete-signature-button-label = Eliminar firma
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Editar descripción
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Editar descripción
================================================
FILE: cookbook/static/pdfjs/web/locale/et/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Eelmine lehekülg
pdfjs-previous-button-label = Eelmine
pdfjs-next-button =
.title = Järgmine lehekülg
pdfjs-next-button-label = Järgmine
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Leht
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = / { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber }/{ $pagesCount })
pdfjs-zoom-out-button =
.title = Vähenda
pdfjs-zoom-out-button-label = Vähenda
pdfjs-zoom-in-button =
.title = Suurenda
pdfjs-zoom-in-button-label = Suurenda
pdfjs-zoom-select =
.title = Suurendamine
pdfjs-presentation-mode-button =
.title = Lülitu esitlusrežiimi
pdfjs-presentation-mode-button-label = Esitlusrežiim
pdfjs-open-file-button =
.title = Ava fail
pdfjs-open-file-button-label = Ava
pdfjs-print-button =
.title = Prindi
pdfjs-print-button-label = Prindi
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Tööriistad
pdfjs-tools-button-label = Tööriistad
pdfjs-first-page-button =
.title = Mine esimesele leheküljele
pdfjs-first-page-button-label = Mine esimesele leheküljele
pdfjs-last-page-button =
.title = Mine viimasele leheküljele
pdfjs-last-page-button-label = Mine viimasele leheküljele
pdfjs-page-rotate-cw-button =
.title = Pööra päripäeva
pdfjs-page-rotate-cw-button-label = Pööra päripäeva
pdfjs-page-rotate-ccw-button =
.title = Pööra vastupäeva
pdfjs-page-rotate-ccw-button-label = Pööra vastupäeva
pdfjs-cursor-text-select-tool-button =
.title = Luba teksti valimise tööriist
pdfjs-cursor-text-select-tool-button-label = Teksti valimise tööriist
pdfjs-cursor-hand-tool-button =
.title = Luba sirvimistööriist
pdfjs-cursor-hand-tool-button-label = Sirvimistööriist
pdfjs-scroll-page-button =
.title = Kasutatakse lehe kaupa kerimist
pdfjs-scroll-page-button-label = Lehe kaupa kerimine
pdfjs-scroll-vertical-button =
.title = Kasuta vertikaalset kerimist
pdfjs-scroll-vertical-button-label = Vertikaalne kerimine
pdfjs-scroll-horizontal-button =
.title = Kasuta horisontaalset kerimist
pdfjs-scroll-horizontal-button-label = Horisontaalne kerimine
pdfjs-scroll-wrapped-button =
.title = Kasuta rohkem mahutavat kerimist
pdfjs-scroll-wrapped-button-label = Rohkem mahutav kerimine
pdfjs-spread-none-button =
.title = Ära kõrvuta lehekülgi
pdfjs-spread-none-button-label = Lehtede kõrvutamine puudub
pdfjs-spread-odd-button =
.title = Kõrvuta leheküljed, alustades paaritute numbritega lehekülgedega
pdfjs-spread-odd-button-label = Kõrvutamine paaritute numbritega alustades
pdfjs-spread-even-button =
.title = Kõrvuta leheküljed, alustades paarisnumbritega lehekülgedega
pdfjs-spread-even-button-label = Kõrvutamine paarisnumbritega alustades
## Document properties dialog
pdfjs-document-properties-button =
.title = Dokumendi omadused…
pdfjs-document-properties-button-label = Dokumendi omadused…
pdfjs-document-properties-file-name = Faili nimi:
pdfjs-document-properties-file-size = Faili suurus:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KiB ({ $size_b } baiti)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MiB ({ $size_b } baiti)
pdfjs-document-properties-title = Pealkiri:
pdfjs-document-properties-author = Autor:
pdfjs-document-properties-subject = Teema:
pdfjs-document-properties-keywords = Märksõnad:
pdfjs-document-properties-creation-date = Loodud:
pdfjs-document-properties-modification-date = Muudetud:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date } { $time }
pdfjs-document-properties-creator = Looja:
pdfjs-document-properties-producer = Generaator:
pdfjs-document-properties-version = Generaatori versioon:
pdfjs-document-properties-page-count = Lehekülgi:
pdfjs-document-properties-page-size = Lehe suurus:
pdfjs-document-properties-page-size-unit-inches = tolli
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = vertikaalpaigutus
pdfjs-document-properties-page-size-orientation-landscape = rõhtpaigutus
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letter
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = "Fast Web View" tugi:
pdfjs-document-properties-linearized-yes = Jah
pdfjs-document-properties-linearized-no = Ei
pdfjs-document-properties-close-button = Sulge
## Print
pdfjs-print-progress-message = Dokumendi ettevalmistamine printimiseks…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Loobu
pdfjs-printing-not-supported = Hoiatus: printimine pole selle brauseri poolt täielikult toetatud.
pdfjs-printing-not-ready = Hoiatus: PDF pole printimiseks täielikult laaditud.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Näita külgriba
pdfjs-toggle-sidebar-notification-button =
.title = Näita külgriba (dokument sisaldab sisukorda/manuseid/kihte)
pdfjs-toggle-sidebar-button-label = Näita külgriba
pdfjs-document-outline-button =
.title = Näita sisukorda (kõigi punktide laiendamiseks/ahendamiseks topeltklõpsa)
pdfjs-document-outline-button-label = Näita sisukorda
pdfjs-attachments-button =
.title = Näita manuseid
pdfjs-attachments-button-label = Manused
pdfjs-layers-button =
.title = Näita kihte (kõikide kihtide vaikeolekusse lähtestamiseks topeltklõpsa)
pdfjs-layers-button-label = Kihid
pdfjs-thumbs-button =
.title = Näita pisipilte
pdfjs-thumbs-button-label = Pisipildid
pdfjs-current-outline-item-button =
.title = Otsi üles praegune kontuuriüksus
pdfjs-current-outline-item-button-label = Praegune kontuuriüksus
pdfjs-findbar-button =
.title = Otsi dokumendist
pdfjs-findbar-button-label = Otsi
pdfjs-additional-layers = Täiendavad kihid
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = { $page }. lehekülg
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = { $page }. lehekülje pisipilt
## Find panel button title and messages
pdfjs-find-input =
.title = Otsi
.placeholder = Otsi dokumendist…
pdfjs-find-previous-button =
.title = Otsi fraasi eelmine esinemiskoht
pdfjs-find-previous-button-label = Eelmine
pdfjs-find-next-button =
.title = Otsi fraasi järgmine esinemiskoht
pdfjs-find-next-button-label = Järgmine
pdfjs-find-highlight-checkbox = Too kõik esile
pdfjs-find-match-case-checkbox-label = Tõstutundlik
pdfjs-find-match-diacritics-checkbox-label = Otsitakse diakriitiliselt
pdfjs-find-entire-word-checkbox-label = Täissõnad
pdfjs-find-reached-top = Jõuti dokumendi algusesse, jätkati lõpust
pdfjs-find-reached-bottom = Jõuti dokumendi lõppu, jätkati algusest
pdfjs-find-not-found = Fraasi ei leitud
## Predefined zoom values
pdfjs-page-scale-width = Mahuta laiusele
pdfjs-page-scale-fit = Mahuta leheküljele
pdfjs-page-scale-auto = Automaatne suurendamine
pdfjs-page-scale-actual = Tegelik suurus
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Lehekülg { $page }
## Loading indicator messages
pdfjs-loading-error = PDFi laadimisel esines viga.
pdfjs-invalid-file-error = Vigane või rikutud PDF-fail.
pdfjs-missing-file-error = PDF-fail puudub.
pdfjs-unexpected-response-error = Ootamatu vastus serverilt.
pdfjs-rendering-error = Lehe renderdamisel esines viga.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date } { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } Annotation]
## Password
pdfjs-password-label = PDF-faili avamiseks sisesta parool.
pdfjs-password-invalid = Vigane parool. Palun proovi uuesti.
pdfjs-password-ok-button = Sobib
pdfjs-password-cancel-button = Loobu
pdfjs-web-fonts-disabled = Veebifondid on keelatud: PDFiga kaasatud fonte pole võimalik kasutada.
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/eu/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Aurreko orria
pdfjs-previous-button-label = Aurrekoa
pdfjs-next-button =
.title = Hurrengo orria
pdfjs-next-button-label = Hurrengoa
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Orria
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = / { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = { $pagesCount }/{ $pageNumber }
pdfjs-zoom-out-button =
.title = Urrundu zooma
pdfjs-zoom-out-button-label = Urrundu zooma
pdfjs-zoom-in-button =
.title = Gerturatu zooma
pdfjs-zoom-in-button-label = Gerturatu zooma
pdfjs-zoom-select =
.title = Zooma
pdfjs-presentation-mode-button =
.title = Aldatu aurkezpen modura
pdfjs-presentation-mode-button-label = Arkezpen modua
pdfjs-open-file-button =
.title = Ireki fitxategia
pdfjs-open-file-button-label = Ireki
pdfjs-print-button =
.title = Inprimatu
pdfjs-print-button-label = Inprimatu
pdfjs-save-button =
.title = Gorde
pdfjs-save-button-label = Gorde
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Deskargatu
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Deskargatu
pdfjs-bookmark-button =
.title = Uneko orria (ikusi uneko orriaren URLa)
pdfjs-bookmark-button-label = Uneko orria
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Tresnak
pdfjs-tools-button-label = Tresnak
pdfjs-first-page-button =
.title = Joan lehen orrira
pdfjs-first-page-button-label = Joan lehen orrira
pdfjs-last-page-button =
.title = Joan azken orrira
pdfjs-last-page-button-label = Joan azken orrira
pdfjs-page-rotate-cw-button =
.title = Biratu erlojuaren norantzan
pdfjs-page-rotate-cw-button-label = Biratu erlojuaren norantzan
pdfjs-page-rotate-ccw-button =
.title = Biratu erlojuaren aurkako norantzan
pdfjs-page-rotate-ccw-button-label = Biratu erlojuaren aurkako norantzan
pdfjs-cursor-text-select-tool-button =
.title = Gaitu testuaren hautapen tresna
pdfjs-cursor-text-select-tool-button-label = Testuaren hautapen tresna
pdfjs-cursor-hand-tool-button =
.title = Gaitu eskuaren tresna
pdfjs-cursor-hand-tool-button-label = Eskuaren tresna
pdfjs-scroll-page-button =
.title = Erabili orriaren korritzea
pdfjs-scroll-page-button-label = Orriaren korritzea
pdfjs-scroll-vertical-button =
.title = Erabili korritze bertikala
pdfjs-scroll-vertical-button-label = Korritze bertikala
pdfjs-scroll-horizontal-button =
.title = Erabili korritze horizontala
pdfjs-scroll-horizontal-button-label = Korritze horizontala
pdfjs-scroll-wrapped-button =
.title = Erabili korritze egokitua
pdfjs-scroll-wrapped-button-label = Korritze egokitua
pdfjs-spread-none-button =
.title = Ez elkartu barreiatutako orriak
pdfjs-spread-none-button-label = Barreiatzerik ez
pdfjs-spread-odd-button =
.title = Elkartu barreiatutako orriak bakoiti zenbakidunekin hasita
pdfjs-spread-odd-button-label = Barreiatze bakoitia
pdfjs-spread-even-button =
.title = Elkartu barreiatutako orriak bikoiti zenbakidunekin hasita
pdfjs-spread-even-button-label = Barreiatze bikoitia
## Document properties dialog
pdfjs-document-properties-button =
.title = Dokumentuaren propietateak…
pdfjs-document-properties-button-label = Dokumentuaren propietateak…
pdfjs-document-properties-file-name = Fitxategi-izena:
pdfjs-document-properties-file-size = Fitxategiaren tamaina:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } byte)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } byte)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } byte)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } byte)
pdfjs-document-properties-title = Izenburua:
pdfjs-document-properties-author = Egilea:
pdfjs-document-properties-subject = Gaia:
pdfjs-document-properties-keywords = Gako-hitzak:
pdfjs-document-properties-creation-date = Sortze-data:
pdfjs-document-properties-modification-date = Aldatze-data:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Sortzailea:
pdfjs-document-properties-producer = PDFaren ekoizlea:
pdfjs-document-properties-version = PDF bertsioa:
pdfjs-document-properties-page-count = Orrialde kopurua:
pdfjs-document-properties-page-size = Orriaren tamaina:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = bertikala
pdfjs-document-properties-page-size-orientation-landscape = horizontala
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Gutuna
pdfjs-document-properties-page-size-name-legal = Legala
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Webeko ikuspegi bizkorra:
pdfjs-document-properties-linearized-yes = Bai
pdfjs-document-properties-linearized-no = Ez
pdfjs-document-properties-close-button = Itxi
## Print
pdfjs-print-progress-message = Dokumentua inprimatzeko prestatzen…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = %{ $progress }
pdfjs-print-progress-close-button = Utzi
pdfjs-printing-not-supported = Abisua: inprimatzeko euskarria ez da erabatekoa nabigatzaile honetan.
pdfjs-printing-not-ready = Abisua: PDFa ez dago erabat kargatuta inprimatzeko.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Txandakatu alboko barra
pdfjs-toggle-sidebar-notification-button =
.title = Txandakatu alboko barra (dokumentuak eskema/eranskinak/geruzak ditu)
pdfjs-toggle-sidebar-button-label = Txandakatu alboko barra
pdfjs-document-outline-button =
.title = Erakutsi dokumentuaren eskema (klik bikoitza elementu guztiak zabaltzeko/tolesteko)
pdfjs-document-outline-button-label = Dokumentuaren eskema
pdfjs-attachments-button =
.title = Erakutsi eranskinak
pdfjs-attachments-button-label = Eranskinak
pdfjs-layers-button =
.title = Erakutsi geruzak (klik bikoitza geruza guztiak egoera lehenetsira berrezartzeko)
pdfjs-layers-button-label = Geruzak
pdfjs-thumbs-button =
.title = Erakutsi koadro txikiak
pdfjs-thumbs-button-label = Koadro txikiak
pdfjs-current-outline-item-button =
.title = Bilatu uneko eskemaren elementua
pdfjs-current-outline-item-button-label = Uneko eskemaren elementua
pdfjs-findbar-button =
.title = Bilatu dokumentuan
pdfjs-findbar-button-label = Bilatu
pdfjs-additional-layers = Geruza gehigarriak
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = { $page }. orria
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = { $page }. orriaren koadro txikia
## Find panel button title and messages
pdfjs-find-input =
.title = Bilatu
.placeholder = Bilatu dokumentuan…
pdfjs-find-previous-button =
.title = Bilatu esaldiaren aurreko parekatzea
pdfjs-find-previous-button-label = Aurrekoa
pdfjs-find-next-button =
.title = Bilatu esaldiaren hurrengo parekatzea
pdfjs-find-next-button-label = Hurrengoa
pdfjs-find-highlight-checkbox = Nabarmendu guztia
pdfjs-find-match-case-checkbox-label = Bat etorri maiuskulekin/minuskulekin
pdfjs-find-match-diacritics-checkbox-label = Bereizi diakritikoak
pdfjs-find-entire-word-checkbox-label = Hitz osoak
pdfjs-find-reached-top = Dokumentuaren hasierara heldu da, bukaeratik jarraitzen
pdfjs-find-reached-bottom = Dokumentuaren bukaerara heldu da, hasieratik jarraitzen
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $total }/{ $current }. bat-etortzea
*[other] { $total }/{ $current }. bat-etortzea
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Bat datorren { $limit } baino gehiago
*[other] Bat datozen { $limit } baino gehiago
}
pdfjs-find-not-found = Esaldia ez da aurkitu
## Predefined zoom values
pdfjs-page-scale-width = Orriaren zabalera
pdfjs-page-scale-fit = Doitu orrira
pdfjs-page-scale-auto = Zoom automatikoa
pdfjs-page-scale-actual = Benetako tamaina
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = %{ $scale }
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = { $page }. orria
## Loading indicator messages
pdfjs-loading-error = Errorea gertatu da PDFa kargatzean.
pdfjs-invalid-file-error = PDF fitxategi baliogabe edo hondatua.
pdfjs-missing-file-error = PDF fitxategia falta da.
pdfjs-unexpected-response-error = Espero gabeko zerbitzariaren erantzuna.
pdfjs-rendering-error = Errorea gertatu da orria errendatzean.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } ohartarazpena]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Idatzi PDF fitxategi hau irekitzeko pasahitza.
pdfjs-password-invalid = Pasahitz baliogabea. Saiatu berriro mesedez.
pdfjs-password-ok-button = Ados
pdfjs-password-cancel-button = Utzi
pdfjs-web-fonts-disabled = Webeko letra-tipoak desgaituta daude: ezin dira kapsulatutako PDF letra-tipoak erabili.
## Editing
pdfjs-editor-free-text-button =
.title = Testua
pdfjs-editor-free-text-button-label = Testua
pdfjs-editor-ink-button =
.title = Marrazkia
pdfjs-editor-ink-button-label = Marrazkia
pdfjs-editor-stamp-button =
.title = Gehitu edo editatu irudiak
pdfjs-editor-stamp-button-label = Gehitu edo editatu irudiak
pdfjs-editor-highlight-button =
.title = Nabarmendu
pdfjs-editor-highlight-button-label = Nabarmendu
pdfjs-highlight-floating-button1 =
.title = Nabarmendu
.aria-label = Nabarmendu
pdfjs-highlight-floating-button-label = Nabarmendu
## Default editor aria labels
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Kendu marrazkia
pdfjs-editor-remove-freetext-button =
.title = Kendu testua
pdfjs-editor-remove-stamp-button =
.title = Kendu irudia
pdfjs-editor-remove-highlight-button =
.title = Kendu nabarmentzea
pdfjs-editor-remove-signature-button =
.title = Kendu sinadura
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Kolorea
pdfjs-editor-free-text-size-input = Tamaina
pdfjs-editor-ink-color-input = Kolorea
pdfjs-editor-ink-thickness-input = Loditasuna
pdfjs-editor-ink-opacity-input = Opakutasuna
pdfjs-editor-stamp-add-image-button =
.title = Gehitu irudia
pdfjs-editor-stamp-add-image-button-label = Gehitu irudia
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Loditasuna
pdfjs-editor-free-highlight-thickness-title =
.title = Aldatu loditasuna testua ez beste elementuak nabarmentzean
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Testu-editorea
.default-content = Hasi idazten…
pdfjs-free-text =
.aria-label = Testu-editorea
pdfjs-free-text-default-content = Hasi idazten…
pdfjs-ink =
.aria-label = Marrazki-editorea
pdfjs-ink-canvas =
.aria-label = Erabiltzaileak sortutako irudia
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Testu alternatiboa
pdfjs-editor-alt-text-edit-button =
.aria-label = Editatu testu alternatiboa
pdfjs-editor-alt-text-edit-button-label = Editatu testu alternatiboa
pdfjs-editor-alt-text-dialog-label = Aukeratu aukera
pdfjs-editor-alt-text-dialog-description = Testu alternatiboak laguntzen du jendeak ezin duenean irudia ikusi edo ez denean kargatzen.
pdfjs-editor-alt-text-add-description-label = Gehitu azalpena
pdfjs-editor-alt-text-add-description-description = Saiatu idazten gaia, ezarpena edo ekintzak deskribatzen dituen esaldi 1 edo 2.
pdfjs-editor-alt-text-mark-decorative-label = Markatu apaingarri gisa
pdfjs-editor-alt-text-mark-decorative-description = Irudiak apaingarrientzat erabiltzen da, adibidez ertz edo ur-marketarako.
pdfjs-editor-alt-text-cancel-button = Utzi
pdfjs-editor-alt-text-save-button = Gorde
pdfjs-editor-alt-text-decorative-tooltip = Apaingarri gisa markatuta
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Adibidez, "gizon gaztea mahaian eserita dago bazkaltzeko"
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Testu alternatiboa
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Goiko ezkerreko izkina — aldatu tamaina
pdfjs-editor-resizer-label-top-middle = Goian erdian — aldatu tamaina
pdfjs-editor-resizer-label-top-right = Goiko eskuineko izkina — aldatu tamaina
pdfjs-editor-resizer-label-middle-right = Erdian eskuinean — aldatu tamaina
pdfjs-editor-resizer-label-bottom-right = Beheko eskuineko izkina — aldatu tamaina
pdfjs-editor-resizer-label-bottom-middle = Behean erdian — aldatu tamaina
pdfjs-editor-resizer-label-bottom-left = Beheko ezkerreko izkina — aldatu tamaina
pdfjs-editor-resizer-label-middle-left = Erdian ezkerrean — aldatu tamaina
pdfjs-editor-resizer-top-left =
.aria-label = Goiko ezkerreko izkina — aldatu tamaina
pdfjs-editor-resizer-top-middle =
.aria-label = Goian erdian — aldatu tamaina
pdfjs-editor-resizer-top-right =
.aria-label = Goiko eskuineko izkina — aldatu tamaina
pdfjs-editor-resizer-middle-right =
.aria-label = Erdian eskuinean — aldatu tamaina
pdfjs-editor-resizer-bottom-right =
.aria-label = Beheko eskuineko izkina — aldatu tamaina
pdfjs-editor-resizer-bottom-middle =
.aria-label = Behean erdian — aldatu tamaina
pdfjs-editor-resizer-bottom-left =
.aria-label = Beheko ezkerreko izkina — aldatu tamaina
pdfjs-editor-resizer-middle-left =
.aria-label = Erdian ezkerrean — aldatu tamaina
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Nabarmentze kolorea
pdfjs-editor-colorpicker-button =
.title = Aldatu kolorea
pdfjs-editor-colorpicker-dropdown =
.aria-label = Kolore-aukerak
pdfjs-editor-colorpicker-yellow =
.title = Horia
pdfjs-editor-colorpicker-green =
.title = Berdea
pdfjs-editor-colorpicker-blue =
.title = Urdina
pdfjs-editor-colorpicker-pink =
.title = Arrosa
pdfjs-editor-colorpicker-red =
.title = Gorria
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Erakutsi denak
pdfjs-editor-highlight-show-all-button =
.title = Erakutsi denak
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Editatu testu alternatiboa (irudiaren azalpena)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Gehitu testu alternatiboa (irudiaren azalpena)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Idatzi zure azalpena hemen…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Azalpen laburra irudia ikusi ezin duen jendearentzat edo irudia kargatu ezin denerako.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Testu alternatibo hau automatikoki sortu da eta okerra izan liteke.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Argibide gehiago
pdfjs-editor-new-alt-text-create-automatically-button-label = Sortu testu alternatiboa automatikoki
pdfjs-editor-new-alt-text-not-now-button = Une honetan ez
pdfjs-editor-new-alt-text-error-title = Ezin da testu alternatiboa automatikoki sortu
pdfjs-editor-new-alt-text-error-description = Idatzi zure testu alternatibo propioa edo saiatu berriro geroago.
pdfjs-editor-new-alt-text-error-close-button = Itxi
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Testu alternatiboaren AA modeloa deskargatzen ({ $totalSize }/{ $downloadedSize } MB)
.aria-valuetext = Testu alternatiboaren AA modeloa deskargatzen ({ $totalSize }/{ $downloadedSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Testu alternatiboa gehituta
pdfjs-editor-new-alt-text-added-button-label = Testu alternatiboa gehituta
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Testu alternatiboa falta da
pdfjs-editor-new-alt-text-missing-button-label = Testu alternatiboa falta da
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Berrikusi testu alternatiboa
pdfjs-editor-new-alt-text-to-review-button-label = Berrikusi testu alternatiboa
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Automatikoki sortua: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Irudiaren testu alternatiboaren ezarpenak
pdfjs-image-alt-text-settings-button-label = Irudiaren testu alternatiboaren ezarpenak
pdfjs-editor-alt-text-settings-dialog-label = Irudiaren testu alternatiboaren ezarpenak
pdfjs-editor-alt-text-settings-automatic-title = Testu alternatibo automatikoa
pdfjs-editor-alt-text-settings-create-model-button-label = Sortu testu alternatiboa automatikoki
pdfjs-editor-alt-text-settings-create-model-description = Azalpenak iradokitzen ditu irudia ikusi ezin duen jendearentzat edo irudia kargatu ezin denerako.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Testu alternatiboaren AA modeloa ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Zure gailuan modu lokalean exekutatzen da eta zure datuak pribatu mantentzen dira. Testu alternatibo automatikorako beharrezkoa.
pdfjs-editor-alt-text-settings-delete-model-button = Ezabatu
pdfjs-editor-alt-text-settings-download-model-button = Deskargatu
pdfjs-editor-alt-text-settings-downloading-model-button = Deskargatzen…
pdfjs-editor-alt-text-settings-editor-title = Testu alternatiboaren editorea
pdfjs-editor-alt-text-settings-show-dialog-button-label = Erakutsi testu alternatiboa irudi bat gehitzean berehala
pdfjs-editor-alt-text-settings-show-dialog-description = Zure irudiek testu alternatiboa duela ziurtatzen laguntzen dizu.
pdfjs-editor-alt-text-settings-close-button = Itxi
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Nabarmentzea kenduta
pdfjs-editor-undo-bar-message-freetext = Testua kenduta
pdfjs-editor-undo-bar-message-ink = Marrazkia kenduta
pdfjs-editor-undo-bar-message-stamp = Irudia kenduta
pdfjs-editor-undo-bar-message-signature = Sinadura kenduta
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] Esku-ohar bat kenduta
*[other] { $count } esku-ohar kenduta
}
pdfjs-editor-undo-bar-undo-button =
.title = Desegin
pdfjs-editor-undo-bar-undo-button-label = Desegin
pdfjs-editor-undo-bar-close-button =
.title = Itxi
pdfjs-editor-undo-bar-close-button-label = Itxi
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label =
Leiho modal honek PDF dokumentu batera gehitzeko sinadurak
sortzea ahalbidetzen dio erabiltzaileari. Erabiltzaileak izena edita
dezake (testu alternatibo modura ere erabiltzen dena) eta sinadura
gordetzeko aukera du gehiagotan erabili ahal izateko.
pdfjs-editor-add-signature-dialog-title = Gehitu sinadura
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Idatzi
.title = Idatzi
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Marraztu
.title = Marraztu
pdfjs-editor-add-signature-image-button = Irudia
.title = Irudia
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Idatzi zure sinadura
.placeholder = Idatzi zure sinadura
pdfjs-editor-add-signature-draw-placeholder = Marraztu zure sinadura
pdfjs-editor-add-signature-draw-thickness-range-label = Loditasuna
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Marrazteko loditasuna: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Igotzeko, jaregin fitxategia hemen
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Edo aukeratu irudi-fitxategiak
*[other] Edo arakatu irudi-fitxategiak
}
## Controls
pdfjs-editor-add-signature-description-label = Azalpena (testu alternatiboa)
pdfjs-editor-add-signature-description-input =
.title = Azalpena (testu alternatiboa)
pdfjs-editor-add-signature-description-default-when-drawing = Sinadura
pdfjs-editor-add-signature-clear-button-label = Garbitu sinadura
pdfjs-editor-add-signature-clear-button =
.title = Garbitu sinadura
pdfjs-editor-add-signature-save-checkbox = Gorde sinadura
pdfjs-editor-add-signature-save-warning-message = Gordetako sinadura kopuruaren mugara heldu zara (5). Gehiago gorde ahal izateko, ken ezazu bat.
pdfjs-editor-add-signature-image-upload-error-title = Ezin da irudia igo
pdfjs-editor-add-signature-image-upload-error-description = Egiaztatu zure sareko konexioa edo saiatu beste irudi batekin.
pdfjs-editor-add-signature-error-close-button = Itxi
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Utzi
pdfjs-editor-add-signature-add-button = Gehitu
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/fa/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = صفحهٔ قبلی
pdfjs-previous-button-label = قبلی
pdfjs-next-button =
.title = صفحهٔ بعدی
pdfjs-next-button-label = بعدی
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = صفحه
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = از { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber }از { $pagesCount })
pdfjs-zoom-out-button =
.title = کوچکنمایی
pdfjs-zoom-out-button-label = کوچکنمایی
pdfjs-zoom-in-button =
.title = بزرگنمایی
pdfjs-zoom-in-button-label = بزرگنمایی
pdfjs-zoom-select =
.title = زوم
pdfjs-presentation-mode-button =
.title = تغییر به حالت ارائه
pdfjs-presentation-mode-button-label = حالت ارائه
pdfjs-open-file-button =
.title = باز کردن پرونده
pdfjs-open-file-button-label = باز کردن
pdfjs-print-button =
.title = چاپ
pdfjs-print-button-label = چاپ
pdfjs-save-button =
.title = ذخیره
pdfjs-save-button-label = ذخیره
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = دریافت
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = دریافت
pdfjs-bookmark-button =
.title = صفحه فعلی (مشاهده نشانی اینترنتی از صفحه فعلی)
pdfjs-bookmark-button-label = صفحه فعلی
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = ابزارها
pdfjs-tools-button-label = ابزارها
pdfjs-first-page-button =
.title = برو به اولین صفحه
pdfjs-first-page-button-label = برو به اولین صفحه
pdfjs-last-page-button =
.title = برو به آخرین صفحه
pdfjs-last-page-button-label = برو به آخرین صفحه
pdfjs-page-rotate-cw-button =
.title = چرخش ساعتگرد
pdfjs-page-rotate-cw-button-label = چرخش ساعتگرد
pdfjs-page-rotate-ccw-button =
.title = چرخش پاد ساعتگرد
pdfjs-page-rotate-ccw-button-label = چرخش پاد ساعتگرد
pdfjs-cursor-text-select-tool-button =
.title = فعال کردن ابزارِ انتخابِ متن
pdfjs-cursor-text-select-tool-button-label = ابزارِ انتخابِ متن
pdfjs-cursor-hand-tool-button =
.title = فعال کردن ابزارِ دست
pdfjs-cursor-hand-tool-button-label = ابزار دست
pdfjs-scroll-page-button =
.title = استفاده از پیمایش صفحه
pdfjs-scroll-page-button-label = پیمایش صفحه
pdfjs-scroll-vertical-button =
.title = استفاده از پیمایش عمودی
pdfjs-scroll-vertical-button-label = پیمایش عمودی
pdfjs-scroll-horizontal-button =
.title = استفاده از پیمایش افقی
pdfjs-scroll-horizontal-button-label = پیمایش افقی
pdfjs-spread-none-button =
.title = صفحات پیوسته را یکی نکنید
pdfjs-spread-none-button-label = بدون صفحات پیوسته
## Document properties dialog
pdfjs-document-properties-button =
.title = خصوصیات سند...
pdfjs-document-properties-button-label = خصوصیات سند...
pdfjs-document-properties-file-name = نام پرونده:
pdfjs-document-properties-file-size = حجم پرونده:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } کیلوبایت ({ $b } بایت)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } مگابایت ({ $b } بایت)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } کیلوبایت ({ $size_b } بایت)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } مگابایت ({ $size_b } بایت)
pdfjs-document-properties-title = عنوان:
pdfjs-document-properties-author = نویسنده:
pdfjs-document-properties-subject = موضوع:
pdfjs-document-properties-keywords = کلیدواژهها:
pdfjs-document-properties-creation-date = تاریخ ایجاد:
pdfjs-document-properties-modification-date = تاریخ ویرایش:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }، { $time }
pdfjs-document-properties-creator = ایجاد کننده:
pdfjs-document-properties-producer = ایجاد کننده PDF:
pdfjs-document-properties-version = نسخه PDF:
pdfjs-document-properties-page-count = تعداد صفحات:
pdfjs-document-properties-page-size = اندازه صفحه:
pdfjs-document-properties-page-size-unit-inches = اینچ
pdfjs-document-properties-page-size-unit-millimeters = میلیمتر
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = نامه
pdfjs-document-properties-page-size-name-legal = حقوقی
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
pdfjs-document-properties-linearized-yes = بله
pdfjs-document-properties-linearized-no = خیر
pdfjs-document-properties-close-button = بستن
## Print
pdfjs-print-progress-message = آماده سازی مدارک برای چاپ کردن…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = لغو
pdfjs-printing-not-supported = هشدار: قابلیت چاپ بهطور کامل در این مرورگر پشتیبانی نمیشود.
pdfjs-printing-not-ready = اخطار: پرونده PDF بطور کامل بارگیری نشده و امکان چاپ وجود ندارد.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = باز و بسته کردن نوار کناری
pdfjs-toggle-sidebar-button-label = تغییرحالت نوارکناری
pdfjs-document-outline-button =
.title = نمایش رئوس مطالب مدارک(برای بازشدن/جمع شدن همه موارد دوبار کلیک کنید)
pdfjs-document-outline-button-label = طرح نوشتار
pdfjs-attachments-button =
.title = نمایش پیوستها
pdfjs-attachments-button-label = پیوستها
pdfjs-layers-button-label = لایهها
pdfjs-thumbs-button =
.title = نمایش تصاویر بندانگشتی
pdfjs-thumbs-button-label = تصاویر بندانگشتی
pdfjs-findbar-button =
.title = جستجو در سند
pdfjs-findbar-button-label = پیدا کردن
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = صفحه { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = تصویر بند انگشتی صفحه { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = پیدا کردن
.placeholder = پیدا کردن در سند…
pdfjs-find-previous-button =
.title = پیدا کردن رخداد قبلی عبارت
pdfjs-find-previous-button-label = قبلی
pdfjs-find-next-button =
.title = پیدا کردن رخداد بعدی عبارت
pdfjs-find-next-button-label = بعدی
pdfjs-find-highlight-checkbox = برجسته و هایلایت کردن همه موارد
pdfjs-find-match-case-checkbox-label = تطبیق کوچکی و بزرگی حروف
pdfjs-find-entire-word-checkbox-label = تمام کلمهها
pdfjs-find-reached-top = به بالای صفحه رسیدیم، از پایین ادامه میدهیم
pdfjs-find-reached-bottom = به آخر صفحه رسیدیم، از بالا ادامه میدهیم
pdfjs-find-not-found = عبارت پیدا نشد
## Predefined zoom values
pdfjs-page-scale-width = عرض صفحه
pdfjs-page-scale-fit = اندازه کردن صفحه
pdfjs-page-scale-auto = بزرگنمایی خودکار
pdfjs-page-scale-actual = اندازه واقعی
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = صفحهٔ { $page }
## Loading indicator messages
pdfjs-loading-error = هنگام بارگیری پرونده PDF خطایی رخ داد.
pdfjs-invalid-file-error = پرونده PDF نامعتبر یامعیوب میباشد.
pdfjs-missing-file-error = پرونده PDF یافت نشد.
pdfjs-unexpected-response-error = پاسخ پیش بینی نشده سرور
pdfjs-rendering-error = هنگام بارگیری صفحه خطایی رخ داد.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }، { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } Annotation]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = جهت باز کردن پرونده PDF گذرواژه را وارد نمائید.
pdfjs-password-invalid = گذرواژه نامعتبر. لطفا مجددا تلاش کنید.
pdfjs-password-ok-button = تأیید
pdfjs-password-cancel-button = لغو
pdfjs-web-fonts-disabled = فونت های تحت وب غیر فعال شده اند: امکان استفاده از نمایش دهنده داخلی PDF وجود ندارد.
## Editing
pdfjs-editor-free-text-button =
.title = متن
pdfjs-editor-free-text-button-label = متن
pdfjs-editor-ink-button =
.title = کشیدن
pdfjs-editor-ink-button-label = کشیدن
pdfjs-editor-stamp-button =
.title = افزودن یا ویرایش تصاویر
pdfjs-editor-stamp-button-label = افزودن یا ویرایش تصاویر
pdfjs-editor-highlight-button =
.title = برجسته کردن
pdfjs-editor-highlight-button-label = برجسته کردن
pdfjs-highlight-floating-button1 =
.title = برجسته کردن
.aria-label = برجسته کردن
pdfjs-highlight-floating-button-label = برجسته کردن
## Default editor aria labels
## Remove button for the various kind of editor.
##
# Editor Parameters
pdfjs-editor-free-text-color-input = رنگ
pdfjs-editor-free-text-size-input = اندازه
pdfjs-editor-ink-color-input = رنگ
pdfjs-editor-stamp-add-image-button =
.title = افزودن تصویر
pdfjs-editor-stamp-add-image-button-label = افزودن تصویر
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = ویرایشگر متن
.default-content = شروع به نوشتن کنید…
pdfjs-free-text =
.aria-label = ویرایشگر متن
pdfjs-free-text-default-content = شروع به نوشتن کنید…
## Alt-text dialog
pdfjs-editor-alt-text-add-description-label = افزودن توضیحات
pdfjs-editor-alt-text-cancel-button = انصراف
pdfjs-editor-alt-text-save-button = ذخیره
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
pdfjs-editor-colorpicker-button =
.title = تغییر رنگ
pdfjs-editor-colorpicker-dropdown =
.aria-label = انتخاب رنگ
pdfjs-editor-colorpicker-yellow =
.title = زرد
pdfjs-editor-colorpicker-green =
.title = سبز
pdfjs-editor-colorpicker-blue =
.title = آبی
pdfjs-editor-colorpicker-pink =
.title = صورتی
pdfjs-editor-colorpicker-red =
.title = قرمز
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = نمایش همه
pdfjs-editor-highlight-show-all-button =
.title = نمایش همه
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = بیشتر بدانید
pdfjs-editor-new-alt-text-not-now-button = اکنون نه
pdfjs-editor-new-alt-text-error-close-button = بستن
## Image alt-text settings
pdfjs-editor-alt-text-settings-delete-model-button = حذف
pdfjs-editor-alt-text-settings-download-model-button = دریافت
pdfjs-editor-alt-text-settings-downloading-model-button = در حال دریافت…
pdfjs-editor-alt-text-settings-close-button = بستن
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/ff/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Hello Ɓennungo
pdfjs-previous-button-label = Ɓennuɗo
pdfjs-next-button =
.title = Hello faango
pdfjs-next-button-label = Yeeso
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Hello
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = e nder { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } of { $pagesCount })
pdfjs-zoom-out-button =
.title = Lonngo Woɗɗa
pdfjs-zoom-out-button-label = Lonngo Woɗɗa
pdfjs-zoom-in-button =
.title = Lonngo Ara
pdfjs-zoom-in-button-label = Lonngo Ara
pdfjs-zoom-select =
.title = Lonngo
pdfjs-presentation-mode-button =
.title = Faytu to Presentation Mode
pdfjs-presentation-mode-button-label = Presentation Mode
pdfjs-open-file-button =
.title = Uddit Fiilde
pdfjs-open-file-button-label = Uddit
pdfjs-print-button =
.title = Winndito
pdfjs-print-button-label = Winndito
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Kuutorɗe
pdfjs-tools-button-label = Kuutorɗe
pdfjs-first-page-button =
.title = Yah to hello adanngo
pdfjs-first-page-button-label = Yah to hello adanngo
pdfjs-last-page-button =
.title = Yah to hello wattindiingo
pdfjs-last-page-button-label = Yah to hello wattindiingo
pdfjs-page-rotate-cw-button =
.title = Yiiltu Faya Ñaamo
pdfjs-page-rotate-cw-button-label = Yiiltu Faya Ñaamo
pdfjs-page-rotate-ccw-button =
.title = Yiiltu Faya Nano
pdfjs-page-rotate-ccw-button-label = Yiiltu Faya Nano
pdfjs-cursor-text-select-tool-button =
.title = Gollin kaɓirgel cuɓirgel binndi
pdfjs-cursor-text-select-tool-button-label = Kaɓirgel cuɓirgel binndi
pdfjs-cursor-hand-tool-button =
.title = Hurmin kuutorgal junngo
pdfjs-cursor-hand-tool-button-label = Kaɓirgel junngo
pdfjs-scroll-vertical-button =
.title = Huutoro gorwitol daringol
pdfjs-scroll-vertical-button-label = Gorwitol daringol
pdfjs-scroll-horizontal-button =
.title = Huutoro gorwitol lelingol
pdfjs-scroll-horizontal-button-label = Gorwitol daringol
pdfjs-scroll-wrapped-button =
.title = Huutoro gorwitol coomingol
pdfjs-scroll-wrapped-button-label = Gorwitol coomingol
pdfjs-spread-none-button =
.title = Hoto tawtu kelle kelle
pdfjs-spread-none-button-label = Alaa Spreads
pdfjs-spread-odd-button =
.title = Tawtu kelle puɗɗortooɗe kelle teelɗe
pdfjs-spread-odd-button-label = Kelle teelɗe
pdfjs-spread-even-button =
.title = Tawtu ɗereeji kelle puɗɗoriiɗi kelle teeltuɗe
pdfjs-spread-even-button-label = Kelle teeltuɗe
## Document properties dialog
pdfjs-document-properties-button =
.title = Keeroraaɗi Winndannde…
pdfjs-document-properties-button-label = Keeroraaɗi Winndannde…
pdfjs-document-properties-file-name = Innde fiilde:
pdfjs-document-properties-file-size = Ɓetol fiilde:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bite)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bite)
pdfjs-document-properties-title = Tiitoonde:
pdfjs-document-properties-author = Binnduɗo:
pdfjs-document-properties-subject = Toɓɓere:
pdfjs-document-properties-keywords = Kelmekele jiytirɗe:
pdfjs-document-properties-creation-date = Ñalnde Sosaa:
pdfjs-document-properties-modification-date = Ñalnde Waylaa:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Cosɗo:
pdfjs-document-properties-producer = Paggiiɗo PDF:
pdfjs-document-properties-version = Yamre PDF:
pdfjs-document-properties-page-count = Limoore Kelle:
pdfjs-document-properties-page-size = Ɓeto Hello:
pdfjs-document-properties-page-size-unit-inches = nder
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = dariingo
pdfjs-document-properties-page-size-orientation-landscape = wertiingo
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Ɓataake
pdfjs-document-properties-page-size-name-legal = Laawol
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Ɗisngo geese yaawngo:
pdfjs-document-properties-linearized-yes = Eey
pdfjs-document-properties-linearized-no = Alaa
pdfjs-document-properties-close-button = Uddu
## Print
pdfjs-print-progress-message = Nana heboo winnditaade fiilannde…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Haaytu
pdfjs-printing-not-supported = Reentino: Winnditagol tammbitaaka no feewi e ndee wanngorde.
pdfjs-printing-not-ready = Reentino: PDF oo loowaaki haa timmi ngam winnditagol.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Toggilo Palal Sawndo
pdfjs-toggle-sidebar-button-label = Toggilo Palal Sawndo
pdfjs-document-outline-button =
.title = Hollu Ƴiyal Fiilannde (dobdobo ngam wertude/taggude teme fof)
pdfjs-document-outline-button-label = Toɓɓe Fiilannde
pdfjs-attachments-button =
.title = Hollu Ɗisanɗe
pdfjs-attachments-button-label = Ɗisanɗe
pdfjs-thumbs-button =
.title = Hollu Dooɓe
pdfjs-thumbs-button-label = Dooɓe
pdfjs-findbar-button =
.title = Yiylo e fiilannde
pdfjs-findbar-button-label = Yiytu
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Hello { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Dooɓre Hello { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Yiytu
.placeholder = Yiylo nder dokimaa
pdfjs-find-previous-button =
.title = Yiylo cilol ɓennugol konngol ngol
pdfjs-find-previous-button-label = Ɓennuɗo
pdfjs-find-next-button =
.title = Yiylo cilol garowol konngol ngol
pdfjs-find-next-button-label = Yeeso
pdfjs-find-highlight-checkbox = Jalbin fof
pdfjs-find-match-case-checkbox-label = Jaaɓnu darnde
pdfjs-find-entire-word-checkbox-label = Kelme timmuɗe tan
pdfjs-find-reached-top = Heɓii fuɗɗorde fiilannde, jokku faya les
pdfjs-find-reached-bottom = Heɓii hoore fiilannde, jokku faya les
pdfjs-find-not-found = Konngi njiyataa
## Predefined zoom values
pdfjs-page-scale-width = Njaajeendi Hello
pdfjs-page-scale-fit = Keƴeendi Hello
pdfjs-page-scale-auto = Loongorde Jaajol
pdfjs-page-scale-actual = Ɓetol Jaati
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
## Loading indicator messages
pdfjs-loading-error = Juumre waɗii tuma nde loowata PDF oo.
pdfjs-invalid-file-error = Fiilde PDF moƴƴaani walla jiibii.
pdfjs-missing-file-error = Fiilde PDF ena ŋakki.
pdfjs-unexpected-response-error = Jaabtol sarworde tijjinooka.
pdfjs-rendering-error = Juumre waɗii tuma nde yoŋkittoo hello.
## Annotations
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } Siiftannde]
## Password
pdfjs-password-label = Naatu finnde ngam uddite ndee fiilde PDF.
pdfjs-password-invalid = Finnde moƴƴaani. Tiiɗno eto kadi.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Haaytu
pdfjs-web-fonts-disabled = Ponte geese ko daaƴaaɗe: horiima huutoraade ponte PDF coomtoraaɗe.
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/fi/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Edellinen sivu
pdfjs-previous-button-label = Edellinen
pdfjs-next-button =
.title = Seuraava sivu
pdfjs-next-button-label = Seuraava
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Sivu
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = / { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } / { $pagesCount })
pdfjs-zoom-out-button =
.title = Loitonna
pdfjs-zoom-out-button-label = Loitonna
pdfjs-zoom-in-button =
.title = Lähennä
pdfjs-zoom-in-button-label = Lähennä
pdfjs-zoom-select =
.title = Suurennus
pdfjs-presentation-mode-button =
.title = Siirry esitystilaan
pdfjs-presentation-mode-button-label = Esitystila
pdfjs-open-file-button =
.title = Avaa tiedosto
pdfjs-open-file-button-label = Avaa
pdfjs-print-button =
.title = Tulosta
pdfjs-print-button-label = Tulosta
pdfjs-save-button =
.title = Tallenna
pdfjs-save-button-label = Tallenna
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Lataa
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Lataa
pdfjs-bookmark-button =
.title = Nykyinen sivu (Näytä URL-osoite nykyiseltä sivulta)
pdfjs-bookmark-button-label = Nykyinen sivu
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Tools
pdfjs-tools-button-label = Tools
pdfjs-first-page-button =
.title = Siirry ensimmäiselle sivulle
pdfjs-first-page-button-label = Siirry ensimmäiselle sivulle
pdfjs-last-page-button =
.title = Siirry viimeiselle sivulle
pdfjs-last-page-button-label = Siirry viimeiselle sivulle
pdfjs-page-rotate-cw-button =
.title = Kierrä oikealle
pdfjs-page-rotate-cw-button-label = Kierrä oikealle
pdfjs-page-rotate-ccw-button =
.title = Kierrä vasemmalle
pdfjs-page-rotate-ccw-button-label = Kierrä vasemmalle
pdfjs-cursor-text-select-tool-button =
.title = Käytä tekstinvalintatyökalua
pdfjs-cursor-text-select-tool-button-label = Tekstinvalintatyökalu
pdfjs-cursor-hand-tool-button =
.title = Käytä käsityökalua
pdfjs-cursor-hand-tool-button-label = Käsityökalu
pdfjs-scroll-page-button =
.title = Käytä sivun vieritystä
pdfjs-scroll-page-button-label = Sivun vieritys
pdfjs-scroll-vertical-button =
.title = Käytä pystysuuntaista vieritystä
pdfjs-scroll-vertical-button-label = Pystysuuntainen vieritys
pdfjs-scroll-horizontal-button =
.title = Käytä vaakasuuntaista vieritystä
pdfjs-scroll-horizontal-button-label = Vaakasuuntainen vieritys
pdfjs-scroll-wrapped-button =
.title = Käytä rivittyvää vieritystä
pdfjs-scroll-wrapped-button-label = Rivittyvä vieritys
pdfjs-spread-none-button =
.title = Älä yhdistä sivuja aukeamiksi
pdfjs-spread-none-button-label = Ei aukeamia
pdfjs-spread-odd-button =
.title = Yhdistä sivut aukeamiksi alkaen parittomalta sivulta
pdfjs-spread-odd-button-label = Parittomalta alkavat aukeamat
pdfjs-spread-even-button =
.title = Yhdistä sivut aukeamiksi alkaen parilliselta sivulta
pdfjs-spread-even-button-label = Parilliselta alkavat aukeamat
## Document properties dialog
pdfjs-document-properties-button =
.title = Dokumentin ominaisuudet…
pdfjs-document-properties-button-label = Dokumentin ominaisuudet…
pdfjs-document-properties-file-name = Tiedoston nimi:
pdfjs-document-properties-file-size = Tiedoston koko:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } kt ({ $b } tavua)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } Mt ({ $b } tavua)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } kt ({ $size_b } tavua)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } Mt ({ $size_b } tavua)
pdfjs-document-properties-title = Otsikko:
pdfjs-document-properties-author = Tekijä:
pdfjs-document-properties-subject = Aihe:
pdfjs-document-properties-keywords = Avainsanat:
pdfjs-document-properties-creation-date = Luomispäivämäärä:
pdfjs-document-properties-modification-date = Muokkauspäivämäärä:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Luoja:
pdfjs-document-properties-producer = PDF-tuottaja:
pdfjs-document-properties-version = PDF-versio:
pdfjs-document-properties-page-count = Sivujen määrä:
pdfjs-document-properties-page-size = Sivun koko:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = pysty
pdfjs-document-properties-page-size-orientation-landscape = vaaka
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letter
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Nopea web-katselu:
pdfjs-document-properties-linearized-yes = Kyllä
pdfjs-document-properties-linearized-no = Ei
pdfjs-document-properties-close-button = Sulje
## Print
pdfjs-print-progress-message = Valmistellaan dokumenttia tulostamista varten…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress } %
pdfjs-print-progress-close-button = Peruuta
pdfjs-printing-not-supported = Varoitus: Selain ei tue kaikkia tulostustapoja.
pdfjs-printing-not-ready = Varoitus: PDF-tiedosto ei ole vielä latautunut kokonaan, eikä sitä voi vielä tulostaa.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Näytä/piilota sivupaneeli
pdfjs-toggle-sidebar-notification-button =
.title = Näytä/piilota sivupaneeli (dokumentissa on sisällys/liitteitä/tasoja)
pdfjs-toggle-sidebar-button-label = Näytä/piilota sivupaneeli
pdfjs-document-outline-button =
.title = Näytä dokumentin sisällys (laajenna tai kutista kohdat kaksoisnapsauttamalla)
pdfjs-document-outline-button-label = Dokumentin sisällys
pdfjs-attachments-button =
.title = Näytä liitteet
pdfjs-attachments-button-label = Liitteet
pdfjs-layers-button =
.title = Näytä tasot (kaksoisnapsauta palauttaaksesi kaikki tasot oletustilaan)
pdfjs-layers-button-label = Tasot
pdfjs-thumbs-button =
.title = Näytä pienoiskuvat
pdfjs-thumbs-button-label = Pienoiskuvat
pdfjs-current-outline-item-button =
.title = Etsi nykyinen sisällyksen kohta
pdfjs-current-outline-item-button-label = Nykyinen sisällyksen kohta
pdfjs-findbar-button =
.title = Etsi dokumentista
pdfjs-findbar-button-label = Etsi
pdfjs-additional-layers = Lisätasot
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Sivu { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Pienoiskuva sivusta { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Etsi
.placeholder = Etsi dokumentista…
pdfjs-find-previous-button =
.title = Etsi hakusanan edellinen osuma
pdfjs-find-previous-button-label = Edellinen
pdfjs-find-next-button =
.title = Etsi hakusanan seuraava osuma
pdfjs-find-next-button-label = Seuraava
pdfjs-find-highlight-checkbox = Korosta kaikki
pdfjs-find-match-case-checkbox-label = Huomioi kirjainkoko
pdfjs-find-match-diacritics-checkbox-label = Erota tarkkeet
pdfjs-find-entire-word-checkbox-label = Kokonaiset sanat
pdfjs-find-reached-top = Päästiin dokumentin alkuun, jatketaan lopusta
pdfjs-find-reached-bottom = Päästiin dokumentin loppuun, jatketaan alusta
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } / { $total } osuma
*[other] { $current } / { $total } osumaa
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Yli { $limit } osuma
*[other] Yli { $limit } osumaa
}
pdfjs-find-not-found = Hakusanaa ei löytynyt
## Predefined zoom values
pdfjs-page-scale-width = Sivun leveys
pdfjs-page-scale-fit = Koko sivu
pdfjs-page-scale-auto = Automaattinen suurennus
pdfjs-page-scale-actual = Todellinen koko
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale } %
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Sivu { $page }
## Loading indicator messages
pdfjs-loading-error = Tapahtui virhe ladattaessa PDF-tiedostoa.
pdfjs-invalid-file-error = Virheellinen tai vioittunut PDF-tiedosto.
pdfjs-missing-file-error = Puuttuva PDF-tiedosto.
pdfjs-unexpected-response-error = Odottamaton vastaus palvelimelta.
pdfjs-rendering-error = Tapahtui virhe piirrettäessä sivua.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type }-merkintä]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Kirjoita PDF-tiedoston salasana.
pdfjs-password-invalid = Virheellinen salasana. Yritä uudestaan.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Peruuta
pdfjs-web-fonts-disabled = Verkkosivujen omat kirjasinlajit on estetty: ei voida käyttää upotettuja PDF-kirjasinlajeja.
## Editing
pdfjs-editor-free-text-button =
.title = Teksti
pdfjs-editor-free-text-button-label = Teksti
pdfjs-editor-ink-button =
.title = Piirros
pdfjs-editor-ink-button-label = Piirros
pdfjs-editor-stamp-button =
.title = Lisää tai muokkaa kuvia
pdfjs-editor-stamp-button-label = Lisää tai muokkaa kuvia
pdfjs-editor-highlight-button =
.title = Korostus
pdfjs-editor-highlight-button-label = Korostus
pdfjs-highlight-floating-button1 =
.title = Korostus
.aria-label = Korostus
pdfjs-highlight-floating-button-label = Korostus
pdfjs-editor-signature-button =
.title = Lisää allekirjoitus
pdfjs-editor-signature-button-label = Lisää allekirjoitus
## Default editor aria labels
# “Highlight” is a noun, the string is used on the editor for highlights.
pdfjs-editor-highlight-editor =
.aria-label = Korostusmuokkain
# “Drawing” is a noun, the string is used on the editor for drawings.
pdfjs-editor-ink-editor =
.aria-label = Piirustusmuokkain
pdfjs-editor-signature-editor =
.aria-label = Allekirjoitusmuokkain
pdfjs-editor-stamp-editor =
.aria-label = Kuvamuokkain
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Poista piirros
pdfjs-editor-remove-freetext-button =
.title = Poista teksti
pdfjs-editor-remove-stamp-button =
.title = Poista kuva
pdfjs-editor-remove-highlight-button =
.title = Poista korostus
pdfjs-editor-remove-signature-button =
.title = Poista allekirjoitus
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Väri
pdfjs-editor-free-text-size-input = Koko
pdfjs-editor-ink-color-input = Väri
pdfjs-editor-ink-thickness-input = Paksuus
pdfjs-editor-ink-opacity-input = Peittävyys
pdfjs-editor-stamp-add-image-button =
.title = Lisää kuva
pdfjs-editor-stamp-add-image-button-label = Lisää kuva
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Paksuus
pdfjs-editor-free-highlight-thickness-title =
.title = Muuta paksuutta korostaessasi muita kohteita kuin tekstiä
pdfjs-editor-add-signature-container =
.aria-label = Allekirjoitussäätimet ja tallennetut allekirjoitukset
pdfjs-editor-signature-add-signature-button =
.title = Lisää uusi allekirjoitus
pdfjs-editor-signature-add-signature-button-label = Lisää uusi allekirjoitus
# Used on the button to use an already saved signature.
# Variables:
# $description (String) - a string describing/labeling the signature.
pdfjs-editor-add-saved-signature-button =
.title = Tallennettu allekirjoitus: { $description }
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Tekstimuokkain
.default-content = Aloita kirjoittaminen…
pdfjs-free-text =
.aria-label = Tekstimuokkain
pdfjs-free-text-default-content = Aloita kirjoittaminen…
pdfjs-ink =
.aria-label = Piirrustusmuokkain
pdfjs-ink-canvas =
.aria-label = Käyttäjän luoma kuva
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Vaihtoehtoinen teksti
pdfjs-editor-alt-text-edit-button =
.aria-label = Muokkaa vaihtoehtoista tekstiä
pdfjs-editor-alt-text-edit-button-label = Muokkaa vaihtoehtoista tekstiä
pdfjs-editor-alt-text-dialog-label = Valitse vaihtoehto
pdfjs-editor-alt-text-dialog-description = Vaihtoehtoinen teksti ("alt-teksti") auttaa ihmisiä, jotka eivät näe kuvaa tai kun kuva ei lataudu.
pdfjs-editor-alt-text-add-description-label = Lisää kuvaus
pdfjs-editor-alt-text-add-description-description = Pyri 1-2 lauseeseen, jotka kuvaavat aihetta, ympäristöä tai toimintaa.
pdfjs-editor-alt-text-mark-decorative-label = Merkitse koristeelliseksi
pdfjs-editor-alt-text-mark-decorative-description = Tätä käytetään koristekuville, kuten reunuksille tai vesileimoille.
pdfjs-editor-alt-text-cancel-button = Peruuta
pdfjs-editor-alt-text-save-button = Tallenna
pdfjs-editor-alt-text-decorative-tooltip = Merkitty koristeelliseksi
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Esimerkiksi "Nuori mies istuu pöytään syömään aterian"
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Vaihtoehtoinen teksti
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Vasen yläkulma - muuta kokoa
pdfjs-editor-resizer-label-top-middle = Ylhäällä keskellä - muuta kokoa
pdfjs-editor-resizer-label-top-right = Oikea yläkulma - muuta kokoa
pdfjs-editor-resizer-label-middle-right = Keskellä oikealla - muuta kokoa
pdfjs-editor-resizer-label-bottom-right = Oikea alakulma - muuta kokoa
pdfjs-editor-resizer-label-bottom-middle = Alhaalla keskellä - muuta kokoa
pdfjs-editor-resizer-label-bottom-left = Vasen alakulma - muuta kokoa
pdfjs-editor-resizer-label-middle-left = Keskellä vasemmalla - muuta kokoa
pdfjs-editor-resizer-top-left =
.aria-label = Vasen yläkulma - muuta kokoa
pdfjs-editor-resizer-top-middle =
.aria-label = Ylhäällä keskellä - muuta kokoa
pdfjs-editor-resizer-top-right =
.aria-label = Oikea yläkulma - muuta kokoa
pdfjs-editor-resizer-middle-right =
.aria-label = Keskellä oikealla - muuta kokoa
pdfjs-editor-resizer-bottom-right =
.aria-label = Oikea alakulma - muuta kokoa
pdfjs-editor-resizer-bottom-middle =
.aria-label = Alhaalla keskellä - muuta kokoa
pdfjs-editor-resizer-bottom-left =
.aria-label = Vasen alakulma - muuta kokoa
pdfjs-editor-resizer-middle-left =
.aria-label = Keskellä vasemmalla - muuta kokoa
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Korostusväri
pdfjs-editor-colorpicker-button =
.title = Vaihda väri
pdfjs-editor-colorpicker-dropdown =
.aria-label = Värivalinnat
pdfjs-editor-colorpicker-yellow =
.title = Keltainen
pdfjs-editor-colorpicker-green =
.title = Vihreä
pdfjs-editor-colorpicker-blue =
.title = Sininen
pdfjs-editor-colorpicker-pink =
.title = Pinkki
pdfjs-editor-colorpicker-red =
.title = Punainen
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Näytä kaikki
pdfjs-editor-highlight-show-all-button =
.title = Näytä kaikki
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Muokkaa vaihtoehtoista tekstiä (kuvan kuvaus)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Lisää vaihtoehtoinen teksti (kuvan kuvaus)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Kirjoita kuvaus tähän…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Lyhyt kuvaus ihmisille, jotka eivät näe kuvaa tai kun kuva ei lataudu.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Tämä vaihtoehtoinen teksti luotiin automaattisesti, ja se voi olla epätarkka.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Lue lisää
pdfjs-editor-new-alt-text-create-automatically-button-label = Luo vaihtoehtoinen teksti automaattisesti
pdfjs-editor-new-alt-text-not-now-button = Ei nyt
pdfjs-editor-new-alt-text-error-title = Vaihtoehtotekstiä ei voitu luoda automaattisesti
pdfjs-editor-new-alt-text-error-description = Kirjoita oma vaihtoehtoinen teksti tai yritä myöhemmin uudelleen.
pdfjs-editor-new-alt-text-error-close-button = Sulje
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Ladataan vaihtoehtoisen tekstin tekoälymallia ({ $downloadedSize } / { $totalSize } Mt)
.aria-valuetext = Ladataan vaihtoehtoisen tekstin tekoälymallia ({ $downloadedSize } / { $totalSize } Mt)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Vaihtoehtoinen teksti lisätty
pdfjs-editor-new-alt-text-added-button-label = Vaihtoehtoinen teksti lisätty
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Vaihtoehtoinen teksti puuttuu
pdfjs-editor-new-alt-text-missing-button-label = Vaihtoehtoinen teksti puuttuu
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Tarkista vaihtoehtoinen teksti
pdfjs-editor-new-alt-text-to-review-button-label = Tarkista vaihtoehtoinen teksti
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Luotu automaattisesti: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Kuvan vaihtoehtoisen tekstin asetukset
pdfjs-image-alt-text-settings-button-label = Kuvan vaihtoehtoisen tekstin asetukset
pdfjs-editor-alt-text-settings-dialog-label = Kuvan vaihtoehtoisen tekstin asetukset
pdfjs-editor-alt-text-settings-automatic-title = Automaattinen vaihtoehtoinen teksti
pdfjs-editor-alt-text-settings-create-model-button-label = Luo vaihtoehtoinen teksti automaattisesti
pdfjs-editor-alt-text-settings-create-model-description = Ehdottaa kuvauksia, jotka auttavat ihmisiä, jotka eivät näe kuvaa tai kun kuva ei lataudu.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Vaihtoehtoisen tekstin tekoälymalli ({ $totalSize } Mt)
pdfjs-editor-alt-text-settings-ai-model-description = Toimii paikallisesti laitteellasi, joten tietosi pysyvät yksityisinä. Vaadittu automaattiselle vaihtoehtoiselle tekstille.
pdfjs-editor-alt-text-settings-delete-model-button = Poista
pdfjs-editor-alt-text-settings-download-model-button = Lataa
pdfjs-editor-alt-text-settings-downloading-model-button = Ladataan…
pdfjs-editor-alt-text-settings-editor-title = Vaihtoehtoisen tekstin muokkain
pdfjs-editor-alt-text-settings-show-dialog-button-label = Näytä vaihtoehtoisen tekstin muokkain heti, kun lisäät kuvan
pdfjs-editor-alt-text-settings-show-dialog-description = Auttaa varmistamaan, että kaikissa kuvissasi on vaihtoehtoinen teksti.
pdfjs-editor-alt-text-settings-close-button = Sulje
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Korostus poistettu
pdfjs-editor-undo-bar-message-freetext = Teksti poistettu
pdfjs-editor-undo-bar-message-ink = Piirustus poistettu
pdfjs-editor-undo-bar-message-stamp = Kuva poistettu
pdfjs-editor-undo-bar-message-signature = Allekirjoitus poistettu
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } merkintä poistettu
*[other] { $count } merkintää poistettu
}
pdfjs-editor-undo-bar-undo-button =
.title = Kumoa
pdfjs-editor-undo-bar-undo-button-label = Kumoa
pdfjs-editor-undo-bar-close-button =
.title = Sulje
pdfjs-editor-undo-bar-close-button-label = Sulje
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = Tämän ikkunan avulla käyttäjä voi luoda allekirjoituksen PDF-asiakirjaan lisättäväksi. Käyttäjä voi muokata nimeä (joka toimii myös vaihtoehtoisena tekstinä) ja valinnaisesti tallentaa allekirjoituksen toistuvaa käyttöä varten.
pdfjs-editor-add-signature-dialog-title = Lisää allekirjoitus
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Kirjoita
.title = Kirjoita
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Piirrä
.title = Piirrä
pdfjs-editor-add-signature-image-button = Kuva
.title = Kuva
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Kirjoita allekirjoituksesi
.placeholder = Kirjoita allekirjoituksesi
pdfjs-editor-add-signature-draw-placeholder = Piirrä allekirjoituksesi
pdfjs-editor-add-signature-draw-thickness-range-label = Paksuus
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Piirustuksen paksuus: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Lähetä tiedosto vetämällä se tähän
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Tai selaa kuvatiedostoja
*[other] Tai selaa kuvatiedostoja
}
## Controls
pdfjs-editor-add-signature-description-label = Kuvaus (vaihtoehtoinen teksti)
pdfjs-editor-add-signature-description-input =
.title = Kuvaus (vaihtoehtoinen teksti)
pdfjs-editor-add-signature-description-default-when-drawing = Allekirjoitus
pdfjs-editor-add-signature-clear-button-label = Tyhjennä allekirjoitus
pdfjs-editor-add-signature-clear-button =
.title = Tyhjennä allekirjoitus
pdfjs-editor-add-signature-save-checkbox = Tallenna allekirjoitus
pdfjs-editor-add-signature-save-warning-message = Olet saavuttanut viiden tallennetun allekirjoituksen rajan. Poista yksi säästääksesi lisää.
pdfjs-editor-add-signature-image-upload-error-title = Kuvaa ei voitu lähettää
pdfjs-editor-add-signature-image-upload-error-description = Tarkista verkkoyhteyden tila tai kokeile toista kuvaa.
pdfjs-editor-add-signature-error-close-button = Sulje
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Peruuta
pdfjs-editor-add-signature-add-button = Lisää
pdfjs-editor-edit-signature-update-button = Päivitä
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Poista allekirjoitus
pdfjs-editor-delete-signature-button-label = Poista allekirjoitus
pdfjs-editor-delete-signature-button1 =
.title = Poista tallennettu allekirjoitus
pdfjs-editor-delete-signature-button-label1 = Poista tallennettu allekirjoitus
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Muokkaa kuvausta
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Muokkaa kuvausta
================================================
FILE: cookbook/static/pdfjs/web/locale/fr/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Page précédente
pdfjs-previous-button-label = Précédent
pdfjs-next-button =
.title = Page suivante
pdfjs-next-button-label = Suivant
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Page
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = sur { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } sur { $pagesCount })
pdfjs-zoom-out-button =
.title = Zoom arrière
pdfjs-zoom-out-button-label = Zoom arrière
pdfjs-zoom-in-button =
.title = Zoom avant
pdfjs-zoom-in-button-label = Zoom avant
pdfjs-zoom-select =
.title = Zoom
pdfjs-presentation-mode-button =
.title = Basculer en mode présentation
pdfjs-presentation-mode-button-label = Mode présentation
pdfjs-open-file-button =
.title = Ouvrir le fichier
pdfjs-open-file-button-label = Ouvrir le fichier
pdfjs-print-button =
.title = Imprimer
pdfjs-print-button-label = Imprimer
pdfjs-save-button =
.title = Enregistrer
pdfjs-save-button-label = Enregistrer
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Télécharger
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Télécharger
pdfjs-bookmark-button =
.title = Page courante (montrer l’adresse de la page courante)
pdfjs-bookmark-button-label = Page courante
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Outils
pdfjs-tools-button-label = Outils
pdfjs-first-page-button =
.title = Aller à la première page
pdfjs-first-page-button-label = Aller à la première page
pdfjs-last-page-button =
.title = Aller à la dernière page
pdfjs-last-page-button-label = Aller à la dernière page
pdfjs-page-rotate-cw-button =
.title = Rotation horaire
pdfjs-page-rotate-cw-button-label = Rotation horaire
pdfjs-page-rotate-ccw-button =
.title = Rotation antihoraire
pdfjs-page-rotate-ccw-button-label = Rotation antihoraire
pdfjs-cursor-text-select-tool-button =
.title = Activer l’outil de sélection de texte
pdfjs-cursor-text-select-tool-button-label = Outil de sélection de texte
pdfjs-cursor-hand-tool-button =
.title = Activer l’outil main
pdfjs-cursor-hand-tool-button-label = Outil main
pdfjs-scroll-page-button =
.title = Utiliser le défilement par page
pdfjs-scroll-page-button-label = Défilement par page
pdfjs-scroll-vertical-button =
.title = Utiliser le défilement vertical
pdfjs-scroll-vertical-button-label = Défilement vertical
pdfjs-scroll-horizontal-button =
.title = Utiliser le défilement horizontal
pdfjs-scroll-horizontal-button-label = Défilement horizontal
pdfjs-scroll-wrapped-button =
.title = Utiliser le défilement par bloc
pdfjs-scroll-wrapped-button-label = Défilement par bloc
pdfjs-spread-none-button =
.title = Ne pas afficher les pages deux à deux
pdfjs-spread-none-button-label = Pas de double affichage
pdfjs-spread-odd-button =
.title = Afficher les pages par deux, impaires à gauche
pdfjs-spread-odd-button-label = Doubles pages, impaires à gauche
pdfjs-spread-even-button =
.title = Afficher les pages par deux, paires à gauche
pdfjs-spread-even-button-label = Doubles pages, paires à gauche
## Document properties dialog
pdfjs-document-properties-button =
.title = Propriétés du document…
pdfjs-document-properties-button-label = Propriétés du document…
pdfjs-document-properties-file-name = Nom du fichier :
pdfjs-document-properties-file-size = Taille du fichier :
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } Ko ({ $b } octets)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } Mo ({ $b } octets)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } Ko ({ $size_b } octets)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } Mo ({ $size_b } octets)
pdfjs-document-properties-title = Titre :
pdfjs-document-properties-author = Auteur :
pdfjs-document-properties-subject = Sujet :
pdfjs-document-properties-keywords = Mots-clés :
pdfjs-document-properties-creation-date = Date de création :
pdfjs-document-properties-modification-date = Modifié le :
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date } à { $time }
pdfjs-document-properties-creator = Créé par :
pdfjs-document-properties-producer = Outil de conversion PDF :
pdfjs-document-properties-version = Version PDF :
pdfjs-document-properties-page-count = Nombre de pages :
pdfjs-document-properties-page-size = Taille de la page :
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = portrait
pdfjs-document-properties-page-size-orientation-landscape = paysage
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = lettre
pdfjs-document-properties-page-size-name-legal = document juridique
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Affichage rapide des pages web :
pdfjs-document-properties-linearized-yes = Oui
pdfjs-document-properties-linearized-no = Non
pdfjs-document-properties-close-button = Fermer
## Print
pdfjs-print-progress-message = Préparation du document pour l’impression…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress } %
pdfjs-print-progress-close-button = Annuler
pdfjs-printing-not-supported = Attention : l’impression n’est pas totalement prise en charge par ce navigateur.
pdfjs-printing-not-ready = Attention : le PDF n’est pas entièrement chargé pour pouvoir l’imprimer.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Afficher/Masquer le panneau latéral
pdfjs-toggle-sidebar-notification-button =
.title = Afficher/Masquer le panneau latéral (le document contient des signets/pièces jointes/calques)
pdfjs-toggle-sidebar-button-label = Afficher/Masquer le panneau latéral
pdfjs-document-outline-button =
.title = Afficher les signets du document (double-cliquer pour développer/réduire tous les éléments)
pdfjs-document-outline-button-label = Signets du document
pdfjs-attachments-button =
.title = Afficher les pièces jointes
pdfjs-attachments-button-label = Pièces jointes
pdfjs-layers-button =
.title = Afficher les calques (double-cliquer pour réinitialiser tous les calques à l’état par défaut)
pdfjs-layers-button-label = Calques
pdfjs-thumbs-button =
.title = Afficher les vignettes
pdfjs-thumbs-button-label = Vignettes
pdfjs-current-outline-item-button =
.title = Trouver l’élément de plan actuel
pdfjs-current-outline-item-button-label = Élément de plan actuel
pdfjs-findbar-button =
.title = Rechercher dans le document
pdfjs-findbar-button-label = Rechercher
pdfjs-additional-layers = Calques additionnels
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Page { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Vignette de la page { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Rechercher
.placeholder = Rechercher dans le document…
pdfjs-find-previous-button =
.title = Trouver l’occurrence précédente de l’expression
pdfjs-find-previous-button-label = Précédent
pdfjs-find-next-button =
.title = Trouver la prochaine occurrence de l’expression
pdfjs-find-next-button-label = Suivant
pdfjs-find-highlight-checkbox = Tout surligner
pdfjs-find-match-case-checkbox-label = Respecter la casse
pdfjs-find-match-diacritics-checkbox-label = Respecter les accents et diacritiques
pdfjs-find-entire-word-checkbox-label = Mots entiers
pdfjs-find-reached-top = Haut de la page atteint, poursuite depuis la fin
pdfjs-find-reached-bottom = Bas de la page atteint, poursuite au début
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count = Occurrence { $current } sur { $total }
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Plus d’{ $limit } occurrence
*[other] Plus de { $limit } occurrences
}
pdfjs-find-not-found = Expression non trouvée
## Predefined zoom values
pdfjs-page-scale-width = Pleine largeur
pdfjs-page-scale-fit = Page entière
pdfjs-page-scale-auto = Zoom automatique
pdfjs-page-scale-actual = Taille réelle
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale } %
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Page { $page }
## Loading indicator messages
pdfjs-loading-error = Une erreur s’est produite lors du chargement du fichier PDF.
pdfjs-invalid-file-error = Fichier PDF invalide ou corrompu.
pdfjs-missing-file-error = Fichier PDF manquant.
pdfjs-unexpected-response-error = Réponse inattendue du serveur.
pdfjs-rendering-error = Une erreur s’est produite lors de l’affichage de la page.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date } à { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [Annotation { $type }]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Veuillez saisir le mot de passe pour ouvrir ce fichier PDF.
pdfjs-password-invalid = Mot de passe incorrect. Veuillez réessayer.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Annuler
pdfjs-web-fonts-disabled = Les polices web sont désactivées : impossible d’utiliser les polices intégrées au PDF.
## Editing
pdfjs-editor-free-text-button =
.title = Texte
pdfjs-editor-free-text-button-label = Texte
pdfjs-editor-ink-button =
.title = Dessiner
pdfjs-editor-ink-button-label = Dessiner
pdfjs-editor-stamp-button =
.title = Ajouter ou modifier des images
pdfjs-editor-stamp-button-label = Ajouter ou modifier des images
pdfjs-editor-highlight-button =
.title = Surligner
pdfjs-editor-highlight-button-label = Surligner
pdfjs-highlight-floating-button1 =
.title = Surligner
.aria-label = Surligner
pdfjs-highlight-floating-button-label = Surligner
pdfjs-editor-signature-button =
.title = Ajouter une signature
pdfjs-editor-signature-button-label = Ajouter une signature
## Default editor aria labels
# “Highlight” is a noun, the string is used on the editor for highlights.
pdfjs-editor-highlight-editor =
.aria-label = Éditeur de surlignage
# “Drawing” is a noun, the string is used on the editor for drawings.
pdfjs-editor-ink-editor =
.aria-label = Éditeur de dessins
pdfjs-editor-signature-editor =
.aria-label = Éditeur de signatures
pdfjs-editor-stamp-editor =
.aria-label = Éditeur d’images
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Supprimer le dessin
pdfjs-editor-remove-freetext-button =
.title = Supprimer le texte
pdfjs-editor-remove-stamp-button =
.title = Supprimer l’image
pdfjs-editor-remove-highlight-button =
.title = Supprimer le surlignage
pdfjs-editor-remove-signature-button =
.title = Retirer la signature
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Couleur
pdfjs-editor-free-text-size-input = Taille
pdfjs-editor-ink-color-input = Couleur
pdfjs-editor-ink-thickness-input = Épaisseur
pdfjs-editor-ink-opacity-input = Opacité
pdfjs-editor-stamp-add-image-button =
.title = Ajouter une image
pdfjs-editor-stamp-add-image-button-label = Ajouter une image
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Épaisseur
pdfjs-editor-free-highlight-thickness-title =
.title = Modifier l’épaisseur pour le surlignage d’éléments non textuels
pdfjs-editor-add-signature-container =
.aria-label = Contrôles de signature et signatures enregistrées
pdfjs-editor-signature-add-signature-button =
.title = Ajouter une nouvelle signature
pdfjs-editor-signature-add-signature-button-label = Ajouter une nouvelle signature
# Used on the button to use an already saved signature.
# Variables:
# $description (String) - a string describing/labeling the signature.
pdfjs-editor-add-saved-signature-button =
.title = Signature enregistrée : { $description }
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Éditeur de texte
.default-content = Commencez à écrire…
pdfjs-free-text =
.aria-label = Éditeur de texte
pdfjs-free-text-default-content = Commencer à écrire…
pdfjs-ink =
.aria-label = Éditeur de dessin
pdfjs-ink-canvas =
.aria-label = Image créée par l’utilisateur·trice
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Texte alternatif
pdfjs-editor-alt-text-edit-button =
.aria-label = Modifier le texte alternatif
pdfjs-editor-alt-text-edit-button-label = Modifier le texte alternatif
pdfjs-editor-alt-text-dialog-label = Sélectionnez une option
pdfjs-editor-alt-text-dialog-description = Le texte alternatif est utile lorsque des personnes ne peuvent pas voir l’image ou que l’image ne se charge pas.
pdfjs-editor-alt-text-add-description-label = Ajouter une description
pdfjs-editor-alt-text-add-description-description = Il est conseillé de rédiger une ou deux phrases décrivant le sujet, le cadre ou les actions.
pdfjs-editor-alt-text-mark-decorative-label = Marquer comme décorative
pdfjs-editor-alt-text-mark-decorative-description = Cette option est utilisée pour les images décoratives, comme les bordures ou les filigranes.
pdfjs-editor-alt-text-cancel-button = Annuler
pdfjs-editor-alt-text-save-button = Enregistrer
pdfjs-editor-alt-text-decorative-tooltip = Marquée comme décorative
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Par exemple, « Un jeune homme est assis à une table pour prendre un repas »
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Texte alternatif
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Coin supérieur gauche — redimensionner
pdfjs-editor-resizer-label-top-middle = Milieu haut — redimensionner
pdfjs-editor-resizer-label-top-right = Coin supérieur droit — redimensionner
pdfjs-editor-resizer-label-middle-right = Milieu droit — redimensionner
pdfjs-editor-resizer-label-bottom-right = Coin inférieur droit — redimensionner
pdfjs-editor-resizer-label-bottom-middle = Centre bas — redimensionner
pdfjs-editor-resizer-label-bottom-left = Coin inférieur gauche — redimensionner
pdfjs-editor-resizer-label-middle-left = Milieu gauche — redimensionner
pdfjs-editor-resizer-top-left =
.aria-label = Coin supérieur gauche — redimensionner
pdfjs-editor-resizer-top-middle =
.aria-label = Milieu haut — redimensionner
pdfjs-editor-resizer-top-right =
.aria-label = Coin supérieur droit — redimensionner
pdfjs-editor-resizer-middle-right =
.aria-label = Milieu droit — redimensionner
pdfjs-editor-resizer-bottom-right =
.aria-label = Coin inférieur droit — redimensionner
pdfjs-editor-resizer-bottom-middle =
.aria-label = Centre bas — redimensionner
pdfjs-editor-resizer-bottom-left =
.aria-label = Coin inférieur gauche — redimensionner
pdfjs-editor-resizer-middle-left =
.aria-label = Milieu gauche — redimensionner
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Couleur de surlignage
pdfjs-editor-colorpicker-button =
.title = Changer de couleur
pdfjs-editor-colorpicker-dropdown =
.aria-label = Choix de couleurs
pdfjs-editor-colorpicker-yellow =
.title = Jaune
pdfjs-editor-colorpicker-green =
.title = Vert
pdfjs-editor-colorpicker-blue =
.title = Bleu
pdfjs-editor-colorpicker-pink =
.title = Rose
pdfjs-editor-colorpicker-red =
.title = Rouge
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Tout afficher
pdfjs-editor-highlight-show-all-button =
.title = Tout afficher
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Modifier le texte alternatif (description de l’image)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Ajouter du texte alternatif (description de l’image)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Rédigez votre description ici…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Courte description pour les personnes qui ne peuvent pas voir l’image ou lorsque l’image ne se charge pas.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Ce texte alternatif a été créé automatiquement et peut être inexact.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = En savoir plus
pdfjs-editor-new-alt-text-create-automatically-button-label = Créer automatiquement le texte alternatif
pdfjs-editor-new-alt-text-not-now-button = Pas maintenant
pdfjs-editor-new-alt-text-error-title = Impossible de créer automatiquement le texte alternatif
pdfjs-editor-new-alt-text-error-description = Veuillez rédiger votre propre texte alternatif ou réessayer plus tard.
pdfjs-editor-new-alt-text-error-close-button = Fermer
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Téléchargement du modèle d’IA de texte alternatif ({ $downloadedSize } sur { $totalSize } Mo)
.aria-valuetext = Téléchargement du modèle d’IA de texte alternatif ({ $downloadedSize } sur { $totalSize } Mo)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Texte alternatif ajouté
pdfjs-editor-new-alt-text-added-button-label = Texte alternatif ajouté
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Texte alternatif manquant
pdfjs-editor-new-alt-text-missing-button-label = Texte alternatif manquant
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Réviser le texte alternatif
pdfjs-editor-new-alt-text-to-review-button-label = Réviser le texte alternatif
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Créé automatiquement : { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Paramètres du texte alternatif des images
pdfjs-image-alt-text-settings-button-label = Paramètres du texte alternatif des images
pdfjs-editor-alt-text-settings-dialog-label = Paramètres du texte alternatif des images
pdfjs-editor-alt-text-settings-automatic-title = Texte alternatif automatique
pdfjs-editor-alt-text-settings-create-model-button-label = Créer automatiquement le texte alternatif
pdfjs-editor-alt-text-settings-create-model-description = Suggère des descriptions pour aider les personnes qui ne peuvent pas voir l’image ou lorsque l’image ne se charge pas.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Modèle d’IA de texte alternatif ({ $totalSize } Mo)
pdfjs-editor-alt-text-settings-ai-model-description = Fonctionne localement sur votre appareil, vos données restent privées. Obligatoire pour la génération automatique de texte alternatif.
pdfjs-editor-alt-text-settings-delete-model-button = Supprimer
pdfjs-editor-alt-text-settings-download-model-button = Télécharger
pdfjs-editor-alt-text-settings-downloading-model-button = Téléchargement…
pdfjs-editor-alt-text-settings-editor-title = Éditeur de texte alternatif
pdfjs-editor-alt-text-settings-show-dialog-button-label = Afficher l’éditeur de texte alternatif immédiatement lors de l’ajout d’une image
pdfjs-editor-alt-text-settings-show-dialog-description = Vous aide à vous assurer que toutes vos images ont du texte alternatif.
pdfjs-editor-alt-text-settings-close-button = Fermer
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Surlignage supprimé
pdfjs-editor-undo-bar-message-freetext = Texte supprimé
pdfjs-editor-undo-bar-message-ink = Dessin supprimé
pdfjs-editor-undo-bar-message-stamp = Image supprimée
pdfjs-editor-undo-bar-message-signature = Signature retirée
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } annotation supprimée
*[other] { $count } annotations supprimées
}
pdfjs-editor-undo-bar-undo-button =
.title = Annuler
pdfjs-editor-undo-bar-undo-button-label = Annuler
pdfjs-editor-undo-bar-close-button =
.title = Fermer
pdfjs-editor-undo-bar-close-button-label = Fermer
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = Cette fenêtre permet de créer une signature à ajouter à un document au format PDF. Il est possible d’en modifier le nom (qui sert également de texte alternatif) et, éventuellement, de l’enregistrer pour une utilisation répétée.
pdfjs-editor-add-signature-dialog-title = Ajout d’une signature
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Saisir
.title = Saisir au clavier
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Dessiner
.title = Dessiner
pdfjs-editor-add-signature-image-button = Image
.title = Image
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Saisissez votre signature
.placeholder = Saisissez votre signature
pdfjs-editor-add-signature-draw-placeholder = Tracez votre signature
pdfjs-editor-add-signature-draw-thickness-range-label = Épaisseur
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Épaisseur du trait : { $thickness }
pdfjs-editor-add-signature-image-placeholder = Déposez un fichier ici pour l’envoyer
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Ou choisissez parmi les fichiers image
*[other] Ou parcourez les fichiers image
}
## Controls
pdfjs-editor-add-signature-description-label = Description (texte alternatif)
pdfjs-editor-add-signature-description-input =
.title = Description (texte alternatif)
pdfjs-editor-add-signature-description-default-when-drawing = Signature
pdfjs-editor-add-signature-clear-button-label = Effacer la signature
pdfjs-editor-add-signature-clear-button =
.title = Effacer la signature
pdfjs-editor-add-signature-save-checkbox = Enregistrer la signature
pdfjs-editor-add-signature-save-warning-message = Vous avez atteint la limite de 5 signatures enregistrées. Supprimez-en une pour en enregistrer une autre.
pdfjs-editor-add-signature-image-upload-error-title = Impossible d’envoyer l’image
pdfjs-editor-add-signature-image-upload-error-description = Vérifiez votre connexion réseau ou essayez avec une autre image.
pdfjs-editor-add-signature-error-close-button = Fermer
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Annuler
pdfjs-editor-add-signature-add-button = Ajouter
pdfjs-editor-edit-signature-update-button = Mettre à jour
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Supprimer la signature
pdfjs-editor-delete-signature-button-label = Supprimer la signature
pdfjs-editor-delete-signature-button1 =
.title = Supprimer la signature enregistrée
pdfjs-editor-delete-signature-button-label1 = Supprimer la signature enregistrée
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Modifier la description
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Modifier la description
================================================
FILE: cookbook/static/pdfjs/web/locale/fur/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Pagjine di prime
pdfjs-previous-button-label = Indaûr
pdfjs-next-button =
.title = Prossime pagjine
pdfjs-next-button-label = Indevant
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Pagjine
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = di { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } di { $pagesCount })
pdfjs-zoom-out-button =
.title = Impiçulìs
pdfjs-zoom-out-button-label = Impiçulìs
pdfjs-zoom-in-button =
.title = Ingrandìs
pdfjs-zoom-in-button-label = Ingrandìs
pdfjs-zoom-select =
.title = Ingrandiment
pdfjs-presentation-mode-button =
.title = Passe ae modalitât presentazion
pdfjs-presentation-mode-button-label = Modalitât presentazion
pdfjs-open-file-button =
.title = Vierç un file
pdfjs-open-file-button-label = Vierç
pdfjs-print-button =
.title = Stampe
pdfjs-print-button-label = Stampe
pdfjs-save-button =
.title = Salve
pdfjs-save-button-label = Salve
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Discjame
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Discjame
pdfjs-bookmark-button =
.title = Pagjine corinte (mostre URL de pagjine atuâl)
pdfjs-bookmark-button-label = Pagjine corinte
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Struments
pdfjs-tools-button-label = Struments
pdfjs-first-page-button =
.title = Va ae prime pagjine
pdfjs-first-page-button-label = Va ae prime pagjine
pdfjs-last-page-button =
.title = Va ae ultime pagjine
pdfjs-last-page-button-label = Va ae ultime pagjine
pdfjs-page-rotate-cw-button =
.title = Zire in sens orari
pdfjs-page-rotate-cw-button-label = Zire in sens orari
pdfjs-page-rotate-ccw-button =
.title = Zire in sens antiorari
pdfjs-page-rotate-ccw-button-label = Zire in sens antiorari
pdfjs-cursor-text-select-tool-button =
.title = Ative il strument di selezion dal test
pdfjs-cursor-text-select-tool-button-label = Strument di selezion dal test
pdfjs-cursor-hand-tool-button =
.title = Ative il strument manute
pdfjs-cursor-hand-tool-button-label = Strument manute
pdfjs-scroll-page-button =
.title = Dopre il scoriment des pagjinis
pdfjs-scroll-page-button-label = Scoriment pagjinis
pdfjs-scroll-vertical-button =
.title = Dopre scoriment verticâl
pdfjs-scroll-vertical-button-label = Scoriment verticâl
pdfjs-scroll-horizontal-button =
.title = Dopre scoriment orizontâl
pdfjs-scroll-horizontal-button-label = Scoriment orizontâl
pdfjs-scroll-wrapped-button =
.title = Dopre scoriment par blocs
pdfjs-scroll-wrapped-button-label = Scoriment par blocs
pdfjs-spread-none-button =
.title = No sta meti dongje pagjinis in cubie
pdfjs-spread-none-button-label = No cubiis di pagjinis
pdfjs-spread-odd-button =
.title = Met dongje cubiis di pagjinis scomençant des pagjinis dispar
pdfjs-spread-odd-button-label = Cubiis di pagjinis, dispar a çampe
pdfjs-spread-even-button =
.title = Met dongje cubiis di pagjinis scomençant des pagjinis pâr
pdfjs-spread-even-button-label = Cubiis di pagjinis, pâr a çampe
## Document properties dialog
pdfjs-document-properties-button =
.title = Proprietâts dal document…
pdfjs-document-properties-button-label = Proprietâts dal document…
pdfjs-document-properties-file-name = Non dal file:
pdfjs-document-properties-file-size = Dimension dal file:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = Titul:
pdfjs-document-properties-author = Autôr:
pdfjs-document-properties-subject = Ogjet:
pdfjs-document-properties-keywords = Peraulis clâf:
pdfjs-document-properties-creation-date = Date di creazion:
pdfjs-document-properties-modification-date = Date di modifiche:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Creatôr
pdfjs-document-properties-producer = Gjeneradôr PDF:
pdfjs-document-properties-version = Version PDF:
pdfjs-document-properties-page-count = Numar di pagjinis:
pdfjs-document-properties-page-size = Dimension de pagjine:
pdfjs-document-properties-page-size-unit-inches = oncis
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = verticâl
pdfjs-document-properties-page-size-orientation-landscape = orizontâl
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letare
pdfjs-document-properties-page-size-name-legal = Legâl
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Visualizazion web svelte:
pdfjs-document-properties-linearized-yes = Sì
pdfjs-document-properties-linearized-no = No
pdfjs-document-properties-close-button = Siere
## Print
pdfjs-print-progress-message = Daûr a prontâ il document pe stampe…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Anule
pdfjs-printing-not-supported = Atenzion: la stampe no je supuartade ad implen di chest navigadôr.
pdfjs-printing-not-ready = Atenzion: il PDF nol è stât cjamât dal dut pe stampe.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Ative/Disative sbare laterâl
pdfjs-toggle-sidebar-notification-button =
.title = Ative/Disative sbare laterâl (il document al conten struture/zontis/strâts)
pdfjs-toggle-sidebar-button-label = Ative/Disative sbare laterâl
pdfjs-document-outline-button =
.title = Mostre la struture dal document (dopli clic par slargjâ/strenzi ducj i elements)
pdfjs-document-outline-button-label = Struture dal document
pdfjs-attachments-button =
.title = Mostre lis zontis
pdfjs-attachments-button-label = Zontis
pdfjs-layers-button =
.title = Mostre i strâts (dopli clic par ristabilî ducj i strâts al stât predefinît)
pdfjs-layers-button-label = Strâts
pdfjs-thumbs-button =
.title = Mostre miniaturis
pdfjs-thumbs-button-label = Miniaturis
pdfjs-current-outline-item-button =
.title = Cjate l'element de struture atuâl
pdfjs-current-outline-item-button-label = Element de struture atuâl
pdfjs-findbar-button =
.title = Cjate tal document
pdfjs-findbar-button-label = Cjate
pdfjs-additional-layers = Strâts adizionâi
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Pagjine { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Miniature de pagjine { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Cjate
.placeholder = Cjate tal document…
pdfjs-find-previous-button =
.title = Cjate il câs precedent dal test
pdfjs-find-previous-button-label = Precedent
pdfjs-find-next-button =
.title = Cjate il câs sucessîf dal test
pdfjs-find-next-button-label = Sucessîf
pdfjs-find-highlight-checkbox = Evidenzie dut
pdfjs-find-match-case-checkbox-label = Fâs distinzion tra maiusculis e minusculis
pdfjs-find-match-diacritics-checkbox-label = Corispondence diacritiche
pdfjs-find-entire-word-checkbox-label = Peraulis interiis
pdfjs-find-reached-top = Si è rivâts al inizi dal document e si à continuât de fin
pdfjs-find-reached-bottom = Si è rivât ae fin dal document e si à continuât dal inizi
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } di { $total } corispondence
*[other] { $current } di { $total } corispondencis
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Plui di { $limit } corispondence
*[other] Plui di { $limit } corispondencis
}
pdfjs-find-not-found = Test no cjatât
## Predefined zoom values
pdfjs-page-scale-width = Largjece de pagjine
pdfjs-page-scale-fit = Pagjine interie
pdfjs-page-scale-auto = Ingrandiment automatic
pdfjs-page-scale-actual = Dimension reâl
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Pagjine { $page }
## Loading indicator messages
pdfjs-loading-error = Al è vignût fûr un erôr intant che si cjariave il PDF.
pdfjs-invalid-file-error = File PDF no valit o ruvinât.
pdfjs-missing-file-error = Al mancje il file PDF.
pdfjs-unexpected-response-error = Rispueste dal servidôr inspietade.
pdfjs-rendering-error = Al è vignût fûr un erôr tal realizâ la visualizazion de pagjine.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [Anotazion { $type }]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Inserìs la password par vierzi chest file PDF.
pdfjs-password-invalid = Password no valide. Par plasê torne prove.
pdfjs-password-ok-button = Va ben
pdfjs-password-cancel-button = Anule
pdfjs-web-fonts-disabled = I caratars dal Web a son disativâts: Impussibil doprâ i caratars PDF incorporâts.
## Editing
pdfjs-editor-free-text-button =
.title = Test
pdfjs-editor-free-text-button-label = Test
pdfjs-editor-ink-button =
.title = Dissen
pdfjs-editor-ink-button-label = Dissen
pdfjs-editor-stamp-button =
.title = Zonte o modifiche imagjins
pdfjs-editor-stamp-button-label = Zonte o modifiche imagjins
pdfjs-editor-highlight-button =
.title = Evidenzie
pdfjs-editor-highlight-button-label = Evidenzie
pdfjs-highlight-floating-button1 =
.title = Evidenzie
.aria-label = Evidenzie
pdfjs-highlight-floating-button-label = Evidenzie
pdfjs-editor-signature-button =
.title = Zonte firme
pdfjs-editor-signature-button-label = Zonte firme
## Default editor aria labels
# “Highlight” is a noun, the string is used on the editor for highlights.
pdfjs-editor-highlight-editor =
.aria-label = Modifiche evidenziazions
# “Drawing” is a noun, the string is used on the editor for drawings.
pdfjs-editor-ink-editor =
.aria-label = Modifiche dissens
pdfjs-editor-signature-editor =
.aria-label = Modifiche firmis
pdfjs-editor-stamp-editor =
.aria-label = Modifiche imagjins
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Gjave dissen
pdfjs-editor-remove-freetext-button =
.title = Gjave test
pdfjs-editor-remove-stamp-button =
.title = Gjave imagjin
pdfjs-editor-remove-highlight-button =
.title = Gjave evidenziazion
pdfjs-editor-remove-signature-button =
.title = Gjave firme
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Colôr
pdfjs-editor-free-text-size-input = Dimension
pdfjs-editor-ink-color-input = Colôr
pdfjs-editor-ink-thickness-input = Spessôr
pdfjs-editor-ink-opacity-input = Opacitât
pdfjs-editor-stamp-add-image-button =
.title = Zonte imagjin
pdfjs-editor-stamp-add-image-button-label = Zonte imagjin
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Spessôr
pdfjs-editor-free-highlight-thickness-title =
.title = Modifiche il spessôr de selezion pai elements che no son testuâi
pdfjs-editor-add-signature-container =
.aria-label = Controi firme e firmis salvadis
pdfjs-editor-signature-add-signature-button =
.title = Zonte gnove firme
pdfjs-editor-signature-add-signature-button-label = Zonte gnove firme
# Used on the button to use an already saved signature.
# Variables:
# $description (String) - a string describing/labeling the signature.
pdfjs-editor-add-saved-signature-button =
.title = Firme salvade: { $description }
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Editôr di test
.default-content = Scomence a scrivi…
pdfjs-free-text =
.aria-label = Editôr di test
pdfjs-free-text-default-content = Scomence a scrivi…
pdfjs-ink =
.aria-label = Editôr dissens
pdfjs-ink-canvas =
.aria-label = Imagjin creade dal utent
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Test alternatîf
pdfjs-editor-alt-text-edit-button =
.aria-label = Modifiche test alternatîf
pdfjs-editor-alt-text-edit-button-label = Modifiche test alternatîf
pdfjs-editor-alt-text-dialog-label = Sielç une opzion
pdfjs-editor-alt-text-dialog-description = Il test alternatîf (“alt text”) al jude cuant che lis personis no puedin viodi la imagjin o cuant che la imagjine no ven cjariade.
pdfjs-editor-alt-text-add-description-label = Zonte une descrizion
pdfjs-editor-alt-text-add-description-description = Ponte a une o dôs frasis che a descrivin l’argoment, la ambientazion o lis azions.
pdfjs-editor-alt-text-mark-decorative-label = Segne come decorative
pdfjs-editor-alt-text-mark-decorative-description = Chest al ven doprât pes imagjins ornamentâls, come i ôrs o lis filigranis.
pdfjs-editor-alt-text-cancel-button = Anule
pdfjs-editor-alt-text-save-button = Salve
pdfjs-editor-alt-text-decorative-tooltip = Segnade come decorative
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Par esempli, “Un zovin si sente a taule par mangjâ”
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Test alternatîf
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Cjanton in alt a çampe — ridimensione
pdfjs-editor-resizer-label-top-middle = Bande superiôr tal mieç — ridimensione
pdfjs-editor-resizer-label-top-right = Cjanton in alt a diestre — ridimensione
pdfjs-editor-resizer-label-middle-right = Bande diestre tal mieç — ridimensione
pdfjs-editor-resizer-label-bottom-right = Cjanton in bas a diestre — ridimensione
pdfjs-editor-resizer-label-bottom-middle = Bande inferiôr tal mieç — ridimensione
pdfjs-editor-resizer-label-bottom-left = Cjanton in bas a çampe — ridimensione
pdfjs-editor-resizer-label-middle-left = Bande di çampe tal mieç — ridimensione
pdfjs-editor-resizer-top-left =
.aria-label = Cjanton in alt a çampe — ridimensione
pdfjs-editor-resizer-top-middle =
.aria-label = Bande superiôr tal mieç — ridimensione
pdfjs-editor-resizer-top-right =
.aria-label = Cjanton in alt a diestre — ridimensione
pdfjs-editor-resizer-middle-right =
.aria-label = Bande diestre tal mieç — ridimensione
pdfjs-editor-resizer-bottom-right =
.aria-label = Cjanton in bas a diestre — ridimensione
pdfjs-editor-resizer-bottom-middle =
.aria-label = Bande inferiôr tal mieç — ridimensione
pdfjs-editor-resizer-bottom-left =
.aria-label = Cjanton in bas a çampe — ridimensione
pdfjs-editor-resizer-middle-left =
.aria-label = Bande di çampe tal mieç — ridimensione
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Colôr par evidenziâ
pdfjs-editor-colorpicker-button =
.title = Cambie colôr
pdfjs-editor-colorpicker-dropdown =
.aria-label = Sieltis di colôr
pdfjs-editor-colorpicker-yellow =
.title = Zâl
pdfjs-editor-colorpicker-green =
.title = Vert
pdfjs-editor-colorpicker-blue =
.title = Blu
pdfjs-editor-colorpicker-pink =
.title = Rose
pdfjs-editor-colorpicker-red =
.title = Ros
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Mostre dut
pdfjs-editor-highlight-show-all-button =
.title = Mostre dut
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Modifiche test alternatîf (descrizion de imagjin)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Zonte test alternatîf (descrizion de imagjin)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Scrîf achì la tô descrizion…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Curte descrizion par personis che no rivin a viodi la imagjin, o che e ven mostrade cuant che no si rive a cjariâle.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Chest test alternatîf al è stât creât in automatic e al è pussibil che nol sedi cret.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Plui informazions
pdfjs-editor-new-alt-text-create-automatically-button-label = Cree test alternatîf in automatic
pdfjs-editor-new-alt-text-not-now-button = No cumò
pdfjs-editor-new-alt-text-error-title = Impussibil creâ test alternatîf in automatic
pdfjs-editor-new-alt-text-error-description = Scrîf il to test alternatîf o prove plui tart.
pdfjs-editor-new-alt-text-error-close-button = Siere
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Daûr a discjariâil model IA pal test alternatîf ({ $downloadedSize } di { $totalSize } MB)
.aria-valuetext = Daûr a discjariâ il model IA pal test alternatîf ({ $downloadedSize } di { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Test alternatîf zontât
pdfjs-editor-new-alt-text-added-button-label = Test alternatîf zontât
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Al mancje il test alternatîf
pdfjs-editor-new-alt-text-missing-button-label = Al mancje il test alternatîf
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Verifiche test alternatîf
pdfjs-editor-new-alt-text-to-review-button-label = Verifiche test alternatîf
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Creât in automatic: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Impostazions test alternatîf pes imagjins
pdfjs-image-alt-text-settings-button-label = Impostazions test alternatîf pes imagjins
pdfjs-editor-alt-text-settings-dialog-label = Impostazions test alternatîf pes imagjins
pdfjs-editor-alt-text-settings-automatic-title = Test alternatîf automatic
pdfjs-editor-alt-text-settings-create-model-button-label = Cree test alternatîf in automatic
pdfjs-editor-alt-text-settings-create-model-description = Al sugjerìs descrizions par judâ lis personis che no rivin a viodi la imagjin o cuant che la imagjin no ven cjariade.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Model IA pal test alternatîf ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Al ven eseguît in locâl sul to dispositîf, cussì che i tiei dâts a restin riservâts. Al è necessari pe gjenerazion automatiche dal test alternatîf.
pdfjs-editor-alt-text-settings-delete-model-button = Elimine
pdfjs-editor-alt-text-settings-download-model-button = Discjame
pdfjs-editor-alt-text-settings-downloading-model-button = Daûr a discjariâ…
pdfjs-editor-alt-text-settings-editor-title = Modifiche test alternatîf
pdfjs-editor-alt-text-settings-show-dialog-button-label = Mostre l'editôr dal test alternatîf a pene che e ven zontade une imagjin
pdfjs-editor-alt-text-settings-show-dialog-description = Ti jude a sigurâti che dutis lis tôs imagjins a vedin il test alternatîf.
pdfjs-editor-alt-text-settings-close-button = Siere
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Evidenziazion gjavade
pdfjs-editor-undo-bar-message-freetext = Test gjavât
pdfjs-editor-undo-bar-message-ink = Dissen gjavât
pdfjs-editor-undo-bar-message-stamp = Imagjin gjavade
pdfjs-editor-undo-bar-message-signature = Firme gjavade
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } note gjavade
*[other] { $count } notis gjavadis
}
pdfjs-editor-undo-bar-undo-button =
.title = Anule
pdfjs-editor-undo-bar-undo-button-label = Anule
pdfjs-editor-undo-bar-close-button =
.title = Siere
pdfjs-editor-undo-bar-close-button-label = Siere
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = Chest barcon al permet al utent di creâ une firme di zontâ a un document PDF. L’utent al pues modificâ il non (che al vignarà doprât ancje come test alternatîf) e, se lu desidere, salvâ la firme par tornâ a doprâle un doman.
pdfjs-editor-add-signature-dialog-title = Zonte une firme
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Scrîf
.title = Scrîf
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Dissegne
.title = Dissegne
pdfjs-editor-add-signature-image-button = Imagjin
.title = Imagjin
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Scrîf la tô firme
.placeholder = Scrîf la tô firme
pdfjs-editor-add-signature-draw-placeholder = Dissegne la tô firme
pdfjs-editor-add-signature-draw-thickness-range-label = Spessôr
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Spessôr de tresse: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Strissine un file achì par cjariâlu
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Opûr sielç i files imagjin
*[other] Opûr sgarfe pai files imagjin
}
## Controls
pdfjs-editor-add-signature-description-label = Descrizion (test alternatîf)
pdfjs-editor-add-signature-description-input =
.title = Descrizion (test alternatîf)
pdfjs-editor-add-signature-description-default-when-drawing = Firme
pdfjs-editor-add-signature-clear-button-label = Nete firme
pdfjs-editor-add-signature-clear-button =
.title = Nete firme
pdfjs-editor-add-signature-save-checkbox = Salve firme
pdfjs-editor-add-signature-save-warning-message = Tu sês rivât/rivade al limit di 5 firmis salvadis. Gjave une par salvânt une altre.
pdfjs-editor-add-signature-image-upload-error-title = Impussibil cjariâ la imagjin
pdfjs-editor-add-signature-image-upload-error-description = Controle la conession di rêt o prove cuntune altre imagjin.
pdfjs-editor-add-signature-error-close-button = Siere
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Anule
pdfjs-editor-add-signature-add-button = Zonte
pdfjs-editor-edit-signature-update-button = Inzorne
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Gjave firme
pdfjs-editor-delete-signature-button-label = Gjave firme
pdfjs-editor-delete-signature-button1 =
.title = Gjave firme salvade
pdfjs-editor-delete-signature-button-label1 = Gjave firme salvade
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Modifiche descrizion
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Modifiche descrizion
================================================
FILE: cookbook/static/pdfjs/web/locale/fy-NL/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Foarige side
pdfjs-previous-button-label = Foarige
pdfjs-next-button =
.title = Folgjende side
pdfjs-next-button-label = Folgjende
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Side
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = fan { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } fan { $pagesCount })
pdfjs-zoom-out-button =
.title = Utzoome
pdfjs-zoom-out-button-label = Utzoome
pdfjs-zoom-in-button =
.title = Ynzoome
pdfjs-zoom-in-button-label = Ynzoome
pdfjs-zoom-select =
.title = Zoome
pdfjs-presentation-mode-button =
.title = Wikselje nei presintaasjemodus
pdfjs-presentation-mode-button-label = Presintaasjemodus
pdfjs-open-file-button =
.title = Bestân iepenje
pdfjs-open-file-button-label = Iepenje
pdfjs-print-button =
.title = Ofdrukke
pdfjs-print-button-label = Ofdrukke
pdfjs-save-button =
.title = Bewarje
pdfjs-save-button-label = Bewarje
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Downloade
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Downloade
pdfjs-bookmark-button =
.title = Aktuele side (URL fan aktuele side besjen)
pdfjs-bookmark-button-label = Aktuele side
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Ark
pdfjs-tools-button-label = Ark
pdfjs-first-page-button =
.title = Gean nei earste side
pdfjs-first-page-button-label = Gean nei earste side
pdfjs-last-page-button =
.title = Gean nei lêste side
pdfjs-last-page-button-label = Gean nei lêste side
pdfjs-page-rotate-cw-button =
.title = Rjochtsom draaie
pdfjs-page-rotate-cw-button-label = Rjochtsom draaie
pdfjs-page-rotate-ccw-button =
.title = Linksom draaie
pdfjs-page-rotate-ccw-button-label = Linksom draaie
pdfjs-cursor-text-select-tool-button =
.title = Tekstseleksjehelpmiddel ynskeakelje
pdfjs-cursor-text-select-tool-button-label = Tekstseleksjehelpmiddel
pdfjs-cursor-hand-tool-button =
.title = Hânhelpmiddel ynskeakelje
pdfjs-cursor-hand-tool-button-label = Hânhelpmiddel
pdfjs-scroll-page-button =
.title = Sideskowen brûke
pdfjs-scroll-page-button-label = Sideskowen
pdfjs-scroll-vertical-button =
.title = Fertikaal skowe brûke
pdfjs-scroll-vertical-button-label = Fertikaal skowe
pdfjs-scroll-horizontal-button =
.title = Horizontaal skowe brûke
pdfjs-scroll-horizontal-button-label = Horizontaal skowe
pdfjs-scroll-wrapped-button =
.title = Skowe mei oersjoch brûke
pdfjs-scroll-wrapped-button-label = Skowe mei oersjoch
pdfjs-spread-none-button =
.title = Sidesprieding net gearfetsje
pdfjs-spread-none-button-label = Gjin sprieding
pdfjs-spread-odd-button =
.title = Sidesprieding gearfetsje te starten mei ûneven nûmers
pdfjs-spread-odd-button-label = Uneven sprieding
pdfjs-spread-even-button =
.title = Sidesprieding gearfetsje te starten mei even nûmers
pdfjs-spread-even-button-label = Even sprieding
## Document properties dialog
pdfjs-document-properties-button =
.title = Dokuminteigenskippen…
pdfjs-document-properties-button-label = Dokuminteigenskippen…
pdfjs-document-properties-file-name = Bestânsnamme:
pdfjs-document-properties-file-size = Bestânsgrutte:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = Titel:
pdfjs-document-properties-author = Auteur:
pdfjs-document-properties-subject = Underwerp:
pdfjs-document-properties-keywords = Kaaiwurden:
pdfjs-document-properties-creation-date = Oanmaakdatum:
pdfjs-document-properties-modification-date = Bewurkingsdatum:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Makker:
pdfjs-document-properties-producer = PDF-makker:
pdfjs-document-properties-version = PDF-ferzje:
pdfjs-document-properties-page-count = Siden:
pdfjs-document-properties-page-size = Sideformaat:
pdfjs-document-properties-page-size-unit-inches = yn
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = steand
pdfjs-document-properties-page-size-orientation-landscape = lizzend
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letter
pdfjs-document-properties-page-size-name-legal = Juridysk
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Flugge webwerjefte:
pdfjs-document-properties-linearized-yes = Ja
pdfjs-document-properties-linearized-no = Nee
pdfjs-document-properties-close-button = Slute
## Print
pdfjs-print-progress-message = Dokumint tariede oar ôfdrukken…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Annulearje
pdfjs-printing-not-supported = Warning: Printen is net folslein stipe troch dizze browser.
pdfjs-printing-not-ready = Warning: PDF is net folslein laden om ôf te drukken.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Sidebalke yn-/útskeakelje
pdfjs-toggle-sidebar-notification-button =
.title = Sidebalke yn-/útskeakelje (dokumint befettet oersjoch/bylagen/lagen)
pdfjs-toggle-sidebar-button-label = Sidebalke yn-/útskeakelje
pdfjs-document-outline-button =
.title = Dokumintoersjoch toane (dûbelklik om alle items út/yn te klappen)
pdfjs-document-outline-button-label = Dokumintoersjoch
pdfjs-attachments-button =
.title = Bylagen toane
pdfjs-attachments-button-label = Bylagen
pdfjs-layers-button =
.title = Lagen toane (dûbelklik om alle lagen nei de standertsteat werom te setten)
pdfjs-layers-button-label = Lagen
pdfjs-thumbs-button =
.title = Foarbylden toane
pdfjs-thumbs-button-label = Foarbylden
pdfjs-current-outline-item-button =
.title = Aktueel item yn ynhâldsopjefte sykje
pdfjs-current-outline-item-button-label = Aktueel item yn ynhâldsopjefte
pdfjs-findbar-button =
.title = Sykje yn dokumint
pdfjs-findbar-button-label = Sykje
pdfjs-additional-layers = Oanfoljende lagen
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Side { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Foarbyld fan side { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Sykje
.placeholder = Sykje yn dokumint…
pdfjs-find-previous-button =
.title = It foarige foarkommen fan de tekst sykje
pdfjs-find-previous-button-label = Foarige
pdfjs-find-next-button =
.title = It folgjende foarkommen fan de tekst sykje
pdfjs-find-next-button-label = Folgjende
pdfjs-find-highlight-checkbox = Alles markearje
pdfjs-find-match-case-checkbox-label = Haadlettergefoelich
pdfjs-find-match-diacritics-checkbox-label = Diakrityske tekens brûke
pdfjs-find-entire-word-checkbox-label = Hiele wurden
pdfjs-find-reached-top = Boppekant fan dokumint berikt, trochgien fan ûnder ôf
pdfjs-find-reached-bottom = Ein fan dokumint berikt, trochgien fan boppe ôf
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } fan { $total } oerienkomst
*[other] { $current } fan { $total } oerienkomsten
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Mear as { $limit } oerienkomst
*[other] Mear as { $limit } oerienkomsten
}
pdfjs-find-not-found = Tekst net fûn
## Predefined zoom values
pdfjs-page-scale-width = Sidebreedte
pdfjs-page-scale-fit = Hiele side
pdfjs-page-scale-auto = Automatysk zoome
pdfjs-page-scale-actual = Werklike grutte
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Side { $page }
## Loading indicator messages
pdfjs-loading-error = Der is in flater bard by it laden fan de PDF.
pdfjs-invalid-file-error = Ynfalide of korruptearre PDF-bestân.
pdfjs-missing-file-error = PDF-bestân ûntbrekt.
pdfjs-unexpected-response-error = Unferwacht serverantwurd.
pdfjs-rendering-error = Der is in flater bard by it renderjen fan de side.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type }-annotaasje]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Jou it wachtwurd om dit PDF-bestân te iepenjen.
pdfjs-password-invalid = Ferkeard wachtwurd. Probearje opnij.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Annulearje
pdfjs-web-fonts-disabled = Weblettertypen binne útskeakele: gebrûk fan ynsluten PDF-lettertypen is net mooglik.
## Editing
pdfjs-editor-free-text-button =
.title = Tekst
pdfjs-editor-free-text-button-label = Tekst
pdfjs-editor-ink-button =
.title = Tekenje
pdfjs-editor-ink-button-label = Tekenje
pdfjs-editor-stamp-button =
.title = Ofbyldingen tafoegje of bewurkje
pdfjs-editor-stamp-button-label = Ofbyldingen tafoegje of bewurkje
pdfjs-editor-highlight-button =
.title = Markearje
pdfjs-editor-highlight-button-label = Markearje
pdfjs-highlight-floating-button1 =
.title = Markearje
.aria-label = Markearje
pdfjs-highlight-floating-button-label = Markearje
pdfjs-editor-signature-button =
.title = Hantekening tafoegje
pdfjs-editor-signature-button-label = Hantekening tafoegje
## Default editor aria labels
# “Highlight” is a noun, the string is used on the editor for highlights.
pdfjs-editor-highlight-editor =
.aria-label = Markearingsbewurker
# “Drawing” is a noun, the string is used on the editor for drawings.
pdfjs-editor-ink-editor =
.aria-label = Tekeningbewurker
pdfjs-editor-signature-editor =
.aria-label = Hantekeningbewurker
pdfjs-editor-stamp-editor =
.aria-label = Ofbyldingsbewurker
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Tekening fuortsmite
pdfjs-editor-remove-freetext-button =
.title = Tekst fuortsmite
pdfjs-editor-remove-stamp-button =
.title = Ofbylding fuortsmite
pdfjs-editor-remove-highlight-button =
.title = Markearring fuortsmite
pdfjs-editor-remove-signature-button =
.title = Hantekening fuortsmite
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Kleur
pdfjs-editor-free-text-size-input = Grutte
pdfjs-editor-ink-color-input = Kleur
pdfjs-editor-ink-thickness-input = Tsjokte
pdfjs-editor-ink-opacity-input = Transparânsje
pdfjs-editor-stamp-add-image-button =
.title = Ofbylding tafoegje
pdfjs-editor-stamp-add-image-button-label = Ofbylding tafoegje
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Tsjokte
pdfjs-editor-free-highlight-thickness-title =
.title = Tsjokte wizigje by aksintuearring fan oare items as tekst
pdfjs-editor-add-signature-container =
.aria-label = Undertekeningsynstellingen en bewarre ûndertekeningen
pdfjs-editor-signature-add-signature-button =
.title = Nije hantekening tafoegje
pdfjs-editor-signature-add-signature-button-label = Nije hantekening tafoegje
# Used on the button to use an already saved signature.
# Variables:
# $description (String) - a string describing/labeling the signature.
pdfjs-editor-add-saved-signature-button =
.title = Bewarre ûndertekening: { $description }
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Tekstbewurker
.default-content = Start mei typen…
pdfjs-free-text =
.aria-label = Tekstbewurker
pdfjs-free-text-default-content = Begjin mei typen…
pdfjs-ink =
.aria-label = Tekeningbewurker
pdfjs-ink-canvas =
.aria-label = Troch brûker makke ôfbylding
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Alternative tekst
pdfjs-editor-alt-text-edit-button =
.aria-label = Alternative tekst bewurkje
pdfjs-editor-alt-text-edit-button-label = Alternative tekst bewurkje
pdfjs-editor-alt-text-dialog-label = Kies in opsje
pdfjs-editor-alt-text-dialog-description = Alternative tekst helpt wannear’t minsken de ôfbylding net sjen kinne of wannear’t dizze net laden wurdt.
pdfjs-editor-alt-text-add-description-label = Foegje in beskriuwing ta
pdfjs-editor-alt-text-add-description-description = Stribje nei 1-2 sinnen dy’t it ûnderwerp, de omjouwing of de aksjes beskriuwe.
pdfjs-editor-alt-text-mark-decorative-label = As dekoratyf markearje
pdfjs-editor-alt-text-mark-decorative-description = Dit wurdt brûkt foar sierlike ôfbyldingen, lykas rânen of wettermerken.
pdfjs-editor-alt-text-cancel-button = Annulearje
pdfjs-editor-alt-text-save-button = Bewarje
pdfjs-editor-alt-text-decorative-tooltip = As dekoratyf markearre
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Bygelyks, ‘In jonge man sit oan in tafel om te iten’
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Alternative tekst
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Linkerboppehoek – formaat wizigje
pdfjs-editor-resizer-label-top-middle = Midden boppe – formaat wizigje
pdfjs-editor-resizer-label-top-right = Rjochterboppehoek – formaat wizigje
pdfjs-editor-resizer-label-middle-right = Midden rjochts – formaat wizigje
pdfjs-editor-resizer-label-bottom-right = Rjochterûnderhoek – formaat wizigje
pdfjs-editor-resizer-label-bottom-middle = Midden ûnder – formaat wizigje
pdfjs-editor-resizer-label-bottom-left = Linkerûnderhoek – formaat wizigje
pdfjs-editor-resizer-label-middle-left = Links midden – formaat wizigje
pdfjs-editor-resizer-top-left =
.aria-label = Linkerboppehoek – formaat wizigje
pdfjs-editor-resizer-top-middle =
.aria-label = Midden boppe – formaat wizigje
pdfjs-editor-resizer-top-right =
.aria-label = Rjochterboppehoek – formaat wizigje
pdfjs-editor-resizer-middle-right =
.aria-label = Midden rjochts – formaat wizigje
pdfjs-editor-resizer-bottom-right =
.aria-label = Rjochterûnderhoek – formaat wizigje
pdfjs-editor-resizer-bottom-middle =
.aria-label = Midden ûnder – formaat wizigje
pdfjs-editor-resizer-bottom-left =
.aria-label = Linkerûnderhoek – formaat wizigje
pdfjs-editor-resizer-middle-left =
.aria-label = Links midden – formaat wizigje
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Markearringskleur
pdfjs-editor-colorpicker-button =
.title = Kleur wizigje
pdfjs-editor-colorpicker-dropdown =
.aria-label = Kleurkarren
pdfjs-editor-colorpicker-yellow =
.title = Giel
pdfjs-editor-colorpicker-green =
.title = Grien
pdfjs-editor-colorpicker-blue =
.title = Blau
pdfjs-editor-colorpicker-pink =
.title = Roze
pdfjs-editor-colorpicker-red =
.title = Read
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Alles toane
pdfjs-editor-highlight-show-all-button =
.title = Alles toane
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Alternative tekst (ôfbyldingsbeskriuwing) bewurkje
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Alternative tekst (ôfbyldingsbeskriuwing) tafoegje
pdfjs-editor-new-alt-text-textarea =
.placeholder = Skriuw hjir jo beskriuwing…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Koarte beskriuwing foar minsken dy’t de ôfbylding net sjen kinne of wannear’t de ôfbylding net laden wurdt.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Dizze alternative tekst is automatysk makke en is mooglik net korrekt.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Mear ynfo
pdfjs-editor-new-alt-text-create-automatically-button-label = Alternative tekst automatysk oanmeitsje
pdfjs-editor-new-alt-text-not-now-button = No net
pdfjs-editor-new-alt-text-error-title = Kin alternative tekst net automatysk oanmeitsje
pdfjs-editor-new-alt-text-error-description = Skriuw jo eigen alternative tekst of probearje it letter nochris.
pdfjs-editor-new-alt-text-error-close-button = Slute
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = AI-model foar alternative tekst downloade ({ $downloadedSize } fan { $totalSize } MB)
.aria-valuetext = AI-model foar alternative tekst downloade ({ $downloadedSize } fan { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Alternative tekst tafoege
pdfjs-editor-new-alt-text-added-button-label = Alternative tekst tafoege
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Alternative tekst ûntbrekt
pdfjs-editor-new-alt-text-missing-button-label = Alternative tekst ûntbrekt
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Alternative tekst beoardiele
pdfjs-editor-new-alt-text-to-review-button-label = Alternative tekst beoardiele
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Automatysk oanmakke: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Ynstellingen foar alternative tekst fan ôfbyldingen
pdfjs-image-alt-text-settings-button-label = Ynstellingen foar alternative tekst fan ôfbyldingen
pdfjs-editor-alt-text-settings-dialog-label = Ynstellingen foar alternative tekst fan ôfbyldingen
pdfjs-editor-alt-text-settings-automatic-title = Automatyske alternative tekst
pdfjs-editor-alt-text-settings-create-model-button-label = Alternative tekst automatysk oanmeitsje
pdfjs-editor-alt-text-settings-create-model-description = Stelt beskriuwingen foar om minsken te helpen dy’t de ôfbylding net sjen kinne of foar wa’t de ôfbylding net laden wurdt.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = AI-model foar alternative tekst ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Wurdt lokaal op jo apparaat útfierd, sadat jo gegevens privee bliuwe. Fereaske foar automatyske alternative tekst.
pdfjs-editor-alt-text-settings-delete-model-button = Fuortsmite
pdfjs-editor-alt-text-settings-download-model-button = Downloade
pdfjs-editor-alt-text-settings-downloading-model-button = Downloade…
pdfjs-editor-alt-text-settings-editor-title = Alternative-tekstbewurker
pdfjs-editor-alt-text-settings-show-dialog-button-label = Alternative-tekstbewurker daliks toane by tafoegjen fan in ôfbylding
pdfjs-editor-alt-text-settings-show-dialog-description = Helpt jo derfoar te soargjen dat al jo ôfbyldingen alternative tekst hawwe.
pdfjs-editor-alt-text-settings-close-button = Slute
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Markearring fuortsmiten
pdfjs-editor-undo-bar-message-freetext = Tekst fuortsmiten
pdfjs-editor-undo-bar-message-ink = Tekening fuortsmiten
pdfjs-editor-undo-bar-message-stamp = Ofbylding fuortsmiten
pdfjs-editor-undo-bar-message-signature = Hantekening fuortsmiten
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } annotaasje fuortsmiten
*[other] { $count } annotaasjes fuortsmiten
}
pdfjs-editor-undo-bar-undo-button =
.title = Ungedien meitsje
pdfjs-editor-undo-bar-undo-button-label = Ungedien meitsje
pdfjs-editor-undo-bar-close-button =
.title = Slute
pdfjs-editor-undo-bar-close-button-label = Slute
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = Mei dizze modal kin de brûker in hantekening meitsje om oan in PDF-dokumint ta te foegjen. De brûker kin de namme bewurkje (dy't ek tsjinnet as alternative tekst), en opsjoneel de ûndertekening bewarje foar werhelle gebrûk.
pdfjs-editor-add-signature-dialog-title = In hantekening tafoegje
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Type
.title = Type
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Tekenje
.title = Tekenje
pdfjs-editor-add-signature-image-button = Ofbylding
.title = Ofbylding
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Jo hantekening type
.placeholder = Jo hantekening type
pdfjs-editor-add-signature-draw-placeholder = Jo hantekening tekenje
pdfjs-editor-add-signature-draw-thickness-range-label = Tsjokte
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Tekentsjokte: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Sleep bestân hjirhinne om op te laden
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Of kies ôfbyldingsbestannen
*[other] Of kies ôfbyldingsbestannen
}
## Controls
pdfjs-editor-add-signature-description-label = Beskriuwing (alternative tekst)
pdfjs-editor-add-signature-description-input =
.title = Beskriuwing (alternative tekst)
pdfjs-editor-add-signature-description-default-when-drawing = Hantekening
pdfjs-editor-add-signature-clear-button-label = Hantekening wiskje
pdfjs-editor-add-signature-clear-button =
.title = Hantekening wiskje
pdfjs-editor-add-signature-save-checkbox = Hantekening bewarje
pdfjs-editor-add-signature-save-warning-message = Jo hawwe de limyt fan 5 bewarre hantekeningen berikt. Ferwiderje ien om in oar te bewarjen.
pdfjs-editor-add-signature-image-upload-error-title = Kin de ôfbylding net oplade
pdfjs-editor-add-signature-image-upload-error-description = Kontrolearje jo netwurkferbining of probearje in oare ôfbylding.
pdfjs-editor-add-signature-error-close-button = Slute
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Annulearje
pdfjs-editor-add-signature-add-button = Tafoegje
pdfjs-editor-edit-signature-update-button = Bywurkje
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Hantekening fuortsmite
pdfjs-editor-delete-signature-button-label = Hantekening fuortsmite
pdfjs-editor-delete-signature-button1 =
.title = Bewarre ûndertekening fuortsmite
pdfjs-editor-delete-signature-button-label1 = Bewarre ûndertekening fuortsmite
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Beskriuwing bewurkje
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Beskriuwing bewurkje
================================================
FILE: cookbook/static/pdfjs/web/locale/ga-IE/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = An Leathanach Roimhe Seo
pdfjs-previous-button-label = Roimhe Seo
pdfjs-next-button =
.title = An Chéad Leathanach Eile
pdfjs-next-button-label = Ar Aghaidh
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Leathanach
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = as { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } as { $pagesCount })
pdfjs-zoom-out-button =
.title = Súmáil Amach
pdfjs-zoom-out-button-label = Súmáil Amach
pdfjs-zoom-in-button =
.title = Súmáil Isteach
pdfjs-zoom-in-button-label = Súmáil Isteach
pdfjs-zoom-select =
.title = Súmáil
pdfjs-presentation-mode-button =
.title = Úsáid an Mód Láithreoireachta
pdfjs-presentation-mode-button-label = Mód Láithreoireachta
pdfjs-open-file-button =
.title = Oscail Comhad
pdfjs-open-file-button-label = Oscail
pdfjs-print-button =
.title = Priontáil
pdfjs-print-button-label = Priontáil
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Uirlisí
pdfjs-tools-button-label = Uirlisí
pdfjs-first-page-button =
.title = Go dtí an chéad leathanach
pdfjs-first-page-button-label = Go dtí an chéad leathanach
pdfjs-last-page-button =
.title = Go dtí an leathanach deiridh
pdfjs-last-page-button-label = Go dtí an leathanach deiridh
pdfjs-page-rotate-cw-button =
.title = Rothlaigh ar deiseal
pdfjs-page-rotate-cw-button-label = Rothlaigh ar deiseal
pdfjs-page-rotate-ccw-button =
.title = Rothlaigh ar tuathal
pdfjs-page-rotate-ccw-button-label = Rothlaigh ar tuathal
pdfjs-cursor-text-select-tool-button =
.title = Cumasaigh an Uirlis Roghnaithe Téacs
pdfjs-cursor-text-select-tool-button-label = Uirlis Roghnaithe Téacs
pdfjs-cursor-hand-tool-button =
.title = Cumasaigh an Uirlis Láimhe
pdfjs-cursor-hand-tool-button-label = Uirlis Láimhe
## Document properties dialog
pdfjs-document-properties-button =
.title = Airíonna na Cáipéise…
pdfjs-document-properties-button-label = Airíonna na Cáipéise…
pdfjs-document-properties-file-name = Ainm an chomhaid:
pdfjs-document-properties-file-size = Méid an chomhaid:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } kB ({ $size_b } beart)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } beart)
pdfjs-document-properties-title = Teideal:
pdfjs-document-properties-author = Údar:
pdfjs-document-properties-subject = Ábhar:
pdfjs-document-properties-keywords = Eochairfhocail:
pdfjs-document-properties-creation-date = Dáta Cruthaithe:
pdfjs-document-properties-modification-date = Dáta Athraithe:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Cruthaitheoir:
pdfjs-document-properties-producer = Cruthaitheoir an PDF:
pdfjs-document-properties-version = Leagan PDF:
pdfjs-document-properties-page-count = Líon Leathanach:
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
##
pdfjs-document-properties-close-button = Dún
## Print
pdfjs-print-progress-message = Cáipéis á hullmhú le priontáil…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Cealaigh
pdfjs-printing-not-supported = Rabhadh: Ní thacaíonn an brabhsálaí le priontáil go hiomlán.
pdfjs-printing-not-ready = Rabhadh: Ní féidir an PDF a phriontáil go dtí go mbeidh an cháipéis iomlán lódáilte.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Scoránaigh an Barra Taoibh
pdfjs-toggle-sidebar-button-label = Scoránaigh an Barra Taoibh
pdfjs-document-outline-button =
.title = Taispeáin Imlíne na Cáipéise (déchliceáil chun chuile rud a leathnú nó a laghdú)
pdfjs-document-outline-button-label = Creatlach na Cáipéise
pdfjs-attachments-button =
.title = Taispeáin Iatáin
pdfjs-attachments-button-label = Iatáin
pdfjs-thumbs-button =
.title = Taispeáin Mionsamhlacha
pdfjs-thumbs-button-label = Mionsamhlacha
pdfjs-findbar-button =
.title = Aimsigh sa Cháipéis
pdfjs-findbar-button-label = Aimsigh
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Leathanach { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Mionsamhail Leathanaigh { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Aimsigh
.placeholder = Aimsigh sa cháipéis…
pdfjs-find-previous-button =
.title = Aimsigh an sampla roimhe seo den nath seo
pdfjs-find-previous-button-label = Roimhe seo
pdfjs-find-next-button =
.title = Aimsigh an chéad sampla eile den nath sin
pdfjs-find-next-button-label = Ar aghaidh
pdfjs-find-highlight-checkbox = Aibhsigh uile
pdfjs-find-match-case-checkbox-label = Cásíogair
pdfjs-find-entire-word-checkbox-label = Focail iomlána
pdfjs-find-reached-top = Ag barr na cáipéise, ag leanúint ón mbun
pdfjs-find-reached-bottom = Ag bun na cáipéise, ag leanúint ón mbarr
pdfjs-find-not-found = Frása gan aimsiú
## Predefined zoom values
pdfjs-page-scale-width = Leithead Leathanaigh
pdfjs-page-scale-fit = Laghdaigh go dtí an Leathanach
pdfjs-page-scale-auto = Súmáil Uathoibríoch
pdfjs-page-scale-actual = Fíormhéid
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
## Loading indicator messages
pdfjs-loading-error = Tharla earráid agus an cháipéis PDF á lódáil.
pdfjs-invalid-file-error = Comhad neamhbhailí nó truaillithe PDF.
pdfjs-missing-file-error = Comhad PDF ar iarraidh.
pdfjs-unexpected-response-error = Freagra ón bhfreastalaí nach rabhthas ag súil leis.
pdfjs-rendering-error = Tharla earráid agus an leathanach á leagan amach.
## Annotations
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [Anótáil { $type }]
## Password
pdfjs-password-label = Cuir an focal faire isteach chun an comhad PDF seo a oscailt.
pdfjs-password-invalid = Focal faire mícheart. Déan iarracht eile.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Cealaigh
pdfjs-web-fonts-disabled = Tá clófhoirne Gréasáin díchumasaithe: ní féidir clófhoirne leabaithe PDF a úsáid.
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/gd/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = An duilleag roimhe
pdfjs-previous-button-label = Air ais
pdfjs-next-button =
.title = An ath-dhuilleag
pdfjs-next-button-label = Air adhart
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Duilleag
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = à { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } à { $pagesCount })
pdfjs-zoom-out-button =
.title = Sùm a-mach
pdfjs-zoom-out-button-label = Sùm a-mach
pdfjs-zoom-in-button =
.title = Sùm a-steach
pdfjs-zoom-in-button-label = Sùm a-steach
pdfjs-zoom-select =
.title = Sùm
pdfjs-presentation-mode-button =
.title = Gearr leum dhan mhodh taisbeanaidh
pdfjs-presentation-mode-button-label = Am modh taisbeanaidh
pdfjs-open-file-button =
.title = Fosgail faidhle
pdfjs-open-file-button-label = Fosgail
pdfjs-print-button =
.title = Clò-bhuail
pdfjs-print-button-label = Clò-bhuail
pdfjs-save-button =
.title = Sàbhail
pdfjs-save-button-label = Sàbhail
pdfjs-bookmark-button =
.title = An duilleag làithreach (Seall an URL on duilleag làithreach)
pdfjs-bookmark-button-label = An duilleag làithreach
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Innealan
pdfjs-tools-button-label = Innealan
pdfjs-first-page-button =
.title = Rach gun chiad duilleag
pdfjs-first-page-button-label = Rach gun chiad duilleag
pdfjs-last-page-button =
.title = Rach gun duilleag mu dheireadh
pdfjs-last-page-button-label = Rach gun duilleag mu dheireadh
pdfjs-page-rotate-cw-button =
.title = Cuairtich gu deiseil
pdfjs-page-rotate-cw-button-label = Cuairtich gu deiseil
pdfjs-page-rotate-ccw-button =
.title = Cuairtich gu tuathail
pdfjs-page-rotate-ccw-button-label = Cuairtich gu tuathail
pdfjs-cursor-text-select-tool-button =
.title = Cuir an comas inneal taghadh an teacsa
pdfjs-cursor-text-select-tool-button-label = Inneal taghadh an teacsa
pdfjs-cursor-hand-tool-button =
.title = Cuir inneal na làimhe an comas
pdfjs-cursor-hand-tool-button-label = Inneal na làimhe
pdfjs-scroll-page-button =
.title = Cleachd sgroladh duilleige
pdfjs-scroll-page-button-label = Sgroladh duilleige
pdfjs-scroll-vertical-button =
.title = Cleachd sgroladh inghearach
pdfjs-scroll-vertical-button-label = Sgroladh inghearach
pdfjs-scroll-horizontal-button =
.title = Cleachd sgroladh còmhnard
pdfjs-scroll-horizontal-button-label = Sgroladh còmhnard
pdfjs-scroll-wrapped-button =
.title = Cleachd sgroladh paisgte
pdfjs-scroll-wrapped-button-label = Sgroladh paisgte
pdfjs-spread-none-button =
.title = Na cuir còmhla sgoileadh dhuilleagan
pdfjs-spread-none-button-label = Gun sgaoileadh dhuilleagan
pdfjs-spread-odd-button =
.title = Cuir còmhla duilleagan sgaoilte a thòisicheas le duilleagan aig a bheil àireamh chorr
pdfjs-spread-odd-button-label = Sgaoileadh dhuilleagan corra
pdfjs-spread-even-button =
.title = Cuir còmhla duilleagan sgaoilte a thòisicheas le duilleagan aig a bheil àireamh chothrom
pdfjs-spread-even-button-label = Sgaoileadh dhuilleagan cothrom
## Document properties dialog
pdfjs-document-properties-button =
.title = Roghainnean na sgrìobhainne…
pdfjs-document-properties-button-label = Roghainnean na sgrìobhainne…
pdfjs-document-properties-file-name = Ainm an fhaidhle:
pdfjs-document-properties-file-size = Meud an fhaidhle:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = Tiotal:
pdfjs-document-properties-author = Ùghdar:
pdfjs-document-properties-subject = Cuspair:
pdfjs-document-properties-keywords = Faclan-luirg:
pdfjs-document-properties-creation-date = Latha a chruthachaidh:
pdfjs-document-properties-modification-date = Latha atharrachaidh:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Cruthadair:
pdfjs-document-properties-producer = Saothraiche a' PDF:
pdfjs-document-properties-version = Tionndadh a' PDF:
pdfjs-document-properties-page-count = Àireamh de dhuilleagan:
pdfjs-document-properties-page-size = Meud na duilleige:
pdfjs-document-properties-page-size-unit-inches = ann an
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = portraid
pdfjs-document-properties-page-size-orientation-landscape = dreach-tìre
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Litir
pdfjs-document-properties-page-size-name-legal = Laghail
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Grad shealladh-lìn:
pdfjs-document-properties-linearized-yes = Tha
pdfjs-document-properties-linearized-no = Chan eil
pdfjs-document-properties-close-button = Dùin
## Print
pdfjs-print-progress-message = Ag ullachadh na sgrìobhainn airson clò-bhualadh…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Sguir dheth
pdfjs-printing-not-supported = Rabhadh: Chan eil am brabhsair seo a' cur làn-taic ri clò-bhualadh.
pdfjs-printing-not-ready = Rabhadh: Cha deach am PDF a luchdadh gu tur airson clò-bhualadh.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Toglaich am bàr-taoibh
pdfjs-toggle-sidebar-notification-button =
.title = Toglaich am bàr-taoibh (tha oir-loidhne/ceanglachain/breathan aig an sgrìobhainn)
pdfjs-toggle-sidebar-button-label = Toglaich am bàr-taoibh
pdfjs-document-outline-button =
.title = Seall oir-loidhne na sgrìobhainn (dèan briogadh dùbailte airson a h-uile nì a leudachadh/a cho-theannadh)
pdfjs-document-outline-button-label = Oir-loidhne na sgrìobhainne
pdfjs-attachments-button =
.title = Seall na ceanglachain
pdfjs-attachments-button-label = Ceanglachain
pdfjs-layers-button =
.title = Seall na breathan (dèan briogadh dùbailte airson a h-uile breath ath-shuidheachadh dhan staid bhunaiteach)
pdfjs-layers-button-label = Breathan
pdfjs-thumbs-button =
.title = Seall na dealbhagan
pdfjs-thumbs-button-label = Dealbhagan
pdfjs-current-outline-item-button =
.title = Lorg nì làithreach na h-oir-loidhne
pdfjs-current-outline-item-button-label = Nì làithreach na h-oir-loidhne
pdfjs-findbar-button =
.title = Lorg san sgrìobhainn
pdfjs-findbar-button-label = Lorg
pdfjs-additional-layers = Barrachd breathan
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Duilleag a { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Dealbhag duilleag a { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Lorg
.placeholder = Lorg san sgrìobhainn...
pdfjs-find-previous-button =
.title = Lorg làthair roimhe na h-abairt seo
pdfjs-find-previous-button-label = Air ais
pdfjs-find-next-button =
.title = Lorg ath-làthair na h-abairt seo
pdfjs-find-next-button-label = Air adhart
pdfjs-find-highlight-checkbox = Soillsich a h-uile
pdfjs-find-match-case-checkbox-label = Aire do litrichean mòra is beaga
pdfjs-find-match-diacritics-checkbox-label = Aire do stràcan
pdfjs-find-entire-word-checkbox-label = Faclan-slàna
pdfjs-find-reached-top = Ràinig sinn barr na duilleige, a' leantainn air adhart o bhonn na duilleige
pdfjs-find-reached-bottom = Ràinig sinn bonn na duilleige, a' leantainn air adhart o bharr na duilleige
pdfjs-find-not-found = Cha deach an abairt a lorg
## Predefined zoom values
pdfjs-page-scale-width = Leud na duilleige
pdfjs-page-scale-fit = Freagair ri meud na duilleige
pdfjs-page-scale-auto = Sùm fèin-obrachail
pdfjs-page-scale-actual = Am fìor-mheud
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Duilleag { $page }
## Loading indicator messages
pdfjs-loading-error = Thachair mearachd rè luchdadh a' PDF.
pdfjs-invalid-file-error = Faidhle PDF a tha mì-dhligheach no coirbte.
pdfjs-missing-file-error = Faidhle PDF a tha a dhìth.
pdfjs-unexpected-response-error = Freagairt on fhrithealaiche ris nach robh dùil.
pdfjs-rendering-error = Thachair mearachd rè reandaradh na duilleige.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [Nòtachadh { $type }]
## Password
pdfjs-password-label = Cuir a-steach am facal-faire gus am faidhle PDF seo fhosgladh.
pdfjs-password-invalid = Tha am facal-faire cearr. Nach fheuch thu ris a-rithist?
pdfjs-password-ok-button = Ceart ma-thà
pdfjs-password-cancel-button = Sguir dheth
pdfjs-web-fonts-disabled = Tha cruthan-clò lìn à comas: Chan urrainn dhuinn cruthan-clò PDF leabaichte a chleachdadh.
## Editing
pdfjs-editor-free-text-button =
.title = Teacsa
pdfjs-editor-free-text-button-label = Teacsa
pdfjs-editor-ink-button =
.title = Tarraing
pdfjs-editor-ink-button-label = Tarraing
## Default editor aria labels
## Remove button for the various kind of editor.
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Dath
pdfjs-editor-free-text-size-input = Meud
pdfjs-editor-ink-color-input = Dath
pdfjs-editor-ink-thickness-input = Tighead
pdfjs-editor-ink-opacity-input = Trìd-dhoilleireachd
pdfjs-free-text =
.aria-label = An deasaiche teacsa
pdfjs-free-text-default-content = Tòisich air sgrìobhadh…
pdfjs-ink =
.aria-label = An deasaiche tharraingean
pdfjs-ink-canvas =
.aria-label = Dealbh a chruthaich cleachdaiche
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/gl/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Páxina anterior
pdfjs-previous-button-label = Anterior
pdfjs-next-button =
.title = Seguinte páxina
pdfjs-next-button-label = Seguinte
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Páxina
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = de { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } de { $pagesCount })
pdfjs-zoom-out-button =
.title = Reducir
pdfjs-zoom-out-button-label = Reducir
pdfjs-zoom-in-button =
.title = Ampliar
pdfjs-zoom-in-button-label = Ampliar
pdfjs-zoom-select =
.title = Zoom
pdfjs-presentation-mode-button =
.title = Cambiar ao modo presentación
pdfjs-presentation-mode-button-label = Modo presentación
pdfjs-open-file-button =
.title = Abrir ficheiro
pdfjs-open-file-button-label = Abrir
pdfjs-print-button =
.title = Imprimir
pdfjs-print-button-label = Imprimir
pdfjs-save-button =
.title = Gardar
pdfjs-save-button-label = Gardar
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Descargar
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Descargar
pdfjs-bookmark-button =
.title = Páxina actual (ver o URL da páxina actual)
pdfjs-bookmark-button-label = Páxina actual
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Ferramentas
pdfjs-tools-button-label = Ferramentas
pdfjs-first-page-button =
.title = Ir á primeira páxina
pdfjs-first-page-button-label = Ir á primeira páxina
pdfjs-last-page-button =
.title = Ir á última páxina
pdfjs-last-page-button-label = Ir á última páxina
pdfjs-page-rotate-cw-button =
.title = Rotar no sentido das agullas do reloxo
pdfjs-page-rotate-cw-button-label = Rotar no sentido das agullas do reloxo
pdfjs-page-rotate-ccw-button =
.title = Rotar no sentido contrario ás agullas do reloxo
pdfjs-page-rotate-ccw-button-label = Rotar no sentido contrario ás agullas do reloxo
pdfjs-cursor-text-select-tool-button =
.title = Activar a ferramenta de selección de texto
pdfjs-cursor-text-select-tool-button-label = Ferramenta de selección de texto
pdfjs-cursor-hand-tool-button =
.title = Activar a ferramenta de man
pdfjs-cursor-hand-tool-button-label = Ferramenta de man
pdfjs-scroll-page-button =
.title = Usar o desprazamento da páxina
pdfjs-scroll-page-button-label = Desprazamento da páxina
pdfjs-scroll-vertical-button =
.title = Usar o desprazamento vertical
pdfjs-scroll-vertical-button-label = Desprazamento vertical
pdfjs-scroll-horizontal-button =
.title = Usar o desprazamento horizontal
pdfjs-scroll-horizontal-button-label = Desprazamento horizontal
pdfjs-scroll-wrapped-button =
.title = Usar o desprazamento en bloque
pdfjs-scroll-wrapped-button-label = Desprazamento por bloque
pdfjs-spread-none-button =
.title = Non agrupar páxinas
pdfjs-spread-none-button-label = Ningún agrupamento
pdfjs-spread-odd-button =
.title = Crea grupo de páxinas que comezan con números de páxina impares
pdfjs-spread-odd-button-label = Agrupamento impar
pdfjs-spread-even-button =
.title = Crea grupo de páxinas que comezan con números de páxina pares
pdfjs-spread-even-button-label = Agrupamento par
## Document properties dialog
pdfjs-document-properties-button =
.title = Propiedades do documento…
pdfjs-document-properties-button-label = Propiedades do documento…
pdfjs-document-properties-file-name = Nome do ficheiro:
pdfjs-document-properties-file-size = Tamaño do ficheiro:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = Título:
pdfjs-document-properties-author = Autor:
pdfjs-document-properties-subject = Asunto:
pdfjs-document-properties-keywords = Palabras clave:
pdfjs-document-properties-creation-date = Data de creación:
pdfjs-document-properties-modification-date = Data de modificación:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Creado por:
pdfjs-document-properties-producer = Xenerador do PDF:
pdfjs-document-properties-version = Versión de PDF:
pdfjs-document-properties-page-count = Número de páxinas:
pdfjs-document-properties-page-size = Tamaño da páxina:
pdfjs-document-properties-page-size-unit-inches = pol
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = vertical
pdfjs-document-properties-page-size-orientation-landscape = horizontal
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Carta
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Visualización rápida das páxinas web:
pdfjs-document-properties-linearized-yes = Si
pdfjs-document-properties-linearized-no = Non
pdfjs-document-properties-close-button = Pechar
## Print
pdfjs-print-progress-message = Preparando o documento para imprimir…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Cancelar
pdfjs-printing-not-supported = Aviso: A impresión non é compatíbel de todo con este navegador.
pdfjs-printing-not-ready = Aviso: O PDF non se cargou completamente para imprimirse.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Amosar/agochar a barra lateral
pdfjs-toggle-sidebar-notification-button =
.title = Alternar barra lateral (o documento contén esquema/anexos/capas)
pdfjs-toggle-sidebar-button-label = Amosar/agochar a barra lateral
pdfjs-document-outline-button =
.title = Amosar a estrutura do documento (dobre clic para expandir/contraer todos os elementos)
pdfjs-document-outline-button-label = Estrutura do documento
pdfjs-attachments-button =
.title = Amosar anexos
pdfjs-attachments-button-label = Anexos
pdfjs-layers-button =
.title = Mostrar capas (prema dúas veces para restaurar todas as capas o estado predeterminado)
pdfjs-layers-button-label = Capas
pdfjs-thumbs-button =
.title = Amosar miniaturas
pdfjs-thumbs-button-label = Miniaturas
pdfjs-current-outline-item-button =
.title = Atopar o elemento delimitado actualmente
pdfjs-current-outline-item-button-label = Elemento delimitado actualmente
pdfjs-findbar-button =
.title = Atopar no documento
pdfjs-findbar-button-label = Atopar
pdfjs-additional-layers = Capas adicionais
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Páxina { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Miniatura da páxina { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Atopar
.placeholder = Atopar no documento…
pdfjs-find-previous-button =
.title = Atopar a anterior aparición da frase
pdfjs-find-previous-button-label = Anterior
pdfjs-find-next-button =
.title = Atopar a seguinte aparición da frase
pdfjs-find-next-button-label = Seguinte
pdfjs-find-highlight-checkbox = Realzar todo
pdfjs-find-match-case-checkbox-label = Diferenciar maiúsculas de minúsculas
pdfjs-find-match-diacritics-checkbox-label = Distinguir os diacríticos
pdfjs-find-entire-word-checkbox-label = Palabras completas
pdfjs-find-reached-top = Chegouse ao inicio do documento, continuar desde o final
pdfjs-find-reached-bottom = Chegouse ao final do documento, continuar desde o inicio
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] Coincidencia { $current } de { $total }
*[other] Coincidencia { $current } de { $total }
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Máis de { $limit } coincidencia
*[other] Máis de { $limit } coincidencias
}
pdfjs-find-not-found = Non se atopou a frase
## Predefined zoom values
pdfjs-page-scale-width = Largura da páxina
pdfjs-page-scale-fit = Axuste de páxina
pdfjs-page-scale-auto = Zoom automático
pdfjs-page-scale-actual = Tamaño actual
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Páxina { $page }
## Loading indicator messages
pdfjs-loading-error = Produciuse un erro ao cargar o PDF.
pdfjs-invalid-file-error = Ficheiro PDF danado ou non válido.
pdfjs-missing-file-error = Falta o ficheiro PDF.
pdfjs-unexpected-response-error = Resposta inesperada do servidor.
pdfjs-rendering-error = Produciuse un erro ao representar a páxina.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [Anotación { $type }]
## Password
pdfjs-password-label = Escriba o contrasinal para abrir este ficheiro PDF.
pdfjs-password-invalid = Contrasinal incorrecto. Tente de novo.
pdfjs-password-ok-button = Aceptar
pdfjs-password-cancel-button = Cancelar
pdfjs-web-fonts-disabled = Desactiváronse as fontes web: foi imposíbel usar as fontes incrustadas no PDF.
## Editing
pdfjs-editor-free-text-button =
.title = Texto
pdfjs-editor-free-text-button-label = Texto
pdfjs-editor-ink-button =
.title = Debuxo
pdfjs-editor-ink-button-label = Debuxo
pdfjs-editor-stamp-button =
.title = Engadir ou editar imaxes
pdfjs-editor-stamp-button-label = Engadir ou editar imaxes
## Default editor aria labels
## Remove button for the various kind of editor.
pdfjs-editor-remove-freetext-button =
.title = Eliminar o texto
pdfjs-editor-remove-stamp-button =
.title = Eliminar a imaxe
pdfjs-editor-remove-highlight-button =
.title = Eliminar o resaltado
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Cor
pdfjs-editor-free-text-size-input = Tamaño
pdfjs-editor-ink-color-input = Cor
pdfjs-editor-ink-thickness-input = Grosor
pdfjs-editor-ink-opacity-input = Opacidade
pdfjs-editor-stamp-add-image-button =
.title = Engadir imaxe
pdfjs-editor-stamp-add-image-button-label = Engadir imaxe
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Grosor
pdfjs-free-text =
.aria-label = Editor de texto
pdfjs-free-text-default-content = Comezar a teclear…
pdfjs-ink =
.aria-label = Editor de debuxos
pdfjs-ink-canvas =
.aria-label = Imaxe creada por unha usuaria
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Texto alternativo
pdfjs-editor-alt-text-edit-button-label = Editar o texto alternativo
pdfjs-editor-alt-text-dialog-label = Escoller unha opción
pdfjs-editor-alt-text-add-description-label = Engadir unha descrición
pdfjs-editor-alt-text-mark-decorative-label = Marcar como decorativo
pdfjs-editor-alt-text-mark-decorative-description = Utilízase para imaxes ornamentais, como bordos ou marcas de auga.
pdfjs-editor-alt-text-cancel-button = Cancelar
pdfjs-editor-alt-text-save-button = Gardar
pdfjs-editor-alt-text-decorative-tooltip = Marcado como decorativo
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Por exemplo, «Un mozo séntase á mesa para comer»
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Esquina superior esquerda: cambia o tamaño
pdfjs-editor-resizer-label-top-middle = Medio superior: cambia o tamaño
pdfjs-editor-resizer-label-top-right = Esquina superior dereita: cambia o tamaño
pdfjs-editor-resizer-label-middle-right = Medio dereito: cambia o tamaño
pdfjs-editor-resizer-label-bottom-right = Esquina inferior dereita: cambia o tamaño
pdfjs-editor-resizer-label-bottom-middle = Abaixo medio: cambia o tamaño
pdfjs-editor-resizer-label-bottom-left = Esquina inferior esquerda: cambia o tamaño
pdfjs-editor-resizer-label-middle-left = Medio esquerdo: cambia o tamaño
pdfjs-editor-resizer-top-left =
.aria-label = Esquina superior esquerda: cambia o tamaño
pdfjs-editor-resizer-top-middle =
.aria-label = Medio superior: cambia o tamaño
pdfjs-editor-resizer-top-right =
.aria-label = Esquina superior dereita: cambia o tamaño
pdfjs-editor-resizer-middle-right =
.aria-label = Medio dereito: cambia o tamaño
pdfjs-editor-resizer-bottom-right =
.aria-label = Esquina inferior dereita: cambia o tamaño
pdfjs-editor-resizer-bottom-middle =
.aria-label = Abaixo medio: cambia o tamaño
pdfjs-editor-resizer-bottom-left =
.aria-label = Esquina inferior esquerda: cambia o tamaño
pdfjs-editor-resizer-middle-left =
.aria-label = Medio esquerdo: cambia o tamaño
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/gn/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Kuatiarogue mboyvegua
pdfjs-previous-button-label = Mboyvegua
pdfjs-next-button =
.title = Kuatiarogue upeigua
pdfjs-next-button-label = Upeigua
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Kuatiarogue
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = { $pagesCount } gui
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } of { $pagesCount })
pdfjs-zoom-out-button =
.title = Momichĩ
pdfjs-zoom-out-button-label = Momichĩ
pdfjs-zoom-in-button =
.title = Mbotuicha
pdfjs-zoom-in-button-label = Mbotuicha
pdfjs-zoom-select =
.title = Tuichakue
pdfjs-presentation-mode-button =
.title = Jehechauka reko moambue
pdfjs-presentation-mode-button-label = Jehechauka reko
pdfjs-open-file-button =
.title = Marandurendápe jeike
pdfjs-open-file-button-label = Jeike
pdfjs-print-button =
.title = Monguatia
pdfjs-print-button-label = Monguatia
pdfjs-save-button =
.title = Ñongatu
pdfjs-save-button-label = Ñongatu
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Mboguejy
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Mboguejy
pdfjs-bookmark-button =
.title = Kuatiarogue ag̃agua (Ehecha URL kuatiarogue ag̃agua)
pdfjs-bookmark-button-label = Kuatiarogue Ag̃agua
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Tembiporu
pdfjs-tools-button-label = Tembiporu
pdfjs-first-page-button =
.title = Kuatiarogue ñepyrũme jeho
pdfjs-first-page-button-label = Kuatiarogue ñepyrũme jeho
pdfjs-last-page-button =
.title = Kuatiarogue pahápe jeho
pdfjs-last-page-button-label = Kuatiarogue pahápe jeho
pdfjs-page-rotate-cw-button =
.title = Aravóicha mbojere
pdfjs-page-rotate-cw-button-label = Aravóicha mbojere
pdfjs-page-rotate-ccw-button =
.title = Aravo rapykue gotyo mbojere
pdfjs-page-rotate-ccw-button-label = Aravo rapykue gotyo mbojere
pdfjs-cursor-text-select-tool-button =
.title = Emyandy moñe’ẽrã jeporavo rembiporu
pdfjs-cursor-text-select-tool-button-label = Moñe’ẽrã jeporavo rembiporu
pdfjs-cursor-hand-tool-button =
.title = Tembiporu po pegua myandy
pdfjs-cursor-hand-tool-button-label = Tembiporu po pegua
pdfjs-scroll-page-button =
.title = Eiporu kuatiarogue jeku’e
pdfjs-scroll-page-button-label = Kuatiarogue jeku’e
pdfjs-scroll-vertical-button =
.title = Eiporu jeku’e ykeguáva
pdfjs-scroll-vertical-button-label = Jeku’e ykeguáva
pdfjs-scroll-horizontal-button =
.title = Eiporu jeku’e yvate gotyo
pdfjs-scroll-horizontal-button-label = Jeku’e yvate gotyo
pdfjs-scroll-wrapped-button =
.title = Eiporu jeku’e mbohyrupyre
pdfjs-scroll-wrapped-button-label = Jeku’e mbohyrupyre
pdfjs-spread-none-button =
.title = Ani ejuaju spreads kuatiarogue ndive
pdfjs-spread-none-button-label = Spreads ỹre
pdfjs-spread-odd-button =
.title = Embojuaju kuatiarogue jepysokue eñepyrũvo kuatiarogue impar-vagui
pdfjs-spread-odd-button-label = Spreads impar
pdfjs-spread-even-button =
.title = Embojuaju kuatiarogue jepysokue eñepyrũvo kuatiarogue par-vagui
pdfjs-spread-even-button-label = Ipukuve uvei
## Document properties dialog
pdfjs-document-properties-button =
.title = Kuatia mba’etee…
pdfjs-document-properties-button-label = Kuatia mba’etee…
pdfjs-document-properties-file-name = Marandurenda réra:
pdfjs-document-properties-file-size = Marandurenda tuichakue:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = Teratee:
pdfjs-document-properties-author = Apohára:
pdfjs-document-properties-subject = Mba’egua:
pdfjs-document-properties-keywords = Jehero:
pdfjs-document-properties-creation-date = Teñoihague arange:
pdfjs-document-properties-modification-date = Iñambue hague arange:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Apo’ypyha:
pdfjs-document-properties-producer = PDF mbosako’iha:
pdfjs-document-properties-version = PDF mbojuehegua:
pdfjs-document-properties-page-count = Kuatiarogue papapy:
pdfjs-document-properties-page-size = Kuatiarogue tuichakue:
pdfjs-document-properties-page-size-unit-inches = Amo
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = Oĩháicha
pdfjs-document-properties-page-size-orientation-landscape = apaisado
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Kuatiañe’ẽ
pdfjs-document-properties-page-size-name-legal = Tee
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Ñanduti jahecha pya’e:
pdfjs-document-properties-linearized-yes = Añete
pdfjs-document-properties-linearized-no = Ahániri
pdfjs-document-properties-close-button = Mboty
## Print
pdfjs-print-progress-message = Embosako’i kuatia emonguatia hag̃ua…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Heja
pdfjs-printing-not-supported = Kyhyjerã: Ñembokuatia ndojokupytypái ko kundahára ndive.
pdfjs-printing-not-ready = Kyhyjerã: Ko PDF nahenyhẽmbái oñembokuatia hag̃uáicha.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Tenda yke moambue
pdfjs-toggle-sidebar-notification-button =
.title = Embojopyru tenda ykegua (kuatia oguereko kuaakaha/moirũha/ñuãha)
pdfjs-toggle-sidebar-button-label = Tenda yke moambue
pdfjs-document-outline-button =
.title = Ehechauka kuatia rape (eikutu mokõi jey embotuicha/emomichĩ hag̃ua opavavete mba’eporu)
pdfjs-document-outline-button-label = Kuatia apopyre
pdfjs-attachments-button =
.title = Moirũha jehechauka
pdfjs-attachments-button-label = Moirũha
pdfjs-layers-button =
.title = Ehechauka ñuãha (eikutu jo’a emomba’apo hag̃ua opaite ñuãha tekoypýpe)
pdfjs-layers-button-label = Ñuãha
pdfjs-thumbs-button =
.title = Mba’emirĩ jehechauka
pdfjs-thumbs-button-label = Mba’emirĩ
pdfjs-current-outline-item-button =
.title = Eheka mba’eporu ag̃aguaitéva
pdfjs-current-outline-item-button-label = Mba’eporu ag̃aguaitéva
pdfjs-findbar-button =
.title = Kuatiápe jeheka
pdfjs-findbar-button-label = Juhu
pdfjs-additional-layers = Ñuãha moirũguáva
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Kuatiarogue { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Kuatiarogue mba’emirĩ { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Juhu
.placeholder = Kuatiápe jejuhu…
pdfjs-find-previous-button =
.title = Ejuhu ñe’ẽrysýi osẽ’ypy hague
pdfjs-find-previous-button-label = Mboyvegua
pdfjs-find-next-button =
.title = Eho ñe’ẽ juhupyre upeiguávape
pdfjs-find-next-button-label = Upeigua
pdfjs-find-highlight-checkbox = Embojekuaavepa
pdfjs-find-match-case-checkbox-label = Ejesareko taiguasu/taimichĩre
pdfjs-find-match-diacritics-checkbox-label = Diacrítico moñondive
pdfjs-find-entire-word-checkbox-label = Ñe’ẽ oĩmbáva
pdfjs-find-reached-top = Ojehupyty kuatia ñepyrũ, oku’ejeýta kuatia paha guive
pdfjs-find-reached-bottom = Ojehupyty kuatia paha, oku’ejeýta kuatia ñepyrũ guive
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } ha { $total } ojueheguáva
*[other] { $current } ha { $total } ojueheguáva
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Hetave { $limit } ojueheguáva
*[other] Hetave { $limit } ojueheguáva
}
pdfjs-find-not-found = Ñe’ẽrysýi ojejuhu’ỹva
## Predefined zoom values
pdfjs-page-scale-width = Kuatiarogue pekue
pdfjs-page-scale-fit = Kuatiarogue ñemoĩporã
pdfjs-page-scale-auto = Tuichakue ijeheguíva
pdfjs-page-scale-actual = Tuichakue ag̃agua
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Kuatiarogue { $page }
## Loading indicator messages
pdfjs-loading-error = Oiko jejavy PDF oñemyeñyhẽnguévo.
pdfjs-invalid-file-error = PDF marandurenda ndoikóiva térã ivaipyréva.
pdfjs-missing-file-error = Ndaipóri PDF marandurenda
pdfjs-unexpected-response-error = Mohendahavusu mbohovái eha’ãrõ’ỹva.
pdfjs-rendering-error = Oiko jejavy ehechaukasévo kuatiarogue.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [Jehaipy { $type }]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Emoinge ñe’ẽñemi eipe’a hag̃ua ko marandurenda PDF.
pdfjs-password-invalid = Ñe’ẽñemi ndoikóiva. Eha’ã jey.
pdfjs-password-ok-button = MONEĨ
pdfjs-password-cancel-button = Heja
pdfjs-web-fonts-disabled = Ñanduti taity oñemongéma: ndaikatumo’ãi eiporu PDF jehai’íva taity.
## Editing
pdfjs-editor-free-text-button =
.title = Moñe’ẽrã
pdfjs-editor-free-text-button-label = Moñe’ẽrã
pdfjs-editor-ink-button =
.title = Moha’ãnga
pdfjs-editor-ink-button-label = Moha’ãnga
pdfjs-editor-stamp-button =
.title = Embojuaju térã embosako’i ta’ãnga
pdfjs-editor-stamp-button-label = Embojuaju térã embosako’i ta’ãnga
pdfjs-editor-highlight-button =
.title = Mbosa’y
pdfjs-editor-highlight-button-label = Mbosa’y
pdfjs-highlight-floating-button1 =
.title = Mbosa’y
.aria-label = Mbosa’y
pdfjs-highlight-floating-button-label = Mbosa’y
pdfjs-editor-signature-button =
.title = Embojuaju teraguapy
pdfjs-editor-signature-button-label = Embojuaju teraguapy
## Default editor aria labels
# “Highlight” is a noun, the string is used on the editor for highlights.
pdfjs-editor-highlight-editor =
.aria-label = Jehechaukarã mbosako’iha
# “Drawing” is a noun, the string is used on the editor for drawings.
pdfjs-editor-ink-editor =
.aria-label = Ta’ãnga’apo moheñoiha
pdfjs-editor-signature-editor =
.aria-label = Teraguapy moheñoiha
pdfjs-editor-stamp-editor =
.aria-label = Ta’ãnga mbosako’iha
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Emboguete ta’ãnga
pdfjs-editor-remove-freetext-button =
.title = Emboguete moñe’ẽrã
pdfjs-editor-remove-stamp-button =
.title = Emboguete ta’ãnga
pdfjs-editor-remove-highlight-button =
.title = Eipe’a jehechaveha
pdfjs-editor-remove-signature-button =
.title = Embogue teraguapy
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Sa’y
pdfjs-editor-free-text-size-input = Tuichakue
pdfjs-editor-ink-color-input = Sa’y
pdfjs-editor-ink-thickness-input = Anambusu
pdfjs-editor-ink-opacity-input = Pytũngy
pdfjs-editor-stamp-add-image-button =
.title = Embojuaju ta’ãnga
pdfjs-editor-stamp-add-image-button-label = Embojuaju ta’ãnga
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Anambusu
pdfjs-editor-free-highlight-thickness-title =
.title = Emoambue anambusukue embosa’ývo mba’eporu ha’e’ỹva moñe’ẽrã
pdfjs-editor-add-signature-container =
.aria-label = Teraguapy ñemaña ha teraguapy ñongatupyre
pdfjs-editor-signature-add-signature-button =
.title = Embojuaju teraguapy pyahu
pdfjs-editor-signature-add-signature-button-label = Embojuaju teraguapy pyahu
# Used on the button to use an already saved signature.
# Variables:
# $description (String) - a string describing/labeling the signature.
pdfjs-editor-add-saved-signature-button =
.title = Teraguapy ñongatupyre: { $description }
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Moñe’ẽrã moheñoiha
.default-content = Eñepyrũ ehai…
pdfjs-free-text =
.aria-label = Moñe’ẽrã moheñoiha
pdfjs-free-text-default-content = Ehai ñepyrũ…
pdfjs-ink =
.aria-label = Ta’ãnga moheñoiha
pdfjs-ink-canvas =
.aria-label = Ta’ãnga omoheñóiva poruhára
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Moñe’ẽrã mokõiháva
pdfjs-editor-alt-text-edit-button =
.aria-label = Embojuruja moñe’ẽrã mokõiháva
pdfjs-editor-alt-text-edit-button-label = Embojuruja moñe’ẽrã mokõiháva
pdfjs-editor-alt-text-dialog-label = Eiporavo poravorã
pdfjs-editor-alt-text-dialog-description = Moñe’ẽrã ykepegua (moñe’ẽrã ykepegua) nepytyvõ nderehecháiramo ta’ãnga térã nahenyhẽiramo.
pdfjs-editor-alt-text-add-description-label = Embojuaju ñemoha’ãnga
pdfjs-editor-alt-text-add-description-description = Ehaimi 1 térã 2 ñe’ẽjuaju oñe’ẽva pe téma rehe, ijere térã mba’eapóre.
pdfjs-editor-alt-text-mark-decorative-label = Emongurusu jeguakárõ
pdfjs-editor-alt-text-mark-decorative-description = Ojeporu ta’ãnga jeguakarã, tembe’y térã ta’ãnga ruguarãramo.
pdfjs-editor-alt-text-cancel-button = Heja
pdfjs-editor-alt-text-save-button = Ñongatu
pdfjs-editor-alt-text-decorative-tooltip = Jeguakárõ mongurusupyre
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Techapyrã: “Peteĩ mitãrusu oguapy mesápe okaru hag̃ua”
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Moñe’ẽrã mokõiháva
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Yvate asu gotyo — emoambue tuichakue
pdfjs-editor-resizer-label-top-middle = Yvate mbytépe — emoambue tuichakue
pdfjs-editor-resizer-label-top-right = Yvate akatúape — emoambue tuichakue
pdfjs-editor-resizer-label-middle-right = Mbyte akatúape — emoambue tuichakue
pdfjs-editor-resizer-label-bottom-right = Yvy gotyo akatúape — emoambue tuichakue
pdfjs-editor-resizer-label-bottom-middle = Yvy gotyo mbytépe — emoambue tuichakue
pdfjs-editor-resizer-label-bottom-left = Iguýpe asu gotyo — emoambue tuichakue
pdfjs-editor-resizer-label-middle-left = Mbyte asu gotyo — emoambue tuichakue
pdfjs-editor-resizer-top-left =
.aria-label = Yvate asu gotyo — emoambue tuichakue
pdfjs-editor-resizer-top-middle =
.aria-label = Yvate mbytépe — emoambue tuichakue
pdfjs-editor-resizer-top-right =
.aria-label = Yvate akatúape — emoambue tuichakue
pdfjs-editor-resizer-middle-right =
.aria-label = Mbyte akatúape — emoambue tuichakue
pdfjs-editor-resizer-bottom-right =
.aria-label = Yvy gotyo akatúape — emoambue tuichakue
pdfjs-editor-resizer-bottom-middle =
.aria-label = Yvy gotyo mbytépe — emoambue tuichakue
pdfjs-editor-resizer-bottom-left =
.aria-label = Iguýpe asu gotyo — emoambue tuichakue
pdfjs-editor-resizer-middle-left =
.aria-label = Mbyte asu gotyo — emoambue tuichakue
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Jehechaveha sa’y
pdfjs-editor-colorpicker-button =
.title = Emoambue sa’y
pdfjs-editor-colorpicker-dropdown =
.aria-label = Sa’y poravopyrã
pdfjs-editor-colorpicker-yellow =
.title = Sa’yju
pdfjs-editor-colorpicker-green =
.title = Hovyũ
pdfjs-editor-colorpicker-blue =
.title = Hovy
pdfjs-editor-colorpicker-pink =
.title = Pytãngy
pdfjs-editor-colorpicker-red =
.title = Pyha
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Techaukapa
pdfjs-editor-highlight-show-all-button =
.title = Techaukapa
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Embosako’i moñe’ẽrã mokõiha (ta’ãngáre ñeñe’ẽ)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Embojuaju moñe’ẽrã mokõiha (ta’ãngáre ñeñe’ẽ)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Edescribi ko’ápe…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Ñemyesakã mbykymi opavave ohecha’ỹva upe ta’ãnga térã pe ta’ãnga nahenyhẽiramo.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Ko moñe’ẽrã mokõiha oñemoheñói ijehegui ha ikatu ndoikoporãi.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Eikuaave
pdfjs-editor-new-alt-text-create-automatically-button-label = Emoheñói moñe’ẽrã mokõiha ijeheguíva
pdfjs-editor-new-alt-text-not-now-button = Ani ko’ág̃a
pdfjs-editor-new-alt-text-error-title = Noñemoheñói moñe’ẽrã mokõiha ijeheguíva
pdfjs-editor-new-alt-text-error-description = Ehai ne moñe’ẽrã mokõiha térã eha’ã jey ag̃amieve.
pdfjs-editor-new-alt-text-error-close-button = Mboty
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Emboguejyhína IA moñe’ẽrã mokõiháva ({ $downloadedSize } { $totalSize } MB) mba’e
.aria-valuetext = Emboguejyhína IA moñe’ẽrã mokõiháva ({ $downloadedSize } { $totalSize } MB) mba’e
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Moñe’ẽrã mokõiha mbojuajupyre
pdfjs-editor-new-alt-text-added-button-label = Oñembojuaju moñe’ẽrã mokõiha
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Ndaipóri moñe’ẽrã mokõiha
pdfjs-editor-new-alt-text-missing-button-label = Ndaipóri moñe’ẽrã mokõiha
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Ehechajey moñe’ẽrã mokõiha
pdfjs-editor-new-alt-text-to-review-button-label = Ehechajey moñe’ẽrã mokõiha
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Heñóiva ijeheguiete: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Ta’ãnga moñe’ẽrã mokõiha ñemboheko
pdfjs-image-alt-text-settings-button-label = Ta’ãnga moñe’ẽrã mokõiha ñemboheko
pdfjs-editor-alt-text-settings-dialog-label = Ta’ãnga moñe’ẽrã mokõiha ñemboheko
pdfjs-editor-alt-text-settings-automatic-title = Moñe’ẽrã mokõiha ijeheguíva
pdfjs-editor-alt-text-settings-create-model-button-label = Emoheñói moñe’ẽrã mokõiha ijeheguíva
pdfjs-editor-alt-text-settings-create-model-description = Ñemyesakã mbykymi opavave tapicha ohecha’ỹva upe ta’ãnga térã pe ta’ãnga nahenyhẽiramo.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Peteĩva IA moñe’ẽrã mokõiha ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Oku’e mba’e’okaitépe umi mba’ekuaarã hekoñemi hag̃ua. Tekotevẽva moñe’ẽrã ykegua ijeheguívape.
pdfjs-editor-alt-text-settings-delete-model-button = Mboguete
pdfjs-editor-alt-text-settings-download-model-button = Mboguejy
pdfjs-editor-alt-text-settings-downloading-model-button = Emboguejyhína…
pdfjs-editor-alt-text-settings-editor-title = Moñe’ẽrã mokõiha mbosako’iha
pdfjs-editor-alt-text-settings-show-dialog-button-label = Ehechauka moñe’ẽrã mokõiha mbosako’iha embojuajúvo ta’ãnga
pdfjs-editor-alt-text-settings-show-dialog-description = Nepytyvõta ta’ãngakuéra orekotaha moñe’ẽrã mokõiha.
pdfjs-editor-alt-text-settings-close-button = Mboty
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Mbosa’ýva mboguete
pdfjs-editor-undo-bar-message-freetext = Moñe’ẽrã mboguepyre
pdfjs-editor-undo-bar-message-ink = Ta’ãnga mboguepyre
pdfjs-editor-undo-bar-message-stamp = Ta’ãnga mboguepyre
pdfjs-editor-undo-bar-message-signature = Teraguapy mboguepyre
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } jehaikue mboguepyre
*[other] { $count } jehaikue mboguepyre
}
pdfjs-editor-undo-bar-undo-button =
.title = Mboguevi
pdfjs-editor-undo-bar-undo-button-label = Mboguevi
pdfjs-editor-undo-bar-close-button =
.title = Mboty
pdfjs-editor-undo-bar-close-button-label = Mboty
## Add a signature dialog
pdfjs-editor-add-signature-dialog-title = Embojuaju teraguapy
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Jehai
.title = Jehai
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Moha’ãnga
.title = Moha’ãnga
pdfjs-editor-add-signature-image-button = Ta’ãnga
.title = Ta’ãnga
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Ehai nde reraguapy
.placeholder = Ehai nde reraguapy
pdfjs-editor-add-signature-draw-placeholder = Emoha’ãnga nde reraguapy
pdfjs-editor-add-signature-draw-thickness-range-label = Anambusu
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Ta’ãnga anambusukue: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Egueru marandurenda ápe ehupi hag̃ua
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Eiporavo ta’ãnga marandurenda
*[other] Eiporavo ta’ãnga marandurenda
}
## Controls
pdfjs-editor-add-signature-description-label = Moha’ãnga (moñe’ẽrã ykepegua)
pdfjs-editor-add-signature-description-input =
.title = Moha’ãnga (moñe’ẽrã ykepegua)
pdfjs-editor-add-signature-description-default-when-drawing = Teraguapy
pdfjs-editor-add-signature-clear-button-label = Emboguete teraguapy
pdfjs-editor-add-signature-clear-button =
.title = Emboguete teraguapy
pdfjs-editor-add-signature-save-checkbox = Eñongatu teraguapy
pdfjs-editor-add-signature-save-warning-message = Ehupytýma 5 mboheraguapy ñongatupyre. Embogue peteĩ eñongatukuaa jey hag̃ua.
pdfjs-editor-add-signature-image-upload-error-title = Ndaikatúi ojehupi pe ta’ãnga
pdfjs-editor-add-signature-image-upload-error-description = Ehechajey ne ñanduti oikópa térã aha’ã ambue ta’ãnga ndive.
pdfjs-editor-add-signature-error-close-button = Mboty
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Heja
pdfjs-editor-add-signature-add-button = Mbojuaju
pdfjs-editor-edit-signature-update-button = Mbohekopyahu
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Embogue teraguapy
pdfjs-editor-delete-signature-button-label = Embogue teraguapy
pdfjs-editor-delete-signature-button1 =
.title = Embogue teraguapy ñongatupyre
pdfjs-editor-delete-signature-button-label1 = Embogue teraguapy ñongatupyre
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Embosako’i moha’ãnga
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Embosako’i moha’ãnga
================================================
FILE: cookbook/static/pdfjs/web/locale/gu-IN/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = પહેલાનુ પાનું
pdfjs-previous-button-label = પહેલાનુ
pdfjs-next-button =
.title = આગળનુ પાનું
pdfjs-next-button-label = આગળનું
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = પાનું
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = નો { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } નો { $pagesCount })
pdfjs-zoom-out-button =
.title = મોટુ કરો
pdfjs-zoom-out-button-label = મોટુ કરો
pdfjs-zoom-in-button =
.title = નાનું કરો
pdfjs-zoom-in-button-label = નાનું કરો
pdfjs-zoom-select =
.title = નાનું મોટુ કરો
pdfjs-presentation-mode-button =
.title = રજૂઆત સ્થિતિમાં જાવ
pdfjs-presentation-mode-button-label = રજૂઆત સ્થિતિ
pdfjs-open-file-button =
.title = ફાઇલ ખોલો
pdfjs-open-file-button-label = ખોલો
pdfjs-print-button =
.title = છાપો
pdfjs-print-button-label = છારો
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = સાધનો
pdfjs-tools-button-label = સાધનો
pdfjs-first-page-button =
.title = પહેલાં પાનામાં જાવ
pdfjs-first-page-button-label = પ્રથમ પાનાં પર જાવ
pdfjs-last-page-button =
.title = છેલ્લા પાનાં પર જાવ
pdfjs-last-page-button-label = છેલ્લા પાનાં પર જાવ
pdfjs-page-rotate-cw-button =
.title = ઘડિયાળનાં કાંટા તરફ ફેરવો
pdfjs-page-rotate-cw-button-label = ઘડિયાળનાં કાંટા તરફ ફેરવો
pdfjs-page-rotate-ccw-button =
.title = ઘડિયાળનાં કાંટાની ઉલટી દિશામાં ફેરવો
pdfjs-page-rotate-ccw-button-label = ઘડિયાળનાં કાંટાની વિરુદ્દ ફેરવો
pdfjs-cursor-text-select-tool-button =
.title = ટેક્સ્ટ પસંદગી ટૂલ સક્ષમ કરો
pdfjs-cursor-text-select-tool-button-label = ટેક્સ્ટ પસંદગી ટૂલ
pdfjs-cursor-hand-tool-button =
.title = હાથનાં સાધનને સક્રિય કરો
pdfjs-cursor-hand-tool-button-label = હેન્ડ ટૂલ
pdfjs-scroll-vertical-button =
.title = ઊભી સ્ક્રોલિંગનો ઉપયોગ કરો
pdfjs-scroll-vertical-button-label = ઊભી સ્ક્રોલિંગ
pdfjs-scroll-horizontal-button =
.title = આડી સ્ક્રોલિંગનો ઉપયોગ કરો
pdfjs-scroll-horizontal-button-label = આડી સ્ક્રોલિંગ
pdfjs-scroll-wrapped-button =
.title = આવરિત સ્ક્રોલિંગનો ઉપયોગ કરો
pdfjs-scroll-wrapped-button-label = આવરિત સ્ક્રોલિંગ
pdfjs-spread-none-button =
.title = પૃષ્ઠ સ્પ્રેડમાં જોડાવશો નહીં
pdfjs-spread-none-button-label = કોઈ સ્પ્રેડ નથી
pdfjs-spread-odd-button =
.title = એકી-ક્રમાંકિત પૃષ્ઠો સાથે પ્રારંભ થતાં પૃષ્ઠ સ્પ્રેડમાં જોડાઓ
pdfjs-spread-odd-button-label = એકી સ્પ્રેડ્સ
pdfjs-spread-even-button =
.title = નંબર-ક્રમાંકિત પૃષ્ઠોથી શરૂ થતાં પૃષ્ઠ સ્પ્રેડમાં જોડાઓ
pdfjs-spread-even-button-label = સરખું ફેલાવવું
## Document properties dialog
pdfjs-document-properties-button =
.title = દસ્તાવેજ ગુણધર્મો…
pdfjs-document-properties-button-label = દસ્તાવેજ ગુણધર્મો…
pdfjs-document-properties-file-name = ફાઇલ નામ:
pdfjs-document-properties-file-size = ફાઇલ માપ:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } બાઇટ)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } બાઇટ)
pdfjs-document-properties-title = શીર્ષક:
pdfjs-document-properties-author = લેખક:
pdfjs-document-properties-subject = વિષય:
pdfjs-document-properties-keywords = કિવર્ડ:
pdfjs-document-properties-creation-date = નિર્માણ તારીખ:
pdfjs-document-properties-modification-date = ફેરફાર તારીખ:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = નિર્માતા:
pdfjs-document-properties-producer = PDF નિર્માતા:
pdfjs-document-properties-version = PDF આવૃત્તિ:
pdfjs-document-properties-page-count = પાનાં ગણતરી:
pdfjs-document-properties-page-size = પૃષ્ઠનું કદ:
pdfjs-document-properties-page-size-unit-inches = ઇંચ
pdfjs-document-properties-page-size-unit-millimeters = મીમી
pdfjs-document-properties-page-size-orientation-portrait = ઉભું
pdfjs-document-properties-page-size-orientation-landscape = આડુ
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = પત્ર
pdfjs-document-properties-page-size-name-legal = કાયદાકીય
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = ઝડપી વૅબ દૃશ્ય:
pdfjs-document-properties-linearized-yes = હા
pdfjs-document-properties-linearized-no = ના
pdfjs-document-properties-close-button = બંધ કરો
## Print
pdfjs-print-progress-message = છાપકામ માટે દસ્તાવેજ તૈયાર કરી રહ્યા છે…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = રદ કરો
pdfjs-printing-not-supported = ચેતવણી: છાપવાનું આ બ્રાઉઝર દ્દારા સંપૂર્ણપણે આધારભૂત નથી.
pdfjs-printing-not-ready = Warning: PDF એ છાપવા માટે સંપૂર્ણપણે લાવેલ છે.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = ટૉગલ બાજુપટ્ટી
pdfjs-toggle-sidebar-button-label = ટૉગલ બાજુપટ્ટી
pdfjs-document-outline-button =
.title = દસ્તાવેજની રૂપરેખા બતાવો(બધી આઇટમ્સને વિસ્તૃત/સંકુચિત કરવા માટે ડબલ-ક્લિક કરો)
pdfjs-document-outline-button-label = દસ્તાવેજ રૂપરેખા
pdfjs-attachments-button =
.title = જોડાણોને બતાવો
pdfjs-attachments-button-label = જોડાણો
pdfjs-thumbs-button =
.title = થંબનેલ્સ બતાવો
pdfjs-thumbs-button-label = થંબનેલ્સ
pdfjs-findbar-button =
.title = દસ્તાવેજમાં શોધો
pdfjs-findbar-button-label = શોધો
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = પાનું { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = પાનાં { $page } નું થંબનેલ્સ
## Find panel button title and messages
pdfjs-find-input =
.title = શોધો
.placeholder = દસ્તાવેજમાં શોધો…
pdfjs-find-previous-button =
.title = શબ્દસમૂહની પાછલી ઘટનાને શોધો
pdfjs-find-previous-button-label = પહેલાંનુ
pdfjs-find-next-button =
.title = શબ્દસમૂહની આગળની ઘટનાને શોધો
pdfjs-find-next-button-label = આગળનું
pdfjs-find-highlight-checkbox = બધુ પ્રકાશિત કરો
pdfjs-find-match-case-checkbox-label = કેસ બંધબેસાડો
pdfjs-find-entire-word-checkbox-label = સંપૂર્ણ શબ્દો
pdfjs-find-reached-top = દસ્તાવેજનાં ટોચે પહોંચી ગયા, તળિયેથી ચાલુ કરેલ હતુ
pdfjs-find-reached-bottom = દસ્તાવેજનાં અંતે પહોંચી ગયા, ઉપરથી ચાલુ કરેલ હતુ
pdfjs-find-not-found = શબ્દસમૂહ મળ્યુ નથી
## Predefined zoom values
pdfjs-page-scale-width = પાનાની પહોળાઇ
pdfjs-page-scale-fit = પાનું બંધબેસતુ
pdfjs-page-scale-auto = આપમેળે નાનુંમોટુ કરો
pdfjs-page-scale-actual = ચોક્કસ માપ
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
## Loading indicator messages
pdfjs-loading-error = ભૂલ ઉદ્ભવી જ્યારે PDF ને લાવી રહ્યા હોય.
pdfjs-invalid-file-error = અયોગ્ય અથવા ભાંગેલ PDF ફાઇલ.
pdfjs-missing-file-error = ગુમ થયેલ PDF ફાઇલ.
pdfjs-unexpected-response-error = અનપેક્ષિત સર્વર પ્રતિસાદ.
pdfjs-rendering-error = ભૂલ ઉદ્ભવી જ્યારે પાનાંનુ રેન્ડ કરી રહ્યા હોય.
## Annotations
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } Annotation]
## Password
pdfjs-password-label = આ PDF ફાઇલને ખોલવા પાસવર્ડને દાખલ કરો.
pdfjs-password-invalid = અયોગ્ય પાસવર્ડ. મહેરબાની કરીને ફરી પ્રયત્ન કરો.
pdfjs-password-ok-button = બરાબર
pdfjs-password-cancel-button = રદ કરો
pdfjs-web-fonts-disabled = વેબ ફોન્ટ નિષ્ક્રિય થયેલ છે: ઍમ્બેડ થયેલ PDF ફોન્ટને વાપરવાનું અસમર્થ.
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/he/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = דף קודם
pdfjs-previous-button-label = קודם
pdfjs-next-button =
.title = דף הבא
pdfjs-next-button-label = הבא
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = דף
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = מתוך { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } מתוך { $pagesCount })
pdfjs-zoom-out-button =
.title = התרחקות
pdfjs-zoom-out-button-label = התרחקות
pdfjs-zoom-in-button =
.title = התקרבות
pdfjs-zoom-in-button-label = התקרבות
pdfjs-zoom-select =
.title = מרחק מתצוגה
pdfjs-presentation-mode-button =
.title = מעבר למצב מצגת
pdfjs-presentation-mode-button-label = מצב מצגת
pdfjs-open-file-button =
.title = פתיחת קובץ
pdfjs-open-file-button-label = פתיחה
pdfjs-print-button =
.title = הדפסה
pdfjs-print-button-label = הדפסה
pdfjs-save-button =
.title = שמירה
pdfjs-save-button-label = שמירה
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = הורדה
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = הורדה
pdfjs-bookmark-button =
.title = עמוד נוכחי (הצגת כתובת האתר מהעמוד הנוכחי)
pdfjs-bookmark-button-label = עמוד נוכחי
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = כלים
pdfjs-tools-button-label = כלים
pdfjs-first-page-button =
.title = מעבר לעמוד הראשון
pdfjs-first-page-button-label = מעבר לעמוד הראשון
pdfjs-last-page-button =
.title = מעבר לעמוד האחרון
pdfjs-last-page-button-label = מעבר לעמוד האחרון
pdfjs-page-rotate-cw-button =
.title = הטיה עם כיוון השעון
pdfjs-page-rotate-cw-button-label = הטיה עם כיוון השעון
pdfjs-page-rotate-ccw-button =
.title = הטיה כנגד כיוון השעון
pdfjs-page-rotate-ccw-button-label = הטיה כנגד כיוון השעון
pdfjs-cursor-text-select-tool-button =
.title = הפעלת כלי בחירת טקסט
pdfjs-cursor-text-select-tool-button-label = כלי בחירת טקסט
pdfjs-cursor-hand-tool-button =
.title = הפעלת כלי היד
pdfjs-cursor-hand-tool-button-label = כלי יד
pdfjs-scroll-page-button =
.title = שימוש בגלילת עמוד
pdfjs-scroll-page-button-label = גלילת עמוד
pdfjs-scroll-vertical-button =
.title = שימוש בגלילה אנכית
pdfjs-scroll-vertical-button-label = גלילה אנכית
pdfjs-scroll-horizontal-button =
.title = שימוש בגלילה אופקית
pdfjs-scroll-horizontal-button-label = גלילה אופקית
pdfjs-scroll-wrapped-button =
.title = שימוש בגלילה רציפה
pdfjs-scroll-wrapped-button-label = גלילה רציפה
pdfjs-spread-none-button =
.title = לא לצרף מפתחי עמודים
pdfjs-spread-none-button-label = ללא מפתחים
pdfjs-spread-odd-button =
.title = צירוף מפתחי עמודים שמתחילים בדפים עם מספרים אי־זוגיים
pdfjs-spread-odd-button-label = מפתחים אי־זוגיים
pdfjs-spread-even-button =
.title = צירוף מפתחי עמודים שמתחילים בדפים עם מספרים זוגיים
pdfjs-spread-even-button-label = מפתחים זוגיים
## Document properties dialog
pdfjs-document-properties-button =
.title = מאפייני מסמך…
pdfjs-document-properties-button-label = מאפייני מסמך…
pdfjs-document-properties-file-name = שם קובץ:
pdfjs-document-properties-file-size = גודל הקובץ:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } ק״ב ({ $b } בתים)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } מ״ב ({ $b } בתים)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } ק״ב ({ $size_b } בתים)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } מ״ב ({ $size_b } בתים)
pdfjs-document-properties-title = כותרת:
pdfjs-document-properties-author = מחבר:
pdfjs-document-properties-subject = נושא:
pdfjs-document-properties-keywords = מילות מפתח:
pdfjs-document-properties-creation-date = תאריך יצירה:
pdfjs-document-properties-modification-date = תאריך שינוי:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = יוצר:
pdfjs-document-properties-producer = יצרן PDF:
pdfjs-document-properties-version = גרסת PDF:
pdfjs-document-properties-page-count = מספר דפים:
pdfjs-document-properties-page-size = גודל העמוד:
pdfjs-document-properties-page-size-unit-inches = אינ׳
pdfjs-document-properties-page-size-unit-millimeters = מ״מ
pdfjs-document-properties-page-size-orientation-portrait = לאורך
pdfjs-document-properties-page-size-orientation-landscape = לרוחב
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = מכתב
pdfjs-document-properties-page-size-name-legal = דף משפטי
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = תצוגת דף מהירה:
pdfjs-document-properties-linearized-yes = כן
pdfjs-document-properties-linearized-no = לא
pdfjs-document-properties-close-button = סגירה
## Print
pdfjs-print-progress-message = מסמך בהכנה להדפסה…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = ביטול
pdfjs-printing-not-supported = אזהרה: הדפסה אינה נתמכת במלואה בדפדפן זה.
pdfjs-printing-not-ready = אזהרה: מסמך ה־PDF לא נטען לחלוטין עד מצב שמאפשר הדפסה.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = הצגה/הסתרה של סרגל הצד
pdfjs-toggle-sidebar-notification-button =
.title = החלפת תצוגת סרגל צד (מסמך שמכיל תוכן עניינים/קבצים מצורפים/שכבות)
pdfjs-toggle-sidebar-button-label = הצגה/הסתרה של סרגל הצד
pdfjs-document-outline-button =
.title = הצגת תוכן העניינים של המסמך (לחיצה כפולה כדי להרחיב או לצמצם את כל הפריטים)
pdfjs-document-outline-button-label = תוכן העניינים של המסמך
pdfjs-attachments-button =
.title = הצגת צרופות
pdfjs-attachments-button-label = צרופות
pdfjs-layers-button =
.title = הצגת שכבות (יש ללחוץ לחיצה כפולה כדי לאפס את כל השכבות למצב ברירת המחדל)
pdfjs-layers-button-label = שכבות
pdfjs-thumbs-button =
.title = הצגת תצוגה מקדימה
pdfjs-thumbs-button-label = תצוגה מקדימה
pdfjs-current-outline-item-button =
.title = מציאת פריט תוכן העניינים הנוכחי
pdfjs-current-outline-item-button-label = פריט תוכן העניינים הנוכחי
pdfjs-findbar-button =
.title = חיפוש במסמך
pdfjs-findbar-button-label = חיפוש
pdfjs-additional-layers = שכבות נוספות
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = עמוד { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = תצוגה מקדימה של עמוד { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = חיפוש
.placeholder = חיפוש במסמך…
pdfjs-find-previous-button =
.title = מציאת המופע הקודם של הביטוי
pdfjs-find-previous-button-label = קודם
pdfjs-find-next-button =
.title = מציאת המופע הבא של הביטוי
pdfjs-find-next-button-label = הבא
pdfjs-find-highlight-checkbox = הדגשת הכול
pdfjs-find-match-case-checkbox-label = התאמת אותיות
pdfjs-find-match-diacritics-checkbox-label = התאמה דיאקריטית
pdfjs-find-entire-word-checkbox-label = מילים שלמות
pdfjs-find-reached-top = הגיע לראש הדף, ממשיך מלמטה
pdfjs-find-reached-bottom = הגיע לסוף הדף, ממשיך מלמעלה
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } מתוך { $total } תוצאות
*[other] { $current } מתוך { $total } תוצאות
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] יותר מתוצאה אחת
*[other] יותר מ־{ $limit } תוצאות
}
pdfjs-find-not-found = הביטוי לא נמצא
## Predefined zoom values
pdfjs-page-scale-width = רוחב העמוד
pdfjs-page-scale-fit = התאמה לעמוד
pdfjs-page-scale-auto = מרחק מתצוגה אוטומטי
pdfjs-page-scale-actual = גודל אמיתי
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = עמוד { $page }
## Loading indicator messages
pdfjs-loading-error = אירעה שגיאה בעת טעינת ה־PDF.
pdfjs-invalid-file-error = קובץ PDF פגום או לא תקין.
pdfjs-missing-file-error = קובץ PDF חסר.
pdfjs-unexpected-response-error = תגובת שרת לא צפויה.
pdfjs-rendering-error = אירעה שגיאה בעת עיבוד הדף.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [הערת { $type }]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = נא להכניס את הססמה לפתיחת קובץ PDF זה.
pdfjs-password-invalid = ססמה שגויה. נא לנסות שוב.
pdfjs-password-ok-button = אישור
pdfjs-password-cancel-button = ביטול
pdfjs-web-fonts-disabled = גופני רשת מנוטרלים: לא ניתן להשתמש בגופני PDF מוטבעים.
## Editing
pdfjs-editor-free-text-button =
.title = טקסט
pdfjs-editor-free-text-button-label = טקסט
pdfjs-editor-ink-button =
.title = ציור
pdfjs-editor-ink-button-label = ציור
pdfjs-editor-stamp-button =
.title = הוספה או עריכת תמונות
pdfjs-editor-stamp-button-label = הוספה או עריכת תמונות
pdfjs-editor-highlight-button =
.title = סימון
pdfjs-editor-highlight-button-label = סימון
pdfjs-highlight-floating-button1 =
.title = סימון
.aria-label = סימון
pdfjs-highlight-floating-button-label = סימון
pdfjs-editor-signature-button =
.title = הוספת חתימה
pdfjs-editor-signature-button-label = הוספת חתימה
## Default editor aria labels
# “Highlight” is a noun, the string is used on the editor for highlights.
pdfjs-editor-highlight-editor =
.aria-label = עורך סימונים
# “Drawing” is a noun, the string is used on the editor for drawings.
pdfjs-editor-ink-editor =
.aria-label = עורך ציורים
pdfjs-editor-signature-editor =
.aria-label = עורך חתימות
pdfjs-editor-stamp-editor =
.aria-label = עורך תמונות
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = הסרת ציור
pdfjs-editor-remove-freetext-button =
.title = הסרת טקסט
pdfjs-editor-remove-stamp-button =
.title = הסרת תמונה
pdfjs-editor-remove-highlight-button =
.title = הסרת סימון
pdfjs-editor-remove-signature-button =
.title = הסרת חתימה
##
# Editor Parameters
pdfjs-editor-free-text-color-input = צבע
pdfjs-editor-free-text-size-input = גודל
pdfjs-editor-ink-color-input = צבע
pdfjs-editor-ink-thickness-input = עובי
pdfjs-editor-ink-opacity-input = אטימות
pdfjs-editor-stamp-add-image-button =
.title = הוספת תמונה
pdfjs-editor-stamp-add-image-button-label = הוספת תמונה
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = עובי
pdfjs-editor-free-highlight-thickness-title =
.title = שינוי עובי בעת סימון פריטים שאינם טקסט
pdfjs-editor-add-signature-container =
.aria-label = פקדי חתימה וחתימות שמורות
pdfjs-editor-signature-add-signature-button =
.title = הוספת חתימה חדשה
pdfjs-editor-signature-add-signature-button-label = הוספת חתימה חדשה
# Used on the button to use an already saved signature.
# Variables:
# $description (String) - a string describing/labeling the signature.
pdfjs-editor-add-saved-signature-button =
.title = חתימה שמורה: { $description }
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = עורך טקסט
.default-content = נא להתחיל להקליד…
pdfjs-free-text =
.aria-label = עורך טקסט
pdfjs-free-text-default-content = להתחיל להקליד…
pdfjs-ink =
.aria-label = עורך ציור
pdfjs-ink-canvas =
.aria-label = תמונה שנוצרה על־ידי משתמש
## Alt-text dialog
pdfjs-editor-alt-text-button-label = טקסט חלופי
pdfjs-editor-alt-text-edit-button =
.aria-label = עריכת טקסט חלופי
pdfjs-editor-alt-text-edit-button-label = עריכת טקסט חלופי
pdfjs-editor-alt-text-dialog-label = בחירת אפשרות
pdfjs-editor-alt-text-dialog-description = טקסט חלופי עוזר כשאנשים לא יכולים לראות את התמונה או כשהיא לא נטענת.
pdfjs-editor-alt-text-add-description-label = הוספת תיאור
pdfjs-editor-alt-text-add-description-description = כדאי לתאר במשפט אחד או שניים את הנושא, התפאורה או הפעולות.
pdfjs-editor-alt-text-mark-decorative-label = סימון כדקורטיבי
pdfjs-editor-alt-text-mark-decorative-description = זה משמש לתמונות נוי, כמו גבולות או סימני מים.
pdfjs-editor-alt-text-cancel-button = ביטול
pdfjs-editor-alt-text-save-button = שמירה
pdfjs-editor-alt-text-decorative-tooltip = מסומן כדקורטיבי
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = לדוגמה, ״גבר צעיר מתיישב ליד שולחן לאכול ארוחה״
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = טקסט חלופי
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = פינה שמאלית עליונה - שינוי גודל
pdfjs-editor-resizer-label-top-middle = למעלה באמצע - שינוי גודל
pdfjs-editor-resizer-label-top-right = פינה ימנית עליונה - שינוי גודל
pdfjs-editor-resizer-label-middle-right = ימינה באמצע - שינוי גודל
pdfjs-editor-resizer-label-bottom-right = פינה ימנית תחתונה - שינוי גודל
pdfjs-editor-resizer-label-bottom-middle = למטה באמצע - שינוי גודל
pdfjs-editor-resizer-label-bottom-left = פינה שמאלית תחתונה - שינוי גודל
pdfjs-editor-resizer-label-middle-left = שמאלה באמצע - שינוי גודל
pdfjs-editor-resizer-top-left =
.aria-label = פינה שמאלית עליונה - שינוי גודל
pdfjs-editor-resizer-top-middle =
.aria-label = למעלה באמצע - שינוי גודל
pdfjs-editor-resizer-top-right =
.aria-label = פינה ימנית עליונה - שינוי גודל
pdfjs-editor-resizer-middle-right =
.aria-label = ימינה באמצע - שינוי גודל
pdfjs-editor-resizer-bottom-right =
.aria-label = פינה ימנית תחתונה - שינוי גודל
pdfjs-editor-resizer-bottom-middle =
.aria-label = למטה באמצע - שינוי גודל
pdfjs-editor-resizer-bottom-left =
.aria-label = פינה שמאלית תחתונה - שינוי גודל
pdfjs-editor-resizer-middle-left =
.aria-label = שמאלה באמצע - שינוי גודל
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = צבע סימון
pdfjs-editor-colorpicker-button =
.title = שינוי צבע
pdfjs-editor-colorpicker-dropdown =
.aria-label = בחירת צבע
pdfjs-editor-colorpicker-yellow =
.title = צהוב
pdfjs-editor-colorpicker-green =
.title = ירוק
pdfjs-editor-colorpicker-blue =
.title = כחול
pdfjs-editor-colorpicker-pink =
.title = ורוד
pdfjs-editor-colorpicker-red =
.title = אדום
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = הצגת הכול
pdfjs-editor-highlight-show-all-button =
.title = הצגת הכול
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = עריכת טקסט חלופי (תיאור תמונה)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = הוספת טקסט חלופי (תיאור תמונה)
pdfjs-editor-new-alt-text-textarea =
.placeholder = נא לכתוב את התיאור שלך כאן…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = תיאור קצר לאנשים שאינם יכולים לראות את התמונה או כאשר התמונה אינה נטענת.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = טקסט חלופי זה נוצר באופן אוטומטי ועשוי להיות לא מדויק.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = מידע נוסף
pdfjs-editor-new-alt-text-create-automatically-button-label = יצירת טקסט חלופי באופן אוטומטי
pdfjs-editor-new-alt-text-not-now-button = לא כעת
pdfjs-editor-new-alt-text-error-title = לא ניתן היה ליצור טקסט חלופי באופן אוטומטי
pdfjs-editor-new-alt-text-error-description = נא לכתוב טקסט חלופי משלך או לנסות שוב מאוחר יותר.
pdfjs-editor-new-alt-text-error-close-button = סגירה
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = בתהליך הורדת מודל AI של טקסט חלופי ({ $downloadedSize } מתוך { $totalSize } מ״ב)
.aria-valuetext = בתהליך הורדת מודל AI של טקסט חלופי ({ $downloadedSize } מתוך { $totalSize } מ״ב)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = נוסף טקסט חלופי
pdfjs-editor-new-alt-text-added-button-label = נוסף טקסט חלופי
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = חסר טקסט חלופי
pdfjs-editor-new-alt-text-missing-button-label = חסר טקסט חלופי
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = סקירת טקסט חלופי
pdfjs-editor-new-alt-text-to-review-button-label = סקירת טקסט חלופי
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = נוצר באופן אוטומטי: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = הגדרות טקסט חלופי של תמונה
pdfjs-image-alt-text-settings-button-label = הגדרות טקסט חלופי של תמונה
pdfjs-editor-alt-text-settings-dialog-label = הגדרות טקסט חלופי של תמונה
pdfjs-editor-alt-text-settings-automatic-title = טקסט חלופי אוטומטי
pdfjs-editor-alt-text-settings-create-model-button-label = יצירת טקסט חלופי באופן אוטומטי
pdfjs-editor-alt-text-settings-create-model-description = הצעת תיאורים כדי לסייע לאנשים שאינם יכולים לראות את התמונה או כאשר התמונה אינה נטענת.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = מודל AI לטקסט חלופי ({ $totalSize } מ״ב)
pdfjs-editor-alt-text-settings-ai-model-description = פועל באופן מקומי במכשיר שלך כך שהנתונים שלך נשארים פרטיים. נדרש עבור טקסט חלופי אוטומטי.
pdfjs-editor-alt-text-settings-delete-model-button = מחיקה
pdfjs-editor-alt-text-settings-download-model-button = הורדה
pdfjs-editor-alt-text-settings-downloading-model-button = בהורדה…
pdfjs-editor-alt-text-settings-editor-title = עורך טקסט חלופי
pdfjs-editor-alt-text-settings-show-dialog-button-label = הצגת עורך טקסט חלופי מיד בעת הוספת תמונה
pdfjs-editor-alt-text-settings-show-dialog-description = מסייע לך לוודא שלכל התמונות שלך יש טקסט חלופי.
pdfjs-editor-alt-text-settings-close-button = סגירה
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = הסימון הוסר
pdfjs-editor-undo-bar-message-freetext = הטקסט הוסר
pdfjs-editor-undo-bar-message-ink = הציור הוסר
pdfjs-editor-undo-bar-message-stamp = התמונה הוסרה
pdfjs-editor-undo-bar-message-signature = החתימה הוסרה
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] הערה אחת הוסרה
*[other] { $count } הערות הוסרו
}
pdfjs-editor-undo-bar-undo-button =
.title = ביטול פעולה
pdfjs-editor-undo-bar-undo-button-label = ביטול פעלה
pdfjs-editor-undo-bar-close-button =
.title = סגירה
pdfjs-editor-undo-bar-close-button-label = סגירה
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = מודל זה מאפשר למשתמש ליצור חתימה להוספה למסמך PDF. המשתמש יכול לערוך את השם (שמשמש גם כטקסט האלטרנטיבי), ובאופן אופציונלי לשמור את החתימה לשימוש חוזר.
pdfjs-editor-add-signature-dialog-title = הוספת חתימה
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = הקלדה
.title = הקלדה
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = ציור
.title = ציור
pdfjs-editor-add-signature-image-button = תמונה
.title = תמונה
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = נא להקליד את החתימה שלך
.placeholder = נא להקליד את החתימה שלך
pdfjs-editor-add-signature-draw-placeholder = נא לצייר את החתימה שלך
pdfjs-editor-add-signature-draw-thickness-range-label = עובי
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = עובי הציור: { $thickness }
pdfjs-editor-add-signature-image-placeholder = יש לגרור לכאן קובץ להעלאה
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] או לבחור בקובצי תמונה
*[other] או לעיין בקובצי תמונה
}
## Controls
pdfjs-editor-add-signature-description-label = תיאור (טקסט חלופי)
pdfjs-editor-add-signature-description-input =
.title = תיאור (טקסט חלופי)
pdfjs-editor-add-signature-description-default-when-drawing = חתימה
pdfjs-editor-add-signature-clear-button-label = ניקוי חתימה
pdfjs-editor-add-signature-clear-button =
.title = ניקוי חתימה
pdfjs-editor-add-signature-save-checkbox = שמירת החתימה
pdfjs-editor-add-signature-save-warning-message = הגעת למגבלה של 5 חתימות שמורות. יש להסיר אחד כדי לשמור עוד.
pdfjs-editor-add-signature-image-upload-error-title = לא ניתן להעלות את התמונה
pdfjs-editor-add-signature-image-upload-error-description = נא לבדוק את החיבור שלך לרשת או לנסות תמונה אחרת.
pdfjs-editor-add-signature-error-close-button = סגירה
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = ביטול
pdfjs-editor-add-signature-add-button = הוספה
pdfjs-editor-edit-signature-update-button = עדכון
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = הסרת חתימה
pdfjs-editor-delete-signature-button-label = הסרת חתימה
pdfjs-editor-delete-signature-button1 =
.title = הסרת חתימה שמורה
pdfjs-editor-delete-signature-button-label1 = הסרת חתימה שמורה
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = עריכת תיאור
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = עריכת תיאור
================================================
FILE: cookbook/static/pdfjs/web/locale/hi-IN/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = पिछला पृष्ठ
pdfjs-previous-button-label = पिछला
pdfjs-next-button =
.title = अगला पृष्ठ
pdfjs-next-button-label = आगे
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = पृष्ठ:
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = { $pagesCount } का
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } of { $pagesCount })
pdfjs-zoom-out-button =
.title = छोटा करें
pdfjs-zoom-out-button-label = छोटा करें
pdfjs-zoom-in-button =
.title = बड़ा करें
pdfjs-zoom-in-button-label = बड़ा करें
pdfjs-zoom-select =
.title = बड़ा-छोटा करें
pdfjs-presentation-mode-button =
.title = प्रस्तुति अवस्था में जाएँ
pdfjs-presentation-mode-button-label = प्रस्तुति अवस्था
pdfjs-open-file-button =
.title = फ़ाइल खोलें
pdfjs-open-file-button-label = खोलें
pdfjs-print-button =
.title = छापें
pdfjs-print-button-label = छापें
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = औज़ार
pdfjs-tools-button-label = औज़ार
pdfjs-first-page-button =
.title = प्रथम पृष्ठ पर जाएँ
pdfjs-first-page-button-label = प्रथम पृष्ठ पर जाएँ
pdfjs-last-page-button =
.title = अंतिम पृष्ठ पर जाएँ
pdfjs-last-page-button-label = अंतिम पृष्ठ पर जाएँ
pdfjs-page-rotate-cw-button =
.title = घड़ी की दिशा में घुमाएँ
pdfjs-page-rotate-cw-button-label = घड़ी की दिशा में घुमाएँ
pdfjs-page-rotate-ccw-button =
.title = घड़ी की दिशा से उल्टा घुमाएँ
pdfjs-page-rotate-ccw-button-label = घड़ी की दिशा से उल्टा घुमाएँ
pdfjs-cursor-text-select-tool-button =
.title = पाठ चयन उपकरण सक्षम करें
pdfjs-cursor-text-select-tool-button-label = पाठ चयन उपकरण
pdfjs-cursor-hand-tool-button =
.title = हस्त उपकरण सक्षम करें
pdfjs-cursor-hand-tool-button-label = हस्त उपकरण
pdfjs-scroll-vertical-button =
.title = लंबवत स्क्रॉलिंग का उपयोग करें
pdfjs-scroll-vertical-button-label = लंबवत स्क्रॉलिंग
pdfjs-scroll-horizontal-button =
.title = क्षितिजिय स्क्रॉलिंग का उपयोग करें
pdfjs-scroll-horizontal-button-label = क्षितिजिय स्क्रॉलिंग
pdfjs-scroll-wrapped-button =
.title = व्राप्पेड स्क्रॉलिंग का उपयोग करें
pdfjs-spread-none-button-label = कोई स्प्रेड उपलब्ध नहीं
pdfjs-spread-odd-button =
.title = विषम-क्रमांकित पृष्ठों से प्रारंभ होने वाले पृष्ठ स्प्रेड में शामिल हों
pdfjs-spread-odd-button-label = विषम फैलाव
## Document properties dialog
pdfjs-document-properties-button =
.title = दस्तावेज़ विशेषता...
pdfjs-document-properties-button-label = दस्तावेज़ विशेषता...
pdfjs-document-properties-file-name = फ़ाइल नाम:
pdfjs-document-properties-file-size = फाइल आकारः
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = शीर्षक:
pdfjs-document-properties-author = लेखकः
pdfjs-document-properties-subject = विषय:
pdfjs-document-properties-keywords = कुंजी-शब्द:
pdfjs-document-properties-creation-date = निर्माण दिनांक:
pdfjs-document-properties-modification-date = संशोधन दिनांक:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = निर्माता:
pdfjs-document-properties-producer = PDF उत्पादक:
pdfjs-document-properties-version = PDF संस्करण:
pdfjs-document-properties-page-count = पृष्ठ गिनती:
pdfjs-document-properties-page-size = पृष्ठ आकार:
pdfjs-document-properties-page-size-unit-inches = इंच
pdfjs-document-properties-page-size-unit-millimeters = मिमी
pdfjs-document-properties-page-size-orientation-portrait = पोर्ट्रेट
pdfjs-document-properties-page-size-orientation-landscape = लैंडस्केप
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = पत्र
pdfjs-document-properties-page-size-name-legal = क़ानूनी
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = तीव्र वेब व्यू:
pdfjs-document-properties-linearized-yes = हाँ
pdfjs-document-properties-linearized-no = नहीं
pdfjs-document-properties-close-button = बंद करें
## Print
pdfjs-print-progress-message = छपाई के लिए दस्तावेज़ को तैयार किया जा रहा है...
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = रद्द करें
pdfjs-printing-not-supported = चेतावनी: इस ब्राउज़र पर छपाई पूरी तरह से समर्थित नहीं है.
pdfjs-printing-not-ready = चेतावनी: PDF छपाई के लिए पूरी तरह से लोड नहीं है.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = स्लाइडर टॉगल करें
pdfjs-toggle-sidebar-button-label = स्लाइडर टॉगल करें
pdfjs-document-outline-button =
.title = दस्तावेज़ की रूपरेखा दिखाइए (सारी वस्तुओं को फलने अथवा समेटने के लिए दो बार क्लिक करें)
pdfjs-document-outline-button-label = दस्तावेज़ आउटलाइन
pdfjs-attachments-button =
.title = संलग्नक दिखायें
pdfjs-attachments-button-label = संलग्नक
pdfjs-thumbs-button =
.title = लघुछवियाँ दिखाएँ
pdfjs-thumbs-button-label = लघु छवि
pdfjs-findbar-button =
.title = दस्तावेज़ में ढूँढ़ें
pdfjs-findbar-button-label = ढूँढें
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = पृष्ठ { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = पृष्ठ { $page } की लघु-छवि
## Find panel button title and messages
pdfjs-find-input =
.title = ढूँढें
.placeholder = दस्तावेज़ में खोजें...
pdfjs-find-previous-button =
.title = वाक्यांश की पिछली उपस्थिति ढूँढ़ें
pdfjs-find-previous-button-label = पिछला
pdfjs-find-next-button =
.title = वाक्यांश की अगली उपस्थिति ढूँढ़ें
pdfjs-find-next-button-label = अगला
pdfjs-find-highlight-checkbox = सभी आलोकित करें
pdfjs-find-match-case-checkbox-label = मिलान स्थिति
pdfjs-find-entire-word-checkbox-label = संपूर्ण शब्द
pdfjs-find-reached-top = पृष्ठ के ऊपर पहुंच गया, नीचे से जारी रखें
pdfjs-find-reached-bottom = पृष्ठ के नीचे में जा पहुँचा, ऊपर से जारी
pdfjs-find-not-found = वाक्यांश नहीं मिला
## Predefined zoom values
pdfjs-page-scale-width = पृष्ठ चौड़ाई
pdfjs-page-scale-fit = पृष्ठ फिट
pdfjs-page-scale-auto = स्वचालित जूम
pdfjs-page-scale-actual = वास्तविक आकार
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
## Loading indicator messages
pdfjs-loading-error = PDF लोड करते समय एक त्रुटि हुई.
pdfjs-invalid-file-error = अमान्य या भ्रष्ट PDF फ़ाइल.
pdfjs-missing-file-error = अनुपस्थित PDF फ़ाइल.
pdfjs-unexpected-response-error = अप्रत्याशित सर्वर प्रतिक्रिया.
pdfjs-rendering-error = पृष्ठ रेंडरिंग के दौरान त्रुटि आई.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } Annotation]
## Password
pdfjs-password-label = इस PDF फ़ाइल को खोलने के लिए कृपया कूटशब्द भरें.
pdfjs-password-invalid = अवैध कूटशब्द, कृपया फिर कोशिश करें.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = रद्द करें
pdfjs-web-fonts-disabled = वेब फॉन्ट्स निष्क्रिय हैं: अंतःस्थापित PDF फॉन्टस के उपयोग में असमर्थ.
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
# Editor Parameters
pdfjs-editor-free-text-color-input = रंग
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/hr/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Prethodna stranica
pdfjs-previous-button-label = Prethodna
pdfjs-next-button =
.title = Sljedeća stranica
pdfjs-next-button-label = Sljedeća
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Stranica
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = od { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } od { $pagesCount })
pdfjs-zoom-out-button =
.title = Umanji
pdfjs-zoom-out-button-label = Umanji
pdfjs-zoom-in-button =
.title = Uvećaj
pdfjs-zoom-in-button-label = Uvećaj
pdfjs-zoom-select =
.title = Zumiranje
pdfjs-presentation-mode-button =
.title = Prebaci u modus prezentacija
pdfjs-presentation-mode-button-label = Modus prezentacija
pdfjs-open-file-button =
.title = Otvori datoteku
pdfjs-open-file-button-label = Otvori
pdfjs-print-button =
.title = Ispiši
pdfjs-print-button-label = Ispiši
pdfjs-save-button =
.title = Spremi
pdfjs-save-button-label = Spremi
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Preuzimanja
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Preuzimanja
pdfjs-bookmark-button =
.title = Trenutna stranica (pogledajte URL s trenutne stranice)
pdfjs-bookmark-button-label = Trenutna stranica
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Alati
pdfjs-tools-button-label = Alati
pdfjs-first-page-button =
.title = Idi na prvu stranicu
pdfjs-first-page-button-label = Idi na prvu stranicu
pdfjs-last-page-button =
.title = Idi na posljednju stranicu
pdfjs-last-page-button-label = Idi na posljednju stranicu
pdfjs-page-rotate-cw-button =
.title = Rotiraj u smjeru kazaljke na satu
pdfjs-page-rotate-cw-button-label = Rotiraj u smjeru kazaljke na satu
pdfjs-page-rotate-ccw-button =
.title = Rotiraj obrnutno od smjera kazaljke na satu
pdfjs-page-rotate-ccw-button-label = Rotiraj obrnutno od smjera kazaljke na satu
pdfjs-cursor-text-select-tool-button =
.title = Aktiviraj alat za biranje teksta
pdfjs-cursor-text-select-tool-button-label = Alat za označavanje teksta
pdfjs-cursor-hand-tool-button =
.title = Aktiviraj ručni alat
pdfjs-cursor-hand-tool-button-label = Ručni alat
pdfjs-scroll-page-button =
.title = Koristi klizanje stranice
pdfjs-scroll-page-button-label = Klizanje stranice
pdfjs-scroll-vertical-button =
.title = Koristi okomito pomicanje
pdfjs-scroll-vertical-button-label = Okomito pomicanje
pdfjs-scroll-horizontal-button =
.title = Koristi vodoravno pomicanje
pdfjs-scroll-horizontal-button-label = Vodoravno pomicanje
pdfjs-scroll-wrapped-button =
.title = Koristi kontinuirani raspored stranica
pdfjs-scroll-wrapped-button-label = Kontinuirani raspored stranica
pdfjs-spread-none-button =
.title = Ne izrađuj duplerice
pdfjs-spread-none-button-label = Pojedinačne stranice
pdfjs-spread-odd-button =
.title = Izradi duplerice koje počinju s neparnim stranicama
pdfjs-spread-odd-button-label = Neparne duplerice
pdfjs-spread-even-button =
.title = Izradi duplerice koje počinju s parnim stranicama
pdfjs-spread-even-button-label = Parne duplerice
## Document properties dialog
pdfjs-document-properties-button =
.title = Svojstva dokumenta …
pdfjs-document-properties-button-label = Svojstva dokumenta …
pdfjs-document-properties-file-name = Ime datoteke:
pdfjs-document-properties-file-size = Veličina datoteke:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bajtova)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bajtova)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bajtova)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bajtova)
pdfjs-document-properties-title = Naslov:
pdfjs-document-properties-author = Autor:
pdfjs-document-properties-subject = Predmet:
pdfjs-document-properties-keywords = Ključne riječi:
pdfjs-document-properties-creation-date = Datum stvaranja:
pdfjs-document-properties-modification-date = Datum promjene:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Stvaratelj:
pdfjs-document-properties-producer = PDF stvaratelj:
pdfjs-document-properties-version = PDF verzija:
pdfjs-document-properties-page-count = Broj stranica:
pdfjs-document-properties-page-size = Dimenzije stranice:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = uspravno
pdfjs-document-properties-page-size-orientation-landscape = položeno
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letter
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Brzi web pregled:
pdfjs-document-properties-linearized-yes = Da
pdfjs-document-properties-linearized-no = Ne
pdfjs-document-properties-close-button = Zatvori
## Print
pdfjs-print-progress-message = Pripremanje dokumenta za ispis…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Odustani
pdfjs-printing-not-supported = Upozorenje: Ovaj preglednik ne podržava u potpunosti ispisivanje.
pdfjs-printing-not-ready = Upozorenje: PDF nije u potpunosti učitan za ispis.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Prikaži/sakrij bočnu traku
pdfjs-toggle-sidebar-notification-button =
.title = Prikazivanje i sklanjanje bočne trake (dokument sadrži strukturu/privitke/slojeve)
pdfjs-toggle-sidebar-button-label = Prikaži/sakrij bočnu traku
pdfjs-document-outline-button =
.title = Prikaži strukturu dokumenta (dvostruki klik za rasklapanje/sklapanje svih stavki)
pdfjs-document-outline-button-label = Struktura dokumenta
pdfjs-attachments-button =
.title = Prikaži privitke
pdfjs-attachments-button-label = Privitci
pdfjs-layers-button =
.title = Prikaži slojeve (dvoklik za vraćanje svih slojeva u standardno stanje)
pdfjs-layers-button-label = Slojevi
pdfjs-thumbs-button =
.title = Prikaži minijature
pdfjs-thumbs-button-label = Minijature
pdfjs-current-outline-item-button =
.title = Pronađi trenutačni element strukture
pdfjs-current-outline-item-button-label = Trenutačni element strukture
pdfjs-findbar-button =
.title = Pronađi u dokumentu
pdfjs-findbar-button-label = Pronađi
pdfjs-additional-layers = Dodatni slojevi
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Stranica { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Minijatura stranice { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Pronađi
.placeholder = Pronađi u dokumentu …
pdfjs-find-previous-button =
.title = Pronađi prethodno pojavljivanje ovog izraza
pdfjs-find-previous-button-label = Prethodno
pdfjs-find-next-button =
.title = Pronađi sljedeće pojavljivanje ovog izraza
pdfjs-find-next-button-label = Dalje
pdfjs-find-highlight-checkbox = Istankni sve
pdfjs-find-match-case-checkbox-label = Razlikovanje velikih i malih slova
pdfjs-find-match-diacritics-checkbox-label = Razlikuj dijakritičke znakove
pdfjs-find-entire-word-checkbox-label = Cijele riječi
pdfjs-find-reached-top = Dosegnut početak dokumenta, nastavak s kraja
pdfjs-find-reached-bottom = Dosegnut kraj dokumenta, nastavak s početka
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } od { $total } rezultata
[few] { $current } od { $total } rezultata
*[other] { $current } od { $total } rezultata
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Više od { $limit } rezultat
[few] Više od { $limit } rezultata
*[other] Više od { $limit } rezultata
}
pdfjs-find-not-found = Izraz nije pronađen
## Predefined zoom values
pdfjs-page-scale-width = Prilagodi širini prozora
pdfjs-page-scale-fit = Prilagodi veličini prozora
pdfjs-page-scale-auto = Automatsko zumiranje
pdfjs-page-scale-actual = Stvarna veličina
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale } %
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Stranica { $page }
## Loading indicator messages
pdfjs-loading-error = Došlo je do greške pri učitavanju PDF-a.
pdfjs-invalid-file-error = Neispravna ili oštećena PDF datoteka.
pdfjs-missing-file-error = Nedostaje PDF datoteka.
pdfjs-unexpected-response-error = Neočekivani odgovor servera.
pdfjs-rendering-error = Došlo je do greške prilikom iscrtavanja stranice.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } Bilješka]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Za otvoranje ove PDF datoteku upiši lozinku.
pdfjs-password-invalid = Neispravna lozinka. Pokušaj ponovo.
pdfjs-password-ok-button = U redu
pdfjs-password-cancel-button = Odustani
pdfjs-web-fonts-disabled = Web fontovi su deaktivirani: nije moguće koristiti ugrađene PDF fontove.
## Editing
pdfjs-editor-free-text-button =
.title = Tekst
pdfjs-editor-free-text-button-label = Tekst
pdfjs-editor-ink-button =
.title = Crtanje
pdfjs-editor-ink-button-label = Crtanje
pdfjs-editor-stamp-button =
.title = Dodajte ili uredite slike
pdfjs-editor-stamp-button-label = Dodajte ili uredite slike
pdfjs-editor-highlight-button =
.title = Istakni
pdfjs-editor-highlight-button-label = Istakni
pdfjs-highlight-floating-button1 =
.title = Istakni
.aria-label = Istakni
pdfjs-highlight-floating-button-label = Istakni
## Default editor aria labels
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Ukloni crtež
pdfjs-editor-remove-freetext-button =
.title = Ukloni tekst
pdfjs-editor-remove-stamp-button =
.title = Ukloni sliku
pdfjs-editor-remove-highlight-button =
.title = Ukloni isticanje
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Boja
pdfjs-editor-free-text-size-input = Veličina
pdfjs-editor-ink-color-input = Boja
pdfjs-editor-ink-thickness-input = Debljina
pdfjs-editor-ink-opacity-input = Neprozirnost
pdfjs-editor-stamp-add-image-button =
.title = Dodaj sliku
pdfjs-editor-stamp-add-image-button-label = Dodaj sliku
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Debljina
pdfjs-editor-free-highlight-thickness-title =
.title = Promjeni debljinu pri isticanju drugih stavki osim teksta
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Uređivač teksta
.default-content = Počni tipkati …
pdfjs-free-text =
.aria-label = Uređivač teksta
pdfjs-free-text-default-content = Počni tipkati …
pdfjs-ink =
.aria-label = Uređivač crteža
pdfjs-ink-canvas =
.aria-label = Slika koju je izradio korisnik
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Alternativni tekst
pdfjs-editor-alt-text-edit-button =
.aria-label = Uredi alternativni tekst
pdfjs-editor-alt-text-edit-button-label = Uredi alternativni tekst
pdfjs-editor-alt-text-dialog-label = Odaberi jednu opciju
pdfjs-editor-alt-text-dialog-description = Alternativni tekst pomaže slijepim osobama ili kada se slika ne učita.
pdfjs-editor-alt-text-add-description-label = Dodaj opis
pdfjs-editor-alt-text-add-description-description = Sažmi sadržaj predmeta, okruženje ili radnje u jednoj ili dvije rečenice.
pdfjs-editor-alt-text-mark-decorative-label = Označi kao ukrasno
pdfjs-editor-alt-text-mark-decorative-description = Ovo se koristi za ukrasne slike, poput rubova ili vodenih žigova.
pdfjs-editor-alt-text-cancel-button = Odustani
pdfjs-editor-alt-text-save-button = Spremi
pdfjs-editor-alt-text-decorative-tooltip = Označeno kao ukrasno
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Na primjer, „Mladić sjeda za stol kako bi jeo”
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Alternativni tekst
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Gornji lijevi kut – promijeni veličinu
pdfjs-editor-resizer-label-top-middle = Sredina gore – promijeni veličinu
pdfjs-editor-resizer-label-top-right = Gornji desni kut – promijeni veličinu
pdfjs-editor-resizer-label-middle-right = Sredina desno – promijeni veličinu
pdfjs-editor-resizer-label-bottom-right = Donji desni kut – promijeni veličinu
pdfjs-editor-resizer-label-bottom-middle = Sredina dolje – promjeni veličinu
pdfjs-editor-resizer-label-bottom-left = Donji lijevi kut – promijeni veličinu
pdfjs-editor-resizer-label-middle-left = Sredina lijevo – promijeni veličinu
pdfjs-editor-resizer-top-left =
.aria-label = Gornji lijevi kut – promijeni veličinu
pdfjs-editor-resizer-top-middle =
.aria-label = Sredina gore – promijeni veličinu
pdfjs-editor-resizer-top-right =
.aria-label = Gornji desni kut – promijeni veličinu
pdfjs-editor-resizer-middle-right =
.aria-label = Sredina desno – promijeni veličinu
pdfjs-editor-resizer-bottom-right =
.aria-label = Donji desni kut – promijeni veličinu
pdfjs-editor-resizer-bottom-middle =
.aria-label = Sredina dolje – promjeni veličinu
pdfjs-editor-resizer-bottom-left =
.aria-label = Donji lijevi kut – promijeni veličinu
pdfjs-editor-resizer-middle-left =
.aria-label = Sredina lijevo – promijeni veličinu
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Boja isticanja
pdfjs-editor-colorpicker-button =
.title = Promjeni boju
pdfjs-editor-colorpicker-dropdown =
.aria-label = Izbor boja
pdfjs-editor-colorpicker-yellow =
.title = Žuta
pdfjs-editor-colorpicker-green =
.title = Zelena
pdfjs-editor-colorpicker-blue =
.title = Plava
pdfjs-editor-colorpicker-pink =
.title = Ružičasta
pdfjs-editor-colorpicker-red =
.title = Crvena
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Prikaži sve
pdfjs-editor-highlight-show-all-button =
.title = Prikaži sve
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Uredi alternativni tekst (opis slike)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Dodaj alternativni tekst (opis slike)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Ovdje upiši tvoj opis …
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Kratki opis koji pomažu osobama koji ne mogu vidjeti sliku ili kada se slika ne učita.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Ovaj je alternativni tekst stvoren automatski i može biti netočan.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Saznaj više
pdfjs-editor-new-alt-text-create-automatically-button-label = Automatski stvori alternativni tekst
pdfjs-editor-new-alt-text-not-now-button = Ne sada
pdfjs-editor-new-alt-text-error-title = Nije bilo moguće automatski izraditi alternativni tekst
pdfjs-editor-new-alt-text-error-description = Napiši vlastiti alternativni tekst ili pokušaj kasnije ponovo.
pdfjs-editor-new-alt-text-error-close-button = Zatvori
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Preuzimanje alternativnog teksta UI modela ({ $downloadedSize } od { $totalSize } MB)
.aria-valuetext = Preuzimanje alternativnog teksta UI modela ({ $downloadedSize } od { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Alternativni tekst je dodan
pdfjs-editor-new-alt-text-added-button-label = Alternativni tekst je dodan
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Nedostaje alternativni tekst
pdfjs-editor-new-alt-text-missing-button-label = Nedostaje alternativni tekst
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Pregledaj alternativni tekst
pdfjs-editor-new-alt-text-to-review-button-label = Pregledaj alternativni tekst
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Stvoreno automatski: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Postavke alternativnog teksta slike
pdfjs-image-alt-text-settings-button-label = Postavke alternativnog teksta slike
pdfjs-editor-alt-text-settings-dialog-label = Postavke alternativnog teksta slike
pdfjs-editor-alt-text-settings-automatic-title = Automatski alternativni tekst
pdfjs-editor-alt-text-settings-create-model-button-label = Stvori alternativni tekst automatski
pdfjs-editor-alt-text-settings-create-model-description = Predlaže opise koji pomažu osobama koji ne mogu vidjeti sliku ili kada se slika ne učita.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Alternativni tekst UI modela ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Radi lokalno na tvom uređaju kako bi tvoji podaci ostali privatni. Potrebno za automatski alternativni tekst.
pdfjs-editor-alt-text-settings-delete-model-button = Izbriši
pdfjs-editor-alt-text-settings-download-model-button = Preuzmi
pdfjs-editor-alt-text-settings-downloading-model-button = Preuzimanje …
pdfjs-editor-alt-text-settings-editor-title = Uređivač alternativnog teksta
pdfjs-editor-alt-text-settings-show-dialog-button-label = Prikaži uređivač alternativnog teksta odmah pri dodavanju slike
pdfjs-editor-alt-text-settings-show-dialog-description = Pomaže osigurati da sve tvoje slike imaju alternativni tekst.
pdfjs-editor-alt-text-settings-close-button = Zatvori
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Isticanje uklonjeno
pdfjs-editor-undo-bar-message-freetext = Tekst uklonjen
pdfjs-editor-undo-bar-message-ink = Crtež uklonjen
pdfjs-editor-undo-bar-message-stamp = Slika uklonjena
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } pribilješka uklonjena
[few] { $count } pribilješke uklonjene
*[other] { $count } pribilješki uklonjeno
}
pdfjs-editor-undo-bar-undo-button =
.title = Poništi
pdfjs-editor-undo-bar-undo-button-label = Poništi
pdfjs-editor-undo-bar-close-button =
.title = Zatvori
pdfjs-editor-undo-bar-close-button-label = Zatvori
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/hsb/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Předchadna strona
pdfjs-previous-button-label = Wróćo
pdfjs-next-button =
.title = Přichodna strona
pdfjs-next-button-label = Dale
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Strona
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = z { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } z { $pagesCount })
pdfjs-zoom-out-button =
.title = Pomjeńšić
pdfjs-zoom-out-button-label = Pomjeńšić
pdfjs-zoom-in-button =
.title = Powjetšić
pdfjs-zoom-in-button-label = Powjetšić
pdfjs-zoom-select =
.title = Skalowanje
pdfjs-presentation-mode-button =
.title = Do prezentaciskeho modusa přeńć
pdfjs-presentation-mode-button-label = Prezentaciski modus
pdfjs-open-file-button =
.title = Dataju wočinić
pdfjs-open-file-button-label = Wočinić
pdfjs-print-button =
.title = Ćišćeć
pdfjs-print-button-label = Ćišćeć
pdfjs-save-button =
.title = Składować
pdfjs-save-button-label = Składować
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Sćahnyć
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Sćahnyć
pdfjs-bookmark-button =
.title = Aktualna strona (URL z aktualneje strony pokazać)
pdfjs-bookmark-button-label = Aktualna strona
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Nastroje
pdfjs-tools-button-label = Nastroje
pdfjs-first-page-button =
.title = K prěnjej stronje
pdfjs-first-page-button-label = K prěnjej stronje
pdfjs-last-page-button =
.title = K poslednjej stronje
pdfjs-last-page-button-label = K poslednjej stronje
pdfjs-page-rotate-cw-button =
.title = K směrej časnika wjerćeć
pdfjs-page-rotate-cw-button-label = K směrej časnika wjerćeć
pdfjs-page-rotate-ccw-button =
.title = Přećiwo směrej časnika wjerćeć
pdfjs-page-rotate-ccw-button-label = Přećiwo směrej časnika wjerćeć
pdfjs-cursor-text-select-tool-button =
.title = Nastroj za wuběranje teksta zmóžnić
pdfjs-cursor-text-select-tool-button-label = Nastroj za wuběranje teksta
pdfjs-cursor-hand-tool-button =
.title = Ručny nastroj zmóžnić
pdfjs-cursor-hand-tool-button-label = Ručny nastroj
pdfjs-scroll-page-button =
.title = Kulenje strony wužiwać
pdfjs-scroll-page-button-label = Kulenje strony
pdfjs-scroll-vertical-button =
.title = Wertikalne suwanje wužiwać
pdfjs-scroll-vertical-button-label = Wertikalne suwanje
pdfjs-scroll-horizontal-button =
.title = Horicontalne suwanje wužiwać
pdfjs-scroll-horizontal-button-label = Horicontalne suwanje
pdfjs-scroll-wrapped-button =
.title = Postupne suwanje wužiwać
pdfjs-scroll-wrapped-button-label = Postupne suwanje
pdfjs-spread-none-button =
.title = Strony njezwjazać
pdfjs-spread-none-button-label = Žana dwójna strona
pdfjs-spread-odd-button =
.title = Strony započinajo z njerunymi stronami zwjazać
pdfjs-spread-odd-button-label = Njerune strony
pdfjs-spread-even-button =
.title = Strony započinajo z runymi stronami zwjazać
pdfjs-spread-even-button-label = Rune strony
## Document properties dialog
pdfjs-document-properties-button =
.title = Dokumentowe kajkosće…
pdfjs-document-properties-button-label = Dokumentowe kajkosće…
pdfjs-document-properties-file-name = Mjeno dataje:
pdfjs-document-properties-file-size = Wulkosć dataje:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bajtow)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bajtow)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bajtow)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bajtow)
pdfjs-document-properties-title = Titul:
pdfjs-document-properties-author = Awtor:
pdfjs-document-properties-subject = Předmjet:
pdfjs-document-properties-keywords = Klučowe słowa:
pdfjs-document-properties-creation-date = Datum wutworjenja:
pdfjs-document-properties-modification-date = Datum změny:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Awtor:
pdfjs-document-properties-producer = PDF-zhotowjer:
pdfjs-document-properties-version = PDF-wersija:
pdfjs-document-properties-page-count = Ličba stronow:
pdfjs-document-properties-page-size = Wulkosć strony:
pdfjs-document-properties-page-size-unit-inches = cól
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = wysoki format
pdfjs-document-properties-page-size-orientation-landscape = prěčny format
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letter
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Fast Web View:
pdfjs-document-properties-linearized-yes = Haj
pdfjs-document-properties-linearized-no = Ně
pdfjs-document-properties-close-button = Začinić
## Print
pdfjs-print-progress-message = Dokument so za ćišćenje přihotuje…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Přetorhnyć
pdfjs-printing-not-supported = Warnowanje: Ćišćenje so přez tutón wobhladowak połnje njepodpěruje.
pdfjs-printing-not-ready = Warnowanje: PDF njeje so za ćišćenje dospołnje začitał.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Bóčnicu pokazać/schować
pdfjs-toggle-sidebar-notification-button =
.title = Bóčnicu přepinać (dokument rozrjad/přiwěški/woršty wobsahuje)
pdfjs-toggle-sidebar-button-label = Bóčnicu pokazać/schować
pdfjs-document-outline-button =
.title = Dokumentowy naćisk pokazać (dwójne kliknjenje, zo bychu so wšě zapiski pokazali/schowali)
pdfjs-document-outline-button-label = Dokumentowa struktura
pdfjs-attachments-button =
.title = Přiwěški pokazać
pdfjs-attachments-button-label = Přiwěški
pdfjs-layers-button =
.title = Woršty pokazać (klikńće dwójce, zo byšće wšě woršty na standardny staw wróćo stajił)
pdfjs-layers-button-label = Woršty
pdfjs-thumbs-button =
.title = Miniatury pokazać
pdfjs-thumbs-button-label = Miniatury
pdfjs-current-outline-item-button =
.title = Aktualny rozrjadowy zapisk pytać
pdfjs-current-outline-item-button-label = Aktualny rozrjadowy zapisk
pdfjs-findbar-button =
.title = W dokumenće pytać
pdfjs-findbar-button-label = Pytać
pdfjs-additional-layers = Dalše woršty
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Strona { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Miniatura strony { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Pytać
.placeholder = W dokumenće pytać…
pdfjs-find-previous-button =
.title = Předchadne wustupowanje pytanskeho wuraza pytać
pdfjs-find-previous-button-label = Wróćo
pdfjs-find-next-button =
.title = Přichodne wustupowanje pytanskeho wuraza pytać
pdfjs-find-next-button-label = Dale
pdfjs-find-highlight-checkbox = Wšě wuzběhnyć
pdfjs-find-match-case-checkbox-label = Wulkopisanje wobkedźbować
pdfjs-find-match-diacritics-checkbox-label = Diakritiske znamješka wužiwać
pdfjs-find-entire-word-checkbox-label = Cyłe słowa
pdfjs-find-reached-top = Spočatk dokumenta docpěty, pokročuje so z kóncom
pdfjs-find-reached-bottom = Kónc dokument docpěty, pokročuje so ze spočatkom
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } z { $total } wotpowědnika
[two] { $current } z { $total } wotpowědnikow
[few] { $current } z { $total } wotpowědnikow
*[other] { $current } z { $total } wotpowědnikow
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Wyše { $limit } wotpowědnik
[two] Wyše { $limit } wotpowědnikaj
[few] Wyše { $limit } wotpowědniki
*[other] Wyše { $limit } wotpowědnikow
}
pdfjs-find-not-found = Pytanski wuraz njeje so namakał
## Predefined zoom values
pdfjs-page-scale-width = Šěrokosć strony
pdfjs-page-scale-fit = Wulkosć strony
pdfjs-page-scale-auto = Awtomatiske skalowanje
pdfjs-page-scale-actual = Aktualna wulkosć
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Strona { $page }
## Loading indicator messages
pdfjs-loading-error = Při začitowanju PDF je zmylk wustupił.
pdfjs-invalid-file-error = Njepłaćiwa abo wobškodźena PDF-dataja.
pdfjs-missing-file-error = Falowaca PDF-dataja.
pdfjs-unexpected-response-error = Njewočakowana serwerowa wotmołwa.
pdfjs-rendering-error = Při zwobraznjenju strony je zmylk wustupił.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [Typ přispomnjenki: { $type }]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Zapodajće hesło, zo byšće PDF-dataju wočinił.
pdfjs-password-invalid = Njepłaćiwe hesło. Prošu spytajće hišće raz.
pdfjs-password-ok-button = W porjadku
pdfjs-password-cancel-button = Přetorhnyć
pdfjs-web-fonts-disabled = Webpisma su znjemóžnjene: njeje móžno, zasadźene PDF-pisma wužiwać.
## Editing
pdfjs-editor-free-text-button =
.title = Tekst
pdfjs-editor-free-text-button-label = Tekst
pdfjs-editor-ink-button =
.title = Rysować
pdfjs-editor-ink-button-label = Rysować
pdfjs-editor-stamp-button =
.title = Wobrazy přidać abo wobdźěłać
pdfjs-editor-stamp-button-label = Wobrazy přidać abo wobdźěłać
pdfjs-editor-highlight-button =
.title = Wuzběhnyć
pdfjs-editor-highlight-button-label = Wuzběhnyć
pdfjs-highlight-floating-button1 =
.title = Wuzběhnjenje
.aria-label = Wuzběhnjenje
pdfjs-highlight-floating-button-label = Wuzběhnjenje
pdfjs-editor-signature-button =
.title = Signaturu přidać
pdfjs-editor-signature-button-label = Signaturu přidać
## Default editor aria labels
# “Highlight” is a noun, the string is used on the editor for highlights.
pdfjs-editor-highlight-editor =
.aria-label = Wuzběhowanski editor
# “Drawing” is a noun, the string is used on the editor for drawings.
pdfjs-editor-ink-editor =
.aria-label = Rysowanski editor
pdfjs-editor-signature-editor =
.aria-label = Editor signaturow
pdfjs-editor-stamp-editor =
.aria-label = Wobrazowy editor
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Rysowanku wotstronić
pdfjs-editor-remove-freetext-button =
.title = Tekst wotstronić
pdfjs-editor-remove-stamp-button =
.title = Wobraz wotstronić
pdfjs-editor-remove-highlight-button =
.title = Wuzběhnjenje wotstronić
pdfjs-editor-remove-signature-button =
.title = Signaturu wotstronić
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Barba
pdfjs-editor-free-text-size-input = Wulkosć
pdfjs-editor-ink-color-input = Barba
pdfjs-editor-ink-thickness-input = Tołstosć
pdfjs-editor-ink-opacity-input = Opacita
pdfjs-editor-stamp-add-image-button =
.title = Wobraz přidać
pdfjs-editor-stamp-add-image-button-label = Wobraz přidać
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Tołstosć
pdfjs-editor-free-highlight-thickness-title =
.title = Tołstosć změnić, hdyž so zapiski wuzběhuja, kotrež tekst njejsu
pdfjs-editor-add-signature-container =
.aria-label = Wodźenske elementy signaturow a składowane signatury
pdfjs-editor-signature-add-signature-button =
.title = Nowu signaturu přidać
pdfjs-editor-signature-add-signature-button-label = Nowu signaturu přidać
# Used on the button to use an already saved signature.
# Variables:
# $description (String) - a string describing/labeling the signature.
pdfjs-editor-add-saved-signature-button =
.title = Składowana signatura: { $description }
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Tekstowy editor
.default-content = Započńće pisać …
pdfjs-free-text =
.aria-label = Tekstowy editor
pdfjs-free-text-default-content = Započńće pisać…
pdfjs-ink =
.aria-label = Rysowanski editor
pdfjs-ink-canvas =
.aria-label = Wobraz wutworjeny wot wužiwarja
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Alternatiwny tekst
pdfjs-editor-alt-text-edit-button =
.aria-label = Alternatiwny tekst wobdźěłać
pdfjs-editor-alt-text-edit-button-label = Alternatiwny tekst wobdźěłać
pdfjs-editor-alt-text-dialog-label = Nastajenje wubrać
pdfjs-editor-alt-text-dialog-description = Alternatiwny tekst pomha, hdyž ludźo njemóža wobraz widźeć abo hdyž so wobraz njezačita.
pdfjs-editor-alt-text-add-description-label = Wopisanje přidać
pdfjs-editor-alt-text-add-description-description = Pisajće 1 sadu abo 2 sadźe, kotrejž temu, nastajenje abo akcije wopisujetej.
pdfjs-editor-alt-text-mark-decorative-label = Jako dekoratiwny markěrować
pdfjs-editor-alt-text-mark-decorative-description = To so za pyšace wobrazy wužiwa, na přikład ramiki abo wodowe znamjenja.
pdfjs-editor-alt-text-cancel-button = Přetorhnyć
pdfjs-editor-alt-text-save-button = Składować
pdfjs-editor-alt-text-decorative-tooltip = Jako dekoratiwny markěrowany
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Na přikład, „Młody muž za blidom sedźi, zo by jědź jědł“
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Alternatiwny tekst
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Horjeka nalěwo – wulkosć změnić
pdfjs-editor-resizer-label-top-middle = Horjeka wosrjedź – wulkosć změnić
pdfjs-editor-resizer-label-top-right = Horjeka naprawo – wulkosć změnić
pdfjs-editor-resizer-label-middle-right = Wosrjedź naprawo – wulkosć změnić
pdfjs-editor-resizer-label-bottom-right = Deleka naprawo – wulkosć změnić
pdfjs-editor-resizer-label-bottom-middle = Deleka wosrjedź – wulkosć změnić
pdfjs-editor-resizer-label-bottom-left = Deleka nalěwo – wulkosć změnić
pdfjs-editor-resizer-label-middle-left = Wosrjedź nalěwo – wulkosć změnić
pdfjs-editor-resizer-top-left =
.aria-label = Horjeka nalěwo – wulkosć změnić
pdfjs-editor-resizer-top-middle =
.aria-label = Horjeka wosrjedź – wulkosć změnić
pdfjs-editor-resizer-top-right =
.aria-label = Horjeka naprawo – wulkosć změnić
pdfjs-editor-resizer-middle-right =
.aria-label = Wosrjedź naprawo – wulkosć změnić
pdfjs-editor-resizer-bottom-right =
.aria-label = Deleka naprawo – wulkosć změnić
pdfjs-editor-resizer-bottom-middle =
.aria-label = Deleka wosrjedź – wulkosć změnić
pdfjs-editor-resizer-bottom-left =
.aria-label = Deleka nalěwo – wulkosć změnić
pdfjs-editor-resizer-middle-left =
.aria-label = Wosrjedź nalěwo – wulkosć změnić
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Barba wuzběhnjenja
pdfjs-editor-colorpicker-button =
.title = Barbu změnić
pdfjs-editor-colorpicker-dropdown =
.aria-label = Wuběr barbow
pdfjs-editor-colorpicker-yellow =
.title = Žołty
pdfjs-editor-colorpicker-green =
.title = Zeleny
pdfjs-editor-colorpicker-blue =
.title = Módry
pdfjs-editor-colorpicker-pink =
.title = Pink
pdfjs-editor-colorpicker-red =
.title = Čerwjeny
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Wšě pokazać
pdfjs-editor-highlight-show-all-button =
.title = Wšě pokazać
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Alternatiwny tekst wobdźěłać (wobrazowe wopisanje)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Alternatiwny tekst přidać (wobrazowe wopisanje)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Pisajće tu swoje wopisanje…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Krótke wopisanje za ludźi, kotřiž njemóžeće wobraz widźeć abo hdyž so wobraz njezačita.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Tutón alternatiwny tekst je so awtomatisce wutworił a je snano njedokładny.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Dalše informacije
pdfjs-editor-new-alt-text-create-automatically-button-label = Alternatiwny tekst awtomatisce wutworić
pdfjs-editor-new-alt-text-not-now-button = Nic nětko
pdfjs-editor-new-alt-text-error-title = Alternatiwny tekst njeda so awtomatisce wutworić
pdfjs-editor-new-alt-text-error-description = Prošu pisajće swój alternatiwny tekst abo spytajće pozdźišo hišće raz.
pdfjs-editor-new-alt-text-error-close-button = Začinić
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Model KI za alternatiwny tekst so sćahuje ({ $downloadedSize } z { $totalSize } MB)
.aria-valuetext = Model KI za alternatiwny tekst so sćahuje ({ $downloadedSize } z { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Alternatiwny tekst je so přidał
pdfjs-editor-new-alt-text-added-button-label = Alternatiwny tekst je so přidał
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Alternatiwny tekst faluje
pdfjs-editor-new-alt-text-missing-button-label = Alternatiwny tekst faluje
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Alternatiwny tekst přepruwować
pdfjs-editor-new-alt-text-to-review-button-label = Alternatiwny tekst přepruwować
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Awtomatisce wutworjeny: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Nastajenja alternatiwneho wobrazoweho teksta
pdfjs-image-alt-text-settings-button-label = Nastajenja alternatiwneho wobrazoweho teksta
pdfjs-editor-alt-text-settings-dialog-label = Nastajenja alternatiwneho wobrazoweho teksta
pdfjs-editor-alt-text-settings-automatic-title = Awtomatiski alternatiwny tekst
pdfjs-editor-alt-text-settings-create-model-button-label = Alternatiwny tekst awtomatisce wutworić
pdfjs-editor-alt-text-settings-create-model-description = Namjetuje wopisanja, zo by ludźom pomhał, kotřiž njemóžeće wobraz widźeć abo hdyž so wobraz njezačita.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Model KI alternatiwneho teksta ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Běži lokalnje na wašim graće, zo bychu waše daty priwatne wostali. Za awtomatiski alternatiwny tekst trěbny.
pdfjs-editor-alt-text-settings-delete-model-button = Zhašeć
pdfjs-editor-alt-text-settings-download-model-button = Sćahnyć
pdfjs-editor-alt-text-settings-downloading-model-button = Sćahuje so…
pdfjs-editor-alt-text-settings-editor-title = Editor za alternatiwny tekst
pdfjs-editor-alt-text-settings-show-dialog-button-label = Editor alternatiwneho teksta hnydom pokazać, hdyž so wobraz přidawa
pdfjs-editor-alt-text-settings-show-dialog-description = Pomha, wam wšěm swojim wobrazam alternatiwny tekst přidać.
pdfjs-editor-alt-text-settings-close-button = Začinić
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Wotstronjene wuzběhnyć
pdfjs-editor-undo-bar-message-freetext = Tekst je so wotstronił
pdfjs-editor-undo-bar-message-ink = Rysowanka je so wotstroniła
pdfjs-editor-undo-bar-message-stamp = Wobraz je so wotstronił
pdfjs-editor-undo-bar-message-signature = Signatura je so wotstroniła
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } přispomnjenka je so wotstroniła
[two] { $count } přispomnjence stej so wotstroniłoj
[few] { $count } přispomnjenki su so wotstronili
*[other] { $count } přispomnjenkow je so wotstroniło
}
pdfjs-editor-undo-bar-undo-button =
.title = Cofnyć
pdfjs-editor-undo-bar-undo-button-label = Cofnyć
pdfjs-editor-undo-bar-close-button =
.title = Začinić
pdfjs-editor-undo-bar-close-button-label = Začinić
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = Tutón modalny dialog wužiwarjej zmóžnja, signaturu wutworić, zo by PDF-dokument přidał. Wužiwar móže mjeno wobdźěłać (kotrež tež jako alternatiwny tekst słuži) a po přeću signaturu za wospjetne wužiwanje składować.
pdfjs-editor-add-signature-dialog-title = Signaturu přidać
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Typ
.title = Typ
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Rysować
.title = Rysować
pdfjs-editor-add-signature-image-button = Wobraz
.title = Wobraz
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Zapodajće swoju signaturu
.placeholder = Zapodajće swoju signaturu
pdfjs-editor-add-signature-draw-placeholder = Rysujće swoju signaturu
pdfjs-editor-add-signature-draw-thickness-range-label = Tołstosć
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Tołstosć rysowanki: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Ćehńće dataju sem, zo byšće ju nahrał
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Abo přepytajće wobrazowe dataje
*[other] Abo přepytajće wobrazowe dataje
}
## Controls
pdfjs-editor-add-signature-description-label = Wopisanje (alternatiwny tekst)
pdfjs-editor-add-signature-description-input =
.title = Wopisanje (alternatiwny tekst)
pdfjs-editor-add-signature-description-default-when-drawing = Signatura
pdfjs-editor-add-signature-clear-button-label = Signaturu zhašeć
pdfjs-editor-add-signature-clear-button =
.title = Signaturu zhašeć
pdfjs-editor-add-signature-save-checkbox = Signaturu składować
pdfjs-editor-add-signature-save-warning-message = Sće limit 5 składowanych signaturow docpěł. Wotstrońće jednu, zo byšće wjace składował.
pdfjs-editor-add-signature-image-upload-error-title = Wobraz njeda so nahrać
pdfjs-editor-add-signature-image-upload-error-description = Přepruwujće swój syćowy zwisk abo spytajće druhi wobraz.
pdfjs-editor-add-signature-error-close-button = Začinić
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Přetorhnyć
pdfjs-editor-add-signature-add-button = Přidać
pdfjs-editor-edit-signature-update-button = Aktualizować
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Signaturu wotstronić
pdfjs-editor-delete-signature-button-label = Signaturu wotstronić
pdfjs-editor-delete-signature-button1 =
.title = Składowanu signaturu wotstronić
pdfjs-editor-delete-signature-button-label1 = Składowanu signaturu wotstronić
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Wopisanje wobdźěłać
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Wopisanje wobdźěłać
================================================
FILE: cookbook/static/pdfjs/web/locale/hu/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Előző oldal
pdfjs-previous-button-label = Előző
pdfjs-next-button =
.title = Következő oldal
pdfjs-next-button-label = Tovább
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Oldal
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = összesen: { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } / { $pagesCount })
pdfjs-zoom-out-button =
.title = Kicsinyítés
pdfjs-zoom-out-button-label = Kicsinyítés
pdfjs-zoom-in-button =
.title = Nagyítás
pdfjs-zoom-in-button-label = Nagyítás
pdfjs-zoom-select =
.title = Nagyítás
pdfjs-presentation-mode-button =
.title = Váltás bemutató módba
pdfjs-presentation-mode-button-label = Bemutató mód
pdfjs-open-file-button =
.title = Fájl megnyitása
pdfjs-open-file-button-label = Megnyitás
pdfjs-print-button =
.title = Nyomtatás
pdfjs-print-button-label = Nyomtatás
pdfjs-save-button =
.title = Mentés
pdfjs-save-button-label = Mentés
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Letöltés
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Letöltés
pdfjs-bookmark-button =
.title = Jelenlegi oldal (webcím megtekintése a jelenlegi oldalról)
pdfjs-bookmark-button-label = Jelenlegi oldal
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Eszközök
pdfjs-tools-button-label = Eszközök
pdfjs-first-page-button =
.title = Ugrás az első oldalra
pdfjs-first-page-button-label = Ugrás az első oldalra
pdfjs-last-page-button =
.title = Ugrás az utolsó oldalra
pdfjs-last-page-button-label = Ugrás az utolsó oldalra
pdfjs-page-rotate-cw-button =
.title = Forgatás az óramutató járásával egyezően
pdfjs-page-rotate-cw-button-label = Forgatás az óramutató járásával egyezően
pdfjs-page-rotate-ccw-button =
.title = Forgatás az óramutató járásával ellentétesen
pdfjs-page-rotate-ccw-button-label = Forgatás az óramutató járásával ellentétesen
pdfjs-cursor-text-select-tool-button =
.title = Szövegkijelölő eszköz bekapcsolása
pdfjs-cursor-text-select-tool-button-label = Szövegkijelölő eszköz
pdfjs-cursor-hand-tool-button =
.title = Kéz eszköz bekapcsolása
pdfjs-cursor-hand-tool-button-label = Kéz eszköz
pdfjs-scroll-page-button =
.title = Oldalgörgetés használata
pdfjs-scroll-page-button-label = Oldalgörgetés
pdfjs-scroll-vertical-button =
.title = Függőleges görgetés használata
pdfjs-scroll-vertical-button-label = Függőleges görgetés
pdfjs-scroll-horizontal-button =
.title = Vízszintes görgetés használata
pdfjs-scroll-horizontal-button-label = Vízszintes görgetés
pdfjs-scroll-wrapped-button =
.title = Rácsos elrendezés használata
pdfjs-scroll-wrapped-button-label = Rácsos elrendezés
pdfjs-spread-none-button =
.title = Ne tapassza össze az oldalakat
pdfjs-spread-none-button-label = Nincs összetapasztás
pdfjs-spread-odd-button =
.title = Lapok összetapasztása, a páratlan számú oldalakkal kezdve
pdfjs-spread-odd-button-label = Összetapasztás: páratlan
pdfjs-spread-even-button =
.title = Lapok összetapasztása, a páros számú oldalakkal kezdve
pdfjs-spread-even-button-label = Összetapasztás: páros
## Document properties dialog
pdfjs-document-properties-button =
.title = Dokumentum tulajdonságai…
pdfjs-document-properties-button-label = Dokumentum tulajdonságai…
pdfjs-document-properties-file-name = Fájlnév:
pdfjs-document-properties-file-size = Fájlméret:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } kB ({ $b } bájt)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bájt)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bájt)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bájt)
pdfjs-document-properties-title = Cím:
pdfjs-document-properties-author = Szerző:
pdfjs-document-properties-subject = Tárgy:
pdfjs-document-properties-keywords = Kulcsszavak:
pdfjs-document-properties-creation-date = Létrehozás dátuma:
pdfjs-document-properties-modification-date = Módosítás dátuma:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Létrehozta:
pdfjs-document-properties-producer = PDF előállító:
pdfjs-document-properties-version = PDF verzió:
pdfjs-document-properties-page-count = Oldalszám:
pdfjs-document-properties-page-size = Lapméret:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = álló
pdfjs-document-properties-page-size-orientation-landscape = fekvő
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letter
pdfjs-document-properties-page-size-name-legal = Jogi információk
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Gyors webes nézet:
pdfjs-document-properties-linearized-yes = Igen
pdfjs-document-properties-linearized-no = Nem
pdfjs-document-properties-close-button = Bezárás
## Print
pdfjs-print-progress-message = Dokumentum előkészítése nyomtatáshoz…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Mégse
pdfjs-printing-not-supported = Figyelmeztetés: Ez a böngésző nem teljesen támogatja a nyomtatást.
pdfjs-printing-not-ready = Figyelmeztetés: A PDF nincs teljesen betöltve a nyomtatáshoz.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Oldalsáv be/ki
pdfjs-toggle-sidebar-notification-button =
.title = Oldalsáv be/ki (a dokumentum vázlatot/mellékleteket/rétegeket tartalmaz)
pdfjs-toggle-sidebar-button-label = Oldalsáv be/ki
pdfjs-document-outline-button =
.title = Dokumentum megjelenítése online (dupla kattintás minden elem kinyitásához/összecsukásához)
pdfjs-document-outline-button-label = Dokumentumvázlat
pdfjs-attachments-button =
.title = Mellékletek megjelenítése
pdfjs-attachments-button-label = Van melléklet
pdfjs-layers-button =
.title = Rétegek megjelenítése (dupla kattintás az összes réteg alapértelmezett állapotra visszaállításához)
pdfjs-layers-button-label = Rétegek
pdfjs-thumbs-button =
.title = Bélyegképek megjelenítése
pdfjs-thumbs-button-label = Bélyegképek
pdfjs-current-outline-item-button =
.title = Jelenlegi vázlatelem megkeresése
pdfjs-current-outline-item-button-label = Jelenlegi vázlatelem
pdfjs-findbar-button =
.title = Keresés a dokumentumban
pdfjs-findbar-button-label = Keresés
pdfjs-additional-layers = További rétegek
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = { $page }. oldal
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = { $page }. oldal bélyegképe
## Find panel button title and messages
pdfjs-find-input =
.title = Keresés
.placeholder = Keresés a dokumentumban…
pdfjs-find-previous-button =
.title = A kifejezés előző előfordulásának keresése
pdfjs-find-previous-button-label = Előző
pdfjs-find-next-button =
.title = A kifejezés következő előfordulásának keresése
pdfjs-find-next-button-label = Tovább
pdfjs-find-highlight-checkbox = Összes kiemelése
pdfjs-find-match-case-checkbox-label = Kis- és nagybetűk megkülönböztetése
pdfjs-find-match-diacritics-checkbox-label = Diakritikus jelek
pdfjs-find-entire-word-checkbox-label = Teljes szavak
pdfjs-find-reached-top = A dokumentum eleje elérve, folytatás a végétől
pdfjs-find-reached-bottom = A dokumentum vége elérve, folytatás az elejétől
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } / { $total } találat
*[other] { $current } / { $total } találat
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Több mint { $limit } találat
*[other] Több mint { $limit } találat
}
pdfjs-find-not-found = A kifejezés nem található
## Predefined zoom values
pdfjs-page-scale-width = Oldalszélesség
pdfjs-page-scale-fit = Teljes oldal
pdfjs-page-scale-auto = Automatikus nagyítás
pdfjs-page-scale-actual = Valódi méret
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = { $page }. oldal
## Loading indicator messages
pdfjs-loading-error = Hiba történt a PDF betöltésekor.
pdfjs-invalid-file-error = Érvénytelen vagy sérült PDF fájl.
pdfjs-missing-file-error = Hiányzó PDF fájl.
pdfjs-unexpected-response-error = Váratlan kiszolgálóválasz.
pdfjs-rendering-error = Hiba történt az oldal feldolgozása közben.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } megjegyzés]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Adja meg a jelszót a PDF fájl megnyitásához.
pdfjs-password-invalid = Helytelen jelszó. Próbálja újra.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Mégse
pdfjs-web-fonts-disabled = Webes betűkészletek letiltva: nem használhatók a beágyazott PDF betűkészletek.
## Editing
pdfjs-editor-free-text-button =
.title = Szöveg
pdfjs-editor-free-text-button-label = Szöveg
pdfjs-editor-ink-button =
.title = Rajzolás
pdfjs-editor-ink-button-label = Rajzolás
pdfjs-editor-stamp-button =
.title = Képek hozzáadása vagy szerkesztése
pdfjs-editor-stamp-button-label = Képek hozzáadása vagy szerkesztése
pdfjs-editor-highlight-button =
.title = Kiemelés
pdfjs-editor-highlight-button-label = Kiemelés
pdfjs-highlight-floating-button1 =
.title = Kiemelés
.aria-label = Kiemelés
pdfjs-highlight-floating-button-label = Kiemelés
pdfjs-editor-signature-button =
.title = Aláírás hozzáadása
pdfjs-editor-signature-button-label = Aláírás hozzáadása
## Default editor aria labels
# “Highlight” is a noun, the string is used on the editor for highlights.
pdfjs-editor-highlight-editor =
.aria-label = Kiemelésszerkesztő
# “Drawing” is a noun, the string is used on the editor for drawings.
pdfjs-editor-ink-editor =
.aria-label = Rajzszerkesztő
pdfjs-editor-signature-editor =
.aria-label = Aláírás-szerkesztő
pdfjs-editor-stamp-editor =
.aria-label = Képszerkesztő
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Rajz eltávolítása
pdfjs-editor-remove-freetext-button =
.title = Szöveg eltávolítása
pdfjs-editor-remove-stamp-button =
.title = Kép eltávolítása
pdfjs-editor-remove-highlight-button =
.title = Kiemelés eltávolítása
pdfjs-editor-remove-signature-button =
.title = Aláírás eltávolítása
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Szín
pdfjs-editor-free-text-size-input = Méret
pdfjs-editor-ink-color-input = Szín
pdfjs-editor-ink-thickness-input = Vastagság
pdfjs-editor-ink-opacity-input = Átlátszatlanság
pdfjs-editor-stamp-add-image-button =
.title = Kép hozzáadása
pdfjs-editor-stamp-add-image-button-label = Kép hozzáadása
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Vastagság
pdfjs-editor-free-highlight-thickness-title =
.title = Vastagság módosítása, ha nem szöveges elemeket emel ki
pdfjs-editor-add-signature-container =
.aria-label = Aláírás-vezérlők és mentett aláírások
pdfjs-editor-signature-add-signature-button =
.title = Új aláírás hozzáadása
pdfjs-editor-signature-add-signature-button-label = Új aláírás hozzáadása
# Used on the button to use an already saved signature.
# Variables:
# $description (String) - a string describing/labeling the signature.
pdfjs-editor-add-saved-signature-button =
.title = Mentett aláírás: { $description }
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Szövegszerkesztő
.default-content = Kezdjen gépelni…
pdfjs-free-text =
.aria-label = Szövegszerkesztő
pdfjs-free-text-default-content = Kezdjen el gépelni…
pdfjs-ink =
.aria-label = Rajzszerkesztő
pdfjs-ink-canvas =
.aria-label = Felhasználó által készített kép
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Alternatív szöveg
pdfjs-editor-alt-text-edit-button =
.aria-label = Alternatív szöveg szerkesztése
pdfjs-editor-alt-text-edit-button-label = Alternatív szöveg szerkesztése
pdfjs-editor-alt-text-dialog-label = Válasszon egy lehetőséget
pdfjs-editor-alt-text-dialog-description = Az alternatív szöveg segít, ha az emberek nem látják a képet, vagy ha az nem töltődik be.
pdfjs-editor-alt-text-add-description-label = Leírás hozzáadása
pdfjs-editor-alt-text-add-description-description = Törekedjen 1-2 mondatra, amely jellemzi a témát, környezetet vagy cselekvést.
pdfjs-editor-alt-text-mark-decorative-label = Megjelölés dekoratívként
pdfjs-editor-alt-text-mark-decorative-description = Ez a díszítőképeknél használatos, mint a szegélyek vagy a vízjelek.
pdfjs-editor-alt-text-cancel-button = Mégse
pdfjs-editor-alt-text-save-button = Mentés
pdfjs-editor-alt-text-decorative-tooltip = Megjelölve dekoratívként
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Például: „Egy fiatal férfi leül enni egy asztalhoz”
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Alternatív szöveg
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Bal felső sarok – átméretezés
pdfjs-editor-resizer-label-top-middle = Felül középen – átméretezés
pdfjs-editor-resizer-label-top-right = Jobb felső sarok – átméretezés
pdfjs-editor-resizer-label-middle-right = Jobbra középen – átméretezés
pdfjs-editor-resizer-label-bottom-right = Jobb alsó sarok – átméretezés
pdfjs-editor-resizer-label-bottom-middle = Alul középen – átméretezés
pdfjs-editor-resizer-label-bottom-left = Bal alsó sarok – átméretezés
pdfjs-editor-resizer-label-middle-left = Balra középen – átméretezés
pdfjs-editor-resizer-top-left =
.aria-label = Bal felső sarok – átméretezés
pdfjs-editor-resizer-top-middle =
.aria-label = Felül középen – átméretezés
pdfjs-editor-resizer-top-right =
.aria-label = Jobb felső sarok – átméretezés
pdfjs-editor-resizer-middle-right =
.aria-label = Jobbra középen – átméretezés
pdfjs-editor-resizer-bottom-right =
.aria-label = Jobb alsó sarok – átméretezés
pdfjs-editor-resizer-bottom-middle =
.aria-label = Alul középen – átméretezés
pdfjs-editor-resizer-bottom-left =
.aria-label = Bal alsó sarok – átméretezés
pdfjs-editor-resizer-middle-left =
.aria-label = Balra középen – átméretezés
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Kiemelés színe
pdfjs-editor-colorpicker-button =
.title = Szín módosítása
pdfjs-editor-colorpicker-dropdown =
.aria-label = Színválasztások
pdfjs-editor-colorpicker-yellow =
.title = Sárga
pdfjs-editor-colorpicker-green =
.title = Zöld
pdfjs-editor-colorpicker-blue =
.title = Kék
pdfjs-editor-colorpicker-pink =
.title = Rózsaszín
pdfjs-editor-colorpicker-red =
.title = Vörös
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Összes megjelenítése
pdfjs-editor-highlight-show-all-button =
.title = Összes megjelenítése
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Alternatív szöveg szerkesztése (képleírás)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Alternatív szöveg hozzáadása (képleírás)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Írja ide a leírását…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Rövid leírás azoknak, akik nem látják a képet, vagy arra az esetre, ha a kép nem tölt be.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Ez az alternatív szöveg automatikusan lett létrehozva, és pontatlan lehet.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = További tudnivalók
pdfjs-editor-new-alt-text-create-automatically-button-label = Alternatív szöveg automatikus létrehozása
pdfjs-editor-new-alt-text-not-now-button = Most nem
pdfjs-editor-new-alt-text-error-title = Az alternatív szöveg automatikus létrehozása nem sikerült
pdfjs-editor-new-alt-text-error-description = Írja meg a saját alternatív szövegét, vagy próbálja újra később.
pdfjs-editor-new-alt-text-error-close-button = Bezárás
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Alternatív szöveg MI modell letöltése ({ $downloadedSize } / { $totalSize } MB)
.aria-valuetext = Alternatív szöveg MI modell letöltése ({ $downloadedSize } / { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Alternatív szöveg hozzáadva
pdfjs-editor-new-alt-text-added-button-label = Alternatív szöveg hozzáadva
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Hiányzó alternatív szöveg
pdfjs-editor-new-alt-text-missing-button-label = Hiányzó alternatív szöveg
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Alternatív szöveg áttekintése
pdfjs-editor-new-alt-text-to-review-button-label = Alternatív szöveg szerkesztése
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Automatikusan létrehozva: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Kép alternatív szövegének beállításai
pdfjs-image-alt-text-settings-button-label = Kép alternatív szövegének beállításai
pdfjs-editor-alt-text-settings-dialog-label = Kép alternatív szövegének beállításai
pdfjs-editor-alt-text-settings-automatic-title = Automatikus alternatív szöveg
pdfjs-editor-alt-text-settings-create-model-button-label = Alternatív szöveg automatikus létrehozása
pdfjs-editor-alt-text-settings-create-model-description = Leírásokat javasol, hogy segítsen azoknak, akik nem látják a képet, vagy arra az esetre, ha a kép nem tölt be.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Alternatív szöveg MI modellje ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Helyben fut az eszközén, így az adatai privátok maradnak. Az automatikus alternatív szövegekhez szükséges.
pdfjs-editor-alt-text-settings-delete-model-button = Törlés
pdfjs-editor-alt-text-settings-download-model-button = Letöltés
pdfjs-editor-alt-text-settings-downloading-model-button = Letöltés…
pdfjs-editor-alt-text-settings-editor-title = Alternatív szöveg szerkesztője
pdfjs-editor-alt-text-settings-show-dialog-button-label = Az alternatív szöveg szerkesztőjének azonnali megjelenítése egy kép hozzáadásakor
pdfjs-editor-alt-text-settings-show-dialog-description = Segít elérni, hogy az összes képén legyen alternatív szöveg.
pdfjs-editor-alt-text-settings-close-button = Bezárás
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Kiemelés eltávolítva
pdfjs-editor-undo-bar-message-freetext = Szöveg eltávolítva
pdfjs-editor-undo-bar-message-ink = Rajz eltávolítva
pdfjs-editor-undo-bar-message-stamp = Kép eltávolítva
pdfjs-editor-undo-bar-message-signature = Aláírás eltávolítva
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } kommentár eltávolítva
*[other] { $count } kommentár eltávolítva
}
pdfjs-editor-undo-bar-undo-button =
.title = Visszavonás
pdfjs-editor-undo-bar-undo-button-label = Visszavonás
pdfjs-editor-undo-bar-close-button =
.title = Bezárás
pdfjs-editor-undo-bar-close-button-label = Bezárás
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = Ez a mód lehetővé teszi a felhasználónak, hogy aláírást hozzon létre, és ezt egy PDF dokumentumhoz adja. A felhasználó szerkesztheti a nevet (ez egyben alternatív szövegként is szolgál), és ismételt felhasználás céljából tetszés szerint mentheti az aláírást.
pdfjs-editor-add-signature-dialog-title = Aláírás hozzáadása
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Beírás
.title = Beírás
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Rajzolás
.title = Rajzolás
pdfjs-editor-add-signature-image-button = Kép
.title = Kép
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Írja be az aláírását
.placeholder = Írja be az aláírását
pdfjs-editor-add-signature-draw-placeholder = Rajzolja le az aláírását
pdfjs-editor-add-signature-draw-thickness-range-label = Vastagság
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Rajzolási vastagság: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Húzzon ide egy fájlt a feltöltéshez
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Vagy tallózzon a képfájlok között
*[other] Vagy tallózzon a képfájlok között
}
## Controls
pdfjs-editor-add-signature-description-label = Leírás (alternatív szöveg)
pdfjs-editor-add-signature-description-input =
.title = Leírás (alternatív szöveg)
pdfjs-editor-add-signature-description-default-when-drawing = Aláírás
pdfjs-editor-add-signature-clear-button-label = Aláírás törlése
pdfjs-editor-add-signature-clear-button =
.title = Aláírás törlése
pdfjs-editor-add-signature-save-checkbox = Aláírás mentése
pdfjs-editor-add-signature-save-warning-message = Elérte a mentett aláírások 5 darabos korlátját. A mentéshez távolítson el egyet.
pdfjs-editor-add-signature-image-upload-error-title = A kép nem tölthető fel
pdfjs-editor-add-signature-image-upload-error-description = Ellenőrizze a hálózati kapcsolatot, vagy próbálkozzon egy másik képpel.
pdfjs-editor-add-signature-error-close-button = Bezárás
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Mégse
pdfjs-editor-add-signature-add-button = Hozzáadás
pdfjs-editor-edit-signature-update-button = Frissítés
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Aláírás eltávolítása
pdfjs-editor-delete-signature-button-label = Aláírás eltávolítása
pdfjs-editor-delete-signature-button1 =
.title = Mentett aláírás eltávolítása
pdfjs-editor-delete-signature-button-label1 = Mentett aláírás eltávolítása
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Leírás szerkesztése
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Leírás szerkesztése
================================================
FILE: cookbook/static/pdfjs/web/locale/hy-AM/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Նախորդ էջը
pdfjs-previous-button-label = Նախորդը
pdfjs-next-button =
.title = Հաջորդ էջը
pdfjs-next-button-label = Հաջորդը
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Էջ.
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = -ը՝ { $pagesCount }-ից
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber }-ը { $pagesCount })-ից
pdfjs-zoom-out-button =
.title = Փոքրացնել
pdfjs-zoom-out-button-label = Փոքրացնել
pdfjs-zoom-in-button =
.title = Խոշորացնել
pdfjs-zoom-in-button-label = Խոշորացնել
pdfjs-zoom-select =
.title = Դիտափոխում
pdfjs-presentation-mode-button =
.title = Անցնել Ներկայացման եղանակին
pdfjs-presentation-mode-button-label = Ներկայացման եղանակ
pdfjs-open-file-button =
.title = Բացել նիշք
pdfjs-open-file-button-label = Բացել
pdfjs-print-button =
.title = Տպել
pdfjs-print-button-label = Տպել
pdfjs-save-button =
.title = Պահպանել
pdfjs-save-button-label = Պահպանել
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Ներբեռնել
pdfjs-bookmark-button-label = Ընթացիկ էջ
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Գործիքներ
pdfjs-tools-button-label = Գործիքներ
pdfjs-first-page-button =
.title = Անցնել առաջին էջին
pdfjs-first-page-button-label = Անցնել առաջին էջին
pdfjs-last-page-button =
.title = Անցնել վերջին էջին
pdfjs-last-page-button-label = Անցնել վերջին էջին
pdfjs-page-rotate-cw-button =
.title = Պտտել ըստ ժամացույցի սլաքի
pdfjs-page-rotate-cw-button-label = Պտտել ըստ ժամացույցի սլաքի
pdfjs-page-rotate-ccw-button =
.title = Պտտել հակառակ ժամացույցի սլաքի
pdfjs-page-rotate-ccw-button-label = Պտտել հակառակ ժամացույցի սլաքի
pdfjs-cursor-text-select-tool-button =
.title = Միացնել գրույթ ընտրելու գործիքը
pdfjs-cursor-text-select-tool-button-label = Գրույթը ընտրելու գործիք
pdfjs-cursor-hand-tool-button =
.title = Միացնել Ձեռքի գործիքը
pdfjs-cursor-hand-tool-button-label = Ձեռքի գործիք
pdfjs-scroll-vertical-button =
.title = Օգտագործել ուղղահայաց ոլորում
pdfjs-scroll-vertical-button-label = Ուղղահայաց ոլորում
pdfjs-scroll-horizontal-button =
.title = Օգտագործել հորիզոնական ոլորում
pdfjs-scroll-horizontal-button-label = Հորիզոնական ոլորում
pdfjs-scroll-wrapped-button =
.title = Օգտագործել փաթաթված ոլորում
pdfjs-scroll-wrapped-button-label = Փաթաթված ոլորում
pdfjs-spread-none-button =
.title = Մի միացեք էջի վերածածկերին
pdfjs-spread-none-button-label = Չկա վերածածկեր
pdfjs-spread-odd-button =
.title = Միացեք էջի վերածածկերին սկսելով՝ կենտ համարակալված էջերով
pdfjs-spread-odd-button-label = Կենտ վերածածկեր
pdfjs-spread-even-button =
.title = Միացեք էջի վերածածկերին սկսելով՝ զույգ համարակալված էջերով
pdfjs-spread-even-button-label = Զույգ վերածածկեր
## Document properties dialog
pdfjs-document-properties-button =
.title = Փաստաթղթի հատկությունները…
pdfjs-document-properties-button-label = Փաստաթղթի հատկությունները…
pdfjs-document-properties-file-name = Նիշքի անունը.
pdfjs-document-properties-file-size = Նիշք չափը.
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } ԿԲ ({ $size_b } բայթ)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } ՄԲ ({ $size_b } բայթ)
pdfjs-document-properties-title = Վերնագիր.
pdfjs-document-properties-author = Հեղինակ․
pdfjs-document-properties-subject = Վերնագիր.
pdfjs-document-properties-keywords = Հիմնաբառ.
pdfjs-document-properties-creation-date = Ստեղծելու ամսաթիվը.
pdfjs-document-properties-modification-date = Փոփոխելու ամսաթիվը.
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Ստեղծող.
pdfjs-document-properties-producer = PDF-ի հեղինակը.
pdfjs-document-properties-version = PDF-ի տարբերակը.
pdfjs-document-properties-page-count = Էջերի քանակը.
pdfjs-document-properties-page-size = Էջի չափը.
pdfjs-document-properties-page-size-unit-inches = ում
pdfjs-document-properties-page-size-unit-millimeters = մմ
pdfjs-document-properties-page-size-orientation-portrait = ուղղաձիգ
pdfjs-document-properties-page-size-orientation-landscape = հորիզոնական
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Նամակ
pdfjs-document-properties-page-size-name-legal = Օրինական
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Արագ վեբ դիտում․
pdfjs-document-properties-linearized-yes = Այո
pdfjs-document-properties-linearized-no = Ոչ
pdfjs-document-properties-close-button = Փակել
## Print
pdfjs-print-progress-message = Նախապատրաստում է փաստաթուղթը տպելուն...
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Չեղարկել
pdfjs-printing-not-supported = Զգուշացում. Տպելը ամբողջությամբ չի աջակցվում դիտարկիչի կողմից։
pdfjs-printing-not-ready = Զգուշացում. PDF-ը ամբողջությամբ չի բեռնավորվել տպելու համար:
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Բացել/Փակել Կողային վահանակը
pdfjs-toggle-sidebar-button-label = Բացել/Փակել Կողային վահանակը
pdfjs-document-outline-button =
.title = Ցուցադրել փաստաթղթի ուրվագիծը (կրկնակի սեղմեք՝ միավորները ընդարձակելու/կոծկելու համար)
pdfjs-document-outline-button-label = Փաստաթղթի բովանդակությունը
pdfjs-attachments-button =
.title = Ցուցադրել կցորդները
pdfjs-attachments-button-label = Կցորդներ
pdfjs-thumbs-button =
.title = Ցուցադրել Մանրապատկերը
pdfjs-thumbs-button-label = Մանրապատկերը
pdfjs-findbar-button =
.title = Գտնել փաստաթղթում
pdfjs-findbar-button-label = Որոնում
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Էջը { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Էջի մանրապատկերը { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Որոնում
.placeholder = Գտնել փաստաթղթում...
pdfjs-find-previous-button =
.title = Գտնել անրահայտության նախորդ հանդիպումը
pdfjs-find-previous-button-label = Նախորդը
pdfjs-find-next-button =
.title = Գտիր արտահայտության հաջորդ հանդիպումը
pdfjs-find-next-button-label = Հաջորդը
pdfjs-find-highlight-checkbox = Գունանշել բոլորը
pdfjs-find-match-case-checkbox-label = Մեծ(փոքր)ատառ հաշվի առնել
pdfjs-find-entire-word-checkbox-label = Ամբողջ բառերը
pdfjs-find-reached-top = Հասել եք փաստաթղթի վերևին, կշարունակվի ներքևից
pdfjs-find-reached-bottom = Հասել եք փաստաթղթի վերջին, կշարունակվի վերևից
pdfjs-find-not-found = Արտահայտությունը չգտնվեց
## Predefined zoom values
pdfjs-page-scale-width = Էջի լայնքը
pdfjs-page-scale-fit = Ձգել էջը
pdfjs-page-scale-auto = Ինքնաշխատ
pdfjs-page-scale-actual = Իրական չափը
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
## Loading indicator messages
pdfjs-loading-error = Սխալ՝ PDF ֆայլը բացելիս։
pdfjs-invalid-file-error = Սխալ կամ վնասված PDF ֆայլ:
pdfjs-missing-file-error = PDF ֆայլը բացակայում է:
pdfjs-unexpected-response-error = Սպասարկիչի անսպասելի պատասխան:
pdfjs-rendering-error = Սխալ՝ էջը ստեղծելիս:
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } Ծանոթություն]
## Password
pdfjs-password-label = Մուտքագրեք PDF-ի գաղտնաբառը:
pdfjs-password-invalid = Գաղտնաբառը սխալ է: Կրկին փորձեք:
pdfjs-password-ok-button = Լավ
pdfjs-password-cancel-button = Չեղարկել
pdfjs-web-fonts-disabled = Վեբ-տառատեսակները անջատված են. հնարավոր չէ օգտագործել ներկառուցված PDF տառատեսակները:
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
pdfjs-free-text-default-content = Սկսել մուտքագրումը…
## Alt-text dialog
pdfjs-editor-alt-text-save-button = Պահպանել
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Ցուցադրել բոլորը
pdfjs-editor-highlight-show-all-button =
.title = Ցուցադրել բոլորը
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/hye/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Նախորդ էջ
pdfjs-previous-button-label = Նախորդը
pdfjs-next-button =
.title = Յաջորդ էջ
pdfjs-next-button-label = Յաջորդը
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = էջ
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = { $pagesCount }-ից
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber }-ը { $pagesCount })-ից
pdfjs-zoom-out-button =
.title = Փոքրացնել
pdfjs-zoom-out-button-label = Փոքրացնել
pdfjs-zoom-in-button =
.title = Խոշորացնել
pdfjs-zoom-in-button-label = Խոշորացնել
pdfjs-zoom-select =
.title = Խոշորացում
pdfjs-presentation-mode-button =
.title = Անցնել ներկայացման եղանակին
pdfjs-presentation-mode-button-label = Ներկայացման եղանակ
pdfjs-open-file-button =
.title = Բացել նիշքը
pdfjs-open-file-button-label = Բացել
pdfjs-print-button =
.title = Տպել
pdfjs-print-button-label = Տպել
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Գործիքներ
pdfjs-tools-button-label = Գործիքներ
pdfjs-first-page-button =
.title = Գնալ դէպի առաջին էջ
pdfjs-first-page-button-label = Գնալ դէպի առաջին էջ
pdfjs-last-page-button =
.title = Գնալ դէպի վերջին էջ
pdfjs-last-page-button-label = Գնալ դէպի վերջին էջ
pdfjs-page-rotate-cw-button =
.title = Պտտել ժամացոյցի սլաքի ուղղութեամբ
pdfjs-page-rotate-cw-button-label = Պտտել ժամացոյցի սլաքի ուղղութեամբ
pdfjs-page-rotate-ccw-button =
.title = Պտտել ժամացոյցի սլաքի հակառակ ուղղութեամբ
pdfjs-page-rotate-ccw-button-label = Պտտել ժամացոյցի սլաքի հակառակ ուղղութեամբ
pdfjs-cursor-text-select-tool-button =
.title = Միացնել գրոյթ ընտրելու գործիքը
pdfjs-cursor-text-select-tool-button-label = Գրուածք ընտրելու գործիք
pdfjs-cursor-hand-tool-button =
.title = Միացնել ձեռքի գործիքը
pdfjs-cursor-hand-tool-button-label = Ձեռքի գործիք
pdfjs-scroll-page-button =
.title = Աւգտագործել էջի ոլորում
pdfjs-scroll-page-button-label = Էջի ոլորում
pdfjs-scroll-vertical-button =
.title = Աւգտագործել ուղղահայեաց ոլորում
pdfjs-scroll-vertical-button-label = Ուղղահայեաց ոլորում
pdfjs-scroll-horizontal-button =
.title = Աւգտագործել հորիզոնական ոլորում
pdfjs-scroll-horizontal-button-label = Հորիզոնական ոլորում
pdfjs-scroll-wrapped-button =
.title = Աւգտագործել փաթաթուած ոլորում
pdfjs-scroll-wrapped-button-label = Փաթաթուած ոլորում
pdfjs-spread-none-button =
.title = Մի միացէք էջի կոնտեքստում
pdfjs-spread-none-button-label = Չկայ կոնտեքստ
pdfjs-spread-odd-button =
.title = Միացէք էջի կոնտեքստին սկսելով՝ կենտ համարակալուած էջերով
pdfjs-spread-odd-button-label = Տարաւրինակ կոնտեքստ
pdfjs-spread-even-button =
.title = Միացէք էջի կոնտեքստին սկսելով՝ զոյգ համարակալուած էջերով
pdfjs-spread-even-button-label = Հաւասար վերածածկեր
## Document properties dialog
pdfjs-document-properties-button =
.title = Փաստաթղթի հատկութիւնները…
pdfjs-document-properties-button-label = Փաստաթղթի յատկութիւնները…
pdfjs-document-properties-file-name = Նիշքի անունը․
pdfjs-document-properties-file-size = Նիշք չափը.
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } ԿԲ ({ $size_b } բայթ)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } ՄԲ ({ $size_b } բայթ)
pdfjs-document-properties-title = Վերնագիր
pdfjs-document-properties-author = Հեղինակ․
pdfjs-document-properties-subject = առարկայ
pdfjs-document-properties-keywords = Հիմնաբառեր
pdfjs-document-properties-creation-date = Ստեղծման ամսաթիւ
pdfjs-document-properties-modification-date = Փոփոխութեան ամսաթիւ.
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Ստեղծող
pdfjs-document-properties-producer = PDF-ի Արտադրողը.
pdfjs-document-properties-version = PDF-ի տարբերակը.
pdfjs-document-properties-page-count = Էջերի քանակը.
pdfjs-document-properties-page-size = Էջի չափը.
pdfjs-document-properties-page-size-unit-inches = ում
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = ուղղաձիգ
pdfjs-document-properties-page-size-orientation-landscape = հորիզոնական
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Նամակ
pdfjs-document-properties-page-size-name-legal = Աւրինական
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Արագ վեբ դիտում․
pdfjs-document-properties-linearized-yes = Այո
pdfjs-document-properties-linearized-no = Ոչ
pdfjs-document-properties-close-button = Փակել
## Print
pdfjs-print-progress-message = Նախապատրաստում է փաստաթուղթը տպելուն…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Չեղարկել
pdfjs-printing-not-supported = Զգուշացում. Տպելը ամբողջութեամբ չի աջակցուում զննարկիչի կողմից։
pdfjs-printing-not-ready = Զգուշացում. PDF֊ը ամբողջութեամբ չի բեռնաւորուել տպելու համար։
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Փոխարկել կողային վահանակը
pdfjs-toggle-sidebar-notification-button =
.title = Փոխանջատել կողմնասիւնը (փաստաթուղթը պարունակում է ուրուագիծ/կցորդներ/շերտեր)
pdfjs-toggle-sidebar-button-label = Փոխարկել կողային վահանակը
pdfjs-document-outline-button =
.title = Ցուցադրել փաստաթղթի ուրուագիծը (կրկնակի սեղմէք՝ միաւորները ընդարձակելու/կոծկելու համար)
pdfjs-document-outline-button-label = Փաստաթղթի ուրուագիծ
pdfjs-attachments-button =
.title = Ցուցադրել կցորդները
pdfjs-attachments-button-label = Կցորդներ
pdfjs-layers-button =
.title = Ցուցադրել շերտերը (կրկնահպել վերակայելու բոլոր շերտերը սկզբնադիր վիճակի)
pdfjs-layers-button-label = Շերտեր
pdfjs-thumbs-button =
.title = Ցուցադրել մանրապատկերը
pdfjs-thumbs-button-label = Մանրապատկեր
pdfjs-current-outline-item-button =
.title = Գտէք ընթացիկ գծագրման տարրը
pdfjs-current-outline-item-button-label = Ընթացիկ գծագրման տարր
pdfjs-findbar-button =
.title = Գտնել փաստաթղթում
pdfjs-findbar-button-label = Որոնում
pdfjs-additional-layers = Լրացուցիչ շերտեր
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Էջը { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Էջի մանրապատկերը { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Որոնում
.placeholder = Գտնել փաստաթղթում…
pdfjs-find-previous-button =
.title = Գտնել արտայայտութեան նախորդ արտայայտութիւնը
pdfjs-find-previous-button-label = Նախորդը
pdfjs-find-next-button =
.title = Գտիր արտայայտութեան յաջորդ արտայայտութիւնը
pdfjs-find-next-button-label = Հաջորդը
pdfjs-find-highlight-checkbox = Գունանշել բոլորը
pdfjs-find-match-case-checkbox-label = Հաշուի առնել հանգամանքը
pdfjs-find-match-diacritics-checkbox-label = Հնչիւնատարբերիչ նշանների համապատասխանեցում
pdfjs-find-entire-word-checkbox-label = Ամբողջ բառերը
pdfjs-find-reached-top = Հասել եք փաստաթղթի վերեւին,շարունակել ներքեւից
pdfjs-find-reached-bottom = Հասել էք փաստաթղթի վերջին, շարունակել վերեւից
pdfjs-find-not-found = Արտայայտութիւնը չգտնուեց
## Predefined zoom values
pdfjs-page-scale-width = Էջի լայնութիւն
pdfjs-page-scale-fit = Հարմարեցնել էջը
pdfjs-page-scale-auto = Ինքնաշխատ խոշորացում
pdfjs-page-scale-actual = Իրական չափը
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Էջ { $page }
## Loading indicator messages
pdfjs-loading-error = PDF նիշքը բացելիս սխալ է տեղի ունեցել։
pdfjs-invalid-file-error = Սխալ կամ վնասուած PDF նիշք։
pdfjs-missing-file-error = PDF նիշքը բացակաիւմ է։
pdfjs-unexpected-response-error = Սպասարկիչի անսպասելի պատասխան։
pdfjs-rendering-error = Սխալ է տեղի ունեցել էջի մեկնաբանման ժամանակ
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } Ծանոթութիւն]
## Password
pdfjs-password-label = Մուտքագրէք գաղտնաբառը այս PDF նիշքը բացելու համար
pdfjs-password-invalid = Գաղտնաբառը սխալ է: Կրկին փորձէք:
pdfjs-password-ok-button = Լաւ
pdfjs-password-cancel-button = Չեղարկել
pdfjs-web-fonts-disabled = Վեբ-տառատեսակները անջատուած են. հնարաւոր չէ աւգտագործել ներկառուցուած PDF տառատեսակները։
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/ia/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Pagina previe
pdfjs-previous-button-label = Previe
pdfjs-next-button =
.title = Pagina sequente
pdfjs-next-button-label = Sequente
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Pagina
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = de { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } de { $pagesCount })
pdfjs-zoom-out-button =
.title = Distantiar
pdfjs-zoom-out-button-label = Distantiar
pdfjs-zoom-in-button =
.title = Approximar
pdfjs-zoom-in-button-label = Approximar
pdfjs-zoom-select =
.title = Zoom
pdfjs-presentation-mode-button =
.title = Excambiar a modo presentation
pdfjs-presentation-mode-button-label = Modo presentation
pdfjs-open-file-button =
.title = Aperir le file
pdfjs-open-file-button-label = Aperir
pdfjs-print-button =
.title = Imprimer
pdfjs-print-button-label = Imprimer
pdfjs-save-button =
.title = Salvar
pdfjs-save-button-label = Salvar
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Discargar
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Discargar
pdfjs-bookmark-button =
.title = Pagina actual (vide le URL del pagina actual)
pdfjs-bookmark-button-label = Pagina actual
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Instrumentos
pdfjs-tools-button-label = Instrumentos
pdfjs-first-page-button =
.title = Ir al prime pagina
pdfjs-first-page-button-label = Ir al prime pagina
pdfjs-last-page-button =
.title = Ir al ultime pagina
pdfjs-last-page-button-label = Ir al ultime pagina
pdfjs-page-rotate-cw-button =
.title = Rotar in senso horari
pdfjs-page-rotate-cw-button-label = Rotar in senso horari
pdfjs-page-rotate-ccw-button =
.title = Rotar in senso antihorari
pdfjs-page-rotate-ccw-button-label = Rotar in senso antihorari
pdfjs-cursor-text-select-tool-button =
.title = Activar le instrumento de selection de texto
pdfjs-cursor-text-select-tool-button-label = Instrumento de selection de texto
pdfjs-cursor-hand-tool-button =
.title = Activar le instrumento mano
pdfjs-cursor-hand-tool-button-label = Instrumento mano
pdfjs-scroll-page-button =
.title = Usar rolamento de pagina
pdfjs-scroll-page-button-label = Rolamento de pagina
pdfjs-scroll-vertical-button =
.title = Usar rolamento vertical
pdfjs-scroll-vertical-button-label = Rolamento vertical
pdfjs-scroll-horizontal-button =
.title = Usar rolamento horizontal
pdfjs-scroll-horizontal-button-label = Rolamento horizontal
pdfjs-scroll-wrapped-button =
.title = Usar rolamento incapsulate
pdfjs-scroll-wrapped-button-label = Rolamento incapsulate
pdfjs-spread-none-button =
.title = Non junger paginas dual
pdfjs-spread-none-button-label = Sin paginas dual
pdfjs-spread-odd-button =
.title = Junger paginas dual a partir de paginas con numeros impar
pdfjs-spread-odd-button-label = Paginas dual impar
pdfjs-spread-even-button =
.title = Junger paginas dual a partir de paginas con numeros par
pdfjs-spread-even-button-label = Paginas dual par
## Document properties dialog
pdfjs-document-properties-button =
.title = Proprietates del documento…
pdfjs-document-properties-button-label = Proprietates del documento…
pdfjs-document-properties-file-name = Nomine del file:
pdfjs-document-properties-file-size = Dimension de file:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = Titulo:
pdfjs-document-properties-author = Autor:
pdfjs-document-properties-subject = Subjecto:
pdfjs-document-properties-keywords = Parolas clave:
pdfjs-document-properties-creation-date = Data de creation:
pdfjs-document-properties-modification-date = Data de modification:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Creator:
pdfjs-document-properties-producer = Productor PDF:
pdfjs-document-properties-version = Version PDF:
pdfjs-document-properties-page-count = Numero de paginas:
pdfjs-document-properties-page-size = Dimension del pagina:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = vertical
pdfjs-document-properties-page-size-orientation-landscape = horizontal
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Littera
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Vista web rapide:
pdfjs-document-properties-linearized-yes = Si
pdfjs-document-properties-linearized-no = No
pdfjs-document-properties-close-button = Clauder
## Print
pdfjs-print-progress-message = Preparation del documento pro le impression…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Cancellar
pdfjs-printing-not-supported = Attention : le impression non es totalmente supportate per ce navigator.
pdfjs-printing-not-ready = Attention: le file PDF non es integremente cargate pro lo poter imprimer.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Monstrar/celar le barra lateral
pdfjs-toggle-sidebar-notification-button =
.title = Monstrar/celar le barra lateral (le documento contine structura/attachamentos/stratos)
pdfjs-toggle-sidebar-button-label = Monstrar/celar le barra lateral
pdfjs-document-outline-button =
.title = Monstrar le schema del documento (clic duple pro expander/contraher tote le elementos)
pdfjs-document-outline-button-label = Schema del documento
pdfjs-attachments-button =
.title = Monstrar le annexos
pdfjs-attachments-button-label = Annexos
pdfjs-layers-button =
.title = Monstrar stratos (clicca duple pro remontar tote le stratos al stato predefinite)
pdfjs-layers-button-label = Stratos
pdfjs-thumbs-button =
.title = Monstrar le vignettes
pdfjs-thumbs-button-label = Vignettes
pdfjs-current-outline-item-button =
.title = Trovar le elemento de structura actual
pdfjs-current-outline-item-button-label = Elemento de structura actual
pdfjs-findbar-button =
.title = Cercar in le documento
pdfjs-findbar-button-label = Cercar
pdfjs-additional-layers = Altere stratos
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Pagina { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Vignette del pagina { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Cercar
.placeholder = Cercar in le documento…
pdfjs-find-previous-button =
.title = Trovar le previe occurrentia del phrase
pdfjs-find-previous-button-label = Previe
pdfjs-find-next-button =
.title = Trovar le successive occurrentia del phrase
pdfjs-find-next-button-label = Sequente
pdfjs-find-highlight-checkbox = Evidentiar toto
pdfjs-find-match-case-checkbox-label = Distinguer majusculas/minusculas
pdfjs-find-match-diacritics-checkbox-label = Differentiar diacriticos
pdfjs-find-entire-word-checkbox-label = Parolas integre
pdfjs-find-reached-top = Initio del documento attingite, continuation ab fin
pdfjs-find-reached-bottom = Fin del documento attingite, continuation ab initio
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } de { $total } correspondentia
*[other] { $current } de { $total } correspondentias
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Plus de { $limit } correspondentia
*[other] Plus de { $limit } correspondentias
}
pdfjs-find-not-found = Phrase non trovate
## Predefined zoom values
pdfjs-page-scale-width = Plen largor del pagina
pdfjs-page-scale-fit = Pagina integre
pdfjs-page-scale-auto = Zoom automatic
pdfjs-page-scale-actual = Dimension real
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Pagina { $page }
## Loading indicator messages
pdfjs-loading-error = Un error occurreva durante que on cargava le file PDF.
pdfjs-invalid-file-error = File PDF corrumpite o non valide.
pdfjs-missing-file-error = File PDF mancante.
pdfjs-unexpected-response-error = Responsa del servitor inexpectate.
pdfjs-rendering-error = Un error occurreva durante que on processava le pagina.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } Annotation]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Insere le contrasigno pro aperir iste file PDF.
pdfjs-password-invalid = Contrasigno invalide. Per favor retenta.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Cancellar
pdfjs-web-fonts-disabled = Le typos de litteras web es disactivate: impossibile usar le typos de litteras PDF incorporate.
## Editing
pdfjs-editor-free-text-button =
.title = Texto
pdfjs-editor-free-text-button-label = Texto
pdfjs-editor-ink-button =
.title = Designar
pdfjs-editor-ink-button-label = Designar
pdfjs-editor-stamp-button =
.title = Adder o rediger imagines
pdfjs-editor-stamp-button-label = Adder o rediger imagines
pdfjs-editor-highlight-button =
.title = Evidentia
pdfjs-editor-highlight-button-label = Evidentia
pdfjs-highlight-floating-button1 =
.title = Evidentiar
.aria-label = Evidentiar
pdfjs-highlight-floating-button-label = Evidentiar
pdfjs-editor-signature-button =
.title = Adder signatura
pdfjs-editor-signature-button-label = Adder signatura
## Default editor aria labels
# “Highlight” is a noun, the string is used on the editor for highlights.
pdfjs-editor-highlight-editor =
.aria-label = Editor de evidentiation
# “Drawing” is a noun, the string is used on the editor for drawings.
pdfjs-editor-ink-editor =
.aria-label = Editor de designos
pdfjs-editor-signature-editor =
.aria-label = Editor de signatura
pdfjs-editor-stamp-editor =
.aria-label = Editor de imagines
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Remover le designo
pdfjs-editor-remove-freetext-button =
.title = Remover texto
pdfjs-editor-remove-stamp-button =
.title = Remover imagine
pdfjs-editor-remove-highlight-button =
.title = Remover evidentia
pdfjs-editor-remove-signature-button =
.title = Remover signatura
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Color
pdfjs-editor-free-text-size-input = Dimension
pdfjs-editor-ink-color-input = Color
pdfjs-editor-ink-thickness-input = Spissor
pdfjs-editor-ink-opacity-input = Opacitate
pdfjs-editor-stamp-add-image-button =
.title = Adder imagine
pdfjs-editor-stamp-add-image-button-label = Adder imagine
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Spissor
pdfjs-editor-free-highlight-thickness-title =
.title = Cambiar spissor evidentiante elementos differente de texto
pdfjs-editor-add-signature-container =
.aria-label = Controlos de signatura e signaturas salvate
pdfjs-editor-signature-add-signature-button =
.title = Adder nove signatura
pdfjs-editor-signature-add-signature-button-label = Adder nove signatura
# Used on the button to use an already saved signature.
# Variables:
# $description (String) - a string describing/labeling the signature.
pdfjs-editor-add-saved-signature-button =
.title = Signatura salvate: { $description }
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Editor de texto
.default-content = Initiar a inserer…
pdfjs-free-text =
.aria-label = Editor de texto
pdfjs-free-text-default-content = Comenciar a scriber…
pdfjs-ink =
.aria-label = Editor de designos
pdfjs-ink-canvas =
.aria-label = Imagine create per le usator
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Texto alternative
pdfjs-editor-alt-text-edit-button =
.aria-label = Rediger texto alternative
pdfjs-editor-alt-text-edit-button-label = Rediger texto alternative
pdfjs-editor-alt-text-dialog-label = Elige un option
pdfjs-editor-alt-text-dialog-description = Le texto alternative (alt text) adjuta quando le personas non pote vider le imagine o quando illo non carga.
pdfjs-editor-alt-text-add-description-label = Adder un description
pdfjs-editor-alt-text-add-description-description = Mira a 1-2 phrases que describe le subjecto, parametro, o actiones.
pdfjs-editor-alt-text-mark-decorative-label = Marcar como decorative
pdfjs-editor-alt-text-mark-decorative-description = Isto es usate pro imagines ornamental, como bordaturas o filigranas.
pdfjs-editor-alt-text-cancel-button = Cancellar
pdfjs-editor-alt-text-save-button = Salvar
pdfjs-editor-alt-text-decorative-tooltip = Marcate como decorative
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Per exemplo, “Un juvene sede a un tabula pro mangiar un repasto”
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Texto alternative
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Angulo superior sinistre — redimensionar
pdfjs-editor-resizer-label-top-middle = Medio superior — redimensionar
pdfjs-editor-resizer-label-top-right = Angulo superior dextre — redimensionar
pdfjs-editor-resizer-label-middle-right = Medio dextre — redimensionar
pdfjs-editor-resizer-label-bottom-right = Angulo inferior dextre — redimensionar
pdfjs-editor-resizer-label-bottom-middle = Medio inferior — redimensionar
pdfjs-editor-resizer-label-bottom-left = Angulo inferior sinistre — redimensionar
pdfjs-editor-resizer-label-middle-left = Medio sinistre — redimensionar
pdfjs-editor-resizer-top-left =
.aria-label = Angulo superior sinistre — redimensionar
pdfjs-editor-resizer-top-middle =
.aria-label = Medio superior — redimensionar
pdfjs-editor-resizer-top-right =
.aria-label = Angulo superior dextre — redimensionar
pdfjs-editor-resizer-middle-right =
.aria-label = Medio dextre — redimensionar
pdfjs-editor-resizer-bottom-right =
.aria-label = Angulo inferior dextre — redimensionar
pdfjs-editor-resizer-bottom-middle =
.aria-label = Medio inferior — redimensionar
pdfjs-editor-resizer-bottom-left =
.aria-label = Angulo inferior sinistre — redimensionar
pdfjs-editor-resizer-middle-left =
.aria-label = Medio sinistre — redimensionar
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Color pro evidentiar
pdfjs-editor-colorpicker-button =
.title = Cambiar color
pdfjs-editor-colorpicker-dropdown =
.aria-label = Electiones del color
pdfjs-editor-colorpicker-yellow =
.title = Jalne
pdfjs-editor-colorpicker-green =
.title = Verde
pdfjs-editor-colorpicker-blue =
.title = Blau
pdfjs-editor-colorpicker-pink =
.title = Rosate
pdfjs-editor-colorpicker-red =
.title = Rubie
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Monstrar toto
pdfjs-editor-highlight-show-all-button =
.title = Monstrar toto
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Rediger texto alternative (description del imagine)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Adder texto alternative (description del imagine)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Scribe tu description ci…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Breve description pro personas qui non pote vider le imagine o quando le imagine non se carga.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Iste texto alternative ha essite create automaticamente e pote esser inexacte.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Pro saper plus
pdfjs-editor-new-alt-text-create-automatically-button-label = Crear texto alternative automaticamente
pdfjs-editor-new-alt-text-not-now-button = Non ora
pdfjs-editor-new-alt-text-error-title = Impossibile crear texto alternative automaticamente
pdfjs-editor-new-alt-text-error-description = Scribe tu proprie texto alternative o retenta plus tarde.
pdfjs-editor-new-alt-text-error-close-button = Clauder
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Discargante modello de intelligentia artificial del texto alternative ({ $downloadedSize } de { $totalSize } MB)
.aria-valuetext = Discargante modello de intelligentia artificial del texto alternative ({ $downloadedSize } de { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Texto alternative addite
pdfjs-editor-new-alt-text-added-button-label = Texto alternative addite
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Texto alternative mancante
pdfjs-editor-new-alt-text-missing-button-label = Texto alternative mancante
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Revider texto alternative
pdfjs-editor-new-alt-text-to-review-button-label = Revider texto alternative
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Automaticamente create: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Parametros del texto alternative del imagine
pdfjs-image-alt-text-settings-button-label = Parametros del texto alternative del imagine
pdfjs-editor-alt-text-settings-dialog-label = Parametros del texto alternative del imagine
pdfjs-editor-alt-text-settings-automatic-title = Texto alternative automatic
pdfjs-editor-alt-text-settings-create-model-button-label = Crear texto alternative automaticamente
pdfjs-editor-alt-text-settings-create-model-description = Suggere descriptiones pro adjutar le personas qui non pote vider le imagine o quando le imagine non carga.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Modello de intelligentia artificial del texto alternative ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Flue localmente sur tu apparato assi tu datos remane private. Necessari pro texto alternative automatic.
pdfjs-editor-alt-text-settings-delete-model-button = Deler
pdfjs-editor-alt-text-settings-download-model-button = Discargar
pdfjs-editor-alt-text-settings-downloading-model-button = Discargante…
pdfjs-editor-alt-text-settings-editor-title = Rediger texto alternative
pdfjs-editor-alt-text-settings-show-dialog-button-label = Monstrar le redactor de texto alternative a pena on adde un imagine
pdfjs-editor-alt-text-settings-show-dialog-description = Te adjuta a verifica que tote tu imagines ha un texto alternative.
pdfjs-editor-alt-text-settings-close-button = Clauder
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Evidentiation removite
pdfjs-editor-undo-bar-message-freetext = Texto removite
pdfjs-editor-undo-bar-message-ink = Designo removite
pdfjs-editor-undo-bar-message-stamp = Imagine removite
pdfjs-editor-undo-bar-message-signature = Signatura removite
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } annotation removite
*[other] { $count } annotationes removite
}
pdfjs-editor-undo-bar-undo-button =
.title = Disfacer
pdfjs-editor-undo-bar-undo-button-label = Disfacer
pdfjs-editor-undo-bar-close-button =
.title = Clauder
pdfjs-editor-undo-bar-close-button-label = Clauder
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = Iste formulario permitte al usator crear un firma a adder a un documento PDF. Le usator pote modificar le nomine (le qual tamben servi de texto alternative) e, si desirate, salvar le firma pro uso repetite.
pdfjs-editor-add-signature-dialog-title = Adder un signatura
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Typar
.title = Typar
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Designar
.title = Designar
pdfjs-editor-add-signature-image-button = Imagine
.title = Imagine
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Insere tu firma
.placeholder = Insere tu firma
pdfjs-editor-add-signature-draw-placeholder = Designa tu firma
pdfjs-editor-add-signature-draw-thickness-range-label = Spissor
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Spissor de designo: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Trahe un file hic pro incargar lo
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] O elige files de imagine
*[other] O folietta files de imagine
}
## Controls
pdfjs-editor-add-signature-description-label = Description (texto alternative)
pdfjs-editor-add-signature-description-input =
.title = Description (texto alternative)
pdfjs-editor-add-signature-description-default-when-drawing = Signatura
pdfjs-editor-add-signature-clear-button-label = Rader signatura
pdfjs-editor-add-signature-clear-button =
.title = Rader signatura
pdfjs-editor-add-signature-save-checkbox = Salvar signatura
pdfjs-editor-add-signature-save-warning-message = Tu ha attingite le limite de 5 firmas salvate. Remove un pro salvar un altere.
pdfjs-editor-add-signature-image-upload-error-title = Non poteva incargar le imagine
pdfjs-editor-add-signature-image-upload-error-description = Verifica tu connexion al rete o tenta un altere imagine.
pdfjs-editor-add-signature-error-close-button = Clauder
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Cancellar
pdfjs-editor-add-signature-add-button = Adder
pdfjs-editor-edit-signature-update-button = Actualisar
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Remover signatura
pdfjs-editor-delete-signature-button-label = Remover signatura
pdfjs-editor-delete-signature-button1 =
.title = Remover signatura salvate
pdfjs-editor-delete-signature-button-label1 = Remover signatura salvate
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Rediger description
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Rediger description
================================================
FILE: cookbook/static/pdfjs/web/locale/id/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Laman Sebelumnya
pdfjs-previous-button-label = Sebelumnya
pdfjs-next-button =
.title = Laman Selanjutnya
pdfjs-next-button-label = Selanjutnya
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Halaman
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = dari { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } dari { $pagesCount })
pdfjs-zoom-out-button =
.title = Perkecil
pdfjs-zoom-out-button-label = Perkecil
pdfjs-zoom-in-button =
.title = Perbesar
pdfjs-zoom-in-button-label = Perbesar
pdfjs-zoom-select =
.title = Perbesaran
pdfjs-presentation-mode-button =
.title = Ganti ke Mode Presentasi
pdfjs-presentation-mode-button-label = Mode Presentasi
pdfjs-open-file-button =
.title = Buka Berkas
pdfjs-open-file-button-label = Buka
pdfjs-print-button =
.title = Cetak
pdfjs-print-button-label = Cetak
pdfjs-save-button =
.title = Simpan
pdfjs-save-button-label = Simpan
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Unduh
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Unduh
pdfjs-bookmark-button =
.title = Laman Saat Ini (Lihat URL dari Laman Sekarang)
pdfjs-bookmark-button-label = Laman Saat Ini
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Alat
pdfjs-tools-button-label = Alat
pdfjs-first-page-button =
.title = Buka Halaman Pertama
pdfjs-first-page-button-label = Buka Halaman Pertama
pdfjs-last-page-button =
.title = Buka Halaman Terakhir
pdfjs-last-page-button-label = Buka Halaman Terakhir
pdfjs-page-rotate-cw-button =
.title = Putar Searah Jarum Jam
pdfjs-page-rotate-cw-button-label = Putar Searah Jarum Jam
pdfjs-page-rotate-ccw-button =
.title = Putar Berlawanan Arah Jarum Jam
pdfjs-page-rotate-ccw-button-label = Putar Berlawanan Arah Jarum Jam
pdfjs-cursor-text-select-tool-button =
.title = Aktifkan Alat Seleksi Teks
pdfjs-cursor-text-select-tool-button-label = Alat Seleksi Teks
pdfjs-cursor-hand-tool-button =
.title = Aktifkan Alat Tangan
pdfjs-cursor-hand-tool-button-label = Alat Tangan
pdfjs-scroll-page-button =
.title = Gunakan Pengguliran Laman
pdfjs-scroll-page-button-label = Pengguliran Laman
pdfjs-scroll-vertical-button =
.title = Gunakan Penggeseran Vertikal
pdfjs-scroll-vertical-button-label = Penggeseran Vertikal
pdfjs-scroll-horizontal-button =
.title = Gunakan Penggeseran Horizontal
pdfjs-scroll-horizontal-button-label = Penggeseran Horizontal
pdfjs-scroll-wrapped-button =
.title = Gunakan Penggeseran Terapit
pdfjs-scroll-wrapped-button-label = Penggeseran Terapit
pdfjs-spread-none-button =
.title = Jangan gabungkan lembar halaman
pdfjs-spread-none-button-label = Tidak Ada Lembaran
pdfjs-spread-odd-button =
.title = Gabungkan lembar lamanan mulai dengan halaman ganjil
pdfjs-spread-odd-button-label = Lembaran Ganjil
pdfjs-spread-even-button =
.title = Gabungkan lembar halaman dimulai dengan halaman genap
pdfjs-spread-even-button-label = Lembaran Genap
## Document properties dialog
pdfjs-document-properties-button =
.title = Properti Dokumen…
pdfjs-document-properties-button-label = Properti Dokumen…
pdfjs-document-properties-file-name = Nama berkas:
pdfjs-document-properties-file-size = Ukuran berkas:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } byte)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } byte)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } byte)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } byte)
pdfjs-document-properties-title = Judul:
pdfjs-document-properties-author = Penyusun:
pdfjs-document-properties-subject = Subjek:
pdfjs-document-properties-keywords = Kata Kunci:
pdfjs-document-properties-creation-date = Tanggal Dibuat:
pdfjs-document-properties-modification-date = Tanggal Dimodifikasi:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Pembuat:
pdfjs-document-properties-producer = Pemroduksi PDF:
pdfjs-document-properties-version = Versi PDF:
pdfjs-document-properties-page-count = Jumlah Halaman:
pdfjs-document-properties-page-size = Ukuran Laman:
pdfjs-document-properties-page-size-unit-inches = inci
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = tegak
pdfjs-document-properties-page-size-orientation-landscape = mendatar
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letter
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Tampilan Web Kilat:
pdfjs-document-properties-linearized-yes = Ya
pdfjs-document-properties-linearized-no = Tidak
pdfjs-document-properties-close-button = Tutup
## Print
pdfjs-print-progress-message = Menyiapkan dokumen untuk pencetakan…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Batalkan
pdfjs-printing-not-supported = Peringatan: Pencetakan tidak didukung secara lengkap pada peramban ini.
pdfjs-printing-not-ready = Peringatan: Berkas PDF masih belum dimuat secara lengkap untuk dapat dicetak.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Aktif/Nonaktifkan Bilah Samping
pdfjs-toggle-sidebar-notification-button =
.title = Aktif/Nonaktifkan Bilah Samping (dokumen berisi kerangka/lampiran/lapisan)
pdfjs-toggle-sidebar-button-label = Aktif/Nonaktifkan Bilah Samping
pdfjs-document-outline-button =
.title = Tampilkan Kerangka Dokumen (klik ganda untuk membentangkan/menciutkan semua item)
pdfjs-document-outline-button-label = Kerangka Dokumen
pdfjs-attachments-button =
.title = Tampilkan Lampiran
pdfjs-attachments-button-label = Lampiran
pdfjs-layers-button =
.title = Tampilkan Lapisan (klik ganda untuk mengatur ulang semua lapisan ke keadaan baku)
pdfjs-layers-button-label = Lapisan
pdfjs-thumbs-button =
.title = Tampilkan Miniatur
pdfjs-thumbs-button-label = Miniatur
pdfjs-current-outline-item-button =
.title = Cari Butir Ikhtisar Saat Ini
pdfjs-current-outline-item-button-label = Butir Ikhtisar Saat Ini
pdfjs-findbar-button =
.title = Temukan di Dokumen
pdfjs-findbar-button-label = Temukan
pdfjs-additional-layers = Lapisan Tambahan
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Laman { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Miniatur Laman { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Temukan
.placeholder = Temukan di dokumen…
pdfjs-find-previous-button =
.title = Temukan kata sebelumnya
pdfjs-find-previous-button-label = Sebelumnya
pdfjs-find-next-button =
.title = Temukan lebih lanjut
pdfjs-find-next-button-label = Selanjutnya
pdfjs-find-highlight-checkbox = Sorot semuanya
pdfjs-find-match-case-checkbox-label = Cocokkan BESAR/kecil
pdfjs-find-match-diacritics-checkbox-label = Pencocokan Diakritik
pdfjs-find-entire-word-checkbox-label = Seluruh teks
pdfjs-find-reached-top = Sampai di awal dokumen, dilanjutkan dari bawah
pdfjs-find-reached-bottom = Sampai di akhir dokumen, dilanjutkan dari atas
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count = { $current } dari { $total } yang cocok
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit = Lebih dari { $limit } kecocokan
pdfjs-find-not-found = Frasa tidak ditemukan
## Predefined zoom values
pdfjs-page-scale-width = Lebar Laman
pdfjs-page-scale-fit = Muat Laman
pdfjs-page-scale-auto = Perbesaran Otomatis
pdfjs-page-scale-actual = Ukuran Asli
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Halaman { $page }
## Loading indicator messages
pdfjs-loading-error = Galat terjadi saat memuat PDF.
pdfjs-invalid-file-error = Berkas PDF tidak valid atau rusak.
pdfjs-missing-file-error = Berkas PDF tidak ada.
pdfjs-unexpected-response-error = Balasan server yang tidak diharapkan.
pdfjs-rendering-error = Galat terjadi saat merender laman.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [Anotasi { $type }]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Masukkan sandi untuk membuka berkas PDF ini.
pdfjs-password-invalid = Sandi tidak valid. Silakan coba lagi.
pdfjs-password-ok-button = Oke
pdfjs-password-cancel-button = Batal
pdfjs-web-fonts-disabled = Font web dinonaktifkan: tidak dapat menggunakan font PDF yang tersemat.
## Editing
pdfjs-editor-free-text-button =
.title = Teks
pdfjs-editor-free-text-button-label = Teks
pdfjs-editor-ink-button =
.title = Gambar
pdfjs-editor-ink-button-label = Gambar
pdfjs-editor-stamp-button =
.title = Tambah atau edit gambar
pdfjs-editor-stamp-button-label = Tambah atau edit gambar
pdfjs-editor-highlight-button =
.title = Sorot
pdfjs-editor-highlight-button-label = Sorot
pdfjs-highlight-floating-button1 =
.title = Sorot
.aria-label = Sorot
pdfjs-highlight-floating-button-label = Sorot
pdfjs-editor-signature-button =
.title = Tambahkan tanda tangan
pdfjs-editor-signature-button-label = Tambahkan tanda tangan
## Default editor aria labels
# “Highlight” is a noun, the string is used on the editor for highlights.
pdfjs-editor-highlight-editor =
.aria-label = Editor sorot
# “Drawing” is a noun, the string is used on the editor for drawings.
pdfjs-editor-ink-editor =
.aria-label = Editor gambar
pdfjs-editor-signature-editor =
.aria-label = Editor tanda tangan
pdfjs-editor-stamp-editor =
.aria-label = Editor gambar
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Hapus gambar
pdfjs-editor-remove-freetext-button =
.title = Hapus teks
pdfjs-editor-remove-stamp-button =
.title = Hapus gambar
pdfjs-editor-remove-highlight-button =
.title = Hapus sorotan
pdfjs-editor-remove-signature-button =
.title = Hapus tanda tangan
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Warna
pdfjs-editor-free-text-size-input = Ukuran
pdfjs-editor-ink-color-input = Warna
pdfjs-editor-ink-thickness-input = Ketebalan
pdfjs-editor-ink-opacity-input = Opasitas
pdfjs-editor-stamp-add-image-button =
.title = Tambahkan gambar
pdfjs-editor-stamp-add-image-button-label = Tambahkan gambar
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Ketebalan
pdfjs-editor-free-highlight-thickness-title =
.title = Ubah ketebalan saat menyorot item selain teks
pdfjs-editor-add-signature-container =
.aria-label = Kontrol tanda tangan dan tanda tangan tersimpan
pdfjs-editor-signature-add-signature-button =
.title = Tambahkan tanda tangan baru
pdfjs-editor-signature-add-signature-button-label = Tambahkan tanda tangan baru
# Used on the button to use an already saved signature.
# Variables:
# $description (String) - a string describing/labeling the signature.
pdfjs-editor-add-saved-signature-button =
.title = Tanda tangan tersimpan: { $description }
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Editor Teks
.default-content = Mulai mengetik…
pdfjs-free-text =
.aria-label = Editor Teks
pdfjs-free-text-default-content = Mulai mengetik…
pdfjs-ink =
.aria-label = Editor Gambar
pdfjs-ink-canvas =
.aria-label = Gambar yang dibuat pengguna
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Teks alternatif
pdfjs-editor-alt-text-edit-button =
.aria-label = Edit teks alternatif
pdfjs-editor-alt-text-edit-button-label = Edit teks alternatif
pdfjs-editor-alt-text-dialog-label = Pilih opsi
pdfjs-editor-alt-text-dialog-description = Teks alternatif membantu ketika orang tidak dapat melihat gambar atau ketika tidak termuat.
pdfjs-editor-alt-text-add-description-label = Tambahkan deskripsi
pdfjs-editor-alt-text-add-description-description = Upayakan 1-2 kalimat yang menggambarkan subjek, latar, atau tindakan.
pdfjs-editor-alt-text-mark-decorative-label = Tandai sebagai dekoratif
pdfjs-editor-alt-text-mark-decorative-description = Ini digunakan untuk gambar hias, seperti batas atau tanda air.
pdfjs-editor-alt-text-cancel-button = Batal
pdfjs-editor-alt-text-save-button = Simpan
pdfjs-editor-alt-text-decorative-tooltip = Ditandai sebagai dekoratif
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Misalnya, “Seorang pemuda duduk di meja untuk makan”
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Teks alternatif
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Pojok kiri atas — ubah ukuran
pdfjs-editor-resizer-label-top-middle = Tengah atas — ubah ukuran
pdfjs-editor-resizer-label-top-right = Pojok kanan atas — ubah ukuran
pdfjs-editor-resizer-label-middle-right = Kanan tengah — ubah ukuran
pdfjs-editor-resizer-label-bottom-right = Pojok kanan bawah — ubah ukuran
pdfjs-editor-resizer-label-bottom-middle = Tengah bawah — ubah ukuran
pdfjs-editor-resizer-label-bottom-left = Pojok kiri bawah — ubah ukuran
pdfjs-editor-resizer-label-middle-left = Kiri tengah — ubah ukuran
pdfjs-editor-resizer-top-left =
.aria-label = Pojok kiri atas — ubah ukuran
pdfjs-editor-resizer-top-middle =
.aria-label = Tengah atas — ubah ukuran
pdfjs-editor-resizer-top-right =
.aria-label = Pojok kanan atas — ubah ukuran
pdfjs-editor-resizer-middle-right =
.aria-label = Kanan tengah — ubah ukuran
pdfjs-editor-resizer-bottom-right =
.aria-label = Pojok kanan bawah — ubah ukuran
pdfjs-editor-resizer-bottom-middle =
.aria-label = Tengah bawah — ubah ukuran
pdfjs-editor-resizer-bottom-left =
.aria-label = Pojok kiri bawah — ubah ukuran
pdfjs-editor-resizer-middle-left =
.aria-label = Kiri tengah — ubah ukuran
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Warna sorot
pdfjs-editor-colorpicker-button =
.title = Ubah warna
pdfjs-editor-colorpicker-dropdown =
.aria-label = Pilihan warna
pdfjs-editor-colorpicker-yellow =
.title = Kuning
pdfjs-editor-colorpicker-green =
.title = Hijau
pdfjs-editor-colorpicker-blue =
.title = Biru
pdfjs-editor-colorpicker-pink =
.title = Merah Jambu
pdfjs-editor-colorpicker-red =
.title = Merah
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Tampilkan semua
pdfjs-editor-highlight-show-all-button =
.title = Tampilkan semua
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Edit teks alternatif (deskripsi gambar)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Tambahkan teks alternatif (deskripsi gambar)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Tulis deskripsi Anda di sini…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Deskripsi singkat untuk orang yang tidak dapat melihat gambar atau saat gambar tidak termuat.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Teks alternatif ini dibuat secara otomatis dan mungkin tidak akurat.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Pelajari lebih lanjut
pdfjs-editor-new-alt-text-create-automatically-button-label = Buat teks alternatif secara otomatis
pdfjs-editor-new-alt-text-not-now-button = Jangan sekarang
pdfjs-editor-new-alt-text-error-title = Tidak bisa membuat teks alternatif secara otomatis
pdfjs-editor-new-alt-text-error-description = Silakan tulis teks alternatif Anda sendiri atau coba lagi nanti.
pdfjs-editor-new-alt-text-error-close-button = Tutup
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Mengunduh model AI teks alternatif ({ $downloadedSize } dari { $totalSize } MB)
.aria-valuetext = Mengunduh model AI teks alternatif ({ $downloadedSize } dari { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Teks alternatif ditambahkan
pdfjs-editor-new-alt-text-added-button-label = Teks alternatif ditambahkan
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Teks alternatif hilang
pdfjs-editor-new-alt-text-missing-button-label = Teks alternatif hilang
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Tinjau teks alternatif
pdfjs-editor-new-alt-text-to-review-button-label = Tinjau teks alternatif
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Dibuat secara otomatis: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Pengaturan teks alternatif gambar
pdfjs-image-alt-text-settings-button-label = Pengaturan teks alternatif gambar
pdfjs-editor-alt-text-settings-dialog-label = Pengaturan teks alternatif gambar
pdfjs-editor-alt-text-settings-automatic-title = Teks alternatif otomatis
pdfjs-editor-alt-text-settings-create-model-button-label = Buat teks alternatif secara otomatis
pdfjs-editor-alt-text-settings-create-model-description = Menyarankan deskripsi untuk membantu orang yang tidak dapat melihat gambar atau ketika gambar tidak termuat.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Model AI teks alternatif ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Berjalan secara lokal di perangkat Anda sehingga data Anda tetap pribadi. Diperlukan untuk teks alternatif otomatis.
pdfjs-editor-alt-text-settings-delete-model-button = Hapus
pdfjs-editor-alt-text-settings-download-model-button = Unduh
pdfjs-editor-alt-text-settings-downloading-model-button = Mengunduh…
pdfjs-editor-alt-text-settings-editor-title = Editor teks alternatif
pdfjs-editor-alt-text-settings-show-dialog-button-label = Tampilkan editor teks alternatif segera saat menambahkan gambar
pdfjs-editor-alt-text-settings-show-dialog-description = Membantu Anda memastikan semua gambar Anda memiliki teks alternatif.
pdfjs-editor-alt-text-settings-close-button = Tutup
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Sorotan dihapus
pdfjs-editor-undo-bar-message-freetext = Teks dihapus
pdfjs-editor-undo-bar-message-ink = Gambar dihapus
pdfjs-editor-undo-bar-message-stamp = Gambar dihapus
pdfjs-editor-undo-bar-message-signature = Tanda tangan dihapus
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple = { $count } anotasi dihapus
pdfjs-editor-undo-bar-undo-button =
.title = Urungkan
pdfjs-editor-undo-bar-undo-button-label = Urungkan
pdfjs-editor-undo-bar-close-button =
.title = Tutup
pdfjs-editor-undo-bar-close-button-label = Tutup
## Add a signature dialog
pdfjs-editor-add-signature-dialog-title = Tambahkan tanda tangan
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Tipe
.title = Tipe
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Gambarkan
.title = Gambarkan
pdfjs-editor-add-signature-image-button = Gambar
.title = Gambar
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Ketik tanda tangan Anda
.placeholder = Ketik tanda tangan Anda
pdfjs-editor-add-signature-draw-placeholder = Buat tanda tangan Anda
pdfjs-editor-add-signature-draw-thickness-range-label = Ketebalan
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Ketebalan gambar: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Seret berkas ke sini untuk mengunggah
## Controls
pdfjs-editor-add-signature-description-default-when-drawing = Tanda tangan
pdfjs-editor-add-signature-clear-button-label = Hapus tanda tangan
pdfjs-editor-add-signature-clear-button =
.title = Hapus tanda tangan
pdfjs-editor-add-signature-save-checkbox = Simpan tanda tangan
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/is/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Fyrri síða
pdfjs-previous-button-label = Fyrri
pdfjs-next-button =
.title = Næsta síða
pdfjs-next-button-label = Næsti
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Síða
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = af { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } af { $pagesCount })
pdfjs-zoom-out-button =
.title = Minnka aðdrátt
pdfjs-zoom-out-button-label = Minnka aðdrátt
pdfjs-zoom-in-button =
.title = Auka aðdrátt
pdfjs-zoom-in-button-label = Auka aðdrátt
pdfjs-zoom-select =
.title = Aðdráttur
pdfjs-presentation-mode-button =
.title = Skipta yfir á kynningarham
pdfjs-presentation-mode-button-label = Kynningarhamur
pdfjs-open-file-button =
.title = Opna skrá
pdfjs-open-file-button-label = Opna
pdfjs-print-button =
.title = Prenta
pdfjs-print-button-label = Prenta
pdfjs-save-button =
.title = Vista
pdfjs-save-button-label = Vista
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Sækja
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Sækja
pdfjs-bookmark-button =
.title = Núverandi síða (Skoða vefslóð frá núverandi síðu)
pdfjs-bookmark-button-label = Núverandi síða
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Verkfæri
pdfjs-tools-button-label = Verkfæri
pdfjs-first-page-button =
.title = Fara á fyrstu síðu
pdfjs-first-page-button-label = Fara á fyrstu síðu
pdfjs-last-page-button =
.title = Fara á síðustu síðu
pdfjs-last-page-button-label = Fara á síðustu síðu
pdfjs-page-rotate-cw-button =
.title = Snúa réttsælis
pdfjs-page-rotate-cw-button-label = Snúa réttsælis
pdfjs-page-rotate-ccw-button =
.title = Snúa rangsælis
pdfjs-page-rotate-ccw-button-label = Snúa rangsælis
pdfjs-cursor-text-select-tool-button =
.title = Virkja textavalsáhald
pdfjs-cursor-text-select-tool-button-label = Textavalsáhald
pdfjs-cursor-hand-tool-button =
.title = Virkja handarverkfæri
pdfjs-cursor-hand-tool-button-label = Handarverkfæri
pdfjs-scroll-page-button =
.title = Nota síðuskrun
pdfjs-scroll-page-button-label = Síðuskrun
pdfjs-scroll-vertical-button =
.title = Nota lóðrétt skrun
pdfjs-scroll-vertical-button-label = Lóðrétt skrun
pdfjs-scroll-horizontal-button =
.title = Nota lárétt skrun
pdfjs-scroll-horizontal-button-label = Lárétt skrun
pdfjs-scroll-wrapped-button =
.title = Nota línuskipt síðuskrun
pdfjs-scroll-wrapped-button-label = Línuskipt síðuskrun
pdfjs-spread-none-button =
.title = Ekki taka þátt í dreifingu síðna
pdfjs-spread-none-button-label = Engin dreifing
pdfjs-spread-odd-button =
.title = Taka þátt í dreifingu síðna með oddatölum
pdfjs-spread-odd-button-label = Oddatöludreifing
pdfjs-spread-even-button =
.title = Taktu þátt í dreifingu síðna með jöfnuntölum
pdfjs-spread-even-button-label = Jafnatöludreifing
## Document properties dialog
pdfjs-document-properties-button =
.title = Eiginleikar skjals…
pdfjs-document-properties-button-label = Eiginleikar skjals…
pdfjs-document-properties-file-name = Skráarnafn:
pdfjs-document-properties-file-size = Skrárstærð:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bæti)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bæti)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = Titill:
pdfjs-document-properties-author = Hönnuður:
pdfjs-document-properties-subject = Efni:
pdfjs-document-properties-keywords = Stikkorð:
pdfjs-document-properties-creation-date = Búið til:
pdfjs-document-properties-modification-date = Dags breytingar:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Höfundur:
pdfjs-document-properties-producer = PDF framleiðandi:
pdfjs-document-properties-version = PDF útgáfa:
pdfjs-document-properties-page-count = Blaðsíðufjöldi:
pdfjs-document-properties-page-size = Stærð síðu:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = skammsnið
pdfjs-document-properties-page-size-orientation-landscape = langsnið
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letter
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Fljótleg vefskoðun:
pdfjs-document-properties-linearized-yes = Já
pdfjs-document-properties-linearized-no = Nei
pdfjs-document-properties-close-button = Loka
## Print
pdfjs-print-progress-message = Undirbý skjal fyrir prentun…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Hætta við
pdfjs-printing-not-supported = Aðvörun: Prentun er ekki með fyllilegan stuðning á þessum vafra.
pdfjs-printing-not-ready = Aðvörun: Ekki er búið að hlaða inn allri PDF skránni fyrir prentun.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Víxla hliðarstiku af/á
pdfjs-toggle-sidebar-notification-button =
.title = Víxla hliðarstiku af/á (skjal inniheldur yfirlit/viðhengi/lög)
pdfjs-toggle-sidebar-button-label = Víxla hliðarstiku af/á
pdfjs-document-outline-button =
.title = Sýna yfirlit skjals (tvísmelltu til að opna/loka öllum hlutum)
pdfjs-document-outline-button-label = Efnisskipan skjals
pdfjs-attachments-button =
.title = Sýna viðhengi
pdfjs-attachments-button-label = Viðhengi
pdfjs-layers-button =
.title = Birta lög (tvísmelltu til að endurstilla öll lög í sjálfgefna stöðu)
pdfjs-layers-button-label = Lög
pdfjs-thumbs-button =
.title = Sýna smámyndir
pdfjs-thumbs-button-label = Smámyndir
pdfjs-current-outline-item-button =
.title = Finna núverandi atriði efnisskipunar
pdfjs-current-outline-item-button-label = Núverandi atriði efnisskipunar
pdfjs-findbar-button =
.title = Leita í skjali
pdfjs-findbar-button-label = Leita
pdfjs-additional-layers = Viðbótarlög
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Síða { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Smámynd af síðu { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Leita
.placeholder = Leita í skjali…
pdfjs-find-previous-button =
.title = Leita að fyrra tilfelli þessara orða
pdfjs-find-previous-button-label = Fyrri
pdfjs-find-next-button =
.title = Leita að næsta tilfelli þessara orða
pdfjs-find-next-button-label = Næsti
pdfjs-find-highlight-checkbox = Lita allt
pdfjs-find-match-case-checkbox-label = Passa við stafstöðu
pdfjs-find-match-diacritics-checkbox-label = Passa við broddstafi
pdfjs-find-entire-word-checkbox-label = Heil orð
pdfjs-find-reached-top = Náði efst í skjal, held áfram neðst
pdfjs-find-reached-bottom = Náði enda skjals, held áfram efst
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } af { $total } passar við
*[other] { $current } af { $total } passa við
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Fleiri en { $limit } passar við
*[other] Fleiri en { $limit } passa við
}
pdfjs-find-not-found = Fann ekki orðið
## Predefined zoom values
pdfjs-page-scale-width = Síðubreidd
pdfjs-page-scale-fit = Passa á síðu
pdfjs-page-scale-auto = Sjálfvirkur aðdráttur
pdfjs-page-scale-actual = Raunstærð
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Síða { $page }
## Loading indicator messages
pdfjs-loading-error = Villa kom upp við að hlaða inn PDF.
pdfjs-invalid-file-error = Ógild eða skemmd PDF skrá.
pdfjs-missing-file-error = Vantar PDF skrá.
pdfjs-unexpected-response-error = Óvænt svar frá netþjóni.
pdfjs-rendering-error = Upp kom villa við að birta síðuna.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } Skýring]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Settu inn lykilorð til að opna þessa PDF-skrá.
pdfjs-password-invalid = Ógilt lykilorð. Reyndu aftur.
pdfjs-password-ok-button = Í lagi
pdfjs-password-cancel-button = Hætta við
pdfjs-web-fonts-disabled = Vef leturgerðir eru óvirkar: get ekki notað innbyggðar PDF leturgerðir.
## Editing
pdfjs-editor-free-text-button =
.title = Texti
pdfjs-editor-free-text-button-label = Texti
pdfjs-editor-ink-button =
.title = Teikna
pdfjs-editor-ink-button-label = Teikna
pdfjs-editor-stamp-button =
.title = Bæta við eða breyta myndum
pdfjs-editor-stamp-button-label = Bæta við eða breyta myndum
pdfjs-editor-highlight-button =
.title = Áherslulita
pdfjs-editor-highlight-button-label = Áherslulita
pdfjs-highlight-floating-button1 =
.title = Áherslulita
.aria-label = Áherslulita
pdfjs-highlight-floating-button-label = Áherslulita
pdfjs-editor-signature-button =
.title = Bæta við undirritun
pdfjs-editor-signature-button-label = Bæta við undirritun
## Default editor aria labels
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Fjarlægja teikningu
pdfjs-editor-remove-freetext-button =
.title = Fjarlægja texta
pdfjs-editor-remove-stamp-button =
.title = Fjarlægja mynd
pdfjs-editor-remove-highlight-button =
.title = Fjarlægja áherslulit
pdfjs-editor-remove-signature-button =
.title = Fjarlægja undirskrift
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Litur
pdfjs-editor-free-text-size-input = Stærð
pdfjs-editor-ink-color-input = Litur
pdfjs-editor-ink-thickness-input = Þykkt
pdfjs-editor-ink-opacity-input = Ógegnsæi
pdfjs-editor-stamp-add-image-button =
.title = Bæta við mynd
pdfjs-editor-stamp-add-image-button-label = Bæta við mynd
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Þykkt
pdfjs-editor-free-highlight-thickness-title =
.title = Breyta þykkt við áherslulitun annarra atriða en texta
pdfjs-editor-signature-add-signature-button =
.title = Bæta við nýrri undirritun
pdfjs-editor-signature-add-signature-button-label = Bæta við nýrri undirritun
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Textaritill
.default-content = Byrjaðu að skrifa…
pdfjs-free-text =
.aria-label = Textaritill
pdfjs-free-text-default-content = Byrjaðu að skrifa…
pdfjs-ink =
.aria-label = Teikniritill
pdfjs-ink-canvas =
.aria-label = Mynd gerð af notanda
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Alt-varatexti
pdfjs-editor-alt-text-edit-button =
.aria-label = Breyta alt-myndatexta
pdfjs-editor-alt-text-edit-button-label = Breyta alt-varatexta
pdfjs-editor-alt-text-dialog-label = Veldu valkost
pdfjs-editor-alt-text-dialog-description = Alt-varatexti (auka-myndatexti) hjálpar þegar fólk getur ekki séð myndina eða þegar hún hleðst ekki inn.
pdfjs-editor-alt-text-add-description-label = Bættu við lýsingu
pdfjs-editor-alt-text-add-description-description = Reyndu að takmarka þetta við 1-2 setningar sem lýsa efninu, umhverfi eða aðgerðum.
pdfjs-editor-alt-text-mark-decorative-label = Merkja sem skraut
pdfjs-editor-alt-text-mark-decorative-description = Þetta er notað fyrir skrautmyndir, eins og borða eða vatnsmerki.
pdfjs-editor-alt-text-cancel-button = Hætta við
pdfjs-editor-alt-text-save-button = Vista
pdfjs-editor-alt-text-decorative-tooltip = Merkt sem skraut
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Til dæmis: „Ungur maður sest við borð til að snæða máltíð“
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Alt-myndatexti
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Efst í vinstra horni - breyta stærð
pdfjs-editor-resizer-label-top-middle = Efst á miðju - breyta stærð
pdfjs-editor-resizer-label-top-right = Efst í hægra horni - breyta stærð
pdfjs-editor-resizer-label-middle-right = Miðja til hægri - breyta stærð
pdfjs-editor-resizer-label-bottom-right = Neðst í hægra horni - breyta stærð
pdfjs-editor-resizer-label-bottom-middle = Neðst á miðju - breyta stærð
pdfjs-editor-resizer-label-bottom-left = Neðst í vinstra horni - breyta stærð
pdfjs-editor-resizer-label-middle-left = Miðja til vinstri - breyta stærð
pdfjs-editor-resizer-top-left =
.aria-label = Efst í vinstra horni - breyta stærð
pdfjs-editor-resizer-top-middle =
.aria-label = Efst á miðju - breyta stærð
pdfjs-editor-resizer-top-right =
.aria-label = Efst í hægra horni - breyta stærð
pdfjs-editor-resizer-middle-right =
.aria-label = Miðja til hægri - breyta stærð
pdfjs-editor-resizer-bottom-right =
.aria-label = Neðst í hægra horni - breyta stærð
pdfjs-editor-resizer-bottom-middle =
.aria-label = Neðst á miðju - breyta stærð
pdfjs-editor-resizer-bottom-left =
.aria-label = Neðst í vinstra horni - breyta stærð
pdfjs-editor-resizer-middle-left =
.aria-label = Miðja til vinstri - breyta stærð
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Áherslulitur
pdfjs-editor-colorpicker-button =
.title = Skipta um lit
pdfjs-editor-colorpicker-dropdown =
.aria-label = Val lita
pdfjs-editor-colorpicker-yellow =
.title = Gult
pdfjs-editor-colorpicker-green =
.title = Grænt
pdfjs-editor-colorpicker-blue =
.title = Blátt
pdfjs-editor-colorpicker-pink =
.title = Bleikt
pdfjs-editor-colorpicker-red =
.title = Rautt
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Birta allt
pdfjs-editor-highlight-show-all-button =
.title = Birta allt
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Breyta alt-myndatexta (lýsingu á mynd)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Bæta við alt-myndatexta (lýsingu á mynd)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Skrifaðu lýsinguna þína hér…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Stutt lýsing fyrir fólk sem getur ekki séð myndina eða þegar myndin hleðst ekki inn.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Þessi alt-myndatexti var búinn til sjálfvirkt og gæti verið ónákvæmur.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Kanna nánar
pdfjs-editor-new-alt-text-create-automatically-button-label = Útbúa alt-myndatexta sjálfvirkt
pdfjs-editor-new-alt-text-not-now-button = Ekki núna
pdfjs-editor-new-alt-text-error-title = Gat ekki búið til alt-myndatexta sjálfkrafa
pdfjs-editor-new-alt-text-error-description = Skrifaðu þinn eiginn alt-myndatexta eða reyndu aftur síðar.
pdfjs-editor-new-alt-text-error-close-button = Loka
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Sækir gervigreindarlíkan með alt-myndatextum ({ $downloadedSize } af { $totalSize } MB)
.aria-valuetext = Sækir gervigreindarlíkan með alt-myndatextum ({ $downloadedSize } af { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Alt-myndatexta bætt við
pdfjs-editor-new-alt-text-added-button-label = Alt-myndatexta bætt við
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Vantar alt-myndatexta
pdfjs-editor-new-alt-text-missing-button-label = Vantar alt-myndatexta
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Yfirfara alt-myndatexta
pdfjs-editor-new-alt-text-to-review-button-label = Yfirfara myndatexta
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Útbúið sjálfvirkt: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Stillingar fyrir alt-texta myndar
pdfjs-image-alt-text-settings-button-label = Stillingar fyrir alt-texta myndar
pdfjs-editor-alt-text-settings-dialog-label = Stillingar fyrir alt-texta myndar
pdfjs-editor-alt-text-settings-automatic-title = Sjálfvirkur alt-myndatexti
pdfjs-editor-alt-text-settings-create-model-button-label = Útbúa alt-myndatexta sjálfvirkt
pdfjs-editor-alt-text-settings-create-model-description = Stingur upp á lýsingum til að hjálpa fólki sem getur ekki séð myndina eða þegar myndin hleðst ekki inn.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Gervigreindarlíkan alt-myndatexta ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Keyrir staðbundið á tækinu þínu svo gögnin þín haldast undir þinni stjórn. Nauðsynlegt fyrir sjálfvirka alt-myndatexta.
pdfjs-editor-alt-text-settings-delete-model-button = Eyða
pdfjs-editor-alt-text-settings-download-model-button = Sækja
pdfjs-editor-alt-text-settings-downloading-model-button = Sæki…
pdfjs-editor-alt-text-settings-editor-title = Ritill fyrir alt-myndatexta
pdfjs-editor-alt-text-settings-show-dialog-button-label = Sýna alt-myndatextaritil strax þegar mynd er bætt við
pdfjs-editor-alt-text-settings-show-dialog-description = Hjálpar þér að tryggja að allar myndirnar þínar séu með alt-myndatexta.
pdfjs-editor-alt-text-settings-close-button = Loka
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Áherslulitun fjarlægð
pdfjs-editor-undo-bar-message-freetext = Texti fjarlægður
pdfjs-editor-undo-bar-message-ink = Teikning fjarlægð
pdfjs-editor-undo-bar-message-stamp = Mynd fjarlægð
pdfjs-editor-undo-bar-message-signature = Undirskrift fjarlægð
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } glósa fjarlægð
*[other] { $count } glósur fjarlægðar
}
pdfjs-editor-undo-bar-undo-button =
.title = Afturkalla
pdfjs-editor-undo-bar-undo-button-label = Afturkalla
pdfjs-editor-undo-bar-close-button =
.title = Loka
pdfjs-editor-undo-bar-close-button-label = Loka
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = Þessi gluggi gerir notandanum kleift að búa til undirskrift til að bæta við PDF-skjal. Notandinn getur breytt nafninu (sem einnig þjónar sem alt-texti), og valið að vista undirskriftina til endurtekinnar notkunar.
pdfjs-editor-add-signature-dialog-title = Bæta við undirskrift
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Tegund
.title = Tegund
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Teikna
.title = Teikna
pdfjs-editor-add-signature-image-button = Mynd
.title = Mynd
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Skrifaðu inn undirskriftina þína
.placeholder = Skrifaðu inn undirskriftina þína
pdfjs-editor-add-signature-draw-placeholder = Teiknaðu undirskriftina þína
pdfjs-editor-add-signature-draw-thickness-range-label = Þykkt
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Sverleiki teikningar: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Dragðu skrá hingað til að senda inn
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Eða skoðaðu myndskrár
*[other] Eða skoðaðu myndskrár
}
## Controls
pdfjs-editor-add-signature-description-label = Lýsing (alt-hjálpartexti)
pdfjs-editor-add-signature-description-input =
.title = Lýsing (alt-hjálpartexti)
pdfjs-editor-add-signature-description-default-when-drawing = Undirskrift
pdfjs-editor-add-signature-clear-button-label = Hreinsa undirskrift
pdfjs-editor-add-signature-clear-button =
.title = Hreinsa undirskrift
pdfjs-editor-add-signature-save-checkbox = Vista undirskrift
pdfjs-editor-add-signature-save-warning-message = Þú hefur náð hámarki 5 vistaðra undirskrifta. Fjarlægðu eina til að geta vistað fleiri.
pdfjs-editor-add-signature-image-upload-error-title = Ekki tókst að senda inn mynd
pdfjs-editor-add-signature-image-upload-error-description = Athugaðu nettenginguna þína eða prófaðu aðra mynd.
pdfjs-editor-add-signature-error-close-button = Loka
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Hætta við
pdfjs-editor-add-signature-add-button = Bæta við
pdfjs-editor-edit-signature-update-button = Uppfæra
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Fjarlægja undirritun
pdfjs-editor-delete-signature-button-label = Fjarlægja undirritun
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Breyta lýsingu
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Breyta lýsingu
================================================
FILE: cookbook/static/pdfjs/web/locale/it/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Pagina precedente
pdfjs-previous-button-label = Precedente
pdfjs-next-button =
.title = Pagina successiva
pdfjs-next-button-label = Successiva
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Pagina
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = di { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } di { $pagesCount })
pdfjs-zoom-out-button =
.title = Riduci zoom
pdfjs-zoom-out-button-label = Riduci zoom
pdfjs-zoom-in-button =
.title = Aumenta zoom
pdfjs-zoom-in-button-label = Aumenta zoom
pdfjs-zoom-select =
.title = Zoom
pdfjs-presentation-mode-button =
.title = Passa alla modalità presentazione
pdfjs-presentation-mode-button-label = Modalità presentazione
pdfjs-open-file-button =
.title = Apri file
pdfjs-open-file-button-label = Apri
pdfjs-print-button =
.title = Stampa
pdfjs-print-button-label = Stampa
pdfjs-save-button =
.title = Salva
pdfjs-save-button-label = Salva
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Scarica
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Scarica
pdfjs-bookmark-button =
.title = Pagina corrente (mostra URL della pagina corrente)
pdfjs-bookmark-button-label = Pagina corrente
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Strumenti
pdfjs-tools-button-label = Strumenti
pdfjs-first-page-button =
.title = Vai alla prima pagina
pdfjs-first-page-button-label = Vai alla prima pagina
pdfjs-last-page-button =
.title = Vai all’ultima pagina
pdfjs-last-page-button-label = Vai all’ultima pagina
pdfjs-page-rotate-cw-button =
.title = Ruota in senso orario
pdfjs-page-rotate-cw-button-label = Ruota in senso orario
pdfjs-page-rotate-ccw-button =
.title = Ruota in senso antiorario
pdfjs-page-rotate-ccw-button-label = Ruota in senso antiorario
pdfjs-cursor-text-select-tool-button =
.title = Attiva strumento di selezione testo
pdfjs-cursor-text-select-tool-button-label = Strumento di selezione testo
pdfjs-cursor-hand-tool-button =
.title = Attiva strumento mano
pdfjs-cursor-hand-tool-button-label = Strumento mano
pdfjs-scroll-page-button =
.title = Utilizza scorrimento pagine
pdfjs-scroll-page-button-label = Scorrimento pagine
pdfjs-scroll-vertical-button =
.title = Scorri le pagine in verticale
pdfjs-scroll-vertical-button-label = Scorrimento verticale
pdfjs-scroll-horizontal-button =
.title = Scorri le pagine in orizzontale
pdfjs-scroll-horizontal-button-label = Scorrimento orizzontale
pdfjs-scroll-wrapped-button =
.title = Scorri le pagine in verticale, disponendole da sinistra a destra e andando a capo automaticamente
pdfjs-scroll-wrapped-button-label = Scorrimento con a capo automatico
pdfjs-spread-none-button =
.title = Non raggruppare pagine
pdfjs-spread-none-button-label = Nessun raggruppamento
pdfjs-spread-odd-button =
.title = Crea gruppi di pagine che iniziano con numeri di pagina dispari
pdfjs-spread-odd-button-label = Raggruppamento dispari
pdfjs-spread-even-button =
.title = Crea gruppi di pagine che iniziano con numeri di pagina pari
pdfjs-spread-even-button-label = Raggruppamento pari
## Document properties dialog
pdfjs-document-properties-button =
.title = Proprietà del documento…
pdfjs-document-properties-button-label = Proprietà del documento…
pdfjs-document-properties-file-name = Nome file:
pdfjs-document-properties-file-size = Dimensione file:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } byte)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } byte)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } kB ({ $size_b } byte)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } byte)
pdfjs-document-properties-title = Titolo:
pdfjs-document-properties-author = Autore:
pdfjs-document-properties-subject = Oggetto:
pdfjs-document-properties-keywords = Parole chiave:
pdfjs-document-properties-creation-date = Data creazione:
pdfjs-document-properties-modification-date = Data modifica:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Autore originale:
pdfjs-document-properties-producer = Produttore PDF:
pdfjs-document-properties-version = Versione PDF:
pdfjs-document-properties-page-count = Conteggio pagine:
pdfjs-document-properties-page-size = Dimensioni pagina:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = verticale
pdfjs-document-properties-page-size-orientation-landscape = orizzontale
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Lettera
pdfjs-document-properties-page-size-name-legal = Legale
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Visualizzazione web veloce:
pdfjs-document-properties-linearized-yes = Sì
pdfjs-document-properties-linearized-no = No
pdfjs-document-properties-close-button = Chiudi
## Print
pdfjs-print-progress-message = Preparazione documento per la stampa…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Annulla
pdfjs-printing-not-supported = Attenzione: la stampa non è completamente supportata da questo browser.
pdfjs-printing-not-ready = Attenzione: il PDF non è ancora stato caricato completamente per la stampa.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Attiva/disattiva barra laterale
pdfjs-toggle-sidebar-notification-button =
.title = Attiva/disattiva barra laterale (il documento contiene struttura/allegati/livelli)
pdfjs-toggle-sidebar-button-label = Attiva/disattiva barra laterale
pdfjs-document-outline-button =
.title = Visualizza la struttura del documento (doppio clic per visualizzare/comprimere tutti gli elementi)
pdfjs-document-outline-button-label = Struttura documento
pdfjs-attachments-button =
.title = Visualizza allegati
pdfjs-attachments-button-label = Allegati
pdfjs-layers-button =
.title = Visualizza livelli (doppio clic per ripristinare tutti i livelli allo stato predefinito)
pdfjs-layers-button-label = Livelli
pdfjs-thumbs-button =
.title = Mostra le miniature
pdfjs-thumbs-button-label = Miniature
pdfjs-current-outline-item-button =
.title = Trova elemento struttura corrente
pdfjs-current-outline-item-button-label = Elemento struttura corrente
pdfjs-findbar-button =
.title = Trova nel documento
pdfjs-findbar-button-label = Trova
pdfjs-additional-layers = Livelli aggiuntivi
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Pagina { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Miniatura della pagina { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Trova
.placeholder = Trova nel documento…
pdfjs-find-previous-button =
.title = Trova l’occorrenza precedente del testo da cercare
pdfjs-find-previous-button-label = Precedente
pdfjs-find-next-button =
.title = Trova l’occorrenza successiva del testo da cercare
pdfjs-find-next-button-label = Successivo
pdfjs-find-highlight-checkbox = Evidenzia
pdfjs-find-match-case-checkbox-label = Maiuscole/minuscole
pdfjs-find-match-diacritics-checkbox-label = Segni diacritici
pdfjs-find-entire-word-checkbox-label = Parole intere
pdfjs-find-reached-top = Raggiunto l’inizio della pagina, continua dalla fine
pdfjs-find-reached-bottom = Raggiunta la fine della pagina, continua dall’inizio
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } di { $total } corrispondenza
*[other] { $current } di { $total } corrispondenze
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Più di una { $limit } corrispondenza
*[other] Più di { $limit } corrispondenze
}
pdfjs-find-not-found = Testo non trovato
## Predefined zoom values
pdfjs-page-scale-width = Larghezza pagina
pdfjs-page-scale-fit = Adatta a una pagina
pdfjs-page-scale-auto = Zoom automatico
pdfjs-page-scale-actual = Dimensioni effettive
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Pagina { $page }
## Loading indicator messages
pdfjs-loading-error = Si è verificato un errore durante il caricamento del PDF.
pdfjs-invalid-file-error = File PDF non valido o danneggiato.
pdfjs-missing-file-error = File PDF non disponibile.
pdfjs-unexpected-response-error = Risposta imprevista del server
pdfjs-rendering-error = Si è verificato un errore durante il rendering della pagina.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [Annotazione: { $type }]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Inserire la password per aprire questo file PDF.
pdfjs-password-invalid = Password non corretta. Riprova.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Annulla
pdfjs-web-fonts-disabled = I web font risultano disattivati: impossibile utilizzare i caratteri incorporati nel PDF.
## Editing
pdfjs-editor-free-text-button =
.title = Testo
pdfjs-editor-free-text-button-label = Testo
pdfjs-editor-ink-button =
.title = Disegno
pdfjs-editor-ink-button-label = Disegno
pdfjs-editor-stamp-button =
.title = Aggiungi o rimuovi immagine
pdfjs-editor-stamp-button-label = Aggiungi o rimuovi immagine
pdfjs-editor-highlight-button =
.title = Evidenzia
pdfjs-editor-highlight-button-label = Evidenzia
pdfjs-highlight-floating-button1 =
.title = Evidenzia
.aria-label = Evidenzia
pdfjs-highlight-floating-button-label = Evidenzia
pdfjs-editor-signature-button =
.title = Aggiungi firma
pdfjs-editor-signature-button-label = Aggiungi firma
## Default editor aria labels
# “Highlight” is a noun, the string is used on the editor for highlights.
pdfjs-editor-highlight-editor =
.aria-label = Modifica evidenziazioni
# “Drawing” is a noun, the string is used on the editor for drawings.
pdfjs-editor-ink-editor =
.aria-label = Modifica disegni
pdfjs-editor-signature-editor =
.aria-label = Modifica firme
pdfjs-editor-stamp-editor =
.aria-label = Modifica immagini
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Rimuovi disegno
pdfjs-editor-remove-freetext-button =
.title = Rimuovi testo
pdfjs-editor-remove-stamp-button =
.title = Rimuovi immagine
pdfjs-editor-remove-highlight-button =
.title = Rimuovi evidenziazione
pdfjs-editor-remove-signature-button =
.title = Rimuovi firma
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Colore
pdfjs-editor-free-text-size-input = Dimensione
pdfjs-editor-ink-color-input = Colore
pdfjs-editor-ink-thickness-input = Spessore
pdfjs-editor-ink-opacity-input = Opacità
pdfjs-editor-stamp-add-image-button =
.title = Aggiungi immagine
pdfjs-editor-stamp-add-image-button-label = Aggiungi immagine
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Spessore
pdfjs-editor-free-highlight-thickness-title =
.title = Modifica lo spessore della selezione per elementi non testuali
pdfjs-editor-add-signature-container =
.aria-label = Controlli firma e firme salvate
pdfjs-editor-signature-add-signature-button =
.title = Aggiungi nuova firma
pdfjs-editor-signature-add-signature-button-label = Aggiungi nuova firma
# Used on the button to use an already saved signature.
# Variables:
# $description (String) - a string describing/labeling the signature.
pdfjs-editor-add-saved-signature-button =
.title = Firma salvata: { $description }
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Editor di testo
.default-content = Inizia a digitare…
pdfjs-free-text =
.aria-label = Editor di testo
pdfjs-free-text-default-content = Inizia a digitare…
pdfjs-ink =
.aria-label = Editor disegni
pdfjs-ink-canvas =
.aria-label = Immagine creata dall’utente
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Testo alternativo
pdfjs-editor-alt-text-edit-button =
.aria-label = Modifica testo alternativo
pdfjs-editor-alt-text-edit-button-label = Modifica testo alternativo
pdfjs-editor-alt-text-dialog-label = Scegli un’opzione
pdfjs-editor-alt-text-dialog-description = Il testo alternativo (“alt text”) aiuta quando le persone non possono vedere l’immagine o quando l’immagine non viene caricata.
pdfjs-editor-alt-text-add-description-label = Aggiungi una descrizione
pdfjs-editor-alt-text-add-description-description = Punta a una o due frasi che descrivono l’argomento, l’ambientazione o le azioni.
pdfjs-editor-alt-text-mark-decorative-label = Contrassegna come decorativa
pdfjs-editor-alt-text-mark-decorative-description = Viene utilizzato per immagini ornamentali, come bordi o filigrane.
pdfjs-editor-alt-text-cancel-button = Annulla
pdfjs-editor-alt-text-save-button = Salva
pdfjs-editor-alt-text-decorative-tooltip = Contrassegnata come decorativa
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Ad esempio, “Un giovane si siede a tavola per mangiare”
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Testo alternativo
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Angolo in alto a sinistra — ridimensiona
pdfjs-editor-resizer-label-top-middle = Lato superiore nel mezzo — ridimensiona
pdfjs-editor-resizer-label-top-right = Angolo in alto a destra — ridimensiona
pdfjs-editor-resizer-label-middle-right = Lato destro nel mezzo — ridimensiona
pdfjs-editor-resizer-label-bottom-right = Angolo in basso a destra — ridimensiona
pdfjs-editor-resizer-label-bottom-middle = Lato inferiore nel mezzo — ridimensiona
pdfjs-editor-resizer-label-bottom-left = Angolo in basso a sinistra — ridimensiona
pdfjs-editor-resizer-label-middle-left = Lato sinistro nel mezzo — ridimensiona
pdfjs-editor-resizer-top-left =
.aria-label = Angolo in alto a sinistra — ridimensiona
pdfjs-editor-resizer-top-middle =
.aria-label = Lato superiore nel mezzo — ridimensiona
pdfjs-editor-resizer-top-right =
.aria-label = Angolo in alto a destra — ridimensiona
pdfjs-editor-resizer-middle-right =
.aria-label = Lato destro nel mezzo — ridimensiona
pdfjs-editor-resizer-bottom-right =
.aria-label = Angolo in basso a destra — ridimensiona
pdfjs-editor-resizer-bottom-middle =
.aria-label = Lato inferiore nel mezzo — ridimensiona
pdfjs-editor-resizer-bottom-left =
.aria-label = Angolo in basso a sinistra — ridimensiona
pdfjs-editor-resizer-middle-left =
.aria-label = Lato sinistro nel mezzo — ridimensiona
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Colore evidenziatore
pdfjs-editor-colorpicker-button =
.title = Cambia colore
pdfjs-editor-colorpicker-dropdown =
.aria-label = Colori disponibili
pdfjs-editor-colorpicker-yellow =
.title = Giallo
pdfjs-editor-colorpicker-green =
.title = Verde
pdfjs-editor-colorpicker-blue =
.title = Blu
pdfjs-editor-colorpicker-pink =
.title = Rosa
pdfjs-editor-colorpicker-red =
.title = Rosso
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Mostra tutto
pdfjs-editor-highlight-show-all-button =
.title = Mostra tutto
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Modifica testo alternativo (descrizione dell’immagine)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Aggiungi testo alternativo (descrizione dell’immagine)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Scrivi qui la tua descrizione…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Breve descrizione per le persone che non possono vedere l’immagine, o mostrata quando l’immagine non si carica.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Questo testo alternativo è stato creato automaticamente e potrebbe non essere accurato.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Ulteriori informazioni
pdfjs-editor-new-alt-text-create-automatically-button-label = Crea automaticamente testo alternativo
pdfjs-editor-new-alt-text-not-now-button = Non adesso
pdfjs-editor-new-alt-text-error-title = Impossibile creare automaticamente il testo alternativo
pdfjs-editor-new-alt-text-error-description = Scrivi il testo alternativo o riprova più tardi.
pdfjs-editor-new-alt-text-error-close-button = Chiudi
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Download in corso del modello IA per il testo alternativo ({ $downloadedSize } di { $totalSize } MB)
.aria-valuetext = Download in corso del modello IA per il testo alternativo ({ $downloadedSize } di { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Aggiunto testo alternativo
pdfjs-editor-new-alt-text-added-button-label = Aggiunto testo alternativo
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Testo alternativo mancante
pdfjs-editor-new-alt-text-missing-button-label = Testo alternativo mancante
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Verifica testo alternativo
pdfjs-editor-new-alt-text-to-review-button-label = Verifica testo alternativo
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Creato automaticamente: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Impostazioni testo alternativo per le immagini
pdfjs-image-alt-text-settings-button-label = Impostazioni testo alternativo per le immagini
pdfjs-editor-alt-text-settings-dialog-label = Impostazioni testo alternativo per le immagini
pdfjs-editor-alt-text-settings-automatic-title = Testo alternativo automatico
pdfjs-editor-alt-text-settings-create-model-button-label = Crea testo alternativo automaticamente
pdfjs-editor-alt-text-settings-create-model-description = Suggerisce una descrizione per le persone che non possono vedere l’immagine, o mostrata quando l’immagine non si carica.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Modello IA per il testo alternativo ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Viene eseguito localmente sul tuo dispositivo in modo che i tuoi dati rimangano riservati. È richiesto per la generazione automatica del testo alternativo.
pdfjs-editor-alt-text-settings-delete-model-button = Elimina
pdfjs-editor-alt-text-settings-download-model-button = Scarica
pdfjs-editor-alt-text-settings-downloading-model-button = Download…
pdfjs-editor-alt-text-settings-editor-title = Modifica testo alternativo
pdfjs-editor-alt-text-settings-show-dialog-button-label = Mostra l’editor del testo alternativo non appena si aggiunge un’immagine
pdfjs-editor-alt-text-settings-show-dialog-description = Ti aiuta ad assicurarti che tutte le tue immagini abbiano il testo alternativo.
pdfjs-editor-alt-text-settings-close-button = Chiudi
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Evidenziazione rimossa
pdfjs-editor-undo-bar-message-freetext = Testo rimosso
pdfjs-editor-undo-bar-message-ink = Disegno rimosso
pdfjs-editor-undo-bar-message-stamp = Immagine rimossa
pdfjs-editor-undo-bar-message-signature = Firma rimossa
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } annotazione rimossa
*[other] { $count } annotazioni rimosse
}
pdfjs-editor-undo-bar-undo-button =
.title = Annulla
pdfjs-editor-undo-bar-undo-button-label = Annulla
pdfjs-editor-undo-bar-close-button =
.title = Chiudi
pdfjs-editor-undo-bar-close-button-label = Chiudi
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = Questa finestra consente all’utente di creare una firma da aggiungere a un documento PDF. L’utente può modificare il nome (che verrà utilizzato anche come testo alternativo) e, se lo desidera, salvare la firma per riutilizzarla in futuro.
pdfjs-editor-add-signature-dialog-title = Aggiungi una firma
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Scrivi
.title = Scrivi
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Disegna
.title = Disegna
pdfjs-editor-add-signature-image-button = Immagine
.title = Immagine
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Digita la tua firma
.placeholder = Digita la tua firma
pdfjs-editor-add-signature-draw-placeholder = Disegna la tua firma
pdfjs-editor-add-signature-draw-thickness-range-label = Spessore
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Spessore del tratto: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Trascina un file qui per caricarlo
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Oppure scegli un file immagine
*[other] Oppure sfoglia i file immagine
}
## Controls
pdfjs-editor-add-signature-description-label = Descrizione (testo alternativo)
pdfjs-editor-add-signature-description-input =
.title = Descrizione (testo alternativo)
pdfjs-editor-add-signature-description-default-when-drawing = Firma
pdfjs-editor-add-signature-clear-button-label = Cancella firma
pdfjs-editor-add-signature-clear-button =
.title = Cancella firma
pdfjs-editor-add-signature-save-checkbox = Salva firma
pdfjs-editor-add-signature-save-warning-message = Hai raggiunto il limite di 5 firme salvate. Rimuovine una per salvarne altre.
pdfjs-editor-add-signature-image-upload-error-title = Impossibile caricare l’immagine
pdfjs-editor-add-signature-image-upload-error-description = Controlla la connessione di rete o prova con un’altra immagine.
pdfjs-editor-add-signature-error-close-button = Chiudi
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Annulla
pdfjs-editor-add-signature-add-button = Aggiungi
pdfjs-editor-edit-signature-update-button = Aggiorna
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Rimuovi firma
pdfjs-editor-delete-signature-button-label = Rimuovi firma
pdfjs-editor-delete-signature-button1 =
.title = Rimuovi firma salvata
pdfjs-editor-delete-signature-button-label1 = Rimuovi firma salvata
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Modifica descrizione
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Modifica descrizione
================================================
FILE: cookbook/static/pdfjs/web/locale/ja/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = 前のページへ戻ります
pdfjs-previous-button-label = 前へ
pdfjs-next-button =
.title = 次のページへ進みます
pdfjs-next-button-label = 次へ
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = ページ
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = / { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } / { $pagesCount })
pdfjs-zoom-out-button =
.title = 表示を縮小します
pdfjs-zoom-out-button-label = 縮小
pdfjs-zoom-in-button =
.title = 表示を拡大します
pdfjs-zoom-in-button-label = 拡大
pdfjs-zoom-select =
.title = 拡大/縮小
pdfjs-presentation-mode-button =
.title = プレゼンテーションモードに切り替えます
pdfjs-presentation-mode-button-label = プレゼンテーションモード
pdfjs-open-file-button =
.title = ファイルを開きます
pdfjs-open-file-button-label = 開く
pdfjs-print-button =
.title = 印刷します
pdfjs-print-button-label = 印刷
pdfjs-save-button =
.title = 保存します
pdfjs-save-button-label = 保存
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = ダウンロードします
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = ダウンロード
pdfjs-bookmark-button =
.title = 現在のページの URL です (現在のページを表示する URL)
pdfjs-bookmark-button-label = 現在のページ
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = ツール
pdfjs-tools-button-label = ツール
pdfjs-first-page-button =
.title = 最初のページへ移動します
pdfjs-first-page-button-label = 最初のページへ移動
pdfjs-last-page-button =
.title = 最後のページへ移動します
pdfjs-last-page-button-label = 最後のページへ移動
pdfjs-page-rotate-cw-button =
.title = ページを右へ回転します
pdfjs-page-rotate-cw-button-label = 右回転
pdfjs-page-rotate-ccw-button =
.title = ページを左へ回転します
pdfjs-page-rotate-ccw-button-label = 左回転
pdfjs-cursor-text-select-tool-button =
.title = テキスト選択ツールを有効にします
pdfjs-cursor-text-select-tool-button-label = テキスト選択ツール
pdfjs-cursor-hand-tool-button =
.title = 手のひらツールを有効にします
pdfjs-cursor-hand-tool-button-label = 手のひらツール
pdfjs-scroll-page-button =
.title = ページ単位でスクロールします
pdfjs-scroll-page-button-label = ページ単位でスクロール
pdfjs-scroll-vertical-button =
.title = 縦スクロールにします
pdfjs-scroll-vertical-button-label = 縦スクロール
pdfjs-scroll-horizontal-button =
.title = 横スクロールにします
pdfjs-scroll-horizontal-button-label = 横スクロール
pdfjs-scroll-wrapped-button =
.title = 折り返しスクロールにします
pdfjs-scroll-wrapped-button-label = 折り返しスクロール
pdfjs-spread-none-button =
.title = 見開きにしません
pdfjs-spread-none-button-label = 見開きにしない
pdfjs-spread-odd-button =
.title = 奇数ページ開始で見開きにします
pdfjs-spread-odd-button-label = 奇数ページ見開き
pdfjs-spread-even-button =
.title = 偶数ページ開始で見開きにします
pdfjs-spread-even-button-label = 偶数ページ見開き
## Document properties dialog
pdfjs-document-properties-button =
.title = 文書のプロパティ...
pdfjs-document-properties-button-label = 文書のプロパティ...
pdfjs-document-properties-file-name = ファイル名:
pdfjs-document-properties-file-size = ファイルサイズ:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } バイト)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } バイト)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } バイト)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } バイト)
pdfjs-document-properties-title = タイトル:
pdfjs-document-properties-author = 作成者:
pdfjs-document-properties-subject = 件名:
pdfjs-document-properties-keywords = キーワード:
pdfjs-document-properties-creation-date = 作成日:
pdfjs-document-properties-modification-date = 更新日:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = アプリケーション:
pdfjs-document-properties-producer = PDF 作成:
pdfjs-document-properties-version = PDF のバージョン:
pdfjs-document-properties-page-count = ページ数:
pdfjs-document-properties-page-size = ページサイズ:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = 縦
pdfjs-document-properties-page-size-orientation-landscape = 横
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = レター
pdfjs-document-properties-page-size-name-legal = リーガル
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = ウェブ表示用に最適化:
pdfjs-document-properties-linearized-yes = はい
pdfjs-document-properties-linearized-no = いいえ
pdfjs-document-properties-close-button = 閉じる
## Print
pdfjs-print-progress-message = 文書の印刷を準備しています...
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = キャンセル
pdfjs-printing-not-supported = 警告: このブラウザーでは印刷が完全にサポートされていません。
pdfjs-printing-not-ready = 警告: PDF を印刷するための読み込みが終了していません。
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = サイドバー表示を切り替えます
pdfjs-toggle-sidebar-notification-button =
.title = サイドバー表示を切り替えます (文書に含まれるアウトライン / 添付 / レイヤー)
pdfjs-toggle-sidebar-button-label = サイドバーの切り替え
pdfjs-document-outline-button =
.title = 文書の目次を表示します (ダブルクリックで項目を開閉します)
pdfjs-document-outline-button-label = 文書の目次
pdfjs-attachments-button =
.title = 添付ファイルを表示します
pdfjs-attachments-button-label = 添付ファイル
pdfjs-layers-button =
.title = レイヤーを表示します (ダブルクリックですべてのレイヤーが初期状態に戻ります)
pdfjs-layers-button-label = レイヤー
pdfjs-thumbs-button =
.title = 縮小版を表示します
pdfjs-thumbs-button-label = 縮小版
pdfjs-current-outline-item-button =
.title = 現在のアウトライン項目を検索
pdfjs-current-outline-item-button-label = 現在のアウトライン項目
pdfjs-findbar-button =
.title = 文書内を検索します
pdfjs-findbar-button-label = 検索
pdfjs-additional-layers = 追加レイヤー
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = { $page } ページ
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = { $page } ページの縮小版
## Find panel button title and messages
pdfjs-find-input =
.title = 検索
.placeholder = 文書内を検索...
pdfjs-find-previous-button =
.title = 現在より前の位置で指定文字列が現れる部分を検索します
pdfjs-find-previous-button-label = 前へ
pdfjs-find-next-button =
.title = 現在より後の位置で指定文字列が現れる部分を検索します
pdfjs-find-next-button-label = 次へ
pdfjs-find-highlight-checkbox = すべて強調表示
pdfjs-find-match-case-checkbox-label = 大文字/小文字を区別
pdfjs-find-match-diacritics-checkbox-label = 発音区別符号を区別
pdfjs-find-entire-word-checkbox-label = 単語一致
pdfjs-find-reached-top = 文書先頭に到達したので末尾から続けて検索します
pdfjs-find-reached-bottom = 文書末尾に到達したので先頭から続けて検索します
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count = { $total } 件中 { $current } 件目
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit = { $limit } 件以上一致
pdfjs-find-not-found = 見つかりませんでした
## Predefined zoom values
pdfjs-page-scale-width = 幅に合わせる
pdfjs-page-scale-fit = ページのサイズに合わせる
pdfjs-page-scale-auto = 自動ズーム
pdfjs-page-scale-actual = 実際のサイズ
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = { $page } ページ
## Loading indicator messages
pdfjs-loading-error = PDF の読み込み中にエラーが発生しました。
pdfjs-invalid-file-error = 無効または破損した PDF ファイル。
pdfjs-missing-file-error = PDF ファイルが見つかりません。
pdfjs-unexpected-response-error = サーバーから予期せぬ応答がありました。
pdfjs-rendering-error = ページのレンダリング中にエラーが発生しました。
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } 注釈]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = この PDF ファイルを開くためのパスワードを入力してください。
pdfjs-password-invalid = パスワードが正しくありません。もう一度試してください。
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = キャンセル
pdfjs-web-fonts-disabled = ウェブフォントが無効になっています: 埋め込まれた PDF のフォントを使用できません。
## Editing
pdfjs-editor-free-text-button =
.title = フリーテキスト注釈を追加します
pdfjs-editor-free-text-button-label = フリーテキスト注釈
pdfjs-editor-ink-button =
.title = インク注釈を追加します
pdfjs-editor-ink-button-label = インク注釈
pdfjs-editor-stamp-button =
.title = 画像を追加または編集します
pdfjs-editor-stamp-button-label = 画像を追加または編集
pdfjs-editor-highlight-button =
.title = 強調します
pdfjs-editor-highlight-button-label = 強調
pdfjs-highlight-floating-button1 =
.title = 強調
.aria-label = 強調します
pdfjs-highlight-floating-button-label = 強調
pdfjs-editor-signature-button =
.title = 署名を追加します
pdfjs-editor-signature-button-label = 署名を追加
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = インク注釈を削除します
pdfjs-editor-remove-freetext-button =
.title = テキストを削除します
pdfjs-editor-remove-stamp-button =
.title = 画像を削除します
pdfjs-editor-remove-highlight-button =
.title = 強調を削除します
pdfjs-editor-remove-signature-button =
.title = 署名を削除します
##
# Editor Parameters
pdfjs-editor-free-text-color-input = 色
pdfjs-editor-free-text-size-input = サイズ
pdfjs-editor-ink-color-input = 色
pdfjs-editor-ink-thickness-input = 太さ
pdfjs-editor-ink-opacity-input = 不透明度
pdfjs-editor-stamp-add-image-button =
.title = 画像を追加します
pdfjs-editor-stamp-add-image-button-label = 画像を追加
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = 太さ
pdfjs-editor-free-highlight-thickness-title =
.title = テキスト以外のアイテムを強調する時の太さを変更します
pdfjs-editor-signature-add-signature-button =
.title = 新しい署名を追加します
pdfjs-editor-signature-add-signature-button-label = 新しい署名を追加
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = フリーテキスト注釈エディター
.default-content = テキストを入力してください...
pdfjs-free-text =
.aria-label = フリーテキスト注釈エディター
pdfjs-free-text-default-content = テキストを入力してください...
pdfjs-ink =
.aria-label = インク注釈エディター
pdfjs-ink-canvas =
.aria-label = ユーザー作成画像
## Alt-text dialog
pdfjs-editor-alt-text-button-label = 代替テキスト
pdfjs-editor-alt-text-edit-button =
.aria-label = 代替テキストを編集
pdfjs-editor-alt-text-edit-button-label = 代替テキストを編集
pdfjs-editor-alt-text-dialog-label = オプションの選択
pdfjs-editor-alt-text-dialog-description = 代替テキストは画像が表示されない場合や読み込まれない場合にユーザーの助けになります。
pdfjs-editor-alt-text-add-description-label = 説明を追加
pdfjs-editor-alt-text-add-description-description = 対象や設定、動作を説明する短い文章を記入してください。
pdfjs-editor-alt-text-mark-decorative-label = 装飾マークを付ける
pdfjs-editor-alt-text-mark-decorative-description = これは区切り線やウォーターマークなどの装飾画像に使用されます。
pdfjs-editor-alt-text-cancel-button = キャンセル
pdfjs-editor-alt-text-save-button = 保存
pdfjs-editor-alt-text-decorative-tooltip = 装飾マークが付いています
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = 例:「若い人がテーブルの席について食事をしています」
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = 代替テキスト
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = 左上隅 — サイズ変更
pdfjs-editor-resizer-label-top-middle = 上中央 — サイズ変更
pdfjs-editor-resizer-label-top-right = 右上隅 — サイズ変更
pdfjs-editor-resizer-label-middle-right = 右中央 — サイズ変更
pdfjs-editor-resizer-label-bottom-right = 右下隅 — サイズ変更
pdfjs-editor-resizer-label-bottom-middle = 下中央 — サイズ変更
pdfjs-editor-resizer-label-bottom-left = 左下隅 — サイズ変更
pdfjs-editor-resizer-label-middle-left = 左中央 — サイズ変更
pdfjs-editor-resizer-top-left =
.aria-label = 左上隅 — サイズ変更
pdfjs-editor-resizer-top-middle =
.aria-label = 上中央 — サイズ変更
pdfjs-editor-resizer-top-right =
.aria-label = 右上隅 — サイズ変更
pdfjs-editor-resizer-middle-right =
.aria-label = 右中央 — サイズ変更
pdfjs-editor-resizer-bottom-right =
.aria-label = 右下隅 — サイズ変更
pdfjs-editor-resizer-bottom-middle =
.aria-label = 下中央 — サイズ変更
pdfjs-editor-resizer-bottom-left =
.aria-label = 左下隅 — サイズ変更
pdfjs-editor-resizer-middle-left =
.aria-label = 左中央 — サイズ変更
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = 強調色
pdfjs-editor-colorpicker-button =
.title = 色を変更します
pdfjs-editor-colorpicker-dropdown =
.aria-label = 色の選択
pdfjs-editor-colorpicker-yellow =
.title = 黄色
pdfjs-editor-colorpicker-green =
.title = 緑色
pdfjs-editor-colorpicker-blue =
.title = 青色
pdfjs-editor-colorpicker-pink =
.title = ピンク色
pdfjs-editor-colorpicker-red =
.title = 赤色
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = すべて表示
# (^m^) en-US: .title = Show all
pdfjs-editor-highlight-show-all-button =
.title = 強調の表示を切り替えます
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = 代替テキストを編集 (画像の説明)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = 代替テキストを追加 (画像の説明)
pdfjs-editor-new-alt-text-textarea =
.placeholder = ここに説明を記入してください...
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = 画像が読み込まれない場合や見えない人のための短い説明です。
pdfjs-editor-new-alt-text-disclaimer1 = この代替テキストは自動的に生成されたため正確でない可能性があります。
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = 詳細情報
pdfjs-editor-new-alt-text-create-automatically-button-label = 代替テキストを自動生成
pdfjs-editor-new-alt-text-not-now-button = 後で
pdfjs-editor-new-alt-text-error-title = 代替テキストを自動生成できませんでした
pdfjs-editor-new-alt-text-error-description = ご自分で代替テキストを書くか後でもう一度試してください。
pdfjs-editor-new-alt-text-error-close-button = 閉じる
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = 代替テキスト AI モデルをダウンロードしています ({ $downloadedSize } / { $totalSize } MB)
.aria-valuetext = 代替テキスト AI モデルをダウンロードしています ({ $downloadedSize } / { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = 代替テキストを追加しました
pdfjs-editor-new-alt-text-added-button-label = 代替テキストを追加しました
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = 代替テキストがありません
pdfjs-editor-new-alt-text-missing-button-label = 代替テキストがありません
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = 代替テキストをレビュー
pdfjs-editor-new-alt-text-to-review-button-label = 代替テキストをレビュー
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = 自動生成されました: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = 画像の代替テキスト設定
pdfjs-image-alt-text-settings-button-label = 画像の代替テキスト設定
pdfjs-editor-alt-text-settings-dialog-label = 画像の代替テキスト設定
pdfjs-editor-alt-text-settings-automatic-title = 自動代替テキスト
pdfjs-editor-alt-text-settings-create-model-button-label = 代替テキストを自動生成
pdfjs-editor-alt-text-settings-create-model-description = 画像が読み込まれない場合や見えない人のために説明を提案します。
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = 代替テキスト AI モデル ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = ローカルの端末上で実行されるためデータは非公開になります。代替テキストの自動生成に必要です。
pdfjs-editor-alt-text-settings-delete-model-button = 削除
pdfjs-editor-alt-text-settings-download-model-button = ダウンロード
pdfjs-editor-alt-text-settings-downloading-model-button = ダウンロード中...
pdfjs-editor-alt-text-settings-editor-title = 代替テキストエディター
pdfjs-editor-alt-text-settings-show-dialog-button-label = 画像の追加時に代替テキストエディターを表示する
pdfjs-editor-alt-text-settings-show-dialog-description = すべての画像に代替テキストを追加する助けになります。
pdfjs-editor-alt-text-settings-close-button = 閉じる
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = 強調表示が削除されました
pdfjs-editor-undo-bar-message-freetext = フリーテキスト注釈が削除されました
pdfjs-editor-undo-bar-message-ink = インク注釈が削除されました
pdfjs-editor-undo-bar-message-stamp = 画像が削除されました
pdfjs-editor-undo-bar-message-signature = 署名が削除されました
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple = { $count } 個の注釈が削除されました
pdfjs-editor-undo-bar-undo-button =
.title = 元に戻す
pdfjs-editor-undo-bar-undo-button-label = 元に戻す
pdfjs-editor-undo-bar-close-button =
.title = 閉じる
pdfjs-editor-undo-bar-close-button-label = 閉じる
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = このダイアログではユーザーが署名を作成して PDF 文書に追加できます。
pdfjs-editor-add-signature-dialog-title = 署名を追加
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = タイプ
.title = キーボード入力します
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = 手書き
.title = 手書き入力します
pdfjs-editor-add-signature-image-button = 画像
.title = 画像を指定します
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = 署名をキーボード入力
.placeholder = 署名をキーボード入力
pdfjs-editor-add-signature-draw-placeholder = 署名を手書き入力
pdfjs-editor-add-signature-draw-thickness-range-label = 線の太さ
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = 線の太さ: { $thickness }
pdfjs-editor-add-signature-image-placeholder = ファイルをここにドラッグしてアップロード
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] または画像ファイルを選択
*[other] または画像ファイルを参照
}
## Controls
pdfjs-editor-add-signature-description-label = 説明 (代替テキスト)
pdfjs-editor-add-signature-description-input =
.title = 説明 (代替テキスト) を追加します
pdfjs-editor-add-signature-description-default-when-drawing = 署名
pdfjs-editor-add-signature-clear-button-label = 署名を消去
pdfjs-editor-add-signature-clear-button =
.title = 署名を消去します
pdfjs-editor-add-signature-save-checkbox = 署名を保存
pdfjs-editor-add-signature-save-warning-message = 保存された署名が上限の 5 個に達しました。さらに保存するにはいずれかを削除してください。
pdfjs-editor-add-signature-image-upload-error-title = 画像をアップロードできません
pdfjs-editor-add-signature-image-upload-error-description = ネットワーク接続を確認するか別の画像を試してください。
pdfjs-editor-add-signature-error-close-button = 閉じる
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = キャンセル
pdfjs-editor-add-signature-add-button = 追加
pdfjs-editor-edit-signature-update-button = 更新
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = 署名を削除します
pdfjs-editor-delete-signature-button-label = 署名を削除
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = 説明を編集
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = 説明の編集
================================================
FILE: cookbook/static/pdfjs/web/locale/ka/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = წინა გვერდი
pdfjs-previous-button-label = წინა
pdfjs-next-button =
.title = შემდეგი გვერდი
pdfjs-next-button-label = შემდეგი
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = გვერდი
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = { $pagesCount }-დან
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } { $pagesCount }-დან)
pdfjs-zoom-out-button =
.title = ზომის შემცირება
pdfjs-zoom-out-button-label = დაშორება
pdfjs-zoom-in-button =
.title = ზომის გაზრდა
pdfjs-zoom-in-button-label = მოახლოება
pdfjs-zoom-select =
.title = ზომა
pdfjs-presentation-mode-button =
.title = წარდგენის რეჟიმზე გადართვა
pdfjs-presentation-mode-button-label = წარდგენის რეჟიმი
pdfjs-open-file-button =
.title = ფაილის გახსნა
pdfjs-open-file-button-label = გახსნა
pdfjs-print-button =
.title = ამობეჭდვა
pdfjs-print-button-label = ამობეჭდვა
pdfjs-save-button =
.title = შენახვა
pdfjs-save-button-label = შენახვა
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = ჩამოტვირთვა
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = ჩამოტვირთვა
pdfjs-bookmark-button =
.title = მიმდინარე გვერდი (ბმული ამ გვერდისთვის)
pdfjs-bookmark-button-label = მიმდინარე გვერდი
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = ხელსაწყოები
pdfjs-tools-button-label = ხელსაწყოები
pdfjs-first-page-button =
.title = პირველ გვერდზე გადასვლა
pdfjs-first-page-button-label = პირველ გვერდზე გადასვლა
pdfjs-last-page-button =
.title = ბოლო გვერდზე გადასვლა
pdfjs-last-page-button-label = ბოლო გვერდზე გადასვლა
pdfjs-page-rotate-cw-button =
.title = საათის ისრის მიმართულებით შებრუნება
pdfjs-page-rotate-cw-button-label = მარჯვნივ გადაბრუნება
pdfjs-page-rotate-ccw-button =
.title = საათის ისრის საპირისპიროდ შებრუნება
pdfjs-page-rotate-ccw-button-label = მარცხნივ გადაბრუნება
pdfjs-cursor-text-select-tool-button =
.title = მოსანიშნი მაჩვენებლის გამოყენება
pdfjs-cursor-text-select-tool-button-label = მოსანიშნი მაჩვენებელი
pdfjs-cursor-hand-tool-button =
.title = გადასაადგილებელი მაჩვენებლის გამოყენება
pdfjs-cursor-hand-tool-button-label = გადასაადგილებელი
pdfjs-scroll-page-button =
.title = გვერდზე გადაადგილების გამოყენება
pdfjs-scroll-page-button-label = გვერდშივე გადაადგილება
pdfjs-scroll-vertical-button =
.title = გვერდების შვეულად ჩვენება
pdfjs-scroll-vertical-button-label = შვეული გადაადგილება
pdfjs-scroll-horizontal-button =
.title = გვერდების თარაზულად ჩვენება
pdfjs-scroll-horizontal-button-label = განივი გადაადგილება
pdfjs-scroll-wrapped-button =
.title = გვერდების ცხრილურად ჩვენება
pdfjs-scroll-wrapped-button-label = ცხრილური გადაადგილება
pdfjs-spread-none-button =
.title = ორ გვერდზე გაშლის გარეშე
pdfjs-spread-none-button-label = ცალგვერდიანი ჩვენება
pdfjs-spread-odd-button =
.title = ორ გვერდზე გაშლა კენტი გვერდიდან
pdfjs-spread-odd-button-label = ორ გვერდზე კენტიდან
pdfjs-spread-even-button =
.title = ორ გვერდზე გაშლა ლუწი გვერდიდან
pdfjs-spread-even-button-label = ორ გვერდზე ლუწიდან
## Document properties dialog
pdfjs-document-properties-button =
.title = დოკუმენტის შესახებ…
pdfjs-document-properties-button-label = დოკუმენტის შესახებ…
pdfjs-document-properties-file-name = ფაილის სახელი:
pdfjs-document-properties-file-size = ფაილის მოცულობა:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } კბაიტი ({ $b } ბაიტი)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } მბაიტი ({ $b } ბაიტი)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } კბ ({ $size_b } ბაიტი)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } მბ ({ $size_b } ბაიტი)
pdfjs-document-properties-title = სათაური:
pdfjs-document-properties-author = შემქმნელი:
pdfjs-document-properties-subject = თემა:
pdfjs-document-properties-keywords = საკვანძო სიტყვები:
pdfjs-document-properties-creation-date = შექმნის დრო:
pdfjs-document-properties-modification-date = ჩასწორების დრო:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = შემდგენელი:
pdfjs-document-properties-producer = PDF-შემდგენელი:
pdfjs-document-properties-version = PDF-ვერსია:
pdfjs-document-properties-page-count = გვერდები:
pdfjs-document-properties-page-size = გვერდის ზომა:
pdfjs-document-properties-page-size-unit-inches = დუიმი
pdfjs-document-properties-page-size-unit-millimeters = მმ
pdfjs-document-properties-page-size-orientation-portrait = შვეულად
pdfjs-document-properties-page-size-orientation-landscape = თარაზულად
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letter
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = მსუბუქი ვებჩვენება:
pdfjs-document-properties-linearized-yes = დიახ
pdfjs-document-properties-linearized-no = არა
pdfjs-document-properties-close-button = დახურვა
## Print
pdfjs-print-progress-message = დოკუმენტი მზადდება ამოსაბეჭდად…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = გაუქმება
pdfjs-printing-not-supported = გაფრთხილება: ამობეჭდვა ამ ბრაუზერში არაა სრულად მხარდაჭერილი.
pdfjs-printing-not-ready = გაფრთხილება: PDF სრულად ჩატვირთული არაა, ამობეჭდვის დასაწყებად.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = გვერდითა ზოლის გამოჩენა/დამალვა
pdfjs-toggle-sidebar-notification-button =
.title = გვერდითი ზოლის გამოჩენა (შეიცავს სარჩევს/დანართს/შრეებს)
pdfjs-toggle-sidebar-button-label = გვერდითა ზოლის გამოჩენა/დამალვა
pdfjs-document-outline-button =
.title = დოკუმენტის სარჩევის ჩვენება (ორმაგი წკაპით თითოეულის ჩამოშლა/აკეცვა)
pdfjs-document-outline-button-label = დოკუმენტის სარჩევი
pdfjs-attachments-button =
.title = დანართების ჩვენება
pdfjs-attachments-button-label = დანართები
pdfjs-layers-button =
.title = შრეების გამოჩენა (ორმაგი წკაპით ყველა შრის ნაგულისხმევზე დაბრუნება)
pdfjs-layers-button-label = შრეები
pdfjs-thumbs-button =
.title = შეთვალიერება
pdfjs-thumbs-button-label = ესკიზები
pdfjs-current-outline-item-button =
.title = მიმდინარე გვერდის მონახვა სარჩევში
pdfjs-current-outline-item-button-label = მიმდინარე გვერდი სარჩევში
pdfjs-findbar-button =
.title = პოვნა დოკუმენტში
pdfjs-findbar-button-label = ძიება
pdfjs-additional-layers = დამატებითი შრეები
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = გვერდი { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = გვერდის შეთვალიერება { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = ძიება
.placeholder = პოვნა დოკუმენტში…
pdfjs-find-previous-button =
.title = წინა დამთხვევის პოვნა
pdfjs-find-previous-button-label = წინა
pdfjs-find-next-button =
.title = მომდევნო დამთხვევის პოვნა
pdfjs-find-next-button-label = შემდეგი
pdfjs-find-highlight-checkbox = ყველაფრის მონიშვნა
pdfjs-find-match-case-checkbox-label = მთავრულით
pdfjs-find-match-diacritics-checkbox-label = ნიშნებით
pdfjs-find-entire-word-checkbox-label = მთლიანი სიტყვები
pdfjs-find-reached-top = მიღწეულია დოკუმენტის დასაწყისი, გრძელდება ბოლოდან
pdfjs-find-reached-bottom = მიღწეულია დოკუმენტის ბოლო, გრძელდება დასაწყისიდან
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] თანხვედრა { $current }, სულ { $total }
*[other] თანხვედრა { $current }, სულ { $total }
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] არანაკლებ { $limit } თანხვედრა
*[other] არანაკლებ { $limit } თანხვედრა
}
pdfjs-find-not-found = ფრაზა ვერ მოიძებნა
## Predefined zoom values
pdfjs-page-scale-width = გვერდის სიგანეზე
pdfjs-page-scale-fit = მთლიანი გვერდი
pdfjs-page-scale-auto = ავტომატური
pdfjs-page-scale-actual = საწყისი ზომა
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = გვერდი { $page }
## Loading indicator messages
pdfjs-loading-error = შეცდომა, PDF-ფაილის ჩატვირთვისას.
pdfjs-invalid-file-error = არამართებული ან დაზიანებული PDF-ფაილი.
pdfjs-missing-file-error = ნაკლული PDF-ფაილი.
pdfjs-unexpected-response-error = სერვერის მოულოდნელი პასუხი.
pdfjs-rendering-error = შეცდომა, გვერდის ჩვენებისას.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } შენიშვნა]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = შეიყვანეთ პაროლი PDF-ფაილის გასახსნელად.
pdfjs-password-invalid = არასწორი პაროლი. გთხოვთ, სცადოთ ხელახლა.
pdfjs-password-ok-button = კარგი
pdfjs-password-cancel-button = გაუქმება
pdfjs-web-fonts-disabled = ვებშრიფტები გამორთულია: ჩაშენებული PDF-შრიფტების გამოყენება ვერ ხერხდება.
## Editing
pdfjs-editor-free-text-button =
.title = წარწერა
pdfjs-editor-free-text-button-label = წარწერა
pdfjs-editor-ink-button =
.title = ხაზვა
pdfjs-editor-ink-button-label = ხაზვა
pdfjs-editor-stamp-button =
.title = სურათების დართვა ან ჩასწორება
pdfjs-editor-stamp-button-label = სურათების დართვა ან ჩასწორება
pdfjs-editor-highlight-button =
.title = მონიშვნა
pdfjs-editor-highlight-button-label = მონიშვნა
pdfjs-highlight-floating-button1 =
.title = მონიშვნა
.aria-label = მონიშვნა
pdfjs-highlight-floating-button-label = მონიშვნა
pdfjs-editor-signature-button =
.title = ხელმოწერის დამატება
pdfjs-editor-signature-button-label = ხელმოწერის დამატება
## Default editor aria labels
# “Highlight” is a noun, the string is used on the editor for highlights.
pdfjs-editor-highlight-editor =
.aria-label = მონიშვნის ჩასწორება
# “Drawing” is a noun, the string is used on the editor for drawings.
pdfjs-editor-ink-editor =
.aria-label = ნახაზის ჩასწორება
pdfjs-editor-signature-editor =
.aria-label = ხელმოწერის ჩასწორება
pdfjs-editor-stamp-editor =
.aria-label = სურათის ჩასწორება
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = დახაზულის მოცილება
pdfjs-editor-remove-freetext-button =
.title = წარწერის მოცილება
pdfjs-editor-remove-stamp-button =
.title = სურათის მოცილება
pdfjs-editor-remove-highlight-button =
.title = მონიშვნის მოცილება
pdfjs-editor-remove-signature-button =
.title = ხელმოწერის მოცილება
##
# Editor Parameters
pdfjs-editor-free-text-color-input = ფერი
pdfjs-editor-free-text-size-input = ზომა
pdfjs-editor-ink-color-input = ფერი
pdfjs-editor-ink-thickness-input = სისქე
pdfjs-editor-ink-opacity-input = გაუმჭვირვალობა
pdfjs-editor-stamp-add-image-button =
.title = სურათის დამატება
pdfjs-editor-stamp-add-image-button-label = სურათის დამატება
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = სისქე
pdfjs-editor-free-highlight-thickness-title =
.title = სისქის შეცვლა წარწერის გარდა სხვა ნაწილების მონიშვნისას
pdfjs-editor-add-signature-container =
.aria-label = ხელმოწერის მართვა და შენახული ხელმოწერები
pdfjs-editor-signature-add-signature-button =
.title = ახალი ხელმოწერის დამატება
pdfjs-editor-signature-add-signature-button-label = ახალი ხელმოწერის დამატება
# Used on the button to use an already saved signature.
# Variables:
# $description (String) - a string describing/labeling the signature.
pdfjs-editor-add-saved-signature-button =
.title = შენახული ხელმოწერა: { $description }
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = ნაწერის ჩასწორება
.default-content = დაიწყეთ აკრეფა…
pdfjs-free-text =
.aria-label = ნაწერის ჩასწორება
pdfjs-free-text-default-content = აკრიფეთ…
pdfjs-ink =
.aria-label = დახაზულის შესწორება
pdfjs-ink-canvas =
.aria-label = მომხმარებლის შექმნილი სურათი
## Alt-text dialog
pdfjs-editor-alt-text-button-label = თანდართული წარწერა
pdfjs-editor-alt-text-edit-button =
.aria-label = დართული წარწერის ჩასწორება
pdfjs-editor-alt-text-edit-button-label = თანდართული წარწერის ჩასწორება
pdfjs-editor-alt-text-dialog-label = არჩევა
pdfjs-editor-alt-text-dialog-description = თანდართული (შემნაცვლებელი) წარწერა გამოსადეგია მათთვის, ვინც ვერ ხედავს სურათებს ან გამოისახება მაშინ, როცა სურათი ვერ ჩაიტვირთება.
pdfjs-editor-alt-text-add-description-label = აღწერილობის მითითება
pdfjs-editor-alt-text-add-description-description = განკუთვნილია 1-2 წინადადებით საგნის, მახასიათებლის ან მოქმედების აღსაწერად.
pdfjs-editor-alt-text-mark-decorative-label = მოინიშნოს მორთულობად
pdfjs-editor-alt-text-mark-decorative-description = განკუთვნილია შესამკობი სურათებისთვის, გარსშემოსავლები ჩარჩოებისა და ჭვირნიშნებისთვის.
pdfjs-editor-alt-text-cancel-button = გაუქმება
pdfjs-editor-alt-text-save-button = შენახვა
pdfjs-editor-alt-text-decorative-tooltip = მოინიშნოს მორთულობად
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = მაგალითად, „ახალგაზრდა მამაკაცი მაგიდასთან ზის და სადილობს“
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = დართული წარწერა
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = ზევით მარცხნივ — ზომაცვლა
pdfjs-editor-resizer-label-top-middle = ზევით შუაში — ზომაცვლა
pdfjs-editor-resizer-label-top-right = ზევით მარჯვნივ — ზომაცვლა
pdfjs-editor-resizer-label-middle-right = შუაში მარჯვნივ — ზომაცვლა
pdfjs-editor-resizer-label-bottom-right = ქვევით მარჯვნივ — ზომაცვლა
pdfjs-editor-resizer-label-bottom-middle = ქვევით შუაში — ზომაცვლა
pdfjs-editor-resizer-label-bottom-left = ზვევით მარცხნივ — ზომაცვლა
pdfjs-editor-resizer-label-middle-left = შუაში მარცხნივ — ზომაცვლა
pdfjs-editor-resizer-top-left =
.aria-label = ზევით მარცხნივ — ზომაცვლა
pdfjs-editor-resizer-top-middle =
.aria-label = ზევით შუაში — ზომაცვლა
pdfjs-editor-resizer-top-right =
.aria-label = ზევით მარჯვნივ — ზომაცვლა
pdfjs-editor-resizer-middle-right =
.aria-label = შუაში მარჯვნივ — ზომაცვლა
pdfjs-editor-resizer-bottom-right =
.aria-label = ქვევით მარჯვნივ — ზომაცვლა
pdfjs-editor-resizer-bottom-middle =
.aria-label = ქვევით შუაში — ზომაცვლა
pdfjs-editor-resizer-bottom-left =
.aria-label = ზვევით მარცხნივ — ზომაცვლა
pdfjs-editor-resizer-middle-left =
.aria-label = შუაში მარცხნივ — ზომაცვლა
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = მოსანიშნი ფერი
pdfjs-editor-colorpicker-button =
.title = ფერის შეცვლა
pdfjs-editor-colorpicker-dropdown =
.aria-label = ფერის არჩევა
pdfjs-editor-colorpicker-yellow =
.title = ყვითელი
pdfjs-editor-colorpicker-green =
.title = მწვანე
pdfjs-editor-colorpicker-blue =
.title = ლურჯი
pdfjs-editor-colorpicker-pink =
.title = ვარდისფერი
pdfjs-editor-colorpicker-red =
.title = წითელი
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = ყველას ჩვენება
pdfjs-editor-highlight-show-all-button =
.title = ყველას ჩვენება
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = დართული წარწერის ჩასწორება (სურათის აღწერის)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = დართული წარწერის დამატება (სურათის აღწერის)
pdfjs-editor-new-alt-text-textarea =
.placeholder = დაწერეთ თქვენი აღწერა აქ…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = მოკლე აღწერა მათთვის, ვინც ვერ ხედავს სურათს ან ვისთანაც ვერ ჩაიტვირთება სურათი.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = ეს დართული წარწერა ავტომატურადაა შედგენილი და შესაძლოა, უმართებულო იყოს.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = ვრცლად
pdfjs-editor-new-alt-text-create-automatically-button-label = დართული წარწერის ავტომატური შედგენა
pdfjs-editor-new-alt-text-not-now-button = ახლა არა
pdfjs-editor-new-alt-text-error-title = დართული წარწერის შედგენა ვერ მოხერხდა
pdfjs-editor-new-alt-text-error-description = გთხოვთ დაწეროთ საკუთარი დანართი და კვლავ სცადოთ მოგვიანებით.
pdfjs-editor-new-alt-text-error-close-button = დახურვა
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = ჩამოიტვირთება დართული წარწერის შესადეგი AI-მოდელი ({ $downloadedSize } ზომით { $totalSize } მბაიტი)
.aria-valuetext = ჩამოიტვირთება დართული წარწერის შესადეგი AI-მოდელი ({ $downloadedSize } ზომით { $totalSize } მბაიტი)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = დართული წარწერა დამატებულია
pdfjs-editor-new-alt-text-added-button-label = დართული წარწერა დამატებულია
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = აკლია დართული წარწერა
pdfjs-editor-new-alt-text-missing-button-label = აკლია დართული წარწერა
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = დართული წარწერის გადახედვა
pdfjs-editor-new-alt-text-to-review-button-label = დართული წარწერის გადახედვა
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = შედგენილია ავტომატურად: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = სურათის დართული წარწერის პარამეტრები
pdfjs-image-alt-text-settings-button-label = სურათის დართული წარწერის პარამეტრები
pdfjs-editor-alt-text-settings-dialog-label = სურათის დართული წარწერის პარამეტრები
pdfjs-editor-alt-text-settings-automatic-title = ავტომატურად დართული წარწერა
pdfjs-editor-alt-text-settings-create-model-button-label = დართული წარწერის ავტომატური შედგენა
pdfjs-editor-alt-text-settings-create-model-description = აღწერს სურათს მათთვის, ვინც ვერ ხედავს ან ვისთანაც ვერ ჩაიტვირთება.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = დართული წარწერის შესადგენი AI-მოდელი ({ $totalSize } მბაიტი)
pdfjs-editor-alt-text-settings-ai-model-description = ეშვება ადგილობრივად თქვენს მოწყობილობასა, ასე რომ მონაცემები დარჩება პირადი. საჭიროა წარწერის ავტომატურად დართვისთვის.
pdfjs-editor-alt-text-settings-delete-model-button = წაშლა
pdfjs-editor-alt-text-settings-download-model-button = ჩამოტვირთვა
pdfjs-editor-alt-text-settings-downloading-model-button = ჩამოიტვრითება...
pdfjs-editor-alt-text-settings-editor-title = დართული წარწერის ჩამსწორებელი
pdfjs-editor-alt-text-settings-show-dialog-button-label = გამოჩნდეს დართული წარწერის ჩამსწორებელი სურათის დამატებისთანავე
pdfjs-editor-alt-text-settings-show-dialog-description = უზრუნველყოფს, რომ თქვენს ყველა სურათს ახლდეს დართული წარწერა.
pdfjs-editor-alt-text-settings-close-button = დახურვა
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = მონიშვნა მოცილებულია
pdfjs-editor-undo-bar-message-freetext = წარწერა მოცილებულია
pdfjs-editor-undo-bar-message-ink = ნახატი მოცილებულია
pdfjs-editor-undo-bar-message-stamp = სურათი მოცილებულია
pdfjs-editor-undo-bar-message-signature = ხელმოწერა მოცილებულია
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } შენიშვნა მოცილებულია
*[other] { $count } შენიშვნა მოცილებულია
}
pdfjs-editor-undo-bar-undo-button =
.title = დაბრუნება
pdfjs-editor-undo-bar-undo-button-label = დაბრუნება
pdfjs-editor-undo-bar-close-button =
.title = დახურვა
pdfjs-editor-undo-bar-close-button-label = დახურვა
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = ეს არე საშუალებას აძლევს მომხმარებელს, შექმნას საკუთარი ხელმოწერა PDF-დოკუმენტისთვის. მომხმარებელს შეეძლება ჩაასწოროს სახელი (რომელიც დართული ტექსტის მოვალეობასაც ასრულებს) და სურვილისამებრ შეინახოს ხელმოწერა განმეორებით გამოსაყენებლად.
pdfjs-editor-add-signature-dialog-title = ხელმოწერის დამატება
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = აკრეფა
.title = აკრეფა
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = მოხაზვა
.title = მოხაზვა
pdfjs-editor-add-signature-image-button = სურათი
.title = სურათი
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = აკრიფეთ ხელმოწერა
.placeholder = აკრიფეთ ხელმოწერა
pdfjs-editor-add-signature-draw-placeholder = მოხაზეთ ხელმოწერა
pdfjs-editor-add-signature-draw-thickness-range-label = სისქე
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = მოხაზულის სისქე: { $thickness }
pdfjs-editor-add-signature-image-placeholder = ჩავლებით გადმოიტანეთ ასატვირთად
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] ან ამოარჩიეთ სურათებიდან
*[other] ან ამოარჩიეთ სურათებიდან
}
## Controls
pdfjs-editor-add-signature-description-label = აღწერილობა (დართული ტექსტი)
pdfjs-editor-add-signature-description-input =
.title = აღწერილობა (დართული ტექსტი)
pdfjs-editor-add-signature-description-default-when-drawing = ხელმოწერა
pdfjs-editor-add-signature-clear-button-label = ხელმოწერის წაშლა
pdfjs-editor-add-signature-clear-button =
.title = ხელმოწერის წაშლა
pdfjs-editor-add-signature-save-checkbox = ხელმოწერის შენახვა
pdfjs-editor-add-signature-save-warning-message = მიღწეულია 5 ხელმოწერის შენახვის ზღვარი. მოაცილეთ რომელიმე ახლის შესანახად.
pdfjs-editor-add-signature-image-upload-error-title = ვერ აიტვირთა სურათი
pdfjs-editor-add-signature-image-upload-error-description = შეამოწმეთ ქსელთან კავშირი ან მოსინჯეთ სხვა სურათი.
pdfjs-editor-add-signature-error-close-button = დახურვა
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = გაუქმება
pdfjs-editor-add-signature-add-button = დამატება
pdfjs-editor-edit-signature-update-button = განახლება
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = ხელმოწერის მოცილება
pdfjs-editor-delete-signature-button-label = ხელმოწერის მოცილება
pdfjs-editor-delete-signature-button1 =
.title = შენახული ხელმოწერის მოცილება
pdfjs-editor-delete-signature-button-label1 = შენახული ხელმოწერის მოცილება
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = აღწერილობის ჩასწორება
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = აღწერილობის ჩასწორება
================================================
FILE: cookbook/static/pdfjs/web/locale/kab/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Asebter azewwar
pdfjs-previous-button-label = Azewwar
pdfjs-next-button =
.title = Asebter d-iteddun
pdfjs-next-button-label = Ddu ɣer zdat
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Asebter
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = ɣef { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } n { $pagesCount })
pdfjs-zoom-out-button =
.title = Semẓi
pdfjs-zoom-out-button-label = Semẓi
pdfjs-zoom-in-button =
.title = Semɣeṛ
pdfjs-zoom-in-button-label = Semɣeṛ
pdfjs-zoom-select =
.title = Semɣeṛ/Semẓi
pdfjs-presentation-mode-button =
.title = Uɣal ɣer Uskar Tihawt
pdfjs-presentation-mode-button-label = Askar Tihawt
pdfjs-open-file-button =
.title = Ldi Afaylu
pdfjs-open-file-button-label = Ldi
pdfjs-print-button =
.title = Siggez
pdfjs-print-button-label = Siggez
pdfjs-save-button =
.title = Sekles
pdfjs-save-button-label = Sekles
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Sader
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Sader
pdfjs-bookmark-button =
.title = Asebter amiran (Sken-d tansa URL seg usebter amiran)
pdfjs-bookmark-button-label = Asebter amiran
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Ifecka
pdfjs-tools-button-label = Ifecka
pdfjs-first-page-button =
.title = Ddu ɣer usebter amezwaru
pdfjs-first-page-button-label = Ddu ɣer usebter amezwaru
pdfjs-last-page-button =
.title = Ddu ɣer usebter aneggaru
pdfjs-last-page-button-label = Ddu ɣer usebter aneggaru
pdfjs-page-rotate-cw-button =
.title = Tuzzya tusrigt
pdfjs-page-rotate-cw-button-label = Tuzzya tusrigt
pdfjs-page-rotate-ccw-button =
.title = Tuzzya amgal-usrig
pdfjs-page-rotate-ccw-button-label = Tuzzya amgal-usrig
pdfjs-cursor-text-select-tool-button =
.title = Rmed afecku n tefrant n uḍris
pdfjs-cursor-text-select-tool-button-label = Afecku n tefrant n uḍris
pdfjs-cursor-hand-tool-button =
.title = Rmed afecku afus
pdfjs-cursor-hand-tool-button-label = Afecku afus
pdfjs-scroll-page-button =
.title = Seqdec adrurem n usebter
pdfjs-scroll-page-button-label = Adrurem n usebter
pdfjs-scroll-vertical-button =
.title = Seqdec adrurem ubdid
pdfjs-scroll-vertical-button-label = Adrurem ubdid
pdfjs-scroll-horizontal-button =
.title = Seqdec adrurem aglawan
pdfjs-scroll-horizontal-button-label = Adrurem aglawan
pdfjs-scroll-wrapped-button =
.title = Seqdec adrurem yuẓen
pdfjs-scroll-wrapped-button-label = Adrurem yuẓen
pdfjs-spread-none-button =
.title = Ur sedday ara isiɣzaf n usebter
pdfjs-spread-none-button-label = Ulac isiɣzaf
pdfjs-spread-odd-button =
.title = Seddu isiɣzaf n usebter ibeddun s yisebtar irayuganen
pdfjs-spread-odd-button-label = Isiɣzaf irayuganen
pdfjs-spread-even-button =
.title = Seddu isiɣzaf n usebter ibeddun s yisebtar iyuganen
pdfjs-spread-even-button-label = Isiɣzaf iyuganen
## Document properties dialog
pdfjs-document-properties-button =
.title = Taɣaṛa n isemli…
pdfjs-document-properties-button-label = Taɣaṛa n isemli…
pdfjs-document-properties-file-name = Isem n ufaylu:
pdfjs-document-properties-file-size = Teɣzi n ufaylu:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } yibiten)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } yibiten)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KAṬ ({ $size_b } ibiten)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MAṬ ({ $size_b } iṭamḍanen)
pdfjs-document-properties-title = Azwel:
pdfjs-document-properties-author = Ameskar:
pdfjs-document-properties-subject = Amgay:
pdfjs-document-properties-keywords = Awalen n tsaruţ
pdfjs-document-properties-creation-date = Azemz n tmerna:
pdfjs-document-properties-modification-date = Azemz n usnifel:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Yerna-t:
pdfjs-document-properties-producer = Afecku n uselket PDF:
pdfjs-document-properties-version = Lqem PDF:
pdfjs-document-properties-page-count = Amḍan n yisebtar:
pdfjs-document-properties-page-size = Tuγzi n usebter:
pdfjs-document-properties-page-size-unit-inches = deg
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = s teɣzi
pdfjs-document-properties-page-size-orientation-landscape = s tehri
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Asekkil
pdfjs-document-properties-page-size-name-legal = Usḍif
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Taskant Web taruradt:
pdfjs-document-properties-linearized-yes = Ih
pdfjs-document-properties-linearized-no = Ala
pdfjs-document-properties-close-button = Mdel
## Print
pdfjs-print-progress-message = Aheggi i usiggez n isemli…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Sefsex
pdfjs-printing-not-supported = Ɣuṛ-k: Asiggez ur ittusefrak ara yakan imaṛṛa deg iminig-a.
pdfjs-printing-not-ready = Ɣuṛ-k: Afaylu PDF ur d-yuli ara imeṛṛa akken ad ittusiggez.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Sken/Fer agalis adisan
pdfjs-toggle-sidebar-notification-button =
.title = Ffer/Sekn agalis adisan (isemli yegber aɣawas/ticeqqufin yeddan/tissiwin)
pdfjs-toggle-sidebar-button-label = Sken/Fer agalis adisan
pdfjs-document-outline-button =
.title = Sken isemli (Senned snat tikal i wesemɣer/Afneẓ n iferdisen meṛṛa)
pdfjs-document-outline-button-label = Isɣalen n isebtar
pdfjs-attachments-button =
.title = Sken ticeqqufin yeddan
pdfjs-attachments-button-label = Ticeqqufin yeddan
pdfjs-layers-button =
.title = Skeen tissiwin (sit sin yiberdan i uwennez n meṛṛa tissiwin ɣer waddad amezwer)
pdfjs-layers-button-label = Tissiwin
pdfjs-thumbs-button =
.title = Sken tanfult.
pdfjs-thumbs-button-label = Tinfulin
pdfjs-current-outline-item-button =
.title = Af-d aferdis n uɣawas amiran
pdfjs-current-outline-item-button-label = Aferdis n uɣawas amiran
pdfjs-findbar-button =
.title = Nadi deg isemli
pdfjs-findbar-button-label = Nadi
pdfjs-additional-layers = Tissiwin-nniḍen
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Asebter { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Tanfult n usebter { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Nadi
.placeholder = Nadi deg isemli…
pdfjs-find-previous-button =
.title = Aff-d tamseḍriwt n twinest n deffir
pdfjs-find-previous-button-label = Azewwar
pdfjs-find-next-button =
.title = Aff-d timseḍriwt n twinest d-iteddun
pdfjs-find-next-button-label = Ddu ɣer zdat
pdfjs-find-highlight-checkbox = Err izirig imaṛṛa
pdfjs-find-match-case-checkbox-label = Qadeṛ amasal n isekkilen
pdfjs-find-match-diacritics-checkbox-label = Qadeṛ ifeskilen
pdfjs-find-entire-word-checkbox-label = Awalen iččuranen
pdfjs-find-reached-top = Yabbeḍ s afella n usebter, tuɣalin s wadda
pdfjs-find-reached-bottom = Tebḍeḍ s adda n usebter, tuɣalin s afella
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] Timeḍriwt { $current } ɣef { $total }
*[other] Timeḍriwin { $current } ɣef { $total }
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Ugar n { $limit } umṣada
*[other] Ugar n { $limit } yimṣadayen
}
pdfjs-find-not-found = Ulac tawinest
## Predefined zoom values
pdfjs-page-scale-width = Tehri n usebter
pdfjs-page-scale-fit = Asebter imaṛṛa
pdfjs-page-scale-auto = Asemɣeṛ/Asemẓi awurman
pdfjs-page-scale-actual = Teɣzi tilawt
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Asebter { $page }
## Loading indicator messages
pdfjs-loading-error = Teḍra-d tuccḍa deg alluy n PDF:
pdfjs-invalid-file-error = Afaylu PDF arameɣtu neɣ yexṣeṛ.
pdfjs-missing-file-error = Ulac afaylu PDF.
pdfjs-unexpected-response-error = Aqeddac yerra-d yir tiririt ur nettwaṛǧi ara.
pdfjs-rendering-error = Teḍra-d tuccḍa deg uskan n usebter.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [Tabzimt { $type }]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Sekcem awal uffir akken ad ldiḍ afaylu-yagi PDF
pdfjs-password-invalid = Awal uffir mačči d ameɣtu, Ɛreḍ tikelt-nniḍen.
pdfjs-password-ok-button = IH
pdfjs-password-cancel-button = Sefsex
pdfjs-web-fonts-disabled = Tisefsiyin web ttwassensent; D awezɣi useqdec n tsefsiyin yettwarnan ɣer PDF.
## Editing
pdfjs-editor-free-text-button =
.title = Aḍris
pdfjs-editor-free-text-button-label = Aḍris
pdfjs-editor-ink-button =
.title = Suneɣ
pdfjs-editor-ink-button-label = Suneɣ
pdfjs-editor-stamp-button =
.title = Rnu neɣ ẓreg tugniwin
pdfjs-editor-stamp-button-label = Rnu neɣ ẓreg tugniwin
pdfjs-editor-highlight-button =
.title = Derrer
pdfjs-editor-highlight-button-label = Derrer
pdfjs-highlight-floating-button1 =
.title = Derrer
.aria-label = Derrer
pdfjs-highlight-floating-button-label = Derrer
## Default editor aria labels
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Kkes asuneɣ
pdfjs-editor-remove-freetext-button =
.title = Kkes aḍris
pdfjs-editor-remove-stamp-button =
.title = Kkes tugna
pdfjs-editor-remove-highlight-button =
.title = Kkes aderrer
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Initen
pdfjs-editor-free-text-size-input = Teɣzi
pdfjs-editor-ink-color-input = Ini
pdfjs-editor-ink-thickness-input = Tuzert
pdfjs-editor-ink-opacity-input = Tebrek
pdfjs-editor-stamp-add-image-button =
.title = Rnu tawlaft
pdfjs-editor-stamp-add-image-button-label = Rnu tawlaft
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Tuzert
pdfjs-editor-free-highlight-thickness-title =
.title = Beddel tuzert mi ara d-tesbeggneḍ iferdisen niḍen ur nelli d aḍris
pdfjs-free-text =
.aria-label = Amaẓrag n uḍris
pdfjs-free-text-default-content = Bdu tira...
pdfjs-ink =
.aria-label = Amaẓrag n usuneɣ
pdfjs-ink-canvas =
.aria-label = Tugna yettwarnan sɣur useqdac
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Aḍris amaskal
pdfjs-editor-alt-text-edit-button-label = Ẓreg aḍris amaskal
pdfjs-editor-alt-text-dialog-label = Fren taxtirt
pdfjs-editor-alt-text-add-description-label = Rnu aglam
pdfjs-editor-alt-text-mark-decorative-label = Creḍ d adlag
pdfjs-editor-alt-text-cancel-button = Sefsex
pdfjs-editor-alt-text-save-button = Sekles
pdfjs-editor-alt-text-decorative-tooltip = Yettwacreḍ d adlag
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Tiɣmert n ufella n zelmeḍ — semsawi teɣzi
pdfjs-editor-resizer-label-top-middle = Talemmat n ufella — semsawi teɣzi
pdfjs-editor-resizer-label-top-right = Tiɣmert n ufella n yeffus — semsawi teɣzi
pdfjs-editor-resizer-label-middle-right = Talemmast tayeffust — semsawi teɣzi
pdfjs-editor-resizer-label-bottom-right = Tiɣmert n wadda n yeffus — semsawi teɣzi
pdfjs-editor-resizer-label-bottom-middle = Talemmat n wadda — semsawi teɣzi
pdfjs-editor-resizer-label-bottom-left = Tiɣmert n wadda n zelmeḍ — semsawi teɣzi
pdfjs-editor-resizer-label-middle-left = Talemmast tazelmdaḍt — semsawi teɣzi
pdfjs-editor-resizer-top-left =
.aria-label = Tiɣmert n ufella n zelmeḍ — semsawi teɣzi
pdfjs-editor-resizer-top-middle =
.aria-label = Talemmat n ufella — semsawi teɣzi
pdfjs-editor-resizer-top-right =
.aria-label = Tiɣmert n ufella n yeffus — semsawi teɣzi
pdfjs-editor-resizer-middle-right =
.aria-label = Talemmast tayeffust — semsawi teɣzi
pdfjs-editor-resizer-bottom-right =
.aria-label = Tiɣmert n wadda n yeffus — semsawi teɣzi
pdfjs-editor-resizer-bottom-middle =
.aria-label = Talemmat n wadda — semsawi teɣzi
pdfjs-editor-resizer-bottom-left =
.aria-label = Tiɣmert n wadda n zelmeḍ — semsawi teɣzi
pdfjs-editor-resizer-middle-left =
.aria-label = Talemmast tazelmdaḍt — semsawi teɣzi
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Ini n uderrer
pdfjs-editor-colorpicker-button =
.title = Senfel ini
pdfjs-editor-colorpicker-dropdown =
.aria-label = Afran n yiniten
pdfjs-editor-colorpicker-yellow =
.title = Awraɣ
pdfjs-editor-colorpicker-green =
.title = Azegzaw
pdfjs-editor-colorpicker-blue =
.title = Amidadi
pdfjs-editor-colorpicker-pink =
.title = Axuxi
pdfjs-editor-colorpicker-red =
.title = Azggaɣ
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Sken akk
pdfjs-editor-highlight-show-all-button =
.title = Sken akk
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Rnu aḍris niḍen (aglam n tugna)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Aru aglam-ik dagi…
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Issin ugar
pdfjs-editor-new-alt-text-create-automatically-button-label = Rnu aḍris niḍen s wudem awurman
pdfjs-editor-new-alt-text-not-now-button = Mačči tura
pdfjs-editor-new-alt-text-error-title = D awezɣi timerna n uḍris niḍen s wudem awurman
pdfjs-editor-new-alt-text-error-close-button = Mdel
## Image alt-text settings
pdfjs-editor-alt-text-settings-delete-model-button = Kkes
pdfjs-editor-alt-text-settings-download-model-button = Sader
pdfjs-editor-alt-text-settings-downloading-model-button = Asader…
pdfjs-editor-alt-text-settings-close-button = Mdel
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/kk/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Алдыңғы парақ
pdfjs-previous-button-label = Алдыңғысы
pdfjs-next-button =
.title = Келесі парақ
pdfjs-next-button-label = Келесі
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Парақ
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = { $pagesCount } ішінен
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = (парақ { $pageNumber }, { $pagesCount } ішінен)
pdfjs-zoom-out-button =
.title = Кішірейту
pdfjs-zoom-out-button-label = Кішірейту
pdfjs-zoom-in-button =
.title = Үлкейту
pdfjs-zoom-in-button-label = Үлкейту
pdfjs-zoom-select =
.title = Масштаб
pdfjs-presentation-mode-button =
.title = Презентация режиміне ауысу
pdfjs-presentation-mode-button-label = Презентация режимі
pdfjs-open-file-button =
.title = Файлды ашу
pdfjs-open-file-button-label = Ашу
pdfjs-print-button =
.title = Баспаға шығару
pdfjs-print-button-label = Баспаға шығару
pdfjs-save-button =
.title = Сақтау
pdfjs-save-button-label = Сақтау
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Жүктеп алу
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Жүктеп алу
pdfjs-bookmark-button =
.title = Ағымдағы бет (Ағымдағы беттен URL адресін көру)
pdfjs-bookmark-button-label = Ағымдағы бет
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Құралдар
pdfjs-tools-button-label = Құралдар
pdfjs-first-page-button =
.title = Алғашқы параққа өту
pdfjs-first-page-button-label = Алғашқы параққа өту
pdfjs-last-page-button =
.title = Соңғы параққа өту
pdfjs-last-page-button-label = Соңғы параққа өту
pdfjs-page-rotate-cw-button =
.title = Сағат тілі бағытымен айналдыру
pdfjs-page-rotate-cw-button-label = Сағат тілі бағытымен бұру
pdfjs-page-rotate-ccw-button =
.title = Сағат тілі бағытына қарсы бұру
pdfjs-page-rotate-ccw-button-label = Сағат тілі бағытына қарсы бұру
pdfjs-cursor-text-select-tool-button =
.title = Мәтінді таңдау құралын іске қосу
pdfjs-cursor-text-select-tool-button-label = Мәтінді таңдау құралы
pdfjs-cursor-hand-tool-button =
.title = Қол құралын іске қосу
pdfjs-cursor-hand-tool-button-label = Қол құралы
pdfjs-scroll-page-button =
.title = Беттерді айналдыруды пайдалану
pdfjs-scroll-page-button-label = Беттерді айналдыру
pdfjs-scroll-vertical-button =
.title = Вертикалды айналдыруды қолдану
pdfjs-scroll-vertical-button-label = Вертикалды айналдыру
pdfjs-scroll-horizontal-button =
.title = Горизонталды айналдыруды қолдану
pdfjs-scroll-horizontal-button-label = Горизонталды айналдыру
pdfjs-scroll-wrapped-button =
.title = Масштабталатын айналдыруды қолдану
pdfjs-scroll-wrapped-button-label = Масштабталатын айналдыру
pdfjs-spread-none-button =
.title = Жазық беттер режимін қолданбау
pdfjs-spread-none-button-label = Жазық беттер режимсіз
pdfjs-spread-odd-button =
.title = Жазық беттер тақ нөмірлі беттерден басталады
pdfjs-spread-odd-button-label = Тақ нөмірлі беттер сол жақтан
pdfjs-spread-even-button =
.title = Жазық беттер жұп нөмірлі беттерден басталады
pdfjs-spread-even-button-label = Жұп нөмірлі беттер сол жақтан
## Document properties dialog
pdfjs-document-properties-button =
.title = Құжат қасиеттері…
pdfjs-document-properties-button-label = Құжат қасиеттері…
pdfjs-document-properties-file-name = Файл аты:
pdfjs-document-properties-file-size = Файл өлшемі:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } КБ ({ $b } байт)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } МБ ({ $b } байт)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } КБ ({ $size_b } байт)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } МБ ({ $size_b } байт)
pdfjs-document-properties-title = Тақырыбы:
pdfjs-document-properties-author = Авторы:
pdfjs-document-properties-subject = Тақырыбы:
pdfjs-document-properties-keywords = Кілт сөздер:
pdfjs-document-properties-creation-date = Жасалған күні:
pdfjs-document-properties-modification-date = Түзету күні:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Жасаған:
pdfjs-document-properties-producer = PDF өндірген:
pdfjs-document-properties-version = PDF нұсқасы:
pdfjs-document-properties-page-count = Беттер саны:
pdfjs-document-properties-page-size = Бет өлшемі:
pdfjs-document-properties-page-size-unit-inches = дюйм
pdfjs-document-properties-page-size-unit-millimeters = мм
pdfjs-document-properties-page-size-orientation-portrait = тік
pdfjs-document-properties-page-size-orientation-landscape = жатық
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letter
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Жылдам Web көрінісі:
pdfjs-document-properties-linearized-yes = Иә
pdfjs-document-properties-linearized-no = Жоқ
pdfjs-document-properties-close-button = Жабу
## Print
pdfjs-print-progress-message = Құжатты баспаға шығару үшін дайындау…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Бас тарту
pdfjs-printing-not-supported = Ескерту: Баспаға шығаруды бұл браузер толығымен қолдамайды.
pdfjs-printing-not-ready = Ескерту: Баспаға шығару үшін, бұл PDF толығымен жүктеліп алынбады.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Бүйір панелін көрсету/жасыру
pdfjs-toggle-sidebar-notification-button =
.title = Бүйір панелін көрсету/жасыру (құжатта құрылымы/салынымдар/қабаттар бар)
pdfjs-toggle-sidebar-button-label = Бүйір панелін көрсету/жасыру
pdfjs-document-outline-button =
.title = Құжат құрылымын көрсету (барлық нәрселерді жазық қылу/жинау үшін қос шерту керек)
pdfjs-document-outline-button-label = Құжат құрамасы
pdfjs-attachments-button =
.title = Салынымдарды көрсету
pdfjs-attachments-button-label = Салынымдар
pdfjs-layers-button =
.title = Қабаттарды көрсету (барлық қабаттарды бастапқы күйге келтіру үшін екі рет шертіңіз)
pdfjs-layers-button-label = Қабаттар
pdfjs-thumbs-button =
.title = Кіші көріністерді көрсету
pdfjs-thumbs-button-label = Кіші көріністер
pdfjs-current-outline-item-button =
.title = Құрылымның ағымдағы элементін табу
pdfjs-current-outline-item-button-label = Құрылымның ағымдағы элементі
pdfjs-findbar-button =
.title = Құжаттан табу
pdfjs-findbar-button-label = Табу
pdfjs-additional-layers = Қосымша қабаттар
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = { $page } парағы
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = { $page } парағы үшін кіші көрінісі
## Find panel button title and messages
pdfjs-find-input =
.title = Табу
.placeholder = Құжаттан табу…
pdfjs-find-previous-button =
.title = Осы сөздердің мәтіннен алдыңғы кездесуін табу
pdfjs-find-previous-button-label = Алдыңғысы
pdfjs-find-next-button =
.title = Осы сөздердің мәтіннен келесі кездесуін табу
pdfjs-find-next-button-label = Келесі
pdfjs-find-highlight-checkbox = Барлығын түспен ерекшелеу
pdfjs-find-match-case-checkbox-label = Регистрді ескеру
pdfjs-find-match-diacritics-checkbox-label = Диакритиканы ескеру
pdfjs-find-entire-word-checkbox-label = Сөздер толығымен
pdfjs-find-reached-top = Құжаттың басына жеттік, соңынан бастап жалғастырамыз
pdfjs-find-reached-bottom = Құжаттың соңына жеттік, басынан бастап жалғастырамыз
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } сәйкестік, барлығы { $total }
*[other] { $current } сәйкестік, барлығы { $total }
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] { $limit } сәйкестіктен көп
*[other] { $limit } сәйкестіктен көп
}
pdfjs-find-not-found = Сөз(дер) табылмады
## Predefined zoom values
pdfjs-page-scale-width = Парақ ені
pdfjs-page-scale-fit = Парақты сыйдыру
pdfjs-page-scale-auto = Автомасштабтау
pdfjs-page-scale-actual = Нақты өлшемі
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Бет { $page }
## Loading indicator messages
pdfjs-loading-error = PDF жүктеу кезінде қате кетті.
pdfjs-invalid-file-error = Зақымдалған немесе қате PDF файл.
pdfjs-missing-file-error = PDF файлы жоқ.
pdfjs-unexpected-response-error = Сервердің күтпеген жауабы.
pdfjs-rendering-error = Парақты өңдеу кезінде қате кетті.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } аңдатпасы]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Бұл PDF файлын ашу үшін парольді енгізіңіз.
pdfjs-password-invalid = Пароль дұрыс емес. Қайталап көріңіз.
pdfjs-password-ok-button = ОК
pdfjs-password-cancel-button = Бас тарту
pdfjs-web-fonts-disabled = Веб қаріптері сөндірілген: құрамына енгізілген PDF қаріптерін қолдану мүмкін емес.
## Editing
pdfjs-editor-free-text-button =
.title = Мәтін
pdfjs-editor-free-text-button-label = Мәтін
pdfjs-editor-ink-button =
.title = Сурет салу
pdfjs-editor-ink-button-label = Сурет салу
pdfjs-editor-stamp-button =
.title = Суреттерді қосу немесе түзету
pdfjs-editor-stamp-button-label = Суреттерді қосу немесе түзету
pdfjs-editor-highlight-button =
.title = Ерекшелеу
pdfjs-editor-highlight-button-label = Ерекшелеу
pdfjs-highlight-floating-button1 =
.title = Ерекшелеу
.aria-label = Ерекшелеу
pdfjs-highlight-floating-button-label = Ерекшелеу
pdfjs-editor-signature-button =
.title = Қолтаңбаны қосу
pdfjs-editor-signature-button-label = Қолтаңбаны қосу
## Default editor aria labels
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Сызбаны өшіру
pdfjs-editor-remove-freetext-button =
.title = Мәтінді өшіру
pdfjs-editor-remove-stamp-button =
.title = Суретті өшіру
pdfjs-editor-remove-highlight-button =
.title = Түспен ерекшелеуді өшіру
pdfjs-editor-remove-signature-button =
.title = Қолтаңбаны өшіру
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Түс
pdfjs-editor-free-text-size-input = Өлшемі
pdfjs-editor-ink-color-input = Түс
pdfjs-editor-ink-thickness-input = Қалыңдығы
pdfjs-editor-ink-opacity-input = Мөлдірсіздігі
pdfjs-editor-stamp-add-image-button =
.title = Суретті қосу
pdfjs-editor-stamp-add-image-button-label = Суретті қосу
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Қалыңдығы
pdfjs-editor-free-highlight-thickness-title =
.title = Мәтіннен басқа элементтерді ерекшелеу кезінде қалыңдықты өзгерту
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Мәтін түзеткіші
.default-content = Теріп бастаңыз…
pdfjs-free-text =
.aria-label = Мәтін түзеткіші
pdfjs-free-text-default-content = Теруді бастау…
pdfjs-ink =
.aria-label = Сурет түзеткіші
pdfjs-ink-canvas =
.aria-label = Пайдаланушы жасаған сурет
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Балама мәтін
pdfjs-editor-alt-text-edit-button =
.aria-label = Балама мәтінді өңдеу
pdfjs-editor-alt-text-edit-button-label = Балама мәтінді өңдеу
pdfjs-editor-alt-text-dialog-label = Опцияны таңдау
pdfjs-editor-alt-text-dialog-description = Балама мәтін адамдар суретті көре алмағанда немесе ол жүктелмегенде көмектеседі.
pdfjs-editor-alt-text-add-description-label = Сипаттаманы қосу
pdfjs-editor-alt-text-add-description-description = Тақырыпты, баптауды немесе әрекетті сипаттайтын 1-2 сөйлемді қолдануға тырысыңыз.
pdfjs-editor-alt-text-mark-decorative-label = Декоративті деп белгілеу
pdfjs-editor-alt-text-mark-decorative-description = Бұл жиектер немесе су белгілері сияқты оюлық суреттер үшін пайдаланылады.
pdfjs-editor-alt-text-cancel-button = Бас тарту
pdfjs-editor-alt-text-save-button = Сақтау
pdfjs-editor-alt-text-decorative-tooltip = Декоративті деп белгіленген
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Мысалы, "Жас жігіт тамақ ішу үшін үстел басына отырады"
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Балама мәтін
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Жоғарғы сол жақ бұрыш — өлшемін өзгерту
pdfjs-editor-resizer-label-top-middle = Жоғарғы ортасы — өлшемін өзгерту
pdfjs-editor-resizer-label-top-right = Жоғарғы оң жақ бұрыш — өлшемін өзгерту
pdfjs-editor-resizer-label-middle-right = Ортаңғы оң жақ — өлшемін өзгерту
pdfjs-editor-resizer-label-bottom-right = Төменгі оң жақ бұрыш — өлшемін өзгерту
pdfjs-editor-resizer-label-bottom-middle = Төменгі ортасы — өлшемін өзгерту
pdfjs-editor-resizer-label-bottom-left = Төменгі сол жақ бұрыш — өлшемін өзгерту
pdfjs-editor-resizer-label-middle-left = Ортаңғы сол жақ — өлшемін өзгерту
pdfjs-editor-resizer-top-left =
.aria-label = Жоғарғы сол жақ бұрыш — өлшемін өзгерту
pdfjs-editor-resizer-top-middle =
.aria-label = Жоғарғы ортасы — өлшемін өзгерту
pdfjs-editor-resizer-top-right =
.aria-label = Жоғарғы оң жақ бұрыш — өлшемін өзгерту
pdfjs-editor-resizer-middle-right =
.aria-label = Ортаңғы оң жақ — өлшемін өзгерту
pdfjs-editor-resizer-bottom-right =
.aria-label = Төменгі оң жақ бұрыш — өлшемін өзгерту
pdfjs-editor-resizer-bottom-middle =
.aria-label = Төменгі ортасы — өлшемін өзгерту
pdfjs-editor-resizer-bottom-left =
.aria-label = Төменгі сол жақ бұрыш — өлшемін өзгерту
pdfjs-editor-resizer-middle-left =
.aria-label = Ортаңғы сол жақ — өлшемін өзгерту
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Ерекшелеу түсі
pdfjs-editor-colorpicker-button =
.title = Түсті өзгерту
pdfjs-editor-colorpicker-dropdown =
.aria-label = Түс таңдаулары
pdfjs-editor-colorpicker-yellow =
.title = Сары
pdfjs-editor-colorpicker-green =
.title = Жасыл
pdfjs-editor-colorpicker-blue =
.title = Көк
pdfjs-editor-colorpicker-pink =
.title = Қызғылт
pdfjs-editor-colorpicker-red =
.title = Қызыл
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Барлығын көрсету
pdfjs-editor-highlight-show-all-button =
.title = Барлығын көрсету
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Балама мәтінді өңдеу (сурет сипаттамасы)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Балама мәтінді қосу (сурет сипаттамасы)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Сипаттамаңызды осында жазыңыз…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Суретті көре алмайтын адамдар үшін немесе сурет жүктелмеген кезіне арналған қысқаша сипаттама.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Бұл балама мәтін автоматты түрде жасалды және дәлсіз болуы мүмкін.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Көбірек білу
pdfjs-editor-new-alt-text-create-automatically-button-label = Балама мәтінді автоматты түрде жасау
pdfjs-editor-new-alt-text-not-now-button = Қазір емес
pdfjs-editor-new-alt-text-error-title = Балама мәтінді автоматты түрде жасау мүмкін болмады
pdfjs-editor-new-alt-text-error-description = Өзіңіздің балама мәтініңізді жазыңыз немесе кейінірек қайталап көріңіз.
pdfjs-editor-new-alt-text-error-close-button = Жабу
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Балама мәтін үшін ЖИ моделі жүктеп алынуда ({ $downloadedSize }/{ $totalSize } МБ)
.aria-valuetext = Балама мәтін үшін ЖИ моделі жүктеп алынуда ({ $downloadedSize }/{ $totalSize } МБ)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Балама мәтін қосылды
pdfjs-editor-new-alt-text-added-button-label = Балама мәтін қосылды
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Балама мәтін жоқ
pdfjs-editor-new-alt-text-missing-button-label = Балама мәтін жоқ
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Балама мәтінге пікір қалдыру
pdfjs-editor-new-alt-text-to-review-button-label = Балама мәтінге пікір қалдыру
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Автоматты түрде жасалды: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Суреттің балама мәтінінің баптаулары
pdfjs-image-alt-text-settings-button-label = Суреттің балама мәтінінің баптаулары
pdfjs-editor-alt-text-settings-dialog-label = Суреттің балама мәтінінің баптаулары
pdfjs-editor-alt-text-settings-automatic-title = Автоматты балама мәтін
pdfjs-editor-alt-text-settings-create-model-button-label = Балама мәтінді автоматты түрде жасау
pdfjs-editor-alt-text-settings-create-model-description = Суретті көре алмайтын адамдар үшін немесе сурет жүктелмеген кезіне арналған сипаттамаларды ұсынады.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Баламалы мәтіннің ЖИ моделі ({ $totalSize } МБ)
pdfjs-editor-alt-text-settings-ai-model-description = Деректеріңіз жеке болып қалуы үшін құрылғыңызда жергілікті түрде жұмыс істейді. Автоматты балама мәтін үшін қажет.
pdfjs-editor-alt-text-settings-delete-model-button = Өшіру
pdfjs-editor-alt-text-settings-download-model-button = Жүктеп алу
pdfjs-editor-alt-text-settings-downloading-model-button = Жүктеліп алынуда…
pdfjs-editor-alt-text-settings-editor-title = Баламалы мәтін редакторы
pdfjs-editor-alt-text-settings-show-dialog-button-label = Суретті қосқанда балама мәтін редакторын бірден көрсету
pdfjs-editor-alt-text-settings-show-dialog-description = Барлық суреттерде балама мәтін бар екеніне көз жеткізуге көмектеседі.
pdfjs-editor-alt-text-settings-close-button = Жабу
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Ерекшелеу өшірілді
pdfjs-editor-undo-bar-message-freetext = Мәтін өшірілді
pdfjs-editor-undo-bar-message-ink = Сызба өшірілді
pdfjs-editor-undo-bar-message-stamp = Сурет өшірілді
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } анимация өшірілді
*[other] { $count } анимация өшірілді
}
pdfjs-editor-undo-bar-undo-button =
.title = Болдырмау
pdfjs-editor-undo-bar-undo-button-label = Болдырмау
pdfjs-editor-undo-bar-close-button =
.title = Жабу
pdfjs-editor-undo-bar-close-button-label = Жабу
## Add a signature dialog
pdfjs-editor-add-signature-dialog-title = Қолтаңба қосу
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Енгізу
.title = Енгізу
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Сурет салу
.title = Сурет салу
pdfjs-editor-add-signature-image-button = Сурет
.title = Сурет
## Tab panels
pdfjs-editor-add-signature-draw-thickness-range-label = Қалыңдығы
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Сызба қалыңздығы: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Жүктеп жіберу үшін файлды осы жерге сүйреңіз
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Немесе сурет файлдарын таңдаңыз
*[other] Немесе сурет файлдарын шолыңыз
}
## Controls
pdfjs-editor-add-signature-description-label = Сипаттама (балама мәтін)
pdfjs-editor-add-signature-description-input =
.title = Сипаттама (балама мәтін)
pdfjs-editor-add-signature-description-default-when-drawing = Қолтаңба
pdfjs-editor-add-signature-clear-button-label = Қолтаңбаны өшіру
pdfjs-editor-add-signature-clear-button =
.title = Қолтаңбаны өшіру
pdfjs-editor-add-signature-save-checkbox = Қолтаңбаны сақтау
pdfjs-editor-add-signature-save-warning-message = Сақталған 5 қолтаңбаның шегіне жеттіңіз. Көбірек сақтау үшін біреуін алып тастаңыз.
pdfjs-editor-add-signature-image-upload-error-title = Суретті жүктеп жіберу мүмкін емес.
pdfjs-editor-add-signature-error-close-button = Жабу
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Бас тарту
pdfjs-editor-add-signature-add-button = Қосу
pdfjs-editor-edit-signature-update-button = Жаңарту
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Қолтаңбаны өшіру
pdfjs-editor-delete-signature-button-label = Қолтаңбаны өшіру
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Сипаттаманы түзету
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Сипаттаманы түзету
================================================
FILE: cookbook/static/pdfjs/web/locale/km/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = ទំព័រមុន
pdfjs-previous-button-label = មុន
pdfjs-next-button =
.title = ទំព័របន្ទាប់
pdfjs-next-button-label = បន្ទាប់
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = ទំព័រ
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = នៃ { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } នៃ { $pagesCount })
pdfjs-zoom-out-button =
.title = បង្រួម
pdfjs-zoom-out-button-label = បង្រួម
pdfjs-zoom-in-button =
.title = ពង្រីក
pdfjs-zoom-in-button-label = ពង្រីក
pdfjs-zoom-select =
.title = ពង្រីក
pdfjs-presentation-mode-button =
.title = ប្ដូរទៅរបៀបបទបង្ហាញ
pdfjs-presentation-mode-button-label = របៀបបទបង្ហាញ
pdfjs-open-file-button =
.title = បើកឯកសារ
pdfjs-open-file-button-label = បើក
pdfjs-print-button =
.title = បោះពុម្ព
pdfjs-print-button-label = បោះពុម្ព
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = ឧបករណ៍
pdfjs-tools-button-label = ឧបករណ៍
pdfjs-first-page-button =
.title = ទៅកាន់ទំព័រដំបូង
pdfjs-first-page-button-label = ទៅកាន់ទំព័រដំបូង
pdfjs-last-page-button =
.title = ទៅកាន់ទំព័រចុងក្រោយ
pdfjs-last-page-button-label = ទៅកាន់ទំព័រចុងក្រោយ
pdfjs-page-rotate-cw-button =
.title = បង្វិលស្របទ្រនិចនាឡិកា
pdfjs-page-rotate-cw-button-label = បង្វិលស្របទ្រនិចនាឡិកា
pdfjs-page-rotate-ccw-button =
.title = បង្វិលច្រាសទ្រនិចនាឡិកា
pdfjs-page-rotate-ccw-button-label = បង្វិលច្រាសទ្រនិចនាឡិកា
pdfjs-cursor-text-select-tool-button =
.title = បើកឧបករណ៍ជ្រើសអត្ថបទ
pdfjs-cursor-text-select-tool-button-label = ឧបករណ៍ជ្រើសអត្ថបទ
pdfjs-cursor-hand-tool-button =
.title = បើកឧបករណ៍ដៃ
pdfjs-cursor-hand-tool-button-label = ឧបករណ៍ដៃ
## Document properties dialog
pdfjs-document-properties-button =
.title = លក្ខណសម្បត្តិឯកសារ…
pdfjs-document-properties-button-label = លក្ខណសម្បត្តិឯកសារ…
pdfjs-document-properties-file-name = ឈ្មោះឯកសារ៖
pdfjs-document-properties-file-size = ទំហំឯកសារ៖
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } បៃ)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } បៃ)
pdfjs-document-properties-title = ចំណងជើង៖
pdfjs-document-properties-author = អ្នកនិពន្ធ៖
pdfjs-document-properties-subject = ប្រធានបទ៖
pdfjs-document-properties-keywords = ពាក្យគន្លឹះ៖
pdfjs-document-properties-creation-date = កាលបរិច្ឆេទបង្កើត៖
pdfjs-document-properties-modification-date = កាលបរិច្ឆេទកែប្រែ៖
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = អ្នកបង្កើត៖
pdfjs-document-properties-producer = កម្មវិធីបង្កើត PDF ៖
pdfjs-document-properties-version = កំណែ PDF ៖
pdfjs-document-properties-page-count = ចំនួនទំព័រ៖
pdfjs-document-properties-page-size-unit-inches = អ៊ីញ
pdfjs-document-properties-page-size-unit-millimeters = មម
pdfjs-document-properties-page-size-orientation-portrait = បញ្ឈរ
pdfjs-document-properties-page-size-orientation-landscape = ផ្តេក
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = សំបុត្រ
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
pdfjs-document-properties-linearized-yes = បាទ/ចាស
pdfjs-document-properties-linearized-no = ទេ
pdfjs-document-properties-close-button = បិទ
## Print
pdfjs-print-progress-message = កំពុងរៀបចំឯកសារសម្រាប់បោះពុម្ព…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = បោះបង់
pdfjs-printing-not-supported = ការព្រមាន ៖ ការបោះពុម្ពមិនត្រូវបានគាំទ្រពេញលេញដោយកម្មវិធីរុករកនេះទេ ។
pdfjs-printing-not-ready = ព្រមាន៖ PDF មិនត្រូវបានផ្ទុកទាំងស្រុងដើម្បីបោះពុម្ពទេ។
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = បិទ/បើកគ្រាប់រំកិល
pdfjs-toggle-sidebar-button-label = បិទ/បើកគ្រាប់រំកិល
pdfjs-document-outline-button =
.title = បង្ហាញគ្រោងឯកសារ (ចុចទ្វេដងដើម្បីពង្រីក/បង្រួមធាតុទាំងអស់)
pdfjs-document-outline-button-label = គ្រោងឯកសារ
pdfjs-attachments-button =
.title = បង្ហាញឯកសារភ្ជាប់
pdfjs-attachments-button-label = ឯកសារភ្ជាប់
pdfjs-thumbs-button =
.title = បង្ហាញរូបភាពតូចៗ
pdfjs-thumbs-button-label = រួបភាពតូចៗ
pdfjs-findbar-button =
.title = រកនៅក្នុងឯកសារ
pdfjs-findbar-button-label = រក
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = ទំព័រ { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = រូបភាពតូចរបស់ទំព័រ { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = រក
.placeholder = រកនៅក្នុងឯកសារ...
pdfjs-find-previous-button =
.title = រកពាក្យ ឬឃ្លាដែលបានជួបមុន
pdfjs-find-previous-button-label = មុន
pdfjs-find-next-button =
.title = រកពាក្យ ឬឃ្លាដែលបានជួបបន្ទាប់
pdfjs-find-next-button-label = បន្ទាប់
pdfjs-find-highlight-checkbox = បន្លិចទាំងអស់
pdfjs-find-match-case-checkbox-label = ករណីដំណូច
pdfjs-find-reached-top = បានបន្តពីខាងក្រោម ទៅដល់ខាងលើនៃឯកសារ
pdfjs-find-reached-bottom = បានបន្តពីខាងលើ ទៅដល់ចុងនៃឯកសារ
pdfjs-find-not-found = រកមិនឃើញពាក្យ ឬឃ្លា
## Predefined zoom values
pdfjs-page-scale-width = ទទឹងទំព័រ
pdfjs-page-scale-fit = សមទំព័រ
pdfjs-page-scale-auto = ពង្រីកស្វ័យប្រវត្តិ
pdfjs-page-scale-actual = ទំហំជាក់ស្ដែង
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
## Loading indicator messages
pdfjs-loading-error = មានកំហុសបានកើតឡើងពេលកំពុងផ្ទុក PDF ។
pdfjs-invalid-file-error = ឯកសារ PDF ខូច ឬមិនត្រឹមត្រូវ ។
pdfjs-missing-file-error = បាត់ឯកសារ PDF
pdfjs-unexpected-response-error = ការឆ្លើយតមម៉ាស៊ីនមេដែលមិនបានរំពឹង។
pdfjs-rendering-error = មានកំហុសបានកើតឡើងពេលបង្ហាញទំព័រ ។
## Annotations
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } ចំណារពន្យល់]
## Password
pdfjs-password-label = បញ្ចូលពាក្យសម្ងាត់ដើម្បីបើកឯកសារ PDF នេះ។
pdfjs-password-invalid = ពាក្យសម្ងាត់មិនត្រឹមត្រូវ។ សូមព្យាយាមម្ដងទៀត។
pdfjs-password-ok-button = យល់ព្រម
pdfjs-password-cancel-button = បោះបង់
pdfjs-web-fonts-disabled = បានបិទពុម្ពអក្សរបណ្ដាញ ៖ មិនអាចប្រើពុម្ពអក្សរ PDF ដែលបានបង្កប់បានទេ ។
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/kn/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = ಹಿಂದಿನ ಪುಟ
pdfjs-previous-button-label = ಹಿಂದಿನ
pdfjs-next-button =
.title = ಮುಂದಿನ ಪುಟ
pdfjs-next-button-label = ಮುಂದಿನ
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = ಪುಟ
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = { $pagesCount } ರಲ್ಲಿ
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pagesCount } ರಲ್ಲಿ { $pageNumber })
pdfjs-zoom-out-button =
.title = ಕಿರಿದಾಗಿಸು
pdfjs-zoom-out-button-label = ಕಿರಿದಾಗಿಸಿ
pdfjs-zoom-in-button =
.title = ಹಿರಿದಾಗಿಸು
pdfjs-zoom-in-button-label = ಹಿರಿದಾಗಿಸಿ
pdfjs-zoom-select =
.title = ಗಾತ್ರಬದಲಿಸು
pdfjs-presentation-mode-button =
.title = ಪ್ರಸ್ತುತಿ (ಪ್ರಸೆಂಟೇಶನ್) ಕ್ರಮಕ್ಕೆ ಬದಲಾಯಿಸು
pdfjs-presentation-mode-button-label = ಪ್ರಸ್ತುತಿ (ಪ್ರಸೆಂಟೇಶನ್) ಕ್ರಮ
pdfjs-open-file-button =
.title = ಕಡತವನ್ನು ತೆರೆ
pdfjs-open-file-button-label = ತೆರೆಯಿರಿ
pdfjs-print-button =
.title = ಮುದ್ರಿಸು
pdfjs-print-button-label = ಮುದ್ರಿಸಿ
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = ಉಪಕರಣಗಳು
pdfjs-tools-button-label = ಉಪಕರಣಗಳು
pdfjs-first-page-button =
.title = ಮೊದಲ ಪುಟಕ್ಕೆ ತೆರಳು
pdfjs-first-page-button-label = ಮೊದಲ ಪುಟಕ್ಕೆ ತೆರಳು
pdfjs-last-page-button =
.title = ಕೊನೆಯ ಪುಟಕ್ಕೆ ತೆರಳು
pdfjs-last-page-button-label = ಕೊನೆಯ ಪುಟಕ್ಕೆ ತೆರಳು
pdfjs-page-rotate-cw-button =
.title = ಪ್ರದಕ್ಷಿಣೆಯಲ್ಲಿ ತಿರುಗಿಸು
pdfjs-page-rotate-cw-button-label = ಪ್ರದಕ್ಷಿಣೆಯಲ್ಲಿ ತಿರುಗಿಸು
pdfjs-page-rotate-ccw-button =
.title = ಅಪ್ರದಕ್ಷಿಣೆಯಲ್ಲಿ ತಿರುಗಿಸು
pdfjs-page-rotate-ccw-button-label = ಅಪ್ರದಕ್ಷಿಣೆಯಲ್ಲಿ ತಿರುಗಿಸು
pdfjs-cursor-text-select-tool-button =
.title = ಪಠ್ಯ ಆಯ್ಕೆ ಉಪಕರಣವನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ
pdfjs-cursor-text-select-tool-button-label = ಪಠ್ಯ ಆಯ್ಕೆಯ ಉಪಕರಣ
pdfjs-cursor-hand-tool-button =
.title = ಕೈ ಉಪಕರಣವನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ
pdfjs-cursor-hand-tool-button-label = ಕೈ ಉಪಕರಣ
## Document properties dialog
pdfjs-document-properties-button =
.title = ಡಾಕ್ಯುಮೆಂಟ್ ಗುಣಗಳು...
pdfjs-document-properties-button-label = ಡಾಕ್ಯುಮೆಂಟ್ ಗುಣಗಳು...
pdfjs-document-properties-file-name = ಕಡತದ ಹೆಸರು:
pdfjs-document-properties-file-size = ಕಡತದ ಗಾತ್ರ:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } ಬೈಟ್ಗಳು)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } ಬೈಟ್ಗಳು)
pdfjs-document-properties-title = ಶೀರ್ಷಿಕೆ:
pdfjs-document-properties-author = ಕರ್ತೃ:
pdfjs-document-properties-subject = ವಿಷಯ:
pdfjs-document-properties-keywords = ಮುಖ್ಯಪದಗಳು:
pdfjs-document-properties-creation-date = ರಚಿಸಿದ ದಿನಾಂಕ:
pdfjs-document-properties-modification-date = ಮಾರ್ಪಡಿಸಲಾದ ದಿನಾಂಕ:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = ರಚಿಸಿದವರು:
pdfjs-document-properties-producer = PDF ಉತ್ಪಾದಕ:
pdfjs-document-properties-version = PDF ಆವೃತ್ತಿ:
pdfjs-document-properties-page-count = ಪುಟದ ಎಣಿಕೆ:
pdfjs-document-properties-page-size-unit-inches = ಇದರಲ್ಲಿ
pdfjs-document-properties-page-size-orientation-portrait = ಭಾವಚಿತ್ರ
pdfjs-document-properties-page-size-orientation-landscape = ಪ್ರಕೃತಿ ಚಿತ್ರ
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
##
pdfjs-document-properties-close-button = ಮುಚ್ಚು
## Print
pdfjs-print-progress-message = ಮುದ್ರಿಸುವುದಕ್ಕಾಗಿ ದಸ್ತಾವೇಜನ್ನು ಸಿದ್ಧಗೊಳಿಸಲಾಗುತ್ತಿದೆ…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = ರದ್ದು ಮಾಡು
pdfjs-printing-not-supported = ಎಚ್ಚರಿಕೆ: ಈ ಜಾಲವೀಕ್ಷಕದಲ್ಲಿ ಮುದ್ರಣಕ್ಕೆ ಸಂಪೂರ್ಣ ಬೆಂಬಲವಿಲ್ಲ.
pdfjs-printing-not-ready = ಎಚ್ಚರಿಕೆ: PDF ಕಡತವು ಮುದ್ರಿಸಲು ಸಂಪೂರ್ಣವಾಗಿ ಲೋಡ್ ಆಗಿಲ್ಲ.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = ಬದಿಪಟ್ಟಿಯನ್ನು ಹೊರಳಿಸು
pdfjs-toggle-sidebar-button-label = ಬದಿಪಟ್ಟಿಯನ್ನು ಹೊರಳಿಸು
pdfjs-document-outline-button-label = ದಸ್ತಾವೇಜಿನ ಹೊರರೇಖೆ
pdfjs-attachments-button =
.title = ಲಗತ್ತುಗಳನ್ನು ತೋರಿಸು
pdfjs-attachments-button-label = ಲಗತ್ತುಗಳು
pdfjs-thumbs-button =
.title = ಚಿಕ್ಕಚಿತ್ರದಂತೆ ತೋರಿಸು
pdfjs-thumbs-button-label = ಚಿಕ್ಕಚಿತ್ರಗಳು
pdfjs-findbar-button =
.title = ದಸ್ತಾವೇಜಿನಲ್ಲಿ ಹುಡುಕು
pdfjs-findbar-button-label = ಹುಡುಕು
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = ಪುಟ { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = ಪುಟವನ್ನು ಚಿಕ್ಕಚಿತ್ರದಂತೆ ತೋರಿಸು { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = ಹುಡುಕು
.placeholder = ದಸ್ತಾವೇಜಿನಲ್ಲಿ ಹುಡುಕು…
pdfjs-find-previous-button =
.title = ವಾಕ್ಯದ ಹಿಂದಿನ ಇರುವಿಕೆಯನ್ನು ಹುಡುಕು
pdfjs-find-previous-button-label = ಹಿಂದಿನ
pdfjs-find-next-button =
.title = ವಾಕ್ಯದ ಮುಂದಿನ ಇರುವಿಕೆಯನ್ನು ಹುಡುಕು
pdfjs-find-next-button-label = ಮುಂದಿನ
pdfjs-find-highlight-checkbox = ಎಲ್ಲವನ್ನು ಹೈಲೈಟ್ ಮಾಡು
pdfjs-find-match-case-checkbox-label = ಕೇಸನ್ನು ಹೊಂದಿಸು
pdfjs-find-reached-top = ದಸ್ತಾವೇಜಿನ ಮೇಲ್ಭಾಗವನ್ನು ತಲುಪಿದೆ, ಕೆಳಗಿನಿಂದ ಆರಂಭಿಸು
pdfjs-find-reached-bottom = ದಸ್ತಾವೇಜಿನ ಕೊನೆಯನ್ನು ತಲುಪಿದೆ, ಮೇಲಿನಿಂದ ಆರಂಭಿಸು
pdfjs-find-not-found = ವಾಕ್ಯವು ಕಂಡು ಬಂದಿಲ್ಲ
## Predefined zoom values
pdfjs-page-scale-width = ಪುಟದ ಅಗಲ
pdfjs-page-scale-fit = ಪುಟದ ಸರಿಹೊಂದಿಕೆ
pdfjs-page-scale-auto = ಸ್ವಯಂಚಾಲಿತ ಗಾತ್ರಬದಲಾವಣೆ
pdfjs-page-scale-actual = ನಿಜವಾದ ಗಾತ್ರ
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
## Loading indicator messages
pdfjs-loading-error = PDF ಅನ್ನು ಲೋಡ್ ಮಾಡುವಾಗ ಒಂದು ದೋಷ ಎದುರಾಗಿದೆ.
pdfjs-invalid-file-error = ಅಮಾನ್ಯವಾದ ಅಥವ ಹಾಳಾದ PDF ಕಡತ.
pdfjs-missing-file-error = PDF ಕಡತ ಇಲ್ಲ.
pdfjs-unexpected-response-error = ಅನಿರೀಕ್ಷಿತವಾದ ಪೂರೈಕೆಗಣಕದ ಪ್ರತಿಕ್ರಿಯೆ.
pdfjs-rendering-error = ಪುಟವನ್ನು ನಿರೂಪಿಸುವಾಗ ಒಂದು ದೋಷ ಎದುರಾಗಿದೆ.
## Annotations
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } ಟಿಪ್ಪಣಿ]
## Password
pdfjs-password-label = PDF ಅನ್ನು ತೆರೆಯಲು ಗುಪ್ತಪದವನ್ನು ನಮೂದಿಸಿ.
pdfjs-password-invalid = ಅಮಾನ್ಯವಾದ ಗುಪ್ತಪದ, ದಯವಿಟ್ಟು ಇನ್ನೊಮ್ಮೆ ಪ್ರಯತ್ನಿಸಿ.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = ರದ್ದು ಮಾಡು
pdfjs-web-fonts-disabled = ಜಾಲ ಅಕ್ಷರಶೈಲಿಯನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ: ಅಡಕಗೊಳಿಸಿದ PDF ಅಕ್ಷರಶೈಲಿಗಳನ್ನು ಬಳಸಲು ಸಾಧ್ಯವಾಗಿಲ್ಲ.
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/ko/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = 이전 페이지
pdfjs-previous-button-label = 이전
pdfjs-next-button =
.title = 다음 페이지
pdfjs-next-button-label = 다음
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = 페이지
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = / { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } / { $pagesCount })
pdfjs-zoom-out-button =
.title = 축소
pdfjs-zoom-out-button-label = 축소
pdfjs-zoom-in-button =
.title = 확대
pdfjs-zoom-in-button-label = 확대
pdfjs-zoom-select =
.title = 확대/축소
pdfjs-presentation-mode-button =
.title = 프레젠테이션 모드로 전환
pdfjs-presentation-mode-button-label = 프레젠테이션 모드
pdfjs-open-file-button =
.title = 파일 열기
pdfjs-open-file-button-label = 열기
pdfjs-print-button =
.title = 인쇄
pdfjs-print-button-label = 인쇄
pdfjs-save-button =
.title = 저장
pdfjs-save-button-label = 저장
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = 다운로드
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = 다운로드
pdfjs-bookmark-button =
.title = 현재 페이지 (현재 페이지에서 URL 보기)
pdfjs-bookmark-button-label = 현재 페이지
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = 도구
pdfjs-tools-button-label = 도구
pdfjs-first-page-button =
.title = 첫 페이지로 이동
pdfjs-first-page-button-label = 첫 페이지로 이동
pdfjs-last-page-button =
.title = 마지막 페이지로 이동
pdfjs-last-page-button-label = 마지막 페이지로 이동
pdfjs-page-rotate-cw-button =
.title = 시계방향으로 회전
pdfjs-page-rotate-cw-button-label = 시계방향으로 회전
pdfjs-page-rotate-ccw-button =
.title = 시계 반대방향으로 회전
pdfjs-page-rotate-ccw-button-label = 시계 반대방향으로 회전
pdfjs-cursor-text-select-tool-button =
.title = 텍스트 선택 도구 활성화
pdfjs-cursor-text-select-tool-button-label = 텍스트 선택 도구
pdfjs-cursor-hand-tool-button =
.title = 손 도구 활성화
pdfjs-cursor-hand-tool-button-label = 손 도구
pdfjs-scroll-page-button =
.title = 페이지 스크롤 사용
pdfjs-scroll-page-button-label = 페이지 스크롤
pdfjs-scroll-vertical-button =
.title = 세로 스크롤 사용
pdfjs-scroll-vertical-button-label = 세로 스크롤
pdfjs-scroll-horizontal-button =
.title = 가로 스크롤 사용
pdfjs-scroll-horizontal-button-label = 가로 스크롤
pdfjs-scroll-wrapped-button =
.title = 래핑(자동 줄 바꿈) 스크롤 사용
pdfjs-scroll-wrapped-button-label = 래핑 스크롤
pdfjs-spread-none-button =
.title = 한 페이지 보기
pdfjs-spread-none-button-label = 펼침 없음
pdfjs-spread-odd-button =
.title = 홀수 페이지로 시작하는 두 페이지 보기
pdfjs-spread-odd-button-label = 홀수 펼침
pdfjs-spread-even-button =
.title = 짝수 페이지로 시작하는 두 페이지 보기
pdfjs-spread-even-button-label = 짝수 펼침
## Document properties dialog
pdfjs-document-properties-button =
.title = 문서 속성…
pdfjs-document-properties-button-label = 문서 속성…
pdfjs-document-properties-file-name = 파일 이름:
pdfjs-document-properties-file-size = 파일 크기:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } 바이트)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } 바이트)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b }바이트)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b }바이트)
pdfjs-document-properties-title = 제목:
pdfjs-document-properties-author = 작성자:
pdfjs-document-properties-subject = 주제:
pdfjs-document-properties-keywords = 키워드:
pdfjs-document-properties-creation-date = 작성 날짜:
pdfjs-document-properties-modification-date = 수정 날짜:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = 작성 프로그램:
pdfjs-document-properties-producer = PDF 변환 소프트웨어:
pdfjs-document-properties-version = PDF 버전:
pdfjs-document-properties-page-count = 페이지 수:
pdfjs-document-properties-page-size = 페이지 크기:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = 세로 방향
pdfjs-document-properties-page-size-orientation-landscape = 가로 방향
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = 레터
pdfjs-document-properties-page-size-name-legal = 리걸
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = 빠른 웹 보기:
pdfjs-document-properties-linearized-yes = 예
pdfjs-document-properties-linearized-no = 아니요
pdfjs-document-properties-close-button = 닫기
## Print
pdfjs-print-progress-message = 인쇄 문서 준비 중…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = 취소
pdfjs-printing-not-supported = 경고: 이 브라우저는 인쇄를 완전히 지원하지 않습니다.
pdfjs-printing-not-ready = 경고: 이 PDF를 인쇄를 할 수 있을 정도로 읽어들이지 못했습니다.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = 사이드바 표시/숨기기
pdfjs-toggle-sidebar-notification-button =
.title = 사이드바 표시/숨기기 (문서에 아웃라인/첨부파일/레이어 포함됨)
pdfjs-toggle-sidebar-button-label = 사이드바 표시/숨기기
pdfjs-document-outline-button =
.title = 문서 아웃라인 보기 (더블 클릭해서 모든 항목 펼치기/접기)
pdfjs-document-outline-button-label = 문서 아웃라인
pdfjs-attachments-button =
.title = 첨부파일 보기
pdfjs-attachments-button-label = 첨부파일
pdfjs-layers-button =
.title = 레이어 보기 (더블 클릭해서 모든 레이어를 기본 상태로 재설정)
pdfjs-layers-button-label = 레이어
pdfjs-thumbs-button =
.title = 미리보기
pdfjs-thumbs-button-label = 미리보기
pdfjs-current-outline-item-button =
.title = 현재 아웃라인 항목 찾기
pdfjs-current-outline-item-button-label = 현재 아웃라인 항목
pdfjs-findbar-button =
.title = 검색
pdfjs-findbar-button-label = 검색
pdfjs-additional-layers = 추가 레이어
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = { $page } 페이지
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = { $page } 페이지 미리보기
## Find panel button title and messages
pdfjs-find-input =
.title = 찾기
.placeholder = 문서에서 찾기…
pdfjs-find-previous-button =
.title = 지정 문자열에 일치하는 1개 부분을 검색
pdfjs-find-previous-button-label = 이전
pdfjs-find-next-button =
.title = 지정 문자열에 일치하는 다음 부분을 검색
pdfjs-find-next-button-label = 다음
pdfjs-find-highlight-checkbox = 모두 강조 표시
pdfjs-find-match-case-checkbox-label = 대/소문자 구분
pdfjs-find-match-diacritics-checkbox-label = 분음 부호 일치
pdfjs-find-entire-word-checkbox-label = 단어 단위로
pdfjs-find-reached-top = 문서 처음까지 검색하고 끝으로 돌아와 검색했습니다.
pdfjs-find-reached-bottom = 문서 끝까지 검색하고 앞으로 돌아와 검색했습니다.
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count = { $current } / { $total } 일치
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit = { $limit }개 이상 일치
pdfjs-find-not-found = 검색 결과 없음
## Predefined zoom values
pdfjs-page-scale-width = 페이지 너비에 맞추기
pdfjs-page-scale-fit = 페이지에 맞추기
pdfjs-page-scale-auto = 자동
pdfjs-page-scale-actual = 실제 크기
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = { $page } 페이지
## Loading indicator messages
pdfjs-loading-error = PDF를 로드하는 동안 오류가 발생했습니다.
pdfjs-invalid-file-error = 잘못되었거나 손상된 PDF 파일.
pdfjs-missing-file-error = PDF 파일 없음.
pdfjs-unexpected-response-error = 예기치 않은 서버 응답입니다.
pdfjs-rendering-error = 페이지를 렌더링하는 동안 오류가 발생했습니다.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date } { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } 주석]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = 이 PDF 파일을 열 수 있는 비밀번호를 입력하세요.
pdfjs-password-invalid = 잘못된 비밀번호입니다. 다시 시도하세요.
pdfjs-password-ok-button = 확인
pdfjs-password-cancel-button = 취소
pdfjs-web-fonts-disabled = 웹 폰트가 비활성화됨: 내장된 PDF 글꼴을 사용할 수 없습니다.
## Editing
pdfjs-editor-free-text-button =
.title = 텍스트
pdfjs-editor-free-text-button-label = 텍스트
pdfjs-editor-ink-button =
.title = 그리기
pdfjs-editor-ink-button-label = 그리기
pdfjs-editor-stamp-button =
.title = 이미지 추가 또는 편집
pdfjs-editor-stamp-button-label = 이미지 추가 또는 편집
pdfjs-editor-highlight-button =
.title = 강조 표시
pdfjs-editor-highlight-button-label = 강조 표시
pdfjs-highlight-floating-button1 =
.title = 강조 표시
.aria-label = 강조 표시
pdfjs-highlight-floating-button-label = 강조 표시
pdfjs-editor-signature-button =
.title = 서명 추가
pdfjs-editor-signature-button-label = 서명 추가
## Default editor aria labels
# “Highlight” is a noun, the string is used on the editor for highlights.
pdfjs-editor-highlight-editor =
.aria-label = 강조 표시 편집기
# “Drawing” is a noun, the string is used on the editor for drawings.
pdfjs-editor-ink-editor =
.aria-label = 그리기 편집기
pdfjs-editor-signature-editor =
.aria-label = 서명 편집기
pdfjs-editor-stamp-editor =
.aria-label = 이미지 편집기
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = 그리기 제거
pdfjs-editor-remove-freetext-button =
.title = 텍스트 제거
pdfjs-editor-remove-stamp-button =
.title = 이미지 제거
pdfjs-editor-remove-highlight-button =
.title = 강조 표시 제거
pdfjs-editor-remove-signature-button =
.title = 서명 제거
##
# Editor Parameters
pdfjs-editor-free-text-color-input = 색상
pdfjs-editor-free-text-size-input = 크기
pdfjs-editor-ink-color-input = 색상
pdfjs-editor-ink-thickness-input = 두께
pdfjs-editor-ink-opacity-input = 불투명도
pdfjs-editor-stamp-add-image-button =
.title = 이미지 추가
pdfjs-editor-stamp-add-image-button-label = 이미지 추가
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = 두께
pdfjs-editor-free-highlight-thickness-title =
.title = 텍스트 이외의 항목을 강조 표시할 때 두께 변경
pdfjs-editor-add-signature-container =
.aria-label = 서명 제어 및 저장된 서명
pdfjs-editor-signature-add-signature-button =
.title = 새 서명 추가
pdfjs-editor-signature-add-signature-button-label = 새 서명 추가
# Used on the button to use an already saved signature.
# Variables:
# $description (String) - a string describing/labeling the signature.
pdfjs-editor-add-saved-signature-button =
.title = 저장된 서명: { $description }
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = 텍스트 편집기
.default-content = 입력을 시작하세요…
pdfjs-free-text =
.aria-label = 텍스트 편집기
pdfjs-free-text-default-content = 입력하세요…
pdfjs-ink =
.aria-label = 그리기 편집기
pdfjs-ink-canvas =
.aria-label = 사용자 생성 이미지
## Alt-text dialog
pdfjs-editor-alt-text-button-label = 대체 텍스트
pdfjs-editor-alt-text-edit-button =
.aria-label = 대체 텍스트 편집
pdfjs-editor-alt-text-edit-button-label = 대체 텍스트 편집
pdfjs-editor-alt-text-dialog-label = 옵션을 선택하세요
pdfjs-editor-alt-text-dialog-description = 대체 텍스트는 사람들이 이미지를 볼 수 없거나 이미지가 로드되지 않을 때 도움이 됩니다.
pdfjs-editor-alt-text-add-description-label = 설명 추가
pdfjs-editor-alt-text-add-description-description = 주제, 설정, 동작을 설명하는 1~2개의 문장을 목표로 하세요.
pdfjs-editor-alt-text-mark-decorative-label = 장식용으로 표시
pdfjs-editor-alt-text-mark-decorative-description = 테두리나 워터마크와 같은 장식적인 이미지에 사용됩니다.
pdfjs-editor-alt-text-cancel-button = 취소
pdfjs-editor-alt-text-save-button = 저장
pdfjs-editor-alt-text-decorative-tooltip = 장식용으로 표시됨
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = 예를 들어, “한 청년이 식탁에 앉아 식사를 하고 있습니다.”
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = 대체 텍스트
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = 왼쪽 위 — 크기 조정
pdfjs-editor-resizer-label-top-middle = 가운데 위 - 크기 조정
pdfjs-editor-resizer-label-top-right = 오른쪽 위 — 크기 조정
pdfjs-editor-resizer-label-middle-right = 오른쪽 가운데 — 크기 조정
pdfjs-editor-resizer-label-bottom-right = 오른쪽 아래 - 크기 조정
pdfjs-editor-resizer-label-bottom-middle = 가운데 아래 — 크기 조정
pdfjs-editor-resizer-label-bottom-left = 왼쪽 아래 - 크기 조정
pdfjs-editor-resizer-label-middle-left = 왼쪽 가운데 — 크기 조정
pdfjs-editor-resizer-top-left =
.aria-label = 왼쪽 위 — 크기 조정
pdfjs-editor-resizer-top-middle =
.aria-label = 가운데 위 - 크기 조정
pdfjs-editor-resizer-top-right =
.aria-label = 오른쪽 위 — 크기 조정
pdfjs-editor-resizer-middle-right =
.aria-label = 오른쪽 가운데 — 크기 조정
pdfjs-editor-resizer-bottom-right =
.aria-label = 오른쪽 아래 - 크기 조정
pdfjs-editor-resizer-bottom-middle =
.aria-label = 가운데 아래 — 크기 조정
pdfjs-editor-resizer-bottom-left =
.aria-label = 왼쪽 아래 - 크기 조정
pdfjs-editor-resizer-middle-left =
.aria-label = 왼쪽 가운데 — 크기 조정
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = 색상
pdfjs-editor-colorpicker-button =
.title = 색상 변경
pdfjs-editor-colorpicker-dropdown =
.aria-label = 색상 선택
pdfjs-editor-colorpicker-yellow =
.title = 노란색
pdfjs-editor-colorpicker-green =
.title = 녹색
pdfjs-editor-colorpicker-blue =
.title = 파란색
pdfjs-editor-colorpicker-pink =
.title = 분홍색
pdfjs-editor-colorpicker-red =
.title = 빨간색
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = 모두 보기
pdfjs-editor-highlight-show-all-button =
.title = 모두 보기
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = 대체 텍스트 (이미지 설명) 편집
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = 대체 텍스트 (이미지 설명) 추가
pdfjs-editor-new-alt-text-textarea =
.placeholder = 여기에 설명을 작성하세요…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = 이미지가 보이지 않거나 이미지가 로딩되지 않는 경우를 위한 간단한 설명입니다.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = 이 대체 텍스트는 자동으로 생성되었으므로 정확하지 않을 수 있습니다.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = 더 알아보기
pdfjs-editor-new-alt-text-create-automatically-button-label = 자동으로 대체 텍스트 생성
pdfjs-editor-new-alt-text-not-now-button = 나중에
pdfjs-editor-new-alt-text-error-title = 대체 텍스트를 자동으로 생성할 수 없습니다.
pdfjs-editor-new-alt-text-error-description = 대체 텍스트를 직접 작성하거나 나중에 다시 시도하세요.
pdfjs-editor-new-alt-text-error-close-button = 닫기
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = 대체 텍스트 AI 모델 다운로드 중 ({ $downloadedSize } / { $totalSize } MB)
.aria-valuetext = 대체 텍스트 AI 모델 다운로드 중 ({ $downloadedSize } / { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = 대체 텍스트 추가됨
pdfjs-editor-new-alt-text-added-button-label = 대체 텍스트 추가됨
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = 대체 텍스트 누락
pdfjs-editor-new-alt-text-missing-button-label = 대체 텍스트 누락
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = 대체 텍스트 검토
pdfjs-editor-new-alt-text-to-review-button-label = 대체 텍스트 검토
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = 자동으로 생성됨: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = 이미지 대체 텍스트 설정
pdfjs-image-alt-text-settings-button-label = 이미지 대체 텍스트 설정
pdfjs-editor-alt-text-settings-dialog-label = 이미지 대체 텍스트 설정
pdfjs-editor-alt-text-settings-automatic-title = 자동 대체 텍스트
pdfjs-editor-alt-text-settings-create-model-button-label = 자동으로 대체 텍스트 생성
pdfjs-editor-alt-text-settings-create-model-description = 이미지가 보이지 않거나 이미지가 로딩되지 않을 때 도움이 되는 설명을 제안합니다.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = 대체 텍스트 AI 모델 ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = 사용자의 장치에서 로컬로 실행되므로 데이터가 비공개로 유지됩니다. 자동 대체 텍스트에 필요합니다.
pdfjs-editor-alt-text-settings-delete-model-button = 삭제
pdfjs-editor-alt-text-settings-download-model-button = 다운로드
pdfjs-editor-alt-text-settings-downloading-model-button = 다운로드 중…
pdfjs-editor-alt-text-settings-editor-title = 대체 텍스트 편집기
pdfjs-editor-alt-text-settings-show-dialog-button-label = 이미지 추가 시 바로 대체 텍스트 편집기 표시
pdfjs-editor-alt-text-settings-show-dialog-description = 모든 이미지에 대체 텍스트가 있는지 확인하는 데 도움이 됩니다.
pdfjs-editor-alt-text-settings-close-button = 닫기
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = 강조 표시 제거됨
pdfjs-editor-undo-bar-message-freetext = 텍스트 제거됨
pdfjs-editor-undo-bar-message-ink = 그리기 제거됨
pdfjs-editor-undo-bar-message-stamp = 이미지 제거됨
pdfjs-editor-undo-bar-message-signature = 서명 제거됨
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple = 주석 { $count }개 제거됨
pdfjs-editor-undo-bar-undo-button =
.title = 실행 취소
pdfjs-editor-undo-bar-undo-button-label = 실행 취소
pdfjs-editor-undo-bar-close-button =
.title = 닫기
pdfjs-editor-undo-bar-close-button-label = 닫기
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = 이 모달로 PDF 문서에 추가 할 서명을 만들 수 있습니다. 사용자는 이름(대체 텍스트 역할도 함)을 편집하고, 반복해 사용할 수 있도록 서명을 저장할 수도 있습니다.
pdfjs-editor-add-signature-dialog-title = 서명 추가
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = 입력
.title = 입력
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = 그리기
.title = 그리기
pdfjs-editor-add-signature-image-button = 이미지
.title = 이미지
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = 서명 입력
.placeholder = 서명 입력
pdfjs-editor-add-signature-draw-placeholder = 서명 그리기
pdfjs-editor-add-signature-draw-thickness-range-label = 두께
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = 그리기 두께: { $thickness }
pdfjs-editor-add-signature-image-placeholder = 업로드할 파일을 여기로 끌어서 놓기
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] 또는 이미지 파일 찾아보기
*[other] 또는 이미지 파일 찾아보기
}
## Controls
pdfjs-editor-add-signature-description-label = 설명 (대체 텍스트)
pdfjs-editor-add-signature-description-input =
.title = 설명 (대체 텍스트)
pdfjs-editor-add-signature-description-default-when-drawing = 서명
pdfjs-editor-add-signature-clear-button-label = 서명 지우기
pdfjs-editor-add-signature-clear-button =
.title = 서명 지우기
pdfjs-editor-add-signature-save-checkbox = 서명 저장
pdfjs-editor-add-signature-save-warning-message = 저장된 서명의 한계에 도달했습니다. 더 저장하려면 하나를 제거하세요.
pdfjs-editor-add-signature-image-upload-error-title = 이미지를 업로드할 수 없음
pdfjs-editor-add-signature-image-upload-error-description = 네트워크 연결을 확인하거나 다른 이미지로 시도하세요.
pdfjs-editor-add-signature-error-close-button = 닫기
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = 취소
pdfjs-editor-add-signature-add-button = 추가
pdfjs-editor-edit-signature-update-button = 업데이트
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = 서명 제거
pdfjs-editor-delete-signature-button-label = 서명 제거
pdfjs-editor-delete-signature-button1 =
.title = 저장된 서명 제거
pdfjs-editor-delete-signature-button-label1 = 저장된 서명 제거
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = 설명 편집
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = 설명 편집
================================================
FILE: cookbook/static/pdfjs/web/locale/lij/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Pagina primma
pdfjs-previous-button-label = Precedente
pdfjs-next-button =
.title = Pagina dòppo
pdfjs-next-button-label = Pròscima
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Pagina
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = de { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } de { $pagesCount })
pdfjs-zoom-out-button =
.title = Diminoisci zoom
pdfjs-zoom-out-button-label = Diminoisci zoom
pdfjs-zoom-in-button =
.title = Aomenta zoom
pdfjs-zoom-in-button-label = Aomenta zoom
pdfjs-zoom-select =
.title = Zoom
pdfjs-presentation-mode-button =
.title = Vanni into mòddo de prezentaçion
pdfjs-presentation-mode-button-label = Mòddo de prezentaçion
pdfjs-open-file-button =
.title = Arvi file
pdfjs-open-file-button-label = Arvi
pdfjs-print-button =
.title = Stanpa
pdfjs-print-button-label = Stanpa
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Atressi
pdfjs-tools-button-label = Atressi
pdfjs-first-page-button =
.title = Vanni a-a primma pagina
pdfjs-first-page-button-label = Vanni a-a primma pagina
pdfjs-last-page-button =
.title = Vanni a l'urtima pagina
pdfjs-last-page-button-label = Vanni a l'urtima pagina
pdfjs-page-rotate-cw-button =
.title = Gia into verso oraio
pdfjs-page-rotate-cw-button-label = Gia into verso oraio
pdfjs-page-rotate-ccw-button =
.title = Gia into verso antioraio
pdfjs-page-rotate-ccw-button-label = Gia into verso antioraio
pdfjs-cursor-text-select-tool-button =
.title = Abilita strumento de seleçion do testo
pdfjs-cursor-text-select-tool-button-label = Strumento de seleçion do testo
pdfjs-cursor-hand-tool-button =
.title = Abilita strumento man
pdfjs-cursor-hand-tool-button-label = Strumento man
pdfjs-scroll-vertical-button =
.title = Deuvia rebelamento verticale
pdfjs-scroll-vertical-button-label = Rebelamento verticale
pdfjs-scroll-horizontal-button =
.title = Deuvia rebelamento orizontâ
pdfjs-scroll-horizontal-button-label = Rebelamento orizontâ
pdfjs-scroll-wrapped-button =
.title = Deuvia rebelamento incapsolou
pdfjs-scroll-wrapped-button-label = Rebelamento incapsolou
pdfjs-spread-none-button =
.title = No unite a-a difuxon de pagina
pdfjs-spread-none-button-label = No difuxon
pdfjs-spread-odd-button =
.title = Uniscite a-a difuxon de pagina co-o numero dèspa
pdfjs-spread-odd-button-label = Difuxon dèspa
pdfjs-spread-even-button =
.title = Uniscite a-a difuxon de pagina co-o numero pari
pdfjs-spread-even-button-label = Difuxon pari
## Document properties dialog
pdfjs-document-properties-button =
.title = Propietæ do documento…
pdfjs-document-properties-button-label = Propietæ do documento…
pdfjs-document-properties-file-name = Nomme schedaio:
pdfjs-document-properties-file-size = Dimenscion schedaio:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } kB ({ $size_b } byte)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } byte)
pdfjs-document-properties-title = Titolo:
pdfjs-document-properties-author = Aoto:
pdfjs-document-properties-subject = Ogetto:
pdfjs-document-properties-keywords = Paròlle ciave:
pdfjs-document-properties-creation-date = Dæta creaçion:
pdfjs-document-properties-modification-date = Dæta cangiamento:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Aotô originale:
pdfjs-document-properties-producer = Produtô PDF:
pdfjs-document-properties-version = Verscion PDF:
pdfjs-document-properties-page-count = Contezzo pagine:
pdfjs-document-properties-page-size = Dimenscion da pagina:
pdfjs-document-properties-page-size-unit-inches = dii gròsci
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = drito
pdfjs-document-properties-page-size-orientation-landscape = desteizo
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letia
pdfjs-document-properties-page-size-name-legal = Lezze
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Vista veloce do Web:
pdfjs-document-properties-linearized-yes = Sci
pdfjs-document-properties-linearized-no = No
pdfjs-document-properties-close-button = Særa
## Print
pdfjs-print-progress-message = Praparo o documento pe-a stanpa…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Anulla
pdfjs-printing-not-supported = Atençion: a stanpa a no l'é conpletamente soportâ da sto navegatô.
pdfjs-printing-not-ready = Atençion: o PDF o no l'é ancon caregou conpletamente pe-a stanpa.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Ativa/dizativa bara de scianco
pdfjs-toggle-sidebar-button-label = Ativa/dizativa bara de scianco
pdfjs-document-outline-button =
.title = Fanni vedde o contorno do documento (scicca doggio pe espande/ridue tutti i elementi)
pdfjs-document-outline-button-label = Contorno do documento
pdfjs-attachments-button =
.title = Fanni vedde alegæ
pdfjs-attachments-button-label = Alegæ
pdfjs-thumbs-button =
.title = Mostra miniatue
pdfjs-thumbs-button-label = Miniatue
pdfjs-findbar-button =
.title = Treuva into documento
pdfjs-findbar-button-label = Treuva
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Pagina { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Miniatua da pagina { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Treuva
.placeholder = Treuva into documento…
pdfjs-find-previous-button =
.title = Treuva a ripetiçion precedente do testo da çercâ
pdfjs-find-previous-button-label = Precedente
pdfjs-find-next-button =
.title = Treuva a ripetiçion dòppo do testo da çercâ
pdfjs-find-next-button-label = Segoente
pdfjs-find-highlight-checkbox = Evidençia
pdfjs-find-match-case-checkbox-label = Maioscole/minoscole
pdfjs-find-entire-word-checkbox-label = Poula intrega
pdfjs-find-reached-top = Razonto a fin da pagina, continoa da l'iniçio
pdfjs-find-reached-bottom = Razonto l'iniçio da pagina, continoa da-a fin
pdfjs-find-not-found = Testo no trovou
## Predefined zoom values
pdfjs-page-scale-width = Larghessa pagina
pdfjs-page-scale-fit = Adatta a una pagina
pdfjs-page-scale-auto = Zoom aotomatico
pdfjs-page-scale-actual = Dimenscioin efetive
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
## Loading indicator messages
pdfjs-loading-error = S'é verificou 'n'erô itno caregamento do PDF.
pdfjs-invalid-file-error = O schedaio PDF o l'é no valido ò aroinou.
pdfjs-missing-file-error = O schedaio PDF o no gh'é.
pdfjs-unexpected-response-error = Risposta inprevista do-u server
pdfjs-rendering-error = Gh'é stæto 'n'erô itno rendering da pagina.
## Annotations
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [Anotaçion: { $type }]
## Password
pdfjs-password-label = Dimme a paròlla segreta pe arvî sto schedaio PDF.
pdfjs-password-invalid = Paròlla segreta sbalia. Preuva torna.
pdfjs-password-ok-button = Va ben
pdfjs-password-cancel-button = Anulla
pdfjs-web-fonts-disabled = I font do web en dizativæ: inposcibile adeuviâ i carateri do PDF.
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/lo/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = ຫນ້າກ່ອນຫນ້າ
pdfjs-previous-button-label = ກ່ອນຫນ້າ
pdfjs-next-button =
.title = ຫນ້າຖັດໄປ
pdfjs-next-button-label = ຖັດໄປ
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = ຫນ້າ
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = ຈາກ { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } ຈາກ { $pagesCount })
pdfjs-zoom-out-button =
.title = ຂະຫຍາຍອອກ
pdfjs-zoom-out-button-label = ຂະຫຍາຍອອກ
pdfjs-zoom-in-button =
.title = ຂະຫຍາຍເຂົ້າ
pdfjs-zoom-in-button-label = ຂະຫຍາຍເຂົ້າ
pdfjs-zoom-select =
.title = ຂະຫຍາຍ
pdfjs-presentation-mode-button =
.title = ສັບປ່ຽນເປັນໂຫມດການນຳສະເຫນີ
pdfjs-presentation-mode-button-label = ໂຫມດການນຳສະເຫນີ
pdfjs-open-file-button =
.title = ເປີດໄຟລ໌
pdfjs-open-file-button-label = ເປີດ
pdfjs-print-button =
.title = ພິມ
pdfjs-print-button-label = ພິມ
pdfjs-save-button =
.title = ບັນທຶກ
pdfjs-save-button-label = ບັນທຶກ
pdfjs-bookmark-button =
.title = ໜ້າປັດຈຸບັນ (ເບິ່ງ URL ຈາກໜ້າປັດຈຸບັນ)
pdfjs-bookmark-button-label = ຫນ້າປັດຈຸບັນ
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = ເຄື່ອງມື
pdfjs-tools-button-label = ເຄື່ອງມື
pdfjs-first-page-button =
.title = ໄປທີ່ຫນ້າທຳອິດ
pdfjs-first-page-button-label = ໄປທີ່ຫນ້າທຳອິດ
pdfjs-last-page-button =
.title = ໄປທີ່ຫນ້າສຸດທ້າຍ
pdfjs-last-page-button-label = ໄປທີ່ຫນ້າສຸດທ້າຍ
pdfjs-page-rotate-cw-button =
.title = ຫມູນຕາມເຂັມໂມງ
pdfjs-page-rotate-cw-button-label = ຫມູນຕາມເຂັມໂມງ
pdfjs-page-rotate-ccw-button =
.title = ຫມູນທວນເຂັມໂມງ
pdfjs-page-rotate-ccw-button-label = ຫມູນທວນເຂັມໂມງ
pdfjs-cursor-text-select-tool-button =
.title = ເປີດໃຊ້ເຄື່ອງມືການເລືອກຂໍ້ຄວາມ
pdfjs-cursor-text-select-tool-button-label = ເຄື່ອງມືເລືອກຂໍ້ຄວາມ
pdfjs-cursor-hand-tool-button =
.title = ເປີດໃຊ້ເຄື່ອງມືມື
pdfjs-cursor-hand-tool-button-label = ເຄື່ອງມືມື
pdfjs-scroll-page-button =
.title = ໃຊ້ການເລື່ອນໜ້າ
pdfjs-scroll-page-button-label = ເລື່ອນໜ້າ
pdfjs-scroll-vertical-button =
.title = ໃຊ້ການເລື່ອນແນວຕັ້ງ
pdfjs-scroll-vertical-button-label = ເລື່ອນແນວຕັ້ງ
pdfjs-scroll-horizontal-button =
.title = ໃຊ້ການເລື່ອນແນວນອນ
pdfjs-scroll-horizontal-button-label = ເລື່ອນແນວນອນ
pdfjs-scroll-wrapped-button =
.title = ໃຊ້ Wrapped Scrolling
pdfjs-scroll-wrapped-button-label = Wrapped Scrolling
pdfjs-spread-none-button =
.title = ບໍ່ຕ້ອງຮ່ວມການແຜ່ກະຈາຍຫນ້າ
pdfjs-spread-none-button-label = ບໍ່ມີການແຜ່ກະຈາຍ
pdfjs-spread-odd-button =
.title = ເຂົ້າຮ່ວມການແຜ່ກະຈາຍຫນ້າເລີ່ມຕົ້ນດ້ວຍຫນ້າເລກຄີກ
pdfjs-spread-odd-button-label = ການແຜ່ກະຈາຍຄີກ
pdfjs-spread-even-button =
.title = ເຂົ້າຮ່ວມການແຜ່ກະຈາຍຂອງຫນ້າເລີ່ມຕົ້ນດ້ວຍຫນ້າເລກຄູ່
pdfjs-spread-even-button-label = ການແຜ່ກະຈາຍຄູ່
## Document properties dialog
pdfjs-document-properties-button =
.title = ຄຸນສົມບັດເອກະສານ...
pdfjs-document-properties-button-label = ຄຸນສົມບັດເອກະສານ...
pdfjs-document-properties-file-name = ຊື່ໄຟລ໌:
pdfjs-document-properties-file-size = ຂະຫນາດໄຟລ໌:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } ໄບຕ໌)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } ໄບຕ໌)
pdfjs-document-properties-title = ຫົວຂໍ້:
pdfjs-document-properties-author = ຜູ້ຂຽນ:
pdfjs-document-properties-subject = ຫົວຂໍ້:
pdfjs-document-properties-keywords = ຄໍາທີ່ຕ້ອງການຄົ້ນຫາ:
pdfjs-document-properties-creation-date = ວັນທີສ້າງ:
pdfjs-document-properties-modification-date = ວັນທີແກ້ໄຂ:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = ຜູ້ສ້າງ:
pdfjs-document-properties-producer = ຜູ້ຜະລິດ PDF:
pdfjs-document-properties-version = ເວີຊັ່ນ PDF:
pdfjs-document-properties-page-count = ຈຳນວນໜ້າ:
pdfjs-document-properties-page-size = ຂະໜາດໜ້າ:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = ລວງຕັ້ງ
pdfjs-document-properties-page-size-orientation-landscape = ລວງນອນ
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = ຈົດໝາຍ
pdfjs-document-properties-page-size-name-legal = ຂໍ້ກົດຫມາຍ
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = ມຸມມອງເວັບທີ່ໄວ:
pdfjs-document-properties-linearized-yes = ແມ່ນ
pdfjs-document-properties-linearized-no = ບໍ່
pdfjs-document-properties-close-button = ປິດ
## Print
pdfjs-print-progress-message = ກຳລັງກະກຽມເອກະສານສຳລັບການພິມ...
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = ຍົກເລີກ
pdfjs-printing-not-supported = ຄຳເຕືອນ: ບຼາວເຊີນີ້ບໍ່ຮອງຮັບການພິມຢ່າງເຕັມທີ່.
pdfjs-printing-not-ready = ຄໍາເຕືອນ: PDF ບໍ່ໄດ້ຖືກໂຫຼດຢ່າງເຕັມທີ່ສໍາລັບການພິມ.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = ເປີດ/ປິດແຖບຂ້າງ
pdfjs-toggle-sidebar-notification-button =
.title = ສະຫຼັບແຖບດ້ານຂ້າງ (ເອກະສານປະກອບມີໂຄງຮ່າງ/ໄຟລ໌ແນບ/ຊັ້ນຂໍ້ມູນ)
pdfjs-toggle-sidebar-button-label = ເປີດ/ປິດແຖບຂ້າງ
pdfjs-document-outline-button =
.title = ສະແດງໂຄງຮ່າງເອກະສານ (ກົດສອງຄັ້ງເພື່ອຂະຫຍາຍ / ຫຍໍ້ລາຍການທັງຫມົດ)
pdfjs-document-outline-button-label = ເຄົ້າຮ່າງເອກະສານ
pdfjs-attachments-button =
.title = ສະແດງໄຟລ໌ແນບ
pdfjs-attachments-button-label = ໄຟລ໌ແນບ
pdfjs-layers-button =
.title = ສະແດງຊັ້ນຂໍ້ມູນ (ຄລິກສອງເທື່ອເພື່ອຣີເຊັດຊັ້ນຂໍ້ມູນທັງໝົດໃຫ້ເປັນສະຖານະເລີ່ມຕົ້ນ)
pdfjs-layers-button-label = ຊັ້ນ
pdfjs-thumbs-button =
.title = ສະແດງຮູບຫຍໍ້
pdfjs-thumbs-button-label = ຮູບຕົວຢ່າງ
pdfjs-current-outline-item-button =
.title = ຊອກຫາລາຍການໂຄງຮ່າງປະຈຸບັນ
pdfjs-current-outline-item-button-label = ລາຍການໂຄງຮ່າງປະຈຸບັນ
pdfjs-findbar-button =
.title = ຊອກຫາໃນເອກະສານ
pdfjs-findbar-button-label = ຄົ້ນຫາ
pdfjs-additional-layers = ຊັ້ນຂໍ້ມູນເພີ່ມເຕີມ
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = ໜ້າ { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = ຮູບຕົວຢ່າງຂອງໜ້າ { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = ຄົ້ນຫາ
.placeholder = ຊອກຫາໃນເອກະສານ...
pdfjs-find-previous-button =
.title = ຊອກຫາການປະກົດຕົວທີ່ຜ່ານມາຂອງປະໂຫຍກ
pdfjs-find-previous-button-label = ກ່ອນຫນ້ານີ້
pdfjs-find-next-button =
.title = ຊອກຫາຕຳແຫນ່ງຖັດໄປຂອງວະລີ
pdfjs-find-next-button-label = ຕໍ່ໄປ
pdfjs-find-highlight-checkbox = ໄຮໄລທ໌ທັງຫມົດ
pdfjs-find-match-case-checkbox-label = ກໍລະນີທີ່ກົງກັນ
pdfjs-find-match-diacritics-checkbox-label = ເຄື່ອງໝາຍກຳກັບການອອກສຽງກົງກັນ
pdfjs-find-entire-word-checkbox-label = ກົງກັນທຸກຄຳ
pdfjs-find-reached-top = ມາຮອດເທິງຂອງເອກະສານ, ສືບຕໍ່ຈາກລຸ່ມ
pdfjs-find-reached-bottom = ຮອດຕອນທ້າຍຂອງເອກະສານ, ສືບຕໍ່ຈາກເທິງ
pdfjs-find-not-found = ບໍ່ພົບວະລີທີ່ຕ້ອງການ
## Predefined zoom values
pdfjs-page-scale-width = ຄວາມກວ້າງໜ້າ
pdfjs-page-scale-fit = ໜ້າພໍດີ
pdfjs-page-scale-auto = ຊູມອັດຕະໂນມັດ
pdfjs-page-scale-actual = ຂະໜາດຕົວຈິງ
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = ໜ້າ { $page }
## Loading indicator messages
pdfjs-loading-error = ມີຂໍ້ຜິດພາດເກີດຂື້ນຂະນະທີ່ກຳລັງໂຫລດ PDF.
pdfjs-invalid-file-error = ໄຟລ໌ PDF ບໍ່ຖືກຕ້ອງຫລືເສຍຫາຍ.
pdfjs-missing-file-error = ບໍ່ມີໄຟລ໌ PDF.
pdfjs-unexpected-response-error = ການຕອບສະໜອງຂອງເຊີບເວີທີ່ບໍ່ຄາດຄິດ.
pdfjs-rendering-error = ມີຂໍ້ຜິດພາດເກີດຂື້ນຂະນະທີ່ກຳລັງເຣັນເດີຫນ້າ.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } ຄຳບັນຍາຍ]
## Password
pdfjs-password-label = ໃສ່ລະຫັດຜ່ານເພື່ອເປີດໄຟລ໌ PDF ນີ້.
pdfjs-password-invalid = ລະຫັດຜ່ານບໍ່ຖືກຕ້ອງ. ກະລຸນາລອງອີກຄັ້ງ.
pdfjs-password-ok-button = ຕົກລົງ
pdfjs-password-cancel-button = ຍົກເລີກ
pdfjs-web-fonts-disabled = ຟອນເວັບຖືກປິດໃຊ້ງານ: ບໍ່ສາມາດໃຊ້ຟອນ PDF ທີ່ຝັງໄວ້ໄດ້.
## Editing
pdfjs-editor-free-text-button =
.title = ຂໍ້ຄວາມ
pdfjs-editor-free-text-button-label = ຂໍ້ຄວາມ
pdfjs-editor-ink-button =
.title = ແຕ້ມ
pdfjs-editor-ink-button-label = ແຕ້ມ
## Default editor aria labels
## Remove button for the various kind of editor.
##
# Editor Parameters
pdfjs-editor-free-text-color-input = ສີ
pdfjs-editor-free-text-size-input = ຂະຫນາດ
pdfjs-editor-ink-color-input = ສີ
pdfjs-editor-ink-thickness-input = ຄວາມຫນາ
pdfjs-editor-ink-opacity-input = ຄວາມໂປ່ງໃສ
pdfjs-free-text =
.aria-label = ຕົວແກ້ໄຂຂໍ້ຄວາມ
pdfjs-free-text-default-content = ເລີ່ມພິມ...
pdfjs-ink =
.aria-label = ຕົວແກ້ໄຂຮູບແຕ້ມ
pdfjs-ink-canvas =
.aria-label = ຮູບພາບທີ່ຜູ້ໃຊ້ສ້າງ
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/locale.json
================================================
{"ach":"ach/viewer.ftl","af":"af/viewer.ftl","an":"an/viewer.ftl","ar":"ar/viewer.ftl","ast":"ast/viewer.ftl","az":"az/viewer.ftl","be":"be/viewer.ftl","bg":"bg/viewer.ftl","bn":"bn/viewer.ftl","bo":"bo/viewer.ftl","br":"br/viewer.ftl","brx":"brx/viewer.ftl","bs":"bs/viewer.ftl","ca":"ca/viewer.ftl","cak":"cak/viewer.ftl","ckb":"ckb/viewer.ftl","cs":"cs/viewer.ftl","cy":"cy/viewer.ftl","da":"da/viewer.ftl","de":"de/viewer.ftl","dsb":"dsb/viewer.ftl","el":"el/viewer.ftl","en-ca":"en-CA/viewer.ftl","en-gb":"en-GB/viewer.ftl","en-us":"en-US/viewer.ftl","eo":"eo/viewer.ftl","es-ar":"es-AR/viewer.ftl","es-cl":"es-CL/viewer.ftl","es-es":"es-ES/viewer.ftl","es-mx":"es-MX/viewer.ftl","et":"et/viewer.ftl","eu":"eu/viewer.ftl","fa":"fa/viewer.ftl","ff":"ff/viewer.ftl","fi":"fi/viewer.ftl","fr":"fr/viewer.ftl","fur":"fur/viewer.ftl","fy-nl":"fy-NL/viewer.ftl","ga-ie":"ga-IE/viewer.ftl","gd":"gd/viewer.ftl","gl":"gl/viewer.ftl","gn":"gn/viewer.ftl","gu-in":"gu-IN/viewer.ftl","he":"he/viewer.ftl","hi-in":"hi-IN/viewer.ftl","hr":"hr/viewer.ftl","hsb":"hsb/viewer.ftl","hu":"hu/viewer.ftl","hy-am":"hy-AM/viewer.ftl","hye":"hye/viewer.ftl","ia":"ia/viewer.ftl","id":"id/viewer.ftl","is":"is/viewer.ftl","it":"it/viewer.ftl","ja":"ja/viewer.ftl","ka":"ka/viewer.ftl","kab":"kab/viewer.ftl","kk":"kk/viewer.ftl","km":"km/viewer.ftl","kn":"kn/viewer.ftl","ko":"ko/viewer.ftl","lij":"lij/viewer.ftl","lo":"lo/viewer.ftl","lt":"lt/viewer.ftl","ltg":"ltg/viewer.ftl","lv":"lv/viewer.ftl","meh":"meh/viewer.ftl","mk":"mk/viewer.ftl","ml":"ml/viewer.ftl","mr":"mr/viewer.ftl","ms":"ms/viewer.ftl","my":"my/viewer.ftl","nb-no":"nb-NO/viewer.ftl","ne-np":"ne-NP/viewer.ftl","nl":"nl/viewer.ftl","nn-no":"nn-NO/viewer.ftl","oc":"oc/viewer.ftl","pa-in":"pa-IN/viewer.ftl","pl":"pl/viewer.ftl","pt-br":"pt-BR/viewer.ftl","pt-pt":"pt-PT/viewer.ftl","rm":"rm/viewer.ftl","ro":"ro/viewer.ftl","ru":"ru/viewer.ftl","sat":"sat/viewer.ftl","sc":"sc/viewer.ftl","scn":"scn/viewer.ftl","sco":"sco/viewer.ftl","si":"si/viewer.ftl","sk":"sk/viewer.ftl","skr":"skr/viewer.ftl","sl":"sl/viewer.ftl","son":"son/viewer.ftl","sq":"sq/viewer.ftl","sr":"sr/viewer.ftl","sv-se":"sv-SE/viewer.ftl","szl":"szl/viewer.ftl","ta":"ta/viewer.ftl","te":"te/viewer.ftl","tg":"tg/viewer.ftl","th":"th/viewer.ftl","tl":"tl/viewer.ftl","tr":"tr/viewer.ftl","trs":"trs/viewer.ftl","uk":"uk/viewer.ftl","ur":"ur/viewer.ftl","uz":"uz/viewer.ftl","vi":"vi/viewer.ftl","wo":"wo/viewer.ftl","xh":"xh/viewer.ftl","zh-cn":"zh-CN/viewer.ftl","zh-tw":"zh-TW/viewer.ftl"}
================================================
FILE: cookbook/static/pdfjs/web/locale/lt/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Ankstesnis puslapis
pdfjs-previous-button-label = Ankstesnis
pdfjs-next-button =
.title = Kitas puslapis
pdfjs-next-button-label = Kitas
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Puslapis
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = iš { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } iš { $pagesCount })
pdfjs-zoom-out-button =
.title = Sumažinti
pdfjs-zoom-out-button-label = Sumažinti
pdfjs-zoom-in-button =
.title = Padidinti
pdfjs-zoom-in-button-label = Padidinti
pdfjs-zoom-select =
.title = Mastelis
pdfjs-presentation-mode-button =
.title = Pereiti į pateikties veikseną
pdfjs-presentation-mode-button-label = Pateikties veiksena
pdfjs-open-file-button =
.title = Atverti failą
pdfjs-open-file-button-label = Atverti
pdfjs-print-button =
.title = Spausdinti
pdfjs-print-button-label = Spausdinti
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Priemonės
pdfjs-tools-button-label = Priemonės
pdfjs-first-page-button =
.title = Eiti į pirmą puslapį
pdfjs-first-page-button-label = Eiti į pirmą puslapį
pdfjs-last-page-button =
.title = Eiti į paskutinį puslapį
pdfjs-last-page-button-label = Eiti į paskutinį puslapį
pdfjs-page-rotate-cw-button =
.title = Pasukti pagal laikrodžio rodyklę
pdfjs-page-rotate-cw-button-label = Pasukti pagal laikrodžio rodyklę
pdfjs-page-rotate-ccw-button =
.title = Pasukti prieš laikrodžio rodyklę
pdfjs-page-rotate-ccw-button-label = Pasukti prieš laikrodžio rodyklę
pdfjs-cursor-text-select-tool-button =
.title = Įjungti teksto žymėjimo įrankį
pdfjs-cursor-text-select-tool-button-label = Teksto žymėjimo įrankis
pdfjs-cursor-hand-tool-button =
.title = Įjungti vilkimo įrankį
pdfjs-cursor-hand-tool-button-label = Vilkimo įrankis
pdfjs-scroll-page-button =
.title = Naudoti puslapio slinkimą
pdfjs-scroll-page-button-label = Puslapio slinkimas
pdfjs-scroll-vertical-button =
.title = Naudoti vertikalų slinkimą
pdfjs-scroll-vertical-button-label = Vertikalus slinkimas
pdfjs-scroll-horizontal-button =
.title = Naudoti horizontalų slinkimą
pdfjs-scroll-horizontal-button-label = Horizontalus slinkimas
pdfjs-scroll-wrapped-button =
.title = Naudoti išklotą slinkimą
pdfjs-scroll-wrapped-button-label = Išklotas slinkimas
pdfjs-spread-none-button =
.title = Nejungti puslapių į dvilapius
pdfjs-spread-none-button-label = Be dvilapių
pdfjs-spread-odd-button =
.title = Sujungti į dvilapius pradedant nelyginiais puslapiais
pdfjs-spread-odd-button-label = Nelyginiai dvilapiai
pdfjs-spread-even-button =
.title = Sujungti į dvilapius pradedant lyginiais puslapiais
pdfjs-spread-even-button-label = Lyginiai dvilapiai
## Document properties dialog
pdfjs-document-properties-button =
.title = Dokumento savybės…
pdfjs-document-properties-button-label = Dokumento savybės…
pdfjs-document-properties-file-name = Failo vardas:
pdfjs-document-properties-file-size = Failo dydis:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } B)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } B)
pdfjs-document-properties-title = Antraštė:
pdfjs-document-properties-author = Autorius:
pdfjs-document-properties-subject = Tema:
pdfjs-document-properties-keywords = Reikšminiai žodžiai:
pdfjs-document-properties-creation-date = Sukūrimo data:
pdfjs-document-properties-modification-date = Modifikavimo data:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Kūrėjas:
pdfjs-document-properties-producer = PDF generatorius:
pdfjs-document-properties-version = PDF versija:
pdfjs-document-properties-page-count = Puslapių skaičius:
pdfjs-document-properties-page-size = Puslapio dydis:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = stačias
pdfjs-document-properties-page-size-orientation-landscape = gulsčias
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Laiškas
pdfjs-document-properties-page-size-name-legal = Dokumentas
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Spartus žiniatinklio rodinys:
pdfjs-document-properties-linearized-yes = Taip
pdfjs-document-properties-linearized-no = Ne
pdfjs-document-properties-close-button = Užverti
## Print
pdfjs-print-progress-message = Dokumentas ruošiamas spausdinimui…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Atsisakyti
pdfjs-printing-not-supported = Dėmesio! Spausdinimas šioje naršyklėje nėra pilnai realizuotas.
pdfjs-printing-not-ready = Dėmesio! PDF failas dar nėra pilnai įkeltas spausdinimui.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Rodyti / slėpti šoninį polangį
pdfjs-toggle-sidebar-notification-button =
.title = Parankinė (dokumentas turi struktūrą / priedų / sluoksnių)
pdfjs-toggle-sidebar-button-label = Šoninis polangis
pdfjs-document-outline-button =
.title = Rodyti dokumento struktūrą (spustelėkite dukart norėdami išplėsti/suskleisti visus elementus)
pdfjs-document-outline-button-label = Dokumento struktūra
pdfjs-attachments-button =
.title = Rodyti priedus
pdfjs-attachments-button-label = Priedai
pdfjs-layers-button =
.title = Rodyti sluoksnius (spustelėkite dukart, norėdami atstatyti visus sluoksnius į numatytąją būseną)
pdfjs-layers-button-label = Sluoksniai
pdfjs-thumbs-button =
.title = Rodyti puslapių miniatiūras
pdfjs-thumbs-button-label = Miniatiūros
pdfjs-current-outline-item-button =
.title = Rasti dabartinį struktūros elementą
pdfjs-current-outline-item-button-label = Dabartinis struktūros elementas
pdfjs-findbar-button =
.title = Ieškoti dokumente
pdfjs-findbar-button-label = Rasti
pdfjs-additional-layers = Papildomi sluoksniai
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = { $page } puslapis
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = { $page } puslapio miniatiūra
## Find panel button title and messages
pdfjs-find-input =
.title = Rasti
.placeholder = Rasti dokumente…
pdfjs-find-previous-button =
.title = Ieškoti ankstesnio frazės egzemplioriaus
pdfjs-find-previous-button-label = Ankstesnis
pdfjs-find-next-button =
.title = Ieškoti tolesnio frazės egzemplioriaus
pdfjs-find-next-button-label = Tolesnis
pdfjs-find-highlight-checkbox = Viską paryškinti
pdfjs-find-match-case-checkbox-label = Skirti didžiąsias ir mažąsias raides
pdfjs-find-match-diacritics-checkbox-label = Skirti diakritinius ženklus
pdfjs-find-entire-word-checkbox-label = Ištisi žodžiai
pdfjs-find-reached-top = Pasiekus dokumento pradžią, paieška pratęsta nuo pabaigos
pdfjs-find-reached-bottom = Pasiekus dokumento pabaigą, paieška pratęsta nuo pradžios
pdfjs-find-not-found = Ieškoma frazė nerasta
## Predefined zoom values
pdfjs-page-scale-width = Priderinti prie lapo pločio
pdfjs-page-scale-fit = Pritaikyti prie lapo dydžio
pdfjs-page-scale-auto = Automatinis mastelis
pdfjs-page-scale-actual = Tikras dydis
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = { $page } puslapis
## Loading indicator messages
pdfjs-loading-error = Įkeliant PDF failą įvyko klaida.
pdfjs-invalid-file-error = Tai nėra PDF failas arba jis yra sugadintas.
pdfjs-missing-file-error = PDF failas nerastas.
pdfjs-unexpected-response-error = Netikėtas serverio atsakas.
pdfjs-rendering-error = Atvaizduojant puslapį įvyko klaida.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [„{ $type }“ tipo anotacija]
## Password
pdfjs-password-label = Įveskite slaptažodį šiam PDF failui atverti.
pdfjs-password-invalid = Slaptažodis neteisingas. Bandykite dar kartą.
pdfjs-password-ok-button = Gerai
pdfjs-password-cancel-button = Atsisakyti
pdfjs-web-fonts-disabled = Saityno šriftai išjungti – PDF faile esančių šriftų naudoti negalima.
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/ltg/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Īprīkšejā lopa
pdfjs-previous-button-label = Īprīkšejā
pdfjs-next-button =
.title = Nuokomuo lopa
pdfjs-next-button-label = Nuokomuo
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Lopa
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = nu { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } nu { $pagesCount })
pdfjs-zoom-out-button =
.title = Attuolynuot
pdfjs-zoom-out-button-label = Attuolynuot
pdfjs-zoom-in-button =
.title = Pītuvynuot
pdfjs-zoom-in-button-label = Pītuvynuot
pdfjs-zoom-select =
.title = Palelynuojums
pdfjs-presentation-mode-button =
.title = Puorslēgtīs iz Prezentacejis režymu
pdfjs-presentation-mode-button-label = Prezentacejis režyms
pdfjs-open-file-button =
.title = Attaiseit failu
pdfjs-open-file-button-label = Attaiseit
pdfjs-print-button =
.title = Drukuošona
pdfjs-print-button-label = Drukōt
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Reiki
pdfjs-tools-button-label = Reiki
pdfjs-first-page-button =
.title = Īt iz pyrmū lopu
pdfjs-first-page-button-label = Īt iz pyrmū lopu
pdfjs-last-page-button =
.title = Īt iz piedejū lopu
pdfjs-last-page-button-label = Īt iz piedejū lopu
pdfjs-page-rotate-cw-button =
.title = Pagrīzt pa pulksteni
pdfjs-page-rotate-cw-button-label = Pagrīzt pa pulksteni
pdfjs-page-rotate-ccw-button =
.title = Pagrīzt pret pulksteni
pdfjs-page-rotate-ccw-button-label = Pagrīzt pret pulksteni
pdfjs-cursor-text-select-tool-button =
.title = Aktivizēt teksta izvieles reiku
pdfjs-cursor-text-select-tool-button-label = Teksta izvieles reiks
pdfjs-cursor-hand-tool-button =
.title = Aktivēt rūkys reiku
pdfjs-cursor-hand-tool-button-label = Rūkys reiks
pdfjs-scroll-vertical-button =
.title = Izmontōt vertikalū ritinōšonu
pdfjs-scroll-vertical-button-label = Vertikalō ritinōšona
pdfjs-scroll-horizontal-button =
.title = Izmontōt horizontalū ritinōšonu
pdfjs-scroll-horizontal-button-label = Horizontalō ritinōšona
pdfjs-scroll-wrapped-button =
.title = Izmontōt mārūgojamū ritinōšonu
pdfjs-scroll-wrapped-button-label = Mārūgojamō ritinōšona
pdfjs-spread-none-button =
.title = Naizmontōt lopu atvāruma režimu
pdfjs-spread-none-button-label = Bez atvārumim
pdfjs-spread-odd-button =
.title = Izmontōt lopu atvārumus sōkut nu napōra numeru lopom
pdfjs-spread-odd-button-label = Napōra lopys pa kreisi
pdfjs-spread-even-button =
.title = Izmontōt lopu atvārumus sōkut nu pōra numeru lopom
pdfjs-spread-even-button-label = Pōra lopys pa kreisi
## Document properties dialog
pdfjs-document-properties-button =
.title = Dokumenta īstatiejumi…
pdfjs-document-properties-button-label = Dokumenta īstatiejumi…
pdfjs-document-properties-file-name = Faila nūsaukums:
pdfjs-document-properties-file-size = Faila izmārs:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } biti)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } biti)
pdfjs-document-properties-title = Nūsaukums:
pdfjs-document-properties-author = Autors:
pdfjs-document-properties-subject = Tema:
pdfjs-document-properties-keywords = Atslāgi vuordi:
pdfjs-document-properties-creation-date = Izveides datums:
pdfjs-document-properties-modification-date = lobuošonys datums:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Radeituojs:
pdfjs-document-properties-producer = PDF producents:
pdfjs-document-properties-version = PDF verseja:
pdfjs-document-properties-page-count = Lopu skaits:
pdfjs-document-properties-page-size = Lopas izmārs:
pdfjs-document-properties-page-size-unit-inches = collas
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = portreta orientaceja
pdfjs-document-properties-page-size-orientation-landscape = ainovys orientaceja
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letter
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Fast Web View:
pdfjs-document-properties-linearized-yes = Jā
pdfjs-document-properties-linearized-no = Nā
pdfjs-document-properties-close-button = Aiztaiseit
## Print
pdfjs-print-progress-message = Preparing document for printing…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Atceļt
pdfjs-printing-not-supported = Uzmaneibu: Drukuošona nu itei puorlūka dorbojās tikai daleji.
pdfjs-printing-not-ready = Uzmaneibu: PDF nav pilneibā īluodeits drukuošonai.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Puorslēgt suonu jūslu
pdfjs-toggle-sidebar-button-label = Puorslēgt suonu jūslu
pdfjs-document-outline-button =
.title = Show Document Outline (double-click to expand/collapse all items)
pdfjs-document-outline-button-label = Dokumenta saturs
pdfjs-attachments-button =
.title = Show Attachments
pdfjs-attachments-button-label = Attachments
pdfjs-thumbs-button =
.title = Paruodeit seiktālus
pdfjs-thumbs-button-label = Seiktāli
pdfjs-findbar-button =
.title = Mekleit dokumentā
pdfjs-findbar-button-label = Mekleit
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Lopa { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Lopys { $page } seiktāls
## Find panel button title and messages
pdfjs-find-input =
.title = Mekleit
.placeholder = Mekleit dokumentā…
pdfjs-find-previous-button =
.title = Atrast īprīkšejū
pdfjs-find-previous-button-label = Īprīkšejā
pdfjs-find-next-button =
.title = Atrast nuokamū
pdfjs-find-next-button-label = Nuokomuo
pdfjs-find-highlight-checkbox = Īkruosuot vysys
pdfjs-find-match-case-checkbox-label = Lelū, mozū burtu jiuteigs
pdfjs-find-reached-top = Sasnīgts dokumenta suokums, turpynojom nu beigom
pdfjs-find-reached-bottom = Sasnīgtys dokumenta beigys, turpynojom nu suokuma
pdfjs-find-not-found = Frāze nav atrosta
## Predefined zoom values
pdfjs-page-scale-width = Lopys plotumā
pdfjs-page-scale-fit = Ītylpynūt lopu
pdfjs-page-scale-auto = Automatiskais izmārs
pdfjs-page-scale-actual = Patīsais izmārs
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
## Loading indicator messages
pdfjs-loading-error = Īluodejūt PDF nūtyka klaida.
pdfjs-invalid-file-error = Nadereigs voi būjuots PDF fails.
pdfjs-missing-file-error = PDF fails nav atrosts.
pdfjs-unexpected-response-error = Unexpected server response.
pdfjs-rendering-error = Attālojūt lopu rodās klaida
## Annotations
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } Annotation]
## Password
pdfjs-password-label = Īvodit paroli, kab attaiseitu PDF failu.
pdfjs-password-invalid = Napareiza parole, raugit vēļreiz.
pdfjs-password-ok-button = Labi
pdfjs-password-cancel-button = Atceļt
pdfjs-web-fonts-disabled = Šķārsteikla fonti nav aktivizāti: Navar īgult PDF fontus.
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/lv/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Iepriekšējā lapa
pdfjs-previous-button-label = Iepriekšējā
pdfjs-next-button =
.title = Nākamā lapa
pdfjs-next-button-label = Nākamā
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Lapa
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = no { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } no { $pagesCount })
pdfjs-zoom-out-button =
.title = Attālināt
pdfjs-zoom-out-button-label = Attālināt
pdfjs-zoom-in-button =
.title = Pietuvināt
pdfjs-zoom-in-button-label = Pietuvināt
pdfjs-zoom-select =
.title = Palielinājums
pdfjs-presentation-mode-button =
.title = Pārslēgties uz Prezentācijas režīmu
pdfjs-presentation-mode-button-label = Prezentācijas režīms
pdfjs-open-file-button =
.title = Atvērt failu
pdfjs-open-file-button-label = Atvērt
pdfjs-print-button =
.title = Drukāšana
pdfjs-print-button-label = Drukāt
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Rīki
pdfjs-tools-button-label = Rīki
pdfjs-first-page-button =
.title = Iet uz pirmo lapu
pdfjs-first-page-button-label = Iet uz pirmo lapu
pdfjs-last-page-button =
.title = Iet uz pēdējo lapu
pdfjs-last-page-button-label = Iet uz pēdējo lapu
pdfjs-page-rotate-cw-button =
.title = Pagriezt pa pulksteni
pdfjs-page-rotate-cw-button-label = Pagriezt pa pulksteni
pdfjs-page-rotate-ccw-button =
.title = Pagriezt pret pulksteni
pdfjs-page-rotate-ccw-button-label = Pagriezt pret pulksteni
pdfjs-cursor-text-select-tool-button =
.title = Aktivizēt teksta izvēles rīku
pdfjs-cursor-text-select-tool-button-label = Teksta izvēles rīks
pdfjs-cursor-hand-tool-button =
.title = Aktivēt rokas rīku
pdfjs-cursor-hand-tool-button-label = Rokas rīks
pdfjs-scroll-vertical-button =
.title = Izmantot vertikālo ritināšanu
pdfjs-scroll-vertical-button-label = Vertikālā ritināšana
pdfjs-scroll-horizontal-button =
.title = Izmantot horizontālo ritināšanu
pdfjs-scroll-horizontal-button-label = Horizontālā ritināšana
pdfjs-scroll-wrapped-button =
.title = Izmantot apkļauto ritināšanu
pdfjs-scroll-wrapped-button-label = Apkļautā ritināšana
pdfjs-spread-none-button =
.title = Nepievienoties lapu izpletumiem
pdfjs-spread-none-button-label = Neizmantot izpletumus
pdfjs-spread-odd-button =
.title = Izmantot lapu izpletumus sākot ar nepāra numuru lapām
pdfjs-spread-odd-button-label = Nepāra izpletumi
pdfjs-spread-even-button =
.title = Izmantot lapu izpletumus sākot ar pāra numuru lapām
pdfjs-spread-even-button-label = Pāra izpletumi
## Document properties dialog
pdfjs-document-properties-button =
.title = Dokumenta iestatījumi…
pdfjs-document-properties-button-label = Dokumenta iestatījumi…
pdfjs-document-properties-file-name = Faila nosaukums:
pdfjs-document-properties-file-size = Faila izmērs:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } biti)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } biti)
pdfjs-document-properties-title = Nosaukums:
pdfjs-document-properties-author = Autors:
pdfjs-document-properties-subject = Tēma:
pdfjs-document-properties-keywords = Atslēgas vārdi:
pdfjs-document-properties-creation-date = Izveides datums:
pdfjs-document-properties-modification-date = LAbošanas datums:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Radītājs:
pdfjs-document-properties-producer = PDF producents:
pdfjs-document-properties-version = PDF versija:
pdfjs-document-properties-page-count = Lapu skaits:
pdfjs-document-properties-page-size = Papīra izmērs:
pdfjs-document-properties-page-size-unit-inches = collas
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = portretorientācija
pdfjs-document-properties-page-size-orientation-landscape = ainavorientācija
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Vēstule
pdfjs-document-properties-page-size-name-legal = Juridiskie teksti
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Ātrā tīmekļa skats:
pdfjs-document-properties-linearized-yes = Jā
pdfjs-document-properties-linearized-no = Nē
pdfjs-document-properties-close-button = Aizvērt
## Print
pdfjs-print-progress-message = Gatavo dokumentu drukāšanai...
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Atcelt
pdfjs-printing-not-supported = Uzmanību: Drukāšana no šī pārlūka darbojas tikai daļēji.
pdfjs-printing-not-ready = Uzmanību: PDF nav pilnībā ielādēts drukāšanai.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Pārslēgt sānu joslu
pdfjs-toggle-sidebar-button-label = Pārslēgt sānu joslu
pdfjs-document-outline-button =
.title = Rādīt dokumenta struktūru (veiciet dubultklikšķi lai izvērstu/sakļautu visus vienumus)
pdfjs-document-outline-button-label = Dokumenta saturs
pdfjs-attachments-button =
.title = Rādīt pielikumus
pdfjs-attachments-button-label = Pielikumi
pdfjs-thumbs-button =
.title = Parādīt sīktēlus
pdfjs-thumbs-button-label = Sīktēli
pdfjs-findbar-button =
.title = Meklēt dokumentā
pdfjs-findbar-button-label = Meklēt
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Lapa { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Lapas { $page } sīktēls
## Find panel button title and messages
pdfjs-find-input =
.title = Meklēt
.placeholder = Meklēt dokumentā…
pdfjs-find-previous-button =
.title = Atrast iepriekšējo
pdfjs-find-previous-button-label = Iepriekšējā
pdfjs-find-next-button =
.title = Atrast nākamo
pdfjs-find-next-button-label = Nākamā
pdfjs-find-highlight-checkbox = Iekrāsot visas
pdfjs-find-match-case-checkbox-label = Lielo, mazo burtu jutīgs
pdfjs-find-entire-word-checkbox-label = Veselus vārdus
pdfjs-find-reached-top = Sasniegts dokumenta sākums, turpinām no beigām
pdfjs-find-reached-bottom = Sasniegtas dokumenta beigas, turpinām no sākuma
pdfjs-find-not-found = Frāze nav atrasta
## Predefined zoom values
pdfjs-page-scale-width = Lapas platumā
pdfjs-page-scale-fit = Ietilpinot lapu
pdfjs-page-scale-auto = Automātiskais izmērs
pdfjs-page-scale-actual = Patiesais izmērs
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
## Loading indicator messages
pdfjs-loading-error = Ielādējot PDF notika kļūda.
pdfjs-invalid-file-error = Nederīgs vai bojāts PDF fails.
pdfjs-missing-file-error = PDF fails nav atrasts.
pdfjs-unexpected-response-error = Negaidīa servera atbilde.
pdfjs-rendering-error = Attēlojot lapu radās kļūda
## Annotations
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } anotācija]
## Password
pdfjs-password-label = Ievadiet paroli, lai atvērtu PDF failu.
pdfjs-password-invalid = Nepareiza parole, mēģiniet vēlreiz.
pdfjs-password-ok-button = Labi
pdfjs-password-cancel-button = Atcelt
pdfjs-web-fonts-disabled = Tīmekļa fonti nav aktivizēti: Nevar iegult PDF fontus.
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/meh/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Página yata
pdfjs-zoom-select =
.title = Nasa´a ka´nu/Nasa´a luli
pdfjs-open-file-button-label = Síne
## Secondary toolbar and context menu
## Document properties dialog
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
pdfjs-document-properties-linearized-yes = Kuvi
pdfjs-document-properties-close-button = Nakasɨ
## Print
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Nkuvi-ka
## Tooltips and alt text for side panel toolbar buttons
pdfjs-findbar-button-label = Nánuku
## Thumbnails panel item (tooltip and alt text for images)
## Find panel button title and messages
## Predefined zoom values
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
## Loading indicator messages
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
## Password
pdfjs-password-cancel-button = Nkuvi-ka
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/mk/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Претходна страница
pdfjs-previous-button-label = Претходна
pdfjs-next-button =
.title = Следна страница
pdfjs-next-button-label = Следна
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Страница
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = од { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } од { $pagesCount })
pdfjs-zoom-out-button =
.title = Намалување
pdfjs-zoom-out-button-label = Намали
pdfjs-zoom-in-button =
.title = Зголемување
pdfjs-zoom-in-button-label = Зголеми
pdfjs-zoom-select =
.title = Променување на големина
pdfjs-presentation-mode-button =
.title = Премини во презентациски режим
pdfjs-presentation-mode-button-label = Презентациски режим
pdfjs-open-file-button =
.title = Отворање датотека
pdfjs-open-file-button-label = Отвори
pdfjs-print-button =
.title = Печатење
pdfjs-print-button-label = Печати
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Алатки
pdfjs-tools-button-label = Алатки
pdfjs-first-page-button =
.title = Оди до првата страница
pdfjs-first-page-button-label = Оди до првата страница
pdfjs-last-page-button =
.title = Оди до последната страница
pdfjs-last-page-button-label = Оди до последната страница
pdfjs-page-rotate-cw-button =
.title = Ротирај по стрелките на часовникот
pdfjs-page-rotate-cw-button-label = Ротирај по стрелките на часовникот
pdfjs-page-rotate-ccw-button =
.title = Ротирај спротивно од стрелките на часовникот
pdfjs-page-rotate-ccw-button-label = Ротирај спротивно од стрелките на часовникот
pdfjs-cursor-text-select-tool-button =
.title = Овозможи алатка за избор на текст
pdfjs-cursor-text-select-tool-button-label = Алатка за избор на текст
## Document properties dialog
pdfjs-document-properties-button =
.title = Својства на документот…
pdfjs-document-properties-button-label = Својства на документот…
pdfjs-document-properties-file-name = Име на датотека:
pdfjs-document-properties-file-size = Големина на датотеката:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } КБ ({ $size_b } бајти)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } МБ ({ $size_b } бајти)
pdfjs-document-properties-title = Наслов:
pdfjs-document-properties-author = Автор:
pdfjs-document-properties-subject = Тема:
pdfjs-document-properties-keywords = Клучни зборови:
pdfjs-document-properties-creation-date = Датум на создавање:
pdfjs-document-properties-modification-date = Датум на промена:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Креатор:
pdfjs-document-properties-version = Верзија на PDF:
pdfjs-document-properties-page-count = Број на страници:
pdfjs-document-properties-page-size = Големина на страница:
pdfjs-document-properties-page-size-unit-inches = инч
pdfjs-document-properties-page-size-unit-millimeters = мм
pdfjs-document-properties-page-size-orientation-portrait = портрет
pdfjs-document-properties-page-size-orientation-landscape = пејзаж
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Писмо
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
pdfjs-document-properties-linearized-yes = Да
pdfjs-document-properties-linearized-no = Не
pdfjs-document-properties-close-button = Затвори
## Print
pdfjs-print-progress-message = Документ се подготвува за печатење…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Откажи
pdfjs-printing-not-supported = Предупредување: Печатењето не е целосно поддржано во овој прелистувач.
pdfjs-printing-not-ready = Предупредување: PDF документот не е целосно вчитан за печатење.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Вклучи странична лента
pdfjs-toggle-sidebar-button-label = Вклучи странична лента
pdfjs-document-outline-button-label = Содржина на документот
pdfjs-attachments-button =
.title = Прикажи додатоци
pdfjs-thumbs-button =
.title = Прикажување на икони
pdfjs-thumbs-button-label = Икони
pdfjs-findbar-button =
.title = Најди во документот
pdfjs-findbar-button-label = Најди
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Страница { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Икона од страница { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Пронајди
.placeholder = Пронајди во документот…
pdfjs-find-previous-button =
.title = Најди ја предходната појава на фразата
pdfjs-find-previous-button-label = Претходно
pdfjs-find-next-button =
.title = Најди ја следната појава на фразата
pdfjs-find-next-button-label = Следно
pdfjs-find-highlight-checkbox = Означи сѐ
pdfjs-find-match-case-checkbox-label = Токму така
pdfjs-find-entire-word-checkbox-label = Цели зборови
pdfjs-find-reached-top = Барањето стигна до почетокот на документот и почнува од крајот
pdfjs-find-reached-bottom = Барањето стигна до крајот на документот и почнува од почеток
pdfjs-find-not-found = Фразата не е пронајдена
## Predefined zoom values
pdfjs-page-scale-width = Ширина на страница
pdfjs-page-scale-fit = Цела страница
pdfjs-page-scale-auto = Автоматска големина
pdfjs-page-scale-actual = Вистинска големина
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
## Loading indicator messages
pdfjs-loading-error = Настана грешка при вчитувањето на PDF-от.
pdfjs-invalid-file-error = Невалидна или корумпирана PDF датотека.
pdfjs-missing-file-error = Недостасува PDF документ.
pdfjs-unexpected-response-error = Неочекуван одговор од серверот.
pdfjs-rendering-error = Настана грешка при прикажувањето на страницата.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
## Password
pdfjs-password-label = Внесете ја лозинката за да ја отворите оваа датотека.
pdfjs-password-invalid = Невалидна лозинка. Обидете се повторно.
pdfjs-password-ok-button = Во ред
pdfjs-password-cancel-button = Откажи
pdfjs-web-fonts-disabled = Интернет фонтовите се оневозможени: не може да се користат вградените PDF фонтови.
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/ml/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = മുമ്പുള്ള താള്
pdfjs-previous-button-label = മുമ്പു്
pdfjs-next-button =
.title = അടുത്ത താള്
pdfjs-next-button-label = അടുത്തതു്
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = താള്
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = { $pagesCount } ലെ
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pagesCount } ലെ { $pageNumber })
pdfjs-zoom-out-button =
.title = ചെറുതാക്കുക
pdfjs-zoom-out-button-label = ചെറുതാക്കുക
pdfjs-zoom-in-button =
.title = വലുതാക്കുക
pdfjs-zoom-in-button-label = വലുതാക്കുക
pdfjs-zoom-select =
.title = വ്യാപ്തി മാറ്റുക
pdfjs-presentation-mode-button =
.title = പ്രസന്റേഷന് രീതിയിലേക്കു് മാറ്റുക
pdfjs-presentation-mode-button-label = പ്രസന്റേഷന് രീതി
pdfjs-open-file-button =
.title = ഫയല് തുറക്കുക
pdfjs-open-file-button-label = തുറക്കുക
pdfjs-print-button =
.title = അച്ചടിക്കുക
pdfjs-print-button-label = അച്ചടിക്കുക
pdfjs-save-button =
.title = കരുതിവയ്ക്കുക
pdfjs-save-button-label = കരുതിവയ്ക്കുക
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = ഇറക്കിവയ്ക്കുക
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = ഇറക്കിവയ്ക്കുക
pdfjs-bookmark-button =
.title = നിലവിലെ താൾ (നിലവിലെ താളിൽ നിന്നു് യൂ.ആർ.എൽ കാണുക)
pdfjs-bookmark-button-label = നിലവിലുള്ള താൾ
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = ഉപകരണങ്ങള്
pdfjs-tools-button-label = ഉപകരണങ്ങള്
pdfjs-first-page-button =
.title = ആദ്യത്തെ താളിലേയ്ക്കു് പോകുക
pdfjs-first-page-button-label = ആദ്യത്തെ താളിലേയ്ക്കു് പോകുക
pdfjs-last-page-button =
.title = അവസാന താളിലേയ്ക്കു് പോകുക
pdfjs-last-page-button-label = അവസാന താളിലേയ്ക്കു് പോകുക
pdfjs-page-rotate-cw-button =
.title = ഘടികാരദിശയില് കറക്കുക
pdfjs-page-rotate-cw-button-label = ഘടികാരദിശയില് കറക്കുക
pdfjs-page-rotate-ccw-button =
.title = ഘടികാര ദിശയ്ക്കു് വിപരീതമായി കറക്കുക
pdfjs-page-rotate-ccw-button-label = ഘടികാര ദിശയ്ക്കു് വിപരീതമായി കറക്കുക
pdfjs-cursor-text-select-tool-button =
.title = ടെക്സ്റ്റ് തിരഞ്ഞെടുക്കൽ ടൂള് പ്രാപ്തമാക്കുക
pdfjs-cursor-text-select-tool-button-label = എഴുത്തു് തിരഞ്ഞെടുക്കൽ കരു
pdfjs-cursor-hand-tool-button =
.title = കൈക്കരു പ്രാപ്തമാക്കുക
pdfjs-cursor-hand-tool-button-label = കൈക്കരു
## Document properties dialog
pdfjs-document-properties-button =
.title = രേഖയുടെ വിശേഷതകള്...
pdfjs-document-properties-button-label = രേഖയുടെ വിശേഷതകള്...
pdfjs-document-properties-file-name = ഫയലിന്റെ പേര്:
pdfjs-document-properties-file-size = ഫയലിന്റെ വലിപ്പം:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } ബൈറ്റുകൾ)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } ബൈറ്റുകൾ)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } കെബി ({ $size_b } ബൈറ്റുകള്)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } എംബി ({ $size_b } ബൈറ്റുകള്)
pdfjs-document-properties-title = തലക്കെട്ട്
pdfjs-document-properties-author = രചയിതാവ്:
pdfjs-document-properties-subject = വിഷയം:
pdfjs-document-properties-keywords = മുഖ്യപദങ്ങൾ
pdfjs-document-properties-creation-date = പൂര്ത്തിയാകുന്ന തീയതി:
pdfjs-document-properties-modification-date = മാറ്റം വരുത്തിയ തീയതി:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = സൃഷ്ടികര്ത്താവ്:
pdfjs-document-properties-producer = പിഡിഎഫ് പ്രൊഡ്യൂസര്:
pdfjs-document-properties-version = പിഡിഎഫ് പതിപ്പ്:
pdfjs-document-properties-page-count = താളിന്റെ എണ്ണം:
pdfjs-document-properties-page-size = താൾ വലുപ്പം
pdfjs-document-properties-page-size-unit-inches = ഇഞ്ചു്
pdfjs-document-properties-page-size-unit-millimeters = മില്ലീമീറ്റർ
pdfjs-document-properties-page-size-orientation-portrait = ഛായപടം രീതിയില്
pdfjs-document-properties-page-size-orientation-landscape = ഭൂദൃശ്യത്തിന്റെ ആകൃതിയില്
pdfjs-document-properties-page-size-name-a-three = ആ 3
pdfjs-document-properties-page-size-name-a-four = ആ 4
pdfjs-document-properties-page-size-name-letter = കത്തു്
pdfjs-document-properties-page-size-name-legal = നിയമപരം
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name },{ $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = വിരവോള ഗോളാന്തരക്കാഴ്ച :
pdfjs-document-properties-linearized-yes = അതെ
pdfjs-document-properties-linearized-no = ഇല്ല
pdfjs-document-properties-close-button = അടയ്ക്കുക
## Print
pdfjs-print-progress-message = അച്ചടിപ്പിനു് പ്രമാണം ഒരുക്കുന്നു...
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = റദ്ദാക്കുക
pdfjs-printing-not-supported = മുന്നറിയിപ്പു്: ഈ അന്വേഷിയന്ത്രമിൽ അച്ചടിപ്പു് മുഴുവനായി പിന്തുണയ്ക്കാരില്ല.
pdfjs-printing-not-ready = മുന്നറിയിപ്പു്: അച്ചടിക്കാനായി ഈ പിഡിഎഫ മൊത്തം ലഭ്യമാക്കിയിട്ടില്ല
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = അണിവക്കം ടോഗിൾ ചെയ്യുക
pdfjs-toggle-sidebar-button-label = അണിവക്കം ടോഗിൾ ചെയ്യുക
pdfjs-document-outline-button =
.title = ഡോക്യുമെന്റിന്റെ ബാഹ്യരേഖ കാണിക്കുക (എല്ലാ ഇനങ്ങളും വിപുലീകരിക്കാനും ചുരുക്കാനും ഇരട്ട ക്ലിക്കുചെയ്യുക)
pdfjs-document-outline-button-label = രേഖയുടെ ഔട്ട്ലൈന്
pdfjs-attachments-button =
.title = അറ്റാച്മെന്റുകള് കാണിയ്ക്കുക
pdfjs-attachments-button-label = അറ്റാച്മെന്റുകള്
pdfjs-layers-button-label = പാളികൾ
pdfjs-thumbs-button =
.title = തംബ്നെയിലുകള് കാണിയ്ക്കുക
pdfjs-thumbs-button-label = തംബ്നെയിലുകള്
pdfjs-findbar-button =
.title = രേഖയില് കണ്ടുപിടിയ്ക്കുക
pdfjs-findbar-button-label = കണ്ടെത്തുക
pdfjs-additional-layers = കൂടാത്ത പാളികൾ
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = താള് { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = { $page } താളിനുള്ള തംബ്നെയില്
## Find panel button title and messages
pdfjs-find-input =
.title = കണ്ടെത്തുക
.placeholder = ഡോക്യുമെന്റില് കണ്ടെത്തുക…
pdfjs-find-previous-button =
.title = വാചകം ഇതിനു മുന്പ് ആവര്ത്തിച്ചത് കണ്ടെത്തുക
pdfjs-find-previous-button-label = മുമ്പു്
pdfjs-find-next-button =
.title = വാചകം വീണ്ടും ആവര്ത്തിക്കുന്നത് കണ്ടെത്തുക
pdfjs-find-next-button-label = അടുത്തതു്
pdfjs-find-highlight-checkbox = എല്ലാം എടുത്തുകാണിയ്ക്കുക
pdfjs-find-match-case-checkbox-label = അക്ഷരങ്ങള് ഒത്തുനോക്കുക
pdfjs-find-entire-word-checkbox-label = മുഴുവൻ വാക്കുകൾ
pdfjs-find-reached-top = രേഖയുടെ മുകളില് എത്തിയിരിക്കുന്നു, താഴെ നിന്നും തുടരുന്നു
pdfjs-find-reached-bottom = രേഖയുടെ അവസാനം വരെ എത്തിയിരിക്കുന്നു, മുകളില് നിന്നും തുടരുന്നു
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } / { $total } പൊരുത്തങ്ങള്
*[other] { $current } / { $total } പൊരുത്തങ്ങള്
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] { $limit } പൊരുത്തങ്ങളില് കൂടുതല്
*[other] { $limit } പൊരുത്തങ്ങളില് കൂടുതല്
}
pdfjs-find-not-found = വാചകം കണ്ടെത്താനായില്ല
## Predefined zoom values
pdfjs-page-scale-width = താളിന്റെ വീതി
pdfjs-page-scale-fit = താള് പാകത്തിനാക്കുക
pdfjs-page-scale-auto = സ്വയമായി വലുതാക്കുക
pdfjs-page-scale-actual = യഥാര്ത്ഥ വ്യാപ്തി
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = താള് { $page }
## Loading indicator messages
pdfjs-loading-error = പിഡിഎഫ് ലഭ്യമാക്കുമ്പോള് പിശക് ഉണ്ടായിരിയ്ക്കുന്നു.
pdfjs-invalid-file-error = തെറ്റായ അല്ലെങ്കില് തകരാറുള്ള പിഡിഎഫ് ഫയല്.
pdfjs-missing-file-error = പിഡിഎഫ് ഫയല് ലഭ്യമല്ല.
pdfjs-unexpected-response-error = പ്രതീക്ഷിക്കാത്ത സെര്വര് മറുപടി.
pdfjs-rendering-error = താള് റെണ്ടര് ചെയ്യുമ്പോള് പിശകുണ്ടായിരിയ്ക്കുന്നു.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } Annotation]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = ഈ പിഡിഎഫ് ഫയല് തുറക്കുന്നതിനു് രഹസ്യവാക്ക് നല്കുക.
pdfjs-password-invalid = തെറ്റായ രഹസ്യവാക്ക്, ദയവായി വീണ്ടും ശ്രമിയ്ക്കുക.
pdfjs-password-ok-button = ശരി
pdfjs-password-cancel-button = റദ്ദാക്കുക
pdfjs-web-fonts-disabled = വെബിനുള്ള അക്ഷരസഞ്ചയങ്ങള് പ്രവര്ത്തന രഹിതം: എംബഡ്ഡ് ചെയ്ത പിഡിഎഫ് അക്ഷരസഞ്ചയങ്ങള് ഉപയോഗിയ്ക്കുവാന് സാധ്യമല്ല.
## Editing
pdfjs-editor-free-text-button =
.title = എഴുത്തു്
pdfjs-editor-free-text-button-label = എഴുത്തു്
pdfjs-editor-ink-button =
.title = വരയ്ക്കുക
pdfjs-editor-ink-button-label = വരയ്ക്കുക
pdfjs-editor-stamp-button =
.title = ചിത്രങ്ങളെ ചേർക്കുക അല്ലെങ്കിൽ തിരുത്തുക
pdfjs-editor-stamp-button-label = ചിത്രങ്ങളെ ചേർക്കുക അല്ലെങ്കിൽ തിരുത്തുക
pdfjs-editor-highlight-button =
.title = അടയാളപ്പെടുക
pdfjs-editor-highlight-button-label = അടയാളപ്പെടുക
pdfjs-highlight-floating-button1 =
.title = അടയാളപ്പെടുക
.aria-label = അടയാളപ്പെടുക
pdfjs-highlight-floating-button-label = അടയാളപ്പെടുക
pdfjs-editor-signature-button =
.title = പുതിയ ഒപ്പു് ചേൎക്കുക
pdfjs-editor-signature-button-label = പുതിയ ഒപ്പു് ചേൎക്കുക
## Default editor aria labels
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = ആലേഖ്യം മാറ്റുക
pdfjs-editor-remove-freetext-button =
.title = എഴുത്തു് മാറ്റുക
pdfjs-editor-remove-stamp-button =
.title = ചിത്രം മാറ്റുക
pdfjs-editor-remove-highlight-button =
.title = അടയാളപ്പെട്ടുതു് മാറ്റുക
pdfjs-editor-remove-signature-button =
.title = ഒപ്പു് മാറ്റുക
##
# Editor Parameters
pdfjs-editor-free-text-color-input = നിറം
pdfjs-editor-free-text-size-input = വലുപ്പം
pdfjs-editor-ink-color-input = നിറം
pdfjs-editor-ink-thickness-input = കനം
pdfjs-editor-ink-opacity-input = അതാര്യത
pdfjs-editor-stamp-add-image-button =
.title = ചിത്രം ചേർക്കുക
pdfjs-editor-stamp-add-image-button-label = ചിത്രം ചേർക്കുക
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = കനം
pdfjs-editor-signature-add-signature-button =
.title = പുതിയ ഒപ്പു് ചേൎക്കുക
pdfjs-editor-signature-add-signature-button-label = പുതിയ ഒപ്പു് ചേൎക്കുക
pdfjs-free-text-default-content = എഴുതാൻ തുടങ്ങുക…
pdfjs-ink-canvas =
.aria-label = ഉപയോക്താവ് ഉണ്ടാക്കിയ ചിത്രം
## Alt-text dialog
pdfjs-editor-alt-text-button-label = മറുയെഴുത്തു്
pdfjs-editor-alt-text-edit-button =
.aria-label = മറുയെഴുത്തു് തിരുത്തുക
pdfjs-editor-alt-text-edit-button-label = മറുയെഴുത്തു് തിരുത്തുക
pdfjs-editor-alt-text-dialog-label = സാധ്യത തിരഞ്ഞെടുക്കൂ
pdfjs-editor-alt-text-add-description-label = ഒരു വിവരണം ചേർക്കുക
pdfjs-editor-alt-text-cancel-button = റദ്ദാക്കുക
pdfjs-editor-alt-text-save-button = കരുതിവയ്ക്കുക
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = മറുയെഴുത്തു്
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = ഇടതു മീത്ത മുക്ക് — വലുപ്പം മാറ്റുക
pdfjs-editor-resizer-label-top-middle = നടുവിൽ മീത്ത മുക്ക് - വലുപ്പം മാറ്റുക
pdfjs-editor-resizer-label-top-right = വലതു മീത്ത മുക്ക് — വലുപ്പം മാറ്റുക
pdfjs-editor-resizer-label-middle-right = വലതു നടുവിലുള്ള മുക്ക് — വലുപ്പം മാറ്റുക
pdfjs-editor-resizer-label-bottom-right = വലതു കീഴിലുള്ള മുക്ക് — വലുപ്പം മാറ്റുക
pdfjs-editor-resizer-label-bottom-middle = നടുവെ കീഴിലുള്ള മുക്ക് — വലുപ്പം മാറ്റുക
pdfjs-editor-resizer-label-bottom-left = ഇടതു കീഴിലുള്ള മുക്ക് — വലുപ്പം മാറ്റുക
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = അടയാളന്നിറം
pdfjs-editor-colorpicker-button =
.title = നിറം മാറ്റുക
pdfjs-editor-colorpicker-dropdown =
.aria-label = നിറസാധ്യതകൾ
pdfjs-editor-colorpicker-yellow =
.title = മഞ്ഞ
pdfjs-editor-colorpicker-green =
.title = പച്ച
pdfjs-editor-colorpicker-blue =
.title = നീല
pdfjs-editor-colorpicker-pink =
.title = പാടലവർണ്ണം
pdfjs-editor-colorpicker-red =
.title = ചുമന്ന
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = എല്ലാം കാണിക്കുക
pdfjs-editor-highlight-show-all-button =
.title = എല്ലാം കാണിക്കുക
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = മറുയെഴുത്തു് തിരുത്തുക (ചിത്ര വിവരണം)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = മറുയെഴുത്തു് ചേൎക്കുക (ചിത്ര വിവരണം)
pdfjs-editor-new-alt-text-textarea =
.placeholder = താങ്ങളുടെ വിവരണം ഇവിടെ എഴുതുക...
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = കൂടുതല് അറിയുക
pdfjs-editor-new-alt-text-create-automatically-button-label = തന്നെതാനെ മറുയെഴുത്തു് ഉണ്ടാക്കുക
pdfjs-editor-new-alt-text-not-now-button = ഇപ്പോഴല്ല
pdfjs-editor-new-alt-text-error-title = തന്നെതാനെ മറുയെഴുത്തു് ഉണ്ടാക്കാൻ പറ്റിയില്ല
pdfjs-editor-new-alt-text-error-close-button = അടയ്ക്കുക
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = മറുയെഴുത്തു് ചേൎത്തു
pdfjs-editor-new-alt-text-added-button-label = മറുയെഴുത്തു് ചേൎത്തു
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = മറുയെഴുത്തു് കാണാന്നില്ല
pdfjs-editor-new-alt-text-missing-button-label = മറുയെഴുത്തു് കാണാന്നില്ല
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = മറുയെഴുത്തു് അവലോകിക്കുക
pdfjs-editor-new-alt-text-to-review-button-label = മറുയെഴുത്തു് അവലോകിക്കുക
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = തന്നെതാനെ ഉണ്ടാക്കി : { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = ചിത്ര മറുയെഴുത്തു് ക്രമീകരണങ്ങൾ
pdfjs-image-alt-text-settings-button-label = ചിത്ര മറുയെഴുത്തു് ക്രമീകരണങ്ങൾ
pdfjs-editor-alt-text-settings-dialog-label = ചിത്ര മറുയെഴുത്തു് ക്രമീകരണങ്ങൾ
pdfjs-editor-alt-text-settings-automatic-title = യാന്ത്രിക മറുയെഴുത്തു്
pdfjs-editor-alt-text-settings-create-model-button-label = തന്നെതാനെ മറുയെഴുത്തു് ഉണ്ടാക്കുക
pdfjs-editor-alt-text-settings-delete-model-button = മായ്ക്കുക
pdfjs-editor-alt-text-settings-download-model-button = ഇറക്കിവയ്ക്കുക
pdfjs-editor-alt-text-settings-downloading-model-button = ഇറക്കിവയ്ക്കുന്നു
pdfjs-editor-alt-text-settings-close-button = അടയ്ക്കുക
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = അടയാളം മാറ്റി
pdfjs-editor-undo-bar-message-freetext = എഴുത്തു് മാറ്റി
pdfjs-editor-undo-bar-message-ink = ആലേഖ്യം മാറ്റി
pdfjs-editor-undo-bar-message-stamp = ചിത്രം മാറ്റി
pdfjs-editor-undo-bar-message-signature = ഒപ്പു് മാറ്റി
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } കുറിപ്പെഴുതലുകൾ മാറ്റി
*[other] { $count } കുറിപ്പെഴുതലുകൾ മാറ്റി
}
pdfjs-editor-undo-bar-undo-button =
.title = പഴയപോലെയാക്കുക
pdfjs-editor-undo-bar-undo-button-label = പഴയപോലെയാക്കുക
pdfjs-editor-undo-bar-close-button =
.title = അടയ്ക്കുക
pdfjs-editor-undo-bar-close-button-label = അടയ്ക്കുക
## Add a signature dialog
pdfjs-editor-add-signature-dialog-title = ഒപ്പു് ചേൎക്കുക
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = തരം
.title = തരം
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = വരയ്ക്കുക
.title = വരയ്ക്കുക
pdfjs-editor-add-signature-image-button = ചിത്രം
.title = ചിത്രം
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = താങ്ങളുടെ ഒപ്പു് ഇവിടെ എഴുതുക
.placeholder = താങ്ങളുടെ ഒപ്പു് ഇവിടെ എഴുതുക
pdfjs-editor-add-signature-draw-placeholder = താങ്ങളുടെ ഒപ്പു് വരയ്ക്കുക
pdfjs-editor-add-signature-draw-thickness-range-label = കനം
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = വരപ്പുകനം: { $thickness }
pdfjs-editor-add-signature-image-placeholder = കയറ്റുവയ്ക്കാൻ വേണ്ടി ഫയലിനു് ഇവിടോട്ടു് വലിച്ചിടുക
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] അല്ലെങ്കിൽ ചിത്രം ഫയലുകൾ തപ്പുക
*[other] അല്ലെങ്കിൽ ചിത്രം ഫയലുകൾ തപ്പുക
}
## Controls
pdfjs-editor-add-signature-description-label = വിവരണം (ഇതരയെഴുതു്)
pdfjs-editor-add-signature-description-input =
.title = വിവരണം (ഇതരയെഴുതു്)
pdfjs-editor-add-signature-description-default-when-drawing = ഒപ്പു്
pdfjs-editor-add-signature-clear-button-label = ഒപ്പു് മായ്ക്കുക
pdfjs-editor-add-signature-clear-button =
.title = ഒപ്പു് മായ്ക്കുക
pdfjs-editor-add-signature-save-checkbox = ഒപ്പു് കരുതിവയ്ക്കുക
pdfjs-editor-add-signature-save-warning-message = താങ്ങളുടെ ഒപ്പുകളുടെ എണ്ണം 5 ആയി. കൂടുതൽ കരുതിവയ്ക്കാൻ വേണ്ടി ഒരെണ്ണം മാറ്റണ്ടിവരും.
pdfjs-editor-add-signature-image-upload-error-title = ചിത്രം കയറ്റുവയ്ക്കാൻ പറ്റിയില്ല
pdfjs-editor-add-signature-image-upload-error-description = താങ്ങളുടെ ശൃംഖല സമ്പൎക്കം പരിശോധിക്കുക അല്ലെങ്കിൽ വേറെയൊരു ചിത്രം ഇട്ടുനോക്കുക
pdfjs-editor-add-signature-error-close-button = അടയ്ക്കുക
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = റദ്ദാക്കുക
pdfjs-editor-add-signature-add-button = ചേൎക്കുക
pdfjs-editor-edit-signature-update-button = പുതുക്കുക
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = ഒപ്പു് മാറ്റുക
pdfjs-editor-delete-signature-button-label = ഒപ്പു് മാറ്റുക
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = വിവരണം തിരുത്തുക
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = വിവരണം തിരുത്തുക
================================================
FILE: cookbook/static/pdfjs/web/locale/mr/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = मागील पृष्ठ
pdfjs-previous-button-label = मागील
pdfjs-next-button =
.title = पुढील पृष्ठ
pdfjs-next-button-label = पुढील
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = पृष्ठ
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = { $pagesCount }पैकी
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pagesCount } पैकी { $pageNumber })
pdfjs-zoom-out-button =
.title = छोटे करा
pdfjs-zoom-out-button-label = छोटे करा
pdfjs-zoom-in-button =
.title = मोठे करा
pdfjs-zoom-in-button-label = मोठे करा
pdfjs-zoom-select =
.title = लहान किंवा मोठे करा
pdfjs-presentation-mode-button =
.title = प्रस्तुतिकरण मोडचा वापर करा
pdfjs-presentation-mode-button-label = प्रस्तुतिकरण मोड
pdfjs-open-file-button =
.title = फाइल उघडा
pdfjs-open-file-button-label = उघडा
pdfjs-print-button =
.title = छपाई करा
pdfjs-print-button-label = छपाई करा
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = साधने
pdfjs-tools-button-label = साधने
pdfjs-first-page-button =
.title = पहिल्या पृष्ठावर जा
pdfjs-first-page-button-label = पहिल्या पृष्ठावर जा
pdfjs-last-page-button =
.title = शेवटच्या पृष्ठावर जा
pdfjs-last-page-button-label = शेवटच्या पृष्ठावर जा
pdfjs-page-rotate-cw-button =
.title = घड्याळाच्या काट्याच्या दिशेने फिरवा
pdfjs-page-rotate-cw-button-label = घड्याळाच्या काट्याच्या दिशेने फिरवा
pdfjs-page-rotate-ccw-button =
.title = घड्याळाच्या काट्याच्या उलट दिशेने फिरवा
pdfjs-page-rotate-ccw-button-label = घड्याळाच्या काट्याच्या उलट दिशेने फिरवा
pdfjs-cursor-text-select-tool-button =
.title = मजकूर निवड साधन कार्यान्वयीत करा
pdfjs-cursor-text-select-tool-button-label = मजकूर निवड साधन
pdfjs-cursor-hand-tool-button =
.title = हात साधन कार्यान्वित करा
pdfjs-cursor-hand-tool-button-label = हस्त साधन
pdfjs-scroll-vertical-button =
.title = अनुलंब स्क्रोलिंग वापरा
pdfjs-scroll-vertical-button-label = अनुलंब स्क्रोलिंग
pdfjs-scroll-horizontal-button =
.title = क्षैतिज स्क्रोलिंग वापरा
pdfjs-scroll-horizontal-button-label = क्षैतिज स्क्रोलिंग
## Document properties dialog
pdfjs-document-properties-button =
.title = दस्तऐवज गुणधर्म…
pdfjs-document-properties-button-label = दस्तऐवज गुणधर्म…
pdfjs-document-properties-file-name = फाइलचे नाव:
pdfjs-document-properties-file-size = फाइल आकार:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } बाइट्स)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } बाइट्स)
pdfjs-document-properties-title = शिर्षक:
pdfjs-document-properties-author = लेखक:
pdfjs-document-properties-subject = विषय:
pdfjs-document-properties-keywords = मुख्यशब्द:
pdfjs-document-properties-creation-date = निर्माण दिनांक:
pdfjs-document-properties-modification-date = दुरूस्ती दिनांक:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = निर्माता:
pdfjs-document-properties-producer = PDF निर्माता:
pdfjs-document-properties-version = PDF आवृत्ती:
pdfjs-document-properties-page-count = पृष्ठ संख्या:
pdfjs-document-properties-page-size = पृष्ठ आकार:
pdfjs-document-properties-page-size-unit-inches = इंच
pdfjs-document-properties-page-size-unit-millimeters = मीमी
pdfjs-document-properties-page-size-orientation-portrait = उभी मांडणी
pdfjs-document-properties-page-size-orientation-landscape = आडवे
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letter
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = जलद वेब दृष्य:
pdfjs-document-properties-linearized-yes = हो
pdfjs-document-properties-linearized-no = नाही
pdfjs-document-properties-close-button = बंद करा
## Print
pdfjs-print-progress-message = छपाई करीता पृष्ठ तयार करीत आहे…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = रद्द करा
pdfjs-printing-not-supported = सावधानता: या ब्राउझरतर्फे छपाइ पूर्णपणे समर्थीत नाही.
pdfjs-printing-not-ready = सावधानता: छपाईकरिता PDF पूर्णतया लोड झाले नाही.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = बाजूचीपट्टी टॉगल करा
pdfjs-toggle-sidebar-button-label = बाजूचीपट्टी टॉगल करा
pdfjs-document-outline-button =
.title = दस्तऐवज बाह्यरेखा दर्शवा (विस्तृत करण्यासाठी दोनवेळा क्लिक करा /सर्व घटक दाखवा)
pdfjs-document-outline-button-label = दस्तऐवज रूपरेषा
pdfjs-attachments-button =
.title = जोडपत्र दाखवा
pdfjs-attachments-button-label = जोडपत्र
pdfjs-thumbs-button =
.title = थंबनेल्स् दाखवा
pdfjs-thumbs-button-label = थंबनेल्स्
pdfjs-findbar-button =
.title = दस्तऐवजात शोधा
pdfjs-findbar-button-label = शोधा
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = पृष्ठ { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = पृष्ठाचे थंबनेल { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = शोधा
.placeholder = दस्तऐवजात शोधा…
pdfjs-find-previous-button =
.title = वाकप्रयोगची मागील घटना शोधा
pdfjs-find-previous-button-label = मागील
pdfjs-find-next-button =
.title = वाकप्रयोगची पुढील घटना शोधा
pdfjs-find-next-button-label = पुढील
pdfjs-find-highlight-checkbox = सर्व ठळक करा
pdfjs-find-match-case-checkbox-label = आकार जुळवा
pdfjs-find-entire-word-checkbox-label = संपूर्ण शब्द
pdfjs-find-reached-top = दस्तऐवजाच्या शीर्षकास पोहचले, तळपासून पुढे
pdfjs-find-reached-bottom = दस्तऐवजाच्या तळाला पोहचले, शीर्षकापासून पुढे
pdfjs-find-not-found = वाकप्रयोग आढळले नाही
## Predefined zoom values
pdfjs-page-scale-width = पृष्ठाची रूंदी
pdfjs-page-scale-fit = पृष्ठ बसवा
pdfjs-page-scale-auto = स्वयं लाहन किंवा मोठे करणे
pdfjs-page-scale-actual = प्रत्यक्ष आकार
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
## Loading indicator messages
pdfjs-loading-error = PDF लोड करतेवेळी त्रुटी आढळली.
pdfjs-invalid-file-error = अवैध किंवा दोषीत PDF फाइल.
pdfjs-missing-file-error = न आढळणारी PDF फाइल.
pdfjs-unexpected-response-error = अनपेक्षित सर्व्हर प्रतिसाद.
pdfjs-rendering-error = पृष्ठ दाखवतेवेळी त्रुटी आढळली.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } टिपण्णी]
## Password
pdfjs-password-label = ही PDF फाइल उघडण्याकरिता पासवर्ड द्या.
pdfjs-password-invalid = अवैध पासवर्ड. कृपया पुन्हा प्रयत्न करा.
pdfjs-password-ok-button = ठीक आहे
pdfjs-password-cancel-button = रद्द करा
pdfjs-web-fonts-disabled = वेब टंक असमर्थीत आहेत: एम्बेडेड PDF टंक वापर अशक्य.
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/ms/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Halaman Dahulu
pdfjs-previous-button-label = Dahulu
pdfjs-next-button =
.title = Halaman Berikut
pdfjs-next-button-label = Berikut
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Halaman
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = daripada { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } daripada { $pagesCount })
pdfjs-zoom-out-button =
.title = Zum Keluar
pdfjs-zoom-out-button-label = Zum Keluar
pdfjs-zoom-in-button =
.title = Zum Masuk
pdfjs-zoom-in-button-label = Zum Masuk
pdfjs-zoom-select =
.title = Zum
pdfjs-presentation-mode-button =
.title = Tukar ke Mod Persembahan
pdfjs-presentation-mode-button-label = Mod Persembahan
pdfjs-open-file-button =
.title = Buka Fail
pdfjs-open-file-button-label = Buka
pdfjs-print-button =
.title = Cetak
pdfjs-print-button-label = Cetak
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Alatan
pdfjs-tools-button-label = Alatan
pdfjs-first-page-button =
.title = Pergi ke Halaman Pertama
pdfjs-first-page-button-label = Pergi ke Halaman Pertama
pdfjs-last-page-button =
.title = Pergi ke Halaman Terakhir
pdfjs-last-page-button-label = Pergi ke Halaman Terakhir
pdfjs-page-rotate-cw-button =
.title = Berputar ikut arah Jam
pdfjs-page-rotate-cw-button-label = Berputar ikut arah Jam
pdfjs-page-rotate-ccw-button =
.title = Pusing berlawan arah jam
pdfjs-page-rotate-ccw-button-label = Pusing berlawan arah jam
pdfjs-cursor-text-select-tool-button =
.title = Dayakan Alatan Pilihan Teks
pdfjs-cursor-text-select-tool-button-label = Alatan Pilihan Teks
pdfjs-cursor-hand-tool-button =
.title = Dayakan Alatan Tangan
pdfjs-cursor-hand-tool-button-label = Alatan Tangan
pdfjs-scroll-vertical-button =
.title = Guna Skrol Menegak
pdfjs-scroll-vertical-button-label = Skrol Menegak
pdfjs-scroll-horizontal-button =
.title = Guna Skrol Mengufuk
pdfjs-scroll-horizontal-button-label = Skrol Mengufuk
pdfjs-scroll-wrapped-button =
.title = Guna Skrol Berbalut
pdfjs-scroll-wrapped-button-label = Skrol Berbalut
pdfjs-spread-none-button =
.title = Jangan hubungkan hamparan halaman
pdfjs-spread-none-button-label = Tanpa Hamparan
pdfjs-spread-odd-button =
.title = Hubungkan hamparan halaman dengan halaman nombor ganjil
pdfjs-spread-odd-button-label = Hamparan Ganjil
pdfjs-spread-even-button =
.title = Hubungkan hamparan halaman dengan halaman nombor genap
pdfjs-spread-even-button-label = Hamparan Seimbang
## Document properties dialog
pdfjs-document-properties-button =
.title = Sifat Dokumen…
pdfjs-document-properties-button-label = Sifat Dokumen…
pdfjs-document-properties-file-name = Nama fail:
pdfjs-document-properties-file-size = Saiz fail:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bait)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bait)
pdfjs-document-properties-title = Tajuk:
pdfjs-document-properties-author = Pengarang:
pdfjs-document-properties-subject = Subjek:
pdfjs-document-properties-keywords = Kata kunci:
pdfjs-document-properties-creation-date = Masa Dicipta:
pdfjs-document-properties-modification-date = Tarikh Ubahsuai:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Pencipta:
pdfjs-document-properties-producer = Pengeluar PDF:
pdfjs-document-properties-version = Versi PDF:
pdfjs-document-properties-page-count = Kiraan Laman:
pdfjs-document-properties-page-size = Saiz Halaman:
pdfjs-document-properties-page-size-unit-inches = dalam
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = potret
pdfjs-document-properties-page-size-orientation-landscape = landskap
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letter
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Paparan Web Pantas:
pdfjs-document-properties-linearized-yes = Ya
pdfjs-document-properties-linearized-no = Tidak
pdfjs-document-properties-close-button = Tutup
## Print
pdfjs-print-progress-message = Menyediakan dokumen untuk dicetak…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Batal
pdfjs-printing-not-supported = Amaran: Cetakan ini tidak sepenuhnya disokong oleh pelayar ini.
pdfjs-printing-not-ready = Amaran: PDF tidak sepenuhnya dimuatkan untuk dicetak.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Togol Bar Sisi
pdfjs-toggle-sidebar-button-label = Togol Bar Sisi
pdfjs-document-outline-button =
.title = Papar Rangka Dokumen (klik-dua-kali untuk kembangkan/kolaps semua item)
pdfjs-document-outline-button-label = Rangka Dokumen
pdfjs-attachments-button =
.title = Papar Lampiran
pdfjs-attachments-button-label = Lampiran
pdfjs-thumbs-button =
.title = Papar Thumbnails
pdfjs-thumbs-button-label = Imej kecil
pdfjs-findbar-button =
.title = Cari didalam Dokumen
pdfjs-findbar-button-label = Cari
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Halaman { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Halaman Imej kecil { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Cari
.placeholder = Cari dalam dokumen…
pdfjs-find-previous-button =
.title = Cari teks frasa berkenaan yang terdahulu
pdfjs-find-previous-button-label = Dahulu
pdfjs-find-next-button =
.title = Cari teks frasa berkenaan yang berikut
pdfjs-find-next-button-label = Berikut
pdfjs-find-highlight-checkbox = Serlahkan semua
pdfjs-find-match-case-checkbox-label = Huruf sepadan
pdfjs-find-entire-word-checkbox-label = Seluruh perkataan
pdfjs-find-reached-top = Mencapai teratas daripada dokumen, sambungan daripada bawah
pdfjs-find-reached-bottom = Mencapai terakhir daripada dokumen, sambungan daripada atas
pdfjs-find-not-found = Frasa tidak ditemui
## Predefined zoom values
pdfjs-page-scale-width = Lebar Halaman
pdfjs-page-scale-fit = Muat Halaman
pdfjs-page-scale-auto = Zoom Automatik
pdfjs-page-scale-actual = Saiz Sebenar
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
## Loading indicator messages
pdfjs-loading-error = Masalah berlaku semasa menuatkan sebuah PDF.
pdfjs-invalid-file-error = Tidak sah atau fail PDF rosak.
pdfjs-missing-file-error = Fail PDF Hilang.
pdfjs-unexpected-response-error = Respon pelayan yang tidak dijangka.
pdfjs-rendering-error = Ralat berlaku ketika memberikan halaman.
## Annotations
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } Anotasi]
## Password
pdfjs-password-label = Masukan kata kunci untuk membuka fail PDF ini.
pdfjs-password-invalid = Kata laluan salah. Cuba lagi.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Batal
pdfjs-web-fonts-disabled = Fon web dinyahdayakan: tidak dapat menggunakan fon terbenam PDF.
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/my/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = အရင် စာမျက်နှာ
pdfjs-previous-button-label = အရင်နေရာ
pdfjs-next-button =
.title = ရှေ့ စာမျက်နှာ
pdfjs-next-button-label = နောက်တခု
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = စာမျက်နှာ
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = { $pagesCount } ၏
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pagesCount } ၏ { $pageNumber })
pdfjs-zoom-out-button =
.title = ချုံ့ပါ
pdfjs-zoom-out-button-label = ချုံ့ပါ
pdfjs-zoom-in-button =
.title = ချဲ့ပါ
pdfjs-zoom-in-button-label = ချဲ့ပါ
pdfjs-zoom-select =
.title = ချုံ့/ချဲ့ပါ
pdfjs-presentation-mode-button =
.title = ဆွေးနွေးတင်ပြစနစ်သို့ ကူးပြောင်းပါ
pdfjs-presentation-mode-button-label = ဆွေးနွေးတင်ပြစနစ်
pdfjs-open-file-button =
.title = ဖိုင်အားဖွင့်ပါ။
pdfjs-open-file-button-label = ဖွင့်ပါ
pdfjs-print-button =
.title = ပုံနှိုပ်ပါ
pdfjs-print-button-label = ပုံနှိုပ်ပါ
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = ကိရိယာများ
pdfjs-tools-button-label = ကိရိယာများ
pdfjs-first-page-button =
.title = ပထမ စာမျက်နှာသို့
pdfjs-first-page-button-label = ပထမ စာမျက်နှာသို့
pdfjs-last-page-button =
.title = နောက်ဆုံး စာမျက်နှာသို့
pdfjs-last-page-button-label = နောက်ဆုံး စာမျက်နှာသို့
pdfjs-page-rotate-cw-button =
.title = နာရီလက်တံ အတိုင်း
pdfjs-page-rotate-cw-button-label = နာရီလက်တံ အတိုင်း
pdfjs-page-rotate-ccw-button =
.title = နာရီလက်တံ ပြောင်းပြန်
pdfjs-page-rotate-ccw-button-label = နာရီလက်တံ ပြောင်းပြန်
## Document properties dialog
pdfjs-document-properties-button =
.title = မှတ်တမ်းမှတ်ရာ ဂုဏ်သတ္တိများ
pdfjs-document-properties-button-label = မှတ်တမ်းမှတ်ရာ ဂုဏ်သတ္တိများ
pdfjs-document-properties-file-name = ဖိုင် :
pdfjs-document-properties-file-size = ဖိုင်ဆိုဒ် :
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } ကီလိုဘိုတ် ({ $size_b }ဘိုတ်)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = ခေါင်းစဉ် -
pdfjs-document-properties-author = ရေးသားသူ:
pdfjs-document-properties-subject = အကြောင်းအရာ:
pdfjs-document-properties-keywords = သော့ချက် စာလုံး:
pdfjs-document-properties-creation-date = ထုတ်လုပ်ရက်စွဲ:
pdfjs-document-properties-modification-date = ပြင်ဆင်ရက်စွဲ:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = ဖန်တီးသူ:
pdfjs-document-properties-producer = PDF ထုတ်လုပ်သူ:
pdfjs-document-properties-version = PDF ဗားရှင်း:
pdfjs-document-properties-page-count = စာမျက်နှာအရေအတွက်:
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
##
pdfjs-document-properties-close-button = ပိတ်
## Print
pdfjs-print-progress-message = Preparing document for printing…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = ပယ်ဖျက်ပါ
pdfjs-printing-not-supported = သတိပေးချက်၊ပရင့်ထုတ်ခြင်းကိုဤဘယောက်ဆာသည် ပြည့်ဝစွာထောက်ပံ့မထားပါ ။
pdfjs-printing-not-ready = သတိပေးချက်: ယခု PDF ဖိုင်သည် ပုံနှိပ်ရန် မပြည့်စုံပါ
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = ဘေးတန်းဖွင့်ပိတ်
pdfjs-toggle-sidebar-button-label = ဖွင့်ပိတ် ဆလိုက်ဒါ
pdfjs-document-outline-button =
.title = စာတမ်းအကျဉ်းချုပ်ကို ပြပါ (စာရင်းအားလုံးကို ချုံ့/ချဲ့ရန် ကလစ်နှစ်ချက်နှိပ်ပါ)
pdfjs-document-outline-button-label = စာတမ်းအကျဉ်းချုပ်
pdfjs-attachments-button =
.title = တွဲချက်များ ပြပါ
pdfjs-attachments-button-label = တွဲထားချက်များ
pdfjs-thumbs-button =
.title = ပုံရိပ်ငယ်များကို ပြပါ
pdfjs-thumbs-button-label = ပုံရိပ်ငယ်များ
pdfjs-findbar-button =
.title = Find in Document
pdfjs-findbar-button-label = ရှာဖွေပါ
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = စာမျက်နှာ { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = စာမျက်နှာရဲ့ ပုံရိပ်ငယ် { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = ရှာဖွေပါ
.placeholder = စာတမ်းထဲတွင် ရှာဖွေရန်…
pdfjs-find-previous-button =
.title = စကားစုရဲ့ အရင် ဖြစ်ပွားမှုကို ရှာဖွေပါ
pdfjs-find-previous-button-label = နောက်သို့
pdfjs-find-next-button =
.title = စကားစုရဲ့ နောက်ထပ် ဖြစ်ပွားမှုကို ရှာဖွေပါ
pdfjs-find-next-button-label = ရှေ့သို့
pdfjs-find-highlight-checkbox = အားလုံးကို မျဉ်းသားပါ
pdfjs-find-match-case-checkbox-label = စာလုံး တိုက်ဆိုင်ပါ
pdfjs-find-reached-top = စာမျက်နှာထိပ် ရောက်နေပြီ၊ အဆုံးကနေ ပြန်စပါ
pdfjs-find-reached-bottom = စာမျက်နှာအဆုံး ရောက်နေပြီ၊ ထိပ်ကနေ ပြန်စပါ
pdfjs-find-not-found = စကားစု မတွေ့ရဘူး
## Predefined zoom values
pdfjs-page-scale-width = စာမျက်နှာ အကျယ်
pdfjs-page-scale-fit = စာမျက်နှာ ကွက်တိ
pdfjs-page-scale-auto = အလိုအလျောက် ချုံ့ချဲ့
pdfjs-page-scale-actual = အမှန်တကယ်ရှိတဲ့ အရွယ်
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
## Loading indicator messages
pdfjs-loading-error = PDF ဖိုင် ကိုဆွဲတင်နေချိန်မှာ အမှားတစ်ခုတွေ့ရပါတယ်။
pdfjs-invalid-file-error = မရသော သို့ ပျက်နေသော PDF ဖိုင်
pdfjs-missing-file-error = PDF ပျောက်ဆုံး
pdfjs-unexpected-response-error = မမျှော်လင့်ထားသော ဆာဗာမှ ပြန်ကြားချက်
pdfjs-rendering-error = စာမျက်နှာကို ပုံဖော်နေချိန်မှာ အမှားတစ်ခုတွေ့ရပါတယ်။
## Annotations
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } အဓိပ္ပာယ်ဖွင့်ဆိုချက်]
## Password
pdfjs-password-label = ယခု PDF ကို ဖွင့်ရန် စကားဝှက်ကို ရိုက်ပါ။
pdfjs-password-invalid = စာဝှက် မှားသည်။ ထပ်ကြိုးစားကြည့်ပါ။
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = ပယ်ဖျက်ပါ
pdfjs-web-fonts-disabled = Web fonts are disabled: unable to use embedded PDF fonts.
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/nb-NO/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Forrige side
pdfjs-previous-button-label = Forrige
pdfjs-next-button =
.title = Neste side
pdfjs-next-button-label = Neste
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Side
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = av { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } av { $pagesCount })
pdfjs-zoom-out-button =
.title = Zoom ut
pdfjs-zoom-out-button-label = Zoom ut
pdfjs-zoom-in-button =
.title = Zoom inn
pdfjs-zoom-in-button-label = Zoom inn
pdfjs-zoom-select =
.title = Zoom
pdfjs-presentation-mode-button =
.title = Bytt til presentasjonsmodus
pdfjs-presentation-mode-button-label = Presentasjonsmodus
pdfjs-open-file-button =
.title = Åpne fil
pdfjs-open-file-button-label = Åpne
pdfjs-print-button =
.title = Skriv ut
pdfjs-print-button-label = Skriv ut
pdfjs-save-button =
.title = Lagre
pdfjs-save-button-label = Lagre
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Last ned
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Last ned
pdfjs-bookmark-button =
.title = Gjeldende side (se URL fra gjeldende side)
pdfjs-bookmark-button-label = Gjeldende side
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Verktøy
pdfjs-tools-button-label = Verktøy
pdfjs-first-page-button =
.title = Gå til første side
pdfjs-first-page-button-label = Gå til første side
pdfjs-last-page-button =
.title = Gå til siste side
pdfjs-last-page-button-label = Gå til siste side
pdfjs-page-rotate-cw-button =
.title = Roter med klokken
pdfjs-page-rotate-cw-button-label = Roter med klokken
pdfjs-page-rotate-ccw-button =
.title = Roter mot klokken
pdfjs-page-rotate-ccw-button-label = Roter mot klokken
pdfjs-cursor-text-select-tool-button =
.title = Aktiver tekstmarkeringsverktøy
pdfjs-cursor-text-select-tool-button-label = Tekstmarkeringsverktøy
pdfjs-cursor-hand-tool-button =
.title = Aktiver handverktøy
pdfjs-cursor-hand-tool-button-label = Handverktøy
pdfjs-scroll-page-button =
.title = Bruk siderulling
pdfjs-scroll-page-button-label = Siderulling
pdfjs-scroll-vertical-button =
.title = Bruk vertikal rulling
pdfjs-scroll-vertical-button-label = Vertikal rulling
pdfjs-scroll-horizontal-button =
.title = Bruk horisontal rulling
pdfjs-scroll-horizontal-button-label = Horisontal rulling
pdfjs-scroll-wrapped-button =
.title = Bruk flersiderulling
pdfjs-scroll-wrapped-button-label = Flersiderulling
pdfjs-spread-none-button =
.title = Vis enkeltsider
pdfjs-spread-none-button-label = Enkeltsider
pdfjs-spread-odd-button =
.title = Vis oppslag med ulike sidenumre til venstre
pdfjs-spread-odd-button-label = Oppslag med forside
pdfjs-spread-even-button =
.title = Vis oppslag med like sidenumre til venstre
pdfjs-spread-even-button-label = Oppslag uten forside
## Document properties dialog
pdfjs-document-properties-button =
.title = Dokumentegenskaper …
pdfjs-document-properties-button-label = Dokumentegenskaper …
pdfjs-document-properties-file-name = Filnavn:
pdfjs-document-properties-file-size = Filstørrelse:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } byte)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } byte)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = Dokumentegenskaper …
pdfjs-document-properties-author = Forfatter:
pdfjs-document-properties-subject = Emne:
pdfjs-document-properties-keywords = Nøkkelord:
pdfjs-document-properties-creation-date = Opprettet dato:
pdfjs-document-properties-modification-date = Endret dato:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Opprettet av:
pdfjs-document-properties-producer = PDF-verktøy:
pdfjs-document-properties-version = PDF-versjon:
pdfjs-document-properties-page-count = Sideantall:
pdfjs-document-properties-page-size = Sidestørrelse:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = stående
pdfjs-document-properties-page-size-orientation-landscape = liggende
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letter
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Hurtig nettvisning:
pdfjs-document-properties-linearized-yes = Ja
pdfjs-document-properties-linearized-no = Nei
pdfjs-document-properties-close-button = Lukk
## Print
pdfjs-print-progress-message = Forbereder dokument for utskrift …
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Avbryt
pdfjs-printing-not-supported = Advarsel: Utskrift er ikke fullstendig støttet av denne nettleseren.
pdfjs-printing-not-ready = Advarsel: PDF er ikke fullstendig innlastet for utskrift.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Slå av/på sidestolpe
pdfjs-toggle-sidebar-notification-button =
.title = Vis/gjem sidestolpe (dokumentet inneholder oversikt/vedlegg/lag)
pdfjs-toggle-sidebar-button-label = Slå av/på sidestolpe
pdfjs-document-outline-button =
.title = Vis dokumentdisposisjonen (dobbeltklikk for å utvide/skjule alle elementer)
pdfjs-document-outline-button-label = Dokumentdisposisjon
pdfjs-attachments-button =
.title = Vis vedlegg
pdfjs-attachments-button-label = Vedlegg
pdfjs-layers-button =
.title = Vis lag (dobbeltklikk for å tilbakestille alle lag til standardtilstand)
pdfjs-layers-button-label = Lag
pdfjs-thumbs-button =
.title = Vis miniatyrbilde
pdfjs-thumbs-button-label = Miniatyrbilde
pdfjs-current-outline-item-button =
.title = Finn gjeldende disposisjonselement
pdfjs-current-outline-item-button-label = Gjeldende disposisjonselement
pdfjs-findbar-button =
.title = Finn i dokumentet
pdfjs-findbar-button-label = Finn
pdfjs-additional-layers = Ytterligere lag
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Side { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Miniatyrbilde av side { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Søk
.placeholder = Søk i dokument…
pdfjs-find-previous-button =
.title = Finn forrige forekomst av frasen
pdfjs-find-previous-button-label = Forrige
pdfjs-find-next-button =
.title = Finn neste forekomst av frasen
pdfjs-find-next-button-label = Neste
pdfjs-find-highlight-checkbox = Uthev alle
pdfjs-find-match-case-checkbox-label = Skill store/små bokstaver
pdfjs-find-match-diacritics-checkbox-label = Samsvar diakritiske tegn
pdfjs-find-entire-word-checkbox-label = Hele ord
pdfjs-find-reached-top = Nådde toppen av dokumentet, fortsetter fra bunnen
pdfjs-find-reached-bottom = Nådde bunnen av dokumentet, fortsetter fra toppen
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } av { $total } treff
*[other] { $current } av { $total } treff
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Mer enn { $limit } treff
*[other] Mer enn { $limit } treff
}
pdfjs-find-not-found = Fant ikke teksten
## Predefined zoom values
pdfjs-page-scale-width = Sidebredde
pdfjs-page-scale-fit = Tilpass til siden
pdfjs-page-scale-auto = Automatisk zoom
pdfjs-page-scale-actual = Virkelig størrelse
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale } %
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Side { $page }
## Loading indicator messages
pdfjs-loading-error = En feil oppstod ved lasting av PDF.
pdfjs-invalid-file-error = Ugyldig eller skadet PDF-fil.
pdfjs-missing-file-error = Manglende PDF-fil.
pdfjs-unexpected-response-error = Uventet serverrespons.
pdfjs-rendering-error = En feil oppstod ved opptegning av siden.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } annotasjon]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Skriv inn passordet for å åpne denne PDF-filen.
pdfjs-password-invalid = Ugyldig passord. Prøv igjen.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Avbryt
pdfjs-web-fonts-disabled = Web-fonter er avslått: Kan ikke bruke innbundne PDF-fonter.
## Editing
pdfjs-editor-free-text-button =
.title = Tekst
pdfjs-editor-free-text-button-label = Tekst
pdfjs-editor-ink-button =
.title = Tegn
pdfjs-editor-ink-button-label = Tegn
pdfjs-editor-stamp-button =
.title = Legg til eller rediger bilder
pdfjs-editor-stamp-button-label = Legg til eller rediger bilder
pdfjs-editor-highlight-button =
.title = Markere
pdfjs-editor-highlight-button-label = Markere
pdfjs-highlight-floating-button1 =
.title = Markere
.aria-label = Markere
pdfjs-highlight-floating-button-label = Markere
pdfjs-editor-signature-button =
.title = Legg til signatur
pdfjs-editor-signature-button-label = Legg til signatur
## Default editor aria labels
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Fjern tegningen
pdfjs-editor-remove-freetext-button =
.title = Fjern tekst
pdfjs-editor-remove-stamp-button =
.title = Fjern bildet
pdfjs-editor-remove-highlight-button =
.title = Fjern utheving
pdfjs-editor-remove-signature-button =
.title = Fjern signatur
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Farge
pdfjs-editor-free-text-size-input = Størrelse
pdfjs-editor-ink-color-input = Farge
pdfjs-editor-ink-thickness-input = Tykkelse
pdfjs-editor-ink-opacity-input = Ugjennomsiktighet
pdfjs-editor-stamp-add-image-button =
.title = Legg til bilde
pdfjs-editor-stamp-add-image-button-label = Legg til bilde
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Tykkelse
pdfjs-editor-free-highlight-thickness-title =
.title = Endre tykkelse når du markerer andre elementer enn tekst
pdfjs-editor-signature-add-signature-button =
.title = Legg til ny signatur
pdfjs-editor-signature-add-signature-button-label = Legg til ny signatur
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Tekstredigering
.default-content = Begynn å skrive…
pdfjs-free-text =
.aria-label = Tekstredigering
pdfjs-free-text-default-content = Begynn å skrive…
pdfjs-ink =
.aria-label = Tegneredigering
pdfjs-ink-canvas =
.aria-label = Brukerskapt bilde
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Alt-tekst
pdfjs-editor-alt-text-edit-button =
.aria-label = Rediger alt-tekst
pdfjs-editor-alt-text-edit-button-label = Rediger alt-tekst tekst
pdfjs-editor-alt-text-dialog-label = Velg et alternativ
pdfjs-editor-alt-text-dialog-description = Alt-tekst (alternativ tekst) hjelper når folk ikke kan se bildet eller når det ikke lastes inn.
pdfjs-editor-alt-text-add-description-label = Legg til en beskrivelse
pdfjs-editor-alt-text-add-description-description = Gå etter 1-2 setninger som beskriver emnet, settingen eller handlingene.
pdfjs-editor-alt-text-mark-decorative-label = Merk som dekorativt
pdfjs-editor-alt-text-mark-decorative-description = Dette brukes til dekorative bilder, som kantlinjer eller vannmerker.
pdfjs-editor-alt-text-cancel-button = Avbryt
pdfjs-editor-alt-text-save-button = Lagre
pdfjs-editor-alt-text-decorative-tooltip = Merket som dekorativ
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = For eksempel, «En ung mann setter seg ved et bord for å spise et måltid»
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Alt-tekst
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Øverste venstre hjørne – endre størrelse
pdfjs-editor-resizer-label-top-middle = Øverst i midten — endre størrelse
pdfjs-editor-resizer-label-top-right = Øverste høyre hjørne – endre størrelse
pdfjs-editor-resizer-label-middle-right = Midt til høyre – endre størrelse
pdfjs-editor-resizer-label-bottom-right = Nederste høyre hjørne – endre størrelse
pdfjs-editor-resizer-label-bottom-middle = Nederst i midten — endre størrelse
pdfjs-editor-resizer-label-bottom-left = Nederste venstre hjørne – endre størrelse
pdfjs-editor-resizer-label-middle-left = Midt til venstre — endre størrelse
pdfjs-editor-resizer-top-left =
.aria-label = Øverste venstre hjørne – endre størrelse
pdfjs-editor-resizer-top-middle =
.aria-label = Øverst i midten — endre størrelse
pdfjs-editor-resizer-top-right =
.aria-label = Øverste høyre hjørne – endre størrelse
pdfjs-editor-resizer-middle-right =
.aria-label = Midt til høyre – endre størrelse
pdfjs-editor-resizer-bottom-right =
.aria-label = Nederste høyre hjørne – endre størrelse
pdfjs-editor-resizer-bottom-middle =
.aria-label = Nederst i midten — endre størrelse
pdfjs-editor-resizer-bottom-left =
.aria-label = Nederste venstre hjørne – endre størrelse
pdfjs-editor-resizer-middle-left =
.aria-label = Midt til venstre — endre størrelse
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Uthevingsfarge
pdfjs-editor-colorpicker-button =
.title = Endre farge
pdfjs-editor-colorpicker-dropdown =
.aria-label = Fargevalg
pdfjs-editor-colorpicker-yellow =
.title = Gul
pdfjs-editor-colorpicker-green =
.title = Grønn
pdfjs-editor-colorpicker-blue =
.title = Blå
pdfjs-editor-colorpicker-pink =
.title = Rosa
pdfjs-editor-colorpicker-red =
.title = Rød
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Vis alle
pdfjs-editor-highlight-show-all-button =
.title = Vis alle
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Rediger alternativ tekst (bildebeskrivelse)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Legg til alternativ tekst (bildebeskrivelse)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Skriv din beskrivelse her…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Kort beskrivelse for folk som ikke kan se bildet eller når bildet ikke lastes inn.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Denne alternative teksten ble opprettet automatisk og kan være unøyaktig.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Les mer
pdfjs-editor-new-alt-text-create-automatically-button-label = Lag alternativ tekst automatisk
pdfjs-editor-new-alt-text-not-now-button = Ikke nå
pdfjs-editor-new-alt-text-error-title = Kunne ikke opprette alternativ tekst automatisk
pdfjs-editor-new-alt-text-error-description = Skriv din egen alternativ-tekst eller prøv igjen senere.
pdfjs-editor-new-alt-text-error-close-button = Lukk
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Laster ned alternativ tekst AI-modell ({ $downloadedSize } av { $totalSize } MB)
.aria-valuetext = Laster ned alternativ tekst AI-modell ({ $downloadedSize } av { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Alt-tekst lagt til
pdfjs-editor-new-alt-text-added-button-label = Alternativ tekst lagt til
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Mangler alternativ tekst
pdfjs-editor-new-alt-text-missing-button-label = Mangler alternativ tekst
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Gjennomgå alt-tekst
pdfjs-editor-new-alt-text-to-review-button-label = Gjennomgå alternativ tekst
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Opprettet automatisk: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Innstillinger for alternativ tekst for bilde
pdfjs-image-alt-text-settings-button-label = Innstillinger for alternativ tekst for bilde
pdfjs-editor-alt-text-settings-dialog-label = Innstillinger for alternativ tekst for bilde
pdfjs-editor-alt-text-settings-automatic-title = Automatisk alternativ tekst
pdfjs-editor-alt-text-settings-create-model-button-label = Opprett alternativ tekst automatisk
pdfjs-editor-alt-text-settings-create-model-description = Foreslår beskrivelser for å hjelpe folk som ikke kan se bildet eller når bildet ikke lastes inn.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Alternativ tekst AI-modell ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Kjører lokalt på enheten din slik at dataene dine forblir private. Nødvendig for automatisk alternativ tekst.
pdfjs-editor-alt-text-settings-delete-model-button = Slett
pdfjs-editor-alt-text-settings-download-model-button = Last ned
pdfjs-editor-alt-text-settings-downloading-model-button = Laster ned…
pdfjs-editor-alt-text-settings-editor-title = Alternativ tekst-redigerer
pdfjs-editor-alt-text-settings-show-dialog-button-label = Vis alternativ tekst-redigerer direkte når du legger til et bilde
pdfjs-editor-alt-text-settings-show-dialog-description = Hjelper deg å sørge for at alle bildene dine har alternativ tekst.
pdfjs-editor-alt-text-settings-close-button = Lukk
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Markering fjernet
pdfjs-editor-undo-bar-message-freetext = Tekst fjernet
pdfjs-editor-undo-bar-message-ink = Tegning fjernet
pdfjs-editor-undo-bar-message-stamp = Bilde fjernet
pdfjs-editor-undo-bar-message-signature = Signatur fjernet
pdfjs-editor-undo-bar-undo-button =
.title = Angre
pdfjs-editor-undo-bar-undo-button-label = Angre
pdfjs-editor-undo-bar-close-button =
.title = Lukk
pdfjs-editor-undo-bar-close-button-label = Lukk
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = Denne modalen lar brukeren lage en signatur for å legge til et PDF-dokument. Brukeren kan redigere navnet (som også fungerer som alt-teksten), og eventuelt lagre signaturen for gjentatt bruk.
pdfjs-editor-add-signature-dialog-title = Legg til en signatur
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Type
.title = Type
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Tegn
.title = Tegn
pdfjs-editor-add-signature-image-button = Bilde
.title = Bilde
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Skriv inn signaturen din
.placeholder = Skriv inn signaturen din
pdfjs-editor-add-signature-draw-placeholder = Tegn signaturen din
pdfjs-editor-add-signature-draw-thickness-range-label = Tykkelse
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Linjetykkelse: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Dra en fil her for å laste opp
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Eller velg bildefiler
*[other] Eller velg bildefiler
}
## Controls
pdfjs-editor-add-signature-description-label = Beskrivelse (alternativ tekst)
pdfjs-editor-add-signature-description-input =
.title = Beskrivelse (alternativ tekst)
pdfjs-editor-add-signature-description-default-when-drawing = Signatur
pdfjs-editor-add-signature-clear-button-label = Fjern signatur
pdfjs-editor-add-signature-clear-button =
.title = Fjern signatur
pdfjs-editor-add-signature-save-checkbox = Lagre signatur
pdfjs-editor-add-signature-save-warning-message = Du har nådd grensen på 5 lagrede signaturer. Fjern en for å lagre en ny.
pdfjs-editor-add-signature-image-upload-error-title = Kunne ikke laste opp bildet
pdfjs-editor-add-signature-image-upload-error-description = Sjekk nettverkstilkoblingen eller prøv et annet bilde.
pdfjs-editor-add-signature-error-close-button = Lukk
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Avbryt
pdfjs-editor-add-signature-add-button = Legg til
pdfjs-editor-edit-signature-update-button = Oppdater
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Fjern signatur
pdfjs-editor-delete-signature-button-label = Fjern signatur
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Rediger beskrivelse
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Rediger beskrivelse
================================================
FILE: cookbook/static/pdfjs/web/locale/ne-NP/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = अघिल्लो पृष्ठ
pdfjs-previous-button-label = अघिल्लो
pdfjs-next-button =
.title = पछिल्लो पृष्ठ
pdfjs-next-button-label = पछिल्लो
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = पृष्ठ
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = { $pagesCount } मध्ये
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pagesCount } को { $pageNumber })
pdfjs-zoom-out-button =
.title = जुम घटाउनुहोस्
pdfjs-zoom-out-button-label = जुम घटाउनुहोस्
pdfjs-zoom-in-button =
.title = जुम बढाउनुहोस्
pdfjs-zoom-in-button-label = जुम बढाउनुहोस्
pdfjs-zoom-select =
.title = जुम गर्नुहोस्
pdfjs-presentation-mode-button =
.title = प्रस्तुति मोडमा जानुहोस्
pdfjs-presentation-mode-button-label = प्रस्तुति मोड
pdfjs-open-file-button =
.title = फाइल खोल्नुहोस्
pdfjs-open-file-button-label = खोल्नुहोस्
pdfjs-print-button =
.title = मुद्रण गर्नुहोस्
pdfjs-print-button-label = मुद्रण गर्नुहोस्
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = औजारहरू
pdfjs-tools-button-label = औजारहरू
pdfjs-first-page-button =
.title = पहिलो पृष्ठमा जानुहोस्
pdfjs-first-page-button-label = पहिलो पृष्ठमा जानुहोस्
pdfjs-last-page-button =
.title = पछिल्लो पृष्ठमा जानुहोस्
pdfjs-last-page-button-label = पछिल्लो पृष्ठमा जानुहोस्
pdfjs-page-rotate-cw-button =
.title = घडीको दिशामा घुमाउनुहोस्
pdfjs-page-rotate-cw-button-label = घडीको दिशामा घुमाउनुहोस्
pdfjs-page-rotate-ccw-button =
.title = घडीको विपरित दिशामा घुमाउनुहोस्
pdfjs-page-rotate-ccw-button-label = घडीको विपरित दिशामा घुमाउनुहोस्
pdfjs-cursor-text-select-tool-button =
.title = पाठ चयन उपकरण सक्षम गर्नुहोस्
pdfjs-cursor-text-select-tool-button-label = पाठ चयन उपकरण
pdfjs-cursor-hand-tool-button =
.title = हाते उपकरण सक्षम गर्नुहोस्
pdfjs-cursor-hand-tool-button-label = हाते उपकरण
pdfjs-scroll-vertical-button =
.title = ठाडो स्क्रोलिङ्ग प्रयोग गर्नुहोस्
pdfjs-scroll-vertical-button-label = ठाडो स्क्र्रोलिङ्ग
pdfjs-scroll-horizontal-button =
.title = तेर्सो स्क्रोलिङ्ग प्रयोग गर्नुहोस्
pdfjs-scroll-horizontal-button-label = तेर्सो स्क्रोलिङ्ग
pdfjs-scroll-wrapped-button =
.title = लिपि स्क्रोलिङ्ग प्रयोग गर्नुहोस्
pdfjs-scroll-wrapped-button-label = लिपि स्क्रोलिङ्ग
pdfjs-spread-none-button =
.title = पृष्ठ स्प्रेडमा सामेल हुनुहुन्न
pdfjs-spread-none-button-label = स्प्रेड छैन
## Document properties dialog
pdfjs-document-properties-button =
.title = कागजात विशेषताहरू...
pdfjs-document-properties-button-label = कागजात विशेषताहरू...
pdfjs-document-properties-file-name = फाइल नाम:
pdfjs-document-properties-file-size = फाइल आकार:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = शीर्षक:
pdfjs-document-properties-author = लेखक:
pdfjs-document-properties-subject = विषयः
pdfjs-document-properties-keywords = शब्दकुञ्जीः
pdfjs-document-properties-creation-date = सिर्जना गरिएको मिति:
pdfjs-document-properties-modification-date = परिमार्जित मिति:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = सर्जक:
pdfjs-document-properties-producer = PDF निर्माता:
pdfjs-document-properties-version = PDF संस्करण
pdfjs-document-properties-page-count = पृष्ठ गणना:
pdfjs-document-properties-page-size = पृष्ठ आकार:
pdfjs-document-properties-page-size-unit-inches = इन्च
pdfjs-document-properties-page-size-unit-millimeters = मि.मि.
pdfjs-document-properties-page-size-orientation-portrait = पोट्रेट
pdfjs-document-properties-page-size-orientation-landscape = परिदृश्य
pdfjs-document-properties-page-size-name-letter = अक्षर
pdfjs-document-properties-page-size-name-legal = कानूनी
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
##
pdfjs-document-properties-linearized-yes = हो
pdfjs-document-properties-linearized-no = होइन
pdfjs-document-properties-close-button = बन्द गर्नुहोस्
## Print
pdfjs-print-progress-message = मुद्रणका लागि कागजात तयारी गरिदै…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = रद्द गर्नुहोस्
pdfjs-printing-not-supported = चेतावनी: यो ब्राउजरमा मुद्रण पूर्णतया समर्थित छैन।
pdfjs-printing-not-ready = चेतावनी: PDF मुद्रणका लागि पूर्णतया लोड भएको छैन।
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = टगल साइडबार
pdfjs-toggle-sidebar-button-label = टगल साइडबार
pdfjs-document-outline-button =
.title = कागजातको रूपरेखा देखाउनुहोस् (सबै वस्तुहरू विस्तार/पतन गर्न डबल-क्लिक गर्नुहोस्)
pdfjs-document-outline-button-label = दस्तावेजको रूपरेखा
pdfjs-attachments-button =
.title = संलग्नहरू देखाउनुहोस्
pdfjs-attachments-button-label = संलग्नकहरू
pdfjs-thumbs-button =
.title = थम्बनेलहरू देखाउनुहोस्
pdfjs-thumbs-button-label = थम्बनेलहरू
pdfjs-findbar-button =
.title = कागजातमा फेला पार्नुहोस्
pdfjs-findbar-button-label = फेला पार्नुहोस्
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = पृष्ठ { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = { $page } पृष्ठको थम्बनेल
## Find panel button title and messages
pdfjs-find-input =
.title = फेला पार्नुहोस्
.placeholder = कागजातमा फेला पार्नुहोस्…
pdfjs-find-previous-button =
.title = यस वाक्यांशको अघिल्लो घटना फेला पार्नुहोस्
pdfjs-find-previous-button-label = अघिल्लो
pdfjs-find-next-button =
.title = यस वाक्यांशको पछिल्लो घटना फेला पार्नुहोस्
pdfjs-find-next-button-label = अर्को
pdfjs-find-highlight-checkbox = सबै हाइलाइट गर्ने
pdfjs-find-match-case-checkbox-label = केस जोडा मिलाउनुहोस्
pdfjs-find-entire-word-checkbox-label = पुरा शब्दहरु
pdfjs-find-reached-top = पृष्ठको शिर्षमा पुगीयो, तलबाट जारी गरिएको थियो
pdfjs-find-reached-bottom = पृष्ठको अन्त्यमा पुगीयो, शिर्षबाट जारी गरिएको थियो
pdfjs-find-not-found = वाक्यांश फेला परेन
## Predefined zoom values
pdfjs-page-scale-width = पृष्ठ चौडाइ
pdfjs-page-scale-fit = पृष्ठ ठिक्क मिल्ने
pdfjs-page-scale-auto = स्वचालित जुम
pdfjs-page-scale-actual = वास्तविक आकार
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
## Loading indicator messages
pdfjs-loading-error = यो PDF लोड गर्दा एउटा त्रुटि देखापर्यो।
pdfjs-invalid-file-error = अवैध वा दुषित PDF फाइल।
pdfjs-missing-file-error = हराईरहेको PDF फाइल।
pdfjs-unexpected-response-error = अप्रत्याशित सर्भर प्रतिक्रिया।
pdfjs-rendering-error = पृष्ठ प्रतिपादन गर्दा एउटा त्रुटि देखापर्यो।
## Annotations
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } Annotation]
## Password
pdfjs-password-label = यस PDF फाइललाई खोल्न गोप्यशब्द प्रविष्ट गर्नुहोस्।
pdfjs-password-invalid = अवैध गोप्यशब्द। पुनः प्रयास गर्नुहोस्।
pdfjs-password-ok-button = ठिक छ
pdfjs-password-cancel-button = रद्द गर्नुहोस्
pdfjs-web-fonts-disabled = वेब फन्ट असक्षम छन्: एम्बेडेड PDF फन्ट प्रयोग गर्न असमर्थ।
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/nl/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Vorige pagina
pdfjs-previous-button-label = Vorige
pdfjs-next-button =
.title = Volgende pagina
pdfjs-next-button-label = Volgende
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Pagina
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = van { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } van { $pagesCount })
pdfjs-zoom-out-button =
.title = Uitzoomen
pdfjs-zoom-out-button-label = Uitzoomen
pdfjs-zoom-in-button =
.title = Inzoomen
pdfjs-zoom-in-button-label = Inzoomen
pdfjs-zoom-select =
.title = Zoomen
pdfjs-presentation-mode-button =
.title = Wisselen naar presentatiemodus
pdfjs-presentation-mode-button-label = Presentatiemodus
pdfjs-open-file-button =
.title = Bestand openen
pdfjs-open-file-button-label = Openen
pdfjs-print-button =
.title = Afdrukken
pdfjs-print-button-label = Afdrukken
pdfjs-save-button =
.title = Opslaan
pdfjs-save-button-label = Opslaan
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Downloaden
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Downloaden
pdfjs-bookmark-button =
.title = Huidige pagina (URL van huidige pagina bekijken)
pdfjs-bookmark-button-label = Huidige pagina
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Hulpmiddelen
pdfjs-tools-button-label = Hulpmiddelen
pdfjs-first-page-button =
.title = Naar eerste pagina gaan
pdfjs-first-page-button-label = Naar eerste pagina gaan
pdfjs-last-page-button =
.title = Naar laatste pagina gaan
pdfjs-last-page-button-label = Naar laatste pagina gaan
pdfjs-page-rotate-cw-button =
.title = Rechtsom draaien
pdfjs-page-rotate-cw-button-label = Rechtsom draaien
pdfjs-page-rotate-ccw-button =
.title = Linksom draaien
pdfjs-page-rotate-ccw-button-label = Linksom draaien
pdfjs-cursor-text-select-tool-button =
.title = Tekstselectiehulpmiddel inschakelen
pdfjs-cursor-text-select-tool-button-label = Tekstselectiehulpmiddel
pdfjs-cursor-hand-tool-button =
.title = Handhulpmiddel inschakelen
pdfjs-cursor-hand-tool-button-label = Handhulpmiddel
pdfjs-scroll-page-button =
.title = Paginascrollen gebruiken
pdfjs-scroll-page-button-label = Paginascrollen
pdfjs-scroll-vertical-button =
.title = Verticaal scrollen gebruiken
pdfjs-scroll-vertical-button-label = Verticaal scrollen
pdfjs-scroll-horizontal-button =
.title = Horizontaal scrollen gebruiken
pdfjs-scroll-horizontal-button-label = Horizontaal scrollen
pdfjs-scroll-wrapped-button =
.title = Scrollen met terugloop gebruiken
pdfjs-scroll-wrapped-button-label = Scrollen met terugloop
pdfjs-spread-none-button =
.title = Dubbele pagina’s niet samenvoegen
pdfjs-spread-none-button-label = Geen dubbele pagina’s
pdfjs-spread-odd-button =
.title = Dubbele pagina’s samenvoegen vanaf oneven pagina’s
pdfjs-spread-odd-button-label = Oneven dubbele pagina’s
pdfjs-spread-even-button =
.title = Dubbele pagina’s samenvoegen vanaf even pagina’s
pdfjs-spread-even-button-label = Even dubbele pagina’s
## Document properties dialog
pdfjs-document-properties-button =
.title = Documenteigenschappen…
pdfjs-document-properties-button-label = Documenteigenschappen…
pdfjs-document-properties-file-name = Bestandsnaam:
pdfjs-document-properties-file-size = Bestandsgrootte:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = Titel:
pdfjs-document-properties-author = Auteur:
pdfjs-document-properties-subject = Onderwerp:
pdfjs-document-properties-keywords = Sleutelwoorden:
pdfjs-document-properties-creation-date = Aanmaakdatum:
pdfjs-document-properties-modification-date = Wijzigingsdatum:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Maker:
pdfjs-document-properties-producer = PDF-producent:
pdfjs-document-properties-version = PDF-versie:
pdfjs-document-properties-page-count = Aantal pagina’s:
pdfjs-document-properties-page-size = Paginagrootte:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = staand
pdfjs-document-properties-page-size-orientation-landscape = liggend
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letter
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Snelle webweergave:
pdfjs-document-properties-linearized-yes = Ja
pdfjs-document-properties-linearized-no = Nee
pdfjs-document-properties-close-button = Sluiten
## Print
pdfjs-print-progress-message = Document voorbereiden voor afdrukken…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Annuleren
pdfjs-printing-not-supported = Waarschuwing: afdrukken wordt niet volledig ondersteund door deze browser.
pdfjs-printing-not-ready = Waarschuwing: de PDF is niet volledig geladen voor afdrukken.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Zijbalk in-/uitschakelen
pdfjs-toggle-sidebar-notification-button =
.title = Zijbalk in-/uitschakelen (document bevat overzicht/bijlagen/lagen)
pdfjs-toggle-sidebar-button-label = Zijbalk in-/uitschakelen
pdfjs-document-outline-button =
.title = Documentoverzicht tonen (dubbelklik om alle items uit/samen te vouwen)
pdfjs-document-outline-button-label = Documentoverzicht
pdfjs-attachments-button =
.title = Bijlagen tonen
pdfjs-attachments-button-label = Bijlagen
pdfjs-layers-button =
.title = Lagen tonen (dubbelklik om alle lagen naar de standaardstatus terug te zetten)
pdfjs-layers-button-label = Lagen
pdfjs-thumbs-button =
.title = Miniaturen tonen
pdfjs-thumbs-button-label = Miniaturen
pdfjs-current-outline-item-button =
.title = Huidig item in inhoudsopgave zoeken
pdfjs-current-outline-item-button-label = Huidig item in inhoudsopgave
pdfjs-findbar-button =
.title = Zoeken in document
pdfjs-findbar-button-label = Zoeken
pdfjs-additional-layers = Aanvullende lagen
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Pagina { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Miniatuur van pagina { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Zoeken
.placeholder = Zoeken in document…
pdfjs-find-previous-button =
.title = De vorige overeenkomst van de tekst zoeken
pdfjs-find-previous-button-label = Vorige
pdfjs-find-next-button =
.title = De volgende overeenkomst van de tekst zoeken
pdfjs-find-next-button-label = Volgende
pdfjs-find-highlight-checkbox = Alles markeren
pdfjs-find-match-case-checkbox-label = Hoofdlettergevoelig
pdfjs-find-match-diacritics-checkbox-label = Diakritische tekens gebruiken
pdfjs-find-entire-word-checkbox-label = Hele woorden
pdfjs-find-reached-top = Bovenkant van document bereikt, doorgegaan vanaf onderkant
pdfjs-find-reached-bottom = Onderkant van document bereikt, doorgegaan vanaf bovenkant
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } van { $total } overeenkomst
*[other] { $current } van { $total } overeenkomsten
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Meer dan { $limit } overeenkomst
*[other] Meer dan { $limit } overeenkomsten
}
pdfjs-find-not-found = Tekst niet gevonden
## Predefined zoom values
pdfjs-page-scale-width = Paginabreedte
pdfjs-page-scale-fit = Hele pagina
pdfjs-page-scale-auto = Automatisch zoomen
pdfjs-page-scale-actual = Werkelijke grootte
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Pagina { $page }
## Loading indicator messages
pdfjs-loading-error = Er is een fout opgetreden bij het laden van de PDF.
pdfjs-invalid-file-error = Ongeldig of beschadigd PDF-bestand.
pdfjs-missing-file-error = PDF-bestand ontbreekt.
pdfjs-unexpected-response-error = Onverwacht serverantwoord.
pdfjs-rendering-error = Er is een fout opgetreden bij het weergeven van de pagina.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type }-aantekening]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Voer het wachtwoord in om dit PDF-bestand te openen.
pdfjs-password-invalid = Ongeldig wachtwoord. Probeer het opnieuw.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Annuleren
pdfjs-web-fonts-disabled = Weblettertypen zijn uitgeschakeld: gebruik van ingebedde PDF-lettertypen is niet mogelijk.
## Editing
pdfjs-editor-free-text-button =
.title = Tekst
pdfjs-editor-free-text-button-label = Tekst
pdfjs-editor-ink-button =
.title = Tekenen
pdfjs-editor-ink-button-label = Tekenen
pdfjs-editor-stamp-button =
.title = Afbeeldingen toevoegen of bewerken
pdfjs-editor-stamp-button-label = Afbeeldingen toevoegen of bewerken
pdfjs-editor-highlight-button =
.title = Markeren
pdfjs-editor-highlight-button-label = Markeren
pdfjs-highlight-floating-button1 =
.title = Markeren
.aria-label = Markeren
pdfjs-highlight-floating-button-label = Markeren
pdfjs-editor-signature-button =
.title = Handtekening toevoegen
pdfjs-editor-signature-button-label = Handtekening toevoegen
## Default editor aria labels
# “Highlight” is a noun, the string is used on the editor for highlights.
pdfjs-editor-highlight-editor =
.aria-label = Markeringsbewerker
# “Drawing” is a noun, the string is used on the editor for drawings.
pdfjs-editor-ink-editor =
.aria-label = Tekeningbewerker
pdfjs-editor-signature-editor =
.aria-label = Handtekeningbewerker
pdfjs-editor-stamp-editor =
.aria-label = Afbeeldingsbewerker
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Tekening verwijderen
pdfjs-editor-remove-freetext-button =
.title = Tekst verwijderen
pdfjs-editor-remove-stamp-button =
.title = Afbeelding verwijderen
pdfjs-editor-remove-highlight-button =
.title = Markering verwijderen
pdfjs-editor-remove-signature-button =
.title = Handtekening verwijderen
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Kleur
pdfjs-editor-free-text-size-input = Grootte
pdfjs-editor-ink-color-input = Kleur
pdfjs-editor-ink-thickness-input = Dikte
pdfjs-editor-ink-opacity-input = Opaciteit
pdfjs-editor-stamp-add-image-button =
.title = Afbeelding toevoegen
pdfjs-editor-stamp-add-image-button-label = Afbeelding toevoegen
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Dikte
pdfjs-editor-free-highlight-thickness-title =
.title = Dikte wijzigen bij accentuering van andere items dan tekst
pdfjs-editor-add-signature-container =
.aria-label = Ondertekeningsinstellingen en opgeslagen ondertekeningen
pdfjs-editor-signature-add-signature-button =
.title = Nieuwe handtekening toevoegen
pdfjs-editor-signature-add-signature-button-label = Nieuwe handtekening toevoegen
# Used on the button to use an already saved signature.
# Variables:
# $description (String) - a string describing/labeling the signature.
pdfjs-editor-add-saved-signature-button =
.title = Opgeslagen ondertekening: { $description }
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Tekstbewerker
.default-content = Start met typen…
pdfjs-free-text =
.aria-label = Tekstbewerker
pdfjs-free-text-default-content = Begin met typen…
pdfjs-ink =
.aria-label = Tekeningbewerker
pdfjs-ink-canvas =
.aria-label = Door gebruiker gemaakte afbeelding
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Alternatieve tekst
pdfjs-editor-alt-text-edit-button =
.aria-label = Alternatieve tekst bewerken
pdfjs-editor-alt-text-edit-button-label = Alternatieve tekst bewerken
pdfjs-editor-alt-text-dialog-label = Kies een optie
pdfjs-editor-alt-text-dialog-description = Alternatieve tekst helpt wanneer mensen de afbeelding niet kunnen zien of wanneer deze niet wordt geladen.
pdfjs-editor-alt-text-add-description-label = Voeg een beschrijving toe
pdfjs-editor-alt-text-add-description-description = Streef naar 1-2 zinnen die het onderwerp, de omgeving of de acties beschrijven.
pdfjs-editor-alt-text-mark-decorative-label = Als decoratief markeren
pdfjs-editor-alt-text-mark-decorative-description = Dit wordt gebruikt voor sierafbeeldingen, zoals randen of watermerken.
pdfjs-editor-alt-text-cancel-button = Annuleren
pdfjs-editor-alt-text-save-button = Opslaan
pdfjs-editor-alt-text-decorative-tooltip = Als decoratief gemarkeerd
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Bijvoorbeeld: ‘Een jonge man gaat aan een tafel zitten om te eten’
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Alternatieve tekst
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Linkerbovenhoek – formaat wijzigen
pdfjs-editor-resizer-label-top-middle = Midden boven – formaat wijzigen
pdfjs-editor-resizer-label-top-right = Rechterbovenhoek – formaat wijzigen
pdfjs-editor-resizer-label-middle-right = Midden rechts – formaat wijzigen
pdfjs-editor-resizer-label-bottom-right = Rechterbenedenhoek – formaat wijzigen
pdfjs-editor-resizer-label-bottom-middle = Midden onder – formaat wijzigen
pdfjs-editor-resizer-label-bottom-left = Linkerbenedenhoek – formaat wijzigen
pdfjs-editor-resizer-label-middle-left = Links midden – formaat wijzigen
pdfjs-editor-resizer-top-left =
.aria-label = Linkerbovenhoek – formaat wijzigen
pdfjs-editor-resizer-top-middle =
.aria-label = Midden boven – formaat wijzigen
pdfjs-editor-resizer-top-right =
.aria-label = Rechterbovenhoek – formaat wijzigen
pdfjs-editor-resizer-middle-right =
.aria-label = Midden rechts – formaat wijzigen
pdfjs-editor-resizer-bottom-right =
.aria-label = Rechterbenedenhoek – formaat wijzigen
pdfjs-editor-resizer-bottom-middle =
.aria-label = Midden onder – formaat wijzigen
pdfjs-editor-resizer-bottom-left =
.aria-label = Linkerbenedenhoek – formaat wijzigen
pdfjs-editor-resizer-middle-left =
.aria-label = Links midden – formaat wijzigen
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Markeringskleur
pdfjs-editor-colorpicker-button =
.title = Kleur wijzigen
pdfjs-editor-colorpicker-dropdown =
.aria-label = Kleurkeuzes
pdfjs-editor-colorpicker-yellow =
.title = Geel
pdfjs-editor-colorpicker-green =
.title = Groen
pdfjs-editor-colorpicker-blue =
.title = Blauw
pdfjs-editor-colorpicker-pink =
.title = Roze
pdfjs-editor-colorpicker-red =
.title = Rood
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Alles tonen
pdfjs-editor-highlight-show-all-button =
.title = Alles tonen
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Alternatieve tekst (afbeeldingsbeschrijving) bewerken
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Alternatieve tekst (afbeeldingsbeschrijving) toevoegen
pdfjs-editor-new-alt-text-textarea =
.placeholder = Schrijf hier uw beschrijving…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Korte beschrijving voor mensen die de afbeelding niet kunnen zien of wanneer de afbeelding niet wordt geladen.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Deze alternatieve tekst is automatisch gemaakt en is mogelijk onjuist.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Meer info
pdfjs-editor-new-alt-text-create-automatically-button-label = Alternatieve tekst automatisch aanmaken
pdfjs-editor-new-alt-text-not-now-button = Niet nu
pdfjs-editor-new-alt-text-error-title = Kan alternatieve tekst niet automatisch aanmaken
pdfjs-editor-new-alt-text-error-description = Schrijf uw eigen alternatieve tekst of probeer het later nog eens.
pdfjs-editor-new-alt-text-error-close-button = Sluiten
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = AI-model voor alternatieve tekst downloaden ({ $downloadedSize } van { $totalSize } MB)
.aria-valuetext = AI-model voor alternatieve tekst downloaden ({ $downloadedSize } van { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Alternatieve tekst toegevoegd
pdfjs-editor-new-alt-text-added-button-label = Alternatieve tekst toegevoegd
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Alternatieve tekst ontbreekt
pdfjs-editor-new-alt-text-missing-button-label = Alternatieve tekst ontbreekt
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Alternatieve tekst beoordelen
pdfjs-editor-new-alt-text-to-review-button-label = Alternatieve tekst beoordelen
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Automatisch aangemaakt: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Instellingen voor alternatieve tekst van afbeeldingen
pdfjs-image-alt-text-settings-button-label = Instellingen voor alternatieve tekst van afbeeldingen
pdfjs-editor-alt-text-settings-dialog-label = Instellingen voor alternatieve tekst van afbeeldingen
pdfjs-editor-alt-text-settings-automatic-title = Automatische alternatieve tekst
pdfjs-editor-alt-text-settings-create-model-button-label = Alternatieve tekst automatisch aanmaken
pdfjs-editor-alt-text-settings-create-model-description = Stelt beschrijvingen voor om mensen te helpen die de afbeelding niet kunnen zien of voor wie de afbeelding niet wordt geladen.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = AI-model voor alternatieve tekst ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Wordt lokaal op uw apparaat uitgevoerd, zodat uw gegevens privé blijven. Vereist voor automatische alternatieve tekst.
pdfjs-editor-alt-text-settings-delete-model-button = Verwijderen
pdfjs-editor-alt-text-settings-download-model-button = Downloaden
pdfjs-editor-alt-text-settings-downloading-model-button = Downloaden…
pdfjs-editor-alt-text-settings-editor-title = Alternatieve-tekstbewerker
pdfjs-editor-alt-text-settings-show-dialog-button-label = Alternatieve-tekstbewerker meteen tonen bij toevoegen van een afbeelding
pdfjs-editor-alt-text-settings-show-dialog-description = Helpt u ervoor te zorgen dat al uw afbeeldingen alternatieve tekst hebben.
pdfjs-editor-alt-text-settings-close-button = Sluiten
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Markering verwijderd
pdfjs-editor-undo-bar-message-freetext = Tekst verwijderd
pdfjs-editor-undo-bar-message-ink = Tekening verwijderd
pdfjs-editor-undo-bar-message-stamp = Afbeelding verwijderd
pdfjs-editor-undo-bar-message-signature = Handtekening verwijderd
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } annotatie verwijderd
*[other] { $count } annotaties verwijderd
}
pdfjs-editor-undo-bar-undo-button =
.title = Ongedaan maken
pdfjs-editor-undo-bar-undo-button-label = Ongedaan maken
pdfjs-editor-undo-bar-close-button =
.title = Sluiten
pdfjs-editor-undo-bar-close-button-label = Sluiten
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = Met deze modal kan de gebruiker een handtekening maken om aan een PDF-document toe te voegen. De gebruiker kan de naam (die ook als alternatieve tekst dient) bewerken en optioneel de ondertekening opslaan voor herhaald gebruik.
pdfjs-editor-add-signature-dialog-title = Een handtekening toevoegen
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Typen
.title = Typen
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Tekenen
.title = Tekenen
pdfjs-editor-add-signature-image-button = Afbeelding
.title = Afbeelding
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Uw handtekening typen
.placeholder = Uw handtekening typen
pdfjs-editor-add-signature-draw-placeholder = Uw handtekening tekenen
pdfjs-editor-add-signature-draw-thickness-range-label = Dikte
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Tekendikte: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Sleep bestand hierheen om te uploaden
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Of kies afbeeldingsbestanden
*[other] Of kies afbeeldingsbestanden
}
## Controls
pdfjs-editor-add-signature-description-label = Beschrijving (alternatieve tekst)
pdfjs-editor-add-signature-description-input =
.title = Beschrijving (alternatieve tekst)
pdfjs-editor-add-signature-description-default-when-drawing = Handtekening
pdfjs-editor-add-signature-clear-button-label = Handtekening wissen
pdfjs-editor-add-signature-clear-button =
.title = Handtekening wissen
pdfjs-editor-add-signature-save-checkbox = Handtekening opslaan
pdfjs-editor-add-signature-save-warning-message = U hebt de limiet van 5 opgeslagen handtekeningen bereikt. Verwijder er een om een andere op te slaan.
pdfjs-editor-add-signature-image-upload-error-title = Kan afbeelding niet uploaden
pdfjs-editor-add-signature-image-upload-error-description = Controleer uw netwerkverbinding of probeer een andere afbeelding.
pdfjs-editor-add-signature-error-close-button = Sluiten
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Annuleren
pdfjs-editor-add-signature-add-button = Toevoegen
pdfjs-editor-edit-signature-update-button = Bijwerken
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Handtekening verwijderen
pdfjs-editor-delete-signature-button-label = Handtekening verwijderen
pdfjs-editor-delete-signature-button1 =
.title = Opgeslagen ondertekening verwijderen
pdfjs-editor-delete-signature-button-label1 = Opgeslagen ondertekening verwijderen
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Beschrijving bewerken
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Beschrijving bewerken
================================================
FILE: cookbook/static/pdfjs/web/locale/nn-NO/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Føregåande side
pdfjs-previous-button-label = Føregåande
pdfjs-next-button =
.title = Neste side
pdfjs-next-button-label = Neste
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Side
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = av { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } av { $pagesCount })
pdfjs-zoom-out-button =
.title = Zoom ut
pdfjs-zoom-out-button-label = Zoom ut
pdfjs-zoom-in-button =
.title = Zoom inn
pdfjs-zoom-in-button-label = Zoom inn
pdfjs-zoom-select =
.title = Zoom
pdfjs-presentation-mode-button =
.title = Byt til presentasjonsmodus
pdfjs-presentation-mode-button-label = Presentasjonsmodus
pdfjs-open-file-button =
.title = Opne fil
pdfjs-open-file-button-label = Opne
pdfjs-print-button =
.title = Skriv ut
pdfjs-print-button-label = Skriv ut
pdfjs-save-button =
.title = Lagre
pdfjs-save-button-label = Lagre
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Last ned
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Last ned
pdfjs-bookmark-button =
.title = Gjeldande side (sjå URL frå gjeldande side)
pdfjs-bookmark-button-label = Gjeldande side
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Verktøy
pdfjs-tools-button-label = Verktøy
pdfjs-first-page-button =
.title = Gå til første side
pdfjs-first-page-button-label = Gå til første side
pdfjs-last-page-button =
.title = Gå til siste side
pdfjs-last-page-button-label = Gå til siste side
pdfjs-page-rotate-cw-button =
.title = Roter med klokka
pdfjs-page-rotate-cw-button-label = Roter med klokka
pdfjs-page-rotate-ccw-button =
.title = Roter mot klokka
pdfjs-page-rotate-ccw-button-label = Roter mot klokka
pdfjs-cursor-text-select-tool-button =
.title = Aktiver tekstmarkeringsverktøy
pdfjs-cursor-text-select-tool-button-label = Tekstmarkeringsverktøy
pdfjs-cursor-hand-tool-button =
.title = Aktiver handverktøy
pdfjs-cursor-hand-tool-button-label = Handverktøy
pdfjs-scroll-page-button =
.title = Bruk siderulling
pdfjs-scroll-page-button-label = Siderulling
pdfjs-scroll-vertical-button =
.title = Bruk vertikal rulling
pdfjs-scroll-vertical-button-label = Vertikal rulling
pdfjs-scroll-horizontal-button =
.title = Bruk horisontal rulling
pdfjs-scroll-horizontal-button-label = Horisontal rulling
pdfjs-scroll-wrapped-button =
.title = Bruk fleirsiderulling
pdfjs-scroll-wrapped-button-label = Fleirsiderulling
pdfjs-spread-none-button =
.title = Vis enkeltsider
pdfjs-spread-none-button-label = Enkeltside
pdfjs-spread-odd-button =
.title = Vis oppslag med ulike sidenummer til venstre
pdfjs-spread-odd-button-label = Oppslag med framside
pdfjs-spread-even-button =
.title = Vis oppslag med like sidenummmer til venstre
pdfjs-spread-even-button-label = Oppslag utan framside
## Document properties dialog
pdfjs-document-properties-button =
.title = Dokumenteigenskapar…
pdfjs-document-properties-button-label = Dokumenteigenskapar…
pdfjs-document-properties-file-name = Filnamn:
pdfjs-document-properties-file-size = Filstorleik:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } kB ({ $b } byte)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } byte)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = Tittel:
pdfjs-document-properties-author = Forfattar:
pdfjs-document-properties-subject = Emne:
pdfjs-document-properties-keywords = Stikkord:
pdfjs-document-properties-creation-date = Dato oppretta:
pdfjs-document-properties-modification-date = Dato endra:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Oppretta av:
pdfjs-document-properties-producer = PDF-verktøy:
pdfjs-document-properties-version = PDF-versjon:
pdfjs-document-properties-page-count = Sidetal:
pdfjs-document-properties-page-size = Sidestørrelse:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = ståande (portrait)
pdfjs-document-properties-page-size-orientation-landscape = liggande (landscape)
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Brev
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Rask nettvising:
pdfjs-document-properties-linearized-yes = Ja
pdfjs-document-properties-linearized-no = Nei
pdfjs-document-properties-close-button = Lat att
## Print
pdfjs-print-progress-message = Førebur dokumentet for utskrift…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Avbryt
pdfjs-printing-not-supported = Åtvaring: Utskrift er ikkje fullstendig støtta av denne nettlesaren.
pdfjs-printing-not-ready = Åtvaring: PDF ikkje fullstendig innlasta for utskrift.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Slå av/på sidestolpe
pdfjs-toggle-sidebar-notification-button =
.title = Vis/gøym sidestolpe (dokumentet inneheld oversikt/vedlegg/lag)
pdfjs-toggle-sidebar-button-label = Slå av/på sidestolpe
pdfjs-document-outline-button =
.title = Vis dokumentdisposisjonen (dobbelklikk for å utvide/gøyme alle elementa)
pdfjs-document-outline-button-label = Dokumentdisposisjon
pdfjs-attachments-button =
.title = Vis vedlegg
pdfjs-attachments-button-label = Vedlegg
pdfjs-layers-button =
.title = Vis lag (dobbeltklikk for å tilbakestille alle lag til standardtilstand)
pdfjs-layers-button-label = Lag
pdfjs-thumbs-button =
.title = Vis miniatyrbilde
pdfjs-thumbs-button-label = Miniatyrbilde
pdfjs-current-outline-item-button =
.title = Finn gjeldande disposisjonselement
pdfjs-current-outline-item-button-label = Gjeldande disposisjonselement
pdfjs-findbar-button =
.title = Finn i dokumentet
pdfjs-findbar-button-label = Finn
pdfjs-additional-layers = Ytterlegare lag
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Side { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Miniatyrbilde av side { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Søk
.placeholder = Søk i dokument…
pdfjs-find-previous-button =
.title = Finn førre førekomst av frasen
pdfjs-find-previous-button-label = Førre
pdfjs-find-next-button =
.title = Finn neste førekomst av frasen
pdfjs-find-next-button-label = Neste
pdfjs-find-highlight-checkbox = Uthev alle
pdfjs-find-match-case-checkbox-label = Skil store/små bokstavar
pdfjs-find-match-diacritics-checkbox-label = Samsvar diakritiske teikn
pdfjs-find-entire-word-checkbox-label = Heile ord
pdfjs-find-reached-top = Nådde toppen av dokumentet, fortset frå botnen
pdfjs-find-reached-bottom = Nådde botnen av dokumentet, fortset frå toppen
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } av { $total } treff
*[other] { $current } av { $total } treff
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Meir enn { $limit } treff
*[other] Meir enn { $limit } treff
}
pdfjs-find-not-found = Fann ikkje teksten
## Predefined zoom values
pdfjs-page-scale-width = Sidebreidde
pdfjs-page-scale-fit = Tilpass til sida
pdfjs-page-scale-auto = Automatisk skalering
pdfjs-page-scale-actual = Verkeleg storleik
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Side { $page }
## Loading indicator messages
pdfjs-loading-error = Ein feil oppstod ved lasting av PDF.
pdfjs-invalid-file-error = Ugyldig eller korrupt PDF-fil.
pdfjs-missing-file-error = Manglande PDF-fil.
pdfjs-unexpected-response-error = Uventa tenarrespons.
pdfjs-rendering-error = Ein feil oppstod under vising av sida.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date } { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } annotasjon]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Skriv inn passordet for å opne denne PDF-fila.
pdfjs-password-invalid = Ugyldig passord. Prøv på nytt.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Avbryt
pdfjs-web-fonts-disabled = Web-skrifter er slått av: Kan ikkje bruke innbundne PDF-skrifter.
## Editing
pdfjs-editor-free-text-button =
.title = Tekst
pdfjs-editor-free-text-button-label = Tekst
pdfjs-editor-ink-button =
.title = Teikne
pdfjs-editor-ink-button-label = Teikne
pdfjs-editor-stamp-button =
.title = Legg til eller rediger bilde
pdfjs-editor-stamp-button-label = Legg til eller rediger bilde
pdfjs-editor-highlight-button =
.title = Markere
pdfjs-editor-highlight-button-label = Markere
pdfjs-highlight-floating-button1 =
.title = Markere
.aria-label = Markere
pdfjs-highlight-floating-button-label = Markere
pdfjs-editor-signature-button =
.title = Legg til signatur
pdfjs-editor-signature-button-label = Legg til signatur
## Default editor aria labels
pdfjs-editor-signature-editor =
.aria-label = Signatur-redigerar
pdfjs-editor-stamp-editor =
.aria-label = Bildredigerar
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Fjern teikninga
pdfjs-editor-remove-freetext-button =
.title = Fjern tekst
pdfjs-editor-remove-stamp-button =
.title = Fjern bildet
pdfjs-editor-remove-highlight-button =
.title = Fjern utheving
pdfjs-editor-remove-signature-button =
.title = Fjern signatur
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Farge
pdfjs-editor-free-text-size-input = Storleik
pdfjs-editor-ink-color-input = Farge
pdfjs-editor-ink-thickness-input = Tjukn
pdfjs-editor-ink-opacity-input = Ugjennomskinleg
pdfjs-editor-stamp-add-image-button =
.title = Legg til bilde
pdfjs-editor-stamp-add-image-button-label = Legg til bilde
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Tjukn
pdfjs-editor-free-highlight-thickness-title =
.title = Endre tjukn når du markerer andre element enn tekst
pdfjs-editor-signature-add-signature-button =
.title = Legg til ny signatur
pdfjs-editor-signature-add-signature-button-label = Legg til ny signatur
# Used on the button to use an already saved signature.
# Variables:
# $description (String) - a string describing/labeling the signature.
pdfjs-editor-add-saved-signature-button =
.title = Lagra signatur: { $description }
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Tekstredigering
.default-content = Begynn å skrive…
pdfjs-free-text =
.aria-label = Tekstredigering
pdfjs-free-text-default-content = Byrje å skrive…
pdfjs-ink =
.aria-label = Teikneredigering
pdfjs-ink-canvas =
.aria-label = Brukarskapt bilde
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Alt-tekst
pdfjs-editor-alt-text-edit-button =
.aria-label = Rediger alt-tekst tekst
pdfjs-editor-alt-text-edit-button-label = Rediger alt-tekst tekst
pdfjs-editor-alt-text-dialog-label = Vel eit alternativ
pdfjs-editor-alt-text-dialog-description = Alt-tekst (alternativ tekst) hjelper når folk ikkje kan sjå bildet eller når det ikkje vert lasta inn.
pdfjs-editor-alt-text-add-description-label = Legg til ei skildring
pdfjs-editor-alt-text-add-description-description = Gå etter 1-2 setninger som skildrar emnet, settinga eller handlingane.
pdfjs-editor-alt-text-mark-decorative-label = Merk som dekorativt
pdfjs-editor-alt-text-mark-decorative-description = Dette vert brukt til dekorative bilde, som kantlinjer eller vassmerke.
pdfjs-editor-alt-text-cancel-button = Avbryt
pdfjs-editor-alt-text-save-button = Lagre
pdfjs-editor-alt-text-decorative-tooltip = Merkt som dekorativ
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Til dømes, «Ein ung mann set seg ved eit bord for å ete eit måltid»
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Alt-tekst
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Øvste venstre hjørne – endre størrelse
pdfjs-editor-resizer-label-top-middle = Øvst i midten — endre størrelse
pdfjs-editor-resizer-label-top-right = Øvste høgre hjørne – endre størrelse
pdfjs-editor-resizer-label-middle-right = Midt til høgre – endre størrelse
pdfjs-editor-resizer-label-bottom-right = Nedste høgre hjørne – endre størrelse
pdfjs-editor-resizer-label-bottom-middle = Nedst i midten — endre størrelse
pdfjs-editor-resizer-label-bottom-left = Nedste venstre hjørne – endre størrelse
pdfjs-editor-resizer-label-middle-left = Midt til venstre — endre størrelse
pdfjs-editor-resizer-top-left =
.aria-label = Øvste venstre hjørne – endre størrelse
pdfjs-editor-resizer-top-middle =
.aria-label = Øvst i midten — endre størrelse
pdfjs-editor-resizer-top-right =
.aria-label = Øvste høgre hjørne – endre størrelse
pdfjs-editor-resizer-middle-right =
.aria-label = Midt til høgre – endre størrelse
pdfjs-editor-resizer-bottom-right =
.aria-label = Nedste høgre hjørne – endre størrelse
pdfjs-editor-resizer-bottom-middle =
.aria-label = Nedst i midten — endre størrelse
pdfjs-editor-resizer-bottom-left =
.aria-label = Nedste venstre hjørne – endre størrelse
pdfjs-editor-resizer-middle-left =
.aria-label = Midt til venstre — endre størrelse
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Uthevingsfarge
pdfjs-editor-colorpicker-button =
.title = Endre farge
pdfjs-editor-colorpicker-dropdown =
.aria-label = Fargeval
pdfjs-editor-colorpicker-yellow =
.title = Gul
pdfjs-editor-colorpicker-green =
.title = Grøn
pdfjs-editor-colorpicker-blue =
.title = Blå
pdfjs-editor-colorpicker-pink =
.title = Rosa
pdfjs-editor-colorpicker-red =
.title = Raud
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Vis alle
pdfjs-editor-highlight-show-all-button =
.title = Vis alle
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Rediger alternativ tekst (bildeskildring)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Legg til alternativ tekst (bildeskildring)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Skriv skildringa di her…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Kort skildring for personar som ikkje kan sjå bildet, eller når bildet ikkje lastar inn.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Denne alternative teksten vart oppretta automatisk, og kan vere unøyaktig.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Les meir
pdfjs-editor-new-alt-text-create-automatically-button-label = Opprett alternativ tekt automatisk
pdfjs-editor-new-alt-text-not-now-button = Ikkje no
pdfjs-editor-new-alt-text-error-title = Klarte ikkje å opprette alternativ tekst automatisk
pdfjs-editor-new-alt-text-error-description = Skriv din eigen alternative tekst eller prøv igjen seinare.
pdfjs-editor-new-alt-text-error-close-button = Lat att
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Lastar ned AI-modell med alternativ tekst ({ $downloadedSize } av { $totalSize } MB)
.aria-valuetext = Lastar ned AI-modell med alternativ tekst ({ $downloadedSize } av { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Alternativ tekst lagt til
pdfjs-editor-new-alt-text-added-button-label = Alternativ tekst lagt til
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Manglande alternativ tekst
pdfjs-editor-new-alt-text-missing-button-label = Manglande alternativ tekst
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Vurder alternativ tekst
pdfjs-editor-new-alt-text-to-review-button-label = Vurder alternativ tekst
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Oppretta automatisk: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Alternative tekst-innstillingar for bilde
pdfjs-image-alt-text-settings-button-label = Alternative tekst-innstillingar for bilde
pdfjs-editor-alt-text-settings-dialog-label = Alternative tekst-innstillingar for bilde
pdfjs-editor-alt-text-settings-automatic-title = Automatisk alternativ tekst
pdfjs-editor-alt-text-settings-create-model-button-label = Opprett alternativ tekt automatisk
pdfjs-editor-alt-text-settings-create-model-description = Foreslår skildringar for å hjelpe folk som ikkje kan sjå bildet eller når bildet ikkje blir lasta inn.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = AI-modell for alternativ tekst ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Køyrer lokalt på eininga di slik at dataa dine blir verande private. Påkravd for automatisk alternativ tekst.
pdfjs-editor-alt-text-settings-delete-model-button = Slett
pdfjs-editor-alt-text-settings-download-model-button = Last ned
pdfjs-editor-alt-text-settings-downloading-model-button = Lastar ned…
pdfjs-editor-alt-text-settings-editor-title = Alternativ tekst-redigerar
pdfjs-editor-alt-text-settings-show-dialog-button-label = Vis alternativ tekst-redigerar direkte når du legg til eit bilde
pdfjs-editor-alt-text-settings-show-dialog-description = Hjelper deg med å sørgje for at alle bilda dine har alternativ tekst.
pdfjs-editor-alt-text-settings-close-button = Lat att
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Markering fjerna
pdfjs-editor-undo-bar-message-freetext = Tekst fjerna
pdfjs-editor-undo-bar-message-ink = Teikning fjerna
pdfjs-editor-undo-bar-message-stamp = Bilde fjerna
pdfjs-editor-undo-bar-message-signature = Signatur fjerna
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } kommentar fjerna
*[other] { $count } kommentarar fjerna
}
pdfjs-editor-undo-bar-undo-button =
.title = Angre
pdfjs-editor-undo-bar-undo-button-label = Angre
pdfjs-editor-undo-bar-close-button =
.title = Lat att
pdfjs-editor-undo-bar-close-button-label = Lat att
## Add a signature dialog
pdfjs-editor-add-signature-dialog-title = Legg til ein signatur
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Type
.title = Type
pdfjs-editor-add-signature-image-button = Bilde
.title = Bilde
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Skriv inn signaturen din
.placeholder = Skriv inn signaturen din
pdfjs-editor-add-signature-draw-placeholder = Teikn signaturen din
pdfjs-editor-add-signature-draw-thickness-range-label = Tjukn
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Linjetjukn: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Drag ei fil hit for å laste opp
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Eller vel bildefiler
*[other] Eller vel bildefiler
}
## Controls
pdfjs-editor-add-signature-description-label = Skildring (alternativ tekst)
pdfjs-editor-add-signature-description-input =
.title = Skildring (alternativ tekst)
pdfjs-editor-add-signature-description-default-when-drawing = Signatur
pdfjs-editor-add-signature-clear-button-label = Fjern signatur
pdfjs-editor-add-signature-clear-button =
.title = Fjern signatur
pdfjs-editor-add-signature-save-checkbox = Lagre signatur
pdfjs-editor-add-signature-save-warning-message = Du har nådd grensa på 5 lagra signaturar. Fjern ein for å lagre ein ny.
pdfjs-editor-add-signature-image-upload-error-title = Klarte ikkje å oppdatere bilde
pdfjs-editor-add-signature-image-upload-error-description = Sjekk nettverkstilkoplinga eller prøv eit annet bilde.
pdfjs-editor-add-signature-error-close-button = Lat att
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Avbryt
pdfjs-editor-add-signature-add-button = Legg til
pdfjs-editor-edit-signature-update-button = Oppdater
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Fjern signatur
pdfjs-editor-delete-signature-button-label = Fjern signatur
pdfjs-editor-delete-signature-button1 =
.title = Fjern lagra signatur
pdfjs-editor-delete-signature-button-label1 = Fjern lagra signatur
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Rediger skildring
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Rediger skildring
================================================
FILE: cookbook/static/pdfjs/web/locale/oc/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Pagina precedenta
pdfjs-previous-button-label = Precedent
pdfjs-next-button =
.title = Pagina seguenta
pdfjs-next-button-label = Seguent
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Pagina
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = sus { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } de { $pagesCount })
pdfjs-zoom-out-button =
.title = Zoom arrièr
pdfjs-zoom-out-button-label = Zoom arrièr
pdfjs-zoom-in-button =
.title = Zoom avant
pdfjs-zoom-in-button-label = Zoom avant
pdfjs-zoom-select =
.title = Zoom
pdfjs-presentation-mode-button =
.title = Bascular en mòde presentacion
pdfjs-presentation-mode-button-label = Mòde Presentacion
pdfjs-open-file-button =
.title = Dobrir lo fichièr
pdfjs-open-file-button-label = Dobrir
pdfjs-print-button =
.title = Imprimir
pdfjs-print-button-label = Imprimir
pdfjs-save-button =
.title = Enregistrar
pdfjs-save-button-label = Enregistrar
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Telecargar
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Telecargar
pdfjs-bookmark-button =
.title = Pagina actuala (mostrar l’adreça de la pagina actuala)
pdfjs-bookmark-button-label = Pagina actuala
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Aisinas
pdfjs-tools-button-label = Aisinas
pdfjs-first-page-button =
.title = Anar a la primièra pagina
pdfjs-first-page-button-label = Anar a la primièra pagina
pdfjs-last-page-button =
.title = Anar a la darrièra pagina
pdfjs-last-page-button-label = Anar a la darrièra pagina
pdfjs-page-rotate-cw-button =
.title = Rotacion orària
pdfjs-page-rotate-cw-button-label = Rotacion orària
pdfjs-page-rotate-ccw-button =
.title = Rotacion antiorària
pdfjs-page-rotate-ccw-button-label = Rotacion antiorària
pdfjs-cursor-text-select-tool-button =
.title = Activar l'aisina de seleccion de tèxte
pdfjs-cursor-text-select-tool-button-label = Aisina de seleccion de tèxte
pdfjs-cursor-hand-tool-button =
.title = Activar l’aisina man
pdfjs-cursor-hand-tool-button-label = Aisina man
pdfjs-scroll-page-button =
.title = Activar lo defilament per pagina
pdfjs-scroll-page-button-label = Defilament per pagina
pdfjs-scroll-vertical-button =
.title = Utilizar lo defilament vertical
pdfjs-scroll-vertical-button-label = Defilament vertical
pdfjs-scroll-horizontal-button =
.title = Utilizar lo defilament orizontal
pdfjs-scroll-horizontal-button-label = Defilament orizontal
pdfjs-scroll-wrapped-button =
.title = Activar lo defilament continú
pdfjs-scroll-wrapped-button-label = Defilament continú
pdfjs-spread-none-button =
.title = Agropar pas las paginas doas a doas
pdfjs-spread-none-button-label = Una sola pagina
pdfjs-spread-odd-button =
.title = Mostrar doas paginas en començant per las paginas imparas a esquèrra
pdfjs-spread-odd-button-label = Dobla pagina, impara a drecha
pdfjs-spread-even-button =
.title = Mostrar doas paginas en començant per las paginas paras a esquèrra
pdfjs-spread-even-button-label = Dobla pagina, para a drecha
## Document properties dialog
pdfjs-document-properties-button =
.title = Proprietats del document…
pdfjs-document-properties-button-label = Proprietats del document…
pdfjs-document-properties-file-name = Nom del fichièr :
pdfjs-document-properties-file-size = Talha del fichièr :
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } Ko ({ $size_b } octets)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } Mo ({ $size_b } octets)
pdfjs-document-properties-title = Títol :
pdfjs-document-properties-author = Autor :
pdfjs-document-properties-subject = Subjècte :
pdfjs-document-properties-keywords = Mots claus :
pdfjs-document-properties-creation-date = Data de creacion :
pdfjs-document-properties-modification-date = Data de modificacion :
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, a { $time }
pdfjs-document-properties-creator = Creator :
pdfjs-document-properties-producer = Aisina de conversion PDF :
pdfjs-document-properties-version = Version PDF :
pdfjs-document-properties-page-count = Nombre de paginas :
pdfjs-document-properties-page-size = Talha de la pagina :
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = retrach
pdfjs-document-properties-page-size-orientation-landscape = païsatge
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letra
pdfjs-document-properties-page-size-name-legal = Document juridic
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Vista web rapida :
pdfjs-document-properties-linearized-yes = Òc
pdfjs-document-properties-linearized-no = Non
pdfjs-document-properties-close-button = Tampar
## Print
pdfjs-print-progress-message = Preparacion del document per l’impression…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Anullar
pdfjs-printing-not-supported = Atencion : l'impression es pas complètament gerida per aqueste navegador.
pdfjs-printing-not-ready = Atencion : lo PDF es pas entièrament cargat per lo poder imprimir.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Afichar/amagar lo panèl lateral
pdfjs-toggle-sidebar-notification-button =
.title = Afichar/amagar lo panèl lateral (lo document conten esquèmas/pèças juntas/calques)
pdfjs-toggle-sidebar-button-label = Afichar/amagar lo panèl lateral
pdfjs-document-outline-button =
.title = Mostrar los esquèmas del document (dobleclicar per espandre/reduire totes los elements)
pdfjs-document-outline-button-label = Marcapaginas del document
pdfjs-attachments-button =
.title = Visualizar las pèças juntas
pdfjs-attachments-button-label = Pèças juntas
pdfjs-layers-button =
.title = Afichar los calques (doble-clicar per reïnicializar totes los calques a l’estat per defaut)
pdfjs-layers-button-label = Calques
pdfjs-thumbs-button =
.title = Afichar las vinhetas
pdfjs-thumbs-button-label = Vinhetas
pdfjs-current-outline-item-button =
.title = Trobar l’element de plan actual
pdfjs-current-outline-item-button-label = Element de plan actual
pdfjs-findbar-button =
.title = Cercar dins lo document
pdfjs-findbar-button-label = Recercar
pdfjs-additional-layers = Calques suplementaris
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Pagina { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Vinheta de la pagina { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Recercar
.placeholder = Cercar dins lo document…
pdfjs-find-previous-button =
.title = Tròba l'ocurréncia precedenta de la frasa
pdfjs-find-previous-button-label = Precedent
pdfjs-find-next-button =
.title = Tròba l'ocurréncia venenta de la frasa
pdfjs-find-next-button-label = Seguent
pdfjs-find-highlight-checkbox = Suslinhar tot
pdfjs-find-match-case-checkbox-label = Respectar la cassa
pdfjs-find-match-diacritics-checkbox-label = Respectar los diacritics
pdfjs-find-entire-word-checkbox-label = Mots entièrs
pdfjs-find-reached-top = Naut de la pagina atenh, perseguida del bas
pdfjs-find-reached-bottom = Bas de la pagina atench, perseguida al començament
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] Ocurréncia { $current } de { $total }
*[other] Ocurréncia { $current } de { $total }
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Mai de { $limit } ocurréncia
*[other] Mai de { $limit } ocurréncias
}
pdfjs-find-not-found = Frasa pas trobada
## Predefined zoom values
pdfjs-page-scale-width = Largor plena
pdfjs-page-scale-fit = Pagina entièra
pdfjs-page-scale-auto = Zoom automatic
pdfjs-page-scale-actual = Talha vertadièra
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Pagina { $page }
## Loading indicator messages
pdfjs-loading-error = Una error s'es producha pendent lo cargament del fichièr PDF.
pdfjs-invalid-file-error = Fichièr PDF invalid o corromput.
pdfjs-missing-file-error = Fichièr PDF mancant.
pdfjs-unexpected-response-error = Responsa de servidor imprevista.
pdfjs-rendering-error = Una error s'es producha pendent l'afichatge de la pagina.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date } a { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [Anotacion { $type }]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Picatz lo senhal per dobrir aqueste fichièr PDF.
pdfjs-password-invalid = Senhal incorrècte. Tornatz ensajar.
pdfjs-password-ok-button = D'acòrdi
pdfjs-password-cancel-button = Anullar
pdfjs-web-fonts-disabled = Las polissas web son desactivadas : impossible d'utilizar las polissas integradas al PDF.
## Editing
pdfjs-editor-free-text-button =
.title = Tèxte
pdfjs-editor-free-text-button-label = Tèxte
pdfjs-editor-ink-button =
.title = Dessenhar
pdfjs-editor-ink-button-label = Dessenhar
pdfjs-editor-stamp-button =
.title = Apondre o modificar d’imatges
pdfjs-editor-stamp-button-label = Apondre o modificar d’imatges
pdfjs-editor-highlight-button =
.title = Subrelinhar
pdfjs-editor-highlight-button-label = Subrelinhar
pdfjs-highlight-floating-button1 =
.title = Subrelinhar
.aria-label = Subrelinhar
pdfjs-highlight-floating-button-label = Subrelinhar
## Default editor aria labels
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Levar lo dessenh
pdfjs-editor-remove-freetext-button =
.title = Suprimir lo tèxte
pdfjs-editor-remove-stamp-button =
.title = Suprimir l’imatge
pdfjs-editor-remove-highlight-button =
.title = Levar lo suslinhatge
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Color
pdfjs-editor-free-text-size-input = Talha
pdfjs-editor-ink-color-input = Color
pdfjs-editor-ink-thickness-input = Espessor
pdfjs-editor-ink-opacity-input = Opacitat
pdfjs-editor-stamp-add-image-button =
.title = Apondre imatge
pdfjs-editor-stamp-add-image-button-label = Apondre imatge
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Espessor
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Editor de tèxte
.default-content = Començatz de picar…
pdfjs-free-text =
.aria-label = Editor de tèxte
pdfjs-free-text-default-content = Començatz d’escriure…
pdfjs-ink =
.aria-label = Editor de dessenh
pdfjs-ink-canvas =
.aria-label = Imatge creat per l’utilizaire
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Tèxt alternatiu
pdfjs-editor-alt-text-edit-button-label = Modificar lo tèxt alternatiu
pdfjs-editor-alt-text-dialog-label = Causir una opcion
pdfjs-editor-alt-text-add-description-label = Apondre una descripcion
pdfjs-editor-alt-text-cancel-button = Anullar
pdfjs-editor-alt-text-save-button = Enregistrar
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Color de suslinhatge
pdfjs-editor-colorpicker-button =
.title = Cambiar de color
pdfjs-editor-colorpicker-dropdown =
.aria-label = Causida de colors
pdfjs-editor-colorpicker-yellow =
.title = Jaune
pdfjs-editor-colorpicker-green =
.title = Verd
pdfjs-editor-colorpicker-blue =
.title = Blau
pdfjs-editor-colorpicker-pink =
.title = Ròse
pdfjs-editor-colorpicker-red =
.title = Roge
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = O afichar tot
pdfjs-editor-highlight-show-all-button =
.title = O afichar tot
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
pdfjs-editor-new-alt-text-error-close-button = Tampar
## Image alt-text settings
pdfjs-editor-alt-text-settings-automatic-title = Tèxte alternatiu automatic
pdfjs-editor-alt-text-settings-create-model-button-label = Crear un tèxte alternatiu automaticament
pdfjs-editor-alt-text-settings-delete-model-button = Suprimir
pdfjs-editor-alt-text-settings-download-model-button = Telecargar
pdfjs-editor-alt-text-settings-downloading-model-button = Telecargament…
pdfjs-editor-alt-text-settings-editor-title = Editor de tèxte alternatiu
pdfjs-editor-alt-text-settings-close-button = Tampar
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-freetext = Tèxte suprimit
pdfjs-editor-undo-bar-message-ink = Dessenh suprimit
pdfjs-editor-undo-bar-message-stamp = Imatge suprimit
pdfjs-editor-undo-bar-undo-button =
.title = Anullar
pdfjs-editor-undo-bar-undo-button-label = Anullar
pdfjs-editor-undo-bar-close-button =
.title = Tampar
pdfjs-editor-undo-bar-close-button-label = Tampar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/pa-IN/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = ਪਿਛਲਾ ਸਫ਼ਾ
pdfjs-previous-button-label = ਪਿੱਛੇ
pdfjs-next-button =
.title = ਅਗਲਾ ਸਫ਼ਾ
pdfjs-next-button-label = ਅੱਗੇ
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = ਸਫ਼ਾ
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = { $pagesCount } ਵਿੱਚੋਂ
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = { $pagesCount }) ਵਿੱਚੋਂ ({ $pageNumber }
pdfjs-zoom-out-button =
.title = ਜ਼ੂਮ ਆਉਟ
pdfjs-zoom-out-button-label = ਜ਼ੂਮ ਆਉਟ
pdfjs-zoom-in-button =
.title = ਜ਼ੂਮ ਇਨ
pdfjs-zoom-in-button-label = ਜ਼ੂਮ ਇਨ
pdfjs-zoom-select =
.title = ਜ਼ੂਨ
pdfjs-presentation-mode-button =
.title = ਪਰਿਜੈਂਟੇਸ਼ਨ ਮੋਡ ਵਿੱਚ ਜਾਓ
pdfjs-presentation-mode-button-label = ਪਰਿਜੈਂਟੇਸ਼ਨ ਮੋਡ
pdfjs-open-file-button =
.title = ਫਾਈਲ ਨੂੰ ਖੋਲ੍ਹੋ
pdfjs-open-file-button-label = ਖੋਲ੍ਹੋ
pdfjs-print-button =
.title = ਪਰਿੰਟ
pdfjs-print-button-label = ਪਰਿੰਟ
pdfjs-save-button =
.title = ਸੰਭਾਲੋ
pdfjs-save-button-label = ਸੰਭਾਲੋ
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = ਡਾਊਨਲੋਡ
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = ਡਾਊਨਲੋਡ
pdfjs-bookmark-button =
.title = ਮੌਜੂਦਾ ਸਫ਼਼ਾ (ਮੌਜੂਦਾ ਸਫ਼ੇ ਤੋਂ URL ਵੇਖੋ)
pdfjs-bookmark-button-label = ਮੌਜੂਦਾ ਸਫ਼਼ਾ
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = ਟੂਲ
pdfjs-tools-button-label = ਟੂਲ
pdfjs-first-page-button =
.title = ਪਹਿਲੇ ਸਫ਼ੇ ਉੱਤੇ ਜਾਓ
pdfjs-first-page-button-label = ਪਹਿਲੇ ਸਫ਼ੇ ਉੱਤੇ ਜਾਓ
pdfjs-last-page-button =
.title = ਆਖਰੀ ਸਫ਼ੇ ਉੱਤੇ ਜਾਓ
pdfjs-last-page-button-label = ਆਖਰੀ ਸਫ਼ੇ ਉੱਤੇ ਜਾਓ
pdfjs-page-rotate-cw-button =
.title = ਸੱਜੇ ਦਾਅ ਘੁੰਮਾਓ
pdfjs-page-rotate-cw-button-label = ਸੱਜੇ ਦਾਅ ਘੁੰਮਾਓ
pdfjs-page-rotate-ccw-button =
.title = ਖੱਬੇ ਦਾਅ ਘੁੰਮਾਓ
pdfjs-page-rotate-ccw-button-label = ਖੱਬੇ ਦਾਅ ਘੁੰਮਾਓ
pdfjs-cursor-text-select-tool-button =
.title = ਲਿਖਤ ਚੋਣ ਟੂਲ ਸਮਰੱਥ ਕਰੋ
pdfjs-cursor-text-select-tool-button-label = ਲਿਖਤ ਚੋਣ ਟੂਲ
pdfjs-cursor-hand-tool-button =
.title = ਹੱਥ ਟੂਲ ਸਮਰੱਥ ਕਰੋ
pdfjs-cursor-hand-tool-button-label = ਹੱਥ ਟੂਲ
pdfjs-scroll-page-button =
.title = ਸਫ਼ਾ ਖਿਸਕਾਉਣ ਨੂੰ ਵਰਤੋਂ
pdfjs-scroll-page-button-label = ਸਫ਼ਾ ਖਿਸਕਾਉਣਾ
pdfjs-scroll-vertical-button =
.title = ਖੜ੍ਹਵੇਂ ਸਕਰਾਉਣ ਨੂੰ ਵਰਤੋਂ
pdfjs-scroll-vertical-button-label = ਖੜ੍ਹਵਾਂ ਸਰਕਾਉਣਾ
pdfjs-scroll-horizontal-button =
.title = ਲੇਟਵੇਂ ਸਰਕਾਉਣ ਨੂੰ ਵਰਤੋਂ
pdfjs-scroll-horizontal-button-label = ਲੇਟਵਾਂ ਸਰਕਾਉਣਾ
pdfjs-scroll-wrapped-button =
.title = ਸਮੇਟੇ ਸਰਕਾਉਣ ਨੂੰ ਵਰਤੋਂ
pdfjs-scroll-wrapped-button-label = ਸਮੇਟਿਆ ਸਰਕਾਉਣਾ
pdfjs-spread-none-button =
.title = ਸਫ਼ਾ ਫੈਲਾਅ ਵਿੱਚ ਸ਼ਾਮਲ ਨਾ ਹੋਵੋ
pdfjs-spread-none-button-label = ਕੋਈ ਫੈਲਾਅ ਨਹੀਂ
pdfjs-spread-odd-button =
.title = ਟਾਂਕ ਅੰਕ ਵਾਲੇ ਸਫ਼ਿਆਂ ਨਾਲ ਸ਼ੁਰੂ ਹੋਣ ਵਾਲੇ ਸਫਿਆਂ ਵਿੱਚ ਸ਼ਾਮਲ ਹੋਵੋ
pdfjs-spread-odd-button-label = ਟਾਂਕ ਫੈਲਾਅ
pdfjs-spread-even-button =
.title = ਜਿਸਤ ਅੰਕ ਵਾਲੇ ਸਫ਼ਿਆਂ ਨਾਲ ਸ਼ੁਰੂ ਹੋਣ ਵਾਲੇ ਸਫਿਆਂ ਵਿੱਚ ਸ਼ਾਮਲ ਹੋਵੋ
pdfjs-spread-even-button-label = ਜਿਸਤ ਫੈਲਾਅ
## Document properties dialog
pdfjs-document-properties-button =
.title = …ਦਸਤਾਵੇਜ਼ ਦੀ ਵਿਸ਼ੇਸ਼ਤਾ
pdfjs-document-properties-button-label = …ਦਸਤਾਵੇਜ਼ ਦੀ ਵਿਸ਼ੇਸ਼ਤਾ
pdfjs-document-properties-file-name = ਫਾਈਲ ਦਾ ਨਾਂ:
pdfjs-document-properties-file-size = ਫਾਈਲ ਦਾ ਆਕਾਰ:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } ਬਾਈਟ)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } ਬਾਈਟ)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } ਬਾਈਟ)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } ਬਾਈਟ)
pdfjs-document-properties-title = ਟਾਈਟਲ:
pdfjs-document-properties-author = ਲੇਖਕ:
pdfjs-document-properties-subject = ਵਿਸ਼ਾ:
pdfjs-document-properties-keywords = ਸ਼ਬਦ:
pdfjs-document-properties-creation-date = ਬਣਾਉਣ ਦੀ ਮਿਤੀ:
pdfjs-document-properties-modification-date = ਸੋਧ ਦੀ ਮਿਤੀ:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = ਨਿਰਮਾਤਾ:
pdfjs-document-properties-producer = PDF ਪ੍ਰੋਡਿਊਸਰ:
pdfjs-document-properties-version = PDF ਵਰਜਨ:
pdfjs-document-properties-page-count = ਸਫ਼ੇ ਦੀ ਗਿਣਤੀ:
pdfjs-document-properties-page-size = ਸਫ਼ਾ ਆਕਾਰ:
pdfjs-document-properties-page-size-unit-inches = ਇੰਚ
pdfjs-document-properties-page-size-unit-millimeters = ਮਿਮੀ
pdfjs-document-properties-page-size-orientation-portrait = ਪੋਰਟਰੇਟ
pdfjs-document-properties-page-size-orientation-landscape = ਲੈਂਡਸਕੇਪ
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = ਲੈਟਰ
pdfjs-document-properties-page-size-name-legal = ਕਨੂੰਨੀ
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = ਤੇਜ਼ ਵੈੱਬ ਝਲਕ:
pdfjs-document-properties-linearized-yes = ਹਾਂ
pdfjs-document-properties-linearized-no = ਨਹੀਂ
pdfjs-document-properties-close-button = ਬੰਦ ਕਰੋ
## Print
pdfjs-print-progress-message = …ਪਰਿੰਟ ਕਰਨ ਲਈ ਦਸਤਾਵੇਜ਼ ਨੂੰ ਤਿਆਰ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = ਰੱਦ ਕਰੋ
pdfjs-printing-not-supported = ਸਾਵਧਾਨ: ਇਹ ਬਰਾਊਜ਼ਰ ਪਰਿੰਟ ਕਰਨ ਲਈ ਪੂਰੀ ਤਰ੍ਹਾਂ ਸਹਾਇਕ ਨਹੀਂ ਹੈ।
pdfjs-printing-not-ready = ਸਾਵਧਾਨ: PDF ਨੂੰ ਪਰਿੰਟ ਕਰਨ ਲਈ ਪੂਰੀ ਤਰ੍ਹਾਂ ਲੋਡ ਨਹੀਂ ਹੈ।
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = ਬਾਹੀ ਬਦਲੋ
pdfjs-toggle-sidebar-notification-button =
.title = ਬਾਹੀ ਨੂੰ ਬਦਲੋ (ਦਸਤਾਵੇਜ਼ ਖਾਕਾ/ਅਟੈਚਮੈਂਟ/ਪਰਤਾਂ ਰੱਖਦਾ ਹੈ)
pdfjs-toggle-sidebar-button-label = ਬਾਹੀ ਬਦਲੋ
pdfjs-document-outline-button =
.title = ਦਸਤਾਵੇਜ਼ ਖਾਕਾ ਦਿਖਾਓ (ਸਾਰੀਆਂ ਆਈਟਮਾਂ ਨੂੰ ਫੈਲਾਉਣ/ਸਮੇਟਣ ਲਈ ਦੋ ਵਾਰ ਕਲਿੱਕ ਕਰੋ)
pdfjs-document-outline-button-label = ਦਸਤਾਵੇਜ਼ ਖਾਕਾ
pdfjs-attachments-button =
.title = ਅਟੈਚਮੈਂਟ ਵੇਖਾਓ
pdfjs-attachments-button-label = ਅਟੈਚਮੈਂਟਾਂ
pdfjs-layers-button =
.title = ਪਰਤਾਂ ਵੇਖਾਓ (ਸਾਰੀਆਂ ਪਰਤਾਂ ਨੂੰ ਮੂਲ ਹਾਲਤ ਉੱਤੇ ਮੁੜ-ਸੈੱਟ ਕਰਨ ਲਈ ਦੋ ਵਾਰ ਕਲਿੱਕ ਕਰੋ)
pdfjs-layers-button-label = ਪਰਤਾਂ
pdfjs-thumbs-button =
.title = ਥੰਮਨੇਲ ਨੂੰ ਵੇਖਾਓ
pdfjs-thumbs-button-label = ਥੰਮਨੇਲ
pdfjs-current-outline-item-button =
.title = ਮੌੌਜੂਦਾ ਖਾਕਾ ਚੀਜ਼ ਲੱਭੋ
pdfjs-current-outline-item-button-label = ਮੌਜੂਦਾ ਖਾਕਾ ਚੀਜ਼
pdfjs-findbar-button =
.title = ਦਸਤਾਵੇਜ਼ ਵਿੱਚ ਲੱਭੋ
pdfjs-findbar-button-label = ਲੱਭੋ
pdfjs-additional-layers = ਵਾਧੂ ਪਰਤਾਂ
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = ਸਫ਼ਾ { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = { $page } ਸਫ਼ੇ ਦਾ ਥੰਮਨੇਲ
## Find panel button title and messages
pdfjs-find-input =
.title = ਲੱਭੋ
.placeholder = …ਦਸਤਾਵੇਜ਼ 'ਚ ਲੱਭੋ
pdfjs-find-previous-button =
.title = ਵਾਕ ਦੀ ਪਿਛਲੀ ਮੌਜੂਦਗੀ ਲੱਭੋ
pdfjs-find-previous-button-label = ਪਿੱਛੇ
pdfjs-find-next-button =
.title = ਵਾਕ ਦੀ ਅਗਲੀ ਮੌਜੂਦਗੀ ਲੱਭੋ
pdfjs-find-next-button-label = ਅੱਗੇ
pdfjs-find-highlight-checkbox = ਸਭ ਉਭਾਰੋ
pdfjs-find-match-case-checkbox-label = ਅੱਖਰ ਆਕਾਰ ਨੂੰ ਮਿਲਾਉ
pdfjs-find-match-diacritics-checkbox-label = ਭੇਦਸੂਚਕ ਮੇਲ
pdfjs-find-entire-word-checkbox-label = ਪੂਰੇ ਸ਼ਬਦ
pdfjs-find-reached-top = ਦਸਤਾਵੇਜ਼ ਦੇ ਉੱਤੇ ਆ ਗਏ ਹਾਂ, ਥੱਲੇ ਤੋਂ ਜਾਰੀ ਰੱਖਿਆ ਹੈ
pdfjs-find-reached-bottom = ਦਸਤਾਵੇਜ਼ ਦੇ ਅੰਤ ਉੱਤੇ ਆ ਗਏ ਹਾਂ, ਉੱਤੇ ਤੋਂ ਜਾਰੀ ਰੱਖਿਆ ਹੈ
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $total } ਵਿੱਚੋਂ { $current } ਮੇਲ
*[other] { $total } ਵਿੱਚੋਂ { $current } ਮੇਲ
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] { $limit } ਤੋਂ ਵੱਧ ਮੇਲ
*[other] { $limit } ਤੋਂ ਵੱਧ ਮੇਲ
}
pdfjs-find-not-found = ਵਾਕ ਨਹੀਂ ਲੱਭਿਆ
## Predefined zoom values
pdfjs-page-scale-width = ਸਫ਼ੇ ਦੀ ਚੌੜਾਈ
pdfjs-page-scale-fit = ਸਫ਼ਾ ਫਿੱਟ
pdfjs-page-scale-auto = ਆਟੋਮੈਟਿਕ ਜ਼ੂਮ ਕਰੋ
pdfjs-page-scale-actual = ਆਟੋਮੈਟਿਕ ਆਕਾਰ
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = ਸਫ਼ਾ { $page }
## Loading indicator messages
pdfjs-loading-error = PDF ਲੋਡ ਕਰਨ ਦੇ ਦੌਰਾਨ ਗਲਤੀ ਆਈ ਹੈ।
pdfjs-invalid-file-error = ਗਲਤ ਜਾਂ ਨਿਕਾਰਾ PDF ਫਾਈਲ ਹੈ।
pdfjs-missing-file-error = ਨਾ-ਮੌਜੂਦ PDF ਫਾਈਲ।
pdfjs-unexpected-response-error = ਅਣਜਾਣ ਸਰਵਰ ਜਵਾਬ।
pdfjs-rendering-error = ਸਫ਼ਾ ਰੈਡਰ ਕਰਨ ਦੇ ਦੌਰਾਨ ਗਲਤੀ ਆਈ ਹੈ।
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } ਵਿਆਖਿਆ]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = ਇਹ PDF ਫਾਈਲ ਨੂੰ ਖੋਲ੍ਹਣ ਲਈ ਪਾਸਵਰਡ ਦਿਉ।
pdfjs-password-invalid = ਗਲਤ ਪਾਸਵਰਡ। ਫੇਰ ਕੋਸ਼ਿਸ਼ ਕਰੋ ਜੀ।
pdfjs-password-ok-button = ਠੀਕ ਹੈ
pdfjs-password-cancel-button = ਰੱਦ ਕਰੋ
pdfjs-web-fonts-disabled = ਵੈਬ ਫੋਂਟ ਬੰਦ ਹਨ: ਇੰਬੈਡ PDF ਫੋਂਟ ਨੂੰ ਵਰਤਣ ਲਈ ਅਸਮਰੱਥ ਹੈ।
## Editing
pdfjs-editor-free-text-button =
.title = ਲਿਖਤ
pdfjs-editor-free-text-button-label = ਲਿਖਤ
pdfjs-editor-ink-button =
.title = ਵਾਹੋ
pdfjs-editor-ink-button-label = ਵਾਹੋ
pdfjs-editor-stamp-button =
.title = ਚਿੱਤਰ ਜੋੜੋ ਜਾਂ ਸੋਧੋ
pdfjs-editor-stamp-button-label = ਚਿੱਤਰ ਜੋੜੋ ਜਾਂ ਸੋਧੋ
pdfjs-editor-highlight-button =
.title = ਹਾਈਲਾਈਟ
pdfjs-editor-highlight-button-label = ਹਾਈਲਾਈਟ
pdfjs-highlight-floating-button1 =
.title = ਹਾਈਲਾਈਟ
.aria-label = ਹਾਈਲਾਈਟ
pdfjs-highlight-floating-button-label = ਹਾਈਲਾਈਟ
pdfjs-editor-signature-button =
.title = ਦਸਤਖ਼ਤ ਜੋੜੋ
pdfjs-editor-signature-button-label = ਦਸਤਖ਼ਤ ਜੋੜੋ
## Default editor aria labels
# “Highlight” is a noun, the string is used on the editor for highlights.
pdfjs-editor-highlight-editor =
.aria-label = ਹਾਈਲਾਈਟ ਸੰਪਾਦਕ
# “Drawing” is a noun, the string is used on the editor for drawings.
pdfjs-editor-ink-editor =
.aria-label = ਡਰਾਇੰਗ ਸੰਪਾਦਕ
pdfjs-editor-signature-editor =
.aria-label = ਦਸਤਖ਼ਤ ਸੰਪਾਦਕ
pdfjs-editor-stamp-editor =
.aria-label = ਚਿੱਤਰ ਸੰਪਾਦਕ
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = ਡਰਾਇੰਗ ਨੂੰ ਹਟਾਓ
pdfjs-editor-remove-freetext-button =
.title = ਲਿਖਤ ਨੂੰ ਹਟਾਓ
pdfjs-editor-remove-stamp-button =
.title = ਚਿੱਤਰ ਨੂੰ ਹਟਾਓ
pdfjs-editor-remove-highlight-button =
.title = ਹਾਈਲਾਈਟ ਨੂੰ ਹਟਾਓ
pdfjs-editor-remove-signature-button =
.title = ਦਸਤਖ਼ਤ ਨੂੰ ਹਟਾਓ
##
# Editor Parameters
pdfjs-editor-free-text-color-input = ਰੰਗ
pdfjs-editor-free-text-size-input = ਆਕਾਰ
pdfjs-editor-ink-color-input = ਰੰਗ
pdfjs-editor-ink-thickness-input = ਮੋਟਾਈ
pdfjs-editor-ink-opacity-input = ਧੁੰਦਲਾਪਨ
pdfjs-editor-stamp-add-image-button =
.title = ਚਿੱਤਰ ਜੋੜੋ
pdfjs-editor-stamp-add-image-button-label = ਚਿੱਤਰ ਜੋੜੋ
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = ਮੋਟਾਈ
pdfjs-editor-free-highlight-thickness-title =
.title = ਚੀਜ਼ਾਂ ਨੂੰ ਹੋਰ ਲਿਖਤਾਂ ਤੋਂ ਉਘਾੜਨ ਸਮੇਂ ਮੋਟਾਈ ਨੂੰ ਬਦਲੋ
pdfjs-editor-add-signature-container =
.aria-label = ਦਸਤਖ਼ਤ ਕੰਟਰੋਲ ਅਤੇ ਸੰਭਾਲੇ ਹੋਏ ਦਸਤਖ਼ਤ
pdfjs-editor-signature-add-signature-button =
.title = ਨਵੇਂ ਦਸਤਖ਼ਤ ਨੂੰ ਜੋੜੋ
pdfjs-editor-signature-add-signature-button-label = ਨਵੇਂ ਦਸਤਖ਼ਤ ਨੂੰ ਜੋੜੋ
# Used on the button to use an already saved signature.
# Variables:
# $description (String) - a string describing/labeling the signature.
pdfjs-editor-add-saved-signature-button =
.title = ਸੰਭਾਲੇ ਦਸਤਖ਼ਤ: { $description }
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = ਲਿਖਤ ਐਡੀਟਰ
.default-content = …ਲਿਖਣਾ ਸ਼ੁਰੂ ਕਰੋ
pdfjs-free-text =
.aria-label = ਲਿਖਤ ਐਡੀਟਰ
pdfjs-free-text-default-content = …ਲਿਖਣਾ ਸ਼ੁਰੂ ਕਰੋ
pdfjs-ink =
.aria-label = ਵਹਾਉਣ ਐਡੀਟਰ
pdfjs-ink-canvas =
.aria-label = ਵਰਤੋਂਕਾਰ ਵਲੋਂ ਬਣਾਇਆ ਚਿੱਤਰ
## Alt-text dialog
pdfjs-editor-alt-text-button-label = ਬਦਲਵੀਂ ਲਿਖਤ
pdfjs-editor-alt-text-edit-button =
.aria-label = ਬਦਲਵੀ ਲਿਖਤ ਨੂੰ ਸੋਧੋ
pdfjs-editor-alt-text-edit-button-label = ਬਦਲਵੀ ਲਿਖਤ ਨੂੰ ਸੋਧੋ
pdfjs-editor-alt-text-dialog-label = ਚੋਣ ਕਰੋ
pdfjs-editor-alt-text-dialog-description = ਚਿੱਤਰ ਨਾ ਦਿੱਸਣ ਜਾਂ ਲੋਡ ਨਾ ਹੋਣ ਦੀ ਹਾਲਤ ਵਿੱਚ Alt ਲਿਖਤ (ਬਦਲਵੀਂ ਲਿਖਤ) ਲੋਕਾਂ ਲਈ ਮਦਦਗਾਰ ਹੁੰਦੀ ਹੈ।
pdfjs-editor-alt-text-add-description-label = ਵਰਣਨ ਜੋੜੋ
pdfjs-editor-alt-text-add-description-description = 1-2 ਵਾਕ ਰੱਖੋ, ਜੋ ਕਿ ਵਿਸ਼ੇ, ਸੈਟਿੰਗ ਜਾਂ ਕਾਰਵਾਈਆਂ ਬਾਰੇ ਦਰਸਾਉਂਦੇ ਹੋਣ।
pdfjs-editor-alt-text-mark-decorative-label = ਸਜਾਵਟ ਵਜੋਂ ਨਿਸ਼ਾਨ ਲਾਇਆ
pdfjs-editor-alt-text-mark-decorative-description = ਇਸ ਨੂੰ ਸਜਾਵਟੀ ਚਿੱਤਰਾਂ ਲਈ ਵਰਤਿਆ ਜਾਂਦਾ ਹੈ ਜਿਵੇਂ ਕਿ ਹਾਸ਼ੀਆ ਜਾਂ ਵਾਟਰਮਾਰਕ ਆਦਿ।
pdfjs-editor-alt-text-cancel-button = ਰੱਦ ਕਰੋ
pdfjs-editor-alt-text-save-button = ਸੰਭਾਲੋ
pdfjs-editor-alt-text-decorative-tooltip = ਸਜਾਵਟ ਵਜੋਂ ਨਿਸ਼ਾਨ ਲਾਓ
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = ਮਿਸਾਲ ਵਜੋਂ, “ਗੱਭਰੂ ਭੋਜਨ ਲੈ ਕੇ ਮੇਜ਼ ਉੱਤੇ ਬੈਠਾ ਹੈ”
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = ਬਦਲਵੀਂ ਲਿਖਤ
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = ਉੱਤੇ ਖੱਬਾ ਕੋਨਾ — ਮੁੜ-ਆਕਾਰ ਕਰੋ
pdfjs-editor-resizer-label-top-middle = ਉੱਤੇ ਮੱਧ — ਮੁੜ-ਆਕਾਰ ਕਰੋ
pdfjs-editor-resizer-label-top-right = ਉੱਤੇ ਸੱਜਾ ਕੋਨਾ — ਮੁੜ-ਆਕਾਰ ਕਰੋ
pdfjs-editor-resizer-label-middle-right = ਮੱਧ ਸੱਜਾ — ਮੁੜ-ਆਕਾਰ ਕਰੋ
pdfjs-editor-resizer-label-bottom-right = ਹੇਠਾਂ ਸੱਜਾ ਕੋਨਾ — ਮੁੜ-ਆਕਾਰ ਕਰੋ
pdfjs-editor-resizer-label-bottom-middle = ਹੇਠਾਂ ਮੱਧ — ਮੁੜ-ਆਕਾਰ ਕਰੋ
pdfjs-editor-resizer-label-bottom-left = ਹੇਠਾਂ ਖੱਬਾ ਕੋਨਾ — ਮੁੜ-ਆਕਾਰ ਕਰੋ
pdfjs-editor-resizer-label-middle-left = ਮੱਧ ਖੱਬਾ — ਮੁੜ-ਆਕਾਰ ਕਰੋ
pdfjs-editor-resizer-top-left =
.aria-label = ਉੱਤੇ ਖੱਬਾ ਕੋਨਾ — ਮੁੜ-ਆਕਾਰ ਕਰੋ
pdfjs-editor-resizer-top-middle =
.aria-label = ਉੱਤੇ ਮੱਧ — ਮੁੜ-ਆਕਾਰ ਕਰੋ
pdfjs-editor-resizer-top-right =
.aria-label = ਉੱਤੇ ਸੱਜਾ ਕੋਨਾ — ਮੁੜ-ਆਕਾਰ ਕਰੋ
pdfjs-editor-resizer-middle-right =
.aria-label = ਮੱਧ ਸੱਜਾ — ਮੁੜ-ਆਕਾਰ ਕਰੋ
pdfjs-editor-resizer-bottom-right =
.aria-label = ਹੇਠਾਂ ਸੱਜਾ ਕੋਨਾ — ਮੁੜ-ਆਕਾਰ ਕਰੋ
pdfjs-editor-resizer-bottom-middle =
.aria-label = ਹੇਠਾਂ ਮੱਧ — ਮੁੜ-ਆਕਾਰ ਕਰੋ
pdfjs-editor-resizer-bottom-left =
.aria-label = ਹੇਠਾਂ ਖੱਬਾ ਕੋਨਾ — ਮੁੜ-ਆਕਾਰ ਕਰੋ
pdfjs-editor-resizer-middle-left =
.aria-label = ਮੱਧ ਖੱਬਾ — ਮੁੜ-ਆਕਾਰ ਕਰੋ
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = ਹਾਈਟਲਾਈਟ ਦਾ ਰੰਗ
pdfjs-editor-colorpicker-button =
.title = ਰੰਗ ਨੂੰ ਬਦਲੋ
pdfjs-editor-colorpicker-dropdown =
.aria-label = ਰੰਗ ਚੋਣਾਂ
pdfjs-editor-colorpicker-yellow =
.title = ਪੀਲਾ
pdfjs-editor-colorpicker-green =
.title = ਹਰਾ
pdfjs-editor-colorpicker-blue =
.title = ਨੀਲਾ
pdfjs-editor-colorpicker-pink =
.title = ਗੁਲਾਬੀ
pdfjs-editor-colorpicker-red =
.title = ਲਾਲ
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = ਸਭ ਵੇਖੋ
pdfjs-editor-highlight-show-all-button =
.title = ਸਭ ਵੇਖੋ
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = ਬਦਲਵੀਂ ਲਿਖਤ (ਚਿੱਤਰ ਦਾ ਵਰਣਨ) ਨੂੰ ਸੋਧੋ
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = ਬਦਲਵੀਂ ਲਿਖਤ (ਚਿੱਤਰ ਦਾ ਵਰਣਨ) ਨੂੰ ਜੋੜੋ
pdfjs-editor-new-alt-text-textarea =
.placeholder = …ਆਪਣਾ ਵਰਣਨਾ ਇੱਥੇ ਲਿਖੋ
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = ਲੋਕ, ਜੋ ਕਿ ਚਿੱਤਰ ਨਹੀਂ ਵੇਖ ਸਕਦੇ ਜਾਂ ਜਦ ਵੀ ਚਿੱਤਰਾਂ ਨੂੰ ਲੋਡ ਨਹੀਂ ਜਾ ਸਕਦਾ, ਉਸ ਲਈ ਛੋਟਾ ਵੇਰਵਾ ਦਿਓ।
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = ਇਹ ਬਦਲਵੀਂ ਲਿਖਤ ਆਪਣੇ-ਆਪ ਤਿਆਰ ਕੀਤੀ ਗਈ ਸੀ ਅਤੇ ਗਲਤ ਵੀ ਹੋ ਸਕਦੀ ਹੈ।
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = ਹੋਰ ਜਾਣੋ
pdfjs-editor-new-alt-text-create-automatically-button-label = ਬਲਦਵੀਂ ਲਿਖਤ ਆਪਣੇ-ਆਪ ਬਣਾਓ
pdfjs-editor-new-alt-text-not-now-button = ਹੁਣੇ ਨਹੀਂ
pdfjs-editor-new-alt-text-error-title = ਬਦਲਵੀਂ ਲਿਖਤ ਆਪਣੇ-ਆਪ ਬਣਾਈ ਨਹੀਂ ਜਾ ਸਕੀ
pdfjs-editor-new-alt-text-error-description = ਆਪਣਾ ਖੁਦ ਦੀ ਬਦਲਵੀਂ ਲਿਖਤ ਲਿਖੋ ਜਾਂ ਫੇਰ ਕੋਸ਼ਿਸ਼ ਕਰੋ।
pdfjs-editor-new-alt-text-error-close-button = ਬੰਦ ਕਰੋ
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = ਬਦਲਵਾਂ ਲਿਖਤ AI ਮਾਡਲ ਡਾਊਨਲੋਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ ({ $totalSize } MB ਵਿੱਚੋਂ { $downloadedSize })
.aria-valuetext = ਬਦਲਵਾਂ ਲਿਖਤ AI ਮਾਡਲ ਡਾਊਨਲੋਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ ({ $totalSize } MB ਵਿੱਚੋਂ { $downloadedSize })
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = ਬਦਲਵੀਂ ਲਿਖਤ ਜੋੜੀ
pdfjs-editor-new-alt-text-added-button-label = ਬਦਲਵੀਂ ਲਿਖਤ ਜੋੜੀ
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = ਬਦਲਵਾਂ ਲਿਖਤ ਗੁੰਮ ਹੈ
pdfjs-editor-new-alt-text-missing-button-label = ਬਦਲਵਾਂ ਲਿਖਤ ਗੁੰਮ ਹੈ
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = ਬਦਲਵੀਂ ਲਿਖਤ ਦਾ ਰੀਵਿਊ ਕਰੋ
pdfjs-editor-new-alt-text-to-review-button-label = ਬਦਲਵੀਂ ਲਿਖਤ ਦਾ ਰੀਵਿਊ ਕਰੋ
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = ਆਪਣੇ-ਆਪ ਬਣਾਇਆ: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = ਚਿੱਤਰ ਬਦਲਵੀਂ ਲਿਖਤ ਦੀਆਂ ਸੈਟਿੰਗਾਂ
pdfjs-image-alt-text-settings-button-label = ਚਿੱਤਰ ਬਦਲਵੀਂ ਲਿਖਤ ਦੀਆਂ ਸੈਟਿੰਗਾਂ
pdfjs-editor-alt-text-settings-dialog-label = ਚਿੱਤਰ ਬਦਲਵੀਂ ਲਿਖਤ ਦੀਆਂ ਸੈਟਿੰਗਾਂ
pdfjs-editor-alt-text-settings-automatic-title = ਆਟੋਮਮੈਟਿਕ ਬਦਲਵੀਂ ਲਿਖਤ
pdfjs-editor-alt-text-settings-create-model-button-label = ਬਲਦਵੀਂ ਲਿਖਤ ਆਪਣੇ-ਆਪ ਬਣਾਓ
pdfjs-editor-alt-text-settings-create-model-description = ਚਿੱਤਰ ਨਾ ਵੇਖ ਸਕਣ ਵਾਲੇ ਲੋਕਾਂ ਦੀ ਮਦਦ ਜਾਂ ਜਦ ਵੀ ਚਿੱਤਰਾਂ ਨੂੰ ਲੋਡ ਨਹੀਂ ਜਾ ਸਕਦਾ, ਉਸ ਲਈ ਛੋਟਾ ਵੇਰਵਾ ਦਿਓ।
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = ਬਦਲਵੀ ਲਿਖਤ ਲਈ AI ਮਾਡਲ ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = ਤੁਹਾਡੇ ਡਿਵਾਈਸ ਉੱਤੇ ਲੋਕਲ ਹੀ ਚੱਲਦਾ ਹੋਣ ਕਰਕੇ ਤੁਹਾਡਾ ਡਾਟਾ ਪ੍ਰਾਈਵੇਟ ਹੀ ਰਹਿੰਦਾ ਹੈ। ਆਟੋਮੈਟਿਕ ਬਦਲਵੀਂ ਲਿਖਤ ਲਈ ਚਾਹੀਦਾ ਹੈ।
pdfjs-editor-alt-text-settings-delete-model-button = ਹਟਾਓ
pdfjs-editor-alt-text-settings-download-model-button = ਡਾਊਨਲੋਡ
pdfjs-editor-alt-text-settings-downloading-model-button = …ਨੂੰ ਡਾਊਨਲੋਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ
pdfjs-editor-alt-text-settings-editor-title = ਬਦਲਵੀਂ ਲਿਖਤ ਐਡੀਟਰ
pdfjs-editor-alt-text-settings-show-dialog-button-label = ਜਦੋਂ ਵਿੱਚ ਚਿੱਤਰ ਜੋੜਿਆ ਜਾਵੇ ਤਾਂ ਫ਼ੌਰਨ ਬਦਲਵੀ ਲਿਖਤ ਸੰਪਾਦਕ ਵੇਖਾਓ
pdfjs-editor-alt-text-settings-show-dialog-description = ਤੁਹਾਡੀ ਮਦਦ ਕਰਦਾ ਹੈ ਕਿ ਤੁਹਾਡੇ ਸਾਰੇ ਚਿੱਤਰਾਂ ਲਈ ਬਦਲਵੀਂ ਲਿਖਤ ਮੌਜੂਦ ਹੋਵੇ।
pdfjs-editor-alt-text-settings-close-button = ਬੰਦ ਕਰੋ
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = ਹਾਈਲਾਈਟ ਨੂੰ ਹਟਾਇਆ ਗਿਆ
pdfjs-editor-undo-bar-message-freetext = ਲਿਖਤ ਨੂੰ ਹਟਾਇਆ ਗਿਆ
pdfjs-editor-undo-bar-message-ink = ਡਰਾਇੰਗ ਨੂੰ ਹਟਾਇਆ ਗਿਆ
pdfjs-editor-undo-bar-message-stamp = ਚਿੱਤਰ ਨੂੰ ਹਟਾਇਆ ਗਿਆ
pdfjs-editor-undo-bar-message-signature = ਦਸਤਖ਼ਤ ਨੂੰ ਹਟਾਇਆ
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } ਵਿਆਖਿਆ ਨੂੰ ਹਟਾਇਆ
*[other] { $count } ਵਿਆਖਿਆਵਾਂ ਨੂੰ ਹਟਾਇਆ
}
pdfjs-editor-undo-bar-undo-button =
.title = ਵਾਪਸ
pdfjs-editor-undo-bar-undo-button-label = ਵਾਪਸ
pdfjs-editor-undo-bar-close-button =
.title = ਬੰਦ ਕਰੋ
pdfjs-editor-undo-bar-close-button-label = ਬੰਦ ਕਰੋ
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = ਇਹ ਮਾਡਲ ਵਰਤੋਂਕਾਰ ਨੂੰ PDF ਦਸਤਾਵੇਜ਼ ਵਿੱਚ ਜੋੜਨ ਲਈ ਦਸਤਖ਼ਤ ਬਣਾਉਣ ਦਿੰਦਾ ਹੈ। ਵਰਤੋਂਕਾਰ ਨਾਂ ਨੂੰ ਸੋਧ ਸਕਦਾ ਹੈ (ਜੋ ਕਿ ਬਦਲਵੀਂ ਲਿਖਤ ਵਜੋਂ ਕੰਮ ਕਰੇਗਾ) ਅਤੇ ਦੁਬਾਰਾ ਵਰਤੋਂ ਕਰਨ ਲਈ ਦਸਤਖ਼ਤਾਂ ਨੂੰ ਸੰਭਾਲ ਵੀ ਸਕਦਾ ਹੈ।
pdfjs-editor-add-signature-dialog-title = ਦਸਤਖ਼ਤ ਨੂੰ ਜੋੜੋ
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = ਕਿਸਮ
.title = ਕਿਸਮ
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = ਵਾਹੋ
.title = ਵਾਹੋ
pdfjs-editor-add-signature-image-button = ਚਿੱਤਰ
.title = ਚਿੱਤਰ
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = ਆਪਣੇ ਦਸਤਖ਼ਤ ਨੂੰ ਟਾਈਪ ਕਰੋ
.placeholder = ਆਪਣੇ ਦਸਤਖ਼ਤ ਨੂੰ ਟਾਈਪ ਕਰੋ
pdfjs-editor-add-signature-draw-placeholder = ਆਪਣੇ ਦਸਤਖ਼ਤ ਨੂੰ ਵਾਹੋ
pdfjs-editor-add-signature-draw-thickness-range-label = ਮੋਟਾਈ
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = ਵਹਾਉਣ ਲਈ ਚੌੜਾਈ: { $thickness }
pdfjs-editor-add-signature-image-placeholder = ਅੱਪਲੋਡ ਕਰਨ ਲਈ ਫ਼ਾਇਲ ਨੂੰ ਇੱਥੇ ਖਿੱਚੋ
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] ਜਾਂ ਚਿੱਤਰ ਫ਼ਾਇਲਾਂ ਨੂੰ ਚੁਣੋ
*[other] ਜਾਂ ਚਿੱਤਰ ਫ਼ਾਇਲਾਂ ਦੀ ਝਲਕ ਵੇਖੋ
}
## Controls
pdfjs-editor-add-signature-description-label = ਵਰਣਨ (ਬਦਲਵੀਂ ਲਿਖਤ)
pdfjs-editor-add-signature-description-input =
.title = ਵਰਣਨ (ਬਦਲਵੀਂ ਲਿਖਤ)
pdfjs-editor-add-signature-description-default-when-drawing = ਦਸਤਖ਼ਤ
pdfjs-editor-add-signature-clear-button-label = ਦਸਤਖ਼ਤ ਨੂੰ ਮਿਟਾਓ
pdfjs-editor-add-signature-clear-button =
.title = ਦਸਤਖ਼ਤ ਨੂੰ ਮਿਟਾਓ
pdfjs-editor-add-signature-save-checkbox = ਦਸਤਖ਼ਤ ਨੂੰ ਸੰਭਾਲੋ
pdfjs-editor-add-signature-save-warning-message = ਤੁਸੀਂ ਵੱਧ ਤੋਂ ਵੱਧ 5 ਸੰਭਾਲੇ ਦਸਤਖ਼ਤਾਂ ਦੀ ਹੱਦ ਤੱਕ ਅੱਪੜੇ। ਹੋਰ ਸੰਭਾਲਣ ਲਈ ਇੱਕ ਨੂੰ ਹਟਾਓ।
pdfjs-editor-add-signature-image-upload-error-title = ਚਿੱਤਰ ਨੂੰ ਅੱਪਲੋਡ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ
pdfjs-editor-add-signature-image-upload-error-description = ਆਪਣੇ ਕਨੈਕਸ਼ਨ ਦੀ ਜਾਂਚ ਕਰੋ ਜਾਂ ਹੋਰ ਚਿੱਤਰ ਨੂੰ ਅਜ਼ਮਾਓ।
pdfjs-editor-add-signature-error-close-button = ਬੰਦ ਕਰੋ
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = ਰੱਦ ਕਰੋ
pdfjs-editor-add-signature-add-button = ਜੋੜੋ
pdfjs-editor-edit-signature-update-button = ਅੱਪਡੇਟ
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = ਦਸਤਖ਼ਤ ਨੂੰ ਹਟਾਓ
pdfjs-editor-delete-signature-button-label = ਦਸਤਖ਼ਤ ਨੂੰ ਹਟਾਓ
pdfjs-editor-delete-signature-button1 =
.title = ਸੰਭਾਲੇ ਹੋਏ ਦਸਤਖ਼ਤ ਨੂੰ ਹਟਾਓ
pdfjs-editor-delete-signature-button-label1 = ਸੰਭਾਲੇ ਹੋਏ ਦਸਤਖ਼ਤ ਨੂੰ ਹਟਾਓ
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = ਵਰਣਨ ਨੂੰ ਸੋਧੋ
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = ਵਰਣਨ ਨੂੰ ਸੋਧੋ
================================================
FILE: cookbook/static/pdfjs/web/locale/pl/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Poprzednia strona
pdfjs-previous-button-label = Poprzednia
pdfjs-next-button =
.title = Następna strona
pdfjs-next-button-label = Następna
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Strona
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = z { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } z { $pagesCount })
pdfjs-zoom-out-button =
.title = Pomniejsz
pdfjs-zoom-out-button-label = Pomniejsz
pdfjs-zoom-in-button =
.title = Powiększ
pdfjs-zoom-in-button-label = Powiększ
pdfjs-zoom-select =
.title = Skala
pdfjs-presentation-mode-button =
.title = Przełącz na tryb prezentacji
pdfjs-presentation-mode-button-label = Tryb prezentacji
pdfjs-open-file-button =
.title = Otwórz plik
pdfjs-open-file-button-label = Otwórz
pdfjs-print-button =
.title = Drukuj
pdfjs-print-button-label = Drukuj
pdfjs-save-button =
.title = Zapisz
pdfjs-save-button-label = Zapisz
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Pobierz
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Pobierz
pdfjs-bookmark-button =
.title = Bieżąca strona (adres do otwarcia na bieżącej stronie)
pdfjs-bookmark-button-label = Bieżąca strona
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Narzędzia
pdfjs-tools-button-label = Narzędzia
pdfjs-first-page-button =
.title = Przejdź do pierwszej strony
pdfjs-first-page-button-label = Przejdź do pierwszej strony
pdfjs-last-page-button =
.title = Przejdź do ostatniej strony
pdfjs-last-page-button-label = Przejdź do ostatniej strony
pdfjs-page-rotate-cw-button =
.title = Obróć zgodnie z ruchem wskazówek zegara
pdfjs-page-rotate-cw-button-label = Obróć zgodnie z ruchem wskazówek zegara
pdfjs-page-rotate-ccw-button =
.title = Obróć przeciwnie do ruchu wskazówek zegara
pdfjs-page-rotate-ccw-button-label = Obróć przeciwnie do ruchu wskazówek zegara
pdfjs-cursor-text-select-tool-button =
.title = Włącz narzędzie zaznaczania tekstu
pdfjs-cursor-text-select-tool-button-label = Narzędzie zaznaczania tekstu
pdfjs-cursor-hand-tool-button =
.title = Włącz narzędzie rączka
pdfjs-cursor-hand-tool-button-label = Narzędzie rączka
pdfjs-scroll-page-button =
.title = Przewijaj strony
pdfjs-scroll-page-button-label = Przewijanie stron
pdfjs-scroll-vertical-button =
.title = Przewijaj dokument w pionie
pdfjs-scroll-vertical-button-label = Przewijanie pionowe
pdfjs-scroll-horizontal-button =
.title = Przewijaj dokument w poziomie
pdfjs-scroll-horizontal-button-label = Przewijanie poziome
pdfjs-scroll-wrapped-button =
.title = Strony dokumentu wyświetlaj i przewijaj w kolumnach
pdfjs-scroll-wrapped-button-label = Widok dwóch stron
pdfjs-spread-none-button =
.title = Nie ustawiaj stron obok siebie
pdfjs-spread-none-button-label = Brak kolumn
pdfjs-spread-odd-button =
.title = Strony nieparzyste ustawiaj na lewo od parzystych
pdfjs-spread-odd-button-label = Nieparzyste po lewej
pdfjs-spread-even-button =
.title = Strony parzyste ustawiaj na lewo od nieparzystych
pdfjs-spread-even-button-label = Parzyste po lewej
## Document properties dialog
pdfjs-document-properties-button =
.title = Właściwości dokumentu…
pdfjs-document-properties-button-label = Właściwości dokumentu…
pdfjs-document-properties-file-name = Nazwa pliku:
pdfjs-document-properties-file-size = Rozmiar pliku:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } B)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } B)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } B)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } B)
pdfjs-document-properties-title = Tytuł:
pdfjs-document-properties-author = Autor:
pdfjs-document-properties-subject = Temat:
pdfjs-document-properties-keywords = Słowa kluczowe:
pdfjs-document-properties-creation-date = Data utworzenia:
pdfjs-document-properties-modification-date = Data modyfikacji:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Utworzony przez:
pdfjs-document-properties-producer = PDF wyprodukowany przez:
pdfjs-document-properties-version = Wersja PDF:
pdfjs-document-properties-page-count = Liczba stron:
pdfjs-document-properties-page-size = Wymiary strony:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = pionowa
pdfjs-document-properties-page-size-orientation-landscape = pozioma
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = US Letter
pdfjs-document-properties-page-size-name-legal = US Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width }×{ $height } { $unit } (orientacja { $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width }×{ $height } { $unit } ({ $name }, orientacja { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Szybki podgląd w Internecie:
pdfjs-document-properties-linearized-yes = tak
pdfjs-document-properties-linearized-no = nie
pdfjs-document-properties-close-button = Zamknij
## Print
pdfjs-print-progress-message = Przygotowywanie dokumentu do druku…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Anuluj
pdfjs-printing-not-supported = Ostrzeżenie: drukowanie nie jest w pełni obsługiwane przez tę przeglądarkę.
pdfjs-printing-not-ready = Ostrzeżenie: dokument PDF nie jest całkowicie wczytany, więc nie można go wydrukować.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Przełącz panel boczny
pdfjs-toggle-sidebar-notification-button =
.title = Przełącz panel boczny (dokument zawiera konspekt/załączniki/warstwy)
pdfjs-toggle-sidebar-button-label = Przełącz panel boczny
pdfjs-document-outline-button =
.title = Konspekt dokumentu (podwójne kliknięcie rozwija lub zwija wszystkie pozycje)
pdfjs-document-outline-button-label = Konspekt dokumentu
pdfjs-attachments-button =
.title = Załączniki
pdfjs-attachments-button-label = Załączniki
pdfjs-layers-button =
.title = Warstwy (podwójne kliknięcie przywraca wszystkie warstwy do stanu domyślnego)
pdfjs-layers-button-label = Warstwy
pdfjs-thumbs-button =
.title = Miniatury
pdfjs-thumbs-button-label = Miniatury
pdfjs-current-outline-item-button =
.title = Znajdź bieżący element konspektu
pdfjs-current-outline-item-button-label = Bieżący element konspektu
pdfjs-findbar-button =
.title = Znajdź w dokumencie
pdfjs-findbar-button-label = Znajdź
pdfjs-additional-layers = Dodatkowe warstwy
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = { $page }. strona
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Miniatura { $page }. strony
## Find panel button title and messages
pdfjs-find-input =
.title = Znajdź
.placeholder = Znajdź w dokumencie…
pdfjs-find-previous-button =
.title = Znajdź poprzednie wystąpienie tekstu
pdfjs-find-previous-button-label = Poprzednie
pdfjs-find-next-button =
.title = Znajdź następne wystąpienie tekstu
pdfjs-find-next-button-label = Następne
pdfjs-find-highlight-checkbox = Wyróżnianie wszystkich
pdfjs-find-match-case-checkbox-label = Rozróżnianie wielkości liter
pdfjs-find-match-diacritics-checkbox-label = Rozróżnianie liter diakrytyzowanych
pdfjs-find-entire-word-checkbox-label = Całe słowa
pdfjs-find-reached-top = Początek dokumentu. Wyszukiwanie od końca.
pdfjs-find-reached-bottom = Koniec dokumentu. Wyszukiwanie od początku.
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current }. z { $total } trafienia
[few] { $current }. z { $total } trafień
*[many] { $current }. z { $total } trafień
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Więcej niż { $limit } trafienie
[few] Więcej niż { $limit } trafienia
*[many] Więcej niż { $limit } trafień
}
pdfjs-find-not-found = Nie znaleziono tekstu
## Predefined zoom values
pdfjs-page-scale-width = Szerokość strony
pdfjs-page-scale-fit = Dopasowanie strony
pdfjs-page-scale-auto = Skala automatyczna
pdfjs-page-scale-actual = Rozmiar oryginalny
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = { $page }. strona
## Loading indicator messages
pdfjs-loading-error = Podczas wczytywania dokumentu PDF wystąpił błąd.
pdfjs-invalid-file-error = Nieprawidłowy lub uszkodzony plik PDF.
pdfjs-missing-file-error = Brak pliku PDF.
pdfjs-unexpected-response-error = Nieoczekiwana odpowiedź serwera.
pdfjs-rendering-error = Podczas renderowania strony wystąpił błąd.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [Przypis: { $type }]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Wprowadź hasło, aby otworzyć ten dokument PDF.
pdfjs-password-invalid = Nieprawidłowe hasło. Proszę spróbować ponownie.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Anuluj
pdfjs-web-fonts-disabled = Czcionki sieciowe są wyłączone: nie można użyć osadzonych czcionek PDF.
## Editing
pdfjs-editor-free-text-button =
.title = Tekst
pdfjs-editor-free-text-button-label = Tekst
pdfjs-editor-ink-button =
.title = Rysunek
pdfjs-editor-ink-button-label = Rysunek
pdfjs-editor-stamp-button =
.title = Dodaj lub edytuj obrazy
pdfjs-editor-stamp-button-label = Dodaj lub edytuj obrazy
pdfjs-editor-highlight-button =
.title = Wyróżnij
pdfjs-editor-highlight-button-label = Wyróżnij
pdfjs-highlight-floating-button1 =
.title = Wyróżnij
.aria-label = Wyróżnij
pdfjs-highlight-floating-button-label = Wyróżnij
pdfjs-editor-signature-button =
.title = Dodaj podpis
pdfjs-editor-signature-button-label = Dodaj podpis
## Default editor aria labels
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Usuń rysunek
pdfjs-editor-remove-freetext-button =
.title = Usuń tekst
pdfjs-editor-remove-stamp-button =
.title = Usuń obraz
pdfjs-editor-remove-highlight-button =
.title = Usuń wyróżnienie
pdfjs-editor-remove-signature-button =
.title = Usuń podpis
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Kolor
pdfjs-editor-free-text-size-input = Rozmiar
pdfjs-editor-ink-color-input = Kolor
pdfjs-editor-ink-thickness-input = Grubość
pdfjs-editor-ink-opacity-input = Nieprzezroczystość
pdfjs-editor-stamp-add-image-button =
.title = Dodaj obraz
pdfjs-editor-stamp-add-image-button-label = Dodaj obraz
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Grubość
pdfjs-editor-free-highlight-thickness-title =
.title = Zmień grubość podczas wyróżniania elementów innych niż tekst
pdfjs-editor-signature-add-signature-button =
.title = Dodaj nowy podpis
pdfjs-editor-signature-add-signature-button-label = Dodaj nowy podpis
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Edytor tekstu
.default-content = Zacznij pisać…
pdfjs-free-text =
.aria-label = Edytor tekstu
pdfjs-free-text-default-content = Zacznij pisać…
pdfjs-ink =
.aria-label = Edytor rysunku
pdfjs-ink-canvas =
.aria-label = Obraz utworzony przez użytkownika
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Tekst alternatywny
pdfjs-editor-alt-text-edit-button =
.aria-label = Edytuj tekst alternatywny
pdfjs-editor-alt-text-edit-button-label = Edytuj tekst alternatywny
pdfjs-editor-alt-text-dialog-label = Wybierz opcję
pdfjs-editor-alt-text-dialog-description = Tekst alternatywny pomaga, kiedy ktoś nie może zobaczyć obrazu lub gdy się nie wczytuje.
pdfjs-editor-alt-text-add-description-label = Dodaj opis
pdfjs-editor-alt-text-add-description-description = Staraj się napisać 1-2 zdania opisujące temat, miejsce lub działania.
pdfjs-editor-alt-text-mark-decorative-label = Oznacz jako dekoracyjne
pdfjs-editor-alt-text-mark-decorative-description = Używane w przypadku obrazów ozdobnych, takich jak obramowania lub znaki wodne.
pdfjs-editor-alt-text-cancel-button = Anuluj
pdfjs-editor-alt-text-save-button = Zapisz
pdfjs-editor-alt-text-decorative-tooltip = Oznaczone jako dekoracyjne
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Na przykład: „Młody człowiek siada przy stole, aby zjeść posiłek”
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Tekst alternatywny
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Lewy górny róg — zmień rozmiar
pdfjs-editor-resizer-label-top-middle = Górny środkowy — zmień rozmiar
pdfjs-editor-resizer-label-top-right = Prawy górny róg — zmień rozmiar
pdfjs-editor-resizer-label-middle-right = Prawy środkowy — zmień rozmiar
pdfjs-editor-resizer-label-bottom-right = Prawy dolny róg — zmień rozmiar
pdfjs-editor-resizer-label-bottom-middle = Dolny środkowy — zmień rozmiar
pdfjs-editor-resizer-label-bottom-left = Lewy dolny róg — zmień rozmiar
pdfjs-editor-resizer-label-middle-left = Lewy środkowy — zmień rozmiar
pdfjs-editor-resizer-top-left =
.aria-label = Lewy górny róg — zmień rozmiar
pdfjs-editor-resizer-top-middle =
.aria-label = Górny środkowy — zmień rozmiar
pdfjs-editor-resizer-top-right =
.aria-label = Prawy górny róg — zmień rozmiar
pdfjs-editor-resizer-middle-right =
.aria-label = Prawy środkowy — zmień rozmiar
pdfjs-editor-resizer-bottom-right =
.aria-label = Prawy dolny róg — zmień rozmiar
pdfjs-editor-resizer-bottom-middle =
.aria-label = Dolny środkowy — zmień rozmiar
pdfjs-editor-resizer-bottom-left =
.aria-label = Lewy dolny róg — zmień rozmiar
pdfjs-editor-resizer-middle-left =
.aria-label = Lewy środkowy — zmień rozmiar
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Kolor wyróżnienia
pdfjs-editor-colorpicker-button =
.title = Zmień kolor
pdfjs-editor-colorpicker-dropdown =
.aria-label = Wybór kolorów
pdfjs-editor-colorpicker-yellow =
.title = Żółty
pdfjs-editor-colorpicker-green =
.title = Zielony
pdfjs-editor-colorpicker-blue =
.title = Niebieski
pdfjs-editor-colorpicker-pink =
.title = Różowy
pdfjs-editor-colorpicker-red =
.title = Czerwony
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Pokaż wszystkie
pdfjs-editor-highlight-show-all-button =
.title = Pokaż wszystkie
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Edytuj tekst alternatywny (opis obrazu)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Dodaj tekst alternatywny (opis obrazu)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Napisz tutaj opis…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Krótki opis dla osób, które nie widzą obrazu lub kiedy obraz się nie wczytuje.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Ten tekst alternatywny został utworzony automatycznie i może być niepoprawny.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Więcej informacji
pdfjs-editor-new-alt-text-create-automatically-button-label = Automatycznie utwórz tekst alternatywny
pdfjs-editor-new-alt-text-not-now-button = Nie teraz
pdfjs-editor-new-alt-text-error-title = Nie można automatycznie utworzyć tekstu alternatywnego
pdfjs-editor-new-alt-text-error-description = Proszę napisać własny tekst alternatywny lub spróbować ponownie później.
pdfjs-editor-new-alt-text-error-close-button = Zamknij
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Pobieranie modelu SI tekstu alternatywnego ({ $downloadedSize } z { $totalSize } MB)
.aria-valuetext = Pobieranie modelu SI tekstu alternatywnego ({ $downloadedSize } z { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Dodano tekst alternatywny
pdfjs-editor-new-alt-text-added-button-label = Dodano tekst alternatywny
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Brak tekstu alternatywnego
pdfjs-editor-new-alt-text-missing-button-label = Brak tekstu alternatywnego
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Przejrzyj tekst alternatywny
pdfjs-editor-new-alt-text-to-review-button-label = Przejrzyj tekst alternatywny
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Utworzono automatycznie: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Ustawienia tekstu alternatywnego obrazów
pdfjs-image-alt-text-settings-button-label = Ustawienia tekstu alternatywnego obrazów
pdfjs-editor-alt-text-settings-dialog-label = Ustawienia tekstu alternatywnego obrazów
pdfjs-editor-alt-text-settings-automatic-title = Automatyczny tekst alternatywny
pdfjs-editor-alt-text-settings-create-model-button-label = Automatyczne tworzenie tekstu alternatywnego
pdfjs-editor-alt-text-settings-create-model-description = Podpowiada opisy, które mogą pomóc osobom, które nie widzą obrazu lub kiedy obraz się nie wczytuje.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Model SI tekstu alternatywnego ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Działa lokalnie na urządzeniu użytkownika, więc Twoje dane pozostają prywatne. Wymagane do funkcji automatycznego tekstu alternatywnego.
pdfjs-editor-alt-text-settings-delete-model-button = Usuń
pdfjs-editor-alt-text-settings-download-model-button = Pobierz
pdfjs-editor-alt-text-settings-downloading-model-button = Pobieranie…
pdfjs-editor-alt-text-settings-editor-title = Edytor tekstu alternatywnego
pdfjs-editor-alt-text-settings-show-dialog-button-label = Wyświetlanie edytora tekstu alternatywnego od razu po dodaniu obrazu
pdfjs-editor-alt-text-settings-show-dialog-description = Pomaga upewnić się, że wszystkie obrazy mają tekst alternatywny.
pdfjs-editor-alt-text-settings-close-button = Zamknij
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Usunięto wyróżnienie
pdfjs-editor-undo-bar-message-freetext = Usunięto tekst
pdfjs-editor-undo-bar-message-ink = Usunięto rysunek
pdfjs-editor-undo-bar-message-stamp = Usunięto obraz
pdfjs-editor-undo-bar-message-signature = Usunięto podpis
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] Usunięto przypis
[few] Usunięto { $count } przypisy
*[many] Usunięto { $count } przypisów
}
pdfjs-editor-undo-bar-undo-button =
.title = Cofnij
pdfjs-editor-undo-bar-undo-button-label = Cofnij
pdfjs-editor-undo-bar-close-button =
.title = Zamknij
pdfjs-editor-undo-bar-close-button-label = Zamknij
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = To okno umożliwia utworzenie podpisu, który można dodać do dokumentu PDF. Można zmienić nazwę (która służy także jako tekst alternatywny) i opcjonalnie zachować podpis do ponownego użycia.
pdfjs-editor-add-signature-dialog-title = Dodanie podpisu
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Wpisz
.title = Wpisz
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Narysuj
.title = Narysuj
pdfjs-editor-add-signature-image-button = Obraz
.title = Obraz
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Wpisz swój podpis
.placeholder = Wpisz swój podpis
pdfjs-editor-add-signature-draw-placeholder = Narysuj swój podpis
pdfjs-editor-add-signature-draw-thickness-range-label = Grubość
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Grubość kreski: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Przeciągnij tutaj plik, aby go przesłać
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Lub wybierz plik obrazu
*[other] Lub przeglądaj pliki obrazów
}
## Controls
pdfjs-editor-add-signature-description-label = Opis (tekst alternatywny)
pdfjs-editor-add-signature-description-input =
.title = Opis (tekst alternatywny)
pdfjs-editor-add-signature-description-default-when-drawing = Podpis
pdfjs-editor-add-signature-clear-button-label = Usuń podpis
pdfjs-editor-add-signature-clear-button =
.title = Usuń podpis
pdfjs-editor-add-signature-save-checkbox = Zachowaj podpis
pdfjs-editor-add-signature-save-warning-message = Osiągnięto ograniczenie wynoszące pięć zachowanych podpisów. Usuń jeden, aby zachować więcej.
pdfjs-editor-add-signature-image-upload-error-title = Nie można przesłać obrazu
pdfjs-editor-add-signature-image-upload-error-description = Sprawdź połączenie sieciowe lub spróbuj przesłać inny obraz.
pdfjs-editor-add-signature-error-close-button = Zamknij
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Anuluj
pdfjs-editor-add-signature-add-button = Dodaj
pdfjs-editor-edit-signature-update-button = Aktualizuj
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Usuń podpis
pdfjs-editor-delete-signature-button-label = Usuń podpis
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Edytuj opis
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Edycja opisu
================================================
FILE: cookbook/static/pdfjs/web/locale/pt-BR/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Página anterior
pdfjs-previous-button-label = Anterior
pdfjs-next-button =
.title = Próxima página
pdfjs-next-button-label = Próxima
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Página
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = de { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } de { $pagesCount })
pdfjs-zoom-out-button =
.title = Reduzir
pdfjs-zoom-out-button-label = Reduzir
pdfjs-zoom-in-button =
.title = Ampliar
pdfjs-zoom-in-button-label = Ampliar
pdfjs-zoom-select =
.title = Zoom
pdfjs-presentation-mode-button =
.title = Mudar para o modo de apresentação
pdfjs-presentation-mode-button-label = Modo de apresentação
pdfjs-open-file-button =
.title = Abrir arquivo
pdfjs-open-file-button-label = Abrir
pdfjs-print-button =
.title = Imprimir
pdfjs-print-button-label = Imprimir
pdfjs-save-button =
.title = Salvar
pdfjs-save-button-label = Salvar
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Baixar
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Baixar
pdfjs-bookmark-button =
.title = Página atual (ver URL da página atual)
pdfjs-bookmark-button-label = Pagina atual
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Ferramentas
pdfjs-tools-button-label = Ferramentas
pdfjs-first-page-button =
.title = Ir para a primeira página
pdfjs-first-page-button-label = Ir para a primeira página
pdfjs-last-page-button =
.title = Ir para a última página
pdfjs-last-page-button-label = Ir para a última página
pdfjs-page-rotate-cw-button =
.title = Girar no sentido horário
pdfjs-page-rotate-cw-button-label = Girar no sentido horário
pdfjs-page-rotate-ccw-button =
.title = Girar no sentido anti-horário
pdfjs-page-rotate-ccw-button-label = Girar no sentido anti-horário
pdfjs-cursor-text-select-tool-button =
.title = Ativar a ferramenta de seleção de texto
pdfjs-cursor-text-select-tool-button-label = Ferramenta de seleção de texto
pdfjs-cursor-hand-tool-button =
.title = Ativar ferramenta de deslocamento
pdfjs-cursor-hand-tool-button-label = Ferramenta de deslocamento
pdfjs-scroll-page-button =
.title = Usar rolagem de página
pdfjs-scroll-page-button-label = Rolagem de página
pdfjs-scroll-vertical-button =
.title = Usar deslocamento vertical
pdfjs-scroll-vertical-button-label = Deslocamento vertical
pdfjs-scroll-horizontal-button =
.title = Usar deslocamento horizontal
pdfjs-scroll-horizontal-button-label = Deslocamento horizontal
pdfjs-scroll-wrapped-button =
.title = Usar deslocamento contido
pdfjs-scroll-wrapped-button-label = Deslocamento contido
pdfjs-spread-none-button =
.title = Não reagrupar páginas
pdfjs-spread-none-button-label = Não estender
pdfjs-spread-odd-button =
.title = Agrupar páginas começando em páginas com números ímpares
pdfjs-spread-odd-button-label = Estender ímpares
pdfjs-spread-even-button =
.title = Agrupar páginas começando em páginas com números pares
pdfjs-spread-even-button-label = Estender pares
## Document properties dialog
pdfjs-document-properties-button =
.title = Propriedades do documento…
pdfjs-document-properties-button-label = Propriedades do documento…
pdfjs-document-properties-file-name = Nome do arquivo:
pdfjs-document-properties-file-size = Tamanho do arquivo:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = Título:
pdfjs-document-properties-author = Autor:
pdfjs-document-properties-subject = Assunto:
pdfjs-document-properties-keywords = Palavras-chave:
pdfjs-document-properties-creation-date = Data da criação:
pdfjs-document-properties-modification-date = Data da modificação:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Criação:
pdfjs-document-properties-producer = Criador do PDF:
pdfjs-document-properties-version = Versão do PDF:
pdfjs-document-properties-page-count = Número de páginas:
pdfjs-document-properties-page-size = Tamanho da página:
pdfjs-document-properties-page-size-unit-inches = pol.
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = retrato
pdfjs-document-properties-page-size-orientation-landscape = paisagem
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Carta
pdfjs-document-properties-page-size-name-legal = Jurídico
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Exibição web rápida:
pdfjs-document-properties-linearized-yes = Sim
pdfjs-document-properties-linearized-no = Não
pdfjs-document-properties-close-button = Fechar
## Print
pdfjs-print-progress-message = Preparando documento para impressão…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress } %
pdfjs-print-progress-close-button = Cancelar
pdfjs-printing-not-supported = Aviso: a impressão não é totalmente suportada neste navegador.
pdfjs-printing-not-ready = Aviso: o PDF não está totalmente carregado para impressão.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Exibir/ocultar painel lateral
pdfjs-toggle-sidebar-notification-button =
.title = Exibir/ocultar painel lateral (documento contém estrutura/anexos/camadas)
pdfjs-toggle-sidebar-button-label = Exibir/ocultar painel lateral
pdfjs-document-outline-button =
.title = Mostrar estrutura do documento (duplo-clique expande/recolhe todos os itens)
pdfjs-document-outline-button-label = Estrutura do documento
pdfjs-attachments-button =
.title = Mostrar anexos
pdfjs-attachments-button-label = Anexos
pdfjs-layers-button =
.title = Mostrar camadas (duplo-clique redefine todas as camadas ao estado predefinido)
pdfjs-layers-button-label = Camadas
pdfjs-thumbs-button =
.title = Mostrar miniaturas
pdfjs-thumbs-button-label = Miniaturas
pdfjs-current-outline-item-button =
.title = Encontrar item atual da estrutura
pdfjs-current-outline-item-button-label = Item atual da estrutura
pdfjs-findbar-button =
.title = Procurar no documento
pdfjs-findbar-button-label = Procurar
pdfjs-additional-layers = Camadas adicionais
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Página { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Miniatura da página { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Procurar
.placeholder = Procurar no documento…
pdfjs-find-previous-button =
.title = Procurar a ocorrência anterior da frase
pdfjs-find-previous-button-label = Anterior
pdfjs-find-next-button =
.title = Procurar a próxima ocorrência da frase
pdfjs-find-next-button-label = Próxima
pdfjs-find-highlight-checkbox = Destacar tudo
pdfjs-find-match-case-checkbox-label = Diferenciar maiúsculas/minúsculas
pdfjs-find-match-diacritics-checkbox-label = Considerar acentuação
pdfjs-find-entire-word-checkbox-label = Palavras completas
pdfjs-find-reached-top = Início do documento alcançado, continuando do fim
pdfjs-find-reached-bottom = Fim do documento alcançado, continuando do início
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } de { $total } ocorrência
*[other] { $current } de { $total } ocorrências
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Mais de { $limit } ocorrência
*[other] Mais de { $limit } ocorrências
}
pdfjs-find-not-found = Não encontrado
## Predefined zoom values
pdfjs-page-scale-width = Largura da página
pdfjs-page-scale-fit = Ajustar à janela
pdfjs-page-scale-auto = Zoom automático
pdfjs-page-scale-actual = Tamanho real
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Página { $page }
## Loading indicator messages
pdfjs-loading-error = Ocorreu um erro ao carregar o PDF.
pdfjs-invalid-file-error = Arquivo PDF corrompido ou inválido.
pdfjs-missing-file-error = Arquivo PDF ausente.
pdfjs-unexpected-response-error = Resposta inesperada do servidor.
pdfjs-rendering-error = Ocorreu um erro ao renderizar a página.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [Anotação { $type }]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Forneça a senha para abrir este arquivo PDF.
pdfjs-password-invalid = Senha inválida. Tente novamente.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Cancelar
pdfjs-web-fonts-disabled = As fontes web estão desativadas: não foi possível usar fontes incorporadas do PDF.
## Editing
pdfjs-editor-free-text-button =
.title = Texto
pdfjs-editor-free-text-button-label = Texto
pdfjs-editor-ink-button =
.title = Desenho
pdfjs-editor-ink-button-label = Desenho
pdfjs-editor-stamp-button =
.title = Adicionar ou editar imagens
pdfjs-editor-stamp-button-label = Adicionar ou editar imagens
pdfjs-editor-highlight-button =
.title = Destaque
pdfjs-editor-highlight-button-label = Destaque
pdfjs-highlight-floating-button1 =
.title = Destaque
.aria-label = Destaque
pdfjs-highlight-floating-button-label = Destaque
pdfjs-editor-signature-button =
.title = Adicionar assinatura
pdfjs-editor-signature-button-label = Adicionar assinatura
## Default editor aria labels
# “Highlight” is a noun, the string is used on the editor for highlights.
pdfjs-editor-highlight-editor =
.aria-label = Editor de destaque
# “Drawing” is a noun, the string is used on the editor for drawings.
pdfjs-editor-ink-editor =
.aria-label = Editor de desenho
pdfjs-editor-signature-editor =
.aria-label = Editor de assinatura
pdfjs-editor-stamp-editor =
.aria-label = Editor de imagem
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Remover desenho
pdfjs-editor-remove-freetext-button =
.title = Remover texto
pdfjs-editor-remove-stamp-button =
.title = Remover imagem
pdfjs-editor-remove-highlight-button =
.title = Remover destaque
pdfjs-editor-remove-signature-button =
.title = Remover assinatura
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Cor
pdfjs-editor-free-text-size-input = Tamanho
pdfjs-editor-ink-color-input = Cor
pdfjs-editor-ink-thickness-input = Espessura
pdfjs-editor-ink-opacity-input = Opacidade
pdfjs-editor-stamp-add-image-button =
.title = Adicionar imagem
pdfjs-editor-stamp-add-image-button-label = Adicionar imagem
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Espessura
pdfjs-editor-free-highlight-thickness-title =
.title = Mudar espessura ao destacar itens que não são texto
pdfjs-editor-add-signature-container =
.aria-label = Controles de assinatura e assinaturas salvas
pdfjs-editor-signature-add-signature-button =
.title = Adicionar nova assinatura
pdfjs-editor-signature-add-signature-button-label = Adicionar nova assinatura
# Used on the button to use an already saved signature.
# Variables:
# $description (String) - a string describing/labeling the signature.
pdfjs-editor-add-saved-signature-button =
.title = Assinatura salva: { $description }
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Editor de texto
.default-content = Comece a digitar…
pdfjs-free-text =
.aria-label = Editor de texto
pdfjs-free-text-default-content = Comece digitando…
pdfjs-ink =
.aria-label = Editor de desenho
pdfjs-ink-canvas =
.aria-label = Imagem criada pelo usuário
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Texto alternativo
pdfjs-editor-alt-text-edit-button =
.aria-label = Editar texto alternativo
pdfjs-editor-alt-text-edit-button-label = Editar texto alternativo
pdfjs-editor-alt-text-dialog-label = Escolha uma opção
pdfjs-editor-alt-text-dialog-description = O texto alternativo ajuda quando uma imagem não aparece ou não é carregada.
pdfjs-editor-alt-text-add-description-label = Adicionar uma descrição
pdfjs-editor-alt-text-add-description-description = Procure usar uma ou duas frases que descrevam o assunto, cenário ou ação.
pdfjs-editor-alt-text-mark-decorative-label = Marcar como decorativa
pdfjs-editor-alt-text-mark-decorative-description = Isto é usado em imagens ornamentais, como bordas ou marcas d'água.
pdfjs-editor-alt-text-cancel-button = Cancelar
pdfjs-editor-alt-text-save-button = Salvar
pdfjs-editor-alt-text-decorative-tooltip = Marcado como decorativa
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Por exemplo, “Um jovem senta-se à mesa para comer uma refeição”
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Texto alternativo
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Canto superior esquerdo — redimensionar
pdfjs-editor-resizer-label-top-middle = No centro do topo — redimensionar
pdfjs-editor-resizer-label-top-right = Canto superior direito — redimensionar
pdfjs-editor-resizer-label-middle-right = No meio à direita — redimensionar
pdfjs-editor-resizer-label-bottom-right = Canto inferior direito — redimensionar
pdfjs-editor-resizer-label-bottom-middle = No centro da base — redimensionar
pdfjs-editor-resizer-label-bottom-left = Canto inferior esquerdo — redimensionar
pdfjs-editor-resizer-label-middle-left = No meio à esquerda — redimensionar
pdfjs-editor-resizer-top-left =
.aria-label = Canto superior esquerdo — redimensionar
pdfjs-editor-resizer-top-middle =
.aria-label = No centro do topo — redimensionar
pdfjs-editor-resizer-top-right =
.aria-label = Canto superior direito — redimensionar
pdfjs-editor-resizer-middle-right =
.aria-label = No meio à direita — redimensionar
pdfjs-editor-resizer-bottom-right =
.aria-label = Canto inferior direito — redimensionar
pdfjs-editor-resizer-bottom-middle =
.aria-label = No centro da base — redimensionar
pdfjs-editor-resizer-bottom-left =
.aria-label = Canto inferior esquerdo — redimensionar
pdfjs-editor-resizer-middle-left =
.aria-label = No meio à esquerda — redimensionar
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Cor de destaque
pdfjs-editor-colorpicker-button =
.title = Mudar cor
pdfjs-editor-colorpicker-dropdown =
.aria-label = Opções de cores
pdfjs-editor-colorpicker-yellow =
.title = Amarelo
pdfjs-editor-colorpicker-green =
.title = Verde
pdfjs-editor-colorpicker-blue =
.title = Azul
pdfjs-editor-colorpicker-pink =
.title = Rosa
pdfjs-editor-colorpicker-red =
.title = Vermelho
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Mostrar todos
pdfjs-editor-highlight-show-all-button =
.title = Mostrar todos
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Editar texto alternativo (descrição da imagem)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Adicionar texto alternativo (descrição da imagem)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Você pode escrever uma descrição aqui…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Descrição curta para pessoas que não conseguem ver a imagem ou quando a imagem não é carregada.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Este texto alternativo foi criado automaticamente, pode não estar correto.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Saiba mais
pdfjs-editor-new-alt-text-create-automatically-button-label = Criar texto alternativo automaticamente
pdfjs-editor-new-alt-text-not-now-button = Agora não
pdfjs-editor-new-alt-text-error-title = Não foi possível criar texto alternativo automaticamente
pdfjs-editor-new-alt-text-error-description = Escreva seu próprio texto alternativo ou tente novamente mais tarde.
pdfjs-editor-new-alt-text-error-close-button = Fechar
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Baixando modelo de inteligência artificial de texto alternativo ({ $downloadedSize } de { $totalSize } MB)
.aria-valuetext = Baixando modelo de inteligência artificial de texto alternativo ({ $downloadedSize } de { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Texto alternativo adicionado
pdfjs-editor-new-alt-text-added-button-label = Texto alternativo adicionado
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Sem texto alternativo
pdfjs-editor-new-alt-text-missing-button-label = Sem texto alternativo
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Revisar texto alternativo
pdfjs-editor-new-alt-text-to-review-button-label = Revisar texto alternativo
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Criado automaticamente: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Configurações de texto alternativo de imagens
pdfjs-image-alt-text-settings-button-label = Configurações de texto alternativo de imagens
pdfjs-editor-alt-text-settings-dialog-label = Configurações de texto alternativo de imagens
pdfjs-editor-alt-text-settings-automatic-title = Texto alternativo automático
pdfjs-editor-alt-text-settings-create-model-button-label = Criar texto alternativo automaticamente
pdfjs-editor-alt-text-settings-create-model-description = Sugere uma descrição para ajudar pessoas que não conseguem ver a imagem ou quando a imagem não é carregada.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Modelo de inteligência artificial de texto alternativo ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Funciona localmente no seu dispositivo para que seus dados permaneçam privativos. Necessário para texto alternativo automático.
pdfjs-editor-alt-text-settings-delete-model-button = Excluir
pdfjs-editor-alt-text-settings-download-model-button = Baixar
pdfjs-editor-alt-text-settings-downloading-model-button = Baixando…
pdfjs-editor-alt-text-settings-editor-title = Editor de texto alternativo
pdfjs-editor-alt-text-settings-show-dialog-button-label = Mostrar o editor de texto alternativo imediatamente ao adicionar uma imagem
pdfjs-editor-alt-text-settings-show-dialog-description = Ajuda a assegurar que todas as suas imagens tenham texto alternativo.
pdfjs-editor-alt-text-settings-close-button = Fechar
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Destaque removido
pdfjs-editor-undo-bar-message-freetext = Texto removido
pdfjs-editor-undo-bar-message-ink = Desenho removido
pdfjs-editor-undo-bar-message-stamp = Imagem removida
pdfjs-editor-undo-bar-message-signature = Assinatura removida
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } anotação removida
*[other] { $count } anotações removidas
}
pdfjs-editor-undo-bar-undo-button =
.title = Desfazer
pdfjs-editor-undo-bar-undo-button-label = Desfazer
pdfjs-editor-undo-bar-close-button =
.title = Fechar
pdfjs-editor-undo-bar-close-button-label = Fechar
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = Esta janela permite ao usuário criar uma assinatura para adicionar a um documento PDF. O usuário pode editar o nome (que também serve como texto alternativo) e, opcionalmente, salvar a assinatura usar novamente.
pdfjs-editor-add-signature-dialog-title = Adicionar uma assinatura
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Digitar
.title = Digitar
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Desenhar
.title = Desenhar
pdfjs-editor-add-signature-image-button = Imagem
.title = Imagem
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Digite sua assinatura
.placeholder = Digite sua assinatura
pdfjs-editor-add-signature-draw-placeholder = Desenhe sua assinatura
pdfjs-editor-add-signature-draw-thickness-range-label = Espessura
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Espessura do desenho: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Arraste um arquivo aqui para enviar
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Ou escolha arquivos de imagem
*[other] Ou escolha arquivos de imagem
}
## Controls
pdfjs-editor-add-signature-description-label = Descrição (texto alternativo)
pdfjs-editor-add-signature-description-input =
.title = Descrição (texto alternativo)
pdfjs-editor-add-signature-description-default-when-drawing = Assinatura
pdfjs-editor-add-signature-clear-button-label = Limpar assinatura
pdfjs-editor-add-signature-clear-button =
.title = Limpar assinatura
pdfjs-editor-add-signature-save-checkbox = Salvar assinatura
pdfjs-editor-add-signature-save-warning-message = Você atingiu o limite de 5 assinaturas salvas. Remova uma para salvar mais.
pdfjs-editor-add-signature-image-upload-error-title = Não foi possível enviar a imagem
pdfjs-editor-add-signature-image-upload-error-description = Verifique sua conexão de rede ou tente outra imagem.
pdfjs-editor-add-signature-error-close-button = Fechar
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Cancelar
pdfjs-editor-add-signature-add-button = Adicionar
pdfjs-editor-edit-signature-update-button = Atualizar
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Remover assinatura
pdfjs-editor-delete-signature-button-label = Remover assinatura
pdfjs-editor-delete-signature-button1 =
.title = Remover assinatura salva
pdfjs-editor-delete-signature-button-label1 = Remover assinatura salva
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Mudar descrição
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Mudar descrição
================================================
FILE: cookbook/static/pdfjs/web/locale/pt-PT/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Página anterior
pdfjs-previous-button-label = Anterior
pdfjs-next-button =
.title = Página seguinte
pdfjs-next-button-label = Seguinte
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Página
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = de { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } de { $pagesCount })
pdfjs-zoom-out-button =
.title = Reduzir
pdfjs-zoom-out-button-label = Reduzir
pdfjs-zoom-in-button =
.title = Ampliar
pdfjs-zoom-in-button-label = Ampliar
pdfjs-zoom-select =
.title = Zoom
pdfjs-presentation-mode-button =
.title = Trocar para o modo de apresentação
pdfjs-presentation-mode-button-label = Modo de apresentação
pdfjs-open-file-button =
.title = Abrir ficheiro
pdfjs-open-file-button-label = Abrir
pdfjs-print-button =
.title = Imprimir
pdfjs-print-button-label = Imprimir
pdfjs-save-button =
.title = Guardar
pdfjs-save-button-label = Guardar
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Transferir
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Transferir
pdfjs-bookmark-button =
.title = Página atual (ver URL da página atual)
pdfjs-bookmark-button-label = Pagina atual
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Ferramentas
pdfjs-tools-button-label = Ferramentas
pdfjs-first-page-button =
.title = Ir para a primeira página
pdfjs-first-page-button-label = Ir para a primeira página
pdfjs-last-page-button =
.title = Ir para a última página
pdfjs-last-page-button-label = Ir para a última página
pdfjs-page-rotate-cw-button =
.title = Rodar à direita
pdfjs-page-rotate-cw-button-label = Rodar à direita
pdfjs-page-rotate-ccw-button =
.title = Rodar à esquerda
pdfjs-page-rotate-ccw-button-label = Rodar à esquerda
pdfjs-cursor-text-select-tool-button =
.title = Ativar ferramenta de seleção de texto
pdfjs-cursor-text-select-tool-button-label = Ferramenta de seleção de texto
pdfjs-cursor-hand-tool-button =
.title = Ativar ferramenta de mão
pdfjs-cursor-hand-tool-button-label = Ferramenta de mão
pdfjs-scroll-page-button =
.title = Utilizar deslocamento da página
pdfjs-scroll-page-button-label = Deslocamento da página
pdfjs-scroll-vertical-button =
.title = Utilizar deslocação vertical
pdfjs-scroll-vertical-button-label = Deslocação vertical
pdfjs-scroll-horizontal-button =
.title = Utilizar deslocação horizontal
pdfjs-scroll-horizontal-button-label = Deslocação horizontal
pdfjs-scroll-wrapped-button =
.title = Utilizar deslocação encapsulada
pdfjs-scroll-wrapped-button-label = Deslocação encapsulada
pdfjs-spread-none-button =
.title = Não juntar páginas dispersas
pdfjs-spread-none-button-label = Sem spreads
pdfjs-spread-odd-button =
.title = Juntar páginas dispersas a partir de páginas com números ímpares
pdfjs-spread-odd-button-label = Spreads ímpares
pdfjs-spread-even-button =
.title = Juntar páginas dispersas a partir de páginas com números pares
pdfjs-spread-even-button-label = Spreads pares
## Document properties dialog
pdfjs-document-properties-button =
.title = Propriedades do documento…
pdfjs-document-properties-button-label = Propriedades do documento…
pdfjs-document-properties-file-name = Nome do ficheiro:
pdfjs-document-properties-file-size = Tamanho do ficheiro:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = Título:
pdfjs-document-properties-author = Autor:
pdfjs-document-properties-subject = Assunto:
pdfjs-document-properties-keywords = Palavras-chave:
pdfjs-document-properties-creation-date = Data de criação:
pdfjs-document-properties-modification-date = Data de modificação:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Criador:
pdfjs-document-properties-producer = Produtor de PDF:
pdfjs-document-properties-version = Versão do PDF:
pdfjs-document-properties-page-count = N.º de páginas:
pdfjs-document-properties-page-size = Tamanho da página:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = retrato
pdfjs-document-properties-page-size-orientation-landscape = paisagem
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Carta
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Vista rápida web:
pdfjs-document-properties-linearized-yes = Sim
pdfjs-document-properties-linearized-no = Não
pdfjs-document-properties-close-button = Fechar
## Print
pdfjs-print-progress-message = A preparar o documento para impressão…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Cancelar
pdfjs-printing-not-supported = Aviso: a impressão não é totalmente suportada por este navegador.
pdfjs-printing-not-ready = Aviso: o PDF ainda não está totalmente carregado.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Alternar barra lateral
pdfjs-toggle-sidebar-notification-button =
.title = Alternar barra lateral (o documento contém contornos/anexos/camadas)
pdfjs-toggle-sidebar-button-label = Alternar barra lateral
pdfjs-document-outline-button =
.title = Mostrar esquema do documento (duplo clique para expandir/colapsar todos os itens)
pdfjs-document-outline-button-label = Esquema do documento
pdfjs-attachments-button =
.title = Mostrar anexos
pdfjs-attachments-button-label = Anexos
pdfjs-layers-button =
.title = Mostrar camadas (clique duas vezes para repor todas as camadas para o estado predefinido)
pdfjs-layers-button-label = Camadas
pdfjs-thumbs-button =
.title = Mostrar miniaturas
pdfjs-thumbs-button-label = Miniaturas
pdfjs-current-outline-item-button =
.title = Encontrar o item atualmente destacado
pdfjs-current-outline-item-button-label = Item atualmente destacado
pdfjs-findbar-button =
.title = Localizar em documento
pdfjs-findbar-button-label = Localizar
pdfjs-additional-layers = Camadas adicionais
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Página { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Miniatura da página { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Localizar
.placeholder = Localizar em documento…
pdfjs-find-previous-button =
.title = Localizar ocorrência anterior da frase
pdfjs-find-previous-button-label = Anterior
pdfjs-find-next-button =
.title = Localizar ocorrência seguinte da frase
pdfjs-find-next-button-label = Seguinte
pdfjs-find-highlight-checkbox = Destacar tudo
pdfjs-find-match-case-checkbox-label = Correspondência
pdfjs-find-match-diacritics-checkbox-label = Corresponder diacríticos
pdfjs-find-entire-word-checkbox-label = Palavras completas
pdfjs-find-reached-top = Topo do documento atingido, a continuar a partir do fundo
pdfjs-find-reached-bottom = Fim do documento atingido, a continuar a partir do topo
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } de { $total } correspondência
*[other] { $current } de { $total } correspondências
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Mais de { $limit } correspondência
*[other] Mais de { $limit } correspondências
}
pdfjs-find-not-found = Frase não encontrada
## Predefined zoom values
pdfjs-page-scale-width = Ajustar à largura
pdfjs-page-scale-fit = Ajustar à página
pdfjs-page-scale-auto = Zoom automático
pdfjs-page-scale-actual = Tamanho real
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Página { $page }
## Loading indicator messages
pdfjs-loading-error = Ocorreu um erro ao carregar o PDF.
pdfjs-invalid-file-error = Ficheiro PDF inválido ou danificado.
pdfjs-missing-file-error = Ficheiro PDF inexistente.
pdfjs-unexpected-response-error = Resposta inesperada do servidor.
pdfjs-rendering-error = Ocorreu um erro ao processar a página.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [Anotação { $type }]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Introduza a palavra-passe para abrir este ficheiro PDF.
pdfjs-password-invalid = Palavra-passe inválida. Por favor, tente novamente.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Cancelar
pdfjs-web-fonts-disabled = Os tipos de letra web estão desativados: não é possível utilizar os tipos de letra PDF embutidos.
## Editing
pdfjs-editor-free-text-button =
.title = Texto
pdfjs-editor-free-text-button-label = Texto
pdfjs-editor-ink-button =
.title = Desenhar
pdfjs-editor-ink-button-label = Desenhar
pdfjs-editor-stamp-button =
.title = Adicionar ou editar imagens
pdfjs-editor-stamp-button-label = Adicionar ou editar imagens
pdfjs-editor-highlight-button =
.title = Destaque
pdfjs-editor-highlight-button-label = Destaque
pdfjs-highlight-floating-button1 =
.title = Realçar
.aria-label = Realçar
pdfjs-highlight-floating-button-label = Realçar
pdfjs-editor-signature-button =
.title = Adicionar assinatura
pdfjs-editor-signature-button-label = Adicionar assinatura
## Default editor aria labels
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Remover desenho
pdfjs-editor-remove-freetext-button =
.title = Remover texto
pdfjs-editor-remove-stamp-button =
.title = Remover imagem
pdfjs-editor-remove-highlight-button =
.title = Remover destaque
pdfjs-editor-remove-signature-button =
.title = Remover assinatura
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Cor
pdfjs-editor-free-text-size-input = Tamanho
pdfjs-editor-ink-color-input = Cor
pdfjs-editor-ink-thickness-input = Espessura
pdfjs-editor-ink-opacity-input = Opacidade
pdfjs-editor-stamp-add-image-button =
.title = Adicionar imagem
pdfjs-editor-stamp-add-image-button-label = Adicionar imagem
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Espessura
pdfjs-editor-free-highlight-thickness-title =
.title = Alterar espessura quando destacar itens que não sejam texto
pdfjs-editor-signature-add-signature-button =
.title = Adicionar nova assinatura
pdfjs-editor-signature-add-signature-button-label = Adicionar nova assinatura
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Editor de texto
.default-content = Comece a escrever…
pdfjs-free-text =
.aria-label = Editor de texto
pdfjs-free-text-default-content = Começar a digitar…
pdfjs-ink =
.aria-label = Editor de desenho
pdfjs-ink-canvas =
.aria-label = Imagem criada pelo utilizador
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Texto alternativo
pdfjs-editor-alt-text-edit-button =
.aria-label = Editar texto alternativo
pdfjs-editor-alt-text-edit-button-label = Editar texto alternativo
pdfjs-editor-alt-text-dialog-label = Escolher uma opção
pdfjs-editor-alt-text-dialog-description = O texto alternativo (texto alternativo) ajuda quando as pessoas não conseguem ver a imagem ou quando a mesma não é carregada.
pdfjs-editor-alt-text-add-description-label = Adicionar uma descrição
pdfjs-editor-alt-text-add-description-description = Aponte para 1-2 frases que descrevam o assunto, definição ou ações.
pdfjs-editor-alt-text-mark-decorative-label = Marcar como decorativa
pdfjs-editor-alt-text-mark-decorative-description = Isto é utilizado para imagens decorativas, tais como limites ou marcas d'água.
pdfjs-editor-alt-text-cancel-button = Cancelar
pdfjs-editor-alt-text-save-button = Guardar
pdfjs-editor-alt-text-decorative-tooltip = Marcada como decorativa
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Por exemplo, “Um jovem senta-se à mesa para comer uma refeição”
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Texto alternativo
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Canto superior esquerdo — redimensionar
pdfjs-editor-resizer-label-top-middle = Superior ao centro — redimensionar
pdfjs-editor-resizer-label-top-right = Canto superior direito — redimensionar
pdfjs-editor-resizer-label-middle-right = Centro à direita — redimensionar
pdfjs-editor-resizer-label-bottom-right = Canto inferior direito — redimensionar
pdfjs-editor-resizer-label-bottom-middle = Inferior ao centro — redimensionar
pdfjs-editor-resizer-label-bottom-left = Canto inferior esquerdo — redimensionar
pdfjs-editor-resizer-label-middle-left = Centro à esquerda — redimensionar
pdfjs-editor-resizer-top-left =
.aria-label = Canto superior esquerdo — redimensionar
pdfjs-editor-resizer-top-middle =
.aria-label = Superior ao centro — redimensionar
pdfjs-editor-resizer-top-right =
.aria-label = Canto superior direito — redimensionar
pdfjs-editor-resizer-middle-right =
.aria-label = Centro à direita — redimensionar
pdfjs-editor-resizer-bottom-right =
.aria-label = Canto inferior direito — redimensionar
pdfjs-editor-resizer-bottom-middle =
.aria-label = Inferior ao centro — redimensionar
pdfjs-editor-resizer-bottom-left =
.aria-label = Canto inferior esquerdo — redimensionar
pdfjs-editor-resizer-middle-left =
.aria-label = Centro à esquerda — redimensionar
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Cor de destaque
pdfjs-editor-colorpicker-button =
.title = Alterar cor
pdfjs-editor-colorpicker-dropdown =
.aria-label = Escolhas de cor
pdfjs-editor-colorpicker-yellow =
.title = Amarelo
pdfjs-editor-colorpicker-green =
.title = Verde
pdfjs-editor-colorpicker-blue =
.title = Azul
pdfjs-editor-colorpicker-pink =
.title = Rosa
pdfjs-editor-colorpicker-red =
.title = Vermelho
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Mostrar tudo
pdfjs-editor-highlight-show-all-button =
.title = Mostrar tudo
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Editar texto alternativo (descrição da imagem)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Adicionar texto alternativo (descrição da imagem)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Escreva a sua descrição aqui…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Descrição curta para as pessoas que não podem visualizar a imagem ou quando a imagem não carrega.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Este texto alternativo foi criado automaticamente e pode ser impreciso.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Saber mais
pdfjs-editor-new-alt-text-create-automatically-button-label = Criar texto alternativo automaticamente
pdfjs-editor-new-alt-text-not-now-button = Agora não
pdfjs-editor-new-alt-text-error-title = Não foi possível criar o texto alternativo automaticamente
pdfjs-editor-new-alt-text-error-description = Escreva o seu próprio texto alternativo ou tente novamente mais tarde.
pdfjs-editor-new-alt-text-error-close-button = Fechar
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = A transferir o modelo de IA de texto alternativo ({ $downloadedSize } de { $totalSize } MB)
.aria-valuetext = A transferir o modelo de IA de texto alternativo ({ $downloadedSize } de { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Texto alternativo adicionado
pdfjs-editor-new-alt-text-added-button-label = Texto alternativo adicionado
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Texto alternativo em falta
pdfjs-editor-new-alt-text-missing-button-label = Texto alternativo em falta
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Rever texto alternativo
pdfjs-editor-new-alt-text-to-review-button-label = Rever texto alternativo
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Criado automaticamente: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Definições de texto alternativo da imagem
pdfjs-image-alt-text-settings-button-label = Definições de texto alternativo da imagem
pdfjs-editor-alt-text-settings-dialog-label = Definições de texto alternativo das imagens
pdfjs-editor-alt-text-settings-automatic-title = Texto alternativo automático
pdfjs-editor-alt-text-settings-create-model-button-label = Criar texto alternativo automaticamente
pdfjs-editor-alt-text-settings-create-model-description = Sugere descrições para ajudar as pessoas que não podem visualizar a imagem ou quando a imagem não carrega.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Modelo de IA de texto alternativo ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = É executado localmente no seu dispositivo para que os seus dados se mantenham privados. É necessário para o texto alternativo automático.
pdfjs-editor-alt-text-settings-delete-model-button = Eliminar
pdfjs-editor-alt-text-settings-download-model-button = Transferir
pdfjs-editor-alt-text-settings-downloading-model-button = A transferir…
pdfjs-editor-alt-text-settings-editor-title = Editor de texto alternativo
pdfjs-editor-alt-text-settings-show-dialog-button-label = Mostrar editor de texto alternativo imediatamente ao adicionar uma imagem
pdfjs-editor-alt-text-settings-show-dialog-description = Ajuda a garantir que todas as suas imagens tenham um texto alternativo.
pdfjs-editor-alt-text-settings-close-button = Fechar
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Destaque removido
pdfjs-editor-undo-bar-message-freetext = Texto removido
pdfjs-editor-undo-bar-message-ink = Desenho removido
pdfjs-editor-undo-bar-message-stamp = Imagem removida
pdfjs-editor-undo-bar-message-signature = Assinatura removida
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } anotação removida
*[other] { $count } anotações removidas
}
pdfjs-editor-undo-bar-undo-button =
.title = Anular
pdfjs-editor-undo-bar-undo-button-label = Anular
pdfjs-editor-undo-bar-close-button =
.title = Fechar
pdfjs-editor-undo-bar-close-button-label = Fechar
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = Este modo permite ao utilizador criar uma assinatura para adicionar a um documento PDF. O utilizador pode editar o nome (que também funciona como texto alternativo) e, opcionalmente, guardar a assinatura para utilizações frequentes.
pdfjs-editor-add-signature-dialog-title = Adicionar uma assinatura
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Digitar
.title = Digitar
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Desenhar
.title = Desenhar
pdfjs-editor-add-signature-image-button = Imagem
.title = Imagem
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Digite a sua assinatura
.placeholder = Digite a sua assinatura
pdfjs-editor-add-signature-draw-placeholder = Desenhe a sua assinatura
pdfjs-editor-add-signature-draw-thickness-range-label = Espessura
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Espessura do desenho: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Arraste um ficheiro aqui para carregar
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Ou escolha ficheiros de imagem
*[other] Ou explore ficheiros de imagem
}
## Controls
pdfjs-editor-add-signature-description-label = Descrição (texto alternativo)
pdfjs-editor-add-signature-description-input =
.title = Descrição (texto alternativo)
pdfjs-editor-add-signature-description-default-when-drawing = Assinatura
pdfjs-editor-add-signature-clear-button-label = Limpar assinatura
pdfjs-editor-add-signature-clear-button =
.title = Limpar assinatura
pdfjs-editor-add-signature-save-checkbox = Guardar assinatura
pdfjs-editor-add-signature-save-warning-message = Atingiu o limite de 5 assinaturas guardadas. Remova uma para guardar mais.
pdfjs-editor-add-signature-image-upload-error-title = Não foi possível carregar a imagem
pdfjs-editor-add-signature-image-upload-error-description = Verifique a sua ligação à rede ou tente outra imagem.
pdfjs-editor-add-signature-error-close-button = Fechar
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Cancelar
pdfjs-editor-add-signature-add-button = Adicionar
pdfjs-editor-edit-signature-update-button = Atualizar
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Remover assinatura
pdfjs-editor-delete-signature-button-label = Remover assinatura
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Editar descrição
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Editar descrição
================================================
FILE: cookbook/static/pdfjs/web/locale/rm/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Pagina precedenta
pdfjs-previous-button-label = Enavos
pdfjs-next-button =
.title = Proxima pagina
pdfjs-next-button-label = Enavant
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Pagina
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = da { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } da { $pagesCount })
pdfjs-zoom-out-button =
.title = Empitschnir
pdfjs-zoom-out-button-label = Empitschnir
pdfjs-zoom-in-button =
.title = Engrondir
pdfjs-zoom-in-button-label = Engrondir
pdfjs-zoom-select =
.title = Zoom
pdfjs-presentation-mode-button =
.title = Midar en il modus da preschentaziun
pdfjs-presentation-mode-button-label = Modus da preschentaziun
pdfjs-open-file-button =
.title = Avrir datoteca
pdfjs-open-file-button-label = Avrir
pdfjs-print-button =
.title = Stampar
pdfjs-print-button-label = Stampar
pdfjs-save-button =
.title = Memorisar
pdfjs-save-button-label = Memorisar
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Telechargiar
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Telechargiar
pdfjs-bookmark-button =
.title = Pagina actuala (mussar l'URL da la pagina actuala)
pdfjs-bookmark-button-label = Pagina actuala
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Utensils
pdfjs-tools-button-label = Utensils
pdfjs-first-page-button =
.title = Siglir a l'emprima pagina
pdfjs-first-page-button-label = Siglir a l'emprima pagina
pdfjs-last-page-button =
.title = Siglir a la davosa pagina
pdfjs-last-page-button-label = Siglir a la davosa pagina
pdfjs-page-rotate-cw-button =
.title = Rotar en direcziun da l'ura
pdfjs-page-rotate-cw-button-label = Rotar en direcziun da l'ura
pdfjs-page-rotate-ccw-button =
.title = Rotar en direcziun cuntraria a l'ura
pdfjs-page-rotate-ccw-button-label = Rotar en direcziun cuntraria a l'ura
pdfjs-cursor-text-select-tool-button =
.title = Activar l'utensil per selecziunar text
pdfjs-cursor-text-select-tool-button-label = Utensil per selecziunar text
pdfjs-cursor-hand-tool-button =
.title = Activar l'utensil da maun
pdfjs-cursor-hand-tool-button-label = Utensil da maun
pdfjs-scroll-page-button =
.title = Utilisar la defilada per pagina
pdfjs-scroll-page-button-label = Defilada per pagina
pdfjs-scroll-vertical-button =
.title = Utilisar il defilar vertical
pdfjs-scroll-vertical-button-label = Defilar vertical
pdfjs-scroll-horizontal-button =
.title = Utilisar il defilar orizontal
pdfjs-scroll-horizontal-button-label = Defilar orizontal
pdfjs-scroll-wrapped-button =
.title = Utilisar il defilar en colonnas
pdfjs-scroll-wrapped-button-label = Defilar en colonnas
pdfjs-spread-none-button =
.title = Betg parallelisar las paginas
pdfjs-spread-none-button-label = Betg parallel
pdfjs-spread-odd-button =
.title = Parallelisar las paginas cun cumenzar cun paginas spèras
pdfjs-spread-odd-button-label = Parallel spèr
pdfjs-spread-even-button =
.title = Parallelisar las paginas cun cumenzar cun paginas pèras
pdfjs-spread-even-button-label = Parallel pèr
## Document properties dialog
pdfjs-document-properties-button =
.title = Caracteristicas dal document…
pdfjs-document-properties-button-label = Caracteristicas dal document…
pdfjs-document-properties-file-name = Num da la datoteca:
pdfjs-document-properties-file-size = Grondezza da la datoteca:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = Titel:
pdfjs-document-properties-author = Autur:
pdfjs-document-properties-subject = Tema:
pdfjs-document-properties-keywords = Chavazzins:
pdfjs-document-properties-creation-date = Data da creaziun:
pdfjs-document-properties-modification-date = Data da modificaziun:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date } { $time }
pdfjs-document-properties-creator = Creà da:
pdfjs-document-properties-producer = Creà il PDF cun:
pdfjs-document-properties-version = Versiun da PDF:
pdfjs-document-properties-page-count = Dumber da paginas:
pdfjs-document-properties-page-size = Grondezza da la pagina:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = vertical
pdfjs-document-properties-page-size-orientation-landscape = orizontal
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letter
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Fast Web View:
pdfjs-document-properties-linearized-yes = Gea
pdfjs-document-properties-linearized-no = Na
pdfjs-document-properties-close-button = Serrar
## Print
pdfjs-print-progress-message = Preparar il document per stampar…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Interrumper
pdfjs-printing-not-supported = Attenziun: Il stampar na funcziunescha anc betg dal tut en quest navigatur.
pdfjs-printing-not-ready = Attenziun: Il PDF n'è betg chargià cumplettamain per stampar.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Activar/deactivar la trav laterala
pdfjs-toggle-sidebar-notification-button =
.title = Activar/deactivar la trav laterala (il document cuntegna structura dal document/agiuntas/nivels)
pdfjs-toggle-sidebar-button-label = Activar/deactivar la trav laterala
pdfjs-document-outline-button =
.title = Mussar la structura dal document (cliccar duas giadas per extender/cumprimer tut ils elements)
pdfjs-document-outline-button-label = Structura dal document
pdfjs-attachments-button =
.title = Mussar agiuntas
pdfjs-attachments-button-label = Agiuntas
pdfjs-layers-button =
.title = Mussar ils nivels (cliccar dubel per restaurar il stadi da standard da tut ils nivels)
pdfjs-layers-button-label = Nivels
pdfjs-thumbs-button =
.title = Mussar las miniaturas
pdfjs-thumbs-button-label = Miniaturas
pdfjs-current-outline-item-button =
.title = Tschertgar l'element da structura actual
pdfjs-current-outline-item-button-label = Element da structura actual
pdfjs-findbar-button =
.title = Tschertgar en il document
pdfjs-findbar-button-label = Tschertgar
pdfjs-additional-layers = Nivels supplementars
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Pagina { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Miniatura da la pagina { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Tschertgar
.placeholder = Tschertgar en il document…
pdfjs-find-previous-button =
.title = Tschertgar la posiziun precedenta da l'expressiun
pdfjs-find-previous-button-label = Enavos
pdfjs-find-next-button =
.title = Tschertgar la proxima posiziun da l'expressiun
pdfjs-find-next-button-label = Enavant
pdfjs-find-highlight-checkbox = Relevar tuts
pdfjs-find-match-case-checkbox-label = Resguardar maiusclas/minusclas
pdfjs-find-match-diacritics-checkbox-label = Resguardar ils segns diacritics
pdfjs-find-entire-word-checkbox-label = Pleds entirs
pdfjs-find-reached-top = Il cumenzament dal document è cuntanschì, la tschertga cuntinuescha a la fin dal document
pdfjs-find-reached-bottom = La fin dal document è cuntanschì, la tschertga cuntinuescha al cumenzament dal document
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } dad { $total } correspundenza
*[other] { $current } da { $total } correspundenzas
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Dapli che { $limit } correspundenza
*[other] Dapli che { $limit } correspundenzas
}
pdfjs-find-not-found = Impussibel da chattar l'expressiun
## Predefined zoom values
pdfjs-page-scale-width = Ladezza da la pagina
pdfjs-page-scale-fit = Entira pagina
pdfjs-page-scale-auto = Zoom automatic
pdfjs-page-scale-actual = Grondezza actuala
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Pagina { $page }
## Loading indicator messages
pdfjs-loading-error = Ina errur è cumparida cun chargiar il PDF.
pdfjs-invalid-file-error = Datoteca PDF nunvalida u donnegiada.
pdfjs-missing-file-error = Datoteca PDF manconta.
pdfjs-unexpected-response-error = Resposta nunspetgada dal server.
pdfjs-rendering-error = Ina errur è cumparida cun visualisar questa pagina.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [Annotaziun da { $type }]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Endatescha il pled-clav per avrir questa datoteca da PDF.
pdfjs-password-invalid = Pled-clav nunvalid. Emprova anc ina giada.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Interrumper
pdfjs-web-fonts-disabled = Scrittiras dal web èn deactivadas: impussibel dad utilisar las scrittiras integradas en il PDF.
## Editing
pdfjs-editor-free-text-button =
.title = Text
pdfjs-editor-free-text-button-label = Text
pdfjs-editor-ink-button =
.title = Dissegnar
pdfjs-editor-ink-button-label = Dissegnar
pdfjs-editor-stamp-button =
.title = Agiuntar u modifitgar maletgs
pdfjs-editor-stamp-button-label = Agiuntar u modifitgar maletgs
pdfjs-editor-highlight-button =
.title = Marcar
pdfjs-editor-highlight-button-label = Marcar
pdfjs-highlight-floating-button1 =
.title = Marcar
.aria-label = Marcar
pdfjs-highlight-floating-button-label = Marcar
pdfjs-editor-signature-button =
.title = Agiuntar ina signatura
pdfjs-editor-signature-button-label = Agiuntar ina signatura
## Default editor aria labels
# “Highlight” is a noun, the string is used on the editor for highlights.
pdfjs-editor-highlight-editor =
.aria-label = Editur per relevar
# “Drawing” is a noun, the string is used on the editor for drawings.
pdfjs-editor-ink-editor =
.aria-label = Editur per dissegnar
pdfjs-editor-signature-editor =
.aria-label = Editur per signaturas
pdfjs-editor-stamp-editor =
.aria-label = Editur per maletgs
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Allontanar il dissegn
pdfjs-editor-remove-freetext-button =
.title = Allontanar il text
pdfjs-editor-remove-stamp-button =
.title = Allontanar la grafica
pdfjs-editor-remove-highlight-button =
.title = Allontanar l'emfasa
pdfjs-editor-remove-signature-button =
.title = Allontanar la signatura
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Colur
pdfjs-editor-free-text-size-input = Grondezza
pdfjs-editor-ink-color-input = Colur
pdfjs-editor-ink-thickness-input = Grossezza
pdfjs-editor-ink-opacity-input = Opacitad
pdfjs-editor-stamp-add-image-button =
.title = Agiuntar in maletg
pdfjs-editor-stamp-add-image-button-label = Agiuntar in maletg
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Grossezza
pdfjs-editor-free-highlight-thickness-title =
.title = Midar la grossezza cun relevar elements betg textuals
pdfjs-editor-add-signature-container =
.aria-label = Controllas da signatura e signaturas memorisadas
pdfjs-editor-signature-add-signature-button =
.title = Agiuntar ina nova signatura
pdfjs-editor-signature-add-signature-button-label = Agiuntar ina nova signatura
# Used on the button to use an already saved signature.
# Variables:
# $description (String) - a string describing/labeling the signature.
pdfjs-editor-add-saved-signature-button =
.title = Signatura memorisada: { $description }
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Editur da text
.default-content = Cumenza a tippar…
pdfjs-free-text =
.aria-label = Editur da text
pdfjs-free-text-default-content = Cumenzar a tippar…
pdfjs-ink =
.aria-label = Editur dissegn
pdfjs-ink-canvas =
.aria-label = Maletg creà da l'utilisader
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Text alternativ
pdfjs-editor-alt-text-edit-button =
.aria-label = Modifitgar il text alternativ
pdfjs-editor-alt-text-edit-button-label = Modifitgar il text alternativ
pdfjs-editor-alt-text-dialog-label = Tscherner ina opziun
pdfjs-editor-alt-text-dialog-description = Il text alternativ (alt text) gida en cas che persunas na vesan betg il maletg u sch'i na reussescha betg d'al chargiar.
pdfjs-editor-alt-text-add-description-label = Agiuntar ina descripziun
pdfjs-editor-alt-text-add-description-description = Scriva idealmain 1-2 frasas che descrivan l'object, la situaziun u las acziuns.
pdfjs-editor-alt-text-mark-decorative-label = Marcar sco decorativ
pdfjs-editor-alt-text-mark-decorative-description = Quai vegn duvrà per maletgs ornamentals, sco urs u filigranas.
pdfjs-editor-alt-text-cancel-button = Interrumper
pdfjs-editor-alt-text-save-button = Memorisar
pdfjs-editor-alt-text-decorative-tooltip = Marcà sco decorativ
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Per exempel: «In um giuven sesa a maisa per mangiar in past»
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Text alternativ
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Chantun sura a sanestra — redimensiunar
pdfjs-editor-resizer-label-top-middle = Sura amez — redimensiunar
pdfjs-editor-resizer-label-top-right = Chantun sura a dretga — redimensiunar
pdfjs-editor-resizer-label-middle-right = Da vart dretga amez — redimensiunar
pdfjs-editor-resizer-label-bottom-right = Chantun sut a dretga — redimensiunar
pdfjs-editor-resizer-label-bottom-middle = Sutvart amez — redimensiunar
pdfjs-editor-resizer-label-bottom-left = Chantun sut a sanestra — redimensiunar
pdfjs-editor-resizer-label-middle-left = Vart sanestra amez — redimensiunar
pdfjs-editor-resizer-top-left =
.aria-label = Chantun sura a sanestra — redimensiunar
pdfjs-editor-resizer-top-middle =
.aria-label = Sura amez — redimensiunar
pdfjs-editor-resizer-top-right =
.aria-label = Chantun sura a dretga — redimensiunar
pdfjs-editor-resizer-middle-right =
.aria-label = Da vart dretga amez — redimensiunar
pdfjs-editor-resizer-bottom-right =
.aria-label = Chantun sut a dretga — redimensiunar
pdfjs-editor-resizer-bottom-middle =
.aria-label = Sutvart amez — redimensiunar
pdfjs-editor-resizer-bottom-left =
.aria-label = Chantun sut a sanestra — redimensiunar
pdfjs-editor-resizer-middle-left =
.aria-label = Vart sanestra amez — redimensiunar
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Colur per l'emfasa
pdfjs-editor-colorpicker-button =
.title = Midar la colur
pdfjs-editor-colorpicker-dropdown =
.aria-label = Colurs disponiblas
pdfjs-editor-colorpicker-yellow =
.title = Mellen
pdfjs-editor-colorpicker-green =
.title = Verd
pdfjs-editor-colorpicker-blue =
.title = Blau
pdfjs-editor-colorpicker-pink =
.title = Rosa
pdfjs-editor-colorpicker-red =
.title = Cotschen
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Mussar tut
pdfjs-editor-highlight-show-all-button =
.title = Mussar tut
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Modifitgar il text alternativ (descripziun dal maletg)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Agiuntar in text alternativ (descripziun dal maletg)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Scriva qua tia descripziun…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Curta descripziun per persunas che na vesan betg il maletg u per cass en ils quals il maletg na vegn betg chargià.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Quest text alternativ è vegnì creà automaticamain ed è eventualmain nunprecis.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Ulteriuras infurmaziuns
pdfjs-editor-new-alt-text-create-automatically-button-label = Crear automaticamain il text alternativ
pdfjs-editor-new-alt-text-not-now-button = Betg ussa
pdfjs-editor-new-alt-text-error-title = I n’è betg reussì da crear automaticamain il text alternativ
pdfjs-editor-new-alt-text-error-description = Scriva per plaschair tes agen text alternativ u emprova pli tard anc ina giada.
pdfjs-editor-new-alt-text-error-close-button = Serrar
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Telechargiar il model IA da text alternativ ({ $downloadedSize } da { $totalSize } MB)
.aria-valuetext = Telechargiar il model IA da text alternativ ({ $downloadedSize } da { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Agiuntà text alternativ
pdfjs-editor-new-alt-text-added-button-label = Text alternativ agiuntà
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Text alternativ manca
pdfjs-editor-new-alt-text-missing-button-label = Text alternativ manca
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Repassar il text alternativ
pdfjs-editor-new-alt-text-to-review-button-label = Repassar il text alternativ
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Creà automaticamain: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Parameters dal text alternativ da maletgs
pdfjs-image-alt-text-settings-button-label = Parameters dal text alternativ da maletgs
pdfjs-editor-alt-text-settings-dialog-label = Parameters dal text alternativ da maletgs
pdfjs-editor-alt-text-settings-automatic-title = Text alternativ automatic
pdfjs-editor-alt-text-settings-create-model-button-label = Crear automaticamain text alternativ
pdfjs-editor-alt-text-settings-create-model-description = Propona descripziuns per gidar a persunas che na vesan betg il maletg u per cass en ils quals il maletg na vegn betg chargià.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Model IA da text alternativ ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Vegn exequì localmain sin tes apparat per che tias datas restian privatas. Necessari per text alternativ automatic.
pdfjs-editor-alt-text-settings-delete-model-button = Stizzar
pdfjs-editor-alt-text-settings-download-model-button = Telechargiar
pdfjs-editor-alt-text-settings-downloading-model-button = Telechargiar…
pdfjs-editor-alt-text-settings-editor-title = Editur per text alternativ
pdfjs-editor-alt-text-settings-show-dialog-button-label = Mussar l’editur per text alternativ directamain cun agiuntar in maletg
pdfjs-editor-alt-text-settings-show-dialog-description = Ta gida a garantir che tut tes maletgs hajan in text alternativ.
pdfjs-editor-alt-text-settings-close-button = Serrar
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Allontanà la marcaziun
pdfjs-editor-undo-bar-message-freetext = Allontanà il text
pdfjs-editor-undo-bar-message-ink = Allontanà il dissegn
pdfjs-editor-undo-bar-message-stamp = Allontanà il maletg
pdfjs-editor-undo-bar-message-signature = Allontanà la signatura
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } annotaziun allontanada
*[other] { $count } annotaziuns allontanadas
}
pdfjs-editor-undo-bar-undo-button =
.title = Revocar
pdfjs-editor-undo-bar-undo-button-label = Revocar
pdfjs-editor-undo-bar-close-button =
.title = Serrar
pdfjs-editor-undo-bar-close-button-label = Serrar
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = Questa fanestra permetta a l’utilisader da crear ina signatura per l’agiuntar ad in document PDF. L’utilisader po modifitgar il num (che serva era sco text alternativ) e memorisar opziunalmain la signatura per l’utilisar anc ina giada en l’avegnir.
pdfjs-editor-add-signature-dialog-title = Agiuntar ina signatura
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Tippar
.title = Tippar
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Dissegnar
.title = Dissegnar
pdfjs-editor-add-signature-image-button = Maletg
.title = Maletg
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Tippa tia signatura
.placeholder = Tippa tia signatura
pdfjs-editor-add-signature-draw-placeholder = Dissegna tia signatura
pdfjs-editor-add-signature-draw-thickness-range-label = Grossezza
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Grossezza dal stritg: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Trair na qua ina datoteca per la transferir
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] U tscherner datotecas da maletg
*[other] U tscherner datotecas da maletg
}
## Controls
pdfjs-editor-add-signature-description-label = Descripziun (text alternativ)
pdfjs-editor-add-signature-description-input =
.title = Descripziun (text alternativ)
pdfjs-editor-add-signature-description-default-when-drawing = Signatura
pdfjs-editor-add-signature-clear-button-label = Stizzar la signatura
pdfjs-editor-add-signature-clear-button =
.title = Stizzar la signatura
pdfjs-editor-add-signature-save-checkbox = Memorisar la signatura
pdfjs-editor-add-signature-save-warning-message = Ti has cuntanschì il dumber maximal da 5 signaturas memorisadas. Allontanar ina per memorisar in’autra.
pdfjs-editor-add-signature-image-upload-error-title = Impussibel da transferir il maletg
pdfjs-editor-add-signature-image-upload-error-description = Controllescha tia connexiun cun la rait u emprova cun in’auter maletg.
pdfjs-editor-add-signature-error-close-button = Serrar
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Interrumper
pdfjs-editor-add-signature-add-button = Agiuntar
pdfjs-editor-edit-signature-update-button = Actualisar
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Allontanar la signatura
pdfjs-editor-delete-signature-button-label = Allontanar la signatura
pdfjs-editor-delete-signature-button1 =
.title = Allontanar la signatura memorisada
pdfjs-editor-delete-signature-button-label1 = Allontanar la signatura memorisada
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Modifitgar la descripziun
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Modifitgar la descripziun
================================================
FILE: cookbook/static/pdfjs/web/locale/ro/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Pagina precedentă
pdfjs-previous-button-label = Înapoi
pdfjs-next-button =
.title = Pagina următoare
pdfjs-next-button-label = Înainte
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Pagina
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = din { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } din { $pagesCount })
pdfjs-zoom-out-button =
.title = Micșorează
pdfjs-zoom-out-button-label = Micșorează
pdfjs-zoom-in-button =
.title = Mărește
pdfjs-zoom-in-button-label = Mărește
pdfjs-zoom-select =
.title = Zoom
pdfjs-presentation-mode-button =
.title = Comută la modul de prezentare
pdfjs-presentation-mode-button-label = Mod de prezentare
pdfjs-open-file-button =
.title = Deschide un fișier
pdfjs-open-file-button-label = Deschide
pdfjs-print-button =
.title = Tipărește
pdfjs-print-button-label = Tipărește
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Instrumente
pdfjs-tools-button-label = Instrumente
pdfjs-first-page-button =
.title = Mergi la prima pagină
pdfjs-first-page-button-label = Mergi la prima pagină
pdfjs-last-page-button =
.title = Mergi la ultima pagină
pdfjs-last-page-button-label = Mergi la ultima pagină
pdfjs-page-rotate-cw-button =
.title = Rotește în sensul acelor de ceas
pdfjs-page-rotate-cw-button-label = Rotește în sensul acelor de ceas
pdfjs-page-rotate-ccw-button =
.title = Rotește în sens invers al acelor de ceas
pdfjs-page-rotate-ccw-button-label = Rotește în sens invers al acelor de ceas
pdfjs-cursor-text-select-tool-button =
.title = Activează instrumentul de selecție a textului
pdfjs-cursor-text-select-tool-button-label = Instrumentul de selecție a textului
pdfjs-cursor-hand-tool-button =
.title = Activează instrumentul mână
pdfjs-cursor-hand-tool-button-label = Unealta mână
pdfjs-scroll-vertical-button =
.title = Folosește derularea verticală
pdfjs-scroll-vertical-button-label = Derulare verticală
pdfjs-scroll-horizontal-button =
.title = Folosește derularea orizontală
pdfjs-scroll-horizontal-button-label = Derulare orizontală
pdfjs-scroll-wrapped-button =
.title = Folosește derularea încadrată
pdfjs-scroll-wrapped-button-label = Derulare încadrată
pdfjs-spread-none-button =
.title = Nu uni paginile broșate
pdfjs-spread-none-button-label = Fără pagini broșate
pdfjs-spread-odd-button =
.title = Unește paginile broșate începând cu cele impare
pdfjs-spread-odd-button-label = Broșare pagini impare
pdfjs-spread-even-button =
.title = Unește paginile broșate începând cu cele pare
pdfjs-spread-even-button-label = Broșare pagini pare
## Document properties dialog
pdfjs-document-properties-button =
.title = Proprietățile documentului…
pdfjs-document-properties-button-label = Proprietățile documentului…
pdfjs-document-properties-file-name = Numele fișierului:
pdfjs-document-properties-file-size = Mărimea fișierului:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } byți)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } byți)
pdfjs-document-properties-title = Titlu:
pdfjs-document-properties-author = Autor:
pdfjs-document-properties-subject = Subiect:
pdfjs-document-properties-keywords = Cuvinte cheie:
pdfjs-document-properties-creation-date = Data creării:
pdfjs-document-properties-modification-date = Data modificării:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Autor:
pdfjs-document-properties-producer = Producător PDF:
pdfjs-document-properties-version = Versiune PDF:
pdfjs-document-properties-page-count = Număr de pagini:
pdfjs-document-properties-page-size = Mărimea paginii:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = verticală
pdfjs-document-properties-page-size-orientation-landscape = orizontală
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Literă
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Vizualizare web rapidă:
pdfjs-document-properties-linearized-yes = Da
pdfjs-document-properties-linearized-no = Nu
pdfjs-document-properties-close-button = Închide
## Print
pdfjs-print-progress-message = Se pregătește documentul pentru tipărire…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Renunță
pdfjs-printing-not-supported = Avertisment: Tipărirea nu este suportată în totalitate de acest browser.
pdfjs-printing-not-ready = Avertisment: PDF-ul nu este încărcat complet pentru tipărire.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Comută bara laterală
pdfjs-toggle-sidebar-button-label = Comută bara laterală
pdfjs-document-outline-button =
.title = Afișează schița documentului (dublu-clic pentru a extinde/restrânge toate elementele)
pdfjs-document-outline-button-label = Schița documentului
pdfjs-attachments-button =
.title = Afișează atașamentele
pdfjs-attachments-button-label = Atașamente
pdfjs-thumbs-button =
.title = Afișează miniaturi
pdfjs-thumbs-button-label = Miniaturi
pdfjs-findbar-button =
.title = Caută în document
pdfjs-findbar-button-label = Caută
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Pagina { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Miniatura paginii { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Caută
.placeholder = Caută în document…
pdfjs-find-previous-button =
.title = Mergi la apariția anterioară a textului
pdfjs-find-previous-button-label = Înapoi
pdfjs-find-next-button =
.title = Mergi la apariția următoare a textului
pdfjs-find-next-button-label = Înainte
pdfjs-find-highlight-checkbox = Evidențiază toate aparițiile
pdfjs-find-match-case-checkbox-label = Ține cont de majuscule și minuscule
pdfjs-find-entire-word-checkbox-label = Cuvinte întregi
pdfjs-find-reached-top = Am ajuns la începutul documentului, continuă de la sfârșit
pdfjs-find-reached-bottom = Am ajuns la sfârșitul documentului, continuă de la început
pdfjs-find-not-found = Nu s-a găsit textul
## Predefined zoom values
pdfjs-page-scale-width = Lățime pagină
pdfjs-page-scale-fit = Potrivire la pagină
pdfjs-page-scale-auto = Zoom automat
pdfjs-page-scale-actual = Mărime reală
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
## Loading indicator messages
pdfjs-loading-error = A intervenit o eroare la încărcarea PDF-ului.
pdfjs-invalid-file-error = Fișier PDF nevalid sau corupt.
pdfjs-missing-file-error = Fișier PDF lipsă.
pdfjs-unexpected-response-error = Răspuns neașteptat de la server.
pdfjs-rendering-error = A intervenit o eroare la randarea paginii.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [Adnotare { $type }]
## Password
pdfjs-password-label = Introdu parola pentru a deschide acest fișier PDF.
pdfjs-password-invalid = Parolă nevalidă. Te rugăm să încerci din nou.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Renunță
pdfjs-web-fonts-disabled = Fonturile web sunt dezactivate: nu se pot folosi fonturile PDF încorporate.
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/ru/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Предыдущая страница
pdfjs-previous-button-label = Предыдущая
pdfjs-next-button =
.title = Следующая страница
pdfjs-next-button-label = Следующая
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Страница
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = из { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } из { $pagesCount })
pdfjs-zoom-out-button =
.title = Уменьшить
pdfjs-zoom-out-button-label = Уменьшить
pdfjs-zoom-in-button =
.title = Увеличить
pdfjs-zoom-in-button-label = Увеличить
pdfjs-zoom-select =
.title = Масштаб
pdfjs-presentation-mode-button =
.title = Перейти в режим презентации
pdfjs-presentation-mode-button-label = Режим презентации
pdfjs-open-file-button =
.title = Открыть файл
pdfjs-open-file-button-label = Открыть
pdfjs-print-button =
.title = Печать
pdfjs-print-button-label = Печать
pdfjs-save-button =
.title = Сохранить
pdfjs-save-button-label = Сохранить
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Загрузить
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Загрузить
pdfjs-bookmark-button =
.title = Текущая страница (просмотр URL-адреса с текущей страницы)
pdfjs-bookmark-button-label = Текущая страница
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Инструменты
pdfjs-tools-button-label = Инструменты
pdfjs-first-page-button =
.title = Перейти на первую страницу
pdfjs-first-page-button-label = Перейти на первую страницу
pdfjs-last-page-button =
.title = Перейти на последнюю страницу
pdfjs-last-page-button-label = Перейти на последнюю страницу
pdfjs-page-rotate-cw-button =
.title = Повернуть по часовой стрелке
pdfjs-page-rotate-cw-button-label = Повернуть по часовой стрелке
pdfjs-page-rotate-ccw-button =
.title = Повернуть против часовой стрелки
pdfjs-page-rotate-ccw-button-label = Повернуть против часовой стрелки
pdfjs-cursor-text-select-tool-button =
.title = Включить Инструмент «Выделение текста»
pdfjs-cursor-text-select-tool-button-label = Инструмент «Выделение текста»
pdfjs-cursor-hand-tool-button =
.title = Включить Инструмент «Рука»
pdfjs-cursor-hand-tool-button-label = Инструмент «Рука»
pdfjs-scroll-page-button =
.title = Использовать прокрутку страниц
pdfjs-scroll-page-button-label = Прокрутка страниц
pdfjs-scroll-vertical-button =
.title = Использовать вертикальную прокрутку
pdfjs-scroll-vertical-button-label = Вертикальная прокрутка
pdfjs-scroll-horizontal-button =
.title = Использовать горизонтальную прокрутку
pdfjs-scroll-horizontal-button-label = Горизонтальная прокрутка
pdfjs-scroll-wrapped-button =
.title = Использовать масштабируемую прокрутку
pdfjs-scroll-wrapped-button-label = Масштабируемая прокрутка
pdfjs-spread-none-button =
.title = Не использовать режим разворотов страниц
pdfjs-spread-none-button-label = Без разворотов страниц
pdfjs-spread-odd-button =
.title = Развороты начинаются с нечётных номеров страниц
pdfjs-spread-odd-button-label = Нечётные страницы слева
pdfjs-spread-even-button =
.title = Развороты начинаются с чётных номеров страниц
pdfjs-spread-even-button-label = Чётные страницы слева
## Document properties dialog
pdfjs-document-properties-button =
.title = Свойства документа…
pdfjs-document-properties-button-label = Свойства документа…
pdfjs-document-properties-file-name = Имя файла:
pdfjs-document-properties-file-size = Размер файла:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } КБ ({ $b } байт)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } МБ ({ $b } байт)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } КБ ({ $size_b } байт)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } МБ ({ $size_b } байт)
pdfjs-document-properties-title = Заголовок:
pdfjs-document-properties-author = Автор:
pdfjs-document-properties-subject = Тема:
pdfjs-document-properties-keywords = Ключевые слова:
pdfjs-document-properties-creation-date = Дата создания:
pdfjs-document-properties-modification-date = Дата изменения:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Приложение:
pdfjs-document-properties-producer = Производитель PDF:
pdfjs-document-properties-version = Версия PDF:
pdfjs-document-properties-page-count = Число страниц:
pdfjs-document-properties-page-size = Размер страницы:
pdfjs-document-properties-page-size-unit-inches = дюймов
pdfjs-document-properties-page-size-unit-millimeters = мм
pdfjs-document-properties-page-size-orientation-portrait = книжная
pdfjs-document-properties-page-size-orientation-landscape = альбомная
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letter
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Быстрый просмотр в Web:
pdfjs-document-properties-linearized-yes = Да
pdfjs-document-properties-linearized-no = Нет
pdfjs-document-properties-close-button = Закрыть
## Print
pdfjs-print-progress-message = Подготовка документа к печати…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Отмена
pdfjs-printing-not-supported = Предупреждение: В этом браузере не полностью поддерживается печать.
pdfjs-printing-not-ready = Предупреждение: PDF не полностью загружен для печати.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Показать/скрыть боковую панель
pdfjs-toggle-sidebar-notification-button =
.title = Показать/скрыть боковую панель (документ имеет содержание/вложения/слои)
pdfjs-toggle-sidebar-button-label = Показать/скрыть боковую панель
pdfjs-document-outline-button =
.title = Показать содержание документа (двойной щелчок, чтобы развернуть/свернуть все элементы)
pdfjs-document-outline-button-label = Содержание документа
pdfjs-attachments-button =
.title = Показать вложения
pdfjs-attachments-button-label = Вложения
pdfjs-layers-button =
.title = Показать слои (дважды щёлкните, чтобы сбросить все слои к состоянию по умолчанию)
pdfjs-layers-button-label = Слои
pdfjs-thumbs-button =
.title = Показать миниатюры
pdfjs-thumbs-button-label = Миниатюры
pdfjs-current-outline-item-button =
.title = Найти текущий элемент структуры
pdfjs-current-outline-item-button-label = Текущий элемент структуры
pdfjs-findbar-button =
.title = Найти в документе
pdfjs-findbar-button-label = Найти
pdfjs-additional-layers = Дополнительные слои
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Страница { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Миниатюра страницы { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Найти
.placeholder = Найти в документе…
pdfjs-find-previous-button =
.title = Найти предыдущее вхождение фразы в текст
pdfjs-find-previous-button-label = Назад
pdfjs-find-next-button =
.title = Найти следующее вхождение фразы в текст
pdfjs-find-next-button-label = Далее
pdfjs-find-highlight-checkbox = Подсветить все
pdfjs-find-match-case-checkbox-label = С учётом регистра
pdfjs-find-match-diacritics-checkbox-label = С учётом диакритических знаков
pdfjs-find-entire-word-checkbox-label = Слова целиком
pdfjs-find-reached-top = Достигнут верх документа, продолжено снизу
pdfjs-find-reached-bottom = Достигнут конец документа, продолжено сверху
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } из { $total } совпадения
[few] { $current } из { $total } совпадений
*[many] { $current } из { $total } совпадений
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Более { $limit } совпадения
[few] Более { $limit } совпадений
*[many] Более { $limit } совпадений
}
pdfjs-find-not-found = Фраза не найдена
## Predefined zoom values
pdfjs-page-scale-width = По ширине страницы
pdfjs-page-scale-fit = По размеру страницы
pdfjs-page-scale-auto = Автоматически
pdfjs-page-scale-actual = Реальный размер
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Страница { $page }
## Loading indicator messages
pdfjs-loading-error = При загрузке PDF произошла ошибка.
pdfjs-invalid-file-error = Некорректный или повреждённый PDF-файл.
pdfjs-missing-file-error = PDF-файл отсутствует.
pdfjs-unexpected-response-error = Неожиданный ответ сервера.
pdfjs-rendering-error = При создании страницы произошла ошибка.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [Аннотация { $type }]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Введите пароль, чтобы открыть этот PDF-файл.
pdfjs-password-invalid = Неверный пароль. Пожалуйста, попробуйте снова.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Отмена
pdfjs-web-fonts-disabled = Веб-шрифты отключены: не удалось задействовать встроенные PDF-шрифты.
## Editing
pdfjs-editor-free-text-button =
.title = Текст
pdfjs-editor-free-text-button-label = Текст
pdfjs-editor-ink-button =
.title = Рисовать
pdfjs-editor-ink-button-label = Рисовать
pdfjs-editor-stamp-button =
.title = Добавить или изменить изображения
pdfjs-editor-stamp-button-label = Добавить или изменить изображения
pdfjs-editor-highlight-button =
.title = Выделение
pdfjs-editor-highlight-button-label = Выделение
pdfjs-highlight-floating-button1 =
.title = Выделение
.aria-label = Выделение
pdfjs-highlight-floating-button-label = Выделение
pdfjs-editor-signature-button =
.title = Добавить подпись
pdfjs-editor-signature-button-label = Добавить подпись
## Default editor aria labels
# “Highlight” is a noun, the string is used on the editor for highlights.
pdfjs-editor-highlight-editor =
.aria-label = Редактор выделения
# “Drawing” is a noun, the string is used on the editor for drawings.
pdfjs-editor-ink-editor =
.aria-label = Редактор изображений
pdfjs-editor-signature-editor =
.aria-label = Редактор подписей
pdfjs-editor-stamp-editor =
.aria-label = Редактор изображений
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Удалить рисунок
pdfjs-editor-remove-freetext-button =
.title = Удалить текст
pdfjs-editor-remove-stamp-button =
.title = Удалить изображение
pdfjs-editor-remove-highlight-button =
.title = Удалить выделение
pdfjs-editor-remove-signature-button =
.title = Удалить подпись
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Цвет
pdfjs-editor-free-text-size-input = Размер
pdfjs-editor-ink-color-input = Цвет
pdfjs-editor-ink-thickness-input = Толщина
pdfjs-editor-ink-opacity-input = Прозрачность
pdfjs-editor-stamp-add-image-button =
.title = Добавить изображение
pdfjs-editor-stamp-add-image-button-label = Добавить изображение
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Толщина
pdfjs-editor-free-highlight-thickness-title =
.title = Изменить толщину при выделении элементов, кроме текста
pdfjs-editor-add-signature-container =
.aria-label = Управление подписями и сохраненные подписи
pdfjs-editor-signature-add-signature-button =
.title = Добавить новую подпись
pdfjs-editor-signature-add-signature-button-label = Добавить новую подпись
# Used on the button to use an already saved signature.
# Variables:
# $description (String) - a string describing/labeling the signature.
pdfjs-editor-add-saved-signature-button =
.title = Сохранённая подпись: { $description }
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Текстовый редактор
.default-content = Начните ввод...
pdfjs-free-text =
.aria-label = Текстовый редактор
pdfjs-free-text-default-content = Начните вводить…
pdfjs-ink =
.aria-label = Редактор рисования
pdfjs-ink-canvas =
.aria-label = Созданное пользователем изображение
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Альтернативный текст
pdfjs-editor-alt-text-edit-button =
.aria-label = Изменить альтернативный текст
pdfjs-editor-alt-text-edit-button-label = Изменить альтернативный текст
pdfjs-editor-alt-text-dialog-label = Выберите вариант
pdfjs-editor-alt-text-dialog-description = Альтернативный текст помогает, когда люди не видят изображение или оно не загружается.
pdfjs-editor-alt-text-add-description-label = Добавить описание
pdfjs-editor-alt-text-add-description-description = Старайтесь составлять 1–2 предложения, описывающих предмет, обстановку или действия.
pdfjs-editor-alt-text-mark-decorative-label = Отметить как декоративное
pdfjs-editor-alt-text-mark-decorative-description = Используется для декоративных изображений, таких как рамки или водяные знаки.
pdfjs-editor-alt-text-cancel-button = Отменить
pdfjs-editor-alt-text-save-button = Сохранить
pdfjs-editor-alt-text-decorative-tooltip = Помечен как декоративный
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Например: «Молодой человек садится за стол, чтобы поесть»
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Альтернативный текст
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Левый верхний угол — изменить размер
pdfjs-editor-resizer-label-top-middle = Вверху посередине — изменить размер
pdfjs-editor-resizer-label-top-right = Верхний правый угол — изменить размер
pdfjs-editor-resizer-label-middle-right = В центре справа — изменить размер
pdfjs-editor-resizer-label-bottom-right = Нижний правый угол — изменить размер
pdfjs-editor-resizer-label-bottom-middle = Внизу посередине — изменить размер
pdfjs-editor-resizer-label-bottom-left = Нижний левый угол — изменить размер
pdfjs-editor-resizer-label-middle-left = В центре слева — изменить размер
pdfjs-editor-resizer-top-left =
.aria-label = Левый верхний угол — изменить размер
pdfjs-editor-resizer-top-middle =
.aria-label = Вверху посередине — изменить размер
pdfjs-editor-resizer-top-right =
.aria-label = Верхний правый угол — изменить размер
pdfjs-editor-resizer-middle-right =
.aria-label = В центре справа — изменить размер
pdfjs-editor-resizer-bottom-right =
.aria-label = Нижний правый угол — изменить размер
pdfjs-editor-resizer-bottom-middle =
.aria-label = Внизу посередине — изменить размер
pdfjs-editor-resizer-bottom-left =
.aria-label = Нижний левый угол — изменить размер
pdfjs-editor-resizer-middle-left =
.aria-label = В центре слева — изменить размер
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Цвет выделения
pdfjs-editor-colorpicker-button =
.title = Изменить цвет
pdfjs-editor-colorpicker-dropdown =
.aria-label = Выбор цвета
pdfjs-editor-colorpicker-yellow =
.title = Жёлтый
pdfjs-editor-colorpicker-green =
.title = Зелёный
pdfjs-editor-colorpicker-blue =
.title = Синий
pdfjs-editor-colorpicker-pink =
.title = Розовый
pdfjs-editor-colorpicker-red =
.title = Красный
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Показать все
pdfjs-editor-highlight-show-all-button =
.title = Показать все
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Изменить альтернативный текст (описание изображения)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Добавить альтернативный текст (описание изображения)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Напишите здесь своё описание…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Короткое описание для людей, которые не видят изображение, или если изображение не загружается.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Этот альтернативный текст был создан автоматически и может быть неточным.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Подробнее
pdfjs-editor-new-alt-text-create-automatically-button-label = Автоматически создавать альтернативный текст
pdfjs-editor-new-alt-text-not-now-button = Не сейчас
pdfjs-editor-new-alt-text-error-title = Не удалось автоматически создать альтернативный текст
pdfjs-editor-new-alt-text-error-description = Пожалуйста, напишите свой альтернативный текст или попробуйте ещё раз позже.
pdfjs-editor-new-alt-text-error-close-button = Закрыть
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Загрузка модели ИИ для альтернативного текста ({ $downloadedSize } из { $totalSize } МБ)
.aria-valuetext = Загрузка модели ИИ для альтернативного текста ({ $downloadedSize } из { $totalSize } МБ)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Альтернативный текст добавлен
pdfjs-editor-new-alt-text-added-button-label = Альтернативный текст добавлен
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Отсутствует альтернативный текст
pdfjs-editor-new-alt-text-missing-button-label = Отсутствует альтернативный текст
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Оценить альтернативный текст
pdfjs-editor-new-alt-text-to-review-button-label = Оценить альтернативный текст
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Создано автоматически: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Настройки альтернативного текста для изображения
pdfjs-image-alt-text-settings-button-label = Настройки альтернативного текста для изображения
pdfjs-editor-alt-text-settings-dialog-label = Настройки альтернативного текста для изображения
pdfjs-editor-alt-text-settings-automatic-title = Автоматический альтернативный текст
pdfjs-editor-alt-text-settings-create-model-button-label = Автоматически создавать альтернативный текст
pdfjs-editor-alt-text-settings-create-model-description = Предлагает описания, чтобы помочь людям, которые не видят изображение, или если изображение не загружается.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = ИИ-модель альтернативного текста ({ $totalSize } МБ)
pdfjs-editor-alt-text-settings-ai-model-description = Запускается локально на вашем устройстве, поэтому ваши данные остаются конфиденциальными. Требуется для автоматического альтернативного текста.
pdfjs-editor-alt-text-settings-delete-model-button = Удалить
pdfjs-editor-alt-text-settings-download-model-button = Загрузить
pdfjs-editor-alt-text-settings-downloading-model-button = Загрузка…
pdfjs-editor-alt-text-settings-editor-title = Редактор альтернативного текста
pdfjs-editor-alt-text-settings-show-dialog-button-label = Сразу показывать редактор альтернативного текста при добавлении изображения
pdfjs-editor-alt-text-settings-show-dialog-description = Помогает вам убедиться, что все ваши изображения имеют альтернативный текст.
pdfjs-editor-alt-text-settings-close-button = Закрыть
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Выделение удалено
pdfjs-editor-undo-bar-message-freetext = Текст удалён
pdfjs-editor-undo-bar-message-ink = Рисунок удалён
pdfjs-editor-undo-bar-message-stamp = Изображение удалено
pdfjs-editor-undo-bar-message-signature = Подпись удалена
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } аннотация удалена
[few] { $count } аннотации удалены
*[many] { $count } аннотаций удалены
}
pdfjs-editor-undo-bar-undo-button =
.title = Отменить
pdfjs-editor-undo-bar-undo-button-label = Отменить
pdfjs-editor-undo-bar-close-button =
.title = Закрыть
pdfjs-editor-undo-bar-close-button-label = Закрыть
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = Это окно позволяет пользователю создать подпись для добавления в PDF-документ. Пользователь может отредактировать имя (которое также используется в качестве альтернативного текста) и, по желанию, сохранить подпись для повторного использования.
pdfjs-editor-add-signature-dialog-title = Добавить подпись
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Ввод
.title = Ввод
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Рисовать
.title = Рисовать
pdfjs-editor-add-signature-image-button = Изображение
.title = Изображение
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Введите свою подпись
.placeholder = Введите свою подпись
pdfjs-editor-add-signature-draw-placeholder = Нарисуйте свою подпись
pdfjs-editor-add-signature-draw-thickness-range-label = Толщина
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Толщина рисунка: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Перетащите сюда файл для загрузки
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Или просмотрите файлы изображений
*[other] Или просмотрите файлы изображений
}
## Controls
pdfjs-editor-add-signature-description-label = Описание (альтернативный текст)
pdfjs-editor-add-signature-description-input =
.title = Описание (альтернативный текст)
pdfjs-editor-add-signature-description-default-when-drawing = Подпись
pdfjs-editor-add-signature-clear-button-label = Удалить подпись
pdfjs-editor-add-signature-clear-button =
.title = Удалить подпись
pdfjs-editor-add-signature-save-checkbox = Сохранить подпись
pdfjs-editor-add-signature-save-warning-message = Вы достигли лимита в 5 сохранённых подписей. Удалите одну, чтобы сохранить другие.
pdfjs-editor-add-signature-image-upload-error-title = Не удалось загрузить изображение
pdfjs-editor-add-signature-image-upload-error-description = Проверьте подключение к сети или попробуйте другое изображение.
pdfjs-editor-add-signature-error-close-button = Закрыть
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Отмена
pdfjs-editor-add-signature-add-button = Добавить
pdfjs-editor-edit-signature-update-button = Обновить
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Удалить подпись
pdfjs-editor-delete-signature-button-label = Удалить подпись
pdfjs-editor-delete-signature-button1 =
.title = Удалить сохранённую подпись
pdfjs-editor-delete-signature-button-label1 = Удалить сохранённую подпись
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Изменить описание
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Изменить описание
================================================
FILE: cookbook/static/pdfjs/web/locale/sat/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = ᱢᱟᱲᱟᱝ ᱥᱟᱦᱴᱟ
pdfjs-previous-button-label = ᱢᱟᱲᱟᱝᱟᱜ
pdfjs-next-button =
.title = ᱤᱱᱟᱹ ᱛᱟᱭᱚᱢ ᱥᱟᱦᱴᱟ
pdfjs-next-button-label = ᱤᱱᱟᱹ ᱛᱟᱭᱚᱢ
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = ᱥᱟᱦᱴᱟ
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = ᱨᱮᱭᱟᱜ { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } ᱠᱷᱚᱱ { $pagesCount })
pdfjs-zoom-out-button =
.title = ᱦᱤᱲᱤᱧ ᱛᱮᱭᱟᱨ
pdfjs-zoom-out-button-label = ᱦᱤᱲᱤᱧ ᱛᱮᱭᱟᱨ
pdfjs-zoom-in-button =
.title = ᱢᱟᱨᱟᱝ ᱛᱮᱭᱟᱨ
pdfjs-zoom-in-button-label = ᱢᱟᱨᱟᱝ ᱛᱮᱭᱟᱨ
pdfjs-zoom-select =
.title = ᱡᱩᱢ
pdfjs-presentation-mode-button =
.title = ᱩᱫᱩᱜ ᱥᱚᱫᱚᱨ ᱚᱵᱚᱥᱛᱟ ᱨᱮ ᱚᱛᱟᱭ ᱢᱮ
pdfjs-presentation-mode-button-label = ᱩᱫᱩᱜ ᱥᱚᱫᱚᱨ ᱚᱵᱚᱥᱛᱟ ᱨᱮ
pdfjs-open-file-button =
.title = ᱨᱮᱫ ᱡᱷᱤᱡᱽ ᱢᱮ
pdfjs-open-file-button-label = ᱡᱷᱤᱡᱽ ᱢᱮ
pdfjs-print-button =
.title = ᱪᱷᱟᱯᱟ
pdfjs-print-button-label = ᱪᱷᱟᱯᱟ
pdfjs-save-button =
.title = ᱥᱟᱺᱪᱟᱣ ᱢᱮ
pdfjs-save-button-label = ᱥᱟᱺᱪᱟᱣ ᱢᱮ
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = ᱰᱟᱣᱩᱱᱞᱚᱰ
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = ᱰᱟᱣᱩᱱᱞᱚᱰ
pdfjs-bookmark-button =
.title = ᱱᱤᱛᱚᱜᱟᱜ ᱥᱟᱦᱴᱟ (ᱱᱤᱛᱚᱜᱟᱜ ᱥᱟᱦᱴᱟ ᱠᱷᱚᱱ URL ᱫᱮᱠᱷᱟᱣ ᱢᱮ)
pdfjs-bookmark-button-label = ᱱᱤᱛᱚᱜᱟᱜ ᱥᱟᱦᱴᱟ
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = ᱦᱟᱹᱛᱤᱭᱟᱹᱨ ᱠᱚ
pdfjs-tools-button-label = ᱦᱟᱹᱛᱤᱭᱟᱹᱨ ᱠᱚ
pdfjs-first-page-button =
.title = ᱯᱩᱭᱞᱩ ᱥᱟᱦᱴᱟ ᱥᱮᱫ ᱪᱟᱞᱟᱜ ᱢᱮ
pdfjs-first-page-button-label = ᱯᱩᱭᱞᱩ ᱥᱟᱦᱴᱟ ᱥᱮᱫ ᱪᱟᱞᱟᱜ ᱢᱮ
pdfjs-last-page-button =
.title = ᱢᱩᱪᱟᱹᱫ ᱥᱟᱦᱴᱟ ᱥᱮᱫ ᱪᱟᱞᱟᱜ ᱢᱮ
pdfjs-last-page-button-label = ᱢᱩᱪᱟᱹᱫ ᱥᱟᱦᱴᱟ ᱥᱮᱫ ᱪᱟᱞᱟᱜ ᱢᱮ
pdfjs-page-rotate-cw-button =
.title = ᱜᱷᱚᱰᱤ ᱦᱤᱥᱟᱹᱵ ᱛᱮ ᱟᱹᱪᱩᱨ
pdfjs-page-rotate-cw-button-label = ᱜᱷᱚᱰᱤ ᱦᱤᱥᱟᱹᱵ ᱛᱮ ᱟᱹᱪᱩᱨ
pdfjs-page-rotate-ccw-button =
.title = ᱜᱷᱚᱰᱤ ᱦᱤᱥᱟᱹᱵ ᱛᱮ ᱩᱞᱴᱟᱹ ᱟᱹᱪᱩᱨ
pdfjs-page-rotate-ccw-button-label = ᱜᱷᱚᱰᱤ ᱦᱤᱥᱟᱹᱵ ᱛᱮ ᱩᱞᱴᱟᱹ ᱟᱹᱪᱩᱨ
pdfjs-cursor-text-select-tool-button =
.title = ᱚᱞ ᱵᱟᱪᱷᱟᱣ ᱦᱟᱹᱛᱤᱭᱟᱨ ᱮᱢ ᱪᱷᱚᱭ ᱢᱮ
pdfjs-cursor-text-select-tool-button-label = ᱚᱞ ᱵᱟᱪᱷᱟᱣ ᱦᱟᱹᱛᱤᱭᱟᱨ
pdfjs-cursor-hand-tool-button =
.title = ᱛᱤ ᱦᱟᱹᱛᱤᱭᱟᱨ ᱮᱢ ᱪᱷᱚᱭ ᱢᱮ
pdfjs-cursor-hand-tool-button-label = ᱛᱤ ᱦᱟᱹᱛᱤᱭᱟᱨ
pdfjs-scroll-page-button =
.title = ᱥᱟᱦᱴᱟ ᱜᱩᱲᱟᱹᱣ ᱵᱮᱵᱷᱟᱨ ᱢᱮ
pdfjs-scroll-page-button-label = ᱥᱟᱦᱴᱟ ᱜᱩᱲᱟᱹᱣ
pdfjs-scroll-vertical-button =
.title = ᱥᱤᱫᱽ ᱜᱩᱲᱟᱹᱣ ᱵᱮᱵᱷᱟᱨ ᱢᱮ
pdfjs-scroll-vertical-button-label = ᱥᱤᱫᱽ ᱜᱩᱲᱟᱹᱣ
pdfjs-scroll-horizontal-button =
.title = ᱜᱤᱛᱤᱡ ᱛᱮ ᱜᱩᱲᱟᱹᱣ ᱵᱮᱵᱷᱟᱨ ᱢᱮ
pdfjs-scroll-horizontal-button-label = ᱜᱤᱛᱤᱡ ᱛᱮ ᱜᱩᱲᱟᱹᱣ
pdfjs-scroll-wrapped-button =
.title = ᱞᱤᱯᱴᱟᱹᱣ ᱜᱩᱰᱨᱟᱹᱣ ᱵᱮᱵᱷᱟᱨ ᱢᱮ
pdfjs-scroll-wrapped-button-label = ᱞᱤᱯᱴᱟᱣ ᱜᱩᱰᱨᱟᱹᱣ
pdfjs-spread-none-button =
.title = ᱟᱞᱚᱢ ᱡᱚᱲᱟᱣ ᱟ ᱥᱟᱦᱴᱟ ᱫᱚ ᱯᱟᱥᱱᱟᱣᱜᱼᱟ
pdfjs-spread-none-button-label = ᱯᱟᱥᱱᱟᱣ ᱵᱟᱹᱱᱩᱜᱼᱟ
pdfjs-spread-odd-button =
.title = ᱥᱟᱦᱴᱟ ᱯᱟᱥᱱᱟᱣ ᱡᱚᱲᱟᱣ ᱢᱮ ᱡᱟᱦᱟᱸ ᱫᱚ ᱚᱰᱼᱮᱞ ᱥᱟᱦᱴᱟᱠᱚ ᱥᱟᱞᱟᱜ ᱮᱛᱦᱚᱵᱚᱜ ᱠᱟᱱᱟ
pdfjs-spread-odd-button-label = ᱚᱰ ᱯᱟᱥᱱᱟᱣ
pdfjs-spread-even-button =
.title = ᱥᱟᱦᱴᱟ ᱯᱟᱥᱱᱟᱣ ᱡᱚᱲᱟᱣ ᱢᱮ ᱡᱟᱦᱟᱸ ᱫᱚ ᱤᱣᱮᱱᱼᱮᱞ ᱥᱟᱦᱴᱟᱠᱚ ᱥᱟᱞᱟᱜ ᱮᱛᱦᱚᱵᱚᱜ ᱠᱟᱱᱟ
pdfjs-spread-even-button-label = ᱯᱟᱥᱱᱟᱣ ᱤᱣᱮᱱ
## Document properties dialog
pdfjs-document-properties-button =
.title = ᱫᱚᱞᱤᱞ ᱜᱩᱱᱠᱚ …
pdfjs-document-properties-button-label = ᱫᱚᱞᱤᱞ ᱜᱩᱱᱠᱚ …
pdfjs-document-properties-file-name = ᱨᱮᱫᱽ ᱧᱩᱛᱩᱢ :
pdfjs-document-properties-file-size = ᱨᱮᱫᱽ ᱢᱟᱯ :
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } ᱵᱟᱭᱤᱴ ᱠᱚ)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } ᱵᱟᱭᱤᱴ ᱠᱚ)
pdfjs-document-properties-title = ᱧᱩᱛᱩᱢ :
pdfjs-document-properties-author = ᱚᱱᱚᱞᱤᱭᱟᱹ :
pdfjs-document-properties-subject = ᱵᱤᱥᱚᱭ :
pdfjs-document-properties-keywords = ᱠᱟᱹᱴᱷᱤ ᱥᱟᱵᱟᱫᱽ :
pdfjs-document-properties-creation-date = ᱛᱮᱭᱟᱨ ᱢᱟᱸᱦᱤᱛ :
pdfjs-document-properties-modification-date = ᱵᱚᱫᱚᱞ ᱦᱚᱪᱚ ᱢᱟᱹᱦᱤᱛ :
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = ᱵᱮᱱᱟᱣᱤᱡ :
pdfjs-document-properties-producer = PDF ᱛᱮᱭᱟᱨ ᱚᱰᱚᱠᱤᱡ :
pdfjs-document-properties-version = PDF ᱵᱷᱟᱹᱨᱥᱚᱱ :
pdfjs-document-properties-page-count = ᱥᱟᱦᱴᱟ ᱞᱮᱠᱷᱟ :
pdfjs-document-properties-page-size = ᱥᱟᱦᱴᱟ ᱢᱟᱯ :
pdfjs-document-properties-page-size-unit-inches = ᱤᱧᱪ
pdfjs-document-properties-page-size-unit-millimeters = ᱢᱤᱢᱤ
pdfjs-document-properties-page-size-orientation-portrait = ᱯᱚᱴᱨᱮᱴ
pdfjs-document-properties-page-size-orientation-landscape = ᱞᱮᱱᱰᱥᱠᱮᱯ
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = ᱪᱤᱴᱷᱤ
pdfjs-document-properties-page-size-name-legal = ᱠᱟᱹᱱᱩᱱᱤ
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = ᱞᱚᱜᱚᱱ ᱣᱮᱵᱽ ᱧᱮᱞ :
pdfjs-document-properties-linearized-yes = ᱦᱚᱭ
pdfjs-document-properties-linearized-no = ᱵᱟᱝ
pdfjs-document-properties-close-button = ᱵᱚᱸᱫᱚᱭ ᱢᱮ
## Print
pdfjs-print-progress-message = ᱪᱷᱟᱯᱟ ᱞᱟᱹᱜᱤᱫ ᱫᱚᱞᱤᱞ ᱛᱮᱭᱟᱨᱚᱜ ᱠᱟᱱᱟ …
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = ᱵᱟᱹᱰᱨᱟᱹ
pdfjs-printing-not-supported = ᱦᱚᱥᱤᱭᱟᱨ : ᱪᱷᱟᱯᱟ ᱱᱚᱣᱟ ᱯᱟᱱᱛᱮᱭᱟᱜ ᱫᱟᱨᱟᱭ ᱛᱮ ᱯᱩᱨᱟᱹᱣ ᱵᱟᱭ ᱜᱚᱲᱚᱣᱟᱠᱟᱱᱟ ᱾
pdfjs-printing-not-ready = ᱦᱩᱥᱤᱭᱟᱹᱨ : ᱪᱷᱟᱯᱟ ᱞᱟᱹᱜᱤᱫ PDF ᱯᱩᱨᱟᱹ ᱵᱟᱭ ᱞᱟᱫᱮ ᱟᱠᱟᱱᱟ ᱾
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = ᱫᱷᱟᱨᱮᱵᱟᱨ ᱥᱮᱫ ᱩᱪᱟᱹᱲᱚᱜ ᱢᱮ
pdfjs-toggle-sidebar-notification-button =
.title = ᱫᱷᱟᱨᱮᱵᱟᱨ ᱥᱮᱫ ᱩᱪᱟᱹᱲᱚᱜ ᱢᱮ (ᱫᱚᱞᱤᱞ ᱨᱮ ᱟᱣᱴᱞᱟᱭᱤᱢ ᱢᱮᱱᱟᱜᱼᱟ/ᱞᱟᱪᱷᱟᱠᱚ/ᱯᱚᱨᱚᱛᱠᱚ)
pdfjs-toggle-sidebar-button-label = ᱫᱷᱟᱨᱮᱵᱟᱨ ᱥᱮᱫ ᱩᱪᱟᱹᱲᱚᱜ ᱢᱮ
pdfjs-document-outline-button =
.title = ᱫᱚᱞᱚᱞ ᱟᱣᱴᱞᱟᱭᱤᱱ ᱫᱮᱠᱷᱟᱣ ᱢᱮ (ᱡᱷᱚᱛᱚ ᱡᱤᱱᱤᱥᱠᱚ ᱵᱟᱨ ᱡᱮᱠᱷᱟ ᱚᱛᱟ ᱠᱮᱛᱮ ᱡᱷᱟᱹᱞ/ᱦᱩᱰᱤᱧ ᱪᱷᱚᱭ ᱢᱮ)
pdfjs-document-outline-button-label = ᱫᱚᱞᱤᱞ ᱛᱮᱭᱟᱨ ᱛᱮᱫ
pdfjs-attachments-button =
.title = ᱞᱟᱴᱷᱟ ᱥᱮᱞᱮᱫ ᱠᱚ ᱩᱫᱩᱜᱽ ᱢᱮ
pdfjs-attachments-button-label = ᱞᱟᱴᱷᱟ ᱥᱮᱞᱮᱫ ᱠᱚ
pdfjs-layers-button =
.title = ᱯᱚᱨᱚᱛ ᱫᱮᱠᱷᱟᱣ ᱢᱮ (ᱢᱩᱞ ᱡᱟᱭᱜᱟ ᱛᱮ ᱡᱷᱚᱛᱚ ᱯᱚᱨᱚᱛᱠᱚ ᱨᱤᱥᱮᱴ ᱞᱟᱹᱜᱤᱫ ᱵᱟᱨ ᱡᱮᱠᱷᱟ ᱚᱛᱚᱭ ᱢᱮ)
pdfjs-layers-button-label = ᱯᱚᱨᱚᱛᱠᱚ
pdfjs-thumbs-button =
.title = ᱪᱤᱛᱟᱹᱨ ᱟᱦᱞᱟ ᱠᱚ ᱩᱫᱩᱜᱽ ᱢᱮ
pdfjs-thumbs-button-label = ᱪᱤᱛᱟᱹᱨ ᱟᱦᱞᱟ ᱠᱚ
pdfjs-current-outline-item-button =
.title = ᱱᱤᱛᱚᱜᱟᱜ ᱟᱣᱴᱞᱟᱭᱤᱱ ᱡᱟᱱᱤᱥ ᱯᱟᱱᱛᱮ ᱢᱮ
pdfjs-current-outline-item-button-label = ᱱᱤᱛᱚᱜᱟᱜ ᱟᱣᱴᱞᱟᱭᱤᱱ ᱡᱟᱱᱤᱥ
pdfjs-findbar-button =
.title = ᱫᱚᱞᱤᱞ ᱨᱮ ᱯᱟᱱᱛᱮ
pdfjs-findbar-button-label = ᱥᱮᱸᱫᱽᱨᱟᱭ ᱢᱮ
pdfjs-additional-layers = ᱵᱟᱹᱲᱛᱤ ᱯᱚᱨᱚᱛᱠᱚ
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = { $page } ᱥᱟᱦᱴᱟ
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = { $page } ᱥᱟᱦᱴᱟ ᱨᱮᱭᱟᱜ ᱪᱤᱛᱟᱹᱨ ᱟᱦᱞᱟ
## Find panel button title and messages
pdfjs-find-input =
.title = ᱥᱮᱸᱫᱽᱨᱟᱭ ᱢᱮ
.placeholder = ᱫᱚᱞᱤᱞ ᱨᱮ ᱯᱟᱱᱛᱮ ᱢᱮ …
pdfjs-find-previous-button =
.title = ᱟᱭᱟᱛ ᱦᱤᱸᱥ ᱨᱮᱭᱟᱜ ᱯᱟᱹᱦᱤᱞ ᱥᱮᱫᱟᱜ ᱚᱰᱚᱠ ᱧᱟᱢ ᱢᱮ
pdfjs-find-previous-button-label = ᱢᱟᱲᱟᱝᱟᱜ
pdfjs-find-next-button =
.title = ᱟᱭᱟᱛ ᱦᱤᱸᱥ ᱨᱮᱭᱟᱜ ᱤᱱᱟᱹ ᱛᱟᱭᱚᱢ ᱚᱰᱚᱠ ᱧᱟᱢ ᱢᱮ
pdfjs-find-next-button-label = ᱤᱱᱟᱹ ᱛᱟᱭᱚᱢ
pdfjs-find-highlight-checkbox = ᱡᱷᱚᱛᱚ ᱩᱫᱩᱜ ᱨᱟᱠᱟᱵ
pdfjs-find-match-case-checkbox-label = ᱡᱚᱲ ᱠᱟᱛᱷᱟ
pdfjs-find-match-diacritics-checkbox-label = ᱵᱤᱥᱮᱥᱚᱠ ᱠᱚ ᱢᱮᱲᱟᱣ ᱢᱮ
pdfjs-find-entire-word-checkbox-label = ᱡᱷᱚᱛᱚ ᱟᱹᱲᱟᱹᱠᱚ
pdfjs-find-reached-top = ᱫᱚᱞᱤᱞ ᱨᱮᱭᱟᱜ ᱪᱤᱴ ᱨᱮ ᱥᱮᱴᱮᱨ, ᱞᱟᱛᱟᱨ ᱠᱷᱚᱱ ᱞᱮᱛᱟᱲ
pdfjs-find-reached-bottom = ᱫᱚᱞᱤᱞ ᱨᱮᱭᱟᱜ ᱢᱩᱪᱟᱹᱫ ᱨᱮ ᱥᱮᱴᱮᱨ, ᱪᱚᱴ ᱠᱷᱚᱱ ᱞᱮᱛᱟᱲ
pdfjs-find-not-found = ᱛᱚᱯᱚᱞ ᱫᱚᱱᱚᱲ ᱵᱟᱝ ᱧᱟᱢ ᱞᱮᱱᱟ
## Predefined zoom values
pdfjs-page-scale-width = ᱥᱟᱦᱴᱟ ᱚᱥᱟᱨ
pdfjs-page-scale-fit = ᱥᱟᱦᱴᱟ ᱠᱷᱟᱯ
pdfjs-page-scale-auto = ᱟᱡᱼᱟᱡ ᱛᱮ ᱦᱩᱰᱤᱧ ᱞᱟᱹᱴᱩ ᱛᱮᱭᱟᱨ
pdfjs-page-scale-actual = ᱴᱷᱤᱠ ᱢᱟᱨᱟᱝ ᱛᱮᱫ
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = { $page } ᱥᱟᱦᱴᱟ
## Loading indicator messages
pdfjs-loading-error = PDF ᱞᱟᱫᱮ ᱡᱚᱦᱚᱜ ᱢᱤᱫ ᱵᱷᱩᱞ ᱦᱩᱭ ᱮᱱᱟ ᱾
pdfjs-invalid-file-error = ᱵᱟᱝ ᱵᱟᱛᱟᱣ ᱟᱨᱵᱟᱝᱠᱷᱟᱱ ᱰᱤᱜᱟᱹᱣ PDF ᱨᱮᱫᱽ ᱾
pdfjs-missing-file-error = ᱟᱫᱟᱜ PDF ᱨᱮᱫᱽ ᱾
pdfjs-unexpected-response-error = ᱵᱟᱝᱵᱩᱡᱷ ᱥᱚᱨᱵᱷᱚᱨ ᱛᱮᱞᱟ ᱾
pdfjs-rendering-error = ᱥᱟᱦᱴᱟ ᱮᱢ ᱡᱚᱦᱚᱠ ᱢᱤᱫ ᱵᱷᱩᱞ ᱦᱩᱭ ᱮᱱᱟ ᱾
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } ᱢᱚᱱᱛᱚ ᱮᱢ]
## Password
pdfjs-password-label = ᱱᱚᱶᱟ PDF ᱨᱮᱫᱽ ᱡᱷᱤᱡᱽ ᱞᱟᱹᱜᱤᱫ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱟᱫᱮᱨ ᱢᱮ ᱾
pdfjs-password-invalid = ᱵᱷᱩᱞ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱾ ᱫᱟᱭᱟᱠᱟᱛᱮ ᱫᱩᱦᱲᱟᱹ ᱪᱮᱥᱴᱟᱭ ᱢᱮ ᱾
pdfjs-password-ok-button = ᱴᱷᱤᱠ
pdfjs-password-cancel-button = ᱵᱟᱹᱰᱨᱟᱹ
pdfjs-web-fonts-disabled = ᱣᱮᱵᱽ ᱪᱤᱠᱤ ᱵᱟᱝ ᱦᱩᱭ ᱦᱚᱪᱚ ᱠᱟᱱᱟ : ᱵᱷᱤᱛᱤᱨ ᱛᱷᱟᱯᱚᱱ PDF ᱪᱤᱠᱤ ᱵᱮᱵᱷᱟᱨ ᱵᱟᱝ ᱦᱩᱭ ᱠᱮᱭᱟ ᱾
## Editing
pdfjs-editor-free-text-button =
.title = ᱚᱞ
pdfjs-editor-free-text-button-label = ᱚᱞ
pdfjs-editor-ink-button =
.title = ᱛᱮᱭᱟᱨ
pdfjs-editor-ink-button-label = ᱛᱮᱭᱟᱨ
pdfjs-editor-stamp-button =
.title = ᱪᱤᱛᱟᱹᱨᱠᱚ ᱥᱮᱞᱮᱫ ᱥᱮ ᱥᱟᱯᱲᱟᱣ ᱢᱮ
pdfjs-editor-stamp-button-label = ᱪᱤᱛᱟᱹᱨᱠᱚ ᱥᱮᱞᱮᱫ ᱥᱮ ᱥᱟᱯᱲᱟᱣ ᱢᱮ
## Default editor aria labels
## Remove button for the various kind of editor.
##
# Editor Parameters
pdfjs-editor-free-text-color-input = ᱨᱚᱝ
pdfjs-editor-free-text-size-input = ᱢᱟᱯ
pdfjs-editor-ink-color-input = ᱨᱚᱝ
pdfjs-editor-ink-thickness-input = ᱢᱚᱴᱟ
pdfjs-editor-ink-opacity-input = ᱟᱨᱯᱟᱨ
pdfjs-editor-stamp-add-image-button =
.title = ᱪᱤᱛᱟᱹᱨ ᱥᱮᱞᱮᱫ ᱢᱮ
pdfjs-editor-stamp-add-image-button-label = ᱪᱤᱛᱟᱹᱨ ᱥᱮᱞᱮᱫ ᱢᱮ
pdfjs-free-text =
.aria-label = ᱚᱞ ᱥᱟᱯᱲᱟᱣᱤᱭᱟᱹ
pdfjs-free-text-default-content = ᱚᱞ ᱮᱛᱦᱚᱵ ᱢᱮ …
pdfjs-ink =
.aria-label = ᱛᱮᱭᱟᱨ ᱥᱟᱯᱲᱟᱣᱤᱭᱟᱹ
pdfjs-ink-canvas =
.aria-label = ᱵᱮᱵᱷᱟᱨᱤᱭᱟᱹ ᱛᱮᱭᱟᱨ ᱠᱟᱫ ᱪᱤᱛᱟᱹᱨ
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/sc/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Pàgina anteriore
pdfjs-previous-button-label = S'ischeda chi b'est primu
pdfjs-next-button =
.title = Pàgina imbeniente
pdfjs-next-button-label = Imbeniente
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Pàgina
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = de { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } de { $pagesCount })
pdfjs-zoom-out-button =
.title = Impitica
pdfjs-zoom-out-button-label = Impitica
pdfjs-zoom-in-button =
.title = Ismànnia
pdfjs-zoom-in-button-label = Ismànnia
pdfjs-zoom-select =
.title = Ismànnia
pdfjs-presentation-mode-button =
.title = Cola a sa modalidade de presentatzione
pdfjs-presentation-mode-button-label = Modalidade de presentatzione
pdfjs-open-file-button =
.title = Aberi s'archìviu
pdfjs-open-file-button-label = Abertu
pdfjs-print-button =
.title = Imprenta
pdfjs-print-button-label = Imprenta
pdfjs-save-button =
.title = Sarva
pdfjs-save-button-label = Sarva
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Iscàrriga
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Iscàrriga
pdfjs-bookmark-button =
.title = Pàgina atuale (ammustra s’URL de sa pàgina atuale)
pdfjs-bookmark-button-label = Pàgina atuale
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Istrumentos
pdfjs-tools-button-label = Istrumentos
pdfjs-first-page-button =
.title = Bae a sa prima pàgina
pdfjs-first-page-button-label = Bae a sa prima pàgina
pdfjs-last-page-button =
.title = Bae a s'ùrtima pàgina
pdfjs-last-page-button-label = Bae a s'ùrtima pàgina
pdfjs-page-rotate-cw-button =
.title = Gira in sensu oràriu
pdfjs-page-rotate-cw-button-label = Gira in sensu oràriu
pdfjs-page-rotate-ccw-button =
.title = Gira in sensu anti-oràriu
pdfjs-page-rotate-ccw-button-label = Gira in sensu anti-oràriu
pdfjs-cursor-text-select-tool-button =
.title = Ativa s'aina de seletzione de testu
pdfjs-cursor-text-select-tool-button-label = Aina de seletzione de testu
pdfjs-cursor-hand-tool-button =
.title = Ativa s'aina de manu
pdfjs-cursor-hand-tool-button-label = Aina de manu
pdfjs-scroll-page-button =
.title = Imprea s'iscurrimentu de pàgina
pdfjs-scroll-page-button-label = Iscurrimentu de pàgina
pdfjs-scroll-vertical-button =
.title = Imprea s'iscurrimentu verticale
pdfjs-scroll-vertical-button-label = Iscurrimentu verticale
pdfjs-scroll-horizontal-button =
.title = Imprea s'iscurrimentu orizontale
pdfjs-scroll-horizontal-button-label = Iscurrimentu orizontale
pdfjs-scroll-wrapped-button =
.title = Imprea s'iscurrimentu continu
pdfjs-scroll-wrapped-button-label = Iscurrimentu continu
## Document properties dialog
pdfjs-document-properties-button =
.title = Propiedades de su documentu…
pdfjs-document-properties-button-label = Propiedades de su documentu…
pdfjs-document-properties-file-name = Nòmine de s'archìviu:
pdfjs-document-properties-file-size = Mannària de s'archìviu:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = Tìtulu:
pdfjs-document-properties-author = Autoria:
pdfjs-document-properties-subject = Ogetu:
pdfjs-document-properties-keywords = Faeddos crae:
pdfjs-document-properties-creation-date = Data de creatzione:
pdfjs-document-properties-modification-date = Data de modìfica:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Creatzione:
pdfjs-document-properties-producer = Produtore de PDF:
pdfjs-document-properties-version = Versione de PDF:
pdfjs-document-properties-page-count = Contu de pàginas:
pdfjs-document-properties-page-size = Mannària de sa pàgina:
pdfjs-document-properties-page-size-unit-inches = pòddighes
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = verticale
pdfjs-document-properties-page-size-orientation-landscape = orizontale
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Lìtera
pdfjs-document-properties-page-size-name-legal = Legale
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Visualizatzione web lestra:
pdfjs-document-properties-linearized-yes = Eja
pdfjs-document-properties-linearized-no = Nono
pdfjs-document-properties-close-button = Serra
## Print
pdfjs-print-progress-message = Aparitzende s'imprenta de su documentu…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Cantzella
pdfjs-printing-not-supported = Atentzione: s'imprenta no est funtzionende de su totu in custu navigadore.
pdfjs-printing-not-ready = Atentzione: su PDF no est istadu carrigadu de su totu pro s'imprenta.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Ativa/disativa sa barra laterale
pdfjs-toggle-sidebar-notification-button =
.title = Ativa/disativa sa barra laterale (su documentu cuntenet un'ischema, alligongiados o livellos)
pdfjs-toggle-sidebar-button-label = Ativa/disativa sa barra laterale
pdfjs-document-outline-button-label = Ischema de su documentu
pdfjs-attachments-button =
.title = Ammustra alligongiados
pdfjs-attachments-button-label = Alliongiados
pdfjs-layers-button =
.title = Ammustra livellos (clic dòpiu pro ripristinare totu is livellos a s'istadu predefinidu)
pdfjs-layers-button-label = Livellos
pdfjs-thumbs-button =
.title = Ammustra miniaturas
pdfjs-thumbs-button-label = Miniaturas
pdfjs-current-outline-item-button =
.title = Agata s'elementu atuale de s'ischema
pdfjs-current-outline-item-button-label = Elementu atuale de s'ischema
pdfjs-findbar-button =
.title = Agata in su documentu
pdfjs-findbar-button-label = Agata
pdfjs-additional-layers = Livellos additzionales
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Pàgina { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Miniatura de sa pàgina { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Agata
.placeholder = Agata in su documentu…
pdfjs-find-previous-button =
.title = Agata s'ocurrèntzia pretzedente de sa fràsia
pdfjs-find-previous-button-label = S'ischeda chi b'est primu
pdfjs-find-next-button =
.title = Agata s'ocurrèntzia imbeniente de sa fràsia
pdfjs-find-next-button-label = Imbeniente
pdfjs-find-highlight-checkbox = Evidèntzia totu
pdfjs-find-match-case-checkbox-label = Distinghe intre majùsculas e minùsculas
pdfjs-find-match-diacritics-checkbox-label = Respeta is diacrìticos
pdfjs-find-entire-word-checkbox-label = Faeddos intreos
pdfjs-find-reached-top = S'est lòmpidu a su cumintzu de su documentu, si sighit dae su bàsciu
pdfjs-find-reached-bottom = Acabbu de su documentu, si sighit dae s'artu
pdfjs-find-not-found = Testu no agatadu
## Predefined zoom values
pdfjs-page-scale-auto = Ingrandimentu automàticu
pdfjs-page-scale-actual = Mannària reale
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Pàgina { $page }
## Loading indicator messages
pdfjs-loading-error = Faddina in sa càrriga de su PDF.
pdfjs-invalid-file-error = Archìviu PDF non vàlidu o corrùmpidu.
pdfjs-missing-file-error = Ammancat s'archìviu PDF.
pdfjs-unexpected-response-error = Risposta imprevista de su serbidore.
pdfjs-rendering-error = Faddina in sa visualizatzione de sa pàgina.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
## Password
pdfjs-password-label = Inserta sa crae pro abèrrere custu archìviu PDF.
pdfjs-password-invalid = Sa crae no est curreta. Torra a nche proare.
pdfjs-password-ok-button = Andat bene
pdfjs-password-cancel-button = Cantzella
pdfjs-web-fonts-disabled = Is tipografias web sunt disativadas: is tipografias incrustadas a su PDF non podent èssere impreadas.
## Editing
pdfjs-editor-free-text-button =
.title = Testu
pdfjs-editor-free-text-button-label = Testu
pdfjs-editor-ink-button =
.title = Disinnu
pdfjs-editor-ink-button-label = Disinnu
pdfjs-editor-stamp-button =
.title = Agiunghe o modìfica immàgines
pdfjs-editor-stamp-button-label = Agiunghe o modìfica immàgines
pdfjs-editor-highlight-button =
.title = Evidèntzia
pdfjs-editor-highlight-button-label = Evidèntzia
pdfjs-highlight-floating-button1 =
.title = Evidèntzia
.aria-label = Evidèntzia
pdfjs-highlight-floating-button-label = Evidèntzia
## Default editor aria labels
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Boga su disinnu
pdfjs-editor-remove-freetext-button =
.title = Boga su testu
pdfjs-editor-remove-stamp-button =
.title = Boga s’immàgine
pdfjs-editor-remove-highlight-button =
.title = Boga s’evidèntzia
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Colore
pdfjs-editor-free-text-size-input = Mannària
pdfjs-editor-ink-color-input = Colore
pdfjs-editor-ink-thickness-input = Grussària
pdfjs-editor-stamp-add-image-button =
.title = Agiunghe un’immàgine
pdfjs-editor-stamp-add-image-button-label = Agiunghe un’immàgine
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Grussària
pdfjs-free-text =
.aria-label = Editore de testu
pdfjs-free-text-default-content = Cumintza a iscrìere…
pdfjs-ink =
.aria-label = Editore de disinnos
pdfjs-ink-canvas =
.aria-label = Immàgine creada dae s’utente
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Testu alternativu
pdfjs-editor-alt-text-edit-button-label = Modifica su testu alternativu
pdfjs-editor-alt-text-dialog-label = Sèbera un’optzione
pdfjs-editor-alt-text-dialog-description = Su testu alternativu (“alt text”) est ùtile pro persones chi non podent bìdere s’immàgine o cando non benit carrigada.
pdfjs-editor-alt-text-add-description-label = Agiunghe una descritzione
pdfjs-editor-alt-text-cancel-button = Annulla
pdfjs-editor-alt-text-save-button = Sarva
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
pdfjs-editor-colorpicker-button =
.title = Modifica su colore
pdfjs-editor-colorpicker-dropdown =
.aria-label = Colores a disponimentu
pdfjs-editor-colorpicker-yellow =
.title = Grogu
pdfjs-editor-colorpicker-green =
.title = Birde
pdfjs-editor-colorpicker-blue =
.title = Biaitu
pdfjs-editor-colorpicker-pink =
.title = Rosa
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
pdfjs-editor-new-alt-text-missing-button-label = Mancat su testu alternativu
pdfjs-editor-new-alt-text-to-review-button-label = Revisiona su testu alternativu
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Creadu in automàticu: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Cunfiguratzione de su testu alternativu de is immàgines
pdfjs-image-alt-text-settings-button-label = Cunfiguratzione de su testu alternativu de is immàgines
pdfjs-editor-alt-text-settings-dialog-label = Cunfiguratzione de su testu alternativu de is immàgines
pdfjs-editor-alt-text-settings-automatic-title = Testu alternativu automàticu
pdfjs-editor-alt-text-settings-create-model-button-label = Crea testu alternativu in automàticu
pdfjs-editor-alt-text-settings-create-model-description = Cussìgiat descritziones pro agiudare a gente chi non podet bìdere s’immàgine o cando non benit carrigada.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Modellu de IA pro su testu alternativu ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Est esecutadu in locale in manera chi is datos tuos abarrent in privadu. Rechestu pro sa generatzione automàtica de testu alternativu.
pdfjs-editor-alt-text-settings-delete-model-button = Cantzella
pdfjs-editor-alt-text-settings-download-model-button = Iscàrriga
pdfjs-editor-alt-text-settings-downloading-model-button = Iscarrighende…
pdfjs-editor-alt-text-settings-editor-title = Editore de testu alternativu
pdfjs-editor-alt-text-settings-show-dialog-button-label = Mustra deretu s’editore de testu alternativu cando siat agiunta un’immàgine
pdfjs-editor-alt-text-settings-show-dialog-description = T’agiudat a assegurare chi totu is immàgines tuas tèngiant unu testu alternativu.
pdfjs-editor-alt-text-settings-close-button = Serra
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/scn/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-zoom-out-button =
.title = Cchiù nicu
pdfjs-zoom-out-button-label = Cchiù nicu
pdfjs-zoom-in-button =
.title = Cchiù granni
pdfjs-zoom-in-button-label = Cchiù granni
## Secondary toolbar and context menu
## Document properties dialog
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Vista web lesta:
pdfjs-document-properties-linearized-yes = Se
## Print
pdfjs-print-progress-close-button = Sfai
## Tooltips and alt text for side panel toolbar buttons
## Thumbnails panel item (tooltip and alt text for images)
## Find panel button title and messages
## Predefined zoom values
pdfjs-page-scale-width = Larghizza dâ pàggina
## PDF page
## Loading indicator messages
## Annotations
## Password
pdfjs-password-cancel-button = Sfai
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/sco/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Page Afore
pdfjs-previous-button-label = Previous
pdfjs-next-button =
.title = Page Efter
pdfjs-next-button-label = Neist
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Page
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = o { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } o { $pagesCount })
pdfjs-zoom-out-button =
.title = Zoom Oot
pdfjs-zoom-out-button-label = Zoom Oot
pdfjs-zoom-in-button =
.title = Zoom In
pdfjs-zoom-in-button-label = Zoom In
pdfjs-zoom-select =
.title = Zoom
pdfjs-presentation-mode-button =
.title = Flit tae Presentation Mode
pdfjs-presentation-mode-button-label = Presentation Mode
pdfjs-open-file-button =
.title = Open File
pdfjs-open-file-button-label = Open
pdfjs-print-button =
.title = Prent
pdfjs-print-button-label = Prent
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Tools
pdfjs-tools-button-label = Tools
pdfjs-first-page-button =
.title = Gang tae First Page
pdfjs-first-page-button-label = Gang tae First Page
pdfjs-last-page-button =
.title = Gang tae Lest Page
pdfjs-last-page-button-label = Gang tae Lest Page
pdfjs-page-rotate-cw-button =
.title = Rotate Clockwise
pdfjs-page-rotate-cw-button-label = Rotate Clockwise
pdfjs-page-rotate-ccw-button =
.title = Rotate Coonterclockwise
pdfjs-page-rotate-ccw-button-label = Rotate Coonterclockwise
pdfjs-cursor-text-select-tool-button =
.title = Enable Text Walin Tool
pdfjs-cursor-text-select-tool-button-label = Text Walin Tool
pdfjs-cursor-hand-tool-button =
.title = Enable Haun Tool
pdfjs-cursor-hand-tool-button-label = Haun Tool
pdfjs-scroll-vertical-button =
.title = Yaise Vertical Scrollin
pdfjs-scroll-vertical-button-label = Vertical Scrollin
pdfjs-scroll-horizontal-button =
.title = Yaise Horizontal Scrollin
pdfjs-scroll-horizontal-button-label = Horizontal Scrollin
pdfjs-scroll-wrapped-button =
.title = Yaise Wrapped Scrollin
pdfjs-scroll-wrapped-button-label = Wrapped Scrollin
pdfjs-spread-none-button =
.title = Dinnae jyn page spreids
pdfjs-spread-none-button-label = Nae Spreids
pdfjs-spread-odd-button =
.title = Jyn page spreids stertin wi odd-numbered pages
pdfjs-spread-odd-button-label = Odd Spreids
pdfjs-spread-even-button =
.title = Jyn page spreids stertin wi even-numbered pages
pdfjs-spread-even-button-label = Even Spreids
## Document properties dialog
pdfjs-document-properties-button =
.title = Document Properties…
pdfjs-document-properties-button-label = Document Properties…
pdfjs-document-properties-file-name = File nemme:
pdfjs-document-properties-file-size = File size:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = Title:
pdfjs-document-properties-author = Author:
pdfjs-document-properties-subject = Subjeck:
pdfjs-document-properties-keywords = Keywirds:
pdfjs-document-properties-creation-date = Date o Makkin:
pdfjs-document-properties-modification-date = Date o Chynges:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Makker:
pdfjs-document-properties-producer = PDF Producer:
pdfjs-document-properties-version = PDF Version:
pdfjs-document-properties-page-count = Page Coont:
pdfjs-document-properties-page-size = Page Size:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = portrait
pdfjs-document-properties-page-size-orientation-landscape = landscape
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letter
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Fast Wab View:
pdfjs-document-properties-linearized-yes = Aye
pdfjs-document-properties-linearized-no = Naw
pdfjs-document-properties-close-button = Sneck
## Print
pdfjs-print-progress-message = Reddin document fur prentin…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Stap
pdfjs-printing-not-supported = Tak tent: Prentin isnae richt supportit by this stravaiger.
pdfjs-printing-not-ready = Tak tent: The PDF isnae richt loadit fur prentin.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Toggle Sidebaur
pdfjs-toggle-sidebar-notification-button =
.title = Toggle Sidebaur (document conteens ootline/attachments/layers)
pdfjs-toggle-sidebar-button-label = Toggle Sidebaur
pdfjs-document-outline-button =
.title = Kythe Document Ootline (double-click fur tae oot-fauld/in-fauld aw items)
pdfjs-document-outline-button-label = Document Ootline
pdfjs-attachments-button =
.title = Kythe Attachments
pdfjs-attachments-button-label = Attachments
pdfjs-layers-button =
.title = Kythe Layers (double-click fur tae reset aw layers tae the staunart state)
pdfjs-layers-button-label = Layers
pdfjs-thumbs-button =
.title = Kythe Thumbnails
pdfjs-thumbs-button-label = Thumbnails
pdfjs-current-outline-item-button =
.title = Find Current Ootline Item
pdfjs-current-outline-item-button-label = Current Ootline Item
pdfjs-findbar-button =
.title = Find in Document
pdfjs-findbar-button-label = Find
pdfjs-additional-layers = Mair Layers
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Page { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Thumbnail o Page { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Find
.placeholder = Find in document…
pdfjs-find-previous-button =
.title = Airt oot the last time this phrase occurred
pdfjs-find-previous-button-label = Previous
pdfjs-find-next-button =
.title = Airt oot the neist time this phrase occurs
pdfjs-find-next-button-label = Neist
pdfjs-find-highlight-checkbox = Highlicht aw
pdfjs-find-match-case-checkbox-label = Match case
pdfjs-find-entire-word-checkbox-label = Hale Wirds
pdfjs-find-reached-top = Raxed tap o document, went on fae the dowp end
pdfjs-find-reached-bottom = Raxed end o document, went on fae the tap
pdfjs-find-not-found = Phrase no fund
## Predefined zoom values
pdfjs-page-scale-width = Page Width
pdfjs-page-scale-fit = Page Fit
pdfjs-page-scale-auto = Automatic Zoom
pdfjs-page-scale-actual = Actual Size
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Page { $page }
## Loading indicator messages
pdfjs-loading-error = An mishanter tuik place while loadin the PDF.
pdfjs-invalid-file-error = No suithfest or camshauchlet PDF file.
pdfjs-missing-file-error = PDF file tint.
pdfjs-unexpected-response-error = Unexpectit server repone.
pdfjs-rendering-error = A mishanter tuik place while renderin the page.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } Annotation]
## Password
pdfjs-password-label = Inpit the passwird fur tae open this PDF file.
pdfjs-password-invalid = Passwird no suithfest. Gonnae gie it anither shot.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Stap
pdfjs-web-fonts-disabled = Wab fonts are disabled: cannae yaise embeddit PDF fonts.
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/si/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = කලින් පිටුව
pdfjs-previous-button-label = කලින්
pdfjs-next-button =
.title = ඊළඟ පිටුව
pdfjs-next-button-label = ඊළඟ
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = පිටුව
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } / { $pagesCount })
pdfjs-zoom-out-button =
.title = කුඩාලනය
pdfjs-zoom-out-button-label = කුඩාලනය
pdfjs-zoom-in-button =
.title = විශාලනය
pdfjs-zoom-in-button-label = විශාලනය
pdfjs-zoom-select =
.title = විශාල කරන්න
pdfjs-presentation-mode-button =
.title = සමර්පණ ප්රකාරය වෙත මාරුවන්න
pdfjs-presentation-mode-button-label = සමර්පණ ප්රකාරය
pdfjs-open-file-button =
.title = ගොනුව අරින්න
pdfjs-open-file-button-label = අරින්න
pdfjs-print-button =
.title = මුද්රණය
pdfjs-print-button-label = මුද්රණය
pdfjs-save-button =
.title = සුරකින්න
pdfjs-save-button-label = සුරකින්න
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = බාගන්න
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = බාගන්න
pdfjs-bookmark-button-label = පවතින පිටුව
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = මෙවලම්
pdfjs-tools-button-label = මෙවලම්
pdfjs-first-page-button =
.title = මුල් පිටුවට යන්න
pdfjs-first-page-button-label = මුල් පිටුවට යන්න
pdfjs-last-page-button =
.title = අවසන් පිටුවට යන්න
pdfjs-last-page-button-label = අවසන් පිටුවට යන්න
pdfjs-cursor-text-select-tool-button =
.title = පෙළ තේරීමේ මෙවලම සබල කරන්න
pdfjs-cursor-text-select-tool-button-label = පෙළ තේරීමේ මෙවලම
pdfjs-cursor-hand-tool-button =
.title = අත් මෙවලම සබල කරන්න
pdfjs-cursor-hand-tool-button-label = අත් මෙවලම
pdfjs-scroll-page-button =
.title = පිටුව අනුචලනය භාවිතය
pdfjs-scroll-page-button-label = පිටුව අනුචලනය
pdfjs-scroll-vertical-button =
.title = සිරස් අනුචලනය භාවිතය
pdfjs-scroll-vertical-button-label = සිරස් අනුචලනය
pdfjs-scroll-horizontal-button =
.title = තිරස් අනුචලනය භාවිතය
pdfjs-scroll-horizontal-button-label = තිරස් අනුචලනය
## Document properties dialog
pdfjs-document-properties-button =
.title = ලේඛනයේ ගුණාංග…
pdfjs-document-properties-button-label = ලේඛනයේ ගුණාංග…
pdfjs-document-properties-file-name = ගොනුවේ නම:
pdfjs-document-properties-file-size = ගොනුවේ ප්රමාණය:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = කි.බ. { $size_kb } (බයිට { $size_b })
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = මෙ.බ. { $size_mb } (බයිට { $size_b })
pdfjs-document-properties-title = සිරැසිය:
pdfjs-document-properties-author = කතෘ:
pdfjs-document-properties-subject = මාතෘකාව:
pdfjs-document-properties-keywords = මූල පද:
pdfjs-document-properties-creation-date = සෑදූ දිනය:
pdfjs-document-properties-modification-date = සංශෝධිත දිනය:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = නිර්මාතෘ:
pdfjs-document-properties-producer = පීඩීඑෆ් සම්පාදක:
pdfjs-document-properties-version = පීඩීඑෆ් අනුවාදය:
pdfjs-document-properties-page-count = පිටු ගණන:
pdfjs-document-properties-page-size = පිටුවේ තරම:
pdfjs-document-properties-page-size-unit-inches = අඟල්
pdfjs-document-properties-page-size-unit-millimeters = මි.මී.
pdfjs-document-properties-page-size-orientation-portrait = සිරස්
pdfjs-document-properties-page-size-orientation-landscape = තිරස්
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width }×{ $height }{ $unit }{ $name }{ $orientation }
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = වේගවත් වියමන දැක්ම:
pdfjs-document-properties-linearized-yes = ඔව්
pdfjs-document-properties-linearized-no = නැහැ
pdfjs-document-properties-close-button = වසන්න
## Print
pdfjs-print-progress-message = මුද්රණය සඳහා ලේඛනය සූදානම් වෙමින්…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = අවලංගු කරන්න
pdfjs-printing-not-supported = අවවාදයයි: මෙම අතිරික්සුව මුද්රණය සඳහා හොඳින් සහාය නොදක්වයි.
pdfjs-printing-not-ready = අවවාදයයි: මුද්රණයට පීඩීඑෆ් ගොනුව සම්පූර්ණයෙන් පූරණය වී නැත.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-document-outline-button-label = ලේඛනයේ වටසන
pdfjs-attachments-button =
.title = ඇමුණුම් පෙන්වන්න
pdfjs-attachments-button-label = ඇමුණුම්
pdfjs-layers-button =
.title = ස්තර පෙන්වන්න (සියළු ස්තර පෙරනිමි තත්වයට යළි සැකසීමට දෙවරක් ඔබන්න)
pdfjs-layers-button-label = ස්තර
pdfjs-thumbs-button =
.title = සිඟිති රූ පෙන්වන්න
pdfjs-thumbs-button-label = සිඟිති රූ
pdfjs-findbar-button =
.title = ලේඛනයෙහි සොයන්න
pdfjs-findbar-button-label = සොයන්න
pdfjs-additional-layers = අතිරේක ස්තර
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = පිටුව { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = පිටුවේ සිඟිත රූව { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = සොයන්න
.placeholder = ලේඛනයේ සොයන්න…
pdfjs-find-previous-button =
.title = මෙම වැකිකඩ කලින් යෙදුණු ස්ථානය සොයන්න
pdfjs-find-previous-button-label = කලින්
pdfjs-find-next-button =
.title = මෙම වැකිකඩ ඊළඟට යෙදෙන ස්ථානය සොයන්න
pdfjs-find-next-button-label = ඊළඟ
pdfjs-find-highlight-checkbox = සියල්ල උද්දීපනය
pdfjs-find-entire-word-checkbox-label = සමස්ත වචන
pdfjs-find-reached-top = ලේඛනයේ මුදුනට ළඟා විය, පහළ සිට ඉහළට
pdfjs-find-reached-bottom = ලේඛනයේ අවසානයට ළඟා විය, ඉහළ සිට පහළට
pdfjs-find-not-found = වැකිකඩ හමු නොවුණි
## Predefined zoom values
pdfjs-page-scale-width = පිටුවේ පළල
pdfjs-page-scale-auto = ස්වයංක්රීය විශාලනය
pdfjs-page-scale-actual = සැබෑ ප්රමාණය
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = පිටුව { $page }
## Loading indicator messages
pdfjs-loading-error = පීඩීඑෆ් පූරණය කිරීමේදී දෝෂයක් සිදු විය.
pdfjs-invalid-file-error = වලංගු නොවන හෝ හානිවූ පීඩීඑෆ් ගොනුවකි.
pdfjs-missing-file-error = මඟහැරුණු පීඩීඑෆ් ගොනුවකි.
pdfjs-unexpected-response-error = අනපේක්ෂිත සේවාදායක ප්රතිචාරයකි.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
## Password
pdfjs-password-label = මෙම පීඩීඑෆ් ගොනුව විවෘත කිරීමට මුරපදය යොදන්න.
pdfjs-password-invalid = වැරදි මුරපදයකි. නැවත උත්සාහ කරන්න.
pdfjs-password-ok-button = හරි
pdfjs-password-cancel-button = අවලංගු
pdfjs-web-fonts-disabled = වියමන අකුරු අබලයි: පීඩීඑෆ් වෙත කාවැද්දූ රුවකුරු භාවිතා කළ නොහැකිය.
## Editing
pdfjs-editor-free-text-button =
.title = පෙළ
pdfjs-editor-free-text-button-label = පෙළ
pdfjs-editor-ink-button =
.title = අඳින්න
pdfjs-editor-ink-button-label = අඳින්න
pdfjs-editor-stamp-button =
.title = රූප සංස්කරණය හෝ එක් කරන්න
pdfjs-editor-stamp-button-label = රූප සංස්කරණය හෝ එක් කරන්න
## Default editor aria labels
## Remove button for the various kind of editor.
##
# Editor Parameters
pdfjs-editor-free-text-color-input = වර්ණය
pdfjs-editor-free-text-size-input = තරම
pdfjs-editor-ink-color-input = වර්ණය
pdfjs-editor-ink-thickness-input = ඝණකම
pdfjs-free-text =
.aria-label = වදන් සකසනය
pdfjs-free-text-default-content = ලිවීීම අරඹන්න…
## Alt-text dialog
pdfjs-editor-alt-text-mark-decorative-description = මෙය දාර හෝ දිය සලකුණු වැනි අලංකාර රූප සඳහා භාවිතා වේ.
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/sk/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Predchádzajúca strana
pdfjs-previous-button-label = Predchádzajúca
pdfjs-next-button =
.title = Nasledujúca strana
pdfjs-next-button-label = Nasledujúca
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Strana
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = z { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } z { $pagesCount })
pdfjs-zoom-out-button =
.title = Zmenšiť veľkosť
pdfjs-zoom-out-button-label = Zmenšiť veľkosť
pdfjs-zoom-in-button =
.title = Zväčšiť veľkosť
pdfjs-zoom-in-button-label = Zväčšiť veľkosť
pdfjs-zoom-select =
.title = Nastavenie veľkosti
pdfjs-presentation-mode-button =
.title = Prepnúť na režim prezentácie
pdfjs-presentation-mode-button-label = Režim prezentácie
pdfjs-open-file-button =
.title = Otvoriť súbor
pdfjs-open-file-button-label = Otvoriť
pdfjs-print-button =
.title = Tlačiť
pdfjs-print-button-label = Tlačiť
pdfjs-save-button =
.title = Uložiť
pdfjs-save-button-label = Uložiť
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Stiahnuť
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Stiahnuť
pdfjs-bookmark-button =
.title = Aktuálna stránka (zobraziť adresu URL z aktuálnej stránky)
pdfjs-bookmark-button-label = Aktuálna stránka
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Nástroje
pdfjs-tools-button-label = Nástroje
pdfjs-first-page-button =
.title = Prejsť na prvú stranu
pdfjs-first-page-button-label = Prejsť na prvú stranu
pdfjs-last-page-button =
.title = Prejsť na poslednú stranu
pdfjs-last-page-button-label = Prejsť na poslednú stranu
pdfjs-page-rotate-cw-button =
.title = Otočiť v smere hodinových ručičiek
pdfjs-page-rotate-cw-button-label = Otočiť v smere hodinových ručičiek
pdfjs-page-rotate-ccw-button =
.title = Otočiť proti smeru hodinových ručičiek
pdfjs-page-rotate-ccw-button-label = Otočiť proti smeru hodinových ručičiek
pdfjs-cursor-text-select-tool-button =
.title = Povoliť výber textu
pdfjs-cursor-text-select-tool-button-label = Výber textu
pdfjs-cursor-hand-tool-button =
.title = Povoliť nástroj ruka
pdfjs-cursor-hand-tool-button-label = Nástroj ruka
pdfjs-scroll-page-button =
.title = Použiť rolovanie po stránkach
pdfjs-scroll-page-button-label = Rolovanie po stránkach
pdfjs-scroll-vertical-button =
.title = Používať zvislé posúvanie
pdfjs-scroll-vertical-button-label = Zvislé posúvanie
pdfjs-scroll-horizontal-button =
.title = Používať vodorovné posúvanie
pdfjs-scroll-horizontal-button-label = Vodorovné posúvanie
pdfjs-scroll-wrapped-button =
.title = Použiť postupné posúvanie
pdfjs-scroll-wrapped-button-label = Postupné posúvanie
pdfjs-spread-none-button =
.title = Nezdružovať stránky
pdfjs-spread-none-button-label = Žiadne združovanie
pdfjs-spread-odd-button =
.title = Združí stránky a umiestni nepárne stránky vľavo
pdfjs-spread-odd-button-label = Združiť stránky (nepárne vľavo)
pdfjs-spread-even-button =
.title = Združí stránky a umiestni párne stránky vľavo
pdfjs-spread-even-button-label = Združiť stránky (párne vľavo)
## Document properties dialog
pdfjs-document-properties-button =
.title = Vlastnosti dokumentu…
pdfjs-document-properties-button-label = Vlastnosti dokumentu…
pdfjs-document-properties-file-name = Názov súboru:
pdfjs-document-properties-file-size = Veľkosť súboru:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } kB ({ $b } bajtov)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bajtov)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } kB ({ $size_b } bajtov)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bajtov)
pdfjs-document-properties-title = Názov:
pdfjs-document-properties-author = Autor:
pdfjs-document-properties-subject = Predmet:
pdfjs-document-properties-keywords = Kľúčové slová:
pdfjs-document-properties-creation-date = Dátum vytvorenia:
pdfjs-document-properties-modification-date = Dátum úpravy:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Aplikácia:
pdfjs-document-properties-producer = Tvorca PDF:
pdfjs-document-properties-version = Verzia PDF:
pdfjs-document-properties-page-count = Počet strán:
pdfjs-document-properties-page-size = Veľkosť stránky:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = na výšku
pdfjs-document-properties-page-size-orientation-landscape = na šírku
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = List
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Rýchle zobrazovanie z webu:
pdfjs-document-properties-linearized-yes = Áno
pdfjs-document-properties-linearized-no = Nie
pdfjs-document-properties-close-button = Zavrieť
## Print
pdfjs-print-progress-message = Príprava dokumentu na tlač…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress } %
pdfjs-print-progress-close-button = Zrušiť
pdfjs-printing-not-supported = Upozornenie: tlač nie je v tomto prehliadači plne podporovaná.
pdfjs-printing-not-ready = Upozornenie: súbor PDF nie je plne načítaný pre tlač.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Prepnúť bočný panel
pdfjs-toggle-sidebar-notification-button =
.title = Prepnúť bočný panel (dokument obsahuje osnovu/prílohy/vrstvy)
pdfjs-toggle-sidebar-button-label = Prepnúť bočný panel
pdfjs-document-outline-button =
.title = Zobraziť osnovu dokumentu (dvojitým kliknutím rozbalíte/zbalíte všetky položky)
pdfjs-document-outline-button-label = Osnova dokumentu
pdfjs-attachments-button =
.title = Zobraziť prílohy
pdfjs-attachments-button-label = Prílohy
pdfjs-layers-button =
.title = Zobraziť vrstvy (dvojitým kliknutím uvediete všetky vrstvy do pôvodného stavu)
pdfjs-layers-button-label = Vrstvy
pdfjs-thumbs-button =
.title = Zobraziť miniatúry
pdfjs-thumbs-button-label = Miniatúry
pdfjs-current-outline-item-button =
.title = Nájsť aktuálnu položku v osnove
pdfjs-current-outline-item-button-label = Aktuálna položka v osnove
pdfjs-findbar-button =
.title = Hľadať v dokumente
pdfjs-findbar-button-label = Hľadať
pdfjs-additional-layers = Ďalšie vrstvy
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Strana { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Miniatúra strany { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Hľadať
.placeholder = Hľadať v dokumente…
pdfjs-find-previous-button =
.title = Vyhľadať predchádzajúci výskyt reťazca
pdfjs-find-previous-button-label = Predchádzajúce
pdfjs-find-next-button =
.title = Vyhľadať ďalší výskyt reťazca
pdfjs-find-next-button-label = Ďalšie
pdfjs-find-highlight-checkbox = Zvýrazniť všetky
pdfjs-find-match-case-checkbox-label = Rozlišovať veľkosť písmen
pdfjs-find-match-diacritics-checkbox-label = Rozlišovať diakritiku
pdfjs-find-entire-word-checkbox-label = Celé slová
pdfjs-find-reached-top = Bol dosiahnutý začiatok stránky, pokračuje sa od konca
pdfjs-find-reached-bottom = Bol dosiahnutý koniec stránky, pokračuje sa od začiatku
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] Výskyt { $current } z { $total }
[few] Výskyt { $current } z { $total }
[many] Výskyt { $current } z { $total }
*[other] Výskyt { $current } z { $total }
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Viac ako { $limit } výskyt
[few] Viac ako { $limit } výskyty
[many] Viac ako { $limit } výskytov
*[other] Viac ako { $limit } výskytov
}
pdfjs-find-not-found = Výraz nebol nájdený
## Predefined zoom values
pdfjs-page-scale-width = Na šírku strany
pdfjs-page-scale-fit = Na veľkosť strany
pdfjs-page-scale-auto = Automatická veľkosť
pdfjs-page-scale-actual = Skutočná veľkosť
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale } %
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Strana { $page }
## Loading indicator messages
pdfjs-loading-error = Počas načítavania dokumentu PDF sa vyskytla chyba.
pdfjs-invalid-file-error = Neplatný alebo poškodený súbor PDF.
pdfjs-missing-file-error = Chýbajúci súbor PDF.
pdfjs-unexpected-response-error = Neočakávaná odpoveď zo servera.
pdfjs-rendering-error = Pri vykresľovaní stránky sa vyskytla chyba.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [Anotácia typu { $type }]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Ak chcete otvoriť tento súbor PDF, zadajte jeho heslo.
pdfjs-password-invalid = Heslo nie je platné. Skúste to znova.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Zrušiť
pdfjs-web-fonts-disabled = Webové písma sú vypnuté: nie je možné použiť písma vložené do súboru PDF.
## Editing
pdfjs-editor-free-text-button =
.title = Text
pdfjs-editor-free-text-button-label = Text
pdfjs-editor-ink-button =
.title = Kresliť
pdfjs-editor-ink-button-label = Kresliť
pdfjs-editor-stamp-button =
.title = Pridať alebo upraviť obrázky
pdfjs-editor-stamp-button-label = Pridať alebo upraviť obrázky
pdfjs-editor-highlight-button =
.title = Zvýrazniť
pdfjs-editor-highlight-button-label = Zvýrazniť
pdfjs-highlight-floating-button1 =
.title = Zvýrazniť
.aria-label = Zvýrazniť
pdfjs-highlight-floating-button-label = Zvýrazniť
pdfjs-editor-signature-button =
.title = Pridať podpis
pdfjs-editor-signature-button-label = Pridať podpis
## Default editor aria labels
# “Highlight” is a noun, the string is used on the editor for highlights.
pdfjs-editor-highlight-editor =
.aria-label = Editor zvýraznenia
# “Drawing” is a noun, the string is used on the editor for drawings.
pdfjs-editor-ink-editor =
.aria-label = Editor kreslenia
pdfjs-editor-signature-editor =
.aria-label = Editor podpisov
pdfjs-editor-stamp-editor =
.aria-label = Editor obrázkov
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Odstrániť kresbu
pdfjs-editor-remove-freetext-button =
.title = Odstrániť text
pdfjs-editor-remove-stamp-button =
.title = Odstrániť obrázok
pdfjs-editor-remove-highlight-button =
.title = Odstrániť zvýraznenie
pdfjs-editor-remove-signature-button =
.title = Odstrániť podpis
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Farba
pdfjs-editor-free-text-size-input = Veľkosť
pdfjs-editor-ink-color-input = Farba
pdfjs-editor-ink-thickness-input = Hrúbka
pdfjs-editor-ink-opacity-input = Priehľadnosť
pdfjs-editor-stamp-add-image-button =
.title = Pridať obrázok
pdfjs-editor-stamp-add-image-button-label = Pridať obrázok
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Hrúbka
pdfjs-editor-free-highlight-thickness-title =
.title = Zmeňte hrúbku pre zvýrazňovanie iných položiek ako textu
pdfjs-editor-add-signature-container =
.aria-label = Ovládacie prvky pre podpisy a uložené podpisy
pdfjs-editor-signature-add-signature-button =
.title = Pridať nový podpis
pdfjs-editor-signature-add-signature-button-label = Pridať nový podpis
# Used on the button to use an already saved signature.
# Variables:
# $description (String) - a string describing/labeling the signature.
pdfjs-editor-add-saved-signature-button =
.title = Uložený podpis: { $description }
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Textový editor
.default-content = Začnite písať…
pdfjs-free-text =
.aria-label = Textový editor
pdfjs-free-text-default-content = Začnite písať…
pdfjs-ink =
.aria-label = Editor kreslenia
pdfjs-ink-canvas =
.aria-label = Obrázok vytvorený používateľom
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Alternatívny text
pdfjs-editor-alt-text-edit-button =
.aria-label = Upraviť alternatívny text
pdfjs-editor-alt-text-edit-button-label = Upraviť alternatívny text
pdfjs-editor-alt-text-dialog-label = Vyberte možnosť
pdfjs-editor-alt-text-dialog-description = Alternatívny text (alt text) pomáha, keď ľudia obrázok nevidia alebo sa nenačítava.
pdfjs-editor-alt-text-add-description-label = Pridať popis
pdfjs-editor-alt-text-add-description-description = Zamerajte sa na 1-2 vety, ktoré popisujú predmet, prostredie alebo akcie.
pdfjs-editor-alt-text-mark-decorative-label = Označiť ako dekoratívny
pdfjs-editor-alt-text-mark-decorative-description = Používa sa na ozdobné obrázky, ako sú okraje alebo vodoznaky.
pdfjs-editor-alt-text-cancel-button = Zrušiť
pdfjs-editor-alt-text-save-button = Uložiť
pdfjs-editor-alt-text-decorative-tooltip = Označený ako dekoratívny
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Napríklad: „Mladý muž si sadá za stôl, aby sa najedol“
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Alternatívny text
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Ľavý horný roh – zmena veľkosti
pdfjs-editor-resizer-label-top-middle = Horný stred – zmena veľkosti
pdfjs-editor-resizer-label-top-right = Pravý horný roh – zmena veľkosti
pdfjs-editor-resizer-label-middle-right = Vpravo uprostred – zmena veľkosti
pdfjs-editor-resizer-label-bottom-right = Pravý dolný roh – zmena veľkosti
pdfjs-editor-resizer-label-bottom-middle = Stred dole – zmena veľkosti
pdfjs-editor-resizer-label-bottom-left = Ľavý dolný roh – zmena veľkosti
pdfjs-editor-resizer-label-middle-left = Vľavo uprostred – zmena veľkosti
pdfjs-editor-resizer-top-left =
.aria-label = Ľavý horný roh – zmena veľkosti
pdfjs-editor-resizer-top-middle =
.aria-label = Horný stred – zmena veľkosti
pdfjs-editor-resizer-top-right =
.aria-label = Pravý horný roh – zmena veľkosti
pdfjs-editor-resizer-middle-right =
.aria-label = Vpravo uprostred – zmena veľkosti
pdfjs-editor-resizer-bottom-right =
.aria-label = Pravý dolný roh – zmena veľkosti
pdfjs-editor-resizer-bottom-middle =
.aria-label = Stred dole – zmena veľkosti
pdfjs-editor-resizer-bottom-left =
.aria-label = Ľavý dolný roh – zmena veľkosti
pdfjs-editor-resizer-middle-left =
.aria-label = Vľavo uprostred – zmena veľkosti
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Farba zvýraznenia
pdfjs-editor-colorpicker-button =
.title = Zmeniť farbu
pdfjs-editor-colorpicker-dropdown =
.aria-label = Výber farieb
pdfjs-editor-colorpicker-yellow =
.title = Žltá
pdfjs-editor-colorpicker-green =
.title = Zelená
pdfjs-editor-colorpicker-blue =
.title = Modrá
pdfjs-editor-colorpicker-pink =
.title = Ružová
pdfjs-editor-colorpicker-red =
.title = Červená
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Zobraziť všetko
pdfjs-editor-highlight-show-all-button =
.title = Zobraziť všetko
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Upraviť alternatívny text (popis obrázka)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Pridať alternatívny text (popis obrázka)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Sem napíšte svoj popis…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Krátky popis pre ľudí, ktorí nevidia obrázok alebo ak sa obrázok nenačíta.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Tento alternatívny text bol vytvorený automaticky a môže byť nepresný.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Ďalšie informácie
pdfjs-editor-new-alt-text-create-automatically-button-label = Automaticky vytvoriť alternatívny text
pdfjs-editor-new-alt-text-not-now-button = Teraz nie
pdfjs-editor-new-alt-text-error-title = Alternatívny text sa nepodarilo vytvoriť automaticky
pdfjs-editor-new-alt-text-error-description = Napíšte svoj vlastný alternatívny text alebo to skúste znova neskôr.
pdfjs-editor-new-alt-text-error-close-button = Zavrieť
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Sťahuje sa model AI pre alternatívne texty ({ $downloadedSize } z { $totalSize } MB)
.aria-valuetext = Sťahuje sa model AI pre alternatívne texty ({ $downloadedSize } z { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Alternatívny text bol pridaný
pdfjs-editor-new-alt-text-added-button-label = Alternatívny text bol pridaný
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Chýbajúci alternatívny text
pdfjs-editor-new-alt-text-missing-button-label = Chýbajúci alternatívny text
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Skontrolovať alternatívny text
pdfjs-editor-new-alt-text-to-review-button-label = Skontrolovať alternatívny text
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Vytvorené automaticky: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Nastavenia alternatívneho textu obrázka
pdfjs-image-alt-text-settings-button-label = Nastavenia alternatívneho textu obrázka
pdfjs-editor-alt-text-settings-dialog-label = Nastavenia alternatívneho textu obrázka
pdfjs-editor-alt-text-settings-automatic-title = Automatický alternatívny text
pdfjs-editor-alt-text-settings-create-model-button-label = Automaticky vytvoriť alternatívny text
pdfjs-editor-alt-text-settings-create-model-description = Navrhuje popisy, ktoré pomôžu ľuďom, ktorým sa obrázok nezobrazuje alebo ak sa obrázok nenačíta.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Model AI pre alternatívne texty ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Beží lokálne na vašom zariadení, takže vaše dáta zostanú súkromné. Vyžaduje sa pre automatický alternatívny text.
pdfjs-editor-alt-text-settings-delete-model-button = Odstrániť
pdfjs-editor-alt-text-settings-download-model-button = Stiahnuť
pdfjs-editor-alt-text-settings-downloading-model-button = Sťahuje sa…
pdfjs-editor-alt-text-settings-editor-title = Editor alternatívneho textu
pdfjs-editor-alt-text-settings-show-dialog-button-label = Pri pridávaní obrázka ihneď zobraziť editor alternatívneho textu
pdfjs-editor-alt-text-settings-show-dialog-description = Pomáha vám zabezpečiť, aby všetky vaše obrázky mali alternatívny text.
pdfjs-editor-alt-text-settings-close-button = Zavrieť
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Zvýraznenie bolo odstránené
pdfjs-editor-undo-bar-message-freetext = Text bol odstránený
pdfjs-editor-undo-bar-message-ink = Kreslenie bolo odstránené
pdfjs-editor-undo-bar-message-stamp = Obrázok bol odstránený
pdfjs-editor-undo-bar-message-signature = Podpis bol odstránený
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } anotácia odstránená
[few] { $count } anotácie odstránené
[many] { $count } anotácií odstránených
*[other] { $count } anotácií odstránených
}
pdfjs-editor-undo-bar-undo-button =
.title = Späť
pdfjs-editor-undo-bar-undo-button-label = Späť
pdfjs-editor-undo-bar-close-button =
.title = Zavrieť
pdfjs-editor-undo-bar-close-button-label = Zavrieť
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = Toto okno umožňuje používateľovi vytvoriť podpis, ktorý sa pridá do dokumentu PDF. Používateľ môže upraviť meno (ktoré zároveň slúži ako alternatívny text) a voliteľne uložiť podpis, ak ho plánuje v budúcnosti znova použiť.
pdfjs-editor-add-signature-dialog-title = Pridať podpis
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Typ
.title = Typ
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Kresliť
.title = Kresliť
pdfjs-editor-add-signature-image-button = Obrázok
.title = Obrázok
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Zadajte svoj podpis
.placeholder = Zadajte svoj podpis
pdfjs-editor-add-signature-draw-placeholder = Nakreslite svoj podpis
pdfjs-editor-add-signature-draw-thickness-range-label = Hrúbka
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Hrúbka ceruzky: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Sem presuňte súbor, ktorý chcete nahrať
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Alebo vyberte súbor s obrázkom
*[other] Alebo vyberte súbor s obrázkom
}
## Controls
pdfjs-editor-add-signature-description-label = Popis (alternatívny text)
pdfjs-editor-add-signature-description-input =
.title = Popis (alternatívny text)
pdfjs-editor-add-signature-description-default-when-drawing = Podpis
pdfjs-editor-add-signature-clear-button-label = Vymazať podpis
pdfjs-editor-add-signature-clear-button =
.title = Vymazať podpis
pdfjs-editor-add-signature-save-checkbox = Uložiť podpis
pdfjs-editor-add-signature-save-warning-message = Dosiahli ste limit 5 uložených podpisov. Ak chcete uložiť ďalší, jeden odstráňte.
pdfjs-editor-add-signature-image-upload-error-title = Obrázok sa nepodarilo nahrať
pdfjs-editor-add-signature-image-upload-error-description = Skontrolujte sieťové pripojenie alebo skúste iný obrázok.
pdfjs-editor-add-signature-error-close-button = Zavrieť
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Zrušiť
pdfjs-editor-add-signature-add-button = Pridať
pdfjs-editor-edit-signature-update-button = Aktualizovať
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Odstrániť podpis
pdfjs-editor-delete-signature-button-label = Odstrániť podpis
pdfjs-editor-delete-signature-button1 =
.title = Odstrániť uložený podpis
pdfjs-editor-delete-signature-button-label1 = Odstrániť uložený podpis
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Upraviť popis
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Upraviť popis
================================================
FILE: cookbook/static/pdfjs/web/locale/skr/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = پچھلا ورقہ
pdfjs-previous-button-label = پچھلا
pdfjs-next-button =
.title = اڳلا ورقہ
pdfjs-next-button-label = اڳلا
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = ورقہ
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = { $pagesCount } دا
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } دا { $pagesCount })
pdfjs-zoom-out-button =
.title = زوم آؤٹ
pdfjs-zoom-out-button-label = زوم آؤٹ
pdfjs-zoom-in-button =
.title = زوم اِن
pdfjs-zoom-in-button-label = زوم اِن
pdfjs-zoom-select =
.title = زوم
pdfjs-presentation-mode-button =
.title = پریزنٹیشن موڈ تے سوئچ کرو
pdfjs-presentation-mode-button-label = پریزنٹیشن موڈ
pdfjs-open-file-button =
.title = فائل کھولو
pdfjs-open-file-button-label = کھولو
pdfjs-print-button =
.title = چھاپو
pdfjs-print-button-label = چھاپو
pdfjs-save-button =
.title = ہتھیکڑا کرو
pdfjs-save-button-label = ہتھیکڑا کرو
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = ڈاؤن لوڈ
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = ڈاؤن لوڈ
pdfjs-bookmark-button =
.title = موجودہ ورقہ (موجودہ ورقے کنوں یوآرایل ݙیکھو)
pdfjs-bookmark-button-label = موجودہ ورقہ
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = اوزار
pdfjs-tools-button-label = اوزار
pdfjs-first-page-button =
.title = پہلے ورقے تے ونڄو
pdfjs-first-page-button-label = پہلے ورقے تے ونڄو
pdfjs-last-page-button =
.title = چھیکڑی ورقے تے ونڄو
pdfjs-last-page-button-label = چھیکڑی ورقے تے ونڄو
pdfjs-page-rotate-cw-button =
.title = گھڑی وانگوں گھماؤ
pdfjs-page-rotate-cw-button-label = گھڑی وانگوں گھماؤ
pdfjs-page-rotate-ccw-button =
.title = گھڑی تے اُپٹھ گھماؤ
pdfjs-page-rotate-ccw-button-label = گھڑی تے اُپٹھ گھماؤ
pdfjs-cursor-text-select-tool-button =
.title = متن منتخب کݨ والا آلہ فعال بݨاؤ
pdfjs-cursor-text-select-tool-button-label = متن منتخب کرݨ والا آلہ
pdfjs-cursor-hand-tool-button =
.title = ہینڈ ٹول فعال بݨاؤ
pdfjs-cursor-hand-tool-button-label = ہینڈ ٹول
pdfjs-scroll-page-button =
.title = پیج سکرولنگ استعمال کرو
pdfjs-scroll-page-button-label = پیج سکرولنگ
pdfjs-scroll-vertical-button =
.title = عمودی سکرولنگ استعمال کرو
pdfjs-scroll-vertical-button-label = عمودی سکرولنگ
pdfjs-scroll-horizontal-button =
.title = افقی سکرولنگ استعمال کرو
pdfjs-scroll-horizontal-button-label = افقی سکرولنگ
pdfjs-scroll-wrapped-button =
.title = ویڑھی ہوئی سکرولنگ استعمال کرو
pdfjs-scroll-wrapped-button-label = وہڑھی ہوئی سکرولنگ
pdfjs-spread-none-button =
.title = پیج سپریڈز وِچ شامل نہ تھیوو۔
pdfjs-spread-none-button-label = کوئی پولھ کائنی
pdfjs-spread-odd-button =
.title = طاق نمبر والے ورقیاں دے نال شروع تھیوݨ والے پیج سپریڈز وِچ شامل تھیوو۔
pdfjs-spread-odd-button-label = تاک پھیلاؤ
pdfjs-spread-even-button =
.title = جفت نمر والے ورقیاں نال شروع تھیوݨ والے پیج سپریڈز وِ شامل تھیوو۔
pdfjs-spread-even-button-label = جفت پھیلاؤ
## Document properties dialog
pdfjs-document-properties-button =
.title = دستاویز خواص…
pdfjs-document-properties-button-label = دستاویز خواص …
pdfjs-document-properties-file-name = فائل دا ناں:
pdfjs-document-properties-file-size = فائل دا سائز:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } بائٹاں)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } بائٹاں)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } کے بی ({ $size_b } بائٹس)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } ایم بی ({ $size_b } بائٹس)
pdfjs-document-properties-title = عنوان:
pdfjs-document-properties-author = تخلیق کار:
pdfjs-document-properties-subject = موضوع:
pdfjs-document-properties-keywords = کلیدی الفاظ:
pdfjs-document-properties-creation-date = تخلیق دی تاریخ:
pdfjs-document-properties-modification-date = ترمیم دی تاریخ:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = تخلیق کار:
pdfjs-document-properties-producer = PDF پیدا کار:
pdfjs-document-properties-version = PDF ورژن:
pdfjs-document-properties-page-count = ورقہ شماری:
pdfjs-document-properties-page-size = ورقہ دی سائز:
pdfjs-document-properties-page-size-unit-inches = وِچ
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = عمودی انداز
pdfjs-document-properties-page-size-orientation-landscape = افقى انداز
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = لیٹر
pdfjs-document-properties-page-size-name-legal = قنونی
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = تکھا ویب نظارہ:
pdfjs-document-properties-linearized-yes = جیا
pdfjs-document-properties-linearized-no = کو
pdfjs-document-properties-close-button = بند کرو
## Print
pdfjs-print-progress-message = چھاپݨ کیتے دستاویز تیار تھیندے پئے ہن …
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = منسوخ کرو
pdfjs-printing-not-supported = چتاوݨی: چھپائی ایں براؤزر تے پوری طراں معاونت شدہ کائنی۔
pdfjs-printing-not-ready = چتاوݨی: PDF چھپائی کیتے پوری طراں لوڈ نئیں تھئی۔
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = سائیڈ بار ٹوگل کرو
pdfjs-toggle-sidebar-notification-button =
.title = سائیڈ بار ٹوگل کرو (دستاویز وِچ آؤٹ لائن/ منسلکات/ پرتاں شامل ہن)
pdfjs-toggle-sidebar-button-label = سائیڈ بار ٹوگل کرو
pdfjs-document-outline-button =
.title = دستاویز دا خاکہ ݙکھاؤ (تمام آئٹمز کوں پھیلاوݨ/سنگوڑݨ کیتے ڈبل کلک کرو)
pdfjs-document-outline-button-label = دستاویز آؤٹ لائن
pdfjs-attachments-button =
.title = نتھیاں ݙکھاؤ
pdfjs-attachments-button-label = منسلکات
pdfjs-layers-button =
.title = پرتاں ݙکھاؤ (تمام پرتاں کوں ڈیفالٹ حالت وِچ دوبارہ ترتیب ݙیوݨ کیتے ڈبل کلک کرو)
pdfjs-layers-button-label = پرتاں
pdfjs-thumbs-button =
.title = تھمبنیل ݙکھاؤ
pdfjs-thumbs-button-label = تھمبنیلز
pdfjs-current-outline-item-button =
.title = موجودہ آؤٹ لائن آئٹم لبھو
pdfjs-current-outline-item-button-label = موجودہ آؤٹ لائن آئٹم
pdfjs-findbar-button =
.title = دستاویز وِچ لبھو
pdfjs-findbar-button-label = لبھو
pdfjs-additional-layers = اضافی پرتاں
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = ورقہ { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = ورقے دا تھمبنیل { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = لبھو
.placeholder = دستاویز وِچ لبھو …
pdfjs-find-previous-button =
.title = فقرے دا پچھلا واقعہ لبھو
pdfjs-find-previous-button-label = پچھلا
pdfjs-find-next-button =
.title = فقرے دا اڳلا واقعہ لبھو
pdfjs-find-next-button-label = اڳلا
pdfjs-find-highlight-checkbox = تمام نشابر کرو
pdfjs-find-match-case-checkbox-label = حروف مشابہ کرو
pdfjs-find-match-diacritics-checkbox-label = ڈائیکرٹکس مشابہ کرو
pdfjs-find-entire-word-checkbox-label = تمام الفاظ
pdfjs-find-reached-top = ورقے دے شروع تے پُج ڳیا، تلوں جاری کیتا ڳیا
pdfjs-find-reached-bottom = ورقے دے پاند تے پُڄ ڳیا، اُتوں شروع کیتا ڳیا
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $total } وِچوں { $current } مشابہ
*[other] { $total } وِچوں { $current } مشابے
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] { $limit } توں ودھ مماثلت۔
*[other] { $limit } توں ودھ مماثلتاں۔
}
pdfjs-find-not-found = فقرہ نئیں ملیا
## Predefined zoom values
pdfjs-page-scale-width = ورقے دی چوڑائی
pdfjs-page-scale-fit = ورقہ فٹنگ
pdfjs-page-scale-auto = آپوں آپ زوم
pdfjs-page-scale-actual = اصل میچا
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = ورقہ { $page }
## Loading indicator messages
pdfjs-loading-error = PDF لوڈ کریندے ویلھے نقص آ ڳیا۔
pdfjs-invalid-file-error = غلط یا خراب شدہ PDF فائل۔
pdfjs-missing-file-error = PDF فائل غائب ہے۔
pdfjs-unexpected-response-error = سرور دا غیر متوقع جواب۔
pdfjs-rendering-error = ورقہ رینڈر کریندے ویلھے ہک خرابی پیش آڳئی۔
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } تشریح]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = ایہ PDF فائل کھولݨ کیتے پاس ورڈ درج کرو۔
pdfjs-password-invalid = غلط پاس ورڈ: براہ مہربانی ولدا کوشش کرو۔
pdfjs-password-ok-button = ٹھیک ہے
pdfjs-password-cancel-button = منسوخ کرو
pdfjs-web-fonts-disabled = ویب فونٹس غیر فعال ہن: ایمبیڈڈ PDF فونٹس استعمال کرݨ کنوں قاصر ہن
## Editing
pdfjs-editor-free-text-button =
.title = متن
pdfjs-editor-free-text-button-label = متن
pdfjs-editor-ink-button =
.title = چھکو
pdfjs-editor-ink-button-label = چھکو
pdfjs-editor-stamp-button =
.title = تصویراں کوں شامل کرو یا ترمیم کرو
pdfjs-editor-stamp-button-label = تصویراں کوں شامل کرو یا ترمیم کرو
pdfjs-editor-highlight-button =
.title = نمایاں کرو
pdfjs-editor-highlight-button-label = نمایاں کرو
pdfjs-highlight-floating-button1 =
.title = نمایاں کرو
.aria-label = نمایاں کرو
pdfjs-highlight-floating-button-label = نمایاں کرو
## Default editor aria labels
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = ڈرائینگ ہٹاؤ
pdfjs-editor-remove-freetext-button =
.title = متن ہٹاؤ
pdfjs-editor-remove-stamp-button =
.title = تصویر ہٹاؤ
pdfjs-editor-remove-highlight-button =
.title = نمایاں ہٹاؤ
##
# Editor Parameters
pdfjs-editor-free-text-color-input = رنگ
pdfjs-editor-free-text-size-input = سائز
pdfjs-editor-ink-color-input = رنگ
pdfjs-editor-ink-thickness-input = ٹھولھ
pdfjs-editor-ink-opacity-input = دھندلاپن
pdfjs-editor-stamp-add-image-button =
.title = تصویر شامل کرو
pdfjs-editor-stamp-add-image-button-label = تصویر شامل کرو
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = مُٹاݨ
pdfjs-editor-free-highlight-thickness-title =
.title = متن توں ان٘ج ٻئے شئیں کوں نمایاں کرݨ ویلے مُٹاݨ کوں بدلو
pdfjs-free-text =
.aria-label = ٹیکسٹ ایڈیٹر
pdfjs-free-text-default-content = ٹائپنگ شروع کرو …
pdfjs-ink =
.aria-label = ڈرا ایڈیٹر
pdfjs-ink-canvas =
.aria-label = صارف دی بݨائی ہوئی تصویر
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Alt متن
pdfjs-editor-alt-text-edit-button-label = alt متن وِچ ترمیم کرو
pdfjs-editor-alt-text-dialog-label = ہِک اختیار چُݨو
pdfjs-editor-alt-text-dialog-description = Alt متن (متبادل متن) اِیں ویلے مَدَت کرین٘دا ہِے جہڑیلے لوک تصویر کوں نِھیں ݙیکھ سڳدے یا جہڑیلے اِیہ لوڈ کائنی تِھین٘دا۔
pdfjs-editor-alt-text-add-description-label = تفصیل شامل کرو
pdfjs-editor-alt-text-add-description-description = 1-2 جملیاں دا مقصد جہڑے موضوع، ترتیب، یا اعمال کوں بیان کرین٘دے ہِن۔
pdfjs-editor-alt-text-mark-decorative-label = آرائشی طور تے نشان زد کرو
pdfjs-editor-alt-text-mark-decorative-description = اِیہ آرائشی تصویراں کِیتے استعمال تِھین٘دا ہِے، جیویں بارڈر یا واٹر مارکس۔
pdfjs-editor-alt-text-cancel-button = منسوخ
pdfjs-editor-alt-text-save-button = محفوظ
pdfjs-editor-alt-text-decorative-tooltip = آرائشی دے طور تے نشان زد تِھی ڳِیا
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = مثال دے طور تے، "ہِک جؤان کھاݨاں کھاوݨ کِیتے میز اُتّے ٻیٹھا ہِے"
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Alt متن
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = اُتلی کَھٻّی نُکّڑ — سائز بدلو
pdfjs-editor-resizer-label-top-middle = اُتلا وِچلا — سائز بدلو
pdfjs-editor-resizer-label-top-right = اُتلی سَڄّی نُکَّڑ — سائز بدلو
pdfjs-editor-resizer-label-middle-right = وِچلا سڄّا — سائز بدلو
pdfjs-editor-resizer-label-bottom-right = تلوِیں سَڄّی نُکَّڑ — سائز بدلو
pdfjs-editor-resizer-label-bottom-middle = تلواں وِچلا — سائز بدلو
pdfjs-editor-resizer-label-bottom-left = تلوِیں کَھٻّی نُکّڑ — سائز بدلو
pdfjs-editor-resizer-label-middle-left = وِچلا کَھٻّا — سائز بدلو
pdfjs-editor-resizer-top-left =
.aria-label = اُتلی کَھٻّی نُکّڑ — سائز بدلو
pdfjs-editor-resizer-top-middle =
.aria-label = اُتلا وِچلا — سائز بدلو
pdfjs-editor-resizer-top-right =
.aria-label = اُتلی سَڄّی نُکَّڑ — سائز بدلو
pdfjs-editor-resizer-middle-right =
.aria-label = وِچلا سڄّا — سائز بدلو
pdfjs-editor-resizer-bottom-right =
.aria-label = تلوِیں سَڄّی نُکَّڑ — سائز بدلو
pdfjs-editor-resizer-bottom-middle =
.aria-label = تلواں وِچلا — سائز بدلو
pdfjs-editor-resizer-bottom-left =
.aria-label = تلوِیں کَھٻّی نُکّڑ — سائز بدلو
pdfjs-editor-resizer-middle-left =
.aria-label = وِچلا کَھٻّا — سائز بدلو
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = نشابر رنگ
pdfjs-editor-colorpicker-button =
.title = رنگ بدلو
pdfjs-editor-colorpicker-dropdown =
.aria-label = رنگ اختیارات
pdfjs-editor-colorpicker-yellow =
.title = پیلا
pdfjs-editor-colorpicker-green =
.title = ساوا
pdfjs-editor-colorpicker-blue =
.title = نیلا
pdfjs-editor-colorpicker-pink =
.title = گلابی
pdfjs-editor-colorpicker-red =
.title = لال
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = سارے ݙکھاؤ
pdfjs-editor-highlight-show-all-button =
.title = سارے ݙکھاؤ
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = آلٹ عبارت وچ تبدیلی کرو (تصویر تفصیل)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = آلٹ عبارت شامل کرو (تصویر تفصیل)
pdfjs-editor-new-alt-text-textarea =
.placeholder = اتھ آپݨی وضاحت لکھو۔۔۔
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = اُنہاں لوکاں کیتے مختصر تفصیل جہڑے تصویر کائنی ݙیکھ سڳدے یا ڄݙݨ تصویر لوڈ کائبی تھیندی۔
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = آلٹ عبارت خودکار تخلیق تھئی ہے تے غلط تھی سڳدی ہے۔
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = ٻیا سِکھو
pdfjs-editor-new-alt-text-create-automatically-button-label = آلٹ عبارت خودکار بݨاؤ
pdfjs-editor-new-alt-text-not-now-button = ہݨ کائناں
pdfjs-editor-new-alt-text-error-title = آلٹ عبارت خودکار نہ بݨاؤ
pdfjs-editor-new-alt-text-error-description = سوہݨا، آپݨی آلٹ عبارت لکھو یا ولدا بعد وچ کوشش کرو۔
pdfjs-editor-new-alt-text-error-close-button = بند کرو
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = آلٹ عبارت اے آئی ماڈل({ $totalSize }ایم بی دے { $downloadedSize }) ڈاؤن لوڈ تھیندا پئے
.aria-valuetext = آلٹ عبارت اے آئی ماڈل({ $totalSize }ایم بی دے { $downloadedSize }) ڈاؤن لوڈ تھیندا پئے
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = آلٹ عبارت شامل تھی ڳئی
pdfjs-editor-new-alt-text-added-button-label = آلٹ عبارت شامل تھی ڳئی
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = متبادل عبارت غائب ہے
pdfjs-editor-new-alt-text-missing-button-label = متبادل عبارت غائب ہے
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = alt متن تے نظرثانی کرو
pdfjs-editor-new-alt-text-to-review-button-label = alt متن تے نظرثانی کرو
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = خودکار تخلیق تھئی: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = تصویر آلٹ عبارت ترتیباں
pdfjs-image-alt-text-settings-button-label = تصویر آلٹ عبارت ترتیباں
pdfjs-editor-alt-text-settings-dialog-label = تصویر آلٹ عبارت ترتیباں
pdfjs-editor-alt-text-settings-automatic-title = خودکار آلٹ عبارت
pdfjs-editor-alt-text-settings-create-model-button-label = آلٹ عبارت خودکار بݨاؤ
pdfjs-editor-alt-text-settings-create-model-description = اُنہاں لوکاں دی مدد کیتے تفصیل تجویز کرو جہڑے تصویر کائنی ݙیکھ سڳدے یا ڄݙݨ تصویر لوڈ کائبی تھیندی۔
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = آلٹ عبارت اے آئی ماڈل ({ $totalSize } ایم بی)
pdfjs-editor-alt-text-settings-ai-model-description = تہاݙی ڈیوائس تے مقامی طور تے چلدا ہے تاں جو تہاݙا ڈیٹا نجی رہوے۔ خودکار آلٹ عبارت کیتے ضروری ہے۔
pdfjs-editor-alt-text-settings-delete-model-button = مٹاؤ
pdfjs-editor-alt-text-settings-download-model-button = ڈاؤن لوڈ
pdfjs-editor-alt-text-settings-downloading-model-button = ڈاؤن لوڈ تھیندا پئے …
pdfjs-editor-alt-text-settings-editor-title = متبادل ٹیکسٹ ایڈیٹر
pdfjs-editor-alt-text-settings-show-dialog-button-label = تصویر شامل کرݨ ویلے فوری طور تے آلٹ ٹیکسٹ ایڈیٹر ݙکھاؤ
pdfjs-editor-alt-text-settings-show-dialog-description = ایہ تہاکوں یقینی بݨاوݨ وچ مدد کریندے جو تہاݙیاں ساریاں تصویراں وچ آلٹ عبارت ہے۔
pdfjs-editor-alt-text-settings-close-button = بند کرو
## "Annotations removed" bar
pdfjs-editor-undo-bar-undo-button =
.title = کیتا اݨ کیتا
pdfjs-editor-undo-bar-undo-button-label = کیتا اݨ کیتا
pdfjs-editor-undo-bar-close-button =
.title = بند کرو
pdfjs-editor-undo-bar-close-button-label = بند کرو
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/sl/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Prejšnja stran
pdfjs-previous-button-label = Nazaj
pdfjs-next-button =
.title = Naslednja stran
pdfjs-next-button-label = Naprej
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Stran
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = od { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } od { $pagesCount })
pdfjs-zoom-out-button =
.title = Pomanjšaj
pdfjs-zoom-out-button-label = Pomanjšaj
pdfjs-zoom-in-button =
.title = Povečaj
pdfjs-zoom-in-button-label = Povečaj
pdfjs-zoom-select =
.title = Povečava
pdfjs-presentation-mode-button =
.title = Preklopi v način predstavitve
pdfjs-presentation-mode-button-label = Način predstavitve
pdfjs-open-file-button =
.title = Odpri datoteko
pdfjs-open-file-button-label = Odpri
pdfjs-print-button =
.title = Natisni
pdfjs-print-button-label = Natisni
pdfjs-save-button =
.title = Shrani
pdfjs-save-button-label = Shrani
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Prenesi
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Prenesi
pdfjs-bookmark-button =
.title = Trenutna stran (prikaži URL, ki vodi do trenutne strani)
pdfjs-bookmark-button-label = Na trenutno stran
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Orodja
pdfjs-tools-button-label = Orodja
pdfjs-first-page-button =
.title = Pojdi na prvo stran
pdfjs-first-page-button-label = Pojdi na prvo stran
pdfjs-last-page-button =
.title = Pojdi na zadnjo stran
pdfjs-last-page-button-label = Pojdi na zadnjo stran
pdfjs-page-rotate-cw-button =
.title = Zavrti v smeri urnega kazalca
pdfjs-page-rotate-cw-button-label = Zavrti v smeri urnega kazalca
pdfjs-page-rotate-ccw-button =
.title = Zavrti v nasprotni smeri urnega kazalca
pdfjs-page-rotate-ccw-button-label = Zavrti v nasprotni smeri urnega kazalca
pdfjs-cursor-text-select-tool-button =
.title = Omogoči orodje za izbor besedila
pdfjs-cursor-text-select-tool-button-label = Orodje za izbor besedila
pdfjs-cursor-hand-tool-button =
.title = Omogoči roko
pdfjs-cursor-hand-tool-button-label = Roka
pdfjs-scroll-page-button =
.title = Uporabi drsenje po strani
pdfjs-scroll-page-button-label = Drsenje po strani
pdfjs-scroll-vertical-button =
.title = Uporabi navpično drsenje
pdfjs-scroll-vertical-button-label = Navpično drsenje
pdfjs-scroll-horizontal-button =
.title = Uporabi vodoravno drsenje
pdfjs-scroll-horizontal-button-label = Vodoravno drsenje
pdfjs-scroll-wrapped-button =
.title = Uporabi ovito drsenje
pdfjs-scroll-wrapped-button-label = Ovito drsenje
pdfjs-spread-none-button =
.title = Ne združuj razponov strani
pdfjs-spread-none-button-label = Brez razponov
pdfjs-spread-odd-button =
.title = Združuj razpone strani z začetkom pri lihih straneh
pdfjs-spread-odd-button-label = Lihi razponi
pdfjs-spread-even-button =
.title = Združuj razpone strani z začetkom pri sodih straneh
pdfjs-spread-even-button-label = Sodi razponi
## Document properties dialog
pdfjs-document-properties-button =
.title = Lastnosti dokumenta …
pdfjs-document-properties-button-label = Lastnosti dokumenta …
pdfjs-document-properties-file-name = Ime datoteke:
pdfjs-document-properties-file-size = Velikost datoteke:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bajtov)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bajtov)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bajtov)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bajtov)
pdfjs-document-properties-title = Ime:
pdfjs-document-properties-author = Avtor:
pdfjs-document-properties-subject = Tema:
pdfjs-document-properties-keywords = Ključne besede:
pdfjs-document-properties-creation-date = Datum nastanka:
pdfjs-document-properties-modification-date = Datum spremembe:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Ustvaril:
pdfjs-document-properties-producer = Izdelovalec PDF:
pdfjs-document-properties-version = Različica PDF:
pdfjs-document-properties-page-count = Število strani:
pdfjs-document-properties-page-size = Velikost strani:
pdfjs-document-properties-page-size-unit-inches = palcev
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = pokončno
pdfjs-document-properties-page-size-orientation-landscape = ležeče
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Pismo
pdfjs-document-properties-page-size-name-legal = Pravno
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Hitri spletni ogled:
pdfjs-document-properties-linearized-yes = Da
pdfjs-document-properties-linearized-no = Ne
pdfjs-document-properties-close-button = Zapri
## Print
pdfjs-print-progress-message = Priprava dokumenta na tiskanje …
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress } %
pdfjs-print-progress-close-button = Prekliči
pdfjs-printing-not-supported = Opozorilo: ta brskalnik ne podpira vseh možnosti tiskanja.
pdfjs-printing-not-ready = Opozorilo: PDF ni v celoti naložen za tiskanje.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Preklopi stransko vrstico
pdfjs-toggle-sidebar-notification-button =
.title = Preklopi stransko vrstico (dokument vsebuje oris/priponke/plasti)
pdfjs-toggle-sidebar-button-label = Preklopi stransko vrstico
pdfjs-document-outline-button =
.title = Prikaži oris dokumenta (dvokliknite za razširitev/strnitev vseh predmetov)
pdfjs-document-outline-button-label = Oris dokumenta
pdfjs-attachments-button =
.title = Prikaži priponke
pdfjs-attachments-button-label = Priponke
pdfjs-layers-button =
.title = Prikaži plasti (dvokliknite za ponastavitev vseh plasti na privzeto stanje)
pdfjs-layers-button-label = Plasti
pdfjs-thumbs-button =
.title = Prikaži sličice
pdfjs-thumbs-button-label = Sličice
pdfjs-current-outline-item-button =
.title = Najdi trenutni predmet orisa
pdfjs-current-outline-item-button-label = Trenutni predmet orisa
pdfjs-findbar-button =
.title = Iskanje po dokumentu
pdfjs-findbar-button-label = Najdi
pdfjs-additional-layers = Dodatne plasti
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Stran { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Sličica strani { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Najdi
.placeholder = Najdi v dokumentu …
pdfjs-find-previous-button =
.title = Najdi prejšnjo ponovitev iskanega
pdfjs-find-previous-button-label = Najdi nazaj
pdfjs-find-next-button =
.title = Najdi naslednjo ponovitev iskanega
pdfjs-find-next-button-label = Najdi naprej
pdfjs-find-highlight-checkbox = Označi vse
pdfjs-find-match-case-checkbox-label = Razlikuj velike/male črke
pdfjs-find-match-diacritics-checkbox-label = Razlikuj diakritične znake
pdfjs-find-entire-word-checkbox-label = Cele besede
pdfjs-find-reached-top = Dosežen začetek dokumenta iz smeri konca
pdfjs-find-reached-bottom = Doseženo konec dokumenta iz smeri začetka
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] Zadetek { $current } od { $total }
[two] Zadetek { $current } od { $total }
[few] Zadetek { $current } od { $total }
*[other] Zadetek { $current } od { $total }
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Več kot { $limit } zadetek
[two] Več kot { $limit } zadetka
[few] Več kot { $limit } zadetki
*[other] Več kot { $limit } zadetkov
}
pdfjs-find-not-found = Iskanega ni mogoče najti
## Predefined zoom values
pdfjs-page-scale-width = Širina strani
pdfjs-page-scale-fit = Prilagodi stran
pdfjs-page-scale-auto = Samodejno
pdfjs-page-scale-actual = Dejanska velikost
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale } %
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Stran { $page }
## Loading indicator messages
pdfjs-loading-error = Med nalaganjem datoteke PDF je prišlo do napake.
pdfjs-invalid-file-error = Neveljavna ali pokvarjena datoteka PDF.
pdfjs-missing-file-error = Ni datoteke PDF.
pdfjs-unexpected-response-error = Nepričakovan odgovor strežnika.
pdfjs-rendering-error = Med pripravljanjem strani je prišlo do napake!
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [Opomba vrste { $type }]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Vnesite geslo za odpiranje te datoteke PDF.
pdfjs-password-invalid = Neveljavno geslo. Poskusite znova.
pdfjs-password-ok-button = V redu
pdfjs-password-cancel-button = Prekliči
pdfjs-web-fonts-disabled = Spletne pisave so onemogočene: vgradnih pisav za PDF ni mogoče uporabiti.
## Editing
pdfjs-editor-free-text-button =
.title = Besedilo
pdfjs-editor-free-text-button-label = Besedilo
pdfjs-editor-ink-button =
.title = Riši
pdfjs-editor-ink-button-label = Riši
pdfjs-editor-stamp-button =
.title = Dodajanje ali urejanje slik
pdfjs-editor-stamp-button-label = Dodajanje ali urejanje slik
pdfjs-editor-highlight-button =
.title = Označevalnik
pdfjs-editor-highlight-button-label = Označevalnik
pdfjs-highlight-floating-button1 =
.title = Označi
.aria-label = Označi
pdfjs-highlight-floating-button-label = Označi
pdfjs-editor-signature-button =
.title = Dodaj podpis
pdfjs-editor-signature-button-label = Dodaj podpis
## Default editor aria labels
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Odstrani risbo
pdfjs-editor-remove-freetext-button =
.title = Odstrani besedilo
pdfjs-editor-remove-stamp-button =
.title = Odstrani sliko
pdfjs-editor-remove-highlight-button =
.title = Odstrani označbo
pdfjs-editor-remove-signature-button =
.title = Odstrani podpis
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Barva
pdfjs-editor-free-text-size-input = Velikost
pdfjs-editor-ink-color-input = Barva
pdfjs-editor-ink-thickness-input = Debelina
pdfjs-editor-ink-opacity-input = Neprosojnost
pdfjs-editor-stamp-add-image-button =
.title = Dodaj sliko
pdfjs-editor-stamp-add-image-button-label = Dodaj sliko
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Debelina
pdfjs-editor-free-highlight-thickness-title =
.title = Spremeni debelino pri označevanju nebesedilnih elementov
pdfjs-editor-signature-add-signature-button =
.title = Dodaj nov podpis
pdfjs-editor-signature-add-signature-button-label = Dodaj nov podpis
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Urejevalnik besedila
.default-content = Začnite tipkati …
pdfjs-free-text =
.aria-label = Urejevalnik besedila
pdfjs-free-text-default-content = Začnite tipkati …
pdfjs-ink =
.aria-label = Urejevalnik risanja
pdfjs-ink-canvas =
.aria-label = Uporabnikova slika
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Nadomestno besedilo
pdfjs-editor-alt-text-edit-button =
.aria-label = Uredi nadomestno besedilo
pdfjs-editor-alt-text-edit-button-label = Uredi nadomestno besedilo
pdfjs-editor-alt-text-dialog-label = Izberite možnost
pdfjs-editor-alt-text-dialog-description = Nadomestno besedilo se prikaže tistim, ki ne vidijo slike, ali če se ta ne naloži.
pdfjs-editor-alt-text-add-description-label = Dodaj opis
pdfjs-editor-alt-text-add-description-description = Poskušajte v enem ali dveh stavkih opisati motiv, okolje ali dejanja.
pdfjs-editor-alt-text-mark-decorative-label = Označi kot okrasno
pdfjs-editor-alt-text-mark-decorative-description = Uporablja se za slike, ki služijo samo okrasu, na primer obrobe ali vodne žige.
pdfjs-editor-alt-text-cancel-button = Prekliči
pdfjs-editor-alt-text-save-button = Shrani
pdfjs-editor-alt-text-decorative-tooltip = Označeno kot okrasno
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Na primer: "Mladenič sedi za mizo pri jedi"
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Nadomestno besedilo
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Zgornji levi kot – spremeni velikost
pdfjs-editor-resizer-label-top-middle = Zgoraj na sredini – spremeni velikost
pdfjs-editor-resizer-label-top-right = Zgornji desni kot – spremeni velikost
pdfjs-editor-resizer-label-middle-right = Desno na sredini – spremeni velikost
pdfjs-editor-resizer-label-bottom-right = Spodnji desni kot – spremeni velikost
pdfjs-editor-resizer-label-bottom-middle = Spodaj na sredini – spremeni velikost
pdfjs-editor-resizer-label-bottom-left = Spodnji levi kot – spremeni velikost
pdfjs-editor-resizer-label-middle-left = Levo na sredini – spremeni velikost
pdfjs-editor-resizer-top-left =
.aria-label = Zgornji levi kot – spremeni velikost
pdfjs-editor-resizer-top-middle =
.aria-label = Zgoraj na sredini – spremeni velikost
pdfjs-editor-resizer-top-right =
.aria-label = Zgornji desni kot – spremeni velikost
pdfjs-editor-resizer-middle-right =
.aria-label = Desno na sredini – spremeni velikost
pdfjs-editor-resizer-bottom-right =
.aria-label = Spodnji desni kot – spremeni velikost
pdfjs-editor-resizer-bottom-middle =
.aria-label = Spodaj na sredini – spremeni velikost
pdfjs-editor-resizer-bottom-left =
.aria-label = Spodnji levi kot – spremeni velikost
pdfjs-editor-resizer-middle-left =
.aria-label = Levo na sredini – spremeni velikost
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Barva označbe
pdfjs-editor-colorpicker-button =
.title = Spremeni barvo
pdfjs-editor-colorpicker-dropdown =
.aria-label = Izbira barve
pdfjs-editor-colorpicker-yellow =
.title = Rumena
pdfjs-editor-colorpicker-green =
.title = Zelena
pdfjs-editor-colorpicker-blue =
.title = Modra
pdfjs-editor-colorpicker-pink =
.title = Roza
pdfjs-editor-colorpicker-red =
.title = Rdeča
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Prikaži vse
pdfjs-editor-highlight-show-all-button =
.title = Prikaži vse
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Uredi nadomestno besedilo (opis slike)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Dodaj nadomestno besedilo (opis slike)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Tukaj napišite svoj opis …
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Kratek opis za ljudi, ki ne morejo videti slike, ali za primer, ko se slika ne naloži.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = To nadomestno besedilo je bilo ustvarjeno samodejno in je lahko netočno.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Več o tem
pdfjs-editor-new-alt-text-create-automatically-button-label = Samodejno ustvari nadomestno besedilo
pdfjs-editor-new-alt-text-not-now-button = Ne zdaj
pdfjs-editor-new-alt-text-error-title = Nadomestnega besedila ni bilo mogoče samodejno ustvariti
pdfjs-editor-new-alt-text-error-description = Sestavite svoje nadomestno besedilo ali poskusite znova pozneje.
pdfjs-editor-new-alt-text-error-close-button = Zapri
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Prenašanje modela UI za nadomestno besedilo ({ $downloadedSize } od { $totalSize } MB)
.aria-valuetext = Prenašanje modela UI za nadomestno besedilo ({ $downloadedSize } od { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Nadomestno besedilo dodano
pdfjs-editor-new-alt-text-added-button-label = Nadomestno besedilo dodano
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Nadomestno besedilo manjka
pdfjs-editor-new-alt-text-missing-button-label = Nadomestno besedilo manjka
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Oceni nadomestno besedilo
pdfjs-editor-new-alt-text-to-review-button-label = Oceni nadomestno besedilo
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Samodejno ustvarjeno: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Nastavitve nadomestnega besedila slike
pdfjs-image-alt-text-settings-button-label = Nastavitve nadomestnega besedila slike
pdfjs-editor-alt-text-settings-dialog-label = Nastavitve nadomestnega besedila slike
pdfjs-editor-alt-text-settings-automatic-title = Samodejno nadomestno besedilo
pdfjs-editor-alt-text-settings-create-model-button-label = Samodejno ustvari nadomestno besedilo
pdfjs-editor-alt-text-settings-create-model-description = Predlaga opise za pomoč ljudem, ki ne morejo videti slike, ali za primer, ko se slika ne naloži.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Model UI za nadomestno besedilo ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Izvaja se lokalno na vaši napravi, tako da vaši podatki ostajajo zasebni. Zahtevano za samodejno nadomestno besedilo.
pdfjs-editor-alt-text-settings-delete-model-button = Izbriši
pdfjs-editor-alt-text-settings-download-model-button = Prenesi
pdfjs-editor-alt-text-settings-downloading-model-button = Prenašanje ...
pdfjs-editor-alt-text-settings-editor-title = Urejevalnik nadomestnega besedila
pdfjs-editor-alt-text-settings-show-dialog-button-label = Ob dodajanju slike takoj prikaži urejevalnik nadomestnega besedila
pdfjs-editor-alt-text-settings-show-dialog-description = Pomaga vam zagotoviti, da imajo vse vaše slike nadomestno besedilo.
pdfjs-editor-alt-text-settings-close-button = Zapri
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Označba odstranjena
pdfjs-editor-undo-bar-message-freetext = Besedilo odstranjeno
pdfjs-editor-undo-bar-message-ink = Risba odstranjena
pdfjs-editor-undo-bar-message-stamp = Slika odstranjena
pdfjs-editor-undo-bar-message-signature = Podpis odstranjen
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } označba odstranjena
[two] { $count } označbi odstranjeni
[few] { $count } označbe odstranjene
*[other] { $count } označb odstranjenih
}
pdfjs-editor-undo-bar-undo-button =
.title = Razveljavi
pdfjs-editor-undo-bar-undo-button-label = Razveljavi
pdfjs-editor-undo-bar-close-button =
.title = Zapri
pdfjs-editor-undo-bar-close-button-label = Zapri
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = Ta način omogoča uporabniku ustvariti podpis, ki ga želi dodati dokumentu PDF. Uporabnik lahko uredi ime (ki se uporablja tudi kot nadomestno besedilo) in podpis po želji shrani za ponovno uporabo.
pdfjs-editor-add-signature-dialog-title = Dodaj podpis
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Natipkaj
.title = Natipkaj
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Nariši
.title = Nariši
pdfjs-editor-add-signature-image-button = Slika
.title = Slika
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Natipkajte svoj podpis
.placeholder = Natipkajte svoj podpis
pdfjs-editor-add-signature-draw-placeholder = Narišite svoj podpis
pdfjs-editor-add-signature-draw-thickness-range-label = Debelina
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Debelina peresa: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Povlecite datoteko sem za nalaganje
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Ali prebrskajte slikovne datoteke
*[other] Ali prebrskajte slikovne datoteke
}
## Controls
pdfjs-editor-add-signature-description-label = Opis (nadomestno besedilo)
pdfjs-editor-add-signature-description-input =
.title = Opis (nadomestno besedilo)
pdfjs-editor-add-signature-description-default-when-drawing = Podpis
pdfjs-editor-add-signature-clear-button-label = Pobriši podpis
pdfjs-editor-add-signature-clear-button =
.title = Pobriši podpis
pdfjs-editor-add-signature-save-checkbox = Shrani podpis
pdfjs-editor-add-signature-save-warning-message = Dosegli ste omejitev 5 shranjenih podpisov. Če želite shraniti novega, enega odstranite.
pdfjs-editor-add-signature-image-upload-error-title = Slike ni bilo mogoče naložiti
pdfjs-editor-add-signature-image-upload-error-description = Preverite svojo povezavo z omrežjem ali poskusite z drugo sliko.
pdfjs-editor-add-signature-error-close-button = Zapri
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Prekliči
pdfjs-editor-add-signature-add-button = Dodaj
pdfjs-editor-edit-signature-update-button = Spremeni
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Odstrani podpis
pdfjs-editor-delete-signature-button-label = Odstrani podpis
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Uredi opis
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Uredi opis
================================================
FILE: cookbook/static/pdfjs/web/locale/son/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Moo bisante
pdfjs-previous-button-label = Bisante
pdfjs-next-button =
.title = Jinehere moo
pdfjs-next-button-label = Jine
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Moo
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = { $pagesCount } ra
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } ka hun { $pagesCount }) ra
pdfjs-zoom-out-button =
.title = Nakasandi
pdfjs-zoom-out-button-label = Nakasandi
pdfjs-zoom-in-button =
.title = Bebbeerandi
pdfjs-zoom-in-button-label = Bebbeerandi
pdfjs-zoom-select =
.title = Bebbeerandi
pdfjs-presentation-mode-button =
.title = Bere cebeyan alhaali
pdfjs-presentation-mode-button-label = Cebeyan alhaali
pdfjs-open-file-button =
.title = Tuku feeri
pdfjs-open-file-button-label = Feeri
pdfjs-print-button =
.title = Kar
pdfjs-print-button-label = Kar
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Goyjinawey
pdfjs-tools-button-label = Goyjinawey
pdfjs-first-page-button =
.title = Koy moo jinaa ga
pdfjs-first-page-button-label = Koy moo jinaa ga
pdfjs-last-page-button =
.title = Koy moo koraa ga
pdfjs-last-page-button-label = Koy moo koraa ga
pdfjs-page-rotate-cw-button =
.title = Kuubi kanbe guma here
pdfjs-page-rotate-cw-button-label = Kuubi kanbe guma here
pdfjs-page-rotate-ccw-button =
.title = Kuubi kanbe wowa here
pdfjs-page-rotate-ccw-button-label = Kuubi kanbe wowa here
## Document properties dialog
pdfjs-document-properties-button =
.title = Takadda mayrawey…
pdfjs-document-properties-button-label = Takadda mayrawey…
pdfjs-document-properties-file-name = Tuku maa:
pdfjs-document-properties-file-size = Tuku adadu:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = KB { $size_kb } (cebsu-ize { $size_b })
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = MB { $size_mb } (cebsu-ize { $size_b })
pdfjs-document-properties-title = Tiiramaa:
pdfjs-document-properties-author = Hantumkaw:
pdfjs-document-properties-subject = Dalil:
pdfjs-document-properties-keywords = Kufalkalimawey:
pdfjs-document-properties-creation-date = Teeyan han:
pdfjs-document-properties-modification-date = Barmayan han:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Teekaw:
pdfjs-document-properties-producer = PDF berandikaw:
pdfjs-document-properties-version = PDF dumi:
pdfjs-document-properties-page-count = Moo hinna:
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
##
pdfjs-document-properties-close-button = Daabu
## Print
pdfjs-print-progress-message = Goo ma takaddaa soolu k'a kar se…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Naŋ
pdfjs-printing-not-supported = Yaamar: Karyan ši tee ka timme nda ceecikaa woo.
pdfjs-printing-not-ready = Yaamar: PDF ši zunbu ka timme karyan še.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Kanjari ceraw zuu
pdfjs-toggle-sidebar-button-label = Kanjari ceraw zuu
pdfjs-document-outline-button =
.title = Takaddaa korfur alhaaloo cebe (naagu cee hinka ka haya-izey kul hayandi/kankamandi)
pdfjs-document-outline-button-label = Takadda filla-boŋ
pdfjs-attachments-button =
.title = Hangarey cebe
pdfjs-attachments-button-label = Hangarey
pdfjs-thumbs-button =
.title = Kabeboy biyey cebe
pdfjs-thumbs-button-label = Kabeboy biyey
pdfjs-findbar-button =
.title = Ceeci takaddaa ra
pdfjs-findbar-button-label = Ceeci
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = { $page } moo
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Kabeboy bii { $page } moo še
## Find panel button title and messages
pdfjs-find-input =
.title = Ceeci
.placeholder = Ceeci takaddaa ra…
pdfjs-find-previous-button =
.title = Kalimaɲaŋoo bangayri bisantaa ceeci
pdfjs-find-previous-button-label = Bisante
pdfjs-find-next-button =
.title = Kalimaɲaŋoo hiino bangayroo ceeci
pdfjs-find-next-button-label = Jine
pdfjs-find-highlight-checkbox = Ikul šilbay
pdfjs-find-match-case-checkbox-label = Harfu-beeriyan hawgay
pdfjs-find-reached-top = A too moŋoo boŋoo, koy jine ka šinitin nda cewoo
pdfjs-find-reached-bottom = A too moɲoo cewoo, koy jine šintioo ga
pdfjs-find-not-found = Kalimaɲaa mana duwandi
## Predefined zoom values
pdfjs-page-scale-width = Mooo hayyan
pdfjs-page-scale-fit = Moo sawayan
pdfjs-page-scale-auto = Boŋše azzaati barmayyan
pdfjs-page-scale-actual = Adadu cimi
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
## Loading indicator messages
pdfjs-loading-error = Firka bangay kaŋ PDF goo ma zumandi.
pdfjs-invalid-file-error = PDF tuku laala wala laybante.
pdfjs-missing-file-error = PDF tuku kumante.
pdfjs-unexpected-response-error = Manti feršikaw tuuruyan maatante.
pdfjs-rendering-error = Firka bangay kaŋ moɲoo goo ma willandi.
## Annotations
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = { $type } maasa-caw]
## Password
pdfjs-password-label = Šennikufal dam ka PDF tukoo woo feeri.
pdfjs-password-invalid = Šennikufal laalo. Ceeci koyne taare.
pdfjs-password-ok-button = Ayyo
pdfjs-password-cancel-button = Naŋ
pdfjs-web-fonts-disabled = Interneti šigirawey kay: ši hin ka goy nda PDF šigira hurantey.
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/sq/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Faqja e Mëparshme
pdfjs-previous-button-label = E mëparshmja
pdfjs-next-button =
.title = Faqja Pasuese
pdfjs-next-button-label = Pasuesja
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Faqe
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = nga { $pagesCount } gjithsej
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } nga { $pagesCount })
pdfjs-zoom-out-button =
.title = Zvogëlojeni
pdfjs-zoom-out-button-label = Zvogëlojeni
pdfjs-zoom-in-button =
.title = Zmadhojeni
pdfjs-zoom-in-button-label = Zmadhojini
pdfjs-zoom-select =
.title = Zmadhim/Zvogëlim
pdfjs-presentation-mode-button =
.title = Kalo te Mënyra Paraqitje
pdfjs-presentation-mode-button-label = Mënyra Paraqitje
pdfjs-open-file-button =
.title = Hapni Kartelë
pdfjs-open-file-button-label = Hape
pdfjs-print-button =
.title = Shtypje
pdfjs-print-button-label = Shtype
pdfjs-save-button =
.title = Ruaje
pdfjs-save-button-label = Ruaje
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Shkarkojeni
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Shkarkoje
pdfjs-bookmark-button =
.title = Faqja e Tanishme (Shihni URL nga Faqja e Tanishme)
pdfjs-bookmark-button-label = Faqja e Tanishme
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Mjete
pdfjs-tools-button-label = Mjete
pdfjs-first-page-button =
.title = Kaloni te Faqja e Parë
pdfjs-first-page-button-label = Kaloni te Faqja e Parë
pdfjs-last-page-button =
.title = Kaloni te Faqja e Fundit
pdfjs-last-page-button-label = Kaloni te Faqja e Fundit
pdfjs-page-rotate-cw-button =
.title = Rrotullojeni Në Kahun Orar
pdfjs-page-rotate-cw-button-label = Rrotulloje Në Kahun Orar
pdfjs-page-rotate-ccw-button =
.title = Rrotullojeni Në Kahun Kundërorar
pdfjs-page-rotate-ccw-button-label = Rrotulloje Në Kahun Kundërorar
pdfjs-cursor-text-select-tool-button =
.title = Aktivizo Mjet Përzgjedhjeje Teksti
pdfjs-cursor-text-select-tool-button-label = Mjet Përzgjedhjeje Teksti
pdfjs-cursor-hand-tool-button =
.title = Aktivizo Mjetin Dorë
pdfjs-cursor-hand-tool-button-label = Mjeti Dorë
pdfjs-scroll-page-button =
.title = Përdor Rrëshqitje Në Faqe
pdfjs-scroll-page-button-label = Rrëshqitje Në Faqe
pdfjs-scroll-vertical-button =
.title = Përdor Rrëshqitje Vertikale
pdfjs-scroll-vertical-button-label = Rrëshqitje Vertikale
pdfjs-scroll-horizontal-button =
.title = Përdor Rrëshqitje Horizontale
pdfjs-scroll-horizontal-button-label = Rrëshqitje Horizontale
pdfjs-scroll-wrapped-button =
.title = Përdor Rrëshqitje Me Mbështjellje
pdfjs-scroll-wrapped-button-label = Rrëshqitje Me Mbështjellje
## Document properties dialog
pdfjs-document-properties-button =
.title = Veti Dokumenti…
pdfjs-document-properties-button-label = Veti Dokumenti…
pdfjs-document-properties-file-name = Emër kartele:
pdfjs-document-properties-file-size = Madhësi kartele:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bajte)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bajte)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bajte)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bajte)
pdfjs-document-properties-title = Titull:
pdfjs-document-properties-author = Autor:
pdfjs-document-properties-subject = Subjekt:
pdfjs-document-properties-keywords = Fjalëkyçe:
pdfjs-document-properties-creation-date = Datë Krijimi:
pdfjs-document-properties-modification-date = Datë Ndryshimi:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Krijues:
pdfjs-document-properties-producer = Prodhues PDF-je:
pdfjs-document-properties-version = Version PDF-je:
pdfjs-document-properties-page-count = Numër Faqesh:
pdfjs-document-properties-page-size = Madhësi Faqeje:
pdfjs-document-properties-page-size-unit-inches = inç
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = portret
pdfjs-document-properties-page-size-orientation-landscape = së gjeri
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letter
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Parje e Shpjetë në Web:
pdfjs-document-properties-linearized-yes = Po
pdfjs-document-properties-linearized-no = Jo
pdfjs-document-properties-close-button = Mbylleni
## Print
pdfjs-print-progress-message = Po përgatitet dokumenti për shtypje…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Anuloje
pdfjs-printing-not-supported = Kujdes: Shtypja s’mbulohet plotësisht nga ky shfletues.
pdfjs-printing-not-ready = Kujdes: PDF-ja s’është ngarkuar plotësisht që ta shtypni.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Shfaqni/Fshihni Anështyllën
pdfjs-toggle-sidebar-notification-button =
.title = Hap/Mbyll Anështylë (dokumenti përmban përvijim/nashkëngjitje/shtresa)
pdfjs-toggle-sidebar-button-label = Shfaq/Fshih Anështyllën
pdfjs-document-outline-button =
.title = Shfaqni Përvijim Dokumenti (dyklikoni që të shfaqen/fshihen krejt elementët)
pdfjs-document-outline-button-label = Përvijim Dokumenti
pdfjs-attachments-button =
.title = Shfaqni Bashkëngjitje
pdfjs-attachments-button-label = Bashkëngjitje
pdfjs-layers-button =
.title = Shfaq Shtresa (dyklikoni që të rikthehen krejt shtresat në gjendjen e tyre parazgjedhje)
pdfjs-layers-button-label = Shtresa
pdfjs-thumbs-button =
.title = Shfaqni Miniatura
pdfjs-thumbs-button-label = Miniatura
pdfjs-current-outline-item-button =
.title = Gjej Objektin e Tanishëm të Përvijuar
pdfjs-current-outline-item-button-label = Objekt i Tanishëm i Përvijuar
pdfjs-findbar-button =
.title = Gjeni në Dokument
pdfjs-findbar-button-label = Gjej
pdfjs-additional-layers = Shtresa Shtesë
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Faqja { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Miniaturë e Faqes { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Gjej
.placeholder = Gjeni në dokument…
pdfjs-find-previous-button =
.title = Gjeni hasjen e mëparshme të togfjalëshit
pdfjs-find-previous-button-label = E mëparshmja
pdfjs-find-next-button =
.title = Gjeni hasjen pasuese të togfjalëshit
pdfjs-find-next-button-label = Pasuesja
pdfjs-find-highlight-checkbox = Theksoji të tëra
pdfjs-find-match-case-checkbox-label = Siç Është Shkruar
pdfjs-find-match-diacritics-checkbox-label = Me Përputhje Me Shenjat Diakritike
pdfjs-find-entire-word-checkbox-label = Fjalë të Plota
pdfjs-find-reached-top = U mbërrit në krye të dokumentit, vazhduar prej fundit
pdfjs-find-reached-bottom = U mbërrit në fund të dokumentit, vazhduar prej kreut
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } nga { $total } përputhje
*[other] { $current } nga { $total } përputhje
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Më tepër se { $limit } përputhje
*[other] Më tepër se { $limit } përputhje
}
pdfjs-find-not-found = Togfjalësh që s’gjendet
## Predefined zoom values
pdfjs-page-scale-width = Gjerësi Faqeje
pdfjs-page-scale-fit = Sa Nxë Faqja
pdfjs-page-scale-auto = Zoom i Vetvetishëm
pdfjs-page-scale-actual = Madhësia Faktike
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Faqja { $page }
## Loading indicator messages
pdfjs-loading-error = Ndodhi një gabim gjatë ngarkimit të PDF-së.
pdfjs-invalid-file-error = Kartelë PDF e pavlefshme ose e dëmtuar.
pdfjs-missing-file-error = Kartelë PDF që mungon.
pdfjs-unexpected-response-error = Përgjigje shërbyesi e papritur.
pdfjs-rendering-error = Ndodhi një gabim gjatë riprodhimit të faqes.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [Nënvizim { $type }]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Jepni fjalëkalimin që të hapet kjo kartelë PDF.
pdfjs-password-invalid = Fjalëkalim i pavlefshëm. Ju lutemi, riprovoni.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Anuloje
pdfjs-web-fonts-disabled = Shkronjat Web janë të çaktivizuara: s’arrihet të përdoren shkronja të trupëzuara në PDF.
## Editing
pdfjs-editor-free-text-button =
.title = Tekst
pdfjs-editor-free-text-button-label = Tekst
pdfjs-editor-ink-button =
.title = Vizatoni
pdfjs-editor-ink-button-label = Vizatoni
pdfjs-editor-stamp-button =
.title = Shtoni ose përpunoni figura
pdfjs-editor-stamp-button-label = Shtoni ose përpunoni figura
pdfjs-editor-highlight-button =
.title = Theksim
pdfjs-editor-highlight-button-label = Theksoje
pdfjs-highlight-floating-button1 =
.title = Theksim
.aria-label = Theksim
pdfjs-highlight-floating-button-label = Theksim
pdfjs-editor-signature-button =
.title = Shtoni nënshkrim
pdfjs-editor-signature-button-label = Shtoni nënshkrim
## Default editor aria labels
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Hiq vizatim
pdfjs-editor-remove-freetext-button =
.title = Hiq tekst
pdfjs-editor-remove-stamp-button =
.title = Hiq figurë
pdfjs-editor-remove-highlight-button =
.title = Hiqe theksimin
pdfjs-editor-remove-signature-button =
.title = Hiqe nënshkrimin
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Ngjyrë
pdfjs-editor-free-text-size-input = Madhësi
pdfjs-editor-ink-color-input = Ngjyrë
pdfjs-editor-ink-thickness-input = Trashësi
pdfjs-editor-ink-opacity-input = Patejdukshmëri
pdfjs-editor-stamp-add-image-button =
.title = Shtoni figurë
pdfjs-editor-stamp-add-image-button-label = Shtoni figurë
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Trashësi
pdfjs-editor-free-highlight-thickness-title =
.title = Ndryshoni trashësinë kur theksoni objekte tjetër nga tekst
pdfjs-editor-signature-add-signature-button =
.title = Shtoni nënshkrim të ri
pdfjs-editor-signature-add-signature-button-label = Shtoni nënshkrim të ri
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Përpunues Tekstesh
.default-content = Filloni të shtypni…
pdfjs-free-text =
.aria-label = Përpunues Tekstesh
pdfjs-free-text-default-content = Filloni të shtypni…
pdfjs-ink =
.aria-label = Përpunues Vizatimesh
pdfjs-ink-canvas =
.aria-label = Figurë e krijuar nga përdoruesi
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Tekst alternativ
pdfjs-editor-alt-text-edit-button =
.aria-label = Përpunoni tekst alternativ
pdfjs-editor-alt-text-edit-button-label = Përpunoni tekst alternativ
pdfjs-editor-alt-text-dialog-label = Zgjidhni një mundësi
pdfjs-editor-alt-text-dialog-description = Teksti alt (tekst alternativ) vjen në ndihmë kur njerëzit s’mund të shohin figurën, ose kur ajo nuk ngarkohet.
pdfjs-editor-alt-text-add-description-label = Shtoni një përshkrim
pdfjs-editor-alt-text-add-description-description = Synoni për 1-2 togfjalësha që përshkruajnë subjektin, rrethanat apo veprimet.
pdfjs-editor-alt-text-mark-decorative-label = Vëri shenjë si dekorative
pdfjs-editor-alt-text-mark-decorative-description = Kjo përdoret për figura zbukuruese, fjala vjen, anë, ose watermark-e.
pdfjs-editor-alt-text-cancel-button = Anuloje
pdfjs-editor-alt-text-save-button = Ruaje
pdfjs-editor-alt-text-decorative-tooltip = Iu vu shenjë si dekorative
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Për shembull, “Një djalosh ulet në një tryezë të hajë”
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Tekst alternativ
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Cepi i sipërm majtas — ripërmasojeni
pdfjs-editor-resizer-label-top-middle = Mesi i pjesës sipër — ripërmasojeni
pdfjs-editor-resizer-label-top-right = Cepi i sipërm djathtas — ripërmasojeni
pdfjs-editor-resizer-label-middle-right = Djathtas në mes — ripërmasojeni
pdfjs-editor-resizer-label-bottom-right = Cepi i poshtëm djathtas — ripërmasojeni
pdfjs-editor-resizer-label-bottom-middle = Mesi i pjesës poshtë — ripërmasojeni
pdfjs-editor-resizer-label-bottom-left = Cepi i poshtëm — ripërmasojeni
pdfjs-editor-resizer-label-middle-left = Majtas në mes — ripërmasojeni
pdfjs-editor-resizer-top-left =
.aria-label = Cepi i sipërm majtas — ripërmasojeni
pdfjs-editor-resizer-top-middle =
.aria-label = Mesi i pjesës sipër — ripërmasojeni
pdfjs-editor-resizer-top-right =
.aria-label = Cepi i sipërm djathtas — ripërmasojeni
pdfjs-editor-resizer-middle-right =
.aria-label = Djathtas në mes — ripërmasojeni
pdfjs-editor-resizer-bottom-right =
.aria-label = Cepi i poshtëm djathtas — ripërmasojeni
pdfjs-editor-resizer-bottom-middle =
.aria-label = Mesi i pjesës poshtë — ripërmasojeni
pdfjs-editor-resizer-bottom-left =
.aria-label = Cepi i poshtëm — ripërmasojeni
pdfjs-editor-resizer-middle-left =
.aria-label = Majtas në mes — ripërmasojeni
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Ngjyrë theksimi
pdfjs-editor-colorpicker-button =
.title = Ndryshoni ngjyrë
pdfjs-editor-colorpicker-dropdown =
.aria-label = Zgjedhje ngjyre
pdfjs-editor-colorpicker-yellow =
.title = E verdhë
pdfjs-editor-colorpicker-green =
.title = E gjelbër
pdfjs-editor-colorpicker-blue =
.title = Blu
pdfjs-editor-colorpicker-pink =
.title = Rozë
pdfjs-editor-colorpicker-red =
.title = E kuqe
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Shfaqi krejt
pdfjs-editor-highlight-show-all-button =
.title = Shfaqi krejt
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Përpunoni tekst alternativ (përshkrim figure)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Shtoni tekst alternativ (përshkrim figure)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Shkruani këtu përshkrimin tuaj…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Përshkrim i shkurtër për persona që s’munden të shohin figurën, ose për kur figura nuk ngarkohet dot.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Ky tekst alternativ qe krijuar automatikisht dhe mund të jetë i pasaktë.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Mësoni më tepër
pdfjs-editor-new-alt-text-create-automatically-button-label = Krijo automatikisht tekst alternativ
pdfjs-editor-new-alt-text-not-now-button = Jo tani
pdfjs-editor-new-alt-text-error-title = S’u krijua dot automatikisht tekst alternativ
pdfjs-editor-new-alt-text-error-description = Ju lutemi, shkruani tekstin tuaj alternativ, ose riprovoni më vonë.
pdfjs-editor-new-alt-text-error-close-button = Mbylle
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Po shkarkohet model IA teksti alternativ ({ $downloadedSize } nga { $totalSize } MB)
.aria-valuetext = Po shkarkohet model IA teksti alternativ ({ $downloadedSize } nga { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = U shtua tekst alternativ
pdfjs-editor-new-alt-text-added-button-label = U shtua tekst alternativ
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Mungon tekst alternativ
pdfjs-editor-new-alt-text-missing-button-label = Mungon tekst alternativ
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Shqyrtoni tekst alternativ
pdfjs-editor-new-alt-text-to-review-button-label = Shqyrtoni tekst alternativ
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Krijuar automatikisht: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Rregullime teksti alternativ figure
pdfjs-image-alt-text-settings-button-label = Rregullime teksti alternativ figure
pdfjs-editor-alt-text-settings-dialog-label = Rregullime teksti alternativ figure
pdfjs-editor-alt-text-settings-automatic-title = Tekst alternativ i automatizuar
pdfjs-editor-alt-text-settings-create-model-button-label = Krijo automatikisht tekst alternativ
pdfjs-editor-alt-text-settings-create-model-description = Sugjeron përshkrime, për të ndihmuar persona që s’munden të shohin figurën, ose për kur figura nuk ngarkohet dot.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Model IA teksti alternativ ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Xhiron lokalisht në pajisjen tuaj, pra të dhënat tuaja mbeten private. E domosdoshme për tekst të automatizuar alternativ.
pdfjs-editor-alt-text-settings-delete-model-button = Fshije
pdfjs-editor-alt-text-settings-download-model-button = Shkarkoje
pdfjs-editor-alt-text-settings-downloading-model-button = Po shkarkohet…
pdfjs-editor-alt-text-settings-editor-title = Përpunues teksti alternativ
pdfjs-editor-alt-text-settings-show-dialog-button-label = Shfaq menjëherë përpunues teksti alternativ, kur shtohet një figurë
pdfjs-editor-alt-text-settings-show-dialog-description = Ju ndihmon të siguroheni se krejt figurat tuaja kanë tekst alternativ.
pdfjs-editor-alt-text-settings-close-button = Mbylle
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = U hoq theksimi
pdfjs-editor-undo-bar-message-freetext = U hoq tekst
pdfjs-editor-undo-bar-message-ink = U hoq vizatim
pdfjs-editor-undo-bar-message-stamp = U hoq figurë
pdfjs-editor-undo-bar-message-signature = Nënshkrimi u hoq
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] U hoq { $count } shënim
*[other] U hoqën { $count } shënime
}
pdfjs-editor-undo-bar-undo-button =
.title = Zhbëje
pdfjs-editor-undo-bar-undo-button-label = Zhbëje
pdfjs-editor-undo-bar-close-button =
.title = Mbylle
pdfjs-editor-undo-bar-close-button-label = Mbylle
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = Kjo dritare modale i lejon përdoruesit të krijojë një nënshkrim për ta shtuar te një dokument PDF. Përdoruesi mund të përpunojë emrin (i cili shërben edhe si tekst alternativ) dhe, nëse do, ta ruajë nënshkrimin, për ta përdorur prapë.
pdfjs-editor-add-signature-dialog-title = Shtoni një nënshkrim
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Lloj
.title = Lloj
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Vizatoni
.title = Vizatoni
pdfjs-editor-add-signature-image-button = Figurë
.title = Figurë
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Shtypni nënshkrimin tuaj
.placeholder = Shtypni nënshkrimin tuaj
pdfjs-editor-add-signature-draw-placeholder = Vizatoni nënshkrimin tuaj
pdfjs-editor-add-signature-draw-thickness-range-label = Trashësi
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Trashësi vizatimi: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Tërhiqni këtu një kartelë për ngarkim
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Ose zgjidhni kartelë figure
*[other] Ose zgjidhni kartelë figure
}
## Controls
pdfjs-editor-add-signature-description-label = Përshkrim (tekst alternativ)
pdfjs-editor-add-signature-description-input =
.title = Përshkrim (tekst alternativ)
pdfjs-editor-add-signature-description-default-when-drawing = Nënshkrim
pdfjs-editor-add-signature-clear-button-label = Spastroje nënshkrimin
pdfjs-editor-add-signature-clear-button =
.title = Spastroje nënshkrimin
pdfjs-editor-add-signature-save-checkbox = Ruaje nënshkrimin
pdfjs-editor-add-signature-save-warning-message = Keni mbërritur në kufirin e 5 nënshkrimeve të ruajtura. Që të ruani tjetër, hiqni një.
pdfjs-editor-add-signature-image-upload-error-title = S’u ngarkua dot figurë
pdfjs-editor-add-signature-image-upload-error-description = Kontrolloni lidhjen tuaj në rrjet, ose provoni figurë tjetër.
pdfjs-editor-add-signature-error-close-button = Mbylle
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Anuloje
pdfjs-editor-add-signature-add-button = Shtoje
pdfjs-editor-edit-signature-update-button = Përditësoje
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Hiqe nënshkrimin
pdfjs-editor-delete-signature-button-label = Hiqe nënshkrimin
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Përpunoni përshkrimin
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Përpunoni përshkrimin
================================================
FILE: cookbook/static/pdfjs/web/locale/sr/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Претходна страница
pdfjs-previous-button-label = Претходна
pdfjs-next-button =
.title = Следећа страница
pdfjs-next-button-label = Следећа
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Страница
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = од { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } од { $pagesCount })
pdfjs-zoom-out-button =
.title = Умањи
pdfjs-zoom-out-button-label = Умањи
pdfjs-zoom-in-button =
.title = Увеличај
pdfjs-zoom-in-button-label = Увеличај
pdfjs-zoom-select =
.title = Увеличавање
pdfjs-presentation-mode-button =
.title = Промени на приказ у режиму презентације
pdfjs-presentation-mode-button-label = Режим презентације
pdfjs-open-file-button =
.title = Отвори датотеку
pdfjs-open-file-button-label = Отвори
pdfjs-print-button =
.title = Штампај
pdfjs-print-button-label = Штампај
pdfjs-save-button =
.title = Сачувај
pdfjs-save-button-label = Сачувај
pdfjs-bookmark-button =
.title = Тренутна страница (погледајте URL са тренутне странице)
pdfjs-bookmark-button-label = Тренутна страница
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Алатке
pdfjs-tools-button-label = Алатке
pdfjs-first-page-button =
.title = Иди на прву страницу
pdfjs-first-page-button-label = Иди на прву страницу
pdfjs-last-page-button =
.title = Иди на последњу страницу
pdfjs-last-page-button-label = Иди на последњу страницу
pdfjs-page-rotate-cw-button =
.title = Ротирај у смеру казаљке на сату
pdfjs-page-rotate-cw-button-label = Ротирај у смеру казаљке на сату
pdfjs-page-rotate-ccw-button =
.title = Ротирај у смеру супротном од казаљке на сату
pdfjs-page-rotate-ccw-button-label = Ротирај у смеру супротном од казаљке на сату
pdfjs-cursor-text-select-tool-button =
.title = Омогући алат за селектовање текста
pdfjs-cursor-text-select-tool-button-label = Алат за селектовање текста
pdfjs-cursor-hand-tool-button =
.title = Омогући алат за померање
pdfjs-cursor-hand-tool-button-label = Алат за померање
pdfjs-scroll-page-button =
.title = Користи скроловање по омоту
pdfjs-scroll-page-button-label = Скроловање странице
pdfjs-scroll-vertical-button =
.title = Користи вертикално скроловање
pdfjs-scroll-vertical-button-label = Вертикално скроловање
pdfjs-scroll-horizontal-button =
.title = Користи хоризонтално скроловање
pdfjs-scroll-horizontal-button-label = Хоризонтално скроловање
pdfjs-scroll-wrapped-button =
.title = Користи скроловање по омоту
pdfjs-scroll-wrapped-button-label = Скроловање по омоту
pdfjs-spread-none-button =
.title = Немој спајати ширења страница
pdfjs-spread-none-button-label = Без распростирања
pdfjs-spread-odd-button =
.title = Споји ширења страница које почињу непарним бројем
pdfjs-spread-odd-button-label = Непарна распростирања
pdfjs-spread-even-button =
.title = Споји ширења страница које почињу парним бројем
pdfjs-spread-even-button-label = Парна распростирања
## Document properties dialog
pdfjs-document-properties-button =
.title = Параметри документа…
pdfjs-document-properties-button-label = Параметри документа…
pdfjs-document-properties-file-name = Име датотеке:
pdfjs-document-properties-file-size = Величина датотеке:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } B)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } B)
pdfjs-document-properties-title = Наслов:
pdfjs-document-properties-author = Аутор:
pdfjs-document-properties-subject = Тема:
pdfjs-document-properties-keywords = Кључне речи:
pdfjs-document-properties-creation-date = Датум креирања:
pdfjs-document-properties-modification-date = Датум модификације:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Стваралац:
pdfjs-document-properties-producer = PDF произвођач:
pdfjs-document-properties-version = PDF верзија:
pdfjs-document-properties-page-count = Број страница:
pdfjs-document-properties-page-size = Величина странице:
pdfjs-document-properties-page-size-unit-inches = ин
pdfjs-document-properties-page-size-unit-millimeters = мм
pdfjs-document-properties-page-size-orientation-portrait = усправно
pdfjs-document-properties-page-size-orientation-landscape = водоравно
pdfjs-document-properties-page-size-name-a-three = А3
pdfjs-document-properties-page-size-name-a-four = А4
pdfjs-document-properties-page-size-name-letter = Слово
pdfjs-document-properties-page-size-name-legal = Права
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Брз веб приказ:
pdfjs-document-properties-linearized-yes = Да
pdfjs-document-properties-linearized-no = Не
pdfjs-document-properties-close-button = Затвори
## Print
pdfjs-print-progress-message = Припремам документ за штампање…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Откажи
pdfjs-printing-not-supported = Упозорење: Штампање није у потпуности подржано у овом прегледачу.
pdfjs-printing-not-ready = Упозорење: PDF није у потпуности учитан за штампу.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Прикажи/сакриј бочни панел
pdfjs-toggle-sidebar-notification-button =
.title = Прикажи/сакриј бочни панел (документ садржи контуру/прилоге/слојеве)
pdfjs-toggle-sidebar-button-label = Прикажи/сакриј бочни панел
pdfjs-document-outline-button =
.title = Прикажи структуру документа (двоструким кликом проширујете/скупљате све ставке)
pdfjs-document-outline-button-label = Контура документа
pdfjs-attachments-button =
.title = Прикажи прилоге
pdfjs-attachments-button-label = Прилози
pdfjs-layers-button =
.title = Прикажи слојеве (дупли клик за враћање свих слојева у подразумевано стање)
pdfjs-layers-button-label = Слојеви
pdfjs-thumbs-button =
.title = Прикажи сличице
pdfjs-thumbs-button-label = Сличице
pdfjs-current-outline-item-button =
.title = Пронађите тренутни елемент структуре
pdfjs-current-outline-item-button-label = Тренутна контура
pdfjs-findbar-button =
.title = Пронађи у документу
pdfjs-findbar-button-label = Пронађи
pdfjs-additional-layers = Додатни слојеви
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Страница { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Сличица од странице { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Пронађи
.placeholder = Пронађи у документу…
pdfjs-find-previous-button =
.title = Пронађи претходно појављивање фразе
pdfjs-find-previous-button-label = Претходна
pdfjs-find-next-button =
.title = Пронађи следеће појављивање фразе
pdfjs-find-next-button-label = Следећа
pdfjs-find-highlight-checkbox = Истакнути све
pdfjs-find-match-case-checkbox-label = Подударања
pdfjs-find-match-diacritics-checkbox-label = Дијакритика
pdfjs-find-entire-word-checkbox-label = Целе речи
pdfjs-find-reached-top = Достигнут врх документа, наставио са дна
pdfjs-find-reached-bottom = Достигнуто дно документа, наставио са врха
pdfjs-find-not-found = Фраза није пронађена
## Predefined zoom values
pdfjs-page-scale-width = Ширина странице
pdfjs-page-scale-fit = Прилагоди страницу
pdfjs-page-scale-auto = Аутоматско увеличавање
pdfjs-page-scale-actual = Стварна величина
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Страница { $page }
## Loading indicator messages
pdfjs-loading-error = Дошло је до грешке приликом учитавања PDF-а.
pdfjs-invalid-file-error = PDF датотека је неважећа или је оштећена.
pdfjs-missing-file-error = Недостаје PDF датотека.
pdfjs-unexpected-response-error = Неочекиван одговор од сервера.
pdfjs-rendering-error = Дошло је до грешке приликом рендеровања ове странице.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } коментар]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Унесите лозинку да бисте отворили овај PDF докуменат.
pdfjs-password-invalid = Неисправна лозинка. Покушајте поново.
pdfjs-password-ok-button = У реду
pdfjs-password-cancel-button = Откажи
pdfjs-web-fonts-disabled = Веб фонтови су онемогућени: не могу користити уграђене PDF фонтове.
## Editing
pdfjs-editor-free-text-button =
.title = Текст
pdfjs-editor-free-text-button-label = Текст
pdfjs-editor-ink-button =
.title = Цртај
pdfjs-editor-ink-button-label = Цртај
pdfjs-editor-stamp-button =
.title = Додај или уреди слике
pdfjs-editor-stamp-button-label = Додај или уреди слике
pdfjs-editor-highlight-button =
.title = Означи
pdfjs-editor-highlight-button-label = Означи
pdfjs-highlight-floating-button1 =
.title = Означи
.aria-label = Означи
pdfjs-highlight-floating-button-label = Означи
## Default editor aria labels
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Уклони цртеж
pdfjs-editor-remove-freetext-button =
.title = Уклони текст
pdfjs-editor-remove-stamp-button =
.title = Уклони слику
pdfjs-editor-remove-highlight-button =
.title = Уклони ознаку
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Боја
pdfjs-editor-free-text-size-input = Величина
pdfjs-editor-ink-color-input = Боја
pdfjs-editor-ink-thickness-input = Дебљина
pdfjs-editor-ink-opacity-input = Опацитет
pdfjs-editor-stamp-add-image-button =
.title = Додај слику
pdfjs-editor-stamp-add-image-button-label = Додај слику
pdfjs-editor-free-highlight-thickness-title =
.title = Промени дебљину при означавању других ставки сем текста
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Уређивач текста
.default-content = Почни куцати…
pdfjs-free-text =
.aria-label = Уређивач текста
pdfjs-free-text-default-content = Почни куцање…
pdfjs-ink =
.aria-label = Уређивач цртежа
pdfjs-ink-canvas =
.aria-label = Кориснички направљена слика
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Алтернативни текст
pdfjs-editor-alt-text-edit-button =
.aria-label = Уреди алтернативни текст
pdfjs-editor-alt-text-edit-button-label = Уреди алтернативни текст
pdfjs-editor-alt-text-dialog-label = Одабери опцију
pdfjs-editor-alt-text-dialog-description = Алтернативни текст помаже слепим и слабовидим особама или када се слика не учита.
pdfjs-editor-alt-text-add-description-label = Додај опис
pdfjs-editor-alt-text-add-description-description = Сажмите у 1-2 реченице које описују предмет, окружење или радње.
pdfjs-editor-alt-text-mark-decorative-label = Означи као украсно
pdfjs-editor-alt-text-mark-decorative-description = Ово је за украсне слике, као што су ивице или водени печати.
pdfjs-editor-alt-text-cancel-button = Откажи
pdfjs-editor-alt-text-save-button = Сачувај
pdfjs-editor-alt-text-decorative-tooltip = Означено као украсно
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = На пример: „Младић седа за сто да једе“
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Алтернативни текст
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Горњи леви угао — промени величину
pdfjs-editor-resizer-label-top-middle = Средина горе — промени величину
pdfjs-editor-resizer-label-top-right = Горњи десни угао — промени величину
pdfjs-editor-resizer-label-middle-right = Средина десно — промени величину
pdfjs-editor-resizer-label-bottom-right = Доњи десни угао — промени величину
pdfjs-editor-resizer-label-bottom-middle = Средина доле — промени величину
pdfjs-editor-resizer-label-bottom-left = Доњи леви угао — промени величину
pdfjs-editor-resizer-label-middle-left = Средина лево — промени величину
pdfjs-editor-resizer-top-left =
.aria-label = Горњи леви угао — промени величину
pdfjs-editor-resizer-top-middle =
.aria-label = Средина горе — промени величину
pdfjs-editor-resizer-top-right =
.aria-label = Горњи десни угао — промени величину
pdfjs-editor-resizer-middle-right =
.aria-label = Средина десно — промени величину
pdfjs-editor-resizer-bottom-right =
.aria-label = Доњи десни угао — промени величину
pdfjs-editor-resizer-bottom-middle =
.aria-label = Средина доле — промени величину
pdfjs-editor-resizer-bottom-left =
.aria-label = Доњи леви угао — промени величину
pdfjs-editor-resizer-middle-left =
.aria-label = Средина лево — промени величину
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Боја означавања
pdfjs-editor-colorpicker-button =
.title = Промени боју
pdfjs-editor-colorpicker-dropdown =
.aria-label = Избор боја
pdfjs-editor-colorpicker-yellow =
.title = Жута
pdfjs-editor-colorpicker-green =
.title = Зелена
pdfjs-editor-colorpicker-blue =
.title = Плава
pdfjs-editor-colorpicker-pink =
.title = Розе
pdfjs-editor-colorpicker-red =
.title = Црвена
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Прикажи све
pdfjs-editor-highlight-show-all-button =
.title = Прикажи све
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Уреди алтернативни текст (опис слике)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Додај алтернативни текст (опис слике)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Напиши опис овде…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Кратак опис за слепе и слабовиде људе или када се слика не успе учитати.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Овај алтернативни текст је направљен аутоматски и може бити нетачан.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Сазнајте више
pdfjs-editor-new-alt-text-create-automatically-button-label = Прави алтернативни текст аутоматски
pdfjs-editor-new-alt-text-not-now-button = Не сада
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/sv-SE/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Föregående sida
pdfjs-previous-button-label = Föregående
pdfjs-next-button =
.title = Nästa sida
pdfjs-next-button-label = Nästa
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Sida
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = av { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } av { $pagesCount })
pdfjs-zoom-out-button =
.title = Zooma ut
pdfjs-zoom-out-button-label = Zooma ut
pdfjs-zoom-in-button =
.title = Zooma in
pdfjs-zoom-in-button-label = Zooma in
pdfjs-zoom-select =
.title = Zoom
pdfjs-presentation-mode-button =
.title = Byt till presentationsläge
pdfjs-presentation-mode-button-label = Presentationsläge
pdfjs-open-file-button =
.title = Öppna fil
pdfjs-open-file-button-label = Öppna
pdfjs-print-button =
.title = Skriv ut
pdfjs-print-button-label = Skriv ut
pdfjs-save-button =
.title = Spara
pdfjs-save-button-label = Spara
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Hämta
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Hämta
pdfjs-bookmark-button =
.title = Aktuell sida (Visa URL från aktuell sida)
pdfjs-bookmark-button-label = Aktuell sida
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Verktyg
pdfjs-tools-button-label = Verktyg
pdfjs-first-page-button =
.title = Gå till första sidan
pdfjs-first-page-button-label = Gå till första sidan
pdfjs-last-page-button =
.title = Gå till sista sidan
pdfjs-last-page-button-label = Gå till sista sidan
pdfjs-page-rotate-cw-button =
.title = Rotera medurs
pdfjs-page-rotate-cw-button-label = Rotera medurs
pdfjs-page-rotate-ccw-button =
.title = Rotera moturs
pdfjs-page-rotate-ccw-button-label = Rotera moturs
pdfjs-cursor-text-select-tool-button =
.title = Aktivera textmarkeringsverktyg
pdfjs-cursor-text-select-tool-button-label = Textmarkeringsverktyg
pdfjs-cursor-hand-tool-button =
.title = Aktivera handverktyg
pdfjs-cursor-hand-tool-button-label = Handverktyg
pdfjs-scroll-page-button =
.title = Använd sidrullning
pdfjs-scroll-page-button-label = Sidrullning
pdfjs-scroll-vertical-button =
.title = Använd vertikal rullning
pdfjs-scroll-vertical-button-label = Vertikal rullning
pdfjs-scroll-horizontal-button =
.title = Använd horisontell rullning
pdfjs-scroll-horizontal-button-label = Horisontell rullning
pdfjs-scroll-wrapped-button =
.title = Använd överlappande rullning
pdfjs-scroll-wrapped-button-label = Överlappande rullning
pdfjs-spread-none-button =
.title = Visa enkelsidor
pdfjs-spread-none-button-label = Enkelsidor
pdfjs-spread-odd-button =
.title = Visa uppslag med olika sidnummer till vänster
pdfjs-spread-odd-button-label = Uppslag med framsida
pdfjs-spread-even-button =
.title = Visa uppslag med lika sidnummer till vänster
pdfjs-spread-even-button-label = Uppslag utan framsida
## Document properties dialog
pdfjs-document-properties-button =
.title = Dokumentegenskaper…
pdfjs-document-properties-button-label = Dokumentegenskaper…
pdfjs-document-properties-file-name = Filnamn:
pdfjs-document-properties-file-size = Filstorlek:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } kB ({ $b } byte)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } byte)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } kB ({ $size_b } byte)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } byte)
pdfjs-document-properties-title = Titel:
pdfjs-document-properties-author = Författare:
pdfjs-document-properties-subject = Ämne:
pdfjs-document-properties-keywords = Nyckelord:
pdfjs-document-properties-creation-date = Skapades:
pdfjs-document-properties-modification-date = Ändrades:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Skapare:
pdfjs-document-properties-producer = PDF-producent:
pdfjs-document-properties-version = PDF-version:
pdfjs-document-properties-page-count = Sidantal:
pdfjs-document-properties-page-size = Pappersstorlek:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = porträtt
pdfjs-document-properties-page-size-orientation-landscape = landskap
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letter
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Snabb webbvisning:
pdfjs-document-properties-linearized-yes = Ja
pdfjs-document-properties-linearized-no = Nej
pdfjs-document-properties-close-button = Stäng
## Print
pdfjs-print-progress-message = Förbereder sidor för utskrift…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Avbryt
pdfjs-printing-not-supported = Varning: Utskrifter stöds inte helt av den här webbläsaren.
pdfjs-printing-not-ready = Varning: PDF:en är inte klar för utskrift.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Visa/dölj sidofält
pdfjs-toggle-sidebar-notification-button =
.title = Växla sidofält (dokumentet innehåller dokumentstruktur/bilagor/lager)
pdfjs-toggle-sidebar-button-label = Visa/dölj sidofält
pdfjs-document-outline-button =
.title = Visa dokumentdisposition (dubbelklicka för att expandera/komprimera alla objekt)
pdfjs-document-outline-button-label = Dokumentöversikt
pdfjs-attachments-button =
.title = Visa Bilagor
pdfjs-attachments-button-label = Bilagor
pdfjs-layers-button =
.title = Visa lager (dubbelklicka för att återställa alla lager till standardläge)
pdfjs-layers-button-label = Lager
pdfjs-thumbs-button =
.title = Visa miniatyrer
pdfjs-thumbs-button-label = Miniatyrer
pdfjs-current-outline-item-button =
.title = Hitta aktuellt dispositionsobjekt
pdfjs-current-outline-item-button-label = Aktuellt dispositionsobjekt
pdfjs-findbar-button =
.title = Sök i dokument
pdfjs-findbar-button-label = Sök
pdfjs-additional-layers = Ytterligare lager
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Sida { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Miniatyr av sida { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Sök
.placeholder = Sök i dokument…
pdfjs-find-previous-button =
.title = Hitta föregående förekomst av frasen
pdfjs-find-previous-button-label = Föregående
pdfjs-find-next-button =
.title = Hitta nästa förekomst av frasen
pdfjs-find-next-button-label = Nästa
pdfjs-find-highlight-checkbox = Markera alla
pdfjs-find-match-case-checkbox-label = Matcha versal/gemen
pdfjs-find-match-diacritics-checkbox-label = Matcha diakritiska tecken
pdfjs-find-entire-word-checkbox-label = Hela ord
pdfjs-find-reached-top = Nådde början av dokumentet, började från slutet
pdfjs-find-reached-bottom = Nådde slutet på dokumentet, började från början
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } av { $total } match
*[other] { $current } av { $total } matchningar
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Mer än { $limit } matchning
*[other] Fler än { $limit } matchningar
}
pdfjs-find-not-found = Frasen hittades inte
## Predefined zoom values
pdfjs-page-scale-width = Sidbredd
pdfjs-page-scale-fit = Anpassa sida
pdfjs-page-scale-auto = Automatisk zoom
pdfjs-page-scale-actual = Verklig storlek
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Sida { $page }
## Loading indicator messages
pdfjs-loading-error = Ett fel uppstod vid laddning av PDF-filen.
pdfjs-invalid-file-error = Ogiltig eller korrupt PDF-fil.
pdfjs-missing-file-error = Saknad PDF-fil.
pdfjs-unexpected-response-error = Oväntat svar från servern.
pdfjs-rendering-error = Ett fel uppstod vid visning av sidan.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date } { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type }-annotering]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Skriv in lösenordet för att öppna PDF-filen.
pdfjs-password-invalid = Ogiltigt lösenord. Försök igen.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Avbryt
pdfjs-web-fonts-disabled = Webbtypsnitt är inaktiverade: kan inte använda inbäddade PDF-typsnitt.
## Editing
pdfjs-editor-free-text-button =
.title = Text
pdfjs-editor-free-text-button-label = Text
pdfjs-editor-ink-button =
.title = Rita
pdfjs-editor-ink-button-label = Rita
pdfjs-editor-stamp-button =
.title = Lägg till eller redigera bilder
pdfjs-editor-stamp-button-label = Lägg till eller redigera bilder
pdfjs-editor-highlight-button =
.title = Markera
pdfjs-editor-highlight-button-label = Markera
pdfjs-highlight-floating-button1 =
.title = Markera
.aria-label = Markera
pdfjs-highlight-floating-button-label = Markera
pdfjs-editor-signature-button =
.title = Lägg till signatur
pdfjs-editor-signature-button-label = Lägg till signatur
## Default editor aria labels
# “Highlight” is a noun, the string is used on the editor for highlights.
pdfjs-editor-highlight-editor =
.aria-label = Markeringsredigerare
# “Drawing” is a noun, the string is used on the editor for drawings.
pdfjs-editor-ink-editor =
.aria-label = Ritredigerare
pdfjs-editor-signature-editor =
.aria-label = Signaturredigerare
pdfjs-editor-stamp-editor =
.aria-label = Bildredigerare
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Ta bort ritning
pdfjs-editor-remove-freetext-button =
.title = Ta bort text
pdfjs-editor-remove-stamp-button =
.title = Ta bort bild
pdfjs-editor-remove-highlight-button =
.title = Ta bort markering
pdfjs-editor-remove-signature-button =
.title = Ta bort signatur
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Färg
pdfjs-editor-free-text-size-input = Storlek
pdfjs-editor-ink-color-input = Färg
pdfjs-editor-ink-thickness-input = Tjocklek
pdfjs-editor-ink-opacity-input = Opacitet
pdfjs-editor-stamp-add-image-button =
.title = Lägg till bild
pdfjs-editor-stamp-add-image-button-label = Lägg till bild
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Tjocklek
pdfjs-editor-free-highlight-thickness-title =
.title = Ändra tjocklek när du markerar andra objekt än text
pdfjs-editor-add-signature-container =
.aria-label = Signaturkontroller och sparade signaturer
pdfjs-editor-signature-add-signature-button =
.title = Lägg till ny signatur
pdfjs-editor-signature-add-signature-button-label = Lägg till ny signatur
# Used on the button to use an already saved signature.
# Variables:
# $description (String) - a string describing/labeling the signature.
pdfjs-editor-add-saved-signature-button =
.title = Sparad signatur: { $description }
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Textredigerare
.default-content = Börja skriva…
pdfjs-free-text =
.aria-label = Textredigerare
pdfjs-free-text-default-content = Börja skriva…
pdfjs-ink =
.aria-label = Ritredigerare
pdfjs-ink-canvas =
.aria-label = Användarskapad bild
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Alternativ text
pdfjs-editor-alt-text-edit-button =
.aria-label = Redigera alternativ text
pdfjs-editor-alt-text-edit-button-label = Redigera alternativ text
pdfjs-editor-alt-text-dialog-label = Välj ett alternativ
pdfjs-editor-alt-text-dialog-description = Alt text (alternativ text) hjälper till när människor inte kan se bilden eller när den inte laddas.
pdfjs-editor-alt-text-add-description-label = Lägg till en beskrivning
pdfjs-editor-alt-text-add-description-description = Sikta på 1-2 meningar som beskriver ämnet, miljön eller handlingen.
pdfjs-editor-alt-text-mark-decorative-label = Markera som dekorativ
pdfjs-editor-alt-text-mark-decorative-description = Detta används för dekorativa bilder, som kanter eller vattenstämplar.
pdfjs-editor-alt-text-cancel-button = Avbryt
pdfjs-editor-alt-text-save-button = Spara
pdfjs-editor-alt-text-decorative-tooltip = Märkt som dekorativ
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Till exempel, "En ung man sätter sig vid ett bord för att äta en måltid"
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Alternativ text
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Det övre vänstra hörnet — ändra storlek
pdfjs-editor-resizer-label-top-middle = Överst i mitten — ändra storlek
pdfjs-editor-resizer-label-top-right = Det övre högra hörnet — ändra storlek
pdfjs-editor-resizer-label-middle-right = Mitten höger — ändra storlek
pdfjs-editor-resizer-label-bottom-right = Nedre högra hörnet — ändra storlek
pdfjs-editor-resizer-label-bottom-middle = Nedre mitten — ändra storlek
pdfjs-editor-resizer-label-bottom-left = Nedre vänstra hörnet — ändra storlek
pdfjs-editor-resizer-label-middle-left = Mitten till vänster — ändra storlek
pdfjs-editor-resizer-top-left =
.aria-label = Det övre vänstra hörnet — ändra storlek
pdfjs-editor-resizer-top-middle =
.aria-label = Överst i mitten — ändra storlek
pdfjs-editor-resizer-top-right =
.aria-label = Det övre högra hörnet — ändra storlek
pdfjs-editor-resizer-middle-right =
.aria-label = Mitten höger — ändra storlek
pdfjs-editor-resizer-bottom-right =
.aria-label = Nedre högra hörnet — ändra storlek
pdfjs-editor-resizer-bottom-middle =
.aria-label = Nedre mitten — ändra storlek
pdfjs-editor-resizer-bottom-left =
.aria-label = Nedre vänstra hörnet — ändra storlek
pdfjs-editor-resizer-middle-left =
.aria-label = Mitten till vänster — ändra storlek
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Markeringsfärg
pdfjs-editor-colorpicker-button =
.title = Ändra färg
pdfjs-editor-colorpicker-dropdown =
.aria-label = Färgval
pdfjs-editor-colorpicker-yellow =
.title = Gul
pdfjs-editor-colorpicker-green =
.title = Grön
pdfjs-editor-colorpicker-blue =
.title = Blå
pdfjs-editor-colorpicker-pink =
.title = Rosa
pdfjs-editor-colorpicker-red =
.title = Röd
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Visa alla
pdfjs-editor-highlight-show-all-button =
.title = Visa alla
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Redigera alternativ text (bildbeskrivning)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Lägg till alternativ text (bildbeskrivning)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Skriv din beskrivning här…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Kort beskrivning för personer som inte kan se bilden eller när bilden inte laddas.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Denna alternativa text skapades automatiskt och kan vara felaktig.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Läs mer
pdfjs-editor-new-alt-text-create-automatically-button-label = Skapa alternativ text automatiskt
pdfjs-editor-new-alt-text-not-now-button = Inte nu
pdfjs-editor-new-alt-text-error-title = Det gick inte att skapa alternativ text automatiskt
pdfjs-editor-new-alt-text-error-description = Skriv din egna alternativa text eller försök igen senare.
pdfjs-editor-new-alt-text-error-close-button = Stäng
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Hämtar AI-modell med alternativ text ({ $downloadedSize } av { $totalSize } MB)
.aria-valuetext = Hämtar AI-modell med alternativ text ({ $downloadedSize } av { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Alternativ text tillagd
pdfjs-editor-new-alt-text-added-button-label = Alternativ text tillagd
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Saknar alternativ text
pdfjs-editor-new-alt-text-missing-button-label = Saknar alternativ text
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Granska alternativ text
pdfjs-editor-new-alt-text-to-review-button-label = Granska alternativ text
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Skapas automatiskt: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Alternativ textinställningar för bild
pdfjs-image-alt-text-settings-button-label = Alternativ textinställningar för bild
pdfjs-editor-alt-text-settings-dialog-label = Alternativ textinställningar för bild
pdfjs-editor-alt-text-settings-automatic-title = Automatisk alternativ text
pdfjs-editor-alt-text-settings-create-model-button-label = Skapa alternativ text automatiskt
pdfjs-editor-alt-text-settings-create-model-description = Föreslår beskrivningar för att hjälpa personer som inte kan se bilden eller när bilden inte laddas.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = AI-modell för alternativ text ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Körs lokalt på din enhet så att din data förblir privat. Krävs för automatisk alternativ text.
pdfjs-editor-alt-text-settings-delete-model-button = Ta bort
pdfjs-editor-alt-text-settings-download-model-button = Hämta
pdfjs-editor-alt-text-settings-downloading-model-button = Hämtar…
pdfjs-editor-alt-text-settings-editor-title = Alternativ textredigerare
pdfjs-editor-alt-text-settings-show-dialog-button-label = Visa alternativ textredigerare direkt när du lägger till en bild
pdfjs-editor-alt-text-settings-show-dialog-description = Hjälper dig att se till att alla dina bilder har alternativ text.
pdfjs-editor-alt-text-settings-close-button = Stäng
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Markering borttagen
pdfjs-editor-undo-bar-message-freetext = Text borttagen
pdfjs-editor-undo-bar-message-ink = Ritning borttagen
pdfjs-editor-undo-bar-message-stamp = Bild borttagen
pdfjs-editor-undo-bar-message-signature = Signatur borttagen
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } anteckning har tagits bort
*[other] { $count } anteckningar har tagits bort
}
pdfjs-editor-undo-bar-undo-button =
.title = Ångra
pdfjs-editor-undo-bar-undo-button-label = Ångra
pdfjs-editor-undo-bar-close-button =
.title = Stäng
pdfjs-editor-undo-bar-close-button-label = Stäng
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = Denna modal tillåter användaren att skapa en signatur för att lägga till i ett PDF-dokument. Användaren kan redigera namnet (som också fungerar som alternativ text) och eventuellt spara signaturen för upprepad användning.
pdfjs-editor-add-signature-dialog-title = Lägg till en signatur
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Typ
.title = Typ
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Rita
.title = Rita
pdfjs-editor-add-signature-image-button = Bild
.title = Bild
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Skriv din signatur
.placeholder = Skriv din signatur
pdfjs-editor-add-signature-draw-placeholder = Rita din signatur
pdfjs-editor-add-signature-draw-thickness-range-label = Tjocklek
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Ritningstjocklek: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Dra en fil hit för att ladda upp
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Eller välj bildfiler
*[other] Eller bläddra bland bildfiler
}
## Controls
pdfjs-editor-add-signature-description-label = Beskrivning (alternativ text)
pdfjs-editor-add-signature-description-input =
.title = Beskrivning (alternativ text)
pdfjs-editor-add-signature-description-default-when-drawing = Signatur
pdfjs-editor-add-signature-clear-button-label = Rensa signatur
pdfjs-editor-add-signature-clear-button =
.title = Rensa signatur
pdfjs-editor-add-signature-save-checkbox = Spara signatur
pdfjs-editor-add-signature-save-warning-message = Du har nått gränsen på 5 sparade signaturer. Ta bort en för att spara fler.
pdfjs-editor-add-signature-image-upload-error-title = Det gick inte att ladda upp bilden
pdfjs-editor-add-signature-image-upload-error-description = Kontrollera din nätverksanslutning eller försök med en annan bild.
pdfjs-editor-add-signature-error-close-button = Stäng
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Avbryt
pdfjs-editor-add-signature-add-button = Lägg till
pdfjs-editor-edit-signature-update-button = Uppdatera
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Ta bort signatur
pdfjs-editor-delete-signature-button-label = Ta bort signatur
pdfjs-editor-delete-signature-button1 =
.title = Ta bort sparad signatur
pdfjs-editor-delete-signature-button-label1 = Ta bort sparad signatur
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Redigera beskrivning
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Redigera beskrivning
================================================
FILE: cookbook/static/pdfjs/web/locale/szl/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Piyrwyjszo strōna
pdfjs-previous-button-label = Piyrwyjszo
pdfjs-next-button =
.title = Nastympno strōna
pdfjs-next-button-label = Dalij
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Strōna
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = ze { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } ze { $pagesCount })
pdfjs-zoom-out-button =
.title = Zmyńsz
pdfjs-zoom-out-button-label = Zmyńsz
pdfjs-zoom-in-button =
.title = Zwiynksz
pdfjs-zoom-in-button-label = Zwiynksz
pdfjs-zoom-select =
.title = Srogość
pdfjs-presentation-mode-button =
.title = Przełōncz na tryb prezyntacyje
pdfjs-presentation-mode-button-label = Tryb prezyntacyje
pdfjs-open-file-button =
.title = Ôdewrzij zbiōr
pdfjs-open-file-button-label = Ôdewrzij
pdfjs-print-button =
.title = Durkuj
pdfjs-print-button-label = Durkuj
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Noczynia
pdfjs-tools-button-label = Noczynia
pdfjs-first-page-button =
.title = Idź ku piyrszyj strōnie
pdfjs-first-page-button-label = Idź ku piyrszyj strōnie
pdfjs-last-page-button =
.title = Idź ku ôstatnij strōnie
pdfjs-last-page-button-label = Idź ku ôstatnij strōnie
pdfjs-page-rotate-cw-button =
.title = Zwyrtnij w prawo
pdfjs-page-rotate-cw-button-label = Zwyrtnij w prawo
pdfjs-page-rotate-ccw-button =
.title = Zwyrtnij w lewo
pdfjs-page-rotate-ccw-button-label = Zwyrtnij w lewo
pdfjs-cursor-text-select-tool-button =
.title = Załōncz noczynie ôbiyranio tekstu
pdfjs-cursor-text-select-tool-button-label = Noczynie ôbiyranio tekstu
pdfjs-cursor-hand-tool-button =
.title = Załōncz noczynie rōnczka
pdfjs-cursor-hand-tool-button-label = Noczynie rōnczka
pdfjs-scroll-vertical-button =
.title = Używej piōnowego przewijanio
pdfjs-scroll-vertical-button-label = Piōnowe przewijanie
pdfjs-scroll-horizontal-button =
.title = Używej poziōmego przewijanio
pdfjs-scroll-horizontal-button-label = Poziōme przewijanie
pdfjs-scroll-wrapped-button =
.title = Używej szichtowego przewijanio
pdfjs-scroll-wrapped-button-label = Szichtowe przewijanie
pdfjs-spread-none-button =
.title = Niy dowej strōn w widoku po dwie
pdfjs-spread-none-button-label = Po jednyj strōnie
pdfjs-spread-odd-button =
.title = Pokoż strōny po dwie; niyporziste po lewyj
pdfjs-spread-odd-button-label = Niyporziste po lewyj
pdfjs-spread-even-button =
.title = Pokoż strōny po dwie; porziste po lewyj
pdfjs-spread-even-button-label = Porziste po lewyj
## Document properties dialog
pdfjs-document-properties-button =
.title = Włosności dokumyntu…
pdfjs-document-properties-button-label = Włosności dokumyntu…
pdfjs-document-properties-file-name = Miano zbioru:
pdfjs-document-properties-file-size = Srogość zbioru:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } B)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } B)
pdfjs-document-properties-title = Tytuł:
pdfjs-document-properties-author = Autōr:
pdfjs-document-properties-subject = Tymat:
pdfjs-document-properties-keywords = Kluczowe słowa:
pdfjs-document-properties-creation-date = Data zrychtowanio:
pdfjs-document-properties-modification-date = Data zmiany:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Zrychtowane ôd:
pdfjs-document-properties-producer = PDF ôd:
pdfjs-document-properties-version = Wersyjo PDF:
pdfjs-document-properties-page-count = Wielość strōn:
pdfjs-document-properties-page-size = Srogość strōny:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = piōnowo
pdfjs-document-properties-page-size-orientation-landscape = poziōmo
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letter
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Gibki necowy podglōnd:
pdfjs-document-properties-linearized-yes = Ja
pdfjs-document-properties-linearized-no = Niy
pdfjs-document-properties-close-button = Zawrzij
## Print
pdfjs-print-progress-message = Rychtowanie dokumyntu do durku…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Pociep
pdfjs-printing-not-supported = Pozōr: Ta przeglōndarka niy cołkiym ôbsuguje durk.
pdfjs-printing-not-ready = Pozōr: Tyn PDF niy ma za tela zaladowany do durku.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Przełōncz posek na rancie
pdfjs-toggle-sidebar-notification-button =
.title = Przełōncz posek na rancie (dokumynt mo struktura/przidowki/warstwy)
pdfjs-toggle-sidebar-button-label = Przełōncz posek na rancie
pdfjs-document-outline-button =
.title = Pokoż struktura dokumyntu (tuplowane klikniyncie rozszyrzo/swijo wszyskie elymynta)
pdfjs-document-outline-button-label = Struktura dokumyntu
pdfjs-attachments-button =
.title = Pokoż przidowki
pdfjs-attachments-button-label = Przidowki
pdfjs-layers-button =
.title = Pokoż warstwy (tuplowane klikniyncie resetuje wszyskie warstwy do bazowego stanu)
pdfjs-layers-button-label = Warstwy
pdfjs-thumbs-button =
.title = Pokoż miniatury
pdfjs-thumbs-button-label = Miniatury
pdfjs-findbar-button =
.title = Znojdź w dokumyncie
pdfjs-findbar-button-label = Znojdź
pdfjs-additional-layers = Nadbytnie warstwy
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Strōna { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Miniatura strōny { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Znojdź
.placeholder = Znojdź w dokumyncie…
pdfjs-find-previous-button =
.title = Znojdź piyrwyjsze pokozanie sie tyj frazy
pdfjs-find-previous-button-label = Piyrwyjszo
pdfjs-find-next-button =
.title = Znojdź nastympne pokozanie sie tyj frazy
pdfjs-find-next-button-label = Dalij
pdfjs-find-highlight-checkbox = Zaznacz wszysko
pdfjs-find-match-case-checkbox-label = Poznowej srogość liter
pdfjs-find-entire-word-checkbox-label = Cołke słowa
pdfjs-find-reached-top = Doszło do samego wiyrchu strōny, dalij ôd spodku
pdfjs-find-reached-bottom = Doszło do samego spodku strōny, dalij ôd wiyrchu
pdfjs-find-not-found = Fraza niy znaleziōno
## Predefined zoom values
pdfjs-page-scale-width = Szyrzka strōny
pdfjs-page-scale-fit = Napasowanie strōny
pdfjs-page-scale-auto = Autōmatyczno srogość
pdfjs-page-scale-actual = Aktualno srogość
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
## Loading indicator messages
pdfjs-loading-error = Przi ladowaniu PDFa pokozoł sie feler.
pdfjs-invalid-file-error = Zły abo felerny zbiōr PDF.
pdfjs-missing-file-error = Chybio zbioru PDF.
pdfjs-unexpected-response-error = Niyôczekowano ôdpowiydź serwera.
pdfjs-rendering-error = Przi renderowaniu strōny pokozoł sie feler.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [Anotacyjo typu { $type }]
## Password
pdfjs-password-label = Wkludź hasło, coby ôdewrzić tyn zbiōr PDF.
pdfjs-password-invalid = Hasło je złe. Sprōbuj jeszcze roz.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Pociep
pdfjs-web-fonts-disabled = Necowe fōnty sōm zastawiōne: niy idzie użyć wkludzōnych fōntōw PDF.
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/ta/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = முந்தைய பக்கம்
pdfjs-previous-button-label = முந்தையது
pdfjs-next-button =
.title = அடுத்த பக்கம்
pdfjs-next-button-label = அடுத்து
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = பக்கம்
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = { $pagesCount } இல்
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = { $pagesCount }) இல் ({ $pageNumber }
pdfjs-zoom-out-button =
.title = சிறிதாக்கு
pdfjs-zoom-out-button-label = சிறிதாக்கு
pdfjs-zoom-in-button =
.title = பெரிதாக்கு
pdfjs-zoom-in-button-label = பெரிதாக்கு
pdfjs-zoom-select =
.title = பெரிதாக்கு
pdfjs-presentation-mode-button =
.title = விளக்ககாட்சி பயன்முறைக்கு மாறு
pdfjs-presentation-mode-button-label = விளக்ககாட்சி பயன்முறை
pdfjs-open-file-button =
.title = கோப்பினை திற
pdfjs-open-file-button-label = திற
pdfjs-print-button =
.title = அச்சிடு
pdfjs-print-button-label = அச்சிடு
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = கருவிகள்
pdfjs-tools-button-label = கருவிகள்
pdfjs-first-page-button =
.title = முதல் பக்கத்திற்கு செல்லவும்
pdfjs-first-page-button-label = முதல் பக்கத்திற்கு செல்லவும்
pdfjs-last-page-button =
.title = கடைசி பக்கத்திற்கு செல்லவும்
pdfjs-last-page-button-label = கடைசி பக்கத்திற்கு செல்லவும்
pdfjs-page-rotate-cw-button =
.title = வலஞ்சுழியாக சுழற்று
pdfjs-page-rotate-cw-button-label = வலஞ்சுழியாக சுழற்று
pdfjs-page-rotate-ccw-button =
.title = இடஞ்சுழியாக சுழற்று
pdfjs-page-rotate-ccw-button-label = இடஞ்சுழியாக சுழற்று
pdfjs-cursor-text-select-tool-button =
.title = உரைத் தெரிவு கருவியைச் செயல்படுத்து
pdfjs-cursor-text-select-tool-button-label = உரைத் தெரிவு கருவி
pdfjs-cursor-hand-tool-button =
.title = கைக் கருவிக்ச் செயற்படுத்து
pdfjs-cursor-hand-tool-button-label = கைக்குருவி
## Document properties dialog
pdfjs-document-properties-button =
.title = ஆவண பண்புகள்...
pdfjs-document-properties-button-label = ஆவண பண்புகள்...
pdfjs-document-properties-file-name = கோப்பு பெயர்:
pdfjs-document-properties-file-size = கோப்பின் அளவு:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } கிபை ({ $size_b } பைட்டுகள்)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } மெபை ({ $size_b } பைட்டுகள்)
pdfjs-document-properties-title = தலைப்பு:
pdfjs-document-properties-author = எழுதியவர்
pdfjs-document-properties-subject = பொருள்:
pdfjs-document-properties-keywords = முக்கிய வார்த்தைகள்:
pdfjs-document-properties-creation-date = படைத்த தேதி :
pdfjs-document-properties-modification-date = திருத்திய தேதி:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = உருவாக்குபவர்:
pdfjs-document-properties-producer = பிடிஎஃப் தயாரிப்பாளர்:
pdfjs-document-properties-version = PDF பதிப்பு:
pdfjs-document-properties-page-count = பக்க எண்ணிக்கை:
pdfjs-document-properties-page-size = பக்க அளவு:
pdfjs-document-properties-page-size-unit-inches = இதில்
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = நிலைபதிப்பு
pdfjs-document-properties-page-size-orientation-landscape = நிலைபரப்பு
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = கடிதம்
pdfjs-document-properties-page-size-name-legal = சட்டபூர்வ
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
pdfjs-document-properties-close-button = மூடுக
## Print
pdfjs-print-progress-message = அச்சிடுவதற்கான ஆவணம் தயாராகிறது...
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = ரத்து
pdfjs-printing-not-supported = எச்சரிக்கை: இந்த உலாவி அச்சிடுதலை முழுமையாக ஆதரிக்கவில்லை.
pdfjs-printing-not-ready = எச்சரிக்கை: PDF அச்சிட முழுவதுமாக ஏற்றப்படவில்லை.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = பக்கப் பட்டியை நிலைமாற்று
pdfjs-toggle-sidebar-button-label = பக்கப் பட்டியை நிலைமாற்று
pdfjs-document-outline-button =
.title = ஆவண அடக்கத்தைக் காட்டு (இருமுறைச் சொடுக்கி அனைத்து உறுப்பிடிகளையும் விரி/சேர்)
pdfjs-document-outline-button-label = ஆவண வெளிவரை
pdfjs-attachments-button =
.title = இணைப்புகளை காண்பி
pdfjs-attachments-button-label = இணைப்புகள்
pdfjs-thumbs-button =
.title = சிறுபடங்களைக் காண்பி
pdfjs-thumbs-button-label = சிறுபடங்கள்
pdfjs-findbar-button =
.title = ஆவணத்தில் கண்டறி
pdfjs-findbar-button-label = தேடு
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = பக்கம் { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = பக்கத்தின் சிறுபடம் { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = கண்டுபிடி
.placeholder = ஆவணத்தில் கண்டறி…
pdfjs-find-previous-button =
.title = இந்த சொற்றொடரின் முந்தைய நிகழ்வை தேடு
pdfjs-find-previous-button-label = முந்தையது
pdfjs-find-next-button =
.title = இந்த சொற்றொடரின் அடுத்த நிகழ்வை தேடு
pdfjs-find-next-button-label = அடுத்து
pdfjs-find-highlight-checkbox = அனைத்தையும் தனிப்படுத்து
pdfjs-find-match-case-checkbox-label = பேரெழுத்தாக்கத்தை உணர்
pdfjs-find-reached-top = ஆவணத்தின் மேல் பகுதியை அடைந்தது, அடிப்பக்கத்திலிருந்து தொடர்ந்தது
pdfjs-find-reached-bottom = ஆவணத்தின் முடிவை அடைந்தது, மேலிருந்து தொடர்ந்தது
pdfjs-find-not-found = சொற்றொடர் காணவில்லை
## Predefined zoom values
pdfjs-page-scale-width = பக்க அகலம்
pdfjs-page-scale-fit = பக்கப் பொருத்தம்
pdfjs-page-scale-auto = தானியக்க பெரிதாக்கல்
pdfjs-page-scale-actual = உண்மையான அளவு
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
## Loading indicator messages
pdfjs-loading-error = PDF ஐ ஏற்றும் போது ஒரு பிழை ஏற்பட்டது.
pdfjs-invalid-file-error = செல்லுபடியாகாத அல்லது சிதைந்த PDF கோப்பு.
pdfjs-missing-file-error = PDF கோப்பு காணவில்லை.
pdfjs-unexpected-response-error = சேவகன் பதில் எதிர்பாரதது.
pdfjs-rendering-error = இந்தப் பக்கத்தை காட்சிப்படுத்தும் போது ஒரு பிழை ஏற்பட்டது.
## Annotations
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } விளக்கம்]
## Password
pdfjs-password-label = இந்த PDF கோப்பை திறக்க கடவுச்சொல்லை உள்ளிடவும்.
pdfjs-password-invalid = செல்லுபடியாகாத கடவுச்சொல், தயை செய்து மீண்டும் முயற்சி செய்க.
pdfjs-password-ok-button = சரி
pdfjs-password-cancel-button = ரத்து
pdfjs-web-fonts-disabled = வலை எழுத்துருக்கள் முடக்கப்பட்டுள்ளன: உட்பொதிக்கப்பட்ட PDF எழுத்துருக்களைப் பயன்படுத்த முடியவில்லை.
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/te/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = మునుపటి పేజీ
pdfjs-previous-button-label = క్రితం
pdfjs-next-button =
.title = తరువాత పేజీ
pdfjs-next-button-label = తరువాత
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = పేజీ
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = మొత్తం { $pagesCount } లో
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = (మొత్తం { $pagesCount } లో { $pageNumber }వది)
pdfjs-zoom-out-button =
.title = జూమ్ తగ్గించు
pdfjs-zoom-out-button-label = జూమ్ తగ్గించు
pdfjs-zoom-in-button =
.title = జూమ్ చేయి
pdfjs-zoom-in-button-label = జూమ్ చేయి
pdfjs-zoom-select =
.title = జూమ్
pdfjs-presentation-mode-button =
.title = ప్రదర్శనా రీతికి మారు
pdfjs-presentation-mode-button-label = ప్రదర్శనా రీతి
pdfjs-open-file-button =
.title = ఫైల్ తెరువు
pdfjs-open-file-button-label = తెరువు
pdfjs-print-button =
.title = ముద్రించు
pdfjs-print-button-label = ముద్రించు
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = పనిముట్లు
pdfjs-tools-button-label = పనిముట్లు
pdfjs-first-page-button =
.title = మొదటి పేజీకి వెళ్ళు
pdfjs-first-page-button-label = మొదటి పేజీకి వెళ్ళు
pdfjs-last-page-button =
.title = చివరి పేజీకి వెళ్ళు
pdfjs-last-page-button-label = చివరి పేజీకి వెళ్ళు
pdfjs-page-rotate-cw-button =
.title = సవ్యదిశలో తిప్పు
pdfjs-page-rotate-cw-button-label = సవ్యదిశలో తిప్పు
pdfjs-page-rotate-ccw-button =
.title = అపసవ్యదిశలో తిప్పు
pdfjs-page-rotate-ccw-button-label = అపసవ్యదిశలో తిప్పు
pdfjs-cursor-text-select-tool-button =
.title = టెక్స్ట్ ఎంపిక సాధనాన్ని ప్రారంభించండి
pdfjs-cursor-text-select-tool-button-label = టెక్స్ట్ ఎంపిక సాధనం
pdfjs-cursor-hand-tool-button =
.title = చేతి సాధనం చేతనించు
pdfjs-cursor-hand-tool-button-label = చేతి సాధనం
pdfjs-scroll-vertical-button-label = నిలువు స్క్రోలింగు
## Document properties dialog
pdfjs-document-properties-button =
.title = పత్రము లక్షణాలు...
pdfjs-document-properties-button-label = పత్రము లక్షణాలు...
pdfjs-document-properties-file-name = దస్త్రం పేరు:
pdfjs-document-properties-file-size = దస్త్రం పరిమాణం:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = శీర్షిక:
pdfjs-document-properties-author = మూలకర్త:
pdfjs-document-properties-subject = విషయం:
pdfjs-document-properties-keywords = కీ పదాలు:
pdfjs-document-properties-creation-date = సృష్టించిన తేదీ:
pdfjs-document-properties-modification-date = సవరించిన తేదీ:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = సృష్టికర్త:
pdfjs-document-properties-producer = PDF ఉత్పాదకి:
pdfjs-document-properties-version = PDF వర్షన్:
pdfjs-document-properties-page-count = పేజీల సంఖ్య:
pdfjs-document-properties-page-size = కాగితం పరిమాణం:
pdfjs-document-properties-page-size-unit-inches = లో
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = నిలువుచిత్రం
pdfjs-document-properties-page-size-orientation-landscape = అడ్డచిత్రం
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = లేఖ
pdfjs-document-properties-page-size-name-legal = చట్టపరమైన
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
pdfjs-document-properties-linearized-yes = అవును
pdfjs-document-properties-linearized-no = కాదు
pdfjs-document-properties-close-button = మూసివేయి
## Print
pdfjs-print-progress-message = ముద్రించడానికి పత్రము సిద్ధమవుతున్నది…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = రద్దుచేయి
pdfjs-printing-not-supported = హెచ్చరిక: ఈ విహారిణి చేత ముద్రణ పూర్తిగా తోడ్పాటు లేదు.
pdfjs-printing-not-ready = హెచ్చరిక: ముద్రణ కొరకు ఈ PDF పూర్తిగా లోడవలేదు.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = పక్కపట్టీ మార్చు
pdfjs-toggle-sidebar-button-label = పక్కపట్టీ మార్చు
pdfjs-document-outline-button =
.title = పత్రము రూపము చూపించు (డబుల్ క్లిక్ చేసి అన్ని అంశాలను విస్తరించు/కూల్చు)
pdfjs-document-outline-button-label = పత్రము అవుట్లైన్
pdfjs-attachments-button =
.title = అనుబంధాలు చూపు
pdfjs-attachments-button-label = అనుబంధాలు
pdfjs-layers-button-label = పొరలు
pdfjs-thumbs-button =
.title = థంబ్నైల్స్ చూపు
pdfjs-thumbs-button-label = థంబ్నైల్స్
pdfjs-findbar-button =
.title = పత్రములో కనుగొనుము
pdfjs-findbar-button-label = కనుగొను
pdfjs-additional-layers = అదనపు పొరలు
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = పేజీ { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = { $page } పేజీ నఖచిత్రం
## Find panel button title and messages
pdfjs-find-input =
.title = కనుగొను
.placeholder = పత్రములో కనుగొను…
pdfjs-find-previous-button =
.title = పదం యొక్క ముందు సంభవాన్ని కనుగొను
pdfjs-find-previous-button-label = మునుపటి
pdfjs-find-next-button =
.title = పదం యొక్క తర్వాతి సంభవాన్ని కనుగొను
pdfjs-find-next-button-label = తరువాత
pdfjs-find-highlight-checkbox = అన్నిటిని ఉద్దీపనం చేయుము
pdfjs-find-match-case-checkbox-label = అక్షరముల తేడాతో పోల్చు
pdfjs-find-entire-word-checkbox-label = పూర్తి పదాలు
pdfjs-find-reached-top = పేజీ పైకి చేరుకున్నది, క్రింది నుండి కొనసాగించండి
pdfjs-find-reached-bottom = పేజీ చివరకు చేరుకున్నది, పైనుండి కొనసాగించండి
pdfjs-find-not-found = పదబంధం కనబడలేదు
## Predefined zoom values
pdfjs-page-scale-width = పేజీ వెడల్పు
pdfjs-page-scale-fit = పేజీ అమర్పు
pdfjs-page-scale-auto = స్వయంచాలక జూమ్
pdfjs-page-scale-actual = యథార్ధ పరిమాణం
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
## Loading indicator messages
pdfjs-loading-error = PDF లోడవుచున్నప్పుడు ఒక దోషం ఎదురైంది.
pdfjs-invalid-file-error = చెల్లని లేదా పాడైన PDF ఫైలు.
pdfjs-missing-file-error = దొరకని PDF ఫైలు.
pdfjs-unexpected-response-error = అనుకోని సర్వర్ స్పందన.
pdfjs-rendering-error = పేజీను రెండర్ చేయుటలో ఒక దోషం ఎదురైంది.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } టీకా]
## Password
pdfjs-password-label = ఈ PDF ఫైల్ తెరుచుటకు సంకేతపదం ప్రవేశపెట్టుము.
pdfjs-password-invalid = సంకేతపదం చెల్లదు. దయచేసి మళ్ళీ ప్రయత్నించండి.
pdfjs-password-ok-button = సరే
pdfjs-password-cancel-button = రద్దుచేయి
pdfjs-web-fonts-disabled = వెబ్ ఫాంట్లు అచేతనించబడెను: ఎంబెడెడ్ PDF ఫాంట్లు ఉపయోగించలేక పోయింది.
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
# Editor Parameters
pdfjs-editor-free-text-color-input = రంగు
pdfjs-editor-free-text-size-input = పరిమాణం
pdfjs-editor-ink-color-input = రంగు
pdfjs-editor-ink-thickness-input = మందం
pdfjs-editor-ink-opacity-input = అకిరణ్యత
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/tg/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Саҳифаи қаблӣ
pdfjs-previous-button-label = Қаблӣ
pdfjs-next-button =
.title = Саҳифаи навбатӣ
pdfjs-next-button-label = Навбатӣ
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Саҳифа
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = аз { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } аз { $pagesCount })
pdfjs-zoom-out-button =
.title = Хурд кардан
pdfjs-zoom-out-button-label = Хурд кардан
pdfjs-zoom-in-button =
.title = Калон кардан
pdfjs-zoom-in-button-label = Калон кардан
pdfjs-zoom-select =
.title = Танзими андоза
pdfjs-presentation-mode-button =
.title = Гузариш ба реҷаи тақдим
pdfjs-presentation-mode-button-label = Реҷаи тақдим
pdfjs-open-file-button =
.title = Кушодани файл
pdfjs-open-file-button-label = Кушодан
pdfjs-print-button =
.title = Чоп кардан
pdfjs-print-button-label = Чоп кардан
pdfjs-save-button =
.title = Нигоҳ доштан
pdfjs-save-button-label = Нигоҳ доштан
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Боргирӣ кардан
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Боргирӣ кардан
pdfjs-bookmark-button =
.title = Саҳифаи ҷорӣ (Дидани нишонии URL аз саҳифаи ҷорӣ)
pdfjs-bookmark-button-label = Саҳифаи ҷорӣ
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Абзорҳо
pdfjs-tools-button-label = Абзорҳо
pdfjs-first-page-button =
.title = Ба саҳифаи аввал гузаред
pdfjs-first-page-button-label = Ба саҳифаи аввал гузаред
pdfjs-last-page-button =
.title = Ба саҳифаи охирин гузаред
pdfjs-last-page-button-label = Ба саҳифаи охирин гузаред
pdfjs-page-rotate-cw-button =
.title = Ба самти ҳаракати ақрабаки соат давр задан
pdfjs-page-rotate-cw-button-label = Ба самти ҳаракати ақрабаки соат давр задан
pdfjs-page-rotate-ccw-button =
.title = Ба муқобили самти ҳаракати ақрабаки соат давр задан
pdfjs-page-rotate-ccw-button-label = Ба муқобили самти ҳаракати ақрабаки соат давр задан
pdfjs-cursor-text-select-tool-button =
.title = Фаъол кардани «Абзори интихоби матн»
pdfjs-cursor-text-select-tool-button-label = Абзори интихоби матн
pdfjs-cursor-hand-tool-button =
.title = Фаъол кардани «Абзори даст»
pdfjs-cursor-hand-tool-button-label = Абзори даст
pdfjs-scroll-page-button =
.title = Истифодаи варақзанӣ
pdfjs-scroll-page-button-label = Варақзанӣ
pdfjs-scroll-vertical-button =
.title = Истифодаи варақзании амудӣ
pdfjs-scroll-vertical-button-label = Варақзании амудӣ
pdfjs-scroll-horizontal-button =
.title = Истифодаи варақзании уфуқӣ
pdfjs-scroll-horizontal-button-label = Варақзании уфуқӣ
pdfjs-scroll-wrapped-button =
.title = Истифодаи варақзании миқёсбандӣ
pdfjs-scroll-wrapped-button-label = Варақзании миқёсбандӣ
pdfjs-spread-none-button =
.title = Густариши саҳифаҳо истифода бурда нашавад
pdfjs-spread-none-button-label = Бе густурдани саҳифаҳо
pdfjs-spread-odd-button =
.title = Густариши саҳифаҳо аз саҳифаҳо бо рақамҳои тоқ оғоз карда мешавад
pdfjs-spread-odd-button-label = Саҳифаҳои тоқ аз тарафи чап
pdfjs-spread-even-button =
.title = Густариши саҳифаҳо аз саҳифаҳо бо рақамҳои ҷуфт оғоз карда мешавад
pdfjs-spread-even-button-label = Саҳифаҳои ҷуфт аз тарафи чап
## Document properties dialog
pdfjs-document-properties-button =
.title = Хусусиятҳои ҳуҷҷат…
pdfjs-document-properties-button-label = Хусусиятҳои ҳуҷҷат…
pdfjs-document-properties-file-name = Номи файл:
pdfjs-document-properties-file-size = Андозаи файл:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } КБ ({ $b } байт)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } МБ ({ $b } байт)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } КБ ({ $size_b } байт)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } МБ ({ $size_b } байт)
pdfjs-document-properties-title = Сарлавҳа:
pdfjs-document-properties-author = Муаллиф:
pdfjs-document-properties-subject = Мавзуъ:
pdfjs-document-properties-keywords = Калимаҳои калидӣ:
pdfjs-document-properties-creation-date = Санаи эҷод:
pdfjs-document-properties-modification-date = Санаи тағйирот:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Эҷодкунанда:
pdfjs-document-properties-producer = Таҳиякунандаи «PDF»:
pdfjs-document-properties-version = Версияи «PDF»:
pdfjs-document-properties-page-count = Шумораи саҳифаҳо:
pdfjs-document-properties-page-size = Андозаи саҳифа:
pdfjs-document-properties-page-size-unit-inches = дюйм
pdfjs-document-properties-page-size-unit-millimeters = мм
pdfjs-document-properties-page-size-orientation-portrait = амудӣ
pdfjs-document-properties-page-size-orientation-landscape = уфуқӣ
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Мактуб
pdfjs-document-properties-page-size-name-legal = Ҳуқуқӣ
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Намоиши тез дар Интернет:
pdfjs-document-properties-linearized-yes = Ҳа
pdfjs-document-properties-linearized-no = Не
pdfjs-document-properties-close-button = Пӯшидан
## Print
pdfjs-print-progress-message = Омодасозии ҳуҷҷат барои чоп…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Бекор кардан
pdfjs-printing-not-supported = Диққат: Чопкунӣ аз тарафи ин браузер ба таври пурра дастгирӣ намешавад.
pdfjs-printing-not-ready = Диққат: Файли «PDF» барои чопкунӣ пурра бор карда нашуд.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Фаъол кардани навори ҷонибӣ
pdfjs-toggle-sidebar-notification-button =
.title = Фаъол кардани навори ҷонибӣ (ҳуҷҷат дорои сохтор/замимаҳо/қабатҳо мебошад)
pdfjs-toggle-sidebar-button-label = Фаъол кардани навори ҷонибӣ
pdfjs-document-outline-button =
.title = Намоиш додани сохтори ҳуҷҷат (барои баркушодан/пеҷондани ҳамаи унсурҳо дубора зер кунед)
pdfjs-document-outline-button-label = Сохтори ҳуҷҷат
pdfjs-attachments-button =
.title = Намоиш додани замимаҳо
pdfjs-attachments-button-label = Замимаҳо
pdfjs-layers-button =
.title = Намоиш додани қабатҳо (барои барқарор кардани ҳамаи қабатҳо ба вазъияти пешфарз дубора зер кунед)
pdfjs-layers-button-label = Қабатҳо
pdfjs-thumbs-button =
.title = Намоиш додани тасвирчаҳо
pdfjs-thumbs-button-label = Тасвирчаҳо
pdfjs-current-outline-item-button =
.title = Ёфтани унсури сохтори ҷорӣ
pdfjs-current-outline-item-button-label = Унсури сохтори ҷорӣ
pdfjs-findbar-button =
.title = Ёфтан дар ҳуҷҷат
pdfjs-findbar-button-label = Ёфтан
pdfjs-additional-layers = Қабатҳои иловагӣ
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Саҳифаи { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Тасвирчаи саҳифаи { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Ёфтан
.placeholder = Ёфтан дар ҳуҷҷат…
pdfjs-find-previous-button =
.title = Ҷустуҷӯи мавриди қаблии ибораи пешниҳодшуда
pdfjs-find-previous-button-label = Қаблӣ
pdfjs-find-next-button =
.title = Ҷустуҷӯи мавриди навбатии ибораи пешниҳодшуда
pdfjs-find-next-button-label = Навбатӣ
pdfjs-find-highlight-checkbox = Ҳамаашро бо ранг ҷудо кардан
pdfjs-find-match-case-checkbox-label = Бо дарназардошти ҳарфҳои хурду калон
pdfjs-find-match-diacritics-checkbox-label = Бо дарназардошти аломатҳои диакритикӣ
pdfjs-find-entire-word-checkbox-label = Калимаҳои пурра
pdfjs-find-reached-top = Ба болои ҳуҷҷат расид, аз поён идома ёфт
pdfjs-find-reached-bottom = Ба поёни ҳуҷҷат расид, аз боло идома ёфт
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } аз { $total } мувофиқат
*[other] { $current } аз { $total } мувофиқат
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Зиёда аз { $limit } мувофиқат
*[other] Зиёда аз { $limit } мувофиқат
}
pdfjs-find-not-found = Ибора ёфт нашуд
## Predefined zoom values
pdfjs-page-scale-width = Аз рӯи паҳнои саҳифа
pdfjs-page-scale-fit = Аз рӯи андозаи саҳифа
pdfjs-page-scale-auto = Андозаи худкор
pdfjs-page-scale-actual = Андозаи воқеӣ
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Саҳифаи { $page }
## Loading indicator messages
pdfjs-loading-error = Ҳангоми боркунии «PDF» хато ба миён омад.
pdfjs-invalid-file-error = Файли «PDF» нодуруст ё вайроншуда мебошад.
pdfjs-missing-file-error = Файли «PDF» ғоиб аст.
pdfjs-unexpected-response-error = Ҷавоби ногаҳон аз сервер.
pdfjs-rendering-error = Ҳангоми шаклсозии саҳифа хато ба миён омад.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [Ҳошиянависӣ - { $type }]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Барои кушодани ин файли «PDF» ниҳонвожаро ворид кунед.
pdfjs-password-invalid = Ниҳонвожаи нодуруст. Лутфан, аз нав кӯшиш кунед.
pdfjs-password-ok-button = ХУБ
pdfjs-password-cancel-button = Бекор кардан
pdfjs-web-fonts-disabled = Шрифтҳои интернетӣ ғайрифаъоланд: истифодаи шрифтҳои дарунсохти «PDF» ғайриимкон аст.
## Editing
pdfjs-editor-free-text-button =
.title = Матн
pdfjs-editor-free-text-button-label = Матн
pdfjs-editor-ink-button =
.title = Расмкашӣ
pdfjs-editor-ink-button-label = Расмкашӣ
pdfjs-editor-stamp-button =
.title = Илова ё таҳрир кардани тасвирҳо
pdfjs-editor-stamp-button-label = Илова ё таҳрир кардани тасвирҳо
pdfjs-editor-highlight-button =
.title = Ҷудокунӣ
pdfjs-editor-highlight-button-label = Ҷудокунӣ
pdfjs-highlight-floating-button1 =
.title = Ҷудокунӣ
.aria-label = Ҷудокунӣ
pdfjs-highlight-floating-button-label = Ҷудокунӣ
pdfjs-editor-signature-button =
.title = Илова кардани имзо
pdfjs-editor-signature-button-label = Илова кардани имзо
## Default editor aria labels
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Тоза кардани нақша
pdfjs-editor-remove-freetext-button =
.title = Тоза кардани матн
pdfjs-editor-remove-stamp-button =
.title = Тоза кардани тасвир
pdfjs-editor-remove-highlight-button =
.title = Тоза кардани ҷудокунӣ
pdfjs-editor-remove-signature-button =
.title = Тоза кардани имзо
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Ранг
pdfjs-editor-free-text-size-input = Андоза
pdfjs-editor-ink-color-input = Ранг
pdfjs-editor-ink-thickness-input = Ғафсӣ
pdfjs-editor-ink-opacity-input = Шаффофӣ
pdfjs-editor-stamp-add-image-button =
.title = Илова кардани тасвир
pdfjs-editor-stamp-add-image-button-label = Илова кардани тасвир
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Ғафсӣ
pdfjs-editor-free-highlight-thickness-title =
.title = Иваз кардани ғафсӣ ҳангоми ҷудокунии унсурҳо ба ғайр аз матн
pdfjs-editor-signature-add-signature-button =
.title = Илова кардани имзои нав
pdfjs-editor-signature-add-signature-button-label = Илова кардани имзои нав
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Муҳаррири матн
.default-content = Матнро ворид кунед…
pdfjs-free-text =
.aria-label = Муҳаррири матн
pdfjs-free-text-default-content = Нависед…
pdfjs-ink =
.aria-label = Муҳаррири расмкашӣ
pdfjs-ink-canvas =
.aria-label = Тасвири эҷодкардаи корбар
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Матни иловагӣ
pdfjs-editor-alt-text-edit-button =
.aria-label = Таҳрир кардани матни ивазкунанда
pdfjs-editor-alt-text-edit-button-label = Таҳрир кардани матни иловагӣ
pdfjs-editor-alt-text-dialog-label = Имконеро интихоб намоед
pdfjs-editor-alt-text-dialog-description = Вақте ки одамон тасвирро дида наметавонанд ё вақте ки тасвир бор карда намешавад, матни иловагӣ (Alt text) кумак мерасонад.
pdfjs-editor-alt-text-add-description-label = Илова кардани тавсиф
pdfjs-editor-alt-text-add-description-description = Кӯшиш кунед, ки 1-2 ҷумлаеро нависед, ки ба мавзӯъ, танзим ё амалҳо тавзеҳ медиҳад.
pdfjs-editor-alt-text-mark-decorative-label = Гузоштан ҳамчун матни ороишӣ
pdfjs-editor-alt-text-mark-decorative-description = Ин барои тасвирҳои ороишӣ, ба монанди марзҳо ё аломатҳои обӣ, истифода мешавад.
pdfjs-editor-alt-text-cancel-button = Бекор кардан
pdfjs-editor-alt-text-save-button = Нигоҳ доштан
pdfjs-editor-alt-text-decorative-tooltip = Ҳамчун матни ороишӣ гузошта шуд
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Барои мисол, «Ман забони тоҷикиро дӯст медорам»
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Матни ивазкунанда
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Кунҷи чапи боло — тағйир додани андоза
pdfjs-editor-resizer-label-top-middle = Канори миёнаи боло — тағйир додани андоза
pdfjs-editor-resizer-label-top-right = Кунҷи рости боло — тағйир додани андоза
pdfjs-editor-resizer-label-middle-right = Канори миёнаи рост — тағйир додани андоза
pdfjs-editor-resizer-label-bottom-right = Кунҷи рости поён — тағйир додани андоза
pdfjs-editor-resizer-label-bottom-middle = Канори миёнаи поён — тағйир додани андоза
pdfjs-editor-resizer-label-bottom-left = Кунҷи чапи поён — тағйир додани андоза
pdfjs-editor-resizer-label-middle-left = Канори миёнаи чап — тағйир додани андоза
pdfjs-editor-resizer-top-left =
.aria-label = Кунҷи чапи боло — тағйир додани андоза
pdfjs-editor-resizer-top-middle =
.aria-label = Канори миёнаи боло — тағйир додани андоза
pdfjs-editor-resizer-top-right =
.aria-label = Кунҷи рости боло — тағйир додани андоза
pdfjs-editor-resizer-middle-right =
.aria-label = Канори миёнаи рост — тағйир додани андоза
pdfjs-editor-resizer-bottom-right =
.aria-label = Кунҷи рости поён — тағйир додани андоза
pdfjs-editor-resizer-bottom-middle =
.aria-label = Канори миёнаи поён — тағйир додани андоза
pdfjs-editor-resizer-bottom-left =
.aria-label = Кунҷи чапи поён — тағйир додани андоза
pdfjs-editor-resizer-middle-left =
.aria-label = Канори миёнаи чап — тағйир додани андоза
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Ранги ҷудокунӣ
pdfjs-editor-colorpicker-button =
.title = Иваз кардани ранг
pdfjs-editor-colorpicker-dropdown =
.aria-label = Интихоби ранг
pdfjs-editor-colorpicker-yellow =
.title = Зард
pdfjs-editor-colorpicker-green =
.title = Сабз
pdfjs-editor-colorpicker-blue =
.title = Кабуд
pdfjs-editor-colorpicker-pink =
.title = Гулобӣ
pdfjs-editor-colorpicker-red =
.title = Сурх
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Ҳамаро намоиш додан
pdfjs-editor-highlight-show-all-button =
.title = Ҳамаро намоиш додан
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Таҳрир кардани матни иловагӣ (тафсири тасвир)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Илова кардани матни иловагӣ (тафсири тасвир)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Тафсири худро дар ин ҷо нависед…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Тавсифи мухтасар барои одамоне, ки аксҳоро дида наметавонанд ё вақте ки аксҳо кушода намешаванд.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Ин матни ивазкунанда ба таври худкор сохта шудааст ва шояд нодуруст бошад.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Маълумоти бештар
pdfjs-editor-new-alt-text-create-automatically-button-label = Ба таври худкор эҷод кардани матни иловагӣ
pdfjs-editor-new-alt-text-not-now-button = Ҳоло не
pdfjs-editor-new-alt-text-error-title = Матни иловагӣ ба таври худкор эҷод карда нашуд
pdfjs-editor-new-alt-text-error-description = Лутфан, матни иловагии худро ворид кунед ё баъдтар аз нав кӯшиш кунед.
pdfjs-editor-new-alt-text-error-close-button = Пӯшидан
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Боргирии модели зеҳни сунъӣ (AI) барои матни ивазкунанда ({ $downloadedSize } аз { $totalSize } МБ)
.aria-valuetext = Боргирии модели зеҳни сунъӣ (AI) барои матни ивазкунанда ({ $downloadedSize } аз { $totalSize } МБ)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Матни иловагӣ илова карда шуд
pdfjs-editor-new-alt-text-added-button-label = Матни иловагӣ илова карда шуд
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Матни иловагӣ вуҷуд надорад
pdfjs-editor-new-alt-text-missing-button-label = Матни иловагӣ вуҷуд надорад
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Бознигарӣ кардани матни иловагӣ
pdfjs-editor-new-alt-text-to-review-button-label = Бознигарӣ кардани матни иловагӣ
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Ба таври худкор сохта шудааст: «{ $generatedAltText }»
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Танзимоти матни иловагии тасвир
pdfjs-image-alt-text-settings-button-label = Танзимоти матни иловагии тасвир
pdfjs-editor-alt-text-settings-dialog-label = Танзимоти матни иловагии тасвир
pdfjs-editor-alt-text-settings-automatic-title = Матни иловагии худкор
pdfjs-editor-alt-text-settings-create-model-button-label = Ба таври худкор эҷод кардани матни иловагӣ
pdfjs-editor-alt-text-settings-create-model-description = Ин имкон барои расонидани кумак ба одамоне, ки аксҳоро дида наметавонанд ё вақте ки аксҳо кушода намешаванд, тавсифи аксҳоро пешниҳод мекунад.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Модели зеҳни сунъӣ «AI» барои матни ивазкунанда ({ $totalSize } МБ)
pdfjs-editor-alt-text-settings-ai-model-description = Дар дастгоҳи шумо ба таври маҳаллӣ кор мекунад, бинобар ин махфияти маълумоти шахсии шумо нигоҳ дошта мешавад. Барои матни ивазкунандаи худкор лозим аст.
pdfjs-editor-alt-text-settings-delete-model-button = Нест кардан
pdfjs-editor-alt-text-settings-download-model-button = Боргирӣ кардан
pdfjs-editor-alt-text-settings-downloading-model-button = Дар ҳоли боргирӣ…
pdfjs-editor-alt-text-settings-editor-title = Муҳаррири матни иловагӣ
pdfjs-editor-alt-text-settings-show-dialog-button-label = Дарҳол нишон додани муҳаррири матни ивазкунанда ҳангоми иловакунии тасвир
pdfjs-editor-alt-text-settings-show-dialog-description = Ба шумо кумак мекунад, ки боварӣ ҳосил кунед, ки ҳамаи тасвирҳои шумо дорои матни ивазкунанда мебошанд.
pdfjs-editor-alt-text-settings-close-button = Пӯшидан
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Ҷудосозӣ тоза карда шуд
pdfjs-editor-undo-bar-message-freetext = Матн тоза карда шуд
pdfjs-editor-undo-bar-message-ink = Расм тоза карда шуд
pdfjs-editor-undo-bar-message-stamp = Тасвир тоза карда шуд
pdfjs-editor-undo-bar-message-signature = Имзо тоза карда шуд
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } ҳошиянависӣ тоза карда шуд
*[other] { $count } ҳошиянависӣ тоза карда шуданд
}
pdfjs-editor-undo-bar-undo-button =
.title = Бекор кардан
pdfjs-editor-undo-bar-undo-button-label = Бекор кардан
pdfjs-editor-undo-bar-close-button =
.title = Пӯшидан
pdfjs-editor-undo-bar-close-button-label = Пӯшидан
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = Ин равзанаи зоҳирӣ ба корбар имкон медиҳад, ки тавонад имзоеро эҷод карда, ба ҳуҷҷати «PDF» илова намояд. Корбар метавонад номро таҳрир кунад (ном, инчунин, ҳамчун матни иловагӣ хизмат мекунад), ва ихтиёран имзоро барои истифодаи такрорӣ нигоҳ медорад.
pdfjs-editor-add-signature-dialog-title = Илова кардани имзо
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Нависед
.title = Нависед
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Имзо гузоред
.title = Имзо гузоред
pdfjs-editor-add-signature-image-button = Тасвир
.title = Тасвир
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Имзои худро бо ном нависед
.placeholder = Имзои худро бо ном нависед
pdfjs-editor-add-signature-draw-placeholder = Имзои худро кашида, гузоред
pdfjs-editor-add-signature-draw-thickness-range-label = Ғафсӣ
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Ғафсии имзо: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Барои бор кардани файл, онро дар ин ҷой кашида, гузоред
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Ё файлҳои тасвириро интихоб кунед
*[other] Ё файлҳои тасвириро интихоб кунед
}
## Controls
pdfjs-editor-add-signature-description-label = Тавсиф (матни иловагӣ)
pdfjs-editor-add-signature-description-input =
.title = Тавсиф (матни иловагӣ)
pdfjs-editor-add-signature-description-default-when-drawing = Имзо
pdfjs-editor-add-signature-clear-button-label = Пок кардани имзо
pdfjs-editor-add-signature-clear-button =
.title = Пок кардани имзо
pdfjs-editor-add-signature-save-checkbox = Нигоҳ доштани имзо
pdfjs-editor-add-signature-save-warning-message = Шумо ба ҳадди 5 имзои нигоҳдошташуда расидед. Барои нигоҳ доштани имзои нав, яке аз имзоҳои нигоҳдошташударо тоза намоед.
pdfjs-editor-add-signature-image-upload-error-title = Тасвир бор карда нашуд
pdfjs-editor-add-signature-image-upload-error-description = Пайвастшавии шабакаи худро санҷед ё тасвири дигареро кӯшиш кунед.
pdfjs-editor-add-signature-error-close-button = Пӯшидан
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Бекор кардан
pdfjs-editor-add-signature-add-button = Илова кардан
pdfjs-editor-edit-signature-update-button = Навсозӣ кардан
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Тоза кардани имзо
pdfjs-editor-delete-signature-button-label = Тоза кардани имзо
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Таҳрир кардани тавсиф
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Таҳрир кардани тавсиф
================================================
FILE: cookbook/static/pdfjs/web/locale/th/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = หน้าก่อนหน้า
pdfjs-previous-button-label = ก่อนหน้า
pdfjs-next-button =
.title = หน้าถัดไป
pdfjs-next-button-label = ถัดไป
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = หน้า
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = จาก { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } จาก { $pagesCount })
pdfjs-zoom-out-button =
.title = ซูมออก
pdfjs-zoom-out-button-label = ซูมออก
pdfjs-zoom-in-button =
.title = ซูมเข้า
pdfjs-zoom-in-button-label = ซูมเข้า
pdfjs-zoom-select =
.title = ซูม
pdfjs-presentation-mode-button =
.title = สลับเป็นโหมดการนำเสนอ
pdfjs-presentation-mode-button-label = โหมดการนำเสนอ
pdfjs-open-file-button =
.title = เปิดไฟล์
pdfjs-open-file-button-label = เปิด
pdfjs-print-button =
.title = พิมพ์
pdfjs-print-button-label = พิมพ์
pdfjs-save-button =
.title = บันทึก
pdfjs-save-button-label = บันทึก
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = ดาวน์โหลด
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = ดาวน์โหลด
pdfjs-bookmark-button =
.title = หน้าปัจจุบัน (ดู URL จากหน้าปัจจุบัน)
pdfjs-bookmark-button-label = หน้าปัจจุบัน
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = เครื่องมือ
pdfjs-tools-button-label = เครื่องมือ
pdfjs-first-page-button =
.title = ไปยังหน้าแรก
pdfjs-first-page-button-label = ไปยังหน้าแรก
pdfjs-last-page-button =
.title = ไปยังหน้าสุดท้าย
pdfjs-last-page-button-label = ไปยังหน้าสุดท้าย
pdfjs-page-rotate-cw-button =
.title = หมุนตามเข็มนาฬิกา
pdfjs-page-rotate-cw-button-label = หมุนตามเข็มนาฬิกา
pdfjs-page-rotate-ccw-button =
.title = หมุนทวนเข็มนาฬิกา
pdfjs-page-rotate-ccw-button-label = หมุนทวนเข็มนาฬิกา
pdfjs-cursor-text-select-tool-button =
.title = เปิดใช้งานเครื่องมือการเลือกข้อความ
pdfjs-cursor-text-select-tool-button-label = เครื่องมือการเลือกข้อความ
pdfjs-cursor-hand-tool-button =
.title = เปิดใช้งานเครื่องมือมือ
pdfjs-cursor-hand-tool-button-label = เครื่องมือมือ
pdfjs-scroll-page-button =
.title = ใช้การเลื่อนหน้า
pdfjs-scroll-page-button-label = การเลื่อนหน้า
pdfjs-scroll-vertical-button =
.title = ใช้การเลื่อนแนวตั้ง
pdfjs-scroll-vertical-button-label = การเลื่อนแนวตั้ง
pdfjs-scroll-horizontal-button =
.title = ใช้การเลื่อนแนวนอน
pdfjs-scroll-horizontal-button-label = การเลื่อนแนวนอน
pdfjs-scroll-wrapped-button =
.title = ใช้การเลื่อนแบบคลุม
pdfjs-scroll-wrapped-button-label = เลื่อนแบบคลุม
pdfjs-spread-none-button =
.title = ไม่ต้องรวมการกระจายหน้า
pdfjs-spread-none-button-label = ไม่กระจาย
pdfjs-spread-odd-button =
.title = รวมการกระจายหน้าเริ่มจากหน้าคี่
pdfjs-spread-odd-button-label = กระจายอย่างเหลือเศษ
pdfjs-spread-even-button =
.title = รวมการกระจายหน้าเริ่มจากหน้าคู่
pdfjs-spread-even-button-label = กระจายอย่างเท่าเทียม
## Document properties dialog
pdfjs-document-properties-button =
.title = คุณสมบัติเอกสาร…
pdfjs-document-properties-button-label = คุณสมบัติเอกสาร…
pdfjs-document-properties-file-name = ชื่อไฟล์:
pdfjs-document-properties-file-size = ขนาดไฟล์:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } ไบต์)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } ไบต์)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } ไบต์)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } ไบต์)
pdfjs-document-properties-title = ชื่อเรื่อง:
pdfjs-document-properties-author = ผู้สร้าง:
pdfjs-document-properties-subject = ชื่อเรื่อง:
pdfjs-document-properties-keywords = คำสำคัญ:
pdfjs-document-properties-creation-date = วันที่สร้าง:
pdfjs-document-properties-modification-date = วันที่แก้ไข:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = ผู้สร้าง:
pdfjs-document-properties-producer = ผู้ผลิต PDF:
pdfjs-document-properties-version = รุ่น PDF:
pdfjs-document-properties-page-count = จำนวนหน้า:
pdfjs-document-properties-page-size = ขนาดหน้า:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = แนวตั้ง
pdfjs-document-properties-page-size-orientation-landscape = แนวนอน
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = จดหมาย
pdfjs-document-properties-page-size-name-legal = ข้อกฎหมาย
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = มุมมองเว็บแบบรวดเร็ว:
pdfjs-document-properties-linearized-yes = ใช่
pdfjs-document-properties-linearized-no = ไม่
pdfjs-document-properties-close-button = ปิด
## Print
pdfjs-print-progress-message = กำลังเตรียมเอกสารสำหรับการพิมพ์…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = ยกเลิก
pdfjs-printing-not-supported = คำเตือน: เบราว์เซอร์นี้ไม่ได้สนับสนุนการพิมพ์อย่างเต็มที่
pdfjs-printing-not-ready = คำเตือน: PDF ไม่ได้รับการโหลดอย่างเต็มที่สำหรับการพิมพ์
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = เปิด/ปิดแถบข้าง
pdfjs-toggle-sidebar-notification-button =
.title = เปิด/ปิดแถบข้าง (เอกสารมีเค้าร่าง/ไฟล์แนบ/เลเยอร์)
pdfjs-toggle-sidebar-button-label = เปิด/ปิดแถบข้าง
pdfjs-document-outline-button =
.title = แสดงเค้าร่างเอกสาร (คลิกสองครั้งเพื่อขยาย/ยุบรายการทั้งหมด)
pdfjs-document-outline-button-label = เค้าร่างเอกสาร
pdfjs-attachments-button =
.title = แสดงไฟล์แนบ
pdfjs-attachments-button-label = ไฟล์แนบ
pdfjs-layers-button =
.title = แสดงเลเยอร์ (คลิกสองครั้งเพื่อรีเซ็ตเลเยอร์ทั้งหมดเป็นสถานะเริ่มต้น)
pdfjs-layers-button-label = เลเยอร์
pdfjs-thumbs-button =
.title = แสดงภาพขนาดย่อ
pdfjs-thumbs-button-label = ภาพขนาดย่อ
pdfjs-current-outline-item-button =
.title = ค้นหารายการเค้าร่างปัจจุบัน
pdfjs-current-outline-item-button-label = รายการเค้าร่างปัจจุบัน
pdfjs-findbar-button =
.title = ค้นหาในเอกสาร
pdfjs-findbar-button-label = ค้นหา
pdfjs-additional-layers = เลเยอร์เพิ่มเติม
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = หน้า { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = ภาพขนาดย่อของหน้า { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = ค้นหา
.placeholder = ค้นหาในเอกสาร…
pdfjs-find-previous-button =
.title = หาตำแหน่งก่อนหน้าของวลี
pdfjs-find-previous-button-label = ก่อนหน้า
pdfjs-find-next-button =
.title = หาตำแหน่งถัดไปของวลี
pdfjs-find-next-button-label = ถัดไป
pdfjs-find-highlight-checkbox = เน้นสีทั้งหมด
pdfjs-find-match-case-checkbox-label = ตัวพิมพ์ใหญ่เล็กตรงกัน
pdfjs-find-match-diacritics-checkbox-label = เครื่องหมายกำกับการออกเสียงตรงกัน
pdfjs-find-entire-word-checkbox-label = ทั้งคำ
pdfjs-find-reached-top = ค้นหาถึงจุดเริ่มต้นของหน้า เริ่มค้นต่อจากด้านล่าง
pdfjs-find-reached-bottom = ค้นหาถึงจุดสิ้นสุดหน้า เริ่มค้นต่อจากด้านบน
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count = { $current } จาก { $total } รายการที่ตรงกัน
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit = มากกว่า { $limit } รายการที่ตรงกัน
pdfjs-find-not-found = ไม่พบวลี
## Predefined zoom values
pdfjs-page-scale-width = ความกว้างหน้า
pdfjs-page-scale-fit = พอดีหน้า
pdfjs-page-scale-auto = ซูมอัตโนมัติ
pdfjs-page-scale-actual = ขนาดจริง
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = หน้า { $page }
## Loading indicator messages
pdfjs-loading-error = เกิดข้อผิดพลาดขณะโหลด PDF
pdfjs-invalid-file-error = ไฟล์ PDF ไม่ถูกต้องหรือเสียหาย
pdfjs-missing-file-error = ไฟล์ PDF หายไป
pdfjs-unexpected-response-error = การตอบสนองของเซิร์ฟเวอร์ที่ไม่คาดคิด
pdfjs-rendering-error = เกิดข้อผิดพลาดขณะเรนเดอร์หน้า
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [คำอธิบายประกอบ { $type }]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = ป้อนรหัสผ่านเพื่อเปิดไฟล์ PDF นี้
pdfjs-password-invalid = รหัสผ่านไม่ถูกต้อง โปรดลองอีกครั้ง
pdfjs-password-ok-button = ตกลง
pdfjs-password-cancel-button = ยกเลิก
pdfjs-web-fonts-disabled = แบบอักษรเว็บถูกปิดใช้งาน: ไม่สามารถใช้แบบอักษร PDF ฝังตัว
## Editing
pdfjs-editor-free-text-button =
.title = ข้อความ
pdfjs-editor-free-text-button-label = ข้อความ
pdfjs-editor-ink-button =
.title = รูปวาด
pdfjs-editor-ink-button-label = รูปวาด
pdfjs-editor-stamp-button =
.title = เพิ่มหรือแก้ไขภาพ
pdfjs-editor-stamp-button-label = เพิ่มหรือแก้ไขภาพ
pdfjs-editor-highlight-button =
.title = เน้น
pdfjs-editor-highlight-button-label = เน้น
pdfjs-highlight-floating-button1 =
.title = เน้นสี
.aria-label = เน้นสี
pdfjs-highlight-floating-button-label = เน้นสี
pdfjs-editor-signature-button =
.title = เพิ่มลายเซ็น
pdfjs-editor-signature-button-label = เพิ่มลายเซ็น
## Default editor aria labels
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = เอาภาพวาดออก
pdfjs-editor-remove-freetext-button =
.title = เอาข้อความออก
pdfjs-editor-remove-stamp-button =
.title = เอาภาพออก
pdfjs-editor-remove-highlight-button =
.title = เอาการเน้นสีออก
pdfjs-editor-remove-signature-button =
.title = ลบลายเซ็น
##
# Editor Parameters
pdfjs-editor-free-text-color-input = สี
pdfjs-editor-free-text-size-input = ขนาด
pdfjs-editor-ink-color-input = สี
pdfjs-editor-ink-thickness-input = ความหนา
pdfjs-editor-ink-opacity-input = ความทึบ
pdfjs-editor-stamp-add-image-button =
.title = เพิ่มภาพ
pdfjs-editor-stamp-add-image-button-label = เพิ่มภาพ
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = ความหนา
pdfjs-editor-free-highlight-thickness-title =
.title = เปลี่ยนความหนาเมื่อเน้นรายการอื่นๆ ที่ไม่ใช่ข้อความ
pdfjs-editor-signature-add-signature-button =
.title = เพิ่มลายเซ็นใหม่
pdfjs-editor-signature-add-signature-button-label = เพิ่มลายเซ็นใหม่
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = ตัวแก้ไขข้อความ
.default-content = เริ่มพิมพ์ได้เลย…
pdfjs-free-text =
.aria-label = ตัวแก้ไขข้อความ
pdfjs-free-text-default-content = เริ่มพิมพ์…
pdfjs-ink =
.aria-label = ตัวแก้ไขรูปวาด
pdfjs-ink-canvas =
.aria-label = ภาพที่ผู้ใช้สร้างขึ้น
## Alt-text dialog
pdfjs-editor-alt-text-button-label = ข้อความทดแทน
pdfjs-editor-alt-text-edit-button =
.aria-label = แก้ไขข้อความทดแทน
pdfjs-editor-alt-text-edit-button-label = แก้ไขข้อความทดแทน
pdfjs-editor-alt-text-dialog-label = เลือกตัวเลือก
pdfjs-editor-alt-text-dialog-description = ข้อความทดแทนสามารถช่วยเหลือได้เมื่อผู้ใช้มองไม่เห็นภาพ หรือภาพไม่โหลด
pdfjs-editor-alt-text-add-description-label = เพิ่มคำอธิบาย
pdfjs-editor-alt-text-add-description-description = แนะนำให้ใช้ 1-2 ประโยคซึ่งอธิบายหัวเรื่อง ฉาก หรือการกระทำ
pdfjs-editor-alt-text-mark-decorative-label = ทำเครื่องหมายเป็นสิ่งตกแต่ง
pdfjs-editor-alt-text-mark-decorative-description = สิ่งนี้ใช้สำหรับภาพที่เป็นสิ่งประดับ เช่น ขอบ หรือลายน้ำ
pdfjs-editor-alt-text-cancel-button = ยกเลิก
pdfjs-editor-alt-text-save-button = บันทึก
pdfjs-editor-alt-text-decorative-tooltip = ทำเครื่องหมายเป็นสิ่งตกแต่งแล้ว
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = ตัวอย่างเช่น “ชายหนุ่มคนหนึ่งนั่งลงที่โต๊ะเพื่อรับประทานอาหารมื้อหนึ่ง”
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = ข้อความทดแทน
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = มุมซ้ายบน — ปรับขนาด
pdfjs-editor-resizer-label-top-middle = ตรงกลางด้านบน — ปรับขนาด
pdfjs-editor-resizer-label-top-right = มุมขวาบน — ปรับขนาด
pdfjs-editor-resizer-label-middle-right = ตรงกลางด้านขวา — ปรับขนาด
pdfjs-editor-resizer-label-bottom-right = มุมขวาล่าง — ปรับขนาด
pdfjs-editor-resizer-label-bottom-middle = ตรงกลางด้านล่าง — ปรับขนาด
pdfjs-editor-resizer-label-bottom-left = มุมซ้ายล่าง — ปรับขนาด
pdfjs-editor-resizer-label-middle-left = ตรงกลางด้านซ้าย — ปรับขนาด
pdfjs-editor-resizer-top-left =
.aria-label = มุมซ้ายบน — ปรับขนาด
pdfjs-editor-resizer-top-middle =
.aria-label = ตรงกลางด้านบน — ปรับขนาด
pdfjs-editor-resizer-top-right =
.aria-label = มุมขวาบน — ปรับขนาด
pdfjs-editor-resizer-middle-right =
.aria-label = ตรงกลางด้านขวา — ปรับขนาด
pdfjs-editor-resizer-bottom-right =
.aria-label = มุมขวาล่าง — ปรับขนาด
pdfjs-editor-resizer-bottom-middle =
.aria-label = ตรงกลางด้านล่าง — ปรับขนาด
pdfjs-editor-resizer-bottom-left =
.aria-label = มุมซ้ายล่าง — ปรับขนาด
pdfjs-editor-resizer-middle-left =
.aria-label = ตรงกลางด้านซ้าย — ปรับขนาด
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = สีเน้น
pdfjs-editor-colorpicker-button =
.title = เปลี่ยนสี
pdfjs-editor-colorpicker-dropdown =
.aria-label = ทางเลือกสี
pdfjs-editor-colorpicker-yellow =
.title = เหลือง
pdfjs-editor-colorpicker-green =
.title = เขียว
pdfjs-editor-colorpicker-blue =
.title = น้ำเงิน
pdfjs-editor-colorpicker-pink =
.title = ชมพู
pdfjs-editor-colorpicker-red =
.title = แดง
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = แสดงทั้งหมด
pdfjs-editor-highlight-show-all-button =
.title = แสดงทั้งหมด
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = แก้ไขข้อความทดแทน (คำอธิบายภาพ)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = เพิ่มข้อความทดแทน (คำอธิบายภาพ)
pdfjs-editor-new-alt-text-textarea =
.placeholder = เขียนคำอธิบายของคุณที่นี่…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = คำอธิบายสั้นๆ สำหรับผู้ที่ไม่สามารถมองเห็นภาพหรือเมื่อภาพไม่โหลด
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = ข้อความทดแทนนี้ถูกสร้างขึ้นโดยอัตโนมัติและอาจไม่ถูกต้อง
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = เรียนรู้เพิ่มเติม
pdfjs-editor-new-alt-text-create-automatically-button-label = สร้างข้อความทดแทนโดยอัตโนมัติ
pdfjs-editor-new-alt-text-not-now-button = ไม่ใช่ตอนนี้
pdfjs-editor-new-alt-text-error-title = ไม่สามารถสร้างข้อความทดแทนโดยอัตโนมัติได้
pdfjs-editor-new-alt-text-error-description = กรุณาเขียนข้อความทดแทนด้วยตัวเองหรือลองใหม่อีกครั้งในภายหลัง
pdfjs-editor-new-alt-text-error-close-button = ปิด
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = กำลังดาวน์โหลดโมเดล AI สำหรับข้อความทดแทน ({ $downloadedSize } จาก { $totalSize } MB)
.aria-valuetext = กำลังดาวน์โหลดโมเดล AI สำหรับข้อความทดแทน ({ $downloadedSize } จาก { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = เพิ่มข้อความทดแทนแล้ว
pdfjs-editor-new-alt-text-added-button-label = เพิ่มข้อความทดแทนแล้ว
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = ขาดข้อความทดแทน
pdfjs-editor-new-alt-text-missing-button-label = ขาดข้อความทดแทน
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = ตรวจสอบข้อความทดแทน
pdfjs-editor-new-alt-text-to-review-button-label = ตรวจสอบข้อความทดแทน
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = สร้างขึ้นโดยอัตโนมัติ: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = ตั้งค่าข้อความทดแทนภาพ
pdfjs-image-alt-text-settings-button-label = ตั้งค่าข้อความทดแทนภาพ
pdfjs-editor-alt-text-settings-dialog-label = ตั้งค่าข้อความทดแทนภาพ
pdfjs-editor-alt-text-settings-automatic-title = การทดแทนด้วยข้อความอัตโนมัติ
pdfjs-editor-alt-text-settings-create-model-button-label = สร้างข้อความทดแทนอัตโนมัติ
pdfjs-editor-alt-text-settings-create-model-description = แนะนำคำอธิบายเพื่อช่วยเหลือผู้ที่ไม่สามารถมองเห็นภาพหรือเมื่อภาพไม่โหลด
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = โมเดล AI สำหรับข้อความทดแทน ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = ทำงานในเครื่องของคุณเพื่อให้ข้อมูลของคุณเป็นส่วนตัว จำเป็นสำหรับข้อความทดแทนอัตโนมัติ
pdfjs-editor-alt-text-settings-delete-model-button = ลบ
pdfjs-editor-alt-text-settings-download-model-button = ดาวน์โหลด
pdfjs-editor-alt-text-settings-downloading-model-button = กำลังดาวน์โหลด…
pdfjs-editor-alt-text-settings-editor-title = ตัวแก้ไขข้อความทดแทน
pdfjs-editor-alt-text-settings-show-dialog-button-label = แสดงตัวแก้ไขข้อความทดแทนทันทีเมื่อเพิ่มภาพ
pdfjs-editor-alt-text-settings-show-dialog-description = ช่วยให้คุณแน่ใจว่าภาพทั้งหมดของคุณมีข้อความทดแทน
pdfjs-editor-alt-text-settings-close-button = ปิด
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = เอาการเน้นสีออกแล้ว
pdfjs-editor-undo-bar-message-freetext = เอาข้อความออกแล้ว
pdfjs-editor-undo-bar-message-ink = เอาภาพวาดออกแล้ว
pdfjs-editor-undo-bar-message-stamp = เอาภาพออกแล้ว
pdfjs-editor-undo-bar-message-signature = ลบลายเซ็นแล้ว
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple = เอาคำอธิบายประกอบ { $count } รายการออกแล้ว
pdfjs-editor-undo-bar-undo-button =
.title = เลิกทำ
pdfjs-editor-undo-bar-undo-button-label = เลิกทำ
pdfjs-editor-undo-bar-close-button =
.title = ปิด
pdfjs-editor-undo-bar-close-button-label = ปิด
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = โมดัลนี้ช่วยให้ผู้ใช้สามารถสร้างลายเซ็นเพื่อใช้เพิ่มลงในเอกสาร PDF ได้ ผู้ใช้สามารถแก้ไขชื่อ (ซึ่งใช้เป็นข้อความทดแทนได้ด้วย) และสามารถเลือกบันทึกลายเซ็นเพื่อใช้งานซ้ำได้
pdfjs-editor-add-signature-dialog-title = เพิ่มลายเซ็น
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = พิมพ์
.title = พิมพ์
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = วาด
.title = วาด
pdfjs-editor-add-signature-image-button = ภาพ
.title = ภาพ
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = พิมพ์ลายเซ็นของคุณ
.placeholder = พิมพ์ลายเซ็นของคุณ
pdfjs-editor-add-signature-draw-placeholder = วาดลายเซ็นของคุณ
pdfjs-editor-add-signature-draw-thickness-range-label = ความหนา
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = ความหนาของการวาด: { $thickness }
pdfjs-editor-add-signature-image-placeholder = ลากไฟล์มาที่นี่เพื่ออัปโหลด
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] หรือเลือกไฟล์ภาพ
*[other] หรือเรียกดูไฟล์ภาพ
}
## Controls
pdfjs-editor-add-signature-description-label = คำอธิบาย (ข้อความทดแทน)
pdfjs-editor-add-signature-description-input =
.title = คำอธิบาย (ข้อความทดแทน)
pdfjs-editor-add-signature-description-default-when-drawing = ลายเซ็น
pdfjs-editor-add-signature-clear-button-label = ล้างลายเซ็น
pdfjs-editor-add-signature-clear-button =
.title = ล้างลายเซ็น
pdfjs-editor-add-signature-save-checkbox = บันทึกลายเซ็น
pdfjs-editor-add-signature-save-warning-message = คุณมีลายเซ็นที่บันทึกถึงจำนวนสูงสุด 5 รายการแล้ว โปรดลบรายการหนึ่งออกเมื่อจะบันทึกเพิ่ม
pdfjs-editor-add-signature-image-upload-error-title = ไม่สามารถอัปโหลดภาพได้
pdfjs-editor-add-signature-image-upload-error-description = ตรวจสอบการเชื่อมต่อเครือข่ายของคุณหรือลองใช้ภาพอื่น
pdfjs-editor-add-signature-error-close-button = ปิด
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = ยกเลิก
pdfjs-editor-add-signature-add-button = เพิ่ม
pdfjs-editor-edit-signature-update-button = อัปเดต
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = เอาลายเซ็นออก
pdfjs-editor-delete-signature-button-label = เอาลายเซ็นออก
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = แก้ไขคำอธิบาย
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = แก้ไขคำอธิบาย
================================================
FILE: cookbook/static/pdfjs/web/locale/tl/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Naunang Pahina
pdfjs-previous-button-label = Nakaraan
pdfjs-next-button =
.title = Sunod na Pahina
pdfjs-next-button-label = Sunod
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Pahina
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = ng { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } ng { $pagesCount })
pdfjs-zoom-out-button =
.title = Paliitin
pdfjs-zoom-out-button-label = Paliitin
pdfjs-zoom-in-button =
.title = Palakihin
pdfjs-zoom-in-button-label = Palakihin
pdfjs-zoom-select =
.title = Mag-zoom
pdfjs-presentation-mode-button =
.title = Lumipat sa Presentation Mode
pdfjs-presentation-mode-button-label = Presentation Mode
pdfjs-open-file-button =
.title = Magbukas ng file
pdfjs-open-file-button-label = Buksan
pdfjs-print-button =
.title = i-Print
pdfjs-print-button-label = i-Print
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Mga Kagamitan
pdfjs-tools-button-label = Mga Kagamitan
pdfjs-first-page-button =
.title = Pumunta sa Unang Pahina
pdfjs-first-page-button-label = Pumunta sa Unang Pahina
pdfjs-last-page-button =
.title = Pumunta sa Huling Pahina
pdfjs-last-page-button-label = Pumunta sa Huling Pahina
pdfjs-page-rotate-cw-button =
.title = Paikutin Pakanan
pdfjs-page-rotate-cw-button-label = Paikutin Pakanan
pdfjs-page-rotate-ccw-button =
.title = Paikutin Pakaliwa
pdfjs-page-rotate-ccw-button-label = Paikutin Pakaliwa
pdfjs-cursor-text-select-tool-button =
.title = I-enable ang Text Selection Tool
pdfjs-cursor-text-select-tool-button-label = Text Selection Tool
pdfjs-cursor-hand-tool-button =
.title = I-enable ang Hand Tool
pdfjs-cursor-hand-tool-button-label = Hand Tool
pdfjs-scroll-vertical-button =
.title = Gumamit ng Vertical Scrolling
pdfjs-scroll-vertical-button-label = Vertical Scrolling
pdfjs-scroll-horizontal-button =
.title = Gumamit ng Horizontal Scrolling
pdfjs-scroll-horizontal-button-label = Horizontal Scrolling
pdfjs-scroll-wrapped-button =
.title = Gumamit ng Wrapped Scrolling
pdfjs-scroll-wrapped-button-label = Wrapped Scrolling
pdfjs-spread-none-button =
.title = Huwag pagsamahin ang mga page spread
pdfjs-spread-none-button-label = No Spreads
pdfjs-spread-odd-button =
.title = Join page spreads starting with odd-numbered pages
pdfjs-spread-odd-button-label = Mga Odd Spread
pdfjs-spread-even-button =
.title = Pagsamahin ang mga page spread na nagsisimula sa mga even-numbered na pahina
pdfjs-spread-even-button-label = Mga Even Spread
## Document properties dialog
pdfjs-document-properties-button =
.title = Mga Katangian ng Dokumento…
pdfjs-document-properties-button-label = Mga Katangian ng Dokumento…
pdfjs-document-properties-file-name = File name:
pdfjs-document-properties-file-size = File size:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = Pamagat:
pdfjs-document-properties-author = May-akda:
pdfjs-document-properties-subject = Paksa:
pdfjs-document-properties-keywords = Mga keyword:
pdfjs-document-properties-creation-date = Petsa ng Pagkakagawa:
pdfjs-document-properties-modification-date = Petsa ng Pagkakabago:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Tagalikha:
pdfjs-document-properties-producer = PDF Producer:
pdfjs-document-properties-version = PDF Version:
pdfjs-document-properties-page-count = Bilang ng Pahina:
pdfjs-document-properties-page-size = Laki ng Pahina:
pdfjs-document-properties-page-size-unit-inches = pulgada
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = patayo
pdfjs-document-properties-page-size-orientation-landscape = pahiga
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letter
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Fast Web View:
pdfjs-document-properties-linearized-yes = Oo
pdfjs-document-properties-linearized-no = Hindi
pdfjs-document-properties-close-button = Isara
## Print
pdfjs-print-progress-message = Inihahanda ang dokumento para sa pag-print…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Kanselahin
pdfjs-printing-not-supported = Babala: Hindi pa ganap na suportado ang pag-print sa browser na ito.
pdfjs-printing-not-ready = Babala: Hindi ganap na nabuksan ang PDF para sa pag-print.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Ipakita/Itago ang Sidebar
pdfjs-toggle-sidebar-notification-button =
.title = Ipakita/Itago ang Sidebar (nagtataglay ang dokumento ng balangkas/mga attachment/mga layer)
pdfjs-toggle-sidebar-button-label = Ipakita/Itago ang Sidebar
pdfjs-document-outline-button =
.title = Ipakita ang Document Outline (mag-double-click para i-expand/collapse ang laman)
pdfjs-document-outline-button-label = Balangkas ng Dokumento
pdfjs-attachments-button =
.title = Ipakita ang mga Attachment
pdfjs-attachments-button-label = Mga attachment
pdfjs-layers-button =
.title = Ipakita ang mga Layer (mag-double click para mareset ang lahat ng layer sa orihinal na estado)
pdfjs-layers-button-label = Mga layer
pdfjs-thumbs-button =
.title = Ipakita ang mga Thumbnail
pdfjs-thumbs-button-label = Mga thumbnail
pdfjs-findbar-button =
.title = Hanapin sa Dokumento
pdfjs-findbar-button-label = Hanapin
pdfjs-additional-layers = Mga Karagdagang Layer
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Pahina { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Thumbnail ng Pahina { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Hanapin
.placeholder = Hanapin sa dokumento…
pdfjs-find-previous-button =
.title = Hanapin ang nakaraang pangyayari ng parirala
pdfjs-find-previous-button-label = Nakaraan
pdfjs-find-next-button =
.title = Hanapin ang susunod na pangyayari ng parirala
pdfjs-find-next-button-label = Susunod
pdfjs-find-highlight-checkbox = I-highlight lahat
pdfjs-find-match-case-checkbox-label = Itugma ang case
pdfjs-find-entire-word-checkbox-label = Buong salita
pdfjs-find-reached-top = Naabot na ang tuktok ng dokumento, ipinagpatuloy mula sa ilalim
pdfjs-find-reached-bottom = Naabot na ang dulo ng dokumento, ipinagpatuloy mula sa tuktok
pdfjs-find-not-found = Hindi natagpuan ang parirala
## Predefined zoom values
pdfjs-page-scale-width = Lapad ng Pahina
pdfjs-page-scale-fit = Pagkasyahin ang Pahina
pdfjs-page-scale-auto = Automatic Zoom
pdfjs-page-scale-actual = Totoong sukat
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
## Loading indicator messages
pdfjs-loading-error = Nagkaproblema habang niloload ang PDF.
pdfjs-invalid-file-error = Di-wasto o sira ang PDF file.
pdfjs-missing-file-error = Nawawalang PDF file.
pdfjs-unexpected-response-error = Hindi inaasahang tugon ng server.
pdfjs-rendering-error = Nagkaproblema habang nirerender ang pahina.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } Annotation]
## Password
pdfjs-password-label = Ipasok ang password upang buksan ang PDF file na ito.
pdfjs-password-invalid = Maling password. Subukan uli.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Kanselahin
pdfjs-web-fonts-disabled = Naka-disable ang mga Web font: hindi kayang gamitin ang mga naka-embed na PDF font.
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/tr/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Önceki sayfa
pdfjs-previous-button-label = Önceki
pdfjs-next-button =
.title = Sonraki sayfa
pdfjs-next-button-label = Sonraki
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Sayfa
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = / { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } / { $pagesCount })
pdfjs-zoom-out-button =
.title = Uzaklaştır
pdfjs-zoom-out-button-label = Uzaklaştır
pdfjs-zoom-in-button =
.title = Yakınlaştır
pdfjs-zoom-in-button-label = Yakınlaştır
pdfjs-zoom-select =
.title = Yakınlaştırma
pdfjs-presentation-mode-button =
.title = Sunum moduna geç
pdfjs-presentation-mode-button-label = Sunum modu
pdfjs-open-file-button =
.title = Dosya aç
pdfjs-open-file-button-label = Aç
pdfjs-print-button =
.title = Yazdır
pdfjs-print-button-label = Yazdır
pdfjs-save-button =
.title = Kaydet
pdfjs-save-button-label = Kaydet
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = İndir
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = İndir
pdfjs-bookmark-button =
.title = Geçerli sayfa (geçerli sayfanın adresini görüntüle)
pdfjs-bookmark-button-label = Geçerli sayfa
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Araçlar
pdfjs-tools-button-label = Araçlar
pdfjs-first-page-button =
.title = İlk sayfaya git
pdfjs-first-page-button-label = İlk sayfaya git
pdfjs-last-page-button =
.title = Son sayfaya git
pdfjs-last-page-button-label = Son sayfaya git
pdfjs-page-rotate-cw-button =
.title = Saat yönünde döndür
pdfjs-page-rotate-cw-button-label = Saat yönünde döndür
pdfjs-page-rotate-ccw-button =
.title = Saat yönünün tersine döndür
pdfjs-page-rotate-ccw-button-label = Saat yönünün tersine döndür
pdfjs-cursor-text-select-tool-button =
.title = Metin seçme aracını etkinleştir
pdfjs-cursor-text-select-tool-button-label = Metin seçme aracı
pdfjs-cursor-hand-tool-button =
.title = El aracını etkinleştir
pdfjs-cursor-hand-tool-button-label = El aracı
pdfjs-scroll-page-button =
.title = Sayfa kaydırmayı kullan
pdfjs-scroll-page-button-label = Sayfa kaydırma
pdfjs-scroll-vertical-button =
.title = Dikey kaydırmayı kullan
pdfjs-scroll-vertical-button-label = Dikey kaydırma
pdfjs-scroll-horizontal-button =
.title = Yatay kaydırmayı kullan
pdfjs-scroll-horizontal-button-label = Yatay kaydırma
pdfjs-scroll-wrapped-button =
.title = Yan yana kaydırmayı kullan
pdfjs-scroll-wrapped-button-label = Yan yana kaydırma
pdfjs-spread-none-button =
.title = Yan yana sayfaları birleştirme
pdfjs-spread-none-button-label = Birleştirme
pdfjs-spread-odd-button =
.title = Yan yana sayfaları tek numaralı sayfalardan başlayarak birleştir
pdfjs-spread-odd-button-label = Tek numaralı
pdfjs-spread-even-button =
.title = Yan yana sayfaları çift numaralı sayfalardan başlayarak birleştir
pdfjs-spread-even-button-label = Çift numaralı
## Document properties dialog
pdfjs-document-properties-button =
.title = Belge özellikleri…
pdfjs-document-properties-button-label = Belge özellikleri…
pdfjs-document-properties-file-name = Dosya adı:
pdfjs-document-properties-file-size = Dosya boyutu:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bayt)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bayt)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bayt)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bayt)
pdfjs-document-properties-title = Başlık:
pdfjs-document-properties-author = Yazar:
pdfjs-document-properties-subject = Konu:
pdfjs-document-properties-keywords = Anahtar kelimeler:
pdfjs-document-properties-creation-date = Oluşturma tarihi:
pdfjs-document-properties-modification-date = Değiştirme tarihi:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date } { $time }
pdfjs-document-properties-creator = Oluşturan:
pdfjs-document-properties-producer = PDF üreticisi:
pdfjs-document-properties-version = PDF sürümü:
pdfjs-document-properties-page-count = Sayfa sayısı:
pdfjs-document-properties-page-size = Sayfa boyutu:
pdfjs-document-properties-page-size-unit-inches = inç
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = dikey
pdfjs-document-properties-page-size-orientation-landscape = yatay
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letter
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Hızlı web görünümü:
pdfjs-document-properties-linearized-yes = Evet
pdfjs-document-properties-linearized-no = Hayır
pdfjs-document-properties-close-button = Kapat
## Print
pdfjs-print-progress-message = Belge yazdırılmaya hazırlanıyor…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = %{ $progress }
pdfjs-print-progress-close-button = İptal
pdfjs-printing-not-supported = Uyarı: Yazdırma bu tarayıcı tarafından tam olarak desteklenmemektedir.
pdfjs-printing-not-ready = Uyarı: PDF tamamen yüklenmedi ve yazdırmaya hazır değil.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Kenar çubuğunu aç/kapat
pdfjs-toggle-sidebar-notification-button =
.title = Kenar çubuğunu aç/kapat (Belge ana hat/ekler/katmanlar içeriyor)
pdfjs-toggle-sidebar-button-label = Kenar çubuğunu aç/kapat
pdfjs-document-outline-button =
.title = Belge ana hatlarını göster (Tüm öğeleri genişletmek/daraltmak için çift tıklayın)
pdfjs-document-outline-button-label = Belge ana hatları
pdfjs-attachments-button =
.title = Ekleri göster
pdfjs-attachments-button-label = Ekler
pdfjs-layers-button =
.title = Katmanları göster (tüm katmanları varsayılan duruma sıfırlamak için çift tıklayın)
pdfjs-layers-button-label = Katmanlar
pdfjs-thumbs-button =
.title = Küçük resimleri göster
pdfjs-thumbs-button-label = Küçük resimler
pdfjs-current-outline-item-button =
.title = Mevcut ana hat öğesini bul
pdfjs-current-outline-item-button-label = Mevcut ana hat öğesi
pdfjs-findbar-button =
.title = Belgede bul
pdfjs-findbar-button-label = Bul
pdfjs-additional-layers = Ek katmanlar
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Sayfa { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = { $page }. sayfanın küçük hâli
## Find panel button title and messages
pdfjs-find-input =
.title = Bul
.placeholder = Belgede bul…
pdfjs-find-previous-button =
.title = Önceki eşleşmeyi bul
pdfjs-find-previous-button-label = Önceki
pdfjs-find-next-button =
.title = Sonraki eşleşmeyi bul
pdfjs-find-next-button-label = Sonraki
pdfjs-find-highlight-checkbox = Tümünü vurgula
pdfjs-find-match-case-checkbox-label = Büyük-küçük harfe duyarlı
pdfjs-find-match-diacritics-checkbox-label = Fonetik işaretleri bul
pdfjs-find-entire-word-checkbox-label = Tam sözcükler
pdfjs-find-reached-top = Belgenin başına ulaşıldı, sonundan devam edildi
pdfjs-find-reached-bottom = Belgenin sonuna ulaşıldı, başından devam edildi
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $total } eşleşmeden { $current }. eşleşme
*[other] { $total } eşleşmeden { $current }. eşleşme
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] { $limit } eşleşmeden fazla
*[other] { $limit } eşleşmeden fazla
}
pdfjs-find-not-found = Eşleşme bulunamadı
## Predefined zoom values
pdfjs-page-scale-width = Sayfa genişliği
pdfjs-page-scale-fit = Sayfayı sığdır
pdfjs-page-scale-auto = Otomatik yakınlaştır
pdfjs-page-scale-actual = Gerçek boyut
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = %{ $scale }
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Sayfa { $page }
## Loading indicator messages
pdfjs-loading-error = PDF yüklenirken bir hata oluştu.
pdfjs-invalid-file-error = Geçersiz veya bozulmuş PDF dosyası.
pdfjs-missing-file-error = PDF dosyası eksik.
pdfjs-unexpected-response-error = Beklenmeyen sunucu yanıtı.
pdfjs-rendering-error = Sayfa yorumlanırken bir hata oluştu.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date } { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } işareti]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Bu PDF dosyasını açmak için parolasını yazın.
pdfjs-password-invalid = Geçersiz parola. Lütfen yeniden deneyin.
pdfjs-password-ok-button = Tamam
pdfjs-password-cancel-button = İptal
pdfjs-web-fonts-disabled = Web fontları devre dışı: Gömülü PDF fontları kullanılamıyor.
## Editing
pdfjs-editor-free-text-button =
.title = Metin
pdfjs-editor-free-text-button-label = Metin
pdfjs-editor-ink-button =
.title = Çiz
pdfjs-editor-ink-button-label = Çiz
pdfjs-editor-stamp-button =
.title = Resim ekle veya düzenle
pdfjs-editor-stamp-button-label = Resim ekle veya düzenle
pdfjs-editor-highlight-button =
.title = Vurgula
pdfjs-editor-highlight-button-label = Vurgula
pdfjs-highlight-floating-button1 =
.title = Vurgula
.aria-label = Vurgula
pdfjs-highlight-floating-button-label = Vurgula
pdfjs-editor-signature-button =
.title = İmza ekle
pdfjs-editor-signature-button-label = İmza ekle
## Default editor aria labels
# “Highlight” is a noun, the string is used on the editor for highlights.
pdfjs-editor-highlight-editor =
.aria-label = Vurgu düzenleyici
# “Drawing” is a noun, the string is used on the editor for drawings.
pdfjs-editor-ink-editor =
.aria-label = Çizim düzenleyici
pdfjs-editor-signature-editor =
.aria-label = İmza düzenleyici
pdfjs-editor-stamp-editor =
.aria-label = Resim düzenleyici
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Çizimi kaldır
pdfjs-editor-remove-freetext-button =
.title = Metni kaldır
pdfjs-editor-remove-stamp-button =
.title = Resmi kaldır
pdfjs-editor-remove-highlight-button =
.title = Vurgulamayı kaldır
pdfjs-editor-remove-signature-button =
.title = İmzayı kaldır
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Renk
pdfjs-editor-free-text-size-input = Boyut
pdfjs-editor-ink-color-input = Renk
pdfjs-editor-ink-thickness-input = Kalınlık
pdfjs-editor-ink-opacity-input = Saydamlık
pdfjs-editor-stamp-add-image-button =
.title = Resim ekle
pdfjs-editor-stamp-add-image-button-label = Resim ekle
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Kalınlık
pdfjs-editor-free-highlight-thickness-title =
.title = Metin dışındaki öğeleri vurgularken kalınlığı değiştir
pdfjs-editor-add-signature-container =
.aria-label = İmza yönetimi ve kayıtlı imzalar
pdfjs-editor-signature-add-signature-button =
.title = Yeni imza ekle
pdfjs-editor-signature-add-signature-button-label = Yeni imza ekle
# Used on the button to use an already saved signature.
# Variables:
# $description (String) - a string describing/labeling the signature.
pdfjs-editor-add-saved-signature-button =
.title = Kayıtlı imza: { $description }
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Metin düzenleyicisi
.default-content = Yazmaya başlayın…
pdfjs-free-text =
.aria-label = Metin düzenleyicisi
pdfjs-free-text-default-content = Yazmaya başlayın…
pdfjs-ink =
.aria-label = Çizim düzenleyicisi
pdfjs-ink-canvas =
.aria-label = Kullanıcı tarafından oluşturulan resim
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Alternatif metin
pdfjs-editor-alt-text-edit-button =
.aria-label = Alternatif metni düzenle
pdfjs-editor-alt-text-edit-button-label = Alternatif metni düzenle
pdfjs-editor-alt-text-dialog-label = Bir seçenek seçin
pdfjs-editor-alt-text-dialog-description = Alternatif metin, insanlar resmi göremediğinde veya resim yüklenmediğinde işe yarar.
pdfjs-editor-alt-text-add-description-label = Açıklama ekle
pdfjs-editor-alt-text-add-description-description = Konuyu, ortamı veya eylemleri tanımlayan bir iki cümle yazmaya çalışın.
pdfjs-editor-alt-text-mark-decorative-label = Dekoratif olarak işaretle
pdfjs-editor-alt-text-mark-decorative-description = Kenarlıklar veya filigranlar gibi dekoratif resimler için kullanılır.
pdfjs-editor-alt-text-cancel-button = Vazgeç
pdfjs-editor-alt-text-save-button = Kaydet
pdfjs-editor-alt-text-decorative-tooltip = Dekoratif olarak işaretlendi
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Örneğin, “Genç bir adam yemek yemek için masaya oturuyor”
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Alternatif metin
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Sol üst köşe — yeniden boyutlandır
pdfjs-editor-resizer-label-top-middle = Üst orta — yeniden boyutlandır
pdfjs-editor-resizer-label-top-right = Sağ üst köşe — yeniden boyutlandır
pdfjs-editor-resizer-label-middle-right = Orta sağ — yeniden boyutlandır
pdfjs-editor-resizer-label-bottom-right = Sağ alt köşe — yeniden boyutlandır
pdfjs-editor-resizer-label-bottom-middle = Alt orta — yeniden boyutlandır
pdfjs-editor-resizer-label-bottom-left = Sol alt köşe — yeniden boyutlandır
pdfjs-editor-resizer-label-middle-left = Orta sol — yeniden boyutlandır
pdfjs-editor-resizer-top-left =
.aria-label = Sol üst köşe — yeniden boyutlandır
pdfjs-editor-resizer-top-middle =
.aria-label = Üst orta — yeniden boyutlandır
pdfjs-editor-resizer-top-right =
.aria-label = Sağ üst köşe — yeniden boyutlandır
pdfjs-editor-resizer-middle-right =
.aria-label = Orta sağ — yeniden boyutlandır
pdfjs-editor-resizer-bottom-right =
.aria-label = Sağ alt köşe — yeniden boyutlandır
pdfjs-editor-resizer-bottom-middle =
.aria-label = Alt orta — yeniden boyutlandır
pdfjs-editor-resizer-bottom-left =
.aria-label = Sol alt köşe — yeniden boyutlandır
pdfjs-editor-resizer-middle-left =
.aria-label = Orta sol — yeniden boyutlandır
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Vurgu rengi
pdfjs-editor-colorpicker-button =
.title = Rengi değiştir
pdfjs-editor-colorpicker-dropdown =
.aria-label = Renk seçenekleri
pdfjs-editor-colorpicker-yellow =
.title = Sarı
pdfjs-editor-colorpicker-green =
.title = Yeşil
pdfjs-editor-colorpicker-blue =
.title = Mavi
pdfjs-editor-colorpicker-pink =
.title = Pembe
pdfjs-editor-colorpicker-red =
.title = Kırmızı
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Tümünü göster
pdfjs-editor-highlight-show-all-button =
.title = Tümünü göster
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Alt metni düzenle (resim açıklaması)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Alt metin ekle (resim açıklaması)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Açıklamanızı buraya yazın…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Görme engelli kişilere gösterilecek veya resmin yüklenemediği durumlarda gösterilecek kısa açıklama.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Bu alt metin otomatik olarak oluşturulmuştur ve hatalı olabilir.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Daha fazla bilgi alın
pdfjs-editor-new-alt-text-create-automatically-button-label = Otomatik olarak alt metin oluştur
pdfjs-editor-new-alt-text-not-now-button = Şimdi değil
pdfjs-editor-new-alt-text-error-title = Alt metin otomatik olarak oluşturulamadı
pdfjs-editor-new-alt-text-error-description = Lütfen kendi alt metninizi yazın veya daha sonra yeniden deneyin.
pdfjs-editor-new-alt-text-error-close-button = Kapat
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Alt metin yapay zekâ modeli indiriliyor ({ $downloadedSize } / { $totalSize } MB)
.aria-valuetext = Alt metin yapay zekâ modeli indiriliyor ({ $downloadedSize } / { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Alternatif metin eklendi
pdfjs-editor-new-alt-text-added-button-label = Alt metin eklendi
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Alternatif metin eksik
pdfjs-editor-new-alt-text-missing-button-label = Alt metin eksik
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Alternatif metni incele
pdfjs-editor-new-alt-text-to-review-button-label = Alt metni incele
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Otomatik olarak oluşturuldu: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Resim alt metni ayarları
pdfjs-image-alt-text-settings-button-label = Resim alt metni ayarları
pdfjs-editor-alt-text-settings-dialog-label = Resim alt metni ayarları
pdfjs-editor-alt-text-settings-automatic-title = Otomatik alt metin
pdfjs-editor-alt-text-settings-create-model-button-label = Otomatik olarak alt metin oluştur
pdfjs-editor-alt-text-settings-create-model-description = Görme engelli kişilere gösterilecek veya resmin yüklenemediği durumlarda gösterilecek açıklamalar önerir.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Alt metin yapay zekâ modeli ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Verilerinizin gizli kalması için cihazınızda yerel olarak çalışır. Otomatik alt metin için gereklidir.
pdfjs-editor-alt-text-settings-delete-model-button = Sil
pdfjs-editor-alt-text-settings-download-model-button = İndir
pdfjs-editor-alt-text-settings-downloading-model-button = İndiriliyor…
pdfjs-editor-alt-text-settings-editor-title = Alt metin düzenleyicisi
pdfjs-editor-alt-text-settings-show-dialog-button-label = Resim eklerken alt metin düzenleyicisini hemen göster
pdfjs-editor-alt-text-settings-show-dialog-description = Tüm resimlerinizin alt metne sahip olduğundan emin olmanızı sağlar.
pdfjs-editor-alt-text-settings-close-button = Kapat
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Vurgulama silindi
pdfjs-editor-undo-bar-message-freetext = Metin silindi
pdfjs-editor-undo-bar-message-ink = Çizim silindi
pdfjs-editor-undo-bar-message-stamp = Görsel silindi
pdfjs-editor-undo-bar-message-signature = İmza kaldırıldı
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } ek açıklama silindi
*[other] { $count } ek açıklama silindi
}
pdfjs-editor-undo-bar-undo-button =
.title = Geri al
pdfjs-editor-undo-bar-undo-button-label = Geri al
pdfjs-editor-undo-bar-close-button =
.title = Kapat
pdfjs-editor-undo-bar-close-button-label = Kapat
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = Bu pencereden PDF belgesine eklemek üzere imza oluşturabilirsiniz. Adınızı düzenleyebilir (adınız alt metin olarak da kullanılır) ve isterseniz ileride tekrar kullanmak üzere imzayı kaydedebilirsiniz.
pdfjs-editor-add-signature-dialog-title = İmza ekle
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Yaz
.title = Yaz
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Çiz
.title = Çiz
pdfjs-editor-add-signature-image-button = Resim
.title = Resim
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = İmzanızı yazın
.placeholder = İmzanızı yazın
pdfjs-editor-add-signature-draw-placeholder = İmzanızı çizin
pdfjs-editor-add-signature-draw-thickness-range-label = Kalınlık
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Çizgi kalınlığı: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Yüklenecek dosyayı buraya sürükleyin
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Veya resim dosyalarına göz atın
*[other] Veya resim dosyalarına göz atın
}
## Controls
pdfjs-editor-add-signature-description-label = Açıklama (alt metin)
pdfjs-editor-add-signature-description-input =
.title = Açıklama (alt metin)
pdfjs-editor-add-signature-description-default-when-drawing = İmza
pdfjs-editor-add-signature-clear-button-label = İmzayı temizle
pdfjs-editor-add-signature-clear-button =
.title = İmzayı temizle
pdfjs-editor-add-signature-save-checkbox = İmzayı kaydet
pdfjs-editor-add-signature-save-warning-message = Kayıtlı 5 imza sınırına ulaştınız. Daha fazla imza kaydetmek için imzalardan birini kaldırın.
pdfjs-editor-add-signature-image-upload-error-title = Resim yüklenemedi
pdfjs-editor-add-signature-image-upload-error-description = Ağ bağlantınızı kontrol edin veya başka bir resim deneyin.
pdfjs-editor-add-signature-error-close-button = Kapat
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Vazgeç
pdfjs-editor-add-signature-add-button = Ekle
pdfjs-editor-edit-signature-update-button = Güncelle
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = İmzayı kaldır
pdfjs-editor-delete-signature-button-label = İmzayı kaldır
pdfjs-editor-delete-signature-button1 =
.title = Kayıtlı imza kaldırdı
pdfjs-editor-delete-signature-button-label1 = Kayıtlı imzayı kaldır
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Açıklamayı düzenle
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Açıklamayı düzenle
================================================
FILE: cookbook/static/pdfjs/web/locale/trs/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Pajinâ gunâj rukùu
pdfjs-previous-button-label = Sa gachin
pdfjs-next-button =
.title = Pajinâ 'na' ñaan
pdfjs-next-button-label = Ne' ñaan
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Ñanj
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = si'iaj { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } of { $pagesCount })
pdfjs-zoom-out-button =
.title = Nagi'iaj li'
pdfjs-zoom-out-button-label = Nagi'iaj li'
pdfjs-zoom-in-button =
.title = Nagi'iaj niko'
pdfjs-zoom-in-button-label = Nagi'iaj niko'
pdfjs-zoom-select =
.title = dàj nìko ma'an
pdfjs-presentation-mode-button =
.title = Naduno' daj ga ma
pdfjs-presentation-mode-button-label = Daj gà ma
pdfjs-open-file-button =
.title = Na'nïn' chrû ñanj
pdfjs-open-file-button-label = Na'nïn
pdfjs-print-button =
.title = Nari' ña du'ua
pdfjs-print-button-label = Nari' ñadu'ua
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Rasun
pdfjs-tools-button-label = Nej rasùun
pdfjs-first-page-button =
.title = gun' riña pajina asiniin
pdfjs-first-page-button-label = Gun' riña pajina asiniin
pdfjs-last-page-button =
.title = Gun' riña pajina rukù ni'in
pdfjs-last-page-button-label = Gun' riña pajina rukù ni'inj
pdfjs-page-rotate-cw-button =
.title = Tanikaj ne' huat
pdfjs-page-rotate-cw-button-label = Tanikaj ne' huat
pdfjs-page-rotate-ccw-button =
.title = Tanikaj ne' chînt'
pdfjs-page-rotate-ccw-button-label = Tanikaj ne' chint
pdfjs-cursor-text-select-tool-button =
.title = Dugi'iaj sun' sa ganahui texto
pdfjs-cursor-text-select-tool-button-label = Nej rasun arajsun' da' nahui' texto
pdfjs-cursor-hand-tool-button =
.title = Nachrun' nej rasun
pdfjs-cursor-hand-tool-button-label = Sa rajsun ro'o'
pdfjs-scroll-vertical-button =
.title = Garasun' dukuán runūu
pdfjs-scroll-vertical-button-label = Dukuán runūu
pdfjs-scroll-horizontal-button =
.title = Garasun' dukuán nikin' nahui
pdfjs-scroll-horizontal-button-label = Dukuán nikin' nahui
pdfjs-scroll-wrapped-button =
.title = Garasun' sa nachree
pdfjs-scroll-wrapped-button-label = Sa nachree
pdfjs-spread-none-button =
.title = Si nagi'iaj nugun'un' nej pagina hua ninin
pdfjs-spread-none-button-label = Ni'io daj hua pagina
pdfjs-spread-odd-button =
.title = Nagi'iaj nugua'ant nej pajina
pdfjs-spread-odd-button-label = Ni'io' daj hua libro gurin
pdfjs-spread-even-button =
.title = Nakāj dugui' ngà nej pajinâ ayi'ì ngà da' hùi hùi
pdfjs-spread-even-button-label = Nahuin nìko nej
## Document properties dialog
pdfjs-document-properties-button =
.title = Nej sa nikāj ñanj…
pdfjs-document-properties-button-label = Nej sa nikāj ñanj…
pdfjs-document-properties-file-name = Si yugui archîbo:
pdfjs-document-properties-file-size = Dàj yachìj archîbo:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = Si yugui:
pdfjs-document-properties-author = Sí girirà:
pdfjs-document-properties-subject = Dugui':
pdfjs-document-properties-keywords = Nej nuguan' huìi:
pdfjs-document-properties-creation-date = Gui gurugui' man:
pdfjs-document-properties-modification-date = Nuguan' nahuin nakà:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Guiri ro'
pdfjs-document-properties-producer = Sa ri PDF:
pdfjs-document-properties-version = PDF Version:
pdfjs-document-properties-page-count = Si Guendâ Pâjina:
pdfjs-document-properties-page-size = Dàj yachìj pâjina:
pdfjs-document-properties-page-size-unit-inches = riña
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = nadu'ua
pdfjs-document-properties-page-size-orientation-landscape = dàj huaj
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Da'ngà'a
pdfjs-document-properties-page-size-name-legal = Nuguan' a'nï'ïn
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Nanèt chre ni'iajt riña Web:
pdfjs-document-properties-linearized-yes = Ga'ue
pdfjs-document-properties-linearized-no = Si ga'ue
pdfjs-document-properties-close-button = Narán
## Print
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Duyichin'
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Nadunā barrâ nù yi'nïn
pdfjs-toggle-sidebar-button-label = Nadunā barrâ nù yi'nïn
pdfjs-findbar-button-label = Narì'
## Thumbnails panel item (tooltip and alt text for images)
## Find panel button title and messages
pdfjs-find-previous-button-label = Sa gachîn
pdfjs-find-next-button-label = Ne' ñaan
pdfjs-find-highlight-checkbox = Daran' sa ña'an
pdfjs-find-match-case-checkbox-label = Match case
pdfjs-find-not-found = Nu narì'ij nugua'anj
## Predefined zoom values
pdfjs-page-scale-actual = Dàj yàchi akuan' nín
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
## Loading indicator messages
## Annotations
## Password
pdfjs-password-ok-button = Ga'ue
pdfjs-password-cancel-button = Duyichin'
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/uk/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Попередня сторінка
pdfjs-previous-button-label = Попередня
pdfjs-next-button =
.title = Наступна сторінка
pdfjs-next-button-label = Наступна
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Сторінка
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = із { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } із { $pagesCount })
pdfjs-zoom-out-button =
.title = Зменшити
pdfjs-zoom-out-button-label = Зменшити
pdfjs-zoom-in-button =
.title = Збільшити
pdfjs-zoom-in-button-label = Збільшити
pdfjs-zoom-select =
.title = Масштаб
pdfjs-presentation-mode-button =
.title = Перейти в режим презентації
pdfjs-presentation-mode-button-label = Режим презентації
pdfjs-open-file-button =
.title = Відкрити файл
pdfjs-open-file-button-label = Відкрити
pdfjs-print-button =
.title = Друк
pdfjs-print-button-label = Друк
pdfjs-save-button =
.title = Зберегти
pdfjs-save-button-label = Зберегти
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Завантажити
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Завантажити
pdfjs-bookmark-button =
.title = Поточна сторінка (перегляд URL-адреси з поточної сторінки)
pdfjs-bookmark-button-label = Поточна сторінка
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Інструменти
pdfjs-tools-button-label = Інструменти
pdfjs-first-page-button =
.title = На першу сторінку
pdfjs-first-page-button-label = На першу сторінку
pdfjs-last-page-button =
.title = На останню сторінку
pdfjs-last-page-button-label = На останню сторінку
pdfjs-page-rotate-cw-button =
.title = Повернути за годинниковою стрілкою
pdfjs-page-rotate-cw-button-label = Повернути за годинниковою стрілкою
pdfjs-page-rotate-ccw-button =
.title = Повернути проти годинникової стрілки
pdfjs-page-rotate-ccw-button-label = Повернути проти годинникової стрілки
pdfjs-cursor-text-select-tool-button =
.title = Увімкнути інструмент вибору тексту
pdfjs-cursor-text-select-tool-button-label = Інструмент вибору тексту
pdfjs-cursor-hand-tool-button =
.title = Увімкнути інструмент "Рука"
pdfjs-cursor-hand-tool-button-label = Інструмент "Рука"
pdfjs-scroll-page-button =
.title = Використовувати прокручування сторінки
pdfjs-scroll-page-button-label = Прокручування сторінки
pdfjs-scroll-vertical-button =
.title = Використовувати вертикальне прокручування
pdfjs-scroll-vertical-button-label = Вертикальне прокручування
pdfjs-scroll-horizontal-button =
.title = Використовувати горизонтальне прокручування
pdfjs-scroll-horizontal-button-label = Горизонтальне прокручування
pdfjs-scroll-wrapped-button =
.title = Використовувати масштабоване прокручування
pdfjs-scroll-wrapped-button-label = Масштабоване прокручування
pdfjs-spread-none-button =
.title = Не використовувати розгорнуті сторінки
pdfjs-spread-none-button-label = Без розгорнутих сторінок
pdfjs-spread-odd-button =
.title = Розгорнуті сторінки починаються з непарних номерів
pdfjs-spread-odd-button-label = Непарні сторінки зліва
pdfjs-spread-even-button =
.title = Розгорнуті сторінки починаються з парних номерів
pdfjs-spread-even-button-label = Парні сторінки зліва
## Document properties dialog
pdfjs-document-properties-button =
.title = Властивості документа…
pdfjs-document-properties-button-label = Властивості документа…
pdfjs-document-properties-file-name = Назва файлу:
pdfjs-document-properties-file-size = Розмір файлу:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } кБ ({ $b } байтів)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } МБ ({ $b } байтів)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } кБ ({ $size_b } байтів)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } МБ ({ $size_b } байтів)
pdfjs-document-properties-title = Заголовок:
pdfjs-document-properties-author = Автор:
pdfjs-document-properties-subject = Тема:
pdfjs-document-properties-keywords = Ключові слова:
pdfjs-document-properties-creation-date = Дата створення:
pdfjs-document-properties-modification-date = Дата зміни:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Створено:
pdfjs-document-properties-producer = Виробник PDF:
pdfjs-document-properties-version = Версія PDF:
pdfjs-document-properties-page-count = Кількість сторінок:
pdfjs-document-properties-page-size = Розмір сторінки:
pdfjs-document-properties-page-size-unit-inches = дюймів
pdfjs-document-properties-page-size-unit-millimeters = мм
pdfjs-document-properties-page-size-orientation-portrait = книжкова
pdfjs-document-properties-page-size-orientation-landscape = альбомна
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letter
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Швидкий перегляд в Інтернеті:
pdfjs-document-properties-linearized-yes = Так
pdfjs-document-properties-linearized-no = Ні
pdfjs-document-properties-close-button = Закрити
## Print
pdfjs-print-progress-message = Підготовка документу до друку…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Скасувати
pdfjs-printing-not-supported = Попередження: Цей браузер не повністю підтримує друк.
pdfjs-printing-not-ready = Попередження: PDF не повністю завантажений для друку.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Бічна панель
pdfjs-toggle-sidebar-notification-button =
.title = Перемкнути бічну панель (документ містить ескіз/вкладення/шари)
pdfjs-toggle-sidebar-button-label = Перемкнути бічну панель
pdfjs-document-outline-button =
.title = Показати схему документу (подвійний клік для розгортання/згортання елементів)
pdfjs-document-outline-button-label = Схема документа
pdfjs-attachments-button =
.title = Показати вкладення
pdfjs-attachments-button-label = Вкладення
pdfjs-layers-button =
.title = Показати шари (двічі клацніть, щоб скинути всі шари до типового стану)
pdfjs-layers-button-label = Шари
pdfjs-thumbs-button =
.title = Показати мініатюри
pdfjs-thumbs-button-label = Мініатюри
pdfjs-current-outline-item-button =
.title = Знайти поточний елемент змісту
pdfjs-current-outline-item-button-label = Поточний елемент змісту
pdfjs-findbar-button =
.title = Знайти в документі
pdfjs-findbar-button-label = Знайти
pdfjs-additional-layers = Додаткові шари
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Сторінка { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Ескіз сторінки { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Знайти
.placeholder = Знайти в документі…
pdfjs-find-previous-button =
.title = Знайти попереднє входження фрази
pdfjs-find-previous-button-label = Попереднє
pdfjs-find-next-button =
.title = Знайти наступне входження фрази
pdfjs-find-next-button-label = Наступне
pdfjs-find-highlight-checkbox = Підсвітити все
pdfjs-find-match-case-checkbox-label = З урахуванням регістру
pdfjs-find-match-diacritics-checkbox-label = Відповідність діакритичних знаків
pdfjs-find-entire-word-checkbox-label = Цілі слова
pdfjs-find-reached-top = Досягнуто початку документу, продовжено з кінця
pdfjs-find-reached-bottom = Досягнуто кінця документу, продовжено з початку
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count =
{ $total ->
[one] { $current } збіг з { $total }
[few] { $current } збіги з { $total }
*[many] { $current } збігів з { $total }
}
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit =
{ $limit ->
[one] Понад { $limit } збіг
[few] Понад { $limit } збіги
*[many] Понад { $limit } збігів
}
pdfjs-find-not-found = Фразу не знайдено
## Predefined zoom values
pdfjs-page-scale-width = За шириною
pdfjs-page-scale-fit = Вмістити
pdfjs-page-scale-auto = Автомасштаб
pdfjs-page-scale-actual = Дійсний розмір
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Сторінка { $page }
## Loading indicator messages
pdfjs-loading-error = Під час завантаження PDF сталася помилка.
pdfjs-invalid-file-error = Недійсний або пошкоджений PDF-файл.
pdfjs-missing-file-error = Відсутній PDF-файл.
pdfjs-unexpected-response-error = Неочікувана відповідь сервера.
pdfjs-rendering-error = Під час виведення сторінки сталася помилка.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type }-анотація]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Введіть пароль для відкриття цього PDF-файлу.
pdfjs-password-invalid = Неправильний пароль. Спробуйте ще раз.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Скасувати
pdfjs-web-fonts-disabled = Вебшрифти вимкнено: неможливо використати вбудовані у PDF шрифти.
## Editing
pdfjs-editor-free-text-button =
.title = Текст
pdfjs-editor-free-text-button-label = Текст
pdfjs-editor-ink-button =
.title = Малювати
pdfjs-editor-ink-button-label = Малювати
pdfjs-editor-stamp-button =
.title = Додати чи редагувати зображення
pdfjs-editor-stamp-button-label = Додати чи редагувати зображення
pdfjs-editor-highlight-button =
.title = Підсвітити
pdfjs-editor-highlight-button-label = Підсвітити
pdfjs-highlight-floating-button1 =
.title = Підсвітити
.aria-label = Підсвітити
pdfjs-highlight-floating-button-label = Підсвітити
pdfjs-editor-signature-button =
.title = Додати підпис
pdfjs-editor-signature-button-label = Додати підпис
## Default editor aria labels
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Вилучити малюнок
pdfjs-editor-remove-freetext-button =
.title = Вилучити текст
pdfjs-editor-remove-stamp-button =
.title = Вилучити зображення
pdfjs-editor-remove-highlight-button =
.title = Вилучити підсвічення
pdfjs-editor-remove-signature-button =
.title = Вилучити підпис
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Колір
pdfjs-editor-free-text-size-input = Розмір
pdfjs-editor-ink-color-input = Колір
pdfjs-editor-ink-thickness-input = Товщина
pdfjs-editor-ink-opacity-input = Прозорість
pdfjs-editor-stamp-add-image-button =
.title = Додати зображення
pdfjs-editor-stamp-add-image-button-label = Додати зображення
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Товщина
pdfjs-editor-free-highlight-thickness-title =
.title = Змінюйте товщину під час підсвічення елементів, крім тексту
pdfjs-editor-signature-add-signature-button =
.title = Додати новий підпис
pdfjs-editor-signature-add-signature-button-label = Додати новий підпис
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Текстовий редактор
.default-content = Напишіть щось…
pdfjs-free-text =
.aria-label = Текстовий редактор
pdfjs-free-text-default-content = Почніть вводити…
pdfjs-ink =
.aria-label = Графічний редактор
pdfjs-ink-canvas =
.aria-label = Зображення, створене користувачем
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Альтернативний текст
pdfjs-editor-alt-text-edit-button =
.aria-label = Редагувати альтернативний текст
pdfjs-editor-alt-text-edit-button-label = Змінити альтернативний текст
pdfjs-editor-alt-text-dialog-label = Вибрати варіант
pdfjs-editor-alt-text-dialog-description = Альтернативний текст допомагає, коли зображення не видно або коли воно не завантажується.
pdfjs-editor-alt-text-add-description-label = Додати опис
pdfjs-editor-alt-text-add-description-description = Намагайтеся створити 1-2 речення, які описують тему, обставини або дії.
pdfjs-editor-alt-text-mark-decorative-label = Позначити декоративним
pdfjs-editor-alt-text-mark-decorative-description = Використовується для декоративних зображень, наприклад рамок або водяних знаків.
pdfjs-editor-alt-text-cancel-button = Скасувати
pdfjs-editor-alt-text-save-button = Зберегти
pdfjs-editor-alt-text-decorative-tooltip = Позначено декоративним
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Наприклад, “Молодий чоловік сідає за стіл їсти”
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Альтернативний текст
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Верхній лівий кут – зміна розміру
pdfjs-editor-resizer-label-top-middle = Вгорі посередині – зміна розміру
pdfjs-editor-resizer-label-top-right = Верхній правий кут – зміна розміру
pdfjs-editor-resizer-label-middle-right = Праворуч посередині – зміна розміру
pdfjs-editor-resizer-label-bottom-right = Нижній правий кут – зміна розміру
pdfjs-editor-resizer-label-bottom-middle = Внизу посередині – зміна розміру
pdfjs-editor-resizer-label-bottom-left = Нижній лівий кут – зміна розміру
pdfjs-editor-resizer-label-middle-left = Ліворуч посередині – зміна розміру
pdfjs-editor-resizer-top-left =
.aria-label = Верхній лівий кут – зміна розміру
pdfjs-editor-resizer-top-middle =
.aria-label = Вгорі посередині – зміна розміру
pdfjs-editor-resizer-top-right =
.aria-label = Верхній правий кут – зміна розміру
pdfjs-editor-resizer-middle-right =
.aria-label = Праворуч посередині – зміна розміру
pdfjs-editor-resizer-bottom-right =
.aria-label = Нижній правий кут – зміна розміру
pdfjs-editor-resizer-bottom-middle =
.aria-label = Внизу посередині – зміна розміру
pdfjs-editor-resizer-bottom-left =
.aria-label = Нижній лівий кут – зміна розміру
pdfjs-editor-resizer-middle-left =
.aria-label = Ліворуч посередині – зміна розміру
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Колір підсвічення
pdfjs-editor-colorpicker-button =
.title = Змінити колір
pdfjs-editor-colorpicker-dropdown =
.aria-label = Вибір кольору
pdfjs-editor-colorpicker-yellow =
.title = Жовтий
pdfjs-editor-colorpicker-green =
.title = Зелений
pdfjs-editor-colorpicker-blue =
.title = Блакитний
pdfjs-editor-colorpicker-pink =
.title = Рожевий
pdfjs-editor-colorpicker-red =
.title = Червоний
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Показати все
pdfjs-editor-highlight-show-all-button =
.title = Показати все
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Редагувати альтернативний текст (опис зображення)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Додати альтернативний текст (опис зображення)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Напишіть свій опис тут…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Короткий опис для людей, які не бачать зображення, або якщо зображення не завантажується.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Цей альтернативний текст створено автоматично, тому він може бути неточним.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Докладніше
pdfjs-editor-new-alt-text-create-automatically-button-label = Автоматично створювати альтернативний текст
pdfjs-editor-new-alt-text-not-now-button = Не зараз
pdfjs-editor-new-alt-text-error-title = Не вдалося автоматично створити альтернативний текст
pdfjs-editor-new-alt-text-error-description = Напишіть власний альтернативний текст або повторіть спробу пізніше.
pdfjs-editor-new-alt-text-error-close-button = Закрити
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Завантаження моделі ШІ для альтернативного тексту ({ $downloadedSize } з { $totalSize } МБ)
.aria-valuetext = Завантаження моделі ШІ для альтернативного тексту ({ $downloadedSize } з { $totalSize } МБ)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Альтернативний текст додано
pdfjs-editor-new-alt-text-added-button-label = Альтернативний текст додано
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Відсутній альтернативний текст
pdfjs-editor-new-alt-text-missing-button-label = Відсутній альтернативний текст
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Переглянути альтернативний текст
pdfjs-editor-new-alt-text-to-review-button-label = Переглянути альтернативний текст
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Створено автоматично: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Налаштування альтернативного тексту зображення
pdfjs-image-alt-text-settings-button-label = Налаштування альтернативного тексту зображення
pdfjs-editor-alt-text-settings-dialog-label = Налаштування альтернативного тексту зображення
pdfjs-editor-alt-text-settings-automatic-title = Автоматичний альтернативний текст
pdfjs-editor-alt-text-settings-create-model-button-label = Автоматично створювати альтернативний текст
pdfjs-editor-alt-text-settings-create-model-description = Пропонує описи, щоб допомогти людям, які не бачать зображення, або якщо зображення не завантажується.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Модель ШІ для альтернативного тексту ({ $totalSize } МБ)
pdfjs-editor-alt-text-settings-ai-model-description = Працює локально на вашому пристрої, тому приватність ваших даних захищена. Призначена для автоматичного створення альтернативного тексту.
pdfjs-editor-alt-text-settings-delete-model-button = Видалити
pdfjs-editor-alt-text-settings-download-model-button = Завантажити
pdfjs-editor-alt-text-settings-downloading-model-button = Завантаження…
pdfjs-editor-alt-text-settings-editor-title = Редактор альтернативного тексту
pdfjs-editor-alt-text-settings-show-dialog-button-label = Показувати редактор альтернативного тексту під час додавання зображення
pdfjs-editor-alt-text-settings-show-dialog-description = Допомагає переконатися, що всі ваші зображення мають альтернативний текст.
pdfjs-editor-alt-text-settings-close-button = Закрити
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Підсвічення вилучено
pdfjs-editor-undo-bar-message-freetext = Текст вилучено
pdfjs-editor-undo-bar-message-ink = Малюнок вилучено
pdfjs-editor-undo-bar-message-stamp = Зображення вилучено
pdfjs-editor-undo-bar-message-signature = Підпис вилучено
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple =
{ $count ->
[one] { $count } анотацію вилучено
[few] { $count } анотації вилучено
*[many] { $count } анотацій вилучено
}
pdfjs-editor-undo-bar-undo-button =
.title = Повернути
pdfjs-editor-undo-bar-undo-button-label = Повернути
pdfjs-editor-undo-bar-close-button =
.title = Закрити
pdfjs-editor-undo-bar-close-button-label = Закрити
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = У цьому вікні користувач може створити підпис для додавання до PDF-документа. Користувач може відредагувати назву (яка також слугує альтернативним текстом) і, за бажання, зберегти підпис для повторного використання.
pdfjs-editor-add-signature-dialog-title = Додати підпис
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Ввести
.title = Ввести
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Малювати
.title = Малювати
pdfjs-editor-add-signature-image-button = Зображення
.title = Зображення
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Введіть свій підпис
.placeholder = Введіть свій підпис
pdfjs-editor-add-signature-draw-placeholder = Намалюйте свій підпис
pdfjs-editor-add-signature-draw-thickness-range-label = Товщина
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Товщина лінії: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Перетягніть файл сюди, щоб вивантажити
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Або виберіть файли зображень
*[other] Або перегляньте файли зображень
}
## Controls
pdfjs-editor-add-signature-description-label = Опис (альтернативний текст)
pdfjs-editor-add-signature-description-input =
.title = Опис (альтернативний текст)
pdfjs-editor-add-signature-description-default-when-drawing = Підпис
pdfjs-editor-add-signature-clear-button-label = Очистити підпис
pdfjs-editor-add-signature-clear-button =
.title = Очистити підпис
pdfjs-editor-add-signature-save-checkbox = Зберегти підпис
pdfjs-editor-add-signature-save-warning-message = Ви досягли ліміту в 5 збережених підписів. Вилучіть один, щоб зберегти інший.
pdfjs-editor-add-signature-image-upload-error-title = Не вдалося вивантажити зображення
pdfjs-editor-add-signature-image-upload-error-description = Перевірте мережеве з'єднання або спробуйте інше зображення.
pdfjs-editor-add-signature-error-close-button = Закрити
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Скасувати
pdfjs-editor-add-signature-add-button = Додати
pdfjs-editor-edit-signature-update-button = Оновити
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Вилучити підпис
pdfjs-editor-delete-signature-button-label = Вилучити підпис
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Редагувати опис
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Редагувати опис
================================================
FILE: cookbook/static/pdfjs/web/locale/ur/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = پچھلا صفحہ
pdfjs-previous-button-label = پچھلا
pdfjs-next-button =
.title = اگلا صفحہ
pdfjs-next-button-label = آگے
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = صفحہ
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = { $pagesCount } کا
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } کا { $pagesCount })
pdfjs-zoom-out-button =
.title = باہر زوم کریں
pdfjs-zoom-out-button-label = باہر زوم کریں
pdfjs-zoom-in-button =
.title = اندر زوم کریں
pdfjs-zoom-in-button-label = اندر زوم کریں
pdfjs-zoom-select =
.title = زوم
pdfjs-presentation-mode-button =
.title = پیشکش موڈ میں چلے جائیں
pdfjs-presentation-mode-button-label = پیشکش موڈ
pdfjs-open-file-button =
.title = مسل کھولیں
pdfjs-open-file-button-label = کھولیں
pdfjs-print-button =
.title = چھاپیں
pdfjs-print-button-label = چھاپیں
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = آلات
pdfjs-tools-button-label = آلات
pdfjs-first-page-button =
.title = پہلے صفحہ پر جائیں
pdfjs-first-page-button-label = پہلے صفحہ پر جائیں
pdfjs-last-page-button =
.title = آخری صفحہ پر جائیں
pdfjs-last-page-button-label = آخری صفحہ پر جائیں
pdfjs-page-rotate-cw-button =
.title = گھڑی وار گھمائیں
pdfjs-page-rotate-cw-button-label = گھڑی وار گھمائیں
pdfjs-page-rotate-ccw-button =
.title = ضد گھڑی وار گھمائیں
pdfjs-page-rotate-ccw-button-label = ضد گھڑی وار گھمائیں
pdfjs-cursor-text-select-tool-button =
.title = متن کے انتخاب کے ٹول کو فعال بناے
pdfjs-cursor-text-select-tool-button-label = متن کے انتخاب کا آلہ
pdfjs-cursor-hand-tool-button =
.title = ہینڈ ٹول کو فعال بناییں
pdfjs-cursor-hand-tool-button-label = ہاتھ کا آلہ
pdfjs-scroll-vertical-button =
.title = عمودی اسکرولنگ کا استعمال کریں
pdfjs-scroll-vertical-button-label = عمودی اسکرولنگ
pdfjs-scroll-horizontal-button =
.title = افقی سکرولنگ کا استعمال کریں
pdfjs-scroll-horizontal-button-label = افقی سکرولنگ
pdfjs-spread-none-button =
.title = صفحہ پھیلانے میں شامل نہ ہوں
pdfjs-spread-none-button-label = کوئی پھیلاؤ نہیں
pdfjs-spread-odd-button-label = تاک پھیلاؤ
pdfjs-spread-even-button-label = جفت پھیلاؤ
## Document properties dialog
pdfjs-document-properties-button =
.title = دستاویز خواص…
pdfjs-document-properties-button-label = دستاویز خواص…
pdfjs-document-properties-file-name = نام مسل:
pdfjs-document-properties-file-size = مسل سائز:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = عنوان:
pdfjs-document-properties-author = تخلیق کار:
pdfjs-document-properties-subject = موضوع:
pdfjs-document-properties-keywords = کلیدی الفاظ:
pdfjs-document-properties-creation-date = تخلیق کی تاریخ:
pdfjs-document-properties-modification-date = ترمیم کی تاریخ:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }، { $time }
pdfjs-document-properties-creator = تخلیق کار:
pdfjs-document-properties-producer = PDF پیدا کار:
pdfjs-document-properties-version = PDF ورژن:
pdfjs-document-properties-page-count = صفحہ شمار:
pdfjs-document-properties-page-size = صفہ کی لمبائ:
pdfjs-document-properties-page-size-unit-inches = میں
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = عمودی انداز
pdfjs-document-properties-page-size-orientation-landscape = افقى انداز
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = خط
pdfjs-document-properties-page-size-name-legal = قانونی
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } { $name } { $orientation }
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = تیز ویب دیکھیں:
pdfjs-document-properties-linearized-yes = ہاں
pdfjs-document-properties-linearized-no = نہیں
pdfjs-document-properties-close-button = بند کریں
## Print
pdfjs-print-progress-message = چھاپنے کرنے کے لیے دستاویز تیار کیے جا رھے ھیں
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = *{ $progress }%*
pdfjs-print-progress-close-button = منسوخ کریں
pdfjs-printing-not-supported = تنبیہ:چھاپنا اس براؤزر پر پوری طرح معاونت شدہ نہیں ہے۔
pdfjs-printing-not-ready = تنبیہ: PDF چھپائی کے لیے پوری طرح لوڈ نہیں ہوئی۔
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = سلائیڈ ٹوگل کریں
pdfjs-toggle-sidebar-button-label = سلائیڈ ٹوگل کریں
pdfjs-document-outline-button =
.title = دستاویز کی سرخیاں دکھایں (تمام اشیاء وسیع / غائب کرنے کے لیے ڈبل کلک کریں)
pdfjs-document-outline-button-label = دستاویز آؤٹ لائن
pdfjs-attachments-button =
.title = منسلکات دکھائیں
pdfjs-attachments-button-label = منسلکات
pdfjs-thumbs-button =
.title = تھمبنیل دکھائیں
pdfjs-thumbs-button-label = مجمل
pdfjs-findbar-button =
.title = دستاویز میں ڈھونڈیں
pdfjs-findbar-button-label = ڈھونڈیں
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = صفحہ { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = صفحے کا مجمل { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = ڈھونڈیں
.placeholder = دستاویز… میں ڈھونڈیں
pdfjs-find-previous-button =
.title = فقرے کا پچھلا وقوع ڈھونڈیں
pdfjs-find-previous-button-label = پچھلا
pdfjs-find-next-button =
.title = فقرے کا اگلہ وقوع ڈھونڈیں
pdfjs-find-next-button-label = آگے
pdfjs-find-highlight-checkbox = تمام نمایاں کریں
pdfjs-find-match-case-checkbox-label = حروف مشابہ کریں
pdfjs-find-entire-word-checkbox-label = تمام الفاظ
pdfjs-find-reached-top = صفحہ کے شروع پر پہنچ گیا، نیچے سے جاری کیا
pdfjs-find-reached-bottom = صفحہ کے اختتام پر پہنچ گیا، اوپر سے جاری کیا
pdfjs-find-not-found = فقرا نہیں ملا
## Predefined zoom values
pdfjs-page-scale-width = صفحہ چوڑائی
pdfjs-page-scale-fit = صفحہ فٹنگ
pdfjs-page-scale-auto = خودکار زوم
pdfjs-page-scale-actual = اصل سائز
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = صفحہ { $page }
## Loading indicator messages
pdfjs-loading-error = PDF لوڈ کرتے وقت نقص آ گیا۔
pdfjs-invalid-file-error = ناجائز یا خراب PDF مسل
pdfjs-missing-file-error = PDF مسل غائب ہے۔
pdfjs-unexpected-response-error = غیرمتوقع پیش کار جواب
pdfjs-rendering-error = صفحہ بناتے ہوئے نقص آ گیا۔
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }.{ $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } نوٹ]
## Password
pdfjs-password-label = PDF مسل کھولنے کے لیے پاس ورڈ داخل کریں.
pdfjs-password-invalid = ناجائز پاس ورڈ. براےؑ کرم دوبارہ کوشش کریں.
pdfjs-password-ok-button = ٹھیک ہے
pdfjs-password-cancel-button = منسوخ کریں
pdfjs-web-fonts-disabled = ویب فانٹ نا اہل ہیں: شامل PDF فانٹ استعمال کرنے میں ناکام۔
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/uz/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Oldingi sahifa
pdfjs-previous-button-label = Oldingi
pdfjs-next-button =
.title = Keyingi sahifa
pdfjs-next-button-label = Keyingi
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = /{ $pagesCount }
pdfjs-zoom-out-button =
.title = Kichiklashtirish
pdfjs-zoom-out-button-label = Kichiklashtirish
pdfjs-zoom-in-button =
.title = Kattalashtirish
pdfjs-zoom-in-button-label = Kattalashtirish
pdfjs-zoom-select =
.title = Masshtab
pdfjs-presentation-mode-button =
.title = Namoyish usuliga oʻtish
pdfjs-presentation-mode-button-label = Namoyish usuli
pdfjs-open-file-button =
.title = Faylni ochish
pdfjs-open-file-button-label = Ochish
pdfjs-print-button =
.title = Chop qilish
pdfjs-print-button-label = Chop qilish
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Vositalar
pdfjs-tools-button-label = Vositalar
pdfjs-first-page-button =
.title = Birinchi sahifaga oʻtish
pdfjs-first-page-button-label = Birinchi sahifaga oʻtish
pdfjs-last-page-button =
.title = Soʻnggi sahifaga oʻtish
pdfjs-last-page-button-label = Soʻnggi sahifaga oʻtish
pdfjs-page-rotate-cw-button =
.title = Soat yoʻnalishi boʻyicha burish
pdfjs-page-rotate-cw-button-label = Soat yoʻnalishi boʻyicha burish
pdfjs-page-rotate-ccw-button =
.title = Soat yoʻnalishiga qarshi burish
pdfjs-page-rotate-ccw-button-label = Soat yoʻnalishiga qarshi burish
## Document properties dialog
pdfjs-document-properties-button =
.title = Hujjat xossalari
pdfjs-document-properties-button-label = Hujjat xossalari
pdfjs-document-properties-file-name = Fayl nomi:
pdfjs-document-properties-file-size = Fayl hajmi:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)
pdfjs-document-properties-title = Nomi:
pdfjs-document-properties-author = Muallifi:
pdfjs-document-properties-subject = Mavzusi:
pdfjs-document-properties-keywords = Kalit so‘zlar
pdfjs-document-properties-creation-date = Yaratilgan sanasi:
pdfjs-document-properties-modification-date = O‘zgartirilgan sanasi
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Yaratuvchi:
pdfjs-document-properties-producer = PDF ishlab chiqaruvchi:
pdfjs-document-properties-version = PDF versiyasi:
pdfjs-document-properties-page-count = Sahifa soni:
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
##
pdfjs-document-properties-close-button = Yopish
## Print
pdfjs-printing-not-supported = Diqqat: chop qilish bruzer tomonidan toʻliq qoʻllab-quvvatlanmaydi.
pdfjs-printing-not-ready = Diqqat: PDF fayl chop qilish uchun toʻliq yuklanmadi.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Yon panelni yoqib/oʻchirib qoʻyish
pdfjs-toggle-sidebar-button-label = Yon panelni yoqib/oʻchirib qoʻyish
pdfjs-document-outline-button-label = Hujjat tuzilishi
pdfjs-attachments-button =
.title = Ilovalarni ko‘rsatish
pdfjs-attachments-button-label = Ilovalar
pdfjs-thumbs-button =
.title = Nishonchalarni koʻrsatish
pdfjs-thumbs-button-label = Nishoncha
pdfjs-findbar-button =
.title = Hujjat ichidan topish
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = { $page } sahifa
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = { $page } sahifa nishonchasi
## Find panel button title and messages
pdfjs-find-previous-button =
.title = Soʻzlardagi oldingi hodisani topish
pdfjs-find-previous-button-label = Oldingi
pdfjs-find-next-button =
.title = Iboradagi keyingi hodisani topish
pdfjs-find-next-button-label = Keyingi
pdfjs-find-highlight-checkbox = Barchasini ajratib koʻrsatish
pdfjs-find-match-case-checkbox-label = Katta-kichik harflarni farqlash
pdfjs-find-reached-top = Hujjatning boshigacha yetib keldik, pastdan davom ettiriladi
pdfjs-find-reached-bottom = Hujjatning oxiriga yetib kelindi, yuqoridan davom ettirladi
pdfjs-find-not-found = Soʻzlar topilmadi
## Predefined zoom values
pdfjs-page-scale-width = Sahifa eni
pdfjs-page-scale-fit = Sahifani moslashtirish
pdfjs-page-scale-auto = Avtomatik masshtab
pdfjs-page-scale-actual = Haqiqiy hajmi
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
## Loading indicator messages
pdfjs-loading-error = PDF yuklanayotganda xato yuz berdi.
pdfjs-invalid-file-error = Xato yoki buzuq PDF fayli.
pdfjs-missing-file-error = PDF fayl kerak.
pdfjs-unexpected-response-error = Kutilmagan server javobi.
pdfjs-rendering-error = Sahifa renderlanayotganda xato yuz berdi.
## Annotations
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } Annotation]
## Password
pdfjs-password-label = PDF faylni ochish uchun parolni kiriting.
pdfjs-password-invalid = Parol - notoʻgʻri. Qaytadan urinib koʻring.
pdfjs-password-ok-button = OK
pdfjs-web-fonts-disabled = Veb shriftlar oʻchirilgan: ichki PDF shriftlardan foydalanib boʻlmmaydi.
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/vi/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Trang trước
pdfjs-previous-button-label = Trước
pdfjs-next-button =
.title = Trang Sau
pdfjs-next-button-label = Tiếp
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Trang
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = trên { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } trên { $pagesCount })
pdfjs-zoom-out-button =
.title = Thu nhỏ
pdfjs-zoom-out-button-label = Thu nhỏ
pdfjs-zoom-in-button =
.title = Phóng to
pdfjs-zoom-in-button-label = Phóng to
pdfjs-zoom-select =
.title = Thu phóng
pdfjs-presentation-mode-button =
.title = Chuyển sang chế độ trình chiếu
pdfjs-presentation-mode-button-label = Chế độ trình chiếu
pdfjs-open-file-button =
.title = Mở tập tin
pdfjs-open-file-button-label = Mở tập tin
pdfjs-print-button =
.title = In
pdfjs-print-button-label = In
pdfjs-save-button =
.title = Lưu
pdfjs-save-button-label = Lưu
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = Tải xuống
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = Tải xuống
pdfjs-bookmark-button =
.title = Trang hiện tại (xem URL từ trang hiện tại)
pdfjs-bookmark-button-label = Trang hiện tại
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Công cụ
pdfjs-tools-button-label = Công cụ
pdfjs-first-page-button =
.title = Về trang đầu
pdfjs-first-page-button-label = Về trang đầu
pdfjs-last-page-button =
.title = Đến trang cuối
pdfjs-last-page-button-label = Đến trang cuối
pdfjs-page-rotate-cw-button =
.title = Xoay theo chiều kim đồng hồ
pdfjs-page-rotate-cw-button-label = Xoay theo chiều kim đồng hồ
pdfjs-page-rotate-ccw-button =
.title = Xoay ngược chiều kim đồng hồ
pdfjs-page-rotate-ccw-button-label = Xoay ngược chiều kim đồng hồ
pdfjs-cursor-text-select-tool-button =
.title = Kích hoạt công cụ chọn vùng văn bản
pdfjs-cursor-text-select-tool-button-label = Công cụ chọn vùng văn bản
pdfjs-cursor-hand-tool-button =
.title = Kích hoạt công cụ con trỏ
pdfjs-cursor-hand-tool-button-label = Công cụ con trỏ
pdfjs-scroll-page-button =
.title = Sử dụng cuộn trang hiện tại
pdfjs-scroll-page-button-label = Cuộn trang hiện tại
pdfjs-scroll-vertical-button =
.title = Sử dụng cuộn dọc
pdfjs-scroll-vertical-button-label = Cuộn dọc
pdfjs-scroll-horizontal-button =
.title = Sử dụng cuộn ngang
pdfjs-scroll-horizontal-button-label = Cuộn ngang
pdfjs-scroll-wrapped-button =
.title = Sử dụng cuộn ngắt dòng
pdfjs-scroll-wrapped-button-label = Cuộn ngắt dòng
pdfjs-spread-none-button =
.title = Không nối rộng trang
pdfjs-spread-none-button-label = Không có phân cách
pdfjs-spread-odd-button =
.title = Nối trang bài bắt đầu với các trang được đánh số lẻ
pdfjs-spread-odd-button-label = Phân cách theo số lẻ
pdfjs-spread-even-button =
.title = Nối trang bài bắt đầu với các trang được đánh số chẵn
pdfjs-spread-even-button-label = Phân cách theo số chẵn
## Document properties dialog
pdfjs-document-properties-button =
.title = Thuộc tính của tài liệu…
pdfjs-document-properties-button-label = Thuộc tính của tài liệu…
pdfjs-document-properties-file-name = Tên tập tin:
pdfjs-document-properties-file-size = Kích thước:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } byte)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } byte)
pdfjs-document-properties-title = Tiêu đề:
pdfjs-document-properties-author = Tác giả:
pdfjs-document-properties-subject = Chủ đề:
pdfjs-document-properties-keywords = Từ khóa:
pdfjs-document-properties-creation-date = Ngày tạo:
pdfjs-document-properties-modification-date = Ngày sửa đổi:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Người tạo:
pdfjs-document-properties-producer = Phần mềm tạo PDF:
pdfjs-document-properties-version = Phiên bản PDF:
pdfjs-document-properties-page-count = Tổng số trang:
pdfjs-document-properties-page-size = Kích thước trang:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = khổ dọc
pdfjs-document-properties-page-size-orientation-landscape = khổ ngang
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Thư
pdfjs-document-properties-page-size-name-legal = Pháp lý
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = Xem nhanh trên web:
pdfjs-document-properties-linearized-yes = Có
pdfjs-document-properties-linearized-no = Không
pdfjs-document-properties-close-button = Ðóng
## Print
pdfjs-print-progress-message = Chuẩn bị trang để in…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Hủy bỏ
pdfjs-printing-not-supported = Cảnh báo: In ấn không được hỗ trợ đầy đủ ở trình duyệt này.
pdfjs-printing-not-ready = Cảnh báo: PDF chưa được tải hết để in.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Bật/Tắt thanh lề
pdfjs-toggle-sidebar-notification-button =
.title = Bật tắt thanh lề (tài liệu bao gồm bản phác thảo/tập tin đính kèm/lớp)
pdfjs-toggle-sidebar-button-label = Bật/Tắt thanh lề
pdfjs-document-outline-button =
.title = Hiển thị tài liệu phác thảo (nhấp đúp vào để mở rộng/thu gọn tất cả các mục)
pdfjs-document-outline-button-label = Bản phác tài liệu
pdfjs-attachments-button =
.title = Hiện nội dung đính kèm
pdfjs-attachments-button-label = Nội dung đính kèm
pdfjs-layers-button =
.title = Hiển thị các lớp (nhấp đúp để đặt lại tất cả các lớp về trạng thái mặc định)
pdfjs-layers-button-label = Lớp
pdfjs-thumbs-button =
.title = Hiển thị ảnh thu nhỏ
pdfjs-thumbs-button-label = Ảnh thu nhỏ
pdfjs-current-outline-item-button =
.title = Tìm mục phác thảo hiện tại
pdfjs-current-outline-item-button-label = Mục phác thảo hiện tại
pdfjs-findbar-button =
.title = Tìm trong tài liệu
pdfjs-findbar-button-label = Tìm
pdfjs-additional-layers = Các lớp bổ sung
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Trang { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Ảnh thu nhỏ của trang { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Tìm
.placeholder = Tìm trong tài liệu…
pdfjs-find-previous-button =
.title = Tìm cụm từ ở phần trước
pdfjs-find-previous-button-label = Trước
pdfjs-find-next-button =
.title = Tìm cụm từ ở phần sau
pdfjs-find-next-button-label = Tiếp
pdfjs-find-highlight-checkbox = Đánh dấu tất cả
pdfjs-find-match-case-checkbox-label = Phân biệt hoa, thường
pdfjs-find-match-diacritics-checkbox-label = Khớp dấu phụ
pdfjs-find-entire-word-checkbox-label = Toàn bộ từ
pdfjs-find-reached-top = Đã đến phần đầu tài liệu, quay trở lại từ cuối
pdfjs-find-reached-bottom = Đã đến phần cuối của tài liệu, quay trở lại từ đầu
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count = { $current } trên { $total } kết quả
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit = Tìm thấy hơn { $limit } kết quả
pdfjs-find-not-found = Không tìm thấy cụm từ này
## Predefined zoom values
pdfjs-page-scale-width = Vừa chiều rộng
pdfjs-page-scale-fit = Vừa chiều cao
pdfjs-page-scale-auto = Tự động chọn kích thước
pdfjs-page-scale-actual = Kích thước thực
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = Trang { $page }
## Loading indicator messages
pdfjs-loading-error = Lỗi khi tải tài liệu PDF.
pdfjs-invalid-file-error = Tập tin PDF hỏng hoặc không hợp lệ.
pdfjs-missing-file-error = Thiếu tập tin PDF.
pdfjs-unexpected-response-error = Máy chủ có phản hồi lạ.
pdfjs-rendering-error = Lỗi khi hiển thị trang.
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date }, { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } Chú thích]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = Nhập mật khẩu để mở tập tin PDF này.
pdfjs-password-invalid = Mật khẩu không đúng. Vui lòng thử lại.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Hủy bỏ
pdfjs-web-fonts-disabled = Phông chữ Web bị vô hiệu hóa: không thể sử dụng các phông chữ PDF được nhúng.
## Editing
pdfjs-editor-free-text-button =
.title = Văn bản
pdfjs-editor-free-text-button-label = Văn bản
pdfjs-editor-ink-button =
.title = Vẽ
pdfjs-editor-ink-button-label = Vẽ
pdfjs-editor-stamp-button =
.title = Thêm hoặc chỉnh sửa hình ảnh
pdfjs-editor-stamp-button-label = Thêm hoặc chỉnh sửa hình ảnh
pdfjs-editor-highlight-button =
.title = Đánh dấu
pdfjs-editor-highlight-button-label = Đánh dấu
pdfjs-highlight-floating-button1 =
.title = Đánh dấu
.aria-label = Đánh dấu
pdfjs-highlight-floating-button-label = Đánh dấu
pdfjs-editor-signature-button =
.title = Thêm chữ ký
pdfjs-editor-signature-button-label = Thêm chữ ký
## Default editor aria labels
# “Highlight” is a noun, the string is used on the editor for highlights.
pdfjs-editor-highlight-editor =
.aria-label = Trình chỉnh sửa đánh dấu
# “Drawing” is a noun, the string is used on the editor for drawings.
pdfjs-editor-ink-editor =
.aria-label = Trình chỉnh sửa bản vẽ
pdfjs-editor-signature-editor =
.aria-label = Trình chỉnh sửa chữ ký
pdfjs-editor-stamp-editor =
.aria-label = Trình chỉnh sửa hình ảnh
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = Xóa bản vẽ
pdfjs-editor-remove-freetext-button =
.title = Xóa văn bản
pdfjs-editor-remove-stamp-button =
.title = Xóa ảnh
pdfjs-editor-remove-highlight-button =
.title = Xóa phần đánh dấu
pdfjs-editor-remove-signature-button =
.title = Xoá chữ ký
##
# Editor Parameters
pdfjs-editor-free-text-color-input = Màu
pdfjs-editor-free-text-size-input = Kích cỡ
pdfjs-editor-ink-color-input = Màu
pdfjs-editor-ink-thickness-input = Độ dày
pdfjs-editor-ink-opacity-input = Độ mờ
pdfjs-editor-stamp-add-image-button =
.title = Thêm hình ảnh
pdfjs-editor-stamp-add-image-button-label = Thêm hình ảnh
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = Độ dày
pdfjs-editor-free-highlight-thickness-title =
.title = Thay đổi độ dày khi đánh dấu các mục không phải là văn bản
pdfjs-editor-add-signature-container =
.aria-label = Kiểm soát chữ ký và chữ ký đã lưu
pdfjs-editor-signature-add-signature-button =
.title = Thêm chữ ký mới
pdfjs-editor-signature-add-signature-button-label = Thêm chữ ký mới
# Used on the button to use an already saved signature.
# Variables:
# $description (String) - a string describing/labeling the signature.
pdfjs-editor-add-saved-signature-button =
.title = Đã lưu chữ ký: { $description }
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = Trình chỉnh sửa văn bản
.default-content = Bắt đầu nhập…
pdfjs-free-text =
.aria-label = Trình sửa văn bản
pdfjs-free-text-default-content = Bắt đầu nhập…
pdfjs-ink =
.aria-label = Trình sửa nét vẽ
pdfjs-ink-canvas =
.aria-label = Hình ảnh do người dùng tạo
## Alt-text dialog
pdfjs-editor-alt-text-button-label = Văn bản thay thế
pdfjs-editor-alt-text-edit-button =
.aria-label = Chỉnh sửa văn bản thay thế
pdfjs-editor-alt-text-edit-button-label = Chỉnh sửa văn bản thay thế
pdfjs-editor-alt-text-dialog-label = Chọn một lựa chọn
pdfjs-editor-alt-text-dialog-description = Văn bản thay thế sẽ hữu ích khi mọi người không thể thấy hình ảnh hoặc khi hình ảnh không tải.
pdfjs-editor-alt-text-add-description-label = Thêm một mô tả
pdfjs-editor-alt-text-add-description-description = Hãy nhắm tới 1-2 câu mô tả chủ đề, bối cảnh hoặc hành động.
pdfjs-editor-alt-text-mark-decorative-label = Đánh dấu là trang trí
pdfjs-editor-alt-text-mark-decorative-description = Điều này được sử dụng cho các hình ảnh trang trí, như đường viền hoặc watermark.
pdfjs-editor-alt-text-cancel-button = Hủy bỏ
pdfjs-editor-alt-text-save-button = Lưu
pdfjs-editor-alt-text-decorative-tooltip = Đã đánh dấu là trang trí
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = Ví dụ: “Một thanh niên ngồi xuống bàn để thưởng thức một bữa ăn”
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = Văn bản thay thế
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = Trên cùng bên trái — thay đổi kích thước
pdfjs-editor-resizer-label-top-middle = Trên cùng ở giữa — thay đổi kích thước
pdfjs-editor-resizer-label-top-right = Trên cùng bên phải — thay đổi kích thước
pdfjs-editor-resizer-label-middle-right = Ở giữa bên phải — thay đổi kích thước
pdfjs-editor-resizer-label-bottom-right = Dưới cùng bên phải — thay đổi kích thước
pdfjs-editor-resizer-label-bottom-middle = Ở giữa dưới cùng — thay đổi kích thước
pdfjs-editor-resizer-label-bottom-left = Góc dưới bên trái — thay đổi kích thước
pdfjs-editor-resizer-label-middle-left = Ở giữa bên trái — thay đổi kích thước
pdfjs-editor-resizer-top-left =
.aria-label = Trên cùng bên trái — thay đổi kích thước
pdfjs-editor-resizer-top-middle =
.aria-label = Trên cùng ở giữa — thay đổi kích thước
pdfjs-editor-resizer-top-right =
.aria-label = Trên cùng bên phải — thay đổi kích thước
pdfjs-editor-resizer-middle-right =
.aria-label = Ở giữa bên phải — thay đổi kích thước
pdfjs-editor-resizer-bottom-right =
.aria-label = Dưới cùng bên phải — thay đổi kích thước
pdfjs-editor-resizer-bottom-middle =
.aria-label = Ở giữa dưới cùng — thay đổi kích thước
pdfjs-editor-resizer-bottom-left =
.aria-label = Góc dưới bên trái — thay đổi kích thước
pdfjs-editor-resizer-middle-left =
.aria-label = Ở giữa bên trái — thay đổi kích thước
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = Màu đánh dấu
pdfjs-editor-colorpicker-button =
.title = Thay đổi màu
pdfjs-editor-colorpicker-dropdown =
.aria-label = Lựa chọn màu sắc
pdfjs-editor-colorpicker-yellow =
.title = Vàng
pdfjs-editor-colorpicker-green =
.title = Xanh lục
pdfjs-editor-colorpicker-blue =
.title = Xanh dương
pdfjs-editor-colorpicker-pink =
.title = Hồng
pdfjs-editor-colorpicker-red =
.title = Đỏ
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = Hiện tất cả
pdfjs-editor-highlight-show-all-button =
.title = Hiện tất cả
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = Chỉnh sửa văn bản thay thế (mô tả hình ảnh)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = Thêm văn bản thay thế (mô tả hình ảnh)
pdfjs-editor-new-alt-text-textarea =
.placeholder = Viết mô tả của bạn ở đây…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = Mô tả ngắn gọn dành cho người không xem được ảnh hoặc khi không thể tải ảnh.
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = Văn bản thay thế này được tạo tự động và có thể không chính xác.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Tìm hiểu thêm
pdfjs-editor-new-alt-text-create-automatically-button-label = Tạo văn bản thay thế tự động
pdfjs-editor-new-alt-text-not-now-button = Không phải bây giờ
pdfjs-editor-new-alt-text-error-title = Không thể tạo tự động văn bản thay thế
pdfjs-editor-new-alt-text-error-description = Vui lòng viết văn bản thay thế của riêng bạn hoặc thử lại sau.
pdfjs-editor-new-alt-text-error-close-button = Đóng
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Đang tải xuống mô hình AI văn bản thay thế ({ $downloadedSize } / { $totalSize } MB)
.aria-valuetext = Đang tải xuống mô hình AI văn bản thay thế ({ $downloadedSize } / { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = Đã thêm văn bản thay thế
pdfjs-editor-new-alt-text-added-button-label = Đã thêm văn bản thay thế
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = Thiếu văn bản thay thế
pdfjs-editor-new-alt-text-missing-button-label = Thiếu văn bản thay thế
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = Xem lại văn bản thay thế
pdfjs-editor-new-alt-text-to-review-button-label = Xem lại văn bản thay thế
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Được tạo tự động: { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = Cài đặt văn bản thay thế của hình ảnh
pdfjs-image-alt-text-settings-button-label = Cài đặt văn bản thay thế của hình ảnh
pdfjs-editor-alt-text-settings-dialog-label = Cài đặt văn bản thay thế của hình ảnh
pdfjs-editor-alt-text-settings-automatic-title = Văn bản thay thế tự động
pdfjs-editor-alt-text-settings-create-model-button-label = Tạo văn bản thay thế tự động
pdfjs-editor-alt-text-settings-create-model-description = Đề xuất mô tả giúp ích cho những người không xem được ảnh hoặc khi không thể tải ảnh.
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = Mô hình AI văn bản khác ({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = Chạy cục bộ trên thiết bị của bạn để dữ liệu của bạn luôn ở chế độ riêng tư. Bắt buộc đối với văn bản thay thế tự động.
pdfjs-editor-alt-text-settings-delete-model-button = Xóa
pdfjs-editor-alt-text-settings-download-model-button = Tải xuống
pdfjs-editor-alt-text-settings-downloading-model-button = Đang tải xuống…
pdfjs-editor-alt-text-settings-editor-title = Trình soạn thảo văn bản thay thế
pdfjs-editor-alt-text-settings-show-dialog-button-label = Hiển thị ngay trình soạn thảo văn bản thay thế khi thêm hình ảnh
pdfjs-editor-alt-text-settings-show-dialog-description = Giúp bạn đảm bảo tất cả hình ảnh của bạn đều có văn bản thay thế.
pdfjs-editor-alt-text-settings-close-button = Đóng
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Đã xóa đánh dấu
pdfjs-editor-undo-bar-message-freetext = Đã xóa văn bản
pdfjs-editor-undo-bar-message-ink = Đã xóa bản vẽ
pdfjs-editor-undo-bar-message-stamp = Đã xóa hình ảnh
pdfjs-editor-undo-bar-message-signature = Chữ ký đã bị xoá
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple = { $count } chú thích đã bị xóa
pdfjs-editor-undo-bar-undo-button =
.title = Hoàn tác
pdfjs-editor-undo-bar-undo-button-label = Hoàn tác
pdfjs-editor-undo-bar-close-button =
.title = Đóng
pdfjs-editor-undo-bar-close-button-label = Đóng
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = Phương thức này cho phép người dùng tạo một chữ ký để thêm vào tài liệu PDF. Người dùng có thể chỉnh sửa tên (cũng đóng vai trò là văn bản thay thế) và tùy chọn lưu chữ ký để sử dụng nhiều lần.
pdfjs-editor-add-signature-dialog-title = Thêm chữ ký
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = Đánh văn bản
.title = Đánh văn bản
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = Vẽ
.title = Vẽ
pdfjs-editor-add-signature-image-button = Hình ảnh
.title = Hình ảnh
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = Nhập chữ ký của bạn
.placeholder = Nhập chữ ký của bạn
pdfjs-editor-add-signature-draw-placeholder = Vẽ chữ ký của bạn
pdfjs-editor-add-signature-draw-thickness-range-label = Độ dày
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = Độ dày bút vẽ: { $thickness }
pdfjs-editor-add-signature-image-placeholder = Kéo một tập tin tại đây để tải lên
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] Hoặc chọn hình ảnh
*[other] Hoặc chọn hình ảnh
}
## Controls
pdfjs-editor-add-signature-description-label = Mô tả (văn bản thay thế)
pdfjs-editor-add-signature-description-input =
.title = Mô tả (văn bản thay thế)
pdfjs-editor-add-signature-description-default-when-drawing = Chữ ký
pdfjs-editor-add-signature-clear-button-label = Xoá chữ ký
pdfjs-editor-add-signature-clear-button =
.title = Xoá chữ ký
pdfjs-editor-add-signature-save-checkbox = Lưu chữ ký
pdfjs-editor-add-signature-save-warning-message = Bạn đã đạt đến giới hạn 5 chữ ký đã lưu. Hãy xóa một cái để lưu thêm.
pdfjs-editor-add-signature-image-upload-error-title = Không thể tải lên hình ảnh
pdfjs-editor-add-signature-image-upload-error-description = Kiểm tra kết nối mạng của bạn hoặc thử hình ảnh khác.
pdfjs-editor-add-signature-error-close-button = Đóng
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = Hủy bỏ
pdfjs-editor-add-signature-add-button = Thêm
pdfjs-editor-edit-signature-update-button = Cập nhật
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = Xoá chữ ký
pdfjs-editor-delete-signature-button-label = Xoá chữ ký
pdfjs-editor-delete-signature-button1 =
.title = Xoá chữ ký đã lưu
pdfjs-editor-delete-signature-button-label1 = Xoá chữ ký đã lưu
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = Chỉnh sửa mô tả
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = Chỉnh sửa mô tả
================================================
FILE: cookbook/static/pdfjs/web/locale/wo/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Xët wi jiitu
pdfjs-previous-button-label = Bi jiitu
pdfjs-next-button =
.title = Xët wi ci topp
pdfjs-next-button-label = Bi ci topp
pdfjs-zoom-out-button =
.title = Wàññi
pdfjs-zoom-out-button-label = Wàññi
pdfjs-zoom-in-button =
.title = Yaatal
pdfjs-zoom-in-button-label = Yaatal
pdfjs-zoom-select =
.title = Yambalaŋ
pdfjs-presentation-mode-button =
.title = Wañarñil ci anamu wone
pdfjs-presentation-mode-button-label = Anamu Wone
pdfjs-open-file-button =
.title = Ubbi benn dencukaay
pdfjs-open-file-button-label = Ubbi
pdfjs-print-button =
.title = Móol
pdfjs-print-button-label = Móol
## Secondary toolbar and context menu
## Document properties dialog
pdfjs-document-properties-title = Bopp:
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
##
## Print
pdfjs-printing-not-supported = Artu: Joowkat bii nanguwul lool mool.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-thumbs-button =
.title = Wone nataal yu ndaw yi
pdfjs-thumbs-button-label = Nataal yu ndaw yi
pdfjs-findbar-button =
.title = Gis ci biir jukki bi
pdfjs-findbar-button-label = Wut
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Xët { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Wiñet bu xët { $page }
## Find panel button title and messages
pdfjs-find-previous-button =
.title = Seet beneen kaddu bu ni mel te jiitu
pdfjs-find-previous-button-label = Bi jiitu
pdfjs-find-next-button =
.title = Seet beneen kaddu bu ni mel
pdfjs-find-next-button-label = Bi ci topp
pdfjs-find-highlight-checkbox = Melaxal lépp
pdfjs-find-match-case-checkbox-label = Sàmm jëmmalin wi
pdfjs-find-reached-top = Jot nañu ndorteel xët wi, kontine dale ko ci suuf
pdfjs-find-reached-bottom = Jot nañu jeexitalu xët wi, kontine ci ndorte
pdfjs-find-not-found = Gisiñu kaddu gi
## Predefined zoom values
pdfjs-page-scale-width = Yaatuwaay bu mët
pdfjs-page-scale-fit = Xët lëmm
pdfjs-page-scale-auto = Yambalaŋ ci saa si
pdfjs-page-scale-actual = Dayo bi am
## PDF page
## Loading indicator messages
pdfjs-loading-error = Am na njumte ci yebum dencukaay PDF bi.
pdfjs-invalid-file-error = Dencukaay PDF bi baaxul walla mu sankar.
pdfjs-rendering-error = Am njumte bu am bi xët bi di wonewu.
## Annotations
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [Karmat { $type }]
## Password
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Neenal
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/xh/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = Iphepha langaphambili
pdfjs-previous-button-label = Okwangaphambili
pdfjs-next-button =
.title = Iphepha elilandelayo
pdfjs-next-button-label = Okulandelayo
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = Iphepha
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = kwali- { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } kwali { $pagesCount })
pdfjs-zoom-out-button =
.title = Bhekelisela Kudana
pdfjs-zoom-out-button-label = Bhekelisela Kudana
pdfjs-zoom-in-button =
.title = Sondeza Kufuphi
pdfjs-zoom-in-button-label = Sondeza Kufuphi
pdfjs-zoom-select =
.title = Yandisa / Nciphisa
pdfjs-presentation-mode-button =
.title = Tshintshela kwimo yonikezelo
pdfjs-presentation-mode-button-label = Imo yonikezelo
pdfjs-open-file-button =
.title = Vula Ifayile
pdfjs-open-file-button-label = Vula
pdfjs-print-button =
.title = Printa
pdfjs-print-button-label = Printa
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = Izixhobo zemiyalelo
pdfjs-tools-button-label = Izixhobo zemiyalelo
pdfjs-first-page-button =
.title = Yiya kwiphepha lokuqala
pdfjs-first-page-button-label = Yiya kwiphepha lokuqala
pdfjs-last-page-button =
.title = Yiya kwiphepha lokugqibela
pdfjs-last-page-button-label = Yiya kwiphepha lokugqibela
pdfjs-page-rotate-cw-button =
.title = Jikelisa ngasekunene
pdfjs-page-rotate-cw-button-label = Jikelisa ngasekunene
pdfjs-page-rotate-ccw-button =
.title = Jikelisa ngasekhohlo
pdfjs-page-rotate-ccw-button-label = Jikelisa ngasekhohlo
pdfjs-cursor-text-select-tool-button =
.title = Vumela iSixhobo sokuKhetha iTeksti
pdfjs-cursor-text-select-tool-button-label = ISixhobo sokuKhetha iTeksti
pdfjs-cursor-hand-tool-button =
.title = Yenza iSixhobo seSandla siSebenze
pdfjs-cursor-hand-tool-button-label = ISixhobo seSandla
## Document properties dialog
pdfjs-document-properties-button =
.title = Iipropati zoxwebhu…
pdfjs-document-properties-button-label = Iipropati zoxwebhu…
pdfjs-document-properties-file-name = Igama lefayile:
pdfjs-document-properties-file-size = Isayizi yefayile:
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB (iibhayiti{ $size_b })
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB (iibhayithi{ $size_b })
pdfjs-document-properties-title = Umxholo:
pdfjs-document-properties-author = Umbhali:
pdfjs-document-properties-subject = Umbandela:
pdfjs-document-properties-keywords = Amagama aphambili:
pdfjs-document-properties-creation-date = Umhla wokwenziwa kwayo:
pdfjs-document-properties-modification-date = Umhla wokulungiswa kwayo:
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = Umntu oyenzileyo:
pdfjs-document-properties-producer = Umvelisi we-PDF:
pdfjs-document-properties-version = Uhlelo lwe-PDF:
pdfjs-document-properties-page-count = Inani lamaphepha:
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
##
pdfjs-document-properties-close-button = Vala
## Print
pdfjs-print-progress-message = Ilungisa uxwebhu ukuze iprinte…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = Rhoxisa
pdfjs-printing-not-supported = Isilumkiso: Ukuprinta akuxhaswa ngokupheleleyo yile bhrawuza.
pdfjs-printing-not-ready = Isilumkiso: IPDF ayihlohlwanga ngokupheleleyo ukwenzela ukuprinta.
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = Togola ngebha eseCaleni
pdfjs-toggle-sidebar-button-label = Togola ngebha eseCaleni
pdfjs-document-outline-button =
.title = Bonisa uLwandlalo loXwebhu (cofa kabini ukuze wandise/diliza zonke izinto)
pdfjs-document-outline-button-label = Isishwankathelo soxwebhu
pdfjs-attachments-button =
.title = Bonisa iziqhotyoshelwa
pdfjs-attachments-button-label = Iziqhoboshelo
pdfjs-thumbs-button =
.title = Bonisa ukrobiso kumfanekiso
pdfjs-thumbs-button-label = Ukrobiso kumfanekiso
pdfjs-findbar-button =
.title = Fumana kuXwebhu
pdfjs-findbar-button-label = Fumana
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = Iphepha { $page }
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = Ukrobiso kumfanekiso wephepha { $page }
## Find panel button title and messages
pdfjs-find-input =
.title = Fumana
.placeholder = Fumana kuXwebhu…
pdfjs-find-previous-button =
.title = Fumanisa isenzeko sangaphambili sebinzana lamagama
pdfjs-find-previous-button-label = Okwangaphambili
pdfjs-find-next-button =
.title = Fumanisa isenzeko esilandelayo sebinzana lamagama
pdfjs-find-next-button-label = Okulandelayo
pdfjs-find-highlight-checkbox = Qaqambisa konke
pdfjs-find-match-case-checkbox-label = Tshatisa ngobukhulu bukanobumba
pdfjs-find-reached-top = Ufike ngaphezulu ephepheni, kusukwa ngezantsi
pdfjs-find-reached-bottom = Ufike ekupheleni kwephepha, kusukwa ngaphezulu
pdfjs-find-not-found = Ibinzana alifunyenwanga
## Predefined zoom values
pdfjs-page-scale-width = Ububanzi bephepha
pdfjs-page-scale-fit = Ukulinganiswa kwephepha
pdfjs-page-scale-auto = Ukwandisa/Ukunciphisa Ngokwayo
pdfjs-page-scale-actual = Ubungakanani bokwenene
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
## Loading indicator messages
pdfjs-loading-error = Imposiso yenzekile xa kulayishwa i-PDF.
pdfjs-invalid-file-error = Ifayile ye-PDF engeyiyo okanye eyonakalisiweyo.
pdfjs-missing-file-error = Ifayile ye-PDF edukileyo.
pdfjs-unexpected-response-error = Impendulo yeseva engalindelekanga.
pdfjs-rendering-error = Imposiso yenzekile xa bekunikezelwa iphepha.
## Annotations
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } Ubhalo-nqaku]
## Password
pdfjs-password-label = Faka ipasiwedi ukuze uvule le fayile yePDF.
pdfjs-password-invalid = Ipasiwedi ayisebenzi. Nceda uzame kwakhona.
pdfjs-password-ok-button = KULUNGILE
pdfjs-password-cancel-button = Rhoxisa
pdfjs-web-fonts-disabled = Iifonti zewebhu ziqhwalelisiwe: ayikwazi ukusebenzisa iifonti ze-PDF ezincanyathelisiweyo.
## Editing
## Default editor aria labels
## Remove button for the various kind of editor.
##
## Alt-text dialog
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
## Color picker
## Show all highlights
## This is a toggle button to show/hide all the highlights.
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
## Image alt-text settings
## "Annotations removed" bar
## Add a signature dialog
## Tab names
## Tab panels
## Controls
## Dialog buttons
## Main menu for adding/removing signatures
## Editor toolbar
## Edit signature description dialog
================================================
FILE: cookbook/static/pdfjs/web/locale/zh-CN/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = 上一页
pdfjs-previous-button-label = 上一页
pdfjs-next-button =
.title = 下一页
pdfjs-next-button-label = 下一页
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = 页面
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = / { $pagesCount }
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = ({ $pageNumber } / { $pagesCount })
pdfjs-zoom-out-button =
.title = 缩小
pdfjs-zoom-out-button-label = 缩小
pdfjs-zoom-in-button =
.title = 放大
pdfjs-zoom-in-button-label = 放大
pdfjs-zoom-select =
.title = 缩放
pdfjs-presentation-mode-button =
.title = 切换到演示模式
pdfjs-presentation-mode-button-label = 演示模式
pdfjs-open-file-button =
.title = 打开文件
pdfjs-open-file-button-label = 打开
pdfjs-print-button =
.title = 打印
pdfjs-print-button-label = 打印
pdfjs-save-button =
.title = 保存
pdfjs-save-button-label = 保存
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = 下载
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = 下载
pdfjs-bookmark-button =
.title = 当前页面(在当前页面查看 URL)
pdfjs-bookmark-button-label = 当前页面
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = 工具
pdfjs-tools-button-label = 工具
pdfjs-first-page-button =
.title = 转到第一页
pdfjs-first-page-button-label = 转到第一页
pdfjs-last-page-button =
.title = 转到最后一页
pdfjs-last-page-button-label = 转到最后一页
pdfjs-page-rotate-cw-button =
.title = 顺时针旋转
pdfjs-page-rotate-cw-button-label = 顺时针旋转
pdfjs-page-rotate-ccw-button =
.title = 逆时针旋转
pdfjs-page-rotate-ccw-button-label = 逆时针旋转
pdfjs-cursor-text-select-tool-button =
.title = 启用文本选择工具
pdfjs-cursor-text-select-tool-button-label = 文本选择工具
pdfjs-cursor-hand-tool-button =
.title = 启用手形工具
pdfjs-cursor-hand-tool-button-label = 手形工具
pdfjs-scroll-page-button =
.title = 使用页面滚动
pdfjs-scroll-page-button-label = 页面滚动
pdfjs-scroll-vertical-button =
.title = 使用垂直滚动
pdfjs-scroll-vertical-button-label = 垂直滚动
pdfjs-scroll-horizontal-button =
.title = 使用水平滚动
pdfjs-scroll-horizontal-button-label = 水平滚动
pdfjs-scroll-wrapped-button =
.title = 使用平铺滚动
pdfjs-scroll-wrapped-button-label = 平铺滚动
pdfjs-spread-none-button =
.title = 不加入衔接页
pdfjs-spread-none-button-label = 单页视图
pdfjs-spread-odd-button =
.title = 加入衔接页使奇数页作为起始页
pdfjs-spread-odd-button-label = 双页视图
pdfjs-spread-even-button =
.title = 加入衔接页使偶数页作为起始页
pdfjs-spread-even-button-label = 书籍视图
## Document properties dialog
pdfjs-document-properties-button =
.title = 文档属性…
pdfjs-document-properties-button-label = 文档属性…
pdfjs-document-properties-file-name = 文件名:
pdfjs-document-properties-file-size = 文件大小:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB({ $b } 字节)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB({ $b } 字节)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } 字节)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } 字节)
pdfjs-document-properties-title = 标题:
pdfjs-document-properties-author = 作者:
pdfjs-document-properties-subject = 主题:
pdfjs-document-properties-keywords = 关键词:
pdfjs-document-properties-creation-date = 创建日期:
pdfjs-document-properties-modification-date = 修改日期:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date }, { $time }
pdfjs-document-properties-creator = 创建者:
pdfjs-document-properties-producer = PDF 生成器:
pdfjs-document-properties-version = PDF 版本:
pdfjs-document-properties-page-count = 页数:
pdfjs-document-properties-page-size = 页面大小:
pdfjs-document-properties-page-size-unit-inches = 英寸
pdfjs-document-properties-page-size-unit-millimeters = 毫米
pdfjs-document-properties-page-size-orientation-portrait = 纵向
pdfjs-document-properties-page-size-orientation-landscape = 横向
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letter
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit }({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit }({ $name },{ $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = 快速 Web 视图:
pdfjs-document-properties-linearized-yes = 是
pdfjs-document-properties-linearized-no = 否
pdfjs-document-properties-close-button = 关闭
## Print
pdfjs-print-progress-message = 正在准备打印文档…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = 取消
pdfjs-printing-not-supported = 警告:此浏览器尚未完整支持打印功能。
pdfjs-printing-not-ready = 警告:此 PDF 未完成加载,无法打印。
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = 切换侧栏
pdfjs-toggle-sidebar-notification-button =
.title = 切换侧栏(文档所含的大纲/附件/图层)
pdfjs-toggle-sidebar-button-label = 切换侧栏
pdfjs-document-outline-button =
.title = 显示文档大纲(双击展开/折叠所有项)
pdfjs-document-outline-button-label = 文档大纲
pdfjs-attachments-button =
.title = 显示附件
pdfjs-attachments-button-label = 附件
pdfjs-layers-button =
.title = 显示图层(双击即可将所有图层重置为默认状态)
pdfjs-layers-button-label = 图层
pdfjs-thumbs-button =
.title = 显示缩略图
pdfjs-thumbs-button-label = 缩略图
pdfjs-current-outline-item-button =
.title = 查找当前大纲项目
pdfjs-current-outline-item-button-label = 当前大纲项目
pdfjs-findbar-button =
.title = 在文档中查找
pdfjs-findbar-button-label = 查找
pdfjs-additional-layers = 其他图层
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = 第 { $page } 页
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = 页面 { $page } 的缩略图
## Find panel button title and messages
pdfjs-find-input =
.title = 查找
.placeholder = 在文档中查找…
pdfjs-find-previous-button =
.title = 查找词语上一次出现的位置
pdfjs-find-previous-button-label = 上一页
pdfjs-find-next-button =
.title = 查找词语后一次出现的位置
pdfjs-find-next-button-label = 下一页
pdfjs-find-highlight-checkbox = 全部高亮显示
pdfjs-find-match-case-checkbox-label = 区分大小写
pdfjs-find-match-diacritics-checkbox-label = 匹配变音符号
pdfjs-find-entire-word-checkbox-label = 全词匹配
pdfjs-find-reached-top = 到达文档开头,从末尾继续
pdfjs-find-reached-bottom = 到达文档末尾,从开头继续
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count = 第 { $current } 项,共找到 { $total } 个匹配项
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit = 匹配超过 { $limit } 项
pdfjs-find-not-found = 找不到指定词语
## Predefined zoom values
pdfjs-page-scale-width = 适合页宽
pdfjs-page-scale-fit = 适合页面
pdfjs-page-scale-auto = 自动缩放
pdfjs-page-scale-actual = 实际大小
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = 第 { $page } 页
## Loading indicator messages
pdfjs-loading-error = 加载 PDF 时发生错误。
pdfjs-invalid-file-error = 无效或损坏的 PDF 文件。
pdfjs-missing-file-error = 缺少 PDF 文件。
pdfjs-unexpected-response-error = 意外的服务器响应。
pdfjs-rendering-error = 渲染页面时发生错误。
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date },{ $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } 注释]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = 输入密码以打开此 PDF 文件。
pdfjs-password-invalid = 密码无效。请重试。
pdfjs-password-ok-button = 确定
pdfjs-password-cancel-button = 取消
pdfjs-web-fonts-disabled = Web 字体已被禁用:无法使用嵌入的 PDF 字体。
## Editing
pdfjs-editor-free-text-button =
.title = 文本
pdfjs-editor-free-text-button-label = 文本
pdfjs-editor-ink-button =
.title = 绘图
pdfjs-editor-ink-button-label = 绘图
pdfjs-editor-stamp-button =
.title = 添加或编辑图像
pdfjs-editor-stamp-button-label = 添加或编辑图像
pdfjs-editor-highlight-button =
.title = 高亮
pdfjs-editor-highlight-button-label = 高亮
pdfjs-highlight-floating-button1 =
.title = 高亮
.aria-label = 高亮
pdfjs-highlight-floating-button-label = 高亮
pdfjs-editor-signature-button =
.title = 添加签名
pdfjs-editor-signature-button-label = 添加签名
## Default editor aria labels
# “Highlight” is a noun, the string is used on the editor for highlights.
pdfjs-editor-highlight-editor =
.aria-label = 高亮编辑器
# “Drawing” is a noun, the string is used on the editor for drawings.
pdfjs-editor-ink-editor =
.aria-label = 绘图编辑器
pdfjs-editor-signature-editor =
.aria-label = 签名编辑器
pdfjs-editor-stamp-editor =
.aria-label = 图像编辑器
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = 移除绘图
pdfjs-editor-remove-freetext-button =
.title = 移除文本
pdfjs-editor-remove-stamp-button =
.title = 移除图像
pdfjs-editor-remove-highlight-button =
.title = 移除高亮
pdfjs-editor-remove-signature-button =
.title = 移除签名
##
# Editor Parameters
pdfjs-editor-free-text-color-input = 颜色
pdfjs-editor-free-text-size-input = 字号
pdfjs-editor-ink-color-input = 颜色
pdfjs-editor-ink-thickness-input = 粗细
pdfjs-editor-ink-opacity-input = 不透明度
pdfjs-editor-stamp-add-image-button =
.title = 添加图像
pdfjs-editor-stamp-add-image-button-label = 添加图像
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = 粗细
pdfjs-editor-free-highlight-thickness-title =
.title = 更改高亮粗细(用于文本以外项目)
pdfjs-editor-add-signature-container =
.aria-label = 签名管理和保存的签名
pdfjs-editor-signature-add-signature-button =
.title = 添加新签名
pdfjs-editor-signature-add-signature-button-label = 添加新签名
# Used on the button to use an already saved signature.
# Variables:
# $description (String) - a string describing/labeling the signature.
pdfjs-editor-add-saved-signature-button =
.title = 保存的签名:{ $description }
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = 文本编辑器
.default-content = 在此键入…
pdfjs-free-text =
.aria-label = 文本编辑器
pdfjs-free-text-default-content = 开始输入…
pdfjs-ink =
.aria-label = 绘图编辑器
pdfjs-ink-canvas =
.aria-label = 用户创建图像
## Alt-text dialog
pdfjs-editor-alt-text-button-label = 替换文字
pdfjs-editor-alt-text-edit-button =
.aria-label = 编辑替换文字
pdfjs-editor-alt-text-edit-button-label = 编辑替换文字
pdfjs-editor-alt-text-dialog-label = 选择一项
pdfjs-editor-alt-text-dialog-description = 替换文字可在用户无法看到或加载图像时,描述其内容。
pdfjs-editor-alt-text-add-description-label = 添加描述
pdfjs-editor-alt-text-add-description-description = 用一两个句子,描述主题、背景或动作。
pdfjs-editor-alt-text-mark-decorative-label = 标记为装饰
pdfjs-editor-alt-text-mark-decorative-description = 用于装饰的图像,例如边框和水印。
pdfjs-editor-alt-text-cancel-button = 取消
pdfjs-editor-alt-text-save-button = 保存
pdfjs-editor-alt-text-decorative-tooltip = 已标记为装饰
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = 例如:一个少年坐到桌前,准备吃饭
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = 替换文字
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = 调整尺寸 - 左上角
pdfjs-editor-resizer-label-top-middle = 调整尺寸 - 顶部中间
pdfjs-editor-resizer-label-top-right = 调整尺寸 - 右上角
pdfjs-editor-resizer-label-middle-right = 调整尺寸 - 右侧中间
pdfjs-editor-resizer-label-bottom-right = 调整尺寸 - 右下角
pdfjs-editor-resizer-label-bottom-middle = 调整大小 - 底部中间
pdfjs-editor-resizer-label-bottom-left = 调整尺寸 - 左下角
pdfjs-editor-resizer-label-middle-left = 调整尺寸 - 左侧中间
pdfjs-editor-resizer-top-left =
.aria-label = 调整尺寸 - 左上角
pdfjs-editor-resizer-top-middle =
.aria-label = 调整尺寸 - 顶部中间
pdfjs-editor-resizer-top-right =
.aria-label = 调整尺寸 - 右上角
pdfjs-editor-resizer-middle-right =
.aria-label = 调整尺寸 - 右侧中间
pdfjs-editor-resizer-bottom-right =
.aria-label = 调整尺寸 - 右下角
pdfjs-editor-resizer-bottom-middle =
.aria-label = 调整大小 - 底部中间
pdfjs-editor-resizer-bottom-left =
.aria-label = 调整尺寸 - 左下角
pdfjs-editor-resizer-middle-left =
.aria-label = 调整尺寸 - 左侧中间
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = 高亮色
pdfjs-editor-colorpicker-button =
.title = 更改颜色
pdfjs-editor-colorpicker-dropdown =
.aria-label = 颜色选择
pdfjs-editor-colorpicker-yellow =
.title = 黄色
pdfjs-editor-colorpicker-green =
.title = 绿色
pdfjs-editor-colorpicker-blue =
.title = 蓝色
pdfjs-editor-colorpicker-pink =
.title = 粉色
pdfjs-editor-colorpicker-red =
.title = 红色
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = 显示全部
pdfjs-editor-highlight-show-all-button =
.title = 显示全部
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = 编辑替换文字(图像描述)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = 添加替换文字(图像描述)
pdfjs-editor-new-alt-text-textarea =
.placeholder = 请在此处撰写描述…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = 向无法看到或加载图像的用户提供的简短描述。
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = 此段替换文字为自动创建,有可能不准确。
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = 详细了解
pdfjs-editor-new-alt-text-create-automatically-button-label = 自动创建替换文字
pdfjs-editor-new-alt-text-not-now-button = 暂时不要
pdfjs-editor-new-alt-text-error-title = 无法自动创建替换文字
pdfjs-editor-new-alt-text-error-description = 请自行撰写替换文字,或稍后再试。
pdfjs-editor-new-alt-text-error-close-button = 关闭
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = 正在下载提供替换文字的 AI 模型({ $downloadedSize }/{ $totalSize } MB)
.aria-valuetext = 正在下载提供替换文字的 AI 模型({ $downloadedSize }/{ $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = 已添加替换文字
pdfjs-editor-new-alt-text-added-button-label = 已添加替换文字
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = 缺少替换文字
pdfjs-editor-new-alt-text-missing-button-label = 缺少替换文字
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = 检查替换文字
pdfjs-editor-new-alt-text-to-review-button-label = 检查替换文字
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = [自动创建] { $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = 图像替换文字设置
pdfjs-image-alt-text-settings-button-label = 图像替换文字设置
pdfjs-editor-alt-text-settings-dialog-label = 图像替换文字设置
pdfjs-editor-alt-text-settings-automatic-title = 自动创建替换文字
pdfjs-editor-alt-text-settings-create-model-button-label = 自动创建替换文字
pdfjs-editor-alt-text-settings-create-model-description = 向无法看到或加载图像的用户提供描述。
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = 提供替换文字的 AI 模型({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = 在您的设备本地运行,可使数据保持私密。自动创建替换文字需要使用此模型。
pdfjs-editor-alt-text-settings-delete-model-button = 删除
pdfjs-editor-alt-text-settings-download-model-button = 下载
pdfjs-editor-alt-text-settings-downloading-model-button = 正在下载…
pdfjs-editor-alt-text-settings-editor-title = 替换文字编辑器
pdfjs-editor-alt-text-settings-show-dialog-button-label = 添加图像后立即显示替换文字编辑器
pdfjs-editor-alt-text-settings-show-dialog-description = 帮助确保所有图像均拥有替换文字。
pdfjs-editor-alt-text-settings-close-button = 关闭
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = 已移除高亮
pdfjs-editor-undo-bar-message-freetext = 已移除文本
pdfjs-editor-undo-bar-message-ink = 已移除绘图
pdfjs-editor-undo-bar-message-stamp = 已移除图像
pdfjs-editor-undo-bar-message-signature = 签名已移除
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple = 已移除 { $count } 条注释
pdfjs-editor-undo-bar-undo-button =
.title = 撤销
pdfjs-editor-undo-bar-undo-button-label = 撤销
pdfjs-editor-undo-bar-close-button =
.title = 关闭
pdfjs-editor-undo-bar-close-button-label = 关闭
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = 用户可通过此模态对话框创建要添加到 PDF 文档中的签名、编辑其名称(同时用作替换文字),并可保存签名以便重复使用。
pdfjs-editor-add-signature-dialog-title = 添加签名
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = 键入
.title = 键入
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = 绘制
.title = 绘制
pdfjs-editor-add-signature-image-button = 图像
.title = 图像
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = 键入签名
.placeholder = 键入签名
pdfjs-editor-add-signature-draw-placeholder = 绘制签名
pdfjs-editor-add-signature-draw-thickness-range-label = 粗细
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = 笔画粗细:{ $thickness }
pdfjs-editor-add-signature-image-placeholder = 拖放文件到此处以上传
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] 或选取图像文件
*[other] 或浏览图像文件
}
## Controls
pdfjs-editor-add-signature-description-label = 描述(替换文字)
pdfjs-editor-add-signature-description-input =
.title = 描述(替换文字)
pdfjs-editor-add-signature-description-default-when-drawing = 签名
pdfjs-editor-add-signature-clear-button-label = 清除签名
pdfjs-editor-add-signature-clear-button =
.title = 清除签名
pdfjs-editor-add-signature-save-checkbox = 保存签名
pdfjs-editor-add-signature-save-warning-message = 最多可保存 5 个签名,请移除一个以继续保存。
pdfjs-editor-add-signature-image-upload-error-title = 无法上传图像
pdfjs-editor-add-signature-image-upload-error-description = 请检查网络连接,或尝试上传其他图像。
pdfjs-editor-add-signature-error-close-button = 关闭
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = 取消
pdfjs-editor-add-signature-add-button = 添加
pdfjs-editor-edit-signature-update-button = 更新
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = 移除签名
pdfjs-editor-delete-signature-button-label = 移除签名
pdfjs-editor-delete-signature-button1 =
.title = 移除已保存的签名
pdfjs-editor-delete-signature-button-label1 = 移除已保存的签名
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = 编辑描述
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = 编辑描述
================================================
FILE: cookbook/static/pdfjs/web/locale/zh-TW/viewer.ftl
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
## Main toolbar buttons (tooltips and alt text for images)
pdfjs-previous-button =
.title = 上一頁
pdfjs-previous-button-label = 上一頁
pdfjs-next-button =
.title = 下一頁
pdfjs-next-button-label = 下一頁
# .title: Tooltip for the pageNumber input.
pdfjs-page-input =
.title = 第
# Variables:
# $pagesCount (Number) - the total number of pages in the document
# This string follows an input field with the number of the page currently displayed.
pdfjs-of-pages = 頁,共 { $pagesCount } 頁
# Variables:
# $pageNumber (Number) - the currently visible page
# $pagesCount (Number) - the total number of pages in the document
pdfjs-page-of-pages = (第 { $pageNumber } 頁,共 { $pagesCount } 頁)
pdfjs-zoom-out-button =
.title = 縮小
pdfjs-zoom-out-button-label = 縮小
pdfjs-zoom-in-button =
.title = 放大
pdfjs-zoom-in-button-label = 放大
pdfjs-zoom-select =
.title = 縮放
pdfjs-presentation-mode-button =
.title = 切換至簡報模式
pdfjs-presentation-mode-button-label = 簡報模式
pdfjs-open-file-button =
.title = 開啟檔案
pdfjs-open-file-button-label = 開啟
pdfjs-print-button =
.title = 列印
pdfjs-print-button-label = 列印
pdfjs-save-button =
.title = 儲存
pdfjs-save-button-label = 儲存
# Used in Firefox for Android as a tooltip for the download button (“download” is a verb).
pdfjs-download-button =
.title = 下載
# Used in Firefox for Android as a label for the download button (“download” is a verb).
# Length of the translation matters since we are in a mobile context, with limited screen estate.
pdfjs-download-button-label = 下載
pdfjs-bookmark-button =
.title = 目前頁面(含目前檢視頁面的網址)
pdfjs-bookmark-button-label = 目前頁面
## Secondary toolbar and context menu
pdfjs-tools-button =
.title = 工具
pdfjs-tools-button-label = 工具
pdfjs-first-page-button =
.title = 跳到第一頁
pdfjs-first-page-button-label = 跳到第一頁
pdfjs-last-page-button =
.title = 跳到最後一頁
pdfjs-last-page-button-label = 跳到最後一頁
pdfjs-page-rotate-cw-button =
.title = 順時針旋轉
pdfjs-page-rotate-cw-button-label = 順時針旋轉
pdfjs-page-rotate-ccw-button =
.title = 逆時針旋轉
pdfjs-page-rotate-ccw-button-label = 逆時針旋轉
pdfjs-cursor-text-select-tool-button =
.title = 開啟文字選擇工具
pdfjs-cursor-text-select-tool-button-label = 文字選擇工具
pdfjs-cursor-hand-tool-button =
.title = 開啟頁面移動工具
pdfjs-cursor-hand-tool-button-label = 頁面移動工具
pdfjs-scroll-page-button =
.title = 使用單頁捲動版面
pdfjs-scroll-page-button-label = 單頁捲動
pdfjs-scroll-vertical-button =
.title = 使用垂直捲動版面
pdfjs-scroll-vertical-button-label = 垂直捲動
pdfjs-scroll-horizontal-button =
.title = 使用水平捲動版面
pdfjs-scroll-horizontal-button-label = 水平捲動
pdfjs-scroll-wrapped-button =
.title = 使用多頁捲動版面
pdfjs-scroll-wrapped-button-label = 多頁捲動
pdfjs-spread-none-button =
.title = 不要進行跨頁顯示
pdfjs-spread-none-button-label = 不跨頁
pdfjs-spread-odd-button =
.title = 從奇數頁開始跨頁
pdfjs-spread-odd-button-label = 奇數跨頁
pdfjs-spread-even-button =
.title = 從偶數頁開始跨頁
pdfjs-spread-even-button-label = 偶數跨頁
## Document properties dialog
pdfjs-document-properties-button =
.title = 文件內容…
pdfjs-document-properties-button-label = 文件內容…
pdfjs-document-properties-file-name = 檔案名稱:
pdfjs-document-properties-file-size = 檔案大小:
# Variables:
# $kb (Number) - the PDF file size in kilobytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB({ $b } 位元組)
# Variables:
# $mb (Number) - the PDF file size in megabytes
# $b (Number) - the PDF file size in bytes
pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB({ $b } 位元組)
# Variables:
# $size_kb (Number) - the PDF file size in kilobytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-kb = { $size_kb } KB({ $size_b } 位元組)
# Variables:
# $size_mb (Number) - the PDF file size in megabytes
# $size_b (Number) - the PDF file size in bytes
pdfjs-document-properties-mb = { $size_mb } MB({ $size_b } 位元組)
pdfjs-document-properties-title = 標題:
pdfjs-document-properties-author = 作者:
pdfjs-document-properties-subject = 主旨:
pdfjs-document-properties-keywords = 關鍵字:
pdfjs-document-properties-creation-date = 建立日期:
pdfjs-document-properties-modification-date = 修改日期:
# Variables:
# $dateObj (Date) - the creation/modification date and time of the PDF file
pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
# Variables:
# $date (Date) - the creation/modification date of the PDF file
# $time (Time) - the creation/modification time of the PDF file
pdfjs-document-properties-date-string = { $date } { $time }
pdfjs-document-properties-creator = 建立者:
pdfjs-document-properties-producer = PDF 產生器:
pdfjs-document-properties-version = PDF 版本:
pdfjs-document-properties-page-count = 頁數:
pdfjs-document-properties-page-size = 頁面大小:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = 垂直
pdfjs-document-properties-page-size-orientation-landscape = 水平
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letter
pdfjs-document-properties-page-size-name-legal = Legal
## Variables:
## $width (Number) - the width of the (current) page
## $height (Number) - the height of the (current) page
## $unit (String) - the unit of measurement of the (current) page
## $name (String) - the name of the (current) page
## $orientation (String) - the orientation of the (current) page
pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit }({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit }({ $name },{ $orientation })
##
# The linearization status of the document; usually called "Fast Web View" in
# English locales of Adobe software.
pdfjs-document-properties-linearized = 快速 Web 檢視:
pdfjs-document-properties-linearized-yes = 是
pdfjs-document-properties-linearized-no = 否
pdfjs-document-properties-close-button = 關閉
## Print
pdfjs-print-progress-message = 正在準備列印文件…
# Variables:
# $progress (Number) - percent value
pdfjs-print-progress-percent = { $progress }%
pdfjs-print-progress-close-button = 取消
pdfjs-printing-not-supported = 警告:此瀏覽器未完整支援列印功能。
pdfjs-printing-not-ready = 警告:此 PDF 未完成下載以供列印。
## Tooltips and alt text for side panel toolbar buttons
pdfjs-toggle-sidebar-button =
.title = 切換側邊欄
pdfjs-toggle-sidebar-notification-button =
.title = 切換側邊欄(包含大綱、附件、圖層的文件)
pdfjs-toggle-sidebar-button-label = 切換側邊欄
pdfjs-document-outline-button =
.title = 顯示文件大綱(雙擊展開/摺疊所有項目)
pdfjs-document-outline-button-label = 文件大綱
pdfjs-attachments-button =
.title = 顯示附件
pdfjs-attachments-button-label = 附件
pdfjs-layers-button =
.title = 顯示圖層(滑鼠雙擊即可將所有圖層重設為預設狀態)
pdfjs-layers-button-label = 圖層
pdfjs-thumbs-button =
.title = 顯示縮圖
pdfjs-thumbs-button-label = 縮圖
pdfjs-current-outline-item-button =
.title = 尋找目前的大綱項目
pdfjs-current-outline-item-button-label = 目前的大綱項目
pdfjs-findbar-button =
.title = 在文件中尋找
pdfjs-findbar-button-label = 尋找
pdfjs-additional-layers = 其他圖層
## Thumbnails panel item (tooltip and alt text for images)
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-title =
.title = 第 { $page } 頁
# Variables:
# $page (Number) - the page number
pdfjs-thumb-page-canvas =
.aria-label = 第 { $page } 頁的縮圖
## Find panel button title and messages
pdfjs-find-input =
.title = 尋找
.placeholder = 在文件中搜尋…
pdfjs-find-previous-button =
.title = 尋找文字前次出現的位置
pdfjs-find-previous-button-label = 上一個
pdfjs-find-next-button =
.title = 尋找文字下次出現的位置
pdfjs-find-next-button-label = 下一個
pdfjs-find-highlight-checkbox = 強調全部
pdfjs-find-match-case-checkbox-label = 區分大小寫
pdfjs-find-match-diacritics-checkbox-label = 符合變音符號
pdfjs-find-entire-word-checkbox-label = 符合整個字
pdfjs-find-reached-top = 已搜尋至文件頂端,自底端繼續搜尋
pdfjs-find-reached-bottom = 已搜尋至文件底端,自頂端繼續搜尋
# Variables:
# $current (Number) - the index of the currently active find result
# $total (Number) - the total number of matches in the document
pdfjs-find-match-count = 第 { $current } 筆符合,共符合 { $total } 筆
# Variables:
# $limit (Number) - the maximum number of matches
pdfjs-find-match-count-limit = 符合超過 { $limit } 項
pdfjs-find-not-found = 找不到指定文字
## Predefined zoom values
pdfjs-page-scale-width = 頁面寬度
pdfjs-page-scale-fit = 縮放至頁面大小
pdfjs-page-scale-auto = 自動縮放
pdfjs-page-scale-actual = 實際大小
# Variables:
# $scale (Number) - percent value for page scale
pdfjs-page-scale-percent = { $scale }%
## PDF page
# Variables:
# $page (Number) - the page number
pdfjs-page-landmark =
.aria-label = 第 { $page } 頁
## Loading indicator messages
pdfjs-loading-error = 載入 PDF 時發生錯誤。
pdfjs-invalid-file-error = 無效或毀損的 PDF 檔案。
pdfjs-missing-file-error = 找不到 PDF 檔案。
pdfjs-unexpected-response-error = 伺服器回應未預期的內容。
pdfjs-rendering-error = 描繪頁面時發生錯誤。
## Annotations
# Variables:
# $date (Date) - the modification date of the annotation
# $time (Time) - the modification time of the annotation
pdfjs-annotation-date-string = { $date } { $time }
# .alt: This is used as a tooltip.
# Variables:
# $type (String) - an annotation type from a list defined in the PDF spec
# (32000-1:2008 Table 169 – Annotation types).
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
pdfjs-text-annotation-type =
.alt = [{ $type } 註解]
# Variables:
# $dateObj (Date) - the modification date and time of the annotation
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
## Password
pdfjs-password-label = 請輸入用來開啟此 PDF 檔案的密碼。
pdfjs-password-invalid = 密碼不正確,請再試一次。
pdfjs-password-ok-button = 確定
pdfjs-password-cancel-button = 取消
pdfjs-web-fonts-disabled = 已停用網路字型 (Web fonts): 無法使用 PDF 內嵌字型。
## Editing
pdfjs-editor-free-text-button =
.title = 文字
pdfjs-editor-free-text-button-label = 文字
pdfjs-editor-ink-button =
.title = 繪圖
pdfjs-editor-ink-button-label = 繪圖
pdfjs-editor-stamp-button =
.title = 新增或編輯圖片
pdfjs-editor-stamp-button-label = 新增或編輯圖片
pdfjs-editor-highlight-button =
.title = 強調
pdfjs-editor-highlight-button-label = 強調
pdfjs-highlight-floating-button1 =
.title = 強調
.aria-label = 強調
pdfjs-highlight-floating-button-label = 強調
pdfjs-editor-signature-button =
.title = 加入簽章
pdfjs-editor-signature-button-label = 加入簽章
## Default editor aria labels
# “Highlight” is a noun, the string is used on the editor for highlights.
pdfjs-editor-highlight-editor =
.aria-label = 強調樣式編輯器
# “Drawing” is a noun, the string is used on the editor for drawings.
pdfjs-editor-ink-editor =
.aria-label = 繪圖編輯器
pdfjs-editor-signature-editor =
.aria-label = 簽章編輯器
pdfjs-editor-stamp-editor =
.aria-label = 圖片編輯器
## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button =
.title = 移除繪圖
pdfjs-editor-remove-freetext-button =
.title = 移除文字
pdfjs-editor-remove-stamp-button =
.title = 移除圖片
pdfjs-editor-remove-highlight-button =
.title = 移除強調範圍
pdfjs-editor-remove-signature-button =
.title = 移除簽章
##
# Editor Parameters
pdfjs-editor-free-text-color-input = 色彩
pdfjs-editor-free-text-size-input = 大小
pdfjs-editor-ink-color-input = 色彩
pdfjs-editor-ink-thickness-input = 線條粗細
pdfjs-editor-ink-opacity-input = 透明度
pdfjs-editor-stamp-add-image-button =
.title = 新增圖片
pdfjs-editor-stamp-add-image-button-label = 新增圖片
# This refers to the thickness of the line used for free highlighting (not bound to text)
pdfjs-editor-free-highlight-thickness-input = 線條粗細
pdfjs-editor-free-highlight-thickness-title =
.title = 更改強調文字以外的項目時的線條粗細
pdfjs-editor-add-signature-container =
.aria-label = 簽章控制元件與儲存的簽章
pdfjs-editor-signature-add-signature-button =
.title = 新增簽章
pdfjs-editor-signature-add-signature-button-label = 新增簽章
# Used on the button to use an already saved signature.
# Variables:
# $description (String) - a string describing/labeling the signature.
pdfjs-editor-add-saved-signature-button =
.title = 已儲存簽章:{ $description }
# .default-content is used as a placeholder in an empty text editor.
pdfjs-free-text2 =
.aria-label = 文字編輯器
.default-content = 請打字…
pdfjs-free-text =
.aria-label = 文本編輯器
pdfjs-free-text-default-content = 在此打字…
pdfjs-ink =
.aria-label = 圖形編輯器
pdfjs-ink-canvas =
.aria-label = 使用者建立的圖片
## Alt-text dialog
pdfjs-editor-alt-text-button-label = 替代文字
pdfjs-editor-alt-text-edit-button =
.aria-label = 編輯替代文字
pdfjs-editor-alt-text-edit-button-label = 編輯替代文字
pdfjs-editor-alt-text-dialog-label = 挑選一種
pdfjs-editor-alt-text-dialog-description = 替代文字可協助盲人,或於圖片無法載入時提供說明。
pdfjs-editor-alt-text-add-description-label = 新增描述
pdfjs-editor-alt-text-add-description-description = 用 1-2 句文字描述主題、背景或動作。
pdfjs-editor-alt-text-mark-decorative-label = 標示為裝飾性內容
pdfjs-editor-alt-text-mark-decorative-description = 這是裝飾性圖片,例如邊框或浮水印。
pdfjs-editor-alt-text-cancel-button = 取消
pdfjs-editor-alt-text-save-button = 儲存
pdfjs-editor-alt-text-decorative-tooltip = 已標示為裝飾性內容
# .placeholder: This is a placeholder for the alt text input area
pdfjs-editor-alt-text-textarea =
.placeholder = 例如:「有一位年輕男人坐在桌子前面吃飯」
# Alternative text (alt text) helps when people can't see the image.
pdfjs-editor-alt-text-button =
.aria-label = 替代文字
## Editor resizers
## This is used in an aria label to help to understand the role of the resizer.
pdfjs-editor-resizer-label-top-left = 左上角 — 調整大小
pdfjs-editor-resizer-label-top-middle = 頂部中間 — 調整大小
pdfjs-editor-resizer-label-top-right = 右上角 — 調整大小
pdfjs-editor-resizer-label-middle-right = 中間右方 — 調整大小
pdfjs-editor-resizer-label-bottom-right = 右下角 — 調整大小
pdfjs-editor-resizer-label-bottom-middle = 底部中間 — 調整大小
pdfjs-editor-resizer-label-bottom-left = 左下角 — 調整大小
pdfjs-editor-resizer-label-middle-left = 中間左方 — 調整大小
pdfjs-editor-resizer-top-left =
.aria-label = 左上角 — 調整大小
pdfjs-editor-resizer-top-middle =
.aria-label = 頂部中間 — 調整大小
pdfjs-editor-resizer-top-right =
.aria-label = 右上角 — 調整大小
pdfjs-editor-resizer-middle-right =
.aria-label = 中間右方 — 調整大小
pdfjs-editor-resizer-bottom-right =
.aria-label = 右下角 — 調整大小
pdfjs-editor-resizer-bottom-middle =
.aria-label = 底部中間 — 調整大小
pdfjs-editor-resizer-bottom-left =
.aria-label = 左下角 — 調整大小
pdfjs-editor-resizer-middle-left =
.aria-label = 中間左方 — 調整大小
## Color picker
# This means "Color used to highlight text"
pdfjs-editor-highlight-colorpicker-label = 強調色彩
pdfjs-editor-colorpicker-button =
.title = 更改色彩
pdfjs-editor-colorpicker-dropdown =
.aria-label = 色彩選項
pdfjs-editor-colorpicker-yellow =
.title = 黃色
pdfjs-editor-colorpicker-green =
.title = 綠色
pdfjs-editor-colorpicker-blue =
.title = 藍色
pdfjs-editor-colorpicker-pink =
.title = 粉紅色
pdfjs-editor-colorpicker-red =
.title = 紅色
## Show all highlights
## This is a toggle button to show/hide all the highlights.
pdfjs-editor-highlight-show-all-button-label = 顯示全部
pdfjs-editor-highlight-show-all-button =
.title = 顯示全部
## New alt-text dialog
## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy.
# Modal header positioned above a text box where users can edit the alt text.
pdfjs-editor-new-alt-text-dialog-edit-label = 編輯替代文字(圖片描述)
# Modal header positioned above a text box where users can add the alt text.
pdfjs-editor-new-alt-text-dialog-add-label = 新增替代文字(圖片描述)
pdfjs-editor-new-alt-text-textarea =
.placeholder = 在此寫下您的描述文字…
# This text refers to the alt text box above this description. It offers a definition of alt text.
pdfjs-editor-new-alt-text-description = 為看不到圖片的讀者,或圖片無法載入時顯示的簡短描述。
# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human.
pdfjs-editor-new-alt-text-disclaimer1 = 此替代文字是自動產生的,可能不夠精確。
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = 更多資訊
pdfjs-editor-new-alt-text-create-automatically-button-label = 自動產生替代文字
pdfjs-editor-new-alt-text-not-now-button = 暫時不要
pdfjs-editor-new-alt-text-error-title = 無法自動產生替代文字
pdfjs-editor-new-alt-text-error-description = 請自行填寫替代文字,或稍後再試一次。
pdfjs-editor-new-alt-text-error-close-button = 關閉
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
# $percent (Number) - the percentage of the downloaded size.
pdfjs-editor-new-alt-text-ai-model-downloading-progress = 正在下載替代文字 AI 模型({ $downloadedSize } / { $totalSize } MB)
.aria-valuetext = 正在下載替代文字 AI 模型({ $downloadedSize } / { $totalSize } MB)
# This is a button that users can click to edit the alt text they have already added.
pdfjs-editor-new-alt-text-added-button =
.aria-label = 已新增替代文字
pdfjs-editor-new-alt-text-added-button-label = 已新增替代文字
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
pdfjs-editor-new-alt-text-missing-button =
.aria-label = 缺少替代文字
pdfjs-editor-new-alt-text-missing-button-label = 缺少替代文字
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
pdfjs-editor-new-alt-text-to-review-button =
.aria-label = 確認替代文字
pdfjs-editor-new-alt-text-to-review-button-label = 確認替代文字
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
# Variables:
# $generatedAltText (String) - the generated alt-text.
pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = 自動產生:{ $generatedAltText }
## Image alt-text settings
pdfjs-image-alt-text-settings-button =
.title = 圖片替代文字設定
pdfjs-image-alt-text-settings-button-label = 圖片替代文字設定
pdfjs-editor-alt-text-settings-dialog-label = 圖片替代文字設定
pdfjs-editor-alt-text-settings-automatic-title = 自動化替代文字
pdfjs-editor-alt-text-settings-create-model-button-label = 自動產生替代文字
pdfjs-editor-alt-text-settings-create-model-description = 為您建議圖片描述,幫助看不到圖片的讀者,或於圖片無法載入時顯示。
# Variables:
# $totalSize (Number) - the total size (in MB) of the AI model.
pdfjs-editor-alt-text-settings-download-model-label = 替代文字 AI 模型({ $totalSize } MB)
pdfjs-editor-alt-text-settings-ai-model-description = 在您的本機裝置上運作,以確保您的資料隱私。必須下載此模型才可以自動產生替代文字。
pdfjs-editor-alt-text-settings-delete-model-button = 刪除
pdfjs-editor-alt-text-settings-download-model-button = 下載
pdfjs-editor-alt-text-settings-downloading-model-button = 下載中…
pdfjs-editor-alt-text-settings-editor-title = 替代文字編輯器
pdfjs-editor-alt-text-settings-show-dialog-button-label = 新增圖片後立即顯示替代文字編輯器
pdfjs-editor-alt-text-settings-show-dialog-description = 幫助您確保所有圖片都有替代文字。
pdfjs-editor-alt-text-settings-close-button = 關閉
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = 已移除強調
pdfjs-editor-undo-bar-message-freetext = 已移除文字
pdfjs-editor-undo-bar-message-ink = 已移除繪圖
pdfjs-editor-undo-bar-message-stamp = 已移除圖片
pdfjs-editor-undo-bar-message-signature = 已移除簽章
# Variables:
# $count (Number) - the number of removed annotations.
pdfjs-editor-undo-bar-message-multiple = 已移除 { $count } 筆註解
pdfjs-editor-undo-bar-undo-button =
.title = 還原
pdfjs-editor-undo-bar-undo-button-label = 還原
pdfjs-editor-undo-bar-close-button =
.title = 關閉
pdfjs-editor-undo-bar-close-button-label = 關閉
## Add a signature dialog
pdfjs-editor-add-signature-dialog-label = 此對話框讓使用者能夠建立簽章以加入 PDF 文件。使用者可以編輯他們的姓名(同時也是替代文字),並選擇性儲存簽章,以供未來重複使用。
pdfjs-editor-add-signature-dialog-title = 加入簽章
## Tab names
# Type is a verb (you can type your name as signature)
pdfjs-editor-add-signature-type-button = 打字
.title = 打字
# Draw is a verb (you can draw your signature)
pdfjs-editor-add-signature-draw-button = 手繪
.title = 手繪
pdfjs-editor-add-signature-image-button = 圖片
.title = 圖片
## Tab panels
pdfjs-editor-add-signature-type-input =
.aria-label = 輸入簽章
.placeholder = 輸入簽章
pdfjs-editor-add-signature-draw-placeholder = 手繪簽章
pdfjs-editor-add-signature-draw-thickness-range-label = 線條粗細
# Variables:
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
pdfjs-editor-add-signature-draw-thickness-range =
.title = 繪製時的線條粗細:{ $thickness }
pdfjs-editor-add-signature-image-placeholder = 將檔案拖曳到此處即可上傳
pdfjs-editor-add-signature-image-browse-link =
{ PLATFORM() ->
[macos] 或選擇圖片檔案
*[other] 或瀏覽圖片檔案
}
## Controls
pdfjs-editor-add-signature-description-label = 描述(替代文字)
pdfjs-editor-add-signature-description-input =
.title = 描述(替代文字)
pdfjs-editor-add-signature-description-default-when-drawing = 簽章
pdfjs-editor-add-signature-clear-button-label = 清除簽章
pdfjs-editor-add-signature-clear-button =
.title = 清除簽章
pdfjs-editor-add-signature-save-checkbox = 儲存簽章
pdfjs-editor-add-signature-save-warning-message = 您已經儲存 5 式簽章,請移除任一式才能再新增。
pdfjs-editor-add-signature-image-upload-error-title = 無法上傳圖片
pdfjs-editor-add-signature-image-upload-error-description = 請檢查您的網路連線,或改用其他圖片。
pdfjs-editor-add-signature-error-close-button = 關閉
## Dialog buttons
pdfjs-editor-add-signature-cancel-button = 取消
pdfjs-editor-add-signature-add-button = 新增
pdfjs-editor-edit-signature-update-button = 更新
## Main menu for adding/removing signatures
pdfjs-editor-delete-signature-button =
.title = 移除簽章
pdfjs-editor-delete-signature-button-label = 移除簽章
pdfjs-editor-delete-signature-button1 =
.title = 移除儲存的簽章
pdfjs-editor-delete-signature-button-label1 = 移除儲存的簽章
## Editor toolbar
pdfjs-editor-add-signature-edit-button-label = 編輯描述
## Edit signature description dialog
pdfjs-editor-edit-signature-dialog-title = 編輯描述
================================================
FILE: cookbook/static/pdfjs/web/pdf.mjs
================================================
/**
* @licstart The following is the entire license notice for the
* JavaScript code in this page
*
* Copyright 2024 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @licend The above is the entire license notice for the
* JavaScript code in this page
*/
/******/ // The require scope
/******/ var __webpack_require__ = {};
/******/
/************************************************************************/
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ // define getter functions for harmony exports
/******/ __webpack_require__.d = (exports, definition) => {
/******/ for(var key in definition) {
/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ }
/******/ }
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */
/******/ (() => {
/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ })();
/******/
/************************************************************************/
var __webpack_exports__ = globalThis.pdfjsLib = {};
// EXPORTS
__webpack_require__.d(__webpack_exports__, {
AbortException: () => (/* reexport */ AbortException),
AnnotationEditorLayer: () => (/* reexport */ AnnotationEditorLayer),
AnnotationEditorParamsType: () => (/* reexport */ AnnotationEditorParamsType),
AnnotationEditorType: () => (/* reexport */ AnnotationEditorType),
AnnotationEditorUIManager: () => (/* reexport */ AnnotationEditorUIManager),
AnnotationLayer: () => (/* reexport */ AnnotationLayer),
AnnotationMode: () => (/* reexport */ AnnotationMode),
AnnotationType: () => (/* reexport */ AnnotationType),
ColorPicker: () => (/* reexport */ ColorPicker),
DOMSVGFactory: () => (/* reexport */ DOMSVGFactory),
DrawLayer: () => (/* reexport */ DrawLayer),
FeatureTest: () => (/* reexport */ util_FeatureTest),
GlobalWorkerOptions: () => (/* reexport */ GlobalWorkerOptions),
ImageKind: () => (/* reexport */ util_ImageKind),
InvalidPDFException: () => (/* reexport */ InvalidPDFException),
MathClamp: () => (/* reexport */ MathClamp),
OPS: () => (/* reexport */ OPS),
OutputScale: () => (/* reexport */ OutputScale),
PDFDataRangeTransport: () => (/* reexport */ PDFDataRangeTransport),
PDFDateString: () => (/* reexport */ PDFDateString),
PDFWorker: () => (/* reexport */ PDFWorker),
PasswordResponses: () => (/* reexport */ PasswordResponses),
PermissionFlag: () => (/* reexport */ PermissionFlag),
PixelsPerInch: () => (/* reexport */ PixelsPerInch),
RenderingCancelledException: () => (/* reexport */ RenderingCancelledException),
ResponseException: () => (/* reexport */ ResponseException),
SignatureExtractor: () => (/* reexport */ SignatureExtractor),
SupportedImageMimeTypes: () => (/* reexport */ SupportedImageMimeTypes),
TextLayer: () => (/* reexport */ TextLayer),
TouchManager: () => (/* reexport */ TouchManager),
Util: () => (/* reexport */ Util),
VerbosityLevel: () => (/* reexport */ VerbosityLevel),
XfaLayer: () => (/* reexport */ XfaLayer),
build: () => (/* reexport */ build),
createValidAbsoluteUrl: () => (/* reexport */ createValidAbsoluteUrl),
fetchData: () => (/* reexport */ fetchData),
getDocument: () => (/* reexport */ getDocument),
getFilenameFromUrl: () => (/* reexport */ getFilenameFromUrl),
getPdfFilenameFromUrl: () => (/* reexport */ getPdfFilenameFromUrl),
getUuid: () => (/* reexport */ getUuid),
getXfaPageViewport: () => (/* reexport */ getXfaPageViewport),
isDataScheme: () => (/* reexport */ isDataScheme),
isPdfFile: () => (/* reexport */ isPdfFile),
isValidExplicitDest: () => (/* reexport */ isValidExplicitDest),
noContextMenu: () => (/* reexport */ noContextMenu),
normalizeUnicode: () => (/* reexport */ normalizeUnicode),
setLayerDimensions: () => (/* reexport */ setLayerDimensions),
shadow: () => (/* reexport */ shadow),
stopEvent: () => (/* reexport */ stopEvent),
version: () => (/* reexport */ version)
});
;// ./src/shared/util.js
const isNodeJS = typeof process === "object" && process + "" === "[object process]" && !process.versions.nw && !(process.versions.electron && process.type && process.type !== "browser");
const IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0];
const FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0];
const LINE_FACTOR = 1.35;
const LINE_DESCENT_FACTOR = 0.35;
const BASELINE_FACTOR = LINE_DESCENT_FACTOR / LINE_FACTOR;
const RenderingIntentFlag = {
ANY: 0x01,
DISPLAY: 0x02,
PRINT: 0x04,
SAVE: 0x08,
ANNOTATIONS_FORMS: 0x10,
ANNOTATIONS_STORAGE: 0x20,
ANNOTATIONS_DISABLE: 0x40,
IS_EDITING: 0x80,
OPLIST: 0x100
};
const AnnotationMode = {
DISABLE: 0,
ENABLE: 1,
ENABLE_FORMS: 2,
ENABLE_STORAGE: 3
};
const AnnotationEditorPrefix = "pdfjs_internal_editor_";
const AnnotationEditorType = {
DISABLE: -1,
NONE: 0,
FREETEXT: 3,
HIGHLIGHT: 9,
STAMP: 13,
INK: 15,
SIGNATURE: 101
};
const AnnotationEditorParamsType = {
RESIZE: 1,
CREATE: 2,
FREETEXT_SIZE: 11,
FREETEXT_COLOR: 12,
FREETEXT_OPACITY: 13,
INK_COLOR: 21,
INK_THICKNESS: 22,
INK_OPACITY: 23,
HIGHLIGHT_COLOR: 31,
HIGHLIGHT_DEFAULT_COLOR: 32,
HIGHLIGHT_THICKNESS: 33,
HIGHLIGHT_FREE: 34,
HIGHLIGHT_SHOW_ALL: 35,
DRAW_STEP: 41
};
const PermissionFlag = {
PRINT: 0x04,
MODIFY_CONTENTS: 0x08,
COPY: 0x10,
MODIFY_ANNOTATIONS: 0x20,
FILL_INTERACTIVE_FORMS: 0x100,
COPY_FOR_ACCESSIBILITY: 0x200,
ASSEMBLE: 0x400,
PRINT_HIGH_QUALITY: 0x800
};
const TextRenderingMode = {
FILL: 0,
STROKE: 1,
FILL_STROKE: 2,
INVISIBLE: 3,
FILL_ADD_TO_PATH: 4,
STROKE_ADD_TO_PATH: 5,
FILL_STROKE_ADD_TO_PATH: 6,
ADD_TO_PATH: 7,
FILL_STROKE_MASK: 3,
ADD_TO_PATH_FLAG: 4
};
const util_ImageKind = {
GRAYSCALE_1BPP: 1,
RGB_24BPP: 2,
RGBA_32BPP: 3
};
const AnnotationType = {
TEXT: 1,
LINK: 2,
FREETEXT: 3,
LINE: 4,
SQUARE: 5,
CIRCLE: 6,
POLYGON: 7,
POLYLINE: 8,
HIGHLIGHT: 9,
UNDERLINE: 10,
SQUIGGLY: 11,
STRIKEOUT: 12,
STAMP: 13,
CARET: 14,
INK: 15,
POPUP: 16,
FILEATTACHMENT: 17,
SOUND: 18,
MOVIE: 19,
WIDGET: 20,
SCREEN: 21,
PRINTERMARK: 22,
TRAPNET: 23,
WATERMARK: 24,
THREED: 25,
REDACT: 26
};
const AnnotationReplyType = {
GROUP: "Group",
REPLY: "R"
};
const AnnotationFlag = {
INVISIBLE: 0x01,
HIDDEN: 0x02,
PRINT: 0x04,
NOZOOM: 0x08,
NOROTATE: 0x10,
NOVIEW: 0x20,
READONLY: 0x40,
LOCKED: 0x80,
TOGGLENOVIEW: 0x100,
LOCKEDCONTENTS: 0x200
};
const AnnotationFieldFlag = {
READONLY: 0x0000001,
REQUIRED: 0x0000002,
NOEXPORT: 0x0000004,
MULTILINE: 0x0001000,
PASSWORD: 0x0002000,
NOTOGGLETOOFF: 0x0004000,
RADIO: 0x0008000,
PUSHBUTTON: 0x0010000,
COMBO: 0x0020000,
EDIT: 0x0040000,
SORT: 0x0080000,
FILESELECT: 0x0100000,
MULTISELECT: 0x0200000,
DONOTSPELLCHECK: 0x0400000,
DONOTSCROLL: 0x0800000,
COMB: 0x1000000,
RICHTEXT: 0x2000000,
RADIOSINUNISON: 0x2000000,
COMMITONSELCHANGE: 0x4000000
};
const AnnotationBorderStyleType = {
SOLID: 1,
DASHED: 2,
BEVELED: 3,
INSET: 4,
UNDERLINE: 5
};
const AnnotationActionEventType = {
E: "Mouse Enter",
X: "Mouse Exit",
D: "Mouse Down",
U: "Mouse Up",
Fo: "Focus",
Bl: "Blur",
PO: "PageOpen",
PC: "PageClose",
PV: "PageVisible",
PI: "PageInvisible",
K: "Keystroke",
F: "Format",
V: "Validate",
C: "Calculate"
};
const DocumentActionEventType = {
WC: "WillClose",
WS: "WillSave",
DS: "DidSave",
WP: "WillPrint",
DP: "DidPrint"
};
const PageActionEventType = {
O: "PageOpen",
C: "PageClose"
};
const VerbosityLevel = {
ERRORS: 0,
WARNINGS: 1,
INFOS: 5
};
const OPS = {
dependency: 1,
setLineWidth: 2,
setLineCap: 3,
setLineJoin: 4,
setMiterLimit: 5,
setDash: 6,
setRenderingIntent: 7,
setFlatness: 8,
setGState: 9,
save: 10,
restore: 11,
transform: 12,
moveTo: 13,
lineTo: 14,
curveTo: 15,
curveTo2: 16,
curveTo3: 17,
closePath: 18,
rectangle: 19,
stroke: 20,
closeStroke: 21,
fill: 22,
eoFill: 23,
fillStroke: 24,
eoFillStroke: 25,
closeFillStroke: 26,
closeEOFillStroke: 27,
endPath: 28,
clip: 29,
eoClip: 30,
beginText: 31,
endText: 32,
setCharSpacing: 33,
setWordSpacing: 34,
setHScale: 35,
setLeading: 36,
setFont: 37,
setTextRenderingMode: 38,
setTextRise: 39,
moveText: 40,
setLeadingMoveText: 41,
setTextMatrix: 42,
nextLine: 43,
showText: 44,
showSpacedText: 45,
nextLineShowText: 46,
nextLineSetSpacingShowText: 47,
setCharWidth: 48,
setCharWidthAndBounds: 49,
setStrokeColorSpace: 50,
setFillColorSpace: 51,
setStrokeColor: 52,
setStrokeColorN: 53,
setFillColor: 54,
setFillColorN: 55,
setStrokeGray: 56,
setFillGray: 57,
setStrokeRGBColor: 58,
setFillRGBColor: 59,
setStrokeCMYKColor: 60,
setFillCMYKColor: 61,
shadingFill: 62,
beginInlineImage: 63,
beginImageData: 64,
endInlineImage: 65,
paintXObject: 66,
markPoint: 67,
markPointProps: 68,
beginMarkedContent: 69,
beginMarkedContentProps: 70,
endMarkedContent: 71,
beginCompat: 72,
endCompat: 73,
paintFormXObjectBegin: 74,
paintFormXObjectEnd: 75,
beginGroup: 76,
endGroup: 77,
beginAnnotation: 80,
endAnnotation: 81,
paintImageMaskXObject: 83,
paintImageMaskXObjectGroup: 84,
paintImageXObject: 85,
paintInlineImageXObject: 86,
paintInlineImageXObjectGroup: 87,
paintImageXObjectRepeat: 88,
paintImageMaskXObjectRepeat: 89,
paintSolidColorImageMask: 90,
constructPath: 91,
setStrokeTransparent: 92,
setFillTransparent: 93
};
const DrawOPS = {
moveTo: 0,
lineTo: 1,
curveTo: 2,
closePath: 3
};
const PasswordResponses = {
NEED_PASSWORD: 1,
INCORRECT_PASSWORD: 2
};
let verbosity = VerbosityLevel.WARNINGS;
function setVerbosityLevel(level) {
if (Number.isInteger(level)) {
verbosity = level;
}
}
function getVerbosityLevel() {
return verbosity;
}
function info(msg) {
if (verbosity >= VerbosityLevel.INFOS) {
console.log(`Info: ${msg}`);
}
}
function warn(msg) {
if (verbosity >= VerbosityLevel.WARNINGS) {
console.log(`Warning: ${msg}`);
}
}
function unreachable(msg) {
throw new Error(msg);
}
function assert(cond, msg) {
if (!cond) {
unreachable(msg);
}
}
function _isValidProtocol(url) {
switch (url?.protocol) {
case "http:":
case "https:":
case "ftp:":
case "mailto:":
case "tel:":
return true;
default:
return false;
}
}
function createValidAbsoluteUrl(url, baseUrl = null, options = null) {
if (!url) {
return null;
}
if (options && typeof url === "string") {
if (options.addDefaultProtocol && url.startsWith("www.")) {
const dots = url.match(/\./g);
if (dots?.length >= 2) {
url = `http://${url}`;
}
}
if (options.tryConvertEncoding) {
try {
url = stringToUTF8String(url);
} catch {}
}
}
const absoluteUrl = baseUrl ? URL.parse(url, baseUrl) : URL.parse(url);
return _isValidProtocol(absoluteUrl) ? absoluteUrl : null;
}
function shadow(obj, prop, value, nonSerializable = false) {
Object.defineProperty(obj, prop, {
value,
enumerable: !nonSerializable,
configurable: true,
writable: false
});
return value;
}
const BaseException = function BaseExceptionClosure() {
function BaseException(message, name) {
this.message = message;
this.name = name;
}
BaseException.prototype = new Error();
BaseException.constructor = BaseException;
return BaseException;
}();
class PasswordException extends BaseException {
constructor(msg, code) {
super(msg, "PasswordException");
this.code = code;
}
}
class UnknownErrorException extends BaseException {
constructor(msg, details) {
super(msg, "UnknownErrorException");
this.details = details;
}
}
class InvalidPDFException extends BaseException {
constructor(msg) {
super(msg, "InvalidPDFException");
}
}
class ResponseException extends BaseException {
constructor(msg, status, missing) {
super(msg, "ResponseException");
this.status = status;
this.missing = missing;
}
}
class FormatError extends BaseException {
constructor(msg) {
super(msg, "FormatError");
}
}
class AbortException extends BaseException {
constructor(msg) {
super(msg, "AbortException");
}
}
function bytesToString(bytes) {
if (typeof bytes !== "object" || bytes?.length === undefined) {
unreachable("Invalid argument for bytesToString");
}
const length = bytes.length;
const MAX_ARGUMENT_COUNT = 8192;
if (length < MAX_ARGUMENT_COUNT) {
return String.fromCharCode.apply(null, bytes);
}
const strBuf = [];
for (let i = 0; i < length; i += MAX_ARGUMENT_COUNT) {
const chunkEnd = Math.min(i + MAX_ARGUMENT_COUNT, length);
const chunk = bytes.subarray(i, chunkEnd);
strBuf.push(String.fromCharCode.apply(null, chunk));
}
return strBuf.join("");
}
function stringToBytes(str) {
if (typeof str !== "string") {
unreachable("Invalid argument for stringToBytes");
}
const length = str.length;
const bytes = new Uint8Array(length);
for (let i = 0; i < length; ++i) {
bytes[i] = str.charCodeAt(i) & 0xff;
}
return bytes;
}
function string32(value) {
return String.fromCharCode(value >> 24 & 0xff, value >> 16 & 0xff, value >> 8 & 0xff, value & 0xff);
}
function objectSize(obj) {
return Object.keys(obj).length;
}
function objectFromMap(map) {
const obj = Object.create(null);
for (const [key, value] of map) {
obj[key] = value;
}
return obj;
}
function isLittleEndian() {
const buffer8 = new Uint8Array(4);
buffer8[0] = 1;
const view32 = new Uint32Array(buffer8.buffer, 0, 1);
return view32[0] === 1;
}
function isEvalSupported() {
try {
new Function("");
return true;
} catch {
return false;
}
}
class util_FeatureTest {
static get isLittleEndian() {
return shadow(this, "isLittleEndian", isLittleEndian());
}
static get isEvalSupported() {
return shadow(this, "isEvalSupported", isEvalSupported());
}
static get isOffscreenCanvasSupported() {
return shadow(this, "isOffscreenCanvasSupported", typeof OffscreenCanvas !== "undefined");
}
static get isImageDecoderSupported() {
return shadow(this, "isImageDecoderSupported", typeof ImageDecoder !== "undefined");
}
static get platform() {
if (typeof navigator !== "undefined" && typeof navigator?.platform === "string" && typeof navigator?.userAgent === "string") {
const {
platform,
userAgent
} = navigator;
return shadow(this, "platform", {
isAndroid: userAgent.includes("Android"),
isLinux: platform.includes("Linux"),
isMac: platform.includes("Mac"),
isWindows: platform.includes("Win"),
isFirefox: userAgent.includes("Firefox")
});
}
return shadow(this, "platform", {
isAndroid: false,
isLinux: false,
isMac: false,
isWindows: false,
isFirefox: false
});
}
static get isCSSRoundSupported() {
return shadow(this, "isCSSRoundSupported", globalThis.CSS?.supports?.("width: round(1.5px, 1px)"));
}
}
const hexNumbers = Array.from(Array(256).keys(), n => n.toString(16).padStart(2, "0"));
class Util {
static makeHexColor(r, g, b) {
return `#${hexNumbers[r]}${hexNumbers[g]}${hexNumbers[b]}`;
}
static transform(m1, m2) {
return [m1[0] * m2[0] + m1[2] * m2[1], m1[1] * m2[0] + m1[3] * m2[1], m1[0] * m2[2] + m1[2] * m2[3], m1[1] * m2[2] + m1[3] * m2[3], m1[0] * m2[4] + m1[2] * m2[5] + m1[4], m1[1] * m2[4] + m1[3] * m2[5] + m1[5]];
}
static applyTransform(p, m) {
const xt = p[0] * m[0] + p[1] * m[2] + m[4];
const yt = p[0] * m[1] + p[1] * m[3] + m[5];
return [xt, yt];
}
static applyInverseTransform(p, m) {
const d = m[0] * m[3] - m[1] * m[2];
const xt = (p[0] * m[3] - p[1] * m[2] + m[2] * m[5] - m[4] * m[3]) / d;
const yt = (-p[0] * m[1] + p[1] * m[0] + m[4] * m[1] - m[5] * m[0]) / d;
return [xt, yt];
}
static getAxialAlignedBoundingBox(r, m) {
const p1 = this.applyTransform(r, m);
const p2 = this.applyTransform(r.slice(2, 4), m);
const p3 = this.applyTransform([r[0], r[3]], m);
const p4 = this.applyTransform([r[2], r[1]], m);
return [Math.min(p1[0], p2[0], p3[0], p4[0]), Math.min(p1[1], p2[1], p3[1], p4[1]), Math.max(p1[0], p2[0], p3[0], p4[0]), Math.max(p1[1], p2[1], p3[1], p4[1])];
}
static inverseTransform(m) {
const d = m[0] * m[3] - m[1] * m[2];
return [m[3] / d, -m[1] / d, -m[2] / d, m[0] / d, (m[2] * m[5] - m[4] * m[3]) / d, (m[4] * m[1] - m[5] * m[0]) / d];
}
static singularValueDecompose2dScale(m) {
const transpose = [m[0], m[2], m[1], m[3]];
const a = m[0] * transpose[0] + m[1] * transpose[2];
const b = m[0] * transpose[1] + m[1] * transpose[3];
const c = m[2] * transpose[0] + m[3] * transpose[2];
const d = m[2] * transpose[1] + m[3] * transpose[3];
const first = (a + d) / 2;
const second = Math.sqrt((a + d) ** 2 - 4 * (a * d - c * b)) / 2;
const sx = first + second || 1;
const sy = first - second || 1;
return [Math.sqrt(sx), Math.sqrt(sy)];
}
static normalizeRect(rect) {
const r = rect.slice(0);
if (rect[0] > rect[2]) {
r[0] = rect[2];
r[2] = rect[0];
}
if (rect[1] > rect[3]) {
r[1] = rect[3];
r[3] = rect[1];
}
return r;
}
static intersect(rect1, rect2) {
const xLow = Math.max(Math.min(rect1[0], rect1[2]), Math.min(rect2[0], rect2[2]));
const xHigh = Math.min(Math.max(rect1[0], rect1[2]), Math.max(rect2[0], rect2[2]));
if (xLow > xHigh) {
return null;
}
const yLow = Math.max(Math.min(rect1[1], rect1[3]), Math.min(rect2[1], rect2[3]));
const yHigh = Math.min(Math.max(rect1[1], rect1[3]), Math.max(rect2[1], rect2[3]));
if (yLow > yHigh) {
return null;
}
return [xLow, yLow, xHigh, yHigh];
}
static pointBoundingBox(x, y, minMax) {
minMax[0] = Math.min(minMax[0], x);
minMax[1] = Math.min(minMax[1], y);
minMax[2] = Math.max(minMax[2], x);
minMax[3] = Math.max(minMax[3], y);
}
static rectBoundingBox(x0, y0, x1, y1, minMax) {
minMax[0] = Math.min(minMax[0], x0, x1);
minMax[1] = Math.min(minMax[1], y0, y1);
minMax[2] = Math.max(minMax[2], x0, x1);
minMax[3] = Math.max(minMax[3], y0, y1);
}
static #getExtremumOnCurve(x0, x1, x2, x3, y0, y1, y2, y3, t, minMax) {
if (t <= 0 || t >= 1) {
return;
}
const mt = 1 - t;
const tt = t * t;
const ttt = tt * t;
const x = mt * (mt * (mt * x0 + 3 * t * x1) + 3 * tt * x2) + ttt * x3;
const y = mt * (mt * (mt * y0 + 3 * t * y1) + 3 * tt * y2) + ttt * y3;
minMax[0] = Math.min(minMax[0], x);
minMax[1] = Math.min(minMax[1], y);
minMax[2] = Math.max(minMax[2], x);
minMax[3] = Math.max(minMax[3], y);
}
static #getExtremum(x0, x1, x2, x3, y0, y1, y2, y3, a, b, c, minMax) {
if (Math.abs(a) < 1e-12) {
if (Math.abs(b) >= 1e-12) {
this.#getExtremumOnCurve(x0, x1, x2, x3, y0, y1, y2, y3, -c / b, minMax);
}
return;
}
const delta = b ** 2 - 4 * c * a;
if (delta < 0) {
return;
}
const sqrtDelta = Math.sqrt(delta);
const a2 = 2 * a;
this.#getExtremumOnCurve(x0, x1, x2, x3, y0, y1, y2, y3, (-b + sqrtDelta) / a2, minMax);
this.#getExtremumOnCurve(x0, x1, x2, x3, y0, y1, y2, y3, (-b - sqrtDelta) / a2, minMax);
}
static bezierBoundingBox(x0, y0, x1, y1, x2, y2, x3, y3, minMax) {
minMax[0] = Math.min(minMax[0], x0, x3);
minMax[1] = Math.min(minMax[1], y0, y3);
minMax[2] = Math.max(minMax[2], x0, x3);
minMax[3] = Math.max(minMax[3], y0, y3);
this.#getExtremum(x0, x1, x2, x3, y0, y1, y2, y3, 3 * (-x0 + 3 * (x1 - x2) + x3), 6 * (x0 - 2 * x1 + x2), 3 * (x1 - x0), minMax);
this.#getExtremum(x0, x1, x2, x3, y0, y1, y2, y3, 3 * (-y0 + 3 * (y1 - y2) + y3), 6 * (y0 - 2 * y1 + y2), 3 * (y1 - y0), minMax);
}
}
const PDFStringTranslateTable = (/* unused pure expression or super */ null && ([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2d8, 0x2c7, 0x2c6, 0x2d9, 0x2dd, 0x2db, 0x2da, 0x2dc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2022, 0x2020, 0x2021, 0x2026, 0x2014, 0x2013, 0x192, 0x2044, 0x2039, 0x203a, 0x2212, 0x2030, 0x201e, 0x201c, 0x201d, 0x2018, 0x2019, 0x201a, 0x2122, 0xfb01, 0xfb02, 0x141, 0x152, 0x160, 0x178, 0x17d, 0x131, 0x142, 0x153, 0x161, 0x17e, 0, 0x20ac]));
function stringToPDFString(str) {
if (str[0] >= "\xEF") {
let encoding;
if (str[0] === "\xFE" && str[1] === "\xFF") {
encoding = "utf-16be";
if (str.length % 2 === 1) {
str = str.slice(0, -1);
}
} else if (str[0] === "\xFF" && str[1] === "\xFE") {
encoding = "utf-16le";
if (str.length % 2 === 1) {
str = str.slice(0, -1);
}
} else if (str[0] === "\xEF" && str[1] === "\xBB" && str[2] === "\xBF") {
encoding = "utf-8";
}
if (encoding) {
try {
const decoder = new TextDecoder(encoding, {
fatal: true
});
const buffer = stringToBytes(str);
const decoded = decoder.decode(buffer);
if (!decoded.includes("\x1b")) {
return decoded;
}
return decoded.replaceAll(/\x1b[^\x1b]*(?:\x1b|$)/g, "");
} catch (ex) {
warn(`stringToPDFString: "${ex}".`);
}
}
}
const strBuf = [];
for (let i = 0, ii = str.length; i < ii; i++) {
const charCode = str.charCodeAt(i);
if (charCode === 0x1b) {
while (++i < ii && str.charCodeAt(i) !== 0x1b) {}
continue;
}
const code = PDFStringTranslateTable[charCode];
strBuf.push(code ? String.fromCharCode(code) : str.charAt(i));
}
return strBuf.join("");
}
function stringToUTF8String(str) {
return decodeURIComponent(escape(str));
}
function utf8StringToString(str) {
return unescape(encodeURIComponent(str));
}
function isArrayEqual(arr1, arr2) {
if (arr1.length !== arr2.length) {
return false;
}
for (let i = 0, ii = arr1.length; i < ii; i++) {
if (arr1[i] !== arr2[i]) {
return false;
}
}
return true;
}
function getModificationDate(date = new Date()) {
const buffer = [date.getUTCFullYear().toString(), (date.getUTCMonth() + 1).toString().padStart(2, "0"), date.getUTCDate().toString().padStart(2, "0"), date.getUTCHours().toString().padStart(2, "0"), date.getUTCMinutes().toString().padStart(2, "0"), date.getUTCSeconds().toString().padStart(2, "0")];
return buffer.join("");
}
let NormalizeRegex = null;
let NormalizationMap = null;
function normalizeUnicode(str) {
if (!NormalizeRegex) {
NormalizeRegex = /([\u00a0\u00b5\u037e\u0eb3\u2000-\u200a\u202f\u2126\ufb00-\ufb04\ufb06\ufb20-\ufb36\ufb38-\ufb3c\ufb3e\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufba1\ufba4-\ufba9\ufbae-\ufbb1\ufbd3-\ufbdc\ufbde-\ufbe7\ufbea-\ufbf8\ufbfc-\ufbfd\ufc00-\ufc5d\ufc64-\ufcf1\ufcf5-\ufd3d\ufd88\ufdf4\ufdfa-\ufdfb\ufe71\ufe77\ufe79\ufe7b\ufe7d]+)|(\ufb05+)/gu;
NormalizationMap = new Map([["ſt", "ſt"]]);
}
return str.replaceAll(NormalizeRegex, (_, p1, p2) => p1 ? p1.normalize("NFKC") : NormalizationMap.get(p2));
}
function getUuid() {
if (typeof crypto.randomUUID === "function") {
return crypto.randomUUID();
}
const buf = new Uint8Array(32);
crypto.getRandomValues(buf);
return bytesToString(buf);
}
const AnnotationPrefix = "pdfjs_internal_id_";
function _isValidExplicitDest(validRef, validName, dest) {
if (!Array.isArray(dest) || dest.length < 2) {
return false;
}
const [page, zoom, ...args] = dest;
if (!validRef(page) && !Number.isInteger(page)) {
return false;
}
if (!validName(zoom)) {
return false;
}
const argsLen = args.length;
let allowNull = true;
switch (zoom.name) {
case "XYZ":
if (argsLen < 2 || argsLen > 3) {
return false;
}
break;
case "Fit":
case "FitB":
return argsLen === 0;
case "FitH":
case "FitBH":
case "FitV":
case "FitBV":
if (argsLen > 1) {
return false;
}
break;
case "FitR":
if (argsLen !== 4) {
return false;
}
allowNull = false;
break;
default:
return false;
}
for (const arg of args) {
if (typeof arg === "number" || allowNull && arg === null) {
continue;
}
return false;
}
return true;
}
function MathClamp(v, min, max) {
return Math.min(Math.max(v, min), max);
}
function toHexUtil(arr) {
if (Uint8Array.prototype.toHex) {
return arr.toHex();
}
return Array.from(arr, num => hexNumbers[num]).join("");
}
function toBase64Util(arr) {
if (Uint8Array.prototype.toBase64) {
return arr.toBase64();
}
return btoa(bytesToString(arr));
}
function fromBase64Util(str) {
if (Uint8Array.fromBase64) {
return Uint8Array.fromBase64(str);
}
return stringToBytes(atob(str));
}
if (typeof Promise.try !== "function") {
Promise.try = function (fn, ...args) {
return new Promise(resolve => {
resolve(fn(...args));
});
};
}
if (typeof Math.sumPrecise !== "function") {
Math.sumPrecise = function (numbers) {
return numbers.reduce((a, b) => a + b, 0);
};
}
;// ./src/display/display_utils.js
const SVG_NS = "http://www.w3.org/2000/svg";
class PixelsPerInch {
static CSS = 96.0;
static PDF = 72.0;
static PDF_TO_CSS_UNITS = this.CSS / this.PDF;
}
async function fetchData(url, type = "text") {
if (isValidFetchUrl(url, document.baseURI)) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(response.statusText);
}
switch (type) {
case "arraybuffer":
return response.arrayBuffer();
case "blob":
return response.blob();
case "json":
return response.json();
}
return response.text();
}
return new Promise((resolve, reject) => {
const request = new XMLHttpRequest();
request.open("GET", url, true);
request.responseType = type;
request.onreadystatechange = () => {
if (request.readyState !== XMLHttpRequest.DONE) {
return;
}
if (request.status === 200 || request.status === 0) {
switch (type) {
case "arraybuffer":
case "blob":
case "json":
resolve(request.response);
return;
}
resolve(request.responseText);
return;
}
reject(new Error(request.statusText));
};
request.send(null);
});
}
class PageViewport {
constructor({
viewBox,
userUnit,
scale,
rotation,
offsetX = 0,
offsetY = 0,
dontFlip = false
}) {
this.viewBox = viewBox;
this.userUnit = userUnit;
this.scale = scale;
this.rotation = rotation;
this.offsetX = offsetX;
this.offsetY = offsetY;
scale *= userUnit;
const centerX = (viewBox[2] + viewBox[0]) / 2;
const centerY = (viewBox[3] + viewBox[1]) / 2;
let rotateA, rotateB, rotateC, rotateD;
rotation %= 360;
if (rotation < 0) {
rotation += 360;
}
switch (rotation) {
case 180:
rotateA = -1;
rotateB = 0;
rotateC = 0;
rotateD = 1;
break;
case 90:
rotateA = 0;
rotateB = 1;
rotateC = 1;
rotateD = 0;
break;
case 270:
rotateA = 0;
rotateB = -1;
rotateC = -1;
rotateD = 0;
break;
case 0:
rotateA = 1;
rotateB = 0;
rotateC = 0;
rotateD = -1;
break;
default:
throw new Error("PageViewport: Invalid rotation, must be a multiple of 90 degrees.");
}
if (dontFlip) {
rotateC = -rotateC;
rotateD = -rotateD;
}
let offsetCanvasX, offsetCanvasY;
let width, height;
if (rotateA === 0) {
offsetCanvasX = Math.abs(centerY - viewBox[1]) * scale + offsetX;
offsetCanvasY = Math.abs(centerX - viewBox[0]) * scale + offsetY;
width = (viewBox[3] - viewBox[1]) * scale;
height = (viewBox[2] - viewBox[0]) * scale;
} else {
offsetCanvasX = Math.abs(centerX - viewBox[0]) * scale + offsetX;
offsetCanvasY = Math.abs(centerY - viewBox[1]) * scale + offsetY;
width = (viewBox[2] - viewBox[0]) * scale;
height = (viewBox[3] - viewBox[1]) * scale;
}
this.transform = [rotateA * scale, rotateB * scale, rotateC * scale, rotateD * scale, offsetCanvasX - rotateA * scale * centerX - rotateC * scale * centerY, offsetCanvasY - rotateB * scale * centerX - rotateD * scale * centerY];
this.width = width;
this.height = height;
}
get rawDims() {
const dims = this.viewBox;
return shadow(this, "rawDims", {
pageWidth: dims[2] - dims[0],
pageHeight: dims[3] - dims[1],
pageX: dims[0],
pageY: dims[1]
});
}
clone({
scale = this.scale,
rotation = this.rotation,
offsetX = this.offsetX,
offsetY = this.offsetY,
dontFlip = false
} = {}) {
return new PageViewport({
viewBox: this.viewBox.slice(),
userUnit: this.userUnit,
scale,
rotation,
offsetX,
offsetY,
dontFlip
});
}
convertToViewportPoint(x, y) {
return Util.applyTransform([x, y], this.transform);
}
convertToViewportRectangle(rect) {
const topLeft = Util.applyTransform([rect[0], rect[1]], this.transform);
const bottomRight = Util.applyTransform([rect[2], rect[3]], this.transform);
return [topLeft[0], topLeft[1], bottomRight[0], bottomRight[1]];
}
convertToPdfPoint(x, y) {
return Util.applyInverseTransform([x, y], this.transform);
}
}
class RenderingCancelledException extends BaseException {
constructor(msg, extraDelay = 0) {
super(msg, "RenderingCancelledException");
this.extraDelay = extraDelay;
}
}
function isDataScheme(url) {
const ii = url.length;
let i = 0;
while (i < ii && url[i].trim() === "") {
i++;
}
return url.substring(i, i + 5).toLowerCase() === "data:";
}
function isPdfFile(filename) {
return typeof filename === "string" && /\.pdf$/i.test(filename);
}
function getFilenameFromUrl(url) {
[url] = url.split(/[#?]/, 1);
return url.substring(url.lastIndexOf("/") + 1);
}
function getPdfFilenameFromUrl(url, defaultFilename = "document.pdf") {
if (typeof url !== "string") {
return defaultFilename;
}
if (isDataScheme(url)) {
warn('getPdfFilenameFromUrl: ignore "data:"-URL for performance reasons.');
return defaultFilename;
}
const reURI = /^(?:(?:[^:]+:)?\/\/[^/]+)?([^?#]*)(\?[^#]*)?(#.*)?$/;
const reFilename = /[^/?#=]+\.pdf\b(?!.*\.pdf\b)/i;
const splitURI = reURI.exec(url);
let suggestedFilename = reFilename.exec(splitURI[1]) || reFilename.exec(splitURI[2]) || reFilename.exec(splitURI[3]);
if (suggestedFilename) {
suggestedFilename = suggestedFilename[0];
if (suggestedFilename.includes("%")) {
try {
suggestedFilename = reFilename.exec(decodeURIComponent(suggestedFilename))[0];
} catch {}
}
}
return suggestedFilename || defaultFilename;
}
class StatTimer {
started = Object.create(null);
times = [];
time(name) {
if (name in this.started) {
warn(`Timer is already running for ${name}`);
}
this.started[name] = Date.now();
}
timeEnd(name) {
if (!(name in this.started)) {
warn(`Timer has not been started for ${name}`);
}
this.times.push({
name,
start: this.started[name],
end: Date.now()
});
delete this.started[name];
}
toString() {
const outBuf = [];
let longest = 0;
for (const {
name
} of this.times) {
longest = Math.max(name.length, longest);
}
for (const {
name,
start,
end
} of this.times) {
outBuf.push(`${name.padEnd(longest)} ${end - start}ms\n`);
}
return outBuf.join("");
}
}
function isValidFetchUrl(url, baseUrl) {
const res = baseUrl ? URL.parse(url, baseUrl) : URL.parse(url);
return res?.protocol === "http:" || res?.protocol === "https:";
}
function noContextMenu(e) {
e.preventDefault();
}
function stopEvent(e) {
e.preventDefault();
e.stopPropagation();
}
function deprecated(details) {
console.log("Deprecated API usage: " + details);
}
class PDFDateString {
static #regex;
static toDateObject(input) {
if (!input || typeof input !== "string") {
return null;
}
this.#regex ||= new RegExp("^D:" + "(\\d{4})" + "(\\d{2})?" + "(\\d{2})?" + "(\\d{2})?" + "(\\d{2})?" + "(\\d{2})?" + "([Z|+|-])?" + "(\\d{2})?" + "'?" + "(\\d{2})?" + "'?");
const matches = this.#regex.exec(input);
if (!matches) {
return null;
}
const year = parseInt(matches[1], 10);
let month = parseInt(matches[2], 10);
month = month >= 1 && month <= 12 ? month - 1 : 0;
let day = parseInt(matches[3], 10);
day = day >= 1 && day <= 31 ? day : 1;
let hour = parseInt(matches[4], 10);
hour = hour >= 0 && hour <= 23 ? hour : 0;
let minute = parseInt(matches[5], 10);
minute = minute >= 0 && minute <= 59 ? minute : 0;
let second = parseInt(matches[6], 10);
second = second >= 0 && second <= 59 ? second : 0;
const universalTimeRelation = matches[7] || "Z";
let offsetHour = parseInt(matches[8], 10);
offsetHour = offsetHour >= 0 && offsetHour <= 23 ? offsetHour : 0;
let offsetMinute = parseInt(matches[9], 10) || 0;
offsetMinute = offsetMinute >= 0 && offsetMinute <= 59 ? offsetMinute : 0;
if (universalTimeRelation === "-") {
hour += offsetHour;
minute += offsetMinute;
} else if (universalTimeRelation === "+") {
hour -= offsetHour;
minute -= offsetMinute;
}
return new Date(Date.UTC(year, month, day, hour, minute, second));
}
}
function getXfaPageViewport(xfaPage, {
scale = 1,
rotation = 0
}) {
const {
width,
height
} = xfaPage.attributes.style;
const viewBox = [0, 0, parseInt(width), parseInt(height)];
return new PageViewport({
viewBox,
userUnit: 1,
scale,
rotation
});
}
function getRGB(color) {
if (color.startsWith("#")) {
const colorRGB = parseInt(color.slice(1), 16);
return [(colorRGB & 0xff0000) >> 16, (colorRGB & 0x00ff00) >> 8, colorRGB & 0x0000ff];
}
if (color.startsWith("rgb(")) {
return color.slice(4, -1).split(",").map(x => parseInt(x));
}
if (color.startsWith("rgba(")) {
return color.slice(5, -1).split(",").map(x => parseInt(x)).slice(0, 3);
}
warn(`Not a valid color format: "${color}"`);
return [0, 0, 0];
}
function getColorValues(colors) {
const span = document.createElement("span");
span.style.visibility = "hidden";
document.body.append(span);
for (const name of colors.keys()) {
span.style.color = name;
const computedColor = window.getComputedStyle(span).color;
colors.set(name, getRGB(computedColor));
}
span.remove();
}
function getCurrentTransform(ctx) {
const {
a,
b,
c,
d,
e,
f
} = ctx.getTransform();
return [a, b, c, d, e, f];
}
function getCurrentTransformInverse(ctx) {
const {
a,
b,
c,
d,
e,
f
} = ctx.getTransform().invertSelf();
return [a, b, c, d, e, f];
}
function setLayerDimensions(div, viewport, mustFlip = false, mustRotate = true) {
if (viewport instanceof PageViewport) {
const {
pageWidth,
pageHeight
} = viewport.rawDims;
const {
style
} = div;
const useRound = util_FeatureTest.isCSSRoundSupported;
const w = `var(--total-scale-factor) * ${pageWidth}px`,
h = `var(--total-scale-factor) * ${pageHeight}px`;
const widthStr = useRound ? `round(down, ${w}, var(--scale-round-x))` : `calc(${w})`,
heightStr = useRound ? `round(down, ${h}, var(--scale-round-y))` : `calc(${h})`;
if (!mustFlip || viewport.rotation % 180 === 0) {
style.width = widthStr;
style.height = heightStr;
} else {
style.width = heightStr;
style.height = widthStr;
}
}
if (mustRotate) {
div.setAttribute("data-main-rotation", viewport.rotation);
}
}
class OutputScale {
constructor() {
const {
pixelRatio
} = OutputScale;
this.sx = pixelRatio;
this.sy = pixelRatio;
}
get scaled() {
return this.sx !== 1 || this.sy !== 1;
}
get symmetric() {
return this.sx === this.sy;
}
limitCanvas(width, height, maxPixels, maxDim) {
let maxAreaScale = Infinity,
maxWidthScale = Infinity,
maxHeightScale = Infinity;
if (maxPixels > 0) {
maxAreaScale = Math.sqrt(maxPixels / (width * height));
}
if (maxDim !== -1) {
maxWidthScale = maxDim / width;
maxHeightScale = maxDim / height;
}
const maxScale = Math.min(maxAreaScale, maxWidthScale, maxHeightScale);
if (this.sx > maxScale || this.sy > maxScale) {
this.sx = maxScale;
this.sy = maxScale;
return true;
}
return false;
}
static get pixelRatio() {
return globalThis.devicePixelRatio || 1;
}
}
const SupportedImageMimeTypes = ["image/apng", "image/avif", "image/bmp", "image/gif", "image/jpeg", "image/png", "image/svg+xml", "image/webp", "image/x-icon"];
;// ./src/display/editor/toolbar.js
class EditorToolbar {
#toolbar = null;
#colorPicker = null;
#editor;
#buttons = null;
#altText = null;
#signatureDescriptionButton = null;
static #l10nRemove = null;
constructor(editor) {
this.#editor = editor;
EditorToolbar.#l10nRemove ||= Object.freeze({
freetext: "pdfjs-editor-remove-freetext-button",
highlight: "pdfjs-editor-remove-highlight-button",
ink: "pdfjs-editor-remove-ink-button",
stamp: "pdfjs-editor-remove-stamp-button",
signature: "pdfjs-editor-remove-signature-button"
});
}
render() {
const editToolbar = this.#toolbar = document.createElement("div");
editToolbar.classList.add("editToolbar", "hidden");
editToolbar.setAttribute("role", "toolbar");
const signal = this.#editor._uiManager._signal;
editToolbar.addEventListener("contextmenu", noContextMenu, {
signal
});
editToolbar.addEventListener("pointerdown", EditorToolbar.#pointerDown, {
signal
});
const buttons = this.#buttons = document.createElement("div");
buttons.className = "buttons";
editToolbar.append(buttons);
const position = this.#editor.toolbarPosition;
if (position) {
const {
style
} = editToolbar;
const x = this.#editor._uiManager.direction === "ltr" ? 1 - position[0] : position[0];
style.insetInlineEnd = `${100 * x}%`;
style.top = `calc(${100 * position[1]}% + var(--editor-toolbar-vert-offset))`;
}
this.#addDeleteButton();
return editToolbar;
}
get div() {
return this.#toolbar;
}
static #pointerDown(e) {
e.stopPropagation();
}
#focusIn(e) {
this.#editor._focusEventsAllowed = false;
stopEvent(e);
}
#focusOut(e) {
this.#editor._focusEventsAllowed = true;
stopEvent(e);
}
#addListenersToElement(element) {
const signal = this.#editor._uiManager._signal;
element.addEventListener("focusin", this.#focusIn.bind(this), {
capture: true,
signal
});
element.addEventListener("focusout", this.#focusOut.bind(this), {
capture: true,
signal
});
element.addEventListener("contextmenu", noContextMenu, {
signal
});
}
hide() {
this.#toolbar.classList.add("hidden");
this.#colorPicker?.hideDropdown();
}
show() {
this.#toolbar.classList.remove("hidden");
this.#altText?.shown();
}
#addDeleteButton() {
const {
editorType,
_uiManager
} = this.#editor;
const button = document.createElement("button");
button.className = "delete";
button.tabIndex = 0;
button.setAttribute("data-l10n-id", EditorToolbar.#l10nRemove[editorType]);
this.#addListenersToElement(button);
button.addEventListener("click", e => {
_uiManager.delete();
}, {
signal: _uiManager._signal
});
this.#buttons.append(button);
}
get #divider() {
const divider = document.createElement("div");
divider.className = "divider";
return divider;
}
async addAltText(altText) {
const button = await altText.render();
this.#addListenersToElement(button);
this.#buttons.prepend(button, this.#divider);
this.#altText = altText;
}
addColorPicker(colorPicker) {
this.#colorPicker = colorPicker;
const button = colorPicker.renderButton();
this.#addListenersToElement(button);
this.#buttons.prepend(button, this.#divider);
}
async addEditSignatureButton(signatureManager) {
const button = this.#signatureDescriptionButton = await signatureManager.renderEditButton(this.#editor);
this.#addListenersToElement(button);
this.#buttons.prepend(button, this.#divider);
}
updateEditSignatureButton(description) {
if (this.#signatureDescriptionButton) {
this.#signatureDescriptionButton.title = description;
}
}
remove() {
this.#toolbar.remove();
this.#colorPicker?.destroy();
this.#colorPicker = null;
}
}
class HighlightToolbar {
#buttons = null;
#toolbar = null;
#uiManager;
constructor(uiManager) {
this.#uiManager = uiManager;
}
#render() {
const editToolbar = this.#toolbar = document.createElement("div");
editToolbar.className = "editToolbar";
editToolbar.setAttribute("role", "toolbar");
editToolbar.addEventListener("contextmenu", noContextMenu, {
signal: this.#uiManager._signal
});
const buttons = this.#buttons = document.createElement("div");
buttons.className = "buttons";
editToolbar.append(buttons);
this.#addHighlightButton();
return editToolbar;
}
#getLastPoint(boxes, isLTR) {
let lastY = 0;
let lastX = 0;
for (const box of boxes) {
const y = box.y + box.height;
if (y < lastY) {
continue;
}
const x = box.x + (isLTR ? box.width : 0);
if (y > lastY) {
lastX = x;
lastY = y;
continue;
}
if (isLTR) {
if (x > lastX) {
lastX = x;
}
} else if (x < lastX) {
lastX = x;
}
}
return [isLTR ? 1 - lastX : lastX, lastY];
}
show(parent, boxes, isLTR) {
const [x, y] = this.#getLastPoint(boxes, isLTR);
const {
style
} = this.#toolbar ||= this.#render();
parent.append(this.#toolbar);
style.insetInlineEnd = `${100 * x}%`;
style.top = `calc(${100 * y}% + var(--editor-toolbar-vert-offset))`;
}
hide() {
this.#toolbar.remove();
}
#addHighlightButton() {
const button = document.createElement("button");
button.className = "highlightButton";
button.tabIndex = 0;
button.setAttribute("data-l10n-id", `pdfjs-highlight-floating-button1`);
const span = document.createElement("span");
button.append(span);
span.className = "visuallyHidden";
span.setAttribute("data-l10n-id", "pdfjs-highlight-floating-button-label");
const signal = this.#uiManager._signal;
button.addEventListener("contextmenu", noContextMenu, {
signal
});
button.addEventListener("click", () => {
this.#uiManager.highlightSelection("floating_button");
}, {
signal
});
this.#buttons.append(button);
}
}
;// ./src/display/editor/tools.js
function bindEvents(obj, element, names) {
for (const name of names) {
element.addEventListener(name, obj[name].bind(obj));
}
}
class IdManager {
#id = 0;
get id() {
return `${AnnotationEditorPrefix}${this.#id++}`;
}
}
class ImageManager {
#baseId = getUuid();
#id = 0;
#cache = null;
static get _isSVGFittingCanvas() {
const svg = `data:image/svg+xml;charset=UTF-8,`;
const canvas = new OffscreenCanvas(1, 3);
const ctx = canvas.getContext("2d", {
willReadFrequently: true
});
const image = new Image();
image.src = svg;
const promise = image.decode().then(() => {
ctx.drawImage(image, 0, 0, 1, 1, 0, 0, 1, 3);
return new Uint32Array(ctx.getImageData(0, 0, 1, 1).data.buffer)[0] === 0;
});
return shadow(this, "_isSVGFittingCanvas", promise);
}
async #get(key, rawData) {
this.#cache ||= new Map();
let data = this.#cache.get(key);
if (data === null) {
return null;
}
if (data?.bitmap) {
data.refCounter += 1;
return data;
}
try {
data ||= {
bitmap: null,
id: `image_${this.#baseId}_${this.#id++}`,
refCounter: 0,
isSvg: false
};
let image;
if (typeof rawData === "string") {
data.url = rawData;
image = await fetchData(rawData, "blob");
} else if (rawData instanceof File) {
image = data.file = rawData;
} else if (rawData instanceof Blob) {
image = rawData;
}
if (image.type === "image/svg+xml") {
const mustRemoveAspectRatioPromise = ImageManager._isSVGFittingCanvas;
const fileReader = new FileReader();
const imageElement = new Image();
const imagePromise = new Promise((resolve, reject) => {
imageElement.onload = () => {
data.bitmap = imageElement;
data.isSvg = true;
resolve();
};
fileReader.onload = async () => {
const url = data.svgUrl = fileReader.result;
imageElement.src = (await mustRemoveAspectRatioPromise) ? `${url}#svgView(preserveAspectRatio(none))` : url;
};
imageElement.onerror = fileReader.onerror = reject;
});
fileReader.readAsDataURL(image);
await imagePromise;
} else {
data.bitmap = await createImageBitmap(image);
}
data.refCounter = 1;
} catch (e) {
warn(e);
data = null;
}
this.#cache.set(key, data);
if (data) {
this.#cache.set(data.id, data);
}
return data;
}
async getFromFile(file) {
const {
lastModified,
name,
size,
type
} = file;
return this.#get(`${lastModified}_${name}_${size}_${type}`, file);
}
async getFromUrl(url) {
return this.#get(url, url);
}
async getFromBlob(id, blobPromise) {
const blob = await blobPromise;
return this.#get(id, blob);
}
async getFromId(id) {
this.#cache ||= new Map();
const data = this.#cache.get(id);
if (!data) {
return null;
}
if (data.bitmap) {
data.refCounter += 1;
return data;
}
if (data.file) {
return this.getFromFile(data.file);
}
if (data.blobPromise) {
const {
blobPromise
} = data;
delete data.blobPromise;
return this.getFromBlob(data.id, blobPromise);
}
return this.getFromUrl(data.url);
}
getFromCanvas(id, canvas) {
this.#cache ||= new Map();
let data = this.#cache.get(id);
if (data?.bitmap) {
data.refCounter += 1;
return data;
}
const offscreen = new OffscreenCanvas(canvas.width, canvas.height);
const ctx = offscreen.getContext("2d");
ctx.drawImage(canvas, 0, 0);
data = {
bitmap: offscreen.transferToImageBitmap(),
id: `image_${this.#baseId}_${this.#id++}`,
refCounter: 1,
isSvg: false
};
this.#cache.set(id, data);
this.#cache.set(data.id, data);
return data;
}
getSvgUrl(id) {
const data = this.#cache.get(id);
if (!data?.isSvg) {
return null;
}
return data.svgUrl;
}
deleteId(id) {
this.#cache ||= new Map();
const data = this.#cache.get(id);
if (!data) {
return;
}
data.refCounter -= 1;
if (data.refCounter !== 0) {
return;
}
const {
bitmap
} = data;
if (!data.url && !data.file) {
const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
const ctx = canvas.getContext("bitmaprenderer");
ctx.transferFromImageBitmap(bitmap);
data.blobPromise = canvas.convertToBlob();
}
bitmap.close?.();
data.bitmap = null;
}
isValidId(id) {
return id.startsWith(`image_${this.#baseId}_`);
}
}
class CommandManager {
#commands = [];
#locked = false;
#maxSize;
#position = -1;
constructor(maxSize = 128) {
this.#maxSize = maxSize;
}
add({
cmd,
undo,
post,
mustExec,
type = NaN,
overwriteIfSameType = false,
keepUndo = false
}) {
if (mustExec) {
cmd();
}
if (this.#locked) {
return;
}
const save = {
cmd,
undo,
post,
type
};
if (this.#position === -1) {
if (this.#commands.length > 0) {
this.#commands.length = 0;
}
this.#position = 0;
this.#commands.push(save);
return;
}
if (overwriteIfSameType && this.#commands[this.#position].type === type) {
if (keepUndo) {
save.undo = this.#commands[this.#position].undo;
}
this.#commands[this.#position] = save;
return;
}
const next = this.#position + 1;
if (next === this.#maxSize) {
this.#commands.splice(0, 1);
} else {
this.#position = next;
if (next < this.#commands.length) {
this.#commands.splice(next);
}
}
this.#commands.push(save);
}
undo() {
if (this.#position === -1) {
return;
}
this.#locked = true;
const {
undo,
post
} = this.#commands[this.#position];
undo();
post?.();
this.#locked = false;
this.#position -= 1;
}
redo() {
if (this.#position < this.#commands.length - 1) {
this.#position += 1;
this.#locked = true;
const {
cmd,
post
} = this.#commands[this.#position];
cmd();
post?.();
this.#locked = false;
}
}
hasSomethingToUndo() {
return this.#position !== -1;
}
hasSomethingToRedo() {
return this.#position < this.#commands.length - 1;
}
cleanType(type) {
if (this.#position === -1) {
return;
}
for (let i = this.#position; i >= 0; i--) {
if (this.#commands[i].type !== type) {
this.#commands.splice(i + 1, this.#position - i);
this.#position = i;
return;
}
}
this.#commands.length = 0;
this.#position = -1;
}
destroy() {
this.#commands = null;
}
}
class KeyboardManager {
constructor(callbacks) {
this.buffer = [];
this.callbacks = new Map();
this.allKeys = new Set();
const {
isMac
} = util_FeatureTest.platform;
for (const [keys, callback, options = {}] of callbacks) {
for (const key of keys) {
const isMacKey = key.startsWith("mac+");
if (isMac && isMacKey) {
this.callbacks.set(key.slice(4), {
callback,
options
});
this.allKeys.add(key.split("+").at(-1));
} else if (!isMac && !isMacKey) {
this.callbacks.set(key, {
callback,
options
});
this.allKeys.add(key.split("+").at(-1));
}
}
}
}
#serialize(event) {
if (event.altKey) {
this.buffer.push("alt");
}
if (event.ctrlKey) {
this.buffer.push("ctrl");
}
if (event.metaKey) {
this.buffer.push("meta");
}
if (event.shiftKey) {
this.buffer.push("shift");
}
this.buffer.push(event.key);
const str = this.buffer.join("+");
this.buffer.length = 0;
return str;
}
exec(self, event) {
if (!this.allKeys.has(event.key)) {
return;
}
const info = this.callbacks.get(this.#serialize(event));
if (!info) {
return;
}
const {
callback,
options: {
bubbles = false,
args = [],
checker = null
}
} = info;
if (checker && !checker(self, event)) {
return;
}
callback.bind(self, ...args, event)();
if (!bubbles) {
stopEvent(event);
}
}
}
class ColorManager {
static _colorsMapping = new Map([["CanvasText", [0, 0, 0]], ["Canvas", [255, 255, 255]]]);
get _colors() {
const colors = new Map([["CanvasText", null], ["Canvas", null]]);
getColorValues(colors);
return shadow(this, "_colors", colors);
}
convert(color) {
const rgb = getRGB(color);
if (!window.matchMedia("(forced-colors: active)").matches) {
return rgb;
}
for (const [name, RGB] of this._colors) {
if (RGB.every((x, i) => x === rgb[i])) {
return ColorManager._colorsMapping.get(name);
}
}
return rgb;
}
getHexCode(name) {
const rgb = this._colors.get(name);
if (!rgb) {
return name;
}
return Util.makeHexColor(...rgb);
}
}
class AnnotationEditorUIManager {
#abortController = new AbortController();
#activeEditor = null;
#allEditors = new Map();
#allLayers = new Map();
#altTextManager = null;
#annotationStorage = null;
#changedExistingAnnotations = null;
#commandManager = new CommandManager();
#copyPasteAC = null;
#currentDrawingSession = null;
#currentPageIndex = 0;
#deletedAnnotationsElementIds = new Set();
#draggingEditors = null;
#editorTypes = null;
#editorsToRescale = new Set();
_editorUndoBar = null;
#enableHighlightFloatingButton = false;
#enableUpdatedAddImage = false;
#enableNewAltTextWhenAddingImage = false;
#filterFactory = null;
#focusMainContainerTimeoutId = null;
#focusManagerAC = null;
#highlightColors = null;
#highlightWhenShiftUp = false;
#highlightToolbar = null;
#idManager = new IdManager();
#isEnabled = false;
#isWaiting = false;
#keyboardManagerAC = null;
#lastActiveElement = null;
#mainHighlightColorPicker = null;
#missingCanvases = null;
#mlManager = null;
#mode = AnnotationEditorType.NONE;
#selectedEditors = new Set();
#selectedTextNode = null;
#signatureManager = null;
#pageColors = null;
#showAllStates = null;
#previousStates = {
isEditing: false,
isEmpty: true,
hasSomethingToUndo: false,
hasSomethingToRedo: false,
hasSelectedEditor: false,
hasSelectedText: false
};
#translation = [0, 0];
#translationTimeoutId = null;
#container = null;
#viewer = null;
#updateModeCapability = null;
static TRANSLATE_SMALL = 1;
static TRANSLATE_BIG = 10;
static get _keyboardManager() {
const proto = AnnotationEditorUIManager.prototype;
const arrowChecker = self => self.#container.contains(document.activeElement) && document.activeElement.tagName !== "BUTTON" && self.hasSomethingToControl();
const textInputChecker = (_self, {
target: el
}) => {
if (el instanceof HTMLInputElement) {
const {
type
} = el;
return type !== "text" && type !== "number";
}
return true;
};
const small = this.TRANSLATE_SMALL;
const big = this.TRANSLATE_BIG;
return shadow(this, "_keyboardManager", new KeyboardManager([[["ctrl+a", "mac+meta+a"], proto.selectAll, {
checker: textInputChecker
}], [["ctrl+z", "mac+meta+z"], proto.undo, {
checker: textInputChecker
}], [["ctrl+y", "ctrl+shift+z", "mac+meta+shift+z", "ctrl+shift+Z", "mac+meta+shift+Z"], proto.redo, {
checker: textInputChecker
}], [["Backspace", "alt+Backspace", "ctrl+Backspace", "shift+Backspace", "mac+Backspace", "mac+alt+Backspace", "mac+ctrl+Backspace", "Delete", "ctrl+Delete", "shift+Delete", "mac+Delete"], proto.delete, {
checker: textInputChecker
}], [["Enter", "mac+Enter"], proto.addNewEditorFromKeyboard, {
checker: (self, {
target: el
}) => !(el instanceof HTMLButtonElement) && self.#container.contains(el) && !self.isEnterHandled
}], [[" ", "mac+ "], proto.addNewEditorFromKeyboard, {
checker: (self, {
target: el
}) => !(el instanceof HTMLButtonElement) && self.#container.contains(document.activeElement)
}], [["Escape", "mac+Escape"], proto.unselectAll], [["ArrowLeft", "mac+ArrowLeft"], proto.translateSelectedEditors, {
args: [-small, 0],
checker: arrowChecker
}], [["ctrl+ArrowLeft", "mac+shift+ArrowLeft"], proto.translateSelectedEditors, {
args: [-big, 0],
checker: arrowChecker
}], [["ArrowRight", "mac+ArrowRight"], proto.translateSelectedEditors, {
args: [small, 0],
checker: arrowChecker
}], [["ctrl+ArrowRight", "mac+shift+ArrowRight"], proto.translateSelectedEditors, {
args: [big, 0],
checker: arrowChecker
}], [["ArrowUp", "mac+ArrowUp"], proto.translateSelectedEditors, {
args: [0, -small],
checker: arrowChecker
}], [["ctrl+ArrowUp", "mac+shift+ArrowUp"], proto.translateSelectedEditors, {
args: [0, -big],
checker: arrowChecker
}], [["ArrowDown", "mac+ArrowDown"], proto.translateSelectedEditors, {
args: [0, small],
checker: arrowChecker
}], [["ctrl+ArrowDown", "mac+shift+ArrowDown"], proto.translateSelectedEditors, {
args: [0, big],
checker: arrowChecker
}]]));
}
constructor(container, viewer, altTextManager, signatureManager, eventBus, pdfDocument, pageColors, highlightColors, enableHighlightFloatingButton, enableUpdatedAddImage, enableNewAltTextWhenAddingImage, mlManager, editorUndoBar, supportsPinchToZoom) {
const signal = this._signal = this.#abortController.signal;
this.#container = container;
this.#viewer = viewer;
this.#altTextManager = altTextManager;
this.#signatureManager = signatureManager;
this._eventBus = eventBus;
eventBus._on("editingaction", this.onEditingAction.bind(this), {
signal
});
eventBus._on("pagechanging", this.onPageChanging.bind(this), {
signal
});
eventBus._on("scalechanging", this.onScaleChanging.bind(this), {
signal
});
eventBus._on("rotationchanging", this.onRotationChanging.bind(this), {
signal
});
eventBus._on("setpreference", this.onSetPreference.bind(this), {
signal
});
eventBus._on("switchannotationeditorparams", evt => this.updateParams(evt.type, evt.value), {
signal
});
this.#addSelectionListener();
this.#addDragAndDropListeners();
this.#addKeyboardManager();
this.#annotationStorage = pdfDocument.annotationStorage;
this.#filterFactory = pdfDocument.filterFactory;
this.#pageColors = pageColors;
this.#highlightColors = highlightColors || null;
this.#enableHighlightFloatingButton = enableHighlightFloatingButton;
this.#enableUpdatedAddImage = enableUpdatedAddImage;
this.#enableNewAltTextWhenAddingImage = enableNewAltTextWhenAddingImage;
this.#mlManager = mlManager || null;
this.viewParameters = {
realScale: PixelsPerInch.PDF_TO_CSS_UNITS,
rotation: 0
};
this.isShiftKeyDown = false;
this._editorUndoBar = editorUndoBar || null;
this._supportsPinchToZoom = supportsPinchToZoom !== false;
}
destroy() {
this.#updateModeCapability?.resolve();
this.#updateModeCapability = null;
this.#abortController?.abort();
this.#abortController = null;
this._signal = null;
for (const layer of this.#allLayers.values()) {
layer.destroy();
}
this.#allLayers.clear();
this.#allEditors.clear();
this.#editorsToRescale.clear();
this.#missingCanvases?.clear();
this.#activeEditor = null;
this.#selectedEditors.clear();
this.#commandManager.destroy();
this.#altTextManager?.destroy();
this.#signatureManager?.destroy();
this.#highlightToolbar?.hide();
this.#highlightToolbar = null;
this.#mainHighlightColorPicker?.destroy();
this.#mainHighlightColorPicker = null;
if (this.#focusMainContainerTimeoutId) {
clearTimeout(this.#focusMainContainerTimeoutId);
this.#focusMainContainerTimeoutId = null;
}
if (this.#translationTimeoutId) {
clearTimeout(this.#translationTimeoutId);
this.#translationTimeoutId = null;
}
this._editorUndoBar?.destroy();
}
combinedSignal(ac) {
return AbortSignal.any([this._signal, ac.signal]);
}
get mlManager() {
return this.#mlManager;
}
get useNewAltTextFlow() {
return this.#enableUpdatedAddImage;
}
get useNewAltTextWhenAddingImage() {
return this.#enableNewAltTextWhenAddingImage;
}
get hcmFilter() {
return shadow(this, "hcmFilter", this.#pageColors ? this.#filterFactory.addHCMFilter(this.#pageColors.foreground, this.#pageColors.background) : "none");
}
get direction() {
return shadow(this, "direction", getComputedStyle(this.#container).direction);
}
get highlightColors() {
return shadow(this, "highlightColors", this.#highlightColors ? new Map(this.#highlightColors.split(",").map(pair => pair.split("=").map(x => x.trim()))) : null);
}
get highlightColorNames() {
return shadow(this, "highlightColorNames", this.highlightColors ? new Map(Array.from(this.highlightColors, e => e.reverse())) : null);
}
setCurrentDrawingSession(layer) {
if (layer) {
this.unselectAll();
this.disableUserSelect(true);
} else {
this.disableUserSelect(false);
}
this.#currentDrawingSession = layer;
}
setMainHighlightColorPicker(colorPicker) {
this.#mainHighlightColorPicker = colorPicker;
}
editAltText(editor, firstTime = false) {
this.#altTextManager?.editAltText(this, editor, firstTime);
}
getSignature(editor) {
this.#signatureManager?.getSignature({
uiManager: this,
editor
});
}
get signatureManager() {
return this.#signatureManager;
}
switchToMode(mode, callback) {
this._eventBus.on("annotationeditormodechanged", callback, {
once: true,
signal: this._signal
});
this._eventBus.dispatch("showannotationeditorui", {
source: this,
mode
});
}
setPreference(name, value) {
this._eventBus.dispatch("setpreference", {
source: this,
name,
value
});
}
onSetPreference({
name,
value
}) {
switch (name) {
case "enableNewAltTextWhenAddingImage":
this.#enableNewAltTextWhenAddingImage = value;
break;
}
}
onPageChanging({
pageNumber
}) {
this.#currentPageIndex = pageNumber - 1;
}
focusMainContainer() {
this.#container.focus();
}
findParent(x, y) {
for (const layer of this.#allLayers.values()) {
const {
x: layerX,
y: layerY,
width,
height
} = layer.div.getBoundingClientRect();
if (x >= layerX && x <= layerX + width && y >= layerY && y <= layerY + height) {
return layer;
}
}
return null;
}
disableUserSelect(value = false) {
this.#viewer.classList.toggle("noUserSelect", value);
}
addShouldRescale(editor) {
this.#editorsToRescale.add(editor);
}
removeShouldRescale(editor) {
this.#editorsToRescale.delete(editor);
}
onScaleChanging({
scale
}) {
this.commitOrRemove();
this.viewParameters.realScale = scale * PixelsPerInch.PDF_TO_CSS_UNITS;
for (const editor of this.#editorsToRescale) {
editor.onScaleChanging();
}
this.#currentDrawingSession?.onScaleChanging();
}
onRotationChanging({
pagesRotation
}) {
this.commitOrRemove();
this.viewParameters.rotation = pagesRotation;
}
#getAnchorElementForSelection({
anchorNode
}) {
return anchorNode.nodeType === Node.TEXT_NODE ? anchorNode.parentElement : anchorNode;
}
#getLayerForTextLayer(textLayer) {
const {
currentLayer
} = this;
if (currentLayer.hasTextLayer(textLayer)) {
return currentLayer;
}
for (const layer of this.#allLayers.values()) {
if (layer.hasTextLayer(textLayer)) {
return layer;
}
}
return null;
}
highlightSelection(methodOfCreation = "") {
const selection = document.getSelection();
if (!selection || selection.isCollapsed) {
return;
}
const {
anchorNode,
anchorOffset,
focusNode,
focusOffset
} = selection;
const text = selection.toString();
const anchorElement = this.#getAnchorElementForSelection(selection);
const textLayer = anchorElement.closest(".textLayer");
const boxes = this.getSelectionBoxes(textLayer);
if (!boxes) {
return;
}
selection.empty();
const layer = this.#getLayerForTextLayer(textLayer);
const isNoneMode = this.#mode === AnnotationEditorType.NONE;
const callback = () => {
layer?.createAndAddNewEditor({
x: 0,
y: 0
}, false, {
methodOfCreation,
boxes,
anchorNode,
anchorOffset,
focusNode,
focusOffset,
text
});
if (isNoneMode) {
this.showAllEditors("highlight", true, true);
}
};
if (isNoneMode) {
this.switchToMode(AnnotationEditorType.HIGHLIGHT, callback);
return;
}
callback();
}
#displayHighlightToolbar() {
const selection = document.getSelection();
if (!selection || selection.isCollapsed) {
return;
}
const anchorElement = this.#getAnchorElementForSelection(selection);
const textLayer = anchorElement.closest(".textLayer");
const boxes = this.getSelectionBoxes(textLayer);
if (!boxes) {
return;
}
this.#highlightToolbar ||= new HighlightToolbar(this);
this.#highlightToolbar.show(textLayer, boxes, this.direction === "ltr");
}
addToAnnotationStorage(editor) {
if (!editor.isEmpty() && this.#annotationStorage && !this.#annotationStorage.has(editor.id)) {
this.#annotationStorage.setValue(editor.id, editor);
}
}
#selectionChange() {
const selection = document.getSelection();
if (!selection || selection.isCollapsed) {
if (this.#selectedTextNode) {
this.#highlightToolbar?.hide();
this.#selectedTextNode = null;
this.#dispatchUpdateStates({
hasSelectedText: false
});
}
return;
}
const {
anchorNode
} = selection;
if (anchorNode === this.#selectedTextNode) {
return;
}
const anchorElement = this.#getAnchorElementForSelection(selection);
const textLayer = anchorElement.closest(".textLayer");
if (!textLayer) {
if (this.#selectedTextNode) {
this.#highlightToolbar?.hide();
this.#selectedTextNode = null;
this.#dispatchUpdateStates({
hasSelectedText: false
});
}
return;
}
this.#highlightToolbar?.hide();
this.#selectedTextNode = anchorNode;
this.#dispatchUpdateStates({
hasSelectedText: true
});
if (this.#mode !== AnnotationEditorType.HIGHLIGHT && this.#mode !== AnnotationEditorType.NONE) {
return;
}
if (this.#mode === AnnotationEditorType.HIGHLIGHT) {
this.showAllEditors("highlight", true, true);
}
this.#highlightWhenShiftUp = this.isShiftKeyDown;
if (!this.isShiftKeyDown) {
const activeLayer = this.#mode === AnnotationEditorType.HIGHLIGHT ? this.#getLayerForTextLayer(textLayer) : null;
activeLayer?.toggleDrawing();
const ac = new AbortController();
const signal = this.combinedSignal(ac);
const pointerup = e => {
if (e.type === "pointerup" && e.button !== 0) {
return;
}
ac.abort();
activeLayer?.toggleDrawing(true);
if (e.type === "pointerup") {
this.#onSelectEnd("main_toolbar");
}
};
window.addEventListener("pointerup", pointerup, {
signal
});
window.addEventListener("blur", pointerup, {
signal
});
}
}
#onSelectEnd(methodOfCreation = "") {
if (this.#mode === AnnotationEditorType.HIGHLIGHT) {
this.highlightSelection(methodOfCreation);
} else if (this.#enableHighlightFloatingButton) {
this.#displayHighlightToolbar();
}
}
#addSelectionListener() {
document.addEventListener("selectionchange", this.#selectionChange.bind(this), {
signal: this._signal
});
}
#addFocusManager() {
if (this.#focusManagerAC) {
return;
}
this.#focusManagerAC = new AbortController();
const signal = this.combinedSignal(this.#focusManagerAC);
window.addEventListener("focus", this.focus.bind(this), {
signal
});
window.addEventListener("blur", this.blur.bind(this), {
signal
});
}
#removeFocusManager() {
this.#focusManagerAC?.abort();
this.#focusManagerAC = null;
}
blur() {
this.isShiftKeyDown = false;
if (this.#highlightWhenShiftUp) {
this.#highlightWhenShiftUp = false;
this.#onSelectEnd("main_toolbar");
}
if (!this.hasSelection) {
return;
}
const {
activeElement
} = document;
for (const editor of this.#selectedEditors) {
if (editor.div.contains(activeElement)) {
this.#lastActiveElement = [editor, activeElement];
editor._focusEventsAllowed = false;
break;
}
}
}
focus() {
if (!this.#lastActiveElement) {
return;
}
const [lastEditor, lastActiveElement] = this.#lastActiveElement;
this.#lastActiveElement = null;
lastActiveElement.addEventListener("focusin", () => {
lastEditor._focusEventsAllowed = true;
}, {
once: true,
signal: this._signal
});
lastActiveElement.focus();
}
#addKeyboardManager() {
if (this.#keyboardManagerAC) {
return;
}
this.#keyboardManagerAC = new AbortController();
const signal = this.combinedSignal(this.#keyboardManagerAC);
window.addEventListener("keydown", this.keydown.bind(this), {
signal
});
window.addEventListener("keyup", this.keyup.bind(this), {
signal
});
}
#removeKeyboardManager() {
this.#keyboardManagerAC?.abort();
this.#keyboardManagerAC = null;
}
#addCopyPasteListeners() {
if (this.#copyPasteAC) {
return;
}
this.#copyPasteAC = new AbortController();
const signal = this.combinedSignal(this.#copyPasteAC);
document.addEventListener("copy", this.copy.bind(this), {
signal
});
document.addEventListener("cut", this.cut.bind(this), {
signal
});
document.addEventListener("paste", this.paste.bind(this), {
signal
});
}
#removeCopyPasteListeners() {
this.#copyPasteAC?.abort();
this.#copyPasteAC = null;
}
#addDragAndDropListeners() {
const signal = this._signal;
document.addEventListener("dragover", this.dragOver.bind(this), {
signal
});
document.addEventListener("drop", this.drop.bind(this), {
signal
});
}
addEditListeners() {
this.#addKeyboardManager();
this.#addCopyPasteListeners();
}
removeEditListeners() {
this.#removeKeyboardManager();
this.#removeCopyPasteListeners();
}
dragOver(event) {
for (const {
type
} of event.dataTransfer.items) {
for (const editorType of this.#editorTypes) {
if (editorType.isHandlingMimeForPasting(type)) {
event.dataTransfer.dropEffect = "copy";
event.preventDefault();
return;
}
}
}
}
drop(event) {
for (const item of event.dataTransfer.items) {
for (const editorType of this.#editorTypes) {
if (editorType.isHandlingMimeForPasting(item.type)) {
editorType.paste(item, this.currentLayer);
event.preventDefault();
return;
}
}
}
}
copy(event) {
event.preventDefault();
this.#activeEditor?.commitOrRemove();
if (!this.hasSelection) {
return;
}
const editors = [];
for (const editor of this.#selectedEditors) {
const serialized = editor.serialize(true);
if (serialized) {
editors.push(serialized);
}
}
if (editors.length === 0) {
return;
}
event.clipboardData.setData("application/pdfjs", JSON.stringify(editors));
}
cut(event) {
this.copy(event);
this.delete();
}
async paste(event) {
event.preventDefault();
const {
clipboardData
} = event;
for (const item of clipboardData.items) {
for (const editorType of this.#editorTypes) {
if (editorType.isHandlingMimeForPasting(item.type)) {
editorType.paste(item, this.currentLayer);
return;
}
}
}
let data = clipboardData.getData("application/pdfjs");
if (!data) {
return;
}
try {
data = JSON.parse(data);
} catch (ex) {
warn(`paste: "${ex.message}".`);
return;
}
if (!Array.isArray(data)) {
return;
}
this.unselectAll();
const layer = this.currentLayer;
try {
const newEditors = [];
for (const editor of data) {
const deserializedEditor = await layer.deserialize(editor);
if (!deserializedEditor) {
return;
}
newEditors.push(deserializedEditor);
}
const cmd = () => {
for (const editor of newEditors) {
this.#addEditorToLayer(editor);
}
this.#selectEditors(newEditors);
};
const undo = () => {
for (const editor of newEditors) {
editor.remove();
}
};
this.addCommands({
cmd,
undo,
mustExec: true
});
} catch (ex) {
warn(`paste: "${ex.message}".`);
}
}
keydown(event) {
if (!this.isShiftKeyDown && event.key === "Shift") {
this.isShiftKeyDown = true;
}
if (this.#mode !== AnnotationEditorType.NONE && !this.isEditorHandlingKeyboard) {
AnnotationEditorUIManager._keyboardManager.exec(this, event);
}
}
keyup(event) {
if (this.isShiftKeyDown && event.key === "Shift") {
this.isShiftKeyDown = false;
if (this.#highlightWhenShiftUp) {
this.#highlightWhenShiftUp = false;
this.#onSelectEnd("main_toolbar");
}
}
}
onEditingAction({
name
}) {
switch (name) {
case "undo":
case "redo":
case "delete":
case "selectAll":
this[name]();
break;
case "highlightSelection":
this.highlightSelection("context_menu");
break;
}
}
#dispatchUpdateStates(details) {
const hasChanged = Object.entries(details).some(([key, value]) => this.#previousStates[key] !== value);
if (hasChanged) {
this._eventBus.dispatch("annotationeditorstateschanged", {
source: this,
details: Object.assign(this.#previousStates, details)
});
if (this.#mode === AnnotationEditorType.HIGHLIGHT && details.hasSelectedEditor === false) {
this.#dispatchUpdateUI([[AnnotationEditorParamsType.HIGHLIGHT_FREE, true]]);
}
}
}
#dispatchUpdateUI(details) {
this._eventBus.dispatch("annotationeditorparamschanged", {
source: this,
details
});
}
setEditingState(isEditing) {
if (isEditing) {
this.#addFocusManager();
this.#addCopyPasteListeners();
this.#dispatchUpdateStates({
isEditing: this.#mode !== AnnotationEditorType.NONE,
isEmpty: this.#isEmpty(),
hasSomethingToUndo: this.#commandManager.hasSomethingToUndo(),
hasSomethingToRedo: this.#commandManager.hasSomethingToRedo(),
hasSelectedEditor: false
});
} else {
this.#removeFocusManager();
this.#removeCopyPasteListeners();
this.#dispatchUpdateStates({
isEditing: false
});
this.disableUserSelect(false);
}
}
registerEditorTypes(types) {
if (this.#editorTypes) {
return;
}
this.#editorTypes = types;
for (const editorType of this.#editorTypes) {
this.#dispatchUpdateUI(editorType.defaultPropertiesToUpdate);
}
}
getId() {
return this.#idManager.id;
}
get currentLayer() {
return this.#allLayers.get(this.#currentPageIndex);
}
getLayer(pageIndex) {
return this.#allLayers.get(pageIndex);
}
get currentPageIndex() {
return this.#currentPageIndex;
}
addLayer(layer) {
this.#allLayers.set(layer.pageIndex, layer);
if (this.#isEnabled) {
layer.enable();
} else {
layer.disable();
}
}
removeLayer(layer) {
this.#allLayers.delete(layer.pageIndex);
}
async updateMode(mode, editId = null, isFromKeyboard = false) {
if (this.#mode === mode) {
return;
}
if (this.#updateModeCapability) {
await this.#updateModeCapability.promise;
if (!this.#updateModeCapability) {
return;
}
}
this.#updateModeCapability = Promise.withResolvers();
this.#mode = mode;
if (mode === AnnotationEditorType.NONE) {
this.setEditingState(false);
this.#disableAll();
this._editorUndoBar?.hide();
this.#updateModeCapability.resolve();
return;
}
if (mode === AnnotationEditorType.SIGNATURE) {
await this.#signatureManager?.loadSignatures();
}
this.setEditingState(true);
await this.#enableAll();
this.unselectAll();
for (const layer of this.#allLayers.values()) {
layer.updateMode(mode);
}
if (!editId) {
if (isFromKeyboard) {
this.addNewEditorFromKeyboard();
}
this.#updateModeCapability.resolve();
return;
}
for (const editor of this.#allEditors.values()) {
if (editor.annotationElementId === editId) {
this.setSelected(editor);
editor.enterInEditMode();
} else {
editor.unselect();
}
}
this.#updateModeCapability.resolve();
}
addNewEditorFromKeyboard() {
if (this.currentLayer.canCreateNewEmptyEditor()) {
this.currentLayer.addNewEditor();
}
}
updateToolbar(mode) {
if (mode === this.#mode) {
return;
}
this._eventBus.dispatch("switchannotationeditormode", {
source: this,
mode
});
}
updateParams(type, value) {
if (!this.#editorTypes) {
return;
}
switch (type) {
case AnnotationEditorParamsType.CREATE:
this.currentLayer.addNewEditor(value);
return;
case AnnotationEditorParamsType.HIGHLIGHT_DEFAULT_COLOR:
this.#mainHighlightColorPicker?.updateColor(value);
break;
case AnnotationEditorParamsType.HIGHLIGHT_SHOW_ALL:
this._eventBus.dispatch("reporttelemetry", {
source: this,
details: {
type: "editing",
data: {
type: "highlight",
action: "toggle_visibility"
}
}
});
(this.#showAllStates ||= new Map()).set(type, value);
this.showAllEditors("highlight", value);
break;
}
for (const editor of this.#selectedEditors) {
editor.updateParams(type, value);
}
for (const editorType of this.#editorTypes) {
editorType.updateDefaultParams(type, value);
}
}
showAllEditors(type, visible, updateButton = false) {
for (const editor of this.#allEditors.values()) {
if (editor.editorType === type) {
editor.show(visible);
}
}
const state = this.#showAllStates?.get(AnnotationEditorParamsType.HIGHLIGHT_SHOW_ALL) ?? true;
if (state !== visible) {
this.#dispatchUpdateUI([[AnnotationEditorParamsType.HIGHLIGHT_SHOW_ALL, visible]]);
}
}
enableWaiting(mustWait = false) {
if (this.#isWaiting === mustWait) {
return;
}
this.#isWaiting = mustWait;
for (const layer of this.#allLayers.values()) {
if (mustWait) {
layer.disableClick();
} else {
layer.enableClick();
}
layer.div.classList.toggle("waiting", mustWait);
}
}
async #enableAll() {
if (!this.#isEnabled) {
this.#isEnabled = true;
const promises = [];
for (const layer of this.#allLayers.values()) {
promises.push(layer.enable());
}
await Promise.all(promises);
for (const editor of this.#allEditors.values()) {
editor.enable();
}
}
}
#disableAll() {
this.unselectAll();
if (this.#isEnabled) {
this.#isEnabled = false;
for (const layer of this.#allLayers.values()) {
layer.disable();
}
for (const editor of this.#allEditors.values()) {
editor.disable();
}
}
}
getEditors(pageIndex) {
const editors = [];
for (const editor of this.#allEditors.values()) {
if (editor.pageIndex === pageIndex) {
editors.push(editor);
}
}
return editors;
}
getEditor(id) {
return this.#allEditors.get(id);
}
addEditor(editor) {
this.#allEditors.set(editor.id, editor);
}
removeEditor(editor) {
if (editor.div.contains(document.activeElement)) {
if (this.#focusMainContainerTimeoutId) {
clearTimeout(this.#focusMainContainerTimeoutId);
}
this.#focusMainContainerTimeoutId = setTimeout(() => {
this.focusMainContainer();
this.#focusMainContainerTimeoutId = null;
}, 0);
}
this.#allEditors.delete(editor.id);
if (editor.annotationElementId) {
this.#missingCanvases?.delete(editor.annotationElementId);
}
this.unselect(editor);
if (!editor.annotationElementId || !this.#deletedAnnotationsElementIds.has(editor.annotationElementId)) {
this.#annotationStorage?.remove(editor.id);
}
}
addDeletedAnnotationElement(editor) {
this.#deletedAnnotationsElementIds.add(editor.annotationElementId);
this.addChangedExistingAnnotation(editor);
editor.deleted = true;
}
isDeletedAnnotationElement(annotationElementId) {
return this.#deletedAnnotationsElementIds.has(annotationElementId);
}
removeDeletedAnnotationElement(editor) {
this.#deletedAnnotationsElementIds.delete(editor.annotationElementId);
this.removeChangedExistingAnnotation(editor);
editor.deleted = false;
}
#addEditorToLayer(editor) {
const layer = this.#allLayers.get(editor.pageIndex);
if (layer) {
layer.addOrRebuild(editor);
} else {
this.addEditor(editor);
this.addToAnnotationStorage(editor);
}
}
setActiveEditor(editor) {
if (this.#activeEditor === editor) {
return;
}
this.#activeEditor = editor;
if (editor) {
this.#dispatchUpdateUI(editor.propertiesToUpdate);
}
}
get #lastSelectedEditor() {
let ed = null;
for (ed of this.#selectedEditors) {}
return ed;
}
updateUI(editor) {
if (this.#lastSelectedEditor === editor) {
this.#dispatchUpdateUI(editor.propertiesToUpdate);
}
}
updateUIForDefaultProperties(editorType) {
this.#dispatchUpdateUI(editorType.defaultPropertiesToUpdate);
}
toggleSelected(editor) {
if (this.#selectedEditors.has(editor)) {
this.#selectedEditors.delete(editor);
editor.unselect();
this.#dispatchUpdateStates({
hasSelectedEditor: this.hasSelection
});
return;
}
this.#selectedEditors.add(editor);
editor.select();
this.#dispatchUpdateUI(editor.propertiesToUpdate);
this.#dispatchUpdateStates({
hasSelectedEditor: true
});
}
setSelected(editor) {
this.#currentDrawingSession?.commitOrRemove();
for (const ed of this.#selectedEditors) {
if (ed !== editor) {
ed.unselect();
}
}
this.#selectedEditors.clear();
this.#selectedEditors.add(editor);
editor.select();
this.#dispatchUpdateUI(editor.propertiesToUpdate);
this.#dispatchUpdateStates({
hasSelectedEditor: true
});
}
isSelected(editor) {
return this.#selectedEditors.has(editor);
}
get firstSelectedEditor() {
return this.#selectedEditors.values().next().value;
}
unselect(editor) {
editor.unselect();
this.#selectedEditors.delete(editor);
this.#dispatchUpdateStates({
hasSelectedEditor: this.hasSelection
});
}
get hasSelection() {
return this.#selectedEditors.size !== 0;
}
get isEnterHandled() {
return this.#selectedEditors.size === 1 && this.firstSelectedEditor.isEnterHandled;
}
undo() {
this.#commandManager.undo();
this.#dispatchUpdateStates({
hasSomethingToUndo: this.#commandManager.hasSomethingToUndo(),
hasSomethingToRedo: true,
isEmpty: this.#isEmpty()
});
this._editorUndoBar?.hide();
}
redo() {
this.#commandManager.redo();
this.#dispatchUpdateStates({
hasSomethingToUndo: true,
hasSomethingToRedo: this.#commandManager.hasSomethingToRedo(),
isEmpty: this.#isEmpty()
});
}
addCommands(params) {
this.#commandManager.add(params);
this.#dispatchUpdateStates({
hasSomethingToUndo: true,
hasSomethingToRedo: false,
isEmpty: this.#isEmpty()
});
}
cleanUndoStack(type) {
this.#commandManager.cleanType(type);
}
#isEmpty() {
if (this.#allEditors.size === 0) {
return true;
}
if (this.#allEditors.size === 1) {
for (const editor of this.#allEditors.values()) {
return editor.isEmpty();
}
}
return false;
}
delete() {
this.commitOrRemove();
const drawingEditor = this.currentLayer?.endDrawingSession(true);
if (!this.hasSelection && !drawingEditor) {
return;
}
const editors = drawingEditor ? [drawingEditor] : [...this.#selectedEditors];
const cmd = () => {
this._editorUndoBar?.show(undo, editors.length === 1 ? editors[0].editorType : editors.length);
for (const editor of editors) {
editor.remove();
}
};
const undo = () => {
for (const editor of editors) {
this.#addEditorToLayer(editor);
}
};
this.addCommands({
cmd,
undo,
mustExec: true
});
}
commitOrRemove() {
this.#activeEditor?.commitOrRemove();
}
hasSomethingToControl() {
return this.#activeEditor || this.hasSelection;
}
#selectEditors(editors) {
for (const editor of this.#selectedEditors) {
editor.unselect();
}
this.#selectedEditors.clear();
for (const editor of editors) {
if (editor.isEmpty()) {
continue;
}
this.#selectedEditors.add(editor);
editor.select();
}
this.#dispatchUpdateStates({
hasSelectedEditor: this.hasSelection
});
}
selectAll() {
for (const editor of this.#selectedEditors) {
editor.commit();
}
this.#selectEditors(this.#allEditors.values());
}
unselectAll() {
if (this.#activeEditor) {
this.#activeEditor.commitOrRemove();
if (this.#mode !== AnnotationEditorType.NONE) {
return;
}
}
if (this.#currentDrawingSession?.commitOrRemove()) {
return;
}
if (!this.hasSelection) {
return;
}
for (const editor of this.#selectedEditors) {
editor.unselect();
}
this.#selectedEditors.clear();
this.#dispatchUpdateStates({
hasSelectedEditor: false
});
}
translateSelectedEditors(x, y, noCommit = false) {
if (!noCommit) {
this.commitOrRemove();
}
if (!this.hasSelection) {
return;
}
this.#translation[0] += x;
this.#translation[1] += y;
const [totalX, totalY] = this.#translation;
const editors = [...this.#selectedEditors];
const TIME_TO_WAIT = 1000;
if (this.#translationTimeoutId) {
clearTimeout(this.#translationTimeoutId);
}
this.#translationTimeoutId = setTimeout(() => {
this.#translationTimeoutId = null;
this.#translation[0] = this.#translation[1] = 0;
this.addCommands({
cmd: () => {
for (const editor of editors) {
if (this.#allEditors.has(editor.id)) {
editor.translateInPage(totalX, totalY);
editor.translationDone();
}
}
},
undo: () => {
for (const editor of editors) {
if (this.#allEditors.has(editor.id)) {
editor.translateInPage(-totalX, -totalY);
editor.translationDone();
}
}
},
mustExec: false
});
}, TIME_TO_WAIT);
for (const editor of editors) {
editor.translateInPage(x, y);
editor.translationDone();
}
}
setUpDragSession() {
if (!this.hasSelection) {
return;
}
this.disableUserSelect(true);
this.#draggingEditors = new Map();
for (const editor of this.#selectedEditors) {
this.#draggingEditors.set(editor, {
savedX: editor.x,
savedY: editor.y,
savedPageIndex: editor.pageIndex,
newX: 0,
newY: 0,
newPageIndex: -1
});
}
}
endDragSession() {
if (!this.#draggingEditors) {
return false;
}
this.disableUserSelect(false);
const map = this.#draggingEditors;
this.#draggingEditors = null;
let mustBeAddedInUndoStack = false;
for (const [{
x,
y,
pageIndex
}, value] of map) {
value.newX = x;
value.newY = y;
value.newPageIndex = pageIndex;
mustBeAddedInUndoStack ||= x !== value.savedX || y !== value.savedY || pageIndex !== value.savedPageIndex;
}
if (!mustBeAddedInUndoStack) {
return false;
}
const move = (editor, x, y, pageIndex) => {
if (this.#allEditors.has(editor.id)) {
const parent = this.#allLayers.get(pageIndex);
if (parent) {
editor._setParentAndPosition(parent, x, y);
} else {
editor.pageIndex = pageIndex;
editor.x = x;
editor.y = y;
}
}
};
this.addCommands({
cmd: () => {
for (const [editor, {
newX,
newY,
newPageIndex
}] of map) {
move(editor, newX, newY, newPageIndex);
}
},
undo: () => {
for (const [editor, {
savedX,
savedY,
savedPageIndex
}] of map) {
move(editor, savedX, savedY, savedPageIndex);
}
},
mustExec: true
});
return true;
}
dragSelectedEditors(tx, ty) {
if (!this.#draggingEditors) {
return;
}
for (const editor of this.#draggingEditors.keys()) {
editor.drag(tx, ty);
}
}
rebuild(editor) {
if (editor.parent === null) {
const parent = this.getLayer(editor.pageIndex);
if (parent) {
parent.changeParent(editor);
parent.addOrRebuild(editor);
} else {
this.addEditor(editor);
this.addToAnnotationStorage(editor);
editor.rebuild();
}
} else {
editor.parent.addOrRebuild(editor);
}
}
get isEditorHandlingKeyboard() {
return this.getActive()?.shouldGetKeyboardEvents() || this.#selectedEditors.size === 1 && this.firstSelectedEditor.shouldGetKeyboardEvents();
}
isActive(editor) {
return this.#activeEditor === editor;
}
getActive() {
return this.#activeEditor;
}
getMode() {
return this.#mode;
}
get imageManager() {
return shadow(this, "imageManager", new ImageManager());
}
getSelectionBoxes(textLayer) {
if (!textLayer) {
return null;
}
const selection = document.getSelection();
for (let i = 0, ii = selection.rangeCount; i < ii; i++) {
if (!textLayer.contains(selection.getRangeAt(i).commonAncestorContainer)) {
return null;
}
}
const {
x: layerX,
y: layerY,
width: parentWidth,
height: parentHeight
} = textLayer.getBoundingClientRect();
let rotator;
switch (textLayer.getAttribute("data-main-rotation")) {
case "90":
rotator = (x, y, w, h) => ({
x: (y - layerY) / parentHeight,
y: 1 - (x + w - layerX) / parentWidth,
width: h / parentHeight,
height: w / parentWidth
});
break;
case "180":
rotator = (x, y, w, h) => ({
x: 1 - (x + w - layerX) / parentWidth,
y: 1 - (y + h - layerY) / parentHeight,
width: w / parentWidth,
height: h / parentHeight
});
break;
case "270":
rotator = (x, y, w, h) => ({
x: 1 - (y + h - layerY) / parentHeight,
y: (x - layerX) / parentWidth,
width: h / parentHeight,
height: w / parentWidth
});
break;
default:
rotator = (x, y, w, h) => ({
x: (x - layerX) / parentWidth,
y: (y - layerY) / parentHeight,
width: w / parentWidth,
height: h / parentHeight
});
break;
}
const boxes = [];
for (let i = 0, ii = selection.rangeCount; i < ii; i++) {
const range = selection.getRangeAt(i);
if (range.collapsed) {
continue;
}
for (const {
x,
y,
width,
height
} of range.getClientRects()) {
if (width === 0 || height === 0) {
continue;
}
boxes.push(rotator(x, y, width, height));
}
}
return boxes.length === 0 ? null : boxes;
}
addChangedExistingAnnotation({
annotationElementId,
id
}) {
(this.#changedExistingAnnotations ||= new Map()).set(annotationElementId, id);
}
removeChangedExistingAnnotation({
annotationElementId
}) {
this.#changedExistingAnnotations?.delete(annotationElementId);
}
renderAnnotationElement(annotation) {
const editorId = this.#changedExistingAnnotations?.get(annotation.data.id);
if (!editorId) {
return;
}
const editor = this.#annotationStorage.getRawValue(editorId);
if (!editor) {
return;
}
if (this.#mode === AnnotationEditorType.NONE && !editor.hasBeenModified) {
return;
}
editor.renderAnnotationElement(annotation);
}
setMissingCanvas(annotationId, annotationElementId, canvas) {
const editor = this.#missingCanvases?.get(annotationId);
if (!editor) {
return;
}
editor.setCanvas(annotationElementId, canvas);
this.#missingCanvases.delete(annotationId);
}
addMissingCanvas(annotationId, editor) {
(this.#missingCanvases ||= new Map()).set(annotationId, editor);
}
}
;// ./src/display/editor/alt_text.js
class AltText {
#altText = null;
#altTextDecorative = false;
#altTextButton = null;
#altTextButtonLabel = null;
#altTextTooltip = null;
#altTextTooltipTimeout = null;
#altTextWasFromKeyBoard = false;
#badge = null;
#editor = null;
#guessedText = null;
#textWithDisclaimer = null;
#useNewAltTextFlow = false;
static #l10nNewButton = null;
static _l10n = null;
constructor(editor) {
this.#editor = editor;
this.#useNewAltTextFlow = editor._uiManager.useNewAltTextFlow;
AltText.#l10nNewButton ||= Object.freeze({
added: "pdfjs-editor-new-alt-text-added-button",
"added-label": "pdfjs-editor-new-alt-text-added-button-label",
missing: "pdfjs-editor-new-alt-text-missing-button",
"missing-label": "pdfjs-editor-new-alt-text-missing-button-label",
review: "pdfjs-editor-new-alt-text-to-review-button",
"review-label": "pdfjs-editor-new-alt-text-to-review-button-label"
});
}
static initialize(l10n) {
AltText._l10n ??= l10n;
}
async render() {
const altText = this.#altTextButton = document.createElement("button");
altText.className = "altText";
altText.tabIndex = "0";
const label = this.#altTextButtonLabel = document.createElement("span");
altText.append(label);
if (this.#useNewAltTextFlow) {
altText.classList.add("new");
altText.setAttribute("data-l10n-id", AltText.#l10nNewButton.missing);
label.setAttribute("data-l10n-id", AltText.#l10nNewButton["missing-label"]);
} else {
altText.setAttribute("data-l10n-id", "pdfjs-editor-alt-text-button");
label.setAttribute("data-l10n-id", "pdfjs-editor-alt-text-button-label");
}
const signal = this.#editor._uiManager._signal;
altText.addEventListener("contextmenu", noContextMenu, {
signal
});
altText.addEventListener("pointerdown", event => event.stopPropagation(), {
signal
});
const onClick = event => {
event.preventDefault();
this.#editor._uiManager.editAltText(this.#editor);
if (this.#useNewAltTextFlow) {
this.#editor._reportTelemetry({
action: "pdfjs.image.alt_text.image_status_label_clicked",
data: {
label: this.#label
}
});
}
};
altText.addEventListener("click", onClick, {
capture: true,
signal
});
altText.addEventListener("keydown", event => {
if (event.target === altText && event.key === "Enter") {
this.#altTextWasFromKeyBoard = true;
onClick(event);
}
}, {
signal
});
await this.#setState();
return altText;
}
get #label() {
return this.#altText && "added" || this.#altText === null && this.guessedText && "review" || "missing";
}
finish() {
if (!this.#altTextButton) {
return;
}
this.#altTextButton.focus({
focusVisible: this.#altTextWasFromKeyBoard
});
this.#altTextWasFromKeyBoard = false;
}
isEmpty() {
if (this.#useNewAltTextFlow) {
return this.#altText === null;
}
return !this.#altText && !this.#altTextDecorative;
}
hasData() {
if (this.#useNewAltTextFlow) {
return this.#altText !== null || !!this.#guessedText;
}
return this.isEmpty();
}
get guessedText() {
return this.#guessedText;
}
async setGuessedText(guessedText) {
if (this.#altText !== null) {
return;
}
this.#guessedText = guessedText;
this.#textWithDisclaimer = await AltText._l10n.get("pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer", {
generatedAltText: guessedText
});
this.#setState();
}
toggleAltTextBadge(visibility = false) {
if (!this.#useNewAltTextFlow || this.#altText) {
this.#badge?.remove();
this.#badge = null;
return;
}
if (!this.#badge) {
const badge = this.#badge = document.createElement("div");
badge.className = "noAltTextBadge";
this.#editor.div.append(badge);
}
this.#badge.classList.toggle("hidden", !visibility);
}
serialize(isForCopying) {
let altText = this.#altText;
if (!isForCopying && this.#guessedText === altText) {
altText = this.#textWithDisclaimer;
}
return {
altText,
decorative: this.#altTextDecorative,
guessedText: this.#guessedText,
textWithDisclaimer: this.#textWithDisclaimer
};
}
get data() {
return {
altText: this.#altText,
decorative: this.#altTextDecorative
};
}
set data({
altText,
decorative,
guessedText,
textWithDisclaimer,
cancel = false
}) {
if (guessedText) {
this.#guessedText = guessedText;
this.#textWithDisclaimer = textWithDisclaimer;
}
if (this.#altText === altText && this.#altTextDecorative === decorative) {
return;
}
if (!cancel) {
this.#altText = altText;
this.#altTextDecorative = decorative;
}
this.#setState();
}
toggle(enabled = false) {
if (!this.#altTextButton) {
return;
}
if (!enabled && this.#altTextTooltipTimeout) {
clearTimeout(this.#altTextTooltipTimeout);
this.#altTextTooltipTimeout = null;
}
this.#altTextButton.disabled = !enabled;
}
shown() {
this.#editor._reportTelemetry({
action: "pdfjs.image.alt_text.image_status_label_displayed",
data: {
label: this.#label
}
});
}
destroy() {
this.#altTextButton?.remove();
this.#altTextButton = null;
this.#altTextButtonLabel = null;
this.#altTextTooltip = null;
this.#badge?.remove();
this.#badge = null;
}
async #setState() {
const button = this.#altTextButton;
if (!button) {
return;
}
if (this.#useNewAltTextFlow) {
button.classList.toggle("done", !!this.#altText);
button.setAttribute("data-l10n-id", AltText.#l10nNewButton[this.#label]);
this.#altTextButtonLabel?.setAttribute("data-l10n-id", AltText.#l10nNewButton[`${this.#label}-label`]);
if (!this.#altText) {
this.#altTextTooltip?.remove();
return;
}
} else {
if (!this.#altText && !this.#altTextDecorative) {
button.classList.remove("done");
this.#altTextTooltip?.remove();
return;
}
button.classList.add("done");
button.setAttribute("data-l10n-id", "pdfjs-editor-alt-text-edit-button");
}
let tooltip = this.#altTextTooltip;
if (!tooltip) {
this.#altTextTooltip = tooltip = document.createElement("span");
tooltip.className = "tooltip";
tooltip.setAttribute("role", "tooltip");
tooltip.id = `alt-text-tooltip-${this.#editor.id}`;
const DELAY_TO_SHOW_TOOLTIP = 100;
const signal = this.#editor._uiManager._signal;
signal.addEventListener("abort", () => {
clearTimeout(this.#altTextTooltipTimeout);
this.#altTextTooltipTimeout = null;
}, {
once: true
});
button.addEventListener("mouseenter", () => {
this.#altTextTooltipTimeout = setTimeout(() => {
this.#altTextTooltipTimeout = null;
this.#altTextTooltip.classList.add("show");
this.#editor._reportTelemetry({
action: "alt_text_tooltip"
});
}, DELAY_TO_SHOW_TOOLTIP);
}, {
signal
});
button.addEventListener("mouseleave", () => {
if (this.#altTextTooltipTimeout) {
clearTimeout(this.#altTextTooltipTimeout);
this.#altTextTooltipTimeout = null;
}
this.#altTextTooltip?.classList.remove("show");
}, {
signal
});
}
if (this.#altTextDecorative) {
tooltip.setAttribute("data-l10n-id", "pdfjs-editor-alt-text-decorative-tooltip");
} else {
tooltip.removeAttribute("data-l10n-id");
tooltip.textContent = this.#altText;
}
if (!tooltip.parentNode) {
button.append(tooltip);
}
const element = this.#editor.getElementForAltText();
element?.setAttribute("aria-describedby", tooltip.id);
}
}
;// ./src/display/touch_manager.js
class TouchManager {
#container;
#isPinching = false;
#isPinchingStopped = null;
#isPinchingDisabled;
#onPinchStart;
#onPinching;
#onPinchEnd;
#pointerDownAC = null;
#signal;
#touchInfo = null;
#touchManagerAC;
#touchMoveAC = null;
constructor({
container,
isPinchingDisabled = null,
isPinchingStopped = null,
onPinchStart = null,
onPinching = null,
onPinchEnd = null,
signal
}) {
this.#container = container;
this.#isPinchingStopped = isPinchingStopped;
this.#isPinchingDisabled = isPinchingDisabled;
this.#onPinchStart = onPinchStart;
this.#onPinching = onPinching;
this.#onPinchEnd = onPinchEnd;
this.#touchManagerAC = new AbortController();
this.#signal = AbortSignal.any([signal, this.#touchManagerAC.signal]);
container.addEventListener("touchstart", this.#onTouchStart.bind(this), {
passive: false,
signal: this.#signal
});
}
get MIN_TOUCH_DISTANCE_TO_PINCH() {
return 35 / OutputScale.pixelRatio;
}
#onTouchStart(evt) {
if (this.#isPinchingDisabled?.()) {
return;
}
if (evt.touches.length === 1) {
if (this.#pointerDownAC) {
return;
}
const pointerDownAC = this.#pointerDownAC = new AbortController();
const signal = AbortSignal.any([this.#signal, pointerDownAC.signal]);
const container = this.#container;
const opts = {
capture: true,
signal,
passive: false
};
const cancelPointerDown = e => {
if (e.pointerType === "touch") {
this.#pointerDownAC?.abort();
this.#pointerDownAC = null;
}
};
container.addEventListener("pointerdown", e => {
if (e.pointerType === "touch") {
stopEvent(e);
cancelPointerDown(e);
}
}, opts);
container.addEventListener("pointerup", cancelPointerDown, opts);
container.addEventListener("pointercancel", cancelPointerDown, opts);
return;
}
if (!this.#touchMoveAC) {
this.#touchMoveAC = new AbortController();
const signal = AbortSignal.any([this.#signal, this.#touchMoveAC.signal]);
const container = this.#container;
const opt = {
signal,
capture: false,
passive: false
};
container.addEventListener("touchmove", this.#onTouchMove.bind(this), opt);
const onTouchEnd = this.#onTouchEnd.bind(this);
container.addEventListener("touchend", onTouchEnd, opt);
container.addEventListener("touchcancel", onTouchEnd, opt);
opt.capture = true;
container.addEventListener("pointerdown", stopEvent, opt);
container.addEventListener("pointermove", stopEvent, opt);
container.addEventListener("pointercancel", stopEvent, opt);
container.addEventListener("pointerup", stopEvent, opt);
this.#onPinchStart?.();
}
stopEvent(evt);
if (evt.touches.length !== 2 || this.#isPinchingStopped?.()) {
this.#touchInfo = null;
return;
}
let [touch0, touch1] = evt.touches;
if (touch0.identifier > touch1.identifier) {
[touch0, touch1] = [touch1, touch0];
}
this.#touchInfo = {
touch0X: touch0.screenX,
touch0Y: touch0.screenY,
touch1X: touch1.screenX,
touch1Y: touch1.screenY
};
}
#onTouchMove(evt) {
if (!this.#touchInfo || evt.touches.length !== 2) {
return;
}
stopEvent(evt);
let [touch0, touch1] = evt.touches;
if (touch0.identifier > touch1.identifier) {
[touch0, touch1] = [touch1, touch0];
}
const {
screenX: screen0X,
screenY: screen0Y
} = touch0;
const {
screenX: screen1X,
screenY: screen1Y
} = touch1;
const touchInfo = this.#touchInfo;
const {
touch0X: pTouch0X,
touch0Y: pTouch0Y,
touch1X: pTouch1X,
touch1Y: pTouch1Y
} = touchInfo;
const prevGapX = pTouch1X - pTouch0X;
const prevGapY = pTouch1Y - pTouch0Y;
const currGapX = screen1X - screen0X;
const currGapY = screen1Y - screen0Y;
const distance = Math.hypot(currGapX, currGapY) || 1;
const pDistance = Math.hypot(prevGapX, prevGapY) || 1;
if (!this.#isPinching && Math.abs(pDistance - distance) <= TouchManager.MIN_TOUCH_DISTANCE_TO_PINCH) {
return;
}
touchInfo.touch0X = screen0X;
touchInfo.touch0Y = screen0Y;
touchInfo.touch1X = screen1X;
touchInfo.touch1Y = screen1Y;
if (!this.#isPinching) {
this.#isPinching = true;
return;
}
const origin = [(screen0X + screen1X) / 2, (screen0Y + screen1Y) / 2];
this.#onPinching?.(origin, pDistance, distance);
}
#onTouchEnd(evt) {
if (evt.touches.length >= 2) {
return;
}
this.#touchMoveAC.abort();
this.#touchMoveAC = null;
this.#onPinchEnd?.();
if (!this.#touchInfo) {
return;
}
stopEvent(evt);
this.#touchInfo = null;
this.#isPinching = false;
}
destroy() {
this.#touchManagerAC?.abort();
this.#touchManagerAC = null;
this.#pointerDownAC?.abort();
this.#pointerDownAC = null;
}
}
;// ./src/display/editor/editor.js
class AnnotationEditor {
#accessibilityData = null;
#allResizerDivs = null;
#altText = null;
#disabled = false;
#dragPointerId = null;
#dragPointerType = "";
#keepAspectRatio = false;
#resizersDiv = null;
#lastPointerCoords = null;
#savedDimensions = null;
#focusAC = null;
#focusedResizerName = "";
#hasBeenClicked = false;
#initialRect = null;
#isEditing = false;
#isInEditMode = false;
#isResizerEnabledForKeyboard = false;
#moveInDOMTimeout = null;
#prevDragX = 0;
#prevDragY = 0;
#telemetryTimeouts = null;
#touchManager = null;
_isCopy = false;
_editToolbar = null;
_initialOptions = Object.create(null);
_initialData = null;
_isVisible = true;
_uiManager = null;
_focusEventsAllowed = true;
static _l10n = null;
static _l10nResizer = null;
#isDraggable = false;
#zIndex = AnnotationEditor._zIndex++;
static _borderLineWidth = -1;
static _colorManager = new ColorManager();
static _zIndex = 1;
static _telemetryTimeout = 1000;
static get _resizerKeyboardManager() {
const resize = AnnotationEditor.prototype._resizeWithKeyboard;
const small = AnnotationEditorUIManager.TRANSLATE_SMALL;
const big = AnnotationEditorUIManager.TRANSLATE_BIG;
return shadow(this, "_resizerKeyboardManager", new KeyboardManager([[["ArrowLeft", "mac+ArrowLeft"], resize, {
args: [-small, 0]
}], [["ctrl+ArrowLeft", "mac+shift+ArrowLeft"], resize, {
args: [-big, 0]
}], [["ArrowRight", "mac+ArrowRight"], resize, {
args: [small, 0]
}], [["ctrl+ArrowRight", "mac+shift+ArrowRight"], resize, {
args: [big, 0]
}], [["ArrowUp", "mac+ArrowUp"], resize, {
args: [0, -small]
}], [["ctrl+ArrowUp", "mac+shift+ArrowUp"], resize, {
args: [0, -big]
}], [["ArrowDown", "mac+ArrowDown"], resize, {
args: [0, small]
}], [["ctrl+ArrowDown", "mac+shift+ArrowDown"], resize, {
args: [0, big]
}], [["Escape", "mac+Escape"], AnnotationEditor.prototype._stopResizingWithKeyboard]]));
}
constructor(parameters) {
this.parent = parameters.parent;
this.id = parameters.id;
this.width = this.height = null;
this.pageIndex = parameters.parent.pageIndex;
this.name = parameters.name;
this.div = null;
this._uiManager = parameters.uiManager;
this.annotationElementId = null;
this._willKeepAspectRatio = false;
this._initialOptions.isCentered = parameters.isCentered;
this._structTreeParentId = null;
const {
rotation,
rawDims: {
pageWidth,
pageHeight,
pageX,
pageY
}
} = this.parent.viewport;
this.rotation = rotation;
this.pageRotation = (360 + rotation - this._uiManager.viewParameters.rotation) % 360;
this.pageDimensions = [pageWidth, pageHeight];
this.pageTranslation = [pageX, pageY];
const [width, height] = this.parentDimensions;
this.x = parameters.x / width;
this.y = parameters.y / height;
this.isAttachedToDOM = false;
this.deleted = false;
}
get editorType() {
return Object.getPrototypeOf(this).constructor._type;
}
static get isDrawer() {
return false;
}
static get _defaultLineColor() {
return shadow(this, "_defaultLineColor", this._colorManager.getHexCode("CanvasText"));
}
static deleteAnnotationElement(editor) {
const fakeEditor = new FakeEditor({
id: editor.parent.getNextId(),
parent: editor.parent,
uiManager: editor._uiManager
});
fakeEditor.annotationElementId = editor.annotationElementId;
fakeEditor.deleted = true;
fakeEditor._uiManager.addToAnnotationStorage(fakeEditor);
}
static initialize(l10n, _uiManager) {
AnnotationEditor._l10n ??= l10n;
AnnotationEditor._l10nResizer ||= Object.freeze({
topLeft: "pdfjs-editor-resizer-top-left",
topMiddle: "pdfjs-editor-resizer-top-middle",
topRight: "pdfjs-editor-resizer-top-right",
middleRight: "pdfjs-editor-resizer-middle-right",
bottomRight: "pdfjs-editor-resizer-bottom-right",
bottomMiddle: "pdfjs-editor-resizer-bottom-middle",
bottomLeft: "pdfjs-editor-resizer-bottom-left",
middleLeft: "pdfjs-editor-resizer-middle-left"
});
if (AnnotationEditor._borderLineWidth !== -1) {
return;
}
const style = getComputedStyle(document.documentElement);
AnnotationEditor._borderLineWidth = parseFloat(style.getPropertyValue("--outline-width")) || 0;
}
static updateDefaultParams(_type, _value) {}
static get defaultPropertiesToUpdate() {
return [];
}
static isHandlingMimeForPasting(mime) {
return false;
}
static paste(item, parent) {
unreachable("Not implemented");
}
get propertiesToUpdate() {
return [];
}
get _isDraggable() {
return this.#isDraggable;
}
set _isDraggable(value) {
this.#isDraggable = value;
this.div?.classList.toggle("draggable", value);
}
get isEnterHandled() {
return true;
}
center() {
const [pageWidth, pageHeight] = this.pageDimensions;
switch (this.parentRotation) {
case 90:
this.x -= this.height * pageHeight / (pageWidth * 2);
this.y += this.width * pageWidth / (pageHeight * 2);
break;
case 180:
this.x += this.width / 2;
this.y += this.height / 2;
break;
case 270:
this.x += this.height * pageHeight / (pageWidth * 2);
this.y -= this.width * pageWidth / (pageHeight * 2);
break;
default:
this.x -= this.width / 2;
this.y -= this.height / 2;
break;
}
this.fixAndSetPosition();
}
addCommands(params) {
this._uiManager.addCommands(params);
}
get currentLayer() {
return this._uiManager.currentLayer;
}
setInBackground() {
this.div.style.zIndex = 0;
}
setInForeground() {
this.div.style.zIndex = this.#zIndex;
}
setParent(parent) {
if (parent !== null) {
this.pageIndex = parent.pageIndex;
this.pageDimensions = parent.pageDimensions;
} else {
this.#stopResizing();
}
this.parent = parent;
}
focusin(event) {
if (!this._focusEventsAllowed) {
return;
}
if (!this.#hasBeenClicked) {
this.parent.setSelected(this);
} else {
this.#hasBeenClicked = false;
}
}
focusout(event) {
if (!this._focusEventsAllowed) {
return;
}
if (!this.isAttachedToDOM) {
return;
}
const target = event.relatedTarget;
if (target?.closest(`#${this.id}`)) {
return;
}
event.preventDefault();
if (!this.parent?.isMultipleSelection) {
this.commitOrRemove();
}
}
commitOrRemove() {
if (this.isEmpty()) {
this.remove();
} else {
this.commit();
}
}
commit() {
this.addToAnnotationStorage();
}
addToAnnotationStorage() {
this._uiManager.addToAnnotationStorage(this);
}
setAt(x, y, tx, ty) {
const [width, height] = this.parentDimensions;
[tx, ty] = this.screenToPageTranslation(tx, ty);
this.x = (x + tx) / width;
this.y = (y + ty) / height;
this.fixAndSetPosition();
}
_moveAfterPaste(baseX, baseY) {
const [parentWidth, parentHeight] = this.parentDimensions;
this.setAt(baseX * parentWidth, baseY * parentHeight, this.width * parentWidth, this.height * parentHeight);
this._onTranslated();
}
#translate([width, height], x, y) {
[x, y] = this.screenToPageTranslation(x, y);
this.x += x / width;
this.y += y / height;
this._onTranslating(this.x, this.y);
this.fixAndSetPosition();
}
translate(x, y) {
this.#translate(this.parentDimensions, x, y);
}
translateInPage(x, y) {
this.#initialRect ||= [this.x, this.y, this.width, this.height];
this.#translate(this.pageDimensions, x, y);
this.div.scrollIntoView({
block: "nearest"
});
}
translationDone() {
this._onTranslated(this.x, this.y);
}
drag(tx, ty) {
this.#initialRect ||= [this.x, this.y, this.width, this.height];
const {
div,
parentDimensions: [parentWidth, parentHeight]
} = this;
this.x += tx / parentWidth;
this.y += ty / parentHeight;
if (this.parent && (this.x < 0 || this.x > 1 || this.y < 0 || this.y > 1)) {
const {
x,
y
} = this.div.getBoundingClientRect();
if (this.parent.findNewParent(this, x, y)) {
this.x -= Math.floor(this.x);
this.y -= Math.floor(this.y);
}
}
let {
x,
y
} = this;
const [bx, by] = this.getBaseTranslation();
x += bx;
y += by;
const {
style
} = div;
style.left = `${(100 * x).toFixed(2)}%`;
style.top = `${(100 * y).toFixed(2)}%`;
this._onTranslating(x, y);
div.scrollIntoView({
block: "nearest"
});
}
_onTranslating(x, y) {}
_onTranslated(x, y) {}
get _hasBeenMoved() {
return !!this.#initialRect && (this.#initialRect[0] !== this.x || this.#initialRect[1] !== this.y);
}
get _hasBeenResized() {
return !!this.#initialRect && (this.#initialRect[2] !== this.width || this.#initialRect[3] !== this.height);
}
getBaseTranslation() {
const [parentWidth, parentHeight] = this.parentDimensions;
const {
_borderLineWidth
} = AnnotationEditor;
const x = _borderLineWidth / parentWidth;
const y = _borderLineWidth / parentHeight;
switch (this.rotation) {
case 90:
return [-x, y];
case 180:
return [x, y];
case 270:
return [x, -y];
default:
return [-x, -y];
}
}
get _mustFixPosition() {
return true;
}
fixAndSetPosition(rotation = this.rotation) {
const {
div: {
style
},
pageDimensions: [pageWidth, pageHeight]
} = this;
let {
x,
y,
width,
height
} = this;
width *= pageWidth;
height *= pageHeight;
x *= pageWidth;
y *= pageHeight;
if (this._mustFixPosition) {
switch (rotation) {
case 0:
x = MathClamp(x, 0, pageWidth - width);
y = MathClamp(y, 0, pageHeight - height);
break;
case 90:
x = MathClamp(x, 0, pageWidth - height);
y = MathClamp(y, width, pageHeight);
break;
case 180:
x = MathClamp(x, width, pageWidth);
y = MathClamp(y, height, pageHeight);
break;
case 270:
x = MathClamp(x, height, pageWidth);
y = MathClamp(y, 0, pageHeight - width);
break;
}
}
this.x = x /= pageWidth;
this.y = y /= pageHeight;
const [bx, by] = this.getBaseTranslation();
x += bx;
y += by;
style.left = `${(100 * x).toFixed(2)}%`;
style.top = `${(100 * y).toFixed(2)}%`;
this.moveInDOM();
}
static #rotatePoint(x, y, angle) {
switch (angle) {
case 90:
return [y, -x];
case 180:
return [-x, -y];
case 270:
return [-y, x];
default:
return [x, y];
}
}
screenToPageTranslation(x, y) {
return AnnotationEditor.#rotatePoint(x, y, this.parentRotation);
}
pageTranslationToScreen(x, y) {
return AnnotationEditor.#rotatePoint(x, y, 360 - this.parentRotation);
}
#getRotationMatrix(rotation) {
switch (rotation) {
case 90:
{
const [pageWidth, pageHeight] = this.pageDimensions;
return [0, -pageWidth / pageHeight, pageHeight / pageWidth, 0];
}
case 180:
return [-1, 0, 0, -1];
case 270:
{
const [pageWidth, pageHeight] = this.pageDimensions;
return [0, pageWidth / pageHeight, -pageHeight / pageWidth, 0];
}
default:
return [1, 0, 0, 1];
}
}
get parentScale() {
return this._uiManager.viewParameters.realScale;
}
get parentRotation() {
return (this._uiManager.viewParameters.rotation + this.pageRotation) % 360;
}
get parentDimensions() {
const {
parentScale,
pageDimensions: [pageWidth, pageHeight]
} = this;
return [pageWidth * parentScale, pageHeight * parentScale];
}
setDims(width, height) {
const [parentWidth, parentHeight] = this.parentDimensions;
const {
style
} = this.div;
style.width = `${(100 * width / parentWidth).toFixed(2)}%`;
if (!this.#keepAspectRatio) {
style.height = `${(100 * height / parentHeight).toFixed(2)}%`;
}
}
fixDims() {
const {
style
} = this.div;
const {
height,
width
} = style;
const widthPercent = width.endsWith("%");
const heightPercent = !this.#keepAspectRatio && height.endsWith("%");
if (widthPercent && heightPercent) {
return;
}
const [parentWidth, parentHeight] = this.parentDimensions;
if (!widthPercent) {
style.width = `${(100 * parseFloat(width) / parentWidth).toFixed(2)}%`;
}
if (!this.#keepAspectRatio && !heightPercent) {
style.height = `${(100 * parseFloat(height) / parentHeight).toFixed(2)}%`;
}
}
getInitialTranslation() {
return [0, 0];
}
#createResizers() {
if (this.#resizersDiv) {
return;
}
this.#resizersDiv = document.createElement("div");
this.#resizersDiv.classList.add("resizers");
const classes = this._willKeepAspectRatio ? ["topLeft", "topRight", "bottomRight", "bottomLeft"] : ["topLeft", "topMiddle", "topRight", "middleRight", "bottomRight", "bottomMiddle", "bottomLeft", "middleLeft"];
const signal = this._uiManager._signal;
for (const name of classes) {
const div = document.createElement("div");
this.#resizersDiv.append(div);
div.classList.add("resizer", name);
div.setAttribute("data-resizer-name", name);
div.addEventListener("pointerdown", this.#resizerPointerdown.bind(this, name), {
signal
});
div.addEventListener("contextmenu", noContextMenu, {
signal
});
div.tabIndex = -1;
}
this.div.prepend(this.#resizersDiv);
}
#resizerPointerdown(name, event) {
event.preventDefault();
const {
isMac
} = util_FeatureTest.platform;
if (event.button !== 0 || event.ctrlKey && isMac) {
return;
}
this.#altText?.toggle(false);
const savedDraggable = this._isDraggable;
this._isDraggable = false;
this.#lastPointerCoords = [event.screenX, event.screenY];
const ac = new AbortController();
const signal = this._uiManager.combinedSignal(ac);
this.parent.togglePointerEvents(false);
window.addEventListener("pointermove", this.#resizerPointermove.bind(this, name), {
passive: true,
capture: true,
signal
});
window.addEventListener("touchmove", stopEvent, {
passive: false,
signal
});
window.addEventListener("contextmenu", noContextMenu, {
signal
});
this.#savedDimensions = {
savedX: this.x,
savedY: this.y,
savedWidth: this.width,
savedHeight: this.height
};
const savedParentCursor = this.parent.div.style.cursor;
const savedCursor = this.div.style.cursor;
this.div.style.cursor = this.parent.div.style.cursor = window.getComputedStyle(event.target).cursor;
const pointerUpCallback = () => {
ac.abort();
this.parent.togglePointerEvents(true);
this.#altText?.toggle(true);
this._isDraggable = savedDraggable;
this.parent.div.style.cursor = savedParentCursor;
this.div.style.cursor = savedCursor;
this.#addResizeToUndoStack();
};
window.addEventListener("pointerup", pointerUpCallback, {
signal
});
window.addEventListener("blur", pointerUpCallback, {
signal
});
}
#resize(x, y, width, height) {
this.width = width;
this.height = height;
this.x = x;
this.y = y;
const [parentWidth, parentHeight] = this.parentDimensions;
this.setDims(parentWidth * width, parentHeight * height);
this.fixAndSetPosition();
this._onResized();
}
_onResized() {}
#addResizeToUndoStack() {
if (!this.#savedDimensions) {
return;
}
const {
savedX,
savedY,
savedWidth,
savedHeight
} = this.#savedDimensions;
this.#savedDimensions = null;
const newX = this.x;
const newY = this.y;
const newWidth = this.width;
const newHeight = this.height;
if (newX === savedX && newY === savedY && newWidth === savedWidth && newHeight === savedHeight) {
return;
}
this.addCommands({
cmd: this.#resize.bind(this, newX, newY, newWidth, newHeight),
undo: this.#resize.bind(this, savedX, savedY, savedWidth, savedHeight),
mustExec: true
});
}
static _round(x) {
return Math.round(x * 10000) / 10000;
}
#resizerPointermove(name, event) {
const [parentWidth, parentHeight] = this.parentDimensions;
const savedX = this.x;
const savedY = this.y;
const savedWidth = this.width;
const savedHeight = this.height;
const minWidth = AnnotationEditor.MIN_SIZE / parentWidth;
const minHeight = AnnotationEditor.MIN_SIZE / parentHeight;
const rotationMatrix = this.#getRotationMatrix(this.rotation);
const transf = (x, y) => [rotationMatrix[0] * x + rotationMatrix[2] * y, rotationMatrix[1] * x + rotationMatrix[3] * y];
const invRotationMatrix = this.#getRotationMatrix(360 - this.rotation);
const invTransf = (x, y) => [invRotationMatrix[0] * x + invRotationMatrix[2] * y, invRotationMatrix[1] * x + invRotationMatrix[3] * y];
let getPoint;
let getOpposite;
let isDiagonal = false;
let isHorizontal = false;
switch (name) {
case "topLeft":
isDiagonal = true;
getPoint = (w, h) => [0, 0];
getOpposite = (w, h) => [w, h];
break;
case "topMiddle":
getPoint = (w, h) => [w / 2, 0];
getOpposite = (w, h) => [w / 2, h];
break;
case "topRight":
isDiagonal = true;
getPoint = (w, h) => [w, 0];
getOpposite = (w, h) => [0, h];
break;
case "middleRight":
isHorizontal = true;
getPoint = (w, h) => [w, h / 2];
getOpposite = (w, h) => [0, h / 2];
break;
case "bottomRight":
isDiagonal = true;
getPoint = (w, h) => [w, h];
getOpposite = (w, h) => [0, 0];
break;
case "bottomMiddle":
getPoint = (w, h) => [w / 2, h];
getOpposite = (w, h) => [w / 2, 0];
break;
case "bottomLeft":
isDiagonal = true;
getPoint = (w, h) => [0, h];
getOpposite = (w, h) => [w, 0];
break;
case "middleLeft":
isHorizontal = true;
getPoint = (w, h) => [0, h / 2];
getOpposite = (w, h) => [w, h / 2];
break;
}
const point = getPoint(savedWidth, savedHeight);
const oppositePoint = getOpposite(savedWidth, savedHeight);
let transfOppositePoint = transf(...oppositePoint);
const oppositeX = AnnotationEditor._round(savedX + transfOppositePoint[0]);
const oppositeY = AnnotationEditor._round(savedY + transfOppositePoint[1]);
let ratioX = 1;
let ratioY = 1;
let deltaX, deltaY;
if (!event.fromKeyboard) {
const {
screenX,
screenY
} = event;
const [lastScreenX, lastScreenY] = this.#lastPointerCoords;
[deltaX, deltaY] = this.screenToPageTranslation(screenX - lastScreenX, screenY - lastScreenY);
this.#lastPointerCoords[0] = screenX;
this.#lastPointerCoords[1] = screenY;
} else {
({
deltaX,
deltaY
} = event);
}
[deltaX, deltaY] = invTransf(deltaX / parentWidth, deltaY / parentHeight);
if (isDiagonal) {
const oldDiag = Math.hypot(savedWidth, savedHeight);
ratioX = ratioY = Math.max(Math.min(Math.hypot(oppositePoint[0] - point[0] - deltaX, oppositePoint[1] - point[1] - deltaY) / oldDiag, 1 / savedWidth, 1 / savedHeight), minWidth / savedWidth, minHeight / savedHeight);
} else if (isHorizontal) {
ratioX = MathClamp(Math.abs(oppositePoint[0] - point[0] - deltaX), minWidth, 1) / savedWidth;
} else {
ratioY = MathClamp(Math.abs(oppositePoint[1] - point[1] - deltaY), minHeight, 1) / savedHeight;
}
const newWidth = AnnotationEditor._round(savedWidth * ratioX);
const newHeight = AnnotationEditor._round(savedHeight * ratioY);
transfOppositePoint = transf(...getOpposite(newWidth, newHeight));
const newX = oppositeX - transfOppositePoint[0];
const newY = oppositeY - transfOppositePoint[1];
this.#initialRect ||= [this.x, this.y, this.width, this.height];
this.width = newWidth;
this.height = newHeight;
this.x = newX;
this.y = newY;
this.setDims(parentWidth * newWidth, parentHeight * newHeight);
this.fixAndSetPosition();
this._onResizing();
}
_onResizing() {}
altTextFinish() {
this.#altText?.finish();
}
async addEditToolbar() {
if (this._editToolbar || this.#isInEditMode) {
return this._editToolbar;
}
this._editToolbar = new EditorToolbar(this);
this.div.append(this._editToolbar.render());
if (this.#altText) {
await this._editToolbar.addAltText(this.#altText);
}
return this._editToolbar;
}
removeEditToolbar() {
if (!this._editToolbar) {
return;
}
this._editToolbar.remove();
this._editToolbar = null;
this.#altText?.destroy();
}
addContainer(container) {
const editToolbarDiv = this._editToolbar?.div;
if (editToolbarDiv) {
editToolbarDiv.before(container);
} else {
this.div.append(container);
}
}
getClientDimensions() {
return this.div.getBoundingClientRect();
}
async addAltTextButton() {
if (this.#altText) {
return;
}
AltText.initialize(AnnotationEditor._l10n);
this.#altText = new AltText(this);
if (this.#accessibilityData) {
this.#altText.data = this.#accessibilityData;
this.#accessibilityData = null;
}
await this.addEditToolbar();
}
get altTextData() {
return this.#altText?.data;
}
set altTextData(data) {
if (!this.#altText) {
return;
}
this.#altText.data = data;
}
get guessedAltText() {
return this.#altText?.guessedText;
}
async setGuessedAltText(text) {
await this.#altText?.setGuessedText(text);
}
serializeAltText(isForCopying) {
return this.#altText?.serialize(isForCopying);
}
hasAltText() {
return !!this.#altText && !this.#altText.isEmpty();
}
hasAltTextData() {
return this.#altText?.hasData() ?? false;
}
render() {
const div = this.div = document.createElement("div");
div.setAttribute("data-editor-rotation", (360 - this.rotation) % 360);
div.className = this.name;
div.setAttribute("id", this.id);
div.tabIndex = this.#disabled ? -1 : 0;
div.setAttribute("role", "application");
if (this.defaultL10nId) {
div.setAttribute("data-l10n-id", this.defaultL10nId);
}
if (!this._isVisible) {
div.classList.add("hidden");
}
this.setInForeground();
this.#addFocusListeners();
const [parentWidth, parentHeight] = this.parentDimensions;
if (this.parentRotation % 180 !== 0) {
div.style.maxWidth = `${(100 * parentHeight / parentWidth).toFixed(2)}%`;
div.style.maxHeight = `${(100 * parentWidth / parentHeight).toFixed(2)}%`;
}
const [tx, ty] = this.getInitialTranslation();
this.translate(tx, ty);
bindEvents(this, div, ["keydown", "pointerdown"]);
if (this.isResizable && this._uiManager._supportsPinchToZoom) {
this.#touchManager ||= new TouchManager({
container: div,
isPinchingDisabled: () => !this.isSelected,
onPinchStart: this.#touchPinchStartCallback.bind(this),
onPinching: this.#touchPinchCallback.bind(this),
onPinchEnd: this.#touchPinchEndCallback.bind(this),
signal: this._uiManager._signal
});
}
this._uiManager._editorUndoBar?.hide();
return div;
}
#touchPinchStartCallback() {
this.#savedDimensions = {
savedX: this.x,
savedY: this.y,
savedWidth: this.width,
savedHeight: this.height
};
this.#altText?.toggle(false);
this.parent.togglePointerEvents(false);
}
#touchPinchCallback(_origin, prevDistance, distance) {
const slowDownFactor = 0.7;
let factor = slowDownFactor * (distance / prevDistance) + 1 - slowDownFactor;
if (factor === 1) {
return;
}
const rotationMatrix = this.#getRotationMatrix(this.rotation);
const transf = (x, y) => [rotationMatrix[0] * x + rotationMatrix[2] * y, rotationMatrix[1] * x + rotationMatrix[3] * y];
const [parentWidth, parentHeight] = this.parentDimensions;
const savedX = this.x;
const savedY = this.y;
const savedWidth = this.width;
const savedHeight = this.height;
const minWidth = AnnotationEditor.MIN_SIZE / parentWidth;
const minHeight = AnnotationEditor.MIN_SIZE / parentHeight;
factor = Math.max(Math.min(factor, 1 / savedWidth, 1 / savedHeight), minWidth / savedWidth, minHeight / savedHeight);
const newWidth = AnnotationEditor._round(savedWidth * factor);
const newHeight = AnnotationEditor._round(savedHeight * factor);
if (newWidth === savedWidth && newHeight === savedHeight) {
return;
}
this.#initialRect ||= [savedX, savedY, savedWidth, savedHeight];
const transfCenterPoint = transf(savedWidth / 2, savedHeight / 2);
const centerX = AnnotationEditor._round(savedX + transfCenterPoint[0]);
const centerY = AnnotationEditor._round(savedY + transfCenterPoint[1]);
const newTransfCenterPoint = transf(newWidth / 2, newHeight / 2);
this.x = centerX - newTransfCenterPoint[0];
this.y = centerY - newTransfCenterPoint[1];
this.width = newWidth;
this.height = newHeight;
this.setDims(parentWidth * newWidth, parentHeight * newHeight);
this.fixAndSetPosition();
this._onResizing();
}
#touchPinchEndCallback() {
this.#altText?.toggle(true);
this.parent.togglePointerEvents(true);
this.#addResizeToUndoStack();
}
pointerdown(event) {
const {
isMac
} = util_FeatureTest.platform;
if (event.button !== 0 || event.ctrlKey && isMac) {
event.preventDefault();
return;
}
this.#hasBeenClicked = true;
if (this._isDraggable) {
this.#setUpDragSession(event);
return;
}
this.#selectOnPointerEvent(event);
}
get isSelected() {
return this._uiManager.isSelected(this);
}
#selectOnPointerEvent(event) {
const {
isMac
} = util_FeatureTest.platform;
if (event.ctrlKey && !isMac || event.shiftKey || event.metaKey && isMac) {
this.parent.toggleSelected(this);
} else {
this.parent.setSelected(this);
}
}
#setUpDragSession(event) {
const {
isSelected
} = this;
this._uiManager.setUpDragSession();
let hasDraggingStarted = false;
const ac = new AbortController();
const signal = this._uiManager.combinedSignal(ac);
const opts = {
capture: true,
passive: false,
signal
};
const cancelDrag = e => {
ac.abort();
this.#dragPointerId = null;
this.#hasBeenClicked = false;
if (!this._uiManager.endDragSession()) {
this.#selectOnPointerEvent(e);
}
if (hasDraggingStarted) {
this._onStopDragging();
}
};
if (isSelected) {
this.#prevDragX = event.clientX;
this.#prevDragY = event.clientY;
this.#dragPointerId = event.pointerId;
this.#dragPointerType = event.pointerType;
window.addEventListener("pointermove", e => {
if (!hasDraggingStarted) {
hasDraggingStarted = true;
this._onStartDragging();
}
const {
clientX: x,
clientY: y,
pointerId
} = e;
if (pointerId !== this.#dragPointerId) {
stopEvent(e);
return;
}
const [tx, ty] = this.screenToPageTranslation(x - this.#prevDragX, y - this.#prevDragY);
this.#prevDragX = x;
this.#prevDragY = y;
this._uiManager.dragSelectedEditors(tx, ty);
}, opts);
window.addEventListener("touchmove", stopEvent, opts);
window.addEventListener("pointerdown", e => {
if (e.pointerType === this.#dragPointerType) {
if (this.#touchManager || e.isPrimary) {
cancelDrag(e);
}
}
stopEvent(e);
}, opts);
}
const pointerUpCallback = e => {
if (!this.#dragPointerId || this.#dragPointerId === e.pointerId) {
cancelDrag(e);
return;
}
stopEvent(e);
};
window.addEventListener("pointerup", pointerUpCallback, {
signal
});
window.addEventListener("blur", pointerUpCallback, {
signal
});
}
_onStartDragging() {}
_onStopDragging() {}
moveInDOM() {
if (this.#moveInDOMTimeout) {
clearTimeout(this.#moveInDOMTimeout);
}
this.#moveInDOMTimeout = setTimeout(() => {
this.#moveInDOMTimeout = null;
this.parent?.moveEditorInDOM(this);
}, 0);
}
_setParentAndPosition(parent, x, y) {
parent.changeParent(this);
this.x = x;
this.y = y;
this.fixAndSetPosition();
this._onTranslated();
}
getRect(tx, ty, rotation = this.rotation) {
const scale = this.parentScale;
const [pageWidth, pageHeight] = this.pageDimensions;
const [pageX, pageY] = this.pageTranslation;
const shiftX = tx / scale;
const shiftY = ty / scale;
const x = this.x * pageWidth;
const y = this.y * pageHeight;
const width = this.width * pageWidth;
const height = this.height * pageHeight;
switch (rotation) {
case 0:
return [x + shiftX + pageX, pageHeight - y - shiftY - height + pageY, x + shiftX + width + pageX, pageHeight - y - shiftY + pageY];
case 90:
return [x + shiftY + pageX, pageHeight - y + shiftX + pageY, x + shiftY + height + pageX, pageHeight - y + shiftX + width + pageY];
case 180:
return [x - shiftX - width + pageX, pageHeight - y + shiftY + pageY, x - shiftX + pageX, pageHeight - y + shiftY + height + pageY];
case 270:
return [x - shiftY - height + pageX, pageHeight - y - shiftX - width + pageY, x - shiftY + pageX, pageHeight - y - shiftX + pageY];
default:
throw new Error("Invalid rotation");
}
}
getRectInCurrentCoords(rect, pageHeight) {
const [x1, y1, x2, y2] = rect;
const width = x2 - x1;
const height = y2 - y1;
switch (this.rotation) {
case 0:
return [x1, pageHeight - y2, width, height];
case 90:
return [x1, pageHeight - y1, height, width];
case 180:
return [x2, pageHeight - y1, width, height];
case 270:
return [x2, pageHeight - y2, height, width];
default:
throw new Error("Invalid rotation");
}
}
onceAdded(focus) {}
isEmpty() {
return false;
}
enableEditMode() {
this.#isInEditMode = true;
}
disableEditMode() {
this.#isInEditMode = false;
}
isInEditMode() {
return this.#isInEditMode;
}
shouldGetKeyboardEvents() {
return this.#isResizerEnabledForKeyboard;
}
needsToBeRebuilt() {
return this.div && !this.isAttachedToDOM;
}
get isOnScreen() {
const {
top,
left,
bottom,
right
} = this.getClientDimensions();
const {
innerHeight,
innerWidth
} = window;
return left < innerWidth && right > 0 && top < innerHeight && bottom > 0;
}
#addFocusListeners() {
if (this.#focusAC || !this.div) {
return;
}
this.#focusAC = new AbortController();
const signal = this._uiManager.combinedSignal(this.#focusAC);
this.div.addEventListener("focusin", this.focusin.bind(this), {
signal
});
this.div.addEventListener("focusout", this.focusout.bind(this), {
signal
});
}
rebuild() {
this.#addFocusListeners();
}
rotate(_angle) {}
resize() {}
serializeDeleted() {
return {
id: this.annotationElementId,
deleted: true,
pageIndex: this.pageIndex,
popupRef: this._initialData?.popupRef || ""
};
}
serialize(isForCopying = false, context = null) {
unreachable("An editor must be serializable");
}
static async deserialize(data, parent, uiManager) {
const editor = new this.prototype.constructor({
parent,
id: parent.getNextId(),
uiManager
});
editor.rotation = data.rotation;
editor.#accessibilityData = data.accessibilityData;
editor._isCopy = data.isCopy || false;
const [pageWidth, pageHeight] = editor.pageDimensions;
const [x, y, width, height] = editor.getRectInCurrentCoords(data.rect, pageHeight);
editor.x = x / pageWidth;
editor.y = y / pageHeight;
editor.width = width / pageWidth;
editor.height = height / pageHeight;
return editor;
}
get hasBeenModified() {
return !!this.annotationElementId && (this.deleted || this.serialize() !== null);
}
remove() {
this.#focusAC?.abort();
this.#focusAC = null;
if (!this.isEmpty()) {
this.commit();
}
if (this.parent) {
this.parent.remove(this);
} else {
this._uiManager.removeEditor(this);
}
if (this.#moveInDOMTimeout) {
clearTimeout(this.#moveInDOMTimeout);
this.#moveInDOMTimeout = null;
}
this.#stopResizing();
this.removeEditToolbar();
if (this.#telemetryTimeouts) {
for (const timeout of this.#telemetryTimeouts.values()) {
clearTimeout(timeout);
}
this.#telemetryTimeouts = null;
}
this.parent = null;
this.#touchManager?.destroy();
this.#touchManager = null;
}
get isResizable() {
return false;
}
makeResizable() {
if (this.isResizable) {
this.#createResizers();
this.#resizersDiv.classList.remove("hidden");
}
}
get toolbarPosition() {
return null;
}
keydown(event) {
if (!this.isResizable || event.target !== this.div || event.key !== "Enter") {
return;
}
this._uiManager.setSelected(this);
this.#savedDimensions = {
savedX: this.x,
savedY: this.y,
savedWidth: this.width,
savedHeight: this.height
};
const children = this.#resizersDiv.children;
if (!this.#allResizerDivs) {
this.#allResizerDivs = Array.from(children);
const boundResizerKeydown = this.#resizerKeydown.bind(this);
const boundResizerBlur = this.#resizerBlur.bind(this);
const signal = this._uiManager._signal;
for (const div of this.#allResizerDivs) {
const name = div.getAttribute("data-resizer-name");
div.setAttribute("role", "spinbutton");
div.addEventListener("keydown", boundResizerKeydown, {
signal
});
div.addEventListener("blur", boundResizerBlur, {
signal
});
div.addEventListener("focus", this.#resizerFocus.bind(this, name), {
signal
});
div.setAttribute("data-l10n-id", AnnotationEditor._l10nResizer[name]);
}
}
const first = this.#allResizerDivs[0];
let firstPosition = 0;
for (const div of children) {
if (div === first) {
break;
}
firstPosition++;
}
const nextFirstPosition = (360 - this.rotation + this.parentRotation) % 360 / 90 * (this.#allResizerDivs.length / 4);
if (nextFirstPosition !== firstPosition) {
if (nextFirstPosition < firstPosition) {
for (let i = 0; i < firstPosition - nextFirstPosition; i++) {
this.#resizersDiv.append(this.#resizersDiv.firstChild);
}
} else if (nextFirstPosition > firstPosition) {
for (let i = 0; i < nextFirstPosition - firstPosition; i++) {
this.#resizersDiv.firstChild.before(this.#resizersDiv.lastChild);
}
}
let i = 0;
for (const child of children) {
const div = this.#allResizerDivs[i++];
const name = div.getAttribute("data-resizer-name");
child.setAttribute("data-l10n-id", AnnotationEditor._l10nResizer[name]);
}
}
this.#setResizerTabIndex(0);
this.#isResizerEnabledForKeyboard = true;
this.#resizersDiv.firstChild.focus({
focusVisible: true
});
event.preventDefault();
event.stopImmediatePropagation();
}
#resizerKeydown(event) {
AnnotationEditor._resizerKeyboardManager.exec(this, event);
}
#resizerBlur(event) {
if (this.#isResizerEnabledForKeyboard && event.relatedTarget?.parentNode !== this.#resizersDiv) {
this.#stopResizing();
}
}
#resizerFocus(name) {
this.#focusedResizerName = this.#isResizerEnabledForKeyboard ? name : "";
}
#setResizerTabIndex(value) {
if (!this.#allResizerDivs) {
return;
}
for (const div of this.#allResizerDivs) {
div.tabIndex = value;
}
}
_resizeWithKeyboard(x, y) {
if (!this.#isResizerEnabledForKeyboard) {
return;
}
this.#resizerPointermove(this.#focusedResizerName, {
deltaX: x,
deltaY: y,
fromKeyboard: true
});
}
#stopResizing() {
this.#isResizerEnabledForKeyboard = false;
this.#setResizerTabIndex(-1);
this.#addResizeToUndoStack();
}
_stopResizingWithKeyboard() {
this.#stopResizing();
this.div.focus();
}
select() {
this.makeResizable();
this.div?.classList.add("selectedEditor");
if (!this._editToolbar) {
this.addEditToolbar().then(() => {
if (this.div?.classList.contains("selectedEditor")) {
this._editToolbar?.show();
}
});
return;
}
this._editToolbar?.show();
this.#altText?.toggleAltTextBadge(false);
}
unselect() {
this.#resizersDiv?.classList.add("hidden");
this.div?.classList.remove("selectedEditor");
if (this.div?.contains(document.activeElement)) {
this._uiManager.currentLayer.div.focus({
preventScroll: true
});
}
this._editToolbar?.hide();
this.#altText?.toggleAltTextBadge(true);
}
updateParams(type, value) {}
disableEditing() {}
enableEditing() {}
enterInEditMode() {}
getElementForAltText() {
return this.div;
}
get contentDiv() {
return this.div;
}
get isEditing() {
return this.#isEditing;
}
set isEditing(value) {
this.#isEditing = value;
if (!this.parent) {
return;
}
if (value) {
this.parent.setSelected(this);
this.parent.setActiveEditor(this);
} else {
this.parent.setActiveEditor(null);
}
}
setAspectRatio(width, height) {
this.#keepAspectRatio = true;
const aspectRatio = width / height;
const {
style
} = this.div;
style.aspectRatio = aspectRatio;
style.height = "auto";
}
static get MIN_SIZE() {
return 16;
}
static canCreateNewEmptyEditor() {
return true;
}
get telemetryInitialData() {
return {
action: "added"
};
}
get telemetryFinalData() {
return null;
}
_reportTelemetry(data, mustWait = false) {
if (mustWait) {
this.#telemetryTimeouts ||= new Map();
const {
action
} = data;
let timeout = this.#telemetryTimeouts.get(action);
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(() => {
this._reportTelemetry(data);
this.#telemetryTimeouts.delete(action);
if (this.#telemetryTimeouts.size === 0) {
this.#telemetryTimeouts = null;
}
}, AnnotationEditor._telemetryTimeout);
this.#telemetryTimeouts.set(action, timeout);
return;
}
data.type ||= this.editorType;
this._uiManager._eventBus.dispatch("reporttelemetry", {
source: this,
details: {
type: "editing",
data
}
});
}
show(visible = this._isVisible) {
this.div.classList.toggle("hidden", !visible);
this._isVisible = visible;
}
enable() {
if (this.div) {
this.div.tabIndex = 0;
}
this.#disabled = false;
}
disable() {
if (this.div) {
this.div.tabIndex = -1;
}
this.#disabled = true;
}
renderAnnotationElement(annotation) {
let content = annotation.container.querySelector(".annotationContent");
if (!content) {
content = document.createElement("div");
content.classList.add("annotationContent", this.editorType);
annotation.container.prepend(content);
} else if (content.nodeName === "CANVAS") {
const canvas = content;
content = document.createElement("div");
content.classList.add("annotationContent", this.editorType);
canvas.before(content);
}
return content;
}
resetAnnotationElement(annotation) {
const {
firstChild
} = annotation.container;
if (firstChild?.nodeName === "DIV" && firstChild.classList.contains("annotationContent")) {
firstChild.remove();
}
}
}
class FakeEditor extends AnnotationEditor {
constructor(params) {
super(params);
this.annotationElementId = params.annotationElementId;
this.deleted = true;
}
serialize() {
return this.serializeDeleted();
}
}
;// ./src/shared/murmurhash3.js
const SEED = 0xc3d2e1f0;
const MASK_HIGH = 0xffff0000;
const MASK_LOW = 0xffff;
class MurmurHash3_64 {
constructor(seed) {
this.h1 = seed ? seed & 0xffffffff : SEED;
this.h2 = seed ? seed & 0xffffffff : SEED;
}
update(input) {
let data, length;
if (typeof input === "string") {
data = new Uint8Array(input.length * 2);
length = 0;
for (let i = 0, ii = input.length; i < ii; i++) {
const code = input.charCodeAt(i);
if (code <= 0xff) {
data[length++] = code;
} else {
data[length++] = code >>> 8;
data[length++] = code & 0xff;
}
}
} else if (ArrayBuffer.isView(input)) {
data = input.slice();
length = data.byteLength;
} else {
throw new Error("Invalid data format, must be a string or TypedArray.");
}
const blockCounts = length >> 2;
const tailLength = length - blockCounts * 4;
const dataUint32 = new Uint32Array(data.buffer, 0, blockCounts);
let k1 = 0,
k2 = 0;
let h1 = this.h1,
h2 = this.h2;
const C1 = 0xcc9e2d51,
C2 = 0x1b873593;
const C1_LOW = C1 & MASK_LOW,
C2_LOW = C2 & MASK_LOW;
for (let i = 0; i < blockCounts; i++) {
if (i & 1) {
k1 = dataUint32[i];
k1 = k1 * C1 & MASK_HIGH | k1 * C1_LOW & MASK_LOW;
k1 = k1 << 15 | k1 >>> 17;
k1 = k1 * C2 & MASK_HIGH | k1 * C2_LOW & MASK_LOW;
h1 ^= k1;
h1 = h1 << 13 | h1 >>> 19;
h1 = h1 * 5 + 0xe6546b64;
} else {
k2 = dataUint32[i];
k2 = k2 * C1 & MASK_HIGH | k2 * C1_LOW & MASK_LOW;
k2 = k2 << 15 | k2 >>> 17;
k2 = k2 * C2 & MASK_HIGH | k2 * C2_LOW & MASK_LOW;
h2 ^= k2;
h2 = h2 << 13 | h2 >>> 19;
h2 = h2 * 5 + 0xe6546b64;
}
}
k1 = 0;
switch (tailLength) {
case 3:
k1 ^= data[blockCounts * 4 + 2] << 16;
case 2:
k1 ^= data[blockCounts * 4 + 1] << 8;
case 1:
k1 ^= data[blockCounts * 4];
k1 = k1 * C1 & MASK_HIGH | k1 * C1_LOW & MASK_LOW;
k1 = k1 << 15 | k1 >>> 17;
k1 = k1 * C2 & MASK_HIGH | k1 * C2_LOW & MASK_LOW;
if (blockCounts & 1) {
h1 ^= k1;
} else {
h2 ^= k1;
}
}
this.h1 = h1;
this.h2 = h2;
}
hexdigest() {
let h1 = this.h1,
h2 = this.h2;
h1 ^= h2 >>> 1;
h1 = h1 * 0xed558ccd & MASK_HIGH | h1 * 0x8ccd & MASK_LOW;
h2 = h2 * 0xff51afd7 & MASK_HIGH | ((h2 << 16 | h1 >>> 16) * 0xafd7ed55 & MASK_HIGH) >>> 16;
h1 ^= h2 >>> 1;
h1 = h1 * 0x1a85ec53 & MASK_HIGH | h1 * 0xec53 & MASK_LOW;
h2 = h2 * 0xc4ceb9fe & MASK_HIGH | ((h2 << 16 | h1 >>> 16) * 0xb9fe1a85 & MASK_HIGH) >>> 16;
h1 ^= h2 >>> 1;
return (h1 >>> 0).toString(16).padStart(8, "0") + (h2 >>> 0).toString(16).padStart(8, "0");
}
}
;// ./src/display/annotation_storage.js
const SerializableEmpty = Object.freeze({
map: null,
hash: "",
transfer: undefined
});
class AnnotationStorage {
#modified = false;
#modifiedIds = null;
#storage = new Map();
constructor() {
this.onSetModified = null;
this.onResetModified = null;
this.onAnnotationEditor = null;
}
getValue(key, defaultValue) {
const value = this.#storage.get(key);
if (value === undefined) {
return defaultValue;
}
return Object.assign(defaultValue, value);
}
getRawValue(key) {
return this.#storage.get(key);
}
remove(key) {
this.#storage.delete(key);
if (this.#storage.size === 0) {
this.resetModified();
}
if (typeof this.onAnnotationEditor === "function") {
for (const value of this.#storage.values()) {
if (value instanceof AnnotationEditor) {
return;
}
}
this.onAnnotationEditor(null);
}
}
setValue(key, value) {
const obj = this.#storage.get(key);
let modified = false;
if (obj !== undefined) {
for (const [entry, val] of Object.entries(value)) {
if (obj[entry] !== val) {
modified = true;
obj[entry] = val;
}
}
} else {
modified = true;
this.#storage.set(key, value);
}
if (modified) {
this.#setModified();
}
if (value instanceof AnnotationEditor && typeof this.onAnnotationEditor === "function") {
this.onAnnotationEditor(value.constructor._type);
}
}
has(key) {
return this.#storage.has(key);
}
getAll() {
return this.#storage.size > 0 ? objectFromMap(this.#storage) : null;
}
setAll(obj) {
for (const [key, val] of Object.entries(obj)) {
this.setValue(key, val);
}
}
get size() {
return this.#storage.size;
}
#setModified() {
if (!this.#modified) {
this.#modified = true;
if (typeof this.onSetModified === "function") {
this.onSetModified();
}
}
}
resetModified() {
if (this.#modified) {
this.#modified = false;
if (typeof this.onResetModified === "function") {
this.onResetModified();
}
}
}
get print() {
return new PrintAnnotationStorage(this);
}
get serializable() {
if (this.#storage.size === 0) {
return SerializableEmpty;
}
const map = new Map(),
hash = new MurmurHash3_64(),
transfer = [];
const context = Object.create(null);
let hasBitmap = false;
for (const [key, val] of this.#storage) {
const serialized = val instanceof AnnotationEditor ? val.serialize(false, context) : val;
if (serialized) {
map.set(key, serialized);
hash.update(`${key}:${JSON.stringify(serialized)}`);
hasBitmap ||= !!serialized.bitmap;
}
}
if (hasBitmap) {
for (const value of map.values()) {
if (value.bitmap) {
transfer.push(value.bitmap);
}
}
}
return map.size > 0 ? {
map,
hash: hash.hexdigest(),
transfer
} : SerializableEmpty;
}
get editorStats() {
let stats = null;
const typeToEditor = new Map();
for (const value of this.#storage.values()) {
if (!(value instanceof AnnotationEditor)) {
continue;
}
const editorStats = value.telemetryFinalData;
if (!editorStats) {
continue;
}
const {
type
} = editorStats;
if (!typeToEditor.has(type)) {
typeToEditor.set(type, Object.getPrototypeOf(value).constructor);
}
stats ||= Object.create(null);
const map = stats[type] ||= new Map();
for (const [key, val] of Object.entries(editorStats)) {
if (key === "type") {
continue;
}
let counters = map.get(key);
if (!counters) {
counters = new Map();
map.set(key, counters);
}
const count = counters.get(val) ?? 0;
counters.set(val, count + 1);
}
}
for (const [type, editor] of typeToEditor) {
stats[type] = editor.computeTelemetryFinalData(stats[type]);
}
return stats;
}
resetModifiedIds() {
this.#modifiedIds = null;
}
get modifiedIds() {
if (this.#modifiedIds) {
return this.#modifiedIds;
}
const ids = [];
for (const value of this.#storage.values()) {
if (!(value instanceof AnnotationEditor) || !value.annotationElementId || !value.serialize()) {
continue;
}
ids.push(value.annotationElementId);
}
return this.#modifiedIds = {
ids: new Set(ids),
hash: ids.join(",")
};
}
}
class PrintAnnotationStorage extends AnnotationStorage {
#serializable;
constructor(parent) {
super();
const {
map,
hash,
transfer
} = parent.serializable;
const clone = structuredClone(map, transfer ? {
transfer
} : null);
this.#serializable = {
map: clone,
hash,
transfer
};
}
get print() {
unreachable("Should not call PrintAnnotationStorage.print");
}
get serializable() {
return this.#serializable;
}
get modifiedIds() {
return shadow(this, "modifiedIds", {
ids: new Set(),
hash: ""
});
}
}
;// ./src/display/font_loader.js
class FontLoader {
#systemFonts = new Set();
constructor({
ownerDocument = globalThis.document,
styleElement = null
}) {
this._document = ownerDocument;
this.nativeFontFaces = new Set();
this.styleElement = null;
this.loadingRequests = [];
this.loadTestFontId = 0;
}
addNativeFontFace(nativeFontFace) {
this.nativeFontFaces.add(nativeFontFace);
this._document.fonts.add(nativeFontFace);
}
removeNativeFontFace(nativeFontFace) {
this.nativeFontFaces.delete(nativeFontFace);
this._document.fonts.delete(nativeFontFace);
}
insertRule(rule) {
if (!this.styleElement) {
this.styleElement = this._document.createElement("style");
this._document.documentElement.getElementsByTagName("head")[0].append(this.styleElement);
}
const styleSheet = this.styleElement.sheet;
styleSheet.insertRule(rule, styleSheet.cssRules.length);
}
clear() {
for (const nativeFontFace of this.nativeFontFaces) {
this._document.fonts.delete(nativeFontFace);
}
this.nativeFontFaces.clear();
this.#systemFonts.clear();
if (this.styleElement) {
this.styleElement.remove();
this.styleElement = null;
}
}
async loadSystemFont({
systemFontInfo: info,
disableFontFace,
_inspectFont
}) {
if (!info || this.#systemFonts.has(info.loadedName)) {
return;
}
assert(!disableFontFace, "loadSystemFont shouldn't be called when `disableFontFace` is set.");
if (this.isFontLoadingAPISupported) {
const {
loadedName,
src,
style
} = info;
const fontFace = new FontFace(loadedName, src, style);
this.addNativeFontFace(fontFace);
try {
await fontFace.load();
this.#systemFonts.add(loadedName);
_inspectFont?.(info);
} catch {
warn(`Cannot load system font: ${info.baseFontName}, installing it could help to improve PDF rendering.`);
this.removeNativeFontFace(fontFace);
}
return;
}
unreachable("Not implemented: loadSystemFont without the Font Loading API.");
}
async bind(font) {
if (font.attached || font.missingFile && !font.systemFontInfo) {
return;
}
font.attached = true;
if (font.systemFontInfo) {
await this.loadSystemFont(font);
return;
}
if (this.isFontLoadingAPISupported) {
const nativeFontFace = font.createNativeFontFace();
if (nativeFontFace) {
this.addNativeFontFace(nativeFontFace);
try {
await nativeFontFace.loaded;
} catch (ex) {
warn(`Failed to load font '${nativeFontFace.family}': '${ex}'.`);
font.disableFontFace = true;
throw ex;
}
}
return;
}
const rule = font.createFontFaceRule();
if (rule) {
this.insertRule(rule);
if (this.isSyncFontLoadingSupported) {
return;
}
await new Promise(resolve => {
const request = this._queueLoadingCallback(resolve);
this._prepareFontLoadEvent(font, request);
});
}
}
get isFontLoadingAPISupported() {
const hasFonts = !!this._document?.fonts;
return shadow(this, "isFontLoadingAPISupported", hasFonts);
}
get isSyncFontLoadingSupported() {
return shadow(this, "isSyncFontLoadingSupported", isNodeJS || util_FeatureTest.platform.isFirefox);
}
_queueLoadingCallback(callback) {
function completeRequest() {
assert(!request.done, "completeRequest() cannot be called twice.");
request.done = true;
while (loadingRequests.length > 0 && loadingRequests[0].done) {
const otherRequest = loadingRequests.shift();
setTimeout(otherRequest.callback, 0);
}
}
const {
loadingRequests
} = this;
const request = {
done: false,
complete: completeRequest,
callback
};
loadingRequests.push(request);
return request;
}
get _loadTestFont() {
const testFont = atob("T1RUTwALAIAAAwAwQ0ZGIDHtZg4AAAOYAAAAgUZGVE1lkzZwAAAEHAAAABxHREVGABQA" + "FQAABDgAAAAeT1MvMlYNYwkAAAEgAAAAYGNtYXABDQLUAAACNAAAAUJoZWFk/xVFDQAA" + "ALwAAAA2aGhlYQdkA+oAAAD0AAAAJGhtdHgD6AAAAAAEWAAAAAZtYXhwAAJQAAAAARgA" + "AAAGbmFtZVjmdH4AAAGAAAAAsXBvc3T/hgAzAAADeAAAACAAAQAAAAEAALZRFsRfDzz1" + "AAsD6AAAAADOBOTLAAAAAM4KHDwAAAAAA+gDIQAAAAgAAgAAAAAAAAABAAADIQAAAFoD" + "6AAAAAAD6AABAAAAAAAAAAAAAAAAAAAAAQAAUAAAAgAAAAQD6AH0AAUAAAKKArwAAACM" + "AooCvAAAAeAAMQECAAACAAYJAAAAAAAAAAAAAQAAAAAAAAAAAAAAAFBmRWQAwAAuAC4D" + "IP84AFoDIQAAAAAAAQAAAAAAAAAAACAAIAABAAAADgCuAAEAAAAAAAAAAQAAAAEAAAAA" + "AAEAAQAAAAEAAAAAAAIAAQAAAAEAAAAAAAMAAQAAAAEAAAAAAAQAAQAAAAEAAAAAAAUA" + "AQAAAAEAAAAAAAYAAQAAAAMAAQQJAAAAAgABAAMAAQQJAAEAAgABAAMAAQQJAAIAAgAB" + "AAMAAQQJAAMAAgABAAMAAQQJAAQAAgABAAMAAQQJAAUAAgABAAMAAQQJAAYAAgABWABY" + "AAAAAAAAAwAAAAMAAAAcAAEAAAAAADwAAwABAAAAHAAEACAAAAAEAAQAAQAAAC7//wAA" + "AC7////TAAEAAAAAAAABBgAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAD/gwAyAAAAAQAAAAAAAAAAAAAAAAAA" + "AAABAAQEAAEBAQJYAAEBASH4DwD4GwHEAvgcA/gXBIwMAYuL+nz5tQXkD5j3CBLnEQAC" + "AQEBIVhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYAAABAQAADwACAQEEE/t3" + "Dov6fAH6fAT+fPp8+nwHDosMCvm1Cvm1DAz6fBQAAAAAAAABAAAAAMmJbzEAAAAAzgTj" + "FQAAAADOBOQpAAEAAAAAAAAADAAUAAQAAAABAAAAAgABAAAAAAAAAAAD6AAAAAAAAA==");
return shadow(this, "_loadTestFont", testFont);
}
_prepareFontLoadEvent(font, request) {
function int32(data, offset) {
return data.charCodeAt(offset) << 24 | data.charCodeAt(offset + 1) << 16 | data.charCodeAt(offset + 2) << 8 | data.charCodeAt(offset + 3) & 0xff;
}
function spliceString(s, offset, remove, insert) {
const chunk1 = s.substring(0, offset);
const chunk2 = s.substring(offset + remove);
return chunk1 + insert + chunk2;
}
let i, ii;
const canvas = this._document.createElement("canvas");
canvas.width = 1;
canvas.height = 1;
const ctx = canvas.getContext("2d");
let called = 0;
function isFontReady(name, callback) {
if (++called > 30) {
warn("Load test font never loaded.");
callback();
return;
}
ctx.font = "30px " + name;
ctx.fillText(".", 0, 20);
const imageData = ctx.getImageData(0, 0, 1, 1);
if (imageData.data[3] > 0) {
callback();
return;
}
setTimeout(isFontReady.bind(null, name, callback));
}
const loadTestFontId = `lt${Date.now()}${this.loadTestFontId++}`;
let data = this._loadTestFont;
const COMMENT_OFFSET = 976;
data = spliceString(data, COMMENT_OFFSET, loadTestFontId.length, loadTestFontId);
const CFF_CHECKSUM_OFFSET = 16;
const XXXX_VALUE = 0x58585858;
let checksum = int32(data, CFF_CHECKSUM_OFFSET);
for (i = 0, ii = loadTestFontId.length - 3; i < ii; i += 4) {
checksum = checksum - XXXX_VALUE + int32(loadTestFontId, i) | 0;
}
if (i < loadTestFontId.length) {
checksum = checksum - XXXX_VALUE + int32(loadTestFontId + "XXX", i) | 0;
}
data = spliceString(data, CFF_CHECKSUM_OFFSET, 4, string32(checksum));
const url = `url(data:font/opentype;base64,${btoa(data)});`;
const rule = `@font-face {font-family:"${loadTestFontId}";src:${url}}`;
this.insertRule(rule);
const div = this._document.createElement("div");
div.style.visibility = "hidden";
div.style.width = div.style.height = "10px";
div.style.position = "absolute";
div.style.top = div.style.left = "0px";
for (const name of [font.loadedName, loadTestFontId]) {
const span = this._document.createElement("span");
span.textContent = "Hi";
span.style.fontFamily = name;
div.append(span);
}
this._document.body.append(div);
isFontReady(loadTestFontId, () => {
div.remove();
request.complete();
});
}
}
class FontFaceObject {
constructor(translatedData, inspectFont = null) {
this.compiledGlyphs = Object.create(null);
for (const i in translatedData) {
this[i] = translatedData[i];
}
this._inspectFont = inspectFont;
}
createNativeFontFace() {
if (!this.data || this.disableFontFace) {
return null;
}
let nativeFontFace;
if (!this.cssFontInfo) {
nativeFontFace = new FontFace(this.loadedName, this.data, {});
} else {
const css = {
weight: this.cssFontInfo.fontWeight
};
if (this.cssFontInfo.italicAngle) {
css.style = `oblique ${this.cssFontInfo.italicAngle}deg`;
}
nativeFontFace = new FontFace(this.cssFontInfo.fontFamily, this.data, css);
}
this._inspectFont?.(this);
return nativeFontFace;
}
createFontFaceRule() {
if (!this.data || this.disableFontFace) {
return null;
}
const url = `url(data:${this.mimetype};base64,${toBase64Util(this.data)});`;
let rule;
if (!this.cssFontInfo) {
rule = `@font-face {font-family:"${this.loadedName}";src:${url}}`;
} else {
let css = `font-weight: ${this.cssFontInfo.fontWeight};`;
if (this.cssFontInfo.italicAngle) {
css += `font-style: oblique ${this.cssFontInfo.italicAngle}deg;`;
}
rule = `@font-face {font-family:"${this.cssFontInfo.fontFamily}";${css}src:${url}}`;
}
this._inspectFont?.(this, url);
return rule;
}
getPathGenerator(objs, character) {
if (this.compiledGlyphs[character] !== undefined) {
return this.compiledGlyphs[character];
}
const objId = this.loadedName + "_path_" + character;
let cmds;
try {
cmds = objs.get(objId);
} catch (ex) {
warn(`getPathGenerator - ignoring character: "${ex}".`);
}
const path = new Path2D(cmds || "");
if (!this.fontExtraProperties) {
objs.delete(objId);
}
return this.compiledGlyphs[character] = path;
}
}
;// ./src/shared/message_handler.js
const CallbackKind = {
DATA: 1,
ERROR: 2
};
const StreamKind = {
CANCEL: 1,
CANCEL_COMPLETE: 2,
CLOSE: 3,
ENQUEUE: 4,
ERROR: 5,
PULL: 6,
PULL_COMPLETE: 7,
START_COMPLETE: 8
};
function onFn() {}
function wrapReason(ex) {
if (ex instanceof AbortException || ex instanceof InvalidPDFException || ex instanceof PasswordException || ex instanceof ResponseException || ex instanceof UnknownErrorException) {
return ex;
}
if (!(ex instanceof Error || typeof ex === "object" && ex !== null)) {
unreachable('wrapReason: Expected "reason" to be a (possibly cloned) Error.');
}
switch (ex.name) {
case "AbortException":
return new AbortException(ex.message);
case "InvalidPDFException":
return new InvalidPDFException(ex.message);
case "PasswordException":
return new PasswordException(ex.message, ex.code);
case "ResponseException":
return new ResponseException(ex.message, ex.status, ex.missing);
case "UnknownErrorException":
return new UnknownErrorException(ex.message, ex.details);
}
return new UnknownErrorException(ex.message, ex.toString());
}
class MessageHandler {
#messageAC = new AbortController();
constructor(sourceName, targetName, comObj) {
this.sourceName = sourceName;
this.targetName = targetName;
this.comObj = comObj;
this.callbackId = 1;
this.streamId = 1;
this.streamSinks = Object.create(null);
this.streamControllers = Object.create(null);
this.callbackCapabilities = Object.create(null);
this.actionHandler = Object.create(null);
comObj.addEventListener("message", this.#onMessage.bind(this), {
signal: this.#messageAC.signal
});
}
#onMessage({
data
}) {
if (data.targetName !== this.sourceName) {
return;
}
if (data.stream) {
this.#processStreamMessage(data);
return;
}
if (data.callback) {
const callbackId = data.callbackId;
const capability = this.callbackCapabilities[callbackId];
if (!capability) {
throw new Error(`Cannot resolve callback ${callbackId}`);
}
delete this.callbackCapabilities[callbackId];
if (data.callback === CallbackKind.DATA) {
capability.resolve(data.data);
} else if (data.callback === CallbackKind.ERROR) {
capability.reject(wrapReason(data.reason));
} else {
throw new Error("Unexpected callback case");
}
return;
}
const action = this.actionHandler[data.action];
if (!action) {
throw new Error(`Unknown action from worker: ${data.action}`);
}
if (data.callbackId) {
const sourceName = this.sourceName,
targetName = data.sourceName,
comObj = this.comObj;
Promise.try(action, data.data).then(function (result) {
comObj.postMessage({
sourceName,
targetName,
callback: CallbackKind.DATA,
callbackId: data.callbackId,
data: result
});
}, function (reason) {
comObj.postMessage({
sourceName,
targetName,
callback: CallbackKind.ERROR,
callbackId: data.callbackId,
reason: wrapReason(reason)
});
});
return;
}
if (data.streamId) {
this.#createStreamSink(data);
return;
}
action(data.data);
}
on(actionName, handler) {
const ah = this.actionHandler;
if (ah[actionName]) {
throw new Error(`There is already an actionName called "${actionName}"`);
}
ah[actionName] = handler;
}
send(actionName, data, transfers) {
this.comObj.postMessage({
sourceName: this.sourceName,
targetName: this.targetName,
action: actionName,
data
}, transfers);
}
sendWithPromise(actionName, data, transfers) {
const callbackId = this.callbackId++;
const capability = Promise.withResolvers();
this.callbackCapabilities[callbackId] = capability;
try {
this.comObj.postMessage({
sourceName: this.sourceName,
targetName: this.targetName,
action: actionName,
callbackId,
data
}, transfers);
} catch (ex) {
capability.reject(ex);
}
return capability.promise;
}
sendWithStream(actionName, data, queueingStrategy, transfers) {
const streamId = this.streamId++,
sourceName = this.sourceName,
targetName = this.targetName,
comObj = this.comObj;
return new ReadableStream({
start: controller => {
const startCapability = Promise.withResolvers();
this.streamControllers[streamId] = {
controller,
startCall: startCapability,
pullCall: null,
cancelCall: null,
isClosed: false
};
comObj.postMessage({
sourceName,
targetName,
action: actionName,
streamId,
data,
desiredSize: controller.desiredSize
}, transfers);
return startCapability.promise;
},
pull: controller => {
const pullCapability = Promise.withResolvers();
this.streamControllers[streamId].pullCall = pullCapability;
comObj.postMessage({
sourceName,
targetName,
stream: StreamKind.PULL,
streamId,
desiredSize: controller.desiredSize
});
return pullCapability.promise;
},
cancel: reason => {
assert(reason instanceof Error, "cancel must have a valid reason");
const cancelCapability = Promise.withResolvers();
this.streamControllers[streamId].cancelCall = cancelCapability;
this.streamControllers[streamId].isClosed = true;
comObj.postMessage({
sourceName,
targetName,
stream: StreamKind.CANCEL,
streamId,
reason: wrapReason(reason)
});
return cancelCapability.promise;
}
}, queueingStrategy);
}
#createStreamSink(data) {
const streamId = data.streamId,
sourceName = this.sourceName,
targetName = data.sourceName,
comObj = this.comObj;
const self = this,
action = this.actionHandler[data.action];
const streamSink = {
enqueue(chunk, size = 1, transfers) {
if (this.isCancelled) {
return;
}
const lastDesiredSize = this.desiredSize;
this.desiredSize -= size;
if (lastDesiredSize > 0 && this.desiredSize <= 0) {
this.sinkCapability = Promise.withResolvers();
this.ready = this.sinkCapability.promise;
}
comObj.postMessage({
sourceName,
targetName,
stream: StreamKind.ENQUEUE,
streamId,
chunk
}, transfers);
},
close() {
if (this.isCancelled) {
return;
}
this.isCancelled = true;
comObj.postMessage({
sourceName,
targetName,
stream: StreamKind.CLOSE,
streamId
});
delete self.streamSinks[streamId];
},
error(reason) {
assert(reason instanceof Error, "error must have a valid reason");
if (this.isCancelled) {
return;
}
this.isCancelled = true;
comObj.postMessage({
sourceName,
targetName,
stream: StreamKind.ERROR,
streamId,
reason: wrapReason(reason)
});
},
sinkCapability: Promise.withResolvers(),
onPull: null,
onCancel: null,
isCancelled: false,
desiredSize: data.desiredSize,
ready: null
};
streamSink.sinkCapability.resolve();
streamSink.ready = streamSink.sinkCapability.promise;
this.streamSinks[streamId] = streamSink;
Promise.try(action, data.data, streamSink).then(function () {
comObj.postMessage({
sourceName,
targetName,
stream: StreamKind.START_COMPLETE,
streamId,
success: true
});
}, function (reason) {
comObj.postMessage({
sourceName,
targetName,
stream: StreamKind.START_COMPLETE,
streamId,
reason: wrapReason(reason)
});
});
}
#processStreamMessage(data) {
const streamId = data.streamId,
sourceName = this.sourceName,
targetName = data.sourceName,
comObj = this.comObj;
const streamController = this.streamControllers[streamId],
streamSink = this.streamSinks[streamId];
switch (data.stream) {
case StreamKind.START_COMPLETE:
if (data.success) {
streamController.startCall.resolve();
} else {
streamController.startCall.reject(wrapReason(data.reason));
}
break;
case StreamKind.PULL_COMPLETE:
if (data.success) {
streamController.pullCall.resolve();
} else {
streamController.pullCall.reject(wrapReason(data.reason));
}
break;
case StreamKind.PULL:
if (!streamSink) {
comObj.postMessage({
sourceName,
targetName,
stream: StreamKind.PULL_COMPLETE,
streamId,
success: true
});
break;
}
if (streamSink.desiredSize <= 0 && data.desiredSize > 0) {
streamSink.sinkCapability.resolve();
}
streamSink.desiredSize = data.desiredSize;
Promise.try(streamSink.onPull || onFn).then(function () {
comObj.postMessage({
sourceName,
targetName,
stream: StreamKind.PULL_COMPLETE,
streamId,
success: true
});
}, function (reason) {
comObj.postMessage({
sourceName,
targetName,
stream: StreamKind.PULL_COMPLETE,
streamId,
reason: wrapReason(reason)
});
});
break;
case StreamKind.ENQUEUE:
assert(streamController, "enqueue should have stream controller");
if (streamController.isClosed) {
break;
}
streamController.controller.enqueue(data.chunk);
break;
case StreamKind.CLOSE:
assert(streamController, "close should have stream controller");
if (streamController.isClosed) {
break;
}
streamController.isClosed = true;
streamController.controller.close();
this.#deleteStreamController(streamController, streamId);
break;
case StreamKind.ERROR:
assert(streamController, "error should have stream controller");
streamController.controller.error(wrapReason(data.reason));
this.#deleteStreamController(streamController, streamId);
break;
case StreamKind.CANCEL_COMPLETE:
if (data.success) {
streamController.cancelCall.resolve();
} else {
streamController.cancelCall.reject(wrapReason(data.reason));
}
this.#deleteStreamController(streamController, streamId);
break;
case StreamKind.CANCEL:
if (!streamSink) {
break;
}
const dataReason = wrapReason(data.reason);
Promise.try(streamSink.onCancel || onFn, dataReason).then(function () {
comObj.postMessage({
sourceName,
targetName,
stream: StreamKind.CANCEL_COMPLETE,
streamId,
success: true
});
}, function (reason) {
comObj.postMessage({
sourceName,
targetName,
stream: StreamKind.CANCEL_COMPLETE,
streamId,
reason: wrapReason(reason)
});
});
streamSink.sinkCapability.reject(dataReason);
streamSink.isCancelled = true;
delete this.streamSinks[streamId];
break;
default:
throw new Error("Unexpected stream case");
}
}
async #deleteStreamController(streamController, streamId) {
await Promise.allSettled([streamController.startCall?.promise, streamController.pullCall?.promise, streamController.cancelCall?.promise]);
delete this.streamControllers[streamId];
}
destroy() {
this.#messageAC?.abort();
this.#messageAC = null;
}
}
;// ./src/display/canvas_factory.js
class BaseCanvasFactory {
#enableHWA = false;
constructor({
enableHWA = false
}) {
this.#enableHWA = enableHWA;
}
create(width, height) {
if (width <= 0 || height <= 0) {
throw new Error("Invalid canvas size");
}
const canvas = this._createCanvas(width, height);
return {
canvas,
context: canvas.getContext("2d", {
willReadFrequently: !this.#enableHWA
})
};
}
reset(canvasAndContext, width, height) {
if (!canvasAndContext.canvas) {
throw new Error("Canvas is not specified");
}
if (width <= 0 || height <= 0) {
throw new Error("Invalid canvas size");
}
canvasAndContext.canvas.width = width;
canvasAndContext.canvas.height = height;
}
destroy(canvasAndContext) {
if (!canvasAndContext.canvas) {
throw new Error("Canvas is not specified");
}
canvasAndContext.canvas.width = 0;
canvasAndContext.canvas.height = 0;
canvasAndContext.canvas = null;
canvasAndContext.context = null;
}
_createCanvas(width, height) {
unreachable("Abstract method `_createCanvas` called.");
}
}
class DOMCanvasFactory extends BaseCanvasFactory {
constructor({
ownerDocument = globalThis.document,
enableHWA = false
}) {
super({
enableHWA
});
this._document = ownerDocument;
}
_createCanvas(width, height) {
const canvas = this._document.createElement("canvas");
canvas.width = width;
canvas.height = height;
return canvas;
}
}
;// ./src/display/cmap_reader_factory.js
class BaseCMapReaderFactory {
constructor({
baseUrl = null,
isCompressed = true
}) {
this.baseUrl = baseUrl;
this.isCompressed = isCompressed;
}
async fetch({
name
}) {
if (!this.baseUrl) {
throw new Error("Ensure that the `cMapUrl` and `cMapPacked` API parameters are provided.");
}
if (!name) {
throw new Error("CMap name must be specified.");
}
const url = this.baseUrl + name + (this.isCompressed ? ".bcmap" : "");
return this._fetch(url).then(cMapData => ({
cMapData,
isCompressed: this.isCompressed
})).catch(reason => {
throw new Error(`Unable to load ${this.isCompressed ? "binary " : ""}CMap at: ${url}`);
});
}
async _fetch(url) {
unreachable("Abstract method `_fetch` called.");
}
}
class DOMCMapReaderFactory extends BaseCMapReaderFactory {
async _fetch(url) {
const data = await fetchData(url, this.isCompressed ? "arraybuffer" : "text");
return data instanceof ArrayBuffer ? new Uint8Array(data) : stringToBytes(data);
}
}
;// ./src/display/filter_factory.js
class BaseFilterFactory {
addFilter(maps) {
return "none";
}
addHCMFilter(fgColor, bgColor) {
return "none";
}
addAlphaFilter(map) {
return "none";
}
addLuminosityFilter(map) {
return "none";
}
addHighlightHCMFilter(filterName, fgColor, bgColor, newFgColor, newBgColor) {
return "none";
}
destroy(keepHCM = false) {}
}
class DOMFilterFactory extends BaseFilterFactory {
#baseUrl;
#_cache;
#_defs;
#docId;
#document;
#_hcmCache;
#id = 0;
constructor({
docId,
ownerDocument = globalThis.document
}) {
super();
this.#docId = docId;
this.#document = ownerDocument;
}
get #cache() {
return this.#_cache ||= new Map();
}
get #hcmCache() {
return this.#_hcmCache ||= new Map();
}
get #defs() {
if (!this.#_defs) {
const div = this.#document.createElement("div");
const {
style
} = div;
style.visibility = "hidden";
style.contain = "strict";
style.width = style.height = 0;
style.position = "absolute";
style.top = style.left = 0;
style.zIndex = -1;
const svg = this.#document.createElementNS(SVG_NS, "svg");
svg.setAttribute("width", 0);
svg.setAttribute("height", 0);
this.#_defs = this.#document.createElementNS(SVG_NS, "defs");
div.append(svg);
svg.append(this.#_defs);
this.#document.body.append(div);
}
return this.#_defs;
}
#createTables(maps) {
if (maps.length === 1) {
const mapR = maps[0];
const buffer = new Array(256);
for (let i = 0; i < 256; i++) {
buffer[i] = mapR[i] / 255;
}
const table = buffer.join(",");
return [table, table, table];
}
const [mapR, mapG, mapB] = maps;
const bufferR = new Array(256);
const bufferG = new Array(256);
const bufferB = new Array(256);
for (let i = 0; i < 256; i++) {
bufferR[i] = mapR[i] / 255;
bufferG[i] = mapG[i] / 255;
bufferB[i] = mapB[i] / 255;
}
return [bufferR.join(","), bufferG.join(","), bufferB.join(",")];
}
#createUrl(id) {
if (this.#baseUrl === undefined) {
this.#baseUrl = "";
const url = this.#document.URL;
if (url !== this.#document.baseURI) {
if (isDataScheme(url)) {
warn('#createUrl: ignore "data:"-URL for performance reasons.');
} else {
this.#baseUrl = url.split("#", 1)[0];
}
}
}
return `url(${this.#baseUrl}#${id})`;
}
addFilter(maps) {
if (!maps) {
return "none";
}
let value = this.#cache.get(maps);
if (value) {
return value;
}
const [tableR, tableG, tableB] = this.#createTables(maps);
const key = maps.length === 1 ? tableR : `${tableR}${tableG}${tableB}`;
value = this.#cache.get(key);
if (value) {
this.#cache.set(maps, value);
return value;
}
const id = `g_${this.#docId}_transfer_map_${this.#id++}`;
const url = this.#createUrl(id);
this.#cache.set(maps, url);
this.#cache.set(key, url);
const filter = this.#createFilter(id);
this.#addTransferMapConversion(tableR, tableG, tableB, filter);
return url;
}
addHCMFilter(fgColor, bgColor) {
const key = `${fgColor}-${bgColor}`;
const filterName = "base";
let info = this.#hcmCache.get(filterName);
if (info?.key === key) {
return info.url;
}
if (info) {
info.filter?.remove();
info.key = key;
info.url = "none";
info.filter = null;
} else {
info = {
key,
url: "none",
filter: null
};
this.#hcmCache.set(filterName, info);
}
if (!fgColor || !bgColor) {
return info.url;
}
const fgRGB = this.#getRGB(fgColor);
fgColor = Util.makeHexColor(...fgRGB);
const bgRGB = this.#getRGB(bgColor);
bgColor = Util.makeHexColor(...bgRGB);
this.#defs.style.color = "";
if (fgColor === "#000000" && bgColor === "#ffffff" || fgColor === bgColor) {
return info.url;
}
const map = new Array(256);
for (let i = 0; i <= 255; i++) {
const x = i / 255;
map[i] = x <= 0.03928 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4;
}
const table = map.join(",");
const id = `g_${this.#docId}_hcm_filter`;
const filter = info.filter = this.#createFilter(id);
this.#addTransferMapConversion(table, table, table, filter);
this.#addGrayConversion(filter);
const getSteps = (c, n) => {
const start = fgRGB[c] / 255;
const end = bgRGB[c] / 255;
const arr = new Array(n + 1);
for (let i = 0; i <= n; i++) {
arr[i] = start + i / n * (end - start);
}
return arr.join(",");
};
this.#addTransferMapConversion(getSteps(0, 5), getSteps(1, 5), getSteps(2, 5), filter);
info.url = this.#createUrl(id);
return info.url;
}
addAlphaFilter(map) {
let value = this.#cache.get(map);
if (value) {
return value;
}
const [tableA] = this.#createTables([map]);
const key = `alpha_${tableA}`;
value = this.#cache.get(key);
if (value) {
this.#cache.set(map, value);
return value;
}
const id = `g_${this.#docId}_alpha_map_${this.#id++}`;
const url = this.#createUrl(id);
this.#cache.set(map, url);
this.#cache.set(key, url);
const filter = this.#createFilter(id);
this.#addTransferMapAlphaConversion(tableA, filter);
return url;
}
addLuminosityFilter(map) {
let value = this.#cache.get(map || "luminosity");
if (value) {
return value;
}
let tableA, key;
if (map) {
[tableA] = this.#createTables([map]);
key = `luminosity_${tableA}`;
} else {
key = "luminosity";
}
value = this.#cache.get(key);
if (value) {
this.#cache.set(map, value);
return value;
}
const id = `g_${this.#docId}_luminosity_map_${this.#id++}`;
const url = this.#createUrl(id);
this.#cache.set(map, url);
this.#cache.set(key, url);
const filter = this.#createFilter(id);
this.#addLuminosityConversion(filter);
if (map) {
this.#addTransferMapAlphaConversion(tableA, filter);
}
return url;
}
addHighlightHCMFilter(filterName, fgColor, bgColor, newFgColor, newBgColor) {
const key = `${fgColor}-${bgColor}-${newFgColor}-${newBgColor}`;
let info = this.#hcmCache.get(filterName);
if (info?.key === key) {
return info.url;
}
if (info) {
info.filter?.remove();
info.key = key;
info.url = "none";
info.filter = null;
} else {
info = {
key,
url: "none",
filter: null
};
this.#hcmCache.set(filterName, info);
}
if (!fgColor || !bgColor) {
return info.url;
}
const [fgRGB, bgRGB] = [fgColor, bgColor].map(this.#getRGB.bind(this));
let fgGray = Math.round(0.2126 * fgRGB[0] + 0.7152 * fgRGB[1] + 0.0722 * fgRGB[2]);
let bgGray = Math.round(0.2126 * bgRGB[0] + 0.7152 * bgRGB[1] + 0.0722 * bgRGB[2]);
let [newFgRGB, newBgRGB] = [newFgColor, newBgColor].map(this.#getRGB.bind(this));
if (bgGray < fgGray) {
[fgGray, bgGray, newFgRGB, newBgRGB] = [bgGray, fgGray, newBgRGB, newFgRGB];
}
this.#defs.style.color = "";
const getSteps = (fg, bg, n) => {
const arr = new Array(256);
const step = (bgGray - fgGray) / n;
const newStart = fg / 255;
const newStep = (bg - fg) / (255 * n);
let prev = 0;
for (let i = 0; i <= n; i++) {
const k = Math.round(fgGray + i * step);
const value = newStart + i * newStep;
for (let j = prev; j <= k; j++) {
arr[j] = value;
}
prev = k + 1;
}
for (let i = prev; i < 256; i++) {
arr[i] = arr[prev - 1];
}
return arr.join(",");
};
const id = `g_${this.#docId}_hcm_${filterName}_filter`;
const filter = info.filter = this.#createFilter(id);
this.#addGrayConversion(filter);
this.#addTransferMapConversion(getSteps(newFgRGB[0], newBgRGB[0], 5), getSteps(newFgRGB[1], newBgRGB[1], 5), getSteps(newFgRGB[2], newBgRGB[2], 5), filter);
info.url = this.#createUrl(id);
return info.url;
}
destroy(keepHCM = false) {
if (keepHCM && this.#_hcmCache?.size) {
return;
}
this.#_defs?.parentNode.parentNode.remove();
this.#_defs = null;
this.#_cache?.clear();
this.#_cache = null;
this.#_hcmCache?.clear();
this.#_hcmCache = null;
this.#id = 0;
}
#addLuminosityConversion(filter) {
const feColorMatrix = this.#document.createElementNS(SVG_NS, "feColorMatrix");
feColorMatrix.setAttribute("type", "matrix");
feColorMatrix.setAttribute("values", "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0.59 0.11 0 0");
filter.append(feColorMatrix);
}
#addGrayConversion(filter) {
const feColorMatrix = this.#document.createElementNS(SVG_NS, "feColorMatrix");
feColorMatrix.setAttribute("type", "matrix");
feColorMatrix.setAttribute("values", "0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0 0 0 1 0");
filter.append(feColorMatrix);
}
#createFilter(id) {
const filter = this.#document.createElementNS(SVG_NS, "filter");
filter.setAttribute("color-interpolation-filters", "sRGB");
filter.setAttribute("id", id);
this.#defs.append(filter);
return filter;
}
#appendFeFunc(feComponentTransfer, func, table) {
const feFunc = this.#document.createElementNS(SVG_NS, func);
feFunc.setAttribute("type", "discrete");
feFunc.setAttribute("tableValues", table);
feComponentTransfer.append(feFunc);
}
#addTransferMapConversion(rTable, gTable, bTable, filter) {
const feComponentTransfer = this.#document.createElementNS(SVG_NS, "feComponentTransfer");
filter.append(feComponentTransfer);
this.#appendFeFunc(feComponentTransfer, "feFuncR", rTable);
this.#appendFeFunc(feComponentTransfer, "feFuncG", gTable);
this.#appendFeFunc(feComponentTransfer, "feFuncB", bTable);
}
#addTransferMapAlphaConversion(aTable, filter) {
const feComponentTransfer = this.#document.createElementNS(SVG_NS, "feComponentTransfer");
filter.append(feComponentTransfer);
this.#appendFeFunc(feComponentTransfer, "feFuncA", aTable);
}
#getRGB(color) {
this.#defs.style.color = color;
return getRGB(getComputedStyle(this.#defs).getPropertyValue("color"));
}
}
;// ./src/display/standard_fontdata_factory.js
class BaseStandardFontDataFactory {
constructor({
baseUrl = null
}) {
this.baseUrl = baseUrl;
}
async fetch({
filename
}) {
if (!this.baseUrl) {
throw new Error("Ensure that the `standardFontDataUrl` API parameter is provided.");
}
if (!filename) {
throw new Error("Font filename must be specified.");
}
const url = `${this.baseUrl}${filename}`;
return this._fetch(url).catch(reason => {
throw new Error(`Unable to load font data at: ${url}`);
});
}
async _fetch(url) {
unreachable("Abstract method `_fetch` called.");
}
}
class DOMStandardFontDataFactory extends BaseStandardFontDataFactory {
async _fetch(url) {
const data = await fetchData(url, "arraybuffer");
return new Uint8Array(data);
}
}
;// ./src/display/wasm_factory.js
class BaseWasmFactory {
constructor({
baseUrl = null
}) {
this.baseUrl = baseUrl;
}
async fetch({
filename
}) {
if (!this.baseUrl) {
throw new Error("Ensure that the `wasmUrl` API parameter is provided.");
}
if (!filename) {
throw new Error("Wasm filename must be specified.");
}
const url = `${this.baseUrl}${filename}`;
return this._fetch(url).catch(reason => {
throw new Error(`Unable to load wasm data at: ${url}`);
});
}
async _fetch(url) {
unreachable("Abstract method `_fetch` called.");
}
}
class DOMWasmFactory extends BaseWasmFactory {
async _fetch(url) {
const data = await fetchData(url, "arraybuffer");
return new Uint8Array(data);
}
}
;// ./src/display/node_utils.js
if (isNodeJS) {
warn("Please use the `legacy` build in Node.js environments.");
}
async function node_utils_fetchData(url) {
const fs = process.getBuiltinModule("fs");
const data = await fs.promises.readFile(url);
return new Uint8Array(data);
}
class NodeFilterFactory extends BaseFilterFactory {}
class NodeCanvasFactory extends BaseCanvasFactory {
_createCanvas(width, height) {
const require = process.getBuiltinModule("module").createRequire(import.meta.url);
const canvas = require("@napi-rs/canvas");
return canvas.createCanvas(width, height);
}
}
class NodeCMapReaderFactory extends BaseCMapReaderFactory {
async _fetch(url) {
return node_utils_fetchData(url);
}
}
class NodeStandardFontDataFactory extends BaseStandardFontDataFactory {
async _fetch(url) {
return node_utils_fetchData(url);
}
}
class NodeWasmFactory extends BaseWasmFactory {
async _fetch(url) {
return node_utils_fetchData(url);
}
}
;// ./src/display/pattern_helper.js
const PathType = {
FILL: "Fill",
STROKE: "Stroke",
SHADING: "Shading"
};
function applyBoundingBox(ctx, bbox) {
if (!bbox) {
return;
}
const width = bbox[2] - bbox[0];
const height = bbox[3] - bbox[1];
const region = new Path2D();
region.rect(bbox[0], bbox[1], width, height);
ctx.clip(region);
}
class BaseShadingPattern {
isModifyingCurrentTransform() {
return false;
}
getPattern() {
unreachable("Abstract method `getPattern` called.");
}
}
class RadialAxialShadingPattern extends BaseShadingPattern {
constructor(IR) {
super();
this._type = IR[1];
this._bbox = IR[2];
this._colorStops = IR[3];
this._p0 = IR[4];
this._p1 = IR[5];
this._r0 = IR[6];
this._r1 = IR[7];
this.matrix = null;
}
_createGradient(ctx) {
let grad;
if (this._type === "axial") {
grad = ctx.createLinearGradient(this._p0[0], this._p0[1], this._p1[0], this._p1[1]);
} else if (this._type === "radial") {
grad = ctx.createRadialGradient(this._p0[0], this._p0[1], this._r0, this._p1[0], this._p1[1], this._r1);
}
for (const colorStop of this._colorStops) {
grad.addColorStop(colorStop[0], colorStop[1]);
}
return grad;
}
getPattern(ctx, owner, inverse, pathType) {
let pattern;
if (pathType === PathType.STROKE || pathType === PathType.FILL) {
const ownerBBox = owner.current.getClippedPathBoundingBox(pathType, getCurrentTransform(ctx)) || [0, 0, 0, 0];
const width = Math.ceil(ownerBBox[2] - ownerBBox[0]) || 1;
const height = Math.ceil(ownerBBox[3] - ownerBBox[1]) || 1;
const tmpCanvas = owner.cachedCanvases.getCanvas("pattern", width, height);
const tmpCtx = tmpCanvas.context;
tmpCtx.clearRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
tmpCtx.beginPath();
tmpCtx.rect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
tmpCtx.translate(-ownerBBox[0], -ownerBBox[1]);
inverse = Util.transform(inverse, [1, 0, 0, 1, ownerBBox[0], ownerBBox[1]]);
tmpCtx.transform(...owner.baseTransform);
if (this.matrix) {
tmpCtx.transform(...this.matrix);
}
applyBoundingBox(tmpCtx, this._bbox);
tmpCtx.fillStyle = this._createGradient(tmpCtx);
tmpCtx.fill();
pattern = ctx.createPattern(tmpCanvas.canvas, "no-repeat");
const domMatrix = new DOMMatrix(inverse);
pattern.setTransform(domMatrix);
} else {
applyBoundingBox(ctx, this._bbox);
pattern = this._createGradient(ctx);
}
return pattern;
}
}
function drawTriangle(data, context, p1, p2, p3, c1, c2, c3) {
const coords = context.coords,
colors = context.colors;
const bytes = data.data,
rowSize = data.width * 4;
let tmp;
if (coords[p1 + 1] > coords[p2 + 1]) {
tmp = p1;
p1 = p2;
p2 = tmp;
tmp = c1;
c1 = c2;
c2 = tmp;
}
if (coords[p2 + 1] > coords[p3 + 1]) {
tmp = p2;
p2 = p3;
p3 = tmp;
tmp = c2;
c2 = c3;
c3 = tmp;
}
if (coords[p1 + 1] > coords[p2 + 1]) {
tmp = p1;
p1 = p2;
p2 = tmp;
tmp = c1;
c1 = c2;
c2 = tmp;
}
const x1 = (coords[p1] + context.offsetX) * context.scaleX;
const y1 = (coords[p1 + 1] + context.offsetY) * context.scaleY;
const x2 = (coords[p2] + context.offsetX) * context.scaleX;
const y2 = (coords[p2 + 1] + context.offsetY) * context.scaleY;
const x3 = (coords[p3] + context.offsetX) * context.scaleX;
const y3 = (coords[p3 + 1] + context.offsetY) * context.scaleY;
if (y1 >= y3) {
return;
}
const c1r = colors[c1],
c1g = colors[c1 + 1],
c1b = colors[c1 + 2];
const c2r = colors[c2],
c2g = colors[c2 + 1],
c2b = colors[c2 + 2];
const c3r = colors[c3],
c3g = colors[c3 + 1],
c3b = colors[c3 + 2];
const minY = Math.round(y1),
maxY = Math.round(y3);
let xa, car, cag, cab;
let xb, cbr, cbg, cbb;
for (let y = minY; y <= maxY; y++) {
if (y < y2) {
const k = y < y1 ? 0 : (y1 - y) / (y1 - y2);
xa = x1 - (x1 - x2) * k;
car = c1r - (c1r - c2r) * k;
cag = c1g - (c1g - c2g) * k;
cab = c1b - (c1b - c2b) * k;
} else {
let k;
if (y > y3) {
k = 1;
} else if (y2 === y3) {
k = 0;
} else {
k = (y2 - y) / (y2 - y3);
}
xa = x2 - (x2 - x3) * k;
car = c2r - (c2r - c3r) * k;
cag = c2g - (c2g - c3g) * k;
cab = c2b - (c2b - c3b) * k;
}
let k;
if (y < y1) {
k = 0;
} else if (y > y3) {
k = 1;
} else {
k = (y1 - y) / (y1 - y3);
}
xb = x1 - (x1 - x3) * k;
cbr = c1r - (c1r - c3r) * k;
cbg = c1g - (c1g - c3g) * k;
cbb = c1b - (c1b - c3b) * k;
const x1_ = Math.round(Math.min(xa, xb));
const x2_ = Math.round(Math.max(xa, xb));
let j = rowSize * y + x1_ * 4;
for (let x = x1_; x <= x2_; x++) {
k = (xa - x) / (xa - xb);
if (k < 0) {
k = 0;
} else if (k > 1) {
k = 1;
}
bytes[j++] = car - (car - cbr) * k | 0;
bytes[j++] = cag - (cag - cbg) * k | 0;
bytes[j++] = cab - (cab - cbb) * k | 0;
bytes[j++] = 255;
}
}
}
function drawFigure(data, figure, context) {
const ps = figure.coords;
const cs = figure.colors;
let i, ii;
switch (figure.type) {
case "lattice":
const verticesPerRow = figure.verticesPerRow;
const rows = Math.floor(ps.length / verticesPerRow) - 1;
const cols = verticesPerRow - 1;
for (i = 0; i < rows; i++) {
let q = i * verticesPerRow;
for (let j = 0; j < cols; j++, q++) {
drawTriangle(data, context, ps[q], ps[q + 1], ps[q + verticesPerRow], cs[q], cs[q + 1], cs[q + verticesPerRow]);
drawTriangle(data, context, ps[q + verticesPerRow + 1], ps[q + 1], ps[q + verticesPerRow], cs[q + verticesPerRow + 1], cs[q + 1], cs[q + verticesPerRow]);
}
}
break;
case "triangles":
for (i = 0, ii = ps.length; i < ii; i += 3) {
drawTriangle(data, context, ps[i], ps[i + 1], ps[i + 2], cs[i], cs[i + 1], cs[i + 2]);
}
break;
default:
throw new Error("illegal figure");
}
}
class MeshShadingPattern extends BaseShadingPattern {
constructor(IR) {
super();
this._coords = IR[2];
this._colors = IR[3];
this._figures = IR[4];
this._bounds = IR[5];
this._bbox = IR[6];
this._background = IR[7];
this.matrix = null;
}
_createMeshCanvas(combinedScale, backgroundColor, cachedCanvases) {
const EXPECTED_SCALE = 1.1;
const MAX_PATTERN_SIZE = 3000;
const BORDER_SIZE = 2;
const offsetX = Math.floor(this._bounds[0]);
const offsetY = Math.floor(this._bounds[1]);
const boundsWidth = Math.ceil(this._bounds[2]) - offsetX;
const boundsHeight = Math.ceil(this._bounds[3]) - offsetY;
const width = Math.min(Math.ceil(Math.abs(boundsWidth * combinedScale[0] * EXPECTED_SCALE)), MAX_PATTERN_SIZE);
const height = Math.min(Math.ceil(Math.abs(boundsHeight * combinedScale[1] * EXPECTED_SCALE)), MAX_PATTERN_SIZE);
const scaleX = boundsWidth / width;
const scaleY = boundsHeight / height;
const context = {
coords: this._coords,
colors: this._colors,
offsetX: -offsetX,
offsetY: -offsetY,
scaleX: 1 / scaleX,
scaleY: 1 / scaleY
};
const paddedWidth = width + BORDER_SIZE * 2;
const paddedHeight = height + BORDER_SIZE * 2;
const tmpCanvas = cachedCanvases.getCanvas("mesh", paddedWidth, paddedHeight);
const tmpCtx = tmpCanvas.context;
const data = tmpCtx.createImageData(width, height);
if (backgroundColor) {
const bytes = data.data;
for (let i = 0, ii = bytes.length; i < ii; i += 4) {
bytes[i] = backgroundColor[0];
bytes[i + 1] = backgroundColor[1];
bytes[i + 2] = backgroundColor[2];
bytes[i + 3] = 255;
}
}
for (const figure of this._figures) {
drawFigure(data, figure, context);
}
tmpCtx.putImageData(data, BORDER_SIZE, BORDER_SIZE);
const canvas = tmpCanvas.canvas;
return {
canvas,
offsetX: offsetX - BORDER_SIZE * scaleX,
offsetY: offsetY - BORDER_SIZE * scaleY,
scaleX,
scaleY
};
}
isModifyingCurrentTransform() {
return true;
}
getPattern(ctx, owner, inverse, pathType) {
applyBoundingBox(ctx, this._bbox);
let scale;
if (pathType === PathType.SHADING) {
scale = Util.singularValueDecompose2dScale(getCurrentTransform(ctx));
} else {
scale = Util.singularValueDecompose2dScale(owner.baseTransform);
if (this.matrix) {
const matrixScale = Util.singularValueDecompose2dScale(this.matrix);
scale = [scale[0] * matrixScale[0], scale[1] * matrixScale[1]];
}
}
const temporaryPatternCanvas = this._createMeshCanvas(scale, pathType === PathType.SHADING ? null : this._background, owner.cachedCanvases);
if (pathType !== PathType.SHADING) {
ctx.setTransform(...owner.baseTransform);
if (this.matrix) {
ctx.transform(...this.matrix);
}
}
ctx.translate(temporaryPatternCanvas.offsetX, temporaryPatternCanvas.offsetY);
ctx.scale(temporaryPatternCanvas.scaleX, temporaryPatternCanvas.scaleY);
return ctx.createPattern(temporaryPatternCanvas.canvas, "no-repeat");
}
}
class DummyShadingPattern extends BaseShadingPattern {
getPattern() {
return "hotpink";
}
}
function getShadingPattern(IR) {
switch (IR[0]) {
case "RadialAxial":
return new RadialAxialShadingPattern(IR);
case "Mesh":
return new MeshShadingPattern(IR);
case "Dummy":
return new DummyShadingPattern();
}
throw new Error(`Unknown IR type: ${IR[0]}`);
}
const PaintType = {
COLORED: 1,
UNCOLORED: 2
};
class TilingPattern {
static MAX_PATTERN_SIZE = 3000;
constructor(IR, ctx, canvasGraphicsFactory, baseTransform) {
this.color = IR[1];
this.operatorList = IR[2];
this.matrix = IR[3];
this.bbox = IR[4];
this.xstep = IR[5];
this.ystep = IR[6];
this.paintType = IR[7];
this.tilingType = IR[8];
this.ctx = ctx;
this.canvasGraphicsFactory = canvasGraphicsFactory;
this.baseTransform = baseTransform;
}
createPatternCanvas(owner) {
const {
bbox,
operatorList,
paintType,
tilingType,
color,
canvasGraphicsFactory
} = this;
let {
xstep,
ystep
} = this;
xstep = Math.abs(xstep);
ystep = Math.abs(ystep);
info("TilingType: " + tilingType);
const x0 = bbox[0],
y0 = bbox[1],
x1 = bbox[2],
y1 = bbox[3];
const width = x1 - x0;
const height = y1 - y0;
const matrixScale = Util.singularValueDecompose2dScale(this.matrix);
const curMatrixScale = Util.singularValueDecompose2dScale(this.baseTransform);
const combinedScaleX = matrixScale[0] * curMatrixScale[0];
const combinedScaleY = matrixScale[1] * curMatrixScale[1];
let canvasWidth = width,
canvasHeight = height,
redrawHorizontally = false,
redrawVertically = false;
const xScaledStep = Math.ceil(xstep * combinedScaleX);
const yScaledStep = Math.ceil(ystep * combinedScaleY);
const xScaledWidth = Math.ceil(width * combinedScaleX);
const yScaledHeight = Math.ceil(height * combinedScaleY);
if (xScaledStep >= xScaledWidth) {
canvasWidth = xstep;
} else {
redrawHorizontally = true;
}
if (yScaledStep >= yScaledHeight) {
canvasHeight = ystep;
} else {
redrawVertically = true;
}
const dimx = this.getSizeAndScale(canvasWidth, this.ctx.canvas.width, combinedScaleX);
const dimy = this.getSizeAndScale(canvasHeight, this.ctx.canvas.height, combinedScaleY);
const tmpCanvas = owner.cachedCanvases.getCanvas("pattern", dimx.size, dimy.size);
const tmpCtx = tmpCanvas.context;
const graphics = canvasGraphicsFactory.createCanvasGraphics(tmpCtx);
graphics.groupLevel = owner.groupLevel;
this.setFillAndStrokeStyleToContext(graphics, paintType, color);
tmpCtx.translate(-dimx.scale * x0, -dimy.scale * y0);
graphics.transform(dimx.scale, 0, 0, dimy.scale, 0, 0);
tmpCtx.save();
this.clipBbox(graphics, x0, y0, x1, y1);
graphics.baseTransform = getCurrentTransform(graphics.ctx);
graphics.executeOperatorList(operatorList);
graphics.endDrawing();
tmpCtx.restore();
if (redrawHorizontally || redrawVertically) {
const image = tmpCanvas.canvas;
if (redrawHorizontally) {
canvasWidth = xstep;
}
if (redrawVertically) {
canvasHeight = ystep;
}
const dimx2 = this.getSizeAndScale(canvasWidth, this.ctx.canvas.width, combinedScaleX);
const dimy2 = this.getSizeAndScale(canvasHeight, this.ctx.canvas.height, combinedScaleY);
const xSize = dimx2.size;
const ySize = dimy2.size;
const tmpCanvas2 = owner.cachedCanvases.getCanvas("pattern-workaround", xSize, ySize);
const tmpCtx2 = tmpCanvas2.context;
const ii = redrawHorizontally ? Math.floor(width / xstep) : 0;
const jj = redrawVertically ? Math.floor(height / ystep) : 0;
for (let i = 0; i <= ii; i++) {
for (let j = 0; j <= jj; j++) {
tmpCtx2.drawImage(image, xSize * i, ySize * j, xSize, ySize, 0, 0, xSize, ySize);
}
}
return {
canvas: tmpCanvas2.canvas,
scaleX: dimx2.scale,
scaleY: dimy2.scale,
offsetX: x0,
offsetY: y0
};
}
return {
canvas: tmpCanvas.canvas,
scaleX: dimx.scale,
scaleY: dimy.scale,
offsetX: x0,
offsetY: y0
};
}
getSizeAndScale(step, realOutputSize, scale) {
const maxSize = Math.max(TilingPattern.MAX_PATTERN_SIZE, realOutputSize);
let size = Math.ceil(step * scale);
if (size >= maxSize) {
size = maxSize;
} else {
scale = size / step;
}
return {
scale,
size
};
}
clipBbox(graphics, x0, y0, x1, y1) {
const bboxWidth = x1 - x0;
const bboxHeight = y1 - y0;
graphics.ctx.rect(x0, y0, bboxWidth, bboxHeight);
graphics.current.updateRectMinMax(getCurrentTransform(graphics.ctx), [x0, y0, x1, y1]);
graphics.clip();
graphics.endPath();
}
setFillAndStrokeStyleToContext(graphics, paintType, color) {
const context = graphics.ctx,
current = graphics.current;
switch (paintType) {
case PaintType.COLORED:
const ctx = this.ctx;
context.fillStyle = ctx.fillStyle;
context.strokeStyle = ctx.strokeStyle;
current.fillColor = ctx.fillStyle;
current.strokeColor = ctx.strokeStyle;
break;
case PaintType.UNCOLORED:
const cssColor = Util.makeHexColor(color[0], color[1], color[2]);
context.fillStyle = cssColor;
context.strokeStyle = cssColor;
current.fillColor = cssColor;
current.strokeColor = cssColor;
break;
default:
throw new FormatError(`Unsupported paint type: ${paintType}`);
}
}
isModifyingCurrentTransform() {
return false;
}
getPattern(ctx, owner, inverse, pathType) {
let matrix = inverse;
if (pathType !== PathType.SHADING) {
matrix = Util.transform(matrix, owner.baseTransform);
if (this.matrix) {
matrix = Util.transform(matrix, this.matrix);
}
}
const temporaryPatternCanvas = this.createPatternCanvas(owner);
let domMatrix = new DOMMatrix(matrix);
domMatrix = domMatrix.translate(temporaryPatternCanvas.offsetX, temporaryPatternCanvas.offsetY);
domMatrix = domMatrix.scale(1 / temporaryPatternCanvas.scaleX, 1 / temporaryPatternCanvas.scaleY);
const pattern = ctx.createPattern(temporaryPatternCanvas.canvas, "repeat");
pattern.setTransform(domMatrix);
return pattern;
}
}
;// ./src/shared/image_utils.js
function convertToRGBA(params) {
switch (params.kind) {
case ImageKind.GRAYSCALE_1BPP:
return convertBlackAndWhiteToRGBA(params);
case ImageKind.RGB_24BPP:
return convertRGBToRGBA(params);
}
return null;
}
function convertBlackAndWhiteToRGBA({
src,
srcPos = 0,
dest,
width,
height,
nonBlackColor = 0xffffffff,
inverseDecode = false
}) {
const black = util_FeatureTest.isLittleEndian ? 0xff000000 : 0x000000ff;
const [zeroMapping, oneMapping] = inverseDecode ? [nonBlackColor, black] : [black, nonBlackColor];
const widthInSource = width >> 3;
const widthRemainder = width & 7;
const srcLength = src.length;
dest = new Uint32Array(dest.buffer);
let destPos = 0;
for (let i = 0; i < height; i++) {
for (const max = srcPos + widthInSource; srcPos < max; srcPos++) {
const elem = srcPos < srcLength ? src[srcPos] : 255;
dest[destPos++] = elem & 0b10000000 ? oneMapping : zeroMapping;
dest[destPos++] = elem & 0b1000000 ? oneMapping : zeroMapping;
dest[destPos++] = elem & 0b100000 ? oneMapping : zeroMapping;
dest[destPos++] = elem & 0b10000 ? oneMapping : zeroMapping;
dest[destPos++] = elem & 0b1000 ? oneMapping : zeroMapping;
dest[destPos++] = elem & 0b100 ? oneMapping : zeroMapping;
dest[destPos++] = elem & 0b10 ? oneMapping : zeroMapping;
dest[destPos++] = elem & 0b1 ? oneMapping : zeroMapping;
}
if (widthRemainder === 0) {
continue;
}
const elem = srcPos < srcLength ? src[srcPos++] : 255;
for (let j = 0; j < widthRemainder; j++) {
dest[destPos++] = elem & 1 << 7 - j ? oneMapping : zeroMapping;
}
}
return {
srcPos,
destPos
};
}
function convertRGBToRGBA({
src,
srcPos = 0,
dest,
destPos = 0,
width,
height
}) {
let i = 0;
const len = width * height * 3;
const len32 = len >> 2;
const src32 = new Uint32Array(src.buffer, srcPos, len32);
if (FeatureTest.isLittleEndian) {
for (; i < len32 - 2; i += 3, destPos += 4) {
const s1 = src32[i];
const s2 = src32[i + 1];
const s3 = src32[i + 2];
dest[destPos] = s1 | 0xff000000;
dest[destPos + 1] = s1 >>> 24 | s2 << 8 | 0xff000000;
dest[destPos + 2] = s2 >>> 16 | s3 << 16 | 0xff000000;
dest[destPos + 3] = s3 >>> 8 | 0xff000000;
}
for (let j = i * 4, jj = srcPos + len; j < jj; j += 3) {
dest[destPos++] = src[j] | src[j + 1] << 8 | src[j + 2] << 16 | 0xff000000;
}
} else {
for (; i < len32 - 2; i += 3, destPos += 4) {
const s1 = src32[i];
const s2 = src32[i + 1];
const s3 = src32[i + 2];
dest[destPos] = s1 | 0xff;
dest[destPos + 1] = s1 << 24 | s2 >>> 8 | 0xff;
dest[destPos + 2] = s2 << 16 | s3 >>> 16 | 0xff;
dest[destPos + 3] = s3 << 8 | 0xff;
}
for (let j = i * 4, jj = srcPos + len; j < jj; j += 3) {
dest[destPos++] = src[j] << 24 | src[j + 1] << 16 | src[j + 2] << 8 | 0xff;
}
}
return {
srcPos: srcPos + len,
destPos
};
}
function grayToRGBA(src, dest) {
if (FeatureTest.isLittleEndian) {
for (let i = 0, ii = src.length; i < ii; i++) {
dest[i] = src[i] * 0x10101 | 0xff000000;
}
} else {
for (let i = 0, ii = src.length; i < ii; i++) {
dest[i] = src[i] * 0x1010100 | 0x000000ff;
}
}
}
;// ./src/display/canvas.js
const MIN_FONT_SIZE = 16;
const MAX_FONT_SIZE = 100;
const EXECUTION_TIME = 15;
const EXECUTION_STEPS = 10;
const MAX_SIZE_TO_COMPILE = 1000;
const FULL_CHUNK_HEIGHT = 16;
const SCALE_MATRIX = new DOMMatrix();
function mirrorContextOperations(ctx, destCtx) {
if (ctx._removeMirroring) {
throw new Error("Context is already forwarding operations.");
}
ctx.__originalSave = ctx.save;
ctx.__originalRestore = ctx.restore;
ctx.__originalRotate = ctx.rotate;
ctx.__originalScale = ctx.scale;
ctx.__originalTranslate = ctx.translate;
ctx.__originalTransform = ctx.transform;
ctx.__originalSetTransform = ctx.setTransform;
ctx.__originalResetTransform = ctx.resetTransform;
ctx.__originalClip = ctx.clip;
ctx.__originalMoveTo = ctx.moveTo;
ctx.__originalLineTo = ctx.lineTo;
ctx.__originalBezierCurveTo = ctx.bezierCurveTo;
ctx.__originalRect = ctx.rect;
ctx.__originalClosePath = ctx.closePath;
ctx.__originalBeginPath = ctx.beginPath;
ctx._removeMirroring = () => {
ctx.save = ctx.__originalSave;
ctx.restore = ctx.__originalRestore;
ctx.rotate = ctx.__originalRotate;
ctx.scale = ctx.__originalScale;
ctx.translate = ctx.__originalTranslate;
ctx.transform = ctx.__originalTransform;
ctx.setTransform = ctx.__originalSetTransform;
ctx.resetTransform = ctx.__originalResetTransform;
ctx.clip = ctx.__originalClip;
ctx.moveTo = ctx.__originalMoveTo;
ctx.lineTo = ctx.__originalLineTo;
ctx.bezierCurveTo = ctx.__originalBezierCurveTo;
ctx.rect = ctx.__originalRect;
ctx.closePath = ctx.__originalClosePath;
ctx.beginPath = ctx.__originalBeginPath;
delete ctx._removeMirroring;
};
ctx.save = function ctxSave() {
destCtx.save();
this.__originalSave();
};
ctx.restore = function ctxRestore() {
destCtx.restore();
this.__originalRestore();
};
ctx.translate = function ctxTranslate(x, y) {
destCtx.translate(x, y);
this.__originalTranslate(x, y);
};
ctx.scale = function ctxScale(x, y) {
destCtx.scale(x, y);
this.__originalScale(x, y);
};
ctx.transform = function ctxTransform(a, b, c, d, e, f) {
destCtx.transform(a, b, c, d, e, f);
this.__originalTransform(a, b, c, d, e, f);
};
ctx.setTransform = function ctxSetTransform(a, b, c, d, e, f) {
destCtx.setTransform(a, b, c, d, e, f);
this.__originalSetTransform(a, b, c, d, e, f);
};
ctx.resetTransform = function ctxResetTransform() {
destCtx.resetTransform();
this.__originalResetTransform();
};
ctx.rotate = function ctxRotate(angle) {
destCtx.rotate(angle);
this.__originalRotate(angle);
};
ctx.clip = function ctxRotate(rule) {
destCtx.clip(rule);
this.__originalClip(rule);
};
ctx.moveTo = function (x, y) {
destCtx.moveTo(x, y);
this.__originalMoveTo(x, y);
};
ctx.lineTo = function (x, y) {
destCtx.lineTo(x, y);
this.__originalLineTo(x, y);
};
ctx.bezierCurveTo = function (cp1x, cp1y, cp2x, cp2y, x, y) {
destCtx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
this.__originalBezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
};
ctx.rect = function (x, y, width, height) {
destCtx.rect(x, y, width, height);
this.__originalRect(x, y, width, height);
};
ctx.closePath = function () {
destCtx.closePath();
this.__originalClosePath();
};
ctx.beginPath = function () {
destCtx.beginPath();
this.__originalBeginPath();
};
}
class CachedCanvases {
constructor(canvasFactory) {
this.canvasFactory = canvasFactory;
this.cache = Object.create(null);
}
getCanvas(id, width, height) {
let canvasEntry;
if (this.cache[id] !== undefined) {
canvasEntry = this.cache[id];
this.canvasFactory.reset(canvasEntry, width, height);
} else {
canvasEntry = this.canvasFactory.create(width, height);
this.cache[id] = canvasEntry;
}
return canvasEntry;
}
delete(id) {
delete this.cache[id];
}
clear() {
for (const id in this.cache) {
const canvasEntry = this.cache[id];
this.canvasFactory.destroy(canvasEntry);
delete this.cache[id];
}
}
}
function drawImageAtIntegerCoords(ctx, srcImg, srcX, srcY, srcW, srcH, destX, destY, destW, destH) {
const [a, b, c, d, tx, ty] = getCurrentTransform(ctx);
if (b === 0 && c === 0) {
const tlX = destX * a + tx;
const rTlX = Math.round(tlX);
const tlY = destY * d + ty;
const rTlY = Math.round(tlY);
const brX = (destX + destW) * a + tx;
const rWidth = Math.abs(Math.round(brX) - rTlX) || 1;
const brY = (destY + destH) * d + ty;
const rHeight = Math.abs(Math.round(brY) - rTlY) || 1;
ctx.setTransform(Math.sign(a), 0, 0, Math.sign(d), rTlX, rTlY);
ctx.drawImage(srcImg, srcX, srcY, srcW, srcH, 0, 0, rWidth, rHeight);
ctx.setTransform(a, b, c, d, tx, ty);
return [rWidth, rHeight];
}
if (a === 0 && d === 0) {
const tlX = destY * c + tx;
const rTlX = Math.round(tlX);
const tlY = destX * b + ty;
const rTlY = Math.round(tlY);
const brX = (destY + destH) * c + tx;
const rWidth = Math.abs(Math.round(brX) - rTlX) || 1;
const brY = (destX + destW) * b + ty;
const rHeight = Math.abs(Math.round(brY) - rTlY) || 1;
ctx.setTransform(0, Math.sign(b), Math.sign(c), 0, rTlX, rTlY);
ctx.drawImage(srcImg, srcX, srcY, srcW, srcH, 0, 0, rHeight, rWidth);
ctx.setTransform(a, b, c, d, tx, ty);
return [rHeight, rWidth];
}
ctx.drawImage(srcImg, srcX, srcY, srcW, srcH, destX, destY, destW, destH);
const scaleX = Math.hypot(a, b);
const scaleY = Math.hypot(c, d);
return [scaleX * destW, scaleY * destH];
}
function compileType3Glyph(imgData) {
const {
width,
height
} = imgData;
if (width > MAX_SIZE_TO_COMPILE || height > MAX_SIZE_TO_COMPILE) {
return null;
}
const POINT_TO_PROCESS_LIMIT = 1000;
const POINT_TYPES = new Uint8Array([0, 2, 4, 0, 1, 0, 5, 4, 8, 10, 0, 8, 0, 2, 1, 0]);
const width1 = width + 1;
const points = new Uint8Array(width1 * (height + 1));
let i, j, j0;
const lineSize = width + 7 & ~7;
const data = new Uint8Array(lineSize * height);
let pos = 0;
for (const elem of imgData.data) {
let mask = 128;
while (mask > 0) {
data[pos++] = elem & mask ? 0 : 255;
mask >>= 1;
}
}
let count = 0;
pos = 0;
if (data[pos] !== 0) {
points[0] = 1;
++count;
}
for (j = 1; j < width; j++) {
if (data[pos] !== data[pos + 1]) {
points[j] = data[pos] ? 2 : 1;
++count;
}
pos++;
}
if (data[pos] !== 0) {
points[j] = 2;
++count;
}
for (i = 1; i < height; i++) {
pos = i * lineSize;
j0 = i * width1;
if (data[pos - lineSize] !== data[pos]) {
points[j0] = data[pos] ? 1 : 8;
++count;
}
let sum = (data[pos] ? 4 : 0) + (data[pos - lineSize] ? 8 : 0);
for (j = 1; j < width; j++) {
sum = (sum >> 2) + (data[pos + 1] ? 4 : 0) + (data[pos - lineSize + 1] ? 8 : 0);
if (POINT_TYPES[sum]) {
points[j0 + j] = POINT_TYPES[sum];
++count;
}
pos++;
}
if (data[pos - lineSize] !== data[pos]) {
points[j0 + j] = data[pos] ? 2 : 4;
++count;
}
if (count > POINT_TO_PROCESS_LIMIT) {
return null;
}
}
pos = lineSize * (height - 1);
j0 = i * width1;
if (data[pos] !== 0) {
points[j0] = 8;
++count;
}
for (j = 1; j < width; j++) {
if (data[pos] !== data[pos + 1]) {
points[j0 + j] = data[pos] ? 4 : 8;
++count;
}
pos++;
}
if (data[pos] !== 0) {
points[j0 + j] = 4;
++count;
}
if (count > POINT_TO_PROCESS_LIMIT) {
return null;
}
const steps = new Int32Array([0, width1, -1, 0, -width1, 0, 0, 0, 1]);
const path = new Path2D();
const {
a,
b,
c,
d,
e,
f
} = new DOMMatrix().scaleSelf(1 / width, -1 / height).translateSelf(0, -height);
for (i = 0; count && i <= height; i++) {
let p = i * width1;
const end = p + width;
while (p < end && !points[p]) {
p++;
}
if (p === end) {
continue;
}
let x = p % width1;
let y = i;
path.moveTo(a * x + c * y + e, b * x + d * y + f);
const p0 = p;
let type = points[p];
do {
const step = steps[type];
do {
p += step;
} while (!points[p]);
const pp = points[p];
if (pp !== 5 && pp !== 10) {
type = pp;
points[p] = 0;
} else {
type = pp & 0x33 * type >> 4;
points[p] &= type >> 2 | type << 2;
}
x = p % width1;
y = p / width1 | 0;
path.lineTo(a * x + c * y + e, b * x + d * y + f);
if (!points[p]) {
--count;
}
} while (p0 !== p);
--i;
}
return path;
}
class CanvasExtraState {
constructor(width, height) {
this.alphaIsShape = false;
this.fontSize = 0;
this.fontSizeScale = 1;
this.textMatrix = IDENTITY_MATRIX;
this.textMatrixScale = 1;
this.fontMatrix = FONT_IDENTITY_MATRIX;
this.leading = 0;
this.x = 0;
this.y = 0;
this.lineX = 0;
this.lineY = 0;
this.charSpacing = 0;
this.wordSpacing = 0;
this.textHScale = 1;
this.textRenderingMode = TextRenderingMode.FILL;
this.textRise = 0;
this.fillColor = "#000000";
this.strokeColor = "#000000";
this.patternFill = false;
this.patternStroke = false;
this.fillAlpha = 1;
this.strokeAlpha = 1;
this.lineWidth = 1;
this.activeSMask = null;
this.transferMaps = "none";
this.startNewPathAndClipBox([0, 0, width, height]);
}
clone() {
const clone = Object.create(this);
clone.clipBox = this.clipBox.slice();
return clone;
}
updateRectMinMax(transform, rect) {
const p1 = Util.applyTransform(rect, transform);
const p2 = Util.applyTransform(rect.slice(2), transform);
const p3 = Util.applyTransform([rect[0], rect[3]], transform);
const p4 = Util.applyTransform([rect[2], rect[1]], transform);
this.minX = Math.min(this.minX, p1[0], p2[0], p3[0], p4[0]);
this.minY = Math.min(this.minY, p1[1], p2[1], p3[1], p4[1]);
this.maxX = Math.max(this.maxX, p1[0], p2[0], p3[0], p4[0]);
this.maxY = Math.max(this.maxY, p1[1], p2[1], p3[1], p4[1]);
}
getPathBoundingBox(pathType = PathType.FILL, transform = null) {
const box = [this.minX, this.minY, this.maxX, this.maxY];
if (pathType === PathType.STROKE) {
if (!transform) {
unreachable("Stroke bounding box must include transform.");
}
const scale = Util.singularValueDecompose2dScale(transform);
const xStrokePad = scale[0] * this.lineWidth / 2;
const yStrokePad = scale[1] * this.lineWidth / 2;
box[0] -= xStrokePad;
box[1] -= yStrokePad;
box[2] += xStrokePad;
box[3] += yStrokePad;
}
return box;
}
updateClipFromPath() {
const intersect = Util.intersect(this.clipBox, this.getPathBoundingBox());
this.startNewPathAndClipBox(intersect || [0, 0, 0, 0]);
}
isEmptyClip() {
return this.minX === Infinity;
}
startNewPathAndClipBox(box) {
this.clipBox = box;
this.minX = Infinity;
this.minY = Infinity;
this.maxX = 0;
this.maxY = 0;
}
getClippedPathBoundingBox(pathType = PathType.FILL, transform = null) {
return Util.intersect(this.clipBox, this.getPathBoundingBox(pathType, transform));
}
}
function putBinaryImageData(ctx, imgData) {
if (imgData instanceof ImageData) {
ctx.putImageData(imgData, 0, 0);
return;
}
const height = imgData.height,
width = imgData.width;
const partialChunkHeight = height % FULL_CHUNK_HEIGHT;
const fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT;
const totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1;
const chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT);
let srcPos = 0,
destPos;
const src = imgData.data;
const dest = chunkImgData.data;
let i, j, thisChunkHeight, elemsInThisChunk;
if (imgData.kind === util_ImageKind.GRAYSCALE_1BPP) {
const srcLength = src.byteLength;
const dest32 = new Uint32Array(dest.buffer, 0, dest.byteLength >> 2);
const dest32DataLength = dest32.length;
const fullSrcDiff = width + 7 >> 3;
const white = 0xffffffff;
const black = util_FeatureTest.isLittleEndian ? 0xff000000 : 0x000000ff;
for (i = 0; i < totalChunks; i++) {
thisChunkHeight = i < fullChunks ? FULL_CHUNK_HEIGHT : partialChunkHeight;
destPos = 0;
for (j = 0; j < thisChunkHeight; j++) {
const srcDiff = srcLength - srcPos;
let k = 0;
const kEnd = srcDiff > fullSrcDiff ? width : srcDiff * 8 - 7;
const kEndUnrolled = kEnd & ~7;
let mask = 0;
let srcByte = 0;
for (; k < kEndUnrolled; k += 8) {
srcByte = src[srcPos++];
dest32[destPos++] = srcByte & 128 ? white : black;
dest32[destPos++] = srcByte & 64 ? white : black;
dest32[destPos++] = srcByte & 32 ? white : black;
dest32[destPos++] = srcByte & 16 ? white : black;
dest32[destPos++] = srcByte & 8 ? white : black;
dest32[destPos++] = srcByte & 4 ? white : black;
dest32[destPos++] = srcByte & 2 ? white : black;
dest32[destPos++] = srcByte & 1 ? white : black;
}
for (; k < kEnd; k++) {
if (mask === 0) {
srcByte = src[srcPos++];
mask = 128;
}
dest32[destPos++] = srcByte & mask ? white : black;
mask >>= 1;
}
}
while (destPos < dest32DataLength) {
dest32[destPos++] = 0;
}
ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
}
} else if (imgData.kind === util_ImageKind.RGBA_32BPP) {
j = 0;
elemsInThisChunk = width * FULL_CHUNK_HEIGHT * 4;
for (i = 0; i < fullChunks; i++) {
dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));
srcPos += elemsInThisChunk;
ctx.putImageData(chunkImgData, 0, j);
j += FULL_CHUNK_HEIGHT;
}
if (i < totalChunks) {
elemsInThisChunk = width * partialChunkHeight * 4;
dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));
ctx.putImageData(chunkImgData, 0, j);
}
} else if (imgData.kind === util_ImageKind.RGB_24BPP) {
thisChunkHeight = FULL_CHUNK_HEIGHT;
elemsInThisChunk = width * thisChunkHeight;
for (i = 0; i < totalChunks; i++) {
if (i >= fullChunks) {
thisChunkHeight = partialChunkHeight;
elemsInThisChunk = width * thisChunkHeight;
}
destPos = 0;
for (j = elemsInThisChunk; j--;) {
dest[destPos++] = src[srcPos++];
dest[destPos++] = src[srcPos++];
dest[destPos++] = src[srcPos++];
dest[destPos++] = 255;
}
ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
}
} else {
throw new Error(`bad image kind: ${imgData.kind}`);
}
}
function putBinaryImageMask(ctx, imgData) {
if (imgData.bitmap) {
ctx.drawImage(imgData.bitmap, 0, 0);
return;
}
const height = imgData.height,
width = imgData.width;
const partialChunkHeight = height % FULL_CHUNK_HEIGHT;
const fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT;
const totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1;
const chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT);
let srcPos = 0;
const src = imgData.data;
const dest = chunkImgData.data;
for (let i = 0; i < totalChunks; i++) {
const thisChunkHeight = i < fullChunks ? FULL_CHUNK_HEIGHT : partialChunkHeight;
({
srcPos
} = convertBlackAndWhiteToRGBA({
src,
srcPos,
dest,
width,
height: thisChunkHeight,
nonBlackColor: 0
}));
ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
}
}
function copyCtxState(sourceCtx, destCtx) {
const properties = ["strokeStyle", "fillStyle", "fillRule", "globalAlpha", "lineWidth", "lineCap", "lineJoin", "miterLimit", "globalCompositeOperation", "font", "filter"];
for (const property of properties) {
if (sourceCtx[property] !== undefined) {
destCtx[property] = sourceCtx[property];
}
}
if (sourceCtx.setLineDash !== undefined) {
destCtx.setLineDash(sourceCtx.getLineDash());
destCtx.lineDashOffset = sourceCtx.lineDashOffset;
}
}
function resetCtxToDefault(ctx) {
ctx.strokeStyle = ctx.fillStyle = "#000000";
ctx.fillRule = "nonzero";
ctx.globalAlpha = 1;
ctx.lineWidth = 1;
ctx.lineCap = "butt";
ctx.lineJoin = "miter";
ctx.miterLimit = 10;
ctx.globalCompositeOperation = "source-over";
ctx.font = "10px sans-serif";
if (ctx.setLineDash !== undefined) {
ctx.setLineDash([]);
ctx.lineDashOffset = 0;
}
if (!isNodeJS) {
const {
filter
} = ctx;
if (filter !== "none" && filter !== "") {
ctx.filter = "none";
}
}
}
function getImageSmoothingEnabled(transform, interpolate) {
if (interpolate) {
return true;
}
const scale = Util.singularValueDecompose2dScale(transform);
scale[0] = Math.fround(scale[0]);
scale[1] = Math.fround(scale[1]);
const actualScale = Math.fround(OutputScale.pixelRatio * PixelsPerInch.PDF_TO_CSS_UNITS);
return scale[0] <= actualScale && scale[1] <= actualScale;
}
const LINE_CAP_STYLES = ["butt", "round", "square"];
const LINE_JOIN_STYLES = ["miter", "round", "bevel"];
const NORMAL_CLIP = {};
const EO_CLIP = {};
class CanvasGraphics {
constructor(canvasCtx, commonObjs, objs, canvasFactory, filterFactory, {
optionalContentConfig,
markedContentStack = null
}, annotationCanvasMap, pageColors) {
this.ctx = canvasCtx;
this.current = new CanvasExtraState(this.ctx.canvas.width, this.ctx.canvas.height);
this.stateStack = [];
this.pendingClip = null;
this.pendingEOFill = false;
this.res = null;
this.xobjs = null;
this.commonObjs = commonObjs;
this.objs = objs;
this.canvasFactory = canvasFactory;
this.filterFactory = filterFactory;
this.groupStack = [];
this.processingType3 = null;
this.baseTransform = null;
this.baseTransformStack = [];
this.groupLevel = 0;
this.smaskStack = [];
this.smaskCounter = 0;
this.tempSMask = null;
this.suspendedCtx = null;
this.contentVisible = true;
this.markedContentStack = markedContentStack || [];
this.optionalContentConfig = optionalContentConfig;
this.cachedCanvases = new CachedCanvases(this.canvasFactory);
this.cachedPatterns = new Map();
this.annotationCanvasMap = annotationCanvasMap;
this.viewportScale = 1;
this.outputScaleX = 1;
this.outputScaleY = 1;
this.pageColors = pageColors;
this._cachedScaleForStroking = [-1, 0];
this._cachedGetSinglePixelWidth = null;
this._cachedBitmapsMap = new Map();
}
getObject(data, fallback = null) {
if (typeof data === "string") {
return data.startsWith("g_") ? this.commonObjs.get(data) : this.objs.get(data);
}
return fallback;
}
beginDrawing({
transform,
viewport,
transparency = false,
background = null
}) {
const width = this.ctx.canvas.width;
const height = this.ctx.canvas.height;
const savedFillStyle = this.ctx.fillStyle;
this.ctx.fillStyle = background || "#ffffff";
this.ctx.fillRect(0, 0, width, height);
this.ctx.fillStyle = savedFillStyle;
if (transparency) {
const transparentCanvas = this.cachedCanvases.getCanvas("transparent", width, height);
this.compositeCtx = this.ctx;
this.transparentCanvas = transparentCanvas.canvas;
this.ctx = transparentCanvas.context;
this.ctx.save();
this.ctx.transform(...getCurrentTransform(this.compositeCtx));
}
this.ctx.save();
resetCtxToDefault(this.ctx);
if (transform) {
this.ctx.transform(...transform);
this.outputScaleX = transform[0];
this.outputScaleY = transform[0];
}
this.ctx.transform(...viewport.transform);
this.viewportScale = viewport.scale;
this.baseTransform = getCurrentTransform(this.ctx);
}
executeOperatorList(operatorList, executionStartIdx, continueCallback, stepper) {
const argsArray = operatorList.argsArray;
const fnArray = operatorList.fnArray;
let i = executionStartIdx || 0;
const argsArrayLen = argsArray.length;
if (argsArrayLen === i) {
return i;
}
const chunkOperations = argsArrayLen - i > EXECUTION_STEPS && typeof continueCallback === "function";
const endTime = chunkOperations ? Date.now() + EXECUTION_TIME : 0;
let steps = 0;
const commonObjs = this.commonObjs;
const objs = this.objs;
let fnId;
while (true) {
if (stepper !== undefined && i === stepper.nextBreakPoint) {
stepper.breakIt(i, continueCallback);
return i;
}
fnId = fnArray[i];
if (fnId !== OPS.dependency) {
this[fnId].apply(this, argsArray[i]);
} else {
for (const depObjId of argsArray[i]) {
const objsPool = depObjId.startsWith("g_") ? commonObjs : objs;
if (!objsPool.has(depObjId)) {
objsPool.get(depObjId, continueCallback);
return i;
}
}
}
i++;
if (i === argsArrayLen) {
return i;
}
if (chunkOperations && ++steps > EXECUTION_STEPS) {
if (Date.now() > endTime) {
continueCallback();
return i;
}
steps = 0;
}
}
}
#restoreInitialState() {
while (this.stateStack.length || this.inSMaskMode) {
this.restore();
}
this.current.activeSMask = null;
this.ctx.restore();
if (this.transparentCanvas) {
this.ctx = this.compositeCtx;
this.ctx.save();
this.ctx.setTransform(1, 0, 0, 1, 0, 0);
this.ctx.drawImage(this.transparentCanvas, 0, 0);
this.ctx.restore();
this.transparentCanvas = null;
}
}
endDrawing() {
this.#restoreInitialState();
this.cachedCanvases.clear();
this.cachedPatterns.clear();
for (const cache of this._cachedBitmapsMap.values()) {
for (const canvas of cache.values()) {
if (typeof HTMLCanvasElement !== "undefined" && canvas instanceof HTMLCanvasElement) {
canvas.width = canvas.height = 0;
}
}
cache.clear();
}
this._cachedBitmapsMap.clear();
this.#drawFilter();
}
#drawFilter() {
if (this.pageColors) {
const hcmFilterId = this.filterFactory.addHCMFilter(this.pageColors.foreground, this.pageColors.background);
if (hcmFilterId !== "none") {
const savedFilter = this.ctx.filter;
this.ctx.filter = hcmFilterId;
this.ctx.drawImage(this.ctx.canvas, 0, 0);
this.ctx.filter = savedFilter;
}
}
}
_scaleImage(img, inverseTransform) {
const width = img.width ?? img.displayWidth;
const height = img.height ?? img.displayHeight;
let widthScale = Math.max(Math.hypot(inverseTransform[0], inverseTransform[1]), 1);
let heightScale = Math.max(Math.hypot(inverseTransform[2], inverseTransform[3]), 1);
let paintWidth = width,
paintHeight = height;
let tmpCanvasId = "prescale1";
let tmpCanvas, tmpCtx;
while (widthScale > 2 && paintWidth > 1 || heightScale > 2 && paintHeight > 1) {
let newWidth = paintWidth,
newHeight = paintHeight;
if (widthScale > 2 && paintWidth > 1) {
newWidth = paintWidth >= 16384 ? Math.floor(paintWidth / 2) - 1 || 1 : Math.ceil(paintWidth / 2);
widthScale /= paintWidth / newWidth;
}
if (heightScale > 2 && paintHeight > 1) {
newHeight = paintHeight >= 16384 ? Math.floor(paintHeight / 2) - 1 || 1 : Math.ceil(paintHeight) / 2;
heightScale /= paintHeight / newHeight;
}
tmpCanvas = this.cachedCanvases.getCanvas(tmpCanvasId, newWidth, newHeight);
tmpCtx = tmpCanvas.context;
tmpCtx.clearRect(0, 0, newWidth, newHeight);
tmpCtx.drawImage(img, 0, 0, paintWidth, paintHeight, 0, 0, newWidth, newHeight);
img = tmpCanvas.canvas;
paintWidth = newWidth;
paintHeight = newHeight;
tmpCanvasId = tmpCanvasId === "prescale1" ? "prescale2" : "prescale1";
}
return {
img,
paintWidth,
paintHeight
};
}
_createMaskCanvas(img) {
const ctx = this.ctx;
const {
width,
height
} = img;
const fillColor = this.current.fillColor;
const isPatternFill = this.current.patternFill;
const currentTransform = getCurrentTransform(ctx);
let cache, cacheKey, scaled, maskCanvas;
if ((img.bitmap || img.data) && img.count > 1) {
const mainKey = img.bitmap || img.data.buffer;
cacheKey = JSON.stringify(isPatternFill ? currentTransform : [currentTransform.slice(0, 4), fillColor]);
cache = this._cachedBitmapsMap.get(mainKey);
if (!cache) {
cache = new Map();
this._cachedBitmapsMap.set(mainKey, cache);
}
const cachedImage = cache.get(cacheKey);
if (cachedImage && !isPatternFill) {
const offsetX = Math.round(Math.min(currentTransform[0], currentTransform[2]) + currentTransform[4]);
const offsetY = Math.round(Math.min(currentTransform[1], currentTransform[3]) + currentTransform[5]);
return {
canvas: cachedImage,
offsetX,
offsetY
};
}
scaled = cachedImage;
}
if (!scaled) {
maskCanvas = this.cachedCanvases.getCanvas("maskCanvas", width, height);
putBinaryImageMask(maskCanvas.context, img);
}
let maskToCanvas = Util.transform(currentTransform, [1 / width, 0, 0, -1 / height, 0, 0]);
maskToCanvas = Util.transform(maskToCanvas, [1, 0, 0, 1, 0, -height]);
const [minX, minY, maxX, maxY] = Util.getAxialAlignedBoundingBox([0, 0, width, height], maskToCanvas);
const drawnWidth = Math.round(maxX - minX) || 1;
const drawnHeight = Math.round(maxY - minY) || 1;
const fillCanvas = this.cachedCanvases.getCanvas("fillCanvas", drawnWidth, drawnHeight);
const fillCtx = fillCanvas.context;
const offsetX = minX;
const offsetY = minY;
fillCtx.translate(-offsetX, -offsetY);
fillCtx.transform(...maskToCanvas);
if (!scaled) {
scaled = this._scaleImage(maskCanvas.canvas, getCurrentTransformInverse(fillCtx));
scaled = scaled.img;
if (cache && isPatternFill) {
cache.set(cacheKey, scaled);
}
}
fillCtx.imageSmoothingEnabled = getImageSmoothingEnabled(getCurrentTransform(fillCtx), img.interpolate);
drawImageAtIntegerCoords(fillCtx, scaled, 0, 0, scaled.width, scaled.height, 0, 0, width, height);
fillCtx.globalCompositeOperation = "source-in";
const inverse = Util.transform(getCurrentTransformInverse(fillCtx), [1, 0, 0, 1, -offsetX, -offsetY]);
fillCtx.fillStyle = isPatternFill ? fillColor.getPattern(ctx, this, inverse, PathType.FILL) : fillColor;
fillCtx.fillRect(0, 0, width, height);
if (cache && !isPatternFill) {
this.cachedCanvases.delete("fillCanvas");
cache.set(cacheKey, fillCanvas.canvas);
}
return {
canvas: fillCanvas.canvas,
offsetX: Math.round(offsetX),
offsetY: Math.round(offsetY)
};
}
setLineWidth(width) {
if (width !== this.current.lineWidth) {
this._cachedScaleForStroking[0] = -1;
}
this.current.lineWidth = width;
this.ctx.lineWidth = width;
}
setLineCap(style) {
this.ctx.lineCap = LINE_CAP_STYLES[style];
}
setLineJoin(style) {
this.ctx.lineJoin = LINE_JOIN_STYLES[style];
}
setMiterLimit(limit) {
this.ctx.miterLimit = limit;
}
setDash(dashArray, dashPhase) {
const ctx = this.ctx;
if (ctx.setLineDash !== undefined) {
ctx.setLineDash(dashArray);
ctx.lineDashOffset = dashPhase;
}
}
setRenderingIntent(intent) {}
setFlatness(flatness) {}
setGState(states) {
for (const [key, value] of states) {
switch (key) {
case "LW":
this.setLineWidth(value);
break;
case "LC":
this.setLineCap(value);
break;
case "LJ":
this.setLineJoin(value);
break;
case "ML":
this.setMiterLimit(value);
break;
case "D":
this.setDash(value[0], value[1]);
break;
case "RI":
this.setRenderingIntent(value);
break;
case "FL":
this.setFlatness(value);
break;
case "Font":
this.setFont(value[0], value[1]);
break;
case "CA":
this.current.strokeAlpha = value;
break;
case "ca":
this.ctx.globalAlpha = this.current.fillAlpha = value;
break;
case "BM":
this.ctx.globalCompositeOperation = value;
break;
case "SMask":
this.current.activeSMask = value ? this.tempSMask : null;
this.tempSMask = null;
this.checkSMaskState();
break;
case "TR":
this.ctx.filter = this.current.transferMaps = this.filterFactory.addFilter(value);
break;
}
}
}
get inSMaskMode() {
return !!this.suspendedCtx;
}
checkSMaskState() {
const inSMaskMode = this.inSMaskMode;
if (this.current.activeSMask && !inSMaskMode) {
this.beginSMaskMode();
} else if (!this.current.activeSMask && inSMaskMode) {
this.endSMaskMode();
}
}
beginSMaskMode() {
if (this.inSMaskMode) {
throw new Error("beginSMaskMode called while already in smask mode");
}
const drawnWidth = this.ctx.canvas.width;
const drawnHeight = this.ctx.canvas.height;
const cacheId = "smaskGroupAt" + this.groupLevel;
const scratchCanvas = this.cachedCanvases.getCanvas(cacheId, drawnWidth, drawnHeight);
this.suspendedCtx = this.ctx;
const ctx = this.ctx = scratchCanvas.context;
ctx.setTransform(this.suspendedCtx.getTransform());
copyCtxState(this.suspendedCtx, ctx);
mirrorContextOperations(ctx, this.suspendedCtx);
this.setGState([["BM", "source-over"]]);
}
endSMaskMode() {
if (!this.inSMaskMode) {
throw new Error("endSMaskMode called while not in smask mode");
}
this.ctx._removeMirroring();
copyCtxState(this.ctx, this.suspendedCtx);
this.ctx = this.suspendedCtx;
this.suspendedCtx = null;
}
compose(dirtyBox) {
if (!this.current.activeSMask) {
return;
}
if (!dirtyBox) {
dirtyBox = [0, 0, this.ctx.canvas.width, this.ctx.canvas.height];
} else {
dirtyBox[0] = Math.floor(dirtyBox[0]);
dirtyBox[1] = Math.floor(dirtyBox[1]);
dirtyBox[2] = Math.ceil(dirtyBox[2]);
dirtyBox[3] = Math.ceil(dirtyBox[3]);
}
const smask = this.current.activeSMask;
const suspendedCtx = this.suspendedCtx;
this.composeSMask(suspendedCtx, smask, this.ctx, dirtyBox);
this.ctx.save();
this.ctx.setTransform(1, 0, 0, 1, 0, 0);
this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
this.ctx.restore();
}
composeSMask(ctx, smask, layerCtx, layerBox) {
const layerOffsetX = layerBox[0];
const layerOffsetY = layerBox[1];
const layerWidth = layerBox[2] - layerOffsetX;
const layerHeight = layerBox[3] - layerOffsetY;
if (layerWidth === 0 || layerHeight === 0) {
return;
}
this.genericComposeSMask(smask.context, layerCtx, layerWidth, layerHeight, smask.subtype, smask.backdrop, smask.transferMap, layerOffsetX, layerOffsetY, smask.offsetX, smask.offsetY);
ctx.save();
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "source-over";
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.drawImage(layerCtx.canvas, 0, 0);
ctx.restore();
}
genericComposeSMask(maskCtx, layerCtx, width, height, subtype, backdrop, transferMap, layerOffsetX, layerOffsetY, maskOffsetX, maskOffsetY) {
let maskCanvas = maskCtx.canvas;
let maskX = layerOffsetX - maskOffsetX;
let maskY = layerOffsetY - maskOffsetY;
if (backdrop) {
const backdropRGB = Util.makeHexColor(...backdrop);
if (maskX < 0 || maskY < 0 || maskX + width > maskCanvas.width || maskY + height > maskCanvas.height) {
const canvas = this.cachedCanvases.getCanvas("maskExtension", width, height);
const ctx = canvas.context;
ctx.drawImage(maskCanvas, -maskX, -maskY);
ctx.globalCompositeOperation = "destination-atop";
ctx.fillStyle = backdropRGB;
ctx.fillRect(0, 0, width, height);
ctx.globalCompositeOperation = "source-over";
maskCanvas = canvas.canvas;
maskX = maskY = 0;
} else {
maskCtx.save();
maskCtx.globalAlpha = 1;
maskCtx.setTransform(1, 0, 0, 1, 0, 0);
const clip = new Path2D();
clip.rect(maskX, maskY, width, height);
maskCtx.clip(clip);
maskCtx.globalCompositeOperation = "destination-atop";
maskCtx.fillStyle = backdropRGB;
maskCtx.fillRect(maskX, maskY, width, height);
maskCtx.restore();
}
}
layerCtx.save();
layerCtx.globalAlpha = 1;
layerCtx.setTransform(1, 0, 0, 1, 0, 0);
if (subtype === "Alpha" && transferMap) {
layerCtx.filter = this.filterFactory.addAlphaFilter(transferMap);
} else if (subtype === "Luminosity") {
layerCtx.filter = this.filterFactory.addLuminosityFilter(transferMap);
}
const clip = new Path2D();
clip.rect(layerOffsetX, layerOffsetY, width, height);
layerCtx.clip(clip);
layerCtx.globalCompositeOperation = "destination-in";
layerCtx.drawImage(maskCanvas, maskX, maskY, width, height, layerOffsetX, layerOffsetY, width, height);
layerCtx.restore();
}
save() {
if (this.inSMaskMode) {
copyCtxState(this.ctx, this.suspendedCtx);
}
this.ctx.save();
const old = this.current;
this.stateStack.push(old);
this.current = old.clone();
}
restore() {
if (this.stateStack.length === 0) {
if (this.inSMaskMode) {
this.endSMaskMode();
}
return;
}
this.current = this.stateStack.pop();
this.ctx.restore();
if (this.inSMaskMode) {
copyCtxState(this.suspendedCtx, this.ctx);
}
this.checkSMaskState();
this.pendingClip = null;
this._cachedScaleForStroking[0] = -1;
this._cachedGetSinglePixelWidth = null;
}
transform(a, b, c, d, e, f) {
this.ctx.transform(a, b, c, d, e, f);
this._cachedScaleForStroking[0] = -1;
this._cachedGetSinglePixelWidth = null;
}
constructPath(op, data, minMax) {
let [path] = data;
if (!minMax) {
path ||= data[0] = new Path2D();
this[op](path);
return;
}
if (!(path instanceof Path2D)) {
const path2d = data[0] = new Path2D();
for (let i = 0, ii = path.length; i < ii;) {
switch (path[i++]) {
case DrawOPS.moveTo:
path2d.moveTo(path[i++], path[i++]);
break;
case DrawOPS.lineTo:
path2d.lineTo(path[i++], path[i++]);
break;
case DrawOPS.curveTo:
path2d.bezierCurveTo(path[i++], path[i++], path[i++], path[i++], path[i++], path[i++]);
break;
case DrawOPS.closePath:
path2d.closePath();
break;
default:
warn(`Unrecognized drawing path operator: ${path[i - 1]}`);
break;
}
}
path = path2d;
}
this.current.updateRectMinMax(getCurrentTransform(this.ctx), minMax);
this[op](path);
}
closePath() {
this.ctx.closePath();
}
stroke(path, consumePath = true) {
const ctx = this.ctx;
const strokeColor = this.current.strokeColor;
ctx.globalAlpha = this.current.strokeAlpha;
if (this.contentVisible) {
if (typeof strokeColor === "object" && strokeColor?.getPattern) {
const baseTransform = strokeColor.isModifyingCurrentTransform() ? ctx.getTransform() : null;
ctx.save();
ctx.strokeStyle = strokeColor.getPattern(ctx, this, getCurrentTransformInverse(ctx), PathType.STROKE);
if (baseTransform) {
const newPath = new Path2D();
newPath.addPath(path, ctx.getTransform().invertSelf().multiplySelf(baseTransform));
path = newPath;
}
this.rescaleAndStroke(path, false);
ctx.restore();
} else {
this.rescaleAndStroke(path, true);
}
}
if (consumePath) {
this.consumePath(path, this.current.getClippedPathBoundingBox(PathType.STROKE, getCurrentTransform(this.ctx)));
}
ctx.globalAlpha = this.current.fillAlpha;
}
closeStroke(path) {
this.stroke(path);
}
fill(path, consumePath = true) {
const ctx = this.ctx;
const fillColor = this.current.fillColor;
const isPatternFill = this.current.patternFill;
let needRestore = false;
if (isPatternFill) {
const baseTransform = fillColor.isModifyingCurrentTransform() ? ctx.getTransform() : null;
ctx.save();
ctx.fillStyle = fillColor.getPattern(ctx, this, getCurrentTransformInverse(ctx), PathType.FILL);
if (baseTransform) {
const newPath = new Path2D();
newPath.addPath(path, ctx.getTransform().invertSelf().multiplySelf(baseTransform));
path = newPath;
}
needRestore = true;
}
const intersect = this.current.getClippedPathBoundingBox();
if (this.contentVisible && intersect !== null) {
if (this.pendingEOFill) {
ctx.fill(path, "evenodd");
this.pendingEOFill = false;
} else {
ctx.fill(path);
}
}
if (needRestore) {
ctx.restore();
}
if (consumePath) {
this.consumePath(path, intersect);
}
}
eoFill(path) {
this.pendingEOFill = true;
this.fill(path);
}
fillStroke(path) {
this.fill(path, false);
this.stroke(path, false);
this.consumePath(path);
}
eoFillStroke(path) {
this.pendingEOFill = true;
this.fillStroke(path);
}
closeFillStroke(path) {
this.fillStroke(path);
}
closeEOFillStroke(path) {
this.pendingEOFill = true;
this.fillStroke(path);
}
endPath(path) {
this.consumePath(path);
}
clip() {
this.pendingClip = NORMAL_CLIP;
}
eoClip() {
this.pendingClip = EO_CLIP;
}
beginText() {
this.current.textMatrix = IDENTITY_MATRIX;
this.current.textMatrixScale = 1;
this.current.x = this.current.lineX = 0;
this.current.y = this.current.lineY = 0;
}
endText() {
const paths = this.pendingTextPaths;
const ctx = this.ctx;
if (paths === undefined) {
ctx.beginPath();
return;
}
const newPath = new Path2D();
const invTransf = ctx.getTransform().invertSelf();
for (const {
transform,
x,
y,
fontSize,
path
} of paths) {
newPath.addPath(path, new DOMMatrix(transform).preMultiplySelf(invTransf).translate(x, y).scale(fontSize, -fontSize));
}
ctx.clip(newPath);
ctx.beginPath();
delete this.pendingTextPaths;
}
setCharSpacing(spacing) {
this.current.charSpacing = spacing;
}
setWordSpacing(spacing) {
this.current.wordSpacing = spacing;
}
setHScale(scale) {
this.current.textHScale = scale / 100;
}
setLeading(leading) {
this.current.leading = -leading;
}
setFont(fontRefName, size) {
const fontObj = this.commonObjs.get(fontRefName);
const current = this.current;
if (!fontObj) {
throw new Error(`Can't find font for ${fontRefName}`);
}
current.fontMatrix = fontObj.fontMatrix || FONT_IDENTITY_MATRIX;
if (current.fontMatrix[0] === 0 || current.fontMatrix[3] === 0) {
warn("Invalid font matrix for font " + fontRefName);
}
if (size < 0) {
size = -size;
current.fontDirection = -1;
} else {
current.fontDirection = 1;
}
this.current.font = fontObj;
this.current.fontSize = size;
if (fontObj.isType3Font) {
return;
}
const name = fontObj.loadedName || "sans-serif";
const typeface = fontObj.systemFontInfo?.css || `"${name}", ${fontObj.fallbackName}`;
let bold = "normal";
if (fontObj.black) {
bold = "900";
} else if (fontObj.bold) {
bold = "bold";
}
const italic = fontObj.italic ? "italic" : "normal";
let browserFontSize = size;
if (size < MIN_FONT_SIZE) {
browserFontSize = MIN_FONT_SIZE;
} else if (size > MAX_FONT_SIZE) {
browserFontSize = MAX_FONT_SIZE;
}
this.current.fontSizeScale = size / browserFontSize;
this.ctx.font = `${italic} ${bold} ${browserFontSize}px ${typeface}`;
}
setTextRenderingMode(mode) {
this.current.textRenderingMode = mode;
}
setTextRise(rise) {
this.current.textRise = rise;
}
moveText(x, y) {
this.current.x = this.current.lineX += x;
this.current.y = this.current.lineY += y;
}
setLeadingMoveText(x, y) {
this.setLeading(-y);
this.moveText(x, y);
}
setTextMatrix(a, b, c, d, e, f) {
this.current.textMatrix = [a, b, c, d, e, f];
this.current.textMatrixScale = Math.hypot(a, b);
this.current.x = this.current.lineX = 0;
this.current.y = this.current.lineY = 0;
}
nextLine() {
this.moveText(0, this.current.leading);
}
#getScaledPath(path, currentTransform, transform) {
const newPath = new Path2D();
newPath.addPath(path, new DOMMatrix(transform).invertSelf().multiplySelf(currentTransform));
return newPath;
}
paintChar(character, x, y, patternFillTransform, patternStrokeTransform) {
const ctx = this.ctx;
const current = this.current;
const font = current.font;
const textRenderingMode = current.textRenderingMode;
const fontSize = current.fontSize / current.fontSizeScale;
const fillStrokeMode = textRenderingMode & TextRenderingMode.FILL_STROKE_MASK;
const isAddToPathSet = !!(textRenderingMode & TextRenderingMode.ADD_TO_PATH_FLAG);
const patternFill = current.patternFill && !font.missingFile;
const patternStroke = current.patternStroke && !font.missingFile;
let path;
if (font.disableFontFace || isAddToPathSet || patternFill || patternStroke) {
path = font.getPathGenerator(this.commonObjs, character);
}
if (font.disableFontFace || patternFill || patternStroke) {
ctx.save();
ctx.translate(x, y);
ctx.scale(fontSize, -fontSize);
let currentTransform;
if (fillStrokeMode === TextRenderingMode.FILL || fillStrokeMode === TextRenderingMode.FILL_STROKE) {
if (patternFillTransform) {
currentTransform = ctx.getTransform();
ctx.setTransform(...patternFillTransform);
ctx.fill(this.#getScaledPath(path, currentTransform, patternFillTransform));
} else {
ctx.fill(path);
}
}
if (fillStrokeMode === TextRenderingMode.STROKE || fillStrokeMode === TextRenderingMode.FILL_STROKE) {
if (patternStrokeTransform) {
currentTransform ||= ctx.getTransform();
ctx.setTransform(...patternStrokeTransform);
const {
a,
b,
c,
d
} = currentTransform;
const invPatternTransform = Util.inverseTransform(patternStrokeTransform);
const transf = Util.transform([a, b, c, d, 0, 0], invPatternTransform);
const [sx, sy] = Util.singularValueDecompose2dScale(transf);
ctx.lineWidth *= Math.max(sx, sy) / fontSize;
ctx.stroke(this.#getScaledPath(path, currentTransform, patternStrokeTransform));
} else {
ctx.lineWidth /= fontSize;
ctx.stroke(path);
}
}
ctx.restore();
} else {
if (fillStrokeMode === TextRenderingMode.FILL || fillStrokeMode === TextRenderingMode.FILL_STROKE) {
ctx.fillText(character, x, y);
}
if (fillStrokeMode === TextRenderingMode.STROKE || fillStrokeMode === TextRenderingMode.FILL_STROKE) {
ctx.strokeText(character, x, y);
}
}
if (isAddToPathSet) {
const paths = this.pendingTextPaths ||= [];
paths.push({
transform: getCurrentTransform(ctx),
x,
y,
fontSize,
path
});
}
}
get isFontSubpixelAAEnabled() {
const {
context: ctx
} = this.cachedCanvases.getCanvas("isFontSubpixelAAEnabled", 10, 10);
ctx.scale(1.5, 1);
ctx.fillText("I", 0, 10);
const data = ctx.getImageData(0, 0, 10, 10).data;
let enabled = false;
for (let i = 3; i < data.length; i += 4) {
if (data[i] > 0 && data[i] < 255) {
enabled = true;
break;
}
}
return shadow(this, "isFontSubpixelAAEnabled", enabled);
}
showText(glyphs) {
const current = this.current;
const font = current.font;
if (font.isType3Font) {
return this.showType3Text(glyphs);
}
const fontSize = current.fontSize;
if (fontSize === 0) {
return undefined;
}
const ctx = this.ctx;
const fontSizeScale = current.fontSizeScale;
const charSpacing = current.charSpacing;
const wordSpacing = current.wordSpacing;
const fontDirection = current.fontDirection;
const textHScale = current.textHScale * fontDirection;
const glyphsLength = glyphs.length;
const vertical = font.vertical;
const spacingDir = vertical ? 1 : -1;
const defaultVMetrics = font.defaultVMetrics;
const widthAdvanceScale = fontSize * current.fontMatrix[0];
const simpleFillText = current.textRenderingMode === TextRenderingMode.FILL && !font.disableFontFace && !current.patternFill;
ctx.save();
ctx.transform(...current.textMatrix);
ctx.translate(current.x, current.y + current.textRise);
if (fontDirection > 0) {
ctx.scale(textHScale, -1);
} else {
ctx.scale(textHScale, 1);
}
let patternFillTransform, patternStrokeTransform;
if (current.patternFill) {
ctx.save();
const pattern = current.fillColor.getPattern(ctx, this, getCurrentTransformInverse(ctx), PathType.FILL);
patternFillTransform = getCurrentTransform(ctx);
ctx.restore();
ctx.fillStyle = pattern;
}
if (current.patternStroke) {
ctx.save();
const pattern = current.strokeColor.getPattern(ctx, this, getCurrentTransformInverse(ctx), PathType.STROKE);
patternStrokeTransform = getCurrentTransform(ctx);
ctx.restore();
ctx.strokeStyle = pattern;
}
let lineWidth = current.lineWidth;
const scale = current.textMatrixScale;
if (scale === 0 || lineWidth === 0) {
const fillStrokeMode = current.textRenderingMode & TextRenderingMode.FILL_STROKE_MASK;
if (fillStrokeMode === TextRenderingMode.STROKE || fillStrokeMode === TextRenderingMode.FILL_STROKE) {
lineWidth = this.getSinglePixelWidth();
}
} else {
lineWidth /= scale;
}
if (fontSizeScale !== 1.0) {
ctx.scale(fontSizeScale, fontSizeScale);
lineWidth /= fontSizeScale;
}
ctx.lineWidth = lineWidth;
if (font.isInvalidPDFjsFont) {
const chars = [];
let width = 0;
for (const glyph of glyphs) {
chars.push(glyph.unicode);
width += glyph.width;
}
ctx.fillText(chars.join(""), 0, 0);
current.x += width * widthAdvanceScale * textHScale;
ctx.restore();
this.compose();
return undefined;
}
let x = 0,
i;
for (i = 0; i < glyphsLength; ++i) {
const glyph = glyphs[i];
if (typeof glyph === "number") {
x += spacingDir * glyph * fontSize / 1000;
continue;
}
let restoreNeeded = false;
const spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing;
const character = glyph.fontChar;
const accent = glyph.accent;
let scaledX, scaledY;
let width = glyph.width;
if (vertical) {
const vmetric = glyph.vmetric || defaultVMetrics;
const vx = -(glyph.vmetric ? vmetric[1] : width * 0.5) * widthAdvanceScale;
const vy = vmetric[2] * widthAdvanceScale;
width = vmetric ? -vmetric[0] : width;
scaledX = vx / fontSizeScale;
scaledY = (x + vy) / fontSizeScale;
} else {
scaledX = x / fontSizeScale;
scaledY = 0;
}
if (font.remeasure && width > 0) {
const measuredWidth = ctx.measureText(character).width * 1000 / fontSize * fontSizeScale;
if (width < measuredWidth && this.isFontSubpixelAAEnabled) {
const characterScaleX = width / measuredWidth;
restoreNeeded = true;
ctx.save();
ctx.scale(characterScaleX, 1);
scaledX /= characterScaleX;
} else if (width !== measuredWidth) {
scaledX += (width - measuredWidth) / 2000 * fontSize / fontSizeScale;
}
}
if (this.contentVisible && (glyph.isInFont || font.missingFile)) {
if (simpleFillText && !accent) {
ctx.fillText(character, scaledX, scaledY);
} else {
this.paintChar(character, scaledX, scaledY, patternFillTransform, patternStrokeTransform);
if (accent) {
const scaledAccentX = scaledX + fontSize * accent.offset.x / fontSizeScale;
const scaledAccentY = scaledY - fontSize * accent.offset.y / fontSizeScale;
this.paintChar(accent.fontChar, scaledAccentX, scaledAccentY, patternFillTransform, patternStrokeTransform);
}
}
}
const charWidth = vertical ? width * widthAdvanceScale - spacing * fontDirection : width * widthAdvanceScale + spacing * fontDirection;
x += charWidth;
if (restoreNeeded) {
ctx.restore();
}
}
if (vertical) {
current.y -= x;
} else {
current.x += x * textHScale;
}
ctx.restore();
this.compose();
return undefined;
}
showType3Text(glyphs) {
const ctx = this.ctx;
const current = this.current;
const font = current.font;
const fontSize = current.fontSize;
const fontDirection = current.fontDirection;
const spacingDir = font.vertical ? 1 : -1;
const charSpacing = current.charSpacing;
const wordSpacing = current.wordSpacing;
const textHScale = current.textHScale * fontDirection;
const fontMatrix = current.fontMatrix || FONT_IDENTITY_MATRIX;
const glyphsLength = glyphs.length;
const isTextInvisible = current.textRenderingMode === TextRenderingMode.INVISIBLE;
let i, glyph, width, spacingLength;
if (isTextInvisible || fontSize === 0) {
return;
}
this._cachedScaleForStroking[0] = -1;
this._cachedGetSinglePixelWidth = null;
ctx.save();
ctx.transform(...current.textMatrix);
ctx.translate(current.x, current.y + current.textRise);
ctx.scale(textHScale, fontDirection);
for (i = 0; i < glyphsLength; ++i) {
glyph = glyphs[i];
if (typeof glyph === "number") {
spacingLength = spacingDir * glyph * fontSize / 1000;
this.ctx.translate(spacingLength, 0);
current.x += spacingLength * textHScale;
continue;
}
const spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing;
const operatorList = font.charProcOperatorList[glyph.operatorListId];
if (!operatorList) {
warn(`Type3 character "${glyph.operatorListId}" is not available.`);
} else if (this.contentVisible) {
this.processingType3 = glyph;
this.save();
ctx.scale(fontSize, fontSize);
ctx.transform(...fontMatrix);
this.executeOperatorList(operatorList);
this.restore();
}
const transformed = Util.applyTransform([glyph.width, 0], fontMatrix);
width = transformed[0] * fontSize + spacing;
ctx.translate(width, 0);
current.x += width * textHScale;
}
ctx.restore();
this.processingType3 = null;
}
setCharWidth(xWidth, yWidth) {}
setCharWidthAndBounds(xWidth, yWidth, llx, lly, urx, ury) {
this.ctx.rect(llx, lly, urx - llx, ury - lly);
this.ctx.clip();
this.endPath();
}
getColorN_Pattern(IR) {
let pattern;
if (IR[0] === "TilingPattern") {
const baseTransform = this.baseTransform || getCurrentTransform(this.ctx);
const canvasGraphicsFactory = {
createCanvasGraphics: ctx => new CanvasGraphics(ctx, this.commonObjs, this.objs, this.canvasFactory, this.filterFactory, {
optionalContentConfig: this.optionalContentConfig,
markedContentStack: this.markedContentStack
})
};
pattern = new TilingPattern(IR, this.ctx, canvasGraphicsFactory, baseTransform);
} else {
pattern = this._getPattern(IR[1], IR[2]);
}
return pattern;
}
setStrokeColorN() {
this.current.strokeColor = this.getColorN_Pattern(arguments);
this.current.patternStroke = true;
}
setFillColorN() {
this.current.fillColor = this.getColorN_Pattern(arguments);
this.current.patternFill = true;
}
setStrokeRGBColor(r, g, b) {
this.ctx.strokeStyle = this.current.strokeColor = Util.makeHexColor(r, g, b);
this.current.patternStroke = false;
}
setStrokeTransparent() {
this.ctx.strokeStyle = this.current.strokeColor = "transparent";
this.current.patternStroke = false;
}
setFillRGBColor(r, g, b) {
this.ctx.fillStyle = this.current.fillColor = Util.makeHexColor(r, g, b);
this.current.patternFill = false;
}
setFillTransparent() {
this.ctx.fillStyle = this.current.fillColor = "transparent";
this.current.patternFill = false;
}
_getPattern(objId, matrix = null) {
let pattern;
if (this.cachedPatterns.has(objId)) {
pattern = this.cachedPatterns.get(objId);
} else {
pattern = getShadingPattern(this.getObject(objId));
this.cachedPatterns.set(objId, pattern);
}
if (matrix) {
pattern.matrix = matrix;
}
return pattern;
}
shadingFill(objId) {
if (!this.contentVisible) {
return;
}
const ctx = this.ctx;
this.save();
const pattern = this._getPattern(objId);
ctx.fillStyle = pattern.getPattern(ctx, this, getCurrentTransformInverse(ctx), PathType.SHADING);
const inv = getCurrentTransformInverse(ctx);
if (inv) {
const {
width,
height
} = ctx.canvas;
const [x0, y0, x1, y1] = Util.getAxialAlignedBoundingBox([0, 0, width, height], inv);
this.ctx.fillRect(x0, y0, x1 - x0, y1 - y0);
} else {
this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10);
}
this.compose(this.current.getClippedPathBoundingBox());
this.restore();
}
beginInlineImage() {
unreachable("Should not call beginInlineImage");
}
beginImageData() {
unreachable("Should not call beginImageData");
}
paintFormXObjectBegin(matrix, bbox) {
if (!this.contentVisible) {
return;
}
this.save();
this.baseTransformStack.push(this.baseTransform);
if (matrix) {
this.transform(...matrix);
}
this.baseTransform = getCurrentTransform(this.ctx);
if (bbox) {
const width = bbox[2] - bbox[0];
const height = bbox[3] - bbox[1];
this.ctx.rect(bbox[0], bbox[1], width, height);
this.current.updateRectMinMax(getCurrentTransform(this.ctx), bbox);
this.clip();
this.endPath();
}
}
paintFormXObjectEnd() {
if (!this.contentVisible) {
return;
}
this.restore();
this.baseTransform = this.baseTransformStack.pop();
}
beginGroup(group) {
if (!this.contentVisible) {
return;
}
this.save();
if (this.inSMaskMode) {
this.endSMaskMode();
this.current.activeSMask = null;
}
const currentCtx = this.ctx;
if (!group.isolated) {
info("TODO: Support non-isolated groups.");
}
if (group.knockout) {
warn("Knockout groups not supported.");
}
const currentTransform = getCurrentTransform(currentCtx);
if (group.matrix) {
currentCtx.transform(...group.matrix);
}
if (!group.bbox) {
throw new Error("Bounding box is required.");
}
let bounds = Util.getAxialAlignedBoundingBox(group.bbox, getCurrentTransform(currentCtx));
const canvasBounds = [0, 0, currentCtx.canvas.width, currentCtx.canvas.height];
bounds = Util.intersect(bounds, canvasBounds) || [0, 0, 0, 0];
const offsetX = Math.floor(bounds[0]);
const offsetY = Math.floor(bounds[1]);
const drawnWidth = Math.max(Math.ceil(bounds[2]) - offsetX, 1);
const drawnHeight = Math.max(Math.ceil(bounds[3]) - offsetY, 1);
this.current.startNewPathAndClipBox([0, 0, drawnWidth, drawnHeight]);
let cacheId = "groupAt" + this.groupLevel;
if (group.smask) {
cacheId += "_smask_" + this.smaskCounter++ % 2;
}
const scratchCanvas = this.cachedCanvases.getCanvas(cacheId, drawnWidth, drawnHeight);
const groupCtx = scratchCanvas.context;
groupCtx.translate(-offsetX, -offsetY);
groupCtx.transform(...currentTransform);
let clip = new Path2D();
const [x0, y0, x1, y1] = group.bbox;
clip.rect(x0, y0, x1 - x0, y1 - y0);
if (group.matrix) {
const path = new Path2D();
path.addPath(clip, new DOMMatrix(group.matrix));
clip = path;
}
groupCtx.clip(clip);
if (group.smask) {
this.smaskStack.push({
canvas: scratchCanvas.canvas,
context: groupCtx,
offsetX,
offsetY,
subtype: group.smask.subtype,
backdrop: group.smask.backdrop,
transferMap: group.smask.transferMap || null,
startTransformInverse: null
});
} else {
currentCtx.setTransform(1, 0, 0, 1, 0, 0);
currentCtx.translate(offsetX, offsetY);
currentCtx.save();
}
copyCtxState(currentCtx, groupCtx);
this.ctx = groupCtx;
this.setGState([["BM", "source-over"], ["ca", 1], ["CA", 1]]);
this.groupStack.push(currentCtx);
this.groupLevel++;
}
endGroup(group) {
if (!this.contentVisible) {
return;
}
this.groupLevel--;
const groupCtx = this.ctx;
const ctx = this.groupStack.pop();
this.ctx = ctx;
this.ctx.imageSmoothingEnabled = false;
if (group.smask) {
this.tempSMask = this.smaskStack.pop();
this.restore();
} else {
this.ctx.restore();
const currentMtx = getCurrentTransform(this.ctx);
this.restore();
this.ctx.save();
this.ctx.setTransform(...currentMtx);
const dirtyBox = Util.getAxialAlignedBoundingBox([0, 0, groupCtx.canvas.width, groupCtx.canvas.height], currentMtx);
this.ctx.drawImage(groupCtx.canvas, 0, 0);
this.ctx.restore();
this.compose(dirtyBox);
}
}
beginAnnotation(id, rect, transform, matrix, hasOwnCanvas) {
this.#restoreInitialState();
resetCtxToDefault(this.ctx);
this.ctx.save();
this.save();
if (this.baseTransform) {
this.ctx.setTransform(...this.baseTransform);
}
if (rect) {
const width = rect[2] - rect[0];
const height = rect[3] - rect[1];
if (hasOwnCanvas && this.annotationCanvasMap) {
transform = transform.slice();
transform[4] -= rect[0];
transform[5] -= rect[1];
rect = rect.slice();
rect[0] = rect[1] = 0;
rect[2] = width;
rect[3] = height;
const [scaleX, scaleY] = Util.singularValueDecompose2dScale(getCurrentTransform(this.ctx));
const {
viewportScale
} = this;
const canvasWidth = Math.ceil(width * this.outputScaleX * viewportScale);
const canvasHeight = Math.ceil(height * this.outputScaleY * viewportScale);
this.annotationCanvas = this.canvasFactory.create(canvasWidth, canvasHeight);
const {
canvas,
context
} = this.annotationCanvas;
this.annotationCanvasMap.set(id, canvas);
this.annotationCanvas.savedCtx = this.ctx;
this.ctx = context;
this.ctx.save();
this.ctx.setTransform(scaleX, 0, 0, -scaleY, 0, height * scaleY);
resetCtxToDefault(this.ctx);
} else {
resetCtxToDefault(this.ctx);
this.endPath();
this.ctx.rect(rect[0], rect[1], width, height);
this.ctx.clip();
this.ctx.beginPath();
}
}
this.current = new CanvasExtraState(this.ctx.canvas.width, this.ctx.canvas.height);
this.transform(...transform);
this.transform(...matrix);
}
endAnnotation() {
if (this.annotationCanvas) {
this.ctx.restore();
this.#drawFilter();
this.ctx = this.annotationCanvas.savedCtx;
delete this.annotationCanvas.savedCtx;
delete this.annotationCanvas;
}
}
paintImageMaskXObject(img) {
if (!this.contentVisible) {
return;
}
const count = img.count;
img = this.getObject(img.data, img);
img.count = count;
const ctx = this.ctx;
const glyph = this.processingType3;
if (glyph) {
if (glyph.compiled === undefined) {
glyph.compiled = compileType3Glyph(img);
}
if (glyph.compiled) {
ctx.fill(glyph.compiled);
return;
}
}
const mask = this._createMaskCanvas(img);
const maskCanvas = mask.canvas;
ctx.save();
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.drawImage(maskCanvas, mask.offsetX, mask.offsetY);
ctx.restore();
this.compose();
}
paintImageMaskXObjectRepeat(img, scaleX, skewX = 0, skewY = 0, scaleY, positions) {
if (!this.contentVisible) {
return;
}
img = this.getObject(img.data, img);
const ctx = this.ctx;
ctx.save();
const currentTransform = getCurrentTransform(ctx);
ctx.transform(scaleX, skewX, skewY, scaleY, 0, 0);
const mask = this._createMaskCanvas(img);
ctx.setTransform(1, 0, 0, 1, mask.offsetX - currentTransform[4], mask.offsetY - currentTransform[5]);
for (let i = 0, ii = positions.length; i < ii; i += 2) {
const trans = Util.transform(currentTransform, [scaleX, skewX, skewY, scaleY, positions[i], positions[i + 1]]);
const [x, y] = Util.applyTransform([0, 0], trans);
ctx.drawImage(mask.canvas, x, y);
}
ctx.restore();
this.compose();
}
paintImageMaskXObjectGroup(images) {
if (!this.contentVisible) {
return;
}
const ctx = this.ctx;
const fillColor = this.current.fillColor;
const isPatternFill = this.current.patternFill;
for (const image of images) {
const {
data,
width,
height,
transform
} = image;
const maskCanvas = this.cachedCanvases.getCanvas("maskCanvas", width, height);
const maskCtx = maskCanvas.context;
maskCtx.save();
const img = this.getObject(data, image);
putBinaryImageMask(maskCtx, img);
maskCtx.globalCompositeOperation = "source-in";
maskCtx.fillStyle = isPatternFill ? fillColor.getPattern(maskCtx, this, getCurrentTransformInverse(ctx), PathType.FILL) : fillColor;
maskCtx.fillRect(0, 0, width, height);
maskCtx.restore();
ctx.save();
ctx.transform(...transform);
ctx.scale(1, -1);
drawImageAtIntegerCoords(ctx, maskCanvas.canvas, 0, 0, width, height, 0, -1, 1, 1);
ctx.restore();
}
this.compose();
}
paintImageXObject(objId) {
if (!this.contentVisible) {
return;
}
const imgData = this.getObject(objId);
if (!imgData) {
warn("Dependent image isn't ready yet");
return;
}
this.paintInlineImageXObject(imgData);
}
paintImageXObjectRepeat(objId, scaleX, scaleY, positions) {
if (!this.contentVisible) {
return;
}
const imgData = this.getObject(objId);
if (!imgData) {
warn("Dependent image isn't ready yet");
return;
}
const width = imgData.width;
const height = imgData.height;
const map = [];
for (let i = 0, ii = positions.length; i < ii; i += 2) {
map.push({
transform: [scaleX, 0, 0, scaleY, positions[i], positions[i + 1]],
x: 0,
y: 0,
w: width,
h: height
});
}
this.paintInlineImageXObjectGroup(imgData, map);
}
applyTransferMapsToCanvas(ctx) {
if (this.current.transferMaps !== "none") {
ctx.filter = this.current.transferMaps;
ctx.drawImage(ctx.canvas, 0, 0);
ctx.filter = "none";
}
return ctx.canvas;
}
applyTransferMapsToBitmap(imgData) {
if (this.current.transferMaps === "none") {
return imgData.bitmap;
}
const {
bitmap,
width,
height
} = imgData;
const tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", width, height);
const tmpCtx = tmpCanvas.context;
tmpCtx.filter = this.current.transferMaps;
tmpCtx.drawImage(bitmap, 0, 0);
tmpCtx.filter = "none";
return tmpCanvas.canvas;
}
paintInlineImageXObject(imgData) {
if (!this.contentVisible) {
return;
}
const width = imgData.width;
const height = imgData.height;
const ctx = this.ctx;
this.save();
if (!isNodeJS) {
const {
filter
} = ctx;
if (filter !== "none" && filter !== "") {
ctx.filter = "none";
}
}
ctx.scale(1 / width, -1 / height);
let imgToPaint;
if (imgData.bitmap) {
imgToPaint = this.applyTransferMapsToBitmap(imgData);
} else if (typeof HTMLElement === "function" && imgData instanceof HTMLElement || !imgData.data) {
imgToPaint = imgData;
} else {
const tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", width, height);
const tmpCtx = tmpCanvas.context;
putBinaryImageData(tmpCtx, imgData);
imgToPaint = this.applyTransferMapsToCanvas(tmpCtx);
}
const scaled = this._scaleImage(imgToPaint, getCurrentTransformInverse(ctx));
ctx.imageSmoothingEnabled = getImageSmoothingEnabled(getCurrentTransform(ctx), imgData.interpolate);
drawImageAtIntegerCoords(ctx, scaled.img, 0, 0, scaled.paintWidth, scaled.paintHeight, 0, -height, width, height);
this.compose();
this.restore();
}
paintInlineImageXObjectGroup(imgData, map) {
if (!this.contentVisible) {
return;
}
const ctx = this.ctx;
let imgToPaint;
if (imgData.bitmap) {
imgToPaint = imgData.bitmap;
} else {
const w = imgData.width;
const h = imgData.height;
const tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", w, h);
const tmpCtx = tmpCanvas.context;
putBinaryImageData(tmpCtx, imgData);
imgToPaint = this.applyTransferMapsToCanvas(tmpCtx);
}
for (const entry of map) {
ctx.save();
ctx.transform(...entry.transform);
ctx.scale(1, -1);
drawImageAtIntegerCoords(ctx, imgToPaint, entry.x, entry.y, entry.w, entry.h, 0, -1, 1, 1);
ctx.restore();
}
this.compose();
}
paintSolidColorImageMask() {
if (!this.contentVisible) {
return;
}
this.ctx.fillRect(0, 0, 1, 1);
this.compose();
}
markPoint(tag) {}
markPointProps(tag, properties) {}
beginMarkedContent(tag) {
this.markedContentStack.push({
visible: true
});
}
beginMarkedContentProps(tag, properties) {
if (tag === "OC") {
this.markedContentStack.push({
visible: this.optionalContentConfig.isVisible(properties)
});
} else {
this.markedContentStack.push({
visible: true
});
}
this.contentVisible = this.isContentVisible();
}
endMarkedContent() {
this.markedContentStack.pop();
this.contentVisible = this.isContentVisible();
}
beginCompat() {}
endCompat() {}
consumePath(path, clipBox) {
const isEmpty = this.current.isEmptyClip();
if (this.pendingClip) {
this.current.updateClipFromPath();
}
if (!this.pendingClip) {
this.compose(clipBox);
}
const ctx = this.ctx;
if (this.pendingClip) {
if (!isEmpty) {
if (this.pendingClip === EO_CLIP) {
ctx.clip(path, "evenodd");
} else {
ctx.clip(path);
}
}
this.pendingClip = null;
}
this.current.startNewPathAndClipBox(this.current.clipBox);
ctx.beginPath();
}
getSinglePixelWidth() {
if (!this._cachedGetSinglePixelWidth) {
const m = getCurrentTransform(this.ctx);
if (m[1] === 0 && m[2] === 0) {
this._cachedGetSinglePixelWidth = 1 / Math.min(Math.abs(m[0]), Math.abs(m[3]));
} else {
const absDet = Math.abs(m[0] * m[3] - m[2] * m[1]);
const normX = Math.hypot(m[0], m[2]);
const normY = Math.hypot(m[1], m[3]);
this._cachedGetSinglePixelWidth = Math.max(normX, normY) / absDet;
}
}
return this._cachedGetSinglePixelWidth;
}
getScaleForStroking() {
if (this._cachedScaleForStroking[0] === -1) {
const {
lineWidth
} = this.current;
const {
a,
b,
c,
d
} = this.ctx.getTransform();
let scaleX, scaleY;
if (b === 0 && c === 0) {
const normX = Math.abs(a);
const normY = Math.abs(d);
if (normX === normY) {
if (lineWidth === 0) {
scaleX = scaleY = 1 / normX;
} else {
const scaledLineWidth = normX * lineWidth;
scaleX = scaleY = scaledLineWidth < 1 ? 1 / scaledLineWidth : 1;
}
} else if (lineWidth === 0) {
scaleX = 1 / normX;
scaleY = 1 / normY;
} else {
const scaledXLineWidth = normX * lineWidth;
const scaledYLineWidth = normY * lineWidth;
scaleX = scaledXLineWidth < 1 ? 1 / scaledXLineWidth : 1;
scaleY = scaledYLineWidth < 1 ? 1 / scaledYLineWidth : 1;
}
} else {
const absDet = Math.abs(a * d - b * c);
const normX = Math.hypot(a, b);
const normY = Math.hypot(c, d);
if (lineWidth === 0) {
scaleX = normY / absDet;
scaleY = normX / absDet;
} else {
const baseArea = lineWidth * absDet;
scaleX = normY > baseArea ? normY / baseArea : 1;
scaleY = normX > baseArea ? normX / baseArea : 1;
}
}
this._cachedScaleForStroking[0] = scaleX;
this._cachedScaleForStroking[1] = scaleY;
}
return this._cachedScaleForStroking;
}
rescaleAndStroke(path, saveRestore) {
const {
ctx,
current: {
lineWidth
}
} = this;
const [scaleX, scaleY] = this.getScaleForStroking();
if (scaleX === scaleY) {
ctx.lineWidth = (lineWidth || 1) * scaleX;
ctx.stroke(path);
return;
}
const dashes = ctx.getLineDash();
if (saveRestore) {
ctx.save();
}
ctx.scale(scaleX, scaleY);
SCALE_MATRIX.a = 1 / scaleX;
SCALE_MATRIX.d = 1 / scaleY;
const newPath = new Path2D();
newPath.addPath(path, SCALE_MATRIX);
if (dashes.length > 0) {
const scale = Math.max(scaleX, scaleY);
ctx.setLineDash(dashes.map(x => x / scale));
ctx.lineDashOffset /= scale;
}
ctx.lineWidth = lineWidth || 1;
ctx.stroke(newPath);
if (saveRestore) {
ctx.restore();
}
}
isContentVisible() {
for (let i = this.markedContentStack.length - 1; i >= 0; i--) {
if (!this.markedContentStack[i].visible) {
return false;
}
}
return true;
}
}
for (const op in OPS) {
if (CanvasGraphics.prototype[op] !== undefined) {
CanvasGraphics.prototype[OPS[op]] = CanvasGraphics.prototype[op];
}
}
;// ./src/display/worker_options.js
class GlobalWorkerOptions {
static #port = null;
static #src = "";
static get workerPort() {
return this.#port;
}
static set workerPort(val) {
if (!(typeof Worker !== "undefined" && val instanceof Worker) && val !== null) {
throw new Error("Invalid `workerPort` type.");
}
this.#port = val;
}
static get workerSrc() {
return this.#src;
}
static set workerSrc(val) {
if (typeof val !== "string") {
throw new Error("Invalid `workerSrc` type.");
}
this.#src = val;
}
}
;// ./src/display/metadata.js
class Metadata {
#metadataMap;
#data;
constructor({
parsedData,
rawData
}) {
this.#metadataMap = parsedData;
this.#data = rawData;
}
getRaw() {
return this.#data;
}
get(name) {
return this.#metadataMap.get(name) ?? null;
}
getAll() {
return objectFromMap(this.#metadataMap);
}
has(name) {
return this.#metadataMap.has(name);
}
}
;// ./src/display/optional_content_config.js
const INTERNAL = Symbol("INTERNAL");
class OptionalContentGroup {
#isDisplay = false;
#isPrint = false;
#userSet = false;
#visible = true;
constructor(renderingIntent, {
name,
intent,
usage,
rbGroups
}) {
this.#isDisplay = !!(renderingIntent & RenderingIntentFlag.DISPLAY);
this.#isPrint = !!(renderingIntent & RenderingIntentFlag.PRINT);
this.name = name;
this.intent = intent;
this.usage = usage;
this.rbGroups = rbGroups;
}
get visible() {
if (this.#userSet) {
return this.#visible;
}
if (!this.#visible) {
return false;
}
const {
print,
view
} = this.usage;
if (this.#isDisplay) {
return view?.viewState !== "OFF";
} else if (this.#isPrint) {
return print?.printState !== "OFF";
}
return true;
}
_setVisible(internal, visible, userSet = false) {
if (internal !== INTERNAL) {
unreachable("Internal method `_setVisible` called.");
}
this.#userSet = userSet;
this.#visible = visible;
}
}
class OptionalContentConfig {
#cachedGetHash = null;
#groups = new Map();
#initialHash = null;
#order = null;
constructor(data, renderingIntent = RenderingIntentFlag.DISPLAY) {
this.renderingIntent = renderingIntent;
this.name = null;
this.creator = null;
if (data === null) {
return;
}
this.name = data.name;
this.creator = data.creator;
this.#order = data.order;
for (const group of data.groups) {
this.#groups.set(group.id, new OptionalContentGroup(renderingIntent, group));
}
if (data.baseState === "OFF") {
for (const group of this.#groups.values()) {
group._setVisible(INTERNAL, false);
}
}
for (const on of data.on) {
this.#groups.get(on)._setVisible(INTERNAL, true);
}
for (const off of data.off) {
this.#groups.get(off)._setVisible(INTERNAL, false);
}
this.#initialHash = this.getHash();
}
#evaluateVisibilityExpression(array) {
const length = array.length;
if (length < 2) {
return true;
}
const operator = array[0];
for (let i = 1; i < length; i++) {
const element = array[i];
let state;
if (Array.isArray(element)) {
state = this.#evaluateVisibilityExpression(element);
} else if (this.#groups.has(element)) {
state = this.#groups.get(element).visible;
} else {
warn(`Optional content group not found: ${element}`);
return true;
}
switch (operator) {
case "And":
if (!state) {
return false;
}
break;
case "Or":
if (state) {
return true;
}
break;
case "Not":
return !state;
default:
return true;
}
}
return operator === "And";
}
isVisible(group) {
if (this.#groups.size === 0) {
return true;
}
if (!group) {
info("Optional content group not defined.");
return true;
}
if (group.type === "OCG") {
if (!this.#groups.has(group.id)) {
warn(`Optional content group not found: ${group.id}`);
return true;
}
return this.#groups.get(group.id).visible;
} else if (group.type === "OCMD") {
if (group.expression) {
return this.#evaluateVisibilityExpression(group.expression);
}
if (!group.policy || group.policy === "AnyOn") {
for (const id of group.ids) {
if (!this.#groups.has(id)) {
warn(`Optional content group not found: ${id}`);
return true;
}
if (this.#groups.get(id).visible) {
return true;
}
}
return false;
} else if (group.policy === "AllOn") {
for (const id of group.ids) {
if (!this.#groups.has(id)) {
warn(`Optional content group not found: ${id}`);
return true;
}
if (!this.#groups.get(id).visible) {
return false;
}
}
return true;
} else if (group.policy === "AnyOff") {
for (const id of group.ids) {
if (!this.#groups.has(id)) {
warn(`Optional content group not found: ${id}`);
return true;
}
if (!this.#groups.get(id).visible) {
return true;
}
}
return false;
} else if (group.policy === "AllOff") {
for (const id of group.ids) {
if (!this.#groups.has(id)) {
warn(`Optional content group not found: ${id}`);
return true;
}
if (this.#groups.get(id).visible) {
return false;
}
}
return true;
}
warn(`Unknown optional content policy ${group.policy}.`);
return true;
}
warn(`Unknown group type ${group.type}.`);
return true;
}
setVisibility(id, visible = true, preserveRB = true) {
const group = this.#groups.get(id);
if (!group) {
warn(`Optional content group not found: ${id}`);
return;
}
if (preserveRB && visible && group.rbGroups.length) {
for (const rbGroup of group.rbGroups) {
for (const otherId of rbGroup) {
if (otherId !== id) {
this.#groups.get(otherId)?._setVisible(INTERNAL, false, true);
}
}
}
}
group._setVisible(INTERNAL, !!visible, true);
this.#cachedGetHash = null;
}
setOCGState({
state,
preserveRB
}) {
let operator;
for (const elem of state) {
switch (elem) {
case "ON":
case "OFF":
case "Toggle":
operator = elem;
continue;
}
const group = this.#groups.get(elem);
if (!group) {
continue;
}
switch (operator) {
case "ON":
this.setVisibility(elem, true, preserveRB);
break;
case "OFF":
this.setVisibility(elem, false, preserveRB);
break;
case "Toggle":
this.setVisibility(elem, !group.visible, preserveRB);
break;
}
}
this.#cachedGetHash = null;
}
get hasInitialVisibility() {
return this.#initialHash === null || this.getHash() === this.#initialHash;
}
getOrder() {
if (!this.#groups.size) {
return null;
}
if (this.#order) {
return this.#order.slice();
}
return [...this.#groups.keys()];
}
getGroups() {
return this.#groups.size > 0 ? objectFromMap(this.#groups) : null;
}
getGroup(id) {
return this.#groups.get(id) || null;
}
getHash() {
if (this.#cachedGetHash !== null) {
return this.#cachedGetHash;
}
const hash = new MurmurHash3_64();
for (const [id, group] of this.#groups) {
hash.update(`${id}:${group.visible}`);
}
return this.#cachedGetHash = hash.hexdigest();
}
}
;// ./src/display/transport_stream.js
class PDFDataTransportStream {
constructor(pdfDataRangeTransport, {
disableRange = false,
disableStream = false
}) {
assert(pdfDataRangeTransport, 'PDFDataTransportStream - missing required "pdfDataRangeTransport" argument.');
const {
length,
initialData,
progressiveDone,
contentDispositionFilename
} = pdfDataRangeTransport;
this._queuedChunks = [];
this._progressiveDone = progressiveDone;
this._contentDispositionFilename = contentDispositionFilename;
if (initialData?.length > 0) {
const buffer = initialData instanceof Uint8Array && initialData.byteLength === initialData.buffer.byteLength ? initialData.buffer : new Uint8Array(initialData).buffer;
this._queuedChunks.push(buffer);
}
this._pdfDataRangeTransport = pdfDataRangeTransport;
this._isStreamingSupported = !disableStream;
this._isRangeSupported = !disableRange;
this._contentLength = length;
this._fullRequestReader = null;
this._rangeReaders = [];
pdfDataRangeTransport.addRangeListener((begin, chunk) => {
this._onReceiveData({
begin,
chunk
});
});
pdfDataRangeTransport.addProgressListener((loaded, total) => {
this._onProgress({
loaded,
total
});
});
pdfDataRangeTransport.addProgressiveReadListener(chunk => {
this._onReceiveData({
chunk
});
});
pdfDataRangeTransport.addProgressiveDoneListener(() => {
this._onProgressiveDone();
});
pdfDataRangeTransport.transportReady();
}
_onReceiveData({
begin,
chunk
}) {
const buffer = chunk instanceof Uint8Array && chunk.byteLength === chunk.buffer.byteLength ? chunk.buffer : new Uint8Array(chunk).buffer;
if (begin === undefined) {
if (this._fullRequestReader) {
this._fullRequestReader._enqueue(buffer);
} else {
this._queuedChunks.push(buffer);
}
} else {
const found = this._rangeReaders.some(function (rangeReader) {
if (rangeReader._begin !== begin) {
return false;
}
rangeReader._enqueue(buffer);
return true;
});
assert(found, "_onReceiveData - no `PDFDataTransportStreamRangeReader` instance found.");
}
}
get _progressiveDataLength() {
return this._fullRequestReader?._loaded ?? 0;
}
_onProgress(evt) {
if (evt.total === undefined) {
this._rangeReaders[0]?.onProgress?.({
loaded: evt.loaded
});
} else {
this._fullRequestReader?.onProgress?.({
loaded: evt.loaded,
total: evt.total
});
}
}
_onProgressiveDone() {
this._fullRequestReader?.progressiveDone();
this._progressiveDone = true;
}
_removeRangeReader(reader) {
const i = this._rangeReaders.indexOf(reader);
if (i >= 0) {
this._rangeReaders.splice(i, 1);
}
}
getFullReader() {
assert(!this._fullRequestReader, "PDFDataTransportStream.getFullReader can only be called once.");
const queuedChunks = this._queuedChunks;
this._queuedChunks = null;
return new PDFDataTransportStreamReader(this, queuedChunks, this._progressiveDone, this._contentDispositionFilename);
}
getRangeReader(begin, end) {
if (end <= this._progressiveDataLength) {
return null;
}
const reader = new PDFDataTransportStreamRangeReader(this, begin, end);
this._pdfDataRangeTransport.requestDataRange(begin, end);
this._rangeReaders.push(reader);
return reader;
}
cancelAllRequests(reason) {
this._fullRequestReader?.cancel(reason);
for (const reader of this._rangeReaders.slice(0)) {
reader.cancel(reason);
}
this._pdfDataRangeTransport.abort();
}
}
class PDFDataTransportStreamReader {
constructor(stream, queuedChunks, progressiveDone = false, contentDispositionFilename = null) {
this._stream = stream;
this._done = progressiveDone || false;
this._filename = isPdfFile(contentDispositionFilename) ? contentDispositionFilename : null;
this._queuedChunks = queuedChunks || [];
this._loaded = 0;
for (const chunk of this._queuedChunks) {
this._loaded += chunk.byteLength;
}
this._requests = [];
this._headersReady = Promise.resolve();
stream._fullRequestReader = this;
this.onProgress = null;
}
_enqueue(chunk) {
if (this._done) {
return;
}
if (this._requests.length > 0) {
const requestCapability = this._requests.shift();
requestCapability.resolve({
value: chunk,
done: false
});
} else {
this._queuedChunks.push(chunk);
}
this._loaded += chunk.byteLength;
}
get headersReady() {
return this._headersReady;
}
get filename() {
return this._filename;
}
get isRangeSupported() {
return this._stream._isRangeSupported;
}
get isStreamingSupported() {
return this._stream._isStreamingSupported;
}
get contentLength() {
return this._stream._contentLength;
}
async read() {
if (this._queuedChunks.length > 0) {
const chunk = this._queuedChunks.shift();
return {
value: chunk,
done: false
};
}
if (this._done) {
return {
value: undefined,
done: true
};
}
const requestCapability = Promise.withResolvers();
this._requests.push(requestCapability);
return requestCapability.promise;
}
cancel(reason) {
this._done = true;
for (const requestCapability of this._requests) {
requestCapability.resolve({
value: undefined,
done: true
});
}
this._requests.length = 0;
}
progressiveDone() {
if (this._done) {
return;
}
this._done = true;
}
}
class PDFDataTransportStreamRangeReader {
constructor(stream, begin, end) {
this._stream = stream;
this._begin = begin;
this._end = end;
this._queuedChunk = null;
this._requests = [];
this._done = false;
this.onProgress = null;
}
_enqueue(chunk) {
if (this._done) {
return;
}
if (this._requests.length === 0) {
this._queuedChunk = chunk;
} else {
const requestsCapability = this._requests.shift();
requestsCapability.resolve({
value: chunk,
done: false
});
for (const requestCapability of this._requests) {
requestCapability.resolve({
value: undefined,
done: true
});
}
this._requests.length = 0;
}
this._done = true;
this._stream._removeRangeReader(this);
}
get isStreamingSupported() {
return false;
}
async read() {
if (this._queuedChunk) {
const chunk = this._queuedChunk;
this._queuedChunk = null;
return {
value: chunk,
done: false
};
}
if (this._done) {
return {
value: undefined,
done: true
};
}
const requestCapability = Promise.withResolvers();
this._requests.push(requestCapability);
return requestCapability.promise;
}
cancel(reason) {
this._done = true;
for (const requestCapability of this._requests) {
requestCapability.resolve({
value: undefined,
done: true
});
}
this._requests.length = 0;
this._stream._removeRangeReader(this);
}
}
;// ./src/display/content_disposition.js
function getFilenameFromContentDispositionHeader(contentDisposition) {
let needsEncodingFixup = true;
let tmp = toParamRegExp("filename\\*", "i").exec(contentDisposition);
if (tmp) {
tmp = tmp[1];
let filename = rfc2616unquote(tmp);
filename = unescape(filename);
filename = rfc5987decode(filename);
filename = rfc2047decode(filename);
return fixupEncoding(filename);
}
tmp = rfc2231getparam(contentDisposition);
if (tmp) {
const filename = rfc2047decode(tmp);
return fixupEncoding(filename);
}
tmp = toParamRegExp("filename", "i").exec(contentDisposition);
if (tmp) {
tmp = tmp[1];
let filename = rfc2616unquote(tmp);
filename = rfc2047decode(filename);
return fixupEncoding(filename);
}
function toParamRegExp(attributePattern, flags) {
return new RegExp("(?:^|;)\\s*" + attributePattern + "\\s*=\\s*" + "(" + '[^";\\s][^;\\s]*' + "|" + '"(?:[^"\\\\]|\\\\"?)+"?' + ")", flags);
}
function textdecode(encoding, value) {
if (encoding) {
if (!/^[\x00-\xFF]+$/.test(value)) {
return value;
}
try {
const decoder = new TextDecoder(encoding, {
fatal: true
});
const buffer = stringToBytes(value);
value = decoder.decode(buffer);
needsEncodingFixup = false;
} catch {}
}
return value;
}
function fixupEncoding(value) {
if (needsEncodingFixup && /[\x80-\xff]/.test(value)) {
value = textdecode("utf-8", value);
if (needsEncodingFixup) {
value = textdecode("iso-8859-1", value);
}
}
return value;
}
function rfc2231getparam(contentDispositionStr) {
const matches = [];
let match;
const iter = toParamRegExp("filename\\*((?!0\\d)\\d+)(\\*?)", "ig");
while ((match = iter.exec(contentDispositionStr)) !== null) {
let [, n, quot, part] = match;
n = parseInt(n, 10);
if (n in matches) {
if (n === 0) {
break;
}
continue;
}
matches[n] = [quot, part];
}
const parts = [];
for (let n = 0; n < matches.length; ++n) {
if (!(n in matches)) {
break;
}
let [quot, part] = matches[n];
part = rfc2616unquote(part);
if (quot) {
part = unescape(part);
if (n === 0) {
part = rfc5987decode(part);
}
}
parts.push(part);
}
return parts.join("");
}
function rfc2616unquote(value) {
if (value.startsWith('"')) {
const parts = value.slice(1).split('\\"');
for (let i = 0; i < parts.length; ++i) {
const quotindex = parts[i].indexOf('"');
if (quotindex !== -1) {
parts[i] = parts[i].slice(0, quotindex);
parts.length = i + 1;
}
parts[i] = parts[i].replaceAll(/\\(.)/g, "$1");
}
value = parts.join('"');
}
return value;
}
function rfc5987decode(extvalue) {
const encodingend = extvalue.indexOf("'");
if (encodingend === -1) {
return extvalue;
}
const encoding = extvalue.slice(0, encodingend);
const langvalue = extvalue.slice(encodingend + 1);
const value = langvalue.replace(/^[^']*'/, "");
return textdecode(encoding, value);
}
function rfc2047decode(value) {
if (!value.startsWith("=?") || /[\x00-\x19\x80-\xff]/.test(value)) {
return value;
}
return value.replaceAll(/=\?([\w-]*)\?([QqBb])\?((?:[^?]|\?(?!=))*)\?=/g, function (matches, charset, encoding, text) {
if (encoding === "q" || encoding === "Q") {
text = text.replaceAll("_", " ");
text = text.replaceAll(/=([0-9a-fA-F]{2})/g, function (match, hex) {
return String.fromCharCode(parseInt(hex, 16));
});
return textdecode(charset, text);
}
try {
text = atob(text);
} catch {}
return textdecode(charset, text);
});
}
return "";
}
;// ./src/display/network_utils.js
function createHeaders(isHttp, httpHeaders) {
const headers = new Headers();
if (!isHttp || !httpHeaders || typeof httpHeaders !== "object") {
return headers;
}
for (const key in httpHeaders) {
const val = httpHeaders[key];
if (val !== undefined) {
headers.append(key, val);
}
}
return headers;
}
function getResponseOrigin(url) {
return URL.parse(url)?.origin ?? null;
}
function validateRangeRequestCapabilities({
responseHeaders,
isHttp,
rangeChunkSize,
disableRange
}) {
const returnValues = {
allowRangeRequests: false,
suggestedLength: undefined
};
const length = parseInt(responseHeaders.get("Content-Length"), 10);
if (!Number.isInteger(length)) {
return returnValues;
}
returnValues.suggestedLength = length;
if (length <= 2 * rangeChunkSize) {
return returnValues;
}
if (disableRange || !isHttp) {
return returnValues;
}
if (responseHeaders.get("Accept-Ranges") !== "bytes") {
return returnValues;
}
const contentEncoding = responseHeaders.get("Content-Encoding") || "identity";
if (contentEncoding !== "identity") {
return returnValues;
}
returnValues.allowRangeRequests = true;
return returnValues;
}
function extractFilenameFromHeader(responseHeaders) {
const contentDisposition = responseHeaders.get("Content-Disposition");
if (contentDisposition) {
let filename = getFilenameFromContentDispositionHeader(contentDisposition);
if (filename.includes("%")) {
try {
filename = decodeURIComponent(filename);
} catch {}
}
if (isPdfFile(filename)) {
return filename;
}
}
return null;
}
function createResponseError(status, url) {
return new ResponseException(`Unexpected server response (${status}) while retrieving PDF "${url}".`, status, status === 404 || status === 0 && url.startsWith("file:"));
}
function validateResponseStatus(status) {
return status === 200 || status === 206;
}
;// ./src/display/fetch_stream.js
function createFetchOptions(headers, withCredentials, abortController) {
return {
method: "GET",
headers,
signal: abortController.signal,
mode: "cors",
credentials: withCredentials ? "include" : "same-origin",
redirect: "follow"
};
}
function getArrayBuffer(val) {
if (val instanceof Uint8Array) {
return val.buffer;
}
if (val instanceof ArrayBuffer) {
return val;
}
warn(`getArrayBuffer - unexpected data format: ${val}`);
return new Uint8Array(val).buffer;
}
class PDFFetchStream {
_responseOrigin = null;
constructor(source) {
this.source = source;
this.isHttp = /^https?:/i.test(source.url);
this.headers = createHeaders(this.isHttp, source.httpHeaders);
this._fullRequestReader = null;
this._rangeRequestReaders = [];
}
get _progressiveDataLength() {
return this._fullRequestReader?._loaded ?? 0;
}
getFullReader() {
assert(!this._fullRequestReader, "PDFFetchStream.getFullReader can only be called once.");
this._fullRequestReader = new PDFFetchStreamReader(this);
return this._fullRequestReader;
}
getRangeReader(begin, end) {
if (end <= this._progressiveDataLength) {
return null;
}
const reader = new PDFFetchStreamRangeReader(this, begin, end);
this._rangeRequestReaders.push(reader);
return reader;
}
cancelAllRequests(reason) {
this._fullRequestReader?.cancel(reason);
for (const reader of this._rangeRequestReaders.slice(0)) {
reader.cancel(reason);
}
}
}
class PDFFetchStreamReader {
constructor(stream) {
this._stream = stream;
this._reader = null;
this._loaded = 0;
this._filename = null;
const source = stream.source;
this._withCredentials = source.withCredentials || false;
this._contentLength = source.length;
this._headersCapability = Promise.withResolvers();
this._disableRange = source.disableRange || false;
this._rangeChunkSize = source.rangeChunkSize;
if (!this._rangeChunkSize && !this._disableRange) {
this._disableRange = true;
}
this._abortController = new AbortController();
this._isStreamingSupported = !source.disableStream;
this._isRangeSupported = !source.disableRange;
const headers = new Headers(stream.headers);
const url = source.url;
fetch(url, createFetchOptions(headers, this._withCredentials, this._abortController)).then(response => {
stream._responseOrigin = getResponseOrigin(response.url);
if (!validateResponseStatus(response.status)) {
throw createResponseError(response.status, url);
}
this._reader = response.body.getReader();
this._headersCapability.resolve();
const responseHeaders = response.headers;
const {
allowRangeRequests,
suggestedLength
} = validateRangeRequestCapabilities({
responseHeaders,
isHttp: stream.isHttp,
rangeChunkSize: this._rangeChunkSize,
disableRange: this._disableRange
});
this._isRangeSupported = allowRangeRequests;
this._contentLength = suggestedLength || this._contentLength;
this._filename = extractFilenameFromHeader(responseHeaders);
if (!this._isStreamingSupported && this._isRangeSupported) {
this.cancel(new AbortException("Streaming is disabled."));
}
}).catch(this._headersCapability.reject);
this.onProgress = null;
}
get headersReady() {
return this._headersCapability.promise;
}
get filename() {
return this._filename;
}
get contentLength() {
return this._contentLength;
}
get isRangeSupported() {
return this._isRangeSupported;
}
get isStreamingSupported() {
return this._isStreamingSupported;
}
async read() {
await this._headersCapability.promise;
const {
value,
done
} = await this._reader.read();
if (done) {
return {
value,
done
};
}
this._loaded += value.byteLength;
this.onProgress?.({
loaded: this._loaded,
total: this._contentLength
});
return {
value: getArrayBuffer(value),
done: false
};
}
cancel(reason) {
this._reader?.cancel(reason);
this._abortController.abort();
}
}
class PDFFetchStreamRangeReader {
constructor(stream, begin, end) {
this._stream = stream;
this._reader = null;
this._loaded = 0;
const source = stream.source;
this._withCredentials = source.withCredentials || false;
this._readCapability = Promise.withResolvers();
this._isStreamingSupported = !source.disableStream;
this._abortController = new AbortController();
const headers = new Headers(stream.headers);
headers.append("Range", `bytes=${begin}-${end - 1}`);
const url = source.url;
fetch(url, createFetchOptions(headers, this._withCredentials, this._abortController)).then(response => {
const responseOrigin = getResponseOrigin(response.url);
if (responseOrigin !== stream._responseOrigin) {
throw new Error(`Expected range response-origin "${responseOrigin}" to match "${stream._responseOrigin}".`);
}
if (!validateResponseStatus(response.status)) {
throw createResponseError(response.status, url);
}
this._readCapability.resolve();
this._reader = response.body.getReader();
}).catch(this._readCapability.reject);
this.onProgress = null;
}
get isStreamingSupported() {
return this._isStreamingSupported;
}
async read() {
await this._readCapability.promise;
const {
value,
done
} = await this._reader.read();
if (done) {
return {
value,
done
};
}
this._loaded += value.byteLength;
this.onProgress?.({
loaded: this._loaded
});
return {
value: getArrayBuffer(value),
done: false
};
}
cancel(reason) {
this._reader?.cancel(reason);
this._abortController.abort();
}
}
;// ./src/display/network.js
const OK_RESPONSE = 200;
const PARTIAL_CONTENT_RESPONSE = 206;
function network_getArrayBuffer(xhr) {
const data = xhr.response;
if (typeof data !== "string") {
return data;
}
return stringToBytes(data).buffer;
}
class NetworkManager {
_responseOrigin = null;
constructor({
url,
httpHeaders,
withCredentials
}) {
this.url = url;
this.isHttp = /^https?:/i.test(url);
this.headers = createHeaders(this.isHttp, httpHeaders);
this.withCredentials = withCredentials || false;
this.currXhrId = 0;
this.pendingRequests = Object.create(null);
}
request(args) {
const xhr = new XMLHttpRequest();
const xhrId = this.currXhrId++;
const pendingRequest = this.pendingRequests[xhrId] = {
xhr
};
xhr.open("GET", this.url);
xhr.withCredentials = this.withCredentials;
for (const [key, val] of this.headers) {
xhr.setRequestHeader(key, val);
}
if (this.isHttp && "begin" in args && "end" in args) {
xhr.setRequestHeader("Range", `bytes=${args.begin}-${args.end - 1}`);
pendingRequest.expectedStatus = PARTIAL_CONTENT_RESPONSE;
} else {
pendingRequest.expectedStatus = OK_RESPONSE;
}
xhr.responseType = "arraybuffer";
assert(args.onError, "Expected `onError` callback to be provided.");
xhr.onerror = () => {
args.onError(xhr.status);
};
xhr.onreadystatechange = this.onStateChange.bind(this, xhrId);
xhr.onprogress = this.onProgress.bind(this, xhrId);
pendingRequest.onHeadersReceived = args.onHeadersReceived;
pendingRequest.onDone = args.onDone;
pendingRequest.onError = args.onError;
pendingRequest.onProgress = args.onProgress;
xhr.send(null);
return xhrId;
}
onProgress(xhrId, evt) {
const pendingRequest = this.pendingRequests[xhrId];
if (!pendingRequest) {
return;
}
pendingRequest.onProgress?.(evt);
}
onStateChange(xhrId, evt) {
const pendingRequest = this.pendingRequests[xhrId];
if (!pendingRequest) {
return;
}
const xhr = pendingRequest.xhr;
if (xhr.readyState >= 2 && pendingRequest.onHeadersReceived) {
pendingRequest.onHeadersReceived();
delete pendingRequest.onHeadersReceived;
}
if (xhr.readyState !== 4) {
return;
}
if (!(xhrId in this.pendingRequests)) {
return;
}
delete this.pendingRequests[xhrId];
if (xhr.status === 0 && this.isHttp) {
pendingRequest.onError(xhr.status);
return;
}
const xhrStatus = xhr.status || OK_RESPONSE;
const ok_response_on_range_request = xhrStatus === OK_RESPONSE && pendingRequest.expectedStatus === PARTIAL_CONTENT_RESPONSE;
if (!ok_response_on_range_request && xhrStatus !== pendingRequest.expectedStatus) {
pendingRequest.onError(xhr.status);
return;
}
const chunk = network_getArrayBuffer(xhr);
if (xhrStatus === PARTIAL_CONTENT_RESPONSE) {
const rangeHeader = xhr.getResponseHeader("Content-Range");
const matches = /bytes (\d+)-(\d+)\/(\d+)/.exec(rangeHeader);
if (matches) {
pendingRequest.onDone({
begin: parseInt(matches[1], 10),
chunk
});
} else {
warn(`Missing or invalid "Content-Range" header.`);
pendingRequest.onError(0);
}
} else if (chunk) {
pendingRequest.onDone({
begin: 0,
chunk
});
} else {
pendingRequest.onError(xhr.status);
}
}
getRequestXhr(xhrId) {
return this.pendingRequests[xhrId].xhr;
}
isPendingRequest(xhrId) {
return xhrId in this.pendingRequests;
}
abortRequest(xhrId) {
const xhr = this.pendingRequests[xhrId].xhr;
delete this.pendingRequests[xhrId];
xhr.abort();
}
}
class PDFNetworkStream {
constructor(source) {
this._source = source;
this._manager = new NetworkManager(source);
this._rangeChunkSize = source.rangeChunkSize;
this._fullRequestReader = null;
this._rangeRequestReaders = [];
}
_onRangeRequestReaderClosed(reader) {
const i = this._rangeRequestReaders.indexOf(reader);
if (i >= 0) {
this._rangeRequestReaders.splice(i, 1);
}
}
getFullReader() {
assert(!this._fullRequestReader, "PDFNetworkStream.getFullReader can only be called once.");
this._fullRequestReader = new PDFNetworkStreamFullRequestReader(this._manager, this._source);
return this._fullRequestReader;
}
getRangeReader(begin, end) {
const reader = new PDFNetworkStreamRangeRequestReader(this._manager, begin, end);
reader.onClosed = this._onRangeRequestReaderClosed.bind(this);
this._rangeRequestReaders.push(reader);
return reader;
}
cancelAllRequests(reason) {
this._fullRequestReader?.cancel(reason);
for (const reader of this._rangeRequestReaders.slice(0)) {
reader.cancel(reason);
}
}
}
class PDFNetworkStreamFullRequestReader {
constructor(manager, source) {
this._manager = manager;
this._url = source.url;
this._fullRequestId = manager.request({
onHeadersReceived: this._onHeadersReceived.bind(this),
onDone: this._onDone.bind(this),
onError: this._onError.bind(this),
onProgress: this._onProgress.bind(this)
});
this._headersCapability = Promise.withResolvers();
this._disableRange = source.disableRange || false;
this._contentLength = source.length;
this._rangeChunkSize = source.rangeChunkSize;
if (!this._rangeChunkSize && !this._disableRange) {
this._disableRange = true;
}
this._isStreamingSupported = false;
this._isRangeSupported = false;
this._cachedChunks = [];
this._requests = [];
this._done = false;
this._storedError = undefined;
this._filename = null;
this.onProgress = null;
}
_onHeadersReceived() {
const fullRequestXhrId = this._fullRequestId;
const fullRequestXhr = this._manager.getRequestXhr(fullRequestXhrId);
this._manager._responseOrigin = getResponseOrigin(fullRequestXhr.responseURL);
const rawResponseHeaders = fullRequestXhr.getAllResponseHeaders();
const responseHeaders = new Headers(rawResponseHeaders ? rawResponseHeaders.trimStart().replace(/[^\S ]+$/, "").split(/[\r\n]+/).map(x => {
const [key, ...val] = x.split(": ");
return [key, val.join(": ")];
}) : []);
const {
allowRangeRequests,
suggestedLength
} = validateRangeRequestCapabilities({
responseHeaders,
isHttp: this._manager.isHttp,
rangeChunkSize: this._rangeChunkSize,
disableRange: this._disableRange
});
if (allowRangeRequests) {
this._isRangeSupported = true;
}
this._contentLength = suggestedLength || this._contentLength;
this._filename = extractFilenameFromHeader(responseHeaders);
if (this._isRangeSupported) {
this._manager.abortRequest(fullRequestXhrId);
}
this._headersCapability.resolve();
}
_onDone(data) {
if (data) {
if (this._requests.length > 0) {
const requestCapability = this._requests.shift();
requestCapability.resolve({
value: data.chunk,
done: false
});
} else {
this._cachedChunks.push(data.chunk);
}
}
this._done = true;
if (this._cachedChunks.length > 0) {
return;
}
for (const requestCapability of this._requests) {
requestCapability.resolve({
value: undefined,
done: true
});
}
this._requests.length = 0;
}
_onError(status) {
this._storedError = createResponseError(status, this._url);
this._headersCapability.reject(this._storedError);
for (const requestCapability of this._requests) {
requestCapability.reject(this._storedError);
}
this._requests.length = 0;
this._cachedChunks.length = 0;
}
_onProgress(evt) {
this.onProgress?.({
loaded: evt.loaded,
total: evt.lengthComputable ? evt.total : this._contentLength
});
}
get filename() {
return this._filename;
}
get isRangeSupported() {
return this._isRangeSupported;
}
get isStreamingSupported() {
return this._isStreamingSupported;
}
get contentLength() {
return this._contentLength;
}
get headersReady() {
return this._headersCapability.promise;
}
async read() {
await this._headersCapability.promise;
if (this._storedError) {
throw this._storedError;
}
if (this._cachedChunks.length > 0) {
const chunk = this._cachedChunks.shift();
return {
value: chunk,
done: false
};
}
if (this._done) {
return {
value: undefined,
done: true
};
}
const requestCapability = Promise.withResolvers();
this._requests.push(requestCapability);
return requestCapability.promise;
}
cancel(reason) {
this._done = true;
this._headersCapability.reject(reason);
for (const requestCapability of this._requests) {
requestCapability.resolve({
value: undefined,
done: true
});
}
this._requests.length = 0;
if (this._manager.isPendingRequest(this._fullRequestId)) {
this._manager.abortRequest(this._fullRequestId);
}
this._fullRequestReader = null;
}
}
class PDFNetworkStreamRangeRequestReader {
constructor(manager, begin, end) {
this._manager = manager;
this._url = manager.url;
this._requestId = manager.request({
begin,
end,
onHeadersReceived: this._onHeadersReceived.bind(this),
onDone: this._onDone.bind(this),
onError: this._onError.bind(this),
onProgress: this._onProgress.bind(this)
});
this._requests = [];
this._queuedChunk = null;
this._done = false;
this._storedError = undefined;
this.onProgress = null;
this.onClosed = null;
}
_onHeadersReceived() {
const responseOrigin = getResponseOrigin(this._manager.getRequestXhr(this._requestId)?.responseURL);
if (responseOrigin !== this._manager._responseOrigin) {
this._storedError = new Error(`Expected range response-origin "${responseOrigin}" to match "${this._manager._responseOrigin}".`);
this._onError(0);
}
}
_close() {
this.onClosed?.(this);
}
_onDone(data) {
const chunk = data.chunk;
if (this._requests.length > 0) {
const requestCapability = this._requests.shift();
requestCapability.resolve({
value: chunk,
done: false
});
} else {
this._queuedChunk = chunk;
}
this._done = true;
for (const requestCapability of this._requests) {
requestCapability.resolve({
value: undefined,
done: true
});
}
this._requests.length = 0;
this._close();
}
_onError(status) {
this._storedError ??= createResponseError(status, this._url);
for (const requestCapability of this._requests) {
requestCapability.reject(this._storedError);
}
this._requests.length = 0;
this._queuedChunk = null;
}
_onProgress(evt) {
if (!this.isStreamingSupported) {
this.onProgress?.({
loaded: evt.loaded
});
}
}
get isStreamingSupported() {
return false;
}
async read() {
if (this._storedError) {
throw this._storedError;
}
if (this._queuedChunk !== null) {
const chunk = this._queuedChunk;
this._queuedChunk = null;
return {
value: chunk,
done: false
};
}
if (this._done) {
return {
value: undefined,
done: true
};
}
const requestCapability = Promise.withResolvers();
this._requests.push(requestCapability);
return requestCapability.promise;
}
cancel(reason) {
this._done = true;
for (const requestCapability of this._requests) {
requestCapability.resolve({
value: undefined,
done: true
});
}
this._requests.length = 0;
if (this._manager.isPendingRequest(this._requestId)) {
this._manager.abortRequest(this._requestId);
}
this._close();
}
}
;// ./src/display/node_stream.js
const urlRegex = /^[a-z][a-z0-9\-+.]+:/i;
function parseUrlOrPath(sourceUrl) {
if (urlRegex.test(sourceUrl)) {
return new URL(sourceUrl);
}
const url = process.getBuiltinModule("url");
return new URL(url.pathToFileURL(sourceUrl));
}
class PDFNodeStream {
constructor(source) {
this.source = source;
this.url = parseUrlOrPath(source.url);
assert(this.url.protocol === "file:", "PDFNodeStream only supports file:// URLs.");
this._fullRequestReader = null;
this._rangeRequestReaders = [];
}
get _progressiveDataLength() {
return this._fullRequestReader?._loaded ?? 0;
}
getFullReader() {
assert(!this._fullRequestReader, "PDFNodeStream.getFullReader can only be called once.");
this._fullRequestReader = new PDFNodeStreamFsFullReader(this);
return this._fullRequestReader;
}
getRangeReader(start, end) {
if (end <= this._progressiveDataLength) {
return null;
}
const rangeReader = new PDFNodeStreamFsRangeReader(this, start, end);
this._rangeRequestReaders.push(rangeReader);
return rangeReader;
}
cancelAllRequests(reason) {
this._fullRequestReader?.cancel(reason);
for (const reader of this._rangeRequestReaders.slice(0)) {
reader.cancel(reason);
}
}
}
class PDFNodeStreamFsFullReader {
constructor(stream) {
this._url = stream.url;
this._done = false;
this._storedError = null;
this.onProgress = null;
const source = stream.source;
this._contentLength = source.length;
this._loaded = 0;
this._filename = null;
this._disableRange = source.disableRange || false;
this._rangeChunkSize = source.rangeChunkSize;
if (!this._rangeChunkSize && !this._disableRange) {
this._disableRange = true;
}
this._isStreamingSupported = !source.disableStream;
this._isRangeSupported = !source.disableRange;
this._readableStream = null;
this._readCapability = Promise.withResolvers();
this._headersCapability = Promise.withResolvers();
const fs = process.getBuiltinModule("fs");
fs.promises.lstat(this._url).then(stat => {
this._contentLength = stat.size;
this._setReadableStream(fs.createReadStream(this._url));
this._headersCapability.resolve();
}, error => {
if (error.code === "ENOENT") {
error = createResponseError(0, this._url.href);
}
this._storedError = error;
this._headersCapability.reject(error);
});
}
get headersReady() {
return this._headersCapability.promise;
}
get filename() {
return this._filename;
}
get contentLength() {
return this._contentLength;
}
get isRangeSupported() {
return this._isRangeSupported;
}
get isStreamingSupported() {
return this._isStreamingSupported;
}
async read() {
await this._readCapability.promise;
if (this._done) {
return {
value: undefined,
done: true
};
}
if (this._storedError) {
throw this._storedError;
}
const chunk = this._readableStream.read();
if (chunk === null) {
this._readCapability = Promise.withResolvers();
return this.read();
}
this._loaded += chunk.length;
this.onProgress?.({
loaded: this._loaded,
total: this._contentLength
});
const buffer = new Uint8Array(chunk).buffer;
return {
value: buffer,
done: false
};
}
cancel(reason) {
if (!this._readableStream) {
this._error(reason);
return;
}
this._readableStream.destroy(reason);
}
_error(reason) {
this._storedError = reason;
this._readCapability.resolve();
}
_setReadableStream(readableStream) {
this._readableStream = readableStream;
readableStream.on("readable", () => {
this._readCapability.resolve();
});
readableStream.on("end", () => {
readableStream.destroy();
this._done = true;
this._readCapability.resolve();
});
readableStream.on("error", reason => {
this._error(reason);
});
if (!this._isStreamingSupported && this._isRangeSupported) {
this._error(new AbortException("streaming is disabled"));
}
if (this._storedError) {
this._readableStream.destroy(this._storedError);
}
}
}
class PDFNodeStreamFsRangeReader {
constructor(stream, start, end) {
this._url = stream.url;
this._done = false;
this._storedError = null;
this.onProgress = null;
this._loaded = 0;
this._readableStream = null;
this._readCapability = Promise.withResolvers();
const source = stream.source;
this._isStreamingSupported = !source.disableStream;
const fs = process.getBuiltinModule("fs");
this._setReadableStream(fs.createReadStream(this._url, {
start,
end: end - 1
}));
}
get isStreamingSupported() {
return this._isStreamingSupported;
}
async read() {
await this._readCapability.promise;
if (this._done) {
return {
value: undefined,
done: true
};
}
if (this._storedError) {
throw this._storedError;
}
const chunk = this._readableStream.read();
if (chunk === null) {
this._readCapability = Promise.withResolvers();
return this.read();
}
this._loaded += chunk.length;
this.onProgress?.({
loaded: this._loaded
});
const buffer = new Uint8Array(chunk).buffer;
return {
value: buffer,
done: false
};
}
cancel(reason) {
if (!this._readableStream) {
this._error(reason);
return;
}
this._readableStream.destroy(reason);
}
_error(reason) {
this._storedError = reason;
this._readCapability.resolve();
}
_setReadableStream(readableStream) {
this._readableStream = readableStream;
readableStream.on("readable", () => {
this._readCapability.resolve();
});
readableStream.on("end", () => {
readableStream.destroy();
this._done = true;
this._readCapability.resolve();
});
readableStream.on("error", reason => {
this._error(reason);
});
if (this._storedError) {
this._readableStream.destroy(this._storedError);
}
}
}
;// ./src/display/text_layer.js
const MAX_TEXT_DIVS_TO_RENDER = 100000;
const DEFAULT_FONT_SIZE = 30;
class TextLayer {
#capability = Promise.withResolvers();
#container = null;
#disableProcessItems = false;
#fontInspectorEnabled = !!globalThis.FontInspector?.enabled;
#lang = null;
#layoutTextParams = null;
#pageHeight = 0;
#pageWidth = 0;
#reader = null;
#rootContainer = null;
#rotation = 0;
#scale = 0;
#styleCache = Object.create(null);
#textContentItemsStr = [];
#textContentSource = null;
#textDivs = [];
#textDivProperties = new WeakMap();
#transform = null;
static #ascentCache = new Map();
static #canvasContexts = new Map();
static #canvasCtxFonts = new WeakMap();
static #minFontSize = null;
static #pendingTextLayers = new Set();
constructor({
textContentSource,
container,
viewport
}) {
if (textContentSource instanceof ReadableStream) {
this.#textContentSource = textContentSource;
} else if (typeof textContentSource === "object") {
this.#textContentSource = new ReadableStream({
start(controller) {
controller.enqueue(textContentSource);
controller.close();
}
});
} else {
throw new Error('No "textContentSource" parameter specified.');
}
this.#container = this.#rootContainer = container;
this.#scale = viewport.scale * OutputScale.pixelRatio;
this.#rotation = viewport.rotation;
this.#layoutTextParams = {
div: null,
properties: null,
ctx: null
};
const {
pageWidth,
pageHeight,
pageX,
pageY
} = viewport.rawDims;
this.#transform = [1, 0, 0, -1, -pageX, pageY + pageHeight];
this.#pageWidth = pageWidth;
this.#pageHeight = pageHeight;
TextLayer.#ensureMinFontSizeComputed();
setLayerDimensions(container, viewport);
this.#capability.promise.finally(() => {
TextLayer.#pendingTextLayers.delete(this);
this.#layoutTextParams = null;
this.#styleCache = null;
}).catch(() => {});
}
static get fontFamilyMap() {
const {
isWindows,
isFirefox
} = util_FeatureTest.platform;
return shadow(this, "fontFamilyMap", new Map([["sans-serif", `${isWindows && isFirefox ? "Calibri, " : ""}sans-serif`], ["monospace", `${isWindows && isFirefox ? "Lucida Console, " : ""}monospace`]]));
}
render() {
const pump = () => {
this.#reader.read().then(({
value,
done
}) => {
if (done) {
this.#capability.resolve();
return;
}
this.#lang ??= value.lang;
Object.assign(this.#styleCache, value.styles);
this.#processItems(value.items);
pump();
}, this.#capability.reject);
};
this.#reader = this.#textContentSource.getReader();
TextLayer.#pendingTextLayers.add(this);
pump();
return this.#capability.promise;
}
update({
viewport,
onBefore = null
}) {
const scale = viewport.scale * OutputScale.pixelRatio;
const rotation = viewport.rotation;
if (rotation !== this.#rotation) {
onBefore?.();
this.#rotation = rotation;
setLayerDimensions(this.#rootContainer, {
rotation
});
}
if (scale !== this.#scale) {
onBefore?.();
this.#scale = scale;
const params = {
div: null,
properties: null,
ctx: TextLayer.#getCtx(this.#lang)
};
for (const div of this.#textDivs) {
params.properties = this.#textDivProperties.get(div);
params.div = div;
this.#layout(params);
}
}
}
cancel() {
const abortEx = new AbortException("TextLayer task cancelled.");
this.#reader?.cancel(abortEx).catch(() => {});
this.#reader = null;
this.#capability.reject(abortEx);
}
get textDivs() {
return this.#textDivs;
}
get textContentItemsStr() {
return this.#textContentItemsStr;
}
#processItems(items) {
if (this.#disableProcessItems) {
return;
}
this.#layoutTextParams.ctx ??= TextLayer.#getCtx(this.#lang);
const textDivs = this.#textDivs,
textContentItemsStr = this.#textContentItemsStr;
for (const item of items) {
if (textDivs.length > MAX_TEXT_DIVS_TO_RENDER) {
warn("Ignoring additional textDivs for performance reasons.");
this.#disableProcessItems = true;
return;
}
if (item.str === undefined) {
if (item.type === "beginMarkedContentProps" || item.type === "beginMarkedContent") {
const parent = this.#container;
this.#container = document.createElement("span");
this.#container.classList.add("markedContent");
if (item.id !== null) {
this.#container.setAttribute("id", `${item.id}`);
}
parent.append(this.#container);
} else if (item.type === "endMarkedContent") {
this.#container = this.#container.parentNode;
}
continue;
}
textContentItemsStr.push(item.str);
this.#appendText(item);
}
}
#appendText(geom) {
const textDiv = document.createElement("span");
const textDivProperties = {
angle: 0,
canvasWidth: 0,
hasText: geom.str !== "",
hasEOL: geom.hasEOL,
fontSize: 0
};
this.#textDivs.push(textDiv);
const tx = Util.transform(this.#transform, geom.transform);
let angle = Math.atan2(tx[1], tx[0]);
const style = this.#styleCache[geom.fontName];
if (style.vertical) {
angle += Math.PI / 2;
}
let fontFamily = this.#fontInspectorEnabled && style.fontSubstitution || style.fontFamily;
fontFamily = TextLayer.fontFamilyMap.get(fontFamily) || fontFamily;
const fontHeight = Math.hypot(tx[2], tx[3]);
const fontAscent = fontHeight * TextLayer.#getAscent(fontFamily, style, this.#lang);
let left, top;
if (angle === 0) {
left = tx[4];
top = tx[5] - fontAscent;
} else {
left = tx[4] + fontAscent * Math.sin(angle);
top = tx[5] - fontAscent * Math.cos(angle);
}
const scaleFactorStr = "calc(var(--total-scale-factor) *";
const divStyle = textDiv.style;
if (this.#container === this.#rootContainer) {
divStyle.left = `${(100 * left / this.#pageWidth).toFixed(2)}%`;
divStyle.top = `${(100 * top / this.#pageHeight).toFixed(2)}%`;
} else {
divStyle.left = `${scaleFactorStr}${left.toFixed(2)}px)`;
divStyle.top = `${scaleFactorStr}${top.toFixed(2)}px)`;
}
divStyle.fontSize = `${scaleFactorStr}${(TextLayer.#minFontSize * fontHeight).toFixed(2)}px)`;
divStyle.fontFamily = fontFamily;
textDivProperties.fontSize = fontHeight;
textDiv.setAttribute("role", "presentation");
textDiv.textContent = geom.str;
textDiv.dir = geom.dir;
if (this.#fontInspectorEnabled) {
textDiv.dataset.fontName = style.fontSubstitutionLoadedName || geom.fontName;
}
if (angle !== 0) {
textDivProperties.angle = angle * (180 / Math.PI);
}
let shouldScaleText = false;
if (geom.str.length > 1) {
shouldScaleText = true;
} else if (geom.str !== " " && geom.transform[0] !== geom.transform[3]) {
const absScaleX = Math.abs(geom.transform[0]),
absScaleY = Math.abs(geom.transform[3]);
if (absScaleX !== absScaleY && Math.max(absScaleX, absScaleY) / Math.min(absScaleX, absScaleY) > 1.5) {
shouldScaleText = true;
}
}
if (shouldScaleText) {
textDivProperties.canvasWidth = style.vertical ? geom.height : geom.width;
}
this.#textDivProperties.set(textDiv, textDivProperties);
this.#layoutTextParams.div = textDiv;
this.#layoutTextParams.properties = textDivProperties;
this.#layout(this.#layoutTextParams);
if (textDivProperties.hasText) {
this.#container.append(textDiv);
}
if (textDivProperties.hasEOL) {
const br = document.createElement("br");
br.setAttribute("role", "presentation");
this.#container.append(br);
}
}
#layout(params) {
const {
div,
properties,
ctx
} = params;
const {
style
} = div;
let transform = "";
if (TextLayer.#minFontSize > 1) {
transform = `scale(${1 / TextLayer.#minFontSize})`;
}
if (properties.canvasWidth !== 0 && properties.hasText) {
const {
fontFamily
} = style;
const {
canvasWidth,
fontSize
} = properties;
TextLayer.#ensureCtxFont(ctx, fontSize * this.#scale, fontFamily);
const {
width
} = ctx.measureText(div.textContent);
if (width > 0) {
transform = `scaleX(${canvasWidth * this.#scale / width}) ${transform}`;
}
}
if (properties.angle !== 0) {
transform = `rotate(${properties.angle}deg) ${transform}`;
}
if (transform.length > 0) {
style.transform = transform;
}
}
static cleanup() {
if (this.#pendingTextLayers.size > 0) {
return;
}
this.#ascentCache.clear();
for (const {
canvas
} of this.#canvasContexts.values()) {
canvas.remove();
}
this.#canvasContexts.clear();
}
static #getCtx(lang = null) {
let ctx = this.#canvasContexts.get(lang ||= "");
if (!ctx) {
const canvas = document.createElement("canvas");
canvas.className = "hiddenCanvasElement";
canvas.lang = lang;
document.body.append(canvas);
ctx = canvas.getContext("2d", {
alpha: false,
willReadFrequently: true
});
this.#canvasContexts.set(lang, ctx);
this.#canvasCtxFonts.set(ctx, {
size: 0,
family: ""
});
}
return ctx;
}
static #ensureCtxFont(ctx, size, family) {
const cached = this.#canvasCtxFonts.get(ctx);
if (size === cached.size && family === cached.family) {
return;
}
ctx.font = `${size}px ${family}`;
cached.size = size;
cached.family = family;
}
static #ensureMinFontSizeComputed() {
if (this.#minFontSize !== null) {
return;
}
const div = document.createElement("div");
div.style.opacity = 0;
div.style.lineHeight = 1;
div.style.fontSize = "1px";
div.style.position = "absolute";
div.textContent = "X";
document.body.append(div);
this.#minFontSize = div.getBoundingClientRect().height;
div.remove();
}
static #getAscent(fontFamily, style, lang) {
const cachedAscent = this.#ascentCache.get(fontFamily);
if (cachedAscent) {
return cachedAscent;
}
const ctx = this.#getCtx(lang);
ctx.canvas.width = ctx.canvas.height = DEFAULT_FONT_SIZE;
this.#ensureCtxFont(ctx, DEFAULT_FONT_SIZE, fontFamily);
const metrics = ctx.measureText("");
const ascent = metrics.fontBoundingBoxAscent;
const descent = Math.abs(metrics.fontBoundingBoxDescent);
ctx.canvas.width = ctx.canvas.height = 0;
let ratio = 0.8;
if (ascent) {
ratio = ascent / (ascent + descent);
} else {
if (util_FeatureTest.platform.isFirefox) {
warn("Enable the `dom.textMetrics.fontBoundingBox.enabled` preference " + "in `about:config` to improve TextLayer rendering.");
}
if (style.ascent) {
ratio = style.ascent;
} else if (style.descent) {
ratio = 1 + style.descent;
}
}
this.#ascentCache.set(fontFamily, ratio);
return ratio;
}
}
;// ./src/display/xfa_text.js
class XfaText {
static textContent(xfa) {
const items = [];
const output = {
items,
styles: Object.create(null)
};
function walk(node) {
if (!node) {
return;
}
let str = null;
const name = node.name;
if (name === "#text") {
str = node.value;
} else if (!XfaText.shouldBuildText(name)) {
return;
} else if (node?.attributes?.textContent) {
str = node.attributes.textContent;
} else if (node.value) {
str = node.value;
}
if (str !== null) {
items.push({
str
});
}
if (!node.children) {
return;
}
for (const child of node.children) {
walk(child);
}
}
walk(xfa);
return output;
}
static shouldBuildText(name) {
return !(name === "textarea" || name === "input" || name === "option" || name === "select");
}
}
;// ./src/display/api.js
const DEFAULT_RANGE_CHUNK_SIZE = 65536;
const RENDERING_CANCELLED_TIMEOUT = 100;
function getDocument(src = {}) {
if (typeof src === "string" || src instanceof URL) {
src = {
url: src
};
} else if (src instanceof ArrayBuffer || ArrayBuffer.isView(src)) {
src = {
data: src
};
}
const task = new PDFDocumentLoadingTask();
const {
docId
} = task;
const url = src.url ? getUrlProp(src.url) : null;
const data = src.data ? getDataProp(src.data) : null;
const httpHeaders = src.httpHeaders || null;
const withCredentials = src.withCredentials === true;
const password = src.password ?? null;
const rangeTransport = src.range instanceof PDFDataRangeTransport ? src.range : null;
const rangeChunkSize = Number.isInteger(src.rangeChunkSize) && src.rangeChunkSize > 0 ? src.rangeChunkSize : DEFAULT_RANGE_CHUNK_SIZE;
let worker = src.worker instanceof PDFWorker ? src.worker : null;
const verbosity = src.verbosity;
const docBaseUrl = typeof src.docBaseUrl === "string" && !isDataScheme(src.docBaseUrl) ? src.docBaseUrl : null;
const cMapUrl = getFactoryUrlProp(src.cMapUrl);
const cMapPacked = src.cMapPacked !== false;
const CMapReaderFactory = src.CMapReaderFactory || (isNodeJS ? NodeCMapReaderFactory : DOMCMapReaderFactory);
const iccUrl = getFactoryUrlProp(src.iccUrl);
const standardFontDataUrl = getFactoryUrlProp(src.standardFontDataUrl);
const StandardFontDataFactory = src.StandardFontDataFactory || (isNodeJS ? NodeStandardFontDataFactory : DOMStandardFontDataFactory);
const wasmUrl = getFactoryUrlProp(src.wasmUrl);
const WasmFactory = src.WasmFactory || (isNodeJS ? NodeWasmFactory : DOMWasmFactory);
const ignoreErrors = src.stopAtErrors !== true;
const maxImageSize = Number.isInteger(src.maxImageSize) && src.maxImageSize > -1 ? src.maxImageSize : -1;
const isEvalSupported = src.isEvalSupported !== false;
const isOffscreenCanvasSupported = typeof src.isOffscreenCanvasSupported === "boolean" ? src.isOffscreenCanvasSupported : !isNodeJS;
const isImageDecoderSupported = typeof src.isImageDecoderSupported === "boolean" ? src.isImageDecoderSupported : !isNodeJS && (util_FeatureTest.platform.isFirefox || !globalThis.chrome);
const canvasMaxAreaInBytes = Number.isInteger(src.canvasMaxAreaInBytes) ? src.canvasMaxAreaInBytes : -1;
const disableFontFace = typeof src.disableFontFace === "boolean" ? src.disableFontFace : isNodeJS;
const fontExtraProperties = src.fontExtraProperties === true;
const enableXfa = src.enableXfa === true;
const ownerDocument = src.ownerDocument || globalThis.document;
const disableRange = src.disableRange === true;
const disableStream = src.disableStream === true;
const disableAutoFetch = src.disableAutoFetch === true;
const pdfBug = src.pdfBug === true;
const CanvasFactory = src.CanvasFactory || (isNodeJS ? NodeCanvasFactory : DOMCanvasFactory);
const FilterFactory = src.FilterFactory || (isNodeJS ? NodeFilterFactory : DOMFilterFactory);
const enableHWA = src.enableHWA === true;
const useWasm = src.useWasm !== false;
const length = rangeTransport ? rangeTransport.length : src.length ?? NaN;
const useSystemFonts = typeof src.useSystemFonts === "boolean" ? src.useSystemFonts : !isNodeJS && !disableFontFace;
const useWorkerFetch = typeof src.useWorkerFetch === "boolean" ? src.useWorkerFetch : !!(CMapReaderFactory === DOMCMapReaderFactory && StandardFontDataFactory === DOMStandardFontDataFactory && WasmFactory === DOMWasmFactory && cMapUrl && standardFontDataUrl && wasmUrl && isValidFetchUrl(cMapUrl, document.baseURI) && isValidFetchUrl(standardFontDataUrl, document.baseURI) && isValidFetchUrl(wasmUrl, document.baseURI));
const styleElement = null;
setVerbosityLevel(verbosity);
const transportFactory = {
canvasFactory: new CanvasFactory({
ownerDocument,
enableHWA
}),
filterFactory: new FilterFactory({
docId,
ownerDocument
}),
cMapReaderFactory: useWorkerFetch ? null : new CMapReaderFactory({
baseUrl: cMapUrl,
isCompressed: cMapPacked
}),
standardFontDataFactory: useWorkerFetch ? null : new StandardFontDataFactory({
baseUrl: standardFontDataUrl
}),
wasmFactory: useWorkerFetch ? null : new WasmFactory({
baseUrl: wasmUrl
})
};
if (!worker) {
const workerParams = {
verbosity,
port: GlobalWorkerOptions.workerPort
};
worker = workerParams.port ? PDFWorker.fromPort(workerParams) : new PDFWorker(workerParams);
task._worker = worker;
}
const docParams = {
docId,
apiVersion: "5.1.91",
data,
password,
disableAutoFetch,
rangeChunkSize,
length,
docBaseUrl,
enableXfa,
evaluatorOptions: {
maxImageSize,
disableFontFace,
ignoreErrors,
isEvalSupported,
isOffscreenCanvasSupported,
isImageDecoderSupported,
canvasMaxAreaInBytes,
fontExtraProperties,
useSystemFonts,
useWasm,
useWorkerFetch,
cMapUrl,
iccUrl,
standardFontDataUrl,
wasmUrl
}
};
const transportParams = {
ownerDocument,
pdfBug,
styleElement,
loadingParams: {
disableAutoFetch,
enableXfa
}
};
worker.promise.then(function () {
if (task.destroyed) {
throw new Error("Loading aborted");
}
if (worker.destroyed) {
throw new Error("Worker was destroyed");
}
const workerIdPromise = worker.messageHandler.sendWithPromise("GetDocRequest", docParams, data ? [data.buffer] : null);
let networkStream;
if (rangeTransport) {
networkStream = new PDFDataTransportStream(rangeTransport, {
disableRange,
disableStream
});
} else if (!data) {
if (!url) {
throw new Error("getDocument - no `url` parameter provided.");
}
let NetworkStream;
if (isNodeJS) {
if (isValidFetchUrl(url)) {
if (typeof fetch === "undefined" || typeof Response === "undefined" || !("body" in Response.prototype)) {
throw new Error("getDocument - the Fetch API was disabled in Node.js, see `--no-experimental-fetch`.");
}
NetworkStream = PDFFetchStream;
} else {
NetworkStream = PDFNodeStream;
}
} else {
NetworkStream = isValidFetchUrl(url) ? PDFFetchStream : PDFNetworkStream;
}
networkStream = new NetworkStream({
url,
length,
httpHeaders,
withCredentials,
rangeChunkSize,
disableRange,
disableStream
});
}
return workerIdPromise.then(workerId => {
if (task.destroyed) {
throw new Error("Loading aborted");
}
if (worker.destroyed) {
throw new Error("Worker was destroyed");
}
const messageHandler = new MessageHandler(docId, workerId, worker.port);
const transport = new WorkerTransport(messageHandler, task, networkStream, transportParams, transportFactory);
task._transport = transport;
messageHandler.send("Ready", null);
});
}).catch(task._capability.reject);
return task;
}
function getUrlProp(val) {
if (val instanceof URL) {
return val.href;
}
if (typeof val === "string") {
if (isNodeJS) {
return val;
}
const url = URL.parse(val, window.location);
if (url) {
return url.href;
}
}
throw new Error("Invalid PDF url data: " + "either string or URL-object is expected in the url property.");
}
function getDataProp(val) {
if (isNodeJS && typeof Buffer !== "undefined" && val instanceof Buffer) {
throw new Error("Please provide binary data as `Uint8Array`, rather than `Buffer`.");
}
if (val instanceof Uint8Array && val.byteLength === val.buffer.byteLength) {
return val;
}
if (typeof val === "string") {
return stringToBytes(val);
}
if (val instanceof ArrayBuffer || ArrayBuffer.isView(val) || typeof val === "object" && !isNaN(val?.length)) {
return new Uint8Array(val);
}
throw new Error("Invalid PDF binary data: either TypedArray, " + "string, or array-like object is expected in the data property.");
}
function getFactoryUrlProp(val) {
if (typeof val !== "string") {
return null;
}
if (val.endsWith("/")) {
return val;
}
throw new Error(`Invalid factory url: "${val}" must include trailing slash.`);
}
const isRefProxy = v => typeof v === "object" && Number.isInteger(v?.num) && v.num >= 0 && Number.isInteger(v?.gen) && v.gen >= 0;
const isNameProxy = v => typeof v === "object" && typeof v?.name === "string";
const isValidExplicitDest = _isValidExplicitDest.bind(null, isRefProxy, isNameProxy);
class PDFDocumentLoadingTask {
static #docId = 0;
_capability = Promise.withResolvers();
_transport = null;
_worker = null;
docId = `d${PDFDocumentLoadingTask.#docId++}`;
destroyed = false;
onPassword = null;
onProgress = null;
get promise() {
return this._capability.promise;
}
async destroy() {
this.destroyed = true;
try {
if (this._worker?.port) {
this._worker._pendingDestroy = true;
}
await this._transport?.destroy();
} catch (ex) {
if (this._worker?.port) {
delete this._worker._pendingDestroy;
}
throw ex;
}
this._transport = null;
this._worker?.destroy();
this._worker = null;
}
async getData() {
return this._transport.getData();
}
}
class PDFDataRangeTransport {
constructor(length, initialData, progressiveDone = false, contentDispositionFilename = null) {
this.length = length;
this.initialData = initialData;
this.progressiveDone = progressiveDone;
this.contentDispositionFilename = contentDispositionFilename;
this._rangeListeners = [];
this._progressListeners = [];
this._progressiveReadListeners = [];
this._progressiveDoneListeners = [];
this._readyCapability = Promise.withResolvers();
}
addRangeListener(listener) {
this._rangeListeners.push(listener);
}
addProgressListener(listener) {
this._progressListeners.push(listener);
}
addProgressiveReadListener(listener) {
this._progressiveReadListeners.push(listener);
}
addProgressiveDoneListener(listener) {
this._progressiveDoneListeners.push(listener);
}
onDataRange(begin, chunk) {
for (const listener of this._rangeListeners) {
listener(begin, chunk);
}
}
onDataProgress(loaded, total) {
this._readyCapability.promise.then(() => {
for (const listener of this._progressListeners) {
listener(loaded, total);
}
});
}
onDataProgressiveRead(chunk) {
this._readyCapability.promise.then(() => {
for (const listener of this._progressiveReadListeners) {
listener(chunk);
}
});
}
onDataProgressiveDone() {
this._readyCapability.promise.then(() => {
for (const listener of this._progressiveDoneListeners) {
listener();
}
});
}
transportReady() {
this._readyCapability.resolve();
}
requestDataRange(begin, end) {
unreachable("Abstract method PDFDataRangeTransport.requestDataRange");
}
abort() {}
}
class PDFDocumentProxy {
constructor(pdfInfo, transport) {
this._pdfInfo = pdfInfo;
this._transport = transport;
}
get annotationStorage() {
return this._transport.annotationStorage;
}
get canvasFactory() {
return this._transport.canvasFactory;
}
get filterFactory() {
return this._transport.filterFactory;
}
get numPages() {
return this._pdfInfo.numPages;
}
get fingerprints() {
return this._pdfInfo.fingerprints;
}
get isPureXfa() {
return shadow(this, "isPureXfa", !!this._transport._htmlForXfa);
}
get allXfaHtml() {
return this._transport._htmlForXfa;
}
getPage(pageNumber) {
return this._transport.getPage(pageNumber);
}
getPageIndex(ref) {
return this._transport.getPageIndex(ref);
}
getDestinations() {
return this._transport.getDestinations();
}
getDestination(id) {
return this._transport.getDestination(id);
}
getPageLabels() {
return this._transport.getPageLabels();
}
getPageLayout() {
return this._transport.getPageLayout();
}
getPageMode() {
return this._transport.getPageMode();
}
getViewerPreferences() {
return this._transport.getViewerPreferences();
}
getOpenAction() {
return this._transport.getOpenAction();
}
getAttachments() {
return this._transport.getAttachments();
}
getJSActions() {
return this._transport.getDocJSActions();
}
getOutline() {
return this._transport.getOutline();
}
getOptionalContentConfig({
intent = "display"
} = {}) {
const {
renderingIntent
} = this._transport.getRenderingIntent(intent);
return this._transport.getOptionalContentConfig(renderingIntent);
}
getPermissions() {
return this._transport.getPermissions();
}
getMetadata() {
return this._transport.getMetadata();
}
getMarkInfo() {
return this._transport.getMarkInfo();
}
getData() {
return this._transport.getData();
}
saveDocument() {
return this._transport.saveDocument();
}
getDownloadInfo() {
return this._transport.downloadInfoCapability.promise;
}
cleanup(keepLoadedFonts = false) {
return this._transport.startCleanup(keepLoadedFonts || this.isPureXfa);
}
destroy() {
return this.loadingTask.destroy();
}
cachedPageNumber(ref) {
return this._transport.cachedPageNumber(ref);
}
get loadingParams() {
return this._transport.loadingParams;
}
get loadingTask() {
return this._transport.loadingTask;
}
getFieldObjects() {
return this._transport.getFieldObjects();
}
hasJSActions() {
return this._transport.hasJSActions();
}
getCalculationOrderIds() {
return this._transport.getCalculationOrderIds();
}
}
class PDFPageProxy {
#pendingCleanup = false;
constructor(pageIndex, pageInfo, transport, pdfBug = false) {
this._pageIndex = pageIndex;
this._pageInfo = pageInfo;
this._transport = transport;
this._stats = pdfBug ? new StatTimer() : null;
this._pdfBug = pdfBug;
this.commonObjs = transport.commonObjs;
this.objs = new PDFObjects();
this._intentStates = new Map();
this.destroyed = false;
}
get pageNumber() {
return this._pageIndex + 1;
}
get rotate() {
return this._pageInfo.rotate;
}
get ref() {
return this._pageInfo.ref;
}
get userUnit() {
return this._pageInfo.userUnit;
}
get view() {
return this._pageInfo.view;
}
getViewport({
scale,
rotation = this.rotate,
offsetX = 0,
offsetY = 0,
dontFlip = false
} = {}) {
return new PageViewport({
viewBox: this.view,
userUnit: this.userUnit,
scale,
rotation,
offsetX,
offsetY,
dontFlip
});
}
getAnnotations({
intent = "display"
} = {}) {
const {
renderingIntent
} = this._transport.getRenderingIntent(intent);
return this._transport.getAnnotations(this._pageIndex, renderingIntent);
}
getJSActions() {
return this._transport.getPageJSActions(this._pageIndex);
}
get filterFactory() {
return this._transport.filterFactory;
}
get isPureXfa() {
return shadow(this, "isPureXfa", !!this._transport._htmlForXfa);
}
async getXfa() {
return this._transport._htmlForXfa?.children[this._pageIndex] || null;
}
render({
canvasContext,
viewport,
intent = "display",
annotationMode = AnnotationMode.ENABLE,
transform = null,
background = null,
optionalContentConfigPromise = null,
annotationCanvasMap = null,
pageColors = null,
printAnnotationStorage = null,
isEditing = false
}) {
this._stats?.time("Overall");
const intentArgs = this._transport.getRenderingIntent(intent, annotationMode, printAnnotationStorage, isEditing);
const {
renderingIntent,
cacheKey
} = intentArgs;
this.#pendingCleanup = false;
optionalContentConfigPromise ||= this._transport.getOptionalContentConfig(renderingIntent);
let intentState = this._intentStates.get(cacheKey);
if (!intentState) {
intentState = Object.create(null);
this._intentStates.set(cacheKey, intentState);
}
if (intentState.streamReaderCancelTimeout) {
clearTimeout(intentState.streamReaderCancelTimeout);
intentState.streamReaderCancelTimeout = null;
}
const intentPrint = !!(renderingIntent & RenderingIntentFlag.PRINT);
if (!intentState.displayReadyCapability) {
intentState.displayReadyCapability = Promise.withResolvers();
intentState.operatorList = {
fnArray: [],
argsArray: [],
lastChunk: false,
separateAnnots: null
};
this._stats?.time("Page Request");
this._pumpOperatorList(intentArgs);
}
const complete = error => {
intentState.renderTasks.delete(internalRenderTask);
if (intentPrint) {
this.#pendingCleanup = true;
}
this.#tryCleanup();
if (error) {
internalRenderTask.capability.reject(error);
this._abortOperatorList({
intentState,
reason: error instanceof Error ? error : new Error(error)
});
} else {
internalRenderTask.capability.resolve();
}
if (this._stats) {
this._stats.timeEnd("Rendering");
this._stats.timeEnd("Overall");
if (globalThis.Stats?.enabled) {
globalThis.Stats.add(this.pageNumber, this._stats);
}
}
};
const internalRenderTask = new InternalRenderTask({
callback: complete,
params: {
canvasContext,
viewport,
transform,
background
},
objs: this.objs,
commonObjs: this.commonObjs,
annotationCanvasMap,
operatorList: intentState.operatorList,
pageIndex: this._pageIndex,
canvasFactory: this._transport.canvasFactory,
filterFactory: this._transport.filterFactory,
useRequestAnimationFrame: !intentPrint,
pdfBug: this._pdfBug,
pageColors
});
(intentState.renderTasks ||= new Set()).add(internalRenderTask);
const renderTask = internalRenderTask.task;
Promise.all([intentState.displayReadyCapability.promise, optionalContentConfigPromise]).then(([transparency, optionalContentConfig]) => {
if (this.destroyed) {
complete();
return;
}
this._stats?.time("Rendering");
if (!(optionalContentConfig.renderingIntent & renderingIntent)) {
throw new Error("Must use the same `intent`-argument when calling the `PDFPageProxy.render` " + "and `PDFDocumentProxy.getOptionalContentConfig` methods.");
}
internalRenderTask.initializeGraphics({
transparency,
optionalContentConfig
});
internalRenderTask.operatorListChanged();
}).catch(complete);
return renderTask;
}
getOperatorList({
intent = "display",
annotationMode = AnnotationMode.ENABLE,
printAnnotationStorage = null,
isEditing = false
} = {}) {
function operatorListChanged() {
if (intentState.operatorList.lastChunk) {
intentState.opListReadCapability.resolve(intentState.operatorList);
intentState.renderTasks.delete(opListTask);
}
}
const intentArgs = this._transport.getRenderingIntent(intent, annotationMode, printAnnotationStorage, isEditing, true);
let intentState = this._intentStates.get(intentArgs.cacheKey);
if (!intentState) {
intentState = Object.create(null);
this._intentStates.set(intentArgs.cacheKey, intentState);
}
let opListTask;
if (!intentState.opListReadCapability) {
opListTask = Object.create(null);
opListTask.operatorListChanged = operatorListChanged;
intentState.opListReadCapability = Promise.withResolvers();
(intentState.renderTasks ||= new Set()).add(opListTask);
intentState.operatorList = {
fnArray: [],
argsArray: [],
lastChunk: false,
separateAnnots: null
};
this._stats?.time("Page Request");
this._pumpOperatorList(intentArgs);
}
return intentState.opListReadCapability.promise;
}
streamTextContent({
includeMarkedContent = false,
disableNormalization = false
} = {}) {
const TEXT_CONTENT_CHUNK_SIZE = 100;
return this._transport.messageHandler.sendWithStream("GetTextContent", {
pageIndex: this._pageIndex,
includeMarkedContent: includeMarkedContent === true,
disableNormalization: disableNormalization === true
}, {
highWaterMark: TEXT_CONTENT_CHUNK_SIZE,
size(textContent) {
return textContent.items.length;
}
});
}
getTextContent(params = {}) {
if (this._transport._htmlForXfa) {
return this.getXfa().then(xfa => XfaText.textContent(xfa));
}
const readableStream = this.streamTextContent(params);
return new Promise(function (resolve, reject) {
function pump() {
reader.read().then(function ({
value,
done
}) {
if (done) {
resolve(textContent);
return;
}
textContent.lang ??= value.lang;
Object.assign(textContent.styles, value.styles);
textContent.items.push(...value.items);
pump();
}, reject);
}
const reader = readableStream.getReader();
const textContent = {
items: [],
styles: Object.create(null),
lang: null
};
pump();
});
}
getStructTree() {
return this._transport.getStructTree(this._pageIndex);
}
_destroy() {
this.destroyed = true;
const waitOn = [];
for (const intentState of this._intentStates.values()) {
this._abortOperatorList({
intentState,
reason: new Error("Page was destroyed."),
force: true
});
if (intentState.opListReadCapability) {
continue;
}
for (const internalRenderTask of intentState.renderTasks) {
waitOn.push(internalRenderTask.completed);
internalRenderTask.cancel();
}
}
this.objs.clear();
this.#pendingCleanup = false;
return Promise.all(waitOn);
}
cleanup(resetStats = false) {
this.#pendingCleanup = true;
const success = this.#tryCleanup();
if (resetStats && success) {
this._stats &&= new StatTimer();
}
return success;
}
#tryCleanup() {
if (!this.#pendingCleanup || this.destroyed) {
return false;
}
for (const {
renderTasks,
operatorList
} of this._intentStates.values()) {
if (renderTasks.size > 0 || !operatorList.lastChunk) {
return false;
}
}
this._intentStates.clear();
this.objs.clear();
this.#pendingCleanup = false;
return true;
}
_startRenderPage(transparency, cacheKey) {
const intentState = this._intentStates.get(cacheKey);
if (!intentState) {
return;
}
this._stats?.timeEnd("Page Request");
intentState.displayReadyCapability?.resolve(transparency);
}
_renderPageChunk(operatorListChunk, intentState) {
for (let i = 0, ii = operatorListChunk.length; i < ii; i++) {
intentState.operatorList.fnArray.push(operatorListChunk.fnArray[i]);
intentState.operatorList.argsArray.push(operatorListChunk.argsArray[i]);
}
intentState.operatorList.lastChunk = operatorListChunk.lastChunk;
intentState.operatorList.separateAnnots = operatorListChunk.separateAnnots;
for (const internalRenderTask of intentState.renderTasks) {
internalRenderTask.operatorListChanged();
}
if (operatorListChunk.lastChunk) {
this.#tryCleanup();
}
}
_pumpOperatorList({
renderingIntent,
cacheKey,
annotationStorageSerializable,
modifiedIds
}) {
const {
map,
transfer
} = annotationStorageSerializable;
const readableStream = this._transport.messageHandler.sendWithStream("GetOperatorList", {
pageIndex: this._pageIndex,
intent: renderingIntent,
cacheKey,
annotationStorage: map,
modifiedIds
}, transfer);
const reader = readableStream.getReader();
const intentState = this._intentStates.get(cacheKey);
intentState.streamReader = reader;
const pump = () => {
reader.read().then(({
value,
done
}) => {
if (done) {
intentState.streamReader = null;
return;
}
if (this._transport.destroyed) {
return;
}
this._renderPageChunk(value, intentState);
pump();
}, reason => {
intentState.streamReader = null;
if (this._transport.destroyed) {
return;
}
if (intentState.operatorList) {
intentState.operatorList.lastChunk = true;
for (const internalRenderTask of intentState.renderTasks) {
internalRenderTask.operatorListChanged();
}
this.#tryCleanup();
}
if (intentState.displayReadyCapability) {
intentState.displayReadyCapability.reject(reason);
} else if (intentState.opListReadCapability) {
intentState.opListReadCapability.reject(reason);
} else {
throw reason;
}
});
};
pump();
}
_abortOperatorList({
intentState,
reason,
force = false
}) {
if (!intentState.streamReader) {
return;
}
if (intentState.streamReaderCancelTimeout) {
clearTimeout(intentState.streamReaderCancelTimeout);
intentState.streamReaderCancelTimeout = null;
}
if (!force) {
if (intentState.renderTasks.size > 0) {
return;
}
if (reason instanceof RenderingCancelledException) {
let delay = RENDERING_CANCELLED_TIMEOUT;
if (reason.extraDelay > 0 && reason.extraDelay < 1000) {
delay += reason.extraDelay;
}
intentState.streamReaderCancelTimeout = setTimeout(() => {
intentState.streamReaderCancelTimeout = null;
this._abortOperatorList({
intentState,
reason,
force: true
});
}, delay);
return;
}
}
intentState.streamReader.cancel(new AbortException(reason.message)).catch(() => {});
intentState.streamReader = null;
if (this._transport.destroyed) {
return;
}
for (const [curCacheKey, curIntentState] of this._intentStates) {
if (curIntentState === intentState) {
this._intentStates.delete(curCacheKey);
break;
}
}
this.cleanup();
}
get stats() {
return this._stats;
}
}
class LoopbackPort {
#listeners = new Map();
#deferred = Promise.resolve();
postMessage(obj, transfer) {
const event = {
data: structuredClone(obj, transfer ? {
transfer
} : null)
};
this.#deferred.then(() => {
for (const [listener] of this.#listeners) {
listener.call(this, event);
}
});
}
addEventListener(name, listener, options = null) {
let rmAbort = null;
if (options?.signal instanceof AbortSignal) {
const {
signal
} = options;
if (signal.aborted) {
warn("LoopbackPort - cannot use an `aborted` signal.");
return;
}
const onAbort = () => this.removeEventListener(name, listener);
rmAbort = () => signal.removeEventListener("abort", onAbort);
signal.addEventListener("abort", onAbort);
}
this.#listeners.set(listener, rmAbort);
}
removeEventListener(name, listener) {
const rmAbort = this.#listeners.get(listener);
rmAbort?.();
this.#listeners.delete(listener);
}
terminate() {
for (const [, rmAbort] of this.#listeners) {
rmAbort?.();
}
this.#listeners.clear();
}
}
class PDFWorker {
static #fakeWorkerId = 0;
static #isWorkerDisabled = false;
static #workerPorts;
static {
if (isNodeJS) {
this.#isWorkerDisabled = true;
GlobalWorkerOptions.workerSrc ||= "./pdf.worker.mjs";
}
this._isSameOrigin = (baseUrl, otherUrl) => {
const base = URL.parse(baseUrl);
if (!base?.origin || base.origin === "null") {
return false;
}
const other = new URL(otherUrl, base);
return base.origin === other.origin;
};
this._createCDNWrapper = url => {
const wrapper = `await import("${url}");`;
return URL.createObjectURL(new Blob([wrapper], {
type: "text/javascript"
}));
};
}
constructor({
name = null,
port = null,
verbosity = getVerbosityLevel()
} = {}) {
this.name = name;
this.destroyed = false;
this.verbosity = verbosity;
this._readyCapability = Promise.withResolvers();
this._port = null;
this._webWorker = null;
this._messageHandler = null;
if (port) {
if (PDFWorker.#workerPorts?.has(port)) {
throw new Error("Cannot use more than one PDFWorker per port.");
}
(PDFWorker.#workerPorts ||= new WeakMap()).set(port, this);
this._initializeFromPort(port);
return;
}
this._initialize();
}
get promise() {
return this._readyCapability.promise;
}
#resolve() {
this._readyCapability.resolve();
this._messageHandler.send("configure", {
verbosity: this.verbosity
});
}
get port() {
return this._port;
}
get messageHandler() {
return this._messageHandler;
}
_initializeFromPort(port) {
this._port = port;
this._messageHandler = new MessageHandler("main", "worker", port);
this._messageHandler.on("ready", function () {});
this.#resolve();
}
_initialize() {
if (PDFWorker.#isWorkerDisabled || PDFWorker.#mainThreadWorkerMessageHandler) {
this._setupFakeWorker();
return;
}
let {
workerSrc
} = PDFWorker;
try {
if (!PDFWorker._isSameOrigin(window.location, workerSrc)) {
workerSrc = PDFWorker._createCDNWrapper(new URL(workerSrc, window.location).href);
}
const worker = new Worker(workerSrc, {
type: "module"
});
const messageHandler = new MessageHandler("main", "worker", worker);
const terminateEarly = () => {
ac.abort();
messageHandler.destroy();
worker.terminate();
if (this.destroyed) {
this._readyCapability.reject(new Error("Worker was destroyed"));
} else {
this._setupFakeWorker();
}
};
const ac = new AbortController();
worker.addEventListener("error", () => {
if (!this._webWorker) {
terminateEarly();
}
}, {
signal: ac.signal
});
messageHandler.on("test", data => {
ac.abort();
if (this.destroyed || !data) {
terminateEarly();
return;
}
this._messageHandler = messageHandler;
this._port = worker;
this._webWorker = worker;
this.#resolve();
});
messageHandler.on("ready", data => {
ac.abort();
if (this.destroyed) {
terminateEarly();
return;
}
try {
sendTest();
} catch {
this._setupFakeWorker();
}
});
const sendTest = () => {
const testObj = new Uint8Array();
messageHandler.send("test", testObj, [testObj.buffer]);
};
sendTest();
return;
} catch {
info("The worker has been disabled.");
}
this._setupFakeWorker();
}
_setupFakeWorker() {
if (!PDFWorker.#isWorkerDisabled) {
warn("Setting up fake worker.");
PDFWorker.#isWorkerDisabled = true;
}
PDFWorker._setupFakeWorkerGlobal.then(WorkerMessageHandler => {
if (this.destroyed) {
this._readyCapability.reject(new Error("Worker was destroyed"));
return;
}
const port = new LoopbackPort();
this._port = port;
const id = `fake${PDFWorker.#fakeWorkerId++}`;
const workerHandler = new MessageHandler(id + "_worker", id, port);
WorkerMessageHandler.setup(workerHandler, port);
this._messageHandler = new MessageHandler(id, id + "_worker", port);
this.#resolve();
}).catch(reason => {
this._readyCapability.reject(new Error(`Setting up fake worker failed: "${reason.message}".`));
});
}
destroy() {
this.destroyed = true;
this._webWorker?.terminate();
this._webWorker = null;
PDFWorker.#workerPorts?.delete(this._port);
this._port = null;
this._messageHandler?.destroy();
this._messageHandler = null;
}
static fromPort(params) {
if (!params?.port) {
throw new Error("PDFWorker.fromPort - invalid method signature.");
}
const cachedPort = this.#workerPorts?.get(params.port);
if (cachedPort) {
if (cachedPort._pendingDestroy) {
throw new Error("PDFWorker.fromPort - the worker is being destroyed.\n" + "Please remember to await `PDFDocumentLoadingTask.destroy()`-calls.");
}
return cachedPort;
}
return new PDFWorker(params);
}
static get workerSrc() {
if (GlobalWorkerOptions.workerSrc) {
return GlobalWorkerOptions.workerSrc;
}
throw new Error('No "GlobalWorkerOptions.workerSrc" specified.');
}
static get #mainThreadWorkerMessageHandler() {
try {
return globalThis.pdfjsWorker?.WorkerMessageHandler || null;
} catch {
return null;
}
}
static get _setupFakeWorkerGlobal() {
const loader = async () => {
if (this.#mainThreadWorkerMessageHandler) {
return this.#mainThreadWorkerMessageHandler;
}
const worker = await import(
/*webpackIgnore: true*/
/*@vite-ignore*/
this.workerSrc);
return worker.WorkerMessageHandler;
};
return shadow(this, "_setupFakeWorkerGlobal", loader());
}
}
class WorkerTransport {
#methodPromises = new Map();
#pageCache = new Map();
#pagePromises = new Map();
#pageRefCache = new Map();
#passwordCapability = null;
constructor(messageHandler, loadingTask, networkStream, params, factory) {
this.messageHandler = messageHandler;
this.loadingTask = loadingTask;
this.commonObjs = new PDFObjects();
this.fontLoader = new FontLoader({
ownerDocument: params.ownerDocument,
styleElement: params.styleElement
});
this.loadingParams = params.loadingParams;
this._params = params;
this.canvasFactory = factory.canvasFactory;
this.filterFactory = factory.filterFactory;
this.cMapReaderFactory = factory.cMapReaderFactory;
this.standardFontDataFactory = factory.standardFontDataFactory;
this.wasmFactory = factory.wasmFactory;
this.destroyed = false;
this.destroyCapability = null;
this._networkStream = networkStream;
this._fullReader = null;
this._lastProgress = null;
this.downloadInfoCapability = Promise.withResolvers();
this.setupMessageHandler();
}
#cacheSimpleMethod(name, data = null) {
const cachedPromise = this.#methodPromises.get(name);
if (cachedPromise) {
return cachedPromise;
}
const promise = this.messageHandler.sendWithPromise(name, data);
this.#methodPromises.set(name, promise);
return promise;
}
get annotationStorage() {
return shadow(this, "annotationStorage", new AnnotationStorage());
}
getRenderingIntent(intent, annotationMode = AnnotationMode.ENABLE, printAnnotationStorage = null, isEditing = false, isOpList = false) {
let renderingIntent = RenderingIntentFlag.DISPLAY;
let annotationStorageSerializable = SerializableEmpty;
switch (intent) {
case "any":
renderingIntent = RenderingIntentFlag.ANY;
break;
case "display":
break;
case "print":
renderingIntent = RenderingIntentFlag.PRINT;
break;
default:
warn(`getRenderingIntent - invalid intent: ${intent}`);
}
const annotationStorage = renderingIntent & RenderingIntentFlag.PRINT && printAnnotationStorage instanceof PrintAnnotationStorage ? printAnnotationStorage : this.annotationStorage;
switch (annotationMode) {
case AnnotationMode.DISABLE:
renderingIntent += RenderingIntentFlag.ANNOTATIONS_DISABLE;
break;
case AnnotationMode.ENABLE:
break;
case AnnotationMode.ENABLE_FORMS:
renderingIntent += RenderingIntentFlag.ANNOTATIONS_FORMS;
break;
case AnnotationMode.ENABLE_STORAGE:
renderingIntent += RenderingIntentFlag.ANNOTATIONS_STORAGE;
annotationStorageSerializable = annotationStorage.serializable;
break;
default:
warn(`getRenderingIntent - invalid annotationMode: ${annotationMode}`);
}
if (isEditing) {
renderingIntent += RenderingIntentFlag.IS_EDITING;
}
if (isOpList) {
renderingIntent += RenderingIntentFlag.OPLIST;
}
const {
ids: modifiedIds,
hash: modifiedIdsHash
} = annotationStorage.modifiedIds;
const cacheKeyBuf = [renderingIntent, annotationStorageSerializable.hash, modifiedIdsHash];
return {
renderingIntent,
cacheKey: cacheKeyBuf.join("_"),
annotationStorageSerializable,
modifiedIds
};
}
destroy() {
if (this.destroyCapability) {
return this.destroyCapability.promise;
}
this.destroyed = true;
this.destroyCapability = Promise.withResolvers();
this.#passwordCapability?.reject(new Error("Worker was destroyed during onPassword callback"));
const waitOn = [];
for (const page of this.#pageCache.values()) {
waitOn.push(page._destroy());
}
this.#pageCache.clear();
this.#pagePromises.clear();
this.#pageRefCache.clear();
if (this.hasOwnProperty("annotationStorage")) {
this.annotationStorage.resetModified();
}
const terminated = this.messageHandler.sendWithPromise("Terminate", null);
waitOn.push(terminated);
Promise.all(waitOn).then(() => {
this.commonObjs.clear();
this.fontLoader.clear();
this.#methodPromises.clear();
this.filterFactory.destroy();
TextLayer.cleanup();
this._networkStream?.cancelAllRequests(new AbortException("Worker was terminated."));
this.messageHandler?.destroy();
this.messageHandler = null;
this.destroyCapability.resolve();
}, this.destroyCapability.reject);
return this.destroyCapability.promise;
}
setupMessageHandler() {
const {
messageHandler,
loadingTask
} = this;
messageHandler.on("GetReader", (data, sink) => {
assert(this._networkStream, "GetReader - no `IPDFStream` instance available.");
this._fullReader = this._networkStream.getFullReader();
this._fullReader.onProgress = evt => {
this._lastProgress = {
loaded: evt.loaded,
total: evt.total
};
};
sink.onPull = () => {
this._fullReader.read().then(function ({
value,
done
}) {
if (done) {
sink.close();
return;
}
assert(value instanceof ArrayBuffer, "GetReader - expected an ArrayBuffer.");
sink.enqueue(new Uint8Array(value), 1, [value]);
}).catch(reason => {
sink.error(reason);
});
};
sink.onCancel = reason => {
this._fullReader.cancel(reason);
sink.ready.catch(readyReason => {
if (this.destroyed) {
return;
}
throw readyReason;
});
};
});
messageHandler.on("ReaderHeadersReady", async data => {
await this._fullReader.headersReady;
const {
isStreamingSupported,
isRangeSupported,
contentLength
} = this._fullReader;
if (!isStreamingSupported || !isRangeSupported) {
if (this._lastProgress) {
loadingTask.onProgress?.(this._lastProgress);
}
this._fullReader.onProgress = evt => {
loadingTask.onProgress?.({
loaded: evt.loaded,
total: evt.total
});
};
}
return {
isStreamingSupported,
isRangeSupported,
contentLength
};
});
messageHandler.on("GetRangeReader", (data, sink) => {
assert(this._networkStream, "GetRangeReader - no `IPDFStream` instance available.");
const rangeReader = this._networkStream.getRangeReader(data.begin, data.end);
if (!rangeReader) {
sink.close();
return;
}
sink.onPull = () => {
rangeReader.read().then(function ({
value,
done
}) {
if (done) {
sink.close();
return;
}
assert(value instanceof ArrayBuffer, "GetRangeReader - expected an ArrayBuffer.");
sink.enqueue(new Uint8Array(value), 1, [value]);
}).catch(reason => {
sink.error(reason);
});
};
sink.onCancel = reason => {
rangeReader.cancel(reason);
sink.ready.catch(readyReason => {
if (this.destroyed) {
return;
}
throw readyReason;
});
};
});
messageHandler.on("GetDoc", ({
pdfInfo
}) => {
this._numPages = pdfInfo.numPages;
this._htmlForXfa = pdfInfo.htmlForXfa;
delete pdfInfo.htmlForXfa;
loadingTask._capability.resolve(new PDFDocumentProxy(pdfInfo, this));
});
messageHandler.on("DocException", ex => {
loadingTask._capability.reject(wrapReason(ex));
});
messageHandler.on("PasswordRequest", ex => {
this.#passwordCapability = Promise.withResolvers();
try {
if (!loadingTask.onPassword) {
throw wrapReason(ex);
}
const updatePassword = password => {
if (password instanceof Error) {
this.#passwordCapability.reject(password);
} else {
this.#passwordCapability.resolve({
password
});
}
};
loadingTask.onPassword(updatePassword, ex.code);
} catch (err) {
this.#passwordCapability.reject(err);
}
return this.#passwordCapability.promise;
});
messageHandler.on("DataLoaded", data => {
loadingTask.onProgress?.({
loaded: data.length,
total: data.length
});
this.downloadInfoCapability.resolve(data);
});
messageHandler.on("StartRenderPage", data => {
if (this.destroyed) {
return;
}
const page = this.#pageCache.get(data.pageIndex);
page._startRenderPage(data.transparency, data.cacheKey);
});
messageHandler.on("commonobj", ([id, type, exportedData]) => {
if (this.destroyed) {
return null;
}
if (this.commonObjs.has(id)) {
return null;
}
switch (type) {
case "Font":
if ("error" in exportedData) {
const exportedError = exportedData.error;
warn(`Error during font loading: ${exportedError}`);
this.commonObjs.resolve(id, exportedError);
break;
}
const inspectFont = this._params.pdfBug && globalThis.FontInspector?.enabled ? (font, url) => globalThis.FontInspector.fontAdded(font, url) : null;
const font = new FontFaceObject(exportedData, inspectFont);
this.fontLoader.bind(font).catch(() => messageHandler.sendWithPromise("FontFallback", {
id
})).finally(() => {
if (!font.fontExtraProperties && font.data) {
font.data = null;
}
this.commonObjs.resolve(id, font);
});
break;
case "CopyLocalImage":
const {
imageRef
} = exportedData;
assert(imageRef, "The imageRef must be defined.");
for (const pageProxy of this.#pageCache.values()) {
for (const [, data] of pageProxy.objs) {
if (data?.ref !== imageRef) {
continue;
}
if (!data.dataLen) {
return null;
}
this.commonObjs.resolve(id, structuredClone(data));
return data.dataLen;
}
}
break;
case "FontPath":
case "Image":
case "Pattern":
this.commonObjs.resolve(id, exportedData);
break;
default:
throw new Error(`Got unknown common object type ${type}`);
}
return null;
});
messageHandler.on("obj", ([id, pageIndex, type, imageData]) => {
if (this.destroyed) {
return;
}
const pageProxy = this.#pageCache.get(pageIndex);
if (pageProxy.objs.has(id)) {
return;
}
if (pageProxy._intentStates.size === 0) {
imageData?.bitmap?.close();
return;
}
switch (type) {
case "Image":
case "Pattern":
pageProxy.objs.resolve(id, imageData);
break;
default:
throw new Error(`Got unknown object type ${type}`);
}
});
messageHandler.on("DocProgress", data => {
if (this.destroyed) {
return;
}
loadingTask.onProgress?.({
loaded: data.loaded,
total: data.total
});
});
messageHandler.on("FetchBinaryData", async data => {
if (this.destroyed) {
throw new Error("Worker was destroyed.");
}
const factory = this[data.type];
if (!factory) {
throw new Error(`${data.type} not initialized, see the \`useWorkerFetch\` parameter.`);
}
return factory.fetch(data);
});
}
getData() {
return this.messageHandler.sendWithPromise("GetData", null);
}
saveDocument() {
if (this.annotationStorage.size <= 0) {
warn("saveDocument called while `annotationStorage` is empty, " + "please use the getData-method instead.");
}
const {
map,
transfer
} = this.annotationStorage.serializable;
return this.messageHandler.sendWithPromise("SaveDocument", {
isPureXfa: !!this._htmlForXfa,
numPages: this._numPages,
annotationStorage: map,
filename: this._fullReader?.filename ?? null
}, transfer).finally(() => {
this.annotationStorage.resetModified();
});
}
getPage(pageNumber) {
if (!Number.isInteger(pageNumber) || pageNumber <= 0 || pageNumber > this._numPages) {
return Promise.reject(new Error("Invalid page request."));
}
const pageIndex = pageNumber - 1,
cachedPromise = this.#pagePromises.get(pageIndex);
if (cachedPromise) {
return cachedPromise;
}
const promise = this.messageHandler.sendWithPromise("GetPage", {
pageIndex
}).then(pageInfo => {
if (this.destroyed) {
throw new Error("Transport destroyed");
}
if (pageInfo.refStr) {
this.#pageRefCache.set(pageInfo.refStr, pageNumber);
}
const page = new PDFPageProxy(pageIndex, pageInfo, this, this._params.pdfBug);
this.#pageCache.set(pageIndex, page);
return page;
});
this.#pagePromises.set(pageIndex, promise);
return promise;
}
getPageIndex(ref) {
if (!isRefProxy(ref)) {
return Promise.reject(new Error("Invalid pageIndex request."));
}
return this.messageHandler.sendWithPromise("GetPageIndex", {
num: ref.num,
gen: ref.gen
});
}
getAnnotations(pageIndex, intent) {
return this.messageHandler.sendWithPromise("GetAnnotations", {
pageIndex,
intent
});
}
getFieldObjects() {
return this.#cacheSimpleMethod("GetFieldObjects");
}
hasJSActions() {
return this.#cacheSimpleMethod("HasJSActions");
}
getCalculationOrderIds() {
return this.messageHandler.sendWithPromise("GetCalculationOrderIds", null);
}
getDestinations() {
return this.messageHandler.sendWithPromise("GetDestinations", null);
}
getDestination(id) {
if (typeof id !== "string") {
return Promise.reject(new Error("Invalid destination request."));
}
return this.messageHandler.sendWithPromise("GetDestination", {
id
});
}
getPageLabels() {
return this.messageHandler.sendWithPromise("GetPageLabels", null);
}
getPageLayout() {
return this.messageHandler.sendWithPromise("GetPageLayout", null);
}
getPageMode() {
return this.messageHandler.sendWithPromise("GetPageMode", null);
}
getViewerPreferences() {
return this.messageHandler.sendWithPromise("GetViewerPreferences", null);
}
getOpenAction() {
return this.messageHandler.sendWithPromise("GetOpenAction", null);
}
getAttachments() {
return this.messageHandler.sendWithPromise("GetAttachments", null);
}
getDocJSActions() {
return this.#cacheSimpleMethod("GetDocJSActions");
}
getPageJSActions(pageIndex) {
return this.messageHandler.sendWithPromise("GetPageJSActions", {
pageIndex
});
}
getStructTree(pageIndex) {
return this.messageHandler.sendWithPromise("GetStructTree", {
pageIndex
});
}
getOutline() {
return this.messageHandler.sendWithPromise("GetOutline", null);
}
getOptionalContentConfig(renderingIntent) {
return this.#cacheSimpleMethod("GetOptionalContentConfig").then(data => new OptionalContentConfig(data, renderingIntent));
}
getPermissions() {
return this.messageHandler.sendWithPromise("GetPermissions", null);
}
getMetadata() {
const name = "GetMetadata",
cachedPromise = this.#methodPromises.get(name);
if (cachedPromise) {
return cachedPromise;
}
const promise = this.messageHandler.sendWithPromise(name, null).then(results => ({
info: results[0],
metadata: results[1] ? new Metadata(results[1]) : null,
contentDispositionFilename: this._fullReader?.filename ?? null,
contentLength: this._fullReader?.contentLength ?? null
}));
this.#methodPromises.set(name, promise);
return promise;
}
getMarkInfo() {
return this.messageHandler.sendWithPromise("GetMarkInfo", null);
}
async startCleanup(keepLoadedFonts = false) {
if (this.destroyed) {
return;
}
await this.messageHandler.sendWithPromise("Cleanup", null);
for (const page of this.#pageCache.values()) {
const cleanupSuccessful = page.cleanup();
if (!cleanupSuccessful) {
throw new Error(`startCleanup: Page ${page.pageNumber} is currently rendering.`);
}
}
this.commonObjs.clear();
if (!keepLoadedFonts) {
this.fontLoader.clear();
}
this.#methodPromises.clear();
this.filterFactory.destroy(true);
TextLayer.cleanup();
}
cachedPageNumber(ref) {
if (!isRefProxy(ref)) {
return null;
}
const refStr = ref.gen === 0 ? `${ref.num}R` : `${ref.num}R${ref.gen}`;
return this.#pageRefCache.get(refStr) ?? null;
}
}
const INITIAL_DATA = Symbol("INITIAL_DATA");
class PDFObjects {
#objs = Object.create(null);
#ensureObj(objId) {
return this.#objs[objId] ||= {
...Promise.withResolvers(),
data: INITIAL_DATA
};
}
get(objId, callback = null) {
if (callback) {
const obj = this.#ensureObj(objId);
obj.promise.then(() => callback(obj.data));
return null;
}
const obj = this.#objs[objId];
if (!obj || obj.data === INITIAL_DATA) {
throw new Error(`Requesting object that isn't resolved yet ${objId}.`);
}
return obj.data;
}
has(objId) {
const obj = this.#objs[objId];
return !!obj && obj.data !== INITIAL_DATA;
}
delete(objId) {
const obj = this.#objs[objId];
if (!obj || obj.data === INITIAL_DATA) {
return false;
}
delete this.#objs[objId];
return true;
}
resolve(objId, data = null) {
const obj = this.#ensureObj(objId);
obj.data = data;
obj.resolve();
}
clear() {
for (const objId in this.#objs) {
const {
data
} = this.#objs[objId];
data?.bitmap?.close();
}
this.#objs = Object.create(null);
}
*[Symbol.iterator]() {
for (const objId in this.#objs) {
const {
data
} = this.#objs[objId];
if (data === INITIAL_DATA) {
continue;
}
yield [objId, data];
}
}
}
class RenderTask {
#internalRenderTask = null;
onContinue = null;
onError = null;
constructor(internalRenderTask) {
this.#internalRenderTask = internalRenderTask;
}
get promise() {
return this.#internalRenderTask.capability.promise;
}
cancel(extraDelay = 0) {
this.#internalRenderTask.cancel(null, extraDelay);
}
get separateAnnots() {
const {
separateAnnots
} = this.#internalRenderTask.operatorList;
if (!separateAnnots) {
return false;
}
const {
annotationCanvasMap
} = this.#internalRenderTask;
return separateAnnots.form || separateAnnots.canvas && annotationCanvasMap?.size > 0;
}
}
class InternalRenderTask {
#rAF = null;
static #canvasInUse = new WeakSet();
constructor({
callback,
params,
objs,
commonObjs,
annotationCanvasMap,
operatorList,
pageIndex,
canvasFactory,
filterFactory,
useRequestAnimationFrame = false,
pdfBug = false,
pageColors = null
}) {
this.callback = callback;
this.params = params;
this.objs = objs;
this.commonObjs = commonObjs;
this.annotationCanvasMap = annotationCanvasMap;
this.operatorListIdx = null;
this.operatorList = operatorList;
this._pageIndex = pageIndex;
this.canvasFactory = canvasFactory;
this.filterFactory = filterFactory;
this._pdfBug = pdfBug;
this.pageColors = pageColors;
this.running = false;
this.graphicsReadyCallback = null;
this.graphicsReady = false;
this._useRequestAnimationFrame = useRequestAnimationFrame === true && typeof window !== "undefined";
this.cancelled = false;
this.capability = Promise.withResolvers();
this.task = new RenderTask(this);
this._cancelBound = this.cancel.bind(this);
this._continueBound = this._continue.bind(this);
this._scheduleNextBound = this._scheduleNext.bind(this);
this._nextBound = this._next.bind(this);
this._canvas = params.canvasContext.canvas;
}
get completed() {
return this.capability.promise.catch(function () {});
}
initializeGraphics({
transparency = false,
optionalContentConfig
}) {
if (this.cancelled) {
return;
}
if (this._canvas) {
if (InternalRenderTask.#canvasInUse.has(this._canvas)) {
throw new Error("Cannot use the same canvas during multiple render() operations. " + "Use different canvas or ensure previous operations were " + "cancelled or completed.");
}
InternalRenderTask.#canvasInUse.add(this._canvas);
}
if (this._pdfBug && globalThis.StepperManager?.enabled) {
this.stepper = globalThis.StepperManager.create(this._pageIndex);
this.stepper.init(this.operatorList);
this.stepper.nextBreakPoint = this.stepper.getNextBreakPoint();
}
const {
canvasContext,
viewport,
transform,
background
} = this.params;
this.gfx = new CanvasGraphics(canvasContext, this.commonObjs, this.objs, this.canvasFactory, this.filterFactory, {
optionalContentConfig
}, this.annotationCanvasMap, this.pageColors);
this.gfx.beginDrawing({
transform,
viewport,
transparency,
background
});
this.operatorListIdx = 0;
this.graphicsReady = true;
this.graphicsReadyCallback?.();
}
cancel(error = null, extraDelay = 0) {
this.running = false;
this.cancelled = true;
this.gfx?.endDrawing();
if (this.#rAF) {
window.cancelAnimationFrame(this.#rAF);
this.#rAF = null;
}
InternalRenderTask.#canvasInUse.delete(this._canvas);
error ||= new RenderingCancelledException(`Rendering cancelled, page ${this._pageIndex + 1}`, extraDelay);
this.callback(error);
this.task.onError?.(error);
}
operatorListChanged() {
if (!this.graphicsReady) {
this.graphicsReadyCallback ||= this._continueBound;
return;
}
this.stepper?.updateOperatorList(this.operatorList);
if (this.running) {
return;
}
this._continue();
}
_continue() {
this.running = true;
if (this.cancelled) {
return;
}
if (this.task.onContinue) {
this.task.onContinue(this._scheduleNextBound);
} else {
this._scheduleNext();
}
}
_scheduleNext() {
if (this._useRequestAnimationFrame) {
this.#rAF = window.requestAnimationFrame(() => {
this.#rAF = null;
this._nextBound().catch(this._cancelBound);
});
} else {
Promise.resolve().then(this._nextBound).catch(this._cancelBound);
}
}
async _next() {
if (this.cancelled) {
return;
}
this.operatorListIdx = this.gfx.executeOperatorList(this.operatorList, this.operatorListIdx, this._continueBound, this.stepper);
if (this.operatorListIdx === this.operatorList.argsArray.length) {
this.running = false;
if (this.operatorList.lastChunk) {
this.gfx.endDrawing();
InternalRenderTask.#canvasInUse.delete(this._canvas);
this.callback();
}
}
}
}
const version = "5.1.91";
const build = "45cbe8bb0";
;// ./src/shared/scripting_utils.js
function makeColorComp(n) {
return Math.floor(Math.max(0, Math.min(1, n)) * 255).toString(16).padStart(2, "0");
}
function scaleAndClamp(x) {
return Math.max(0, Math.min(255, 255 * x));
}
class ColorConverters {
static CMYK_G([c, y, m, k]) {
return ["G", 1 - Math.min(1, 0.3 * c + 0.59 * m + 0.11 * y + k)];
}
static G_CMYK([g]) {
return ["CMYK", 0, 0, 0, 1 - g];
}
static G_RGB([g]) {
return ["RGB", g, g, g];
}
static G_rgb([g]) {
g = scaleAndClamp(g);
return [g, g, g];
}
static G_HTML([g]) {
const G = makeColorComp(g);
return `#${G}${G}${G}`;
}
static RGB_G([r, g, b]) {
return ["G", 0.3 * r + 0.59 * g + 0.11 * b];
}
static RGB_rgb(color) {
return color.map(scaleAndClamp);
}
static RGB_HTML(color) {
return `#${color.map(makeColorComp).join("")}`;
}
static T_HTML() {
return "#00000000";
}
static T_rgb() {
return [null];
}
static CMYK_RGB([c, y, m, k]) {
return ["RGB", 1 - Math.min(1, c + k), 1 - Math.min(1, m + k), 1 - Math.min(1, y + k)];
}
static CMYK_rgb([c, y, m, k]) {
return [scaleAndClamp(1 - Math.min(1, c + k)), scaleAndClamp(1 - Math.min(1, m + k)), scaleAndClamp(1 - Math.min(1, y + k))];
}
static CMYK_HTML(components) {
const rgb = this.CMYK_RGB(components).slice(1);
return this.RGB_HTML(rgb);
}
static RGB_CMYK([r, g, b]) {
const c = 1 - r;
const m = 1 - g;
const y = 1 - b;
const k = Math.min(c, m, y);
return ["CMYK", c, m, y, k];
}
}
;// ./src/display/svg_factory.js
class BaseSVGFactory {
create(width, height, skipDimensions = false) {
if (width <= 0 || height <= 0) {
throw new Error("Invalid SVG dimensions");
}
const svg = this._createSVG("svg:svg");
svg.setAttribute("version", "1.1");
if (!skipDimensions) {
svg.setAttribute("width", `${width}px`);
svg.setAttribute("height", `${height}px`);
}
svg.setAttribute("preserveAspectRatio", "none");
svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
return svg;
}
createElement(type) {
if (typeof type !== "string") {
throw new Error("Invalid SVG element type");
}
return this._createSVG(type);
}
_createSVG(type) {
unreachable("Abstract method `_createSVG` called.");
}
}
class DOMSVGFactory extends BaseSVGFactory {
_createSVG(type) {
return document.createElementNS(SVG_NS, type);
}
}
;// ./src/display/xfa_layer.js
class XfaLayer {
static setupStorage(html, id, element, storage, intent) {
const storedData = storage.getValue(id, {
value: null
});
switch (element.name) {
case "textarea":
if (storedData.value !== null) {
html.textContent = storedData.value;
}
if (intent === "print") {
break;
}
html.addEventListener("input", event => {
storage.setValue(id, {
value: event.target.value
});
});
break;
case "input":
if (element.attributes.type === "radio" || element.attributes.type === "checkbox") {
if (storedData.value === element.attributes.xfaOn) {
html.setAttribute("checked", true);
} else if (storedData.value === element.attributes.xfaOff) {
html.removeAttribute("checked");
}
if (intent === "print") {
break;
}
html.addEventListener("change", event => {
storage.setValue(id, {
value: event.target.checked ? event.target.getAttribute("xfaOn") : event.target.getAttribute("xfaOff")
});
});
} else {
if (storedData.value !== null) {
html.setAttribute("value", storedData.value);
}
if (intent === "print") {
break;
}
html.addEventListener("input", event => {
storage.setValue(id, {
value: event.target.value
});
});
}
break;
case "select":
if (storedData.value !== null) {
html.setAttribute("value", storedData.value);
for (const option of element.children) {
if (option.attributes.value === storedData.value) {
option.attributes.selected = true;
} else if (option.attributes.hasOwnProperty("selected")) {
delete option.attributes.selected;
}
}
}
html.addEventListener("input", event => {
const options = event.target.options;
const value = options.selectedIndex === -1 ? "" : options[options.selectedIndex].value;
storage.setValue(id, {
value
});
});
break;
}
}
static setAttributes({
html,
element,
storage = null,
intent,
linkService
}) {
const {
attributes
} = element;
const isHTMLAnchorElement = html instanceof HTMLAnchorElement;
if (attributes.type === "radio") {
attributes.name = `${attributes.name}-${intent}`;
}
for (const [key, value] of Object.entries(attributes)) {
if (value === null || value === undefined) {
continue;
}
switch (key) {
case "class":
if (value.length) {
html.setAttribute(key, value.join(" "));
}
break;
case "dataId":
break;
case "id":
html.setAttribute("data-element-id", value);
break;
case "style":
Object.assign(html.style, value);
break;
case "textContent":
html.textContent = value;
break;
default:
if (!isHTMLAnchorElement || key !== "href" && key !== "newWindow") {
html.setAttribute(key, value);
}
}
}
if (isHTMLAnchorElement) {
linkService.addLinkAttributes(html, attributes.href, attributes.newWindow);
}
if (storage && attributes.dataId) {
this.setupStorage(html, attributes.dataId, element, storage);
}
}
static render(parameters) {
const storage = parameters.annotationStorage;
const linkService = parameters.linkService;
const root = parameters.xfaHtml;
const intent = parameters.intent || "display";
const rootHtml = document.createElement(root.name);
if (root.attributes) {
this.setAttributes({
html: rootHtml,
element: root,
intent,
linkService
});
}
const isNotForRichText = intent !== "richText";
const rootDiv = parameters.div;
rootDiv.append(rootHtml);
if (parameters.viewport) {
const transform = `matrix(${parameters.viewport.transform.join(",")})`;
rootDiv.style.transform = transform;
}
if (isNotForRichText) {
rootDiv.setAttribute("class", "xfaLayer xfaFont");
}
const textDivs = [];
if (root.children.length === 0) {
if (root.value) {
const node = document.createTextNode(root.value);
rootHtml.append(node);
if (isNotForRichText && XfaText.shouldBuildText(root.name)) {
textDivs.push(node);
}
}
return {
textDivs
};
}
const stack = [[root, -1, rootHtml]];
while (stack.length > 0) {
const [parent, i, html] = stack.at(-1);
if (i + 1 === parent.children.length) {
stack.pop();
continue;
}
const child = parent.children[++stack.at(-1)[1]];
if (child === null) {
continue;
}
const {
name
} = child;
if (name === "#text") {
const node = document.createTextNode(child.value);
textDivs.push(node);
html.append(node);
continue;
}
const childHtml = child?.attributes?.xmlns ? document.createElementNS(child.attributes.xmlns, name) : document.createElement(name);
html.append(childHtml);
if (child.attributes) {
this.setAttributes({
html: childHtml,
element: child,
storage,
intent,
linkService
});
}
if (child.children?.length > 0) {
stack.push([child, -1, childHtml]);
} else if (child.value) {
const node = document.createTextNode(child.value);
if (isNotForRichText && XfaText.shouldBuildText(name)) {
textDivs.push(node);
}
childHtml.append(node);
}
}
for (const el of rootDiv.querySelectorAll(".xfaNonInteractive input, .xfaNonInteractive textarea")) {
el.setAttribute("readOnly", true);
}
return {
textDivs
};
}
static update(parameters) {
const transform = `matrix(${parameters.viewport.transform.join(",")})`;
parameters.div.style.transform = transform;
parameters.div.hidden = false;
}
}
;// ./src/display/annotation_layer.js
const DEFAULT_TAB_INDEX = 1000;
const annotation_layer_DEFAULT_FONT_SIZE = 9;
const GetElementsByNameSet = new WeakSet();
class AnnotationElementFactory {
static create(parameters) {
const subtype = parameters.data.annotationType;
switch (subtype) {
case AnnotationType.LINK:
return new LinkAnnotationElement(parameters);
case AnnotationType.TEXT:
return new TextAnnotationElement(parameters);
case AnnotationType.WIDGET:
const fieldType = parameters.data.fieldType;
switch (fieldType) {
case "Tx":
return new TextWidgetAnnotationElement(parameters);
case "Btn":
if (parameters.data.radioButton) {
return new RadioButtonWidgetAnnotationElement(parameters);
} else if (parameters.data.checkBox) {
return new CheckboxWidgetAnnotationElement(parameters);
}
return new PushButtonWidgetAnnotationElement(parameters);
case "Ch":
return new ChoiceWidgetAnnotationElement(parameters);
case "Sig":
return new SignatureWidgetAnnotationElement(parameters);
}
return new WidgetAnnotationElement(parameters);
case AnnotationType.POPUP:
return new PopupAnnotationElement(parameters);
case AnnotationType.FREETEXT:
return new FreeTextAnnotationElement(parameters);
case AnnotationType.LINE:
return new LineAnnotationElement(parameters);
case AnnotationType.SQUARE:
return new SquareAnnotationElement(parameters);
case AnnotationType.CIRCLE:
return new CircleAnnotationElement(parameters);
case AnnotationType.POLYLINE:
return new PolylineAnnotationElement(parameters);
case AnnotationType.CARET:
return new CaretAnnotationElement(parameters);
case AnnotationType.INK:
return new InkAnnotationElement(parameters);
case AnnotationType.POLYGON:
return new PolygonAnnotationElement(parameters);
case AnnotationType.HIGHLIGHT:
return new HighlightAnnotationElement(parameters);
case AnnotationType.UNDERLINE:
return new UnderlineAnnotationElement(parameters);
case AnnotationType.SQUIGGLY:
return new SquigglyAnnotationElement(parameters);
case AnnotationType.STRIKEOUT:
return new StrikeOutAnnotationElement(parameters);
case AnnotationType.STAMP:
return new StampAnnotationElement(parameters);
case AnnotationType.FILEATTACHMENT:
return new FileAttachmentAnnotationElement(parameters);
default:
return new AnnotationElement(parameters);
}
}
}
class AnnotationElement {
#updates = null;
#hasBorder = false;
#popupElement = null;
constructor(parameters, {
isRenderable = false,
ignoreBorder = false,
createQuadrilaterals = false
} = {}) {
this.isRenderable = isRenderable;
this.data = parameters.data;
this.layer = parameters.layer;
this.linkService = parameters.linkService;
this.downloadManager = parameters.downloadManager;
this.imageResourcesPath = parameters.imageResourcesPath;
this.renderForms = parameters.renderForms;
this.svgFactory = parameters.svgFactory;
this.annotationStorage = parameters.annotationStorage;
this.enableScripting = parameters.enableScripting;
this.hasJSActions = parameters.hasJSActions;
this._fieldObjects = parameters.fieldObjects;
this.parent = parameters.parent;
if (isRenderable) {
this.container = this._createContainer(ignoreBorder);
}
if (createQuadrilaterals) {
this._createQuadrilaterals();
}
}
static _hasPopupData({
titleObj,
contentsObj,
richText
}) {
return !!(titleObj?.str || contentsObj?.str || richText?.str);
}
get _isEditable() {
return this.data.isEditable;
}
get hasPopupData() {
return AnnotationElement._hasPopupData(this.data);
}
updateEdited(params) {
if (!this.container) {
return;
}
this.#updates ||= {
rect: this.data.rect.slice(0)
};
const {
rect
} = params;
if (rect) {
this.#setRectEdited(rect);
}
this.#popupElement?.popup.updateEdited(params);
}
resetEdited() {
if (!this.#updates) {
return;
}
this.#setRectEdited(this.#updates.rect);
this.#popupElement?.popup.resetEdited();
this.#updates = null;
}
#setRectEdited(rect) {
const {
container: {
style
},
data: {
rect: currentRect,
rotation
},
parent: {
viewport: {
rawDims: {
pageWidth,
pageHeight,
pageX,
pageY
}
}
}
} = this;
currentRect?.splice(0, 4, ...rect);
style.left = `${100 * (rect[0] - pageX) / pageWidth}%`;
style.top = `${100 * (pageHeight - rect[3] + pageY) / pageHeight}%`;
if (rotation === 0) {
style.width = `${100 * (rect[2] - rect[0]) / pageWidth}%`;
style.height = `${100 * (rect[3] - rect[1]) / pageHeight}%`;
} else {
this.setRotation(rotation);
}
}
_createContainer(ignoreBorder) {
const {
data,
parent: {
page,
viewport
}
} = this;
const container = document.createElement("section");
container.setAttribute("data-annotation-id", data.id);
if (!(this instanceof WidgetAnnotationElement)) {
container.tabIndex = DEFAULT_TAB_INDEX;
}
const {
style
} = container;
style.zIndex = this.parent.zIndex++;
if (data.alternativeText) {
container.title = data.alternativeText;
}
if (data.noRotate) {
container.classList.add("norotate");
}
if (!data.rect || this instanceof PopupAnnotationElement) {
const {
rotation
} = data;
if (!data.hasOwnCanvas && rotation !== 0) {
this.setRotation(rotation, container);
}
return container;
}
const {
width,
height
} = this;
if (!ignoreBorder && data.borderStyle.width > 0) {
style.borderWidth = `${data.borderStyle.width}px`;
const horizontalRadius = data.borderStyle.horizontalCornerRadius;
const verticalRadius = data.borderStyle.verticalCornerRadius;
if (horizontalRadius > 0 || verticalRadius > 0) {
const radius = `calc(${horizontalRadius}px * var(--total-scale-factor)) / calc(${verticalRadius}px * var(--total-scale-factor))`;
style.borderRadius = radius;
} else if (this instanceof RadioButtonWidgetAnnotationElement) {
const radius = `calc(${width}px * var(--total-scale-factor)) / calc(${height}px * var(--total-scale-factor))`;
style.borderRadius = radius;
}
switch (data.borderStyle.style) {
case AnnotationBorderStyleType.SOLID:
style.borderStyle = "solid";
break;
case AnnotationBorderStyleType.DASHED:
style.borderStyle = "dashed";
break;
case AnnotationBorderStyleType.BEVELED:
warn("Unimplemented border style: beveled");
break;
case AnnotationBorderStyleType.INSET:
warn("Unimplemented border style: inset");
break;
case AnnotationBorderStyleType.UNDERLINE:
style.borderBottomStyle = "solid";
break;
default:
break;
}
const borderColor = data.borderColor || null;
if (borderColor) {
this.#hasBorder = true;
style.borderColor = Util.makeHexColor(borderColor[0] | 0, borderColor[1] | 0, borderColor[2] | 0);
} else {
style.borderWidth = 0;
}
}
const rect = Util.normalizeRect([data.rect[0], page.view[3] - data.rect[1] + page.view[1], data.rect[2], page.view[3] - data.rect[3] + page.view[1]]);
const {
pageWidth,
pageHeight,
pageX,
pageY
} = viewport.rawDims;
style.left = `${100 * (rect[0] - pageX) / pageWidth}%`;
style.top = `${100 * (rect[1] - pageY) / pageHeight}%`;
const {
rotation
} = data;
if (data.hasOwnCanvas || rotation === 0) {
style.width = `${100 * width / pageWidth}%`;
style.height = `${100 * height / pageHeight}%`;
} else {
this.setRotation(rotation, container);
}
return container;
}
setRotation(angle, container = this.container) {
if (!this.data.rect) {
return;
}
const {
pageWidth,
pageHeight
} = this.parent.viewport.rawDims;
let {
width,
height
} = this;
if (angle % 180 !== 0) {
[width, height] = [height, width];
}
container.style.width = `${100 * width / pageWidth}%`;
container.style.height = `${100 * height / pageHeight}%`;
container.setAttribute("data-main-rotation", (360 - angle) % 360);
}
get _commonActions() {
const setColor = (jsName, styleName, event) => {
const color = event.detail[jsName];
const colorType = color[0];
const colorArray = color.slice(1);
event.target.style[styleName] = ColorConverters[`${colorType}_HTML`](colorArray);
this.annotationStorage.setValue(this.data.id, {
[styleName]: ColorConverters[`${colorType}_rgb`](colorArray)
});
};
return shadow(this, "_commonActions", {
display: event => {
const {
display
} = event.detail;
const hidden = display % 2 === 1;
this.container.style.visibility = hidden ? "hidden" : "visible";
this.annotationStorage.setValue(this.data.id, {
noView: hidden,
noPrint: display === 1 || display === 2
});
},
print: event => {
this.annotationStorage.setValue(this.data.id, {
noPrint: !event.detail.print
});
},
hidden: event => {
const {
hidden
} = event.detail;
this.container.style.visibility = hidden ? "hidden" : "visible";
this.annotationStorage.setValue(this.data.id, {
noPrint: hidden,
noView: hidden
});
},
focus: event => {
setTimeout(() => event.target.focus({
preventScroll: false
}), 0);
},
userName: event => {
event.target.title = event.detail.userName;
},
readonly: event => {
event.target.disabled = event.detail.readonly;
},
required: event => {
this._setRequired(event.target, event.detail.required);
},
bgColor: event => {
setColor("bgColor", "backgroundColor", event);
},
fillColor: event => {
setColor("fillColor", "backgroundColor", event);
},
fgColor: event => {
setColor("fgColor", "color", event);
},
textColor: event => {
setColor("textColor", "color", event);
},
borderColor: event => {
setColor("borderColor", "borderColor", event);
},
strokeColor: event => {
setColor("strokeColor", "borderColor", event);
},
rotation: event => {
const angle = event.detail.rotation;
this.setRotation(angle);
this.annotationStorage.setValue(this.data.id, {
rotation: angle
});
}
});
}
_dispatchEventFromSandbox(actions, jsEvent) {
const commonActions = this._commonActions;
for (const name of Object.keys(jsEvent.detail)) {
const action = actions[name] || commonActions[name];
action?.(jsEvent);
}
}
_setDefaultPropertiesFromJS(element) {
if (!this.enableScripting) {
return;
}
const storedData = this.annotationStorage.getRawValue(this.data.id);
if (!storedData) {
return;
}
const commonActions = this._commonActions;
for (const [actionName, detail] of Object.entries(storedData)) {
const action = commonActions[actionName];
if (action) {
const eventProxy = {
detail: {
[actionName]: detail
},
target: element
};
action(eventProxy);
delete storedData[actionName];
}
}
}
_createQuadrilaterals() {
if (!this.container) {
return;
}
const {
quadPoints
} = this.data;
if (!quadPoints) {
return;
}
const [rectBlX, rectBlY, rectTrX, rectTrY] = this.data.rect.map(x => Math.fround(x));
if (quadPoints.length === 8) {
const [trX, trY, blX, blY] = quadPoints.subarray(2, 6);
if (rectTrX === trX && rectTrY === trY && rectBlX === blX && rectBlY === blY) {
return;
}
}
const {
style
} = this.container;
let svgBuffer;
if (this.#hasBorder) {
const {
borderColor,
borderWidth
} = style;
style.borderWidth = 0;
svgBuffer = ["url('data:image/svg+xml;utf8,", `')`);
style.backgroundImage = svgBuffer.join("");
}
this.container.append(svg);
this.container.style.clipPath = `url(#${id})`;
}
_createPopup() {
const {
data
} = this;
const popup = this.#popupElement = new PopupAnnotationElement({
data: {
color: data.color,
titleObj: data.titleObj,
modificationDate: data.modificationDate,
contentsObj: data.contentsObj,
richText: data.richText,
parentRect: data.rect,
borderStyle: 0,
id: `popup_${data.id}`,
rotation: data.rotation
},
parent: this.parent,
elements: [this]
});
this.parent.div.append(popup.render());
}
render() {
unreachable("Abstract method `AnnotationElement.render` called");
}
_getElementsByName(name, skipId = null) {
const fields = [];
if (this._fieldObjects) {
const fieldObj = this._fieldObjects[name];
if (fieldObj) {
for (const {
page,
id,
exportValues
} of fieldObj) {
if (page === -1) {
continue;
}
if (id === skipId) {
continue;
}
const exportValue = typeof exportValues === "string" ? exportValues : null;
const domElement = document.querySelector(`[data-element-id="${id}"]`);
if (domElement && !GetElementsByNameSet.has(domElement)) {
warn(`_getElementsByName - element not allowed: ${id}`);
continue;
}
fields.push({
id,
exportValue,
domElement
});
}
}
return fields;
}
for (const domElement of document.getElementsByName(name)) {
const {
exportValue
} = domElement;
const id = domElement.getAttribute("data-element-id");
if (id === skipId) {
continue;
}
if (!GetElementsByNameSet.has(domElement)) {
continue;
}
fields.push({
id,
exportValue,
domElement
});
}
return fields;
}
show() {
if (this.container) {
this.container.hidden = false;
}
this.popup?.maybeShow();
}
hide() {
if (this.container) {
this.container.hidden = true;
}
this.popup?.forceHide();
}
getElementsToTriggerPopup() {
return this.container;
}
addHighlightArea() {
const triggers = this.getElementsToTriggerPopup();
if (Array.isArray(triggers)) {
for (const element of triggers) {
element.classList.add("highlightArea");
}
} else {
triggers.classList.add("highlightArea");
}
}
_editOnDoubleClick() {
if (!this._isEditable) {
return;
}
const {
annotationEditorType: mode,
data: {
id: editId
}
} = this;
this.container.addEventListener("dblclick", () => {
this.linkService.eventBus?.dispatch("switchannotationeditormode", {
source: this,
mode,
editId
});
});
}
get width() {
return this.data.rect[2] - this.data.rect[0];
}
get height() {
return this.data.rect[3] - this.data.rect[1];
}
}
class LinkAnnotationElement extends AnnotationElement {
constructor(parameters, options = null) {
super(parameters, {
isRenderable: true,
ignoreBorder: !!options?.ignoreBorder,
createQuadrilaterals: true
});
this.isTooltipOnly = parameters.data.isTooltipOnly;
}
render() {
const {
data,
linkService
} = this;
const link = document.createElement("a");
link.setAttribute("data-element-id", data.id);
let isBound = false;
if (data.url) {
linkService.addLinkAttributes(link, data.url, data.newWindow);
isBound = true;
} else if (data.action) {
this._bindNamedAction(link, data.action);
isBound = true;
} else if (data.attachment) {
this.#bindAttachment(link, data.attachment, data.attachmentDest);
isBound = true;
} else if (data.setOCGState) {
this.#bindSetOCGState(link, data.setOCGState);
isBound = true;
} else if (data.dest) {
this._bindLink(link, data.dest);
isBound = true;
} else {
if (data.actions && (data.actions.Action || data.actions["Mouse Up"] || data.actions["Mouse Down"]) && this.enableScripting && this.hasJSActions) {
this._bindJSAction(link, data);
isBound = true;
}
if (data.resetForm) {
this._bindResetFormAction(link, data.resetForm);
isBound = true;
} else if (this.isTooltipOnly && !isBound) {
this._bindLink(link, "");
isBound = true;
}
}
this.container.classList.add("linkAnnotation");
if (isBound) {
this.container.append(link);
}
return this.container;
}
#setInternalLink() {
this.container.setAttribute("data-internal-link", "");
}
_bindLink(link, destination) {
link.href = this.linkService.getDestinationHash(destination);
link.onclick = () => {
if (destination) {
this.linkService.goToDestination(destination);
}
return false;
};
if (destination || destination === "") {
this.#setInternalLink();
}
}
_bindNamedAction(link, action) {
link.href = this.linkService.getAnchorUrl("");
link.onclick = () => {
this.linkService.executeNamedAction(action);
return false;
};
this.#setInternalLink();
}
#bindAttachment(link, attachment, dest = null) {
link.href = this.linkService.getAnchorUrl("");
if (attachment.description) {
link.title = attachment.description;
}
link.onclick = () => {
this.downloadManager?.openOrDownloadData(attachment.content, attachment.filename, dest);
return false;
};
this.#setInternalLink();
}
#bindSetOCGState(link, action) {
link.href = this.linkService.getAnchorUrl("");
link.onclick = () => {
this.linkService.executeSetOCGState(action);
return false;
};
this.#setInternalLink();
}
_bindJSAction(link, data) {
link.href = this.linkService.getAnchorUrl("");
const map = new Map([["Action", "onclick"], ["Mouse Up", "onmouseup"], ["Mouse Down", "onmousedown"]]);
for (const name of Object.keys(data.actions)) {
const jsName = map.get(name);
if (!jsName) {
continue;
}
link[jsName] = () => {
this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
source: this,
detail: {
id: data.id,
name
}
});
return false;
};
}
if (!link.onclick) {
link.onclick = () => false;
}
this.#setInternalLink();
}
_bindResetFormAction(link, resetForm) {
const otherClickAction = link.onclick;
if (!otherClickAction) {
link.href = this.linkService.getAnchorUrl("");
}
this.#setInternalLink();
if (!this._fieldObjects) {
warn(`_bindResetFormAction - "resetForm" action not supported, ` + "ensure that the `fieldObjects` parameter is provided.");
if (!otherClickAction) {
link.onclick = () => false;
}
return;
}
link.onclick = () => {
otherClickAction?.();
const {
fields: resetFormFields,
refs: resetFormRefs,
include
} = resetForm;
const allFields = [];
if (resetFormFields.length !== 0 || resetFormRefs.length !== 0) {
const fieldIds = new Set(resetFormRefs);
for (const fieldName of resetFormFields) {
const fields = this._fieldObjects[fieldName] || [];
for (const {
id
} of fields) {
fieldIds.add(id);
}
}
for (const fields of Object.values(this._fieldObjects)) {
for (const field of fields) {
if (fieldIds.has(field.id) === include) {
allFields.push(field);
}
}
}
} else {
for (const fields of Object.values(this._fieldObjects)) {
allFields.push(...fields);
}
}
const storage = this.annotationStorage;
const allIds = [];
for (const field of allFields) {
const {
id
} = field;
allIds.push(id);
switch (field.type) {
case "text":
{
const value = field.defaultValue || "";
storage.setValue(id, {
value
});
break;
}
case "checkbox":
case "radiobutton":
{
const value = field.defaultValue === field.exportValues;
storage.setValue(id, {
value
});
break;
}
case "combobox":
case "listbox":
{
const value = field.defaultValue || "";
storage.setValue(id, {
value
});
break;
}
default:
continue;
}
const domElement = document.querySelector(`[data-element-id="${id}"]`);
if (!domElement) {
continue;
} else if (!GetElementsByNameSet.has(domElement)) {
warn(`_bindResetFormAction - element not allowed: ${id}`);
continue;
}
domElement.dispatchEvent(new Event("resetform"));
}
if (this.enableScripting) {
this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
source: this,
detail: {
id: "app",
ids: allIds,
name: "ResetForm"
}
});
}
return false;
};
}
}
class TextAnnotationElement extends AnnotationElement {
constructor(parameters) {
super(parameters, {
isRenderable: true
});
}
render() {
this.container.classList.add("textAnnotation");
const image = document.createElement("img");
image.src = this.imageResourcesPath + "annotation-" + this.data.name.toLowerCase() + ".svg";
image.setAttribute("data-l10n-id", "pdfjs-text-annotation-type");
image.setAttribute("data-l10n-args", JSON.stringify({
type: this.data.name
}));
if (!this.data.popupRef && this.hasPopupData) {
this._createPopup();
}
this.container.append(image);
return this.container;
}
}
class WidgetAnnotationElement extends AnnotationElement {
render() {
return this.container;
}
showElementAndHideCanvas(element) {
if (this.data.hasOwnCanvas) {
if (element.previousSibling?.nodeName === "CANVAS") {
element.previousSibling.hidden = true;
}
element.hidden = false;
}
}
_getKeyModifier(event) {
return util_FeatureTest.platform.isMac ? event.metaKey : event.ctrlKey;
}
_setEventListener(element, elementData, baseName, eventName, valueGetter) {
if (baseName.includes("mouse")) {
element.addEventListener(baseName, event => {
this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
source: this,
detail: {
id: this.data.id,
name: eventName,
value: valueGetter(event),
shift: event.shiftKey,
modifier: this._getKeyModifier(event)
}
});
});
} else {
element.addEventListener(baseName, event => {
if (baseName === "blur") {
if (!elementData.focused || !event.relatedTarget) {
return;
}
elementData.focused = false;
} else if (baseName === "focus") {
if (elementData.focused) {
return;
}
elementData.focused = true;
}
if (!valueGetter) {
return;
}
this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
source: this,
detail: {
id: this.data.id,
name: eventName,
value: valueGetter(event)
}
});
});
}
}
_setEventListeners(element, elementData, names, getter) {
for (const [baseName, eventName] of names) {
if (eventName === "Action" || this.data.actions?.[eventName]) {
if (eventName === "Focus" || eventName === "Blur") {
elementData ||= {
focused: false
};
}
this._setEventListener(element, elementData, baseName, eventName, getter);
if (eventName === "Focus" && !this.data.actions?.Blur) {
this._setEventListener(element, elementData, "blur", "Blur", null);
} else if (eventName === "Blur" && !this.data.actions?.Focus) {
this._setEventListener(element, elementData, "focus", "Focus", null);
}
}
}
}
_setBackgroundColor(element) {
const color = this.data.backgroundColor || null;
element.style.backgroundColor = color === null ? "transparent" : Util.makeHexColor(color[0], color[1], color[2]);
}
_setTextStyle(element) {
const TEXT_ALIGNMENT = ["left", "center", "right"];
const {
fontColor
} = this.data.defaultAppearanceData;
const fontSize = this.data.defaultAppearanceData.fontSize || annotation_layer_DEFAULT_FONT_SIZE;
const style = element.style;
let computedFontSize;
const BORDER_SIZE = 2;
const roundToOneDecimal = x => Math.round(10 * x) / 10;
if (this.data.multiLine) {
const height = Math.abs(this.data.rect[3] - this.data.rect[1] - BORDER_SIZE);
const numberOfLines = Math.round(height / (LINE_FACTOR * fontSize)) || 1;
const lineHeight = height / numberOfLines;
computedFontSize = Math.min(fontSize, roundToOneDecimal(lineHeight / LINE_FACTOR));
} else {
const height = Math.abs(this.data.rect[3] - this.data.rect[1] - BORDER_SIZE);
computedFontSize = Math.min(fontSize, roundToOneDecimal(height / LINE_FACTOR));
}
style.fontSize = `calc(${computedFontSize}px * var(--total-scale-factor))`;
style.color = Util.makeHexColor(fontColor[0], fontColor[1], fontColor[2]);
if (this.data.textAlignment !== null) {
style.textAlign = TEXT_ALIGNMENT[this.data.textAlignment];
}
}
_setRequired(element, isRequired) {
if (isRequired) {
element.setAttribute("required", true);
} else {
element.removeAttribute("required");
}
element.setAttribute("aria-required", isRequired);
}
}
class TextWidgetAnnotationElement extends WidgetAnnotationElement {
constructor(parameters) {
const isRenderable = parameters.renderForms || parameters.data.hasOwnCanvas || !parameters.data.hasAppearance && !!parameters.data.fieldValue;
super(parameters, {
isRenderable
});
}
setPropertyOnSiblings(base, key, value, keyInStorage) {
const storage = this.annotationStorage;
for (const element of this._getElementsByName(base.name, base.id)) {
if (element.domElement) {
element.domElement[key] = value;
}
storage.setValue(element.id, {
[keyInStorage]: value
});
}
}
render() {
const storage = this.annotationStorage;
const id = this.data.id;
this.container.classList.add("textWidgetAnnotation");
let element = null;
if (this.renderForms) {
const storedData = storage.getValue(id, {
value: this.data.fieldValue
});
let textContent = storedData.value || "";
const maxLen = storage.getValue(id, {
charLimit: this.data.maxLen
}).charLimit;
if (maxLen && textContent.length > maxLen) {
textContent = textContent.slice(0, maxLen);
}
let fieldFormattedValues = storedData.formattedValue || this.data.textContent?.join("\n") || null;
if (fieldFormattedValues && this.data.comb) {
fieldFormattedValues = fieldFormattedValues.replaceAll(/\s+/g, "");
}
const elementData = {
userValue: textContent,
formattedValue: fieldFormattedValues,
lastCommittedValue: null,
commitKey: 1,
focused: false
};
if (this.data.multiLine) {
element = document.createElement("textarea");
element.textContent = fieldFormattedValues ?? textContent;
if (this.data.doNotScroll) {
element.style.overflowY = "hidden";
}
} else {
element = document.createElement("input");
element.type = this.data.password ? "password" : "text";
element.setAttribute("value", fieldFormattedValues ?? textContent);
if (this.data.doNotScroll) {
element.style.overflowX = "hidden";
}
}
if (this.data.hasOwnCanvas) {
element.hidden = true;
}
GetElementsByNameSet.add(element);
element.setAttribute("data-element-id", id);
element.disabled = this.data.readOnly;
element.name = this.data.fieldName;
element.tabIndex = DEFAULT_TAB_INDEX;
this._setRequired(element, this.data.required);
if (maxLen) {
element.maxLength = maxLen;
}
element.addEventListener("input", event => {
storage.setValue(id, {
value: event.target.value
});
this.setPropertyOnSiblings(element, "value", event.target.value, "value");
elementData.formattedValue = null;
});
element.addEventListener("resetform", event => {
const defaultValue = this.data.defaultFieldValue ?? "";
element.value = elementData.userValue = defaultValue;
elementData.formattedValue = null;
});
let blurListener = event => {
const {
formattedValue
} = elementData;
if (formattedValue !== null && formattedValue !== undefined) {
event.target.value = formattedValue;
}
event.target.scrollLeft = 0;
};
if (this.enableScripting && this.hasJSActions) {
element.addEventListener("focus", event => {
if (elementData.focused) {
return;
}
const {
target
} = event;
if (elementData.userValue) {
target.value = elementData.userValue;
}
elementData.lastCommittedValue = target.value;
elementData.commitKey = 1;
if (!this.data.actions?.Focus) {
elementData.focused = true;
}
});
element.addEventListener("updatefromsandbox", jsEvent => {
this.showElementAndHideCanvas(jsEvent.target);
const actions = {
value(event) {
elementData.userValue = event.detail.value ?? "";
storage.setValue(id, {
value: elementData.userValue.toString()
});
event.target.value = elementData.userValue;
},
formattedValue(event) {
const {
formattedValue
} = event.detail;
elementData.formattedValue = formattedValue;
if (formattedValue !== null && formattedValue !== undefined && event.target !== document.activeElement) {
event.target.value = formattedValue;
}
storage.setValue(id, {
formattedValue
});
},
selRange(event) {
event.target.setSelectionRange(...event.detail.selRange);
},
charLimit: event => {
const {
charLimit
} = event.detail;
const {
target
} = event;
if (charLimit === 0) {
target.removeAttribute("maxLength");
return;
}
target.setAttribute("maxLength", charLimit);
let value = elementData.userValue;
if (!value || value.length <= charLimit) {
return;
}
value = value.slice(0, charLimit);
target.value = elementData.userValue = value;
storage.setValue(id, {
value
});
this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
source: this,
detail: {
id,
name: "Keystroke",
value,
willCommit: true,
commitKey: 1,
selStart: target.selectionStart,
selEnd: target.selectionEnd
}
});
}
};
this._dispatchEventFromSandbox(actions, jsEvent);
});
element.addEventListener("keydown", event => {
elementData.commitKey = 1;
let commitKey = -1;
if (event.key === "Escape") {
commitKey = 0;
} else if (event.key === "Enter" && !this.data.multiLine) {
commitKey = 2;
} else if (event.key === "Tab") {
elementData.commitKey = 3;
}
if (commitKey === -1) {
return;
}
const {
value
} = event.target;
if (elementData.lastCommittedValue === value) {
return;
}
elementData.lastCommittedValue = value;
elementData.userValue = value;
this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
source: this,
detail: {
id,
name: "Keystroke",
value,
willCommit: true,
commitKey,
selStart: event.target.selectionStart,
selEnd: event.target.selectionEnd
}
});
});
const _blurListener = blurListener;
blurListener = null;
element.addEventListener("blur", event => {
if (!elementData.focused || !event.relatedTarget) {
return;
}
if (!this.data.actions?.Blur) {
elementData.focused = false;
}
const {
value
} = event.target;
elementData.userValue = value;
if (elementData.lastCommittedValue !== value) {
this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
source: this,
detail: {
id,
name: "Keystroke",
value,
willCommit: true,
commitKey: elementData.commitKey,
selStart: event.target.selectionStart,
selEnd: event.target.selectionEnd
}
});
}
_blurListener(event);
});
if (this.data.actions?.Keystroke) {
element.addEventListener("beforeinput", event => {
elementData.lastCommittedValue = null;
const {
data,
target
} = event;
const {
value,
selectionStart,
selectionEnd
} = target;
let selStart = selectionStart,
selEnd = selectionEnd;
switch (event.inputType) {
case "deleteWordBackward":
{
const match = value.substring(0, selectionStart).match(/\w*[^\w]*$/);
if (match) {
selStart -= match[0].length;
}
break;
}
case "deleteWordForward":
{
const match = value.substring(selectionStart).match(/^[^\w]*\w*/);
if (match) {
selEnd += match[0].length;
}
break;
}
case "deleteContentBackward":
if (selectionStart === selectionEnd) {
selStart -= 1;
}
break;
case "deleteContentForward":
if (selectionStart === selectionEnd) {
selEnd += 1;
}
break;
}
event.preventDefault();
this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
source: this,
detail: {
id,
name: "Keystroke",
value,
change: data || "",
willCommit: false,
selStart,
selEnd
}
});
});
}
this._setEventListeners(element, elementData, [["focus", "Focus"], ["blur", "Blur"], ["mousedown", "Mouse Down"], ["mouseenter", "Mouse Enter"], ["mouseleave", "Mouse Exit"], ["mouseup", "Mouse Up"]], event => event.target.value);
}
if (blurListener) {
element.addEventListener("blur", blurListener);
}
if (this.data.comb) {
const fieldWidth = this.data.rect[2] - this.data.rect[0];
const combWidth = fieldWidth / maxLen;
element.classList.add("comb");
element.style.letterSpacing = `calc(${combWidth}px * var(--total-scale-factor) - 1ch)`;
}
} else {
element = document.createElement("div");
element.textContent = this.data.fieldValue;
element.style.verticalAlign = "middle";
element.style.display = "table-cell";
if (this.data.hasOwnCanvas) {
element.hidden = true;
}
}
this._setTextStyle(element);
this._setBackgroundColor(element);
this._setDefaultPropertiesFromJS(element);
this.container.append(element);
return this.container;
}
}
class SignatureWidgetAnnotationElement extends WidgetAnnotationElement {
constructor(parameters) {
super(parameters, {
isRenderable: !!parameters.data.hasOwnCanvas
});
}
}
class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement {
constructor(parameters) {
super(parameters, {
isRenderable: parameters.renderForms
});
}
render() {
const storage = this.annotationStorage;
const data = this.data;
const id = data.id;
let value = storage.getValue(id, {
value: data.exportValue === data.fieldValue
}).value;
if (typeof value === "string") {
value = value !== "Off";
storage.setValue(id, {
value
});
}
this.container.classList.add("buttonWidgetAnnotation", "checkBox");
const element = document.createElement("input");
GetElementsByNameSet.add(element);
element.setAttribute("data-element-id", id);
element.disabled = data.readOnly;
this._setRequired(element, this.data.required);
element.type = "checkbox";
element.name = data.fieldName;
if (value) {
element.setAttribute("checked", true);
}
element.setAttribute("exportValue", data.exportValue);
element.tabIndex = DEFAULT_TAB_INDEX;
element.addEventListener("change", event => {
const {
name,
checked
} = event.target;
for (const checkbox of this._getElementsByName(name, id)) {
const curChecked = checked && checkbox.exportValue === data.exportValue;
if (checkbox.domElement) {
checkbox.domElement.checked = curChecked;
}
storage.setValue(checkbox.id, {
value: curChecked
});
}
storage.setValue(id, {
value: checked
});
});
element.addEventListener("resetform", event => {
const defaultValue = data.defaultFieldValue || "Off";
event.target.checked = defaultValue === data.exportValue;
});
if (this.enableScripting && this.hasJSActions) {
element.addEventListener("updatefromsandbox", jsEvent => {
const actions = {
value(event) {
event.target.checked = event.detail.value !== "Off";
storage.setValue(id, {
value: event.target.checked
});
}
};
this._dispatchEventFromSandbox(actions, jsEvent);
});
this._setEventListeners(element, null, [["change", "Validate"], ["change", "Action"], ["focus", "Focus"], ["blur", "Blur"], ["mousedown", "Mouse Down"], ["mouseenter", "Mouse Enter"], ["mouseleave", "Mouse Exit"], ["mouseup", "Mouse Up"]], event => event.target.checked);
}
this._setBackgroundColor(element);
this._setDefaultPropertiesFromJS(element);
this.container.append(element);
return this.container;
}
}
class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement {
constructor(parameters) {
super(parameters, {
isRenderable: parameters.renderForms
});
}
render() {
this.container.classList.add("buttonWidgetAnnotation", "radioButton");
const storage = this.annotationStorage;
const data = this.data;
const id = data.id;
let value = storage.getValue(id, {
value: data.fieldValue === data.buttonValue
}).value;
if (typeof value === "string") {
value = value !== data.buttonValue;
storage.setValue(id, {
value
});
}
if (value) {
for (const radio of this._getElementsByName(data.fieldName, id)) {
storage.setValue(radio.id, {
value: false
});
}
}
const element = document.createElement("input");
GetElementsByNameSet.add(element);
element.setAttribute("data-element-id", id);
element.disabled = data.readOnly;
this._setRequired(element, this.data.required);
element.type = "radio";
element.name = data.fieldName;
if (value) {
element.setAttribute("checked", true);
}
element.tabIndex = DEFAULT_TAB_INDEX;
element.addEventListener("change", event => {
const {
name,
checked
} = event.target;
for (const radio of this._getElementsByName(name, id)) {
storage.setValue(radio.id, {
value: false
});
}
storage.setValue(id, {
value: checked
});
});
element.addEventListener("resetform", event => {
const defaultValue = data.defaultFieldValue;
event.target.checked = defaultValue !== null && defaultValue !== undefined && defaultValue === data.buttonValue;
});
if (this.enableScripting && this.hasJSActions) {
const pdfButtonValue = data.buttonValue;
element.addEventListener("updatefromsandbox", jsEvent => {
const actions = {
value: event => {
const checked = pdfButtonValue === event.detail.value;
for (const radio of this._getElementsByName(event.target.name)) {
const curChecked = checked && radio.id === id;
if (radio.domElement) {
radio.domElement.checked = curChecked;
}
storage.setValue(radio.id, {
value: curChecked
});
}
}
};
this._dispatchEventFromSandbox(actions, jsEvent);
});
this._setEventListeners(element, null, [["change", "Validate"], ["change", "Action"], ["focus", "Focus"], ["blur", "Blur"], ["mousedown", "Mouse Down"], ["mouseenter", "Mouse Enter"], ["mouseleave", "Mouse Exit"], ["mouseup", "Mouse Up"]], event => event.target.checked);
}
this._setBackgroundColor(element);
this._setDefaultPropertiesFromJS(element);
this.container.append(element);
return this.container;
}
}
class PushButtonWidgetAnnotationElement extends LinkAnnotationElement {
constructor(parameters) {
super(parameters, {
ignoreBorder: parameters.data.hasAppearance
});
}
render() {
const container = super.render();
container.classList.add("buttonWidgetAnnotation", "pushButton");
const linkElement = container.lastChild;
if (this.enableScripting && this.hasJSActions && linkElement) {
this._setDefaultPropertiesFromJS(linkElement);
linkElement.addEventListener("updatefromsandbox", jsEvent => {
this._dispatchEventFromSandbox({}, jsEvent);
});
}
return container;
}
}
class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
constructor(parameters) {
super(parameters, {
isRenderable: parameters.renderForms
});
}
render() {
this.container.classList.add("choiceWidgetAnnotation");
const storage = this.annotationStorage;
const id = this.data.id;
const storedData = storage.getValue(id, {
value: this.data.fieldValue
});
const selectElement = document.createElement("select");
GetElementsByNameSet.add(selectElement);
selectElement.setAttribute("data-element-id", id);
selectElement.disabled = this.data.readOnly;
this._setRequired(selectElement, this.data.required);
selectElement.name = this.data.fieldName;
selectElement.tabIndex = DEFAULT_TAB_INDEX;
let addAnEmptyEntry = this.data.combo && this.data.options.length > 0;
if (!this.data.combo) {
selectElement.size = this.data.options.length;
if (this.data.multiSelect) {
selectElement.multiple = true;
}
}
selectElement.addEventListener("resetform", event => {
const defaultValue = this.data.defaultFieldValue;
for (const option of selectElement.options) {
option.selected = option.value === defaultValue;
}
});
for (const option of this.data.options) {
const optionElement = document.createElement("option");
optionElement.textContent = option.displayValue;
optionElement.value = option.exportValue;
if (storedData.value.includes(option.exportValue)) {
optionElement.setAttribute("selected", true);
addAnEmptyEntry = false;
}
selectElement.append(optionElement);
}
let removeEmptyEntry = null;
if (addAnEmptyEntry) {
const noneOptionElement = document.createElement("option");
noneOptionElement.value = " ";
noneOptionElement.setAttribute("hidden", true);
noneOptionElement.setAttribute("selected", true);
selectElement.prepend(noneOptionElement);
removeEmptyEntry = () => {
noneOptionElement.remove();
selectElement.removeEventListener("input", removeEmptyEntry);
removeEmptyEntry = null;
};
selectElement.addEventListener("input", removeEmptyEntry);
}
const getValue = isExport => {
const name = isExport ? "value" : "textContent";
const {
options,
multiple
} = selectElement;
if (!multiple) {
return options.selectedIndex === -1 ? null : options[options.selectedIndex][name];
}
return Array.prototype.filter.call(options, option => option.selected).map(option => option[name]);
};
let selectedValues = getValue(false);
const getItems = event => {
const options = event.target.options;
return Array.prototype.map.call(options, option => ({
displayValue: option.textContent,
exportValue: option.value
}));
};
if (this.enableScripting && this.hasJSActions) {
selectElement.addEventListener("updatefromsandbox", jsEvent => {
const actions = {
value(event) {
removeEmptyEntry?.();
const value = event.detail.value;
const values = new Set(Array.isArray(value) ? value : [value]);
for (const option of selectElement.options) {
option.selected = values.has(option.value);
}
storage.setValue(id, {
value: getValue(true)
});
selectedValues = getValue(false);
},
multipleSelection(event) {
selectElement.multiple = true;
},
remove(event) {
const options = selectElement.options;
const index = event.detail.remove;
options[index].selected = false;
selectElement.remove(index);
if (options.length > 0) {
const i = Array.prototype.findIndex.call(options, option => option.selected);
if (i === -1) {
options[0].selected = true;
}
}
storage.setValue(id, {
value: getValue(true),
items: getItems(event)
});
selectedValues = getValue(false);
},
clear(event) {
while (selectElement.length !== 0) {
selectElement.remove(0);
}
storage.setValue(id, {
value: null,
items: []
});
selectedValues = getValue(false);
},
insert(event) {
const {
index,
displayValue,
exportValue
} = event.detail.insert;
const selectChild = selectElement.children[index];
const optionElement = document.createElement("option");
optionElement.textContent = displayValue;
optionElement.value = exportValue;
if (selectChild) {
selectChild.before(optionElement);
} else {
selectElement.append(optionElement);
}
storage.setValue(id, {
value: getValue(true),
items: getItems(event)
});
selectedValues = getValue(false);
},
items(event) {
const {
items
} = event.detail;
while (selectElement.length !== 0) {
selectElement.remove(0);
}
for (const item of items) {
const {
displayValue,
exportValue
} = item;
const optionElement = document.createElement("option");
optionElement.textContent = displayValue;
optionElement.value = exportValue;
selectElement.append(optionElement);
}
if (selectElement.options.length > 0) {
selectElement.options[0].selected = true;
}
storage.setValue(id, {
value: getValue(true),
items: getItems(event)
});
selectedValues = getValue(false);
},
indices(event) {
const indices = new Set(event.detail.indices);
for (const option of event.target.options) {
option.selected = indices.has(option.index);
}
storage.setValue(id, {
value: getValue(true)
});
selectedValues = getValue(false);
},
editable(event) {
event.target.disabled = !event.detail.editable;
}
};
this._dispatchEventFromSandbox(actions, jsEvent);
});
selectElement.addEventListener("input", event => {
const exportValue = getValue(true);
const change = getValue(false);
storage.setValue(id, {
value: exportValue
});
event.preventDefault();
this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
source: this,
detail: {
id,
name: "Keystroke",
value: selectedValues,
change,
changeEx: exportValue,
willCommit: false,
commitKey: 1,
keyDown: false
}
});
});
this._setEventListeners(selectElement, null, [["focus", "Focus"], ["blur", "Blur"], ["mousedown", "Mouse Down"], ["mouseenter", "Mouse Enter"], ["mouseleave", "Mouse Exit"], ["mouseup", "Mouse Up"], ["input", "Action"], ["input", "Validate"]], event => event.target.value);
} else {
selectElement.addEventListener("input", function (event) {
storage.setValue(id, {
value: getValue(true)
});
});
}
if (this.data.combo) {
this._setTextStyle(selectElement);
} else {}
this._setBackgroundColor(selectElement);
this._setDefaultPropertiesFromJS(selectElement);
this.container.append(selectElement);
return this.container;
}
}
class PopupAnnotationElement extends AnnotationElement {
constructor(parameters) {
const {
data,
elements
} = parameters;
super(parameters, {
isRenderable: AnnotationElement._hasPopupData(data)
});
this.elements = elements;
this.popup = null;
}
render() {
this.container.classList.add("popupAnnotation");
const popup = this.popup = new PopupElement({
container: this.container,
color: this.data.color,
titleObj: this.data.titleObj,
modificationDate: this.data.modificationDate,
contentsObj: this.data.contentsObj,
richText: this.data.richText,
rect: this.data.rect,
parentRect: this.data.parentRect || null,
parent: this.parent,
elements: this.elements,
open: this.data.open
});
const elementIds = [];
for (const element of this.elements) {
element.popup = popup;
element.container.ariaHasPopup = "dialog";
elementIds.push(element.data.id);
element.addHighlightArea();
}
this.container.setAttribute("aria-controls", elementIds.map(id => `${AnnotationPrefix}${id}`).join(","));
return this.container;
}
}
class PopupElement {
#boundKeyDown = this.#keyDown.bind(this);
#boundHide = this.#hide.bind(this);
#boundShow = this.#show.bind(this);
#boundToggle = this.#toggle.bind(this);
#color = null;
#container = null;
#contentsObj = null;
#dateObj = null;
#elements = null;
#parent = null;
#parentRect = null;
#pinned = false;
#popup = null;
#position = null;
#rect = null;
#richText = null;
#titleObj = null;
#updates = null;
#wasVisible = false;
constructor({
container,
color,
elements,
titleObj,
modificationDate,
contentsObj,
richText,
parent,
rect,
parentRect,
open
}) {
this.#container = container;
this.#titleObj = titleObj;
this.#contentsObj = contentsObj;
this.#richText = richText;
this.#parent = parent;
this.#color = color;
this.#rect = rect;
this.#parentRect = parentRect;
this.#elements = elements;
this.#dateObj = PDFDateString.toDateObject(modificationDate);
this.trigger = elements.flatMap(e => e.getElementsToTriggerPopup());
for (const element of this.trigger) {
element.addEventListener("click", this.#boundToggle);
element.addEventListener("mouseenter", this.#boundShow);
element.addEventListener("mouseleave", this.#boundHide);
element.classList.add("popupTriggerArea");
}
for (const element of elements) {
element.container?.addEventListener("keydown", this.#boundKeyDown);
}
this.#container.hidden = true;
if (open) {
this.#toggle();
}
}
render() {
if (this.#popup) {
return;
}
const popup = this.#popup = document.createElement("div");
popup.className = "popup";
if (this.#color) {
const baseColor = popup.style.outlineColor = Util.makeHexColor(...this.#color);
popup.style.backgroundColor = `color-mix(in srgb, ${baseColor} 30%, white)`;
}
const header = document.createElement("span");
header.className = "header";
const title = document.createElement("h1");
header.append(title);
({
dir: title.dir,
str: title.textContent
} = this.#titleObj);
popup.append(header);
if (this.#dateObj) {
const modificationDate = document.createElement("span");
modificationDate.classList.add("popupDate");
modificationDate.setAttribute("data-l10n-id", "pdfjs-annotation-date-time-string");
modificationDate.setAttribute("data-l10n-args", JSON.stringify({
dateObj: this.#dateObj.valueOf()
}));
header.append(modificationDate);
}
const html = this.#html;
if (html) {
XfaLayer.render({
xfaHtml: html,
intent: "richText",
div: popup
});
popup.lastChild.classList.add("richText", "popupContent");
} else {
const contents = this._formatContents(this.#contentsObj);
popup.append(contents);
}
this.#container.append(popup);
}
get #html() {
const richText = this.#richText;
const contentsObj = this.#contentsObj;
if (richText?.str && (!contentsObj?.str || contentsObj.str === richText.str)) {
return this.#richText.html || null;
}
return null;
}
get #fontSize() {
return this.#html?.attributes?.style?.fontSize || 0;
}
get #fontColor() {
return this.#html?.attributes?.style?.color || null;
}
#makePopupContent(text) {
const popupLines = [];
const popupContent = {
str: text,
html: {
name: "div",
attributes: {
dir: "auto"
},
children: [{
name: "p",
children: popupLines
}]
}
};
const lineAttributes = {
style: {
color: this.#fontColor,
fontSize: this.#fontSize ? `calc(${this.#fontSize}px * var(--total-scale-factor))` : ""
}
};
for (const line of text.split("\n")) {
popupLines.push({
name: "span",
value: line,
attributes: lineAttributes
});
}
return popupContent;
}
_formatContents({
str,
dir
}) {
const p = document.createElement("p");
p.classList.add("popupContent");
p.dir = dir;
const lines = str.split(/(?:\r\n?|\n)/);
for (let i = 0, ii = lines.length; i < ii; ++i) {
const line = lines[i];
p.append(document.createTextNode(line));
if (i < ii - 1) {
p.append(document.createElement("br"));
}
}
return p;
}
#keyDown(event) {
if (event.altKey || event.shiftKey || event.ctrlKey || event.metaKey) {
return;
}
if (event.key === "Enter" || event.key === "Escape" && this.#pinned) {
this.#toggle();
}
}
updateEdited({
rect,
popupContent
}) {
this.#updates ||= {
contentsObj: this.#contentsObj,
richText: this.#richText
};
if (rect) {
this.#position = null;
}
if (popupContent) {
this.#richText = this.#makePopupContent(popupContent);
this.#contentsObj = null;
}
this.#popup?.remove();
this.#popup = null;
}
resetEdited() {
if (!this.#updates) {
return;
}
({
contentsObj: this.#contentsObj,
richText: this.#richText
} = this.#updates);
this.#updates = null;
this.#popup?.remove();
this.#popup = null;
this.#position = null;
}
#setPosition() {
if (this.#position !== null) {
return;
}
const {
page: {
view
},
viewport: {
rawDims: {
pageWidth,
pageHeight,
pageX,
pageY
}
}
} = this.#parent;
let useParentRect = !!this.#parentRect;
let rect = useParentRect ? this.#parentRect : this.#rect;
for (const element of this.#elements) {
if (!rect || Util.intersect(element.data.rect, rect) !== null) {
rect = element.data.rect;
useParentRect = true;
break;
}
}
const normalizedRect = Util.normalizeRect([rect[0], view[3] - rect[1] + view[1], rect[2], view[3] - rect[3] + view[1]]);
const HORIZONTAL_SPACE_AFTER_ANNOTATION = 5;
const parentWidth = useParentRect ? rect[2] - rect[0] + HORIZONTAL_SPACE_AFTER_ANNOTATION : 0;
const popupLeft = normalizedRect[0] + parentWidth;
const popupTop = normalizedRect[1];
this.#position = [100 * (popupLeft - pageX) / pageWidth, 100 * (popupTop - pageY) / pageHeight];
const {
style
} = this.#container;
style.left = `${this.#position[0]}%`;
style.top = `${this.#position[1]}%`;
}
#toggle() {
this.#pinned = !this.#pinned;
if (this.#pinned) {
this.#show();
this.#container.addEventListener("click", this.#boundToggle);
this.#container.addEventListener("keydown", this.#boundKeyDown);
} else {
this.#hide();
this.#container.removeEventListener("click", this.#boundToggle);
this.#container.removeEventListener("keydown", this.#boundKeyDown);
}
}
#show() {
if (!this.#popup) {
this.render();
}
if (!this.isVisible) {
this.#setPosition();
this.#container.hidden = false;
this.#container.style.zIndex = parseInt(this.#container.style.zIndex) + 1000;
} else if (this.#pinned) {
this.#container.classList.add("focused");
}
}
#hide() {
this.#container.classList.remove("focused");
if (this.#pinned || !this.isVisible) {
return;
}
this.#container.hidden = true;
this.#container.style.zIndex = parseInt(this.#container.style.zIndex) - 1000;
}
forceHide() {
this.#wasVisible = this.isVisible;
if (!this.#wasVisible) {
return;
}
this.#container.hidden = true;
}
maybeShow() {
if (!this.#wasVisible) {
return;
}
if (!this.#popup) {
this.#show();
}
this.#wasVisible = false;
this.#container.hidden = false;
}
get isVisible() {
return this.#container.hidden === false;
}
}
class FreeTextAnnotationElement extends AnnotationElement {
constructor(parameters) {
super(parameters, {
isRenderable: true,
ignoreBorder: true
});
this.textContent = parameters.data.textContent;
this.textPosition = parameters.data.textPosition;
this.annotationEditorType = AnnotationEditorType.FREETEXT;
}
render() {
this.container.classList.add("freeTextAnnotation");
if (this.textContent) {
const content = document.createElement("div");
content.classList.add("annotationTextContent");
content.setAttribute("role", "comment");
for (const line of this.textContent) {
const lineSpan = document.createElement("span");
lineSpan.textContent = line;
content.append(lineSpan);
}
this.container.append(content);
}
if (!this.data.popupRef && this.hasPopupData) {
this._createPopup();
}
this._editOnDoubleClick();
return this.container;
}
}
class LineAnnotationElement extends AnnotationElement {
#line = null;
constructor(parameters) {
super(parameters, {
isRenderable: true,
ignoreBorder: true
});
}
render() {
this.container.classList.add("lineAnnotation");
const {
data,
width,
height
} = this;
const svg = this.svgFactory.create(width, height, true);
const line = this.#line = this.svgFactory.createElement("svg:line");
line.setAttribute("x1", data.rect[2] - data.lineCoordinates[0]);
line.setAttribute("y1", data.rect[3] - data.lineCoordinates[1]);
line.setAttribute("x2", data.rect[2] - data.lineCoordinates[2]);
line.setAttribute("y2", data.rect[3] - data.lineCoordinates[3]);
line.setAttribute("stroke-width", data.borderStyle.width || 1);
line.setAttribute("stroke", "transparent");
line.setAttribute("fill", "transparent");
svg.append(line);
this.container.append(svg);
if (!data.popupRef && this.hasPopupData) {
this._createPopup();
}
return this.container;
}
getElementsToTriggerPopup() {
return this.#line;
}
addHighlightArea() {
this.container.classList.add("highlightArea");
}
}
class SquareAnnotationElement extends AnnotationElement {
#square = null;
constructor(parameters) {
super(parameters, {
isRenderable: true,
ignoreBorder: true
});
}
render() {
this.container.classList.add("squareAnnotation");
const {
data,
width,
height
} = this;
const svg = this.svgFactory.create(width, height, true);
const borderWidth = data.borderStyle.width;
const square = this.#square = this.svgFactory.createElement("svg:rect");
square.setAttribute("x", borderWidth / 2);
square.setAttribute("y", borderWidth / 2);
square.setAttribute("width", width - borderWidth);
square.setAttribute("height", height - borderWidth);
square.setAttribute("stroke-width", borderWidth || 1);
square.setAttribute("stroke", "transparent");
square.setAttribute("fill", "transparent");
svg.append(square);
this.container.append(svg);
if (!data.popupRef && this.hasPopupData) {
this._createPopup();
}
return this.container;
}
getElementsToTriggerPopup() {
return this.#square;
}
addHighlightArea() {
this.container.classList.add("highlightArea");
}
}
class CircleAnnotationElement extends AnnotationElement {
#circle = null;
constructor(parameters) {
super(parameters, {
isRenderable: true,
ignoreBorder: true
});
}
render() {
this.container.classList.add("circleAnnotation");
const {
data,
width,
height
} = this;
const svg = this.svgFactory.create(width, height, true);
const borderWidth = data.borderStyle.width;
const circle = this.#circle = this.svgFactory.createElement("svg:ellipse");
circle.setAttribute("cx", width / 2);
circle.setAttribute("cy", height / 2);
circle.setAttribute("rx", width / 2 - borderWidth / 2);
circle.setAttribute("ry", height / 2 - borderWidth / 2);
circle.setAttribute("stroke-width", borderWidth || 1);
circle.setAttribute("stroke", "transparent");
circle.setAttribute("fill", "transparent");
svg.append(circle);
this.container.append(svg);
if (!data.popupRef && this.hasPopupData) {
this._createPopup();
}
return this.container;
}
getElementsToTriggerPopup() {
return this.#circle;
}
addHighlightArea() {
this.container.classList.add("highlightArea");
}
}
class PolylineAnnotationElement extends AnnotationElement {
#polyline = null;
constructor(parameters) {
super(parameters, {
isRenderable: true,
ignoreBorder: true
});
this.containerClassName = "polylineAnnotation";
this.svgElementName = "svg:polyline";
}
render() {
this.container.classList.add(this.containerClassName);
const {
data: {
rect,
vertices,
borderStyle,
popupRef
},
width,
height
} = this;
if (!vertices) {
return this.container;
}
const svg = this.svgFactory.create(width, height, true);
let points = [];
for (let i = 0, ii = vertices.length; i < ii; i += 2) {
const x = vertices[i] - rect[0];
const y = rect[3] - vertices[i + 1];
points.push(`${x},${y}`);
}
points = points.join(" ");
const polyline = this.#polyline = this.svgFactory.createElement(this.svgElementName);
polyline.setAttribute("points", points);
polyline.setAttribute("stroke-width", borderStyle.width || 1);
polyline.setAttribute("stroke", "transparent");
polyline.setAttribute("fill", "transparent");
svg.append(polyline);
this.container.append(svg);
if (!popupRef && this.hasPopupData) {
this._createPopup();
}
return this.container;
}
getElementsToTriggerPopup() {
return this.#polyline;
}
addHighlightArea() {
this.container.classList.add("highlightArea");
}
}
class PolygonAnnotationElement extends PolylineAnnotationElement {
constructor(parameters) {
super(parameters);
this.containerClassName = "polygonAnnotation";
this.svgElementName = "svg:polygon";
}
}
class CaretAnnotationElement extends AnnotationElement {
constructor(parameters) {
super(parameters, {
isRenderable: true,
ignoreBorder: true
});
}
render() {
this.container.classList.add("caretAnnotation");
if (!this.data.popupRef && this.hasPopupData) {
this._createPopup();
}
return this.container;
}
}
class InkAnnotationElement extends AnnotationElement {
#polylinesGroupElement = null;
#polylines = [];
constructor(parameters) {
super(parameters, {
isRenderable: true,
ignoreBorder: true
});
this.containerClassName = "inkAnnotation";
this.svgElementName = "svg:polyline";
this.annotationEditorType = this.data.it === "InkHighlight" ? AnnotationEditorType.HIGHLIGHT : AnnotationEditorType.INK;
}
#getTransform(rotation, rect) {
switch (rotation) {
case 90:
return {
transform: `rotate(90) translate(${-rect[0]},${rect[1]}) scale(1,-1)`,
width: rect[3] - rect[1],
height: rect[2] - rect[0]
};
case 180:
return {
transform: `rotate(180) translate(${-rect[2]},${rect[1]}) scale(1,-1)`,
width: rect[2] - rect[0],
height: rect[3] - rect[1]
};
case 270:
return {
transform: `rotate(270) translate(${-rect[2]},${rect[3]}) scale(1,-1)`,
width: rect[3] - rect[1],
height: rect[2] - rect[0]
};
default:
return {
transform: `translate(${-rect[0]},${rect[3]}) scale(1,-1)`,
width: rect[2] - rect[0],
height: rect[3] - rect[1]
};
}
}
render() {
this.container.classList.add(this.containerClassName);
const {
data: {
rect,
rotation,
inkLists,
borderStyle,
popupRef
}
} = this;
const {
transform,
width,
height
} = this.#getTransform(rotation, rect);
const svg = this.svgFactory.create(width, height, true);
const g = this.#polylinesGroupElement = this.svgFactory.createElement("svg:g");
svg.append(g);
g.setAttribute("stroke-width", borderStyle.width || 1);
g.setAttribute("stroke-linecap", "round");
g.setAttribute("stroke-linejoin", "round");
g.setAttribute("stroke-miterlimit", 10);
g.setAttribute("stroke", "transparent");
g.setAttribute("fill", "transparent");
g.setAttribute("transform", transform);
for (let i = 0, ii = inkLists.length; i < ii; i++) {
const polyline = this.svgFactory.createElement(this.svgElementName);
this.#polylines.push(polyline);
polyline.setAttribute("points", inkLists[i].join(","));
g.append(polyline);
}
if (!popupRef && this.hasPopupData) {
this._createPopup();
}
this.container.append(svg);
this._editOnDoubleClick();
return this.container;
}
updateEdited(params) {
super.updateEdited(params);
const {
thickness,
points,
rect
} = params;
const g = this.#polylinesGroupElement;
if (thickness >= 0) {
g.setAttribute("stroke-width", thickness || 1);
}
if (points) {
for (let i = 0, ii = this.#polylines.length; i < ii; i++) {
this.#polylines[i].setAttribute("points", points[i].join(","));
}
}
if (rect) {
const {
transform,
width,
height
} = this.#getTransform(this.data.rotation, rect);
const root = g.parentElement;
root.setAttribute("viewBox", `0 0 ${width} ${height}`);
g.setAttribute("transform", transform);
}
}
getElementsToTriggerPopup() {
return this.#polylines;
}
addHighlightArea() {
this.container.classList.add("highlightArea");
}
}
class HighlightAnnotationElement extends AnnotationElement {
constructor(parameters) {
super(parameters, {
isRenderable: true,
ignoreBorder: true,
createQuadrilaterals: true
});
this.annotationEditorType = AnnotationEditorType.HIGHLIGHT;
}
render() {
if (!this.data.popupRef && this.hasPopupData) {
this._createPopup();
}
this.container.classList.add("highlightAnnotation");
this._editOnDoubleClick();
return this.container;
}
}
class UnderlineAnnotationElement extends AnnotationElement {
constructor(parameters) {
super(parameters, {
isRenderable: true,
ignoreBorder: true,
createQuadrilaterals: true
});
}
render() {
if (!this.data.popupRef && this.hasPopupData) {
this._createPopup();
}
this.container.classList.add("underlineAnnotation");
return this.container;
}
}
class SquigglyAnnotationElement extends AnnotationElement {
constructor(parameters) {
super(parameters, {
isRenderable: true,
ignoreBorder: true,
createQuadrilaterals: true
});
}
render() {
if (!this.data.popupRef && this.hasPopupData) {
this._createPopup();
}
this.container.classList.add("squigglyAnnotation");
return this.container;
}
}
class StrikeOutAnnotationElement extends AnnotationElement {
constructor(parameters) {
super(parameters, {
isRenderable: true,
ignoreBorder: true,
createQuadrilaterals: true
});
}
render() {
if (!this.data.popupRef && this.hasPopupData) {
this._createPopup();
}
this.container.classList.add("strikeoutAnnotation");
return this.container;
}
}
class StampAnnotationElement extends AnnotationElement {
constructor(parameters) {
super(parameters, {
isRenderable: true,
ignoreBorder: true
});
this.annotationEditorType = AnnotationEditorType.STAMP;
}
render() {
this.container.classList.add("stampAnnotation");
this.container.setAttribute("role", "img");
if (!this.data.popupRef && this.hasPopupData) {
this._createPopup();
}
this._editOnDoubleClick();
return this.container;
}
}
class FileAttachmentAnnotationElement extends AnnotationElement {
#trigger = null;
constructor(parameters) {
super(parameters, {
isRenderable: true
});
const {
file
} = this.data;
this.filename = file.filename;
this.content = file.content;
this.linkService.eventBus?.dispatch("fileattachmentannotation", {
source: this,
...file
});
}
render() {
this.container.classList.add("fileAttachmentAnnotation");
const {
container,
data
} = this;
let trigger;
if (data.hasAppearance || data.fillAlpha === 0) {
trigger = document.createElement("div");
} else {
trigger = document.createElement("img");
trigger.src = `${this.imageResourcesPath}annotation-${/paperclip/i.test(data.name) ? "paperclip" : "pushpin"}.svg`;
if (data.fillAlpha && data.fillAlpha < 1) {
trigger.style = `filter: opacity(${Math.round(data.fillAlpha * 100)}%);`;
}
}
trigger.addEventListener("dblclick", this.#download.bind(this));
this.#trigger = trigger;
const {
isMac
} = util_FeatureTest.platform;
container.addEventListener("keydown", evt => {
if (evt.key === "Enter" && (isMac ? evt.metaKey : evt.ctrlKey)) {
this.#download();
}
});
if (!data.popupRef && this.hasPopupData) {
this._createPopup();
} else {
trigger.classList.add("popupTriggerArea");
}
container.append(trigger);
return container;
}
getElementsToTriggerPopup() {
return this.#trigger;
}
addHighlightArea() {
this.container.classList.add("highlightArea");
}
#download() {
this.downloadManager?.openOrDownloadData(this.content, this.filename);
}
}
class AnnotationLayer {
#accessibilityManager = null;
#annotationCanvasMap = null;
#editableAnnotations = new Map();
#structTreeLayer = null;
constructor({
div,
accessibilityManager,
annotationCanvasMap,
annotationEditorUIManager,
page,
viewport,
structTreeLayer
}) {
this.div = div;
this.#accessibilityManager = accessibilityManager;
this.#annotationCanvasMap = annotationCanvasMap;
this.#structTreeLayer = structTreeLayer || null;
this.page = page;
this.viewport = viewport;
this.zIndex = 0;
this._annotationEditorUIManager = annotationEditorUIManager;
}
hasEditableAnnotations() {
return this.#editableAnnotations.size > 0;
}
async #appendElement(element, id) {
const contentElement = element.firstChild || element;
const annotationId = contentElement.id = `${AnnotationPrefix}${id}`;
const ariaAttributes = await this.#structTreeLayer?.getAriaAttributes(annotationId);
if (ariaAttributes) {
for (const [key, value] of ariaAttributes) {
contentElement.setAttribute(key, value);
}
}
this.div.append(element);
this.#accessibilityManager?.moveElementInDOM(this.div, element, contentElement, false);
}
async render(params) {
const {
annotations
} = params;
const layer = this.div;
setLayerDimensions(layer, this.viewport);
const popupToElements = new Map();
const elementParams = {
data: null,
layer,
linkService: params.linkService,
downloadManager: params.downloadManager,
imageResourcesPath: params.imageResourcesPath || "",
renderForms: params.renderForms !== false,
svgFactory: new DOMSVGFactory(),
annotationStorage: params.annotationStorage || new AnnotationStorage(),
enableScripting: params.enableScripting === true,
hasJSActions: params.hasJSActions,
fieldObjects: params.fieldObjects,
parent: this,
elements: null
};
for (const data of annotations) {
if (data.noHTML) {
continue;
}
const isPopupAnnotation = data.annotationType === AnnotationType.POPUP;
if (!isPopupAnnotation) {
if (data.rect[2] === data.rect[0] || data.rect[3] === data.rect[1]) {
continue;
}
} else {
const elements = popupToElements.get(data.id);
if (!elements) {
continue;
}
elementParams.elements = elements;
}
elementParams.data = data;
const element = AnnotationElementFactory.create(elementParams);
if (!element.isRenderable) {
continue;
}
if (!isPopupAnnotation && data.popupRef) {
const elements = popupToElements.get(data.popupRef);
if (!elements) {
popupToElements.set(data.popupRef, [element]);
} else {
elements.push(element);
}
}
const rendered = element.render();
if (data.hidden) {
rendered.style.visibility = "hidden";
}
await this.#appendElement(rendered, data.id);
if (element._isEditable) {
this.#editableAnnotations.set(element.data.id, element);
this._annotationEditorUIManager?.renderAnnotationElement(element);
}
}
this.#setAnnotationCanvasMap();
}
async addLinkAnnotations(annotations, linkService) {
const elementParams = {
data: null,
layer: this.div,
linkService,
svgFactory: new DOMSVGFactory(),
parent: this
};
for (const data of annotations) {
data.borderStyle ||= AnnotationLayer._defaultBorderStyle;
elementParams.data = data;
const element = AnnotationElementFactory.create(elementParams);
if (!element.isRenderable) {
continue;
}
const rendered = element.render();
await this.#appendElement(rendered, data.id);
}
}
update({
viewport
}) {
const layer = this.div;
this.viewport = viewport;
setLayerDimensions(layer, {
rotation: viewport.rotation
});
this.#setAnnotationCanvasMap();
layer.hidden = false;
}
#setAnnotationCanvasMap() {
if (!this.#annotationCanvasMap) {
return;
}
const layer = this.div;
for (const [id, canvas] of this.#annotationCanvasMap) {
const element = layer.querySelector(`[data-annotation-id="${id}"]`);
if (!element) {
continue;
}
canvas.className = "annotationContent";
const {
firstChild
} = element;
if (!firstChild) {
element.append(canvas);
} else if (firstChild.nodeName === "CANVAS") {
firstChild.replaceWith(canvas);
} else if (!firstChild.classList.contains("annotationContent")) {
firstChild.before(canvas);
} else {
firstChild.after(canvas);
}
const editableAnnotation = this.#editableAnnotations.get(id);
if (!editableAnnotation) {
continue;
}
if (editableAnnotation._hasNoCanvas) {
this._annotationEditorUIManager?.setMissingCanvas(id, element.id, canvas);
editableAnnotation._hasNoCanvas = false;
} else {
editableAnnotation.canvas = canvas;
}
}
this.#annotationCanvasMap.clear();
}
getEditableAnnotations() {
return Array.from(this.#editableAnnotations.values());
}
getEditableAnnotation(id) {
return this.#editableAnnotations.get(id);
}
static get _defaultBorderStyle() {
return shadow(this, "_defaultBorderStyle", Object.freeze({
width: 1,
rawWidth: 1,
style: AnnotationBorderStyleType.SOLID,
dashArray: [3],
horizontalCornerRadius: 0,
verticalCornerRadius: 0
}));
}
}
;// ./src/display/editor/freetext.js
const EOL_PATTERN = /\r\n?|\n/g;
class FreeTextEditor extends AnnotationEditor {
#color;
#content = "";
#editorDivId = `${this.id}-editor`;
#editModeAC = null;
#fontSize;
static _freeTextDefaultContent = "";
static _internalPadding = 0;
static _defaultColor = null;
static _defaultFontSize = 10;
static get _keyboardManager() {
const proto = FreeTextEditor.prototype;
const arrowChecker = self => self.isEmpty();
const small = AnnotationEditorUIManager.TRANSLATE_SMALL;
const big = AnnotationEditorUIManager.TRANSLATE_BIG;
return shadow(this, "_keyboardManager", new KeyboardManager([[["ctrl+s", "mac+meta+s", "ctrl+p", "mac+meta+p"], proto.commitOrRemove, {
bubbles: true
}], [["ctrl+Enter", "mac+meta+Enter", "Escape", "mac+Escape"], proto.commitOrRemove], [["ArrowLeft", "mac+ArrowLeft"], proto._translateEmpty, {
args: [-small, 0],
checker: arrowChecker
}], [["ctrl+ArrowLeft", "mac+shift+ArrowLeft"], proto._translateEmpty, {
args: [-big, 0],
checker: arrowChecker
}], [["ArrowRight", "mac+ArrowRight"], proto._translateEmpty, {
args: [small, 0],
checker: arrowChecker
}], [["ctrl+ArrowRight", "mac+shift+ArrowRight"], proto._translateEmpty, {
args: [big, 0],
checker: arrowChecker
}], [["ArrowUp", "mac+ArrowUp"], proto._translateEmpty, {
args: [0, -small],
checker: arrowChecker
}], [["ctrl+ArrowUp", "mac+shift+ArrowUp"], proto._translateEmpty, {
args: [0, -big],
checker: arrowChecker
}], [["ArrowDown", "mac+ArrowDown"], proto._translateEmpty, {
args: [0, small],
checker: arrowChecker
}], [["ctrl+ArrowDown", "mac+shift+ArrowDown"], proto._translateEmpty, {
args: [0, big],
checker: arrowChecker
}]]));
}
static _type = "freetext";
static _editorType = AnnotationEditorType.FREETEXT;
constructor(params) {
super({
...params,
name: "freeTextEditor"
});
this.#color = params.color || FreeTextEditor._defaultColor || AnnotationEditor._defaultLineColor;
this.#fontSize = params.fontSize || FreeTextEditor._defaultFontSize;
}
static initialize(l10n, uiManager) {
AnnotationEditor.initialize(l10n, uiManager);
const style = getComputedStyle(document.documentElement);
this._internalPadding = parseFloat(style.getPropertyValue("--freetext-padding"));
}
static updateDefaultParams(type, value) {
switch (type) {
case AnnotationEditorParamsType.FREETEXT_SIZE:
FreeTextEditor._defaultFontSize = value;
break;
case AnnotationEditorParamsType.FREETEXT_COLOR:
FreeTextEditor._defaultColor = value;
break;
}
}
updateParams(type, value) {
switch (type) {
case AnnotationEditorParamsType.FREETEXT_SIZE:
this.#updateFontSize(value);
break;
case AnnotationEditorParamsType.FREETEXT_COLOR:
this.#updateColor(value);
break;
}
}
static get defaultPropertiesToUpdate() {
return [[AnnotationEditorParamsType.FREETEXT_SIZE, FreeTextEditor._defaultFontSize], [AnnotationEditorParamsType.FREETEXT_COLOR, FreeTextEditor._defaultColor || AnnotationEditor._defaultLineColor]];
}
get propertiesToUpdate() {
return [[AnnotationEditorParamsType.FREETEXT_SIZE, this.#fontSize], [AnnotationEditorParamsType.FREETEXT_COLOR, this.#color]];
}
#updateFontSize(fontSize) {
const setFontsize = size => {
this.editorDiv.style.fontSize = `calc(${size}px * var(--total-scale-factor))`;
this.translate(0, -(size - this.#fontSize) * this.parentScale);
this.#fontSize = size;
this.#setEditorDimensions();
};
const savedFontsize = this.#fontSize;
this.addCommands({
cmd: setFontsize.bind(this, fontSize),
undo: setFontsize.bind(this, savedFontsize),
post: this._uiManager.updateUI.bind(this._uiManager, this),
mustExec: true,
type: AnnotationEditorParamsType.FREETEXT_SIZE,
overwriteIfSameType: true,
keepUndo: true
});
}
#updateColor(color) {
const setColor = col => {
this.#color = this.editorDiv.style.color = col;
};
const savedColor = this.#color;
this.addCommands({
cmd: setColor.bind(this, color),
undo: setColor.bind(this, savedColor),
post: this._uiManager.updateUI.bind(this._uiManager, this),
mustExec: true,
type: AnnotationEditorParamsType.FREETEXT_COLOR,
overwriteIfSameType: true,
keepUndo: true
});
}
_translateEmpty(x, y) {
this._uiManager.translateSelectedEditors(x, y, true);
}
getInitialTranslation() {
const scale = this.parentScale;
return [-FreeTextEditor._internalPadding * scale, -(FreeTextEditor._internalPadding + this.#fontSize) * scale];
}
rebuild() {
if (!this.parent) {
return;
}
super.rebuild();
if (this.div === null) {
return;
}
if (!this.isAttachedToDOM) {
this.parent.add(this);
}
}
enableEditMode() {
if (this.isInEditMode()) {
return;
}
this.parent.setEditingState(false);
this.parent.updateToolbar(AnnotationEditorType.FREETEXT);
super.enableEditMode();
this.overlayDiv.classList.remove("enabled");
this.editorDiv.contentEditable = true;
this._isDraggable = false;
this.div.removeAttribute("aria-activedescendant");
this.#editModeAC = new AbortController();
const signal = this._uiManager.combinedSignal(this.#editModeAC);
this.editorDiv.addEventListener("keydown", this.editorDivKeydown.bind(this), {
signal
});
this.editorDiv.addEventListener("focus", this.editorDivFocus.bind(this), {
signal
});
this.editorDiv.addEventListener("blur", this.editorDivBlur.bind(this), {
signal
});
this.editorDiv.addEventListener("input", this.editorDivInput.bind(this), {
signal
});
this.editorDiv.addEventListener("paste", this.editorDivPaste.bind(this), {
signal
});
}
disableEditMode() {
if (!this.isInEditMode()) {
return;
}
this.parent.setEditingState(true);
super.disableEditMode();
this.overlayDiv.classList.add("enabled");
this.editorDiv.contentEditable = false;
this.div.setAttribute("aria-activedescendant", this.#editorDivId);
this._isDraggable = true;
this.#editModeAC?.abort();
this.#editModeAC = null;
this.div.focus({
preventScroll: true
});
this.isEditing = false;
this.parent.div.classList.add("freetextEditing");
}
focusin(event) {
if (!this._focusEventsAllowed) {
return;
}
super.focusin(event);
if (event.target !== this.editorDiv) {
this.editorDiv.focus();
}
}
onceAdded(focus) {
if (this.width) {
return;
}
this.enableEditMode();
if (focus) {
this.editorDiv.focus();
}
if (this._initialOptions?.isCentered) {
this.center();
}
this._initialOptions = null;
}
isEmpty() {
return !this.editorDiv || this.editorDiv.innerText.trim() === "";
}
remove() {
this.isEditing = false;
if (this.parent) {
this.parent.setEditingState(true);
this.parent.div.classList.add("freetextEditing");
}
super.remove();
}
#extractText() {
const buffer = [];
this.editorDiv.normalize();
let prevChild = null;
for (const child of this.editorDiv.childNodes) {
if (prevChild?.nodeType === Node.TEXT_NODE && child.nodeName === "BR") {
continue;
}
buffer.push(FreeTextEditor.#getNodeContent(child));
prevChild = child;
}
return buffer.join("\n");
}
#setEditorDimensions() {
const [parentWidth, parentHeight] = this.parentDimensions;
let rect;
if (this.isAttachedToDOM) {
rect = this.div.getBoundingClientRect();
} else {
const {
currentLayer,
div
} = this;
const savedDisplay = div.style.display;
const savedVisibility = div.classList.contains("hidden");
div.classList.remove("hidden");
div.style.display = "hidden";
currentLayer.div.append(this.div);
rect = div.getBoundingClientRect();
div.remove();
div.style.display = savedDisplay;
div.classList.toggle("hidden", savedVisibility);
}
if (this.rotation % 180 === this.parentRotation % 180) {
this.width = rect.width / parentWidth;
this.height = rect.height / parentHeight;
} else {
this.width = rect.height / parentWidth;
this.height = rect.width / parentHeight;
}
this.fixAndSetPosition();
}
commit() {
if (!this.isInEditMode()) {
return;
}
super.commit();
this.disableEditMode();
const savedText = this.#content;
const newText = this.#content = this.#extractText().trimEnd();
if (savedText === newText) {
return;
}
const setText = text => {
this.#content = text;
if (!text) {
this.remove();
return;
}
this.#setContent();
this._uiManager.rebuild(this);
this.#setEditorDimensions();
};
this.addCommands({
cmd: () => {
setText(newText);
},
undo: () => {
setText(savedText);
},
mustExec: false
});
this.#setEditorDimensions();
}
shouldGetKeyboardEvents() {
return this.isInEditMode();
}
enterInEditMode() {
this.enableEditMode();
this.editorDiv.focus();
}
dblclick(event) {
this.enterInEditMode();
}
keydown(event) {
if (event.target === this.div && event.key === "Enter") {
this.enterInEditMode();
event.preventDefault();
}
}
editorDivKeydown(event) {
FreeTextEditor._keyboardManager.exec(this, event);
}
editorDivFocus(event) {
this.isEditing = true;
}
editorDivBlur(event) {
this.isEditing = false;
}
editorDivInput(event) {
this.parent.div.classList.toggle("freetextEditing", this.isEmpty());
}
disableEditing() {
this.editorDiv.setAttribute("role", "comment");
this.editorDiv.removeAttribute("aria-multiline");
}
enableEditing() {
this.editorDiv.setAttribute("role", "textbox");
this.editorDiv.setAttribute("aria-multiline", true);
}
render() {
if (this.div) {
return this.div;
}
let baseX, baseY;
if (this._isCopy || this.annotationElementId) {
baseX = this.x;
baseY = this.y;
}
super.render();
this.editorDiv = document.createElement("div");
this.editorDiv.className = "internal";
this.editorDiv.setAttribute("id", this.#editorDivId);
this.editorDiv.setAttribute("data-l10n-id", "pdfjs-free-text2");
this.editorDiv.setAttribute("data-l10n-attrs", "default-content");
this.enableEditing();
this.editorDiv.contentEditable = true;
const {
style
} = this.editorDiv;
style.fontSize = `calc(${this.#fontSize}px * var(--total-scale-factor))`;
style.color = this.#color;
this.div.append(this.editorDiv);
this.overlayDiv = document.createElement("div");
this.overlayDiv.classList.add("overlay", "enabled");
this.div.append(this.overlayDiv);
bindEvents(this, this.div, ["dblclick", "keydown"]);
if (this._isCopy || this.annotationElementId) {
const [parentWidth, parentHeight] = this.parentDimensions;
if (this.annotationElementId) {
const {
position
} = this._initialData;
let [tx, ty] = this.getInitialTranslation();
[tx, ty] = this.pageTranslationToScreen(tx, ty);
const [pageWidth, pageHeight] = this.pageDimensions;
const [pageX, pageY] = this.pageTranslation;
let posX, posY;
switch (this.rotation) {
case 0:
posX = baseX + (position[0] - pageX) / pageWidth;
posY = baseY + this.height - (position[1] - pageY) / pageHeight;
break;
case 90:
posX = baseX + (position[0] - pageX) / pageWidth;
posY = baseY - (position[1] - pageY) / pageHeight;
[tx, ty] = [ty, -tx];
break;
case 180:
posX = baseX - this.width + (position[0] - pageX) / pageWidth;
posY = baseY - (position[1] - pageY) / pageHeight;
[tx, ty] = [-tx, -ty];
break;
case 270:
posX = baseX + (position[0] - pageX - this.height * pageHeight) / pageWidth;
posY = baseY + (position[1] - pageY - this.width * pageWidth) / pageHeight;
[tx, ty] = [-ty, tx];
break;
}
this.setAt(posX * parentWidth, posY * parentHeight, tx, ty);
} else {
this._moveAfterPaste(baseX, baseY);
}
this.#setContent();
this._isDraggable = true;
this.editorDiv.contentEditable = false;
} else {
this._isDraggable = false;
this.editorDiv.contentEditable = true;
}
return this.div;
}
static #getNodeContent(node) {
return (node.nodeType === Node.TEXT_NODE ? node.nodeValue : node.innerText).replaceAll(EOL_PATTERN, "");
}
editorDivPaste(event) {
const clipboardData = event.clipboardData || window.clipboardData;
const {
types
} = clipboardData;
if (types.length === 1 && types[0] === "text/plain") {
return;
}
event.preventDefault();
const paste = FreeTextEditor.#deserializeContent(clipboardData.getData("text") || "").replaceAll(EOL_PATTERN, "\n");
if (!paste) {
return;
}
const selection = window.getSelection();
if (!selection.rangeCount) {
return;
}
this.editorDiv.normalize();
selection.deleteFromDocument();
const range = selection.getRangeAt(0);
if (!paste.includes("\n")) {
range.insertNode(document.createTextNode(paste));
this.editorDiv.normalize();
selection.collapseToStart();
return;
}
const {
startContainer,
startOffset
} = range;
const bufferBefore = [];
const bufferAfter = [];
if (startContainer.nodeType === Node.TEXT_NODE) {
const parent = startContainer.parentElement;
bufferAfter.push(startContainer.nodeValue.slice(startOffset).replaceAll(EOL_PATTERN, ""));
if (parent !== this.editorDiv) {
let buffer = bufferBefore;
for (const child of this.editorDiv.childNodes) {
if (child === parent) {
buffer = bufferAfter;
continue;
}
buffer.push(FreeTextEditor.#getNodeContent(child));
}
}
bufferBefore.push(startContainer.nodeValue.slice(0, startOffset).replaceAll(EOL_PATTERN, ""));
} else if (startContainer === this.editorDiv) {
let buffer = bufferBefore;
let i = 0;
for (const child of this.editorDiv.childNodes) {
if (i++ === startOffset) {
buffer = bufferAfter;
}
buffer.push(FreeTextEditor.#getNodeContent(child));
}
}
this.#content = `${bufferBefore.join("\n")}${paste}${bufferAfter.join("\n")}`;
this.#setContent();
const newRange = new Range();
let beforeLength = Math.sumPrecise(bufferBefore.map(line => line.length));
for (const {
firstChild
} of this.editorDiv.childNodes) {
if (firstChild.nodeType === Node.TEXT_NODE) {
const length = firstChild.nodeValue.length;
if (beforeLength <= length) {
newRange.setStart(firstChild, beforeLength);
newRange.setEnd(firstChild, beforeLength);
break;
}
beforeLength -= length;
}
}
selection.removeAllRanges();
selection.addRange(newRange);
}
#setContent() {
this.editorDiv.replaceChildren();
if (!this.#content) {
return;
}
for (const line of this.#content.split("\n")) {
const div = document.createElement("div");
div.append(line ? document.createTextNode(line) : document.createElement("br"));
this.editorDiv.append(div);
}
}
#serializeContent() {
return this.#content.replaceAll("\xa0", " ");
}
static #deserializeContent(content) {
return content.replaceAll(" ", "\xa0");
}
get contentDiv() {
return this.editorDiv;
}
static async deserialize(data, parent, uiManager) {
let initialData = null;
if (data instanceof FreeTextAnnotationElement) {
const {
data: {
defaultAppearanceData: {
fontSize,
fontColor
},
rect,
rotation,
id,
popupRef
},
textContent,
textPosition,
parent: {
page: {
pageNumber
}
}
} = data;
if (!textContent || textContent.length === 0) {
return null;
}
initialData = data = {
annotationType: AnnotationEditorType.FREETEXT,
color: Array.from(fontColor),
fontSize,
value: textContent.join("\n"),
position: textPosition,
pageIndex: pageNumber - 1,
rect: rect.slice(0),
rotation,
id,
deleted: false,
popupRef
};
}
const editor = await super.deserialize(data, parent, uiManager);
editor.#fontSize = data.fontSize;
editor.#color = Util.makeHexColor(...data.color);
editor.#content = FreeTextEditor.#deserializeContent(data.value);
editor.annotationElementId = data.id || null;
editor._initialData = initialData;
return editor;
}
serialize(isForCopying = false) {
if (this.isEmpty()) {
return null;
}
if (this.deleted) {
return this.serializeDeleted();
}
const padding = FreeTextEditor._internalPadding * this.parentScale;
const rect = this.getRect(padding, padding);
const color = AnnotationEditor._colorManager.convert(this.isAttachedToDOM ? getComputedStyle(this.editorDiv).color : this.#color);
const serialized = {
annotationType: AnnotationEditorType.FREETEXT,
color,
fontSize: this.#fontSize,
value: this.#serializeContent(),
pageIndex: this.pageIndex,
rect,
rotation: this.rotation,
structTreeParentId: this._structTreeParentId
};
if (isForCopying) {
serialized.isCopy = true;
return serialized;
}
if (this.annotationElementId && !this.#hasElementChanged(serialized)) {
return null;
}
serialized.id = this.annotationElementId;
return serialized;
}
#hasElementChanged(serialized) {
const {
value,
fontSize,
color,
pageIndex
} = this._initialData;
return this._hasBeenMoved || serialized.value !== value || serialized.fontSize !== fontSize || serialized.color.some((c, i) => c !== color[i]) || serialized.pageIndex !== pageIndex;
}
renderAnnotationElement(annotation) {
const content = super.renderAnnotationElement(annotation);
if (this.deleted) {
return content;
}
const {
style
} = content;
style.fontSize = `calc(${this.#fontSize}px * var(--total-scale-factor))`;
style.color = this.#color;
content.replaceChildren();
for (const line of this.#content.split("\n")) {
const div = document.createElement("div");
div.append(line ? document.createTextNode(line) : document.createElement("br"));
content.append(div);
}
const padding = FreeTextEditor._internalPadding * this.parentScale;
annotation.updateEdited({
rect: this.getRect(padding, padding),
popupContent: this.#content
});
return content;
}
resetAnnotationElement(annotation) {
super.resetAnnotationElement(annotation);
annotation.resetEdited();
}
}
;// ./src/display/editor/drawers/outline.js
class Outline {
static PRECISION = 1e-4;
toSVGPath() {
unreachable("Abstract method `toSVGPath` must be implemented.");
}
get box() {
unreachable("Abstract getter `box` must be implemented.");
}
serialize(_bbox, _rotation) {
unreachable("Abstract method `serialize` must be implemented.");
}
static _rescale(src, tx, ty, sx, sy, dest) {
dest ||= new Float32Array(src.length);
for (let i = 0, ii = src.length; i < ii; i += 2) {
dest[i] = tx + src[i] * sx;
dest[i + 1] = ty + src[i + 1] * sy;
}
return dest;
}
static _rescaleAndSwap(src, tx, ty, sx, sy, dest) {
dest ||= new Float32Array(src.length);
for (let i = 0, ii = src.length; i < ii; i += 2) {
dest[i] = tx + src[i + 1] * sx;
dest[i + 1] = ty + src[i] * sy;
}
return dest;
}
static _translate(src, tx, ty, dest) {
dest ||= new Float32Array(src.length);
for (let i = 0, ii = src.length; i < ii; i += 2) {
dest[i] = tx + src[i];
dest[i + 1] = ty + src[i + 1];
}
return dest;
}
static svgRound(x) {
return Math.round(x * 10000);
}
static _normalizePoint(x, y, parentWidth, parentHeight, rotation) {
switch (rotation) {
case 90:
return [1 - y / parentWidth, x / parentHeight];
case 180:
return [1 - x / parentWidth, 1 - y / parentHeight];
case 270:
return [y / parentWidth, 1 - x / parentHeight];
default:
return [x / parentWidth, y / parentHeight];
}
}
static _normalizePagePoint(x, y, rotation) {
switch (rotation) {
case 90:
return [1 - y, x];
case 180:
return [1 - x, 1 - y];
case 270:
return [y, 1 - x];
default:
return [x, y];
}
}
static createBezierPoints(x1, y1, x2, y2, x3, y3) {
return [(x1 + 5 * x2) / 6, (y1 + 5 * y2) / 6, (5 * x2 + x3) / 6, (5 * y2 + y3) / 6, (x2 + x3) / 2, (y2 + y3) / 2];
}
}
;// ./src/display/editor/drawers/freedraw.js
class FreeDrawOutliner {
#box;
#bottom = [];
#innerMargin;
#isLTR;
#top = [];
#last = new Float32Array(18);
#lastX;
#lastY;
#min;
#min_dist;
#scaleFactor;
#thickness;
#points = [];
static #MIN_DIST = 8;
static #MIN_DIFF = 2;
static #MIN = FreeDrawOutliner.#MIN_DIST + FreeDrawOutliner.#MIN_DIFF;
constructor({
x,
y
}, box, scaleFactor, thickness, isLTR, innerMargin = 0) {
this.#box = box;
this.#thickness = thickness * scaleFactor;
this.#isLTR = isLTR;
this.#last.set([NaN, NaN, NaN, NaN, x, y], 6);
this.#innerMargin = innerMargin;
this.#min_dist = FreeDrawOutliner.#MIN_DIST * scaleFactor;
this.#min = FreeDrawOutliner.#MIN * scaleFactor;
this.#scaleFactor = scaleFactor;
this.#points.push(x, y);
}
isEmpty() {
return isNaN(this.#last[8]);
}
#getLastCoords() {
const lastTop = this.#last.subarray(4, 6);
const lastBottom = this.#last.subarray(16, 18);
const [x, y, width, height] = this.#box;
return [(this.#lastX + (lastTop[0] - lastBottom[0]) / 2 - x) / width, (this.#lastY + (lastTop[1] - lastBottom[1]) / 2 - y) / height, (this.#lastX + (lastBottom[0] - lastTop[0]) / 2 - x) / width, (this.#lastY + (lastBottom[1] - lastTop[1]) / 2 - y) / height];
}
add({
x,
y
}) {
this.#lastX = x;
this.#lastY = y;
const [layerX, layerY, layerWidth, layerHeight] = this.#box;
let [x1, y1, x2, y2] = this.#last.subarray(8, 12);
const diffX = x - x2;
const diffY = y - y2;
const d = Math.hypot(diffX, diffY);
if (d < this.#min) {
return false;
}
const diffD = d - this.#min_dist;
const K = diffD / d;
const shiftX = K * diffX;
const shiftY = K * diffY;
let x0 = x1;
let y0 = y1;
x1 = x2;
y1 = y2;
x2 += shiftX;
y2 += shiftY;
this.#points?.push(x, y);
const nX = -shiftY / diffD;
const nY = shiftX / diffD;
const thX = nX * this.#thickness;
const thY = nY * this.#thickness;
this.#last.set(this.#last.subarray(2, 8), 0);
this.#last.set([x2 + thX, y2 + thY], 4);
this.#last.set(this.#last.subarray(14, 18), 12);
this.#last.set([x2 - thX, y2 - thY], 16);
if (isNaN(this.#last[6])) {
if (this.#top.length === 0) {
this.#last.set([x1 + thX, y1 + thY], 2);
this.#top.push(NaN, NaN, NaN, NaN, (x1 + thX - layerX) / layerWidth, (y1 + thY - layerY) / layerHeight);
this.#last.set([x1 - thX, y1 - thY], 14);
this.#bottom.push(NaN, NaN, NaN, NaN, (x1 - thX - layerX) / layerWidth, (y1 - thY - layerY) / layerHeight);
}
this.#last.set([x0, y0, x1, y1, x2, y2], 6);
return !this.isEmpty();
}
this.#last.set([x0, y0, x1, y1, x2, y2], 6);
const angle = Math.abs(Math.atan2(y0 - y1, x0 - x1) - Math.atan2(shiftY, shiftX));
if (angle < Math.PI / 2) {
[x1, y1, x2, y2] = this.#last.subarray(2, 6);
this.#top.push(NaN, NaN, NaN, NaN, ((x1 + x2) / 2 - layerX) / layerWidth, ((y1 + y2) / 2 - layerY) / layerHeight);
[x1, y1, x0, y0] = this.#last.subarray(14, 18);
this.#bottom.push(NaN, NaN, NaN, NaN, ((x0 + x1) / 2 - layerX) / layerWidth, ((y0 + y1) / 2 - layerY) / layerHeight);
return true;
}
[x0, y0, x1, y1, x2, y2] = this.#last.subarray(0, 6);
this.#top.push(((x0 + 5 * x1) / 6 - layerX) / layerWidth, ((y0 + 5 * y1) / 6 - layerY) / layerHeight, ((5 * x1 + x2) / 6 - layerX) / layerWidth, ((5 * y1 + y2) / 6 - layerY) / layerHeight, ((x1 + x2) / 2 - layerX) / layerWidth, ((y1 + y2) / 2 - layerY) / layerHeight);
[x2, y2, x1, y1, x0, y0] = this.#last.subarray(12, 18);
this.#bottom.push(((x0 + 5 * x1) / 6 - layerX) / layerWidth, ((y0 + 5 * y1) / 6 - layerY) / layerHeight, ((5 * x1 + x2) / 6 - layerX) / layerWidth, ((5 * y1 + y2) / 6 - layerY) / layerHeight, ((x1 + x2) / 2 - layerX) / layerWidth, ((y1 + y2) / 2 - layerY) / layerHeight);
return true;
}
toSVGPath() {
if (this.isEmpty()) {
return "";
}
const top = this.#top;
const bottom = this.#bottom;
if (isNaN(this.#last[6]) && !this.isEmpty()) {
return this.#toSVGPathTwoPoints();
}
const buffer = [];
buffer.push(`M${top[4]} ${top[5]}`);
for (let i = 6; i < top.length; i += 6) {
if (isNaN(top[i])) {
buffer.push(`L${top[i + 4]} ${top[i + 5]}`);
} else {
buffer.push(`C${top[i]} ${top[i + 1]} ${top[i + 2]} ${top[i + 3]} ${top[i + 4]} ${top[i + 5]}`);
}
}
this.#toSVGPathEnd(buffer);
for (let i = bottom.length - 6; i >= 6; i -= 6) {
if (isNaN(bottom[i])) {
buffer.push(`L${bottom[i + 4]} ${bottom[i + 5]}`);
} else {
buffer.push(`C${bottom[i]} ${bottom[i + 1]} ${bottom[i + 2]} ${bottom[i + 3]} ${bottom[i + 4]} ${bottom[i + 5]}`);
}
}
this.#toSVGPathStart(buffer);
return buffer.join(" ");
}
#toSVGPathTwoPoints() {
const [x, y, width, height] = this.#box;
const [lastTopX, lastTopY, lastBottomX, lastBottomY] = this.#getLastCoords();
return `M${(this.#last[2] - x) / width} ${(this.#last[3] - y) / height} L${(this.#last[4] - x) / width} ${(this.#last[5] - y) / height} L${lastTopX} ${lastTopY} L${lastBottomX} ${lastBottomY} L${(this.#last[16] - x) / width} ${(this.#last[17] - y) / height} L${(this.#last[14] - x) / width} ${(this.#last[15] - y) / height} Z`;
}
#toSVGPathStart(buffer) {
const bottom = this.#bottom;
buffer.push(`L${bottom[4]} ${bottom[5]} Z`);
}
#toSVGPathEnd(buffer) {
const [x, y, width, height] = this.#box;
const lastTop = this.#last.subarray(4, 6);
const lastBottom = this.#last.subarray(16, 18);
const [lastTopX, lastTopY, lastBottomX, lastBottomY] = this.#getLastCoords();
buffer.push(`L${(lastTop[0] - x) / width} ${(lastTop[1] - y) / height} L${lastTopX} ${lastTopY} L${lastBottomX} ${lastBottomY} L${(lastBottom[0] - x) / width} ${(lastBottom[1] - y) / height}`);
}
newFreeDrawOutline(outline, points, box, scaleFactor, innerMargin, isLTR) {
return new FreeDrawOutline(outline, points, box, scaleFactor, innerMargin, isLTR);
}
getOutlines() {
const top = this.#top;
const bottom = this.#bottom;
const last = this.#last;
const [layerX, layerY, layerWidth, layerHeight] = this.#box;
const points = new Float32Array((this.#points?.length ?? 0) + 2);
for (let i = 0, ii = points.length - 2; i < ii; i += 2) {
points[i] = (this.#points[i] - layerX) / layerWidth;
points[i + 1] = (this.#points[i + 1] - layerY) / layerHeight;
}
points[points.length - 2] = (this.#lastX - layerX) / layerWidth;
points[points.length - 1] = (this.#lastY - layerY) / layerHeight;
if (isNaN(last[6]) && !this.isEmpty()) {
return this.#getOutlineTwoPoints(points);
}
const outline = new Float32Array(this.#top.length + 24 + this.#bottom.length);
let N = top.length;
for (let i = 0; i < N; i += 2) {
if (isNaN(top[i])) {
outline[i] = outline[i + 1] = NaN;
continue;
}
outline[i] = top[i];
outline[i + 1] = top[i + 1];
}
N = this.#getOutlineEnd(outline, N);
for (let i = bottom.length - 6; i >= 6; i -= 6) {
for (let j = 0; j < 6; j += 2) {
if (isNaN(bottom[i + j])) {
outline[N] = outline[N + 1] = NaN;
N += 2;
continue;
}
outline[N] = bottom[i + j];
outline[N + 1] = bottom[i + j + 1];
N += 2;
}
}
this.#getOutlineStart(outline, N);
return this.newFreeDrawOutline(outline, points, this.#box, this.#scaleFactor, this.#innerMargin, this.#isLTR);
}
#getOutlineTwoPoints(points) {
const last = this.#last;
const [layerX, layerY, layerWidth, layerHeight] = this.#box;
const [lastTopX, lastTopY, lastBottomX, lastBottomY] = this.#getLastCoords();
const outline = new Float32Array(36);
outline.set([NaN, NaN, NaN, NaN, (last[2] - layerX) / layerWidth, (last[3] - layerY) / layerHeight, NaN, NaN, NaN, NaN, (last[4] - layerX) / layerWidth, (last[5] - layerY) / layerHeight, NaN, NaN, NaN, NaN, lastTopX, lastTopY, NaN, NaN, NaN, NaN, lastBottomX, lastBottomY, NaN, NaN, NaN, NaN, (last[16] - layerX) / layerWidth, (last[17] - layerY) / layerHeight, NaN, NaN, NaN, NaN, (last[14] - layerX) / layerWidth, (last[15] - layerY) / layerHeight], 0);
return this.newFreeDrawOutline(outline, points, this.#box, this.#scaleFactor, this.#innerMargin, this.#isLTR);
}
#getOutlineStart(outline, pos) {
const bottom = this.#bottom;
outline.set([NaN, NaN, NaN, NaN, bottom[4], bottom[5]], pos);
return pos += 6;
}
#getOutlineEnd(outline, pos) {
const lastTop = this.#last.subarray(4, 6);
const lastBottom = this.#last.subarray(16, 18);
const [layerX, layerY, layerWidth, layerHeight] = this.#box;
const [lastTopX, lastTopY, lastBottomX, lastBottomY] = this.#getLastCoords();
outline.set([NaN, NaN, NaN, NaN, (lastTop[0] - layerX) / layerWidth, (lastTop[1] - layerY) / layerHeight, NaN, NaN, NaN, NaN, lastTopX, lastTopY, NaN, NaN, NaN, NaN, lastBottomX, lastBottomY, NaN, NaN, NaN, NaN, (lastBottom[0] - layerX) / layerWidth, (lastBottom[1] - layerY) / layerHeight], pos);
return pos += 24;
}
}
class FreeDrawOutline extends Outline {
#box;
#bbox = new Float32Array(4);
#innerMargin;
#isLTR;
#points;
#scaleFactor;
#outline;
constructor(outline, points, box, scaleFactor, innerMargin, isLTR) {
super();
this.#outline = outline;
this.#points = points;
this.#box = box;
this.#scaleFactor = scaleFactor;
this.#innerMargin = innerMargin;
this.#isLTR = isLTR;
this.lastPoint = [NaN, NaN];
this.#computeMinMax(isLTR);
const [x, y, width, height] = this.#bbox;
for (let i = 0, ii = outline.length; i < ii; i += 2) {
outline[i] = (outline[i] - x) / width;
outline[i + 1] = (outline[i + 1] - y) / height;
}
for (let i = 0, ii = points.length; i < ii; i += 2) {
points[i] = (points[i] - x) / width;
points[i + 1] = (points[i + 1] - y) / height;
}
}
toSVGPath() {
const buffer = [`M${this.#outline[4]} ${this.#outline[5]}`];
for (let i = 6, ii = this.#outline.length; i < ii; i += 6) {
if (isNaN(this.#outline[i])) {
buffer.push(`L${this.#outline[i + 4]} ${this.#outline[i + 5]}`);
continue;
}
buffer.push(`C${this.#outline[i]} ${this.#outline[i + 1]} ${this.#outline[i + 2]} ${this.#outline[i + 3]} ${this.#outline[i + 4]} ${this.#outline[i + 5]}`);
}
buffer.push("Z");
return buffer.join(" ");
}
serialize([blX, blY, trX, trY], rotation) {
const width = trX - blX;
const height = trY - blY;
let outline;
let points;
switch (rotation) {
case 0:
outline = Outline._rescale(this.#outline, blX, trY, width, -height);
points = Outline._rescale(this.#points, blX, trY, width, -height);
break;
case 90:
outline = Outline._rescaleAndSwap(this.#outline, blX, blY, width, height);
points = Outline._rescaleAndSwap(this.#points, blX, blY, width, height);
break;
case 180:
outline = Outline._rescale(this.#outline, trX, blY, -width, height);
points = Outline._rescale(this.#points, trX, blY, -width, height);
break;
case 270:
outline = Outline._rescaleAndSwap(this.#outline, trX, trY, -width, -height);
points = Outline._rescaleAndSwap(this.#points, trX, trY, -width, -height);
break;
}
return {
outline: Array.from(outline),
points: [Array.from(points)]
};
}
#computeMinMax(isLTR) {
const outline = this.#outline;
let lastX = outline[4];
let lastY = outline[5];
const minMax = [lastX, lastY, lastX, lastY];
let lastPointX = lastX;
let lastPointY = lastY;
const ltrCallback = isLTR ? Math.max : Math.min;
for (let i = 6, ii = outline.length; i < ii; i += 6) {
const x = outline[i + 4],
y = outline[i + 5];
if (isNaN(outline[i])) {
Util.pointBoundingBox(x, y, minMax);
if (lastPointY < y) {
lastPointX = x;
lastPointY = y;
} else if (lastPointY === y) {
lastPointX = ltrCallback(lastPointX, x);
}
} else {
const bbox = [Infinity, Infinity, -Infinity, -Infinity];
Util.bezierBoundingBox(lastX, lastY, ...outline.slice(i, i + 6), bbox);
Util.rectBoundingBox(...bbox, minMax);
if (lastPointY < bbox[3]) {
lastPointX = bbox[2];
lastPointY = bbox[3];
} else if (lastPointY === bbox[3]) {
lastPointX = ltrCallback(lastPointX, bbox[2]);
}
}
lastX = x;
lastY = y;
}
const bbox = this.#bbox;
bbox[0] = minMax[0] - this.#innerMargin;
bbox[1] = minMax[1] - this.#innerMargin;
bbox[2] = minMax[2] - minMax[0] + 2 * this.#innerMargin;
bbox[3] = minMax[3] - minMax[1] + 2 * this.#innerMargin;
this.lastPoint = [lastPointX, lastPointY];
}
get box() {
return this.#bbox;
}
newOutliner(point, box, scaleFactor, thickness, isLTR, innerMargin = 0) {
return new FreeDrawOutliner(point, box, scaleFactor, thickness, isLTR, innerMargin);
}
getNewOutline(thickness, innerMargin) {
const [x, y, width, height] = this.#bbox;
const [layerX, layerY, layerWidth, layerHeight] = this.#box;
const sx = width * layerWidth;
const sy = height * layerHeight;
const tx = x * layerWidth + layerX;
const ty = y * layerHeight + layerY;
const outliner = this.newOutliner({
x: this.#points[0] * sx + tx,
y: this.#points[1] * sy + ty
}, this.#box, this.#scaleFactor, thickness, this.#isLTR, innerMargin ?? this.#innerMargin);
for (let i = 2; i < this.#points.length; i += 2) {
outliner.add({
x: this.#points[i] * sx + tx,
y: this.#points[i + 1] * sy + ty
});
}
return outliner.getOutlines();
}
}
;// ./src/display/editor/drawers/highlight.js
class HighlightOutliner {
#box;
#lastPoint;
#verticalEdges = [];
#intervals = [];
constructor(boxes, borderWidth = 0, innerMargin = 0, isLTR = true) {
const minMax = [Infinity, Infinity, -Infinity, -Infinity];
const NUMBER_OF_DIGITS = 4;
const EPSILON = 10 ** -NUMBER_OF_DIGITS;
for (const {
x,
y,
width,
height
} of boxes) {
const x1 = Math.floor((x - borderWidth) / EPSILON) * EPSILON;
const x2 = Math.ceil((x + width + borderWidth) / EPSILON) * EPSILON;
const y1 = Math.floor((y - borderWidth) / EPSILON) * EPSILON;
const y2 = Math.ceil((y + height + borderWidth) / EPSILON) * EPSILON;
const left = [x1, y1, y2, true];
const right = [x2, y1, y2, false];
this.#verticalEdges.push(left, right);
Util.rectBoundingBox(x1, y1, x2, y2, minMax);
}
const bboxWidth = minMax[2] - minMax[0] + 2 * innerMargin;
const bboxHeight = minMax[3] - minMax[1] + 2 * innerMargin;
const shiftedMinX = minMax[0] - innerMargin;
const shiftedMinY = minMax[1] - innerMargin;
const lastEdge = this.#verticalEdges.at(isLTR ? -1 : -2);
const lastPoint = [lastEdge[0], lastEdge[2]];
for (const edge of this.#verticalEdges) {
const [x, y1, y2] = edge;
edge[0] = (x - shiftedMinX) / bboxWidth;
edge[1] = (y1 - shiftedMinY) / bboxHeight;
edge[2] = (y2 - shiftedMinY) / bboxHeight;
}
this.#box = new Float32Array([shiftedMinX, shiftedMinY, bboxWidth, bboxHeight]);
this.#lastPoint = lastPoint;
}
getOutlines() {
this.#verticalEdges.sort((a, b) => a[0] - b[0] || a[1] - b[1] || a[2] - b[2]);
const outlineVerticalEdges = [];
for (const edge of this.#verticalEdges) {
if (edge[3]) {
outlineVerticalEdges.push(...this.#breakEdge(edge));
this.#insert(edge);
} else {
this.#remove(edge);
outlineVerticalEdges.push(...this.#breakEdge(edge));
}
}
return this.#getOutlines(outlineVerticalEdges);
}
#getOutlines(outlineVerticalEdges) {
const edges = [];
const allEdges = new Set();
for (const edge of outlineVerticalEdges) {
const [x, y1, y2] = edge;
edges.push([x, y1, edge], [x, y2, edge]);
}
edges.sort((a, b) => a[1] - b[1] || a[0] - b[0]);
for (let i = 0, ii = edges.length; i < ii; i += 2) {
const edge1 = edges[i][2];
const edge2 = edges[i + 1][2];
edge1.push(edge2);
edge2.push(edge1);
allEdges.add(edge1);
allEdges.add(edge2);
}
const outlines = [];
let outline;
while (allEdges.size > 0) {
const edge = allEdges.values().next().value;
let [x, y1, y2, edge1, edge2] = edge;
allEdges.delete(edge);
let lastPointX = x;
let lastPointY = y1;
outline = [x, y2];
outlines.push(outline);
while (true) {
let e;
if (allEdges.has(edge1)) {
e = edge1;
} else if (allEdges.has(edge2)) {
e = edge2;
} else {
break;
}
allEdges.delete(e);
[x, y1, y2, edge1, edge2] = e;
if (lastPointX !== x) {
outline.push(lastPointX, lastPointY, x, lastPointY === y1 ? y1 : y2);
lastPointX = x;
}
lastPointY = lastPointY === y1 ? y2 : y1;
}
outline.push(lastPointX, lastPointY);
}
return new HighlightOutline(outlines, this.#box, this.#lastPoint);
}
#binarySearch(y) {
const array = this.#intervals;
let start = 0;
let end = array.length - 1;
while (start <= end) {
const middle = start + end >> 1;
const y1 = array[middle][0];
if (y1 === y) {
return middle;
}
if (y1 < y) {
start = middle + 1;
} else {
end = middle - 1;
}
}
return end + 1;
}
#insert([, y1, y2]) {
const index = this.#binarySearch(y1);
this.#intervals.splice(index, 0, [y1, y2]);
}
#remove([, y1, y2]) {
const index = this.#binarySearch(y1);
for (let i = index; i < this.#intervals.length; i++) {
const [start, end] = this.#intervals[i];
if (start !== y1) {
break;
}
if (start === y1 && end === y2) {
this.#intervals.splice(i, 1);
return;
}
}
for (let i = index - 1; i >= 0; i--) {
const [start, end] = this.#intervals[i];
if (start !== y1) {
break;
}
if (start === y1 && end === y2) {
this.#intervals.splice(i, 1);
return;
}
}
}
#breakEdge(edge) {
const [x, y1, y2] = edge;
const results = [[x, y1, y2]];
const index = this.#binarySearch(y2);
for (let i = 0; i < index; i++) {
const [start, end] = this.#intervals[i];
for (let j = 0, jj = results.length; j < jj; j++) {
const [, y3, y4] = results[j];
if (end <= y3 || y4 <= start) {
continue;
}
if (y3 >= start) {
if (y4 > end) {
results[j][1] = end;
} else {
if (jj === 1) {
return [];
}
results.splice(j, 1);
j--;
jj--;
}
continue;
}
results[j][2] = start;
if (y4 > end) {
results.push([x, end, y4]);
}
}
}
return results;
}
}
class HighlightOutline extends Outline {
#box;
#outlines;
constructor(outlines, box, lastPoint) {
super();
this.#outlines = outlines;
this.#box = box;
this.lastPoint = lastPoint;
}
toSVGPath() {
const buffer = [];
for (const polygon of this.#outlines) {
let [prevX, prevY] = polygon;
buffer.push(`M${prevX} ${prevY}`);
for (let i = 2; i < polygon.length; i += 2) {
const x = polygon[i];
const y = polygon[i + 1];
if (x === prevX) {
buffer.push(`V${y}`);
prevY = y;
} else if (y === prevY) {
buffer.push(`H${x}`);
prevX = x;
}
}
buffer.push("Z");
}
return buffer.join(" ");
}
serialize([blX, blY, trX, trY], _rotation) {
const outlines = [];
const width = trX - blX;
const height = trY - blY;
for (const outline of this.#outlines) {
const points = new Array(outline.length);
for (let i = 0; i < outline.length; i += 2) {
points[i] = blX + outline[i] * width;
points[i + 1] = trY - outline[i + 1] * height;
}
outlines.push(points);
}
return outlines;
}
get box() {
return this.#box;
}
get classNamesForOutlining() {
return ["highlightOutline"];
}
}
class FreeHighlightOutliner extends FreeDrawOutliner {
newFreeDrawOutline(outline, points, box, scaleFactor, innerMargin, isLTR) {
return new FreeHighlightOutline(outline, points, box, scaleFactor, innerMargin, isLTR);
}
}
class FreeHighlightOutline extends FreeDrawOutline {
newOutliner(point, box, scaleFactor, thickness, isLTR, innerMargin = 0) {
return new FreeHighlightOutliner(point, box, scaleFactor, thickness, isLTR, innerMargin);
}
}
;// ./src/display/editor/color_picker.js
class ColorPicker {
#button = null;
#buttonSwatch = null;
#defaultColor;
#dropdown = null;
#dropdownWasFromKeyboard = false;
#isMainColorPicker = false;
#editor = null;
#eventBus;
#openDropdownAC = null;
#uiManager = null;
#type;
static #l10nColor = null;
static get _keyboardManager() {
return shadow(this, "_keyboardManager", new KeyboardManager([[["Escape", "mac+Escape"], ColorPicker.prototype._hideDropdownFromKeyboard], [[" ", "mac+ "], ColorPicker.prototype._colorSelectFromKeyboard], [["ArrowDown", "ArrowRight", "mac+ArrowDown", "mac+ArrowRight"], ColorPicker.prototype._moveToNext], [["ArrowUp", "ArrowLeft", "mac+ArrowUp", "mac+ArrowLeft"], ColorPicker.prototype._moveToPrevious], [["Home", "mac+Home"], ColorPicker.prototype._moveToBeginning], [["End", "mac+End"], ColorPicker.prototype._moveToEnd]]));
}
constructor({
editor = null,
uiManager = null
}) {
if (editor) {
this.#isMainColorPicker = false;
this.#type = AnnotationEditorParamsType.HIGHLIGHT_COLOR;
this.#editor = editor;
} else {
this.#isMainColorPicker = true;
this.#type = AnnotationEditorParamsType.HIGHLIGHT_DEFAULT_COLOR;
}
this.#uiManager = editor?._uiManager || uiManager;
this.#eventBus = this.#uiManager._eventBus;
this.#defaultColor = editor?.color || this.#uiManager?.highlightColors.values().next().value || "#FFFF98";
ColorPicker.#l10nColor ||= Object.freeze({
blue: "pdfjs-editor-colorpicker-blue",
green: "pdfjs-editor-colorpicker-green",
pink: "pdfjs-editor-colorpicker-pink",
red: "pdfjs-editor-colorpicker-red",
yellow: "pdfjs-editor-colorpicker-yellow"
});
}
renderButton() {
const button = this.#button = document.createElement("button");
button.className = "colorPicker";
button.tabIndex = "0";
button.setAttribute("data-l10n-id", "pdfjs-editor-colorpicker-button");
button.setAttribute("aria-haspopup", true);
const signal = this.#uiManager._signal;
button.addEventListener("click", this.#openDropdown.bind(this), {
signal
});
button.addEventListener("keydown", this.#keyDown.bind(this), {
signal
});
const swatch = this.#buttonSwatch = document.createElement("span");
swatch.className = "swatch";
swatch.setAttribute("aria-hidden", true);
swatch.style.backgroundColor = this.#defaultColor;
button.append(swatch);
return button;
}
renderMainDropdown() {
const dropdown = this.#dropdown = this.#getDropdownRoot();
dropdown.setAttribute("aria-orientation", "horizontal");
dropdown.setAttribute("aria-labelledby", "highlightColorPickerLabel");
return dropdown;
}
#getDropdownRoot() {
const div = document.createElement("div");
const signal = this.#uiManager._signal;
div.addEventListener("contextmenu", noContextMenu, {
signal
});
div.className = "dropdown";
div.role = "listbox";
div.setAttribute("aria-multiselectable", false);
div.setAttribute("aria-orientation", "vertical");
div.setAttribute("data-l10n-id", "pdfjs-editor-colorpicker-dropdown");
for (const [name, color] of this.#uiManager.highlightColors) {
const button = document.createElement("button");
button.tabIndex = "0";
button.role = "option";
button.setAttribute("data-color", color);
button.title = name;
button.setAttribute("data-l10n-id", ColorPicker.#l10nColor[name]);
const swatch = document.createElement("span");
button.append(swatch);
swatch.className = "swatch";
swatch.style.backgroundColor = color;
button.setAttribute("aria-selected", color === this.#defaultColor);
button.addEventListener("click", this.#colorSelect.bind(this, color), {
signal
});
div.append(button);
}
div.addEventListener("keydown", this.#keyDown.bind(this), {
signal
});
return div;
}
#colorSelect(color, event) {
event.stopPropagation();
this.#eventBus.dispatch("switchannotationeditorparams", {
source: this,
type: this.#type,
value: color
});
}
_colorSelectFromKeyboard(event) {
if (event.target === this.#button) {
this.#openDropdown(event);
return;
}
const color = event.target.getAttribute("data-color");
if (!color) {
return;
}
this.#colorSelect(color, event);
}
_moveToNext(event) {
if (!this.#isDropdownVisible) {
this.#openDropdown(event);
return;
}
if (event.target === this.#button) {
this.#dropdown.firstChild?.focus();
return;
}
event.target.nextSibling?.focus();
}
_moveToPrevious(event) {
if (event.target === this.#dropdown?.firstChild || event.target === this.#button) {
if (this.#isDropdownVisible) {
this._hideDropdownFromKeyboard();
}
return;
}
if (!this.#isDropdownVisible) {
this.#openDropdown(event);
}
event.target.previousSibling?.focus();
}
_moveToBeginning(event) {
if (!this.#isDropdownVisible) {
this.#openDropdown(event);
return;
}
this.#dropdown.firstChild?.focus();
}
_moveToEnd(event) {
if (!this.#isDropdownVisible) {
this.#openDropdown(event);
return;
}
this.#dropdown.lastChild?.focus();
}
#keyDown(event) {
ColorPicker._keyboardManager.exec(this, event);
}
#openDropdown(event) {
if (this.#isDropdownVisible) {
this.hideDropdown();
return;
}
this.#dropdownWasFromKeyboard = event.detail === 0;
if (!this.#openDropdownAC) {
this.#openDropdownAC = new AbortController();
window.addEventListener("pointerdown", this.#pointerDown.bind(this), {
signal: this.#uiManager.combinedSignal(this.#openDropdownAC)
});
}
if (this.#dropdown) {
this.#dropdown.classList.remove("hidden");
return;
}
const root = this.#dropdown = this.#getDropdownRoot();
this.#button.append(root);
}
#pointerDown(event) {
if (this.#dropdown?.contains(event.target)) {
return;
}
this.hideDropdown();
}
hideDropdown() {
this.#dropdown?.classList.add("hidden");
this.#openDropdownAC?.abort();
this.#openDropdownAC = null;
}
get #isDropdownVisible() {
return this.#dropdown && !this.#dropdown.classList.contains("hidden");
}
_hideDropdownFromKeyboard() {
if (this.#isMainColorPicker) {
return;
}
if (!this.#isDropdownVisible) {
this.#editor?.unselect();
return;
}
this.hideDropdown();
this.#button.focus({
preventScroll: true,
focusVisible: this.#dropdownWasFromKeyboard
});
}
updateColor(color) {
if (this.#buttonSwatch) {
this.#buttonSwatch.style.backgroundColor = color;
}
if (!this.#dropdown) {
return;
}
const i = this.#uiManager.highlightColors.values();
for (const child of this.#dropdown.children) {
child.setAttribute("aria-selected", i.next().value === color);
}
}
destroy() {
this.#button?.remove();
this.#button = null;
this.#buttonSwatch = null;
this.#dropdown?.remove();
this.#dropdown = null;
}
}
;// ./src/display/editor/highlight.js
class HighlightEditor extends AnnotationEditor {
#anchorNode = null;
#anchorOffset = 0;
#boxes;
#clipPathId = null;
#colorPicker = null;
#focusOutlines = null;
#focusNode = null;
#focusOffset = 0;
#highlightDiv = null;
#highlightOutlines = null;
#id = null;
#isFreeHighlight = false;
#lastPoint = null;
#opacity;
#outlineId = null;
#text = "";
#thickness;
#methodOfCreation = "";
static _defaultColor = null;
static _defaultOpacity = 1;
static _defaultThickness = 12;
static _type = "highlight";
static _editorType = AnnotationEditorType.HIGHLIGHT;
static _freeHighlightId = -1;
static _freeHighlight = null;
static _freeHighlightClipId = "";
static get _keyboardManager() {
const proto = HighlightEditor.prototype;
return shadow(this, "_keyboardManager", new KeyboardManager([[["ArrowLeft", "mac+ArrowLeft"], proto._moveCaret, {
args: [0]
}], [["ArrowRight", "mac+ArrowRight"], proto._moveCaret, {
args: [1]
}], [["ArrowUp", "mac+ArrowUp"], proto._moveCaret, {
args: [2]
}], [["ArrowDown", "mac+ArrowDown"], proto._moveCaret, {
args: [3]
}]]));
}
constructor(params) {
super({
...params,
name: "highlightEditor"
});
this.color = params.color || HighlightEditor._defaultColor;
this.#thickness = params.thickness || HighlightEditor._defaultThickness;
this.#opacity = params.opacity || HighlightEditor._defaultOpacity;
this.#boxes = params.boxes || null;
this.#methodOfCreation = params.methodOfCreation || "";
this.#text = params.text || "";
this._isDraggable = false;
this.defaultL10nId = "pdfjs-editor-highlight-editor";
if (params.highlightId > -1) {
this.#isFreeHighlight = true;
this.#createFreeOutlines(params);
this.#addToDrawLayer();
} else if (this.#boxes) {
this.#anchorNode = params.anchorNode;
this.#anchorOffset = params.anchorOffset;
this.#focusNode = params.focusNode;
this.#focusOffset = params.focusOffset;
this.#createOutlines();
this.#addToDrawLayer();
this.rotate(this.rotation);
}
}
get telemetryInitialData() {
return {
action: "added",
type: this.#isFreeHighlight ? "free_highlight" : "highlight",
color: this._uiManager.highlightColorNames.get(this.color),
thickness: this.#thickness,
methodOfCreation: this.#methodOfCreation
};
}
get telemetryFinalData() {
return {
type: "highlight",
color: this._uiManager.highlightColorNames.get(this.color)
};
}
static computeTelemetryFinalData(data) {
return {
numberOfColors: data.get("color").size
};
}
#createOutlines() {
const outliner = new HighlightOutliner(this.#boxes, 0.001);
this.#highlightOutlines = outliner.getOutlines();
[this.x, this.y, this.width, this.height] = this.#highlightOutlines.box;
const outlinerForOutline = new HighlightOutliner(this.#boxes, 0.0025, 0.001, this._uiManager.direction === "ltr");
this.#focusOutlines = outlinerForOutline.getOutlines();
const {
lastPoint
} = this.#focusOutlines;
this.#lastPoint = [(lastPoint[0] - this.x) / this.width, (lastPoint[1] - this.y) / this.height];
}
#createFreeOutlines({
highlightOutlines,
highlightId,
clipPathId
}) {
this.#highlightOutlines = highlightOutlines;
const extraThickness = 1.5;
this.#focusOutlines = highlightOutlines.getNewOutline(this.#thickness / 2 + extraThickness, 0.0025);
if (highlightId >= 0) {
this.#id = highlightId;
this.#clipPathId = clipPathId;
this.parent.drawLayer.finalizeDraw(highlightId, {
bbox: highlightOutlines.box,
path: {
d: highlightOutlines.toSVGPath()
}
});
this.#outlineId = this.parent.drawLayer.drawOutline({
rootClass: {
highlightOutline: true,
free: true
},
bbox: this.#focusOutlines.box,
path: {
d: this.#focusOutlines.toSVGPath()
}
}, true);
} else if (this.parent) {
const angle = this.parent.viewport.rotation;
this.parent.drawLayer.updateProperties(this.#id, {
bbox: HighlightEditor.#rotateBbox(this.#highlightOutlines.box, (angle - this.rotation + 360) % 360),
path: {
d: highlightOutlines.toSVGPath()
}
});
this.parent.drawLayer.updateProperties(this.#outlineId, {
bbox: HighlightEditor.#rotateBbox(this.#focusOutlines.box, angle),
path: {
d: this.#focusOutlines.toSVGPath()
}
});
}
const [x, y, width, height] = highlightOutlines.box;
switch (this.rotation) {
case 0:
this.x = x;
this.y = y;
this.width = width;
this.height = height;
break;
case 90:
{
const [pageWidth, pageHeight] = this.parentDimensions;
this.x = y;
this.y = 1 - x;
this.width = width * pageHeight / pageWidth;
this.height = height * pageWidth / pageHeight;
break;
}
case 180:
this.x = 1 - x;
this.y = 1 - y;
this.width = width;
this.height = height;
break;
case 270:
{
const [pageWidth, pageHeight] = this.parentDimensions;
this.x = 1 - y;
this.y = x;
this.width = width * pageHeight / pageWidth;
this.height = height * pageWidth / pageHeight;
break;
}
}
const {
lastPoint
} = this.#focusOutlines;
this.#lastPoint = [(lastPoint[0] - x) / width, (lastPoint[1] - y) / height];
}
static initialize(l10n, uiManager) {
AnnotationEditor.initialize(l10n, uiManager);
HighlightEditor._defaultColor ||= uiManager.highlightColors?.values().next().value || "#fff066";
}
static updateDefaultParams(type, value) {
switch (type) {
case AnnotationEditorParamsType.HIGHLIGHT_DEFAULT_COLOR:
HighlightEditor._defaultColor = value;
break;
case AnnotationEditorParamsType.HIGHLIGHT_THICKNESS:
HighlightEditor._defaultThickness = value;
break;
}
}
translateInPage(x, y) {}
get toolbarPosition() {
return this.#lastPoint;
}
updateParams(type, value) {
switch (type) {
case AnnotationEditorParamsType.HIGHLIGHT_COLOR:
this.#updateColor(value);
break;
case AnnotationEditorParamsType.HIGHLIGHT_THICKNESS:
this.#updateThickness(value);
break;
}
}
static get defaultPropertiesToUpdate() {
return [[AnnotationEditorParamsType.HIGHLIGHT_DEFAULT_COLOR, HighlightEditor._defaultColor], [AnnotationEditorParamsType.HIGHLIGHT_THICKNESS, HighlightEditor._defaultThickness]];
}
get propertiesToUpdate() {
return [[AnnotationEditorParamsType.HIGHLIGHT_COLOR, this.color || HighlightEditor._defaultColor], [AnnotationEditorParamsType.HIGHLIGHT_THICKNESS, this.#thickness || HighlightEditor._defaultThickness], [AnnotationEditorParamsType.HIGHLIGHT_FREE, this.#isFreeHighlight]];
}
#updateColor(color) {
const setColorAndOpacity = (col, opa) => {
this.color = col;
this.#opacity = opa;
this.parent?.drawLayer.updateProperties(this.#id, {
root: {
fill: col,
"fill-opacity": opa
}
});
this.#colorPicker?.updateColor(col);
};
const savedColor = this.color;
const savedOpacity = this.#opacity;
this.addCommands({
cmd: setColorAndOpacity.bind(this, color, HighlightEditor._defaultOpacity),
undo: setColorAndOpacity.bind(this, savedColor, savedOpacity),
post: this._uiManager.updateUI.bind(this._uiManager, this),
mustExec: true,
type: AnnotationEditorParamsType.HIGHLIGHT_COLOR,
overwriteIfSameType: true,
keepUndo: true
});
this._reportTelemetry({
action: "color_changed",
color: this._uiManager.highlightColorNames.get(color)
}, true);
}
#updateThickness(thickness) {
const savedThickness = this.#thickness;
const setThickness = th => {
this.#thickness = th;
this.#changeThickness(th);
};
this.addCommands({
cmd: setThickness.bind(this, thickness),
undo: setThickness.bind(this, savedThickness),
post: this._uiManager.updateUI.bind(this._uiManager, this),
mustExec: true,
type: AnnotationEditorParamsType.INK_THICKNESS,
overwriteIfSameType: true,
keepUndo: true
});
this._reportTelemetry({
action: "thickness_changed",
thickness
}, true);
}
async addEditToolbar() {
const toolbar = await super.addEditToolbar();
if (!toolbar) {
return null;
}
if (this._uiManager.highlightColors) {
this.#colorPicker = new ColorPicker({
editor: this
});
toolbar.addColorPicker(this.#colorPicker);
}
return toolbar;
}
disableEditing() {
super.disableEditing();
this.div.classList.toggle("disabled", true);
}
enableEditing() {
super.enableEditing();
this.div.classList.toggle("disabled", false);
}
fixAndSetPosition() {
return super.fixAndSetPosition(this.#getRotation());
}
getBaseTranslation() {
return [0, 0];
}
getRect(tx, ty) {
return super.getRect(tx, ty, this.#getRotation());
}
onceAdded(focus) {
if (!this.annotationElementId) {
this.parent.addUndoableEditor(this);
}
if (focus) {
this.div.focus();
}
}
remove() {
this.#cleanDrawLayer();
this._reportTelemetry({
action: "deleted"
});
super.remove();
}
rebuild() {
if (!this.parent) {
return;
}
super.rebuild();
if (this.div === null) {
return;
}
this.#addToDrawLayer();
if (!this.isAttachedToDOM) {
this.parent.add(this);
}
}
setParent(parent) {
let mustBeSelected = false;
if (this.parent && !parent) {
this.#cleanDrawLayer();
} else if (parent) {
this.#addToDrawLayer(parent);
mustBeSelected = !this.parent && this.div?.classList.contains("selectedEditor");
}
super.setParent(parent);
this.show(this._isVisible);
if (mustBeSelected) {
this.select();
}
}
#changeThickness(thickness) {
if (!this.#isFreeHighlight) {
return;
}
this.#createFreeOutlines({
highlightOutlines: this.#highlightOutlines.getNewOutline(thickness / 2)
});
this.fixAndSetPosition();
const [parentWidth, parentHeight] = this.parentDimensions;
this.setDims(this.width * parentWidth, this.height * parentHeight);
}
#cleanDrawLayer() {
if (this.#id === null || !this.parent) {
return;
}
this.parent.drawLayer.remove(this.#id);
this.#id = null;
this.parent.drawLayer.remove(this.#outlineId);
this.#outlineId = null;
}
#addToDrawLayer(parent = this.parent) {
if (this.#id !== null) {
return;
}
({
id: this.#id,
clipPathId: this.#clipPathId
} = parent.drawLayer.draw({
bbox: this.#highlightOutlines.box,
root: {
viewBox: "0 0 1 1",
fill: this.color,
"fill-opacity": this.#opacity
},
rootClass: {
highlight: true,
free: this.#isFreeHighlight
},
path: {
d: this.#highlightOutlines.toSVGPath()
}
}, false, true));
this.#outlineId = parent.drawLayer.drawOutline({
rootClass: {
highlightOutline: true,
free: this.#isFreeHighlight
},
bbox: this.#focusOutlines.box,
path: {
d: this.#focusOutlines.toSVGPath()
}
}, this.#isFreeHighlight);
if (this.#highlightDiv) {
this.#highlightDiv.style.clipPath = this.#clipPathId;
}
}
static #rotateBbox([x, y, width, height], angle) {
switch (angle) {
case 90:
return [1 - y - height, x, height, width];
case 180:
return [1 - x - width, 1 - y - height, width, height];
case 270:
return [y, 1 - x - width, height, width];
}
return [x, y, width, height];
}
rotate(angle) {
const {
drawLayer
} = this.parent;
let box;
if (this.#isFreeHighlight) {
angle = (angle - this.rotation + 360) % 360;
box = HighlightEditor.#rotateBbox(this.#highlightOutlines.box, angle);
} else {
box = HighlightEditor.#rotateBbox([this.x, this.y, this.width, this.height], angle);
}
drawLayer.updateProperties(this.#id, {
bbox: box,
root: {
"data-main-rotation": angle
}
});
drawLayer.updateProperties(this.#outlineId, {
bbox: HighlightEditor.#rotateBbox(this.#focusOutlines.box, angle),
root: {
"data-main-rotation": angle
}
});
}
render() {
if (this.div) {
return this.div;
}
const div = super.render();
if (this.#text) {
div.setAttribute("aria-label", this.#text);
div.setAttribute("role", "mark");
}
if (this.#isFreeHighlight) {
div.classList.add("free");
} else {
this.div.addEventListener("keydown", this.#keydown.bind(this), {
signal: this._uiManager._signal
});
}
const highlightDiv = this.#highlightDiv = document.createElement("div");
div.append(highlightDiv);
highlightDiv.setAttribute("aria-hidden", "true");
highlightDiv.className = "internal";
highlightDiv.style.clipPath = this.#clipPathId;
const [parentWidth, parentHeight] = this.parentDimensions;
this.setDims(this.width * parentWidth, this.height * parentHeight);
bindEvents(this, this.#highlightDiv, ["pointerover", "pointerleave"]);
this.enableEditing();
return div;
}
pointerover() {
if (!this.isSelected) {
this.parent?.drawLayer.updateProperties(this.#outlineId, {
rootClass: {
hovered: true
}
});
}
}
pointerleave() {
if (!this.isSelected) {
this.parent?.drawLayer.updateProperties(this.#outlineId, {
rootClass: {
hovered: false
}
});
}
}
#keydown(event) {
HighlightEditor._keyboardManager.exec(this, event);
}
_moveCaret(direction) {
this.parent.unselect(this);
switch (direction) {
case 0:
case 2:
this.#setCaret(true);
break;
case 1:
case 3:
this.#setCaret(false);
break;
}
}
#setCaret(start) {
if (!this.#anchorNode) {
return;
}
const selection = window.getSelection();
if (start) {
selection.setPosition(this.#anchorNode, this.#anchorOffset);
} else {
selection.setPosition(this.#focusNode, this.#focusOffset);
}
}
select() {
super.select();
if (!this.#outlineId) {
return;
}
this.parent?.drawLayer.updateProperties(this.#outlineId, {
rootClass: {
hovered: false,
selected: true
}
});
}
unselect() {
super.unselect();
if (!this.#outlineId) {
return;
}
this.parent?.drawLayer.updateProperties(this.#outlineId, {
rootClass: {
selected: false
}
});
if (!this.#isFreeHighlight) {
this.#setCaret(false);
}
}
get _mustFixPosition() {
return !this.#isFreeHighlight;
}
show(visible = this._isVisible) {
super.show(visible);
if (this.parent) {
this.parent.drawLayer.updateProperties(this.#id, {
rootClass: {
hidden: !visible
}
});
this.parent.drawLayer.updateProperties(this.#outlineId, {
rootClass: {
hidden: !visible
}
});
}
}
#getRotation() {
return this.#isFreeHighlight ? this.rotation : 0;
}
#serializeBoxes() {
if (this.#isFreeHighlight) {
return null;
}
const [pageWidth, pageHeight] = this.pageDimensions;
const [pageX, pageY] = this.pageTranslation;
const boxes = this.#boxes;
const quadPoints = new Float32Array(boxes.length * 8);
let i = 0;
for (const {
x,
y,
width,
height
} of boxes) {
const sx = x * pageWidth + pageX;
const sy = (1 - y) * pageHeight + pageY;
quadPoints[i] = quadPoints[i + 4] = sx;
quadPoints[i + 1] = quadPoints[i + 3] = sy;
quadPoints[i + 2] = quadPoints[i + 6] = sx + width * pageWidth;
quadPoints[i + 5] = quadPoints[i + 7] = sy - height * pageHeight;
i += 8;
}
return quadPoints;
}
#serializeOutlines(rect) {
return this.#highlightOutlines.serialize(rect, this.#getRotation());
}
static startHighlighting(parent, isLTR, {
target: textLayer,
x,
y
}) {
const {
x: layerX,
y: layerY,
width: parentWidth,
height: parentHeight
} = textLayer.getBoundingClientRect();
const ac = new AbortController();
const signal = parent.combinedSignal(ac);
const pointerUpCallback = e => {
ac.abort();
this.#endHighlight(parent, e);
};
window.addEventListener("blur", pointerUpCallback, {
signal
});
window.addEventListener("pointerup", pointerUpCallback, {
signal
});
window.addEventListener("pointerdown", stopEvent, {
capture: true,
passive: false,
signal
});
window.addEventListener("contextmenu", noContextMenu, {
signal
});
textLayer.addEventListener("pointermove", this.#highlightMove.bind(this, parent), {
signal
});
this._freeHighlight = new FreeHighlightOutliner({
x,
y
}, [layerX, layerY, parentWidth, parentHeight], parent.scale, this._defaultThickness / 2, isLTR, 0.001);
({
id: this._freeHighlightId,
clipPathId: this._freeHighlightClipId
} = parent.drawLayer.draw({
bbox: [0, 0, 1, 1],
root: {
viewBox: "0 0 1 1",
fill: this._defaultColor,
"fill-opacity": this._defaultOpacity
},
rootClass: {
highlight: true,
free: true
},
path: {
d: this._freeHighlight.toSVGPath()
}
}, true, true));
}
static #highlightMove(parent, event) {
if (this._freeHighlight.add(event)) {
parent.drawLayer.updateProperties(this._freeHighlightId, {
path: {
d: this._freeHighlight.toSVGPath()
}
});
}
}
static #endHighlight(parent, event) {
if (!this._freeHighlight.isEmpty()) {
parent.createAndAddNewEditor(event, false, {
highlightId: this._freeHighlightId,
highlightOutlines: this._freeHighlight.getOutlines(),
clipPathId: this._freeHighlightClipId,
methodOfCreation: "main_toolbar"
});
} else {
parent.drawLayer.remove(this._freeHighlightId);
}
this._freeHighlightId = -1;
this._freeHighlight = null;
this._freeHighlightClipId = "";
}
static async deserialize(data, parent, uiManager) {
let initialData = null;
if (data instanceof HighlightAnnotationElement) {
const {
data: {
quadPoints,
rect,
rotation,
id,
color,
opacity,
popupRef
},
parent: {
page: {
pageNumber
}
}
} = data;
initialData = data = {
annotationType: AnnotationEditorType.HIGHLIGHT,
color: Array.from(color),
opacity,
quadPoints,
boxes: null,
pageIndex: pageNumber - 1,
rect: rect.slice(0),
rotation,
id,
deleted: false,
popupRef
};
} else if (data instanceof InkAnnotationElement) {
const {
data: {
inkLists,
rect,
rotation,
id,
color,
borderStyle: {
rawWidth: thickness
},
popupRef
},
parent: {
page: {
pageNumber
}
}
} = data;
initialData = data = {
annotationType: AnnotationEditorType.HIGHLIGHT,
color: Array.from(color),
thickness,
inkLists,
boxes: null,
pageIndex: pageNumber - 1,
rect: rect.slice(0),
rotation,
id,
deleted: false,
popupRef
};
}
const {
color,
quadPoints,
inkLists,
opacity
} = data;
const editor = await super.deserialize(data, parent, uiManager);
editor.color = Util.makeHexColor(...color);
editor.#opacity = opacity || 1;
if (inkLists) {
editor.#thickness = data.thickness;
}
editor.annotationElementId = data.id || null;
editor._initialData = initialData;
const [pageWidth, pageHeight] = editor.pageDimensions;
const [pageX, pageY] = editor.pageTranslation;
if (quadPoints) {
const boxes = editor.#boxes = [];
for (let i = 0; i < quadPoints.length; i += 8) {
boxes.push({
x: (quadPoints[i] - pageX) / pageWidth,
y: 1 - (quadPoints[i + 1] - pageY) / pageHeight,
width: (quadPoints[i + 2] - quadPoints[i]) / pageWidth,
height: (quadPoints[i + 1] - quadPoints[i + 5]) / pageHeight
});
}
editor.#createOutlines();
editor.#addToDrawLayer();
editor.rotate(editor.rotation);
} else if (inkLists) {
editor.#isFreeHighlight = true;
const points = inkLists[0];
const point = {
x: points[0] - pageX,
y: pageHeight - (points[1] - pageY)
};
const outliner = new FreeHighlightOutliner(point, [0, 0, pageWidth, pageHeight], 1, editor.#thickness / 2, true, 0.001);
for (let i = 0, ii = points.length; i < ii; i += 2) {
point.x = points[i] - pageX;
point.y = pageHeight - (points[i + 1] - pageY);
outliner.add(point);
}
const {
id,
clipPathId
} = parent.drawLayer.draw({
bbox: [0, 0, 1, 1],
root: {
viewBox: "0 0 1 1",
fill: editor.color,
"fill-opacity": editor._defaultOpacity
},
rootClass: {
highlight: true,
free: true
},
path: {
d: outliner.toSVGPath()
}
}, true, true);
editor.#createFreeOutlines({
highlightOutlines: outliner.getOutlines(),
highlightId: id,
clipPathId
});
editor.#addToDrawLayer();
editor.rotate(editor.parentRotation);
}
return editor;
}
serialize(isForCopying = false) {
if (this.isEmpty() || isForCopying) {
return null;
}
if (this.deleted) {
return this.serializeDeleted();
}
const rect = this.getRect(0, 0);
const color = AnnotationEditor._colorManager.convert(this.color);
const serialized = {
annotationType: AnnotationEditorType.HIGHLIGHT,
color,
opacity: this.#opacity,
thickness: this.#thickness,
quadPoints: this.#serializeBoxes(),
outlines: this.#serializeOutlines(rect),
pageIndex: this.pageIndex,
rect,
rotation: this.#getRotation(),
structTreeParentId: this._structTreeParentId
};
if (this.annotationElementId && !this.#hasElementChanged(serialized)) {
return null;
}
serialized.id = this.annotationElementId;
return serialized;
}
#hasElementChanged(serialized) {
const {
color
} = this._initialData;
return serialized.color.some((c, i) => c !== color[i]);
}
renderAnnotationElement(annotation) {
annotation.updateEdited({
rect: this.getRect(0, 0)
});
return null;
}
static canCreateNewEmptyEditor() {
return false;
}
}
;// ./src/display/editor/draw.js
class DrawingOptions {
#svgProperties = Object.create(null);
updateProperty(name, value) {
this[name] = value;
this.updateSVGProperty(name, value);
}
updateProperties(properties) {
if (!properties) {
return;
}
for (const [name, value] of Object.entries(properties)) {
if (!name.startsWith("_")) {
this.updateProperty(name, value);
}
}
}
updateSVGProperty(name, value) {
this.#svgProperties[name] = value;
}
toSVGProperties() {
const root = this.#svgProperties;
this.#svgProperties = Object.create(null);
return {
root
};
}
reset() {
this.#svgProperties = Object.create(null);
}
updateAll(options = this) {
this.updateProperties(options);
}
clone() {
unreachable("Not implemented");
}
}
class DrawingEditor extends AnnotationEditor {
#drawOutlines = null;
#mustBeCommitted;
_drawId = null;
static _currentDrawId = -1;
static _currentParent = null;
static #currentDraw = null;
static #currentDrawingAC = null;
static #currentDrawingOptions = null;
static #currentPointerId = NaN;
static #currentPointerType = null;
static #currentPointerIds = null;
static #currentMoveTimestamp = NaN;
static _INNER_MARGIN = 3;
constructor(params) {
super(params);
this.#mustBeCommitted = params.mustBeCommitted || false;
this._addOutlines(params);
}
_addOutlines(params) {
if (params.drawOutlines) {
this.#createDrawOutlines(params);
this.#addToDrawLayer();
}
}
#createDrawOutlines({
drawOutlines,
drawId,
drawingOptions
}) {
this.#drawOutlines = drawOutlines;
this._drawingOptions ||= drawingOptions;
if (drawId >= 0) {
this._drawId = drawId;
this.parent.drawLayer.finalizeDraw(drawId, drawOutlines.defaultProperties);
} else {
this._drawId = this.#createDrawing(drawOutlines, this.parent);
}
this.#updateBbox(drawOutlines.box);
}
#createDrawing(drawOutlines, parent) {
const {
id
} = parent.drawLayer.draw(DrawingEditor._mergeSVGProperties(this._drawingOptions.toSVGProperties(), drawOutlines.defaultSVGProperties), false, false);
return id;
}
static _mergeSVGProperties(p1, p2) {
const p1Keys = new Set(Object.keys(p1));
for (const [key, value] of Object.entries(p2)) {
if (p1Keys.has(key)) {
Object.assign(p1[key], value);
} else {
p1[key] = value;
}
}
return p1;
}
static getDefaultDrawingOptions(_options) {
unreachable("Not implemented");
}
static get typesMap() {
unreachable("Not implemented");
}
static get isDrawer() {
return true;
}
static get supportMultipleDrawings() {
return false;
}
static updateDefaultParams(type, value) {
const propertyName = this.typesMap.get(type);
if (propertyName) {
this._defaultDrawingOptions.updateProperty(propertyName, value);
}
if (this._currentParent) {
DrawingEditor.#currentDraw.updateProperty(propertyName, value);
this._currentParent.drawLayer.updateProperties(this._currentDrawId, this._defaultDrawingOptions.toSVGProperties());
}
}
updateParams(type, value) {
const propertyName = this.constructor.typesMap.get(type);
if (propertyName) {
this._updateProperty(type, propertyName, value);
}
}
static get defaultPropertiesToUpdate() {
const properties = [];
const options = this._defaultDrawingOptions;
for (const [type, name] of this.typesMap) {
properties.push([type, options[name]]);
}
return properties;
}
get propertiesToUpdate() {
const properties = [];
const {
_drawingOptions
} = this;
for (const [type, name] of this.constructor.typesMap) {
properties.push([type, _drawingOptions[name]]);
}
return properties;
}
_updateProperty(type, name, value) {
const options = this._drawingOptions;
const savedValue = options[name];
const setter = val => {
options.updateProperty(name, val);
const bbox = this.#drawOutlines.updateProperty(name, val);
if (bbox) {
this.#updateBbox(bbox);
}
this.parent?.drawLayer.updateProperties(this._drawId, options.toSVGProperties());
};
this.addCommands({
cmd: setter.bind(this, value),
undo: setter.bind(this, savedValue),
post: this._uiManager.updateUI.bind(this._uiManager, this),
mustExec: true,
type,
overwriteIfSameType: true,
keepUndo: true
});
}
_onResizing() {
this.parent?.drawLayer.updateProperties(this._drawId, DrawingEditor._mergeSVGProperties(this.#drawOutlines.getPathResizingSVGProperties(this.#convertToDrawSpace()), {
bbox: this.#rotateBox()
}));
}
_onResized() {
this.parent?.drawLayer.updateProperties(this._drawId, DrawingEditor._mergeSVGProperties(this.#drawOutlines.getPathResizedSVGProperties(this.#convertToDrawSpace()), {
bbox: this.#rotateBox()
}));
}
_onTranslating(_x, _y) {
this.parent?.drawLayer.updateProperties(this._drawId, {
bbox: this.#rotateBox()
});
}
_onTranslated() {
this.parent?.drawLayer.updateProperties(this._drawId, DrawingEditor._mergeSVGProperties(this.#drawOutlines.getPathTranslatedSVGProperties(this.#convertToDrawSpace(), this.parentDimensions), {
bbox: this.#rotateBox()
}));
}
_onStartDragging() {
this.parent?.drawLayer.updateProperties(this._drawId, {
rootClass: {
moving: true
}
});
}
_onStopDragging() {
this.parent?.drawLayer.updateProperties(this._drawId, {
rootClass: {
moving: false
}
});
}
commit() {
super.commit();
this.disableEditMode();
this.disableEditing();
}
disableEditing() {
super.disableEditing();
this.div.classList.toggle("disabled", true);
}
enableEditing() {
super.enableEditing();
this.div.classList.toggle("disabled", false);
}
getBaseTranslation() {
return [0, 0];
}
get isResizable() {
return true;
}
onceAdded(focus) {
if (!this.annotationElementId) {
this.parent.addUndoableEditor(this);
}
this._isDraggable = true;
if (this.#mustBeCommitted) {
this.#mustBeCommitted = false;
this.commit();
this.parent.setSelected(this);
if (focus && this.isOnScreen) {
this.div.focus();
}
}
}
remove() {
this.#cleanDrawLayer();
super.remove();
}
rebuild() {
if (!this.parent) {
return;
}
super.rebuild();
if (this.div === null) {
return;
}
this.#addToDrawLayer();
this.#updateBbox(this.#drawOutlines.box);
if (!this.isAttachedToDOM) {
this.parent.add(this);
}
}
setParent(parent) {
let mustBeSelected = false;
if (this.parent && !parent) {
this._uiManager.removeShouldRescale(this);
this.#cleanDrawLayer();
} else if (parent) {
this._uiManager.addShouldRescale(this);
this.#addToDrawLayer(parent);
mustBeSelected = !this.parent && this.div?.classList.contains("selectedEditor");
}
super.setParent(parent);
if (mustBeSelected) {
this.select();
}
}
#cleanDrawLayer() {
if (this._drawId === null || !this.parent) {
return;
}
this.parent.drawLayer.remove(this._drawId);
this._drawId = null;
this._drawingOptions.reset();
}
#addToDrawLayer(parent = this.parent) {
if (this._drawId !== null && this.parent === parent) {
return;
}
if (this._drawId !== null) {
this.parent.drawLayer.updateParent(this._drawId, parent.drawLayer);
return;
}
this._drawingOptions.updateAll();
this._drawId = this.#createDrawing(this.#drawOutlines, parent);
}
#convertToParentSpace([x, y, width, height]) {
const {
parentDimensions: [pW, pH],
rotation
} = this;
switch (rotation) {
case 90:
return [y, 1 - x, width * (pH / pW), height * (pW / pH)];
case 180:
return [1 - x, 1 - y, width, height];
case 270:
return [1 - y, x, width * (pH / pW), height * (pW / pH)];
default:
return [x, y, width, height];
}
}
#convertToDrawSpace() {
const {
x,
y,
width,
height,
parentDimensions: [pW, pH],
rotation
} = this;
switch (rotation) {
case 90:
return [1 - y, x, width * (pW / pH), height * (pH / pW)];
case 180:
return [1 - x, 1 - y, width, height];
case 270:
return [y, 1 - x, width * (pW / pH), height * (pH / pW)];
default:
return [x, y, width, height];
}
}
#updateBbox(bbox) {
[this.x, this.y, this.width, this.height] = this.#convertToParentSpace(bbox);
if (this.div) {
this.fixAndSetPosition();
const [parentWidth, parentHeight] = this.parentDimensions;
this.setDims(this.width * parentWidth, this.height * parentHeight);
}
this._onResized();
}
#rotateBox() {
const {
x,
y,
width,
height,
rotation,
parentRotation,
parentDimensions: [pW, pH]
} = this;
switch ((rotation * 4 + parentRotation) / 90) {
case 1:
return [1 - y - height, x, height, width];
case 2:
return [1 - x - width, 1 - y - height, width, height];
case 3:
return [y, 1 - x - width, height, width];
case 4:
return [x, y - width * (pW / pH), height * (pH / pW), width * (pW / pH)];
case 5:
return [1 - y, x, width * (pW / pH), height * (pH / pW)];
case 6:
return [1 - x - height * (pH / pW), 1 - y, height * (pH / pW), width * (pW / pH)];
case 7:
return [y - width * (pW / pH), 1 - x - height * (pH / pW), width * (pW / pH), height * (pH / pW)];
case 8:
return [x - width, y - height, width, height];
case 9:
return [1 - y, x - width, height, width];
case 10:
return [1 - x, 1 - y, width, height];
case 11:
return [y - height, 1 - x, height, width];
case 12:
return [x - height * (pH / pW), y, height * (pH / pW), width * (pW / pH)];
case 13:
return [1 - y - width * (pW / pH), x - height * (pH / pW), width * (pW / pH), height * (pH / pW)];
case 14:
return [1 - x, 1 - y - width * (pW / pH), height * (pH / pW), width * (pW / pH)];
case 15:
return [y, 1 - x, width * (pW / pH), height * (pH / pW)];
default:
return [x, y, width, height];
}
}
rotate() {
if (!this.parent) {
return;
}
this.parent.drawLayer.updateProperties(this._drawId, DrawingEditor._mergeSVGProperties({
bbox: this.#rotateBox()
}, this.#drawOutlines.updateRotation((this.parentRotation - this.rotation + 360) % 360)));
}
onScaleChanging() {
if (!this.parent) {
return;
}
this.#updateBbox(this.#drawOutlines.updateParentDimensions(this.parentDimensions, this.parent.scale));
}
static onScaleChangingWhenDrawing() {}
render() {
if (this.div) {
return this.div;
}
let baseX, baseY;
if (this._isCopy) {
baseX = this.x;
baseY = this.y;
}
const div = super.render();
div.classList.add("draw");
const drawDiv = document.createElement("div");
div.append(drawDiv);
drawDiv.setAttribute("aria-hidden", "true");
drawDiv.className = "internal";
const [parentWidth, parentHeight] = this.parentDimensions;
this.setDims(this.width * parentWidth, this.height * parentHeight);
this._uiManager.addShouldRescale(this);
this.disableEditing();
if (this._isCopy) {
this._moveAfterPaste(baseX, baseY);
}
return div;
}
static createDrawerInstance(_x, _y, _parentWidth, _parentHeight, _rotation) {
unreachable("Not implemented");
}
static startDrawing(parent, uiManager, _isLTR, event) {
const {
target,
offsetX: x,
offsetY: y,
pointerId,
pointerType
} = event;
if (DrawingEditor.#currentPointerType && DrawingEditor.#currentPointerType !== pointerType) {
return;
}
const {
viewport: {
rotation
}
} = parent;
const {
width: parentWidth,
height: parentHeight
} = target.getBoundingClientRect();
const ac = DrawingEditor.#currentDrawingAC = new AbortController();
const signal = parent.combinedSignal(ac);
DrawingEditor.#currentPointerId ||= pointerId;
DrawingEditor.#currentPointerType ??= pointerType;
window.addEventListener("pointerup", e => {
if (DrawingEditor.#currentPointerId === e.pointerId) {
this._endDraw(e);
} else {
DrawingEditor.#currentPointerIds?.delete(e.pointerId);
}
}, {
signal
});
window.addEventListener("pointercancel", e => {
if (DrawingEditor.#currentPointerId === e.pointerId) {
this._currentParent.endDrawingSession();
} else {
DrawingEditor.#currentPointerIds?.delete(e.pointerId);
}
}, {
signal
});
window.addEventListener("pointerdown", e => {
if (DrawingEditor.#currentPointerType !== e.pointerType) {
return;
}
(DrawingEditor.#currentPointerIds ||= new Set()).add(e.pointerId);
if (DrawingEditor.#currentDraw.isCancellable()) {
DrawingEditor.#currentDraw.removeLastElement();
if (DrawingEditor.#currentDraw.isEmpty()) {
this._currentParent.endDrawingSession(true);
} else {
this._endDraw(null);
}
}
}, {
capture: true,
passive: false,
signal
});
window.addEventListener("contextmenu", noContextMenu, {
signal
});
target.addEventListener("pointermove", this._drawMove.bind(this), {
signal
});
target.addEventListener("touchmove", e => {
if (e.timeStamp === DrawingEditor.#currentMoveTimestamp) {
stopEvent(e);
}
}, {
signal
});
parent.toggleDrawing();
uiManager._editorUndoBar?.hide();
if (DrawingEditor.#currentDraw) {
parent.drawLayer.updateProperties(this._currentDrawId, DrawingEditor.#currentDraw.startNew(x, y, parentWidth, parentHeight, rotation));
return;
}
uiManager.updateUIForDefaultProperties(this);
DrawingEditor.#currentDraw = this.createDrawerInstance(x, y, parentWidth, parentHeight, rotation);
DrawingEditor.#currentDrawingOptions = this.getDefaultDrawingOptions();
this._currentParent = parent;
({
id: this._currentDrawId
} = parent.drawLayer.draw(this._mergeSVGProperties(DrawingEditor.#currentDrawingOptions.toSVGProperties(), DrawingEditor.#currentDraw.defaultSVGProperties), true, false));
}
static _drawMove(event) {
DrawingEditor.#currentMoveTimestamp = -1;
if (!DrawingEditor.#currentDraw) {
return;
}
const {
offsetX,
offsetY,
pointerId
} = event;
if (DrawingEditor.#currentPointerId !== pointerId) {
return;
}
if (DrawingEditor.#currentPointerIds?.size >= 1) {
this._endDraw(event);
return;
}
this._currentParent.drawLayer.updateProperties(this._currentDrawId, DrawingEditor.#currentDraw.add(offsetX, offsetY));
DrawingEditor.#currentMoveTimestamp = event.timeStamp;
stopEvent(event);
}
static _cleanup(all) {
if (all) {
this._currentDrawId = -1;
this._currentParent = null;
DrawingEditor.#currentDraw = null;
DrawingEditor.#currentDrawingOptions = null;
DrawingEditor.#currentPointerType = null;
DrawingEditor.#currentMoveTimestamp = NaN;
}
if (DrawingEditor.#currentDrawingAC) {
DrawingEditor.#currentDrawingAC.abort();
DrawingEditor.#currentDrawingAC = null;
DrawingEditor.#currentPointerId = NaN;
DrawingEditor.#currentPointerIds = null;
}
}
static _endDraw(event) {
const parent = this._currentParent;
if (!parent) {
return;
}
parent.toggleDrawing(true);
this._cleanup(false);
if (event?.target === parent.div) {
parent.drawLayer.updateProperties(this._currentDrawId, DrawingEditor.#currentDraw.end(event.offsetX, event.offsetY));
}
if (this.supportMultipleDrawings) {
const draw = DrawingEditor.#currentDraw;
const drawId = this._currentDrawId;
const lastElement = draw.getLastElement();
parent.addCommands({
cmd: () => {
parent.drawLayer.updateProperties(drawId, draw.setLastElement(lastElement));
},
undo: () => {
parent.drawLayer.updateProperties(drawId, draw.removeLastElement());
},
mustExec: false,
type: AnnotationEditorParamsType.DRAW_STEP
});
return;
}
this.endDrawing(false);
}
static endDrawing(isAborted) {
const parent = this._currentParent;
if (!parent) {
return null;
}
parent.toggleDrawing(true);
parent.cleanUndoStack(AnnotationEditorParamsType.DRAW_STEP);
if (!DrawingEditor.#currentDraw.isEmpty()) {
const {
pageDimensions: [pageWidth, pageHeight],
scale
} = parent;
const editor = parent.createAndAddNewEditor({
offsetX: 0,
offsetY: 0
}, false, {
drawId: this._currentDrawId,
drawOutlines: DrawingEditor.#currentDraw.getOutlines(pageWidth * scale, pageHeight * scale, scale, this._INNER_MARGIN),
drawingOptions: DrawingEditor.#currentDrawingOptions,
mustBeCommitted: !isAborted
});
this._cleanup(true);
return editor;
}
parent.drawLayer.remove(this._currentDrawId);
this._cleanup(true);
return null;
}
createDrawingOptions(_data) {}
static deserializeDraw(_pageX, _pageY, _pageWidth, _pageHeight, _innerWidth, _data) {
unreachable("Not implemented");
}
static async deserialize(data, parent, uiManager) {
const {
rawDims: {
pageWidth,
pageHeight,
pageX,
pageY
}
} = parent.viewport;
const drawOutlines = this.deserializeDraw(pageX, pageY, pageWidth, pageHeight, this._INNER_MARGIN, data);
const editor = await super.deserialize(data, parent, uiManager);
editor.createDrawingOptions(data);
editor.#createDrawOutlines({
drawOutlines
});
editor.#addToDrawLayer();
editor.onScaleChanging();
editor.rotate();
return editor;
}
serializeDraw(isForCopying) {
const [pageX, pageY] = this.pageTranslation;
const [pageWidth, pageHeight] = this.pageDimensions;
return this.#drawOutlines.serialize([pageX, pageY, pageWidth, pageHeight], isForCopying);
}
renderAnnotationElement(annotation) {
annotation.updateEdited({
rect: this.getRect(0, 0)
});
return null;
}
static canCreateNewEmptyEditor() {
return false;
}
}
;// ./src/display/editor/drawers/inkdraw.js
class InkDrawOutliner {
#last = new Float64Array(6);
#line;
#lines;
#rotation;
#thickness;
#points;
#lastSVGPath = "";
#lastIndex = 0;
#outlines = new InkDrawOutline();
#parentWidth;
#parentHeight;
constructor(x, y, parentWidth, parentHeight, rotation, thickness) {
this.#parentWidth = parentWidth;
this.#parentHeight = parentHeight;
this.#rotation = rotation;
this.#thickness = thickness;
[x, y] = this.#normalizePoint(x, y);
const line = this.#line = [NaN, NaN, NaN, NaN, x, y];
this.#points = [x, y];
this.#lines = [{
line,
points: this.#points
}];
this.#last.set(line, 0);
}
updateProperty(name, value) {
if (name === "stroke-width") {
this.#thickness = value;
}
}
#normalizePoint(x, y) {
return Outline._normalizePoint(x, y, this.#parentWidth, this.#parentHeight, this.#rotation);
}
isEmpty() {
return !this.#lines || this.#lines.length === 0;
}
isCancellable() {
return this.#points.length <= 10;
}
add(x, y) {
[x, y] = this.#normalizePoint(x, y);
const [x1, y1, x2, y2] = this.#last.subarray(2, 6);
const diffX = x - x2;
const diffY = y - y2;
const d = Math.hypot(this.#parentWidth * diffX, this.#parentHeight * diffY);
if (d <= 2) {
return null;
}
this.#points.push(x, y);
if (isNaN(x1)) {
this.#last.set([x2, y2, x, y], 2);
this.#line.push(NaN, NaN, NaN, NaN, x, y);
return {
path: {
d: this.toSVGPath()
}
};
}
if (isNaN(this.#last[0])) {
this.#line.splice(6, 6);
}
this.#last.set([x1, y1, x2, y2, x, y], 0);
this.#line.push(...Outline.createBezierPoints(x1, y1, x2, y2, x, y));
return {
path: {
d: this.toSVGPath()
}
};
}
end(x, y) {
const change = this.add(x, y);
if (change) {
return change;
}
if (this.#points.length === 2) {
return {
path: {
d: this.toSVGPath()
}
};
}
return null;
}
startNew(x, y, parentWidth, parentHeight, rotation) {
this.#parentWidth = parentWidth;
this.#parentHeight = parentHeight;
this.#rotation = rotation;
[x, y] = this.#normalizePoint(x, y);
const line = this.#line = [NaN, NaN, NaN, NaN, x, y];
this.#points = [x, y];
const last = this.#lines.at(-1);
if (last) {
last.line = new Float32Array(last.line);
last.points = new Float32Array(last.points);
}
this.#lines.push({
line,
points: this.#points
});
this.#last.set(line, 0);
this.#lastIndex = 0;
this.toSVGPath();
return null;
}
getLastElement() {
return this.#lines.at(-1);
}
setLastElement(element) {
if (!this.#lines) {
return this.#outlines.setLastElement(element);
}
this.#lines.push(element);
this.#line = element.line;
this.#points = element.points;
this.#lastIndex = 0;
return {
path: {
d: this.toSVGPath()
}
};
}
removeLastElement() {
if (!this.#lines) {
return this.#outlines.removeLastElement();
}
this.#lines.pop();
this.#lastSVGPath = "";
for (let i = 0, ii = this.#lines.length; i < ii; i++) {
const {
line,
points
} = this.#lines[i];
this.#line = line;
this.#points = points;
this.#lastIndex = 0;
this.toSVGPath();
}
return {
path: {
d: this.#lastSVGPath
}
};
}
toSVGPath() {
const firstX = Outline.svgRound(this.#line[4]);
const firstY = Outline.svgRound(this.#line[5]);
if (this.#points.length === 2) {
this.#lastSVGPath = `${this.#lastSVGPath} M ${firstX} ${firstY} Z`;
return this.#lastSVGPath;
}
if (this.#points.length <= 6) {
const i = this.#lastSVGPath.lastIndexOf("M");
this.#lastSVGPath = `${this.#lastSVGPath.slice(0, i)} M ${firstX} ${firstY}`;
this.#lastIndex = 6;
}
if (this.#points.length === 4) {
const secondX = Outline.svgRound(this.#line[10]);
const secondY = Outline.svgRound(this.#line[11]);
this.#lastSVGPath = `${this.#lastSVGPath} L ${secondX} ${secondY}`;
this.#lastIndex = 12;
return this.#lastSVGPath;
}
const buffer = [];
if (this.#lastIndex === 0) {
buffer.push(`M ${firstX} ${firstY}`);
this.#lastIndex = 6;
}
for (let i = this.#lastIndex, ii = this.#line.length; i < ii; i += 6) {
const [c1x, c1y, c2x, c2y, x, y] = this.#line.slice(i, i + 6).map(Outline.svgRound);
buffer.push(`C${c1x} ${c1y} ${c2x} ${c2y} ${x} ${y}`);
}
this.#lastSVGPath += buffer.join(" ");
this.#lastIndex = this.#line.length;
return this.#lastSVGPath;
}
getOutlines(parentWidth, parentHeight, scale, innerMargin) {
const last = this.#lines.at(-1);
last.line = new Float32Array(last.line);
last.points = new Float32Array(last.points);
this.#outlines.build(this.#lines, parentWidth, parentHeight, scale, this.#rotation, this.#thickness, innerMargin);
this.#last = null;
this.#line = null;
this.#lines = null;
this.#lastSVGPath = null;
return this.#outlines;
}
get defaultSVGProperties() {
return {
root: {
viewBox: "0 0 10000 10000"
},
rootClass: {
draw: true
},
bbox: [0, 0, 1, 1]
};
}
}
class InkDrawOutline extends Outline {
#bbox;
#currentRotation = 0;
#innerMargin;
#lines;
#parentWidth;
#parentHeight;
#parentScale;
#rotation;
#thickness;
build(lines, parentWidth, parentHeight, parentScale, rotation, thickness, innerMargin) {
this.#parentWidth = parentWidth;
this.#parentHeight = parentHeight;
this.#parentScale = parentScale;
this.#rotation = rotation;
this.#thickness = thickness;
this.#innerMargin = innerMargin ?? 0;
this.#lines = lines;
this.#computeBbox();
}
get thickness() {
return this.#thickness;
}
setLastElement(element) {
this.#lines.push(element);
return {
path: {
d: this.toSVGPath()
}
};
}
removeLastElement() {
this.#lines.pop();
return {
path: {
d: this.toSVGPath()
}
};
}
toSVGPath() {
const buffer = [];
for (const {
line
} of this.#lines) {
buffer.push(`M${Outline.svgRound(line[4])} ${Outline.svgRound(line[5])}`);
if (line.length === 6) {
buffer.push("Z");
continue;
}
if (line.length === 12 && isNaN(line[6])) {
buffer.push(`L${Outline.svgRound(line[10])} ${Outline.svgRound(line[11])}`);
continue;
}
for (let i = 6, ii = line.length; i < ii; i += 6) {
const [c1x, c1y, c2x, c2y, x, y] = line.subarray(i, i + 6).map(Outline.svgRound);
buffer.push(`C${c1x} ${c1y} ${c2x} ${c2y} ${x} ${y}`);
}
}
return buffer.join("");
}
serialize([pageX, pageY, pageWidth, pageHeight], isForCopying) {
const serializedLines = [];
const serializedPoints = [];
const [x, y, width, height] = this.#getBBoxWithNoMargin();
let tx, ty, sx, sy, x1, y1, x2, y2, rescaleFn;
switch (this.#rotation) {
case 0:
rescaleFn = Outline._rescale;
tx = pageX;
ty = pageY + pageHeight;
sx = pageWidth;
sy = -pageHeight;
x1 = pageX + x * pageWidth;
y1 = pageY + (1 - y - height) * pageHeight;
x2 = pageX + (x + width) * pageWidth;
y2 = pageY + (1 - y) * pageHeight;
break;
case 90:
rescaleFn = Outline._rescaleAndSwap;
tx = pageX;
ty = pageY;
sx = pageWidth;
sy = pageHeight;
x1 = pageX + y * pageWidth;
y1 = pageY + x * pageHeight;
x2 = pageX + (y + height) * pageWidth;
y2 = pageY + (x + width) * pageHeight;
break;
case 180:
rescaleFn = Outline._rescale;
tx = pageX + pageWidth;
ty = pageY;
sx = -pageWidth;
sy = pageHeight;
x1 = pageX + (1 - x - width) * pageWidth;
y1 = pageY + y * pageHeight;
x2 = pageX + (1 - x) * pageWidth;
y2 = pageY + (y + height) * pageHeight;
break;
case 270:
rescaleFn = Outline._rescaleAndSwap;
tx = pageX + pageWidth;
ty = pageY + pageHeight;
sx = -pageWidth;
sy = -pageHeight;
x1 = pageX + (1 - y - height) * pageWidth;
y1 = pageY + (1 - x - width) * pageHeight;
x2 = pageX + (1 - y) * pageWidth;
y2 = pageY + (1 - x) * pageHeight;
break;
}
for (const {
line,
points
} of this.#lines) {
serializedLines.push(rescaleFn(line, tx, ty, sx, sy, isForCopying ? new Array(line.length) : null));
serializedPoints.push(rescaleFn(points, tx, ty, sx, sy, isForCopying ? new Array(points.length) : null));
}
return {
lines: serializedLines,
points: serializedPoints,
rect: [x1, y1, x2, y2]
};
}
static deserialize(pageX, pageY, pageWidth, pageHeight, innerMargin, {
paths: {
lines,
points
},
rotation,
thickness
}) {
const newLines = [];
let tx, ty, sx, sy, rescaleFn;
switch (rotation) {
case 0:
rescaleFn = Outline._rescale;
tx = -pageX / pageWidth;
ty = pageY / pageHeight + 1;
sx = 1 / pageWidth;
sy = -1 / pageHeight;
break;
case 90:
rescaleFn = Outline._rescaleAndSwap;
tx = -pageY / pageHeight;
ty = -pageX / pageWidth;
sx = 1 / pageHeight;
sy = 1 / pageWidth;
break;
case 180:
rescaleFn = Outline._rescale;
tx = pageX / pageWidth + 1;
ty = -pageY / pageHeight;
sx = -1 / pageWidth;
sy = 1 / pageHeight;
break;
case 270:
rescaleFn = Outline._rescaleAndSwap;
tx = pageY / pageHeight + 1;
ty = pageX / pageWidth + 1;
sx = -1 / pageHeight;
sy = -1 / pageWidth;
break;
}
if (!lines) {
lines = [];
for (const point of points) {
const len = point.length;
if (len === 2) {
lines.push(new Float32Array([NaN, NaN, NaN, NaN, point[0], point[1]]));
continue;
}
if (len === 4) {
lines.push(new Float32Array([NaN, NaN, NaN, NaN, point[0], point[1], NaN, NaN, NaN, NaN, point[2], point[3]]));
continue;
}
const line = new Float32Array(3 * (len - 2));
lines.push(line);
let [x1, y1, x2, y2] = point.subarray(0, 4);
line.set([NaN, NaN, NaN, NaN, x1, y1], 0);
for (let i = 4; i < len; i += 2) {
const x = point[i];
const y = point[i + 1];
line.set(Outline.createBezierPoints(x1, y1, x2, y2, x, y), (i - 2) * 3);
[x1, y1, x2, y2] = [x2, y2, x, y];
}
}
}
for (let i = 0, ii = lines.length; i < ii; i++) {
newLines.push({
line: rescaleFn(lines[i].map(x => x ?? NaN), tx, ty, sx, sy),
points: rescaleFn(points[i].map(x => x ?? NaN), tx, ty, sx, sy)
});
}
const outlines = new this.prototype.constructor();
outlines.build(newLines, pageWidth, pageHeight, 1, rotation, thickness, innerMargin);
return outlines;
}
#getMarginComponents(thickness = this.#thickness) {
const margin = this.#innerMargin + thickness / 2 * this.#parentScale;
return this.#rotation % 180 === 0 ? [margin / this.#parentWidth, margin / this.#parentHeight] : [margin / this.#parentHeight, margin / this.#parentWidth];
}
#getBBoxWithNoMargin() {
const [x, y, width, height] = this.#bbox;
const [marginX, marginY] = this.#getMarginComponents(0);
return [x + marginX, y + marginY, width - 2 * marginX, height - 2 * marginY];
}
#computeBbox() {
const bbox = this.#bbox = new Float32Array([Infinity, Infinity, -Infinity, -Infinity]);
for (const {
line
} of this.#lines) {
if (line.length <= 12) {
for (let i = 4, ii = line.length; i < ii; i += 6) {
Util.pointBoundingBox(line[i], line[i + 1], bbox);
}
continue;
}
let lastX = line[4],
lastY = line[5];
for (let i = 6, ii = line.length; i < ii; i += 6) {
const [c1x, c1y, c2x, c2y, x, y] = line.subarray(i, i + 6);
Util.bezierBoundingBox(lastX, lastY, c1x, c1y, c2x, c2y, x, y, bbox);
lastX = x;
lastY = y;
}
}
const [marginX, marginY] = this.#getMarginComponents();
bbox[0] = MathClamp(bbox[0] - marginX, 0, 1);
bbox[1] = MathClamp(bbox[1] - marginY, 0, 1);
bbox[2] = MathClamp(bbox[2] + marginX, 0, 1);
bbox[3] = MathClamp(bbox[3] + marginY, 0, 1);
bbox[2] -= bbox[0];
bbox[3] -= bbox[1];
}
get box() {
return this.#bbox;
}
updateProperty(name, value) {
if (name === "stroke-width") {
return this.#updateThickness(value);
}
return null;
}
#updateThickness(thickness) {
const [oldMarginX, oldMarginY] = this.#getMarginComponents();
this.#thickness = thickness;
const [newMarginX, newMarginY] = this.#getMarginComponents();
const [diffMarginX, diffMarginY] = [newMarginX - oldMarginX, newMarginY - oldMarginY];
const bbox = this.#bbox;
bbox[0] -= diffMarginX;
bbox[1] -= diffMarginY;
bbox[2] += 2 * diffMarginX;
bbox[3] += 2 * diffMarginY;
return bbox;
}
updateParentDimensions([width, height], scale) {
const [oldMarginX, oldMarginY] = this.#getMarginComponents();
this.#parentWidth = width;
this.#parentHeight = height;
this.#parentScale = scale;
const [newMarginX, newMarginY] = this.#getMarginComponents();
const diffMarginX = newMarginX - oldMarginX;
const diffMarginY = newMarginY - oldMarginY;
const bbox = this.#bbox;
bbox[0] -= diffMarginX;
bbox[1] -= diffMarginY;
bbox[2] += 2 * diffMarginX;
bbox[3] += 2 * diffMarginY;
return bbox;
}
updateRotation(rotation) {
this.#currentRotation = rotation;
return {
path: {
transform: this.rotationTransform
}
};
}
get viewBox() {
return this.#bbox.map(Outline.svgRound).join(" ");
}
get defaultProperties() {
const [x, y] = this.#bbox;
return {
root: {
viewBox: this.viewBox
},
path: {
"transform-origin": `${Outline.svgRound(x)} ${Outline.svgRound(y)}`
}
};
}
get rotationTransform() {
const [,, width, height] = this.#bbox;
let a = 0,
b = 0,
c = 0,
d = 0,
e = 0,
f = 0;
switch (this.#currentRotation) {
case 90:
b = height / width;
c = -width / height;
e = width;
break;
case 180:
a = -1;
d = -1;
e = width;
f = height;
break;
case 270:
b = -height / width;
c = width / height;
f = height;
break;
default:
return "";
}
return `matrix(${a} ${b} ${c} ${d} ${Outline.svgRound(e)} ${Outline.svgRound(f)})`;
}
getPathResizingSVGProperties([newX, newY, newWidth, newHeight]) {
const [marginX, marginY] = this.#getMarginComponents();
const [x, y, width, height] = this.#bbox;
if (Math.abs(width - marginX) <= Outline.PRECISION || Math.abs(height - marginY) <= Outline.PRECISION) {
const tx = newX + newWidth / 2 - (x + width / 2);
const ty = newY + newHeight / 2 - (y + height / 2);
return {
path: {
"transform-origin": `${Outline.svgRound(newX)} ${Outline.svgRound(newY)}`,
transform: `${this.rotationTransform} translate(${tx} ${ty})`
}
};
}
const s1x = (newWidth - 2 * marginX) / (width - 2 * marginX);
const s1y = (newHeight - 2 * marginY) / (height - 2 * marginY);
const s2x = width / newWidth;
const s2y = height / newHeight;
return {
path: {
"transform-origin": `${Outline.svgRound(x)} ${Outline.svgRound(y)}`,
transform: `${this.rotationTransform} scale(${s2x} ${s2y}) ` + `translate(${Outline.svgRound(marginX)} ${Outline.svgRound(marginY)}) scale(${s1x} ${s1y}) ` + `translate(${Outline.svgRound(-marginX)} ${Outline.svgRound(-marginY)})`
}
};
}
getPathResizedSVGProperties([newX, newY, newWidth, newHeight]) {
const [marginX, marginY] = this.#getMarginComponents();
const bbox = this.#bbox;
const [x, y, width, height] = bbox;
bbox[0] = newX;
bbox[1] = newY;
bbox[2] = newWidth;
bbox[3] = newHeight;
if (Math.abs(width - marginX) <= Outline.PRECISION || Math.abs(height - marginY) <= Outline.PRECISION) {
const tx = newX + newWidth / 2 - (x + width / 2);
const ty = newY + newHeight / 2 - (y + height / 2);
for (const {
line,
points
} of this.#lines) {
Outline._translate(line, tx, ty, line);
Outline._translate(points, tx, ty, points);
}
return {
root: {
viewBox: this.viewBox
},
path: {
"transform-origin": `${Outline.svgRound(newX)} ${Outline.svgRound(newY)}`,
transform: this.rotationTransform || null,
d: this.toSVGPath()
}
};
}
const s1x = (newWidth - 2 * marginX) / (width - 2 * marginX);
const s1y = (newHeight - 2 * marginY) / (height - 2 * marginY);
const tx = -s1x * (x + marginX) + newX + marginX;
const ty = -s1y * (y + marginY) + newY + marginY;
if (s1x !== 1 || s1y !== 1 || tx !== 0 || ty !== 0) {
for (const {
line,
points
} of this.#lines) {
Outline._rescale(line, tx, ty, s1x, s1y, line);
Outline._rescale(points, tx, ty, s1x, s1y, points);
}
}
return {
root: {
viewBox: this.viewBox
},
path: {
"transform-origin": `${Outline.svgRound(newX)} ${Outline.svgRound(newY)}`,
transform: this.rotationTransform || null,
d: this.toSVGPath()
}
};
}
getPathTranslatedSVGProperties([newX, newY], parentDimensions) {
const [newParentWidth, newParentHeight] = parentDimensions;
const bbox = this.#bbox;
const tx = newX - bbox[0];
const ty = newY - bbox[1];
if (this.#parentWidth === newParentWidth && this.#parentHeight === newParentHeight) {
for (const {
line,
points
} of this.#lines) {
Outline._translate(line, tx, ty, line);
Outline._translate(points, tx, ty, points);
}
} else {
const sx = this.#parentWidth / newParentWidth;
const sy = this.#parentHeight / newParentHeight;
this.#parentWidth = newParentWidth;
this.#parentHeight = newParentHeight;
for (const {
line,
points
} of this.#lines) {
Outline._rescale(line, tx, ty, sx, sy, line);
Outline._rescale(points, tx, ty, sx, sy, points);
}
bbox[2] *= sx;
bbox[3] *= sy;
}
bbox[0] = newX;
bbox[1] = newY;
return {
root: {
viewBox: this.viewBox
},
path: {
d: this.toSVGPath(),
"transform-origin": `${Outline.svgRound(newX)} ${Outline.svgRound(newY)}`
}
};
}
get defaultSVGProperties() {
const bbox = this.#bbox;
return {
root: {
viewBox: this.viewBox
},
rootClass: {
draw: true
},
path: {
d: this.toSVGPath(),
"transform-origin": `${Outline.svgRound(bbox[0])} ${Outline.svgRound(bbox[1])}`,
transform: this.rotationTransform || null
},
bbox
};
}
}
;// ./src/display/editor/ink.js
class InkDrawingOptions extends DrawingOptions {
constructor(viewerParameters) {
super();
this._viewParameters = viewerParameters;
super.updateProperties({
fill: "none",
stroke: AnnotationEditor._defaultLineColor,
"stroke-opacity": 1,
"stroke-width": 1,
"stroke-linecap": "round",
"stroke-linejoin": "round",
"stroke-miterlimit": 10
});
}
updateSVGProperty(name, value) {
if (name === "stroke-width") {
value ??= this["stroke-width"];
value *= this._viewParameters.realScale;
}
super.updateSVGProperty(name, value);
}
clone() {
const clone = new InkDrawingOptions(this._viewParameters);
clone.updateAll(this);
return clone;
}
}
class InkEditor extends DrawingEditor {
static _type = "ink";
static _editorType = AnnotationEditorType.INK;
static _defaultDrawingOptions = null;
constructor(params) {
super({
...params,
name: "inkEditor"
});
this._willKeepAspectRatio = true;
this.defaultL10nId = "pdfjs-editor-ink-editor";
}
static initialize(l10n, uiManager) {
AnnotationEditor.initialize(l10n, uiManager);
this._defaultDrawingOptions = new InkDrawingOptions(uiManager.viewParameters);
}
static getDefaultDrawingOptions(options) {
const clone = this._defaultDrawingOptions.clone();
clone.updateProperties(options);
return clone;
}
static get supportMultipleDrawings() {
return true;
}
static get typesMap() {
return shadow(this, "typesMap", new Map([[AnnotationEditorParamsType.INK_THICKNESS, "stroke-width"], [AnnotationEditorParamsType.INK_COLOR, "stroke"], [AnnotationEditorParamsType.INK_OPACITY, "stroke-opacity"]]));
}
static createDrawerInstance(x, y, parentWidth, parentHeight, rotation) {
return new InkDrawOutliner(x, y, parentWidth, parentHeight, rotation, this._defaultDrawingOptions["stroke-width"]);
}
static deserializeDraw(pageX, pageY, pageWidth, pageHeight, innerMargin, data) {
return InkDrawOutline.deserialize(pageX, pageY, pageWidth, pageHeight, innerMargin, data);
}
static async deserialize(data, parent, uiManager) {
let initialData = null;
if (data instanceof InkAnnotationElement) {
const {
data: {
inkLists,
rect,
rotation,
id,
color,
opacity,
borderStyle: {
rawWidth: thickness
},
popupRef
},
parent: {
page: {
pageNumber
}
}
} = data;
initialData = data = {
annotationType: AnnotationEditorType.INK,
color: Array.from(color),
thickness,
opacity,
paths: {
points: inkLists
},
boxes: null,
pageIndex: pageNumber - 1,
rect: rect.slice(0),
rotation,
id,
deleted: false,
popupRef
};
}
const editor = await super.deserialize(data, parent, uiManager);
editor.annotationElementId = data.id || null;
editor._initialData = initialData;
return editor;
}
onScaleChanging() {
if (!this.parent) {
return;
}
super.onScaleChanging();
const {
_drawId,
_drawingOptions,
parent
} = this;
_drawingOptions.updateSVGProperty("stroke-width");
parent.drawLayer.updateProperties(_drawId, _drawingOptions.toSVGProperties());
}
static onScaleChangingWhenDrawing() {
const parent = this._currentParent;
if (!parent) {
return;
}
super.onScaleChangingWhenDrawing();
this._defaultDrawingOptions.updateSVGProperty("stroke-width");
parent.drawLayer.updateProperties(this._currentDrawId, this._defaultDrawingOptions.toSVGProperties());
}
createDrawingOptions({
color,
thickness,
opacity
}) {
this._drawingOptions = InkEditor.getDefaultDrawingOptions({
stroke: Util.makeHexColor(...color),
"stroke-width": thickness,
"stroke-opacity": opacity
});
}
serialize(isForCopying = false) {
if (this.isEmpty()) {
return null;
}
if (this.deleted) {
return this.serializeDeleted();
}
const {
lines,
points,
rect
} = this.serializeDraw(isForCopying);
const {
_drawingOptions: {
stroke,
"stroke-opacity": opacity,
"stroke-width": thickness
}
} = this;
const serialized = {
annotationType: AnnotationEditorType.INK,
color: AnnotationEditor._colorManager.convert(stroke),
opacity,
thickness,
paths: {
lines,
points
},
pageIndex: this.pageIndex,
rect,
rotation: this.rotation,
structTreeParentId: this._structTreeParentId
};
if (isForCopying) {
serialized.isCopy = true;
return serialized;
}
if (this.annotationElementId && !this.#hasElementChanged(serialized)) {
return null;
}
serialized.id = this.annotationElementId;
return serialized;
}
#hasElementChanged(serialized) {
const {
color,
thickness,
opacity,
pageIndex
} = this._initialData;
return this._hasBeenMoved || this._hasBeenResized || serialized.color.some((c, i) => c !== color[i]) || serialized.thickness !== thickness || serialized.opacity !== opacity || serialized.pageIndex !== pageIndex;
}
renderAnnotationElement(annotation) {
const {
points,
rect
} = this.serializeDraw(false);
annotation.updateEdited({
rect,
thickness: this._drawingOptions["stroke-width"],
points
});
return null;
}
}
;// ./src/display/editor/drawers/contour.js
class ContourDrawOutline extends InkDrawOutline {
toSVGPath() {
let path = super.toSVGPath();
if (!path.endsWith("Z")) {
path += "Z";
}
return path;
}
}
;// ./src/display/editor/drawers/signaturedraw.js
const BASE_HEADER_LENGTH = 8;
const POINTS_PROPERTIES_NUMBER = 3;
class SignatureExtractor {
static #PARAMETERS = {
maxDim: 512,
sigmaSFactor: 0.02,
sigmaR: 25,
kernelSize: 16
};
static #neighborIndexToId(i0, j0, i, j) {
i -= i0;
j -= j0;
if (i === 0) {
return j > 0 ? 0 : 4;
}
if (i === 1) {
return j + 6;
}
return 2 - j;
}
static #neighborIdToIndex = new Int32Array([0, 1, -1, 1, -1, 0, -1, -1, 0, -1, 1, -1, 1, 0, 1, 1]);
static #clockwiseNonZero(buf, width, i0, j0, i, j, offset) {
const id = this.#neighborIndexToId(i0, j0, i, j);
for (let k = 0; k < 8; k++) {
const kk = (-k + id - offset + 16) % 8;
const shiftI = this.#neighborIdToIndex[2 * kk];
const shiftJ = this.#neighborIdToIndex[2 * kk + 1];
if (buf[(i0 + shiftI) * width + (j0 + shiftJ)] !== 0) {
return kk;
}
}
return -1;
}
static #counterClockwiseNonZero(buf, width, i0, j0, i, j, offset) {
const id = this.#neighborIndexToId(i0, j0, i, j);
for (let k = 0; k < 8; k++) {
const kk = (k + id + offset + 16) % 8;
const shiftI = this.#neighborIdToIndex[2 * kk];
const shiftJ = this.#neighborIdToIndex[2 * kk + 1];
if (buf[(i0 + shiftI) * width + (j0 + shiftJ)] !== 0) {
return kk;
}
}
return -1;
}
static #findContours(buf, width, height, threshold) {
const N = buf.length;
const types = new Int32Array(N);
for (let i = 0; i < N; i++) {
types[i] = buf[i] <= threshold ? 1 : 0;
}
for (let i = 1; i < height - 1; i++) {
types[i * width] = types[i * width + width - 1] = 0;
}
for (let i = 0; i < width; i++) {
types[i] = types[width * height - 1 - i] = 0;
}
let nbd = 1;
let lnbd;
const contours = [];
for (let i = 1; i < height - 1; i++) {
lnbd = 1;
for (let j = 1; j < width - 1; j++) {
const ij = i * width + j;
const pix = types[ij];
if (pix === 0) {
continue;
}
let i2 = i;
let j2 = j;
if (pix === 1 && types[ij - 1] === 0) {
nbd += 1;
j2 -= 1;
} else if (pix >= 1 && types[ij + 1] === 0) {
nbd += 1;
j2 += 1;
if (pix > 1) {
lnbd = pix;
}
} else {
if (pix !== 1) {
lnbd = Math.abs(pix);
}
continue;
}
const points = [j, i];
const isHole = j2 === j + 1;
const contour = {
isHole,
points,
id: nbd,
parent: 0
};
contours.push(contour);
let contour0;
for (const c of contours) {
if (c.id === lnbd) {
contour0 = c;
break;
}
}
if (!contour0) {
contour.parent = isHole ? lnbd : 0;
} else if (contour0.isHole) {
contour.parent = isHole ? contour0.parent : lnbd;
} else {
contour.parent = isHole ? lnbd : contour0.parent;
}
const k = this.#clockwiseNonZero(types, width, i, j, i2, j2, 0);
if (k === -1) {
types[ij] = -nbd;
if (types[ij] !== 1) {
lnbd = Math.abs(types[ij]);
}
continue;
}
let shiftI = this.#neighborIdToIndex[2 * k];
let shiftJ = this.#neighborIdToIndex[2 * k + 1];
const i1 = i + shiftI;
const j1 = j + shiftJ;
i2 = i1;
j2 = j1;
let i3 = i;
let j3 = j;
while (true) {
const kk = this.#counterClockwiseNonZero(types, width, i3, j3, i2, j2, 1);
shiftI = this.#neighborIdToIndex[2 * kk];
shiftJ = this.#neighborIdToIndex[2 * kk + 1];
const i4 = i3 + shiftI;
const j4 = j3 + shiftJ;
points.push(j4, i4);
const ij3 = i3 * width + j3;
if (types[ij3 + 1] === 0) {
types[ij3] = -nbd;
} else if (types[ij3] === 1) {
types[ij3] = nbd;
}
if (i4 === i && j4 === j && i3 === i1 && j3 === j1) {
if (types[ij] !== 1) {
lnbd = Math.abs(types[ij]);
}
break;
} else {
i2 = i3;
j2 = j3;
i3 = i4;
j3 = j4;
}
}
}
}
return contours;
}
static #douglasPeuckerHelper(points, start, end, output) {
if (end - start <= 4) {
for (let i = start; i < end - 2; i += 2) {
output.push(points[i], points[i + 1]);
}
return;
}
const ax = points[start];
const ay = points[start + 1];
const abx = points[end - 4] - ax;
const aby = points[end - 3] - ay;
const dist = Math.hypot(abx, aby);
const nabx = abx / dist;
const naby = aby / dist;
const aa = nabx * ay - naby * ax;
const m = aby / abx;
const invS = 1 / dist;
const phi = Math.atan(m);
const cosPhi = Math.cos(phi);
const sinPhi = Math.sin(phi);
const tmax = invS * (Math.abs(cosPhi) + Math.abs(sinPhi));
const poly = invS * (1 - tmax + tmax ** 2);
const partialPhi = Math.max(Math.atan(Math.abs(sinPhi + cosPhi) * poly), Math.atan(Math.abs(sinPhi - cosPhi) * poly));
let dmax = 0;
let index = start;
for (let i = start + 2; i < end - 2; i += 2) {
const d = Math.abs(aa - nabx * points[i + 1] + naby * points[i]);
if (d > dmax) {
index = i;
dmax = d;
}
}
if (dmax > (dist * partialPhi) ** 2) {
this.#douglasPeuckerHelper(points, start, index + 2, output);
this.#douglasPeuckerHelper(points, index, end, output);
} else {
output.push(ax, ay);
}
}
static #douglasPeucker(points) {
const output = [];
const len = points.length;
this.#douglasPeuckerHelper(points, 0, len, output);
output.push(points[len - 2], points[len - 1]);
return output.length <= 4 ? null : output;
}
static #bilateralFilter(buf, width, height, sigmaS, sigmaR, kernelSize) {
const kernel = new Float32Array(kernelSize ** 2);
const sigmaS2 = -2 * sigmaS ** 2;
const halfSize = kernelSize >> 1;
for (let i = 0; i < kernelSize; i++) {
const x = (i - halfSize) ** 2;
for (let j = 0; j < kernelSize; j++) {
kernel[i * kernelSize + j] = Math.exp((x + (j - halfSize) ** 2) / sigmaS2);
}
}
const rangeValues = new Float32Array(256);
const sigmaR2 = -2 * sigmaR ** 2;
for (let i = 0; i < 256; i++) {
rangeValues[i] = Math.exp(i ** 2 / sigmaR2);
}
const N = buf.length;
const out = new Uint8Array(N);
const histogram = new Uint32Array(256);
for (let i = 0; i < height; i++) {
for (let j = 0; j < width; j++) {
const ij = i * width + j;
const center = buf[ij];
let sum = 0;
let norm = 0;
for (let k = 0; k < kernelSize; k++) {
const y = i + k - halfSize;
if (y < 0 || y >= height) {
continue;
}
for (let l = 0; l < kernelSize; l++) {
const x = j + l - halfSize;
if (x < 0 || x >= width) {
continue;
}
const neighbour = buf[y * width + x];
const w = kernel[k * kernelSize + l] * rangeValues[Math.abs(neighbour - center)];
sum += neighbour * w;
norm += w;
}
}
const pix = out[ij] = Math.round(sum / norm);
histogram[pix]++;
}
}
return [out, histogram];
}
static #getHistogram(buf) {
const histogram = new Uint32Array(256);
for (const g of buf) {
histogram[g]++;
}
return histogram;
}
static #toUint8(buf) {
const N = buf.length;
const out = new Uint8ClampedArray(N >> 2);
let max = -Infinity;
let min = Infinity;
for (let i = 0, ii = out.length; i < ii; i++) {
const A = buf[(i << 2) + 3];
if (A === 0) {
max = out[i] = 0xff;
continue;
}
const pix = out[i] = buf[i << 2];
if (pix > max) {
max = pix;
}
if (pix < min) {
min = pix;
}
}
const ratio = 255 / (max - min);
for (let i = 0; i < N; i++) {
out[i] = (out[i] - min) * ratio;
}
return out;
}
static #guessThreshold(histogram) {
let i;
let M = -Infinity;
let L = -Infinity;
const min = histogram.findIndex(v => v !== 0);
let pos = min;
let spos = min;
for (i = min; i < 256; i++) {
const v = histogram[i];
if (v > M) {
if (i - pos > L) {
L = i - pos;
spos = i - 1;
}
M = v;
pos = i;
}
}
for (i = spos - 1; i >= 0; i--) {
if (histogram[i] > histogram[i + 1]) {
break;
}
}
return i;
}
static #getGrayPixels(bitmap) {
const originalBitmap = bitmap;
const {
width,
height
} = bitmap;
const {
maxDim
} = this.#PARAMETERS;
let newWidth = width;
let newHeight = height;
if (width > maxDim || height > maxDim) {
let prevWidth = width;
let prevHeight = height;
let steps = Math.log2(Math.max(width, height) / maxDim);
const isteps = Math.floor(steps);
steps = steps === isteps ? isteps - 1 : isteps;
for (let i = 0; i < steps; i++) {
newWidth = prevWidth;
newHeight = prevHeight;
if (newWidth > maxDim) {
newWidth = Math.ceil(newWidth / 2);
}
if (newHeight > maxDim) {
newHeight = Math.ceil(newHeight / 2);
}
const offscreen = new OffscreenCanvas(newWidth, newHeight);
const ctx = offscreen.getContext("2d");
ctx.drawImage(bitmap, 0, 0, prevWidth, prevHeight, 0, 0, newWidth, newHeight);
prevWidth = newWidth;
prevHeight = newHeight;
if (bitmap !== originalBitmap) {
bitmap.close();
}
bitmap = offscreen.transferToImageBitmap();
}
const ratio = Math.min(maxDim / newWidth, maxDim / newHeight);
newWidth = Math.round(newWidth * ratio);
newHeight = Math.round(newHeight * ratio);
}
const offscreen = new OffscreenCanvas(newWidth, newHeight);
const ctx = offscreen.getContext("2d", {
willReadFrequently: true
});
ctx.filter = "grayscale(1)";
ctx.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height, 0, 0, newWidth, newHeight);
const grayImage = ctx.getImageData(0, 0, newWidth, newHeight).data;
const uint8Buf = this.#toUint8(grayImage);
return [uint8Buf, newWidth, newHeight];
}
static extractContoursFromText(text, {
fontFamily,
fontStyle,
fontWeight
}, pageWidth, pageHeight, rotation, innerMargin) {
let canvas = new OffscreenCanvas(1, 1);
let ctx = canvas.getContext("2d", {
alpha: false
});
const fontSize = 200;
const font = ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`;
const {
actualBoundingBoxLeft,
actualBoundingBoxRight,
actualBoundingBoxAscent,
actualBoundingBoxDescent,
fontBoundingBoxAscent,
fontBoundingBoxDescent,
width
} = ctx.measureText(text);
const SCALE = 1.5;
const canvasWidth = Math.ceil(Math.max(Math.abs(actualBoundingBoxLeft) + Math.abs(actualBoundingBoxRight) || 0, width) * SCALE);
const canvasHeight = Math.ceil(Math.max(Math.abs(actualBoundingBoxAscent) + Math.abs(actualBoundingBoxDescent) || fontSize, Math.abs(fontBoundingBoxAscent) + Math.abs(fontBoundingBoxDescent) || fontSize) * SCALE);
canvas = new OffscreenCanvas(canvasWidth, canvasHeight);
ctx = canvas.getContext("2d", {
alpha: true,
willReadFrequently: true
});
ctx.font = font;
ctx.filter = "grayscale(1)";
ctx.fillStyle = "white";
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
ctx.fillStyle = "black";
ctx.fillText(text, canvasWidth * (SCALE - 1) / 2, canvasHeight * (3 - SCALE) / 2);
const uint8Buf = this.#toUint8(ctx.getImageData(0, 0, canvasWidth, canvasHeight).data);
const histogram = this.#getHistogram(uint8Buf);
const threshold = this.#guessThreshold(histogram);
const contourList = this.#findContours(uint8Buf, canvasWidth, canvasHeight, threshold);
return this.processDrawnLines({
lines: {
curves: contourList,
width: canvasWidth,
height: canvasHeight
},
pageWidth,
pageHeight,
rotation,
innerMargin,
mustSmooth: true,
areContours: true
});
}
static process(bitmap, pageWidth, pageHeight, rotation, innerMargin) {
const [uint8Buf, width, height] = this.#getGrayPixels(bitmap);
const [buffer, histogram] = this.#bilateralFilter(uint8Buf, width, height, Math.hypot(width, height) * this.#PARAMETERS.sigmaSFactor, this.#PARAMETERS.sigmaR, this.#PARAMETERS.kernelSize);
const threshold = this.#guessThreshold(histogram);
const contourList = this.#findContours(buffer, width, height, threshold);
return this.processDrawnLines({
lines: {
curves: contourList,
width,
height
},
pageWidth,
pageHeight,
rotation,
innerMargin,
mustSmooth: true,
areContours: true
});
}
static processDrawnLines({
lines,
pageWidth,
pageHeight,
rotation,
innerMargin,
mustSmooth,
areContours
}) {
if (rotation % 180 !== 0) {
[pageWidth, pageHeight] = [pageHeight, pageWidth];
}
const {
curves,
width,
height
} = lines;
const thickness = lines.thickness ?? 0;
const linesAndPoints = [];
const ratio = Math.min(pageWidth / width, pageHeight / height);
const xScale = ratio / pageWidth;
const yScale = ratio / pageHeight;
const newCurves = [];
for (const {
points
} of curves) {
const reducedPoints = mustSmooth ? this.#douglasPeucker(points) : points;
if (!reducedPoints) {
continue;
}
newCurves.push(reducedPoints);
const len = reducedPoints.length;
const newPoints = new Float32Array(len);
const line = new Float32Array(3 * (len === 2 ? 2 : len - 2));
linesAndPoints.push({
line,
points: newPoints
});
if (len === 2) {
newPoints[0] = reducedPoints[0] * xScale;
newPoints[1] = reducedPoints[1] * yScale;
line.set([NaN, NaN, NaN, NaN, newPoints[0], newPoints[1]], 0);
continue;
}
let [x1, y1, x2, y2] = reducedPoints;
x1 *= xScale;
y1 *= yScale;
x2 *= xScale;
y2 *= yScale;
newPoints.set([x1, y1, x2, y2], 0);
line.set([NaN, NaN, NaN, NaN, x1, y1], 0);
for (let i = 4; i < len; i += 2) {
const x = newPoints[i] = reducedPoints[i] * xScale;
const y = newPoints[i + 1] = reducedPoints[i + 1] * yScale;
line.set(Outline.createBezierPoints(x1, y1, x2, y2, x, y), (i - 2) * 3);
[x1, y1, x2, y2] = [x2, y2, x, y];
}
}
if (linesAndPoints.length === 0) {
return null;
}
const outline = areContours ? new ContourDrawOutline() : new InkDrawOutline();
outline.build(linesAndPoints, pageWidth, pageHeight, 1, rotation, areContours ? 0 : thickness, innerMargin);
return {
outline,
newCurves,
areContours,
thickness,
width,
height
};
}
static async compressSignature({
outlines,
areContours,
thickness,
width,
height
}) {
let minDiff = Infinity;
let maxDiff = -Infinity;
let outlinesLength = 0;
for (const points of outlines) {
outlinesLength += points.length;
for (let i = 2, ii = points.length; i < ii; i++) {
const dx = points[i] - points[i - 2];
minDiff = Math.min(minDiff, dx);
maxDiff = Math.max(maxDiff, dx);
}
}
let bufferType;
if (minDiff >= -128 && maxDiff <= 127) {
bufferType = Int8Array;
} else if (minDiff >= -32768 && maxDiff <= 32767) {
bufferType = Int16Array;
} else {
bufferType = Int32Array;
}
const len = outlines.length;
const headerLength = BASE_HEADER_LENGTH + POINTS_PROPERTIES_NUMBER * len;
const header = new Uint32Array(headerLength);
let offset = 0;
header[offset++] = headerLength * Uint32Array.BYTES_PER_ELEMENT + (outlinesLength - 2 * len) * bufferType.BYTES_PER_ELEMENT;
header[offset++] = 0;
header[offset++] = width;
header[offset++] = height;
header[offset++] = areContours ? 0 : 1;
header[offset++] = Math.max(0, Math.floor(thickness ?? 0));
header[offset++] = len;
header[offset++] = bufferType.BYTES_PER_ELEMENT;
for (const points of outlines) {
header[offset++] = points.length - 2;
header[offset++] = points[0];
header[offset++] = points[1];
}
const cs = new CompressionStream("deflate-raw");
const writer = cs.writable.getWriter();
await writer.ready;
writer.write(header);
const BufferCtor = bufferType.prototype.constructor;
for (const points of outlines) {
const diffs = new BufferCtor(points.length - 2);
for (let i = 2, ii = points.length; i < ii; i++) {
diffs[i - 2] = points[i] - points[i - 2];
}
writer.write(diffs);
}
writer.close();
const buf = await new Response(cs.readable).arrayBuffer();
const bytes = new Uint8Array(buf);
return toBase64Util(bytes);
}
static async decompressSignature(signatureData) {
try {
const bytes = fromBase64Util(signatureData);
const {
readable,
writable
} = new DecompressionStream("deflate-raw");
const writer = writable.getWriter();
await writer.ready;
writer.write(bytes).then(async () => {
await writer.ready;
await writer.close();
}).catch(() => {});
let data = null;
let offset = 0;
for await (const chunk of readable) {
data ||= new Uint8Array(new Uint32Array(chunk.buffer, 0, 4)[0]);
data.set(chunk, offset);
offset += chunk.length;
}
const header = new Uint32Array(data.buffer, 0, data.length >> 2);
const version = header[1];
if (version !== 0) {
throw new Error(`Invalid version: ${version}`);
}
const width = header[2];
const height = header[3];
const areContours = header[4] === 0;
const thickness = header[5];
const numberOfDrawings = header[6];
const bufferType = header[7];
const outlines = [];
const diffsOffset = (BASE_HEADER_LENGTH + POINTS_PROPERTIES_NUMBER * numberOfDrawings) * Uint32Array.BYTES_PER_ELEMENT;
let diffs;
switch (bufferType) {
case Int8Array.BYTES_PER_ELEMENT:
diffs = new Int8Array(data.buffer, diffsOffset);
break;
case Int16Array.BYTES_PER_ELEMENT:
diffs = new Int16Array(data.buffer, diffsOffset);
break;
case Int32Array.BYTES_PER_ELEMENT:
diffs = new Int32Array(data.buffer, diffsOffset);
break;
}
offset = 0;
for (let i = 0; i < numberOfDrawings; i++) {
const len = header[POINTS_PROPERTIES_NUMBER * i + BASE_HEADER_LENGTH];
const points = new Float32Array(len + 2);
outlines.push(points);
for (let j = 0; j < POINTS_PROPERTIES_NUMBER - 1; j++) {
points[j] = header[POINTS_PROPERTIES_NUMBER * i + BASE_HEADER_LENGTH + j + 1];
}
for (let j = 0; j < len; j++) {
points[j + 2] = points[j] + diffs[offset++];
}
}
return {
areContours,
thickness,
outlines,
width,
height
};
} catch (e) {
warn(`decompressSignature: ${e}`);
return null;
}
}
}
;// ./src/display/editor/signature.js
class SignatureOptions extends DrawingOptions {
constructor() {
super();
super.updateProperties({
fill: "CanvasText",
"stroke-width": 0
});
}
clone() {
const clone = new SignatureOptions();
clone.updateAll(this);
return clone;
}
}
class DrawnSignatureOptions extends InkDrawingOptions {
constructor(viewerParameters) {
super(viewerParameters);
super.updateProperties({
stroke: "CanvasText",
"stroke-width": 1
});
}
clone() {
const clone = new DrawnSignatureOptions(this._viewParameters);
clone.updateAll(this);
return clone;
}
}
class SignatureEditor extends DrawingEditor {
#isExtracted = false;
#description = null;
#signatureData = null;
#signatureUUID = null;
static _type = "signature";
static _editorType = AnnotationEditorType.SIGNATURE;
static _defaultDrawingOptions = null;
constructor(params) {
super({
...params,
mustBeCommitted: true,
name: "signatureEditor"
});
this._willKeepAspectRatio = true;
this.#signatureData = params.signatureData || null;
this.#description = null;
this.defaultL10nId = "pdfjs-editor-signature-editor1";
}
static initialize(l10n, uiManager) {
AnnotationEditor.initialize(l10n, uiManager);
this._defaultDrawingOptions = new SignatureOptions();
this._defaultDrawnSignatureOptions = new DrawnSignatureOptions(uiManager.viewParameters);
}
static getDefaultDrawingOptions(options) {
const clone = this._defaultDrawingOptions.clone();
clone.updateProperties(options);
return clone;
}
static get supportMultipleDrawings() {
return false;
}
static get typesMap() {
return shadow(this, "typesMap", new Map());
}
static get isDrawer() {
return false;
}
get telemetryFinalData() {
return {
type: "signature",
hasDescription: !!this.#description
};
}
static computeTelemetryFinalData(data) {
const hasDescriptionStats = data.get("hasDescription");
return {
hasAltText: hasDescriptionStats.get(true) ?? 0,
hasNoAltText: hasDescriptionStats.get(false) ?? 0
};
}
get isResizable() {
return true;
}
onScaleChanging() {
if (this._drawId === null) {
return;
}
super.onScaleChanging();
}
render() {
if (this.div) {
return this.div;
}
let baseX, baseY;
const {
_isCopy
} = this;
if (_isCopy) {
this._isCopy = false;
baseX = this.x;
baseY = this.y;
}
super.render();
if (this._drawId === null) {
if (this.#signatureData) {
const {
lines,
mustSmooth,
areContours,
description,
uuid,
heightInPage
} = this.#signatureData;
const {
rawDims: {
pageWidth,
pageHeight
},
rotation
} = this.parent.viewport;
const outline = SignatureExtractor.processDrawnLines({
lines,
pageWidth,
pageHeight,
rotation,
innerMargin: SignatureEditor._INNER_MARGIN,
mustSmooth,
areContours
});
this.addSignature(outline, heightInPage, description, uuid);
} else {
this.div.setAttribute("data-l10n-args", JSON.stringify({
description: ""
}));
this.div.hidden = true;
this._uiManager.getSignature(this);
}
}
if (_isCopy) {
this._isCopy = true;
this._moveAfterPaste(baseX, baseY);
}
return this.div;
}
setUuid(uuid) {
this.#signatureUUID = uuid;
this.addEditToolbar();
}
getUuid() {
return this.#signatureUUID;
}
get description() {
return this.#description;
}
set description(description) {
this.#description = description;
super.addEditToolbar().then(toolbar => {
toolbar?.updateEditSignatureButton(description);
});
}
getSignaturePreview() {
const {
newCurves,
areContours,
thickness,
width,
height
} = this.#signatureData;
const maxDim = Math.max(width, height);
const outlineData = SignatureExtractor.processDrawnLines({
lines: {
curves: newCurves.map(points => ({
points
})),
thickness,
width,
height
},
pageWidth: maxDim,
pageHeight: maxDim,
rotation: 0,
innerMargin: 0,
mustSmooth: false,
areContours
});
return {
areContours,
outline: outlineData.outline
};
}
async addEditToolbar() {
const toolbar = await super.addEditToolbar();
if (!toolbar) {
return null;
}
if (this._uiManager.signatureManager && this.#description !== null) {
await toolbar.addEditSignatureButton(this._uiManager.signatureManager, this.#signatureUUID, this.#description);
toolbar.show();
}
return toolbar;
}
addSignature(data, heightInPage, description, uuid) {
const {
x: savedX,
y: savedY
} = this;
const {
outline
} = this.#signatureData = data;
this.#isExtracted = outline instanceof ContourDrawOutline;
this.#description = description;
this.div.setAttribute("data-l10n-args", JSON.stringify({
description
}));
let drawingOptions;
if (this.#isExtracted) {
drawingOptions = SignatureEditor.getDefaultDrawingOptions();
} else {
drawingOptions = SignatureEditor._defaultDrawnSignatureOptions.clone();
drawingOptions.updateProperties({
"stroke-width": outline.thickness
});
}
this._addOutlines({
drawOutlines: outline,
drawingOptions
});
const [parentWidth, parentHeight] = this.parentDimensions;
const [, pageHeight] = this.pageDimensions;
let newHeight = heightInPage / pageHeight;
newHeight = newHeight >= 1 ? 0.5 : newHeight;
this.width *= newHeight / this.height;
if (this.width >= 1) {
newHeight *= 0.9 / this.width;
this.width = 0.9;
}
this.height = newHeight;
this.setDims(parentWidth * this.width, parentHeight * this.height);
this.x = savedX;
this.y = savedY;
this.center();
this._onResized();
this.onScaleChanging();
this.rotate();
this._uiManager.addToAnnotationStorage(this);
this.setUuid(uuid);
this._reportTelemetry({
action: "pdfjs.signature.inserted",
data: {
hasBeenSaved: !!uuid,
hasDescription: !!description
}
});
this.div.hidden = false;
}
getFromImage(bitmap) {
const {
rawDims: {
pageWidth,
pageHeight
},
rotation
} = this.parent.viewport;
return SignatureExtractor.process(bitmap, pageWidth, pageHeight, rotation, SignatureEditor._INNER_MARGIN);
}
getFromText(text, fontInfo) {
const {
rawDims: {
pageWidth,
pageHeight
},
rotation
} = this.parent.viewport;
return SignatureExtractor.extractContoursFromText(text, fontInfo, pageWidth, pageHeight, rotation, SignatureEditor._INNER_MARGIN);
}
getDrawnSignature(curves) {
const {
rawDims: {
pageWidth,
pageHeight
},
rotation
} = this.parent.viewport;
return SignatureExtractor.processDrawnLines({
lines: curves,
pageWidth,
pageHeight,
rotation,
innerMargin: SignatureEditor._INNER_MARGIN,
mustSmooth: false,
areContours: false
});
}
createDrawingOptions({
areContours,
thickness
}) {
if (areContours) {
this._drawingOptions = SignatureEditor.getDefaultDrawingOptions();
} else {
this._drawingOptions = SignatureEditor._defaultDrawnSignatureOptions.clone();
this._drawingOptions.updateProperties({
"stroke-width": thickness
});
}
}
serialize(isForCopying = false) {
if (this.isEmpty()) {
return null;
}
const {
lines,
points,
rect
} = this.serializeDraw(isForCopying);
const {
_drawingOptions: {
"stroke-width": thickness
}
} = this;
const serialized = {
annotationType: AnnotationEditorType.SIGNATURE,
isSignature: true,
areContours: this.#isExtracted,
color: [0, 0, 0],
thickness: this.#isExtracted ? 0 : thickness,
pageIndex: this.pageIndex,
rect,
rotation: this.rotation,
structTreeParentId: this._structTreeParentId
};
if (isForCopying) {
serialized.paths = {
lines,
points
};
serialized.uuid = this.#signatureUUID;
serialized.isCopy = true;
} else {
serialized.lines = lines;
}
if (this.#description) {
serialized.accessibilityData = {
type: "Figure",
alt: this.#description
};
}
return serialized;
}
static deserializeDraw(pageX, pageY, pageWidth, pageHeight, innerMargin, data) {
if (data.areContours) {
return ContourDrawOutline.deserialize(pageX, pageY, pageWidth, pageHeight, innerMargin, data);
}
return InkDrawOutline.deserialize(pageX, pageY, pageWidth, pageHeight, innerMargin, data);
}
static async deserialize(data, parent, uiManager) {
const editor = await super.deserialize(data, parent, uiManager);
editor.#isExtracted = data.areContours;
editor.#description = data.accessibilityData?.alt || "";
editor.#signatureUUID = data.uuid;
return editor;
}
}
;// ./src/display/editor/stamp.js
class StampEditor extends AnnotationEditor {
#bitmap = null;
#bitmapId = null;
#bitmapPromise = null;
#bitmapUrl = null;
#bitmapFile = null;
#bitmapFileName = "";
#canvas = null;
#missingCanvas = false;
#resizeTimeoutId = null;
#isSvg = false;
#hasBeenAddedInUndoStack = false;
static _type = "stamp";
static _editorType = AnnotationEditorType.STAMP;
constructor(params) {
super({
...params,
name: "stampEditor"
});
this.#bitmapUrl = params.bitmapUrl;
this.#bitmapFile = params.bitmapFile;
this.defaultL10nId = "pdfjs-editor-stamp-editor";
}
static initialize(l10n, uiManager) {
AnnotationEditor.initialize(l10n, uiManager);
}
static isHandlingMimeForPasting(mime) {
return SupportedImageMimeTypes.includes(mime);
}
static paste(item, parent) {
parent.pasteEditor(AnnotationEditorType.STAMP, {
bitmapFile: item.getAsFile()
});
}
altTextFinish() {
if (this._uiManager.useNewAltTextFlow) {
this.div.hidden = false;
}
super.altTextFinish();
}
get telemetryFinalData() {
return {
type: "stamp",
hasAltText: !!this.altTextData?.altText
};
}
static computeTelemetryFinalData(data) {
const hasAltTextStats = data.get("hasAltText");
return {
hasAltText: hasAltTextStats.get(true) ?? 0,
hasNoAltText: hasAltTextStats.get(false) ?? 0
};
}
#getBitmapFetched(data, fromId = false) {
if (!data) {
this.remove();
return;
}
this.#bitmap = data.bitmap;
if (!fromId) {
this.#bitmapId = data.id;
this.#isSvg = data.isSvg;
}
if (data.file) {
this.#bitmapFileName = data.file.name;
}
this.#createCanvas();
}
#getBitmapDone() {
this.#bitmapPromise = null;
this._uiManager.enableWaiting(false);
if (!this.#canvas) {
return;
}
if (this._uiManager.useNewAltTextWhenAddingImage && this._uiManager.useNewAltTextFlow && this.#bitmap) {
this._editToolbar.hide();
this._uiManager.editAltText(this, true);
return;
}
if (!this._uiManager.useNewAltTextWhenAddingImage && this._uiManager.useNewAltTextFlow && this.#bitmap) {
this._reportTelemetry({
action: "pdfjs.image.image_added",
data: {
alt_text_modal: false,
alt_text_type: "empty"
}
});
try {
this.mlGuessAltText();
} catch {}
}
this.div.focus();
}
async mlGuessAltText(imageData = null, updateAltTextData = true) {
if (this.hasAltTextData()) {
return null;
}
const {
mlManager
} = this._uiManager;
if (!mlManager) {
throw new Error("No ML.");
}
if (!(await mlManager.isEnabledFor("altText"))) {
throw new Error("ML isn't enabled for alt text.");
}
const {
data,
width,
height
} = imageData || this.copyCanvas(null, null, true).imageData;
const response = await mlManager.guess({
name: "altText",
request: {
data,
width,
height,
channels: data.length / (width * height)
}
});
if (!response) {
throw new Error("No response from the AI service.");
}
if (response.error) {
throw new Error("Error from the AI service.");
}
if (response.cancel) {
return null;
}
if (!response.output) {
throw new Error("No valid response from the AI service.");
}
const altText = response.output;
await this.setGuessedAltText(altText);
if (updateAltTextData && !this.hasAltTextData()) {
this.altTextData = {
alt: altText,
decorative: false
};
}
return altText;
}
#getBitmap() {
if (this.#bitmapId) {
this._uiManager.enableWaiting(true);
this._uiManager.imageManager.getFromId(this.#bitmapId).then(data => this.#getBitmapFetched(data, true)).finally(() => this.#getBitmapDone());
return;
}
if (this.#bitmapUrl) {
const url = this.#bitmapUrl;
this.#bitmapUrl = null;
this._uiManager.enableWaiting(true);
this.#bitmapPromise = this._uiManager.imageManager.getFromUrl(url).then(data => this.#getBitmapFetched(data)).finally(() => this.#getBitmapDone());
return;
}
if (this.#bitmapFile) {
const file = this.#bitmapFile;
this.#bitmapFile = null;
this._uiManager.enableWaiting(true);
this.#bitmapPromise = this._uiManager.imageManager.getFromFile(file).then(data => this.#getBitmapFetched(data)).finally(() => this.#getBitmapDone());
return;
}
const input = document.createElement("input");
input.type = "file";
input.accept = SupportedImageMimeTypes.join(",");
const signal = this._uiManager._signal;
this.#bitmapPromise = new Promise(resolve => {
input.addEventListener("change", async () => {
if (!input.files || input.files.length === 0) {
this.remove();
} else {
this._uiManager.enableWaiting(true);
const data = await this._uiManager.imageManager.getFromFile(input.files[0]);
this._reportTelemetry({
action: "pdfjs.image.image_selected",
data: {
alt_text_modal: this._uiManager.useNewAltTextFlow
}
});
this.#getBitmapFetched(data);
}
resolve();
}, {
signal
});
input.addEventListener("cancel", () => {
this.remove();
resolve();
}, {
signal
});
}).finally(() => this.#getBitmapDone());
input.click();
}
remove() {
if (this.#bitmapId) {
this.#bitmap = null;
this._uiManager.imageManager.deleteId(this.#bitmapId);
this.#canvas?.remove();
this.#canvas = null;
if (this.#resizeTimeoutId) {
clearTimeout(this.#resizeTimeoutId);
this.#resizeTimeoutId = null;
}
}
super.remove();
}
rebuild() {
if (!this.parent) {
if (this.#bitmapId) {
this.#getBitmap();
}
return;
}
super.rebuild();
if (this.div === null) {
return;
}
if (this.#bitmapId && this.#canvas === null) {
this.#getBitmap();
}
if (!this.isAttachedToDOM) {
this.parent.add(this);
}
}
onceAdded(focus) {
this._isDraggable = true;
if (focus) {
this.div.focus();
}
}
isEmpty() {
return !(this.#bitmapPromise || this.#bitmap || this.#bitmapUrl || this.#bitmapFile || this.#bitmapId || this.#missingCanvas);
}
get isResizable() {
return true;
}
render() {
if (this.div) {
return this.div;
}
let baseX, baseY;
if (this._isCopy) {
baseX = this.x;
baseY = this.y;
}
super.render();
this.div.hidden = true;
this.addAltTextButton();
if (!this.#missingCanvas) {
if (this.#bitmap) {
this.#createCanvas();
} else {
this.#getBitmap();
}
}
if (this._isCopy) {
this._moveAfterPaste(baseX, baseY);
}
this._uiManager.addShouldRescale(this);
return this.div;
}
setCanvas(annotationElementId, canvas) {
const {
id: bitmapId,
bitmap
} = this._uiManager.imageManager.getFromCanvas(annotationElementId, canvas);
canvas.remove();
if (bitmapId && this._uiManager.imageManager.isValidId(bitmapId)) {
this.#bitmapId = bitmapId;
if (bitmap) {
this.#bitmap = bitmap;
}
this.#missingCanvas = false;
this.#createCanvas();
}
}
_onResized() {
this.onScaleChanging();
}
onScaleChanging() {
if (!this.parent) {
return;
}
if (this.#resizeTimeoutId !== null) {
clearTimeout(this.#resizeTimeoutId);
}
const TIME_TO_WAIT = 200;
this.#resizeTimeoutId = setTimeout(() => {
this.#resizeTimeoutId = null;
this.#drawBitmap();
}, TIME_TO_WAIT);
}
#createCanvas() {
const {
div
} = this;
let {
width,
height
} = this.#bitmap;
const [pageWidth, pageHeight] = this.pageDimensions;
const MAX_RATIO = 0.75;
if (this.width) {
width = this.width * pageWidth;
height = this.height * pageHeight;
} else if (width > MAX_RATIO * pageWidth || height > MAX_RATIO * pageHeight) {
const factor = Math.min(MAX_RATIO * pageWidth / width, MAX_RATIO * pageHeight / height);
width *= factor;
height *= factor;
}
const [parentWidth, parentHeight] = this.parentDimensions;
this.setDims(width * parentWidth / pageWidth, height * parentHeight / pageHeight);
this._uiManager.enableWaiting(false);
const canvas = this.#canvas = document.createElement("canvas");
canvas.setAttribute("role", "img");
this.addContainer(canvas);
this.width = width / pageWidth;
this.height = height / pageHeight;
if (this._initialOptions?.isCentered) {
this.center();
} else {
this.fixAndSetPosition();
}
this._initialOptions = null;
if (!this._uiManager.useNewAltTextWhenAddingImage || !this._uiManager.useNewAltTextFlow || this.annotationElementId) {
div.hidden = false;
}
this.#drawBitmap();
if (!this.#hasBeenAddedInUndoStack) {
this.parent.addUndoableEditor(this);
this.#hasBeenAddedInUndoStack = true;
}
this._reportTelemetry({
action: "inserted_image"
});
if (this.#bitmapFileName) {
this.div.setAttribute("aria-description", this.#bitmapFileName);
}
}
copyCanvas(maxDataDimension, maxPreviewDimension, createImageData = false) {
if (!maxDataDimension) {
maxDataDimension = 224;
}
const {
width: bitmapWidth,
height: bitmapHeight
} = this.#bitmap;
const outputScale = new OutputScale();
let bitmap = this.#bitmap;
let width = bitmapWidth,
height = bitmapHeight;
let canvas = null;
if (maxPreviewDimension) {
if (bitmapWidth > maxPreviewDimension || bitmapHeight > maxPreviewDimension) {
const ratio = Math.min(maxPreviewDimension / bitmapWidth, maxPreviewDimension / bitmapHeight);
width = Math.floor(bitmapWidth * ratio);
height = Math.floor(bitmapHeight * ratio);
}
canvas = document.createElement("canvas");
const scaledWidth = canvas.width = Math.ceil(width * outputScale.sx);
const scaledHeight = canvas.height = Math.ceil(height * outputScale.sy);
if (!this.#isSvg) {
bitmap = this.#scaleBitmap(scaledWidth, scaledHeight);
}
const ctx = canvas.getContext("2d");
ctx.filter = this._uiManager.hcmFilter;
let white = "white",
black = "#cfcfd8";
if (this._uiManager.hcmFilter !== "none") {
black = "black";
} else if (window.matchMedia?.("(prefers-color-scheme: dark)").matches) {
white = "#8f8f9d";
black = "#42414d";
}
const boxDim = 15;
const boxDimWidth = boxDim * outputScale.sx;
const boxDimHeight = boxDim * outputScale.sy;
const pattern = new OffscreenCanvas(boxDimWidth * 2, boxDimHeight * 2);
const patternCtx = pattern.getContext("2d");
patternCtx.fillStyle = white;
patternCtx.fillRect(0, 0, boxDimWidth * 2, boxDimHeight * 2);
patternCtx.fillStyle = black;
patternCtx.fillRect(0, 0, boxDimWidth, boxDimHeight);
patternCtx.fillRect(boxDimWidth, boxDimHeight, boxDimWidth, boxDimHeight);
ctx.fillStyle = ctx.createPattern(pattern, "repeat");
ctx.fillRect(0, 0, scaledWidth, scaledHeight);
ctx.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height, 0, 0, scaledWidth, scaledHeight);
}
let imageData = null;
if (createImageData) {
let dataWidth, dataHeight;
if (outputScale.symmetric && bitmap.width < maxDataDimension && bitmap.height < maxDataDimension) {
dataWidth = bitmap.width;
dataHeight = bitmap.height;
} else {
bitmap = this.#bitmap;
if (bitmapWidth > maxDataDimension || bitmapHeight > maxDataDimension) {
const ratio = Math.min(maxDataDimension / bitmapWidth, maxDataDimension / bitmapHeight);
dataWidth = Math.floor(bitmapWidth * ratio);
dataHeight = Math.floor(bitmapHeight * ratio);
if (!this.#isSvg) {
bitmap = this.#scaleBitmap(dataWidth, dataHeight);
}
}
}
const offscreen = new OffscreenCanvas(dataWidth, dataHeight);
const offscreenCtx = offscreen.getContext("2d", {
willReadFrequently: true
});
offscreenCtx.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height, 0, 0, dataWidth, dataHeight);
imageData = {
width: dataWidth,
height: dataHeight,
data: offscreenCtx.getImageData(0, 0, dataWidth, dataHeight).data
};
}
return {
canvas,
width,
height,
imageData
};
}
#scaleBitmap(width, height) {
const {
width: bitmapWidth,
height: bitmapHeight
} = this.#bitmap;
let newWidth = bitmapWidth;
let newHeight = bitmapHeight;
let bitmap = this.#bitmap;
while (newWidth > 2 * width || newHeight > 2 * height) {
const prevWidth = newWidth;
const prevHeight = newHeight;
if (newWidth > 2 * width) {
newWidth = newWidth >= 16384 ? Math.floor(newWidth / 2) - 1 : Math.ceil(newWidth / 2);
}
if (newHeight > 2 * height) {
newHeight = newHeight >= 16384 ? Math.floor(newHeight / 2) - 1 : Math.ceil(newHeight / 2);
}
const offscreen = new OffscreenCanvas(newWidth, newHeight);
const ctx = offscreen.getContext("2d");
ctx.drawImage(bitmap, 0, 0, prevWidth, prevHeight, 0, 0, newWidth, newHeight);
bitmap = offscreen.transferToImageBitmap();
}
return bitmap;
}
#drawBitmap() {
const [parentWidth, parentHeight] = this.parentDimensions;
const {
width,
height
} = this;
const outputScale = new OutputScale();
const scaledWidth = Math.ceil(width * parentWidth * outputScale.sx);
const scaledHeight = Math.ceil(height * parentHeight * outputScale.sy);
const canvas = this.#canvas;
if (!canvas || canvas.width === scaledWidth && canvas.height === scaledHeight) {
return;
}
canvas.width = scaledWidth;
canvas.height = scaledHeight;
const bitmap = this.#isSvg ? this.#bitmap : this.#scaleBitmap(scaledWidth, scaledHeight);
const ctx = canvas.getContext("2d");
ctx.filter = this._uiManager.hcmFilter;
ctx.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height, 0, 0, scaledWidth, scaledHeight);
}
#serializeBitmap(toUrl) {
if (toUrl) {
if (this.#isSvg) {
const url = this._uiManager.imageManager.getSvgUrl(this.#bitmapId);
if (url) {
return url;
}
}
const canvas = document.createElement("canvas");
({
width: canvas.width,
height: canvas.height
} = this.#bitmap);
const ctx = canvas.getContext("2d");
ctx.drawImage(this.#bitmap, 0, 0);
return canvas.toDataURL();
}
if (this.#isSvg) {
const [pageWidth, pageHeight] = this.pageDimensions;
const width = Math.round(this.width * pageWidth * PixelsPerInch.PDF_TO_CSS_UNITS);
const height = Math.round(this.height * pageHeight * PixelsPerInch.PDF_TO_CSS_UNITS);
const offscreen = new OffscreenCanvas(width, height);
const ctx = offscreen.getContext("2d");
ctx.drawImage(this.#bitmap, 0, 0, this.#bitmap.width, this.#bitmap.height, 0, 0, width, height);
return offscreen.transferToImageBitmap();
}
return structuredClone(this.#bitmap);
}
static async deserialize(data, parent, uiManager) {
let initialData = null;
let missingCanvas = false;
if (data instanceof StampAnnotationElement) {
const {
data: {
rect,
rotation,
id,
structParent,
popupRef
},
container,
parent: {
page: {
pageNumber
}
},
canvas
} = data;
let bitmapId, bitmap;
if (canvas) {
delete data.canvas;
({
id: bitmapId,
bitmap
} = uiManager.imageManager.getFromCanvas(container.id, canvas));
canvas.remove();
} else {
missingCanvas = true;
data._hasNoCanvas = true;
}
const altText = (await parent._structTree.getAriaAttributes(`${AnnotationPrefix}${id}`))?.get("aria-label") || "";
initialData = data = {
annotationType: AnnotationEditorType.STAMP,
bitmapId,
bitmap,
pageIndex: pageNumber - 1,
rect: rect.slice(0),
rotation,
id,
deleted: false,
accessibilityData: {
decorative: false,
altText
},
isSvg: false,
structParent,
popupRef
};
}
const editor = await super.deserialize(data, parent, uiManager);
const {
rect,
bitmap,
bitmapUrl,
bitmapId,
isSvg,
accessibilityData
} = data;
if (missingCanvas) {
uiManager.addMissingCanvas(data.id, editor);
editor.#missingCanvas = true;
} else if (bitmapId && uiManager.imageManager.isValidId(bitmapId)) {
editor.#bitmapId = bitmapId;
if (bitmap) {
editor.#bitmap = bitmap;
}
} else {
editor.#bitmapUrl = bitmapUrl;
}
editor.#isSvg = isSvg;
const [parentWidth, parentHeight] = editor.pageDimensions;
editor.width = (rect[2] - rect[0]) / parentWidth;
editor.height = (rect[3] - rect[1]) / parentHeight;
editor.annotationElementId = data.id || null;
if (accessibilityData) {
editor.altTextData = accessibilityData;
}
editor._initialData = initialData;
editor.#hasBeenAddedInUndoStack = !!initialData;
return editor;
}
serialize(isForCopying = false, context = null) {
if (this.isEmpty()) {
return null;
}
if (this.deleted) {
return this.serializeDeleted();
}
const serialized = {
annotationType: AnnotationEditorType.STAMP,
bitmapId: this.#bitmapId,
pageIndex: this.pageIndex,
rect: this.getRect(0, 0),
rotation: this.rotation,
isSvg: this.#isSvg,
structTreeParentId: this._structTreeParentId
};
if (isForCopying) {
serialized.bitmapUrl = this.#serializeBitmap(true);
serialized.accessibilityData = this.serializeAltText(true);
serialized.isCopy = true;
return serialized;
}
const {
decorative,
altText
} = this.serializeAltText(false);
if (!decorative && altText) {
serialized.accessibilityData = {
type: "Figure",
alt: altText
};
}
if (this.annotationElementId) {
const changes = this.#hasElementChanged(serialized);
if (changes.isSame) {
return null;
}
if (changes.isSameAltText) {
delete serialized.accessibilityData;
} else {
serialized.accessibilityData.structParent = this._initialData.structParent ?? -1;
}
}
serialized.id = this.annotationElementId;
if (context === null) {
return serialized;
}
context.stamps ||= new Map();
const area = this.#isSvg ? (serialized.rect[2] - serialized.rect[0]) * (serialized.rect[3] - serialized.rect[1]) : null;
if (!context.stamps.has(this.#bitmapId)) {
context.stamps.set(this.#bitmapId, {
area,
serialized
});
serialized.bitmap = this.#serializeBitmap(false);
} else if (this.#isSvg) {
const prevData = context.stamps.get(this.#bitmapId);
if (area > prevData.area) {
prevData.area = area;
prevData.serialized.bitmap.close();
prevData.serialized.bitmap = this.#serializeBitmap(false);
}
}
return serialized;
}
#hasElementChanged(serialized) {
const {
pageIndex,
accessibilityData: {
altText
}
} = this._initialData;
const isSamePageIndex = serialized.pageIndex === pageIndex;
const isSameAltText = (serialized.accessibilityData?.alt || "") === altText;
return {
isSame: !this._hasBeenMoved && !this._hasBeenResized && isSamePageIndex && isSameAltText,
isSameAltText
};
}
renderAnnotationElement(annotation) {
annotation.updateEdited({
rect: this.getRect(0, 0)
});
return null;
}
}
;// ./src/display/editor/annotation_editor_layer.js
class AnnotationEditorLayer {
#accessibilityManager;
#allowClick = false;
#annotationLayer = null;
#clickAC = null;
#editorFocusTimeoutId = null;
#editors = new Map();
#hadPointerDown = false;
#isDisabling = false;
#isEnabling = false;
#drawingAC = null;
#focusedElement = null;
#textLayer = null;
#textSelectionAC = null;
#uiManager;
static _initialized = false;
static #editorTypes = new Map([FreeTextEditor, InkEditor, StampEditor, HighlightEditor, SignatureEditor].map(type => [type._editorType, type]));
constructor({
uiManager,
pageIndex,
div,
structTreeLayer,
accessibilityManager,
annotationLayer,
drawLayer,
textLayer,
viewport,
l10n
}) {
const editorTypes = [...AnnotationEditorLayer.#editorTypes.values()];
if (!AnnotationEditorLayer._initialized) {
AnnotationEditorLayer._initialized = true;
for (const editorType of editorTypes) {
editorType.initialize(l10n, uiManager);
}
}
uiManager.registerEditorTypes(editorTypes);
this.#uiManager = uiManager;
this.pageIndex = pageIndex;
this.div = div;
this.#accessibilityManager = accessibilityManager;
this.#annotationLayer = annotationLayer;
this.viewport = viewport;
this.#textLayer = textLayer;
this.drawLayer = drawLayer;
this._structTree = structTreeLayer;
this.#uiManager.addLayer(this);
}
get isEmpty() {
return this.#editors.size === 0;
}
get isInvisible() {
return this.isEmpty && this.#uiManager.getMode() === AnnotationEditorType.NONE;
}
updateToolbar(mode) {
this.#uiManager.updateToolbar(mode);
}
updateMode(mode = this.#uiManager.getMode()) {
this.#cleanup();
switch (mode) {
case AnnotationEditorType.NONE:
this.disableTextSelection();
this.togglePointerEvents(false);
this.toggleAnnotationLayerPointerEvents(true);
this.disableClick();
return;
case AnnotationEditorType.INK:
this.disableTextSelection();
this.togglePointerEvents(true);
this.enableClick();
break;
case AnnotationEditorType.HIGHLIGHT:
this.enableTextSelection();
this.togglePointerEvents(false);
this.disableClick();
break;
default:
this.disableTextSelection();
this.togglePointerEvents(true);
this.enableClick();
}
this.toggleAnnotationLayerPointerEvents(false);
const {
classList
} = this.div;
for (const editorType of AnnotationEditorLayer.#editorTypes.values()) {
classList.toggle(`${editorType._type}Editing`, mode === editorType._editorType);
}
this.div.hidden = false;
}
hasTextLayer(textLayer) {
return textLayer === this.#textLayer?.div;
}
setEditingState(isEditing) {
this.#uiManager.setEditingState(isEditing);
}
addCommands(params) {
this.#uiManager.addCommands(params);
}
cleanUndoStack(type) {
this.#uiManager.cleanUndoStack(type);
}
toggleDrawing(enabled = false) {
this.div.classList.toggle("drawing", !enabled);
}
togglePointerEvents(enabled = false) {
this.div.classList.toggle("disabled", !enabled);
}
toggleAnnotationLayerPointerEvents(enabled = false) {
this.#annotationLayer?.div.classList.toggle("disabled", !enabled);
}
async enable() {
this.#isEnabling = true;
this.div.tabIndex = 0;
this.togglePointerEvents(true);
const annotationElementIds = new Set();
for (const editor of this.#editors.values()) {
editor.enableEditing();
editor.show(true);
if (editor.annotationElementId) {
this.#uiManager.removeChangedExistingAnnotation(editor);
annotationElementIds.add(editor.annotationElementId);
}
}
if (!this.#annotationLayer) {
this.#isEnabling = false;
return;
}
const editables = this.#annotationLayer.getEditableAnnotations();
for (const editable of editables) {
editable.hide();
if (this.#uiManager.isDeletedAnnotationElement(editable.data.id)) {
continue;
}
if (annotationElementIds.has(editable.data.id)) {
continue;
}
const editor = await this.deserialize(editable);
if (!editor) {
continue;
}
this.addOrRebuild(editor);
editor.enableEditing();
}
this.#isEnabling = false;
}
disable() {
this.#isDisabling = true;
this.div.tabIndex = -1;
this.togglePointerEvents(false);
const changedAnnotations = new Map();
const resetAnnotations = new Map();
for (const editor of this.#editors.values()) {
editor.disableEditing();
if (!editor.annotationElementId) {
continue;
}
if (editor.serialize() !== null) {
changedAnnotations.set(editor.annotationElementId, editor);
continue;
} else {
resetAnnotations.set(editor.annotationElementId, editor);
}
this.getEditableAnnotation(editor.annotationElementId)?.show();
editor.remove();
}
if (this.#annotationLayer) {
const editables = this.#annotationLayer.getEditableAnnotations();
for (const editable of editables) {
const {
id
} = editable.data;
if (this.#uiManager.isDeletedAnnotationElement(id)) {
continue;
}
let editor = resetAnnotations.get(id);
if (editor) {
editor.resetAnnotationElement(editable);
editor.show(false);
editable.show();
continue;
}
editor = changedAnnotations.get(id);
if (editor) {
this.#uiManager.addChangedExistingAnnotation(editor);
if (editor.renderAnnotationElement(editable)) {
editor.show(false);
}
}
editable.show();
}
}
this.#cleanup();
if (this.isEmpty) {
this.div.hidden = true;
}
const {
classList
} = this.div;
for (const editorType of AnnotationEditorLayer.#editorTypes.values()) {
classList.remove(`${editorType._type}Editing`);
}
this.disableTextSelection();
this.toggleAnnotationLayerPointerEvents(true);
this.#isDisabling = false;
}
getEditableAnnotation(id) {
return this.#annotationLayer?.getEditableAnnotation(id) || null;
}
setActiveEditor(editor) {
const currentActive = this.#uiManager.getActive();
if (currentActive === editor) {
return;
}
this.#uiManager.setActiveEditor(editor);
}
enableTextSelection() {
this.div.tabIndex = -1;
if (this.#textLayer?.div && !this.#textSelectionAC) {
this.#textSelectionAC = new AbortController();
const signal = this.#uiManager.combinedSignal(this.#textSelectionAC);
this.#textLayer.div.addEventListener("pointerdown", this.#textLayerPointerDown.bind(this), {
signal
});
this.#textLayer.div.classList.add("highlighting");
}
}
disableTextSelection() {
this.div.tabIndex = 0;
if (this.#textLayer?.div && this.#textSelectionAC) {
this.#textSelectionAC.abort();
this.#textSelectionAC = null;
this.#textLayer.div.classList.remove("highlighting");
}
}
#textLayerPointerDown(event) {
this.#uiManager.unselectAll();
const {
target
} = event;
if (target === this.#textLayer.div || (target.getAttribute("role") === "img" || target.classList.contains("endOfContent")) && this.#textLayer.div.contains(target)) {
const {
isMac
} = util_FeatureTest.platform;
if (event.button !== 0 || event.ctrlKey && isMac) {
return;
}
this.#uiManager.showAllEditors("highlight", true, true);
this.#textLayer.div.classList.add("free");
this.toggleDrawing();
HighlightEditor.startHighlighting(this, this.#uiManager.direction === "ltr", {
target: this.#textLayer.div,
x: event.x,
y: event.y
});
this.#textLayer.div.addEventListener("pointerup", () => {
this.#textLayer.div.classList.remove("free");
this.toggleDrawing(true);
}, {
once: true,
signal: this.#uiManager._signal
});
event.preventDefault();
}
}
enableClick() {
if (this.#clickAC) {
return;
}
this.#clickAC = new AbortController();
const signal = this.#uiManager.combinedSignal(this.#clickAC);
this.div.addEventListener("pointerdown", this.pointerdown.bind(this), {
signal
});
const pointerup = this.pointerup.bind(this);
this.div.addEventListener("pointerup", pointerup, {
signal
});
this.div.addEventListener("pointercancel", pointerup, {
signal
});
}
disableClick() {
this.#clickAC?.abort();
this.#clickAC = null;
}
attach(editor) {
this.#editors.set(editor.id, editor);
const {
annotationElementId
} = editor;
if (annotationElementId && this.#uiManager.isDeletedAnnotationElement(annotationElementId)) {
this.#uiManager.removeDeletedAnnotationElement(editor);
}
}
detach(editor) {
this.#editors.delete(editor.id);
this.#accessibilityManager?.removePointerInTextLayer(editor.contentDiv);
if (!this.#isDisabling && editor.annotationElementId) {
this.#uiManager.addDeletedAnnotationElement(editor);
}
}
remove(editor) {
this.detach(editor);
this.#uiManager.removeEditor(editor);
editor.div.remove();
editor.isAttachedToDOM = false;
}
changeParent(editor) {
if (editor.parent === this) {
return;
}
if (editor.parent && editor.annotationElementId) {
this.#uiManager.addDeletedAnnotationElement(editor.annotationElementId);
AnnotationEditor.deleteAnnotationElement(editor);
editor.annotationElementId = null;
}
this.attach(editor);
editor.parent?.detach(editor);
editor.setParent(this);
if (editor.div && editor.isAttachedToDOM) {
editor.div.remove();
this.div.append(editor.div);
}
}
add(editor) {
if (editor.parent === this && editor.isAttachedToDOM) {
return;
}
this.changeParent(editor);
this.#uiManager.addEditor(editor);
this.attach(editor);
if (!editor.isAttachedToDOM) {
const div = editor.render();
this.div.append(div);
editor.isAttachedToDOM = true;
}
editor.fixAndSetPosition();
editor.onceAdded(!this.#isEnabling);
this.#uiManager.addToAnnotationStorage(editor);
editor._reportTelemetry(editor.telemetryInitialData);
}
moveEditorInDOM(editor) {
if (!editor.isAttachedToDOM) {
return;
}
const {
activeElement
} = document;
if (editor.div.contains(activeElement) && !this.#editorFocusTimeoutId) {
editor._focusEventsAllowed = false;
this.#editorFocusTimeoutId = setTimeout(() => {
this.#editorFocusTimeoutId = null;
if (!editor.div.contains(document.activeElement)) {
editor.div.addEventListener("focusin", () => {
editor._focusEventsAllowed = true;
}, {
once: true,
signal: this.#uiManager._signal
});
activeElement.focus();
} else {
editor._focusEventsAllowed = true;
}
}, 0);
}
editor._structTreeParentId = this.#accessibilityManager?.moveElementInDOM(this.div, editor.div, editor.contentDiv, true);
}
addOrRebuild(editor) {
if (editor.needsToBeRebuilt()) {
editor.parent ||= this;
editor.rebuild();
editor.show();
} else {
this.add(editor);
}
}
addUndoableEditor(editor) {
const cmd = () => editor._uiManager.rebuild(editor);
const undo = () => {
editor.remove();
};
this.addCommands({
cmd,
undo,
mustExec: false
});
}
getNextId() {
return this.#uiManager.getId();
}
get #currentEditorType() {
return AnnotationEditorLayer.#editorTypes.get(this.#uiManager.getMode());
}
combinedSignal(ac) {
return this.#uiManager.combinedSignal(ac);
}
#createNewEditor(params) {
const editorType = this.#currentEditorType;
return editorType ? new editorType.prototype.constructor(params) : null;
}
canCreateNewEmptyEditor() {
return this.#currentEditorType?.canCreateNewEmptyEditor();
}
async pasteEditor(mode, params) {
this.#uiManager.updateToolbar(mode);
await this.#uiManager.updateMode(mode);
const {
offsetX,
offsetY
} = this.#getCenterPoint();
const id = this.getNextId();
const editor = this.#createNewEditor({
parent: this,
id,
x: offsetX,
y: offsetY,
uiManager: this.#uiManager,
isCentered: true,
...params
});
if (editor) {
this.add(editor);
}
}
async deserialize(data) {
return (await AnnotationEditorLayer.#editorTypes.get(data.annotationType ?? data.annotationEditorType)?.deserialize(data, this, this.#uiManager)) || null;
}
createAndAddNewEditor(event, isCentered, data = {}) {
const id = this.getNextId();
const editor = this.#createNewEditor({
parent: this,
id,
x: event.offsetX,
y: event.offsetY,
uiManager: this.#uiManager,
isCentered,
...data
});
if (editor) {
this.add(editor);
}
return editor;
}
#getCenterPoint() {
const {
x,
y,
width,
height
} = this.div.getBoundingClientRect();
const tlX = Math.max(0, x);
const tlY = Math.max(0, y);
const brX = Math.min(window.innerWidth, x + width);
const brY = Math.min(window.innerHeight, y + height);
const centerX = (tlX + brX) / 2 - x;
const centerY = (tlY + brY) / 2 - y;
const [offsetX, offsetY] = this.viewport.rotation % 180 === 0 ? [centerX, centerY] : [centerY, centerX];
return {
offsetX,
offsetY
};
}
addNewEditor(data = {}) {
this.createAndAddNewEditor(this.#getCenterPoint(), true, data);
}
setSelected(editor) {
this.#uiManager.setSelected(editor);
}
toggleSelected(editor) {
this.#uiManager.toggleSelected(editor);
}
unselect(editor) {
this.#uiManager.unselect(editor);
}
pointerup(event) {
const {
isMac
} = util_FeatureTest.platform;
if (event.button !== 0 || event.ctrlKey && isMac) {
return;
}
if (event.target !== this.div) {
return;
}
if (!this.#hadPointerDown) {
return;
}
this.#hadPointerDown = false;
if (this.#currentEditorType?.isDrawer && this.#currentEditorType.supportMultipleDrawings) {
return;
}
if (!this.#allowClick) {
this.#allowClick = true;
return;
}
const currentMode = this.#uiManager.getMode();
if (currentMode === AnnotationEditorType.STAMP || currentMode === AnnotationEditorType.SIGNATURE) {
this.#uiManager.unselectAll();
return;
}
this.createAndAddNewEditor(event, false);
}
pointerdown(event) {
if (this.#uiManager.getMode() === AnnotationEditorType.HIGHLIGHT) {
this.enableTextSelection();
}
if (this.#hadPointerDown) {
this.#hadPointerDown = false;
return;
}
const {
isMac
} = util_FeatureTest.platform;
if (event.button !== 0 || event.ctrlKey && isMac) {
return;
}
if (event.target !== this.div) {
return;
}
this.#hadPointerDown = true;
if (this.#currentEditorType?.isDrawer) {
this.startDrawingSession(event);
return;
}
const editor = this.#uiManager.getActive();
this.#allowClick = !editor || editor.isEmpty();
}
startDrawingSession(event) {
this.div.focus({
preventScroll: true
});
if (this.#drawingAC) {
this.#currentEditorType.startDrawing(this, this.#uiManager, false, event);
return;
}
this.#uiManager.setCurrentDrawingSession(this);
this.#drawingAC = new AbortController();
const signal = this.#uiManager.combinedSignal(this.#drawingAC);
this.div.addEventListener("blur", ({
relatedTarget
}) => {
if (relatedTarget && !this.div.contains(relatedTarget)) {
this.#focusedElement = null;
this.commitOrRemove();
}
}, {
signal
});
this.#currentEditorType.startDrawing(this, this.#uiManager, false, event);
}
pause(on) {
if (on) {
const {
activeElement
} = document;
if (this.div.contains(activeElement)) {
this.#focusedElement = activeElement;
}
return;
}
if (this.#focusedElement) {
setTimeout(() => {
this.#focusedElement?.focus();
this.#focusedElement = null;
}, 0);
}
}
endDrawingSession(isAborted = false) {
if (!this.#drawingAC) {
return null;
}
this.#uiManager.setCurrentDrawingSession(null);
this.#drawingAC.abort();
this.#drawingAC = null;
this.#focusedElement = null;
return this.#currentEditorType.endDrawing(isAborted);
}
findNewParent(editor, x, y) {
const layer = this.#uiManager.findParent(x, y);
if (layer === null || layer === this) {
return false;
}
layer.changeParent(editor);
return true;
}
commitOrRemove() {
if (this.#drawingAC) {
this.endDrawingSession();
return true;
}
return false;
}
onScaleChanging() {
if (!this.#drawingAC) {
return;
}
this.#currentEditorType.onScaleChangingWhenDrawing(this);
}
destroy() {
this.commitOrRemove();
if (this.#uiManager.getActive()?.parent === this) {
this.#uiManager.commitOrRemove();
this.#uiManager.setActiveEditor(null);
}
if (this.#editorFocusTimeoutId) {
clearTimeout(this.#editorFocusTimeoutId);
this.#editorFocusTimeoutId = null;
}
for (const editor of this.#editors.values()) {
this.#accessibilityManager?.removePointerInTextLayer(editor.contentDiv);
editor.setParent(null);
editor.isAttachedToDOM = false;
editor.div.remove();
}
this.div = null;
this.#editors.clear();
this.#uiManager.removeLayer(this);
}
#cleanup() {
for (const editor of this.#editors.values()) {
if (editor.isEmpty()) {
editor.remove();
}
}
}
render({
viewport
}) {
this.viewport = viewport;
setLayerDimensions(this.div, viewport);
for (const editor of this.#uiManager.getEditors(this.pageIndex)) {
this.add(editor);
editor.rebuild();
}
this.updateMode();
}
update({
viewport
}) {
this.#uiManager.commitOrRemove();
this.#cleanup();
const oldRotation = this.viewport.rotation;
const rotation = viewport.rotation;
this.viewport = viewport;
setLayerDimensions(this.div, {
rotation
});
if (oldRotation !== rotation) {
for (const editor of this.#editors.values()) {
editor.rotate(rotation);
}
}
}
get pageDimensions() {
const {
pageWidth,
pageHeight
} = this.viewport.rawDims;
return [pageWidth, pageHeight];
}
get scale() {
return this.#uiManager.viewParameters.realScale;
}
}
;// ./src/display/draw_layer.js
class DrawLayer {
#parent = null;
#mapping = new Map();
#toUpdate = new Map();
static #id = 0;
constructor({
pageIndex
}) {
this.pageIndex = pageIndex;
}
setParent(parent) {
if (!this.#parent) {
this.#parent = parent;
return;
}
if (this.#parent !== parent) {
if (this.#mapping.size > 0) {
for (const root of this.#mapping.values()) {
root.remove();
parent.append(root);
}
}
this.#parent = parent;
}
}
static get _svgFactory() {
return shadow(this, "_svgFactory", new DOMSVGFactory());
}
static #setBox(element, [x, y, width, height]) {
const {
style
} = element;
style.top = `${100 * y}%`;
style.left = `${100 * x}%`;
style.width = `${100 * width}%`;
style.height = `${100 * height}%`;
}
#createSVG() {
const svg = DrawLayer._svgFactory.create(1, 1, true);
this.#parent.append(svg);
svg.setAttribute("aria-hidden", true);
return svg;
}
#createClipPath(defs, pathId) {
const clipPath = DrawLayer._svgFactory.createElement("clipPath");
defs.append(clipPath);
const clipPathId = `clip_${pathId}`;
clipPath.setAttribute("id", clipPathId);
clipPath.setAttribute("clipPathUnits", "objectBoundingBox");
const clipPathUse = DrawLayer._svgFactory.createElement("use");
clipPath.append(clipPathUse);
clipPathUse.setAttribute("href", `#${pathId}`);
clipPathUse.classList.add("clip");
return clipPathId;
}
#updateProperties(element, properties) {
for (const [key, value] of Object.entries(properties)) {
if (value === null) {
element.removeAttribute(key);
} else {
element.setAttribute(key, value);
}
}
}
draw(properties, isPathUpdatable = false, hasClip = false) {
const id = DrawLayer.#id++;
const root = this.#createSVG();
const defs = DrawLayer._svgFactory.createElement("defs");
root.append(defs);
const path = DrawLayer._svgFactory.createElement("path");
defs.append(path);
const pathId = `path_p${this.pageIndex}_${id}`;
path.setAttribute("id", pathId);
path.setAttribute("vector-effect", "non-scaling-stroke");
if (isPathUpdatable) {
this.#toUpdate.set(id, path);
}
const clipPathId = hasClip ? this.#createClipPath(defs, pathId) : null;
const use = DrawLayer._svgFactory.createElement("use");
root.append(use);
use.setAttribute("href", `#${pathId}`);
this.updateProperties(root, properties);
this.#mapping.set(id, root);
return {
id,
clipPathId: `url(#${clipPathId})`
};
}
drawOutline(properties, mustRemoveSelfIntersections) {
const id = DrawLayer.#id++;
const root = this.#createSVG();
const defs = DrawLayer._svgFactory.createElement("defs");
root.append(defs);
const path = DrawLayer._svgFactory.createElement("path");
defs.append(path);
const pathId = `path_p${this.pageIndex}_${id}`;
path.setAttribute("id", pathId);
path.setAttribute("vector-effect", "non-scaling-stroke");
let maskId;
if (mustRemoveSelfIntersections) {
const mask = DrawLayer._svgFactory.createElement("mask");
defs.append(mask);
maskId = `mask_p${this.pageIndex}_${id}`;
mask.setAttribute("id", maskId);
mask.setAttribute("maskUnits", "objectBoundingBox");
const rect = DrawLayer._svgFactory.createElement("rect");
mask.append(rect);
rect.setAttribute("width", "1");
rect.setAttribute("height", "1");
rect.setAttribute("fill", "white");
const use = DrawLayer._svgFactory.createElement("use");
mask.append(use);
use.setAttribute("href", `#${pathId}`);
use.setAttribute("stroke", "none");
use.setAttribute("fill", "black");
use.setAttribute("fill-rule", "nonzero");
use.classList.add("mask");
}
const use1 = DrawLayer._svgFactory.createElement("use");
root.append(use1);
use1.setAttribute("href", `#${pathId}`);
if (maskId) {
use1.setAttribute("mask", `url(#${maskId})`);
}
const use2 = use1.cloneNode();
root.append(use2);
use1.classList.add("mainOutline");
use2.classList.add("secondaryOutline");
this.updateProperties(root, properties);
this.#mapping.set(id, root);
return id;
}
finalizeDraw(id, properties) {
this.#toUpdate.delete(id);
this.updateProperties(id, properties);
}
updateProperties(elementOrId, properties) {
if (!properties) {
return;
}
const {
root,
bbox,
rootClass,
path
} = properties;
const element = typeof elementOrId === "number" ? this.#mapping.get(elementOrId) : elementOrId;
if (!element) {
return;
}
if (root) {
this.#updateProperties(element, root);
}
if (bbox) {
DrawLayer.#setBox(element, bbox);
}
if (rootClass) {
const {
classList
} = element;
for (const [className, value] of Object.entries(rootClass)) {
classList.toggle(className, value);
}
}
if (path) {
const defs = element.firstChild;
const pathElement = defs.firstChild;
this.#updateProperties(pathElement, path);
}
}
updateParent(id, layer) {
if (layer === this) {
return;
}
const root = this.#mapping.get(id);
if (!root) {
return;
}
layer.#parent.append(root);
this.#mapping.delete(id);
layer.#mapping.set(id, root);
}
remove(id) {
this.#toUpdate.delete(id);
if (this.#parent === null) {
return;
}
this.#mapping.get(id).remove();
this.#mapping.delete(id);
}
destroy() {
this.#parent = null;
for (const root of this.#mapping.values()) {
root.remove();
}
this.#mapping.clear();
this.#toUpdate.clear();
}
}
;// ./src/pdf.js
const pdfjsVersion = "5.1.91";
const pdfjsBuild = "45cbe8bb0";
{
globalThis.pdfjsTestingUtils = {
HighlightOutliner: HighlightOutliner
};
}
var __webpack_exports__AbortException = __webpack_exports__.AbortException;
var __webpack_exports__AnnotationEditorLayer = __webpack_exports__.AnnotationEditorLayer;
var __webpack_exports__AnnotationEditorParamsType = __webpack_exports__.AnnotationEditorParamsType;
var __webpack_exports__AnnotationEditorType = __webpack_exports__.AnnotationEditorType;
var __webpack_exports__AnnotationEditorUIManager = __webpack_exports__.AnnotationEditorUIManager;
var __webpack_exports__AnnotationLayer = __webpack_exports__.AnnotationLayer;
var __webpack_exports__AnnotationMode = __webpack_exports__.AnnotationMode;
var __webpack_exports__AnnotationType = __webpack_exports__.AnnotationType;
var __webpack_exports__ColorPicker = __webpack_exports__.ColorPicker;
var __webpack_exports__DOMSVGFactory = __webpack_exports__.DOMSVGFactory;
var __webpack_exports__DrawLayer = __webpack_exports__.DrawLayer;
var __webpack_exports__FeatureTest = __webpack_exports__.FeatureTest;
var __webpack_exports__GlobalWorkerOptions = __webpack_exports__.GlobalWorkerOptions;
var __webpack_exports__ImageKind = __webpack_exports__.ImageKind;
var __webpack_exports__InvalidPDFException = __webpack_exports__.InvalidPDFException;
var __webpack_exports__MathClamp = __webpack_exports__.MathClamp;
var __webpack_exports__OPS = __webpack_exports__.OPS;
var __webpack_exports__OutputScale = __webpack_exports__.OutputScale;
var __webpack_exports__PDFDataRangeTransport = __webpack_exports__.PDFDataRangeTransport;
var __webpack_exports__PDFDateString = __webpack_exports__.PDFDateString;
var __webpack_exports__PDFWorker = __webpack_exports__.PDFWorker;
var __webpack_exports__PasswordResponses = __webpack_exports__.PasswordResponses;
var __webpack_exports__PermissionFlag = __webpack_exports__.PermissionFlag;
var __webpack_exports__PixelsPerInch = __webpack_exports__.PixelsPerInch;
var __webpack_exports__RenderingCancelledException = __webpack_exports__.RenderingCancelledException;
var __webpack_exports__ResponseException = __webpack_exports__.ResponseException;
var __webpack_exports__SignatureExtractor = __webpack_exports__.SignatureExtractor;
var __webpack_exports__SupportedImageMimeTypes = __webpack_exports__.SupportedImageMimeTypes;
var __webpack_exports__TextLayer = __webpack_exports__.TextLayer;
var __webpack_exports__TouchManager = __webpack_exports__.TouchManager;
var __webpack_exports__Util = __webpack_exports__.Util;
var __webpack_exports__VerbosityLevel = __webpack_exports__.VerbosityLevel;
var __webpack_exports__XfaLayer = __webpack_exports__.XfaLayer;
var __webpack_exports__build = __webpack_exports__.build;
var __webpack_exports__createValidAbsoluteUrl = __webpack_exports__.createValidAbsoluteUrl;
var __webpack_exports__fetchData = __webpack_exports__.fetchData;
var __webpack_exports__getDocument = __webpack_exports__.getDocument;
var __webpack_exports__getFilenameFromUrl = __webpack_exports__.getFilenameFromUrl;
var __webpack_exports__getPdfFilenameFromUrl = __webpack_exports__.getPdfFilenameFromUrl;
var __webpack_exports__getUuid = __webpack_exports__.getUuid;
var __webpack_exports__getXfaPageViewport = __webpack_exports__.getXfaPageViewport;
var __webpack_exports__isDataScheme = __webpack_exports__.isDataScheme;
var __webpack_exports__isPdfFile = __webpack_exports__.isPdfFile;
var __webpack_exports__isValidExplicitDest = __webpack_exports__.isValidExplicitDest;
var __webpack_exports__noContextMenu = __webpack_exports__.noContextMenu;
var __webpack_exports__normalizeUnicode = __webpack_exports__.normalizeUnicode;
var __webpack_exports__setLayerDimensions = __webpack_exports__.setLayerDimensions;
var __webpack_exports__shadow = __webpack_exports__.shadow;
var __webpack_exports__stopEvent = __webpack_exports__.stopEvent;
var __webpack_exports__version = __webpack_exports__.version;
export { __webpack_exports__AbortException as AbortException, __webpack_exports__AnnotationEditorLayer as AnnotationEditorLayer, __webpack_exports__AnnotationEditorParamsType as AnnotationEditorParamsType, __webpack_exports__AnnotationEditorType as AnnotationEditorType, __webpack_exports__AnnotationEditorUIManager as AnnotationEditorUIManager, __webpack_exports__AnnotationLayer as AnnotationLayer, __webpack_exports__AnnotationMode as AnnotationMode, __webpack_exports__AnnotationType as AnnotationType, __webpack_exports__ColorPicker as ColorPicker, __webpack_exports__DOMSVGFactory as DOMSVGFactory, __webpack_exports__DrawLayer as DrawLayer, __webpack_exports__FeatureTest as FeatureTest, __webpack_exports__GlobalWorkerOptions as GlobalWorkerOptions, __webpack_exports__ImageKind as ImageKind, __webpack_exports__InvalidPDFException as InvalidPDFException, __webpack_exports__MathClamp as MathClamp, __webpack_exports__OPS as OPS, __webpack_exports__OutputScale as OutputScale, __webpack_exports__PDFDataRangeTransport as PDFDataRangeTransport, __webpack_exports__PDFDateString as PDFDateString, __webpack_exports__PDFWorker as PDFWorker, __webpack_exports__PasswordResponses as PasswordResponses, __webpack_exports__PermissionFlag as PermissionFlag, __webpack_exports__PixelsPerInch as PixelsPerInch, __webpack_exports__RenderingCancelledException as RenderingCancelledException, __webpack_exports__ResponseException as ResponseException, __webpack_exports__SignatureExtractor as SignatureExtractor, __webpack_exports__SupportedImageMimeTypes as SupportedImageMimeTypes, __webpack_exports__TextLayer as TextLayer, __webpack_exports__TouchManager as TouchManager, __webpack_exports__Util as Util, __webpack_exports__VerbosityLevel as VerbosityLevel, __webpack_exports__XfaLayer as XfaLayer, __webpack_exports__build as build, __webpack_exports__createValidAbsoluteUrl as createValidAbsoluteUrl, __webpack_exports__fetchData as fetchData, __webpack_exports__getDocument as getDocument, __webpack_exports__getFilenameFromUrl as getFilenameFromUrl, __webpack_exports__getPdfFilenameFromUrl as getPdfFilenameFromUrl, __webpack_exports__getUuid as getUuid, __webpack_exports__getXfaPageViewport as getXfaPageViewport, __webpack_exports__isDataScheme as isDataScheme, __webpack_exports__isPdfFile as isPdfFile, __webpack_exports__isValidExplicitDest as isValidExplicitDest, __webpack_exports__noContextMenu as noContextMenu, __webpack_exports__normalizeUnicode as normalizeUnicode, __webpack_exports__setLayerDimensions as setLayerDimensions, __webpack_exports__shadow as shadow, __webpack_exports__stopEvent as stopEvent, __webpack_exports__version as version };
//# sourceMappingURL=pdf.mjs.map
================================================
FILE: cookbook/static/pdfjs/web/pdf.sandbox.mjs
================================================
/**
* @licstart The following is the entire license notice for the
* JavaScript code in this page
*
* Copyright 2024 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @licend The above is the entire license notice for the
* JavaScript code in this page
*/
/******/ // The require scope
/******/ var __webpack_require__ = {};
/******/
/************************************************************************/
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ // define getter functions for harmony exports
/******/ __webpack_require__.d = (exports, definition) => {
/******/ for(var key in definition) {
/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ }
/******/ }
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */
/******/ (() => {
/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ })();
/******/
/************************************************************************/
var __webpack_exports__ = globalThis.pdfjsSandbox = {};
// EXPORTS
__webpack_require__.d(__webpack_exports__, {
QuickJSSandbox: () => (/* binding */ QuickJSSandbox)
});
;// ./external/quickjs/quickjs-eval.js
var Module=(()=>{var _scriptDir=typeof document!=='undefined'&&document.currentScript?document.currentScript.src:undefined;return function(moduleArg={}){var d=moduleArg,k,n;d.ready=new Promise((a,b)=>{k=a;n=b;});var p=Object.assign({},d),q="";"undefined"!=typeof document&&document.currentScript&&(q=document.currentScript.src);_scriptDir&&(q=_scriptDir);q.startsWith("blob:")?q="":q=q.substr(0,q.replace(/[?#].*/,"").lastIndexOf("/")+1);var aa=d.print||console.log.bind(console),u=d.printErr||console.error.bind(console);Object.assign(d,p);p=null;var v;d.wasmBinary&&(v=d.wasmBinary);"object"!=typeof WebAssembly&&w("no native wasm support detected");var x,y=!1,z,A,B,C;function D(){var a=x.buffer;d.HEAP8=z=new Int8Array(a);d.HEAP16=new Int16Array(a);d.HEAPU8=A=new Uint8Array(a);d.HEAPU16=new Uint16Array(a);d.HEAP32=B=new Int32Array(a);d.HEAPU32=C=new Uint32Array(a);d.HEAPF32=new Float32Array(a);d.HEAPF64=new Float64Array(a);}var E=[],F=[],G=[];function ba(){var a=d.preRun.shift();E.unshift(a);}var H=0,I=null,J=null;function w(a){d.onAbort?.(a);a="Aborted("+a+")";u(a);y=!0;a=new WebAssembly.RuntimeError(a+". Build with -sASSERTIONS for more info.");n(a);throw a;}var K=a=>a.startsWith("data:application/octet-stream;base64,"),L;L="data:application/octet-stream;base64,AGFzbQEAAAABzgZtYAJ/fwBgA39/fwF/YAR/fn9/AX5gAn9/AX9gBX9+f39/AX5gAX8Bf2ADf39/AGAEf39/fwF/YAJ/fgF+YAF/AGABfAF8YAV/f39/fwF/YAJ/fgBgAn9+AX9gAn9/AX5gA39/fgF/YAN/fn8BfmAGf35/f39/AX5gA39+fwBgA39+fwF/YAZ/f39/f38Bf2AEf39/fwBgBn9+fn9/fwF+YAR/f35/AX9gA39+fgF+YAN/f38BfmADf35+AX9gBH9/f38BfmAFf35+fn4AYAJ8fAF8YAF/AX5gBH9/f34Bf2AFf35+f38BfmAFf39/f38AYAd/fn9+fn5/AX9gBX9/f35+AX9gB39/f39/f38Bf2AAAGAFf35/fn8Bf2AEf35+fwBgBH9+fn8BfmAFf35+fn8Bf2AFf39/f38BfmAEf39+fgF/YAF+AX9gBH9+f34BfmAEf35/fwBgBH9+fn8Bf2AJf39/f39/f39/AX9gCH9/f39/f39/AX9gA39+fgBgBH9+f38Bf2AGf35/fn5/AX9gBX9+fn9/AGABfgF+YAd/fn9/f39/AX5gAX8BfGADf39+AGAEf35/fgF/YAV/f35/fwF/YAR/fn5+AX9gBn9/f39/fwF+YAN+f38Bf2AHf39/f39/fwBgAnx/AXxgA39/fgF+YAJ+fwF/YAN8fH8BfGAEf39+fwBgBH9+fn4BfmAAAX9gBn98f39/fwF/YAABfGAFf35/fn8BfmAGf39+fn5+AX9gAn5/AGACf3wAYAV/f39/fgF+YAR/f35/AX5gBH9+f34AYAd/fn5+f39/AX5gBH5+fn4Bf2AKf39/f39/f39/fwF/YAd/f39/f39+AX5gBX9+f39/AGAHfH9/f39/fwBgBX98f39/AX5gAXwBf2AFf39+f38AYAZ/fn5+fn8Bf2AGf35/f39/AX9gBH98f38Bf2AGf39/f39/AGAEf39/fgF+YAV/fn9/fwF/YAV/fn5+fgF/YAJ/fwF8YAV/fn5/fwF/YAV/f35+fgF+YAV/f35+fwF/YAJ8fwF/YAJ8fAF/YAh/fn5+fn9+fgF+YAN/fnwBfmAAAX5gB39/f35+fn8Bf2ACfn4BfGADfn5+AX9gA39/fAACSQwBYQFhABUBYQFiACUBYQFjAAcBYQFkAAYBYQFlAEgBYQFmAAABYQFn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IAILLQEBfwJAIAAoAgAiAUUNACAAKAIQIgBFDQAgASgCACAAQQAgASgCBBEBABoLCzEAIAFBAE4EQCAAQbYBEA0gACABEDggACgCQCIAKAKkAiABQRRsaiAAKAKEAjYCBAsLJwEBfyMAQRBrIgIkACACIAE2AgwgACACQQxqQQQQchogAkEQaiQACxcAIAAgASACQoCAgIAwIAMgBEECENIBCxgBAX4gASkDACEDIAEgAjcDACAAIAMQDAszAQF/IAIEQCAAIQMDQCADIAEtAAA6AAAgA0EBaiEDIAFBAWohASACQQFrIgINAAsLIAALwQUCAn4GfyMAQeAAayIJJAAgA0EAIANBAEobIQsDQCAKIAtHBEAgACACIApBBHRqIgMoAgAQsAUhBiADLQAEIQdCgICAgDAhBAJAAkACQAJAAkACQAJAAkACQAJAIAMtAAUOCgECAgUHAwQIBQAGCyAAIAMoAggQsAUhCAJ+AkACQAJAIAMoAgxBAWoOAwIAAQkLIAAgACkDwAEiBCAIIARBABARDAILIAAgACgCKCkDECIEIAggBEEAEBEMAQsgACABIAggAUEAEBELIQQgACAIEBAgBkHLAUYEQEEBIQcMCAsgBkHUAUcNB0EAIQcMBwsCQCAGQcsBRgRAQQEhBwwBCyAGQdQBRw0AQQAhBwsgACABIAZBAiADIAcQgAMaDAcLIAAgASAGQoCAgIAwIAMoAggEfiAJIAMoAgA2AhAgCUEgaiIIQcAAQeEqIAlBEGoQSBogACADKAIIIAhBAEEKQQggAy0ABUECRhsgAy4BBhCCAQVCgICAgDALIgQgAygCDAR+IAkgAygCADYCACAJQSBqIghBwABB2iogCRBIGiAAIAMoAgwgCEEBQQtBCSADLQAFQQJGGyADLgEGEIIBBUKAgICAMAsiBSAHQYA6chBqGiAAIAQQDCAAIAUQDAwGCyADKQMIIgRCgICAgAh8Qv////8PWARAIARC/////w+DIQQMBQtCgICAgMB+IAS5vSIEQoCAgIDAgYD8/wB9IARC////////////AINCgICAgICAgPj/AFYbIQQMBAtCgICAgMB+IAMpAwgiBEKAgICAwIGA/P8AfSAEQv///////////wCDQoCAgICAgID4/wBWGyEEDAMLIAAgASAGQQIgAyAHEIADGgwDCxABAAsgAzUCCCEECyAAIAEgBiAEIAcQFRoLIAAgBhAQIApBAWohCgwBCwsgCUHgAGokAAuMAgICfgF/AkACQAJAAkACQAJAAkACQAJAQQcgAUIgiKciBCAEQQdrQW5JG0EKag4SAgAGBAAAAAAAAQMFAAAAAAEDAAsgAEGbHkEAEBJCgICAgOAADwsgBEF1SQ0GIAGnIgAgACgCAEEBajYCAAwGCyAAQSEQhgEhAgwECyAAQQQQhgEhAgwDCyAAIABBBRCGASICQTAgAacpAgRC/////weDQQAQFRoMAgsgAEEGEIYBIQIMAQsgAEEHEIYBIQILQoCAgIDgACEDIAJCgICAgHCDQoCAgIDgAFIEfiAEQXVPBEAgAaciBCAEKAIAQQFqNgIACyAAIAIgARC9ASACBUKAgICA4AALDwsgAQsyAQF/AkAgAUIgiKdBdUkNACABpyICIAIoAgAiAkEBazYCACACQQFKDQAgACABEJcFCwsLACAAQeweQQAQEgujBAELfyAAKAIAIQUjAEEQayIIIAI2AgxBfyEJAkADQAJAIAggAiIDQQRqIgI2AgwgAygCACIHQX9GDQAgACgCBCEKA0AgASIEIApODQMgBCAEIAVqIgwtAAAiBkECdEHgrgFqIg0tAABqIgEgCkoNAyAGQcYBRgRAIAwoAAEhCQwBCwsgBiAHRwRAIAdBGHYgBkYgBiAHQRB2Qf8BcUZyIAYgB0H/AXFGckUgBiAHQQh2Qf8BcUdxIAZFIAdBgAJJcnINAyAAIAY2AhALIARBAWohBAJAAkACQAJAAkACQAJAAkAgDS0AA0EFaw4YAAkACQkBCQkBCQkBAQECAgICBAUGBwkDCQsgBCAFai0AACEEIAggA0EIaiICNgIMIAMoAgQiA0F/RgRAIAAgBDYCFAwJCyADIARGDQgMCQsgBCAFai8AACEEIAggA0EIaiICNgIMIAMoAgQiA0F/RgRAIAAgBDYCFAwICyADIARGDQcMCAsgACAEIAVqKAAANgIYDAYLIAAgBCAFaiIDKAAANgIYIAAgAy8ABDYCHAwFCyAAIAQgBWooAAA2AiAMBAsgACAEIAVqIgMoAAA2AiAgACADLQAENgIcDAMLIAAgBCAFaiIDKAAANgIgIAAgAy8ABDYCHAwCCyAAIAQgBWoiAygAADYCICAAIAMoAAQ2AhggACADLQAINgIcDAELCyAAIAk2AgwgACABNgIIQQEhCwsgCwskAQF/IAAoAhAiAkEQaiABIAIoAgARAwAiAUUEQCAAEHALIAELCwAgACABQQAQjQQLJwEBfyMAQRBrIgIkACACIAE7AQ4gACACQQ5qQQIQchogAkEQaiQAC9QBAgR/An5BfyECAkACQAJAAkACQAJAAkAgAUIgiKciA0EKag4RAwUFAgUFBQUFBAABAQEFBQYFCyABp0EARw8LIAGnDwsgAacpAgQhByAAIAEQDCAHQv////8Hg0IAUg8LIAGnKAIMIQQgACABEAwgBEH/////B2pBfkkPCyABpywABSEFIAAgARAMIAVBAE4PCyADQQdrQW1NBEAgAUKAgICAwIGA/P8AfEL///////////8Ag0IBfUKAgICAgICA+P8AVA8LIAAgARAMQQEhAgsgAgs/AQJ/IwBBEGsiAiQAAn8gASAAKAIQRwRAIAIgATYCACAAQcyQASACEBNBfwwBCyAAEA8LIQMgAkEQaiQAIAMLCwAgACABQQEQ6QULGQAgAEEAEFAaIABCgICAgPD/////ADcCBAvDCgIFfxF+IwBB4ABrIgUkACAEQv///////z+DIQwgAiAEhUKAgICAgICAgIB/gyEKIAJC////////P4MiDUIgiCEOIARCMIinQf//AXEhBwJAAkAgAkIwiKdB//8BcSIJQf//AWtBgoB+TwRAIAdB//8Ba0GBgH5LDQELIAFQIAJC////////////AIMiC0KAgICAgIDA//8AVCALQoCAgICAgMD//wBRG0UEQCACQoCAgICAgCCEIQoMAgsgA1AgBEL///////////8AgyICQoCAgICAgMD//wBUIAJCgICAgICAwP//AFEbRQRAIARCgICAgICAIIQhCiADIQEMAgsgASALQoCAgICAgMD//wCFhFAEQCACIAOEUARAQoCAgICAgOD//wAhCkIAIQEMAwsgCkKAgICAgIDA//8AhCEKQgAhAQwCCyADIAJCgICAgICAwP//AIWEUARAIAEgC4QhGUIAIQEgGVAEQEKAgICAgIDg//8AIQoMAwsgCkKAgICAgIDA//8AhCEKDAILIAEgC4RQBEBCACEBDAILIAIgA4RQBEBCACEBDAILIAtC////////P1gEQCAFQdAAaiABIA0gASANIA1QIgYbeSAG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////B4OENwIEIABBADYCBCABrUKAgICAkH+ECwsPACAAKAJAQYACaiABEBsLEwAgACABIAIgAyABQYCAARDQAQsNACAAIAEgAkEGEMgCCx8BAX8gACgCJCIBIAEoAgBBAWo2AgAgACABQQIQ5wULZwECfwJ/IAAoAggiAiAAKAIMTgRAQX8gACACQQFqIAEQxAINARoLIAAgACgCCCIDQQFqNgIIIAAoAgRBEGohAgJAIAAoAhAEQCACIANBAXRqIAE7AQAMAQsgAiADaiABOgAAC0EACwt6AQN/AkACQCAAIgFBA3FFDQAgAS0AAEUEQEEADwsDQCABQQFqIgFBA3FFDQEgAS0AAA0ACwwBCwNAIAEiAkEEaiEBIAIoAgAiA0F/cyADQYGChAhrcUGAgYKEeHFFDQALA0AgAiIBQQFqIQIgAS0AAA0ACwsgASAAawsNACAAIAEgAkEAEJkDCywBAX8jAEEQayIDJAAgAyACNgIMIABB3ABqQYABIAEgAhDJAhogA0EQaiQAC+gDAQl/IwBBIGsiBSQAIAEgAiABKAIMIAIoAgxJIgYbIgcoAgQgAiABIAYbIggoAgRzIQoCQAJAIAcoAgwiAkUEQAJAIAgoAggiAUH/////B0cEQCAHKAIIIgJB/////wdHDQELIAAQKkEAIQIMAwsgAUH+////B0cgAkH+////B0dxRQRAAkAgAUH+////B0YEQCACQYCAgIB4Rg0BDAQLIAFBgICAgHhHIAJB/v///wdHcg0DCyAAECpBASECDAMLIAAgChCAAUEAIQIMAgsgCCgCDCILIQkgAiEGIARBB3FBBkYEQCACIANBIWpBBXYiASABIAJKGyEGIAsgASABIAtKGyEJCwJ/AkAgACAIRg0AIAAgB0YNACAADAELIAAoAgAhASAFQgA3AhggBUKAgICAgICAgIB/NwIQIAUgATYCDCAAIQwgBUEMagshASAHKAIQIQAgCCgCECENAn8gASAGIAlqEFAEQCABECpBIAwBCyABKAIQIA0gC0ECdGogCUECdGsgCSAAIAJBAnRqIAZBAnRrIAYQ8AEgASAKNgIEIAEgBygCCCAIKAIIajYCCCABIAMgBBCbAgshAiABIAVBDGoiAEcNASAMIAAQvwQMAQsgACAKEH9BACECCyAFQSBqJAAgAgsKACAAIAFBARBHCygB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ajAAAEL/////D4MPCyAEKAIoIANNDQggBCgCJCADajEAAA8LIAQoAiggA00NByAEKAIkIANBAXRqMgEAQv////8Pgw8LIAQoAiggA00NBiAEKAIkIANBAXRqMwEADwsgBCgCKCADTQ0FIAQoAiQgA0ECdGo1AgAPCyAEKAIoIANNDQQgBCgCJCADQQJ0aigCACIAQQBOBEAgAK0PC0KAgICAwH4gALi9IgFCgICAgMCBgPz/AH0gAUKAgICAgICA+P8AVhsPCyAEKAIoIANNDQMgACAEKAIkIANBA3RqKQMAEL8CDwsgBCgCKCADTQ0CIAAgBCgCJCADQQN0aikDABCIBA8LIAQoAiggA00NAUKAgICAwH4gBCgCJCADQQJ0aioCALu9IgFCgICAgMCBgPz/AH0gAUL///////////8Ag0KAgICAgICA+P8AVhsPCyAEKAIoIANNDQBCgICAgMB+IAQoAiQgA0EDdGopAwAiAUKAgICAwIGA/P8AfSABQv///////////wCDQoCAgICAgID4/wBWGw8LIAAgAhAwIQMgACACEAwgA0UEQEKAgICA4AAPCyAAIAEgAyABQQAQESEBIAAgAxAQCyABC4IDAgR/An4CQCAAKQNwIgVQRSAFIAApA3ggACgCBCIBIAAoAiwiAmusfCIGV3FFBEAjAEEQayICJABBfyEBAkACfyAAIAAoAkgiA0EBayADcjYCSCAAKAIUIAAoAhxHBEAgAEEAQQAgACgCJBEBABoLIABBADYCHCAAQgA3AxAgACgCACIDQQRxBEAgACADQSByNgIAQX8MAQsgACAAKAIsIAAoAjBqIgQ2AgggACAENgIEIANBG3RBH3ULDQAgACACQQ9qQQEgACgCIBEBAEEBRw0AIAItAA8hAQsgAkEQaiQAIAEiA0EATg0BIAAoAgQhASAAKAIsIQILIABCfzcDcCAAIAE2AmggACAGIAIgAWusfDcDeEF/DwsgBkIBfCEGIAAoAgQhASAAKAIIIQICQCAAKQNwIgVQDQAgBSAGfSIFIAIgAWusWQ0AIAEgBadqIQILIAAgAjYCaCAAIAYgACgCLCIAIAFrrHw3A3ggACABTwRAIAFBAWsgAzoAAAsgAwtPAQF/An9BACAAKAIMIAFGDQAaIAAoAgAiAigCACAAKAIQIAFBAnQgAigCBBEBACECIAEEQEF/IAJFDQEaCyAAIAE2AgwgACACNgIQQQALC9EBAQZ/IABBAWohBQJAAkAgAC0AACIDwCIHQQBOBEAgBSEBDAELQX8hBCAHQUBrQf8BcSIDQT1LDQEgA0ECdEGU9gFqKAIAIgYgAU4NASAGQQFrIQggACAGakEBaiEBIAcgBkHz9QFqLQAAcSEDQQAhAANAIAAgBkcEQCAFLAAAIgRBv39KBEBBfw8FIARBP3EgA0EGdHIhAyAAQQFqIQAgBUEBaiEFDAILAAsLQX8hBCADIAhBAnRBgPYBaigCAEkNAQsgAiABNgIAIAMhBAsgBAsLACAAIAFBABDpBQsJACAAQQEQrQELugEBAn8CQAJAIAJC/////wdYBEAgACABIAKnQYCAgIB4chBuIgRBAEwNASAAIAEgAhBOIgJCgICAgHCDQoCAgIDgAFINAkF/IQQMAgsgACACEIsDIgVFBEBBfyEEDAELAkAgACABIAUQbiIEQQBMBEBCgICAgDAhAgwBCyAAIAEgBSABQQAQESICQoCAgIBwg0KAgICA4ABSDQBBfyEECyAAIAUQEAwBC0KAgICAMCECCyADIAI3AwAgBAsbAQF/IAAgARA1BH9BAAUgAEH7OUEAEBJBfwsLYwEBfyACQiCIp0F1TwRAIAKnIgUgBSgCAEEBajYCAAsCQCAAIAEgAhDTBSIFDQACQCABKAIAIgBBAEgEQCAAIARqIgBBACAAQQBKGyEDDAELIAAgA0wNAQsgASADNgIACyAFCxgAIAAtAABBIHFFBEAgASACIAAQlwQaCwsPACAAKAJAQYACaiABEA4LrgIAAkACQAJAAkAgAkEDTARAAkACQAJAAkACQAJAAkACQAJAIAFB2ABrDgkAAQIDBAUGBwgKCyAAIAJBO2tB/wFxEA4PCyAAIAJBN2tB/wFxEA4PCyAAIAJBM2tB/wFxEA4PCyAAIAJBL2tB/wFxEA4PCyAAIAJBK2tB/wFxEA4PCyAAIAJBJ2tB/wFxEA4PCyAAIAJBI2tB/wFxEA4PCyAAIAJBH2tB/wFxEA4PCyAAIAJBG2tB/wFxEA4PCyACQf8BSw0BAkACQAJAIAFB2ABrDgMAAQIECyAAQcIBEA4MBQsgAEHDARAODAQLIABBxAEQDgwDCyABQSJGDQELIAAgAUH/AXEQDiAAIAJB//8DcRAmDwsgACACQRJrQf8BcRAODwsgACACQf8BcRAOCzgBAX8CQAJAIAFCgICAgHBUDQAgAaciAy8BBiACRw0AIAMoAiAiAw0BCyAAIAIQigNBACEDCyADC0EBAX8gAQRAA0AgAiADRkUEQCAAIAEgA0EDdGooAgQQECADQQFqIQMMAQsLIAAoAhAiAEEQaiABIAAoAgQRAAALCywBAX8gACgCECICQRBqIAEgAigCABEDACICBEAgAkEAIAEQLA8LIAAQcCACC20BAX8jAEGAAmsiBSQAIARBgMAEcSACIANMckUEQCAFIAFB/wFxIAIgA2siA0GAAiADQYACSSIBGxAsGiABRQRAA0AgACAFQYACEFcgA0GAAmsiA0H/AUsNAAsLIAAgBSADEFcLIAVBgAJqJAALvgECAn4BfwJAAkAgAUKAgICAcINCgICAgDBRBEAgACgCKCACQQN0aikDACIDQiCIp0F0Sw0BDAILIAAgAUE8IAFBABARIgNCgICAgHCDQoCAgIDgAFEEQCADDwsgA0L/////b1YNASAAIAMQDCAAIAEQ/AIiBUUEQEKAgICA4AAPCyAFKAIoIAJBA3RqKQMAIgNCIIinQXVJDQELIAOnIgUgBSgCAEEBajYCAAsgACADIAIQRyEEIAAgAxAMIAQLDAAgAEHZ6gBBABASCw0AIAAgASABED0Q6gELdQEBfiAAIAEgBH4gAiADfnwgA0IgiCICIAFCIIgiBH58IANC/////w+DIgMgAUL/////D4MiAX4iBUIgiCADIAR+fCIDQiCIfCABIAJ+IANC/////w+DfCIBQiCIfDcDCCAAIAVC/////w+DIAFCIIaENwMAC1ABAX4CQCADQcAAcQRAIAEgA0FAaq2GIQJCACEBDAELIANFDQAgAiADrSIEhiABQcAAIANrrYiEIQIgASAEhiEBCyAAIAE3AwAgACACNwMIC2QAAkACQCABQQBIDQAgACgCrAIgAUwNACAAKAKkAiABQRRsaiIAIAAoAgAgAmoiADYCACAAQQBIDQEgAA8LQdwXQajsAEHzpwFBr8MAEAAAC0HphQFBqOwAQfanAUGvwwAQAAALcAECfyAEIAMoAgBKBH8jAEEQayIFJAAgACABKAIAIAQgAygCAEEDbEECbSIAIAAgBEgbIgAgAmwgBUEMahCnASIEBH8gAyAFKAIMIAJu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EGsiBCQAAkAgAkIgiKciA0ECTQRAIAEgAqe3OQMAQQAhAwwBCyADQQdrQW1NBEAgASACQoCAgIDAgYD8/wB8NwMAQQAhAwwBCwJ/IAAgAhCWASICQoCAgIBwg0KAgICA4ABRBEBEAAAAAAAA+H8hBUF/DAELAnwCQAJAQQcgAkIgiKciAyADQQdrQW5JGyIDQXZHBEAgA0EHRg0CIAMNASACp7cMAwsgAqdBBGogBEEIahCxBCAAIAIQDCAEKwMIIQVBAAwDCxABAAsgAkKAgICAwIGA/P8AfL8LIQVBAAshAyABIAU5AwALIARBEGokACADC90BAQN/AkAgAUKAgICAcFoEQCABpyEDA0ACQCADLQAFQQRxRQ0AIAAoAhAoAkQgAy8BBkEYbGooAhQiBEUNACAEKAIQIgRFDQAgAyADKAIAQQFqNgIAIAAgA61CgICAgHCEIgEgAiAEERMAIQUgACABEAwgBQ8LIAMgAygCAEEBajYCACAAQQAgAyACEEMhBCAAIAOtQoCAgIBwhBAMIAQNAgJAIAMvAQZBFWtB//8DcUEKSw0AIAAgAhCTAyIERQ0AIARBH3UPCyADKAIQKAIsIgMNAAsLQQAhBAsgBAvNCQIEfwV+IwBB8ABrIgYkACAEQv///////////wCDIQkCQAJAIAFQIgUgAkL///////////8AgyIKQoCAgICAgMD//wB9QoCAgICAgMCAgH9UIApQG0UEQCADQgBSIAlCgICAgICAwP//AH0iC0KAgICAgIDAgIB/ViALQoCAgICAgMCAgH9RGw0BCyAFIApCgICAgICAwP//AFQgCkKAgICAgIDA//8AURtFBEAgAkKAgICAgIAghCEEIAEhAwwCCyADUCAJQoCAgICAgMD//wBUIAlCgICAgICAwP//AFEbRQRAIARCgICAgICAIIQhBAwCCyABIApCgICAgICAwP//AIWEUARAQoCAgICAgOD//wAgAiABIAOFIAIgBIVCgICAgICAgICAf4WEUCIFGyEEQgAgASAFGyEDDAILIAMgCUKAgICAgIDA//8AhYRQDQEgASAKhFAEQCADIAmEQgBSDQIgASADgyEDIAIgBIMhBAwCCyADIAmEUEUNACABIQMgAiEEDAELIAMgASABIANUIAkgClYgCSAKURsiCBshCiAEIAIgCBsiDEL/////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/wBWGwsQTgtTAQF/IAAoAhAiBEEQaiABIAIgBCgCCBEBACIBIAJFckUEQCAAEHAgAQ8LIAMEQCADIAEgACgCECgCDBEFACIAIAJrIgJBACAAIAJPGzYCAAsgAQsNACAAQQAgAUEAEJoDC/kBAgN+An8jAEEQayIFJAACfiABvSIEQv///////////wCDIgJCgICAgICAgAh9Qv/////////v/wBYBEAgAkI8hiEDIAJCBIhCgICAgICAgIA8fAwBCyACQoCAgICAgID4/wBaBEAgBEI8hiEDIARCBIhCgICAgICAwP//AIQMAQsgAlAEQEIADAELIAUgAkIAIAKnZ0EgaiACQiCIp2cgAkKAgICAEFQbIgZBMWoQYiAFKQMAIQMgBSkDCEKAgICAgIDAAIVBjPgAIAZrrUIwhoQLIQIgACADNwMAIAAgAiAEQoCAgICAgICAgH+DhDcDCCAFQRBqJAALKgEBfyMAQRBrIgMkACADIAI2AgwgACABIAJBpANBABCUBBogA0EQaiQAC0cAIAAgAUkEQCAAIAEgAhAeGg8LIAIEQCAAIAJqIQAgASACaiEBA0AgAEEBayIAIAFBAWsiAS0AADoAACACQQFrIgINAAsLCyABAX4gACAAIAIgASADQQRBABCCASIFIAEgBBC/ASAFC4sMAQZ/IwBBIGsiAyQAAkACQAJAAkACQAJ/IAAoAhAiAkGDf0cEQEEAIAJBV0cNARogACgCQCIELQBsQQFxRQRAIABBl+AAQQAQEwwDCyAEKAJkRQRAIABB6jtBABATDAMLQX8hBSAAEA8NBQJAAkACQAJAIAAoAhAiBEEpaw4EAgEBAgALIARB3QBGIARBOmtBAklyIARB/QBGcg0BCyAAKAIwDQBBACECIARBKkYEQCAAEA8NCEEBIQILIAAgARCtAUUNAQwHCyAAQQYQDUEAIQILIAAoAkAtAGwhASACBEAgABAtIQUgABAtIQIgAEGAAUH/ACABQQNGGxANIABBDhANIABBBhANIABBBhANIAAgBRAaIABBhgEQDSABQQNHIgdFBEAgAEGMARANCyAAQYMBEA0gAEHCABANIABB6gAQFyAAQesAQX8QGCEGIAAgAhAaQYoBIQQgACAHBH9BigEFIABBwQAQDSAAQcEAEBdBiwELEA0gAEEREA0gAEHrAEF/EBghBCAA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AqcQvAJFIQQMBgsgAacgAqdGIAVBeEZxIQQMBQsgBUF/Rw0EIAGnIAKnRiEEDAQLIAGntyEIIAVBB0cEQCAFDQQgAqe3DAILIAJCgICAgMCBgPz/AHy/DAELIAFCgICAgMCBgPz/AHy/IQggBQRAIAVBB0cNAyACQoCAgIDAgYD8/wB8vwwBCyACp7cLIQkCQCADBEACQAJAIAi9IgFC////////////AIMiAkKBgICAgICA+P8AWgRAIAm9Qv///////////wCDQoGAgICAgID4/wBUIQQMAQsgCb0iCkL///////////8Ag0KBgICAgICA+P8AVA0BCyAEIAJCgICAgICAgPj/AFZzIQQMBQsgA0ECRw0BCyAIIAlhIQQMAwsgASAKUSEEDAILIAVBdkcNACAAIAdBHGoiBiABEK0CIgMgACAHQQhqIAIQrQIiBRC9AiEEIAMgBkYEQCAGEBkLIAUgB0EIaiIDRw0AIAMQGQsgACABEAwgACACEAwLIAdBMGokACAECy8BAX8jAEHQAGsiAyQAIAMgACADQRBqIAEQgQE2AgAgACACIAMQEiADQdAAaiQACw0AIAAgASABED0QnQMLHQEBfyAAIAFB/wFxEA4gACgCBCEDIAAgAhAbIAMLEgAgACABIAIgAyAEQZQDELEDC1IBAX8gACgCDCIDRQRAQQAPCyAAIAAoAghB/////wNBgYCAgHwgASABQYGAgIB8TBsiASABQf////8DThtqNgIIIABB/////wMgAiADQQAQ3AILHwEBfyAAKAIMIgNFBEBBAA8LIAAgASACIANBABDcAgsgACABQgA3AgwgAUKAgICAgICAgIB/NwIEIAEgADYCAAtmAQF/An9BACAAKAIIIgIgAU8NABpBfyAAKAIMDQAaIAAoAhQgACgCACACQQNsQQF2IgIgASABIAJJGyIBIAAoAhARAQAiAkUEQCAAQQE2AgxBfw8LIAAgATYCCCAAIAI2AgBBAAsLZwECfwJAIAFCgICAgHBUDQAgAaciAy8BBkEEayIEQR1LQQEgBHRBz4CAgAJxRXINACAAIAMpAyAQDCADIAI3AyAPCyAAIAIQDCABQoCAgIBwg0KAgICA4ABSBEAgAEHu0gBBABASCwshAQF/IAAgASAAIAIQtgEiAiADIAQQFSEFIAAgAhAQIAULRwIBfgF/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PEdxRQRAIAAgBigCNBCeAQsgACAEEBoLIAAgBigCMCAGKAIsIAYoAjQgBigCPEEBIAwQwQELIAAoAhBB3QBGDQAgB0Glf0YEQEGQ0wAhCAwECyAAQSwQKEUNAQwFCwsgAEGFARANIAAoAkAiASABKAKwAigCADYCsAIgABAPDQMLAkAgBUUNACAAKAIQQT1HDQBBfyEHIABB7ABBfxAYIQEgABAPDQQgACAKEBogAwRAIABBDhANCyAAEFMNBCAAQewAIAsQGBogACABEBpBASEHDAQLIANFBEAgAEHDPUEAEBMMAwsgACgCQCgCgAIgDWpBswEgDiANaxAsGiAAKAJAKAKkAiAKQRRsaiIAIAAoAgBBAWs2AgBBACEHDAMLIAAgCEEAEBMMAQsgACgCACAGKAI0EBALQX8hBwsgBkFAayQAIAcLKwAgACgCQCgCpAFBAE4EQCAAQQYQDSAAQdkAEA0gACAAKAJALwGkARAUCwsTACAAIAEgAiADIARBAEEAEN0BC6YBAQF/IwBBEGsiAyQAIAMgAjcDCAJAIAAgAUGHASABQQAQESICQoCAgIBwg0KAgICA4ABRDQAgACACEDUEQCAAIAIgAUEBIANBCGoQNiICQv////9vViACQoCAgICwf4NCgICAgCBRcg0BIAAgAhAMIABBpcEAQQAQEkKAgICA4AAhAgwBCyAAIAIQDCAAIAEgACADQQhqEIoFIQILIANBEGokACACC6MBAgN/AX4gAEEQaiECIAEoAgAiBEEBaiEDAkAgACkCBCIFQoCAgIAIg1BFBEAgAiAEQQF0ai8BACIAQYD4A3FBgLADRyADIAWnQf////8HcU5yDQEgAiADQQF0ai8BACICQYD4A3FBgLgDRw0BIABBCnRBgPg/cSACQf8HcXJBgIAEaiEAIARBAmohAwwBCyACIARqLQAAIQALIAEgAzYCACAACxIAIAFB2AFOBEAgACABEIYFCwthACAAIAEgAkKAgICACHxC/////w9YBH4gAkL/////D4MFQoCAgIDAfiACub0iAkKAgICAwIGA/P8AfSACQv///////////wCDQoCAgICAgID4/wBWGwsgAyAEQQdyEJQBCzkAIABB/wBNBEAgAEEDdkH8////AXFBsP8BaigCACAAdkEBcQ8LIABBfnFBjMAARiAAEJYGQQBHcgs1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QfD//wFxayIYJAAgBSEVIAYEQCAHIAQgByAIGyIIQQAgCEEAShsiCGsiCUEAIAcgCU8bIREDQAJAIAggCkYEQANAIAggEUYNAiAYIAhBA3RqQoCAgIAwNwMAIAhBAWohCAwACwALIAUgCkEDdCIJaikDACIBQiCIp0F1TwRAIAGnIhUgFSgCAEEBajYCAAsgCSAYaiABNwMAIBFBAWohESAKQQFqIQoMAQsLIA4gBzYCVCAYIRULIA4gFTYCQCAOIBggBkEDdGoiETYCREEAIQgDQCAIIBdHBEAgESAIQQN0akKAgICAMDcDACAIQQFqIQgMAQsLIBIoAhQhBiAOIBYoAowBNgIwIBYgDkEwaiIUNgKMASASKAIwIQkgESAXQQN0aiIIIRcLQQAMAQtBAQshBwNAAkACQAJAAkAgB0UEQCAEQQN0ISMgA0KAgICAcIMhOyARQQhqIRogEUEQaiEbIBFBGGohHCAVQQhqIR0gFUEQaiEeIBVBGGohHyAUQRhqISQgAkIgiKciIEF+cSElIANCIIinISYgBK0hOiADpyEhIA5BMGohJyAOQegAaiEiIAghBwJAA0ACQCAGQQFqIQxCgICAgDAhNQJ/AkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJ/AkACQAJAAkACQAJAAkACQAJAAkACQAJA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AAsgCCABNwMQIAhBGGohBwzeAQsgCCAIQQhrIgYpAwA3AwAgCEEQaykDACIBQiCIp0F1TwRAIAGnIgcgBygCAEEBajYCAAsgBiABNwMAIAhBCGohBwzdAQsgCCAIQQhrIgYpAwAiATcDACAGIAhBEGsiBikDADcDACABQiCIp0F1TwRAIAGnIgcgBygCAEEBajYCAAsgBiABNwMAIAhBCGohBwzcAQsgCCAIQQhrIgYpAwAiATcDACAIQRBrIgcpAwAhNSAHIAhBGGsiBykDADcDACAGIDU3AwAgAUIgiKdBdU8EQCABpyIGIAYoAgBBAWo2AgALIAcgATcDACAIQQhqIQcM2wELIAggCEEIayIGKQMAIgE3AwAgCEEQayIHKQMAITUgByAIQRhrIgcpAwA3AwAgBiA1NwMAIAcgCEEgayIGKQMANwMAIAFCIIinQXVPBEAgAaciByAHKAIAQQFqNgIACyAGIAE3AwAgCEEIaiEHDNoBCyAIQRBrIgYpAwAhASAGIAhBGGsiBikDADcDACAGIAE3AwAM0wELIAhBGGsiBikDACEBIAYgCEEQayIGKQMANwMAIAhBCGsiBykDACE1IAcgATcDACAGIDU3AwAM0gELIAhBIGsiBikDACEBIAYgCEEYayIGKQMANwMAIAhBEGsiBykDACE1IAcgCEEIayIHKQMANwMAIAYgNTcDACAHIAE3AwAM0QELIAhBKGsiBikDACEBIAYgCEEgayIGKQMANwMAIAhBGGsiBykDACE1IAcgCEEQayIHKQMANwMAIAYgNTcDACAHIAhBCGsiBikDADcDACAGIAE3AwAM0AELIAhBCGsiBikDACEBIAYgCEEQayIGKQMANwMAIAhBGGsiBykDACE1IAcgATcDACAGIDU3AwAMzwELIAhBEGsiBikDACEBIAYgCEEYayIGKQMANwMAIAhBIGsiBykDACE1IAcgATcDACAGIDU3AwAMzgELIAhBEGsiBikDACEBIAYgCEEYayIGKQMANwMAIAhBIGsiBykDACE1IAcgCEEoayIHKQMANwMAIAYgNTcDACAHIAE3AwAMzQELIAhBCGsiBikDACEBIAYgCEEQayIGKQMANwMAIAYgATcDAAzMAQsgCEEgayIGKQMAIQEgBiAIQRBrIgYpAwA3AwAgCEEIayIHKQMAITUgByAIQRhr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xH4iAUIAUg0EIA0gD3JBAE4NBSALQoCAgIDA/v8DNwMADA0LIAtCgICAgMB+IA+3IA23o70iAUKAgICAwIGA/P8AfSABQv///////////wCDQoCAgICAgID4/wBWGzcDAAwMCyANQQBKIA9BAE5xRQRAIAsCfiAPtyANtxCZBCI8vSIBAn8gPJlEAAAAAAAA4EFjBEAgPKoMAQtBgICAgHgLIgq3vVEEQCAKrQwBC0KAgICAwH4gAUKAgICAwIGA/P8AfSABQv///////////wCDQoCAgICAgID4/wBWGws3AwAMDAsgDyANcK0hAQwCCyAPtyE8IAsCfgJ8IA23Ij29QoCAgICAgID4/wCDQoCAgICAgID4/wBRBEBEAAAAAAAA+H8gPJlEAAAAAAAA8D9hDQEaCyA8ID0QowMLIjy9IgECfyA8mUQAAAAAAADgQWMEQCA8qgwBC0GAgICAeAsiCre9UQRAIAqtDAELQoCAgIDAfiABQoCAgIDAgYD8/wB9IAFC////////////AINCgICAgICAgPj/AFYbCzcDAAwKCyA1xCABxH0hAQsgAUKAgICACHxC/////w9WDQEgASE2CyA2Qv////8PgwwBC0KAgICAwH4gAbm9IgFCgICAgMCBgPz/AH0gAUL///////////8Ag0KAgICAgICA+P8AVhsLNwMADAULIBBBdkcgD0F2R3FFBEAgCSAKIAsgNSABIAkoAhAoAqwCESMADQcMBQsgCSAHQQhqIDUQbQ0FIAkgByABEG0NBgsCQAJAAkACQCAKQZsBaw4GAAECBAUDBAsgBysDCCAHKwMAogwFCyAHKwMIIAcrAwCjDAQLIAcrAwggBysDABCZBAwDCyAHKwMIITwgBysDACI9vUKAgICAgICA+P8Ag0KAgICAgICA+P8AUQRARAAAAAAAAPh/IDyZRAAAAAAAAPA/YQ0DGgsgPCA9EKMDDAILEAEACyAHKwMIIAcrAwChCyE8IAtCgICAgMB+IDy9IgFCgICAgMCBgPz/AH0gAUL///////////8Ag0KAgICAgICA+P8AVhs3AwALQQAMAgsgCSABEAwLIAtCgICAgDA3AwAgDUKAgICAMDcDAEF/CyEwIAdBEGokACAwDUsgCEEIayEHDEcLIAhBBGsoAgAiB0UgB0EHa0FuSXINQCAIIQcgCSAIQY4BEOEBRQ1GDEoLAkACfCAIQQhrIgcpAwAiAUIgiKciCkUEQEQAAAAAAAAAgCABpyIGRQ0BGkQAAAAAAADgQSAGQYCAgIB4Rg0BGiAHQgAgAX1C/////w+DNwMADEILIApBB2tBbUsNASABQoCAgIDA/v8Dfb8LITwgB0KAgICAwH4gPL0iAUKAgICAwIGA/P8AfSABQv///////////wCDQoCAgICAgID4/wBWGzcDAAxACyAIIQcgCSAIQY0BEOEBRQ1FDEkLIAhBCGsiBykDACIBQv////8PViABQv////8HUXJFBEAgByABQgF8Qv////8PgzcDAAw/CyAIIQcgCSAIQZABEOEBRQ1EDEgLIAhBCGsiBykDACIBQv////8PViABQoCAgIAIUXJFBEAgByABQgF9Qv////8PgzcDAAw+CyAIIQcgCSAIQY8BEOEBRQ1DDEcLIAkgCEEIayIHKQMAEGUiAUKAgICAcINCgICAgOAAUQRAIAdCgICAgDA3AwAMRwsgByABNwMAIAFCIIinQXVPBEAgAaciByAHKAIAQQFqNgIACyAIIAE3AwAgCSAIQQhqIgcgCkECaxDhAUUNQgxGCyAGQQJqIQwgESAGLQABQQN0aiIHKQMAIgFC/////w9WIAFC/////wdRckUEQCAHIAFCAXxC/////w+DNwMADDwLIAFCIIinQXVPBEAgAaciCiAKKAIAQQFqNgIACyAOIAE3A2AgCSAiQZABEOEBDUUgCSAHIA4pA2AQHQw7CyAGQQJqIQwgESAGLQABQQN0aiIHKQMAIgFC/////w9WIAFCgICAgAhRckUEQCAHIAFCAX1C/////w+DNwMADDsLIAFCIIinQXVPBEAgAaciCiAKKAIAQQFqNgIACyAOIAE3A2AgCSAiQY8BEOEBDUQgCSAHIA4pA2AQHQw6CyAIQQhrIgcpAwAiAUL/////D1gEQCAHIAFC/////w+FNwMADDoLIAghByMAQRBrIgokAAJ/AkACQCAJIAhBCGsiCykDABBlIgFCgICAgHCDIjVCgICAgOAAUQ0AIDVCgICAgOB+UQRAIAkgC0GWASABIAkoAhAoAqgCER8ADQEMAgsgCSAKQQxqIAEQlQENACALIAo1AgxC/////w+FNwMADAELIAtCgICAgDA3AwBBfwwBC0EACyExIApBEGok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//8AgyIGQoCAgICAgMD//wBWIAZCgICAgICAwP//AFEbDQAgACAChCAFIAaEhFAEQEEADwsgASADg0IAWQRAQX8hBCAAIAJUIAEgA1MgASADURsNASAAIAKFIAEgA4WEQgBSDwtBfyEEIAAgAlYgASADVSABIANRGw0AIAAgAoUgASADhYRCAFIhBAsgBAuhAgEFfwNAAkACQAJAAkACfyACIAdMIgkgBCAGTHJFBEAgASAHQQJ0aigCACIIIAMgBkECdGooAgAiCUkEQCAIDAILIAggCUcNAyAGQQFqIQYgB0EBaiEHIAghCQwECyAJDQEgASAHQQJ0aigCAAshCSAHQQFqIQcMAgsgBCAGTA0CIAMgBkECdGooAgAhCQsgBkEBaiEGCwJ/AkACQAJAAkAgBQ4DAwABAgsgBiAHcUEBcQwDCyAGIAdzQQFxDAILEAEACyAGIAdyQQFxCyAAKAIAIghBAXFGDQEgACgCBCAITARAIAAgCEEBahDRAgRAQX8PCyAAKAIAIQgLIAAgCEEBajYCACAAKAIIIAhBAnRqIAk2AgAMAQsLIAAQmAZBAAvmAQAgAAJ/IAEoAggiAEH+////B04EQEEAIAJBAXENARpB/////wcgAEH+////B0cNARogASgCBEH/////B2oMAQtBACAAQQBMDQAaIABBH00EQEEAIAEoAhAgASgCDEECdGpBBGsoAgBBICAAa3YiAGsgACABKAIEGwwBCyACQQFxRQRAQf////8HIAEoAgRFDQEaQYCAgIB4IABBIEcNARogASgCECABKAIMQQJ0akEEaygCABpBgICAgHgMAQtBACABKAIQIAEoAgwiAiACQQV0IABrEHEiAGsgACABKAIEGws2AgALEgAgACABIAIgAyAEQZMDELEDCw4AIABBACABQRByELoBC58BAgR/An4gAzUCACEJA0AgAiAFRkUEQCAAIAVBAnQiB2ogBq0gASAHajUCACAJfnwiCj4CACAFQQFqIQUgCkIgiKchBgwBCwsgACACQQJ0IgdqIAY2AgBBASAEIARBAU0bIQRBASEFA0AgBCAFRkUEQCAAIAVBAnQiBmoiCCAHaiAIIAEgAiADIAZqKAIAEL0ENgIAIAVBAWohBQwBCwsLWgEFfyADQQAgA0EAShshBgNAIAUgBkcEQCAAIAVBAnQiA2ogASAD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gICAgOAAUg0AIAEhAgwBCwJAIAKnIgUpAgQiCkL/////B4NQDQAgAaciAykCBCELAkAgAygCAEEBRyAKIAuFQoCAgIAIg0IAUnINACADIAAoAhAoAgwRBQAgBSkCBCIKpyIEQf////8HcSIHIAMpAgQiC6ciBkH/////B3EiCGogBEEfdnQgBkEfdiIJQRFzakkNACAFQRBqIQYgA0EQaiEEIAkEQCAEIAhBAXRqIAYgB0EBdBAeGiADIAMpAgQiCiAFKQIEfEL/////B4MgCkKAgICAeIOENwIEDAILIAQgCGogBiAHEB4aIAMgAykCBCIKIAUpAgR8Qv////8HgyILIApCgICAgHiDhDcCBCAEIAunakEAOgAADAELAn4CQAJAIAunQf////8HcSAKp0H/////B3FqIgdBgICAgARPBEAgAEHkyABBABA6DAELIAAgByAKIAuEpyIGQR92EOkBIggNAQtCgICAgOAADAELIAhBEGohBAJAIAZBAE4EQCAEIANBEGogAygCBEH/////B3EQHiIEIAMoAgRB/////wdxaiAFQRBqIAUoAgRB/////wdxEB4aIAQgB2pBADoAAAwBCyAEIAMgAygCBEH/////B3EQkwUgBCADKAIEQQF0aiAFIAUoAgRB/////wdxEJMFCyAIrUKAgICAkH+ECyEKIAAgARAMDAELIAEhCgsgACACEAwgCgsPACAAIAFCgICAgDAQggILCwAgAEGfCUEAEBILjgIBA38jAEEQayIFJAAgBSAAOQMIIAUgAUEBayIHNgIAIAZBgAFB9t8AIAUQSBogAyAGLQAAQS1GNgIAIAQgBi0AAToAACABQQJOBEAgBEEBaiAGQQNqIAcQHhoLIAEgBGpBADoAACACIQkgASAGaiABQQFKakECaiEBA0AgASICQQFqIQEgAiwAACIDEI8GDQALQQEhBAJAAkACQCADQf8BcUEraw4DAQIAAgtBACEECyABLAAAIQMgASECC0EAIQEgA0EwayIDQQlNBEADQCABQQpsIANrIQEgAiwAASEIIAJBAWohAiAIQTBrIgNBCkkNAAsLIAlBACABayABIAQbQQFqNgIAIAVBEGokAAuADAIHfwV+IwBBoANrIgUkAAJAIAG9IgxCgICAgICAgPj/AINCgICAgICAgPj/AFEEQCAMQv//////////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QCAHqwwBC0EACyIDNgIcIAcgA7hhDQMMAQsgAwRAQX8hAyAAIAIQlgEiAkKAgICAcINCgICAgOAAUQ0EIAAgBEEcaiACQQEQvgJFDQMMBAsgACAEQRxqIAIQdQRAIAAgAhAMDAILQX8hAyAAIAIQlgEiAkKAgICAcINCgICAgOAAUQ0DIAAgBEEEaiACQQAQvgINAyAEKAIEIAQoAhxGDQILIABBiscAQQAQRAtBfyEDDAELIAEgBCgCHDYCAEEAIQMLIARBIGokACADC0ABAX4gABDiASICQoCAgIBwg0KAgICA4ABSBEAgAqdBBGogARCcAkUEQCACDwsgACACEAwgABBwC0KAgICA4AALMgEBfyMAQdAAayICJAAgAiAAIAJBEGogARCBATYCACAAQbzpACACEMMCIAJB0ABqJAALoAECAX8BfiMAQRBrIgUkACAFIAQ2AgxBfyEEIAAgASAFQQxqENMBRQRAIAMoAgAiAEF8cSABIAIgAygCBCAAQQNxQQJ0QYS3AWooAgARGwAhBiADEOAFIAUoAgwiACAAKAIAQf////8DcTYCACADQoCAgIAwIAYgBkKAgICAcINCgICAgOAAUSIAGzcDAEF/QQAgABshBAsgBUEQaiQAIAQLFQECfiAAIAEQ6AEhAyAAIAEQDCADCw0AIAAgASACQQIQyAIL1QEBA38jAEEQayIFJABBfyEDAkAgACgCFA0AAkACQCABQYCAgIAETgRAIAAoAgBB5MgAQQAQOgwBCyABIAAoAgxBA2xBAm0iBCABIARKGyEBIAAoAhAiBCACQYACSHJFBEAgACABEOADIQMMAwsgACgCACAAKAIEIAEgBHQgBGtBEWogBUEMahCnASICDQELIAAQ9wIMAQsgBSgCDCEDIAAgAjYCBCAAQf////8DIAMgACgCEHYgAWoiACAAQf////8DThs2AgxBACEDCyAFQRBqJAAgAwsqAQF/IAAoAhAiA0EQaiABIAIgAygCCBEBACIBIAJFckUEQCAAEHALIAELgQECAn8BfgJAIAEpAgQiBEL//////////79/VgRAIAEoAgwhAAwBCyAAKAI0IARCIIinIAAoAiRBAWtxQQJ0aiECIAAoAjghAwNAIAMgAigCACIAQQJ0aigCACICIAFGDQEgAkEMaiECIAANAAtBxocBQajsAEH/FEH4DhAAAAsgAAupBwIJfwF+AkACQAJAAn8gAkECTARAIAIgASkCBCIMQj6Ip0YEQCAAIAEQxgIiBEHXAUoNBSABIAEoAgBBAWs2AgAgBA8LIAAoAjQgACgCJEEBayABIAIQ6wVB/////wNxIgdxIgpBAnRqIQMgDKdB/////wdxIQUDQCACIAMoAgAiBEUNAhoCQCAAKAI4IARBAnRqKAIAIgMpAgQiDKdB/////wdxIAVHIAxCPoinIAJHciAMQiCIp0H/////A3EgB0dyDQAgAyABIAUQ6gUNACAEQdgBSA0EIAMgAygCAEEBajYCAAwECyADQQxqIQMMAAsACyACQQNHIQdBAwshBQJAIAAoAjwNAEEAIQQgAEEQaiILIAAoAjhB0wEgACgCLEEDbEECbSICIAJB0wFMGyICQQJ0IAAoAggRAQAiCEUNASAAKAIsIgkhAyAJRQRAIAtBECAAKAIAEQMAIgZFBEAgCyAIIAAoAgQRAAAMAwsgBkKAgICAgICAgEA3AgQgBkEBNgIAIAZBADYADCAIIAY2AgAgACAAKAIoQQFqNgIoQQEhAwsgACADNgI8IAAgCDYCOCAAIAI2AiwgCSACIAIgCUkbIQQgAkEBayEGA0AgAyAERg0BIAAoAjggA0ECdGpBASADQQFqIgJBAXRBAXIgAyAGRhs2AgAgAiEDDAALAAsCQCABBEAgASkCBCIMQv//////////P1gEQCABIAwgBa1CPoaENwIEDAILIABBEGogDKciAkEfdSACQf////8HcSACQR92dGpBEWogACgCABEDACICRQRAQQAhBAwECyACQQE2AgAgAiACKQIEQv////93gyABKQIEQoCAgIAIg4QiDDcCBCACIAxCgICAgHiDIAEpAgRC/////weDhDcCBCACQRBqIAFBEGogASgCBCIDQf////8HcSADQR92dCADQX9zQR92ahAeGiAAIAEQkAQgAiEBDAELIABBEGpBECAAKAIAEQMAIgFFBEBBAA8LIAFCgYCAgICAgICAfzcCAAsgACAAKAI4IAAoAjwiBEECdGoiAigCAEEBdjYCPCACIAE2AgAgASAENgIMIAEgATUCBCAHrUIghoQgBa1CPoaENwIEIAAgACgCKEEBajYCKCAFQQNGDQIgASAAKAI0IApBAnRqIgEoAgA2AgwgASAENgIAIAAo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QQAQEgwBCwJAIARFIAJBAExyRQRAIAAgBSAHQgAgAUF/EPMCDQIMAQsgASEICyACQQAgAkEAShutIQlCACEBA0AgASAJUgRAIAMgAadBA3RqKQMAIgdCIIinQXVPBEAgB6ciAiACKAIAQQFqNgIACyABIAh8IQogAUIBfCEBIAAgBSAKIAcQe0EATg0BDAILCyAAIAVBMCAGQoCAgIAIfCIIQv////8PWAR+IAZC/////w+DBUKAgICAwH4gBrm9IgFCgICAgMCBgPz/AH0gAUL///////////8Ag0KAgICAgICA+P8AVhsLEDlBAEgNACAAIAUQDCAGQv////8PgyAIQv////8PWA0BGkKAgICAwH4gBrm9IgFCgICAgMCBgPz/AH0gAUL///////////8Ag0KAgICAgICA+P8AVhsMAQsgACAFEAxCgICAgOAACyELIAxBEGokACALCxUBAn4gACABEIcFIQMgACABEAwgAwv5DgIKfgR/IwBBEGsiECQAIBAgAjcDCAJAAkACfgJAAkACQAJAAkACQAJAAkACQAJAQQcgAkIgiKciDiAOQQdrQW5JGyIOQQdqDg8EAwMDAwMABQUFAwMDAwECCwJAAkACQAJAIAKnIg4vAQYiD0EEaw4DAQACAwtCgICAgDAhByAAIAIQNCICQoCAgIBwg0KAgICA4ABRDQsgACACEO4DIgJCgICAgHCDQoCAgIDgAFENCyABKAIoIAIQhAEhDgwOC0KAgICAMCEHIAAgAhCWASICQoCAgIBwg0KAgICA4ABRDQogASgCKCACEIQBIQ4MDQsgASgCKCAOKQMgEI0BIQ4gACACEAwMDAsgD0EhRg0HQoCAgIAwIQYgACABKQMIQQEgEEEIahDxAyIEQoCAgIDwAINCgICAgOAAUQ0GIAAgBBAnBEAgAEHJ3wBBABASDAcLIANCIIinQXVPBEAgA6ciDiAOKAIAQQFqNgIACyABKQMYIgRCIIinQXVPBEAgBKciDiAOKAIAQQFqNgIACwJAAkACQAJAIAAgAyAEELYCIghCgICAgHCDQoCAgIDgAFEEQEKAgICAMCEHDAELIAEpAxgiBEKAgICAcINCgICAgJB/UQRAIASnKAIEQf////8HcUUNAwsgCEIgiKdBdU8EQCAIpyIOIA4oAgBBAWo2AgALIABB65YBIAhB7JYB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BEAgAiAANgIAC0EBC4cDAgN+BH8CQCABKAIIIgZB/v///wdOBEBBASEHIAJBAXENAUL///////////8AIQMgBkH+////B0cNASABNAIEQv///////////wB8IQMMAQsgBkEATARADAELIAZBP00EQCABKAIQIAEoAgwiCEECdGoiCUEEaygCACECQgAgBkEgTQR+IAJBICAGa3atBSAIQQJPBH4gCUEIazUCAAVCAAsgAq1CIIaEQcAAIAZrrYgLIgN9IAMgASgCBBshAwwBCyACQQFxRQRAIAEoAgRFBEBC////////////ACEDQQEhBwwCC0KAgICAgICAgIB/IQNBASEHIAZBwABHDQEgASgCECABKAIMIgFBAnRqIgJBBGs1AgBCIIYhBCABQQJPBH4gAkEIazUCAAVCAAsgBIRCgICAgICAgICAf1IhBwwBC0IAIAEoAhAiCCABKAIMIgIgAkEFdCAGayIGEHGtIAggAiAGQSBqEHGtQiCGhCIDfSADIAEoAgQbIQMLIAAgAzcDACAHC60CAgJ/An4jAEEgayICJAACQCAAKAIIQf////8HRgRAQoCAgICAgID8/wAhBAwBCyAAKAIAIQMgAkIANwIYIAJCgICAgICAgICAfzcCECACIAM2AgwgAkEMaiIDIAAQSRoCfiACKAIUIgBB/f///wdMBEAgA0E1QcgEELoBGiACKAIUIQALQoCAgICAgID4/wAgAEH+////B0YNABpCACAAQYCAgIB4Rg0AGiACKAIcIQMCfiACKAIYQQJGBEAgAykCAAwBCyADNQIAQiCGCyEEIABBgnhMBEAgBEGOeCAAa62IIQRCAAwBCyAEQguIQv////////8HgyEEIABB/gdqrUI0hgshBSAEIAWEIAI1AhBCP4aEIQQgAkEMahAZCyABIAQ3AwAgAkEgaiQACw0AIAAgASACQQIQsAMLIwACQAJAAkAgAg4CAAECCyAAIAFyDwsgACABcw8LIAAgAXEL4QgBEX8gAigCBCAFcyIFIAEoAgQiBnMhDQJAIAEgAhDyASIIIA1Fcg0AIAEoAghB/f///wdKDQAgACAEQQdxQQJGEIABQQAPCyAFIAYgCEEASCIGGyEFIAEgAiAGGyEKAkACQAJAIAIgASAGGyIIKAIMIgcEQCAKKAIMIgsNAQsgCCgCCCIBQf7///8H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CyAAIAsQDCAAIAgQDCAAIAoQDCAAIAEQDAwBCyABIQ0LIAVBIGokACANC0sBAn8gACABRwRAIAAoAhAiAgRAIAAoAgAiAygCACACQQAgAygCBBEBABoLIAAgASkCADcCACAAIAEoAhA2AhAgACABKQIINwIICwv0AQIDfgF/AkAgAykDACIEQoCAgIBwWgRAIAMpAwgiBUL/////b1YNAQsgABAiQoCAgIDgAA8LQoCAgIDgACEGIABCgICAgCBBLBBHIgFCgICAgHCDQoCAgIDgAFIEfiAAQRgQJCICRQRAIAAgARAMQoCAgIDgAA8LIASnIgMgAygCAEEBajYCACACIAQ3AwAgBaciByAHKAIAQQFqNgIAIAIgBTcDCCAAIAQQNSEAIAJBADoAESACIAA6ABAgAUKAgICAcFoEQCABpyIAIAI2AiAgACAALQAFQe8BcSADLQAFQRBxcjoABQsgAQVCgICAgOAACwsbACAAEBkgAEIANwIQIABCADcCCCAAQgA3AgALCQAgASACEPgFCxMAIABBEGogASACIAAoAggRAQALqAECAX8CfiAAvSIEQv///////////wCDQoGAgICAgID4/wBaBEAgAb1C////////////AINCgYCAgICAgPj/AFQPC0F/IQICQCAAIAFjDQAgAb0iA0L///////////8Ag0KAgICAgICA+P8AVg0AQQEhAiAAIAFkDQBBACECIABEAAAAAAAAAABiDQAgBEIAUwRAIANCP4enQX9zDwsgA0I/iKchAgsgAgvKBQIFfwN+IwBBMGsiAiQAIAIgATcDECACQQA2AgwgAiAANgIIIAIgAykDACIKNwMYAkACQCAKQoCAgIBwgyILQoCAgIAwUgRAQoCAgIDgACEJIAAgChBVDQELQoCAgIDgACEJIAAgARCKASIFQQBIDQACQCAFQQJJDQAgAaciAy8BBkEVayIEQf//A3FBC08NAiACIARBAnRB/P8PcSIEQZz1AWooAgA2AiBBASADLwEGQcqeAWotAAAiBnQhCCADKAIkIQcgC0KAgICAMFIEQCAAIAVBAnQQJCIERQ0CQQAhAwNAIAMgBUZFBEAgBCADQQJ0aiADNgIAIANBAWohAwwBCwsgAiAINgIoIAIgBzYCJCAEIAVBBEHLACACQQhqENcBAkACQAJAAkAgAigCDA4CAAEDCyAAIAUgBnQiAxAkIgYNAQsgACgCECIAQRBqIAQgACgCBBEAAAwECyAGIAcgAxAeIQZBACEDAkACQAJAAkACQCAIQQFrDggAAQkCCQkJAwkLA0AgAyAFRg0EIAMgB2ogBiAEIANBAnRqKAIAai0AADoAACADQQFqIQMMAAsACwNAIAMgBUYNAyAHIANBAXRqIAYgBCADQQJ0aigCAEEBdGovAQA7AQAgA0EBaiEDDAALAAsDQCADIAVGDQIgByADQQJ0IghqIAYgBCAIaigCAEECdGooAgA2AgAgA0EBaiEDDAALAAsDQCADIAVGDQEgByADQQN0aiAGIAQgA0ECdGooAgBBA3RqKQMANwMAIANBAWohAwwACwALIAAoAhAiA0EQaiAGIAMoAgQRAAALIAAoAhAiAEEQaiAEIAAoAgQRAAAMAQsgByAFIAggBEHI9QFqKAIAIAJBCGoQ1wEgAigCDA0BCyABQiCIp0F1TwRAIAGnIgAgACgCAEEBajYCAAsgASEJCyACQTBqJAAgCQ8LEAEAC+cCAQF+IAAgARCKASICQQBIBEBCgICAgOAADwsCQCACRQ0AAkACQAJAAkACQCABpyIALwEGQcqeAWotAAAOBAABAgMECyAAKAIkIgAgAmohAgNAIAAgAkEBayICTw0FIAAtAAAhAyAAIAItAAA6AAAgAiADOgAAIABBAWohAAwACwALIAAoAiQiACACQQF0aiECA0AgACACQQJrIgJPDQQgAC8BACEDIAAgAi8BADsBACACIAM7AQAgAEECaiEADAALAAsgACgCJCIAIAJBAnRqIQIDQCAAIAJBBGsiAk8NAyAAKAIAIQMgACACKAIANgIAIAIgAzYCACAAQQRqIQAMAAsACyAAKAIkIgAgAkEDdGohAgNAIAAgAkEIayICTw0CIAApAwAhBCAAIAIpAwA3AwAgAiAENwMAIABBCGohAAwACwALEAEACyABQiCIp0F1TwRAIAGnIgAgACgCAEEBajYCAAsgAQtRAgF/AX5CgICAgOAAIQQgACABIAIQayIDBH4gAygCICIDKAIMKAIgLQAEBEAgAkUEQEIADwsgABBfQoCAgIDgAA8LIAM1AhAFQoCAgIDgAAsLNwAgACABIAIQayIARQRAQoCAgIDgAA8LIAAoAiAoAgwiACAAKAIAQQFqNgIAIACtQoCAgIBwhAsMACAAKAIQIAEQ5wML2gEBAn4CQAJAIAJFBEAgAUKAgICAcIMhBSAAQS8QKSEEDAELAn4gAUKAgICAcIMiBUKAgICAMFIgAykDACIEQoCAgIBwg0KAgICAgH9SckUEQCAAQbmMASAAIAAoAhAgBKcQxgIQKUGrjAEQsgEMAQsgACAEECULIgRCgICAgHCDQoCAgIDgAFENAQsgBUKAgICAMFENACAAIAFBBRBeIgFCgICAgHCDQoCAgIDgAFIEQCAAIAEgBBC9ASAAIAFBMCAEpykCBEL/////B4NBABAVGgsgASEECyAEC0YBAX8CQCAAKAIIIAJqIgMgACgCDEoEQCAAIAMgARDEAg0BCwNAIAJBAEwEQEEADwsgAkEBayECIAAgARCHAUUNAAsLQX8LlQECBX8BfiABKQIEIginQf////8HcSIDRQRAIAIPCyAAKAIEQf////8HcSEHAn8gCEKAgICACINQRQRAIAEvARAMAQsgAS0AEAshBSADQQFrIQYgByADayEEAkADQCACIARKDQEgACAFIAIQoAEiA0EASCADIARKcg0BIAAgASADQQFqIgJBASAGELwDDQALIAMPC0F/C6cBAgN/AX4CQAJAIAAgARD2AyIDQQBIDQAgA0UNAUGbHiECIAAgACABQe4AIAFBABARIgVCgICAgHCDIgFCgICAgCBRIAFCgICAgDBRcgR/QZseBSABQoCAgIDgAFENASAAIAUQNCIBQoCAgIBwg0KAgICA4ABRDQFBACECIAGnQecAQQAQoAEhBCAAIAEQDCAEQQBODQJB2ssAC0EAEBILQX8hAgsgAguhAQIDfwF+AkACQCAAKQIEIgRCgICAgAiDUA0AIABBEGohAiAEp0H/////B3EhA0EAIQADQCAAIANODQECQCACIABBAXRqLwEAIgFBgPADcUGAsANHBEAgACEBDAELIAFB/7cDSw0DIABBAWoiASADTg0DIAIgAUEBdGovAQBBgEBrQf//A3FBgPgDSQ0DCyABQQFqIQAMAAsAC0F/IQALIAALVQEBfwJAAkACQCABQiCIp0EBag4DAAECAQsgAaciAi8BBkEGRw0AIAIpAyAiAUKAgICAcINCgICAgBBRDQELIABBlMAAQQAQEkKAgICA4AAhAQsgAQsQAEHOkQEgAEELEJICQQBHC4kB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ADcCECAAQRBqIAIgACgCBBEAAAwBCyACQoCAgIAwNwMoIAJCgICAgDA3AyAgAkEBNgIECyABIAEoAgxBAWs2AgwLC4YBACAAIAEgBEEiahBaIgJFBEBCgICAgOAADwsgACACIAMpAwAiAUIAIAFCIIinQQdrQW5PGyABIAFCgICAgMCBgPz/AHxC////////////AINQGxDyAiIARQRAQoCAgIAwDwsgACkDKCIBQiCIp0F1TwRAIAGnIgAgACgCAEEBajYCAAsgAQu7BQIDfgd/IwBBEGsiCyQAQoCAgIDgACEHAkAgACABIARBImoQWiICRQ0AIAIoAgBFIAMpAwAiBUIAIAVCIIinQQdrQW5PGyAFIAVCgICAgMCBgPz/AHxC////////////AINQGyIFQv////9vVnJFBEAgABAiDAELQoCAgIAwIQYgBEEBcUUEQCADKQMIIQYLAkAgACACIAUQ8gIiAwRAIAAgAykDKBAMDAELIABBMBAkIgNFDQEgAyACNgIIIANCATcDAAJAIAIoAgAEQCADIAWnIgQoAhg2AgwgBCADNgIYDAELIAVCIIinQXVJDQAgBaciBCAEKAIAQQFqNgIACyADIAU3AyAgAigCECIJIAIoAhQiBEEBayAFENkDcUEDdGoiCCgCACIKIANBGGoiDDYCBCADIAg2AhwgAyAKNgIYIAggDDYCACACKAIEIgggA0EQaiIKNgIEIAMgAkEEaiIMNgIUIAMgCDYCECACIAo2AgQgAiACKAIMQQFqIgg2AgwgCCACKAIYSQ0AIAAgCUEEIARBAXQgBEEBRhsiAEEDdCALQQxqEKcBIghFDQAgCygCDEEDdiAAaiEEQQAhAANAIAAgBEZFBEAgCCAAQQN0aiIJIAk2AgQgCSAJNgIAIABBAWohAAwBCwsgBEEBayEKIAJBCGohAANAIAwgACgCACIARwRAIABBDGsoAgBFBEAgCCAAKQMQENkDIApxQQN0aiIJKAIAIg0gAEEIaiIONgIEIAAgCTYCDCAAIA02AgggCSAONgIACyAAQQRqIQAMAQsLIAIgBDYCFCACIAg2AhAgAiAEQQF0NgIYCyAGQiCIp0F1TwRAIAanIgAgACgCAEEBajYCAAsgAyAGNwMoIAFCIIinQXVPBEAgAaciACAAKAIAQQFqNgIACyABIQcLIAtBEGok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AmoQI0UNDAJAIAMoAqgGIgVBAEgEQCADKALYBiEFDAELIAMgBTYC2AYLIAEgAygCxAYgBRAuIANBwAZqQfIBEA4gAygCrAZBAXMhBQwSCyADQX82AogDIANCw/aAgOABNwOAAyADQZwGaiAEIANBgANqECNFDQsCQCADKAKoBiICQQBIBEAgAygC2AYhAgwBCyADIAI2AtgGCyABIAMoAsQGIAIQLiADQcAGaiICIAMtAKwGEA4gAiADKAK8BhAbDBALIANBfzYCuAMgA0LZuP2CcDcDsAMgA0GcBmogBCADQbADahAjRQ0KIAMoAqgGIgJBAE4EQCADIAI2AtgGCyADQo6AgIBwNwOgAyADKAKsBiIFQQFqIQYCQCADQZwGaiADKAKkBiICIANBoANqECMEfyADKAKoBiICQQBOBEAgAyACNgLYBgsgAyADKAKwBjYClANBfyEEIANBfzYCmAMgAyAFQQFrNgKQAyADQZwGaiADKAKkBiICIANBkANqECNFDQEgAygCpAYhAiADKAKoBgVBfwshBCAGIQULIAEgAygCxAYgAygC2AYQLiADQcAGaiAFIAMoArAGEFkgBEEASA0TIAMgBDYC2AYMEwsgBi8AASICQf8BSw0JIANCjoCAgHA3AswEIAMgAjYCyAQgA0KRpYKAkAs3A8AEAkAgA0GcBmoiBiAEIANBwARqECNFBEAgA0KOgICAcDcDsAQgAyACNgKsBCADQdkANgKoBCADQo+hgoCQAjcDoAQgBiAEIANBoARqECNFDQELAkAgAygCqAYiBUEASARAIAMoAtgGIQUMAQsgAyAFNgLYBgsgASADKALEBiAFEC4gA0HABmoiBEGUAUGTASADKAKsBkF9cUGQAUYbEA4gBCACQf8BcRAODA8LIANCjoCAgHA3ApQEIAMgAjYCkAQgA0KRgICAkAs3A4gEIANChICAgOATNwOABCADQZwGaiAEIANBgARqECMEQAJAIAMoAqgGIgVBAEgEQCADKALYBiEFDAELIAMgBTYC2AYLIAEgAygCxAYgBRAuAkAgAygCvAZBL0YEQCADQcAGakHBARAODAELIANBwAZqIgRBBBAOIAQgAygCvAYQGwsgA0HABmoiBEGVARAOIAQgAkH/AXEQDgwPCyADQo6AgIBwNwL0AyADIAI2AvADIANCkYCAgJALNwPoAyADQoGAgIDgEzcD4AMgA0GcBmogBCADQeADahAjBEACQCADKAKoBiIFQQBIBEAgAygC2AYhBQwBCyADIAU2AtgGCyABIAMoAsQGIAUQLiADQcAGaiIEIAMoArQGEM4DIARBlQEQDiAEIAJB/wFxEA4MDwsgA0KOgICAcDcD2AMgAyACNgLUAyADQdkANgLQAyADQp6BgICQAjcDyAMgA0LYtvmCcDcDwAMgA0GcBmogBCADQcADahAjBEACQCADKAKoBiIFQQBIBEAgAygC2AYhBQwBCyADIAU2AtgGCyABIAMoAsQGIAUQLiADQcAGaiIEIAMoAqwGIAMoArAGEFkgBEGVARAOIAQgAkH/AXEQDgwPCyABIAMoAsQGIAMoAtgGEC4gA0HABmpB2AAgAhBZIAQhAgwSCyAGLwABIQIgASADKALEBiADKALYBhAuIANBwAZqIAUgAhBZIAQhAgwRCyADIAYvAAEiAjYC5AQgA0F/NgLoBCADIAVBAWs2AuAEIANBnAZqIAQgA0HgBGoQIwRAAkAgAygCqAYiBEEASARAIAMoAtgGIQQMAQsgAyAENgLYBgsgASADKALEBiAEEC4gA0HABmogBUEBaiACEFkMDQsgASADKALEBiADKALYBhAuIANBwAZqIAUgAhBZIAQhAgwQCyABIAogCCAEIANB2AZqEKUCIQQMBgsgASgC1AIhCyABKALMAiEGQQAhCUEAIQgDQAJAIAkgC0gEQEEDIQogBigCACICQeoAa0EDTwRAIAJB7QFHDQJBASEKCwJAIAEoAqQCIAYoAgxBFGxqKAIMIAYoAggiBWsiBEGAf0ggBCAKQf8AakpyRQRAIAZBATYCBCACQe0BRgRAQewBIQIgBkHsATYCAAwCCyAGIAJBgAFqIgI2AgAMAQsgAkHsAEcgBEGAgAJqQf//A0tyDQIgBkLtgYCAIDcCAEECIQpB7QEhAgsgAygCwAYgBWpBAWsgAjoAACAGKAIEIgIgAygCwAYgBWpqIgQgBCAKaiADKALEBiAFIApqIAJqaxCrASADIAMoAsQGIAprNgLEBkEAIQQgASgCrAIiAkEAIAJBAEobIQcgASgCpAIhAgNAIAQgB0YEQCABKALUAiELIAYhByAJIQQDQAJAIAsgBEEBaiIETARAQQAhAiABKALgAiIEQQAgBEEAShshBANAIAIgBEYNAiAFIAEoAtgCIAJBA3RqIgcoAgAiDEkEQCAHIAwgCms2AgALIAJBAWohAgwACwALIAciAkEQaiEHIAIoAhgiDCAFTA0BIAIgDCAKazYCGAwBCwsgCEEBaiEIDAMLIAUgAigCDCILSARAIAIgCyAKazYCDAsgAkEUaiECIARBAWohBAwACwALIAEoAswCIQIgCARAQQAhBQNAIAUgC0gEQCABKAKkAiACKAIMQRRsaigCDCACKAIIIgRrIQYCQAJAAkACQCACKAIEQQFrDgQAAQMCAwsgAygCwAYgBGogBjoAACABKALUAiELDAILIAMoAsAGIARqIAY7AAAMAQsgAygCwAYgBGogBjYAAAsgAkEQaiECIAVBAWohBQwBCwsgASgCzAIhAgsgACgCECIEQRBqIAIgBCgCBBEAACABQQA2AswCIAAoAhAiAkEQaiABKAKkAiACKAIEEQAAIAFBADYCpAIgASgC2AIhAgJAIAEtAG5BAnEEQCACIQUMAQtBACEFIAJFDQAgASgC8AIhByABKAIAIAFB9AJqIggQgwJBACECQQAhCgNAIAEoAtgCIQUgAiABKALgAk4NAQJAIAUgAkEDdGoiBigCBCIEQQBIIAQgB0ZyDQAgBigCACIGIAprIgVBAEgNAAJAIAQgB2siB0EBaiIKQQRLIAVBMktyRQRAIAggCiAFQQVsakEBakH/AXEQDgwBCyAIQQAQDiAIIAUQsQUgCCAHQQF0IAdBH3VzELEFCyAGIQogBCEHCyACQQFqIQIMAAsACyAAKAIQIgJBEGogBSACKAIEEQAAIAFBADYC2AIgDRCJASANIAMpAtAGNwIQIA0gAykCyAY3AgggDSADKQLABjcCACABQQE2AqACIAEoAowCBEAgABBwDCALIAEoAoACIQcgAyABKAKEAiIENgKcBiADIAAgBEEBdBAkIgY2AqQGIAZFDR9BACECIARBACAEQQBKGyEFA0AgAiAFRwRAIAYgAkEBdGpB//8DOwEAIAJBAWohAgwBCwsgA0EANgKsBiADIAAgBEECdBAkIgI2AqgGAkAgAkUNACADQgA3ArAGIANBADYCoAYgACADQZwGakEAQQBBAEF/ELABDQADQCADKAKsBiECAkACQAJAIAMoArAGIgRBAEoEQCADIARBAWsiBDYCsAYgByACIARBAnRq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IAIgCTYCSCACIAo2AkQgAkHBAEHQACAHQQxIGzYCTCACIAdBC2pBDG9BAWo2AkAgAkGQAmogA2pBwAAgA2tB5vQAIAJBQGsQSCADaiEDCyAAIAJBkAJqIAMQ6gEhEQsgAkHQAmokACARCzcCA38BfiMAQRBrIgAkACAAEI0GIAApAwAhAyAAKAIIIQIgAEEQaiQAIAJB6AdtrCADQugHfnwLhwEBAXwgACADKQMAEKgBIgJFBEBCgICAgOAADwsgAhAHIQQgACACEDEgBL0iAQJ/IASZRAAAAAAAAOBBYwRAIASqDAELQYCAgIB4CyIAt71RBEAgAK0PC0KAgICAwH4gAUKAgICAwIGA/P8AfSABQv///////////wCDQoCAgICAgID4/wBWGwvxAQIGfwF+IABBCBAkIgRFBEBBfw8LIARCATcCACACpyEGIAJCIIinQXVJIQgDQAJAAkAgA0ECRg0AIAAgACkDMCADQS5yEEciCUKAgICAcINCgICAgOAAUgRAIABBEBAkIgUNAiAAIAkQDAtBfyEHIANFDQAgACABKQMAEAwLIAAoAhAgBBD/BCAHDwsgBCAEKAIAQQFqNgIAIAUgBDYCCCAIRQRAIAYgBigCAEEBajYCAAsgBSACNwMAIAlCgICAgHBaBEAgCacgBTYCIAsgACAJQS9BARCYAyABIANBA3RqIAk3AwAgA0EBaiEDDAALAAt/AQV/IABBEGohBCABQQxqIQUgASgCECECA0AgAiAFRkUEQCACKAIEIQYgACACKQMQECEgACACKQMYECEgACACKQMgECEgACACKQMoECEgBCACIAAoAgQRAAAgBiECDAELCyABKAIIIgMEQCAAIAMQzgELIAQgASAAKAIEEQAAC+EDAgR/An4jAEFAaiICJAAgAiAAIAEQsQIiBjcDOAJAAkAgASgCIARAIAZCgICAgHCDQoCAgIDgAFENASAAIAEpAyhCgICAgDBBASACQThqEBwhBiAAIAIpAzgQDCAAIAYQDAwCCyACIAEoAmRBCGsiAykDADcDKCADQoCAgIAwNwMAIAAgBhAMQQAhAyAAIAApA1AgACACQShqQQAQ3gEhBiAAIAIpAygQDCAGQoCAgIBwg0KAgICA4ABRDQADQAJAIANBAkcEQCACQRBqIANBA3RqIAAgACkDMCADQTFqEEciBzcD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aiIGKAIEIAJBABARIgNCgICAgHCDQoCAgIDgAFENAyAGKAIEIQYCfyAEBEAgACABIAYgAxA5DAELIAAgASAGIANBBxAVC0EASA0DCyAFQQFqIQUMAQsLIAAgCCALEFtBACEJDAELIAAgCCALEFsLIAdBMGokACAJC6YBAQF+AkACQAJ+IARBBHEEQEEpIQIgACABEEoMAQtBKCECIAAgARAgCyIBQoCAgIBwg0KAgICA4ABRDQAgACACEIYBIgVCgICAgHCDQoCAgIDgAFENACAAQRAQJCICBEAgAkEANgIMIAIgBEEDcTYCCCACIAE3AwAgBUKAgICAcFQNAiAFpyACNgIgDAILIAAgBRAMCyAAIAEQDEKAgICA4AAPCyAFC8QBAQR/IAGnIgUgAjYCICAFQgA3AiQCQCACKAI8IgZFDQACQCAAIAZBAnQQXCIIRQ0AIAUgCDYCJEEAIQUDQCAFIAIoAjxODQIgAigCJCAFQQN0aiIHLwECIQYCQCAHLQAAIgdBAXEEQCAAIAQgBiAHQQF2QQFxEP8DIgYNAQwDCyADIAZBAnRqKAIAIgYgBigCAEEBajYCAAsgCCAFQQJ0aiAGNgIAIAVBAWohBQwACwALIAAgARAMQoCAgIDgACEBCyABC4IBAQJ+IAAgARApIQICQCABQQBIDQAgACgCECgCOCABQQJ0aigCACkCBCIDp0GAgICAeEYgA0KAgICA8P///z+DUCADQv//////////v39WcSADQoCAgICAgICAQINCgICAgICAgICAf1FyRXINACAAQa/wACACQa3wABCyASECCyACC2QBAn8CQAJAIAFCgICAgHBUDQAgARCUBQ0AQX8hAyAAIAIQMCIERQ0BIAAgBBDEBSECIAAgBBAQIAJCgICAgHCDQoCAgIDgAFENASAAIAFBNyACQQEQFUEASA0BC0EAIQMLIAMLNQACQCACRSABQoCAgIBwVHINACABEJQFDQAgACABQTcgACACEClBARAVQQBODQBBfw8LQQALDAAgACABQbYVELUBC2gCAX8BfgJAIAAgAUHqACABQQAQESIEQoCAgIBwg0KAgICA4ABSBEAgACAEECchAyAAIAFBwQAgAUEAEBEiAUKAgICAcINCgICAgOAAUg0BC0EAIQNCgICAgOAAIQELIAIgAzYCACABCxQBAn4gACABECAhAyAAIAEQDCADC/sBAgR/AX4gACgCyAEiBSgCECIEQTBqIQYgBCAEKAIYIAFxQX9zQQJ0aigCACEEAkADQCAERQ0BIAEgBiAEQQFrIgdBA3RqIgQoAgRHBEAgBCgCAEH///8fcSEEDAELCyAFKAIUIAdBA3RqIQUCQCADQQFGDQAgBTUCBEIghkKAgICAwABRBEAgACACEAwgACAEKAIEENEBQX8PCyAELQADQQhxDQAgACACEAwgAEGAgAEgARDnAQ8LIAAgBSACEB1BAA8LIAAgACkDwAEiCCABIAIgCAJ/IAAoAhAoAowBIgMEQEGAgAYgAygCKEEBcQ0BGgtBgIACCxDQAQuKAQEBfwJAIAJCgICAgHCDQoCAgICQf1EgA0KAgICAcINCgICAgJB/UXFFBEAgAEGl5gBBABASDAELIAAgAUESEF4iAUKAgICAcINCgICAgOAAUQ0AIAGnIgQgAz4CJCAEIAI+AiAgACABQdYAQgBBAhAVGiABDwsgACADEAwgACACEAxCgICAgOAACw0AIAAgAUHMjQEQgQMLZwEBfwJAIAFBAE4EQCAAKAIQIgIoAiwgAU0NASACKAI4IAFBAnRqKAIAIgEgASgCAEEBajYCACAAIAFBBBDmAw8LQYaJAUGo7ABB1RdBycAAEAAAC0GQzgBBqOwAQdYXQcnAABAAAAuxAgEEfwJAAkACQAJAIAJCgICAgHBUDQAgAqciAy8BBhDgAUUNACADKAIoIgRFDQAgBCgCECIDQTBqIQUgAyADKAIYQX9zQQJ0QdR5cmooAgAhAwNAIANFDQMgBSADQQFrIgNBA3RqIgYoAgRBygFHBEAgBigCAEH///8fcSEDDAELCyABQoCAgIBwVA0AIAQoAhQgA0EDdGopAwAiAkKAgICAcINCgICAgIB/UQ0BCyAAECIMAgsgACACEIgCIQMgAacoAhAiAEEwaiEEIAAgAyAAKAIYcUF/c0ECdGooAgAhAANAIABFBEBBAA8LIAQgAEEDdGoiBUEIayEAIAMgBUEEaygCAEYEQCAAQQBHDwUgACgCAEH///8fcSEADAELAAsACyAAQZ3kAEEAEBILQX8LRAEBfyAAQeQBaiECIABB4AFqIQADfyAAIAIoAgAiAkYEQEEADwsgASACQQRrKAIARgR/IAJBCGsFIAJBBGohAgwBCwsLiQECA38BfgJAIAAoAhAoAowBIgJFDQADQCABQQBMBEADQCACKQMIIgRCgICAgHBUDQMgBKciAS8BBhDgAUUNAyABKAIgIgEvABEiA0GAwABxRQRAIANBgAhxRQ0EIAAgASgCQBAWDwsgAigCACICDQAMAwsACyABQQFrIQEgAigCACICDQALC0EACykBAX8gAkIgiKdBdU8EQCACpyIDIAMoAgBBAWo2AgALIAAgASACEIUEC/QBAwF+An8BfANAAkBBfyEEAkACQAJAQQcgAkIgiKciBSAFQQdrQW5JGw4IAAAAAAICAwECCyACxCEDQQAhBAwCC0EAIQQgAkKAgICAwIGA/P8AfCICQv///////////wCDQoCAgICAgID4/wBWDQFCgICAgICAgICAfyEDIAK/IgZEAAAAAAAA4MNjDQFC////////////ACEDIAZEAAAAAAAA4ENkDQEgBplEAAAAAAAA4ENjBEAgBrAhAwwCC0KAgICAgICAgIB/IQMMAQsgACACEJYBIgJCgICAgHCDQoCAgIDgAFINAQsLIAEgAzcDACAEC+YBAgN/AXwDQAJAQX8hBAJAAkACQEEHIAJCIIinIgUgBUEHa0FuSRsOCAAAAAACAgMBAgsgAqchA0EAIQQMAgtBACEEIAJCgICAgMCBgPz/AHwiAkL///////////8Ag0KAgICAgICA+P8AVgRADAILQYCAgIB4IQMgAr8iBkQAAAAAAADgwWMNAUH/////ByEDIAZEAADA////30FkDQEgBplEAAAAAAAA4EFjBEAgBqohAwwCC0GAgICAeCEDDAELIAAgAhCWASICQoCAgIBwg0KAgICA4ABSDQELCyABIAM2AgAgBAttAAJAAkACQAJAAkAgAkEEdkEDcUEBaw4DAAECAwsgASgCACICBEAgACACrUKAgICAcIQQIQsgASgCBCIBRQ0DIAAgAa1CgICAgHCEECEPCyAAIAEoAgAQ5QEPCyABEOAFDwsgACABKQMAECELC/UBAQl/QX8hAiABIAFBAWtxRQRAIABBEGoiCCABQQJ0IgMgACgCABEDACIFBH8gBUEAIAMQLCEGIAFB/////wNqQf////8DcSEJIAAoAjQhBwNAIAQgACgCJE9FBEAgByAEQQJ0aigCACECA0AgAgRAIAAoAjggAkECdGooAgAiAygC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QQpPBH8gAEHBAGsgA00EQCAAQTdrDwsgAiAAQdcAayAAQeEAayABTxsFIAQLC6IDAQJ/IAAgASgCBBAQA0AgASgCECEDIAIgASgCFE5FBEAgACADIAJBA3RqKAIAEBAgAkEBaiECDAELCyAAKAIQIgJBEGogAyACKAIEEQAAQQAhAgNAAkAgASgCHCEDIAIgASgCIE4NACADIAJBFGxqIgMoAghFBEAgACgCECADKAIEEOUBCyAAIAMoAhAQECAAIAMoAgwQECACQQFqIQIMAQsLIAAoAhAiAkEQaiADIAIoAgQRAAAgACgCECICQRBqIAEoAiggAigCBBEAAEEAIQIDQCABKAI0IQMgAiABKAI4TkUEQCAAIAMgAkEMbGooAgQQECACQQFqIQIMAQsLIAAoAhAiAkEQaiADIAIoAgQRAAAgACgCECICQRBqIAEoAmQgAigCBBEAACAAIAEpA0AQDCAAIAEpA0gQDCAAIAEpA6gBEAwgACABKQOwARAMIAAgASkDiAEQDCAAIAEpA5ABEAwgACABKQOYARAMIAEoAggiAiABKAIMIgM2AgQgAyACNgIAIAFCADcCCCAAKAIQIgBBEGogASAAKAIEEQAAC9IDAgJ+An8jAEEgayIEJAACQCABQv///////////wCDIgNCgICAgICAwIA8fSADQoCAgICAgMD/wwB9VARAIAFCBIYgAEI8iIQhAyAAQv//////////D4MiAEKBgICAgICAgAhaBEAgA0KBgICAgICAgMAAfCECDAILIANCgICAgICAgIBAfSECIABCgICAgICAgIAIUg0BIAIgA0IBg3whAgwBCyAAUCADQoCAgICAgMD//wBUIANCgICAgICAwP//AFEbRQRAIAFCBIYgAEI8iIRC/////////wODQoCAgICAgID8/wCEIQIMAQtCgICAgICAgPj/ACECIANC////////v//DAFYNAEIAIQIgA0IwiKciBUGR9wBJDQAgBEEQaiAAIAFC////////P4NCgICAgICAwACEIgIgBUGB9wBrEGIgBCAAIAJBgfgAIAVrEI0CIAQpAwhCBIYgBCkDACIAQjyIhCECIAQpAxAgBCkDGIRCAFKtIABC//////////8Pg4QiAEKBgICAgICAgAhaBEAgAkIBfCECDAELIABCgICAgICAgIAIUg0AIAJCAYMgAnwhAgsgBEEgaiQAIAIgAUKAgICAgICAgIB/g4S/C6oPAgV/D34jAEHQAmsiBSQAIARC////////P4MhCiACQv///////z+DIQsgAiAEhUKAgICAgICAgIB/gyEMIARCMIinQf//AXEhCAJAAkAgAkIwiKdB//8BcSIJQf//AWtBgoB+TwRAIAhB//8Ba0GBgH5LDQELIAFQIAJC////////////AIMiDUKAgICAgIDA//8AVCANQoCAgICAgMD//wBRG0UEQCACQoCAgICAgCCEIQwMAgsgA1AgBEL///////////8AgyICQoCAgICAgMD//wBUIAJCgICAgICAwP//AFEbRQRAIARCgICAgICAIIQhDCADIQEMAgsgASANQoCAgICAgMD//wCFhFAEQCADIAJCgICAgICAwP//AIWEUARAQgAhAUKAgICAgIDg//8AIQwMAwsgDEKAgICAgIDA//8AhCEMQgAhAQwCCyADIAJCgICAgICAwP//AIWEUARAQgAhAQwCCyABIA2EUARAQoCAgICAgOD//wAgDCACIAOEUBshDEIAIQEMAgsgAiADhFAEQCAMQoCAgICAgMD//wCEIQxCACEBDAILIA1C////////P1gEQCAFQcACaiABIAsgASALIAtQIgYbeSAGQQZ0rXynIgZBD2sQYkEQIAZrIQYgBSkDyAIhCyAFKQPAAiEBCyACQv///////z9WDQAgBUGwAmogAyAKIAMgCiAKUCIHG3kgB0EGdK18pyIHQQ9rEGIgBiAHakEQayEGIAUpA7gCIQogBSkDsAIhAwsgBUGgAmogCkKAgICAgIDAAIQiEkIPhiADQjGIhCICQgBCgICAgLDmvIL1ACACfSIEQgAQYSAFQZACakIAIAUpA6gCfUIAIARCABBhIAVBgAJqIAUpA5gCQgGGIAUpA5ACQj+IhCIEQgAgAkIAEGEgBUHwAWogBEIAQgAgBSkDiAJ9QgAQYSAFQeABaiAFKQP4AUIBhiAFKQPwAUI/iIQiBEIAIAJCABBhIAVB0AFqIARCAEIAIAUpA+gBfUIAEGEgBUHAAWogBSkD2AFCAYYgBSkD0AFCP4iEIgRCACACQgAQYSAFQbABaiAEQgBCACAFKQPIAX1CABBhIAVBoAFqIAJCACAFKQO4AUIBhiAFKQOwAUI/iIRCAX0iAkIA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IAUgA0E5ShshAwJAIBdCB1cEQCADIAdBBHRqIQcMAQsgF0IcWARAIAJBMGogAxB4IAJBIGogHCAYQgBCgICAgICAwP0/ECsgAkEQaiACKQMwIAIpAzggAikDICIcIAIpAygiGBArIAIgAikDECACKQMYIBkgGxBvIAIpAwghGyACKQMAIRkMAQsgA0UgCHINACACQdAAaiAcIBhCAEKAgICAgICA/z8QKyACQUBrIAIpA1AgAikDWCAZIBsQbyACKQNIIRtBASEIIAIpA0AhGQsgF0IBfCEXQQEhCQsgACgCBCIDIAAoAmhHBH8gACADQQFqNgIEIAMtAAAFIAAQTwshAwwBCwsCfiAJRQRAIAApA3BCAFkEQAJAIAAgACgCBCIDQQFrNgIEIAAgA0ECazYCBCABRQ0AIAAgA0EDazYCBAsLIAJB4ABqIAS3RAAAAAAAAAAAohCpASACKQNgIRkgAikDaAwBCyAXQgdXBEAgFyEYA0AgB0EEdCEHIBhCAXwiGEIIUg0ACwsCQAJAAkAgA0FfcUHQAEYEQCAAEIEGIhhCgICAgICAgICAf1INAyAAKQNwQgBZDQEMAgtCACEYIAApA3BCAFMNAgsgACAAKAIEQQFrNgIEC0IAIRgLIAdFBEAgAkHwAGogBLdEAAAAAAAAAACiEKkBIAIpA3AhGSACKQN4DAELIBogFyABG0IChiAYfEIgfSIXQQAgD2utVQRAQcTUBEHEADYCACACQaABaiAEEHggAkGQAWogAikDoAEgAikDqAFCf0L///////+///8AECsgAkGAAWogAikDkAEgAikDmAFCf0L///////+///8AECsgAikDgAEhGSACKQOIAQwBCyAPQeIBa6wgF1cEQCAHQQBOBEADQCACQaADaiAZIBtCAEKAgICAgIDA/79/EG8gGSAbQoCAgICAgID/PxD1BSEAIAJBkANqIBkgGyACKQOgAyAZIABBAE4iABsgAikDqAMgGyAAGxBvIBdCAX0hFyACKQOYAyEbIAIpA5ADIRkgB0EBdCAAciIHQQBODQALCwJ+IBcgD6x9QiB8IhinIgBBACAAQQBKGyANIBggDa1TGyIAQfEATgRAIAJBgANqIAQQeCACKQOIAyEaIAIpA4ADIRxCAAwBCyACQeACakQAAAAAAADwP0GQASAAaxDVARCpASACQdAC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QQFqNgIEIAEtAAAMAQsgABBPC0Ewa0EKSQ0ACwsgACkDcEIAWQRAIAAgACgCBEEBazYCBAtCACAFfSAFIAMbIQUMAQtCgICAgICAgICAfyEFIAApA3BCAFMNACAAIAAoAgRBAWs2AgRCgICAgICAgICAfw8LIAULygYCBX8EfiMAQYABayIFJAACQAJAAkAgAyAEQgBCABDrAUUNAAJ/IARC////////P4MhCwJ/IARCMIinQf//AXEiBkH//wFHBEBBBCAGDQEaQQJBAyADIAuEUBsMAgsgAyALhFALCyEJIAJCMIinIghB//8BcSIHQf//AUYNACAJDQELIAVBEGogASACIAMgBBArIAUgBSkDECICIAUpAxgiASACIAEQ9AUgBSkDCCECIAUpAwAhBAwBCyABIAJC////////////AIMiCyADIARC////////////AIMiChDrAUEATARAIAEgCyADIAoQ6wEEQCABIQQMAgsgBUHwAGogASACQgBCABArIAUpA3ghAiAFKQNwIQQMAQsgBEIwiKdB//8BcSEGIAcEfiABBSAFQeAAaiABIAtCAEKAgICAgIDAu8AAECsgBSkDaCILQjCIp0H4AGshByAFKQNgCyEEIAZFBEAgBUHQAGogAyAKQgBCgICAgICAwLvAABArIAUpA1giCkIwiKdB+ABrIQYgBSkDUCEDCyAKQv///////z+DQoCAgICAgMAAhCEMIAtC////////P4NCgICAgICAwACEIQsgBiAHSARAA0ACfiALIAx9IAMgBFatfSIKQgBZBEAgCiAEIAN9IgSEUARAIAVBIGogASACQgBCABArIAUpAyghAiAFKQMgIQQMBQsgCkIBhiAEQj+IhAwBCyALQgGGIARCP4iECyELIARCAYYhBCAHQQFrIgcgBkoNAAsgBiEHCwJAIAsgDH0gAyAEVq19IgpCAFMEQCALIQoMAQsgCiAEIAN9IgSEQgBSDQAgBUEwaiABIAJCAEIAECsgBSkDOCECIAUpAzAhBAwBCyAKQv///////z9YBEADQCAEQj+IIQ0gB0EBayEHIARCAYYhBCANIApCAYaEIgpCgICAgICAwABUDQALCyAIQYCAAnEhBiAHQQBMBEAgBUFAayAEIApC////////P4MgB0H4AGogBnKtQjCGhEIAQoCAgICAgMDDPxAr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6e0AQdjsAEHUIUGzxAAQAAALCyAFQQxqEBkgBUEgaiQAQRALrwEBAn8jAEEgayIEJAAgACgCACEFIARBCGogA0EAEO0BIAAgASAEKAIIIgEgAUEfdSIBcyABayIBIAJBwAAgAUEBa2dBAXRrQQAgAUECTxtqQQhqIgJB4A8QrwMhASADKAIEBEAgBEIANwIYIARCgICAgICAgICAfzcCECAEIAU2AgwgBEEMaiIDQgEQMhogACADIAAgAkHgDxCIASABciEBIAMQGQsgBEEgaiQAIAELkAYCCH8BfiMAQfAAayIDJAAgACABRwRAIAAoAgAhBCADQgA3AmggA0KAgICAgICAgIB/NwJgIAMgBDYCXCADQdwAaiIFIAEQSRogA0IANwJUIANCgICAgICAgICAfzcCTCADIAQ2AkggAygCZCEGIANBADYCZCADQcgAaiIBQqrVqtUKEDIaIANBADYCUCAFIAEQrAIEQCADIAMoAmRBAWo2AmQgBkEBayEGCyADQcgAahAZIAJBAWpBAXYQuQQhBSADQgA3AlQgA0KAgICAgICAgIB/NwJMIAMgBDYCSCADQgA3AkAgA0KAgICAgICAgIB/NwI4IAMgBDYCNCADQdwAaiIBIAFCf0H/////A0EAEHoaIAVBACAFQQBKGyEJIAIgBWogAiAFQQF0bkEBaiIKQQF0akEgaiECQQAhAQNAIAEgCUZFBEAgA0HIAGoiByADQdwAaiIIQgEgAkEAEHoaIANBNGoiCyAHIAJBBhC1BCAHIAtCASACQQAQehogCCAIIAcgAkEAEIgBGiABQQFqIQEMAQsLIANCADcCLCADQoCAgICAgICAgH83AiQgAyAENgIgIANCADcCGCADQoCAgICAgICAgH83AhAgAyAENgIMIANBIGoiASADQdwAaiIEQgIgAkEAEHoaIAEgBCABIAJBABCIARogA0EMaiABIAEgAkEAEEAaIABCABAyGiAKrSEMA0AgDKdBAExFBEAgA0HIAGoiAUIBEDIaIANBNGoiBCAMQgGGQv7///8Pg0IBhBAyGiABIAEgBCACQQAQiAEaIAAgACABIAJBABC4ARogACAAIANBDGogAkEAEEAaIAxCAX0hDAwBCwsgACAAQgEgAkEAEHoaIAAgACADQSBqIgEgAkEAEEAaIAEQGSADQQxqEBkgA0E0ahAZ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C0KAgICAMCEFIAZCgICAgHCDQoCAgIAwUQ0DIAAgBkEBEJABGgwDC0KAgICAMCEJQoCAgIAwIQZCgICAgDAhBSAAIAQQICIHQoCAgIBwg0KAgICA4ABRDQILIAAgDUEIaiAHEC9BAEgNACANAn4gDSkDCCIEQoCAgIAIfEL/////D1gEQCAEQv////8PgwwBC0KAgICAwH4gBLm9IgVCgICAgMCBgPz/AH0gBUL///////////8Ag0KAgICAgICA+P8AVhsLIgg3AyAgACABQQEgDUEgahC/AyEFIAAgCBAMAkAgBUKAgICAcINCgICAgOAAUQ0AQgAhCCAEQgAgBEIAVRshDANAIAggDFENBCAAIAcgCBBsIgRCgICAgHCDQoCAgIDgAFENAQJAIA4EQCAEIQEMAQsgDSAENwMgIA0gCEL/////D4M3AyggACAKIAtBAiANQSBqEBwhASAAIAQQDCABQoCAgIBwg0KAgICA4ABRDQILIAAgBSAIIAEQeyERIAhCAXwhCCARQQBODQALCwwBC0KAgICAMCEFCyAAIAUQDEKAgICA4AAhBQsgACAHEAwgACAGEAwgACAJEAwgDUEwaiQAIAULDwAgACsDACABKwMAEMQECzkBAX5CgICAgMB+IAEpAwAiAkKAgICAwIGA/P8AfSACQv///////////wCDQoCAgICAgID4/wBWGwsRACAAKgIAuyABKgIAuxDEBAs7AQF+QoCAgIDAfiABKgIAu70iAkKAgICAwIGA/P8AfSACQv///////////wCDQoCAgICAgID4/wBWGwsZAQJ+IAEpAwAiAyAAKQMAIgRUIAMgBFZrCwwAIAAgASkDABCIBAsZAQJ+IAEpAwAiAyAAKQMAIgRTIAMgBFVrCwwAIAAgASkDABC/AgsXACABKAIAIgEgACgCACIASSAAIAFJaws9AQF+IAEoAgAiAEEATgRAIACtDwtCgICAgMB+IAC4vSICQoCAgIDAgYD8/wB9IAJCgICAgICAgPj/AFYbC9sFAwV/A34BfCMAQUBqIgUkAAJAAnwCQAJAAkACQAJAIAJBACABQoCAgIBwgyILQoCAgIAwUhsiAg4CAgABCwJAIAMpAwAiCUKAgICAcFQNACAJpyIELwEGQQpHDQAgBCkDICIKQiCIpyIEQQAgBEELakESSRsNACAAIAUg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IQULIAJBEGokACAFCx0AIAAgAUEAEGsiAEUEQEKAgICA4AAPCyAANQIoCz0BAX5CgICAgBAhASADKQMAIgRCgICAgHBaBH4gBKcvAQZBFWtB//8DcUEMSa1CgICAgBCEBUKAgICAEAsL7gMCBX4CfyMAQSBrIgokAEKAgICA4AAhBQJAIAAgASAEEFoiC0UNACALLQAEBEAgABBfDAELIAAgCkEYaiADKQMAQgAgCzQCACIGIAYQZg0AIAogBjcDECADKQMIIgdCgICAgHCDQoCAgIAwUgRAIAAgCkEQaiAHQgAgBiAGEGYNASAKKQMQIQYLIAopAxghCSAAIAFCgICAgDAQ/QEiB0KAgICAcIMiBUKAgICA4ABRBEAgByEFDAELIAYgCX0iBkIAIAZCAFUbIQgCQCAFQoCAgIAwUQRAIABCgICAgDAgCCAEEOUDIQUMAQsgCiAGQv////8HVwR+IAhC/////w+DBUKAgICAwH4gCLm9IgVCgICAgMCBgPz/AH0gBUL///////////8Ag0KAgICAgICA+P8AVhsLNwMIIAAgB0EBIApBCGoQowEhBSAAIAcQDCAAIAopAwgQDAsgBUKAgICAcINCgICAgOAAUQ0AAkAgACAFIAQQWiICRQ0AIAAgBSABEE0EQCAAQco0QQAQEgwBCwJAIAItAAQNACACNAIAIAhTBEAgAEHOwgBBABASDAILIAstAAQNACACKAIIIAsoAgggCadqIAinEB4aDAILIAAQXwsgACAFEAxCgICAgOAAIQULIApBIGokACAFC1EAIAAgASACEFoiAEUEQEKAgICA4AAPCyAAKAIAIgBBAE4EQCAArQ8LQoCAgIDAfiAAuL0iAUKAgICAwIGA/P8AfSABQoCAgICAgID4/wBWGwv/AwICfwF+AkACQAJAAkACQAJAIAFCgICAgHBaBEAgAaciAi8BBkErRg0BCyAEQQE2AgAMAQsgAigCICEGIARBATYCACAGDQELIABBsS1BABASDAELIAYoAgQhAgJAAkACQAJ/AkACQAJAAkAgBigCACIHQQFrDgQCAgcBAAsgBUUNAiAAIAYQyQQLQoCAgIAwIQEgBUEBaw4CAwQHCyADKQMAIgFCIIinQXVPBEAgAaciAyADKAIAQQFqNgIACwJAIAVBAkcNAEEBIQMgB0EBRw0AIAAgARCYASAGKAIEDAILIAIoAmQiAyAFrTcDACADQQhrIAE3AwAgAiADQQhqNgJkC0EAIQMgAgsiBSADNgIcIAZBAzYCACAAIAUQsQIhASAGQQE2AgAgBigCBCgCIARAIAAgBhDJBCABDwsgAUKAgICAEFoNBSACKAJkQQhrIgApAwAhCCAAQoCAgIAwNwMAIAFCAlEEQCAGQQI2AgAgBEECNgIAIAgPCyAEQQA2AgAgCA8LIAMpAwAiAUIgiKdBdUkNAyABpyIAIAAoAgBBAWo2AgAgAQ8LIAMpAwAiAUIgiKdBdU8EQCABpyICIAIoAgBBAWo2AgALIAAgARCYAQwBCyAAQY8tQQAQEgtCgICAgOAAIQELIAEPC0HW8QBBqOwAQaCUAUHEFBAAAAt3AQF+IAMpAwAiAUKAgICAcINCgICAgIB/UgRAIABBkcEAQQAQEkKAgICA4AAPC0KAgICAMCEEIAGnIgApAgRCgICAgICAgIBAg0KAgICAgICAgIB/UQR+IAAgACgCAEEBajYCACABQoCAgICQf4QFQoCAgIAwCws8AQF+QoCAgIDgACEBIAAgAykDABAlIgRCgICAgHCDQoCAgIDgAFIEfiAAIASnQQIQ5gMFQoCAgIDgAAsLVgIBfgF/IAAgARC7AyIBQoCAgIBwg0KAgICA4ABRBEAgAQ8LQoCAgIAwIQIgAaciAygCBEGAgICAeEcEQCAAIAAoAhAgAxDGAhApIQILIAAgARAMIAILCQAgACABELsDC1sBAX4jAEEQayICJAAgAiAAIAEQuwMiATcDCAJAIAFCgICAgHCDQoCAgIDgAFEEQCABIQQMAQsgAEKAgICAMEEBIAJBCGoQygQhBCAAIAEQDAsgAkEQaiQAIAQLLQBCgICAgOAAIAAgAykDACADKQMIQQAQiQIiAEEAR61CgICAgBCEIABBAEgbC6ABAQN+IAMpAwAiBSEEIAJBBE4EQCADKQMYIQQLIAVC/////29YBEAgABAiQoCAgIDgAA8LIAMpAxAhAUKAgICA4AAhBgJAIAAgAykDCBAwIgJFDQAgAUIgiKdBdU8EQCABpyIDIAMoAgBBAWo2AgALIAAgBSACIAEgBEEAENABIQMgACACEBAgA0EASA0AIANBAEetQoCAgIAQhCEGCyAGCyoAIAMpAwAiAUL/////b1gEQCAAECJCgICAgOAADwsgACABQQNBABCyAgtjAQF+IAMpAwAiBEL/////b1gEQCAAECJCgICAgOAADwtCgICAgOAAIQECQCAAIAMpAwgQMCICRQ0AIAAgBCACEG4hAyAAIAIQECADQQBIDQAgA0EAR61CgICAgBCEIQELIAELYwEDfgJAAkAgAykDACIBQv////9vWARAIAAQIgwBCyADKQMIIQUgASEEIAJBA04EQCADKQMQIQQLIAAgBRAwIgINAQtCgICAgOAADwsgACABIAIgBEEAEBEhBiAAIAIQECAGC2YBAX4gAykDACIEQv////9vWARAIAAQIkKAgICA4AAPC0KAgICA4AAhAQJAIAAgAykDCBAwIgJFDQAgACAEIAJBABDNASEDIAAgAhAQIANBAEgNACADQQBHrUKAgICAEIQhAQsgAQuaAQIBfwJ+IwBBEGsiBCQAIAMpAwghBSADKQMAIgYhAQJAAkACQAJAIAJBA0gNACADKQMQIgFCgICAgHBaBEAgAactAAVBEHENAQsgAEGdLEEAEBIMAQsgACAEQQxqIAUQ/QMiAg0BC0KAgICA4AAhAQwBCyAAIAYgASAEKAIMIgMgAhD+AiEBIAAgAiADEIYDCyAEQRBqJAAgAQt5AQF/IAFCgICAgHCDQoCAgIAwUgRAIABBnSxBABASQoCAgIDgAA8LAn4CQCACRQ0AIAMpAwAiAUKAgICAcINCgICAgDBRDQBCgICAgOAAIAAgARAlIgFCgICAgHCDQoCAgIDgAFENARogAachBAsgACAEQQMQ5gMLCxUAIAAgAykDACADIANBCGpBAhCIAws3ACMAQRBrIgIkACAAIAJBDGogAykDABB1IQAgAigCDCEDIAJBEGokAEKAgICA4AAgA2etIAAbC04AIwBBEGsiAiQAQoCAgIDgACEBAkAgACACQQxqIAMpAwAQdQ0AIAAgAkEIaiADKQMIEHUNACACKAIIIAIoAgxsrSEBCyACQRBqJAAgAQsGACAAtrsLfwAgACAAKQPQASIBQgyIIAGFIgFCGYYgAYUiAUIbiCABhSIBNwPQAUKAgICAwH4gAUKdurP7lJL9oiV+QgyIQoCAgICAgID4P4S/RAAAAAAAAPC/oL0iAUKAgICAwIGA/P8AfSABQv///////////wCDQoCAgICAgID4/wBWGwuIBAMFfAV/AX4jAEEQayIKJAAgCkIANwMIAkACQCACQQBMDQBCgICAgOAAIQEgACAKQQhqIAMpAwAQQg0BQQEhCyAKKwMIIQQgAkEBRwRAA0AgAiALRg0CIAAgCiADIAtBA3RqKQMAEEINAyALQQFqIQsgCisDACEFIwBBIGsiCSQAAkAgBJkiByAFmSIGIAe9IAa9VCIMGyIEvSIOQjSIpyINQf8PRg0AIAYgByAMGyEFAkAgDlANACAFvUI0iKciDEH/D0YNACAMIA1rQcEATgRAIAcgBqAhBAwCCwJ8IAxB/gtPBEAgBEQAAAAAAAAwFKIhBCAFRAAAAAAAADAUoiEFRAAAAAAAALBrDAELRAAAAAAAAPA/IA1BvARLDQAaIAREAAAAAAAAsGuiIQQgBUQAAAAAAACwa6IhBUQAAAAAAAAwFAshCCAJQRhqIAlBEGogBRCIBiAJQQhqIAkgBBCIBiAIIAkrAwAgCSsDEKAgCSsDCKAgCSsDGKCfoiEEDAELIAUhBAsgCUEgaiQADAALAAsgBJkhBAsgBL0iAQJ/IASZRAAAAAAAAOBBYwRAIASqDAELQYCAgIB4CyIAt71RBEAgAK0hAQwBC0KAgICAwH4gAUKAgICAwIGA/P8AfSABQv///////////wCDQoCAgICAgID4/wBWGyEBCyAKQRBqJAAgAQtOACAAIABEAAAAAAAA8L9EAAAAAAAA8D8gAEQAAAAAAAAAAGMbIAC9Qv///////////wCDQoCAgICAgID4/wBWGyAARAAAAAAAAAAAYRsLgwECAn4BfyAAvSIBQjSIp0H/D3EiA0H+B00EQCABQoCAgICAgICAgH+DIQIgA0H+B0cgAUKAgICAgICA8L9/UXJFBEAgAkKAgICAgICA+D+Evw8LIAK/DwsgA0GyCE0EfCABQj+HIAF8QgFBswggA2uthiIBQgGIfEIAIAF9g78FIAALC4IFAwJ8BX8CfiMAQRBrIgkkAAJ+QoCAgIDA/v/7/wBCgICAgMD+/3sgBBsgAkUNABoCfCADKQMAIgFC/////w9YBEBBASACIAJBAUwbIQogAachCEEBIQcDQCAHIApHBEAgCLcgAyAHQQN0aikDACIBQoCAgIAQWg0DGiAIIAGnIgsgCCALShsgCCALIAggC0gbIAQbIQggB0EBaiEHDAELCyAIrQwCC0KAgICA4AAgACAJQQhqIAEQQg0BGkEBIQcgCSsDCAshBSAHIAIgAiAHSBshAgNAIAIgB0cEQEKAgICA4AAgACAJIAMgB0EDdGopAwAQQg0CGgJAIAW9IgxC////////////AINCgICAgICAgPj/AFYNACAJKwMAIga9IgFC////////////AINCgICAgICAgPj/AFYEQCAGIQUMAQsgBUQAAAAAAAAAAGEgBkQAAAAAAAAAAGFxIQogBARAIAoEQCABIAyDvyEFDAILIAUgBSAGpSAGvUL///////////8Ag0KAgICAgICA+P8AVhsgBiAFvUL///////////8Ag0KAgICAgICA+P8AWBshBQwBCyAKBEAgASAMhL8hBQwBCyAFIAUgBqQgBr1C////////////AINCgICAgICAgPj/AFYbIAYgBb1C////////////AINCgICAgICAgPj/AFgbIQULIAdBAWohBwwBCwsgBb0iAQJ/IAWZRAAAAAAAAOBBYwRAIAWqDAELQYCAgIB4CyIAt71RBEAgAK0MAQtCgICAgMB+IAFCgICAgMCBgPz/AH0gAUL///////////8Ag0KAgICAgICA+P8AVhsLIQ0gCUEQaiQAIA0L4wECAX4CfyMAQRBrIgIkAAJAIAAgAUEpEFoiA0UEQCAEQQA2AgBCgICAgOAAIQEMAQtCgICAgDAhAQJAIAMpAwAiBkKAgICAcINCgICAgDBSBEAgAiADKAIMIgU2AgwgBSAGpyIHKAIEQf////8HcUkNASAAIAYQDCADQoCAgIAwNwMACyAEQQE2AgAMAQsgByACQQxqEMYBIQggAyACKAIMNgIMIARBADYCACAIQf//A00EQCAAIAhB//8DcRCUAyEBDAELIAAgByAFQQF0akEQakECEJIDIQELIAJBEGokACABC5EDAgN/An4jAEEgayICJABCgICAgOAAIQgCQCAAIAEQSiIBQoCAgIBwg0KAgICA4ABRDQAgACACQQhqIgVBBxA+GiAFQTwQPBogBSAEQQN0IgRB0OEBaigCACIGEIMBGiAEQdThAWooAgAiBARAIAVBIBA8GiAFIAQQgwEaIAVB2pEBEIMBGiAAIAMpAwAQSiIJQoCAgIBwg0KAgICA4ABRBEAgACABEAwgAigCCCgCECIAQRBqIAIoAgwgACgCBBEAAAwCCyAJpyIH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QdAAaiQAIAYLuAICA38DfiMAQSBrIgIkAEKAgICA4AAhBwJAAkACQCAAIAEQSiIBQoCAgIBwg0KAgICA4ABRDQAgACACIAMpAwAQ4wENACACKQMAIghCgICAgAhaBEAgAEHTGEEAEEQMAQsgCKciA0EBRg0BIAGnIgQpAgQiCaciBkH/////B3EiBUUNASAJQv////8HgyAIfkKAgICABFoEQCAAQeTIAEEAEDoMAQsgACACQQhqIAMgBWwgBkEfdhCZAw0AAkAgBUEBRwRAA0AgA0EATA0CIAJBCGogBEEAIAUQSxogA0EBayEDDAALAAsgAkEIagJ/IAQtAAdBgAFxBEAgBC8BEAwBCyAELQAQCyADEMsEGgsgACABEAwgAkEIahA3IQcMAgsgACABEAwMAQsgASEHCyACQSBqJAAgBwtYAQF+IAAgAykDABDkAUEAR61CgICAgBCEIQQgAUKAgICAcINCgICAgDBRBEAgBA8LIAAgAUEGEF4iAUKAgICAcINCgICAgOAAUgRAIAAgASAEEL0BCyABC8EBAgJ/An4jAEEQayIEJABCgICAgOAAIQYCQCAAIAEQSiIBQoCAgIBwg0KAgICA4ABRBEAgASEGDAELAkAgACAEQQxqIAMpAwAgAaciBSgCBEH/////B3EiAiACEFYNACAEIAI2AgggAykDCCIHQoCAgIBwg0KAgICAMFIEQCAAIARBCGogByACIAIQVg0BIAQoAgghAgsgACAFIAQoAgwiAyACIAMgAiADShsQjgEhBgsgACABEAwLIARBEGokACAGC8ABAgN/An4jAEEQayICJABCgICAgOAAIQcCQCAAIAEQSiIBQoCAgIBwg0KAgICA4ABRBEAgASEHDAELAkAgACACQQxqIAMpAwAgAaciBigCBEH/////B3EiBCAEEFYNACACIAQgAigCDCIFayIENgIIIAAgBiAFIAMpAwgiCEKAgICAcINCgICAgDBSBH8gACACQQhqIAggBEEAEFYNASACKAIIBSAECyAFahCOASEHCyAAIAEQDAsgAkEQaiQAIAcL0wECAn8CfiMAQRBrIgIkAEKAgICA4AAhBgJAIAAgARBKIgFCgICAgHCDQoCAgIDgAFEEQCABIQYMAQsCQCAAIAJBDGogAykDACABpyIFKAIEQf////8HcUEAEFYNACACIAUo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ABogAy0AAEEBcUUEQCADKAI4IQAgAygCNCIBBEAgASAANgI4CyAABEAgACABNgI0CyADQZjVBCgCAEYEQEGY1QQgADYCAAsgAygCYBDUASADENQBCyAMEAogDBDUAQsgB0HQAWokAAsJACAAIAEQzwQLLAAgACABEM8EIgFCgICAgHCDQoCAgIDgAFIEfiAAQQNBAiABpxsQKQUgAQsLkAECAXwBfiMAQRBrIgIkAAJ+IAMpAwAiAUIgiKciAwRAQoCAgIAQIANBC2pBEkkNARoLQoCAgIDgACAAIAJBCGogARBCDQAaIAIrAwgiBJlE////////P0NlIAS9QoCAgICAgID4/wCDQoCAgICAgID4/wBSIAScIARhcXGtQoCAgIAQhAshBSACQRBqJAAgBQsmAEKAgICA4AAgACADKQMAENkFIgBBAEetQoCAgIAQhCAAQQBIGwsvAQF+An4gAygCBCICBEBCgICAgBAiBCACQQtqQRJJDQEaCyAAIAQgAyADENIECwsvAQF+An4gAygCBCICBEBCgICAgBAiBCACQQtqQRJJDQEaCyAAIAQgAyADENMECwsJACAAIAEQngILowECAn4BfyMAQRBrIgIkAAJ+IAAgARCeAiIFQoCAgIBwg0KAgICA4ABRBEAgBQwBC0EKIQcCQAJAIAQNACADKQMAIgFCgICAgHCDQoCAgIAwUQ0AIAAgARDbBCIHQQBIDQELQoCAgIDgACAAIAJBCGogBRBtDQEaIAAgAisDCCAHQQBBABC6AgwBCyAAIAUQDEKAgICA4AALIQYgAkEQaiQAIAYLkAICAX4BfCMAQRBrIgIkAEKAgICA4AAhBAJAIAAgARCeAiIBQoCAgIBwg0KAgICA4ABRBEAgASEEDAELIAAgAiABEG0NAAJAAkAgAykDACIBQoCAgIBwg0KAgICAMFEEQCACKwMAIgW9IQEMAQsgACACQQxqIAEQswENAiACKwMAIgW9IgFCgICAgICAgPj/AINCgICAgICAgPj/AFINAQsgAEKAgICAwH4gAUKAgICAwIGA/P8AfSAFvUL///////////8Ag0KAgICAgICA+P8AVhsQNCEEDAELIAIoAgwiA0HlAGtBm39NBEAgAEHrIUEAEEQMAQsgACAFQQogA0EBELoCIQQLIAJBEGokACAEC80BAgF+AnwjAEEQ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IAQgCjcDACAEQQhqIQQgCEIBfCEIDAELCyAJIAx8IQgDQCAIIAtZDQIgBiAIp0EDdGopAwAiCUIgiKdBdU8EQCAJpyIDIAMoAgBBAWo2AgALIAQgCTcDACAEQQhqIQQgCEIBfCEIDAALAAsDQAJAIAkgClEEQCADQRBqIQNCACEJA0AgCSAPUQ0CIAMgCadBA3RqKQMAIhBCIIinQXVPBEAgEKciBiAGKAIAQQFqNgIACyAEIBA3AwAgBEEIaiEEIAlCAXwhCQwACwALIAAgDiAJIAQQVEF/Rg0DIARBCGohBCAJQgF8IQkMAQsLIAogDHwhCQNAIAkgC1kNASAAIA4gCSAEEFRBf0YNAiAEQQhqIQQgCUIBfCEJDAALAAsgAiAERgRAIAFCgICAgDAgACABQTAgDUKAgICACFoEfkKAgICAwH4gDbm9IghCgICAgMCBgPz/AH0gCEL///////////8Ag0KAgICAgICA+P8AVhsFIA0LEDlBAEgiAxshC0KAgICA4AAgASADGyEIIAIhBAwDC0GJFkGo7ABB4rkCQfHqABAAAAsgASELDAELQQAhAkKAgICAMCELCwNAIAIgBEZFBEAgBEKAgICAMDcDACAEQQhqIQQMAQsLIAAgCxAMIAAgDhAMIAVBMGokACAIC8gIAgl+A38jAEEwayIOJABCgICAgDAhBQJAAkAgACAOQSBqIAAgARAgIgoQLw0AIAAgDkEYaiADKQMAQgAgDikDICIGIAYQZg0AAkAgBARAAkACQAJAIAIOAgIAAQsgBiAOKQMYfSEHQQAhAgwBCyAAIA5BEGogAykDCEIAIAYgDikDGH1CABBmDQMgAkECayECIA4pAxAhBwsgBiACrXwgB31CgICAgICAgBBTDQEgAEH0yABBABASDAILIA4gBjcDECAGIQEgAykDCCINQoCAgIBwg0KAgICAMFIEfiAAIA5BEGogDUIAIAEgARBmDQIgDikDEAUgAQsgDikDGH0iAUIAIAFCAFUbIQdBACECCyAAIAogB0KAgICACHxC/////w9YBH4gB0L/////D4MFQoCAgIDAfiAHub0iAUKAgICAwIGA/P8AfSABQv///////////wCDQoCAgICAgID4/wBWGwsiBRCfAiEBIAAgBRAMAkAgAUKAgICAcINCgICAgOAAUQ0AIA4pAxgiDSAH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AiAAIAFBNyABQQAQESIBQoCAgIBwg0KAgICAMFEEfiAAQS8QKQUgAQtBnggQsgELC+EFAwN+B38DfAJAIAAgARBVDQAgACAAKQMwQQ4QRyIFQoCAgIBwg0KAgICA4ABRDQAgBaciCSABQoCAgIBwWgR/IAGnLQAFQRBxBUEACyAJLQAFQe8BcXI6AAUCQCAAQQEgAiACQQFMGyIKQQFrIghBA3RBGGoQJCIHRQ0AIAFCIIinQXVPBEAgAaciAiACKAIAQQFqNgIACyAHIAE3AwAgAykDACIEQiCIp0F1TwRAIASnIgIgAigCAEEBajYCAAsgByAINgIQIAcgBDcDCCAHQRhqIQtBACECA0AgAiAIRwRAIAMgAkEBaiIMQQN0aikDACIEQiCIp0F1TwRAIASnIg0gDSgCAEEBajYCAAsgCyACQQN0aiAENwMAIAwhAgwBCwsgCSAHNgIgAn8gAUL/////b1gEQCAAECJBfwwBCyAAQQAgAadBMBBDCyICQQBIDQACQCACRQ0AIAAgAUEwIAFBABARIgRCgICAgHCDQoCAgIDgAFENASAEQv////8PWARAIASnIgIgCGtBACACIApOG60hBgwBCyAEQiCIp0EHa0FtTQRAAkAgBEKAgICAwIGA/P8AfCIEQv///////////wCDQoCAgICAgID4/wBWDQAgBL+dIg8gCLciEGUNACAPIBChIQ4LIA69IgQCfyAOmUQAAAAAAADgQWMEQCAOqgwBC0GAgICAeAsiAre9UQRAIAKtIQYMAgtCgICAgMB+IARCgICAgMCBgPz/AH0gBEL///////////8Ag0KAgICAgICA+P8AVhshBgwBCyAAIAQQDAsgACAFQTAgBkEBEBUaIABBgJIBIAAgAUE3IAFBABARIgFCgICAgHCDIgRCgICAgJB/UgR+IARCgICAgOAAUQ0BIAAgARAMIABBLxApBSABC0HslgEQsgEiAUKAgICAcINCgICAgOAAUQ0AIAAgBUE3IAFBARAVGiAFDwsgACAFEAwLQoCAgIDgAAswACACQQBMBEAgACABQoCAgIAwQQBBABAcDwsgACABIAMpAwAgAkEBayADQQhqEBwLgwICAX4BfyMAQSBrIgIkAEKAgICA4AAhBQJAAkAgACABECAiAUKAgICAcINCgICAgOAAUQ0AIAAgAykDABAw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ARAgCzYAIAMpAwAiAUIgiKciAkF/RiAERSACQX5xQQJHcXJFBEAgABAiQoCAgIDgAA8LIAAgARDoAQuJAQEBfiADKQMAIgFC/////29WIAFCgICAgHCDQoCAgIAgUXJFBEAgAEHe0gBBABASQoCAgIDgAA8LAkAgACABEEEiAUKAgICAcINCgICAgOAAUgRAIAMpAwgiBEKAgICAcINCgICAgDBRDQEgACABIAQQ2gRFDQEgACABEAwLQoCAgIDgAA8LIAELnwIBA34gAUL/////b1gEQCAAECJCgICAgOAADwtCgICAgOAAIQUCfiAAIAFBNyABQQAQESIEQoCAgIBwg0KAgICAMFEEQCAAQZQBECkMAQsgACAEEDQLIgRCgICAgHCDIgZCgICAgOAAUgR+An4gACABQTMgAUEAEBEiAUKAgICAcINCgICAgDBRBEAgAEEvECkMAQsgACABEDQLIgFCgICAgHCDIgVCgICAgOAAUQRAIAAgBBAMQoCAgIDgAA8LAkAgBkKAgICAkH9RBEAgBKcoAgRB/////wdxRQ0BCyAFQoCAgICQf1EEQCABpygCBEH/////B3FFDQELIABB7JYBIARBpJIBELIBIQQLIAAgBCABELYCBUKAgICA4AALC5UCAgF+An8jAEEwayICJABCgICAgOAAIQECQCAAIAJBKGogAykDABCkAQ0AIAAQ4gEiBUKAgICAcINCgICAgOAAUQ0AIAAgAkEUaiADKQMIEK4CIgZFBEAgACAFEAwMAQsgACgC2AEgAhC7ASACQgEQMhogAiACKQMoIgGnIgdBARC5ARogAiACQn9B/////wNBARB6GiAFp0EEaiIDIAYgAhCyBBoCQCAERSABUHINACACQgEQMhogAiAHQQFrQQEQuQEaIAMgAhDyAUEASA0AIAJCARAyGiACIAdBARC5ARogAyADIAJB/////wNBARDuARoLIAIQGSAAIAYgAkEUahDmASAFEK8CIQELIAJBMGokACABCwkAIAAgARDcBAt0AgJ+AX8gACABENwEIgFCgICAgHCDQoCAgIDgAFEEQCABDwtBCiEGAn4CQCACRQ0AIAMpAwAiBEKAgICAcINCgICAgDBRDQAgACAEENsEIgZBAE4NAEKAgICA4AAMAQsgACABIAYQogULIQUgACABEAwgBQvOAQIBfwJ+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/////////wCDQoCAgICAgID4/wBWGwvvAQEDfiMAQRBrIgIkAEKAgICA4AAhBAJAIAAgACABECAiAUEBELsCIgVCgICAgHCDQoCAgIDgAFENACAFQiCIpyIDQQAgA0ELakESSRtFBEAgACACQQhqIAUQQkEASA0BQoCAgIAgIQQgAikDCEKAgICAgICA+P8Ag0KAgICAgICA+P8AUQ0BC0KAgICA4AAhBCAAIAFB48oAEIcCIgZCgICAgHCDQoCAgIDgAFENACAAIAYQNUUEQCAAQergAEEAEBIgACAGEAwMAQsgACAGIAFBAEEAEDYhBAsgACABEAwgACAFEAwgAkEQaiQAIAQLjAIDAXwBfgF/IwBBEGsiAiQAQoCAgIDgACEFAkAgACACQQhqIgYgARCmAg0AIAAgBiADKQMAEEINACACAn4gAisDCCIEvUKAgICAgICA+P8Ag0KAgICAgICA+P8AUgRAIASdIgREAAAAAACwnUCgIAQgBEQAAAAAAABZQGMbIAQgBEQAAAAAAAAAAGYbIQQLIAS9IgUCfyAEmUQAAAAAAADgQWMEQCAEqgwBC0GAgICAeAsiA7e9UQRAIAOtDAELQoCAgIDAfiAFQoCAgIDAgYD8/wB9IAVC////////////AINCgICAgICAgPj/AFYbCzcDACAAIAFBASACQREQ+AQhBQsgAkEQaiQAIAULigEDAX4BfAF/IwBBEGsiAiQAQoCAgIDgACEEAkAgACACQQhqIgYgARCmAg0AIAAgBiADKQMAEEINACAAIAEgAisDCCIFnUQAAAAAAAAAAKBEAAAAAAAA+H8gBUQAANzCCLI+Q2UbRAAAAAAAAPh/IAVEAADcwgiyPsNmGxD5BCEECyACQRBqJAAgBAvZAQIBfAF+IwBB0ABrIgIkAAJ+QoCAgIDgACAAIAEgAiAEQQ9xQQAQ1QMiAEEASA0AGkKAgICAwH4gAEUNABogBEGAAnEEQCACIAIrAwBEAAAAAACwncCgOQMACyACIARBBHZBD3FBA3RqKwMAIgW9IgECfyAFmUQAAAAAAADgQWMEQCAFqgwBC0GAgICAeAsiBLe9UQRAIAStDAELQoCAgIDAfiABQoCAgIDAgYD8/wB9IAFC////////////AINCgICAgICAgPj/AFYbCyEGIAJB0ABqJAAgBguHAQIBfAF+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IAMpAxAiBUIgiKdBdU8EQCAFpyIJIAkoAgBBAWo2AgALIAggBTcDCAJAIAQNACADKQMYIgVCIIinQXVJDQAgBaciCSAJKAIAQQFqNgIACyAIIAE3AxAgCCAFNwMAIAAgByAGQQMgCBAcIQUgACAIKQMAEAwgBEUEQCAAIAgpAwgQDAsgAygCBCEDIAAoAhAgAhDiAyAFQoCAgIBwg0KAgICA4ABRDQIgACAFEAwLDAALAAsgCEEgaiQAIAULVAAgACABIAJBImoQWiIARQRAQoCAgIDgAA8LIAAoAgwiAEEATgRAIACtDwtCgICAgMB+IAC4vSIBQoCAgIDAgYD8/wB9IAFCgICAgICAgPj/AFYbC1kBAX8gACABIARBImoQWiICRQRAQoCAgIDgAA8LIAJBBGohAyACKAIIIQQDfiADIARGBH5CgICAgDAFIARBEGshBSAEKAIEIQQgACgCECACIAUQgAUMAQsLC3UAIAAgASAEQSJqEFoiAkUEQEKAgICA4AAPCyAAIAIgAykDACIBQgAgAUIgiKdBB2tBbk8bIAEgAUKAgICAwIGA/P8AfEL///////////8Ag1AbEPICIgNFBEBCgICAgBAPCyAAKAIQIAIgAxCABUKBgICAEAthACAAIAEgBEEiahBaIgJFBEBCgICAgOAADwsgACACIAMpAwAiAUIAIAFCIIinQQdrQW5PGyABIAFCgICAgMCBgPz/AHxC////////////AINQGxDyAkEAR61CgICAgBCECwgAQoCAgIAwC0oAAkAgBSkDACIBQoCAgIBwVA0AIAGnIgIvAQZBLEcNACACKAIgIgJFDQAgAkEBOgARIAAgARAMIAVCgICAgCA3AwALQoCAgIAwC88BAQN+IwBBEGsiAiQAQoCAgIDgACEFAkACQAJ+QoCAgIAwIABCgICAgDAgACADEMAEIgRCgICAgHCDQoCAgIDgAFENABogAiAENwMIQoCAgIDgACAAQcMAQQBBAEEBIAJBCGoQhQEiBkKAgICAcINCgICAgOAAUQ0AGiAAEDMiAUKAgICAcINCgICAgOAAUg0BIAYLIQEgACAEEAwgACABEAwMAQsgACABQYQBIARBBxAVGiAAIAFBhQEgBkEHEBUaIAEhBQsgAkEQaiQAIAULswMCA38CfiMAQdAAayIGJABBfyEHAkAgACAGQcgAaiABQcMAEH4iCEUNACAGKQNIIgFCgICAgHCDQoCAgIAwUQRAIAgpAwAhASADQiCIp0F1TwRAIAOnIgcgBygCAEEBajYCAAsgACABIAIgAyAEIAUQ0AEhBwwBCyAAIAIQUiIJQoCAgIBwg0KAgICA4ABRBEAgACABEAwMAQsgCCkDACEKIAYgBDcDOCAGIAM3AzAgBiAJNwMoIAYgCjcDICAAIAEgCCkDCEEEIAZBIGoQNiEBIAAgCRAMIAFCgICAgHCDQoCAgIDgAFENAAJAAkAgACABECciBwRAIAAgBiAIKAIAIAIQQyICQQBIDQEgAkUNAwJAIAYoAgAiAkETcUUEQCAAIAYpAwggAxBNRQ0BDAQLIAJBEXFBEEcNAyAGNQIcQiCGQoCAgIAwUg0DCyAAIAYQRiAAQdEcQQAQEgwBCyAFQYCAAXFFBEBBACEHIAVBgIACcUUNAyAAKAIQKAKMASICRQ0DIAItAChBAXFFDQMLIABBwAlBABASC0F/IQcMAQsgACAGEEYLIAZB0ABqJAAgBwvTAgICfwJ+IwBBQGoiBCQAAkACQCAAIARBOGogAUHCABB+IgVFDQAgBCkDOCIBQoCAgIBwg0KAgICAMFEEQCAAIAUpAwAgAiADQQAQESEBDAILIAAgAhBSIgZCgICAgHCDQoCAgIDgAFEEQCAAIAEQDAwBCyAFKQMAIQcgBCADNwMwIAQgBjcDKCAEIAc3AyAgACABIAUpAwhBAyAEQSBqEDYhASAAIAYQDCABQoCAgIBwgyIDQoCAgIDgAFENACAAIAQgBSgCACACEEMiAkEASA0AIAJFDQECQAJAIAQoAgAiAkETcUUEQCAAIAQpAwggARBNRQ0BDAILIAJBEXFBEEcgA0KAgICAMFFyDQEgBDUCFEIghkKAgICAMFINAQsgACAEEEYgACABEAwgAEGoHUEAEBIMAQsgACAEEEYMAQtCgICAgOAAIQELIARBQGskACABC5gCAgR/An4jAEFAaiIDJABBfyEEAkAgACADQThqIAFB5AAQfiIFRQ0AIAMpAzgiAUKAgICAcINCgICAgDBRBEAgACAFKQMAIAIQbiEEDAELIAAgAhBSIgdCgICAgHCDQoCAgIDgAFEEQCAAIAEQDAwBCyAFKQMAIQggAyAHNwMoIAMgCDcDICAAIAEgBSkDCEECIANBIGoQNiEBIAAgBxAMIAFCgICAgHCDQoCAgIDgAFENACAAIAEQJyIEDQACQCAAIAMgBSgCACIEIAIQQyICQQBOBEAgAkUNASADKAIAIQYgACADEEYgBkEBcQRAIAQtAAVBAXENAgsgAEG4KkEAEBILQX8hBAwBC0EAIQQLIANBQGskACAEC50GAgd/A34jAEFAaiIHJABBfyEIAkAgACAHQThqIAFB5gAQfiIJRQ0AIAcpAzgiDkKAgICAcINCgICAgDBRBEAgACAJKQMAIAIgAyAEIAUgBhBqIQgMAQsgACACEFIiD0KAgICAcINCgICAgOAAUgRAIAAQMyIBQoCAgIBwg0KAgICA4ABSBEAgBkGAEHEiDQRAIARCIIinQXVPBEAgBKciCiAKKAIAQQFqNgIACyAAIAFBwgAgBEEHEBUaCyAGQYAgcSIKBEAgBUIgiKdBdU8EQCAFpyILIAsoAgBBAWo2AgALIAAgAUHDACAFQQcQFRoLIAZBgMAAcSILBEAgA0IgiKdBdU8EQCADpyIMIAwoAgBBAWo2AgALIAAgAUHBACADQQcQFRoLIAZBgARxIgwEQCAAIAFBPyAGQQF2QQFxrUKAgICAEIRBBxAVGgsgBkGACHEEQCAAIAFBwAAgBkECdkEBca1CgICAgBCEQQcQFRoLIAZBgAJxBEAgACABQT4gBkEBca1CgICAgBCEQQcQFRoLIAkpAwAhECAHIAE3AzAgByAPNwMoIAcgEDcDICAAIA4gCSkDCEEDIAdBIGoQNiEOIAAgDxAMIAAgARAMIA5CgICAgHCDQoCAgIDgAFENAiAAIA4QJ0UEQEEAIQggBkGAgAFxRQ0DIABBmTlBABASQX8hCAwDCyAAIAcgCSgCACIJIAIQQyICQQBIDQIgBkGBAnEhCAJAAkAgAkUEQCAIQYACRg0BQQEhCCAJLQAFQQFxRQ0BDAULAkAgBygCACICIAYQjwNFIAJBAXEgCEGAAkZxcg0AAkAgBkGAMHEEQCACQRFxQRBHDQEgDQRAIAAgBCAHKQMQEE1FDQMLIApFDQEgACAFIAcpAxgQTQ0BDAILIAtFDQAgBkECcUUgAkEDcSICQQJGcQ0BIAINACAAIAMgBykDCBBNRQ0BCyAMRQ0CIAcoAgBBE3FBAkcNAgsgACAHEEYLIABBiAtBABASQX8hCAwDCyAAIAcQRkEBIQgMAgsgACAPEAwLIAAgDhAMCyAHQUBrJAAgCAutAgIDfwJ+IwBBQGoiAyQAQX8hBAJAIAAgA0E4aiABQeUAEH4iBUUNACADKQM4IgFCgICAgHCDQoCAgIAwUQRAIAAgBSkDACACQQAQzQEhBAwBCyAAIAIQUiIGQoCAgIBwg0KAgICA4ABRBEAgACABEAwMAQsgBSkDACEHIAMgBjcDKCADIAc3AyAgACABIAUpAwhBAiADQSBqEDYhASAAIAYQDCABQoCAgIBwg0KAgICA4ABRDQAgACABECciBEUEQEEAIQQMAQsCQCAAIAMgBSgCACACEEMiAkEATgRAIAJFDQICQCADLQAAQQFxBEAgACAFKQMAEJcBIgJBAEgNASACDQMLIABB5QpBABASCyAAIAMQRgtBfyEEDAELIAAgAxBGCyADQUBrJAAgBAuDBgIPfwJ+IwBBQGoiBSQAQX8hCwJAIAAgBUE4aiADQegAEH4iB0UNACAFKQM4IgNCgICAgHCDQoCAgIAwUQRAIAAgASACIAcoAgBBAxB9IQsMAQsgACADIAcpAwhBASAHEDYiA0KAgICAcINCgICAgOAAUQ0AIAVBADYCLCAFQQA2AjQgBUEANgIwIAAgBUE0aiADEMoBIQYgBSgCNCEKAkAgBg0AAkAgCkUNACAAIApBA3QQXCIJDQBBACEJDAELAn8CQANAAkAgBCAKRgRAQQEgCiAKQQFNGyEIQQEhBANAIAQgCEYNAiAJIAQgCSAEQQN0aigCBBCDBSEQIARBAWohBCAQQQBIDQALIABBogpBABASQQAMBAsgACADIAQQpgEiE0KAgICAcIMiFEKAgICAgH9RIBRCgICAgJB/UXJFBEBBACAUQoCAgIDgAFENBBogACATEAwgAEHbJUEAEBJBAAwECyAAIBMQMCEIIAAgExAMIAhFDQIgCSAEQQN0aiIGQQA2AgAgBiAINgIEIARBAWohBAwBCwtBACAAIAcpAwAQlwEiDEEASA0BGiAHLQARBEAgABC4AgwBCyAAIAVBLGogBUEwaiAHKAIAQQMQfSERIAUoAjAhBCAFKAIsIQggEQ0CQQAhBgNAIAQgBkcEQCAHLQARBEAgABC4AgwFCyAAIAVBCGoiDiAHKAIAIAggBkEDdGoiDSgCBBBDIg9BAEgNBAJAIA9FDQAgACAOEEYgDARAIAUoAghBAXENAQsgCSAKIA0oAgQQgwUiDUEASARAIABBjSBBABASDAYLIAwNACAJIA1BA3RqQQE2AgALIAZBAWohBgwBCwsCQCAMDQBBACEHA0AgByAKRg0BIAdBA3QhEiAHQQFqIQcgEiAJaigCAA0ACyAAQdMIQQAQEgwDCyAAIAggBBBbIAAgAxAMIAEgCTYCACACIAo2AgBBACELDAMLQQALIQRBACEICyAAIAggBBBbIAAgCSAKEFsgACADEAwLIAVBQGskACALC64EAgV/An4jAEHgAGsiBCQAQX8hBQJAIAAgBEHYAGogAkHnABB+IgZFDQAgBigCACEHIAQpA1giAkKAgICAcINCgICAgDBRBEAgACABIAcgAxBDIQUMAQsgACADEFIiCUKAgICAcINCgICAgOAAUQRAIAAgAhAMDAELIAYpAwAhCiAEIAk3A0ggBCAKNwNAIAAgAiAGKQMIQQIgBEFAaxA2IQIgACAJEAwgAkKAgICAcIMiCUKAgICA4ABRDQACQAJAAkAgCUKAgICAMFEgAkL/////b1ZyRQRAIAAgAhAMDAELIAAgBCAHIAMQQyIDQQBIDQICQCADRQRAQQAhBSAJQoCAgIAwUQ0FDAELIAAgBBBGIAlCgICAgDBSDQAgBC0AAEEBcUUNAUEAIQUgBy0ABUEBcUUNAQwEC0F/IQUgACAGKQMAEJcBIgZBAEgNAiAAIARBIGogAhCEBSEIIAAgAhAMIAhBAEgNAwJAIAMEQCAEKAIAIgVBgDpBgM4AIAQoAiAiA0EQcRsgA3IQjwNFDQEgA0EBcQ0DIAVBAXENASADQRJxDQMgBUECcQ0BDAMLIAZFDQAgBC0AIEEBcQ0CCyAAIARBIGoQRgsgAEGaK0EAEBJBfyEFDAILAkAgAQRAIAEgBCkDIDcDACABIAQpAzg3AxggASAEKQMwNwMQIAEgBCkDKDcDCAwBCyAAIARBIGoQRgtBASEFDAELIAAgAhAMCyAEQeAAaiQAIAULDQAgACABIAJBLBDvBQsLACAAIAFBLBDwBQsWACAAIAMpAwAgAykDCCADKQMQEPIDC9EBAgN+An8jAEEQayIHJAACQCAAIAdBDGogAykDABDfASIIRQRAQoCAgIDgACEEDAELIAAgCCAHKAIMQcn/ABDzAyEBIAAgCBAxAkAgAkECSCABQoCAgIBwg0KAgICA4ABRcg0AIAAgAykDCCIGEDVFDQBCgICAgOAAIQQCQCAAEDMiBUKAgICAcINCgICAgOAAUQRAIAEhBQwBCyAAIAVBLyABQQcQFUEASA0AIAAgBUEvIAYQhQUhBAsgACAFEAwMAQsgASEECyAHQRBqJAAgBAsQACAAIAMpAwBBESAEELICC6UDAQR+IwBBEGsiAyQAIAQCfwJAAkACQAJAIAAgAUEqEFoiAkUEQEKAgICAMCEBDAELIAIoAhgEQEKAgICAMCEBQQEMBQsgACACKQMAIgggAikDCCIGEMUBIgFCgICAgHCDIgdCgICAgOAAUg0BC0KAgICAMCEHDAELIAdCgICAgCBRBEAgAkEBNgIYQoCAgIAwIQFBAQwDCyACKAIQBEAgACAAIAFCABBOEDQiB0KAgICAcIMiCUKAgICA4ABRDQECQCAJQoCAgICQf1INACAHpygCBEH/////B3ENACAAIANBCGogACAIQdYAIAhBABAREKEBQQBIDQIgACAIQdYAAn4gBqcgAykDCCACKAIUEPQCIgZCgICAgAh8Qv////8PWARAIAZC/////w+DDAELQoCAgIDAfiAGub0iBkKAgICAwIGA/P8AfSAGQv///////////wCDQoCAgICAgID4/wBWGwsQOUEASA0CCyAAIAcQDAwCCyACQQE2AhgMAQsgACABEAwgACAHEAxCgICAgOAAIQELQQALNgIAIANBEGokACABCyAAIAFCIIinQXVPBEAgAaciACAAKAIAQQFqNgIACyABC/EHAgR/C34jAEEwayIEJAACQCABQv////9vWARAIAAQIkKAgICA4AAhAQwBC0KAgICAMCEIAkACQCAAIAMpAwAQJSIPQoCAgIBwg0KAgICA4ABRBEBCgICAgDAhDEKAgICAMCEBQoCAgIAwIQ1CgICAgDAhEAwBCyAAIAEgACkDSBD9ASIQQoCAgIBwg0KAgICA4ABRBEBCgICAgDAhDEKAgICAMCEBQoCAgIAwIQ0MAQsCQAJAIAAgACABQe4AIAFBABAREDQiDUKAgICAcINCgICAgOAAUQ0AIA2nIgJB9QBBABCgASEGIAJB+QBBABCgAUEASARAIABB7JYBIA1B0A4QsgEiDUKAgICAcINCgICAgOAAUQ0BCyAEIA03AyggBCABNwMgIAAgEEECIARBIGoQowEiDEKAgICAcINCgICAgOAAUQ0BIAAQOyIBQoCAgIBwg0KAgICA4ABRBEBCgICAgOAAIQEMAwtBfyECAkAgAykDCCIJQoCAgIBwg0KAgICAMFENACAAIARBHGogCRB1QQBIDQMgBCgCHCICDQAMBAsCQAJAIA+nIgcpAgQiCKdB/////wdxIgUEQCAGQX9zQR92IQYgCEL/////B4MhESACrSESQgAhCUKAgICAMCEIQQAhAgNAIAKtIQogAiEDA0AgAyAFTw0DIAAgDEHWACADrSIOEDlBAEgNByAAIAgQDAJAIAAgDCAPEMUBIghCgICAgHCDIgtCgICAgCBRDQAgC0KAgICA4ABRDQggACAEQRBqIAAgDEHWACAMQQAQERChAQ0IIAQgBCkDECILIBEgCyARUxsiCzcDECAKIAtRDQAgACAHIAIgAxCOASIKQoCAgIBwg0KAgICA4ABRDQggACABIAkgChBnQQBIDQggCUIBfCIKIBJRDQkgACAEQQhqIAgQLw0IIAunIQJCASELIAlCASAEKQMIIg4gDkIBVxt8IQkDQCAJIApRDQMgACAAIAggCxBsEDQiDkKAgICAcINCgICAgOAAUQ0JIAAgASAKIA4QZ0EASA0JIAtCAXwhCyAKQgF8IgogElINAAsMCQsgByAOIAYQ9AKnIQMMAAsACwALIAAgDCAPEMUBIghCgICAgHCDIglCgICAgCBSDQFCACEJQQAhAgsgACAHIAIgBSACIAVJGyAFEI4BIgpCgICAgHCDQoCAgIDgAFENAyAAIAEgCSAKEGdBAEgNAwwECyAJQoCAgIDgAFINAwwCC0KAgICAMCEMC0KAgICAMCEBCyAAIAEQDEKAgICA4AAhAQsgACAPEAwgACAQEAwgACAMEAwgACANEAwgACAIEAwLIARBMGokACABC+ACAQd+IAFC/////29YBEAgABAiQoCAgIDgAA8LQoCAgIDgACEIQoCAgIAwIQYCQAJAAkAgACADKQMAECUiB0KAgICAcINCgICAgOAAUQRAQoCAgIAwIQQMAQsgACABQdYAIAFBABARIgRCgICAgHCDQoCAgIDgAFENACAAIARCABBNRQRAIAAgAUHWAEIAEDlBAEgNAQsgACABIAcQxQEiBUKAgICAcIMiCUKAgICA4ABRDQEgACABQdYAIAFBABARIgZCgICAgHCDQoCAgIDgAFENAQJAIAAgBiAEEE0EQCAAIAQQDAwBCyAAIAFB1gAgBBA5QQBODQBCgICAgDAhBAwCCyAAIAcQDCAAIAYQDEL/////DyEIIAlCgICAgCBRDQIgACAFQdgAIAVBABARIQogACAFEAwgCg8LQoCAgIAwIQULIAAgBRAMIAAgBxAMIAAgBhAMIAAgBBAMCyAIC84EAgZ+AX8jAEEgayICJAACQCABQv////9vWARAIAAQIkKAgICA4AAhBwwBC0KAgICA4AAhB0KAgICAMCEIAkAgACADKQMAECUiCUKAgICAcINCgICAgOAAUQRAQoCAgIAwIQRCgICAgDAhBUKAgICAMCEGDAELAkACQCAAIAEgACkDSBD9ASIGQoCAgIBwg0KAgICA4ABRBEBCgICAgDAhBAwBCyAAIAAgAUHuACABQQAQERA0IgRCgICAgHCDQoCAgIDgAFINAQtCgICAgDAhBQwBCyACIAQ3AxggAiABNwMQIAAgBkECIAJBEGoQowEiBUKAgICAcINCgICAgOAAUQ0AIAAgAkEIaiAAIAFB1gAgAUEAEBEQoQENACAAIAVB1gACfiACKQMIIgFCgICAgAh8Qv////8PWARAIAFC/////w+DDAELQoCAgIDAfiABub0iAUKAgICAwIGA/P8AfSABQv///////////wCDQoCAgICAgID4/wBWGwsQOUEASA0AQoCAgIDgACEIIABBKhCGASIBQoCAgIBwg0KAgICA4ABRDQAgAEEgECQiA0UEQCABIQgMAQsgAyAJNwMIIAMgBTcDACADIASnIgpB5wBBABCgAUF/c0EfdjYCECAKQfUAQQAQoAEhCiADQQA2AhggAyAKQX9zQR92NgIUIAFCgICAgHBaBEAgAacgAzYCIAsgACAGEAwgACAEEAwgASEHDAELIAAgCRAMIAAgBhAMIAAgBBAMIAAgBRAMIAAgCBAMCyACQSBqJAAgBwv+BAIIfgJ/IwBBEGsiAiQAAkAgAUL/////b1gEQCAAECJCgICAgOAAIQcMAQtCgICAgOAAIQdCgICAgDAhBQJAAkACQCAAIAMpAwAQJSIJQoCAgIBwg0KAgICA4ABRBEBCgICAgDAhCAwBCyAAIAFB7gAgAUEAEBEiCEKAgICAcINCgICAgOAAUQ0AIAAgCBA0IghCgICAgHCDQoCAgIDgAFENACAIp0HnAEEAEKABQX9GBEAgACABIAkQxQEhBwwDCyAAIAAgAUHwACABQQAQERAnIgxBAEgNACAAIAFB1gBCABA5QQBIDQAgABA7IgZCgICAgHCDQoCAgIDgAFEEQEKAgICA4AAhBgwCCyAJpyENA0ACQCAAIAUQDCAAIAEgCRDFASIFQoCAgIBwgyIEQoCAgIAgUQ0AIARCgICAgOAAUQ0DAkAgACAAIAVCABBOEDQiBEKAgICAcIMiC0KAgICAkH9SBEBBACEDIAtCgICAgOAAUQ0FDAELIASnKAIEQf////8HcUUhAwsgACAGIAogBBB7QQBIDQMgCkIBfCEKIANFDQEgACACQQhqIAAgAUHWACABQQAQERChAUEASA0DIAAgAUHWAAJ+IA0gAikDCCAMEPQCIgRCgICAgAh8Qv////8PWARAIARC/////w+DDAELQoCAgIDAfiAEub0iBEKAgICAwIGA/P8AfSAEQv///////////wCDQoCAgICAgID4/wBWGwsQOUEATg0BDAMLCyAKpwRAIAYhBwwDCyAAIAYQDEKAgICAICEHDAILQoCAgIAwIQYLIAAgBhAMCyAAIAUQDCAAIAgQDCAAIAkQDAsgAkEQaiQAIAcLjgEBAn8gASgCACICQQBKBEAgASACQQFrIgI2AgACQCACDQAgAS0ABEHwAXFBEEcNACABKAIIIgIgASgCDCIDNgIEIAMgAjYCACABQQA2AgggACgCYCICIAFBCGoiAzYCBCABIABB4ABqNgIMIAEgAjYCCCAAIAM2AmALDwtB5oQBQajsAEHWLEHN4wAQAAAL8BQCDn8OfiMAQZABayIEJAACQCABQv////9vWARAIAAQIkKAgICA4AAhFQwBCyADKQMIIR4gACAEQThqQQAQPhogBEEANgIwIARCgICAgMAANwMoIAQgADYCACAEIARBCGoiBzYCBEKAgICA4AAhFUKAgICAMCEWQoCAgIAwIRdCgICAgDAhE0KAgICAMCEUQoCAgIAwIR1CgICAgDAhHAJAAkAgACADKQMAECUiGEKAgICAcINCgICAgOAAUQ0AIAAgHhA1IglFBEAgACAeECUiHUKAgICAcINCgICAgOAAUQ0BIB2nIQULAkACQCAAIAFB7gAgAUEAEBEiHEKAgICA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QQRqIQMMAAsACwuaAQEHfyABpygCICIDBEAgAEEQaiEEIANBBGohBiADKAIIIQIDQCACIAZHBEAgAigCBCEIIAJBEGshBSACQQxrKAIARQRAAkAgAygCAARAIAUQnAUMAQsgACACKQMQECELIAAgAikDGBAhCyAEIAUgACgCBBEAACAIIQIMAQsLIAQgAygCECAAKAIEEQAAIAQgAyAAKAIEEQAACwsbAQF/IAGnKAIgIgMEQCAAIAMoAgwgAhEAAAsLUgEDfyABpygCICICBEAgAigCBCIDBEAgAigCACIEIAM2AgQgAyAENgIAIAJCADcCAAsgACACNQIMQoCAgIBwhBAhIABBEGogAiAAKAIEEQAACwupAQEGfyABpygCICIDBEAgA0EMaiEFIAMoAhAhAgNAIAIgBUcEQCACKAIEIQcgAkIANwIAIAIoAgghBCAHIQIgBC8BBkEgRg0BIARCADcCJAwBCwsCQAJAIAMtAAVFDQAgACgCyAEiAkUNACAAKALQASADKAIIIAIRAAAMAQsgAygCGCICRQ0AIAAgAygCFCADKAIIIAIRBgALIABBEGogAyAAKAIEEQAACwspAQF/IAAgAaciAjUCJEKAgICAkH+EECEgACACNQIgQoCAgICQf4QQIQshACABpygCICkDACIBQoCAgIBgWgRAIAAgAacgAhEAAAsLaQEDfyAAIAGnKAIgIgIpAwAQISACLQARRQRAA0AgAigCFCEEIAMgAigCDE9FBEAgACAEIANBA3RqKAIEEMcBIANBAWohAwwBCwsgAEEQaiAEIAAoAgQRAAALIABBEGogAiAAKAIEEQAAC2wBA38CQCABQoCAgIBwVA0AIAGnIgMvAQZBD0cNACADKAIgIgRFDQAgBEEIaiEFQQAhAwNAIAMgBC0ABU8NASAFIANBA3RqKQMAIgFCgICAgGBaBEAgACABpyACEQAACyADQQFqIQMMAAsACwtqAQN/AkAgAUKAgICAcFQNACABpyICLwEGQQ9HDQAgAigCICIDRQ0AIANBCGohBEEAIQIDQCACIAMtAAVPRQRAIAAgBCACQQN0aikDABAhIAJBAWohAgwBCwsgAEEQaiADIAAoAgQRAAALC38BA38gAacoAiAiBCkDACIBQoCAgIBgWgRAIAAgAacgAhEAAAsgBCkDCCIBQoCAgIBgWgRAIAAgAacgAhEAAAsgBEEYaiEFA0AgBCgCECADSgRAIAUgA0EDdGopAwAiAUKAgICAYFoEQCAAIAGnIAIRAAALIANBAWohAwwBCwsLWQEDfyAAIAGnKAIgIgIpAwAQISAAIAIpAwgQISACQRhqIQQDQCADIAIoAhBORQRAIAAgBCADQQN0aikDABAhIANBAWohAwwBCwsgAEEQaiACIAAoAgQRAAALcgEEfyABpyIDKAIgIQQgAygCJCEFIAMoAigiAwRAIAAgAyACEQAACyAEBEACQCAFRQ0AQQAhAwNAIAMgBCgCPE4NASAFIANBAnRqKAIAIgYEQCAAIAYgAhEAAAsgA0EBaiEDDAALAAsgACAEIAIRAAALC3wBA38gAaciAigCKCIDBEAgACADrUKAgICAcIQQIQsgAigCICIDBEAgAigCJCIEBEBBACECA0AgAiADKAI8TkUEQCAAIAQgAkECdGooAgAQ5QEgAkEBaiECDAELCyAAQRBqIAQgACgCBBEAAAsgACADrUKAgICAYIQQIQsLEgAgAacoAiAiAARAIAAQngMLCx4AIAGnKQMgIgFCgICAgGBaBEAgACABpyACEQAACwsZACAAIAGnIgApAyAQISAAQoCAgIAwNwMgC0QBAn8gAachBANAIAQoAiggA0sEQCAEKAIkIANBA3RqKQMAIgFCgICAgGBaBEAgACABpyACEQAACyADQQFqIQMMAQsLC0YBA38gAachAwNAIAMoAiQhBCACIAMoAihPRQRAIAAgBCACQQN0aikDABAhIAJBAWohAgwBCwsgAEEQaiAEIAAoAgQRAAALEQAgAEEQaiACIAAoAgQRAAAL2wECAX8CfiMAQSBrIgMkACABQQNGBEAgAikDECEEIAIpAwghBQJAIAAgA0EQaiACKQMAEKoFQQBIBEBCgICAgOAAIQQMAQsgACAEIAVBAiADQRBqEBwiBEKAgICAcINCgICAgOAAUQRAIAAoAhAiASkDgAEhBCABQoCAgIAgNwOAASADIAQ3AwggACADKQMYQoCAgIAwQQEgA0EIahAcIQQgACADKQMIEAwLIAAgAykDEBAMIAAgAykDGBAMCyADQSBqJAAgBA8LQZuCAUGo7ABBy/UCQaDtABAAAAuIAQIBfgF/QQAhAkKAgICAMCEBA0ACQCACQQJHBH4gBSACQQN0IgRq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Y29kZV0KfQBjYW5ub3QgbWl4ID8/IHdpdGggJiYgb3IgfHwAcHJveHk6IHByb3BlcnR5IG5vdCBwcmVzZW50IGluIHRhcmdldCB3ZXJlIHJldHVybmVkIGJ5IG5vbiBleHRlbnNpYmxlIHByb3h5AHJldm9rZWQgcHJveHkAUHJveHkAYWRkX3Byb3BlcnR5AHByb3h5OiBjYW5ub3Qgc2V0IHByb3BlcnR5AG5vIHNldHRlciBmb3IgcHJvcGVydHkAdmFsdWUgaGFzIG5vIHByb3BlcnR5AGNvdWxkIG5vdCBkZWxldGUgcHJvcGVydHkAcHJveHk6IGR1cGxpY2F0ZSBwcm9wZXJ0eQBKU19EZWZpbmVBdXRvSW5pdFByb3BlcnR5AGhhc093blByb3BlcnR5AHByb3h5OiBpbmNvbnNpc3RlbnQgZGVsZXRlUHJvcGVydHkAcHJveHk6IGluY29uc2lzdGVudCBkZWZpbmVQcm9wZXJ0eQBKU19EZWZpbmVQcm9wZXJ0eQAhbXItPmVtcHR5AGluZmluaXR5AEluZmluaXR5AG91dCBvZiBtZW1vcnkAdW5rbm93biB1bmljb2RlIGdlbmVyYWwgY2F0ZWdvcnkAR2VuZXJhbF9DYXRlZ29yeQBldmVyeQBhbnkAYXBwbHkAJyVzJyBpcyByZWFkLW9ubHkAZXhwZWN0aW5nIGNhdGNoIG9yIGZpbmFsbHkAc3RpY2t5AGJpZ2ludCBhcmUgZm9yYmlkZGVuIGluIEpTT04uc3RyaW5naWZ5AHN1YmFycmF5AGVtcHR5IGFycmF5AG5vbiBpbnRlZ2VyIGluZGV4IGluIHR5cGVkIGFycmF5AG5lZ2F0aXZlIGluZGV4IGluIHR5cGVkIGFycmF5AG91dC1vZi1ib3VuZCBpbmRleCBpbiB0eXBlZCBhcnJheQBjYW5ub3QgY3JlYXRlIG51bWVyaWMgaW5kZXggaW4gdHlwZWQgYXJyYXkAaXNBcnJheQBUeXBlZEFycmF5AGdldERheQBnZXRVVENEYXkAZ3JvdXBCeQBtLT5kZnNfYW5jZXN0b3JfaW5kZXggPD0gbS0+ZGZzX2luZGV4AGpzX2dldF9hdG9tX2luZGV4AGludmFsaWQgYXJyYXkgaW5kZXgASlNfQXRvbUlzQXJyYXlJbmRleABmaW5kTGFzdEluZGV4AGZpbmRJbmRleABpbnZhbGlkIGV4cG9ydCBzeW50YXgAaW52YWxpZCBhc3NpZ25tZW50IHN5bnRheABtYXgAXHUlMDR4AGludmFsaWQgb3Bjb2RlOiBwYz0ldSBvcGNvZGU9MHglMDJ4AC0rICAgMFgweAAtMFgrMFggMFgtMHgrMHggMHgAbGluZSB0ZXJtaW5hdG9yIG5vdCBhbGxvd2VkIGFmdGVyIHRocm93AGJmX3BvdwBub3cAaW50ZWdlciBvdmVyZmxvdwBzdGFjayBvdmVyZmxvdwBtdXN0IGJlIGNhbGxlZCB3aXRoIG5ldwBpc1ZpZXcARGF0YVZpZXcAcmF3ACV1AGNsYXNzIGRlY2xhcmF0aW9ucyBjYW4ndCBhcHBlYXIgaW4gc2luZ2xlLXN0YXRlbWVudCBjb250ZXh0AGZ1bmN0aW9uIGRlY2xhcmF0aW9ucyBjYW4ndCBhcHBlYXIgaW4gc2luZ2xlLXN0YXRlbWVudCBjb250ZXh0AGxleGljYWwgZGVjbGFyYXRpb25zIGNhbid0IGFwcGVhciBpbiBzaW5nbGUtc3RhdGVtZW50IGNvbnRleHQAZHVwbGljYXRlIGFyZ3VtZW50IG5hbWVzIG5vdCBhbGxvd2VkIGluIHRoaXMgY29udGV4dABkdXBsaWNhdGUgcGFyYW1ldGVyIG5hbWVzIG5vdCBhbGxvd2VkIGluIHRoaXMgY29udGV4dABpbXBvcnQubWV0YSBub3Qgc3VwcG9ydGVkIGluIHRoaXMgY29udGV4dABKU19GcmVlQ29udGV4dABKU0NvbnRleHQAanNfbWFwX2l0ZXJhdG9yX25leHQAanNfZ2VuZXJhdG9yX25leHQAanNfYXN5bmNfZ2VuZXJhdG9yX3Jlc3VtZV9uZXh0AHVuZXhwZWN0ZWQgZW5kIG9mIGlucHV0AHR0AGV4cG9ydGVkIHZhcmlhYmxlICclcycgZG9lcyBub3QgZXhpc3QAcHJpdmF0ZSBjbGFzcyBmaWVsZCAnJXMnIGRvZXMgbm90IGV4aXN0AHRlc3QAYXNzaWdubWVudCByZXN0IHByb3BlcnR5IG11c3QgYmUgbGFzdABwdmFsID09IGxhc3QAZmluZExhc3QAYmZfc3FydABzb3J0AGNicnQAdHJpbVN0YXJ0AHBhZFN0YXJ0AHVua25vd24gdW5pY29kZSBzY3JpcHQAU2NyaXB0AGh5cG90AGZyZWVfemVyb19yZWZjb3VudABzdHJfaW5kZXggPT0gbnVtX2tleXNfY291bnQgKyBzdHJfa2V5c19jb3VudABudW1faW5kZXggPT0gbnVtX2tleXNfY291bnQAc3ltX2luZGV4ID09IGF0b21fY291bnQAbGFiZWwgPj0gMCAmJiBsYWJlbCA8IHMtPmxhYmVsX2NvdW50AGxhYjEgPj0gMCAmJiBsYWIxIDwgcy0+bGFiZWxfY291bnQAdmFsIDwgcy0+Y2FwdHVyZV9jb3VudAB2YWwyIDwgcy0+Y2FwdHVyZV9jb3VudABpbnZhbGlkIHJlcGVhdCBjb3VudABpbnZhbGlkIHJlcGV0aXRpb24gY291bnQAZm9udABpbnZhbGlkIGNvZGUgcG9pbnQAZnJvbUNvZGVQb2ludABpbnZhbGlkIGhpbnQAY2Fubm90IGNvbnZlcnQgTmFOIG9yIEluZmluaXR5IHRvIGJpZ2ludABjYW5ub3QgY29udmVydCB0byBiaWdpbnQAYm90aCBvcGVyYW5kcyBtdXN0IGJlIGJpZ2ludABub3QgYSBiaWdpbnQAcHJpdmF0ZSBtZXRob2QgaXMgYWxyZWFkeSBwcmVzZW50AGVuY29kZVVSSUNvbXBvbmVudABkZWNvZGVVUklDb21wb25lbnQAdW5leHBlY3RlZCBlbmQgb2YgY29tbWVudABpbnZhbGlkIHN3aXRjaCBzdGF0ZW1lbnQAQmlnSW50AHBhcnNlSW50AGR1cGxpY2F0ZSBkZWZhdWx0AHNwbGl0AGV4cGVjdGluZyBoZXggZGlnaXQAdHJpbVJpZ2h0AHJlZHVjZVJpZ2h0AHVuc2hpZnQAdHJpbUxlZnQAaW52YWxpZCBvZmZzZXQAaW52YWxpZCBieXRlT2Zmc2V0AGdldFRpbWV6b25lT2Zmc2V0AHJlc29sdmluZyBmdW5jdGlvbiBhbHJlYWR5IHNldABwcm94eTogaW5jb25zaXN0ZW50IHNldABmaW5kX2p1bXBfdGFyZ2V0AGV4cGVjdGluZyB0YXJnZXQAaW52YWxpZCBkZXN0cnVjdHVyaW5nIHRhcmdldABwcm94eTogaW5jb25zaXN0ZW50IGdldABXZWFrU2V0AGNvbnN0cnVjdABKU19GcmVlQXRvbVN0cnVjdAB1c2Ugc3RyaWN0AFJlZmxlY3QAcmVqZWN0AG5vdCBhbiBBc3luY0dlbmVyYXRvciBvYmplY3QAY2Fubm90IGNvbnZlcnQgdG8gb2JqZWN0AGludmFsaWQgYnJhbmQgb24gb2JqZWN0AG9wZXJhbmQgJ3Byb3RvdHlwZScgcHJvcGVydHkgaXMgbm90IGFuIG9iamVjdABpdGVyYXRvciBtdXN0IHJldHVybiBhbiBvYmplY3QAbm90IGEgRGF0ZSBvYmplY3QAbm90IGEgb2JqZWN0AEpTT2JqZWN0AHBhcnNlRmxvYXQAZmxhdABub3RoaW5nIHRvIHJlcGVhdABjb25jYXQAY29kZVBvaW50QXQAY2hhckF0AGNoYXJDb2RlQXQAa2V5cwBwcm94eTogdGFyZ2V0IHByb3BlcnR5IG11c3QgYmUgcHJlc2VudCBpbiBwcm94eSBvd25LZXlzACAgZmFzdCBhcnJheXMAZXhwb3J0ICclcycgaW4gbW9kdWxlICclcycgaXMgYW1iaWd1b3VzAHByaXZhdGUgY2xhc3MgZmllbGQgJyVzJyBhbHJlYWR5IGV4aXN0cwB0b28gbWFueSBhcmd1bWVudHMAVG9vIG1hbnkgY2FsbCBhcmd1bWVudHMAdG9vIG1hbnkgZWxlbWVudHMAICBlbGVtZW50cwBpbnZhbGlkIG51bWJlciBvZiBkaWdpdHMAYmluYXJ5IG9iamVjdHMAaW52YWxpZCBwcm9wZXJ0eSBhY2Nlc3MAanNfb3BfZGVmaW5lX2NsYXNzAGZkLT5ieXRlX2NvZGUuYnVmW2RlZmluZV9jbGFzc19wb3NdID09IE9QX2RlZmluZV9jbGFzcwBfX2dldENsYXNzAHNldEhvdXJzAGdldEhvdXJzAHNldFVUQ0hvdXJzAGdldFVUQ0hvdXJzAGdhdGhlcl9hdmFpbGFibGVfYW5jZXN0b3JzAGdldE93blByb3BlcnR5RGVzY3JpcHRvcnMAd2l0aFJlc29sdmVycwB0b28gbWFueSBpbWJyaWNhdGVkIHF1YW50aWZpZXJzAHVuaWNvZGVfcHJvcF9vcHMAYWNvcwBmb3IgYXdhaXQgaXMgb25seSB2YWxpZCBpbiBhc3luY2hyb25vdXMgZnVuY3Rpb25zAG5ldy50YXJnZXQgb25seSBhbGxvd2VkIHdpdGhpbiBmdW5jdGlvbnMAYnl0ZWNvZGUgZnVuY3Rpb25zAEMgZnVuY3Rpb25zAHByb3h5OiBpbmNvbnNpc3RlbnQgcHJldmVudEV4dGVuc2lvbnMAU2NyaXB0X0V4dGVuc2lvbnMAYXRvbXMAcHJveHk6IHByb3BlcnRpZXMgbXVzdCBiZSBzdHJpbmdzIG9yIHN5bWJvbHMAZ2V0T3duUHJvcGVydHlTeW1ib2xzAHJlc29sdmVfbGFiZWxzAEpTX0V2YWxUaGlzAHN0cmluZ3MAaW52YWxpZCBkZXNjcmlwdG9yIGZsYWdzAGludmFsaWQgcmVndWxhciBleHByZXNzaW9uIGZsYWdzAHZhbHVlcwBzZXRNaW51dGVzAGdldE1pbnV0ZXMAc2V0VVRDTWludXRlcwBnZXRVVENNaW51dGVzAHRvbyBtYW55IGNhcHR1cmVzACAgc2hhcGVzAGdldE93blByb3BlcnR5TmFtZXMAZ2NfZnJlZV9jeWNsZXMAYWRkX2V2YWxfdmFyaWFibGVzAHJlc29sdmVfdmFyaWFibGVzAHRvbyBtYW55IGxvY2FsIHZhcmlhYmxlcwB0b28gbWFueSBjbG9zdXJlIHZhcmlhYmxlcwBjb21wYWN0X3Byb3BlcnRpZXMAICBwcm9wZXJ0aWVzAGRlZmluZVByb3BlcnRpZXMAZW50cmllcwBmcm9tRW50cmllcwB0b28gbWFueSByYW5nZXMAaW5jbHVkZXMAaGFzSW5kaWNlcwBzZXRNaWxsaXNlY29uZHMAZ2V0TWlsbGlzZWNvbmRzAHNldFVUQ01pbGxpc2Vjb25kcwBnZXRVVENNaWxsaXNlY29uZHMAc2V0U2Vjb25kcwBnZXRTZWNvbmRzAHNldFVUQ1NlY29uZHMAZ2V0VVRDU2Vjb25kcwBpdGFsaWNzAGFicwBwcm94eTogaW5jb25zaXN0ZW50IGhhcwAlLipzACAoJXMAc2V0ICVzAGdldCAlcwAgICAgYXQgJXMAbm90IGEgJXMAdW5zdXBwb3J0ZWQga2V5d29yZDogJXMAc3Vic3RyAHByb3h5OiBpbmNvbnNpc3RlbnQgZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yAHN1cGVyKCkgaXMgb25seSB2YWxpZCBpbiBhIGRlcml2ZWQgY2xhc3MgY29uc3RydWN0b3IAcGFyZW50IGNsYXNzIG11c3QgYmUgY29uc3RydWN0b3IAbm90IGEgY29uc3RydWN0b3IAQXJyYXkgSXRlcmF0b3IAU2V0IEl0ZXJhdG9yAE1hcCBJdGVyYXRvcgBSZWdFeHAgU3RyaW5nIEl0ZXJhdG9yAG5vdCBhbiBBc3luYy1mcm9tLVN5bmMgSXRlcmF0b3IAY2Fubm90IGludm9rZSBhIHJ1bm5pbmcgZ2VuZXJhdG9yAG5vdCBhIGdlbmVyYXRvcgBBc3luY0dlbmVyYXRvcgBzeW50YXggZXJyb3IAU3ludGF4RXJyb3IARXZhbEVycm9yAEludGVybmFsRXJyb3IAQWdncmVnYXRlRXJyb3IAVHlwZUVycm9yAFJhbmdlRXJyb3IAUmVmZXJlbmNlRXJyb3IAVVJJRXJyb3IAZmxvb3IAZm9udGNvbG9yAGFuY2hvcgBmb3IAa2V5Rm9yAGV4cGVjdGluZyBzdXJyb2dhdGUgcGFpcgBhIGRlY2xhcmF0aW9uIGluIHRoZSBoZWFkIG9mIGEgZm9yLSVzIGxvb3AgY2FuJ3QgaGF2ZSBhbiBpbml0aWFsaXplcgAnYXJndW1lbnRzJyBpZGVudGlmaWVyIGlzIG5vdCBhbGxvd2VkIGluIGNsYXNzIGZpZWxkIGluaXRpYWxpemVyAGludmFsaWQgbnVtYmVyIG9mIGFyZ3VtZW50cyBmb3IgZ2V0dGVyIG9yIHNldHRlcgBpbnZhbGlkIHNldHRlcgBpbnZhbGlkIGdldHRlcgBmaWx0ZXIAbWlzc2luZyBmb3JtYWwgcGFyYW1ldGVyACJ1c2Ugc3RyaWN0IiBub3QgYWxsb3dlZCBpbiBmdW5jdGlvbiB3aXRoIGRlZmF1bHQgb3IgZGVzdHJ1Y3R1cmluZyBwYXJhbWV0ZXIAaW52YWxpZCBjaGFyYWN0ZXIAdW5leHBlY3RlZCBjaGFyYWN0ZXIAcHJpdmF0ZSBjbGFzcyBmaWVsZCBmb3JiaWRkZW4gYWZ0ZXIgc3VwZXIAaW52YWxpZCByZWRlZmluaXRpb24gb2YgbGV4aWNhbCBpZGVudGlmaWVyACdsZXQnIGlzIG5vdCBhIHZhbGlkIGxleGljYWwgaWRlbnRpZmllcgBpbnZhbGlkIHJlZGVmaW5pdGlvbiBvZiBnbG9iYWwgaWRlbnRpZmllcgB5aWVsZCBpcyBhIHJlc2VydmVkIGlkZW50aWZpZXIAJyVzJyBpcyBhIHJlc2VydmVkIGlkZW50aWZpZXIAb3RoZXIAYXRvbTFfaXNfaW50ZWdlciAmJiBhdG9tMl9pc19pbnRlZ2VyAGNhbm5vdCBjb252ZXJ0IHRvIGJpZ2ludDogbm90IGFuIGludGVnZXIAaXNJbnRlZ2VyAGlzU2FmZUludGVnZXIAYnVmZmVyAFNoYXJlZEFycmF5QnVmZmVyAGNhbm5vdCB1c2UgaWRlbnRpY2FsIEFycmF5QnVmZmVyAGNhbm5vdCBjb252ZXJ0IGJpZ2ludCB0byBudW1iZXIAY2Fubm90IGNvbnZlcnQgc3ltYm9sIHRvIG51bWJlcgBub3QgYSBudW1iZXIAbGluZU51bWJlcgBtYWxmb3JtZWQgdW5pY29kZSBjaGFyAGNsZWFyAHNldFllYXIAZ2V0WWVhcgBzZXRGdWxsWWVhcgBnZXRGdWxsWWVhcgBzZXRVVENGdWxsWWVhcgBnZXRVVENGdWxsWWVhcgBxICE9IHIAdW5leHBlY3RlZCBsaW5lIHRlcm1pbmF0b3IgaW4gcmVnZXhwAHVuZXhwZWN0ZWQgZW5kIG9mIHJlZ2V4cABSZWdFeHAAc3VwAGludmFsaWQgZ3JvdXAAcG9wAGNvbnRpbnVlIG11c3QgYmUgaW5zaWRlIGxvb3AAYmZfbG9naWNfb3AAZHVtcABudW1fa2V5c19jbXAAdXNlIHN0cmlwAG1hcABmbGF0TWFwAFdlYWtNYXAAZXhwZWN0aW5nICd7JyBhZnRlciBccABsb2cxcABkaXZpc2lvbiBieSB6ZXJvADBvAGhhc093bgByZXR1cm4AcHJvbWlzZSBzZWxmIHJlc29sdXRpb24Ab3V0IG9mIG1lbW9yeSBpbiByZWdleHAgZXhlY3V0aW9uAGRlc2NyaXB0aW9uACFtLT5ldmFsX2hhc19leGNlcHRpb24AIW1vZHVsZS0+ZXZhbF9oYXNfZXhjZXB0aW9uAHByb3h5OiBkZWZpbmVQcm9wZXJ0eSBleGNlcHRpb24AanNfYXN5bmNfZ2VuZXJhdG9yX3Jlc29sdmVfZnVuY3Rpb24AanNfY3JlYXRlX2Z1bmN0aW9uAHNldC9hZGQgaXMgbm90IGEgZnVuY3Rpb24AcmV0dXJuIG5vdCBpbiBhIGZ1bmN0aW9uAEFzeW5jR2VuZXJhdG9yRnVuY3Rpb24AY2FsbEV4dGVybmFsRnVuY3Rpb24AQXN5bmNGdW5jdGlvbgBqc19pbm5lcl9tb2R1bGVfZXZhbHVhdGlvbgAhbS0+YXN5bmNfZXZhbHVhdGlvbgBtb2R1bGUtPmFzeW5jX2V2YWx1YXRpb24AaW52YWxpZCBvcGVyYXRpb24AdW5zdXBwb3J0ZWQgb3BlcmF0aW9uAGF3YWl0IGluIGRlZmF1bHQgZXhwcmVzc2lvbgB5aWVsZCBpbiBkZWZhdWx0IGV4cHJlc3Npb24AaW52YWxpZCBkZWNpbWFsIGVzY2FwZSBpbiByZWd1bGFyIGV4cHJlc3Npb24AYmFjayByZWZlcmVuY2Ugb3V0IG9mIHJhbmdlIGluIHJlZ3VsYXIgZXhwcmVzc2lvbgBpbnZhbGlkIGVzY2FwZSBzZXF1ZW5jZSBpbiByZWd1bGFyIGV4cHJlc3Npb24AZXhwZWN0ZWQgJ29mJyBvciAnaW4nIGluIGZvciBjb250cm9sIGV4cHJlc3Npb24AdG9vIGNvbXBsaWNhdGVkIGRlc3RydWN0dXJpbmcgZXhwcmVzc2lvbgBleHBlY3RlZCAnfScgYWZ0ZXIgdGVtcGxhdGUgZXhwcmVzc2lvbgB0b1ByZWNpc2lvbgBhc2luAGpvaW4AbWluAGNvcHlXaXRoaW4AdGVtcGxhdGUgbGl0ZXJhbCBjYW5ub3QgYXBwZWFyIGluIGFuIG9wdGlvbmFsIGNoYWluAGNpcmN1bGFyIHByb3RvdHlwZSBjaGFpbgBhc3NpZ24AIXktPnNpZ24AaXNGcm96ZW4AKHBvcyArIGxlbikgPD0gYmNfYnVmX2xlbgB1bmV4cGVjdGVkIGVsbGlwc2lzIHRva2VuAHRoZW4Ac2V0dGVyIGlzIGZvcmJpZGRlbgBudWxsIG9yIHVuZGVmaW5lZCBhcmUgZm9yYmlkZGVuAGF0YW4AbmFuAG5vdCBhIGJvb2xlYW4AQm9vbGVhbgBnY19zY2FuAGJhZCBub3JtYWxpemF0aW9uIGZvcm0ASlNfTmV3U3ltYm9sRnJvbUF0b20AZnJvbQByYW5kb20AdHJpbQBiZl9kaXZyZW0AbS0+Y3ljbGVfcm9vdCA9PSBtAGltdWwAbm90IGEgc3ltYm9sAFN5bWJvbABSZWdFeHAgZXhlYyBtZXRob2QgbXVzdCByZXR1cm4gYW4gb2JqZWN0IG9yIG51bGwAcGFyZW50IHByb3RvdHlwZSBtdXN0IGJlIGFuIG9iamVjdCBvciBudWxsAGNhbm5vdCBzZXQgcHJvcGVydHkgJyVzJyBvZiBudWxsAGNhbm5vdCByZWFkIHByb3BlcnR5ICclcycgb2YgbnVsbABOdWxsAGZpbGwAbmV3IEFycmF5QnVmZmVyIGlzIHRvbyBzbWFsbABUeXBlZEFycmF5IGxlbmd0aCBpcyB0b28gc21hbGwAY2FsbABkb3RBbGwAbWF0Y2hBbGwAcmVwbGFjZUFsbABjZWlsAHVwZGF0ZV9sYWJlbABiY19idWZbcG9zXSA9PSBPUF9sYWJlbABldmFsAGludmFsaWQgYmlnaW50IGxpdGVyYWwAaW52YWxpZCBudW1iZXIgbGl0ZXJhbABtYWxmb3JtZWQgZXNjYXBlIHNlcXVlbmNlIGluIHN0cmluZyBsaXRlcmFsAGJmX2V4cF9pbnRlcm5hbABiZl9sb2dfaW50ZXJuYWwAYmZfZnRvYV9pbnRlcm5hbABKU19TZXRQcm9wZXJ0eUludGVybmFsAEpTX0dldE93blByb3BlcnR5TmFtZXNJbnRlcm5hbABfX0pTX0V2YWxJbnRlcm5hbAB0b0V4cG9uZW50aWFsAHNlYWwAZ2xvYmFsAGJsaW5rAHJldHVybiBpbiBhIHN0YXRpYyBpbml0aWFsaXplciBibG9jawBzdGFjawBscmVfZXhlY19iYWNrdHJhY2sAcy0+aXNfd2VhawBiZl9wb3dfdWkAc2V0TW9udGgAZ2V0TW9udGgAc2V0VVRDTW9udGgAZ2V0VVRDTW9udGgAaW52YWxpZCBrZXl3b3JkOiB3aXRoAHN0YXJ0c1dpdGgAZW5kc1dpdGgAcHJvcCA9PSBKU19BVE9NX2xlbmd0aABpbnZhbGlkIGFycmF5IGxlbmd0aABpbnZhbGlkIGFycmF5IGJ1ZmZlciBsZW5ndGgAaW52YWxpZCBsZW5ndGgAaW52YWxpZCBieXRlTGVuZ3RoAE1hdGgAcHVzaABhY29zaABKU19SZXNpemVBdG9tSGFzaABhc2luaABhdGFuaABicmVhayBtdXN0IGJlIGluc2lkZSBsb29wIG9yIHN3aXRjaABtYXRjaABuaXBfY2F0Y2gAc2VhcmNoAGZvckVhY2gAYmZfbG9nAEFycmF5IHRvbyBsb25nAHN0cmluZyB0b28gbG9uZwBBcnJheSBsb28gbG9uZwBzdWJzdHJpbmcAY2Fubm90IGNvbnZlcnQgc3ltYm9sIHRvIHN0cmluZwB1bmV4cGVjdGVkIGVuZCBvZiBzdHJpbmcAbm90IGEgc3RyaW5nAGludmFsaWQgY2hhcmFjdGVyIGluIGEgSlNPTiBzdHJpbmcAdG9TdHJpbmcAdG9EYXRlU3RyaW5nAHRvTG9jYWxlRGF0ZVN0cmluZwB0b1RpbWVTdHJpbmcAdG9Mb2NhbGVUaW1lU3RyaW5nAHRvTG9jYWxlU3RyaW5nAHRvR01UU3RyaW5nAEpTU3RyaW5nAHRvSVNPU3RyaW5nAHRvVVRDU3RyaW5nAGpzX2lubmVyX21vZHVsZV9saW5raW5nAGR1cGxpY2F0ZSBpbXBvcnQgYmluZGluZwBpbnZhbGlkIGltcG9ydCBiaW5kaW5nAHByb21pc2UgaXMgcGVuZGluZwBiaWcAcmVnZXhwIG11c3QgaGF2ZSB0aGUgJ2cnIGZsYWcAb2YAaW5mAEluZgBkaWZmID09IChpbnQ4X3QpZGlmZgBkaWZmID09IChpbnQxNl90KWRpZmYAaHJlZgBnY19kZWNyZWYAZnJlZV92YXJfcmVmAG9wdGltaXplX3Njb3BlX21ha2VfZ2xvYmFsX3JlZgByZXNldF93ZWFrX3JlZgBkZWxldGVfd2Vha19yZWYAb3B0aW1pemVfc2NvcGVfbWFrZV9yZWYAaW5kZXhPZgBsYXN0SW5kZXhPZgB2YWx1ZU9mAHNldFByb3RvdHlwZU9mAGdldFByb3RvdHlwZU9mAGlzUHJvdG90eXBlT2YAJS4qZgBmb250c2l6ZQBuZXdfc2l6ZSA8PSBzaC0+cHJvcF9zaXplAGRlc2NyIDwgcnQtPmF0b21fc2l6ZQBhdG9tIDwgcnQtPmF0b21fc2l6ZQBjb21wdXRlX3N0YWNrX3NpemUAbiA8IGJ1Zl9zaXplAG5vcm1hbGl6ZQBjcl9yZWdleHBfY2Fub25pY2FsaXplAGZyZWV6ZQByZXNvbHZlAHRvUHJpbWl0aXZlAHB1dF9sdmFsdWUAdW5rbm93biB1bmljb2RlIHByb3BlcnR5IHZhbHVlAHJlc3QgZWxlbWVudCBjYW5ub3QgaGF2ZSBhIGRlZmF1bHQgdmFsdWUAaW52YWxpZCByZXQgdmFsdWUAX19KU19BdG9tVG9WYWx1ZQBfX3F1b3RlAGlzRmluaXRlAGRlbGV0ZQBjcmVhdGUAc2V0RGF0ZQBnZXREYXRlAHNldFVUQ0RhdGUAZ2V0VVRDRGF0ZQBJbnZhbGlkIERhdGUAcmV2ZXJzZQBwYXJzZQBwcm94eSBwcmV2ZW50RXh0ZW5zaW9ucyBoYW5kbGVyIHJldHVybmVkIGZhbHNlAG1vZHVsZSBuYW1lc3BhY2UgcHJvcGVydGllcyBoYXZlIHdyaXRhYmxlID0gZmFsc2UAUHJvbWlzZQB0b0xvd2VyQ2FzZQB0b0xvY2FsZUxvd2VyQ2FzZQB0b1VwcGVyQ2FzZQB0b0xvY2FsZVVwcGVyQ2FzZQBpZ25vcmVDYXNlAGxvY2FsZUNvbXBhcmUAcHJveHk6IGluY29uc2lzdGVudCBwcm90b3R5cGUAcHJveHk6IGJhZCBwcm90b3R5cGUAbm90IGEgcHJvdG90eXBlAGludmFsaWQgb2JqZWN0IHR5cGUAdW5lc2NhcGUAbm9uZQByZXN0IGVsZW1lbnQgbXVzdCBiZSB0aGUgbGFzdCBvbmUAbXVsdGlsaW5lACAgcGMybGluZQBhc3luY19mdW5jX3Jlc3VtZQBzb21lAEpTX0ZyZWVSdW50aW1lAEpTUnVudGltZQBzZXRUaW1lAGdldFRpbWUAYXN5bmNfZnVuY19mcmVlX2ZyYW1lAHNldF9vYmplY3RfbmFtZQBleHBlY3RpbmcgcHJvcGVydHkgbmFtZQB1bmtub3duIHVuaWNvZGUgcHJvcGVydHkgbmFtZQBpbnZhbGlkIHByb3BlcnR5IG5hbWUAZHVwbGljYXRlIF9fcHJvdG9fXyBwcm9wZXJ0eSBuYW1lAGludmFsaWQgcmVkZWZpbml0aW9uIG9mIHBhcmFtZXRlciBuYW1lAGV4cGVjdGluZyBncm91cCBuYW1lAGR1cGxpY2F0ZSBncm91cCBuYW1lAGludmFsaWQgZ3JvdXAgbmFtZQBkdXBsaWNhdGUgbGFiZWwgbmFtZQBpbnZhbGlkIGZpcnN0IGNoYXJhY3RlciBvZiBwcml2YXRlIG5hbWUAaW52YWxpZCBsZXhpY2FsIHZhcmlhYmxlIG5hbWUAaW52YWxpZCBtZXRob2QgbmFtZQBleHBlY3RpbmcgZmllbGQgbmFtZQBpbnZhbGlkIGZpZWxkIG5hbWUAY2xhc3Mgc3RhdGVtZW50IHJlcXVpcmVzIGEgbmFtZQBmaWxlTmFtZQBqc19saW5rX21vZHVsZQBqc19ldmFsdWF0ZV9tb2R1bGUAbW9kdWxlLT5jeWNsZV9yb290ID09IG1vZHVsZQBjb21waWxlAG9iamVjdCBpcyBub3QgZXh0ZW5zaWJsZQBwcm94eTogaW5jb25zaXN0ZW50IGlzRXh0ZW5zaWJsZQBjYW5ub3QgaGF2ZSBzZXR0ZXIvZ2V0dGVyIGFuZCB2YWx1ZSBvciB3cml0YWJsZQBwcm9wZXJ0eSBpcyBub3QgY29uZmlndXJhYmxlAHZhbHVlIGlzIG5vdCBpdGVyYWJsZQBwcm9wZXJ0eUlzRW51bWVyYWJsZQBtaXNzaW5nIGluaXRpYWxpemVyIGZvciBjb25zdCB2YXJpYWJsZQBsZXhpY2FsIHZhcmlhYmxlAGludmFsaWQgcmVkZWZpbml0aW9uIG9mIGEgdmFyaWFibGUAcmV2b2NhYmxlAHN0cmlrZQBtcF9kaXZub3JtX2xhcmdlAGludmFsaWQgY2xhc3MgcmFuZ2UAbWVzc2FnZQBpbnZhbGlkIGx2YWx1ZSBpbiBzdHJpY3QgbW9kZQBpbnZhbGlkIHZhcmlhYmxlIG5hbWUgaW4gc3RyaWN0IG1vZGUAY2Fubm90IGRlbGV0ZSBhIGRpcmVjdCByZWZlcmVuY2UgaW4gc3RyaWN0IG1vZGUAb2N0YWwgZXNjYXBlIHNlcXVlbmNlcyBhcmUgbm90IGFsbG93ZWQgaW4gc3RyaWN0IG1vZGUAb2N0YWwgbGl0ZXJhbHMgYXJlIGRlcHJlY2F0ZWQgaW4gc3RyaWN0IG1vZGUAdW5pY29kZQAgIGJ5dGVjb2RlAEpTRnVuY3Rpb25CeXRlY29kZQBza2lwX2RlYWRfY29kZQBpbnZhbGlkIGFyZ3VtZW50IG5hbWUgaW4gc3RyaWN0IGNvZGUAaW52YWxpZCBmdW5jdGlvbiBuYW1lIGluIHN0cmljdCBjb2RlAGludmFsaWQgcmVkZWZpbml0aW9uIG9mIGdsb2JhbCBpZGVudGlmaWVyIGluIG1vZHVsZSBjb2RlAGltcG9ydC5tZXRhIG9ubHkgdmFsaWQgaW4gbW9kdWxlIGNvZGUAZnJvbUNoYXJDb2RlAGludmFsaWQgZm9yIGluL29mIGxlZnQgaGFuZC1zaWRlAGludmFsaWQgYXNzaWdubWVudCBsZWZ0LWhhbmQgc2lkZQByZWR1Y2UAc291cmNlACd0aGlzJyBjYW4gYmUgaW5pdGlhbGl6ZWQgb25seSBvbmNlAHByb3BlcnR5IGNvbnN0cnVjdG9yIGFwcGVhcnMgbW9yZSB0aGFuIG9uY2UAaW52YWxpZCBVVEYtOCBzZXF1ZW5jZQBjaXJjdWxhciByZWZlcmVuY2UAc2xpY2UAc3BsaWNlAHJhY2UAcmVwbGFjZQAlKy4qZQB1bmV4cGVjdGVkICdhd2FpdCcga2V5d29yZAB1bmV4cGVjdGVkICd5aWVsZCcga2V5d29yZABtYXBfZGVjcmVmX3JlY29yZABpdGVyYXRvciBkb2VzIG5vdCBoYXZlIGEgdGhyb3cgbWV0aG9kAG9iamVjdCBuZWVkcyB0b0lTT1N0cmluZyBtZXRob2QAJ3N1cGVyJyBpcyBvbmx5IHZhbGlkIGluIGEgbWV0aG9kAGZyb3VuZABfX2JmX3JvdW5kAGJyZWFrL2NvbnRpbnVlIGxhYmVsIG5vdCBmb3VuZABvdXQgb2YgYm91bmQAZmluZABiaW5kAGludmFsaWQgaW5kZXggZm9yIGFwcGVuZABleHRyYW5lb3VzIGNoYXJhY3RlcnMgYXQgdGhlIGVuZAB1bmV4cGVjdGVkIGRhdGEgYXQgdGhlIGVuZAB1bmV4cGVjdGVkIGVuZABpbnZhbGlkIGluY3JlbWVudC9kZWNyZW1lbnQgb3BlcmFuZABpbnZhbGlkICdpbnN0YW5jZW9mJyByaWdodCBvcGVyYW5kAGludmFsaWQgJ2luJyBvcGVyYW5kAHRyaW1FbmQAcGFkRW5kAGJvbGQAJWxsZABnY19kZWNyZWZfY2hpbGQAcmVzb2x2ZV9zY29wZV9wcml2YXRlX2ZpZWxkAGNhbm5vdCBkZWxldGUgYSBwcml2YXRlIGNsYXNzIGZpZWxkAGV4cGVjdGluZyA8YnJhbmQ+IHByaXZhdGUgZmllbGQAJXMgaXMgbm90IGluaXRpYWxpemVkAGZpeGVkAHRvRml4ZWQAc2V0X29iamVjdF9uYW1lX2NvbXB1dGVkAHJlZ2V4IG5vdCBzdXBwb3J0ZWQAZXZhbCBpcyBub3Qgc3VwcG9ydGVkAFJlZ0V4cCBhcmUgbm90IHN1cHBvcnRlZAB0b1NvcnRlZABpbnRlcnJ1cHRlZAAhcy0+aXNfY29tcGxldGVkACVzIG9iamVjdCBleHBlY3RlZABpZGVudGlmaWVyIGV4cGVjdGVkAGJ5dGVjb2RlIGZ1bmN0aW9uIGV4cGVjdGVkAHN0cmluZyBleHBlY3RlZABmcm9tIGNsYXVzZSBleHBlY3RlZABmdW5jdGlvbiBuYW1lIGV4cGVjdGVkAHZhcmlhYmxlIG5hbWUgZXhwZWN0ZWQAbWV0YSBleHBlY3RlZABqc19hc3luY19tb2R1bGVfZXhlY3V0aW9uX3JlamVjdGVkAGpzX3NldF9tb2R1bGVfZXZhbHVhdGVkAG1lbW9yeSBhbGxvY2F0ZWQAbWVtb3J5IHVzZWQAdG9SZXZlcnNlZABkZXJpdmVkIGNsYXNzIGNvbnN0cnVjdG9yIG11c3QgcmV0dXJuIGFuIG9iamVjdCBvciB1bmRlZmluZWQAY2Fubm90IHNldCBwcm9wZXJ0eSAnJXMnIG9mIHVuZGVmaW5lZABjYW5ub3QgcmVhZCBwcm9wZXJ0eSAnJXMnIG9mIHVuZGVmaW5lZABmbGFncyBtdXN0IGJlIHVuZGVmaW5lZABVbmRlZmluZWQAcHJpdmF0ZSBjbGFzcyBmaWVsZCBpcyBhbHJlYWR5IGRlZmluZWQAJyVzJyBpcyBub3QgZGVmaW5lZABncm91cCBuYW1lIG5vdCBkZWZpbmVkAGlzV2VsbEZvcm1lZAB0b1dlbGxGb3JtZWQAYWxsU2V0dGxlZABqc19hc3luY19tb2R1bGVfZXhlY3V0aW9uX2Z1bGZpbGxlZABjYW5ub3QgYmUgY2FsbGVkAGlzU2VhbGVkACFzaC0+aXNfaGFzaGVkAEFycmF5QnVmZmVyIGlzIGRldGFjaGVkAGpzX2FycmF5X3RvU3BsaWNlZABhZGQAJSswN2QAJTA0ZAAlMDJkJTAyZABwJStkACVjJStkACUwMmQvJTAyZC8lMCpkACUuM3MgJS4zcyAlMDJkICUwKmQAcCVkACVjJWQAOiVkAGludmFsaWQgdGhyb3cgdmFyIHR5cGUgJWQAc2MAanNfZGVmX21hbGxvYwB0cnVuYwBnYwBleGVjAGJmX2ludGVnZXJfdG9fcmFkaXhfcmVjAC90bXAvcXVpY2tqcy9xdWlja2pzLmMAL3RtcC9xdWlja2pzL2xpYnJlZ2V4cC5jAC90bXAvcXVpY2tqcy9saWJiZi5jAC90bXAvcXVpY2tqcy9saWJ1bmljb2RlLmMAc3ViAHByb21pc2VfcmVhY3Rpb25fam9iAGpzX3Byb21pc2VfcmVzb2x2ZV90aGVuYWJsZV9qb2IAMGIAciAhPSBhICYmIHIgIT0gYgBxICE9IGEgJiYgcSAhPSBiAHJ3YQByICE9IGEAX19sb29rdXBTZXR0ZXJfXwBfX2RlZmluZVNldHRlcl9fAF9fbG9va3VwR2V0dGVyX18AX19kZWZpbmVHZXR0ZXJfXwBfX3Byb3RvX18AW1N5bWJvbC5zcGxpdF0AW1N5bWJvbC5zcGVjaWVzXQBbU3ltYm9sLml0ZXJhdG9yXQBbU3ltYm9sLmFzeW5jSXRlcmF0b3JdAFtTeW1ib2wubWF0Y2hBbGxdAFtTeW1ib2wubWF0Y2hdAFtTeW1ib2wuc2VhcmNoXQBbU3ltYm9sLnRvU3RyaW5nVGFnXQBbU3ltYm9sLnRvUHJpbWl0aXZlXQBbdW5zdXBwb3J0ZWQgdHlwZV0AW2Z1bmN0aW9uIGJ5dGVjb2RlXQBbU3ltYm9sLmhhc0luc3RhbmNlXQBbU3ltYm9sLnJlcGxhY2VdAFsAJTAyZDolMDJkOiUwMmQuJTAzZFoAUE9TSVRJVkVfSU5GSU5JVFkATkVHQVRJVkVfSU5GSU5JVFkAcC0+Y2xhc3NfaWQgPT0gSlNfQ0xBU1NfQVJSQVkAc3RhY2tfbGVuIDwgUE9QX1NUQUNLX0xFTl9NQVgALSUwMmQtJTAyZFQASlNfQXRvbUdldFN0clJUAG9wY29kZSA8IFJFT1BfQ09VTlQASlNfVkFMVUVfR0VUX1RBRyhmdW5jX3JldCkgPT0gSlNfVEFHX0lOVABCWVRFU19QRVJfRUxFTUVOVAAlMDJkOiUwMmQ6JTAyZCBHTVQASlNfVkFMVUVfR0VUX1RBRyhzZi0+Y3VyX2Z1bmMpID09IEpTX1RBR19PQkpFQ1QAdmFyX2tpbmQgPT0gSlNfVkFSX1BSSVZBVEVfU0VUVEVSAE1BWF9TQUZFX0lOVEVHRVIATUlOX1NBRkVfSU5URUdFUgBhc1VpbnROAGFzSW50TgBpc05hTgBEYXRlIHZhbHVlIGlzIE5hTgB0b0pTT04ARVBTSUxPTgBwLT5nY19vYmpfdHlwZSA9PSBKU19HQ19PQkpfVFlQRV9KU19PQkpFQ1QgfHwgcC0+Z2Nfb2JqX3R5cGUgPT0gSlNfR0NfT0JKX1RZUEVfRlVOQ1RJT05fQllURUNPREUgfHwgcC0+Z2Nfb2JqX3R5cGUgPT0gSlNfR0NfT0JKX1RZUEVfQVNZTkNfRlVOQ1RJT04ATkFOACUwMmQ6JTAyZDolMDJkICVjTQBzdGFja190b3AgPT0gTlVMTABzLT5sYWJlbF9zbG90c1tsYWJlbF0uZmlyc3RfcmVsb2MgPT0gTlVMTABsYWJlbF9zbG90c1tpXS5maXJzdF9yZWxvYyA9PSBOVUxMAHBycyAhPSBOVUxMAHNmLT5jdXJfc3AgIT0gTlVMTABzZiAhPSBOVUxMAG1yMSAhPSBOVUxMAHZhcl9raW5kICE9IEpTX1ZBUl9OT1JNQUwAYi0+ZnVuY19raW5kID09IEpTX0ZVTkNfTk9STUFMAGVuY29kZVVSSQBkZWNvZGVVUkkAUEkAc3BlY2lhbCA9PSBQVVRfTFZBTFVFX05PS0VFUCB8fCBzcGVjaWFsID09IFBVVF9MVkFMVUVfTk9LRUVQX0RFUFRIAHMtPnN0YXRlID09IEpTX0FTWU5DX0dFTkVSQVRPUl9TVEFURV9FWEVDVVRJTkcAbTEtPnN0YXR1cyA9PSBKU19NT0RVTEVfU1RBVFVTX0VWQUxVQVRJTkcAbTEtPnN0YXR1cyA9PSBKU19NT0RVTEVfU1RBVFVTX0xJTktJTkcAcHJlYyAhPSBCRl9QUkVDX0lORgBwcmVjMSAhPSBCRl9QUkVDX0lORgAwMTIzNDU2Nzg5QUJDREVGAFNJWkUATUFYX1ZBTFVFAE1JTl9WQUxVRQBOQU1FAGV2YWxfdHlwZSA9PSBKU19FVkFMX1RZUEVfR0xPQkFMIHx8IGV2YWxfdHlwZSA9PSBKU19FVkFMX1RZUEVfTU9EVUxFAExPRzJFAExPRzEwRQBzLT5zdGF0ZSA9PSBKU19BU1lOQ19HRU5FUkFUT1JfU1RBVEVfQVdBSVRJTkdfUkVUVVJOIHx8IHMtPnN0YXRlID09IEpTX0FTWU5DX0dFTkVSQVRPUl9TVEFURV9DT01QTEVURUQAbS0+c3RhdHVzID09IEpTX01PRFVMRV9TVEFUVVNfVU5MSU5LRUQgfHwgbS0+c3RhdHVzID09IEpTX01PRFVMRV9TVEFUVVNfTElOS0VEIHx8IG0tPnN0YXR1cyA9PSBKU19NT0RVTEVfU1RBVFVTX0VWQUxVQVRJTkdfQVNZTkMgfHwgbS0+c3RhdHVzID09IEpTX01PRFVMRV9TVEFUVVNfRVZBTFVBVEVEAG0xLT5zdGF0dXMgPT0gSlNfTU9EVUxFX1NUQVRVU19FVkFMVUFUSU5HIHx8IG0xLT5zdGF0dXMgPT0gSlNfTU9EVUxFX1NUQVRVU19FVkFMVUFUSU5HX0FTWU5DIHx8IG0xLT5zdGF0dXMgPT0gSlNfTU9EVUxFX1NUQVRVU19FVkFMVUFURUQAbTEtPnN0YXR1cyA9PSBKU19NT0RVTEVfU1RBVFVTX0xJTktJTkcgfHwgbTEtPnN0YXR1cyA9PSBKU19NT0RVTEVfU1RBVFVTX0xJTktFRCB8fCBtMS0+c3RhdHVzID09IEpTX01PRFVMRV9TVEFUVVNfRVZBTFVBVElOR19BU1lOQyB8fCBtMS0+c3RhdHVzID09IEpTX01PRFVMRV9TVEFUVVNfRVZBTFVBVEVEAG0tPnN0YXR1cyA9PSBKU19NT0RVTEVfU1RBVFVTX0xJTktFRABtLT5zdGF0dXMgPT0gSlNfTU9EVUxFX1NUQVRVU19VTkxJTktFRABVVEMAbS0+c3RhdHVzID09IEpTX01PRFVMRV9TVEFUVVNfRVZBTFVBVElOR19BU1lOQwBtb2R1bGUtPnN0YXR1cyA9PSBKU19NT0RVTEVfU1RBVFVTX0VWQUxVQVRJTkdfQVNZTkMAPGlucHV0PgA8aW5pdFNjcmlwdD4APGV2YWxTY3JpcHQ+ADxzZXQ+ADxhbm9ueW1vdXM+ADxjb21tRnVuPgA8Y2FsbEV4dGVybmFsRnVuY3Rpb24+ADxudWxsPgBiaWdpbnQgb3BlcmFuZHMgYXJlIGZvcmJpZGRlbiBmb3IgPj4+ACZxdW90OwBzZXRVaW50OABnZXRVaW50OABzZXRJbnQ4AGdldEludDgAbWFsZm9ybWVkIFVURi04AHJhZGl4IG11c3QgYmUgYmV0d2VlbiAyIGFuZCAzNgBzZXRVaW50MTYAZ2V0VWludDE2AHNldEludDE2AGdldEludDE2AGFyZ2MgPT0gNQBzZXRCaWdVaW50NjQAZ2V0QmlnVWludDY0AHNldEJpZ0ludDY0AGdldEJpZ0ludDY0AHNldEZsb2F0NjQAZ2V0RmxvYXQ2NABhcmdjID09IDMAYXRhbjIAbG9nMgBTUVJUMV8yAFNRUlQyAExOMgBjbHozMgBzZXRVaW50MzIAZ2V0VWludDMyAHNldEludDMyAGdldEludDMyAHNldEZsb2F0MzIAZ2V0RmxvYXQzMgBzdGFja19sZW4gPj0gMgBKU19BdG9tSXNOdW1lcmljSW5kZXgxAGpzX2ZjdnQxAEpTX0NvbXBhY3RCaWdJbnQxAGV4cG0xAHIgIT0gYTEgJiYgciAhPSBiMQBscy0+YWRkciA9PSAtMQBucSA+PSAxAHN0YWNrX2xlbiA+PSAxAHAtPmhlYWRlci5yZWZfY291bnQgPT0gMQBwLT5zaGFwZS0+aGVhZGVyLnJlZl9jb3VudCA9PSAxAHN0YWNrX2xlbiA9PSAxAGpzX2ZyZWVfc2hhcGUwAGxvZzEwAExOMTAAcC0+cmVmX2NvdW50ID4gMAB2YXJfcmVmLT5oZWFkZXIucmVmX2NvdW50ID4gMABtLT5wZW5kaW5nX2FzeW5jX2RlcGVuZGVuY2llcyA+IDAAc3RhY2tfc2l6ZSA+IDAAY3Bvb2xfaWR4ID49IDAAcnQtPmF0b21fY291bnQgPj0gMABscy0+cmVmX2NvdW50ID49IDAAcy0+aXNfZXZhbCB8fCBzLT5jbG9zdXJlX3Zhcl9jb3VudCA9PSAwAHAtPnJlZl9jb3VudCA9PSAwAGN0eC0+aGVhZGVyLnJlZl9jb3VudCA9PSAwAHNoLT5oZWFkZXIucmVmX2NvdW50ID09IDAAcC0+bWFyayA9PSAwAChwci0+dS5pbml0LnJlYWxtX2FuZF9pZCAmIDMpID09IDAAKG5ld19oYXNoX3NpemUgJiAobmV3X2hhc2hfc2l6ZSAtIDEpKSA9PSAwAGkgIT0gMABzaXplICE9IDAAXiRcLiorPygpW117fXwvADwvADAuAG1pc3NpbmcgYmluZGluZyBwYXR0ZXJuLi4uAGJpZ2ludCBhcmd1bWVudCB3aXRoIHVuYXJ5ICsAYXN5bmMgZnVuY3Rpb24gKgAKfSkAbGlzdF9lbXB0eSgmcnQtPmdjX29ial9saXN0KQBqID09IChzaC0+cHJvcF9jb3VudCAtIHNoLT5kZWxldGVkX3Byb3BfY291bnQpACFfX0pTX0F0b21Jc1RhZ2dlZEludChkZXNjcikAIWF0b21faXNfZnJlZShwKQAobnVsbCkAIChuYXRpdmUpAGpzX2NsYXNzX2hhc19ieXRlY29kZShwLT5jbGFzc19pZCkAbmlwX2NhdGNoOiBubyBjYXRjaCBvcCAocGM9JWQpAGluY29uc2lzdGVudCBjYXRjaCBwb3NpdGlvbjogJWQgJWQgKHBjPSVkKQBpbmNvbnNpc3RlbnQgc3RhY2sgc2l6ZTogJWQgJWQgKHBjPSVkKQBieXRlY29kZSBidWZmZXIgb3ZlcmZsb3cgKG9wPSVkLCBwYz0lZCkAc3RhY2sgb3ZlcmZsb3cgKG9wPSVkLCBwYz0lZCkAc3RhY2sgdW5kZXJmbG93IChvcD0lZCwgcGM9JWQpAGludmFsaWQgb3Bjb2RlIChvcD0lZCwgcGM9JWQpACg/OikAaWR4IDwgY291bnRvZihjYXNlX2NvbnZfdGFibGUxKQBubyBmdW5jdGlvbiBmaWxlbmFtZSBmb3IgaW1wb3J0KCkALV8uIX4qJygpACBhbm9ueW1vdXMoAFN5bWJvbCgAZXhwZWN0aW5nICd9JwBjbGFzcyBjb25zdHJ1Y3RvcnMgbXVzdCBiZSBpbnZva2VkIHdpdGggJ25ldycAZXhwZWN0aW5nICdhcycAdW5leHBlY3RlZCB0b2tlbiBpbiBleHByZXNzaW9uOiAnJS4qcycAdW5leHBlY3RlZCB0b2tlbjogJyUuKnMnAHJlZGVjbGFyYXRpb24gb2YgJyVzJwBkdXBsaWNhdGUgZXhwb3J0ZWQgbmFtZSAn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XwAAcF8AAAwLBQQCAgAAuwAAAHoAAAB7AAAAvAAAAHwAAAB9AAAAvQAAAHwAAAB9AAAAvgAAAFIAAABTAAAAvwAAAH4AAAB/AAAAwAAAAH4AAAB/AAAALwAAAIAAAACBAAAAwQAAAFIAAABTAAAAwgAAAIIAAACDAAAAAAAAAOkWAAAaFwAAJRcAAN0WAAAQFwAANBcAAPMWAAABFwAAY29weVdpdGhpbgBlbnRyaWVzAGZpbGwAZmluZABmaW5kSW5kZXgAZmluZExhc3QAZmluZExhc3RJbmRleABmbGF0AGZsYXRNYXAAaW5jbHVkZXMAa2V5cwB0b1JldmVyc2VkAHRvU29ydGVkAHRvU3BsaWNlZAB2YWx1ZXMAAAAAAAEBAgIDAwIDAAAAAAAAbnVsbABmYWxzZQB0cnVlAGlmAGVsc2UAcmV0dXJuAHZhcgB0aGlzAGRlbGV0ZQB2b2lkAHR5cGVvZgBuZXcAaW4AaW5zdGFuY2VvZgBkbwB3aGlsZQBmb3IAYnJlYWsAY29udGludWUAc3dpdGNoAGNhc2UAZGVmYXVsdAB0aHJvdwB0cnkAY2F0Y2gAZmluYWxseQBmdW5jdGlvbgBkZWJ1Z2dlcgB3aXRoAGNsYXNzAGNvbnN0AGVudW0AZXhwb3J0AGV4dGVuZHMAaW1wb3J0AHN1cGVyAGltcGxlbWVudHMAaW50ZXJmYWNlAGxldABwYWNrYWdlAHByaXZhdGUAcHJvdGVjdGVkAHB1YmxpYwBzdGF0aWMAeWllbGQAYXdhaXQAAGxlbmd0aABmaWxlTmFtZQBsaW5lTnVtYmVyAG1lc3NhZ2UAY2F1c2UAZXJyb3JzAHN0YWNrAG5hbWUAdG9TdHJpbmcAdG9Mb2NhbGVTdHJpbmcAdmFsdWVPZgBldmFsAHByb3RvdHlwZQBjb25zdHJ1Y3RvcgBjb25maWd1cmFibGUAd3JpdGFibGUAZW51bWVyYWJsZQB2YWx1ZQBnZXQAc2V0AG9mAF9fcHJvdG9fXwB1bmRlZmluZWQAbnVtYmVyAGJvb2xlYW4Ac3RyaW5nAG9iamVjdABzeW1ib2wAaW50ZWdlcgB1bmtub3duAGFyZ3VtZW50cwBjYWxsZWUAY2FsbGVyADxldmFsPgA8cmV0PgA8dmFyPgA8YXJnX3Zhcj4APHdpdGg+AGxhc3RJbmRleAB0YXJnZXQAaW5kZXgAaW5wdXQAZGVmaW5lUHJvcGVydGllcwBhcHBseQBqb2luAGNvbmNhdABzcGxpdABjb25zdHJ1Y3QAZ2V0UHJvdG90eXBlT2YAc2V0UHJvdG90eXBlT2YAaXNFeHRlbnNpYmxlAHByZXZlbnRFeHRlbnNpb25zAGhhcwBkZWxldGVQcm9wZXJ0eQBkZWZpbmVQcm9wZXJ0eQBnZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3IAb3duS2V5cwBhZGQAZG9uZQBuZXh0AHZhbHVlcwBzb3VyY2UAZmxhZ3MAZ2xvYmFsAHVuaWNvZGUAcmF3AG5ldy50YXJnZXQAdGhpcy5hY3RpdmVfZnVuYwA8aG9tZV9vYmplY3Q+ADxjb21wdXRlZF9maWVsZD4APHN0YXRpY19jb21wdXRlZF9maWVsZD4APGNsYXNzX2ZpZWxkc19pbml0PgA8YnJhbmQ+ACNjb25zdHJ1Y3RvcgBhcwBmcm9tAG1ldGEAKmRlZmF1bHQqACoATW9kdWxlAHRoZW4AcmVzb2x2ZQByZWplY3QAcHJvbWlzZQBwcm94eQByZXZva2UAYXN5bmMAZXhlYwBncm91cHMAaW5kaWNlcwBzdGF0dXMAcmVhc29uAGdsb2JhbFRoaXMAYmlnaW50AG5vdC1lcXVhbAB0aW1lZC1vdXQAb2sAdG9KU09OAE9iamVjdABBcnJheQBFcnJvcgBOdW1iZXIAU3RyaW5nAEJvb2xlYW4AU3ltYm9sAEFyZ3VtZW50cwBNYXRoAEpTT04ARGF0ZQBGdW5jdGlvbgBHZW5lcmF0b3JGdW5jdGlvbgBGb3JJbkl0ZXJhdG9yAFJlZ0V4cABBcnJheUJ1ZmZlcgBTaGFyZWRBcnJheUJ1ZmZlcgBVaW50OENsYW1wZWRBcnJheQBJbnQ4QXJyYXkAVWludDhBcnJheQBJbnQxNkFycmF5AFVpbnQxNkFycmF5AEludDMyQXJyYXkAVWludDMyQXJyYXkAQmlnSW50NjRBcnJheQBCaWdVaW50NjRBcnJheQBGbG9hdDMyQXJyYXkARmxvYXQ2NEFycmF5AERhdGFWaWV3AEJpZ0ludABNYXAAU2V0AFdlYWtNYXAAV2Vha1NldABNYXAgSXRlcmF0b3IAU2V0IEl0ZXJhdG9yAEFycmF5IEl0ZXJhdG9yAFN0cmluZyBJdGVyYXRvcgBSZWdFeHAgU3RyaW5nIEl0ZXJhdG9yAEdlbmVyYXRvcgBQcm94eQBQcm9taXNlAFByb21pc2VSZXNvbHZlRnVuY3Rpb24AUHJvbWlzZVJlamVjdEZ1bmN0aW9uAEFzeW5jRnVuY3Rpb24AQXN5bmNGdW5jdGlvblJlc29sdmUAQXN5bmNGdW5jdGlvblJlamVjdABBc3luY0dlbmVyYXRvckZ1bmN0aW9uAEFzeW5jR2VuZXJhdG9yAEV2YWxFcnJvcgBSYW5nZUVycm9yAFJlZmVyZW5jZUVycm9yAFN5bnRheEVycm9yAFR5cGVFcnJvcgBVUklFcnJvcgBJbnRlcm5hbEVycm9yADxicmFuZD4AU3ltYm9sLnRvUHJpbWl0aXZlAFN5bWJvbC5pdGVyYXRvcgBTeW1ib2wubWF0Y2gAU3ltYm9sLm1hdGNoQWxsAFN5bWJvbC5yZXBsYWNlAFN5bWJvbC5zZWFyY2gAU3ltYm9sLnNwbGl0AFN5bWJvbC50b1N0cmluZ1RhZwBTeW1ib2wuaXNDb25jYXRTcHJlYWRhYmxlAFN5bWJvbC5oYXNJbnN0YW5jZQBTeW1ib2wuc3BlY2llcwBTeW1ib2wudW5zY29wYWJsZXMAU3ltYm9sLmFzeW5jSXRlcmF0b3IAAAAAAAEAAAAFAAEUBQABFQUAARUFAAEXBQABFwEAAQABAAEAAQABAAEAAQABAAEAAQABAAIAAQUDAAEKAQEAAAECAQABAwIAAQECAAECAwABAgQAAQMGAAECAwABAwQAAQQFAAEDAwABBAQAAQUFAAECAgABBAQAAQMDAAEDAwABBAQAAQUFAAMCAQ0DAQENAwEADQMCAQ0DAgANAwABDQMDAQoBAQAAAQAAAAEBAgABAAAAAQICAAECAAABAQAAAQEAAAYAABgFAQEPAwIBCgECAQABAQEAAQEBAAUAARcFAAEXBQABFwUBABcFAQAXBQIAFwECAwABAwAABgAAGAYAABgGAQAYBQEBFwUBAhcFAgAXAQIBAAEDAAABAwEAAQIBAAECAgABAwAAAQMBAAEEAAAFAgEXBQEBFwECAgABAgEAAQICAAEDAgABAwIAAgMDBQYCARgCAwEFBgICGAYDAxgDAAEQAwEAEAMBARADAAERAwEAEQMBAREDAAESAwEAEgMBARIDAAAQAwABEAMBABADAQAQAwABEAMAARIDAQASAwEAEgMAABAFAQAWBQEAFgUAABYFAAEWBQAAFgEBAAABAgEAAQEBAAEBAQABAgIACgEAGgoCARoKAQAaCgEAGgoBABoKAQAaBwACGQcAAhkHAAIZBQACFwEBAQABAQMAAQEDAAEBAwACAwUFAQEBAAEBAgABAwAAAQQEAAIEBQUBAAAAAQECAAEBAgABAQIAAQEBAAEBAQABAQEAAQEBAAEBAQABAQIAAQECAAIAAAcCAAAHAgEABwEBAQABAQEAAQEBAAECAQAFAAEXAQIBAAECAQABAgEAAQIBAAECAQABAgEAAQIBAAECAQABAgEAAQIBAAECAQABAgEAAQIBAAECAQABAgEAAQIBAAECAQABAgEAAQIBAAECAQABAgEAAQIBAAEBAQABAgEAAQAAAAMAAAoDAAAKBQAAFgcAARkHAAEZBwEAGQcAARkLAAIbBwACGQcAAhkHAAEZBwEBGQcBAhkHAgAZBwEBGQUBARcBAgEABQEBEwUAABMBAAEBAQABAQEAAQEBAAEBAQABAQEAAQEBAAEBAQABAQEAAQECAAEGAwABCwIAAQgCAAEIAQABAAIAAQcCAQAHAgEBBwEAAQIBAAECAQABAgEAAQIBAQACAQEAAgEBAAIBAQACAQEBAgEBAQIBAQECAQEBAgEAAQMBAAEDAQABAwEAAQMBAQADAQEAAwEBAAMBAQADAQEBAwEBAQMBAQEDAQEBAwEAAQQBAAEEAQABBAEAAQQBAQAEAQEABAEBAAQBAQAEAQEBBAEBAQQBAQEEAQEBBAEBAQACAQAJAgEACQIAAAkDAAAMAQEBDgEBAQ4BAQEOAQEBDgEBAQABAQEAAQEBAAEBAQCEAAAAhQAAAIYAAAANABAAMAA0AEGgtwEL9RBbJwAAAwAAAAAAAACHAAAAdRMAAAEBAACIAAAAAAAAAFsvAAABAQAAiQAAAAAAAAC/IgAAAQIBAIoAAAAAAAAAEikAAAECAgCKAAAAAAAAALIpAAABAgQAigAAAAAAAACPIQAAAQIIAIoAAAAAAAAAJi4AAAECEACKAAAAAAAAAFcGAAABAiAAigAAAAAAAACpFAAAAQJAAIoAAAAAAAAACzYAAAMAAAABAAAAQgAAAP0rAAADAAAAAgAAAIsAAADeCgAAAwAAAAEAAACMAAAA9iQAAAMAAAAAAAAAjQAAAB44AAADAAAAAgAAAI4AAACZNwAAAwAAAAEAAACPAAAAhzcAAAMAAAABAAAAkAAAAKg3AAADAAAAAQAAAJEAAAA+NwAAAwAAAAIAAACSAAAATTcAAAEBAACTAAAAAAAAAHAKAAADAAAAAAwAAJQAAAC4NwAAAQMAAFgWAAAAAAAAwTkAAAMIAAAQXQAAAwAAAGcoAAADAAAAAgAAAJUAAAB7BgAAAwAAAAMAAACWAAAAuDcAAAEDAADBOQAAAAAAABItAAADAAAAAgAAAJcAAABlDgAAAwAAAAIBAACYAAAAvA4AAAMAAAABAQAAmQAAAEwVAAADAAAAAQEAAJoAAAAeKAAAAwAAAAEBAACbAAAA2hoAAAMAAAAAAQAAnAAAAFYnAAABAgAAnQAAAAAAAABGJAAAAwAAAAEBAACeAAAAexMAAAMABAAAAQAAnwAAAAgQAAADAAAAAAEAAJ8AAAB8FAAAAwAIAAABAACfAAAAXjcAAAMJAAB8FAAA/////7g3AAABAwAA3RsAAAAAAACENQAAAwABAAEBAACYAAAATBUAAAMAAQABAQAAmgAAAB4oAAADAAEAAQEAAJsAAADaGgAAAwABAAABAACcAAAAVicAAAECAQCdAAAAAAAAAEYkAAADAAEAAQEAAJ4AAAB7EwAAAwABAAABAACfAAAACBAAAAMJAAB7EwAA/////143AAADCQAAexMAAP////98FAAAAwAJAAABAACfAAAAuDcAAAEDAADEDgAAAAAAAGUOAAADAAIAAgEAAJgAAAC8DgAAAwACAAEBAACZAAAATBUAAAMAAgABAQAAmgAAAB4oAAADAAIAAQEAAJsAAAC4NwAAAQMAANkbAAAAAAAAhDUAAAMAAwABAQAAmAAAAEwVAAADAAMAAQEAAJoAAAAeKAAAAwADAAEBAACbAAAAuDcAAAEDAADADgAAAAAAAHAKAAADAAAAAAwAAKAAAAC4NwAAAQMAAEsWAAAAAAAAcAoAAAMAAQAADAAAoAAAALg3AAABAwAAPhYAAAAAAABKBwAAAwABAAIBAAChAAAATTcAAAEBAACTAAAAAAAAANIfAAADAAAAAgAAAKIAAAA5JAAAAwAAAAEAAACjAAAATwYAAAMAAAABAAAApAAAALg3AAABAwAAzigAAAAAAACDJwAAAwAAAAEBAAClAAAA9w4AAAMAAQABAQAApQAAAIshAAADAAAAAQEAAKYAAAABNQAAAwABAAEBAACmAAAAIAYAAAMAAgABAQAApgAAAOkvAAADAAAAAQAAAKcAAADfEQAAAwAAAAAAAACoAAAATTcAAAEBAACTAAAAAAAAALg3AAABAwAATx0AAAAAAABwNwAAAwAAAAAAAACpAAAAcAoAAAMAAAABAQAAqgAAABkcAAADAAEAAQEAAKoAAABoCAAAAwACAAEBAACqAAAAcAoAAAMAAAABAQAAqwAAABkcAAADAAEAAQEAAKsAAABoCAAAAwACAAEBAACrAAAAuDcAAAEDAADBFgAAAAAAALg3AAABAwAAIx0AAAAAAAC0JgAAAwAAAAAAAACsAAAA9iQAAAMAEwAAAQAArQAAAM03AAADAAAAAQAAAK4AAABvJQAAAwADAAABAACtAAAATiUAAAMJAABvJQAA/////2MlAAADACMAAAEAAK0AAAD/JAAAAwARAAABAACtAAAAHyUAAAMAEgAAAQAArQAAAD8lAAADADMAAAEAAK0AAAAMJQAAAwAxAAABAACtAAAALCUAAAMAMgAAAQAArQAAACAOAAADAAAAAAAAAK8AAAD+KQAAAwAAAAAAAACsAAAA6BoAAAMAAQEAAQAAsAAAAPwaAAADAAEAAAEAALAAAAAXGwAAAwAAAAABAACwAAAAKCMAAAMAEQAAAQAAsAAAAD0jAAADABAAAAEAALAAAAA0KAAAAwAhAAABAACwAAAARygAAAMAIAAAAQAAsAAAAIkRAAADADEAAAEAALAAAACeEQAAAwAwAAABAACwAAAAjRMAAAMAQQAAAQAAsAAAAKYTAAADAEAAAAEAALAAAAAFFQAAAwBRAAABAACwAAAAHhUAAAMAUAAAAQAAsAAAAMQUAAADAGEAAAEAALAAAADnFAAAAwBgAAABAACwAAAAOQcAAAMAcQAAAQAAsAAAAEAHAAADAHAAAAEAALAAAAD2KQAAAwAAAAEAAACxAAAAtBQAAAMAcQYBAQAAsgAAANQUAAADAHAGAQEAALIAAAD6FAAAAwBxBQIBAACyAAAAEBUAAAMAcAUCAQAAsgAAAIITAAADAHEEAwEAALIAAACYEwAAAwBwBAMBAACyAAAAgBEAAAMAcQMEAQAAsgAAAJIRAAADAHADBAEAALIAAAAsKAAAAwAxAgEBAACyAAAAPCgAAAMAMAIBAQAAsgAAAB8jAAADADEBAgEAALIAAAAxIwAAAwAwAQIBAACyAAAA4BoAAAMAAAABAAAAswAAAPAaAAADADEAAwEAALIAAAAIGwAAAwAwAAMBAACyAAAAvzkAAAMAAAABAAAAtAAAAFN1bk1vblR1ZVdlZFRodUZyaVNhdABBoMgBCyRKYW5GZWJNYXJBcHJNYXlKdW5KdWxBdWdTZXBPY3ROb3ZEZWMAQdDIAQu2Dh8AAAAcAAAAHwAAAB4AAAAfAAAAHgAAAB8AAAAfAAAAHgAAAB8AAAAeAAAAHwAAAHUIAAADAAAAAAAAALUAAABnKAAAAwAAAAEAAAC2AAAAYj8AAAMAAAAHAAAAtwAAAJucnZ6foaKjrq+woAAAAAD2JAAAAwAAAAAAAAC4AAAAtCYAAAMAAAAAAAAAuQAAALg3AAABAwAAmw0AAAAAAACYOQAAAwAAAAIBAAC6AAAAoDkAAAMAAQACAQAAugAAAPYkAAADAAAAAAAAALsAAACwKwAAAwMAADcXAAAAAAAASC0AAAMDAABsSwAAAAAAACUoAAADAAAAAgAAALwAAADLJgAAAwAAAAEBAAC9AAAAvCYAAAMAAAACAAAAvgAAAJwFAAADAAAAAwEAAL8AAABrFAAAAwAAAAIAAADAAAAAzxMAAAMAAAABAAAAwQAAAAgTAAADAAAAAQAAAMIAAABKBwAAAwAAAAIBAAChAAAACBAAAAMAAAABAQAAwwAAAHsTAAADAAEAAQEAAMMAAAB8FAAAAwACAAEBAADDAAAAMiwAAAMAAAABAQAAxAAAALESAAADAAAAAQEAAMUAAACuFQAAAwAAAAIBAADGAAAAxREAAAMAAAABAAAAxwAAADYTAAADAAAAAgAAAMgAAACFHwAAAwAAAAIAAADJAAAAuiIAAAMAAAABAQAAygAAAHwnAAADAAEAAQEAAMoAAABBNQAAAwAAAAEBAADLAAAAlR8AAAMAAQABAQAAywAAAHURAAADAAAAAQAAAMwAAACEFAAAAwAAAAEAAADNAAAAEhwAAAMAAAACAAAAzgAAAPYkAAADAAAAAAAAAM8AAAA/JQAAAwAAAAAAAADQAAAAtCYAAAMAAAAAAAAA0QAAAFYFAAADAAAAAQAAANIAAADaJgAAAwAAAAEAAADTAAAAoiwAAAMAAAABAAAA1AAAADQ3AAABAQAA1QAAANYAAAAjNwAAAwAAAAIBAADXAAAAATcAAAMAAQACAQAA1wAAABI3AAADAAAAAQEAANgAAADwNgAAAwABAAEBAADYAAAAiiEAAAMAAAABAAAA2QAAACQGAAADAAAAAgEAANoAAADvMAAAAwAAAAEAAADbAAAA9iQAAAMAAAAAAAAA3AAAAAk4AAADAAAAAQAAAN0AAAC1KwAAAQEAAN4AAAAAAAAAuBoAAAEBAADfAAAAAAAAAF43AAADAAAAAAAAAKkAAADnDwAAAwAAAAEAAADgAAAAWiMAAAMAAAACAAAA4QAAAOMPAAADAAAAAQAAAOIAAAAaBgAAAwAAAAEBAADjAAAA2CkAAAMAAQABAQAA4wAAAEYkAAADAAIAAQEAAOMAAADNGwAAAwADAAEBAADjAAAATRgAAAMABAABAQAA4wAAAFQvAAADAAAAAQEAAOQAAADhDQAAAwABAAEBAADkAAAASSEAAAMAAAABAAAA5QAAAOowAAADAAAAAQEAAOYAAADABwAAAwABAAEBAADmAAAAFgsAAAMAAgABAQAA5gAAALIHAAADAAMAAQEAAOYAAACgJgAAAwAAAAEAAADnAAAAqCYAAAMAAAABAAAA6AAAAKAUAAADAAAAAQAAAOkAAAAkHwAAAwAAAAEBAADqAAAA9iQAAAMAAAAAAAAA6wAAAD8lAAADAAEAAAEAAOoAAACEGwAAAwAAAAABAADsAAAA4iMAAAMAAAABAQAA7QAAAO8NAAADAAEAAAEAAOwAAADtDQAAAwABAAEBAADtAAAAXygAAAMAAAAAAAAA7gAAAN4zAAADAAAAAAAAAO8AAAAnCwAAAwAAAAEAAADwAAAAvTIAAAMAAAABAAAA8QAAANwvAAADAAAAAgEAAPIAAADiLwAAAwABAAIBAADyAAAAejUAAAMAAAACAAAA8wAAAC0fAAADAAAAAgAAAPQAAADRGwAAAwABAAEBAAD1AAAAzA8AAAMAAAAAAQAA9QAAAHsTAAADAAEAAAEAADUAAABeNwAAAwkAAHsTAAD/////CBAAAAMAAAAAAQAANQAAAHwUAAADAAIAAAEAADUAAAAmBwAAAwAAAAEAAAD2AAAAXiAAAAMAAAABAAAA9wAAAPglAAADAAAAAAAAAPgAAABNNwAAAQEAAJMAAAAAAAAAcAoAAAMAAAAADAAANgAAALg3AAABAwAALxYAAAAAAACiDQAAAwAAAAIAAAD5AAAAwQ8AAAMAAAABAAAA+gAAAKc5AAADAAAAAQAAAPsAAAAVKAAAAwAAAAEAAAD8AAAAUTsAAAMAAAABAQAA/QAAAFUNAAADAAEAAQEAAP0AAABHOwAAAwAAAAEBAAD+AAAAQg0AAAMAAQABAQAA/gAAAIQpAAADAAAAAQAAAP8AAACCKQAAAwAAAAEAAAAAAQAA0QUAAAAGAAAAAAAAAADwf7s5AAAABgAAAAAAAAAA+H+BNAAAAAcAQZDXAQtlOh0AAAMAAAACAAAAAQEAALEbAAADAAAAAgAAAAIBAABBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWmFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6MDEyMzQ1Njc4OUAqXystLi8AQYDYAQuWA6wiAAADAAAAAQAAAAMBAABZMgAAAwAAAAEAAAAEAQAAEx8AAAMAAAABAAAABQEAAPYkAAADAAAAAQEAAAYBAAA/JQAAAwABAAABAAAGAQAAtCYAAAMAAAAAAAAABwEAAKINAAADCQAAog0AAAAAAADBDwAAAwkAAMEPAAAAAAAApzkAAAMAAAABAAAACAEAABUoAAADAAAAAQAAAAkBAAAZGgAAAwAAAAEAAAAKAQAAIxoAAAMAAAABAAAACwEAAGA8AAAABgAA////////739qPAAAAAYAAAEAAAAAAAAAuzkAAAAGAAAAAAAAAAD4f1g4AAAABgAAAAAAAAAA8P9GOAAAAAYAAAAAAAAAAPB/xjkAAAAGAAAAAAAAAACwPHY5AAAABgAA////////P0OHOQAAAAYAAP///////z/D9iQAAAMAAAAAAAAADAEAALQmAAADAAAAAAAAAA0BAAAELwAAAwAAAAEAAAAOAQAAmQwAAAMAAAABAAAADwEAAMEIAAADAAAAAQAAABABAADDIwAAAQQAQaDbAQuSB+cPAAADAAEAAQEAABEBAAD9DwAAAwAAAAEAAAASAQAA9g8AAAMAAAABAQAAEQEAAOMPAAADAAAAAQAAABMBAADqDwAAAwAAAAEAAAAUAQAA5zQAAAMAAAAAAAAAFQEAAPQ0AAADAAAAAAAAABYBAACgJgAAAwAAAAEBAAAXAQAAqCYAAAMAAQABAQAAFwEAAKAUAAADAAAAAQEAABgBAABqIwAAAwACAAEBAAAYAQAAXyMAAAMAAQABAQAAGAEAAC8kAAADAM0AAQEAABkBAACWIQAAAwDOAAEBAAAZAQAAPyQAAAMA0AABAQAAGQEAAL0NAAADAAAAAgAAABoBAACDJAAAAwAAAAIAAAAbAQAAkxUAAAMAAAACAAAAHAEAANwvAAADAAAAAgAAAB0BAADcDwAAAwAAAAEAAAAeAQAA7i8AAAMAAAACAQAAHwEAAJ8hAAADAAEAAgEAAB8BAAC8MQAAAwABAAEBAAAgAQAAOwsAAAMAAAABAQAAIAEAAGogAAADAAMAAAEAACEBAAC0MQAAAwACAAABAAAhAQAA1w0AAAMJAAC0MQAA/////zELAAADAAEAAAEAACEBAAD1DQAAAwkAADELAAD/////9iQAAAMAAAAAAAAAIgEAALQmAAADAAAAAAAAACIBAAANKAAAAwAAAAEAAAAjAQAAHSkAAAMAAAABAAAAJAEAANYoAAADAAEAAAEAACUBAAD0KAAAAwAAAAABAAAlAQAA4igAAAMAAQAAAQAAJQEAAAApAAADAAAAAAEAACUBAABeNwAAAwAFAAABAAA1AAAATRcAAAMAAAABAQAAJgEAANYlAAADAAEAAAEAACYBAADGIgAAAwACAAABAAAmAQAAwzEAAAMAAwAAAQAAJgEAAFMyAAADAAQAAAEAACYBAABDFwAAAwAFAAEBAAAmAQAA7SYAAAMABgABAQAAJgEAACwVAAADAAcAAAEAACYBAADHIgAAAwAIAAEBAAAmAQAAhCEAAAMACQAAAQAAJgEAABwtAAADAAoAAAEAACYBAACHNgAAAwALAAABAAAmAQAAchsAAAMADAAAAQAAJgEAAO42AACwKwAA1iUAAAAAAADGIgAAAAAAAOM2AAAAAAAAjQoAAAAAAACBDAAARxcAAIEMAABWJwAAHSMAAAAAAADuNgAALiYAAIQhAAAAAAAAHC0AAAAAAACHNgAAAAAAAHIbAEHA4gELmhJwCgAAAwAAAAAMAAAnAQAAuDcAAAEDAABfFgAAAAAAAN0jAAADCAAAcHEAACwAAAApHwAAAwAAAAIBAAAoAQAA+gcAAAMAAQACAQAAKAEAADQVAAADAAAAAQYAACkBAAA9FwAAAwAAAAEGAAAqAQAAqiEAAAMAAAABBgAAKwEAALgwAAADAAAAAQYAACwBAAAiCwAAAwAAAAEGAAAtAQAAHhIAAAMAAAABBgAALgEAAB8fAAADAAAAAQYAAC8BAAALIAAAAwAAAAEGAAAwAQAAJUEAAAMAAAACBwAAMQEAAB8SAAADAAAAAQYAADIBAABnGwAAAwAAAAEGAAAzAQAAUSQAAAMAAAABBgAANAEAAHEIAAADAAAAAgcAADUBAAAgHwAAAwAAAAEGAAA2AQAADCAAAAMAAAABBgAANwEAAAI2AAADAAAAAQYAADgBAACQHwAAAwAAAAEGAAA5AQAA6CMAAAMAAAABBgAAOgEAAAAkAAADAAAAAQYAADsBAAAGJAAAAwAAAAEGAAA8AQAA5yMAAAMAAAABBgAAPQEAAP8jAAADAAAAAQYAAD4BAAAFJAAAAwAAAAEGAAA/AQAAxUEAAAMAAAABBgAAQAEAAPgbAAADAAAAAQYAAEEBAAArQQAAAwAAAAEGAABCAQAAW0IAAAMAAAABBgAAQwEAACwLAAADAAAAAQYAAEQBAABiCwAAAwAAAAIAAABFAQAAYyAAAAMAAAAAAAAARgEAAKwwAAADAAAAAQYAAEcBAACMIAAAAwAAAAIAAABIAQAAQkEAAAMAAAABAAAASQEAALg3AAABAwAA3SMAAAAAAADJPAAAAAYAAGlXFIsKvwVAYUIAAAAGAAAWVbW7sWsCQD5BAAAABgAA7zn6/kIu5j++PAAAAAYAAP6CK2VHFfc/xDwAAAAGAAAO5SYVe8vbP1s7AAAABgAAGC1EVPshCUAwQQAAAAYAAM07f2aeoOY/OEEAAAAGAADNO39mnqD2P+8OAAADCAAAQHQAAA4AAAAkBgAAAwAAAAMAAABKAQAAyA4AAAMAAAACAAAASwEAAJwFAAADAAEAAwEAAL8AAAB5BQAAAwAAAAIAAABMAQAAvA4AAAMAAAACAAAATQEAAK4VAAADAAEAAgEAAMYAAADLJgAAAwABAAEBAAC9AAAATBUAAAMAAAACAAAATgEAADIsAAADAAEAAQEAAMQAAAA9EAAAAwAAAAEAAABPAQAAsRIAAAMAAQABAQAAxQAAAGUOAAADAAAAAwAAAFABAAC8JgAAAwAAAAIAAABRAQAAuDcAAAEDAADvDgAAAAAAAPYkAAADAAAAAAAAAFIBAAC0JgAAAwAAAAAAAABTAQAAzTcAAAMAAAABAAAAUwEAALg3AAABAwAAniAAAAAAAABaHAAAAQEAAFQBAAAAAAAAVBcAAAMAAAABAAAAVQEAAFgXAAADAAAAAQAAAFYBAABwCgAAAwAAAAEMAABXAQAAGRwAAAMAAQABDAAAVwEAAGgIAAADAAIAAQwAAFcBAAC4NwAAAQMAAMYWAAAAAAAAuDcAAAEDAAAoHQAAAAAAANIjAAABAhMAWAEAAAAAAADcLwAAAwATAAIBAABZAQAAuDcAAAEDAABfGgAAAAAAALEIAAADAAAAAQAAAFoBAABNNwAAAQEAAJMAAAAAAAAA0iMAAAECFABYAQAAAAAAANwvAAADABQAAgEAAFkBAAC4NwAAAQMAADgaAAAAAAAATTcAAAEBAACTAAAAAAAAAMMjAAABAQAAWwEAAAAAAADnDwAAAwAAAAEAAABcAQAAWiMAAAMAAAACAAAAXQEAADEaAAABAgAAXgEAAAAAAADSIwAAAQIAAF8BAAAAAAAAFQ4AAAECAABgAQAAAAAAAGUOAAADAAAAAQAAAGEBAAB7EwAAAwABAAABAABiAQAAXjcAAAMJAAB7EwAA/////wgQAAADAAAAAAEAAGIBAAB8FAAAAwACAAABAABiAQAAuDcAAAEBAABjAQAAAAAAAC0fAAADAAAAAgAAAGQBAAAaBgAAAwAIAAEBAADjAAAA2CkAAAMACQABAQAA4wAAAEYkAAADAAoAAQEAAOMAAADNGwAAAwALAAEBAADjAAAATRgAAAMADAABAQAA4wAAAFQvAAADAAgAAQEAAOQAAADhDQAAAwAJAAEBAADkAAAASSEAAAMAAAABAAAAZQEAAOowAAADAAAAAQEAAGYBAADABwAAAwABAAEBAABmAQAAFgsAAAMAAgABAQAAZgEAALIHAAADAAMAAQEAAGYBAABfKAAAAwAAAAAAAABnAQAA3jMAAAMAAAAAAAAAaAEAANwvAAADAAAAAgAAAGkBAACFBgAAAwAAAAIAAABqAQAAJwsAAAMAAAABAAAAawEAAL0yAAADAAAAAQAAAGwBAAAkHwAAAwAAAAEBAABtAQAAPyUAAAMAAQAAAQAAbQEAAKAmAAADAAAAAQEAAG4BAACoJgAAAwABAAEBAABuAQAAoBQAAAMA//8BAQAAbgEAAF4gAAADAAAAAQAAAG8BAAD4JQAAAwAAAAAAAABwAQAATTcAAAEBAACTAAAAAAAAADEaAAABAgEAXgEAAAAAAADSIwAAAQIBAF8BAAAAAAAAFQ4AAAECAQBgAQAAAAAAAGxAAAADABYAAQEAAHEBAABbQAAAAwAXAAEBAABxAQAAwEAAAAMAGAABAQAAcQEAAK1AAAADABkAAQEAAHEBAABlQQAAAwAaAAEBAABxAQAAUkEAAAMAGwABAQAAcQEAAPlAAAADABwAAQEAAHEBAADgQAAAAwAdAAEBAABxAQAAeUEAAAMAHgABAQAAcQEAABBBAAADAB8AAQEAAHEBAABkQAAAAwAWAAIBAAByAQAAUkAAAAMAFwACAQAAcgEAALdAAAADABgAAgEAAHIBAACjQAAAAwAZAAIBAAByAQAAXEEAAAMAGgACAQAAcgEAAEhBAAADABsAAgEAAHIBAADtQAAAAwAcAAIBAAByAQAA00AAAAMAHQACAQAAcgEAAG5BAAADAB4AAgEAAHIBAAAFQQAAAwAfAAIBAAByAQAAuDcAAAEDAAC4CABB5PQBC6UDAgAAAAAAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAAAAAAAAQAAAAEAAAArRAAA8EgAACVEAABzAQAAdAEAAHMBAAB1AQAAdgEAAHcBAAB4AQAAeQEAAHoBAAB7AQAAfAEAAH0BAAB+AQAAfQEAAH8BAACAAQAAgQEAAIIBAACDAQAAhAEAAIUBAACGAQAAHw8HAwEAAAAAAAAAgAAAAAAIAAAAAAEAAAAgAAAAAAQBAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAQAAAAEAAAABAAAAAQAAAAFAAAABQBBk/gBC5UCgAAAAABgTsJQp/TU1AAAAEAAAAAA0mggN8rlHgqNZIQxej4VuHUymC3EaVOdqqqqKquqqqowJ2EoVHpqaqEmiCbm/fM+gxMAJUSnyLoGZ7QjCcfAgvEplyLtPciy/X+eIStXraWIO8Mgqyl82gAAACAAAAAAfrVQH7OEWKzGLLIeb+KmihjhIR6yql0MIc2dHeQ0mEN4TCQdZQ16NokFtBwMPhesW9lLHA0r16ho1+obTM74mGk0kBvlcg8FP0M7GxVvsC51b+saOPxGnOs4oBoX/TsOYjBZGlaMjbPD9BUa5qKVK9ww1hn53n3MmZmZGZqZmZmA7F8ZMZRginvuKBn5Ik8Lz2r0GBjjBoxGMsIYPZ8K3ABBs/oBC7AEIEcDuDIAAABAJjxNSkcDuFL92dVZAAAAYI4GcGUmPE1q8KmzbkcDuHKOAGp2/dnVeW0/BX0AAACA337Mgo4GcIWuBe+HJjxNikXdjYzwqbOOAQXBkEcDuJJMeJqUjgBqltYJKJj92dWZj5R0m20/BZ2zxoieAAAAoDeta6HffsyiIxYjpI4GcKUAAAAAgACAAIEAggCDAIQAhQCGAIcAiACJAIoAiwCMAI0AjgCPAJAAkACRAJIAkwCUAJUAlgCWAJcAmACZAJoAmwCbAJwAnQCeAJ8AoACgAKEAogCjAKMApAClAKYApwCnAKgAqQCqAKoAqwCsAK0ArQCuAK8AsACwALEAsgCyALMAtAC1ALUAtgC3ALcAuAC5ALkAugC7ALsAvAC9AL0AvgC/AMAAwADBAMEAwgDDAMMAxADFAMUAxgDHAMcAyADJAMkAygDLAMsAzADMAM0AzgDOAM8A0ADQANEA0QDSANMA0wDUANQA1QDWANYA1wDXANgA2QDZANoA2gDbANsA3ADdAN0A3gDeAN8A4ADgAOEA4QDiAOIA4wDjAOQA5QDlAOYA5gDnAOcA6ADoAOkA6gDqAOsA6wDsAOwA7QDtAO4A7gDvAPAA8ADxAPEA8gDyAPMA8wD0APQA9QD1APYA9gD3APcA+AD4APkA+QD6APoA+wD7APwA/AD9AP0A/gD+AP8AIBQQDQwLCgoJCQgICAgIBwcHBwcHBwYGBgYGBgYGBgYGBgYAQfD+AQsqCgAJAA4AIAAhAKAAoQCAFoEWACALICggKiAvIDAgXyBgIAAwATD//gD/AEGk/wELLRAAAAD+//+H/v//BwAAAAAQAP8D/v//h/7//wfMfwAAcH8AAOB/AAABADAAOgBB4P8BCxEEADAAOgBBAFsAXwBgAGEAewBBgIACC7QNAQMFAQEBAQUFBQECAgMFBQEBAQICAwMFBQEBAREAAAAwmiAAAJowAHOBWgAwF2AAMAdsALOBbwAAF3AAAAd8AACBfwBAMIAAwwGYAJCBmABABpkAQJCcALSBpABALqUAMAG8AECGvABwgb8AAAHAADCBwABABMEAMAHDAECCwwAwgsQAQILFADABxwAwgccAMAHIAECCyAAwgckAMAHKAACBygAwAcsAMIHLAEACzAAAAc0AMAHOADCBzgAAAc8AMIHPAEAG0AAwAdMAQILTADCB1ABAAtYAMAHXAECC1wAwgtgAQITZADCB2wBAAtwAQALeAACB3wBQA+IAUIPjAFAD5QBAkOYAAIHuAEAS7wC0AfgAUIP4AEAC+gAwAfsAMIH7AEAo/AAwARABQBIRATEBHQFAgh0BMIEeATEBHwEBgh8BQIIgATCBIQEwASIBMIEiAUAKIwEBASgBAYEoAQEBKQEAgSkBAAEqAQACKwEAgSwBAIEtAQEBLgEAATABAYEwAQCBMQEBgTIBAQEzAQABNAEAgTQBAQE1AQGBNQEBATYBAIE3AQGBOAEAATkBAIE6AQGBPgEAAUABAQFBAQCBQQEBgUMBAAFEAQCBRAEAAkUBAAFGAQABSQEBgU4BAQFPAXOBogFABLgBQAK7AQCDvQEwgb8BMAHDATADxAEwAcYBMALHAdAByAEwkcgBMInRAQAB1gEAg9YB0wHYAQCR2AFzAeEBAInhAQAB5gEAguYBMIHnAXMB6AFzgegBc4HqAXMB6wEAgesBQBjsAXMB+AFzgfgBAAH5AQCB+QGgAfoBc4H6AUCC+wEwgfwBQAL9ATCD/gEwEAACMCAIAgAgGAIAECgCQCIwAkA2RQIwAWACQI5gAgCBZwJAYGgCMKaYAgCmsAK1gcMCMSZQCDGBYwgxgWYIACtoCACDfggRUNAJEAb4CSAG/Al0AUAOdIFADnQBQQ50gUEOdAFCDnSBQg50AUMOgIFDDoABRA4wK0gOMINeDgGBvA4Bgb4OAQHHDkB+AA9AGD8PtQFLD7aBSw+2AUwPtoFMD7cBTQ+AgU0PMAFPD0BgUA8ACIAPMAiEDwAGiA8wBowPAAiQDzAIlA8ACJgPMAicDwAGoA8wBqQPsAGoDwCBqA/TAakPAIGpD9MBqg8AgaoP0wGrDwCBqw8wgawPMIGtDzCBrg8wga8PAAiwDzAItA8AArgPAAS5DwACuw8BArwPAQK9DwECvg+3CMAPZwjED7gIyA9oCMwPuAjQD2gI1A8AAtgPuQHZD7GB2Q+5AdoPsQHbD9eB2w8wAtwPMALdD2EB3g9zAd8PuQHhD7KB4Q+6AeIPsgHjD9iB4w8wBOQPYgHmDwAC6A/QAekP0IHpD7AB6w/QgesPMALsDzAC7Q8BAvAP0wHxD9OB8Q+6AfIPAYHyD7AB8w/TgfMPMAL0DzAC9Q8xAfYPugH5D7KB+Q+7AfoPsgH7D9mB+w8wAvwPMAL9D2IB/g+gAZMQoAGVEKCBlRAxAZkQAQGnEDEQsBABELgQQILBEDEaWxIBGmgSMTAAFgEwGBZAAjAWMAExFjCBMRYwATIWAIEyFgABMxZAhjMWMIE2FjABNxYwgTcWMAE4FkACORZAgjoWMAI/FkBkQBZAhHUWQAJ5FgAmgBYAgZMWAIGWFkAuIFNAHEBTQA6RU0A+mVNAhLxTMIG+U0AKv1NAgsVTMIHGU0AEyFMBAcpTQBTLUzAB1VMwgdVTMAHWUzCB1lMwAddTMAHYUzCB2FMwAdlTMYHZU0AQ2lMxAeJTMIHiUzAB41NAhONTQALoU0AE61NAgvpTAYGpVSBQuFWyAYB9soGAfbIBgX3agYF92gGCfbOBgn2zAYN9u4GJfbsBin27gYp9vAGLfbuBi30xmpB/AZqgfzEoAIIBKBSCMSRYggEkbIIxC7iCMQ++gjEHxoIxAsqCAYvLggGP0YIBh9mCAYLdgjEzQIYBM2CGMSBQjAEgYIwxICC3ASAwtzEigPQBIpH0AAAAAAAAAABAqYCOgPyA04CMgI2BjQKA4YCRhZoBAAERAAEECAEIMAgBFSAAOZkxnYRAlIDWgqaAQWKApoBLcoBMAvgCgI+AsEDbCIBB0ICMgI+M5AMBiQAUKBARAgEYCyRLJgEBhuWAYHm2gUCRgb2IlAWAmICiAICbEoJDNKIGgI1gXBUBEKmAiGDMRNSAxgEICQuAiwAGgMADDwaAmwMEABaAQVOBmICYgJ6AmICegJiAnoCYgJ6AmAdHM4mAky1BBL1QwZmFmYWZAEHAjQILFbkC4MAdIOUsILEHIcHWIUrxAYrxAQBB4I0CC+EFpgWAioCiAIDGAwADAYFB9kC/GRiICIBA+oZAzgSAsKwAAQEAq4CKhYmKAKKAiZSPgOQ4iQOgAICdmtqKuYoYCJeXqoKrBg2HqLm2AAM7AoaJgYyAjoC5Ax+Ak4GZAYG4AwsJEoCdCoCKgbgDIAuAk4GVKIC5AQAfBoGKgZ2AvICLgLECgLYAFBAegYqBnIC5AQUEgZOBm4G4Cx+Ak4GcgMcGEIDZAYaKiOEBiIgAhsiBmgAAgLaNBAGEioCjiIDlGCgJgZgLgo+DjAENgI6A3YBCX4JDsYKcgZ2BnYG/CDcBihAgrISygMCBoYD1E4GIBYJA2gmAuQAwAAE9iQimB56wg68AIASAp4iLgZ8ZCIK3AAoAgrk5gb+F0RCMBhgoEbG+jICh5EG8AIKKgoyCjIKMgYsngYkBAYSwIIkAjICPjLKgS4qB8IL8gI6A35+ugEHUgKMaJIDchdyCYG8VgEThhUENgOEYiQCbg8+BjaHNgJaC5hIPAgOAmAyAQJaBmZGMgKWHmIqtgq8BGYGQgJSBwSkJgYsHgKKAioCyABEMCICagI0MCIDjhIiC+AEDgGBPL4BAkpBCPI8Qi4+hAYBAqAYFgIqAogCAroCsgcKAlIJCAIBA4YBAlIREBCipgIhCRRAMg6cTgECkgUI8g0GCgc+CxYqwg/qAtY6oAYGJgrAZCQOAiYCxgqMgh72Ai4GziIkZgN4RAA0BgECcAoeUgbgKgKQyhEDCORCAloDTKAMIgUDtHQiBmoHUOQCB6QABKIDkERiEQQKIAUD/CAOAQI8ZC4CfiacpH4CIKYKtjAFBlTAogNGVDgEB+SoACDCAxwoAgEFagYqBsyQAgFTskIWOYDaZhLqGiINECoC+kL8IgWBAChgwgUydCINSW62BlkIfgoiPDp2DQJOCR7q2g7E4jYCVII5FTzCQDgEEhL2ggECfjUFvgLyDQfqEQ9+G7IdKroRsDACAnd//QO8AQdCTAgtFvgUA/gcAUgqgwQsAgg0APxCA1BdAzxog9RwAgCAAFqAAxqgAwqpgVv4gsQcBdRAB6xIhQRYBXBoBQx8BLs9BJeAB8AEOAEGglAIL1A7AmYWZroCJAwSWgJ6AQcmDi40mAIBAgCAJGAUAEACTgNKAQIqHQKWApQiFqMaaG6yqogjiAI4OgYkRgI8AnZzYioCXoIgLBJUYiAKAlpiGioSXBZCpubUQkQaJjo8fCYGVBgATEI+AjAiCjYGJBysJlQYBAQGeGICSgo+IAoCVBgEEEJGAjoGWgIo5CZUGAQQQnQiCjoCQACoQGggACgoSi5WAszgQloCPEJkRAYGdAzgQloCJBBCeCIGOgZCIAoCoCI8EF4KXLJGCl4CIAA65rwGLhrkIACCXAICJAYgBIICUg5+AvjijmoTyqpOAjysaAg4TjIuAkKUAIIGqgEFMAw4AA4GoA4GgAw4AA4GOgLgDgcKkj4/VDYJCa4GQgJmEyoKKhpGMko2RjYwCjrOiA4DC2IaoAITFiZ6wnQyKq4OZtZaItNGA3K6Qh7WdjIGJq5mjqIKJo4GIhqoKqBgoCgRAv79BFQ2BpQ0PAAAAgJ6BtAYAEgYTDYOMIgbzgIyAj4zkAwGJAA0oAACAjwskGJCoSnZA5CsRi6UAIIG3MI+WiDAwMDAwMDCGQiWCmIg0DIPVHIDZA4SqgN2Qn6+PQf9Zv79gVozCrYFBDIKPiYGTro+egc+miIHmgb8hAASXjwIDgJacs42xvSoAgYqbiZaYnIaum4CPIImJIKiWEIeTlhCCsQARDAgAlxGKMospKYWIMDCqgI2F8pxgK6OLloOwYCEDQW2B6aWGiyQAiYCMBAABAYDroEFqkb+BtaeL8yBAhqOZhZmK2BUNDQqii4CZgJIBgI6BjaH6xLRBCpyCsK6fjJ2EpYmdgaMfBKlAnZGjg6ODp4ezi4qAjgYBgIqAjgYBwkE2iJWJh5coqYCIxCkAqwEQgZaJloiewJIBiZWJmcW3Kb+AjhgQnKmcgpyiOJuatYmViZKMke3ItrKMsoyjQVupKc2ciQeVqZGtlJqWi7S4CYCMrJ+YmaOcAQeiEIuvjYOUAICikYCYkoG+MAAYjoCJhq6lOQmVBgEEEJGAi4RAnbSRg5OCna+TCIBAt66og6Ovk4C6qoyAxpqkhkC4q/O/njkBOAiXjgCA3TmmjwCAm4CJpzCUgIqtkoCRyEEGiICkkICwne8wCKWUgJgoCJ+NgEFGko4AjICh+4DOQ5nl7pBAw0pL4I5EL5CFT7hCRmAhuEI4hp6QzpCdka+Pg56UhJJCr7//yiDBjL8IgJtX94dE1amIYCLmGDAIQSKOgJwRgI0fQYtJA+qEjIKIholXZdSAxgEICQuAiwAGgMADDwaAmwMEABaAQVOBmICYgJ6AmICegJiAnoCYgJ6AmAdHM54tQQS9QJGsiYaPgEFAnZGrQeObQvMwGAiOgEDEusMwRLMYmgEACICJAwAAKBgAAAIBAAgAAAAAAQALBgMDAICJgJAiBICQUUNgpt+fUDmFQN2BVoGNXTBMHkIdReFTSoRQXwAAAAD2AyCmBwCpCSCxCgC6CyA7DSDHDiBJEgCbFgCsGQDAHYCAICBwLQAAMgDapwBMqiDH1yD8/SCdAiGWBQHzCAGzDCFzEWE0EwEbFyGKGgE0HyG/agEjsaGt1AFv1wH/52Fe7gHh6yKwIwMAAAAAAAAAr4mkgNaAQkfvloBA+oRBCKwAAQEAx4qvnijkMSkIGYmWgJ2a2oqOiaCIiICXGIgCBKqCu4epl4CgtRCRBokJiZCCtwAxCYKIgIkJiY0BgrcAIwkSgJOLEIqCtwA4EIKTCYmJKIK3ADEJFoKJCYmRgLoiEIOIgI2Jj4S2ADAQHoGKCYmQgrcAMBAegYoJiRCLg7YIMBCDiICJCYmQgsUDKAA9iQm8AYaLOInWAYiKMIm9DYmKAAADgbCTAYSKgKOIgOOTgImLGxARMoOMi4COQr6CiIhDn4ObgpyBnYG/n4gBiaAQikCOgPWLg4uJif+Ku4S4iYCcgYqFiZWNgI+whK6QiomQiIuCnYyBiauNr5OHiYWJ9RCUGCgKQMW/Qj6BkoD6jBiCi0v9gkCMgN+fQimF6IFgdYSJxAOJn4HPgUEPAgOAliOA0oGxkYmJhZGMipuHmIyrg66NjomKgImJro2LBwmJoIKxABEMCICoJIFA6zgJiWBPI4BC4I+PjxGXgkC/iaSAQryAQOGAQJSEQSSJRVYQDIOnE4BApIFCPB+JQXCBz4LFirCD+YK0jp6KCYmDrIowrIkqo42AiSGrgIuCr407gIvRiygIQJyLhIkrtggxCYKIgIkJMoRAv5GIiRjQk4uJQNQxiJqB0ZCOidCMh4nSjoOJQPGOQKSJxSgJGACBi4n2MTKAm4mnMB+AiIqtj0GUOIePibeVgI35KgAIMAeJryAIJ4lBSIOICICvMoSMiVTlBY5gNgmJ1YmlhLqGmIlD9AC2M9CAioFgTKqBUmCtgZZCHSIvOYadg0CTgkWIsUH/toOxOI2AlSCORU8wkA4BBOOAQJ+GiIlBY4C8jUHxjUPVhuw0iVKViWwFBUDvAEGAowILhBP6BgBwCQDwCkBXDADwDWDHDyDqF0AFGwBBIAAMqIA3qiBQ/iA6DSF0EQFaFCFEGYFaHaH1aiFF0kGv4iHwAQ4AQWRsYW0sQWRsbQBBaG9tLEFob20AQW5hdG9saWFuX0hpZXJvZ2x5cGhzLEhsdXcAQXJhYmljLEFyYWIAQXJtZW5pYW4sQXJtbgBBdmVzdGFuLEF2c3QAQmFsaW5lc2UsQmFsaQBCYW11bSxCYW11AEJhc3NhX1ZhaCxCYXNzAEJhdGFrLEJhdGsAQmVuZ2FsaSxCZW5nAEJoYWlrc3VraSxCaGtzAEJvcG9tb2ZvLEJvcG8AQnJhaG1pLEJyYWgAQnJhaWxsZSxCcmFpAEJ1Z2luZXNlLEJ1Z2kAQnVoaWQsQnVoZABDYW5hZGlhbl9BYm9yaWdpbmFsLENhbnMAQ2FyaWFuLENhcmkAQ2F1Y2FzaWFuX0FsYmFuaWFuLEFnaGIAQ2hha21hLENha20AQ2hhbSxDaGFtAENoZXJva2VlLENoZXIAQ2hvcmFzbWlhbixDaHJzAENvbW1vbixaeXl5AENvcHRpYyxDb3B0LFFhYWMAQ3VuZWlmb3JtLFhzdXgAQ3lwcmlvdCxDcHJ0AEN5cmlsbGljLEN5cmwAQ3lwcm9fTWlub2FuLENwbW4ARGVzZXJldCxEc3J0AERldmFuYWdhcmksRGV2YQBEaXZlc19Ba3VydSxEaWFrAERvZ3JhLERvZ3IARHVwbG95YW4sRHVwbABFZ3lwdGlhbl9IaWVyb2dseXBocyxFZ3lwAEVsYmFzYW4sRWxiYQBFbHltYWljLEVseW0ARXRoaW9waWMsRXRoaQBHZW9yZ2lhbixHZW9yAEdsYWdvbGl0aWMsR2xhZwBHb3RoaWMsR290aABHcmFudGhhLEdyYW4AR3JlZWssR3JlawBHdWphcmF0aSxHdWpyAEd1bmphbGFfR29uZGksR29uZwBHdXJtdWtoaSxHdXJ1AEhhbixIYW5pAEhhbmd1bCxIYW5nAEhhbmlmaV9Sb2hpbmd5YSxSb2hnAEhhbnVub28sSGFubwBIYXRyYW4sSGF0cgBIZWJyZXcsSGVicgBIaXJhZ2FuYSxIaXJhAEltcGVyaWFsX0FyYW1haWMsQXJtaQBJbmhlcml0ZWQsWmluaCxRYWFpAEluc2NyaXB0aW9uYWxfUGFobGF2aSxQaGxpAEluc2NyaXB0aW9uYWxfUGFydGhpYW4sUHJ0aQBKYXZhbmVzZSxKYXZhAEthaXRoaSxLdGhpAEthbm5hZGEsS25kYQBLYXRha2FuYSxLYW5hAEthd2ksS2F3aQBLYXlhaF9MaSxLYWxpAEtoYXJvc2h0aGksS2hhcgBLaG1lcixLaG1yAEtob2praSxLaG9qAEtoaXRhbl9TbWFsbF9TY3JpcHQsS2l0cwBLaHVkYXdhZGksU2luZABMYW8sTGFvbwBMYXRpbixMYXRuAExlcGNoYSxMZXBjAExpbWJ1LExpbWIATGluZWFyX0EsTGluYQBMaW5lYXJfQixMaW5iAExpc3UsTGlzdQBMeWNpYW4sTHljaQBMeWRpYW4sTHlkaQBNYWthc2FyLE1ha2EATWFoYWphbmksTWFoagBNYWxheWFsYW0sTWx5bQBNYW5kYWljLE1hbmQATWFuaWNoYWVhbixNYW5pAE1hcmNoZW4sTWFyYwBNYXNhcmFtX0dvbmRpLEdvbm0ATWVkZWZhaWRyaW4sTWVkZgBNZWV0ZWlfTWF5ZWssTXRlaQBNZW5kZV9LaWtha3VpLE1lbmQATWVyb2l0aWNfQ3Vyc2l2ZSxNZXJjAE1lcm9pdGljX0hpZXJvZ2x5cGhzLE1lcm8ATWlhbyxQbHJkAE1vZGksTW9kaQBNb25nb2xpYW4sTW9uZwBNcm8sTXJvbwBNdWx0YW5pLE11bHQATXlhbm1hcixNeW1yAE5hYmF0YWVhbixOYmF0AE5hZ19NdW5kYXJpLE5hZ20ATmFuZGluYWdhcmksTmFuZABOZXdfVGFpX0x1ZSxUYWx1AE5ld2EsTmV3YQBOa28sTmtvbwBOdXNodSxOc2h1AE55aWFrZW5nX1B1YWNodWVfSG1vbmcsSG1ucABPZ2hhbSxPZ2FtAE9sX0NoaWtpLE9sY2sAT2xkX0h1bmdhcmlhbixIdW5nAE9sZF9JdGFsaWMsSXRhbABPbGRfTm9ydGhfQXJhYmlhbixOYXJiAE9sZF9QZXJtaWMsUGVybQBPbGRfUGVyc2lhbixYcGVvAE9sZF9Tb2dkaWFuLFNvZ28AT2xkX1NvdXRoX0FyYWJpYW4sU2FyYgBPbGRfVHVya2ljLE9ya2gAT2xkX1V5Z2h1cixPdWdyAE9yaXlhLE9yeWEAT3NhZ2UsT3NnZQBPc21hbnlhLE9zbWEAUGFoYXdoX0htb25nLEhtbmcAUGFsbXlyZW5lLFBhbG0AUGF1X0Npbl9IYXUsUGF1YwBQaGFnc19QYSxQaGFnAFBob2VuaWNpYW4sUGhueABQc2FsdGVyX1BhaGxhdmksUGhscABSZWphbmcsUmpuZwBSdW5pYyxSdW5yAFNhbWFyaXRhbixTYW1yAFNhdXJhc2h0cmEsU2F1cgBTaGFyYWRhLFNocmQAU2hhdmlhbixTaGF3AFNpZGRoYW0sU2lkZABTaWduV3JpdGluZyxTZ253AFNpbmhhbGEsU2luaABTb2dkaWFuLFNvZ2QAU29yYV9Tb21wZW5nLFNvcmEAU295b21ibyxTb3lvAFN1bmRhbmVzZSxTdW5kAFN5bG90aV9OYWdyaSxTeWxvAFN5cmlhYyxTeXJjAFRhZ2Fsb2csVGdsZwBUYWdiYW53YSxUYWdiAFRhaV9MZSxUYWxlAFRhaV9UaGFtLExhbmEAVGFpX1ZpZXQsVGF2dABUYWtyaSxUYWtyAFRhbWlsLFRhbWwAVGFuZ3V0LFRhbmcAVGVsdWd1LFRlbHUAVGhhYW5hLFRoYWEAVGhhaSxUaGFpAFRpYmV0YW4sVGlidABUaWZpbmFnaCxUZm5nAFRpcmh1dGEsVGlyaABUYW5nc2EsVG5zYQBUb3RvLFRvdG8AVWdhcml0aWMsVWdhcgBWYWksVmFpaQBWaXRoa3VxaSxWaXRoAFdhbmNobyxXY2hvAFdhcmFuZ19DaXRpLFdhcmEAWWV6aWRpLFllemkAWWksWWlpaQBaYW5hYmF6YXJfU3F1YXJlLFphbmIAQZC2AgvyIMAZmUeFGZlHrhmAR44ZgEeEGZZHgBmeR4AZ4WBHphmER4QZgQ2TGeAPOIMsgBmCLAGDLIAZgCwDgCyAGYAsgBmCLACALACTLAC+LI0ajyzgJB2BOOBIHQClBQGxBQGCBQC2NQeaNQOFNQqEBIAZhQSAGY0EgBmCBIAZnwSAGYkEijiZBIA44AsEgBmhBI2LALuLAYKLrwSxlQ26ZgGCZq1/AY5/AJtSAYBSAIqLBJ4EAIEEBckEgBmcBNAggziOIIEZmSCDCwCHCwGBCwGVCwCGCwCACwKDCwGI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eW1ib2wAU28sT3RoZXJfU3ltYm9sAFBjLENvbm5lY3Rvcl9QdW5jdHVhdGlvbgBQZCxEYXNoX1B1bmN0dWF0aW9uAFBzLE9wZW5fUHVuY3R1YXRpb24AUGUsQ2xvc2VfUHVuY3R1YXRpb24AUGksSW5pdGlhbF9QdW5jdHVhdGlvbgBQZixGaW5hbF9QdW5jdHVhdGlvbgBQbyxPdGhlcl9QdW5jdHVhdGlvbgBacyxTcGFjZV9TZXBhcmF0b3IAWmwsTGluZV9TZXBhcmF0b3IAWnAsUGFyYWdyYXBoX1NlcGFyYXRvcgBDYyxDb250cm9sLGNudHJsAENmLEZvcm1hdABDcyxTdXJyb2dhdGUAQ28sUHJpdmF0ZV9Vc2UATEMsQ2FzZWRfTGV0dGVyAEwsTGV0dGVyAE0sTWFyayxDb21iaW5pbmdfTWFyawBOLE51bWJlcgBTLFN5bWJvbABQLFB1bmN0dWF0aW9uLHB1bmN0AFosU2VwYXJhdG9yAEMsT3RoZXIAQZDXAguwCA4AAAA+AAAAwAEAAAAOAAAA8AAAAAB/AAAAgAMBAAA8QVNDSUlfSGV4X0RpZ2l0LEFIZXgAQmlkaV9Db250cm9sLEJpZGlfQwBEYXNoAERlcHJlY2F0ZWQsRGVwAERpYWNyaXRpYyxEaWEARXh0ZW5kZXIsRXh0AEhleF9EaWdpdCxIZXgASURTX0JpbmFyeV9PcGVyYXRvcixJRFNCAElEU19UcmluYXJ5X09wZXJhdG9yLElEU1QASWRlb2dyYXBoaWMsSWRlbwBKb2luX0NvbnRyb2wsSm9pbl9DAExvZ2ljYWxfT3JkZXJfRXhjZXB0aW9uLExPRQBOb25jaGFyYWN0ZXJfQ29kZV9Qb2ludCxOQ2hhcgBQYXR0ZXJuX1N5bnRheCxQYXRfU3luAFBhdHRlcm5fV2hpdGVfU3BhY2UsUGF0X1dTAFF1b3RhdGlvbl9NYXJrLFFNYXJrAFJhZGljYWwAUmVnaW9uYWxfSW5kaWNhdG9yLFJJAFNlbnRlbmNlX1Rlcm1pbmFsLFNUZXJtAFNvZnRfRG90dGVkLFNEAFRlcm1pbmFsX1B1bmN0dWF0aW9uLFRlcm0AVW5pZmllZF9JZGVvZ3JhcGgsVUlkZW8AVmFyaWF0aW9uX1NlbGVjdG9yLFZTAFdoaXRlX1NwYWNlLHNwYWNlAEJpZGlfTWlycm9yZWQsQmlkaV9NAEVtb2ppAEVtb2ppX0NvbXBvbmVudCxFQ29tcABFbW9qaV9Nb2RpZmllcixFTW9kAEVtb2ppX01vZGlmaWVyX0Jhc2UsRUJhc2UARW1vamlfUHJlc2VudGF0aW9uLEVQcmVzAEV4dGVuZGVkX1BpY3RvZ3JhcGhpYyxFeHRQaWN0AERlZmF1bHRfSWdub3JhYmxlX0NvZGVfUG9pbnQsREkASURfU3RhcnQsSURTAENhc2VfSWdub3JhYmxlLENJAEFTQ0lJAEFscGhhYmV0aWMsQWxwaGEAQW55AEFzc2lnbmVkAENhc2VkAENoYW5nZXNfV2hlbl9DYXNlZm9sZGVkLENXQ0YAQ2hhbmdlc19XaGVuX0Nhc2VtYXBwZWQsQ1dDTQBDaGFuZ2VzX1doZW5fTG93ZXJjYXNlZCxDV0wAQ2hhbmdlc19XaGVuX05GS0NfQ2FzZWZvbGRlZCxDV0tDRgBDaGFuZ2VzX1doZW5fVGl0bGVjYXNlZCxDV1QAQ2hhbmdlc19XaGVuX1VwcGVyY2FzZWQsQ1dVAEdyYXBoZW1lX0Jhc2UsR3JfQmFzZQBHcmFwaGVtZV9FeHRlbmQsR3JfRXh0AElEX0NvbnRpbnVlLElEQwBMb3dlcmNhc2UsTG93ZXIATWF0aABVcHBlcmNhc2UsVXBwZXIAWElEX0NvbnRpbnVlLFhJREMAWElEX1N0YXJ0LFhJRFMAQdDfAgvyAgEAnAYHTQMEEACPCwAAEQAIAFNKUQBSAFMAOlRVAFdZP11cAEZhY0JkAGYAaABqAGwAbgAAQAAAAAAaAJMAACA1ACcAIQAkIioAE2ttACYkJxQWGBscPh4/Hzk9IiFBHkAlJSYoICpILEMuSzBMMkRCmQAAlY99foOEEoCCdncSe6N8eHmKkpimoIUAmqGTdTOVAI4AdJmYl5YAAJ4AnAChoBUuLzC0tU+qqRIUHiEiIio0NaanNh9JAACXAVraHTYFAMTDxsXIx8rJzMvE1UXWQtdG2M7Q0tTa2e72/g4HD4CfACGAo+0AwEDGYOfb5pnAAAAGYNwp/RUSBhb43QYVEoQIxhb/3wPAQABGYN7gbTc4ORUUFxYAGhkcGwBft2VERwBPYk5QAABIAAAAo6SlAAAAAAC2AABaAEcAW1ZY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n4PLdYFAzXWfjM11gcDTdS0D1HWfiNV1gcDZdZ8D2nWBwNt1nwzcdYFA4nUtg+J1nwjkdYFA6HWfg+h1gUDqdZ+M6nWBwPB1LQTxdR+F83UfBfZ1H4X4dR8F+3Ufhf11nwQMeJ9BDnifBQ94A8IReK3QEngDARt4LQKAe61NgXsDQoh7gcCJey1FinsDBI17gYCQewPckXstBaB7rciie4NEqHutyKp7lwBAfCFFQHwlDUR8h4BKfBXBSnwXQUt8Hw1MfBeCUnyZgFN8l8BTfJeBWnyXAGR8LwGAfIGAgHwDFoR8wQSQfAMBlHwfBfx+rAEAvhDRAL6sRwm+EDkNviyHKb4sAi2+kDcuvpD/Sb4QvGm+AAAAACAAAABhAAIABAAGALwDCAAKAAwAFQCVAKUAuQDBAMMAxwDLANEA1wDdAOAA5gD4AAgBCgFzABABEgEUASABLAFEAU0BUwFiAWgBagF2AZIBlAGpAbsBxwHRAdUBuQLXATsA2QHbAbcA4QH8AQwCGAIdAiMCJwKjAzMCPwJCAksCTgJRAl0CYAJpAmwCbwJ1AngCgQKKApwCnwKjAq8CuQLFAskCzQLRAtUC5wLtAvEC9QL5Av0CBQMJAw0DEwMXAxsDIwMnAysDLwM1Az0DQQNJA00DUQMLD1cDWwNfA2MDZwNrA28DcwN5A30DgQOFA4kDjQORA5UDmQOdA6ED3BClA8kDzQPZA90D4QPvA/EDPQRPBJkE8AQCBUoFZAVsBXAFcwWaBfoF/gUHBgsGFAYYBh4GIgYoBo4GlAaYBp4GogarBqwD8watA/YGrgP5Bq8D/AbMA/8GzQMCB84DBQcJBw0HEQeGAzIHNQe5AzcHOweIA1MHiQNWB5ADaweKA3cHsAOJB44DmQefB6MHjAO4B48Duwe0AL4HwAfCBxAgywcuAM0HzwcgANIH1gfbB98H5AfqB/AHIAD2BxIiAQgFCAcIHQglCCcIQwAtCDAIkAE2CDkITgBFCEcITAhOCFEIWgCpA1oAUwhXCGAIaQBiCGUIbwh0CHoIfgiiCEkApAimCKkIVgCrCK0IsAi0CFgAtgi4CLsIwAjCCMUIdgDHCMkIzAjQCHgA0gjUCNcI2wjeCOQI5wjwCPMI9gj5CAIJBgkLCQ8JFAkXCRoJIwksCTsJPglBCUQJRwlKCVYJXAlgCWIJZAloCWoJ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";if(!K(L)){var M=L;L=d.locateFile?d.locateFile(M,q):q+M;}function ca(){var a=L;return Promise.resolve().then(()=>{if(a==L&&v)var b=new Uint8Array(v);else{if(K(a)){b=atob(a.slice(37));for(var c=new Uint8Array(b.length),e=0;eWebAssembly.instantiate(c,a)).then(c=>c).then(b,c=>{u(`failed to asynchronously prepare wasm: ${c}`);w(c);});}function ea(a,b){return da(a,b);}var N=a=>{for(;0{for(var c=b+NaN,e=b;a[e]&&!(e>=c);)++e;if(16f?c+=String.fromCharCode(f):(f-=65536,c+=String.fromCharCode(55296|f>>10,56320|f&1023));}}else c+=String.fromCharCode(f);}return c;},fa=[0,31,60,91,121,152,182,213,244,274,305,335],ha=[0,31,59,90,120,151,181,212,243,273,304,334],Q=a=>{for(var b=0,c=0;c=e?b++:2047>=e?b+=2:55296<=e&&57343>=e?(b+=4,++c):b+=3;}return b;},R=(a,b,c)=>{var e=A;if(0=g){var l=a.charCodeAt(++f);g=65536+((g&1023)<<10)|l&1023;}if(127>=g){if(b>=c)break;e[b++]=g;}else{if(2047>=g){if(b+1>=c)break;e[b++]=192|g>>6;}else{if(65535>=g){if(b+2>=c)break;e[b++]=224|g>>12;}else{if(b+3>=c)break;e[b++]=240|g>>18;e[b++]=128|g>>12&63;}e[b++]=128|g>>6&63;}e[b++]=128|g&63;}}e[b]=0;}},T=a=>{var b=Q(a)+1,c=S(b);c&&R(a,c,b);return c;};function U(){}var ia=[null,[],[]],ka=(a,b,c,e)=>{var f={string:h=>{var r=0;if(null!==h&&void 0!==h&&0!==h){r=Q(h)+1;var Y=V(r);R(h,Y,r);r=Y;}return r;},array:h=>{var r=V(h.length);z.set(h,r);return r;}};a=d["_"+a];var g=[],l=0;if(e)for(var m=0;m{a=a?P(A,a):"";b=null!==b?JSON.parse(b?P(A,b):""):[];try{const e=d.externalCall(a,b);return e?T(e):null;}catch(e){return d.HEAPU8[c]=1,T(e.message);}};var la={a:(a,b,c,e)=>{w(`Assertion failed: ${a?P(A,a):""}, at: `+[b?b?P(A,b):"":"unknown filename",c,e?e?P(A,e):"":"unknown function"]);},i:function(a,b,c){a=new Date(1E3*(b+2097152>>>0<4194305-!!a?(a>>>0)+4294967296*b:NaN));B[c>>2]=a.getSeconds();B[c+4>>2]=a.getMinutes();B[c+8>>2]=a.getHours();B[c+12>>2]=a.getDate();B[c+16>>2]=a.getMonth();B[c+20>>2]=a.getFullYear()-1900;B[c+24>>2]=a.getDay();b=a.getFullYear();B[c+28>>2]=(0!==b%4||0===b%100&&0!==b%400?ha:fa)[a.getMonth()]+a.getDate()-1|0;B[c+36>>2]=-(60*a.getTimezoneOffset());b=new Date(a.getFullYear(),6,1).getTimezoneOffset();var e=new Date(a.getFullYear(),0,1).getTimezoneOffset();B[c+32>>2]=(b!=e&&a.getTimezoneOffset()==Math.min(e,b))|0;},d:(a,b,c)=>{function e(t){return(t=t.toTimeString().match(/\(([A-Za-z ]+)\)$/))?t[1]:"GMT";}var f=new Date().getFullYear(),g=new Date(f,0,1),l=new Date(f,6,1);f=g.getTimezoneOffset();var m=l.getTimezoneOffset();C[a>>2]=60*Math.max(f,m);B[b>>2]=Number(f!=m);a=e(g);b=e(l);a=T(a);b=T(b);m>2]=a,C[c+4>>2]=b):(C[c>>2]=b,C[c+4>>2]=a);},b:()=>{w("");},g:U,f:function(a,b){a=a?P(A,a):"";let c;try{c=window.JSON.parse(a);}catch(e){c=a;}0!==b?window.alert(a):window.console.log("DUMP",c);},e:()=>Date.now(),j:a=>{var b=A.length;a>>>=0;if(2147483648=c;c*=2){var e=b*(1+.2/c);e=Math.min(e,a+100663296);var f=Math;e=Math.max(a,e);a:{f=(f.min.call(f,2147483648,e+(65536-e%65536)%65536)-x.buffer.byteLength+65535)/65536;try{x.grow(f);D();var g=1;break a;}catch(l){}g=void 0;}if(g)return!0;}return!1;},c:(a,b,c,e)=>{for(var f=0,g=0;g>2],m=C[b+4>>2];b+=8;for(var t=0;t>2]=f;return 0;},k:function(a){a=a?P(A,a):"";window.console.log(a);},h:function(a){a=a?P(A,a):"";return Date.parse(a);},l:function(a,b,c,e){a=a?P(A,a):"";b=b?P(A,b):"";c=c?P(A,c):"";c=`Quickjs -- ${a}: ${b}\n${c}`;0!==e?window.alert(c):window.console.error(c);}},X=function(){function a(c){X=c.exports;x=X.m;D();F.unshift(X.n);H--;d.monitorRunDependencies?.(H);0==H&&(null!==I&&(clearInterval(I),I=null),J&&(c=J,J=null,c()));return X;}var b={a:la};H++;d.monitorRunDependencies?.(H);if(d.instantiateWasm)try{return d.instantiateWasm(b,a);}catch(c){u(`Module.instantiateWasm callback failed with error: ${c}`),n(c);}ea(b,function(c){a(c.instance);}).catch(n);return{};}();d._evalInSandbox=(a,b)=>(d._evalInSandbox=X.o)(a,b);d._nukeSandbox=()=>(d._nukeSandbox=X.p)();d._init=(a,b)=>(d._init=X.q)(a,b);d._commFun=(a,b)=>(d._commFun=X.r)(a,b);d._dumpMemoryUse=()=>(d._dumpMemoryUse=X.s)();var S=a=>(S=X.t)(a);d._free=a=>(d._free=X.u)(a);var W=()=>(W=X.w)(),ja=a=>(ja=X.x)(a),V=a=>(V=X.y)(a);d.ccall=ka;d.cwrap=(a,b,c,e)=>{var f=!c||c.every(g=>"number"===g||"boolean"===g);return"string"!==b&&f&&!e?d["_"+a]:function(){return ka(a,b,c,arguments,e);};};d.stringToNewUTF8=T;var Z;J=function ma(){Z||na();Z||(J=ma);};function na(){function a(){if(!Z&&(Z=!0,d.calledRun=!0,!y)){N(F);k(d);if(d.onRuntimeInitialized)d.onRuntimeInitialized();if(d.postRun)for("function"==typeof d.postRun&&(d.postRun=[d.postRun]);d.postRun.length;){var b=d.postRun.shift();G.unshift(b);}N(G);}}if(!(0 {
if (typeof callbackId !== "number" || typeof nMilliseconds !== "number") {
return;
}
if (callbackId === 0) {
this.win.clearTimeout(this.timeoutIds.get(callbackId));
}
const id = this.win.setTimeout(() => {
this.timeoutIds.delete(callbackId);
this.callSandboxFunction("timeoutCb", {
callbackId,
interval: false
});
}, nMilliseconds);
this.timeoutIds.set(callbackId, id);
},
clearTimeout: callbackId => {
this.win.clearTimeout(this.timeoutIds.get(callbackId));
this.timeoutIds.delete(callbackId);
},
setInterval: (callbackId, nMilliseconds) => {
if (typeof callbackId !== "number" || typeof nMilliseconds !== "number") {
return;
}
const id = this.win.setInterval(() => {
this.callSandboxFunction("timeoutCb", {
callbackId,
interval: true
});
}, nMilliseconds);
this.timeoutIds.set(callbackId, id);
},
clearInterval: callbackId => {
this.win.clearInterval(this.timeoutIds.get(callbackId));
this.timeoutIds.delete(callbackId);
},
alert: cMsg => {
if (typeof cMsg !== "string") {
return;
}
this.win.alert(cMsg);
},
confirm: cMsg => {
if (typeof cMsg !== "string") {
return false;
}
return this.win.confirm(cMsg);
},
prompt: (cQuestion, cDefault) => {
if (typeof cQuestion !== "string" || typeof cDefault !== "string") {
return null;
}
return this.win.prompt(cQuestion, cDefault);
},
parseURL: cUrl => {
const url = new this.win.URL(cUrl);
const props = ["hash", "host", "hostname", "href", "origin", "password", "pathname", "port", "protocol", "search", "searchParams", "username"];
return Object.fromEntries(props.map(name => [name, url[name].toString()]));
},
send: data => {
if (!data) {
return;
}
const event = new this.win.CustomEvent("updatefromsandbox", {
detail: this.importValueFromSandbox(data)
});
this.win.dispatchEvent(event);
}
};
Object.setPrototypeOf(externals, null);
return (name, args) => {
try {
const result = externals[name](...args);
return this.exportValueToSandbox(result);
} catch (error) {
throw this.createErrorForSandbox(error?.toString() ?? "");
}
};
}
}
;// ./src/pdf.sandbox.js
const pdfjsVersion = "5.1.91";
const pdfjsBuild = "45cbe8bb0";
class SandboxSupport extends SandboxSupportBase {
exportValueToSandbox(val) {
return JSON.stringify(val);
}
importValueFromSandbox(val) {
return val;
}
createErrorForSandbox(errorMessage) {
return new Error(errorMessage);
}
}
class Sandbox {
constructor(win, module) {
this.support = new SandboxSupport(win, this);
module.externalCall = this.support.createSandboxExternals();
this._module = module;
this._alertOnError = 0;
}
create(data) {
const code = ["var __webpack_exports__ = globalThis.pdfjsSandbox = {};\n\n;// ./src/scripting_api/constants.js\nconst Border = Object.freeze({\n s: \"solid\",\n d: \"dashed\",\n b: \"beveled\",\n i: \"inset\",\n u: \"underline\"\n});\nconst Cursor = Object.freeze({\n visible: 0,\n hidden: 1,\n delay: 2\n});\nconst Display = Object.freeze({\n visible: 0,\n hidden: 1,\n noPrint: 2,\n noView: 3\n});\nconst Font = Object.freeze({\n Times: \"Times-Roman\",\n TimesB: \"Times-Bold\",\n TimesI: \"Times-Italic\",\n TimesBI: \"Times-BoldItalic\",\n Helv: \"Helvetica\",\n HelvB: \"Helvetica-Bold\",\n HelvI: \"Helvetica-Oblique\",\n HelvBI: \"Helvetica-BoldOblique\",\n Cour: \"Courier\",\n CourB: \"Courier-Bold\",\n CourI: \"Courier-Oblique\",\n CourBI: \"Courier-BoldOblique\",\n Symbol: \"Symbol\",\n ZapfD: \"ZapfDingbats\",\n KaGo: \"HeiseiKakuGo-W5-UniJIS-UCS2-H\",\n KaMi: \"HeiseiMin-W3-UniJIS-UCS2-H\"\n});\nconst Highlight = Object.freeze({\n n: \"none\",\n i: \"invert\",\n p: \"push\",\n o: \"outline\"\n});\nconst Position = Object.freeze({\n textOnly: 0,\n iconOnly: 1,\n iconTextV: 2,\n textIconV: 3,\n iconTextH: 4,\n textIconH: 5,\n overlay: 6\n});\nconst ScaleHow = Object.freeze({\n proportional: 0,\n anamorphic: 1\n});\nconst ScaleWhen = Object.freeze({\n always: 0,\n never: 1,\n tooBig: 2,\n tooSmall: 3\n});\nconst Style = Object.freeze({\n ch: \"check\",\n cr: \"cross\",\n di: \"diamond\",\n ci: \"circle\",\n st: \"star\",\n sq: \"square\"\n});\nconst Trans = Object.freeze({\n blindsH: \"BlindsHorizontal\",\n blindsV: \"BlindsVertical\",\n boxI: \"BoxIn\",\n boxO: \"BoxOut\",\n dissolve: \"Dissolve\",\n glitterD: \"GlitterDown\",\n glitterR: \"GlitterRight\",\n glitterRD: \"GlitterRightDown\",\n random: \"Random\",\n replace: \"Replace\",\n splitHI: \"SplitHorizontalIn\",\n splitHO: \"SplitHorizontalOut\",\n splitVI: \"SplitVerticalIn\",\n splitVO: \"SplitVerticalOut\",\n wipeD: \"WipeDown\",\n wipeL: \"WipeLeft\",\n wipeR: \"WipeRight\",\n wipeU: \"WipeUp\"\n});\nconst ZoomType = Object.freeze({\n none: \"NoVary\",\n fitP: \"FitPage\",\n fitW: \"FitWidth\",\n fitH: \"FitHeight\",\n fitV: \"FitVisibleWidth\",\n pref: \"Preferred\",\n refW: \"ReflowWidth\"\n});\nconst GlobalConstants = Object.freeze({\n IDS_GREATER_THAN: \"Invalid value: must be greater than or equal to % s.\",\n IDS_GT_AND_LT: \"Invalid value: must be greater than or equal to % s \" + \"and less than or equal to % s.\",\n IDS_LESS_THAN: \"Invalid value: must be less than or equal to % s.\",\n IDS_INVALID_MONTH: \"** Invalid **\",\n IDS_INVALID_DATE: \"Invalid date / time: please ensure that the date / time exists. Field\",\n IDS_INVALID_DATE2: \" should match format \",\n IDS_INVALID_VALUE: \"The value entered does not match the format of the field\",\n IDS_AM: \"am\",\n IDS_PM: \"pm\",\n IDS_MONTH_INFO: \"January[1] February[2] March[3] April[4] May[5] \" + \"June[6] July[7] August[8] September[9] October[10] \" + \"November[11] December[12] Sept[9] Jan[1] Feb[2] Mar[3] \" + \"Apr[4] Jun[6] Jul[7] Aug[8] Sep[9] Oct[10] Nov[11] Dec[12]\",\n IDS_STARTUP_CONSOLE_MSG: \"** ^ _ ^ **\",\n RE_NUMBER_ENTRY_DOT_SEP: [\"[+-]?\\\\d*\\\\.?\\\\d*\"],\n RE_NUMBER_COMMIT_DOT_SEP: [\"[+-]?\\\\d+(\\\\.\\\\d+)?\", \"[+-]?\\\\.\\\\d+\", \"[+-]?\\\\d+\\\\.\"],\n RE_NUMBER_ENTRY_COMMA_SEP: [\"[+-]?\\\\d*,?\\\\d*\"],\n RE_NUMBER_COMMIT_COMMA_SEP: [\"[+-]?\\\\d+([.,]\\\\d+)?\", \"[+-]?[.,]\\\\d+\", \"[+-]?\\\\d+[.,]\"],\n RE_ZIP_ENTRY: [\"\\\\d{0,5}\"],\n RE_ZIP_COMMIT: [\"\\\\d{5}\"],\n RE_ZIP4_ENTRY: [\"\\\\d{0,5}(\\\\.|[- ])?\\\\d{0,4}\"],\n RE_ZIP4_COMMIT: [\"\\\\d{5}(\\\\.|[- ])?\\\\d{4}\"],\n RE_PHONE_ENTRY: [\"\\\\d{0,3}(\\\\.|[- ])?\\\\d{0,3}(\\\\.|[- ])?\\\\d{0,4}\", \"\\\\(\\\\d{0,3}\", \"\\\\(\\\\d{0,3}\\\\)(\\\\.|[- ])?\\\\d{0,3}(\\\\.|[- ])?\\\\d{0,4}\", \"\\\\(\\\\d{0,3}(\\\\.|[- ])?\\\\d{0,3}(\\\\.|[- ])?\\\\d{0,4}\", \"\\\\d{0,3}\\\\)(\\\\.|[- ])?\\\\d{0,3}(\\\\.|[- ])?\\\\d{0,4}\", \"011(\\\\.|[- \\\\d])*\"],\n RE_PHONE_COMMIT: [\"\\\\d{3}(\\\\.|[- ])?\\\\d{4}\", \"\\\\d{3}(\\\\.|[- ])?\\\\d{3}(\\\\.|[- ])?\\\\d{4}\", \"\\\\(\\\\d{3}\\\\)(\\\\.|[- ])?\\\\d{3}(\\\\.|[- ])?\\\\d{4}\", \"011(\\\\.|[- \\\\d])*\"],\n RE_SSN_ENTRY: [\"\\\\d{0,3}(\\\\.|[- ])?\\\\d{0,2}(\\\\.|[- ])?\\\\d{0,4}\"],\n RE_SSN_COMMIT: [\"\\\\d{3}(\\\\.|[- ])?\\\\d{2}(\\\\.|[- ])?\\\\d{4}\"]\n});\n\n;// ./src/scripting_api/common.js\nconst FieldType = {\n none: 0,\n number: 1,\n percent: 2,\n date: 3,\n time: 4\n};\nfunction createActionsMap(actions) {\n const actionsMap = new Map();\n if (actions) {\n for (const [eventType, actionsForEvent] of Object.entries(actions)) {\n actionsMap.set(eventType, actionsForEvent);\n }\n }\n return actionsMap;\n}\nfunction getFieldType(actions) {\n let format = actions.get(\"Format\");\n if (!format) {\n return FieldType.none;\n }\n format = format[0];\n format = format.trim();\n if (format.startsWith(\"AFNumber_\")) {\n return FieldType.number;\n }\n if (format.startsWith(\"AFPercent_\")) {\n return FieldType.percent;\n }\n if (format.startsWith(\"AFDate_\")) {\n return FieldType.date;\n }\n if (format.startsWith(\"AFTime_\")) {\n return FieldType.time;\n }\n return FieldType.none;\n}\n\n;// ./src/shared/scripting_utils.js\nfunction makeColorComp(n) {\n return Math.floor(Math.max(0, Math.min(1, n)) * 255).toString(16).padStart(2, \"0\");\n}\nfunction scaleAndClamp(x) {\n return Math.max(0, Math.min(255, 255 * x));\n}\nclass ColorConverters {\n static CMYK_G([c, y, m, k]) {\n return [\"G\", 1 - Math.min(1, 0.3 * c + 0.59 * m + 0.11 * y + k)];\n }\n static G_CMYK([g]) {\n return [\"CMYK\", 0, 0, 0, 1 - g];\n }\n static G_RGB([g]) {\n return [\"RGB\", g, g, g];\n }\n static G_rgb([g]) {\n g = scaleAndClamp(g);\n return [g, g, g];\n }\n static G_HTML([g]) {\n const G = makeColorComp(g);\n return `#${G}${G}${G}`;\n }\n static RGB_G([r, g, b]) {\n return [\"G\", 0.3 * r + 0.59 * g + 0.11 * b];\n }\n static RGB_rgb(color) {\n return color.map(scaleAndClamp);\n }\n static RGB_HTML(color) {\n return `#${color.map(makeColorComp).join(\"\")}`;\n }\n static T_HTML() {\n return \"#00000000\";\n }\n static T_rgb() {\n return [null];\n }\n static CMYK_RGB([c, y, m, k]) {\n return [\"RGB\", 1 - Math.min(1, c + k), 1 - Math.min(1, m + k), 1 - Math.min(1, y + k)];\n }\n static CMYK_rgb([c, y, m, k]) {\n return [scaleAndClamp(1 - Math.min(1, c + k)), scaleAndClamp(1 - Math.min(1, m + k)), scaleAndClamp(1 - Math.min(1, y + k))];\n }\n static CMYK_HTML(components) {\n const rgb = this.CMYK_RGB(components).slice(1);\n return this.RGB_HTML(rgb);\n }\n static RGB_CMYK([r, g, b]) {\n const c = 1 - r;\n const m = 1 - g;\n const y = 1 - b;\n const k = Math.min(c, m, y);\n return [\"CMYK\", c, m, y, k];\n }\n}\n\n;// ./src/scripting_api/pdf_object.js\nclass PDFObject {\n constructor(data) {\n this._expandos = Object.create(null);\n this._send = data.send || null;\n this._id = data.id || null;\n }\n}\n\n;// ./src/scripting_api/color.js\n\n\nclass Color extends PDFObject {\n constructor() {\n super({});\n this.transparent = [\"T\"];\n this.black = [\"G\", 0];\n this.white = [\"G\", 1];\n this.red = [\"RGB\", 1, 0, 0];\n this.green = [\"RGB\", 0, 1, 0];\n this.blue = [\"RGB\", 0, 0, 1];\n this.cyan = [\"CMYK\", 1, 0, 0, 0];\n this.magenta = [\"CMYK\", 0, 1, 0, 0];\n this.yellow = [\"CMYK\", 0, 0, 1, 0];\n this.dkGray = [\"G\", 0.25];\n this.gray = [\"G\", 0.5];\n this.ltGray = [\"G\", 0.75];\n }\n static _isValidSpace(cColorSpace) {\n return typeof cColorSpace === \"string\" && (cColorSpace === \"T\" || cColorSpace === \"G\" || cColorSpace === \"RGB\" || cColorSpace === \"CMYK\");\n }\n static _isValidColor(colorArray) {\n if (!Array.isArray(colorArray) || colorArray.length === 0) {\n return false;\n }\n const space = colorArray[0];\n if (!Color._isValidSpace(space)) {\n return false;\n }\n switch (space) {\n case \"T\":\n if (colorArray.length !== 1) {\n return false;\n }\n break;\n case \"G\":\n if (colorArray.length !== 2) {\n return false;\n }\n break;\n case \"RGB\":\n if (colorArray.length !== 4) {\n return false;\n }\n break;\n case \"CMYK\":\n if (colorArray.length !== 5) {\n return false;\n }\n break;\n default:\n return false;\n }\n return colorArray.slice(1).every(c => typeof c === \"number\" && c >= 0 && c <= 1);\n }\n static _getCorrectColor(colorArray) {\n return Color._isValidColor(colorArray) ? colorArray : [\"G\", 0];\n }\n convert(colorArray, cColorSpace) {\n if (!Color._isValidSpace(cColorSpace)) {\n return this.black;\n }\n if (cColorSpace === \"T\") {\n return [\"T\"];\n }\n colorArray = Color._getCorrectColor(colorArray);\n if (colorArray[0] === cColorSpace) {\n return colorArray;\n }\n if (colorArray[0] === \"T\") {\n return this.convert(this.black, cColorSpace);\n }\n return ColorConverters[`${colorArray[0]}_${cColorSpace}`](colorArray.slice(1));\n }\n equal(colorArray1, colorArray2) {\n colorArray1 = Color._getCorrectColor(colorArray1);\n colorArray2 = Color._getCorrectColor(colorArray2);\n if (colorArray1[0] === \"T\" || colorArray2[0] === \"T\") {\n return colorArray1[0] === \"T\" && colorArray2[0] === \"T\";\n }\n if (colorArray1[0] !== colorArray2[0]) {\n colorArray2 = this.convert(colorArray2, colorArray1[0]);\n }\n return colorArray1.slice(1).every((c, i) => c === colorArray2[i + 1]);\n }\n}\n\n;// ./src/scripting_api/app_utils.js\nconst VIEWER_TYPE = \"PDF.js\";\nconst VIEWER_VARIATION = \"Full\";\nconst VIEWER_VERSION = 21.00720099;\nconst FORMS_VERSION = 21.00720099;\nconst USERACTIVATION_CALLBACKID = 0;\nconst USERACTIVATION_MAXTIME_VALIDITY = 5000;\nfunction serializeError(error) {\n const value = `${error.toString()}\\n${error.stack}`;\n return {\n command: \"error\",\n value\n };\n}\n\n;// ./src/scripting_api/field.js\n\n\n\n\nclass Field extends PDFObject {\n constructor(data) {\n super(data);\n this.alignment = data.alignment || \"left\";\n this.borderStyle = data.borderStyle || \"\";\n this.buttonAlignX = data.buttonAlignX || 50;\n this.buttonAlignY = data.buttonAlignY || 50;\n this.buttonFitBounds = data.buttonFitBounds;\n this.buttonPosition = data.buttonPosition;\n this.buttonScaleHow = data.buttonScaleHow;\n this.ButtonScaleWhen = data.buttonScaleWhen;\n this.calcOrderIndex = data.calcOrderIndex;\n this.comb = data.comb;\n this.commitOnSelChange = data.commitOnSelChange;\n this.currentValueIndices = data.currentValueIndices;\n this.defaultStyle = data.defaultStyle;\n this.defaultValue = data.defaultValue;\n this.doNotScroll = data.doNotScroll;\n this.doNotSpellCheck = data.doNotSpellCheck;\n this.delay = data.delay;\n this.display = data.display;\n this.doc = data.doc.wrapped;\n this.editable = data.editable;\n this.exportValues = data.exportValues;\n this.fileSelect = data.fileSelect;\n this.hidden = data.hidden;\n this.highlight = data.highlight;\n this.lineWidth = data.lineWidth;\n this.multiline = data.multiline;\n this.multipleSelection = !!data.multipleSelection;\n this.name = data.name;\n this.password = data.password;\n this.print = data.print;\n this.radiosInUnison = data.radiosInUnison;\n this.readonly = data.readonly;\n this.rect = data.rect;\n this.required = data.required;\n this.richText = data.richText;\n this.richValue = data.richValue;\n this.style = data.style;\n this.submitName = data.submitName;\n this.textFont = data.textFont;\n this.textSize = data.textSize;\n this.type = data.type;\n this.userName = data.userName;\n this._actions = createActionsMap(data.actions);\n this._browseForFileToSubmit = data.browseForFileToSubmit || null;\n this._buttonCaption = null;\n this._buttonIcon = null;\n this._charLimit = data.charLimit;\n this._children = null;\n this._currentValueIndices = data.currentValueIndices || 0;\n this._document = data.doc;\n this._fieldPath = data.fieldPath;\n this._fillColor = data.fillColor || [\"T\"];\n this._isChoice = Array.isArray(data.items);\n this._items = data.items || [];\n this._hasValue = data.hasOwnProperty(\"value\");\n this._page = data.page || 0;\n this._strokeColor = data.strokeColor || [\"G\", 0];\n this._textColor = data.textColor || [\"G\", 0];\n this._value = null;\n this._kidIds = data.kidIds || null;\n this._fieldType = getFieldType(this._actions);\n this._siblings = data.siblings || null;\n this._rotation = data.rotation || 0;\n this._globalEval = data.globalEval;\n this._appObjects = data.appObjects;\n this.value = data.value || \"\";\n }\n get currentValueIndices() {\n if (!this._isChoice) {\n return 0;\n }\n return this._currentValueIndices;\n }\n set currentValueIndices(indices) {\n if (!this._isChoice) {\n return;\n }\n if (!Array.isArray(indices)) {\n indices = [indices];\n }\n if (!indices.every(i => typeof i === \"number\" && Number.isInteger(i) && i >= 0 && i < this.numItems)) {\n return;\n }\n indices.sort();\n if (this.multipleSelection) {\n this._currentValueIndices = indices;\n this._value = [];\n indices.forEach(i => {\n this._value.push(this._items[i].displayValue);\n });\n } else if (indices.length > 0) {\n indices = indices.splice(1, indices.length - 1);\n this._currentValueIndices = indices[0];\n this._value = this._items[this._currentValueIndices];\n }\n this._send({\n id: this._id,\n indices\n });\n }\n get fillColor() {\n return this._fillColor;\n }\n set fillColor(color) {\n if (Color._isValidColor(color)) {\n this._fillColor = color;\n }\n }\n get bgColor() {\n return this.fillColor;\n }\n set bgColor(color) {\n this.fillColor = color;\n }\n get charLimit() {\n return this._charLimit;\n }\n set charLimit(limit) {\n if (typeof limit !== \"number\") {\n throw new Error(\"Invalid argument value\");\n }\n this._charLimit = Math.max(0, Math.floor(limit));\n }\n get numItems() {\n if (!this._isChoice) {\n throw new Error(\"Not a choice widget\");\n }\n return this._items.length;\n }\n set numItems(_) {\n throw new Error(\"field.numItems is read-only\");\n }\n get strokeColor() {\n return this._strokeColor;\n }\n set strokeColor(color) {\n if (Color._isValidColor(color)) {\n this._strokeColor = color;\n }\n }\n get borderColor() {\n return this.strokeColor;\n }\n set borderColor(color) {\n this.strokeColor = color;\n }\n get page() {\n return this._page;\n }\n set page(_) {\n throw new Error(\"field.page is read-only\");\n }\n get rotation() {\n return this._rotation;\n }\n set rotation(angle) {\n angle = Math.floor(angle);\n if (angle % 90 !== 0) {\n throw new Error(\"Invalid rotation: must be a multiple of 90\");\n }\n angle %= 360;\n if (angle < 0) {\n angle += 360;\n }\n this._rotation = angle;\n }\n get textColor() {\n return this._textColor;\n }\n set textColor(color) {\n if (Color._isValidColor(color)) {\n this._textColor = color;\n }\n }\n get fgColor() {\n return this.textColor;\n }\n set fgColor(color) {\n this.textColor = color;\n }\n get value() {\n return this._value;\n }\n set value(value) {\n if (this._isChoice) {\n this._setChoiceValue(value);\n return;\n }\n if (value === \"\" || typeof value !== \"string\" || this._fieldType >= FieldType.date) {\n this._originalValue = undefined;\n this._value = value;\n return;\n }\n this._originalValue = value;\n const _value = value.trim().replace(\",\", \".\");\n this._value = !isNaN(_value) ? parseFloat(_value) : value;\n }\n _getValue() {\n return this._originalValue ?? this.value;\n }\n _setChoiceValue(value) {\n if (this.multipleSelection) {\n if (!Array.isArray(value)) {\n value = [value];\n }\n const values = new Set(value);\n if (Array.isArray(this._currentValueIndices)) {\n this._currentValueIndices.length = 0;\n this._value.length = 0;\n } else {\n this._currentValueIndices = [];\n this._value = [];\n }\n this._items.forEach((item, i) => {\n if (values.has(item.exportValue)) {\n this._currentValueIndices.push(i);\n this._value.push(item.exportValue);\n }\n });\n } else {\n if (Array.isArray(value)) {\n value = value[0];\n }\n const index = this._items.findIndex(({\n exportValue\n }) => value === exportValue);\n if (index !== -1) {\n this._currentValueIndices = index;\n this._value = this._items[index].exportValue;\n }\n }\n }\n get valueAsString() {\n return (this._value ?? \"\").toString();\n }\n set valueAsString(_) {}\n browseForFileToSubmit() {\n if (this._browseForFileToSubmit) {\n this._browseForFileToSubmit();\n }\n }\n buttonGetCaption(nFace = 0) {\n if (this._buttonCaption) {\n return this._buttonCaption[nFace];\n }\n return \"\";\n }\n buttonGetIcon(nFace = 0) {\n if (this._buttonIcon) {\n return this._buttonIcon[nFace];\n }\n return null;\n }\n buttonImportIcon(cPath = null, nPave = 0) {}\n buttonSetCaption(cCaption, nFace = 0) {\n if (!this._buttonCaption) {\n this._buttonCaption = [\"\", \"\", \"\"];\n }\n this._buttonCaption[nFace] = cCaption;\n }\n buttonSetIcon(oIcon, nFace = 0) {\n if (!this._buttonIcon) {\n this._buttonIcon = [null, null, null];\n }\n this._buttonIcon[nFace] = oIcon;\n }\n checkThisBox(nWidget, bCheckIt = true) {}\n clearItems() {\n if (!this._isChoice) {\n throw new Error(\"Not a choice widget\");\n }\n this._items = [];\n this._send({\n id: this._id,\n clear: null\n });\n }\n deleteItemAt(nIdx = null) {\n if (!this._isChoice) {\n throw new Error(\"Not a choice widget\");\n }\n if (!this.numItems) {\n return;\n }\n if (nIdx === null) {\n nIdx = Array.isArray(this._currentValueIndices) ? this._currentValueIndices[0] : this._currentValueIndices;\n nIdx ||= 0;\n }\n if (nIdx < 0 || nIdx >= this.numItems) {\n nIdx = this.numItems - 1;\n }\n this._items.splice(nIdx, 1);\n if (Array.isArray(this._currentValueIndices)) {\n let index = this._currentValueIndices.findIndex(i => i >= nIdx);\n if (index !== -1) {\n if (this._currentValueIndices[index] === nIdx) {\n this._currentValueIndices.splice(index, 1);\n }\n for (const ii = this._currentValueIndices.length; index < ii; index++) {\n --this._currentValueIndices[index];\n }\n }\n } else if (this._currentValueIndices === nIdx) {\n this._currentValueIndices = this.numItems > 0 ? 0 : -1;\n } else if (this._currentValueIndices > nIdx) {\n --this._currentValueIndices;\n }\n this._send({\n id: this._id,\n remove: nIdx\n });\n }\n getItemAt(nIdx = -1, bExportValue = false) {\n if (!this._isChoice) {\n throw new Error(\"Not a choice widget\");\n }\n if (nIdx < 0 || nIdx >= this.numItems) {\n nIdx = this.numItems - 1;\n }\n const item = this._items[nIdx];\n return bExportValue ? item.exportValue : item.displayValue;\n }\n getArray() {\n if (this._kidIds) {\n const array = [];\n const fillArrayWithKids = kidIds => {\n for (const id of kidIds) {\n const obj = this._appObjects[id];\n if (!obj) {\n continue;\n }\n if (obj.obj._hasValue) {\n array.push(obj.wrapped);\n }\n if (obj.obj._kidIds) {\n fillArrayWithKids(obj.obj._kidIds);\n }\n }\n };\n fillArrayWithKids(this._kidIds);\n return array;\n }\n if (this._children === null) {\n this._children = this._document.obj._getTerminalChildren(this._fieldPath);\n }\n return this._children;\n }\n getLock() {\n return undefined;\n }\n isBoxChecked(nWidget) {\n return false;\n }\n isDefaultChecked(nWidget) {\n return false;\n }\n insertItemAt(cName, cExport = undefined, nIdx = 0) {\n if (!this._isChoice) {\n throw new Error(\"Not a choice widget\");\n }\n if (!cName) {\n return;\n }\n if (nIdx < 0 || nIdx > this.numItems) {\n nIdx = this.numItems;\n }\n if (this._items.some(({\n displayValue\n }) => displayValue === cName)) {\n return;\n }\n if (cExport === undefined) {\n cExport = cName;\n }\n const data = {\n displayValue: cName,\n exportValue: cExport\n };\n this._items.splice(nIdx, 0, data);\n if (Array.isArray(this._currentValueIndices)) {\n let index = this._currentValueIndices.findIndex(i => i >= nIdx);\n if (index !== -1) {\n for (const ii = this._currentValueIndices.length; index < ii; index++) {\n ++this._currentValueIndices[index];\n }\n }\n } else if (this._currentValueIndices >= nIdx) {\n ++this._currentValueIndices;\n }\n this._send({\n id: this._id,\n insert: {\n index: nIdx,\n ...data\n }\n });\n }\n setAction(cTrigger, cScript) {\n if (typeof cTrigger !== \"string\" || typeof cScript !== \"string\") {\n return;\n }\n if (!(cTrigger in this._actions)) {\n this._actions[cTrigger] = [];\n }\n this._actions[cTrigger].push(cScript);\n }\n setFocus() {\n this._send({\n id: this._id,\n focus: true\n });\n }\n setItems(oArray) {\n if (!this._isChoice) {\n throw new Error(\"Not a choice widget\");\n }\n this._items.length = 0;\n for (const element of oArray) {\n let displayValue, exportValue;\n if (Array.isArray(element)) {\n displayValue = element[0]?.toString() || \"\";\n exportValue = element[1]?.toString() || \"\";\n } else {\n displayValue = exportValue = element?.toString() || \"\";\n }\n this._items.push({\n displayValue,\n exportValue\n });\n }\n this._currentValueIndices = 0;\n this._send({\n id: this._id,\n items: this._items\n });\n }\n setLock() {}\n signatureGetModifications() {}\n signatureGetSeedValue() {}\n signatureInfo() {}\n signatureSetSeedValue() {}\n signatureSign() {}\n signatureValidate() {}\n _isButton() {\n return false;\n }\n _reset() {\n this.value = this.defaultValue;\n }\n _runActions(event) {\n const eventName = event.name;\n if (!this._actions.has(eventName)) {\n return false;\n }\n const actions = this._actions.get(eventName);\n for (const action of actions) {\n try {\n this._globalEval(action);\n } catch (error) {\n const serializedError = serializeError(error);\n serializedError.value = `Error when executing \"${eventName}\" for field \"${this._id}\"\\n${serializedError.value}`;\n this._send(serializedError);\n }\n }\n return true;\n }\n}\nclass RadioButtonField extends Field {\n constructor(otherButtons, data) {\n super(data);\n this.exportValues = [this.exportValues];\n this._radioIds = [this._id];\n this._radioActions = [this._actions];\n for (const radioData of otherButtons) {\n this.exportValues.push(radioData.exportValues);\n this._radioIds.push(radioData.id);\n this._radioActions.push(createActionsMap(radioData.actions));\n if (this._value === radioData.exportValues) {\n this._id = radioData.id;\n }\n }\n this._hasBeenInitialized = true;\n this._value = data.value || \"\";\n }\n get _siblings() {\n return this._radioIds.filter(id => id !== this._id);\n }\n set _siblings(_) {}\n get value() {\n return this._value;\n }\n set value(value) {\n if (!this._hasBeenInitialized) {\n return;\n }\n if (value === null || value === undefined) {\n this._value = \"\";\n }\n const i = this.exportValues.indexOf(value);\n if (0 <= i && i < this._radioIds.length) {\n this._id = this._radioIds[i];\n this._value = value;\n } else if (value === \"Off\" && this._radioIds.length === 2) {\n const nextI = (1 + this._radioIds.indexOf(this._id)) % 2;\n this._id = this._radioIds[nextI];\n this._value = this.exportValues[nextI];\n }\n }\n checkThisBox(nWidget, bCheckIt = true) {\n if (nWidget < 0 || nWidget >= this._radioIds.length || !bCheckIt) {\n return;\n }\n this._id = this._radioIds[nWidget];\n this._value = this.exportValues[nWidget];\n this._send({\n id: this._id,\n value: this._value\n });\n }\n isBoxChecked(nWidget) {\n return nWidget >= 0 && nWidget < this._radioIds.length && this._id === this._radioIds[nWidget];\n }\n isDefaultChecked(nWidget) {\n return nWidget >= 0 && nWidget < this.exportValues.length && this.defaultValue === this.exportValues[nWidget];\n }\n _getExportValue(state) {\n const i = this._radioIds.indexOf(this._id);\n return this.exportValues[i];\n }\n _runActions(event) {\n const i = this._radioIds.indexOf(this._id);\n this._actions = this._radioActions[i];\n return super._runActions(event);\n }\n _isButton() {\n return true;\n }\n}\nclass CheckboxField extends RadioButtonField {\n get value() {\n return this._value;\n }\n set value(value) {\n if (!value || value === \"Off\") {\n this._value = \"Off\";\n } else {\n super.value = value;\n }\n }\n _getExportValue(state) {\n return state ? super._getExportValue(state) : \"Off\";\n }\n isBoxChecked(nWidget) {\n if (this._value === \"Off\") {\n return false;\n }\n return super.isBoxChecked(nWidget);\n }\n isDefaultChecked(nWidget) {\n if (this.defaultValue === \"Off\") {\n return this._value === \"Off\";\n }\n return super.isDefaultChecked(nWidget);\n }\n checkThisBox(nWidget, bCheckIt = true) {\n if (nWidget < 0 || nWidget >= this._radioIds.length) {\n return;\n }\n this._id = this._radioIds[nWidget];\n this._value = bCheckIt ? this.exportValues[nWidget] : \"Off\";\n this._send({\n id: this._id,\n value: this._value\n });\n }\n}\n\n;// ./src/scripting_api/aform.js\n\nclass AForm {\n constructor(document, app, util, color) {\n this._document = document;\n this._app = app;\n this._util = util;\n this._color = color;\n this._dateFormats = [\"m/d\", \"m/d/yy\", \"mm/dd/yy\", \"mm/yy\", \"d-mmm\", \"d-mmm-yy\", \"dd-mmm-yy\", \"yy-mm-dd\", \"mmm-yy\", \"mmmm-yy\", \"mmm d, yyyy\", \"mmmm d, yyyy\", \"m/d/yy h:MM tt\", \"m/d/yy HH:MM\"];\n this._timeFormats = [\"HH:MM\", \"h:MM tt\", \"HH:MM:ss\", \"h:MM:ss tt\"];\n this._emailRegex = new RegExp(\"^[a-zA-Z0-9.!#$%&'*+\\\\/=?^_`{|}~-]+\" + \"@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\" + \"(?:\\\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$\");\n }\n _mkTargetName(event) {\n return event.target ? `[ ${event.target.name} ]` : \"\";\n }\n _parseDate(cFormat, cDate, strict = false) {\n let date = null;\n try {\n date = this._util._scand(cFormat, cDate, strict);\n } catch {}\n if (date) {\n return date;\n }\n if (strict) {\n return null;\n }\n date = Date.parse(cDate);\n return isNaN(date) ? null : new Date(date);\n }\n AFMergeChange(event = globalThis.event) {\n if (event.willCommit) {\n return event.value.toString();\n }\n return this._app._eventDispatcher.mergeChange(event);\n }\n AFParseDateEx(cString, cOrder) {\n return this._parseDate(cOrder, cString);\n }\n AFExtractNums(str) {\n if (typeof str === \"number\") {\n return [str];\n }\n if (!str || typeof str !== \"string\") {\n return null;\n }\n const first = str.charAt(0);\n if (first === \".\" || first === \",\") {\n str = `0${str}`;\n }\n const numbers = str.match(/(\\d+)/g);\n if (numbers.length === 0) {\n return null;\n }\n return numbers;\n }\n AFMakeNumber(str) {\n if (typeof str === \"number\") {\n return str;\n }\n if (typeof str !== \"string\") {\n return null;\n }\n str = str.trim().replace(\",\", \".\");\n const number = parseFloat(str);\n if (isNaN(number) || !isFinite(number)) {\n return null;\n }\n return number;\n }\n AFMakeArrayFromList(string) {\n if (typeof string === \"string\") {\n return string.split(/, ?/g);\n }\n return string;\n }\n AFNumber_Format(nDec, sepStyle, negStyle, currStyle, strCurrency, bCurrencyPrepend) {\n const event = globalThis.event;\n let value = this.AFMakeNumber(event.value);\n if (value === null) {\n event.value = \"\";\n return;\n }\n const sign = Math.sign(value);\n const buf = [];\n let hasParen = false;\n if (sign === -1 && bCurrencyPrepend && negStyle === 0) {\n buf.push(\"-\");\n }\n if ((negStyle === 2 || negStyle === 3) && sign === -1) {\n buf.push(\"(\");\n hasParen = true;\n }\n if (bCurrencyPrepend) {\n buf.push(strCurrency);\n }\n sepStyle = Math.min(Math.max(0, Math.floor(sepStyle)), 4);\n buf.push(\"%,\", sepStyle, \".\", nDec.toString(), \"f\");\n if (!bCurrencyPrepend) {\n buf.push(strCurrency);\n }\n if (hasParen) {\n buf.push(\")\");\n }\n if (negStyle === 1 || negStyle === 3) {\n event.target.textColor = sign === 1 ? this._color.black : this._color.red;\n }\n if ((negStyle !== 0 || bCurrencyPrepend) && sign === -1) {\n value = -value;\n }\n const formatStr = buf.join(\"\");\n event.value = this._util.printf(formatStr, value);\n }\n AFNumber_Keystroke(nDec, sepStyle, negStyle, currStyle, strCurrency, bCurrencyPrepend) {\n const event = globalThis.event;\n let value = this.AFMergeChange(event);\n if (!value) {\n return;\n }\n value = value.trim();\n let pattern;\n if (sepStyle > 1) {\n pattern = event.willCommit ? /^[+-]?(\\d+(,\\d*)?|,\\d+)$/ : /^[+-]?\\d*,?\\d*$/;\n } else {\n pattern = event.willCommit ? /^[+-]?(\\d+(\\.\\d*)?|\\.\\d+)$/ : /^[+-]?\\d*\\.?\\d*$/;\n }\n if (!pattern.test(value)) {\n if (event.willCommit) {\n const err = `${GlobalConstants.IDS_INVALID_VALUE} ${this._mkTargetName(event)}`;\n this._app.alert(err);\n }\n event.rc = false;\n }\n if (event.willCommit && sepStyle > 1) {\n event.value = parseFloat(value.replace(\",\", \".\"));\n }\n }\n AFPercent_Format(nDec, sepStyle, percentPrepend = false) {\n if (typeof nDec !== \"number\") {\n return;\n }\n if (typeof sepStyle !== \"number\") {\n return;\n }\n if (nDec < 0) {\n throw new Error(\"Invalid nDec value in AFPercent_Format\");\n }\n const event = globalThis.event;\n if (nDec > 512) {\n event.value = \"%\";\n return;\n }\n nDec = Math.floor(nDec);\n sepStyle = Math.min(Math.max(0, Math.floor(sepStyle)), 4);\n let value = this.AFMakeNumber(event.value);\n if (value === null) {\n event.value = \"%\";\n return;\n }\n const formatStr = `%,${sepStyle}.${nDec}f`;\n value = this._util.printf(formatStr, value * 100);\n event.value = percentPrepend ? `%${value}` : `${value}%`;\n }\n AFPercent_Keystroke(nDec, sepStyle) {\n this.AFNumber_Keystroke(nDec, sepStyle, 0, 0, \"\", true);\n }\n AFDate_FormatEx(cFormat) {\n const event = globalThis.event;\n const value = event.value;\n if (!value) {\n return;\n }\n const date = this._parseDate(cFormat, value);\n if (date !== null) {\n event.value = this._util.printd(cFormat, date);\n }\n }\n AFDate_Format(pdf) {\n if (pdf >= 0 && pdf < this._dateFormats.length) {\n this.AFDate_FormatEx(this._dateFormats[pdf]);\n }\n }\n AFDate_KeystrokeEx(cFormat) {\n const event = globalThis.event;\n if (!event.willCommit) {\n return;\n }\n const value = this.AFMergeChange(event);\n if (!value) {\n return;\n }\n if (this._parseDate(cFormat, value, true) === null) {\n const invalid = GlobalConstants.IDS_INVALID_DATE;\n const invalid2 = GlobalConstants.IDS_INVALID_DATE2;\n const err = `${invalid} ${this._mkTargetName(event)}${invalid2}${cFormat}`;\n this._app.alert(err);\n event.rc = false;\n }\n }\n AFDate_Keystroke(pdf) {\n if (pdf >= 0 && pdf < this._dateFormats.length) {\n this.AFDate_KeystrokeEx(this._dateFormats[pdf]);\n }\n }\n AFRange_Validate(bGreaterThan, nGreaterThan, bLessThan, nLessThan) {\n const event = globalThis.event;\n if (!event.value) {\n return;\n }\n const value = this.AFMakeNumber(event.value);\n if (value === null) {\n return;\n }\n bGreaterThan = !!bGreaterThan;\n bLessThan = !!bLessThan;\n if (bGreaterThan) {\n nGreaterThan = this.AFMakeNumber(nGreaterThan);\n if (nGreaterThan === null) {\n return;\n }\n }\n if (bLessThan) {\n nLessThan = this.AFMakeNumber(nLessThan);\n if (nLessThan === null) {\n return;\n }\n }\n let err = \"\";\n if (bGreaterThan && bLessThan) {\n if (value < nGreaterThan || value > nLessThan) {\n err = this._util.printf(GlobalConstants.IDS_GT_AND_LT, nGreaterThan, nLessThan);\n }\n } else if (bGreaterThan) {\n if (value < nGreaterThan) {\n err = this._util.printf(GlobalConstants.IDS_GREATER_THAN, nGreaterThan);\n }\n } else if (value > nLessThan) {\n err = this._util.printf(GlobalConstants.IDS_LESS_THAN, nLessThan);\n }\n if (err) {\n this._app.alert(err);\n event.rc = false;\n }\n }\n AFSimple(cFunction, nValue1, nValue2) {\n const value1 = this.AFMakeNumber(nValue1);\n if (value1 === null) {\n throw new Error(\"Invalid nValue1 in AFSimple\");\n }\n const value2 = this.AFMakeNumber(nValue2);\n if (value2 === null) {\n throw new Error(\"Invalid nValue2 in AFSimple\");\n }\n switch (cFunction) {\n case \"AVG\":\n return (value1 + value2) / 2;\n case \"SUM\":\n return value1 + value2;\n case \"PRD\":\n return value1 * value2;\n case \"MIN\":\n return Math.min(value1, value2);\n case \"MAX\":\n return Math.max(value1, value2);\n }\n throw new Error(\"Invalid cFunction in AFSimple\");\n }\n AFSimple_Calculate(cFunction, cFields) {\n const actions = {\n AVG: args => args.reduce((acc, value) => acc + value, 0) / args.length,\n SUM: args => args.reduce((acc, value) => acc + value, 0),\n PRD: args => args.reduce((acc, value) => acc * value, 1),\n MIN: args => args.reduce((acc, value) => Math.min(acc, value), Number.MAX_VALUE),\n MAX: args => args.reduce((acc, value) => Math.max(acc, value), Number.MIN_VALUE)\n };\n if (!(cFunction in actions)) {\n throw new TypeError(\"Invalid function in AFSimple_Calculate\");\n }\n const event = globalThis.event;\n const values = [];\n cFields = this.AFMakeArrayFromList(cFields);\n for (const cField of cFields) {\n const field = this._document.getField(cField);\n if (!field) {\n continue;\n }\n for (const child of field.getArray()) {\n const number = this.AFMakeNumber(child.value);\n values.push(number ?? 0);\n }\n }\n if (values.length === 0) {\n event.value = 0;\n return;\n }\n const res = actions[cFunction](values);\n event.value = Math.round(1e6 * res) / 1e6;\n }\n AFSpecial_Format(psf) {\n const event = globalThis.event;\n if (!event.value) {\n return;\n }\n psf = this.AFMakeNumber(psf);\n let formatStr;\n switch (psf) {\n case 0:\n formatStr = \"99999\";\n break;\n case 1:\n formatStr = \"99999-9999\";\n break;\n case 2:\n formatStr = this._util.printx(\"9999999999\", event.value).length >= 10 ? \"(999) 999-9999\" : \"999-9999\";\n break;\n case 3:\n formatStr = \"999-99-9999\";\n break;\n default:\n throw new Error(\"Invalid psf in AFSpecial_Format\");\n }\n event.value = this._util.printx(formatStr, event.value);\n }\n AFSpecial_KeystrokeEx(cMask) {\n const event = globalThis.event;\n const simplifiedFormatStr = cMask.replaceAll(/[^9AOX]/g, \"\");\n this.#AFSpecial_KeystrokeEx_helper(simplifiedFormatStr, null, false);\n if (event.rc) {\n return;\n }\n event.rc = true;\n this.#AFSpecial_KeystrokeEx_helper(cMask, null, true);\n }\n #AFSpecial_KeystrokeEx_helper(cMask, value, warn) {\n if (!cMask) {\n return;\n }\n const event = globalThis.event;\n value ||= this.AFMergeChange(event);\n if (!value) {\n return;\n }\n const checkers = new Map([[\"9\", char => char >= \"0\" && char <= \"9\"], [\"A\", char => \"a\" <= char && char <= \"z\" || \"A\" <= char && char <= \"Z\"], [\"O\", char => \"a\" <= char && char <= \"z\" || \"A\" <= char && char <= \"Z\" || \"0\" <= char && char <= \"9\"], [\"X\", char => true]]);\n function _checkValidity(_value, _cMask) {\n for (let i = 0, ii = _value.length; i < ii; i++) {\n const mask = _cMask.charAt(i);\n const char = _value.charAt(i);\n const checker = checkers.get(mask);\n if (checker) {\n if (!checker(char)) {\n return false;\n }\n } else if (mask !== char) {\n return false;\n }\n }\n return true;\n }\n const err = `${GlobalConstants.IDS_INVALID_VALUE} = \"${cMask}\"`;\n if (value.length > cMask.length) {\n if (warn) {\n this._app.alert(err);\n }\n event.rc = false;\n return;\n }\n if (event.willCommit) {\n if (value.length < cMask.length) {\n if (warn) {\n this._app.alert(err);\n }\n event.rc = false;\n return;\n }\n if (!_checkValidity(value, cMask)) {\n if (warn) {\n this._app.alert(err);\n }\n event.rc = false;\n return;\n }\n event.value += cMask.substring(value.length);\n return;\n }\n if (value.length < cMask.length) {\n cMask = cMask.substring(0, value.length);\n }\n if (!_checkValidity(value, cMask)) {\n if (warn) {\n this._app.alert(err);\n }\n event.rc = false;\n }\n }\n AFSpecial_Keystroke(psf) {\n const event = globalThis.event;\n psf = this.AFMakeNumber(psf);\n let value = this.AFMergeChange(event);\n let formatStr, secondFormatStr;\n switch (psf) {\n case 0:\n formatStr = \"99999\";\n break;\n case 1:\n formatStr = \"99999-9999\";\n break;\n case 2:\n formatStr = \"999-9999\";\n secondFormatStr = \"(999) 999-9999\";\n break;\n case 3:\n formatStr = \"999-99-9999\";\n break;\n default:\n throw new Error(\"Invalid psf in AFSpecial_Keystroke\");\n }\n const formats = secondFormatStr ? [formatStr, secondFormatStr] : [formatStr];\n for (const format of formats) {\n this.#AFSpecial_KeystrokeEx_helper(format, value, false);\n if (event.rc) {\n return;\n }\n event.rc = true;\n }\n const re = /([-()]|\\s)+/g;\n value = value.replaceAll(re, \"\");\n for (const format of formats) {\n this.#AFSpecial_KeystrokeEx_helper(format.replaceAll(re, \"\"), value, false);\n if (event.rc) {\n return;\n }\n event.rc = true;\n }\n this.AFSpecial_KeystrokeEx((secondFormatStr && value.match(/\\d/g) || []).length > 7 ? secondFormatStr : formatStr);\n }\n AFTime_FormatEx(cFormat) {\n this.AFDate_FormatEx(cFormat);\n }\n AFTime_Format(pdf) {\n if (pdf >= 0 && pdf < this._timeFormats.length) {\n this.AFDate_FormatEx(this._timeFormats[pdf]);\n }\n }\n AFTime_KeystrokeEx(cFormat) {\n this.AFDate_KeystrokeEx(cFormat);\n }\n AFTime_Keystroke(pdf) {\n if (pdf >= 0 && pdf < this._timeFormats.length) {\n this.AFDate_KeystrokeEx(this._timeFormats[pdf]);\n }\n }\n eMailValidate(str) {\n return this._emailRegex.test(str);\n }\n AFExactMatch(rePatterns, str) {\n if (rePatterns instanceof RegExp) {\n return str.match(rePatterns)?.[0] === str || 0;\n }\n return rePatterns.findIndex(re => str.match(re)?.[0] === str) + 1;\n }\n}\n\n;// ./src/scripting_api/event.js\n\nclass Event {\n constructor(data) {\n this.change = data.change || \"\";\n this.changeEx = data.changeEx || null;\n this.commitKey = data.commitKey || 0;\n this.fieldFull = data.fieldFull || false;\n this.keyDown = data.keyDown || false;\n this.modifier = data.modifier || false;\n this.name = data.name;\n this.rc = true;\n this.richChange = data.richChange || [];\n this.richChangeEx = data.richChangeEx || [];\n this.richValue = data.richValue || [];\n this.selEnd = data.selEnd ?? -1;\n this.selStart = data.selStart ?? -1;\n this.shift = data.shift || false;\n this.source = data.source || null;\n this.target = data.target || null;\n this.targetName = \"\";\n this.type = \"Field\";\n this.value = data.value || \"\";\n this.willCommit = data.willCommit || false;\n }\n}\nclass EventDispatcher {\n constructor(document, calculationOrder, objects, externalCall) {\n this._document = document;\n this._calculationOrder = calculationOrder;\n this._objects = objects;\n this._externalCall = externalCall;\n this._document.obj._eventDispatcher = this;\n this._isCalculating = false;\n }\n mergeChange(event) {\n let value = event.value;\n if (Array.isArray(value)) {\n return value;\n }\n if (typeof value !== \"string\") {\n value = value.toString();\n }\n const prefix = event.selStart >= 0 ? value.substring(0, event.selStart) : \"\";\n const postfix = event.selEnd >= 0 && event.selEnd <= value.length ? value.substring(event.selEnd) : \"\";\n return `${prefix}${event.change}${postfix}`;\n }\n userActivation() {\n this._document.obj._userActivation = true;\n this._externalCall(\"setTimeout\", [USERACTIVATION_CALLBACKID, USERACTIVATION_MAXTIME_VALIDITY]);\n }\n dispatch(baseEvent) {\n const id = baseEvent.id;\n if (!(id in this._objects)) {\n let event;\n if (id === \"doc\" || id === \"page\") {\n event = globalThis.event = new Event(baseEvent);\n event.source = event.target = this._document.wrapped;\n event.name = baseEvent.name;\n }\n if (id === \"doc\") {\n const eventName = event.name;\n if (eventName === \"Open\") {\n this.userActivation();\n this._document.obj._initActions();\n this.formatAll();\n }\n if (![\"DidPrint\", \"DidSave\", \"WillPrint\", \"WillSave\"].includes(eventName)) {\n this.userActivation();\n }\n this._document.obj._dispatchDocEvent(event.name);\n } else if (id === \"page\") {\n this.userActivation();\n this._document.obj._dispatchPageEvent(event.name, baseEvent.actions, baseEvent.pageNumber);\n } else if (id === \"app\" && baseEvent.name === \"ResetForm\") {\n this.userActivation();\n for (const fieldId of baseEvent.ids) {\n const obj = this._objects[fieldId];\n obj?.obj._reset();\n }\n }\n return;\n }\n const name = baseEvent.name;\n const source = this._objects[id];\n const event = globalThis.event = new Event(baseEvent);\n let savedChange;\n this.userActivation();\n if (source.obj._isButton()) {\n source.obj._id = id;\n event.value = source.obj._getExportValue(event.value);\n if (name === \"Action\") {\n source.obj._value = event.value;\n }\n }\n switch (name) {\n case \"Keystroke\":\n savedChange = {\n value: event.value,\n changeEx: event.changeEx,\n change: event.change,\n selStart: event.selStart,\n selEnd: event.selEnd\n };\n break;\n case \"Blur\":\n case \"Focus\":\n Object.defineProperty(event, \"value\", {\n configurable: false,\n writable: false,\n enumerable: true,\n value: event.value\n });\n break;\n case \"Validate\":\n this.runValidation(source, event);\n return;\n case \"Action\":\n this.runActions(source, source, event, name);\n this.runCalculate(source, event);\n return;\n }\n this.runActions(source, source, event, name);\n if (name !== \"Keystroke\") {\n return;\n }\n if (event.rc) {\n if (event.willCommit) {\n this.runValidation(source, event);\n } else {\n if (source.obj._isChoice) {\n source.obj.value = savedChange.changeEx;\n source.obj._send({\n id: source.obj._id,\n siblings: source.obj._siblings,\n value: source.obj.value\n });\n return;\n }\n const value = source.obj.value = this.mergeChange(event);\n let selStart, selEnd;\n if (event.selStart !== savedChange.selStart || event.selEnd !== savedChange.selEnd) {\n selStart = event.selStart;\n selEnd = event.selEnd;\n } else {\n selEnd = selStart = savedChange.selStart + event.change.length;\n }\n source.obj._send({\n id: source.obj._id,\n siblings: source.obj._siblings,\n value,\n selRange: [selStart, selEnd]\n });\n }\n } else if (!event.willCommit) {\n source.obj._send({\n id: source.obj._id,\n siblings: source.obj._siblings,\n value: savedChange.value,\n selRange: [savedChange.selStart, savedChange.selEnd]\n });\n } else {\n source.obj._send({\n id: source.obj._id,\n siblings: source.obj._siblings,\n value: \"\",\n formattedValue: null,\n selRange: [0, 0]\n });\n }\n }\n formatAll() {\n const event = globalThis.event = new Event({});\n for (const source of Object.values(this._objects)) {\n event.value = source.obj._getValue();\n this.runActions(source, source, event, \"Format\");\n }\n }\n runValidation(source, event) {\n const didValidateRun = this.runActions(source, source, event, \"Validate\");\n if (event.rc) {\n source.obj.value = event.value;\n this.runCalculate(source, event);\n const savedValue = event.value = source.obj._getValue();\n let formattedValue = null;\n if (this.runActions(source, source, event, \"Format\")) {\n formattedValue = event.value?.toString?.();\n }\n source.obj._send({\n id: source.obj._id,\n siblings: source.obj._siblings,\n value: savedValue,\n formattedValue\n });\n event.value = savedValue;\n } else if (didValidateRun) {\n source.obj._send({\n id: source.obj._id,\n siblings: source.obj._siblings,\n value: \"\",\n formattedValue: null,\n selRange: [0, 0],\n focus: true\n });\n }\n }\n runActions(source, target, event, eventName) {\n event.source = source.wrapped;\n event.target = target.wrapped;\n event.name = eventName;\n event.targetName = target.obj.name;\n event.rc = true;\n return target.obj._runActions(event);\n }\n calculateNow() {\n if (!this._calculationOrder || this._isCalculating || !this._document.obj.calculate) {\n return;\n }\n this._isCalculating = true;\n const first = this._calculationOrder[0];\n const source = this._objects[first];\n globalThis.event = new Event({});\n this.runCalculate(source, globalThis.event);\n this._isCalculating = false;\n }\n runCalculate(source, event) {\n if (!this._calculationOrder || !this._document.obj.calculate) {\n return;\n }\n for (const targetId of this._calculationOrder) {\n if (!(targetId in this._objects)) {\n continue;\n }\n if (!this._document.obj.calculate) {\n break;\n }\n event.value = null;\n const target = this._objects[targetId];\n let savedValue = target.obj._getValue();\n this.runActions(source, target, event, \"Calculate\");\n if (!event.rc) {\n continue;\n }\n if (event.value !== null) {\n target.obj.value = event.value;\n } else {\n event.value = target.obj._getValue();\n }\n this.runActions(target, target, event, \"Validate\");\n if (!event.rc) {\n if (target.obj._getValue() !== savedValue) {\n target.wrapped.value = savedValue;\n }\n continue;\n }\n if (event.value === null) {\n event.value = target.obj._getValue();\n }\n savedValue = target.obj._getValue();\n let formattedValue = null;\n if (this.runActions(target, target, event, \"Format\")) {\n formattedValue = event.value?.toString?.();\n }\n target.obj._send({\n id: target.obj._id,\n siblings: target.obj._siblings,\n value: savedValue,\n formattedValue\n });\n }\n }\n}\n\n;// ./src/scripting_api/fullscreen.js\n\n\nclass FullScreen extends PDFObject {\n constructor(data) {\n super(data);\n this._backgroundColor = [];\n this._clickAdvances = true;\n this._cursor = Cursor.hidden;\n this._defaultTransition = \"\";\n this._escapeExits = true;\n this._isFullScreen = true;\n this._loop = false;\n this._timeDelay = 3600;\n this._usePageTiming = false;\n this._useTimer = false;\n }\n get backgroundColor() {\n return this._backgroundColor;\n }\n set backgroundColor(_) {}\n get clickAdvances() {\n return this._clickAdvances;\n }\n set clickAdvances(_) {}\n get cursor() {\n return this._cursor;\n }\n set cursor(_) {}\n get defaultTransition() {\n return this._defaultTransition;\n }\n set defaultTransition(_) {}\n get escapeExits() {\n return this._escapeExits;\n }\n set escapeExits(_) {}\n get isFullScreen() {\n return this._isFullScreen;\n }\n set isFullScreen(_) {}\n get loop() {\n return this._loop;\n }\n set loop(_) {}\n get timeDelay() {\n return this._timeDelay;\n }\n set timeDelay(_) {}\n get transitions() {\n return [\"Replace\", \"WipeRight\", \"WipeLeft\", \"WipeDown\", \"WipeUp\", \"SplitHorizontalIn\", \"SplitHorizontalOut\", \"SplitVerticalIn\", \"SplitVerticalOut\", \"BlindsHorizontal\", \"BlindsVertical\", \"BoxIn\", \"BoxOut\", \"GlitterRight\", \"GlitterDown\", \"GlitterRightDown\", \"Dissolve\", \"Random\"];\n }\n set transitions(_) {\n throw new Error(\"fullscreen.transitions is read-only\");\n }\n get usePageTiming() {\n return this._usePageTiming;\n }\n set usePageTiming(_) {}\n get useTimer() {\n return this._useTimer;\n }\n set useTimer(_) {}\n}\n\n;// ./src/scripting_api/thermometer.js\n\nclass Thermometer extends PDFObject {\n constructor(data) {\n super(data);\n this._cancelled = false;\n this._duration = 100;\n this._text = \"\";\n this._value = 0;\n }\n get cancelled() {\n return this._cancelled;\n }\n set cancelled(_) {\n throw new Error(\"thermometer.cancelled is read-only\");\n }\n get duration() {\n return this._duration;\n }\n set duration(val) {\n this._duration = val;\n }\n get text() {\n return this._text;\n }\n set text(val) {\n this._text = val;\n }\n get value() {\n return this._value;\n }\n set value(val) {\n this._value = val;\n }\n begin() {}\n end() {}\n}\n\n;// ./src/scripting_api/app.js\n\n\n\n\n\n\nclass App extends PDFObject {\n constructor(data) {\n super(data);\n this._constants = null;\n this._focusRect = true;\n this._fs = null;\n this._language = App._getLanguage(data.language);\n this._openInPlace = false;\n this._platform = App._getPlatform(data.platform);\n this._runtimeHighlight = false;\n this._runtimeHighlightColor = [\"T\"];\n this._thermometer = null;\n this._toolbar = false;\n this._document = data._document;\n this._proxyHandler = data.proxyHandler;\n this._objects = Object.create(null);\n this._eventDispatcher = new EventDispatcher(this._document, data.calculationOrder, this._objects, data.externalCall);\n this._timeoutIds = new WeakMap();\n if (typeof FinalizationRegistry !== \"undefined\") {\n this._timeoutIdsRegistry = new FinalizationRegistry(this._cleanTimeout.bind(this));\n } else {\n this._timeoutIdsRegistry = null;\n }\n this._timeoutCallbackIds = new Map();\n this._timeoutCallbackId = USERACTIVATION_CALLBACKID + 1;\n this._globalEval = data.globalEval;\n this._externalCall = data.externalCall;\n }\n _dispatchEvent(pdfEvent) {\n this._eventDispatcher.dispatch(pdfEvent);\n }\n _registerTimeoutCallback(cExpr) {\n const id = this._timeoutCallbackId++;\n this._timeoutCallbackIds.set(id, cExpr);\n return id;\n }\n _unregisterTimeoutCallback(id) {\n this._timeoutCallbackIds.delete(id);\n }\n _evalCallback({\n callbackId,\n interval\n }) {\n if (callbackId === USERACTIVATION_CALLBACKID) {\n this._document.obj._userActivation = false;\n return;\n }\n const expr = this._timeoutCallbackIds.get(callbackId);\n if (!interval) {\n this._unregisterTimeoutCallback(callbackId);\n }\n if (expr) {\n this._globalEval(expr);\n }\n }\n _registerTimeout(callbackId, interval) {\n const timeout = Object.create(null);\n const id = {\n callbackId,\n interval\n };\n this._timeoutIds.set(timeout, id);\n this._timeoutIdsRegistry?.register(timeout, id);\n return timeout;\n }\n _unregisterTimeout(timeout) {\n this._timeoutIdsRegistry?.unregister(timeout);\n const data = this._timeoutIds.get(timeout);\n if (!data) {\n return;\n }\n this._timeoutIds.delete(timeout);\n this._cleanTimeout(data);\n }\n _cleanTimeout({\n callbackId,\n interval\n }) {\n this._unregisterTimeoutCallback(callbackId);\n if (interval) {\n this._externalCall(\"clearInterval\", [callbackId]);\n } else {\n this._externalCall(\"clearTimeout\", [callbackId]);\n }\n }\n static _getPlatform(platform) {\n if (typeof platform === \"string\") {\n platform = platform.toLowerCase();\n if (platform.includes(\"win\")) {\n return \"WIN\";\n } else if (platform.includes(\"mac\")) {\n return \"MAC\";\n }\n }\n return \"UNIX\";\n }\n static _getLanguage(language) {\n const [main, sub] = language.toLowerCase().split(/[-_]/);\n switch (main) {\n case \"zh\":\n if (sub === \"cn\" || sub === \"sg\") {\n return \"CHS\";\n }\n return \"CHT\";\n case \"da\":\n return \"DAN\";\n case \"de\":\n return \"DEU\";\n case \"es\":\n return \"ESP\";\n case \"fr\":\n return \"FRA\";\n case \"it\":\n return \"ITA\";\n case \"ko\":\n return \"KOR\";\n case \"ja\":\n return \"JPN\";\n case \"nl\":\n return \"NLD\";\n case \"no\":\n return \"NOR\";\n case \"pt\":\n if (sub === \"br\") {\n return \"PTB\";\n }\n return \"ENU\";\n case \"fi\":\n return \"SUO\";\n case \"SV\":\n return \"SVE\";\n default:\n return \"ENU\";\n }\n }\n get activeDocs() {\n return [this._document.wrapped];\n }\n set activeDocs(_) {\n throw new Error(\"app.activeDocs is read-only\");\n }\n get calculate() {\n return this._document.obj.calculate;\n }\n set calculate(calculate) {\n this._document.obj.calculate = calculate;\n }\n get constants() {\n if (!this._constants) {\n this._constants = Object.freeze({\n align: Object.freeze({\n left: 0,\n center: 1,\n right: 2,\n top: 3,\n bottom: 4\n })\n });\n }\n return this._constants;\n }\n set constants(_) {\n throw new Error(\"app.constants is read-only\");\n }\n get focusRect() {\n return this._focusRect;\n }\n set focusRect(val) {\n this._focusRect = val;\n }\n get formsVersion() {\n return FORMS_VERSION;\n }\n set formsVersion(_) {\n throw new Error(\"app.formsVersion is read-only\");\n }\n get fromPDFConverters() {\n return [];\n }\n set fromPDFConverters(_) {\n throw new Error(\"app.fromPDFConverters is read-only\");\n }\n get fs() {\n if (this._fs === null) {\n this._fs = new Proxy(new FullScreen({\n send: this._send\n }), this._proxyHandler);\n }\n return this._fs;\n }\n set fs(_) {\n throw new Error(\"app.fs is read-only\");\n }\n get language() {\n return this._language;\n }\n set language(_) {\n throw new Error(\"app.language is read-only\");\n }\n get media() {\n return undefined;\n }\n set media(_) {\n throw new Error(\"app.media is read-only\");\n }\n get monitors() {\n return [];\n }\n set monitors(_) {\n throw new Error(\"app.monitors is read-only\");\n }\n get numPlugins() {\n return 0;\n }\n set numPlugins(_) {\n throw new Error(\"app.numPlugins is read-only\");\n }\n get openInPlace() {\n return this._openInPlace;\n }\n set openInPlace(val) {\n this._openInPlace = val;\n }\n get platform() {\n return this._platform;\n }\n set platform(_) {\n throw new Error(\"app.platform is read-only\");\n }\n get plugins() {\n return [];\n }\n set plugins(_) {\n throw new Error(\"app.plugins is read-only\");\n }\n get printColorProfiles() {\n return [];\n }\n set printColorProfiles(_) {\n throw new Error(\"app.printColorProfiles is read-only\");\n }\n get printerNames() {\n return [];\n }\n set printerNames(_) {\n throw new Error(\"app.printerNames is read-only\");\n }\n get runtimeHighlight() {\n return this._runtimeHighlight;\n }\n set runtimeHighlight(val) {\n this._runtimeHighlight = val;\n }\n get runtimeHighlightColor() {\n return this._runtimeHighlightColor;\n }\n set runtimeHighlightColor(val) {\n if (Color._isValidColor(val)) {\n this._runtimeHighlightColor = val;\n }\n }\n get thermometer() {\n if (this._thermometer === null) {\n this._thermometer = new Proxy(new Thermometer({\n send: this._send\n }), this._proxyHandler);\n }\n return this._thermometer;\n }\n set thermometer(_) {\n throw new Error(\"app.thermometer is read-only\");\n }\n get toolbar() {\n return this._toolbar;\n }\n set toolbar(val) {\n this._toolbar = val;\n }\n get toolbarHorizontal() {\n return this.toolbar;\n }\n set toolbarHorizontal(value) {\n this.toolbar = value;\n }\n get toolbarVertical() {\n return this.toolbar;\n }\n set toolbarVertical(value) {\n this.toolbar = value;\n }\n get viewerType() {\n return VIEWER_TYPE;\n }\n set viewerType(_) {\n throw new Error(\"app.viewerType is read-only\");\n }\n get viewerVariation() {\n return VIEWER_VARIATION;\n }\n set viewerVariation(_) {\n throw new Error(\"app.viewerVariation is read-only\");\n }\n get viewerVersion() {\n return VIEWER_VERSION;\n }\n set viewerVersion(_) {\n throw new Error(\"app.viewerVersion is read-only\");\n }\n addMenuItem() {}\n addSubMenu() {}\n addToolButton() {}\n alert(cMsg, nIcon = 0, nType = 0, cTitle = \"PDF.js\", oDoc = null, oCheckbox = null) {\n if (!this._document.obj._userActivation) {\n return 0;\n }\n this._document.obj._userActivation = false;\n if (cMsg && typeof cMsg === \"object\") {\n nType = cMsg.nType;\n cMsg = cMsg.cMsg;\n }\n cMsg = (cMsg || \"\").toString();\n if (!cMsg) {\n return 0;\n }\n nType = typeof nType !== \"number\" || isNaN(nType) || nType < 0 || nType > 3 ? 0 : nType;\n if (nType >= 2) {\n return this._externalCall(\"confirm\", [cMsg]) ? 4 : 3;\n }\n this._externalCall(\"alert\", [cMsg]);\n return 1;\n }\n beep() {}\n beginPriv() {}\n browseForDoc() {}\n clearInterval(oInterval) {\n this._unregisterTimeout(oInterval);\n }\n clearTimeOut(oTime) {\n this._unregisterTimeout(oTime);\n }\n endPriv() {}\n execDialog() {}\n execMenuItem(item) {\n if (!this._document.obj._userActivation) {\n return;\n }\n this._document.obj._userActivation = false;\n switch (item) {\n case \"SaveAs\":\n if (this._document.obj._disableSaving) {\n return;\n }\n this._send({\n command: item\n });\n break;\n case \"FirstPage\":\n case \"LastPage\":\n case \"NextPage\":\n case \"PrevPage\":\n case \"ZoomViewIn\":\n case \"ZoomViewOut\":\n this._send({\n command: item\n });\n break;\n case \"FitPage\":\n this._send({\n command: \"zoom\",\n value: \"page-fit\"\n });\n break;\n case \"Print\":\n if (this._document.obj._disablePrinting) {\n return;\n }\n this._send({\n command: \"print\"\n });\n break;\n }\n }\n getNthPlugInName() {}\n getPath() {}\n goBack() {}\n goForward() {}\n hideMenuItem() {}\n hideToolbarButton() {}\n launchURL() {}\n listMenuItems() {}\n listToolbarButtons() {}\n loadPolicyFile() {}\n mailGetAddrs() {}\n mailMsg() {}\n newDoc() {}\n newCollection() {}\n newFDF() {}\n openDoc() {}\n openFDF() {}\n popUpMenu() {}\n popUpMenuEx() {}\n removeToolButton() {}\n response(cQuestion, cTitle = \"\", cDefault = \"\", bPassword = \"\", cLabel = \"\") {\n if (cQuestion && typeof cQuestion === \"object\") {\n cDefault = cQuestion.cDefault;\n cQuestion = cQuestion.cQuestion;\n }\n cQuestion = (cQuestion || \"\").toString();\n cDefault = (cDefault || \"\").toString();\n return this._externalCall(\"prompt\", [cQuestion, cDefault || \"\"]);\n }\n setInterval(cExpr, nMilliseconds = 0) {\n if (cExpr && typeof cExpr === \"object\") {\n nMilliseconds = cExpr.nMilliseconds || 0;\n cExpr = cExpr.cExpr;\n }\n if (typeof cExpr !== \"string\") {\n throw new TypeError(\"First argument of app.setInterval must be a string\");\n }\n if (typeof nMilliseconds !== \"number\") {\n throw new TypeError(\"Second argument of app.setInterval must be a number\");\n }\n const callbackId = this._registerTimeoutCallback(cExpr);\n this._externalCall(\"setInterval\", [callbackId, nMilliseconds]);\n return this._registerTimeout(callbackId, true);\n }\n setTimeOut(cExpr, nMilliseconds = 0) {\n if (cExpr && typeof cExpr === \"object\") {\n nMilliseconds = cExpr.nMilliseconds || 0;\n cExpr = cExpr.cExpr;\n }\n if (typeof cExpr !== \"string\") {\n throw new TypeError(\"First argument of app.setTimeOut must be a string\");\n }\n if (typeof nMilliseconds !== \"number\") {\n throw new TypeError(\"Second argument of app.setTimeOut must be a number\");\n }\n const callbackId = this._registerTimeoutCallback(cExpr);\n this._externalCall(\"setTimeout\", [callbackId, nMilliseconds]);\n return this._registerTimeout(callbackId, false);\n }\n trustedFunction() {}\n trustPropagatorFunction() {}\n}\n\n;// ./src/scripting_api/console.js\n\nclass Console extends PDFObject {\n clear() {\n this._send({\n id: \"clear\"\n });\n }\n hide() {}\n println(msg) {\n if (typeof msg === \"string\") {\n this._send({\n command: \"println\",\n value: \"PDF.js Console:: \" + msg\n });\n }\n }\n show() {}\n}\n\n;// ./src/scripting_api/print_params.js\nclass PrintParams {\n constructor(data) {\n this.binaryOk = true;\n this.bitmapDPI = 150;\n this.booklet = {\n binding: 0,\n duplexMode: 0,\n subsetFrom: 0,\n subsetTo: -1\n };\n this.colorOverride = 0;\n this.colorProfile = \"\";\n this.constants = Object.freeze({\n bookletBindings: Object.freeze({\n Left: 0,\n Right: 1,\n LeftTall: 2,\n RightTall: 3\n }),\n bookletDuplexMode: Object.freeze({\n BothSides: 0,\n FrontSideOnly: 1,\n BasicSideOnly: 2\n }),\n colorOverrides: Object.freeze({\n auto: 0,\n gray: 1,\n mono: 2\n }),\n fontPolicies: Object.freeze({\n everyPage: 0,\n jobStart: 1,\n pageRange: 2\n }),\n handling: Object.freeze({\n none: 0,\n fit: 1,\n shrink: 2,\n tileAll: 3,\n tileLarge: 4,\n nUp: 5,\n booklet: 6\n }),\n interactionLevel: Object.freeze({\n automatic: 0,\n full: 1,\n silent: 2\n }),\n nUpPageOrders: Object.freeze({\n Horizontal: 0,\n HorizontalReversed: 1,\n Vertical: 2\n }),\n printContents: Object.freeze({\n doc: 0,\n docAndComments: 1,\n formFieldsOnly: 2\n }),\n flagValues: Object.freeze({\n applyOverPrint: 1,\n applySoftProofSettings: 1 << 1,\n applyWorkingColorSpaces: 1 << 2,\n emitHalftones: 1 << 3,\n emitPostScriptXObjects: 1 << 4,\n emitFormsAsPSForms: 1 << 5,\n maxJP2KRes: 1 << 6,\n setPageSize: 1 << 7,\n suppressBG: 1 << 8,\n suppressCenter: 1 << 9,\n suppressCJKFontSubst: 1 << 10,\n suppressCropClip: 1 << 1,\n suppressRotate: 1 << 12,\n suppressTransfer: 1 << 13,\n suppressUCR: 1 << 14,\n useTrapAnnots: 1 << 15,\n usePrintersMarks: 1 << 16\n }),\n rasterFlagValues: Object.freeze({\n textToOutline: 1,\n strokesToOutline: 1 << 1,\n allowComplexClip: 1 << 2,\n preserveOverprint: 1 << 3\n }),\n subsets: Object.freeze({\n all: 0,\n even: 1,\n odd: 2\n }),\n tileMarks: Object.freeze({\n none: 0,\n west: 1,\n east: 2\n }),\n usages: Object.freeze({\n auto: 0,\n use: 1,\n noUse: 2\n })\n });\n this.downloadFarEastFonts = false;\n this.fileName = \"\";\n this.firstPage = 0;\n this.flags = 0;\n this.fontPolicy = 0;\n this.gradientDPI = 150;\n this.interactive = 1;\n this.lastPage = data.lastPage;\n this.npUpAutoRotate = false;\n this.npUpNumPagesH = 2;\n this.npUpNumPagesV = 2;\n this.npUpPageBorder = false;\n this.npUpPageOrder = 0;\n this.pageHandling = 0;\n this.pageSubset = 0;\n this.printAsImage = false;\n this.printContent = 0;\n this.printerName = \"\";\n this.psLevel = 0;\n this.rasterFlags = 0;\n this.reversePages = false;\n this.tileLabel = false;\n this.tileMark = 0;\n this.tileOverlap = 0;\n this.tileScale = 1.0;\n this.transparencyLevel = 75;\n this.usePrinterCRD = 0;\n this.useT1Conversion = 0;\n }\n}\n\n;// ./src/scripting_api/doc.js\n\n\n\n\n\nconst DOC_EXTERNAL = false;\nclass InfoProxyHandler {\n static get(obj, prop) {\n return obj[prop.toLowerCase()];\n }\n static set(obj, prop, value) {\n throw new Error(`doc.info.${prop} is read-only`);\n }\n}\nclass Doc extends PDFObject {\n constructor(data) {\n super(data);\n this._expandos = globalThis;\n this._baseURL = data.baseURL || \"\";\n this._calculate = true;\n this._delay = false;\n this._dirty = false;\n this._disclosed = false;\n this._media = undefined;\n this._metadata = data.metadata || \"\";\n this._noautocomplete = undefined;\n this._nocache = undefined;\n this._spellDictionaryOrder = [];\n this._spellLanguageOrder = [];\n this._printParams = null;\n this._fields = new Map();\n this._fieldNames = [];\n this._event = null;\n this._author = data.Author || \"\";\n this._creator = data.Creator || \"\";\n this._creationDate = this._getDate(data.CreationDate) || null;\n this._docID = data.docID || [\"\", \"\"];\n this._documentFileName = data.filename || \"\";\n this._filesize = data.filesize || 0;\n this._keywords = data.Keywords || \"\";\n this._layout = data.layout || \"\";\n this._modDate = this._getDate(data.ModDate) || null;\n this._numFields = 0;\n this._numPages = data.numPages || 1;\n this._pageNum = data.pageNum || 0;\n this._producer = data.Producer || \"\";\n this._securityHandler = data.EncryptFilterName || null;\n this._subject = data.Subject || \"\";\n this._title = data.Title || \"\";\n this._URL = data.URL || \"\";\n this._info = new Proxy({\n title: this._title,\n author: this._author,\n authors: data.authors || [this._author],\n subject: this._subject,\n keywords: this._keywords,\n creator: this._creator,\n producer: this._producer,\n creationdate: this._creationDate,\n moddate: this._modDate,\n trapped: data.Trapped || \"Unknown\"\n }, InfoProxyHandler);\n this._zoomType = ZoomType.none;\n this._zoom = data.zoom || 100;\n this._actions = createActionsMap(data.actions);\n this._globalEval = data.globalEval;\n this._pageActions = null;\n this._userActivation = false;\n this._disablePrinting = false;\n this._disableSaving = false;\n this._otherPageActions = null;\n }\n _initActions() {\n const dontRun = new Set([\"WillClose\", \"WillSave\", \"DidSave\", \"WillPrint\", \"DidPrint\", \"OpenAction\"]);\n this._disableSaving = true;\n for (const actionName of this._actions.keys()) {\n if (!dontRun.has(actionName)) {\n this._runActions(actionName);\n }\n }\n this._runActions(\"OpenAction\");\n this._disableSaving = false;\n }\n _dispatchDocEvent(name) {\n switch (name) {\n case \"Open\":\n this._disableSaving = true;\n this._runActions(\"OpenAction\");\n this._disableSaving = false;\n break;\n case \"WillPrint\":\n this._disablePrinting = true;\n try {\n this._runActions(name);\n } catch (error) {\n this._send(serializeError(error));\n }\n this._send({\n command: \"WillPrintFinished\"\n });\n this._disablePrinting = false;\n break;\n case \"WillSave\":\n this._disableSaving = true;\n this._runActions(name);\n this._disableSaving = false;\n break;\n default:\n this._runActions(name);\n }\n }\n _dispatchPageEvent(name, actions, pageNumber) {\n if (name === \"PageOpen\") {\n this._pageActions ||= new Map();\n if (!this._pageActions.has(pageNumber)) {\n this._pageActions.set(pageNumber, createActionsMap(actions));\n }\n this._pageNum = pageNumber - 1;\n }\n for (const acts of [this._pageActions, this._otherPageActions]) {\n actions = acts?.get(pageNumber)?.get(name);\n if (actions) {\n for (const action of actions) {\n this._globalEval(action);\n }\n }\n }\n }\n _runActions(name) {\n const actions = this._actions.get(name);\n if (!actions) {\n return;\n }\n for (const action of actions) {\n try {\n this._globalEval(action);\n } catch (error) {\n const serializedError = serializeError(error);\n serializedError.value = `Error when executing \"${name}\" for document\\n${serializedError.value}`;\n this._send(serializedError);\n }\n }\n }\n _addField(name, field) {\n this._fields.set(name, field);\n this._fieldNames.push(name);\n this._numFields++;\n const po = field.obj._actions.get(\"PageOpen\");\n const pc = field.obj._actions.get(\"PageClose\");\n if (po || pc) {\n this._otherPageActions ||= new Map();\n let actions = this._otherPageActions.get(field.obj._page + 1);\n if (!actions) {\n actions = new Map();\n this._otherPageActions.set(field.obj._page + 1, actions);\n }\n if (po) {\n let poActions = actions.get(\"PageOpen\");\n if (!poActions) {\n poActions = [];\n actions.set(\"PageOpen\", poActions);\n }\n poActions.push(...po);\n }\n if (pc) {\n let pcActions = actions.get(\"PageClose\");\n if (!pcActions) {\n pcActions = [];\n actions.set(\"PageClose\", pcActions);\n }\n pcActions.push(...pc);\n }\n }\n }\n _getDate(date) {\n if (!date || date.length < 15 || !date.startsWith(\"D:\")) {\n return date;\n }\n date = date.substring(2);\n const year = date.substring(0, 4);\n const month = date.substring(4, 6);\n const day = date.substring(6, 8);\n const hour = date.substring(8, 10);\n const minute = date.substring(10, 12);\n const o = date.charAt(12);\n let second, offsetPos;\n if (o === \"Z\" || o === \"+\" || o === \"-\") {\n second = \"00\";\n offsetPos = 12;\n } else {\n second = date.substring(12, 14);\n offsetPos = 14;\n }\n const offset = date.substring(offsetPos).replaceAll(\"'\", \"\");\n return new Date(`${year}-${month}-${day}T${hour}:${minute}:${second}${offset}`);\n }\n get author() {\n return this._author;\n }\n set author(_) {\n throw new Error(\"doc.author is read-only\");\n }\n get baseURL() {\n return this._baseURL;\n }\n set baseURL(baseURL) {\n this._baseURL = baseURL;\n }\n get bookmarkRoot() {\n return undefined;\n }\n set bookmarkRoot(_) {\n throw new Error(\"doc.bookmarkRoot is read-only\");\n }\n get calculate() {\n return this._calculate;\n }\n set calculate(calculate) {\n this._calculate = calculate;\n }\n get creator() {\n return this._creator;\n }\n set creator(_) {\n throw new Error(\"doc.creator is read-only\");\n }\n get dataObjects() {\n return [];\n }\n set dataObjects(_) {\n throw new Error(\"doc.dataObjects is read-only\");\n }\n get delay() {\n return this._delay;\n }\n set delay(delay) {\n this._delay = delay;\n }\n get dirty() {\n return this._dirty;\n }\n set dirty(dirty) {\n this._dirty = dirty;\n }\n get disclosed() {\n return this._disclosed;\n }\n set disclosed(disclosed) {\n this._disclosed = disclosed;\n }\n get docID() {\n return this._docID;\n }\n set docID(_) {\n throw new Error(\"doc.docID is read-only\");\n }\n get documentFileName() {\n return this._documentFileName;\n }\n set documentFileName(_) {\n throw new Error(\"doc.documentFileName is read-only\");\n }\n get dynamicXFAForm() {\n return false;\n }\n set dynamicXFAForm(_) {\n throw new Error(\"doc.dynamicXFAForm is read-only\");\n }\n get external() {\n return DOC_EXTERNAL;\n }\n set external(_) {\n throw new Error(\"doc.external is read-only\");\n }\n get filesize() {\n return this._filesize;\n }\n set filesize(_) {\n throw new Error(\"doc.filesize is read-only\");\n }\n get hidden() {\n return false;\n }\n set hidden(_) {\n throw new Error(\"doc.hidden is read-only\");\n }\n get hostContainer() {\n return undefined;\n }\n set hostContainer(_) {\n throw new Error(\"doc.hostContainer is read-only\");\n }\n get icons() {\n return undefined;\n }\n set icons(_) {\n throw new Error(\"doc.icons is read-only\");\n }\n get info() {\n return this._info;\n }\n set info(_) {\n throw new Error(\"doc.info is read-only\");\n }\n get innerAppWindowRect() {\n return [0, 0, 0, 0];\n }\n set innerAppWindowRect(_) {\n throw new Error(\"doc.innerAppWindowRect is read-only\");\n }\n get innerDocWindowRect() {\n return [0, 0, 0, 0];\n }\n set innerDocWindowRect(_) {\n throw new Error(\"doc.innerDocWindowRect is read-only\");\n }\n get isModal() {\n return false;\n }\n set isModal(_) {\n throw new Error(\"doc.isModal is read-only\");\n }\n get keywords() {\n return this._keywords;\n }\n set keywords(_) {\n throw new Error(\"doc.keywords is read-only\");\n }\n get layout() {\n return this._layout;\n }\n set layout(value) {\n if (!this._userActivation) {\n return;\n }\n this._userActivation = false;\n if (typeof value !== \"string\") {\n return;\n }\n if (value !== \"SinglePage\" && value !== \"OneColumn\" && value !== \"TwoColumnLeft\" && value !== \"TwoPageLeft\" && value !== \"TwoColumnRight\" && value !== \"TwoPageRight\") {\n value = \"SinglePage\";\n }\n this._send({\n command: \"layout\",\n value\n });\n this._layout = value;\n }\n get media() {\n return this._media;\n }\n set media(media) {\n this._media = media;\n }\n get metadata() {\n return this._metadata;\n }\n set metadata(metadata) {\n this._metadata = metadata;\n }\n get modDate() {\n return this._modDate;\n }\n set modDate(_) {\n throw new Error(\"doc.modDate is read-only\");\n }\n get mouseX() {\n return 0;\n }\n set mouseX(_) {\n throw new Error(\"doc.mouseX is read-only\");\n }\n get mouseY() {\n return 0;\n }\n set mouseY(_) {\n throw new Error(\"doc.mouseY is read-only\");\n }\n get noautocomplete() {\n return this._noautocomplete;\n }\n set noautocomplete(noautocomplete) {\n this._noautocomplete = noautocomplete;\n }\n get nocache() {\n return this._nocache;\n }\n set nocache(nocache) {\n this._nocache = nocache;\n }\n get numFields() {\n return this._numFields;\n }\n set numFields(_) {\n throw new Error(\"doc.numFields is read-only\");\n }\n get numPages() {\n return this._numPages;\n }\n set numPages(_) {\n throw new Error(\"doc.numPages is read-only\");\n }\n get numTemplates() {\n return 0;\n }\n set numTemplates(_) {\n throw new Error(\"doc.numTemplates is read-only\");\n }\n get outerAppWindowRect() {\n return [0, 0, 0, 0];\n }\n set outerAppWindowRect(_) {\n throw new Error(\"doc.outerAppWindowRect is read-only\");\n }\n get outerDocWindowRect() {\n return [0, 0, 0, 0];\n }\n set outerDocWindowRect(_) {\n throw new Error(\"doc.outerDocWindowRect is read-only\");\n }\n get pageNum() {\n return this._pageNum;\n }\n set pageNum(value) {\n if (!this._userActivation) {\n return;\n }\n this._userActivation = false;\n if (typeof value !== \"number\" || value < 0 || value >= this._numPages) {\n return;\n }\n this._send({\n command: \"page-num\",\n value\n });\n this._pageNum = value;\n }\n get pageWindowRect() {\n return [0, 0, 0, 0];\n }\n set pageWindowRect(_) {\n throw new Error(\"doc.pageWindowRect is read-only\");\n }\n get path() {\n return \"\";\n }\n set path(_) {\n throw new Error(\"doc.path is read-only\");\n }\n get permStatusReady() {\n return true;\n }\n set permStatusReady(_) {\n throw new Error(\"doc.permStatusReady is read-only\");\n }\n get producer() {\n return this._producer;\n }\n set producer(_) {\n throw new Error(\"doc.producer is read-only\");\n }\n get requiresFullSave() {\n return false;\n }\n set requiresFullSave(_) {\n throw new Error(\"doc.requiresFullSave is read-only\");\n }\n get securityHandler() {\n return this._securityHandler;\n }\n set securityHandler(_) {\n throw new Error(\"doc.securityHandler is read-only\");\n }\n get selectedAnnots() {\n return [];\n }\n set selectedAnnots(_) {\n throw new Error(\"doc.selectedAnnots is read-only\");\n }\n get sounds() {\n return [];\n }\n set sounds(_) {\n throw new Error(\"doc.sounds is read-only\");\n }\n get spellDictionaryOrder() {\n return this._spellDictionaryOrder;\n }\n set spellDictionaryOrder(spellDictionaryOrder) {\n this._spellDictionaryOrder = spellDictionaryOrder;\n }\n get spellLanguageOrder() {\n return this._spellLanguageOrder;\n }\n set spellLanguageOrder(spellLanguageOrder) {\n this._spellLanguageOrder = spellLanguageOrder;\n }\n get subject() {\n return this._subject;\n }\n set subject(_) {\n throw new Error(\"doc.subject is read-only\");\n }\n get templates() {\n return [];\n }\n set templates(_) {\n throw new Error(\"doc.templates is read-only\");\n }\n get title() {\n return this._title;\n }\n set title(_) {\n throw new Error(\"doc.title is read-only\");\n }\n get URL() {\n return this._URL;\n }\n set URL(_) {\n throw new Error(\"doc.URL is read-only\");\n }\n get viewState() {\n return undefined;\n }\n set viewState(_) {\n throw new Error(\"doc.viewState is read-only\");\n }\n get xfa() {\n return this._xfa;\n }\n set xfa(_) {\n throw new Error(\"doc.xfa is read-only\");\n }\n get XFAForeground() {\n return false;\n }\n set XFAForeground(_) {\n throw new Error(\"doc.XFAForeground is read-only\");\n }\n get zoomType() {\n return this._zoomType;\n }\n set zoomType(type) {\n if (!this._userActivation) {\n return;\n }\n this._userActivation = false;\n if (typeof type !== \"string\") {\n return;\n }\n switch (type) {\n case ZoomType.none:\n this._send({\n command: \"zoom\",\n value: 1\n });\n break;\n case ZoomType.fitP:\n this._send({\n command: \"zoom\",\n value: \"page-fit\"\n });\n break;\n case ZoomType.fitW:\n this._send({\n command: \"zoom\",\n value: \"page-width\"\n });\n break;\n case ZoomType.fitH:\n this._send({\n command: \"zoom\",\n value: \"page-height\"\n });\n break;\n case ZoomType.fitV:\n this._send({\n command: \"zoom\",\n value: \"auto\"\n });\n break;\n case ZoomType.pref:\n case ZoomType.refW:\n break;\n default:\n return;\n }\n this._zoomType = type;\n }\n get zoom() {\n return this._zoom;\n }\n set zoom(value) {\n if (!this._userActivation) {\n return;\n }\n this._userActivation = false;\n if (typeof value !== \"number\" || value < 8.33 || value > 6400) {\n return;\n }\n this._send({\n command: \"zoom\",\n value: value / 100\n });\n }\n addAnnot() {}\n addField() {}\n addIcon() {}\n addLink() {}\n addRecipientListCryptFilter() {}\n addRequirement() {}\n addScript() {}\n addThumbnails() {}\n addWatermarkFromFile() {}\n addWatermarkFromText() {}\n addWeblinks() {}\n bringToFront() {}\n calculateNow() {\n this._eventDispatcher.calculateNow();\n }\n closeDoc() {}\n colorConvertPage() {}\n createDataObject() {}\n createTemplate() {}\n deletePages() {}\n deleteSound() {}\n embedDocAsDataObject() {}\n embedOutputIntent() {}\n encryptForRecipients() {}\n encryptUsingPolicy() {}\n exportAsFDF() {}\n exportAsFDFStr() {}\n exportAsText() {}\n exportAsXFDF() {}\n exportAsXFDFStr() {}\n exportDataObject() {}\n exportXFAData() {}\n extractPages() {}\n flattenPages() {}\n getAnnot() {}\n getAnnots() {}\n getAnnot3D() {}\n getAnnots3D() {}\n getColorConvertAction() {}\n getDataObject() {}\n getDataObjectContents() {}\n _getField(cName) {\n if (cName && typeof cName === \"object\") {\n cName = cName.cName;\n }\n if (typeof cName !== \"string\") {\n throw new TypeError(\"Invalid field name: must be a string\");\n }\n const searchedField = this._fields.get(cName);\n if (searchedField) {\n return searchedField;\n }\n const parts = cName.split(\"#\");\n let childIndex = NaN;\n if (parts.length === 2) {\n childIndex = Math.floor(parseFloat(parts[1]));\n cName = parts[0];\n }\n for (const [name, field] of this._fields.entries()) {\n if (name.endsWith(cName)) {\n if (!isNaN(childIndex)) {\n const children = this._getChildren(name);\n if (childIndex < 0 || childIndex >= children.length) {\n childIndex = 0;\n }\n if (childIndex < children.length) {\n this._fields.set(cName, children[childIndex]);\n return children[childIndex];\n }\n }\n this._fields.set(cName, field);\n return field;\n }\n }\n return null;\n }\n getField(cName) {\n const field = this._getField(cName);\n if (!field) {\n return null;\n }\n return field.wrapped;\n }\n _getChildren(fieldName) {\n const len = fieldName.length;\n const children = [];\n const pattern = /^\\.[^.]+$/;\n for (const [name, field] of this._fields.entries()) {\n if (name.startsWith(fieldName)) {\n const finalPart = name.slice(len);\n if (pattern.test(finalPart)) {\n children.push(field);\n }\n }\n }\n return children;\n }\n _getTerminalChildren(fieldName) {\n const children = [];\n const len = fieldName.length;\n for (const [name, field] of this._fields.entries()) {\n if (name.startsWith(fieldName)) {\n const finalPart = name.slice(len);\n if (field.obj._hasValue && (finalPart === \"\" || finalPart.startsWith(\".\"))) {\n children.push(field.wrapped);\n }\n }\n }\n return children;\n }\n getIcon() {}\n getLegalWarnings() {}\n getLinks() {}\n getNthFieldName(nIndex) {\n if (nIndex && typeof nIndex === \"object\") {\n nIndex = nIndex.nIndex;\n }\n if (typeof nIndex !== \"number\") {\n throw new TypeError(\"Invalid field index: must be a number\");\n }\n if (0 <= nIndex && nIndex < this.numFields) {\n return this._fieldNames[Math.trunc(nIndex)];\n }\n return null;\n }\n getNthTemplate() {\n return null;\n }\n getOCGs() {}\n getOCGOrder() {}\n getPageBox() {}\n getPageLabel() {}\n getPageNthWord() {}\n getPageNthWordQuads() {}\n getPageNumWords() {}\n getPageRotation() {}\n getPageTransition() {}\n getPrintParams() {\n return this._printParams ||= new PrintParams({\n lastPage: this._numPages - 1\n });\n }\n getSound() {}\n getTemplate() {}\n getURL() {}\n gotoNamedDest() {}\n importAnFDF() {}\n importAnXFDF() {}\n importDataObject() {}\n importIcon() {}\n importSound() {}\n importTextData() {}\n importXFAData() {}\n insertPages() {}\n mailDoc() {}\n mailForm() {}\n movePage() {}\n newPage() {}\n openDataObject() {}\n print(bUI = true, nStart = 0, nEnd = -1, bSilent = false, bShrinkToFit = false, bPrintAsImage = false, bReverse = false, bAnnotations = true, printParams = null) {\n if (this._disablePrinting || !this._userActivation) {\n return;\n }\n this._userActivation = false;\n if (bUI && typeof bUI === \"object\") {\n nStart = bUI.nStart;\n nEnd = bUI.nEnd;\n bSilent = bUI.bSilent;\n bShrinkToFit = bUI.bShrinkToFit;\n bPrintAsImage = bUI.bPrintAsImage;\n bReverse = bUI.bReverse;\n bAnnotations = bUI.bAnnotations;\n printParams = bUI.printParams;\n bUI = bUI.bUI;\n }\n if (printParams) {\n nStart = printParams.firstPage;\n nEnd = printParams.lastPage;\n }\n nStart = typeof nStart === \"number\" ? Math.max(0, Math.trunc(nStart)) : 0;\n nEnd = typeof nEnd === \"number\" ? Math.max(0, Math.trunc(nEnd)) : -1;\n this._send({\n command: \"print\",\n start: nStart,\n end: nEnd\n });\n }\n removeDataObject() {}\n removeField() {}\n removeIcon() {}\n removeLinks() {}\n removeRequirement() {}\n removeScript() {}\n removeTemplate() {}\n removeThumbnails() {}\n removeWeblinks() {}\n replacePages() {}\n resetForm(aFields = null) {\n if (aFields && typeof aFields === \"object\" && !Array.isArray(aFields)) {\n aFields = aFields.aFields;\n }\n if (aFields && !Array.isArray(aFields)) {\n aFields = [aFields];\n }\n let mustCalculate = false;\n let fieldsToReset;\n if (aFields) {\n fieldsToReset = [];\n for (const fieldName of aFields) {\n if (!fieldName) {\n continue;\n }\n if (typeof fieldName !== \"string\") {\n fieldsToReset = null;\n break;\n }\n const field = this._getField(fieldName);\n if (!field) {\n continue;\n }\n fieldsToReset.push(field);\n mustCalculate = true;\n }\n }\n if (!fieldsToReset) {\n fieldsToReset = this._fields.values();\n mustCalculate = this._fields.size !== 0;\n }\n for (const field of fieldsToReset) {\n field.obj.value = field.obj.defaultValue;\n this._send({\n id: field.obj._id,\n siblings: field.obj._siblings,\n value: field.obj.defaultValue,\n formattedValue: null,\n selRange: [0, 0]\n });\n }\n if (mustCalculate) {\n this.calculateNow();\n }\n }\n saveAs() {}\n scroll() {}\n selectPageNthWord() {}\n setAction() {}\n setDataObjectContents() {}\n setOCGOrder() {}\n setPageAction() {}\n setPageBoxes() {}\n setPageLabels() {}\n setPageRotations() {}\n setPageTabOrder() {}\n setPageTransitions() {}\n spawnPageFromTemplate() {}\n submitForm() {}\n syncAnnotScan() {}\n}\n\n;// ./src/scripting_api/proxy.js\nclass ProxyHandler {\n constructor() {\n this.nosend = new Set([\"delay\"]);\n }\n get(obj, prop) {\n if (prop in obj._expandos) {\n const val = obj._expandos[prop];\n if (typeof val === \"function\") {\n return val.bind(obj);\n }\n return val;\n }\n if (typeof prop === \"string\" && !prop.startsWith(\"_\") && prop in obj) {\n const val = obj[prop];\n if (typeof val === \"function\") {\n return val.bind(obj);\n }\n return val;\n }\n return undefined;\n }\n set(obj, prop, value) {\n if (obj._kidIds) {\n obj._kidIds.forEach(id => {\n obj._appObjects[id].wrapped[prop] = value;\n });\n }\n if (typeof prop === \"string\" && !prop.startsWith(\"_\") && prop in obj) {\n const old = obj[prop];\n obj[prop] = value;\n if (!this.nosend.has(prop) && obj._send && obj._id !== null && typeof old !== \"function\") {\n const data = {\n id: obj._id\n };\n data[prop] = prop === \"value\" ? obj._getValue() : obj[prop];\n if (!obj._siblings) {\n obj._send(data);\n } else {\n data.siblings = obj._siblings;\n obj._send(data);\n }\n }\n } else {\n obj._expandos[prop] = value;\n }\n return true;\n }\n has(obj, prop) {\n return prop in obj._expandos || typeof prop === \"string\" && !prop.startsWith(\"_\") && prop in obj;\n }\n getPrototypeOf(obj) {\n return null;\n }\n setPrototypeOf(obj, proto) {\n return false;\n }\n isExtensible(obj) {\n return true;\n }\n preventExtensions(obj) {\n return false;\n }\n getOwnPropertyDescriptor(obj, prop) {\n if (prop in obj._expandos) {\n return {\n configurable: true,\n enumerable: true,\n value: obj._expandos[prop]\n };\n }\n if (typeof prop === \"string\" && !prop.startsWith(\"_\") && prop in obj) {\n return {\n configurable: true,\n enumerable: true,\n value: obj[prop]\n };\n }\n return undefined;\n }\n defineProperty(obj, key, descriptor) {\n Object.defineProperty(obj._expandos, key, descriptor);\n return true;\n }\n deleteProperty(obj, prop) {\n if (prop in obj._expandos) {\n delete obj._expandos[prop];\n }\n }\n ownKeys(obj) {\n const fromExpandos = Reflect.ownKeys(obj._expandos);\n const fromObj = Reflect.ownKeys(obj).filter(k => !k.startsWith(\"_\"));\n return fromExpandos.concat(fromObj);\n }\n}\n\n;// ./src/scripting_api/util.js\n\nclass Util extends PDFObject {\n #dateActionsCache = null;\n constructor(data) {\n super(data);\n this._scandCache = new Map();\n this._months = [\"January\", \"February\", \"March\", \"April\", \"May\", \"June\", \"July\", \"August\", \"September\", \"October\", \"November\", \"December\"];\n this._days = [\"Sunday\", \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\"];\n this.MILLISECONDS_IN_DAY = 86400000;\n this.MILLISECONDS_IN_WEEK = 604800000;\n this._externalCall = data.externalCall;\n }\n printf(...args) {\n if (args.length === 0) {\n throw new Error(\"Invalid number of params in printf\");\n }\n if (typeof args[0] !== \"string\") {\n throw new TypeError(\"First argument of printf must be a string\");\n }\n const pattern = /%(,[0-4])?([+ 0#]+)?(\\d+)?(\\.\\d+)?(.)/g;\n const PLUS = 1;\n const SPACE = 2;\n const ZERO = 4;\n const HASH = 8;\n let i = 0;\n return args[0].replaceAll(pattern, function (match, nDecSep, cFlags, nWidth, nPrecision, cConvChar) {\n if (cConvChar !== \"d\" && cConvChar !== \"f\" && cConvChar !== \"s\" && cConvChar !== \"x\") {\n const buf = [\"%\"];\n for (const str of [nDecSep, cFlags, nWidth, nPrecision, cConvChar]) {\n if (str) {\n buf.push(str);\n }\n }\n return buf.join(\"\");\n }\n i++;\n if (i === args.length) {\n throw new Error(\"Not enough arguments in printf\");\n }\n const arg = args[i];\n if (cConvChar === \"s\") {\n return arg.toString();\n }\n let flags = 0;\n if (cFlags) {\n for (const flag of cFlags) {\n switch (flag) {\n case \"+\":\n flags |= PLUS;\n break;\n case \" \":\n flags |= SPACE;\n break;\n case \"0\":\n flags |= ZERO;\n break;\n case \"#\":\n flags |= HASH;\n break;\n }\n }\n }\n cFlags = flags;\n if (nWidth) {\n nWidth = parseInt(nWidth);\n }\n let intPart = Math.trunc(arg);\n if (cConvChar === \"x\") {\n let hex = Math.abs(intPart).toString(16).toUpperCase();\n if (nWidth !== undefined) {\n hex = hex.padStart(nWidth, cFlags & ZERO ? \"0\" : \" \");\n }\n if (cFlags & HASH) {\n hex = `0x${hex}`;\n }\n return hex;\n }\n if (nPrecision) {\n nPrecision = parseInt(nPrecision.substring(1));\n }\n nDecSep = nDecSep ? nDecSep.substring(1) : \"0\";\n const separators = {\n 0: [\",\", \".\"],\n 1: [\"\", \".\"],\n 2: [\".\", \",\"],\n 3: [\"\", \",\"],\n 4: [\"'\", \".\"]\n };\n const [thousandSep, decimalSep] = separators[nDecSep];\n let decPart = \"\";\n if (cConvChar === \"f\") {\n decPart = nPrecision !== undefined ? Math.abs(arg - intPart).toFixed(nPrecision) : Math.abs(arg - intPart).toString();\n if (decPart.length > 2) {\n if (/^1\\.0+$/.test(decPart)) {\n intPart += Math.sign(arg);\n decPart = `${decimalSep}${decPart.split(\".\")[1]}`;\n } else {\n decPart = `${decimalSep}${decPart.substring(2)}`;\n }\n } else {\n if (decPart === \"1\") {\n intPart += Math.sign(arg);\n }\n decPart = cFlags & HASH ? \".\" : \"\";\n }\n }\n let sign = \"\";\n if (intPart < 0) {\n sign = \"-\";\n intPart = -intPart;\n } else if (cFlags & PLUS) {\n sign = \"+\";\n } else if (cFlags & SPACE) {\n sign = \" \";\n }\n if (thousandSep && intPart >= 1000) {\n const buf = [];\n while (true) {\n buf.push((intPart % 1000).toString().padStart(3, \"0\"));\n intPart = Math.trunc(intPart / 1000);\n if (intPart < 1000) {\n buf.push(intPart.toString());\n break;\n }\n }\n intPart = buf.reverse().join(thousandSep);\n } else {\n intPart = intPart.toString();\n }\n let n = `${intPart}${decPart}`;\n if (nWidth !== undefined) {\n n = n.padStart(nWidth - sign.length, cFlags & ZERO ? \"0\" : \" \");\n }\n return `${sign}${n}`;\n });\n }\n iconStreamFromIcon() {}\n printd(cFormat, oDate) {\n switch (cFormat) {\n case 0:\n return this.printd(\"D:yyyymmddHHMMss\", oDate);\n case 1:\n return this.printd(\"yyyy.mm.dd HH:MM:ss\", oDate);\n case 2:\n return this.printd(\"m/d/yy h:MM:ss tt\", oDate);\n }\n const handlers = {\n mmmm: data => this._months[data.month],\n mmm: data => this._months[data.month].substring(0, 3),\n mm: data => (data.month + 1).toString().padStart(2, \"0\"),\n m: data => (data.month + 1).toString(),\n dddd: data => this._days[data.dayOfWeek],\n ddd: data => this._days[data.dayOfWeek].substring(0, 3),\n dd: data => data.day.toString().padStart(2, \"0\"),\n d: data => data.day.toString(),\n yyyy: data => data.year.toString(),\n yy: data => (data.year % 100).toString().padStart(2, \"0\"),\n HH: data => data.hours.toString().padStart(2, \"0\"),\n H: data => data.hours.toString(),\n hh: data => (1 + (data.hours + 11) % 12).toString().padStart(2, \"0\"),\n h: data => (1 + (data.hours + 11) % 12).toString(),\n MM: data => data.minutes.toString().padStart(2, \"0\"),\n M: data => data.minutes.toString(),\n ss: data => data.seconds.toString().padStart(2, \"0\"),\n s: data => data.seconds.toString(),\n tt: data => data.hours < 12 ? \"am\" : \"pm\",\n t: data => data.hours < 12 ? \"a\" : \"p\"\n };\n const data = {\n year: oDate.getFullYear(),\n month: oDate.getMonth(),\n day: oDate.getDate(),\n dayOfWeek: oDate.getDay(),\n hours: oDate.getHours(),\n minutes: oDate.getMinutes(),\n seconds: oDate.getSeconds()\n };\n const patterns = /(mmmm|mmm|mm|m|dddd|ddd|dd|d|yyyy|yy|HH|H|hh|h|MM|M|ss|s|tt|t|\\\\.)/g;\n return cFormat.replaceAll(patterns, function (match, pattern) {\n if (pattern in handlers) {\n return handlers[pattern](data);\n }\n return pattern.charCodeAt(1);\n });\n }\n printx(cFormat, cSource) {\n cSource = (cSource ?? \"\").toString();\n const handlers = [x => x, x => x.toUpperCase(), x => x.toLowerCase()];\n const buf = [];\n let i = 0;\n const ii = cSource.length;\n let currCase = handlers[0];\n let escaped = false;\n for (const command of cFormat) {\n if (escaped) {\n buf.push(command);\n escaped = false;\n continue;\n }\n if (i >= ii) {\n break;\n }\n switch (command) {\n case \"?\":\n buf.push(currCase(cSource.charAt(i++)));\n break;\n case \"X\":\n while (i < ii) {\n const char = cSource.charAt(i++);\n if (\"a\" <= char && char <= \"z\" || \"A\" <= char && char <= \"Z\" || \"0\" <= char && char <= \"9\") {\n buf.push(currCase(char));\n break;\n }\n }\n break;\n case \"A\":\n while (i < ii) {\n const char = cSource.charAt(i++);\n if (\"a\" <= char && char <= \"z\" || \"A\" <= char && char <= \"Z\") {\n buf.push(currCase(char));\n break;\n }\n }\n break;\n case \"9\":\n while (i < ii) {\n const char = cSource.charAt(i++);\n if (\"0\" <= char && char <= \"9\") {\n buf.push(char);\n break;\n }\n }\n break;\n case \"*\":\n while (i < ii) {\n buf.push(currCase(cSource.charAt(i++)));\n }\n break;\n case \"\\\\\":\n escaped = true;\n break;\n case \">\":\n currCase = handlers[1];\n break;\n case \"<\":\n currCase = handlers[2];\n break;\n case \"=\":\n currCase = handlers[0];\n break;\n default:\n buf.push(command);\n }\n }\n return buf.join(\"\");\n }\n #tryToGuessDate(cFormat, cDate) {\n let actions = (this.#dateActionsCache ||= new Map()).get(cFormat);\n if (!actions) {\n actions = [];\n this.#dateActionsCache.set(cFormat, actions);\n cFormat.replaceAll(/(d+)|(m+)|(y+)|(H+)|(M+)|(s+)/g, function (_match, d, m, y, H, M, s) {\n if (d) {\n actions.push((n, data) => {\n if (n >= 1 && n <= 31) {\n data.day = n;\n return true;\n }\n return false;\n });\n } else if (m) {\n actions.push((n, data) => {\n if (n >= 1 && n <= 12) {\n data.month = n - 1;\n return true;\n }\n return false;\n });\n } else if (y) {\n actions.push((n, data) => {\n if (n < 50) {\n n += 2000;\n } else if (n < 100) {\n n += 1900;\n }\n data.year = n;\n return true;\n });\n } else if (H) {\n actions.push((n, data) => {\n if (n >= 0 && n <= 23) {\n data.hours = n;\n return true;\n }\n return false;\n });\n } else if (M) {\n actions.push((n, data) => {\n if (n >= 0 && n <= 59) {\n data.minutes = n;\n return true;\n }\n return false;\n });\n } else if (s) {\n actions.push((n, data) => {\n if (n >= 0 && n <= 59) {\n data.seconds = n;\n return true;\n }\n return false;\n });\n }\n return \"\";\n });\n }\n const number = /\\d+/g;\n let i = 0;\n let array;\n const data = {\n year: new Date().getFullYear(),\n month: 0,\n day: 1,\n hours: 12,\n minutes: 0,\n seconds: 0\n };\n while ((array = number.exec(cDate)) !== null) {\n if (i < actions.length) {\n if (!actions[i++](parseInt(array[0]), data)) {\n return null;\n }\n } else {\n break;\n }\n }\n if (i === 0) {\n return null;\n }\n return new Date(data.year, data.month, data.day, data.hours, data.minutes, data.seconds);\n }\n scand(cFormat, cDate) {\n return this._scand(cFormat, cDate);\n }\n _scand(cFormat, cDate, strict = false) {\n if (typeof cDate !== \"string\") {\n return new Date(cDate);\n }\n if (cDate === \"\") {\n return new Date();\n }\n switch (cFormat) {\n case 0:\n return this.scand(\"D:yyyymmddHHMMss\", cDate);\n case 1:\n return this.scand(\"yyyy.mm.dd HH:MM:ss\", cDate);\n case 2:\n return this.scand(\"m/d/yy h:MM:ss tt\", cDate);\n }\n if (!this._scandCache.has(cFormat)) {\n const months = this._months;\n const days = this._days;\n const handlers = {\n mmmm: {\n pattern: `(${months.join(\"|\")})`,\n action: (value, data) => {\n data.month = months.indexOf(value);\n }\n },\n mmm: {\n pattern: `(${months.map(month => month.substring(0, 3)).join(\"|\")})`,\n action: (value, data) => {\n data.month = months.findIndex(month => month.substring(0, 3) === value);\n }\n },\n mm: {\n pattern: `(\\\\d{2})`,\n action: (value, data) => {\n data.month = parseInt(value) - 1;\n }\n },\n m: {\n pattern: `(\\\\d{1,2})`,\n action: (value, data) => {\n data.month = parseInt(value) - 1;\n }\n },\n dddd: {\n pattern: `(${days.join(\"|\")})`,\n action: (value, data) => {\n data.day = days.indexOf(value);\n }\n },\n ddd: {\n pattern: `(${days.map(day => day.substring(0, 3)).join(\"|\")})`,\n action: (value, data) => {\n data.day = days.findIndex(day => day.substring(0, 3) === value);\n }\n },\n dd: {\n pattern: \"(\\\\d{2})\",\n action: (value, data) => {\n data.day = parseInt(value);\n }\n },\n d: {\n pattern: \"(\\\\d{1,2})\",\n action: (value, data) => {\n data.day = parseInt(value);\n }\n },\n yyyy: {\n pattern: \"(\\\\d{4})\",\n action: (value, data) => {\n data.year = parseInt(value);\n }\n },\n yy: {\n pattern: \"(\\\\d{2})\",\n action: (value, data) => {\n data.year = 2000 + parseInt(value);\n }\n },\n HH: {\n pattern: \"(\\\\d{2})\",\n action: (value, data) => {\n data.hours = parseInt(value);\n }\n },\n H: {\n pattern: \"(\\\\d{1,2})\",\n action: (value, data) => {\n data.hours = parseInt(value);\n }\n },\n hh: {\n pattern: \"(\\\\d{2})\",\n action: (value, data) => {\n data.hours = parseInt(value);\n }\n },\n h: {\n pattern: \"(\\\\d{1,2})\",\n action: (value, data) => {\n data.hours = parseInt(value);\n }\n },\n MM: {\n pattern: \"(\\\\d{2})\",\n action: (value, data) => {\n data.minutes = parseInt(value);\n }\n },\n M: {\n pattern: \"(\\\\d{1,2})\",\n action: (value, data) => {\n data.minutes = parseInt(value);\n }\n },\n ss: {\n pattern: \"(\\\\d{2})\",\n action: (value, data) => {\n data.seconds = parseInt(value);\n }\n },\n s: {\n pattern: \"(\\\\d{1,2})\",\n action: (value, data) => {\n data.seconds = parseInt(value);\n }\n },\n tt: {\n pattern: \"([aApP][mM])\",\n action: (value, data) => {\n const char = value.charAt(0);\n data.am = char === \"a\" || char === \"A\";\n }\n },\n t: {\n pattern: \"([aApP])\",\n action: (value, data) => {\n data.am = value === \"a\" || value === \"A\";\n }\n }\n };\n const escapedFormat = cFormat.replaceAll(/[.*+\\-?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const patterns = /(mmmm|mmm|mm|m|dddd|ddd|dd|d|yyyy|yy|HH|H|hh|h|MM|M|ss|s|tt|t)/g;\n const actions = [];\n const re = escapedFormat.replaceAll(patterns, function (match, patternElement) {\n const {\n pattern,\n action\n } = handlers[patternElement];\n actions.push(action);\n return pattern;\n });\n this._scandCache.set(cFormat, [re, actions]);\n }\n const [re, actions] = this._scandCache.get(cFormat);\n const matches = new RegExp(`^${re}$`, \"g\").exec(cDate);\n if (!matches || matches.length !== actions.length + 1) {\n return strict ? null : this.#tryToGuessDate(cFormat, cDate);\n }\n const data = {\n year: new Date().getFullYear(),\n month: 0,\n day: 1,\n hours: 12,\n minutes: 0,\n seconds: 0,\n am: null\n };\n actions.forEach((action, i) => action(matches[i + 1], data));\n if (data.am !== null) {\n data.hours = data.hours % 12 + (data.am ? 0 : 12);\n }\n return new Date(data.year, data.month, data.day, data.hours, data.minutes, data.seconds);\n }\n spansToXML() {}\n stringFromStream() {}\n xmlToSpans() {}\n}\n\n;// ./src/scripting_api/initialization.js\n\n\n\n\n\n\n\n\n\n\nfunction initSandbox(params) {\n delete globalThis.pdfjsScripting;\n const externalCall = globalThis.callExternalFunction;\n delete globalThis.callExternalFunction;\n const globalEval = code => globalThis.eval(code);\n const send = data => externalCall(\"send\", [data]);\n const proxyHandler = new ProxyHandler();\n const {\n data\n } = params;\n const doc = new Doc({\n send,\n globalEval,\n ...data.docInfo\n });\n const _document = {\n obj: doc,\n wrapped: new Proxy(doc, proxyHandler)\n };\n const app = new App({\n send,\n globalEval,\n externalCall,\n _document,\n calculationOrder: data.calculationOrder,\n proxyHandler,\n ...data.appInfo\n });\n const util = new Util({\n externalCall\n });\n const appObjects = app._objects;\n if (data.objects) {\n const annotations = [];\n for (const [name, objs] of Object.entries(data.objects)) {\n annotations.length = 0;\n let container = null;\n for (const obj of objs) {\n if (obj.type !== \"\") {\n annotations.push(obj);\n } else {\n container = obj;\n }\n }\n let obj = container;\n if (annotations.length > 0) {\n obj = annotations[0];\n obj.send = send;\n }\n obj.globalEval = globalEval;\n obj.doc = _document;\n obj.fieldPath = name;\n obj.appObjects = appObjects;\n const otherFields = annotations.slice(1);\n let field;\n switch (obj.type) {\n case \"radiobutton\":\n {\n field = new RadioButtonField(otherFields, obj);\n break;\n }\n case \"checkbox\":\n {\n field = new CheckboxField(otherFields, obj);\n break;\n }\n default:\n if (otherFields.length > 0) {\n obj.siblings = otherFields.map(x => x.id);\n }\n field = new Field(obj);\n }\n const wrapped = new Proxy(field, proxyHandler);\n const _object = {\n obj: field,\n wrapped\n };\n doc._addField(name, _object);\n for (const object of objs) {\n appObjects[object.id] = _object;\n }\n if (container) {\n appObjects[container.id] = _object;\n }\n }\n }\n const color = new Color();\n globalThis.event = null;\n globalThis.global = Object.create(null);\n globalThis.app = new Proxy(app, proxyHandler);\n globalThis.color = new Proxy(color, proxyHandler);\n globalThis.console = new Proxy(new Console({\n send\n }), proxyHandler);\n globalThis.util = new Proxy(util, proxyHandler);\n globalThis.border = Border;\n globalThis.cursor = Cursor;\n globalThis.display = Display;\n globalThis.font = Font;\n globalThis.highlight = Highlight;\n globalThis.position = Position;\n globalThis.scaleHow = ScaleHow;\n globalThis.scaleWhen = ScaleWhen;\n globalThis.style = Style;\n globalThis.trans = Trans;\n globalThis.zoomtype = ZoomType;\n globalThis.ADBE = {\n Reader_Value_Asked: true,\n Viewer_Value_Asked: true\n };\n const aform = new AForm(doc, app, util, color);\n for (const name of Object.getOwnPropertyNames(AForm.prototype)) {\n if (name !== \"constructor\" && !name.startsWith(\"_\")) {\n globalThis[name] = aform[name].bind(aform);\n }\n }\n for (const [name, value] of Object.entries(GlobalConstants)) {\n Object.defineProperty(globalThis, name, {\n value,\n writable: false\n });\n }\n Object.defineProperties(globalThis, {\n ColorConvert: {\n value: color.convert.bind(color),\n writable: true\n },\n ColorEqual: {\n value: color.equal.bind(color),\n writable: true\n }\n });\n const properties = Object.create(null);\n for (const name of Object.getOwnPropertyNames(Doc.prototype)) {\n if (name === \"constructor\" || name.startsWith(\"_\")) {\n continue;\n }\n const descriptor = Object.getOwnPropertyDescriptor(Doc.prototype, name);\n if (descriptor.get) {\n properties[name] = {\n get: descriptor.get.bind(doc),\n set: descriptor.set.bind(doc)\n };\n } else {\n properties[name] = {\n value: Doc.prototype[name].bind(doc)\n };\n }\n }\n Object.defineProperties(globalThis, properties);\n const functions = {\n dispatchEvent: app._dispatchEvent.bind(app),\n timeoutCb: app._evalCallback.bind(app)\n };\n return (name, args) => {\n try {\n functions[name](args);\n } catch (error) {\n send(serializeError(error));\n }\n };\n}\n\n;// ./src/pdf.scripting.js\n\nconst pdfjsVersion = \"5.1.91\";\nconst pdfjsBuild = \"45cbe8bb0\";\nglobalThis.pdfjsScripting = {\n initSandbox: initSandbox\n};\n"];
code.push("delete dump;");
let success = false;
let buf = 0;
try {
const sandboxData = JSON.stringify(data);
code.push(`pdfjsScripting.initSandbox({ data: ${sandboxData} })`);
buf = this._module.stringToNewUTF8(code.join("\n"));
success = !!this._module.ccall("init", "number", ["number", "number"], [buf, this._alertOnError]);
} catch (error) {
console.error(error);
} finally {
if (buf) {
this._module.ccall("free", "number", ["number"], [buf]);
}
}
if (success) {
this.support.commFun = this._module.cwrap("commFun", null, ["string", "string"]);
} else {
this.nukeSandbox();
throw new Error("Cannot start sandbox");
}
}
dispatchEvent(event) {
this.support?.callSandboxFunction("dispatchEvent", event);
}
dumpMemoryUse() {
this._module?.ccall("dumpMemoryUse", null, []);
}
nukeSandbox() {
if (this._module !== null) {
this.support.destroy();
this.support = null;
this._module.ccall("nukeSandbox", null, []);
this._module = null;
}
}
evalForTesting(code, key) {
throw new Error("Not implemented: evalForTesting");
}
}
function QuickJSSandbox() {
return quickjs_eval().then(module => new Sandbox(window, module));
}
var __webpack_exports__QuickJSSandbox = __webpack_exports__.QuickJSSandbox;
export { __webpack_exports__QuickJSSandbox as QuickJSSandbox };
//# sourceMappingURL=pdf.sandbox.mjs.map
================================================
FILE: cookbook/static/pdfjs/web/pdf.worker.mjs
================================================
/**
* @licstart The following is the entire license notice for the
* JavaScript code in this page
*
* Copyright 2024 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @licend The above is the entire license notice for the
* JavaScript code in this page
*/
/******/ // The require scope
/******/ var __webpack_require__ = {};
/******/
/************************************************************************/
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ // define getter functions for harmony exports
/******/ __webpack_require__.d = (exports, definition) => {
/******/ for(var key in definition) {
/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ }
/******/ }
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */
/******/ (() => {
/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ })();
/******/
/************************************************************************/
var __webpack_exports__ = globalThis.pdfjsWorker = {};
// EXPORTS
__webpack_require__.d(__webpack_exports__, {
WorkerMessageHandler: () => (/* reexport */ WorkerMessageHandler)
});
;// ./src/shared/util.js
const isNodeJS = typeof process === "object" && process + "" === "[object process]" && !process.versions.nw && !(process.versions.electron && process.type && process.type !== "browser");
const IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0];
const FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0];
const LINE_FACTOR = 1.35;
const LINE_DESCENT_FACTOR = 0.35;
const BASELINE_FACTOR = LINE_DESCENT_FACTOR / LINE_FACTOR;
const RenderingIntentFlag = {
ANY: 0x01,
DISPLAY: 0x02,
PRINT: 0x04,
SAVE: 0x08,
ANNOTATIONS_FORMS: 0x10,
ANNOTATIONS_STORAGE: 0x20,
ANNOTATIONS_DISABLE: 0x40,
IS_EDITING: 0x80,
OPLIST: 0x100
};
const AnnotationMode = {
DISABLE: 0,
ENABLE: 1,
ENABLE_FORMS: 2,
ENABLE_STORAGE: 3
};
const AnnotationEditorPrefix = "pdfjs_internal_editor_";
const AnnotationEditorType = {
DISABLE: -1,
NONE: 0,
FREETEXT: 3,
HIGHLIGHT: 9,
STAMP: 13,
INK: 15,
SIGNATURE: 101
};
const AnnotationEditorParamsType = {
RESIZE: 1,
CREATE: 2,
FREETEXT_SIZE: 11,
FREETEXT_COLOR: 12,
FREETEXT_OPACITY: 13,
INK_COLOR: 21,
INK_THICKNESS: 22,
INK_OPACITY: 23,
HIGHLIGHT_COLOR: 31,
HIGHLIGHT_DEFAULT_COLOR: 32,
HIGHLIGHT_THICKNESS: 33,
HIGHLIGHT_FREE: 34,
HIGHLIGHT_SHOW_ALL: 35,
DRAW_STEP: 41
};
const PermissionFlag = {
PRINT: 0x04,
MODIFY_CONTENTS: 0x08,
COPY: 0x10,
MODIFY_ANNOTATIONS: 0x20,
FILL_INTERACTIVE_FORMS: 0x100,
COPY_FOR_ACCESSIBILITY: 0x200,
ASSEMBLE: 0x400,
PRINT_HIGH_QUALITY: 0x800
};
const TextRenderingMode = {
FILL: 0,
STROKE: 1,
FILL_STROKE: 2,
INVISIBLE: 3,
FILL_ADD_TO_PATH: 4,
STROKE_ADD_TO_PATH: 5,
FILL_STROKE_ADD_TO_PATH: 6,
ADD_TO_PATH: 7,
FILL_STROKE_MASK: 3,
ADD_TO_PATH_FLAG: 4
};
const ImageKind = {
GRAYSCALE_1BPP: 1,
RGB_24BPP: 2,
RGBA_32BPP: 3
};
const AnnotationType = {
TEXT: 1,
LINK: 2,
FREETEXT: 3,
LINE: 4,
SQUARE: 5,
CIRCLE: 6,
POLYGON: 7,
POLYLINE: 8,
HIGHLIGHT: 9,
UNDERLINE: 10,
SQUIGGLY: 11,
STRIKEOUT: 12,
STAMP: 13,
CARET: 14,
INK: 15,
POPUP: 16,
FILEATTACHMENT: 17,
SOUND: 18,
MOVIE: 19,
WIDGET: 20,
SCREEN: 21,
PRINTERMARK: 22,
TRAPNET: 23,
WATERMARK: 24,
THREED: 25,
REDACT: 26
};
const AnnotationReplyType = {
GROUP: "Group",
REPLY: "R"
};
const AnnotationFlag = {
INVISIBLE: 0x01,
HIDDEN: 0x02,
PRINT: 0x04,
NOZOOM: 0x08,
NOROTATE: 0x10,
NOVIEW: 0x20,
READONLY: 0x40,
LOCKED: 0x80,
TOGGLENOVIEW: 0x100,
LOCKEDCONTENTS: 0x200
};
const AnnotationFieldFlag = {
READONLY: 0x0000001,
REQUIRED: 0x0000002,
NOEXPORT: 0x0000004,
MULTILINE: 0x0001000,
PASSWORD: 0x0002000,
NOTOGGLETOOFF: 0x0004000,
RADIO: 0x0008000,
PUSHBUTTON: 0x0010000,
COMBO: 0x0020000,
EDIT: 0x0040000,
SORT: 0x0080000,
FILESELECT: 0x0100000,
MULTISELECT: 0x0200000,
DONOTSPELLCHECK: 0x0400000,
DONOTSCROLL: 0x0800000,
COMB: 0x1000000,
RICHTEXT: 0x2000000,
RADIOSINUNISON: 0x2000000,
COMMITONSELCHANGE: 0x4000000
};
const AnnotationBorderStyleType = {
SOLID: 1,
DASHED: 2,
BEVELED: 3,
INSET: 4,
UNDERLINE: 5
};
const AnnotationActionEventType = {
E: "Mouse Enter",
X: "Mouse Exit",
D: "Mouse Down",
U: "Mouse Up",
Fo: "Focus",
Bl: "Blur",
PO: "PageOpen",
PC: "PageClose",
PV: "PageVisible",
PI: "PageInvisible",
K: "Keystroke",
F: "Format",
V: "Validate",
C: "Calculate"
};
const DocumentActionEventType = {
WC: "WillClose",
WS: "WillSave",
DS: "DidSave",
WP: "WillPrint",
DP: "DidPrint"
};
const PageActionEventType = {
O: "PageOpen",
C: "PageClose"
};
const VerbosityLevel = {
ERRORS: 0,
WARNINGS: 1,
INFOS: 5
};
const OPS = {
dependency: 1,
setLineWidth: 2,
setLineCap: 3,
setLineJoin: 4,
setMiterLimit: 5,
setDash: 6,
setRenderingIntent: 7,
setFlatness: 8,
setGState: 9,
save: 10,
restore: 11,
transform: 12,
moveTo: 13,
lineTo: 14,
curveTo: 15,
curveTo2: 16,
curveTo3: 17,
closePath: 18,
rectangle: 19,
stroke: 20,
closeStroke: 21,
fill: 22,
eoFill: 23,
fillStroke: 24,
eoFillStroke: 25,
closeFillStroke: 26,
closeEOFillStroke: 27,
endPath: 28,
clip: 29,
eoClip: 30,
beginText: 31,
endText: 32,
setCharSpacing: 33,
setWordSpacing: 34,
setHScale: 35,
setLeading: 36,
setFont: 37,
setTextRenderingMode: 38,
setTextRise: 39,
moveText: 40,
setLeadingMoveText: 41,
setTextMatrix: 42,
nextLine: 43,
showText: 44,
showSpacedText: 45,
nextLineShowText: 46,
nextLineSetSpacingShowText: 47,
setCharWidth: 48,
setCharWidthAndBounds: 49,
setStrokeColorSpace: 50,
setFillColorSpace: 51,
setStrokeColor: 52,
setStrokeColorN: 53,
setFillColor: 54,
setFillColorN: 55,
setStrokeGray: 56,
setFillGray: 57,
setStrokeRGBColor: 58,
setFillRGBColor: 59,
setStrokeCMYKColor: 60,
setFillCMYKColor: 61,
shadingFill: 62,
beginInlineImage: 63,
beginImageData: 64,
endInlineImage: 65,
paintXObject: 66,
markPoint: 67,
markPointProps: 68,
beginMarkedContent: 69,
beginMarkedContentProps: 70,
endMarkedContent: 71,
beginCompat: 72,
endCompat: 73,
paintFormXObjectBegin: 74,
paintFormXObjectEnd: 75,
beginGroup: 76,
endGroup: 77,
beginAnnotation: 80,
endAnnotation: 81,
paintImageMaskXObject: 83,
paintImageMaskXObjectGroup: 84,
paintImageXObject: 85,
paintInlineImageXObject: 86,
paintInlineImageXObjectGroup: 87,
paintImageXObjectRepeat: 88,
paintImageMaskXObjectRepeat: 89,
paintSolidColorImageMask: 90,
constructPath: 91,
setStrokeTransparent: 92,
setFillTransparent: 93
};
const DrawOPS = {
moveTo: 0,
lineTo: 1,
curveTo: 2,
closePath: 3
};
const PasswordResponses = {
NEED_PASSWORD: 1,
INCORRECT_PASSWORD: 2
};
let verbosity = VerbosityLevel.WARNINGS;
function setVerbosityLevel(level) {
if (Number.isInteger(level)) {
verbosity = level;
}
}
function getVerbosityLevel() {
return verbosity;
}
function info(msg) {
if (verbosity >= VerbosityLevel.INFOS) {
console.log(`Info: ${msg}`);
}
}
function warn(msg) {
if (verbosity >= VerbosityLevel.WARNINGS) {
console.log(`Warning: ${msg}`);
}
}
function unreachable(msg) {
throw new Error(msg);
}
function assert(cond, msg) {
if (!cond) {
unreachable(msg);
}
}
function _isValidProtocol(url) {
switch (url?.protocol) {
case "http:":
case "https:":
case "ftp:":
case "mailto:":
case "tel:":
return true;
default:
return false;
}
}
function createValidAbsoluteUrl(url, baseUrl = null, options = null) {
if (!url) {
return null;
}
if (options && typeof url === "string") {
if (options.addDefaultProtocol && url.startsWith("www.")) {
const dots = url.match(/\./g);
if (dots?.length >= 2) {
url = `http://${url}`;
}
}
if (options.tryConvertEncoding) {
try {
url = stringToUTF8String(url);
} catch {}
}
}
const absoluteUrl = baseUrl ? URL.parse(url, baseUrl) : URL.parse(url);
return _isValidProtocol(absoluteUrl) ? absoluteUrl : null;
}
function shadow(obj, prop, value, nonSerializable = false) {
Object.defineProperty(obj, prop, {
value,
enumerable: !nonSerializable,
configurable: true,
writable: false
});
return value;
}
const BaseException = function BaseExceptionClosure() {
function BaseException(message, name) {
this.message = message;
this.name = name;
}
BaseException.prototype = new Error();
BaseException.constructor = BaseException;
return BaseException;
}();
class PasswordException extends BaseException {
constructor(msg, code) {
super(msg, "PasswordException");
this.code = code;
}
}
class UnknownErrorException extends BaseException {
constructor(msg, details) {
super(msg, "UnknownErrorException");
this.details = details;
}
}
class InvalidPDFException extends BaseException {
constructor(msg) {
super(msg, "InvalidPDFException");
}
}
class ResponseException extends BaseException {
constructor(msg, status, missing) {
super(msg, "ResponseException");
this.status = status;
this.missing = missing;
}
}
class FormatError extends BaseException {
constructor(msg) {
super(msg, "FormatError");
}
}
class AbortException extends BaseException {
constructor(msg) {
super(msg, "AbortException");
}
}
function bytesToString(bytes) {
if (typeof bytes !== "object" || bytes?.length === undefined) {
unreachable("Invalid argument for bytesToString");
}
const length = bytes.length;
const MAX_ARGUMENT_COUNT = 8192;
if (length < MAX_ARGUMENT_COUNT) {
return String.fromCharCode.apply(null, bytes);
}
const strBuf = [];
for (let i = 0; i < length; i += MAX_ARGUMENT_COUNT) {
const chunkEnd = Math.min(i + MAX_ARGUMENT_COUNT, length);
const chunk = bytes.subarray(i, chunkEnd);
strBuf.push(String.fromCharCode.apply(null, chunk));
}
return strBuf.join("");
}
function stringToBytes(str) {
if (typeof str !== "string") {
unreachable("Invalid argument for stringToBytes");
}
const length = str.length;
const bytes = new Uint8Array(length);
for (let i = 0; i < length; ++i) {
bytes[i] = str.charCodeAt(i) & 0xff;
}
return bytes;
}
function string32(value) {
return String.fromCharCode(value >> 24 & 0xff, value >> 16 & 0xff, value >> 8 & 0xff, value & 0xff);
}
function objectSize(obj) {
return Object.keys(obj).length;
}
function objectFromMap(map) {
const obj = Object.create(null);
for (const [key, value] of map) {
obj[key] = value;
}
return obj;
}
function isLittleEndian() {
const buffer8 = new Uint8Array(4);
buffer8[0] = 1;
const view32 = new Uint32Array(buffer8.buffer, 0, 1);
return view32[0] === 1;
}
function isEvalSupported() {
try {
new Function("");
return true;
} catch {
return false;
}
}
class FeatureTest {
static get isLittleEndian() {
return shadow(this, "isLittleEndian", isLittleEndian());
}
static get isEvalSupported() {
return shadow(this, "isEvalSupported", isEvalSupported());
}
static get isOffscreenCanvasSupported() {
return shadow(this, "isOffscreenCanvasSupported", typeof OffscreenCanvas !== "undefined");
}
static get isImageDecoderSupported() {
return shadow(this, "isImageDecoderSupported", typeof ImageDecoder !== "undefined");
}
static get platform() {
if (typeof navigator !== "undefined" && typeof navigator?.platform === "string" && typeof navigator?.userAgent === "string") {
const {
platform,
userAgent
} = navigator;
return shadow(this, "platform", {
isAndroid: userAgent.includes("Android"),
isLinux: platform.includes("Linux"),
isMac: platform.includes("Mac"),
isWindows: platform.includes("Win"),
isFirefox: userAgent.includes("Firefox")
});
}
return shadow(this, "platform", {
isAndroid: false,
isLinux: false,
isMac: false,
isWindows: false,
isFirefox: false
});
}
static get isCSSRoundSupported() {
return shadow(this, "isCSSRoundSupported", globalThis.CSS?.supports?.("width: round(1.5px, 1px)"));
}
}
const hexNumbers = Array.from(Array(256).keys(), n => n.toString(16).padStart(2, "0"));
class Util {
static makeHexColor(r, g, b) {
return `#${hexNumbers[r]}${hexNumbers[g]}${hexNumbers[b]}`;
}
static transform(m1, m2) {
return [m1[0] * m2[0] + m1[2] * m2[1], m1[1] * m2[0] + m1[3] * m2[1], m1[0] * m2[2] + m1[2] * m2[3], m1[1] * m2[2] + m1[3] * m2[3], m1[0] * m2[4] + m1[2] * m2[5] + m1[4], m1[1] * m2[4] + m1[3] * m2[5] + m1[5]];
}
static applyTransform(p, m) {
const xt = p[0] * m[0] + p[1] * m[2] + m[4];
const yt = p[0] * m[1] + p[1] * m[3] + m[5];
return [xt, yt];
}
static applyInverseTransform(p, m) {
const d = m[0] * m[3] - m[1] * m[2];
const xt = (p[0] * m[3] - p[1] * m[2] + m[2] * m[5] - m[4] * m[3]) / d;
const yt = (-p[0] * m[1] + p[1] * m[0] + m[4] * m[1] - m[5] * m[0]) / d;
return [xt, yt];
}
static getAxialAlignedBoundingBox(r, m) {
const p1 = this.applyTransform(r, m);
const p2 = this.applyTransform(r.slice(2, 4), m);
const p3 = this.applyTransform([r[0], r[3]], m);
const p4 = this.applyTransform([r[2], r[1]], m);
return [Math.min(p1[0], p2[0], p3[0], p4[0]), Math.min(p1[1], p2[1], p3[1], p4[1]), Math.max(p1[0], p2[0], p3[0], p4[0]), Math.max(p1[1], p2[1], p3[1], p4[1])];
}
static inverseTransform(m) {
const d = m[0] * m[3] - m[1] * m[2];
return [m[3] / d, -m[1] / d, -m[2] / d, m[0] / d, (m[2] * m[5] - m[4] * m[3]) / d, (m[4] * m[1] - m[5] * m[0]) / d];
}
static singularValueDecompose2dScale(m) {
const transpose = [m[0], m[2], m[1], m[3]];
const a = m[0] * transpose[0] + m[1] * transpose[2];
const b = m[0] * transpose[1] + m[1] * transpose[3];
const c = m[2] * transpose[0] + m[3] * transpose[2];
const d = m[2] * transpose[1] + m[3] * transpose[3];
const first = (a + d) / 2;
const second = Math.sqrt((a + d) ** 2 - 4 * (a * d - c * b)) / 2;
const sx = first + second || 1;
const sy = first - second || 1;
return [Math.sqrt(sx), Math.sqrt(sy)];
}
static normalizeRect(rect) {
const r = rect.slice(0);
if (rect[0] > rect[2]) {
r[0] = rect[2];
r[2] = rect[0];
}
if (rect[1] > rect[3]) {
r[1] = rect[3];
r[3] = rect[1];
}
return r;
}
static intersect(rect1, rect2) {
const xLow = Math.max(Math.min(rect1[0], rect1[2]), Math.min(rect2[0], rect2[2]));
const xHigh = Math.min(Math.max(rect1[0], rect1[2]), Math.max(rect2[0], rect2[2]));
if (xLow > xHigh) {
return null;
}
const yLow = Math.max(Math.min(rect1[1], rect1[3]), Math.min(rect2[1], rect2[3]));
const yHigh = Math.min(Math.max(rect1[1], rect1[3]), Math.max(rect2[1], rect2[3]));
if (yLow > yHigh) {
return null;
}
return [xLow, yLow, xHigh, yHigh];
}
static pointBoundingBox(x, y, minMax) {
minMax[0] = Math.min(minMax[0], x);
minMax[1] = Math.min(minMax[1], y);
minMax[2] = Math.max(minMax[2], x);
minMax[3] = Math.max(minMax[3], y);
}
static rectBoundingBox(x0, y0, x1, y1, minMax) {
minMax[0] = Math.min(minMax[0], x0, x1);
minMax[1] = Math.min(minMax[1], y0, y1);
minMax[2] = Math.max(minMax[2], x0, x1);
minMax[3] = Math.max(minMax[3], y0, y1);
}
static #getExtremumOnCurve(x0, x1, x2, x3, y0, y1, y2, y3, t, minMax) {
if (t <= 0 || t >= 1) {
return;
}
const mt = 1 - t;
const tt = t * t;
const ttt = tt * t;
const x = mt * (mt * (mt * x0 + 3 * t * x1) + 3 * tt * x2) + ttt * x3;
const y = mt * (mt * (mt * y0 + 3 * t * y1) + 3 * tt * y2) + ttt * y3;
minMax[0] = Math.min(minMax[0], x);
minMax[1] = Math.min(minMax[1], y);
minMax[2] = Math.max(minMax[2], x);
minMax[3] = Math.max(minMax[3], y);
}
static #getExtremum(x0, x1, x2, x3, y0, y1, y2, y3, a, b, c, minMax) {
if (Math.abs(a) < 1e-12) {
if (Math.abs(b) >= 1e-12) {
this.#getExtremumOnCurve(x0, x1, x2, x3, y0, y1, y2, y3, -c / b, minMax);
}
return;
}
const delta = b ** 2 - 4 * c * a;
if (delta < 0) {
return;
}
const sqrtDelta = Math.sqrt(delta);
const a2 = 2 * a;
this.#getExtremumOnCurve(x0, x1, x2, x3, y0, y1, y2, y3, (-b + sqrtDelta) / a2, minMax);
this.#getExtremumOnCurve(x0, x1, x2, x3, y0, y1, y2, y3, (-b - sqrtDelta) / a2, minMax);
}
static bezierBoundingBox(x0, y0, x1, y1, x2, y2, x3, y3, minMax) {
minMax[0] = Math.min(minMax[0], x0, x3);
minMax[1] = Math.min(minMax[1], y0, y3);
minMax[2] = Math.max(minMax[2], x0, x3);
minMax[3] = Math.max(minMax[3], y0, y3);
this.#getExtremum(x0, x1, x2, x3, y0, y1, y2, y3, 3 * (-x0 + 3 * (x1 - x2) + x3), 6 * (x0 - 2 * x1 + x2), 3 * (x1 - x0), minMax);
this.#getExtremum(x0, x1, x2, x3, y0, y1, y2, y3, 3 * (-y0 + 3 * (y1 - y2) + y3), 6 * (y0 - 2 * y1 + y2), 3 * (y1 - y0), minMax);
}
}
const PDFStringTranslateTable = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2d8, 0x2c7, 0x2c6, 0x2d9, 0x2dd, 0x2db, 0x2da, 0x2dc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2022, 0x2020, 0x2021, 0x2026, 0x2014, 0x2013, 0x192, 0x2044, 0x2039, 0x203a, 0x2212, 0x2030, 0x201e, 0x201c, 0x201d, 0x2018, 0x2019, 0x201a, 0x2122, 0xfb01, 0xfb02, 0x141, 0x152, 0x160, 0x178, 0x17d, 0x131, 0x142, 0x153, 0x161, 0x17e, 0, 0x20ac];
function stringToPDFString(str) {
if (str[0] >= "\xEF") {
let encoding;
if (str[0] === "\xFE" && str[1] === "\xFF") {
encoding = "utf-16be";
if (str.length % 2 === 1) {
str = str.slice(0, -1);
}
} else if (str[0] === "\xFF" && str[1] === "\xFE") {
encoding = "utf-16le";
if (str.length % 2 === 1) {
str = str.slice(0, -1);
}
} else if (str[0] === "\xEF" && str[1] === "\xBB" && str[2] === "\xBF") {
encoding = "utf-8";
}
if (encoding) {
try {
const decoder = new TextDecoder(encoding, {
fatal: true
});
const buffer = stringToBytes(str);
const decoded = decoder.decode(buffer);
if (!decoded.includes("\x1b")) {
return decoded;
}
return decoded.replaceAll(/\x1b[^\x1b]*(?:\x1b|$)/g, "");
} catch (ex) {
warn(`stringToPDFString: "${ex}".`);
}
}
}
const strBuf = [];
for (let i = 0, ii = str.length; i < ii; i++) {
const charCode = str.charCodeAt(i);
if (charCode === 0x1b) {
while (++i < ii && str.charCodeAt(i) !== 0x1b) {}
continue;
}
const code = PDFStringTranslateTable[charCode];
strBuf.push(code ? String.fromCharCode(code) : str.charAt(i));
}
return strBuf.join("");
}
function stringToUTF8String(str) {
return decodeURIComponent(escape(str));
}
function utf8StringToString(str) {
return unescape(encodeURIComponent(str));
}
function isArrayEqual(arr1, arr2) {
if (arr1.length !== arr2.length) {
return false;
}
for (let i = 0, ii = arr1.length; i < ii; i++) {
if (arr1[i] !== arr2[i]) {
return false;
}
}
return true;
}
function getModificationDate(date = new Date()) {
const buffer = [date.getUTCFullYear().toString(), (date.getUTCMonth() + 1).toString().padStart(2, "0"), date.getUTCDate().toString().padStart(2, "0"), date.getUTCHours().toString().padStart(2, "0"), date.getUTCMinutes().toString().padStart(2, "0"), date.getUTCSeconds().toString().padStart(2, "0")];
return buffer.join("");
}
let NormalizeRegex = null;
let NormalizationMap = null;
function normalizeUnicode(str) {
if (!NormalizeRegex) {
NormalizeRegex = /([\u00a0\u00b5\u037e\u0eb3\u2000-\u200a\u202f\u2126\ufb00-\ufb04\ufb06\ufb20-\ufb36\ufb38-\ufb3c\ufb3e\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufba1\ufba4-\ufba9\ufbae-\ufbb1\ufbd3-\ufbdc\ufbde-\ufbe7\ufbea-\ufbf8\ufbfc-\ufbfd\ufc00-\ufc5d\ufc64-\ufcf1\ufcf5-\ufd3d\ufd88\ufdf4\ufdfa-\ufdfb\ufe71\ufe77\ufe79\ufe7b\ufe7d]+)|(\ufb05+)/gu;
NormalizationMap = new Map([["ſt", "ſt"]]);
}
return str.replaceAll(NormalizeRegex, (_, p1, p2) => p1 ? p1.normalize("NFKC") : NormalizationMap.get(p2));
}
function getUuid() {
if (typeof crypto.randomUUID === "function") {
return crypto.randomUUID();
}
const buf = new Uint8Array(32);
crypto.getRandomValues(buf);
return bytesToString(buf);
}
const AnnotationPrefix = "pdfjs_internal_id_";
function _isValidExplicitDest(validRef, validName, dest) {
if (!Array.isArray(dest) || dest.length < 2) {
return false;
}
const [page, zoom, ...args] = dest;
if (!validRef(page) && !Number.isInteger(page)) {
return false;
}
if (!validName(zoom)) {
return false;
}
const argsLen = args.length;
let allowNull = true;
switch (zoom.name) {
case "XYZ":
if (argsLen < 2 || argsLen > 3) {
return false;
}
break;
case "Fit":
case "FitB":
return argsLen === 0;
case "FitH":
case "FitBH":
case "FitV":
case "FitBV":
if (argsLen > 1) {
return false;
}
break;
case "FitR":
if (argsLen !== 4) {
return false;
}
allowNull = false;
break;
default:
return false;
}
for (const arg of args) {
if (typeof arg === "number" || allowNull && arg === null) {
continue;
}
return false;
}
return true;
}
function MathClamp(v, min, max) {
return Math.min(Math.max(v, min), max);
}
function toHexUtil(arr) {
if (Uint8Array.prototype.toHex) {
return arr.toHex();
}
return Array.from(arr, num => hexNumbers[num]).join("");
}
function toBase64Util(arr) {
if (Uint8Array.prototype.toBase64) {
return arr.toBase64();
}
return btoa(bytesToString(arr));
}
function fromBase64Util(str) {
if (Uint8Array.fromBase64) {
return Uint8Array.fromBase64(str);
}
return stringToBytes(atob(str));
}
if (typeof Promise.try !== "function") {
Promise.try = function (fn, ...args) {
return new Promise(resolve => {
resolve(fn(...args));
});
};
}
if (typeof Math.sumPrecise !== "function") {
Math.sumPrecise = function (numbers) {
return numbers.reduce((a, b) => a + b, 0);
};
}
;// ./src/core/primitives.js
const CIRCULAR_REF = Symbol("CIRCULAR_REF");
const EOF = Symbol("EOF");
let CmdCache = Object.create(null);
let NameCache = Object.create(null);
let RefCache = Object.create(null);
function clearPrimitiveCaches() {
CmdCache = Object.create(null);
NameCache = Object.create(null);
RefCache = Object.create(null);
}
class Name {
constructor(name) {
this.name = name;
}
static get(name) {
return NameCache[name] ||= new Name(name);
}
}
class Cmd {
constructor(cmd) {
this.cmd = cmd;
}
static get(cmd) {
return CmdCache[cmd] ||= new Cmd(cmd);
}
}
const nonSerializable = function nonSerializableClosure() {
return nonSerializable;
};
class Dict {
constructor(xref = null) {
this._map = new Map();
this.xref = xref;
this.objId = null;
this.suppressEncryption = false;
this.__nonSerializable__ = nonSerializable;
}
assignXref(newXref) {
this.xref = newXref;
}
get size() {
return this._map.size;
}
get(key1, key2, key3) {
let value = this._map.get(key1);
if (value === undefined && key2 !== undefined) {
value = this._map.get(key2);
if (value === undefined && key3 !== undefined) {
value = this._map.get(key3);
}
}
if (value instanceof Ref && this.xref) {
return this.xref.fetch(value, this.suppressEncryption);
}
return value;
}
async getAsync(key1, key2, key3) {
let value = this._map.get(key1);
if (value === undefined && key2 !== undefined) {
value = this._map.get(key2);
if (value === undefined && key3 !== undefined) {
value = this._map.get(key3);
}
}
if (value instanceof Ref && this.xref) {
return this.xref.fetchAsync(value, this.suppressEncryption);
}
return value;
}
getArray(key1, key2, key3) {
let value = this._map.get(key1);
if (value === undefined && key2 !== undefined) {
value = this._map.get(key2);
if (value === undefined && key3 !== undefined) {
value = this._map.get(key3);
}
}
if (value instanceof Ref && this.xref) {
value = this.xref.fetch(value, this.suppressEncryption);
}
if (Array.isArray(value)) {
value = value.slice();
for (let i = 0, ii = value.length; i < ii; i++) {
if (value[i] instanceof Ref && this.xref) {
value[i] = this.xref.fetch(value[i], this.suppressEncryption);
}
}
}
return value;
}
getRaw(key) {
return this._map.get(key);
}
getKeys() {
return [...this._map.keys()];
}
getRawValues() {
return [...this._map.values()];
}
set(key, value) {
this._map.set(key, value);
}
has(key) {
return this._map.has(key);
}
*[Symbol.iterator]() {
for (const [key, value] of this._map) {
yield [key, value instanceof Ref && this.xref ? this.xref.fetch(value, this.suppressEncryption) : value];
}
}
static get empty() {
const emptyDict = new Dict(null);
emptyDict.set = (key, value) => {
unreachable("Should not call `set` on the empty dictionary.");
};
return shadow(this, "empty", emptyDict);
}
static merge({
xref,
dictArray,
mergeSubDicts = false
}) {
const mergedDict = new Dict(xref),
properties = new Map();
for (const dict of dictArray) {
if (!(dict instanceof Dict)) {
continue;
}
for (const [key, value] of dict._map) {
let property = properties.get(key);
if (property === undefined) {
property = [];
properties.set(key, property);
} else if (!mergeSubDicts || !(value instanceof Dict)) {
continue;
}
property.push(value);
}
}
for (const [name, values] of properties) {
if (values.length === 1 || !(values[0] instanceof Dict)) {
mergedDict._map.set(name, values[0]);
continue;
}
const subDict = new Dict(xref);
for (const dict of values) {
for (const [key, value] of dict._map) {
if (!subDict._map.has(key)) {
subDict._map.set(key, value);
}
}
}
if (subDict.size > 0) {
mergedDict._map.set(name, subDict);
}
}
properties.clear();
return mergedDict.size > 0 ? mergedDict : Dict.empty;
}
clone() {
const dict = new Dict(this.xref);
for (const key of this.getKeys()) {
dict.set(key, this.getRaw(key));
}
return dict;
}
delete(key) {
delete this._map[key];
}
}
class Ref {
constructor(num, gen) {
this.num = num;
this.gen = gen;
}
toString() {
if (this.gen === 0) {
return `${this.num}R`;
}
return `${this.num}R${this.gen}`;
}
static fromString(str) {
const ref = RefCache[str];
if (ref) {
return ref;
}
const m = /^(\d+)R(\d*)$/.exec(str);
if (!m || m[1] === "0") {
return null;
}
return RefCache[str] = new Ref(parseInt(m[1]), !m[2] ? 0 : parseInt(m[2]));
}
static get(num, gen) {
const key = gen === 0 ? `${num}R` : `${num}R${gen}`;
return RefCache[key] ||= new Ref(num, gen);
}
}
class RefSet {
constructor(parent = null) {
this._set = new Set(parent?._set);
}
has(ref) {
return this._set.has(ref.toString());
}
put(ref) {
this._set.add(ref.toString());
}
remove(ref) {
this._set.delete(ref.toString());
}
[Symbol.iterator]() {
return this._set.values();
}
clear() {
this._set.clear();
}
}
class RefSetCache {
constructor() {
this._map = new Map();
}
get size() {
return this._map.size;
}
get(ref) {
return this._map.get(ref.toString());
}
has(ref) {
return this._map.has(ref.toString());
}
put(ref, obj) {
this._map.set(ref.toString(), obj);
}
putAlias(ref, aliasRef) {
this._map.set(ref.toString(), this.get(aliasRef));
}
[Symbol.iterator]() {
return this._map.values();
}
clear() {
this._map.clear();
}
*values() {
yield* this._map.values();
}
*items() {
for (const [ref, value] of this._map) {
yield [Ref.fromString(ref), value];
}
}
}
function isName(v, name) {
return v instanceof Name && (name === undefined || v.name === name);
}
function isCmd(v, cmd) {
return v instanceof Cmd && (cmd === undefined || v.cmd === cmd);
}
function isDict(v, type) {
return v instanceof Dict && (type === undefined || isName(v.get("Type"), type));
}
function isRefsEqual(v1, v2) {
return v1.num === v2.num && v1.gen === v2.gen;
}
;// ./src/core/base_stream.js
class BaseStream {
get length() {
unreachable("Abstract getter `length` accessed");
}
get isEmpty() {
unreachable("Abstract getter `isEmpty` accessed");
}
get isDataLoaded() {
return shadow(this, "isDataLoaded", true);
}
getByte() {
unreachable("Abstract method `getByte` called");
}
getBytes(length) {
unreachable("Abstract method `getBytes` called");
}
async getImageData(length, decoderOptions) {
return this.getBytes(length, decoderOptions);
}
async asyncGetBytes() {
unreachable("Abstract method `asyncGetBytes` called");
}
get isAsync() {
return false;
}
get isAsyncDecoder() {
return false;
}
get canAsyncDecodeImageFromBuffer() {
return false;
}
async getTransferableImage() {
return null;
}
peekByte() {
const peekedByte = this.getByte();
if (peekedByte !== -1) {
this.pos--;
}
return peekedByte;
}
peekBytes(length) {
const bytes = this.getBytes(length);
this.pos -= bytes.length;
return bytes;
}
getUint16() {
const b0 = this.getByte();
const b1 = this.getByte();
if (b0 === -1 || b1 === -1) {
return -1;
}
return (b0 << 8) + b1;
}
getInt32() {
const b0 = this.getByte();
const b1 = this.getByte();
const b2 = this.getByte();
const b3 = this.getByte();
return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3;
}
getByteRange(begin, end) {
unreachable("Abstract method `getByteRange` called");
}
getString(length) {
return bytesToString(this.getBytes(length));
}
skip(n) {
this.pos += n || 1;
}
reset() {
unreachable("Abstract method `reset` called");
}
moveStart() {
unreachable("Abstract method `moveStart` called");
}
makeSubStream(start, length, dict = null) {
unreachable("Abstract method `makeSubStream` called");
}
getBaseStreams() {
return null;
}
}
;// ./src/core/core_utils.js
const PDF_VERSION_REGEXP = /^[1-9]\.\d$/;
const MAX_INT_32 = 2 ** 31 - 1;
const MIN_INT_32 = -(2 ** 31);
function getLookupTableFactory(initializer) {
let lookup;
return function () {
if (initializer) {
lookup = Object.create(null);
initializer(lookup);
initializer = null;
}
return lookup;
};
}
class MissingDataException extends BaseException {
constructor(begin, end) {
super(`Missing data [${begin}, ${end})`, "MissingDataException");
this.begin = begin;
this.end = end;
}
}
class ParserEOFException extends BaseException {
constructor(msg) {
super(msg, "ParserEOFException");
}
}
class XRefEntryException extends BaseException {
constructor(msg) {
super(msg, "XRefEntryException");
}
}
class XRefParseException extends BaseException {
constructor(msg) {
super(msg, "XRefParseException");
}
}
function arrayBuffersToBytes(arr) {
const length = arr.length;
if (length === 0) {
return new Uint8Array(0);
}
if (length === 1) {
return new Uint8Array(arr[0]);
}
let dataLength = 0;
for (let i = 0; i < length; i++) {
dataLength += arr[i].byteLength;
}
const data = new Uint8Array(dataLength);
let pos = 0;
for (let i = 0; i < length; i++) {
const item = new Uint8Array(arr[i]);
data.set(item, pos);
pos += item.byteLength;
}
return data;
}
async function fetchBinaryData(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch file "${url}" with "${response.statusText}".`);
}
return new Uint8Array(await response.arrayBuffer());
}
function getInheritableProperty({
dict,
key,
getArray = false,
stopWhenFound = true
}) {
let values;
const visited = new RefSet();
while (dict instanceof Dict && !(dict.objId && visited.has(dict.objId))) {
if (dict.objId) {
visited.put(dict.objId);
}
const value = getArray ? dict.getArray(key) : dict.get(key);
if (value !== undefined) {
if (stopWhenFound) {
return value;
}
(values ||= []).push(value);
}
dict = dict.get("Parent");
}
return values;
}
function getParentToUpdate(dict, ref, xref) {
const visited = new RefSet();
const firstDict = dict;
const result = {
dict: null,
ref: null
};
while (dict instanceof Dict && !visited.has(ref)) {
visited.put(ref);
if (dict.has("T")) {
break;
}
ref = dict.getRaw("Parent");
if (!(ref instanceof Ref)) {
return result;
}
dict = xref.fetch(ref);
}
if (dict instanceof Dict && dict !== firstDict) {
result.dict = dict;
result.ref = ref;
}
return result;
}
const ROMAN_NUMBER_MAP = ["", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM", "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC", "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"];
function toRomanNumerals(number, lowerCase = false) {
assert(Number.isInteger(number) && number > 0, "The number should be a positive integer.");
const roman = "M".repeat(number / 1000 | 0) + ROMAN_NUMBER_MAP[number % 1000 / 100 | 0] + ROMAN_NUMBER_MAP[10 + (number % 100 / 10 | 0)] + ROMAN_NUMBER_MAP[20 + number % 10];
return lowerCase ? roman.toLowerCase() : roman;
}
function log2(x) {
return x > 0 ? Math.ceil(Math.log2(x)) : 0;
}
function readInt8(data, offset) {
return data[offset] << 24 >> 24;
}
function readInt16(data, offset) {
return (data[offset] << 24 | data[offset + 1] << 16) >> 16;
}
function readUint16(data, offset) {
return data[offset] << 8 | data[offset + 1];
}
function readUint32(data, offset) {
return (data[offset] << 24 | data[offset + 1] << 16 | data[offset + 2] << 8 | data[offset + 3]) >>> 0;
}
function isWhiteSpace(ch) {
return ch === 0x20 || ch === 0x09 || ch === 0x0d || ch === 0x0a;
}
function isBooleanArray(arr, len) {
return Array.isArray(arr) && (len === null || arr.length === len) && arr.every(x => typeof x === "boolean");
}
function isNumberArray(arr, len) {
if (Array.isArray(arr)) {
return (len === null || arr.length === len) && arr.every(x => typeof x === "number");
}
return ArrayBuffer.isView(arr) && !(arr instanceof BigInt64Array || arr instanceof BigUint64Array) && (len === null || arr.length === len);
}
function lookupMatrix(arr, fallback) {
return isNumberArray(arr, 6) ? arr : fallback;
}
function lookupRect(arr, fallback) {
return isNumberArray(arr, 4) ? arr : fallback;
}
function lookupNormalRect(arr, fallback) {
return isNumberArray(arr, 4) ? Util.normalizeRect(arr) : fallback;
}
function parseXFAPath(path) {
const positionPattern = /(.+)\[(\d+)\]$/;
return path.split(".").map(component => {
const m = component.match(positionPattern);
if (m) {
return {
name: m[1],
pos: parseInt(m[2], 10)
};
}
return {
name: component,
pos: 0
};
});
}
function escapePDFName(str) {
const buffer = [];
let start = 0;
for (let i = 0, ii = str.length; i < ii; i++) {
const char = str.charCodeAt(i);
if (char < 0x21 || char > 0x7e || char === 0x23 || char === 0x28 || char === 0x29 || char === 0x3c || char === 0x3e || char === 0x5b || char === 0x5d || char === 0x7b || char === 0x7d || char === 0x2f || char === 0x25) {
if (start < i) {
buffer.push(str.substring(start, i));
}
buffer.push(`#${char.toString(16)}`);
start = i + 1;
}
}
if (buffer.length === 0) {
return str;
}
if (start < str.length) {
buffer.push(str.substring(start, str.length));
}
return buffer.join("");
}
function escapeString(str) {
return str.replaceAll(/([()\\\n\r])/g, match => {
if (match === "\n") {
return "\\n";
} else if (match === "\r") {
return "\\r";
}
return `\\${match}`;
});
}
function _collectJS(entry, xref, list, parents) {
if (!entry) {
return;
}
let parent = null;
if (entry instanceof Ref) {
if (parents.has(entry)) {
return;
}
parent = entry;
parents.put(parent);
entry = xref.fetch(entry);
}
if (Array.isArray(entry)) {
for (const element of entry) {
_collectJS(element, xref, list, parents);
}
} else if (entry instanceof Dict) {
if (isName(entry.get("S"), "JavaScript")) {
const js = entry.get("JS");
let code;
if (js instanceof BaseStream) {
code = js.getString();
} else if (typeof js === "string") {
code = js;
}
code &&= stringToPDFString(code).replaceAll("\x00", "");
if (code) {
list.push(code);
}
}
_collectJS(entry.getRaw("Next"), xref, list, parents);
}
if (parent) {
parents.remove(parent);
}
}
function collectActions(xref, dict, eventType) {
const actions = Object.create(null);
const additionalActionsDicts = getInheritableProperty({
dict,
key: "AA",
stopWhenFound: false
});
if (additionalActionsDicts) {
for (let i = additionalActionsDicts.length - 1; i >= 0; i--) {
const additionalActions = additionalActionsDicts[i];
if (!(additionalActions instanceof Dict)) {
continue;
}
for (const key of additionalActions.getKeys()) {
const action = eventType[key];
if (!action) {
continue;
}
const actionDict = additionalActions.getRaw(key);
const parents = new RefSet();
const list = [];
_collectJS(actionDict, xref, list, parents);
if (list.length > 0) {
actions[action] = list;
}
}
}
}
if (dict.has("A")) {
const actionDict = dict.get("A");
const parents = new RefSet();
const list = [];
_collectJS(actionDict, xref, list, parents);
if (list.length > 0) {
actions.Action = list;
}
}
return objectSize(actions) > 0 ? actions : null;
}
const XMLEntities = {
0x3c: "<",
0x3e: ">",
0x26: "&",
0x22: """,
0x27: "'"
};
function* codePointIter(str) {
for (let i = 0, ii = str.length; i < ii; i++) {
const char = str.codePointAt(i);
if (char > 0xd7ff && (char < 0xe000 || char > 0xfffd)) {
i++;
}
yield char;
}
}
function encodeToXmlString(str) {
const buffer = [];
let start = 0;
for (let i = 0, ii = str.length; i < ii; i++) {
const char = str.codePointAt(i);
if (0x20 <= char && char <= 0x7e) {
const entity = XMLEntities[char];
if (entity) {
if (start < i) {
buffer.push(str.substring(start, i));
}
buffer.push(entity);
start = i + 1;
}
} else {
if (start < i) {
buffer.push(str.substring(start, i));
}
buffer.push(`${char.toString(16).toUpperCase()};`);
if (char > 0xd7ff && (char < 0xe000 || char > 0xfffd)) {
i++;
}
start = i + 1;
}
}
if (buffer.length === 0) {
return str;
}
if (start < str.length) {
buffer.push(str.substring(start, str.length));
}
return buffer.join("");
}
function validateFontName(fontFamily, mustWarn = false) {
const m = /^("|').*("|')$/.exec(fontFamily);
if (m && m[1] === m[2]) {
const re = new RegExp(`[^\\\\]${m[1]}`);
if (re.test(fontFamily.slice(1, -1))) {
if (mustWarn) {
warn(`FontFamily contains unescaped ${m[1]}: ${fontFamily}.`);
}
return false;
}
} else {
for (const ident of fontFamily.split(/[ \t]+/)) {
if (/^(\d|(-(\d|-)))/.test(ident) || !/^[\w-\\]+$/.test(ident)) {
if (mustWarn) {
warn(`FontFamily contains invalid : ${fontFamily}.`);
}
return false;
}
}
}
return true;
}
function validateCSSFont(cssFontInfo) {
const DEFAULT_CSS_FONT_OBLIQUE = "14";
const DEFAULT_CSS_FONT_WEIGHT = "400";
const CSS_FONT_WEIGHT_VALUES = new Set(["100", "200", "300", "400", "500", "600", "700", "800", "900", "1000", "normal", "bold", "bolder", "lighter"]);
const {
fontFamily,
fontWeight,
italicAngle
} = cssFontInfo;
if (!validateFontName(fontFamily, true)) {
return false;
}
const weight = fontWeight ? fontWeight.toString() : "";
cssFontInfo.fontWeight = CSS_FONT_WEIGHT_VALUES.has(weight) ? weight : DEFAULT_CSS_FONT_WEIGHT;
const angle = parseFloat(italicAngle);
cssFontInfo.italicAngle = isNaN(angle) || angle < -90 || angle > 90 ? DEFAULT_CSS_FONT_OBLIQUE : italicAngle.toString();
return true;
}
function recoverJsURL(str) {
const URL_OPEN_METHODS = ["app.launchURL", "window.open", "xfa.host.gotoURL"];
const regex = new RegExp("^\\s*(" + URL_OPEN_METHODS.join("|").replaceAll(".", "\\.") + ")\\((?:'|\")([^'\"]*)(?:'|\")(?:,\\s*(\\w+)\\)|\\))", "i");
const jsUrl = regex.exec(str);
if (jsUrl?.[2]) {
return {
url: jsUrl[2],
newWindow: jsUrl[1] === "app.launchURL" && jsUrl[3] === "true"
};
}
return null;
}
function numberToString(value) {
if (Number.isInteger(value)) {
return value.toString();
}
const roundedValue = Math.round(value * 100);
if (roundedValue % 100 === 0) {
return (roundedValue / 100).toString();
}
if (roundedValue % 10 === 0) {
return value.toFixed(1);
}
return value.toFixed(2);
}
function getNewAnnotationsMap(annotationStorage) {
if (!annotationStorage) {
return null;
}
const newAnnotationsByPage = new Map();
for (const [key, value] of annotationStorage) {
if (!key.startsWith(AnnotationEditorPrefix)) {
continue;
}
let annotations = newAnnotationsByPage.get(value.pageIndex);
if (!annotations) {
annotations = [];
newAnnotationsByPage.set(value.pageIndex, annotations);
}
annotations.push(value);
}
return newAnnotationsByPage.size > 0 ? newAnnotationsByPage : null;
}
function stringToAsciiOrUTF16BE(str) {
return isAscii(str) ? str : stringToUTF16String(str, true);
}
function isAscii(str) {
return /^[\x00-\x7F]*$/.test(str);
}
function stringToUTF16HexString(str) {
const buf = [];
for (let i = 0, ii = str.length; i < ii; i++) {
const char = str.charCodeAt(i);
buf.push(hexNumbers[char >> 8 & 0xff], hexNumbers[char & 0xff]);
}
return buf.join("");
}
function stringToUTF16String(str, bigEndian = false) {
const buf = [];
if (bigEndian) {
buf.push("\xFE\xFF");
}
for (let i = 0, ii = str.length; i < ii; i++) {
const char = str.charCodeAt(i);
buf.push(String.fromCharCode(char >> 8 & 0xff), String.fromCharCode(char & 0xff));
}
return buf.join("");
}
function getRotationMatrix(rotation, width, height) {
switch (rotation) {
case 90:
return [0, 1, -1, 0, width, 0];
case 180:
return [-1, 0, 0, -1, width, height];
case 270:
return [0, -1, 1, 0, 0, height];
default:
throw new Error("Invalid rotation");
}
}
function getSizeInBytes(x) {
return Math.ceil(Math.ceil(Math.log2(1 + x)) / 8);
}
;// ./external/qcms/qcms_utils.js
class QCMS {
static _module = null;
static _mustAddAlpha = false;
static _destBuffer = null;
}
function copy_result(ptr, len) {
const {
_module,
_mustAddAlpha,
_destBuffer
} = QCMS;
const result = new Uint8Array(_module.memory.buffer, ptr, len);
if (result.length === _destBuffer.length) {
_destBuffer.set(result);
return;
}
if (_mustAddAlpha) {
for (let i = 0, j = 0, ii = result.length; i < ii; i += 3, j += 4) {
_destBuffer[j] = result[i];
_destBuffer[j + 1] = result[i + 1];
_destBuffer[j + 2] = result[i + 2];
_destBuffer[j + 3] = 255;
}
} else {
for (let i = 0, j = 0, ii = result.length; i < ii; i += 3, j += 4) {
_destBuffer[j] = result[i];
_destBuffer[j + 1] = result[i + 1];
_destBuffer[j + 2] = result[i + 2];
}
}
}
function copy_rgb(ptr) {
QCMS._destBuffer.set(new Uint8Array(QCMS._module.memory.buffer, ptr, 3));
}
;// ./external/qcms/qcms.js
let wasm;
const cachedTextDecoder = typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', {
ignoreBOM: true,
fatal: true
}) : {
decode: () => {
throw Error('TextDecoder not available');
}
};
if (typeof TextDecoder !== 'undefined') {
cachedTextDecoder.decode();
}
;
let cachedUint8ArrayMemory0 = null;
function getUint8ArrayMemory0() {
if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
}
return cachedUint8ArrayMemory0;
}
function getStringFromWasm0(ptr, len) {
ptr = ptr >>> 0;
return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
}
let WASM_VECTOR_LEN = 0;
function passArray8ToWasm0(arg, malloc) {
const ptr = malloc(arg.length * 1, 1) >>> 0;
getUint8ArrayMemory0().set(arg, ptr / 1);
WASM_VECTOR_LEN = arg.length;
return ptr;
}
function qcms_convert_array(transformer, src) {
const ptr0 = passArray8ToWasm0(src, wasm.__wbindgen_malloc);
const len0 = WASM_VECTOR_LEN;
wasm.qcms_convert_array(transformer, ptr0, len0);
}
function qcms_convert_one(transformer, src) {
wasm.qcms_convert_one(transformer, src);
}
function qcms_convert_three(transformer, src1, src2, src3) {
wasm.qcms_convert_three(transformer, src1, src2, src3);
}
function qcms_convert_four(transformer, src1, src2, src3, src4) {
wasm.qcms_convert_four(transformer, src1, src2, src3, src4);
}
function qcms_transformer_from_memory(mem, in_type, intent) {
const ptr0 = passArray8ToWasm0(mem, wasm.__wbindgen_malloc);
const len0 = WASM_VECTOR_LEN;
const ret = wasm.qcms_transformer_from_memory(ptr0, len0, in_type, intent);
return ret >>> 0;
}
function qcms_drop_transformer(transformer) {
wasm.qcms_drop_transformer(transformer);
}
const DataType = Object.freeze({
RGB8: 0,
"0": "RGB8",
RGBA8: 1,
"1": "RGBA8",
BGRA8: 2,
"2": "BGRA8",
Gray8: 3,
"3": "Gray8",
GrayA8: 4,
"4": "GrayA8",
CMYK: 5,
"5": "CMYK"
});
const Intent = Object.freeze({
Perceptual: 0,
"0": "Perceptual",
RelativeColorimetric: 1,
"1": "RelativeColorimetric",
Saturation: 2,
"2": "Saturation",
AbsoluteColorimetric: 3,
"3": "AbsoluteColorimetric"
});
async function __wbg_load(module, imports) {
if (typeof Response === 'function' && module instanceof Response) {
if (typeof WebAssembly.instantiateStreaming === 'function') {
try {
return await WebAssembly.instantiateStreaming(module, imports);
} catch (e) {
if (module.headers.get('Content-Type') != 'application/wasm') {
console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
} else {
throw e;
}
}
}
const bytes = await module.arrayBuffer();
return await WebAssembly.instantiate(bytes, imports);
} else {
const instance = await WebAssembly.instantiate(module, imports);
if (instance instanceof WebAssembly.Instance) {
return {
instance,
module
};
} else {
return instance;
}
}
}
function __wbg_get_imports() {
const imports = {};
imports.wbg = {};
imports.wbg.__wbg_copyresult_b08ee7d273f295dd = function (arg0, arg1) {
copy_result(arg0 >>> 0, arg1 >>> 0);
};
imports.wbg.__wbg_copyrgb_d60ce17bb05d9b67 = function (arg0) {
copy_rgb(arg0 >>> 0);
};
imports.wbg.__wbindgen_init_externref_table = function () {
const table = wasm.__wbindgen_export_0;
const offset = table.grow(4);
table.set(0, undefined);
table.set(offset + 0, undefined);
table.set(offset + 1, null);
table.set(offset + 2, true);
table.set(offset + 3, false);
};
imports.wbg.__wbindgen_throw = function (arg0, arg1) {
throw new Error(getStringFromWasm0(arg0, arg1));
};
return imports;
}
function __wbg_init_memory(imports, memory) {}
function __wbg_finalize_init(instance, module) {
wasm = instance.exports;
__wbg_init.__wbindgen_wasm_module = module;
cachedUint8ArrayMemory0 = null;
wasm.__wbindgen_start();
return wasm;
}
function initSync(module) {
if (wasm !== undefined) return wasm;
if (typeof module !== 'undefined') {
if (Object.getPrototypeOf(module) === Object.prototype) {
({
module
} = module);
} else {
console.warn('using deprecated parameters for `initSync()`; pass a single object instead');
}
}
const imports = __wbg_get_imports();
__wbg_init_memory(imports);
if (!(module instanceof WebAssembly.Module)) {
module = new WebAssembly.Module(module);
}
const instance = new WebAssembly.Instance(module, imports);
return __wbg_finalize_init(instance, module);
}
async function __wbg_init(module_or_path) {
if (wasm !== undefined) return wasm;
if (typeof module_or_path !== 'undefined') {
if (Object.getPrototypeOf(module_or_path) === Object.prototype) {
({
module_or_path
} = module_or_path);
} else {
console.warn('using deprecated parameters for the initialization function; pass a single object instead');
}
}
if (typeof module_or_path === 'undefined') {
module_or_path = new URL('qcms_bg.wasm', import.meta.url);
}
const imports = __wbg_get_imports();
if (typeof module_or_path === 'string' || typeof Request === 'function' && module_or_path instanceof Request || typeof URL === 'function' && module_or_path instanceof URL) {
module_or_path = fetch(module_or_path);
}
__wbg_init_memory(imports);
const {
instance,
module
} = await __wbg_load(await module_or_path, imports);
return __wbg_finalize_init(instance, module);
}
/* harmony default export */ const qcms = ((/* unused pure expression or super */ null && (__wbg_init)));
;// ./src/core/colorspace.js
function resizeRgbImage(src, dest, w1, h1, w2, h2, alpha01) {
const COMPONENTS = 3;
alpha01 = alpha01 !== 1 ? 0 : alpha01;
const xRatio = w1 / w2;
const yRatio = h1 / h2;
let newIndex = 0,
oldIndex;
const xScaled = new Uint16Array(w2);
const w1Scanline = w1 * COMPONENTS;
for (let i = 0; i < w2; i++) {
xScaled[i] = Math.floor(i * xRatio) * COMPONENTS;
}
for (let i = 0; i < h2; i++) {
const py = Math.floor(i * yRatio) * w1Scanline;
for (let j = 0; j < w2; j++) {
oldIndex = py + xScaled[j];
dest[newIndex++] = src[oldIndex++];
dest[newIndex++] = src[oldIndex++];
dest[newIndex++] = src[oldIndex++];
newIndex += alpha01;
}
}
}
function resizeRgbaImage(src, dest, w1, h1, w2, h2, alpha01) {
const xRatio = w1 / w2;
const yRatio = h1 / h2;
let newIndex = 0;
const xScaled = new Uint16Array(w2);
if (alpha01 === 1) {
for (let i = 0; i < w2; i++) {
xScaled[i] = Math.floor(i * xRatio);
}
const src32 = new Uint32Array(src.buffer);
const dest32 = new Uint32Array(dest.buffer);
const rgbMask = FeatureTest.isLittleEndian ? 0x00ffffff : 0xffffff00;
for (let i = 0; i < h2; i++) {
const buf = src32.subarray(Math.floor(i * yRatio) * w1);
for (let j = 0; j < w2; j++) {
dest32[newIndex++] |= buf[xScaled[j]] & rgbMask;
}
}
} else {
const COMPONENTS = 4;
const w1Scanline = w1 * COMPONENTS;
for (let i = 0; i < w2; i++) {
xScaled[i] = Math.floor(i * xRatio) * COMPONENTS;
}
for (let i = 0; i < h2; i++) {
const buf = src.subarray(Math.floor(i * yRatio) * w1Scanline);
for (let j = 0; j < w2; j++) {
const oldIndex = xScaled[j];
dest[newIndex++] = buf[oldIndex];
dest[newIndex++] = buf[oldIndex + 1];
dest[newIndex++] = buf[oldIndex + 2];
}
}
}
}
function copyRgbaImage(src, dest, alpha01) {
if (alpha01 === 1) {
const src32 = new Uint32Array(src.buffer);
const dest32 = new Uint32Array(dest.buffer);
const rgbMask = FeatureTest.isLittleEndian ? 0x00ffffff : 0xffffff00;
for (let i = 0, ii = src32.length; i < ii; i++) {
dest32[i] |= src32[i] & rgbMask;
}
} else {
let j = 0;
for (let i = 0, ii = src.length; i < ii; i += 4) {
dest[j++] = src[i];
dest[j++] = src[i + 1];
dest[j++] = src[i + 2];
}
}
}
class ColorSpace {
constructor(name, numComps) {
this.name = name;
this.numComps = numComps;
}
getRgb(src, srcOffset) {
const rgb = new Uint8ClampedArray(3);
this.getRgbItem(src, srcOffset, rgb, 0);
return rgb;
}
getRgbItem(src, srcOffset, dest, destOffset) {
unreachable("Should not call ColorSpace.getRgbItem");
}
getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
unreachable("Should not call ColorSpace.getRgbBuffer");
}
getOutputLength(inputLength, alpha01) {
unreachable("Should not call ColorSpace.getOutputLength");
}
isPassthrough(bits) {
return false;
}
isDefaultDecode(decodeMap, bpc) {
return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
}
fillRgb(dest, originalWidth, originalHeight, width, height, actualHeight, bpc, comps, alpha01) {
const count = originalWidth * originalHeight;
let rgbBuf = null;
const numComponentColors = 1 << bpc;
const needsResizing = originalHeight !== height || originalWidth !== width;
if (this.isPassthrough(bpc)) {
rgbBuf = comps;
} else if (this.numComps === 1 && count > numComponentColors && this.name !== "DeviceGray" && this.name !== "DeviceRGB") {
const allColors = bpc <= 8 ? new Uint8Array(numComponentColors) : new Uint16Array(numComponentColors);
for (let i = 0; i < numComponentColors; i++) {
allColors[i] = i;
}
const colorMap = new Uint8ClampedArray(numComponentColors * 3);
this.getRgbBuffer(allColors, 0, numComponentColors, colorMap, 0, bpc, 0);
if (!needsResizing) {
let destPos = 0;
for (let i = 0; i < count; ++i) {
const key = comps[i] * 3;
dest[destPos++] = colorMap[key];
dest[destPos++] = colorMap[key + 1];
dest[destPos++] = colorMap[key + 2];
destPos += alpha01;
}
} else {
rgbBuf = new Uint8Array(count * 3);
let rgbPos = 0;
for (let i = 0; i < count; ++i) {
const key = comps[i] * 3;
rgbBuf[rgbPos++] = colorMap[key];
rgbBuf[rgbPos++] = colorMap[key + 1];
rgbBuf[rgbPos++] = colorMap[key + 2];
}
}
} else if (!needsResizing) {
this.getRgbBuffer(comps, 0, width * actualHeight, dest, 0, bpc, alpha01);
} else {
rgbBuf = new Uint8ClampedArray(count * 3);
this.getRgbBuffer(comps, 0, count, rgbBuf, 0, bpc, 0);
}
if (rgbBuf) {
if (needsResizing) {
resizeRgbImage(rgbBuf, dest, originalWidth, originalHeight, width, height, alpha01);
} else {
let destPos = 0,
rgbPos = 0;
for (let i = 0, ii = width * actualHeight; i < ii; i++) {
dest[destPos++] = rgbBuf[rgbPos++];
dest[destPos++] = rgbBuf[rgbPos++];
dest[destPos++] = rgbBuf[rgbPos++];
destPos += alpha01;
}
}
}
}
get usesZeroToOneRange() {
return shadow(this, "usesZeroToOneRange", true);
}
static isDefaultDecode(decode, numComps) {
if (!Array.isArray(decode)) {
return true;
}
if (numComps * 2 !== decode.length) {
warn("The decode map is not the correct length");
return true;
}
for (let i = 0, ii = decode.length; i < ii; i += 2) {
if (decode[i] !== 0 || decode[i + 1] !== 1) {
return false;
}
}
return true;
}
}
class AlternateCS extends ColorSpace {
constructor(numComps, base, tintFn) {
super("Alternate", numComps);
this.base = base;
this.tintFn = tintFn;
this.tmpBuf = new Float32Array(base.numComps);
}
getRgbItem(src, srcOffset, dest, destOffset) {
const tmpBuf = this.tmpBuf;
this.tintFn(src, srcOffset, tmpBuf, 0);
this.base.getRgbItem(tmpBuf, 0, dest, destOffset);
}
getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
const tintFn = this.tintFn;
const base = this.base;
const scale = 1 / ((1 << bits) - 1);
const baseNumComps = base.numComps;
const usesZeroToOneRange = base.usesZeroToOneRange;
const isPassthrough = (base.isPassthrough(8) || !usesZeroToOneRange) && alpha01 === 0;
let pos = isPassthrough ? destOffset : 0;
const baseBuf = isPassthrough ? dest : new Uint8ClampedArray(baseNumComps * count);
const numComps = this.numComps;
const scaled = new Float32Array(numComps);
const tinted = new Float32Array(baseNumComps);
let i, j;
for (i = 0; i < count; i++) {
for (j = 0; j < numComps; j++) {
scaled[j] = src[srcOffset++] * scale;
}
tintFn(scaled, 0, tinted, 0);
if (usesZeroToOneRange) {
for (j = 0; j < baseNumComps; j++) {
baseBuf[pos++] = tinted[j] * 255;
}
} else {
base.getRgbItem(tinted, 0, baseBuf, pos);
pos += baseNumComps;
}
}
if (!isPassthrough) {
base.getRgbBuffer(baseBuf, 0, count, dest, destOffset, 8, alpha01);
}
}
getOutputLength(inputLength, alpha01) {
return this.base.getOutputLength(inputLength * this.base.numComps / this.numComps, alpha01);
}
}
class PatternCS extends ColorSpace {
constructor(baseCS) {
super("Pattern", null);
this.base = baseCS;
}
isDefaultDecode(decodeMap, bpc) {
unreachable("Should not call PatternCS.isDefaultDecode");
}
}
class IndexedCS extends ColorSpace {
constructor(base, highVal, lookup) {
super("Indexed", 1);
this.base = base;
const length = base.numComps * (highVal + 1);
this.lookup = new Uint8Array(length);
if (lookup instanceof BaseStream) {
const bytes = lookup.getBytes(length);
this.lookup.set(bytes);
} else if (typeof lookup === "string") {
for (let i = 0; i < length; ++i) {
this.lookup[i] = lookup.charCodeAt(i) & 0xff;
}
} else {
throw new FormatError(`IndexedCS - unrecognized lookup table: ${lookup}`);
}
}
getRgbItem(src, srcOffset, dest, destOffset) {
const numComps = this.base.numComps;
const start = src[srcOffset] * numComps;
this.base.getRgbBuffer(this.lookup, start, 1, dest, destOffset, 8, 0);
}
getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
const base = this.base;
const numComps = base.numComps;
const outputDelta = base.getOutputLength(numComps, alpha01);
const lookup = this.lookup;
for (let i = 0; i < count; ++i) {
const lookupPos = src[srcOffset++] * numComps;
base.getRgbBuffer(lookup, lookupPos, 1, dest, destOffset, 8, alpha01);
destOffset += outputDelta;
}
}
getOutputLength(inputLength, alpha01) {
return this.base.getOutputLength(inputLength * this.base.numComps, alpha01);
}
isDefaultDecode(decodeMap, bpc) {
if (!Array.isArray(decodeMap)) {
return true;
}
if (decodeMap.length !== 2) {
warn("Decode map length is not correct");
return true;
}
if (!Number.isInteger(bpc) || bpc < 1) {
warn("Bits per component is not correct");
return true;
}
return decodeMap[0] === 0 && decodeMap[1] === (1 << bpc) - 1;
}
}
class DeviceGrayCS extends ColorSpace {
constructor() {
super("DeviceGray", 1);
}
getRgbItem(src, srcOffset, dest, destOffset) {
const c = src[srcOffset] * 255;
dest[destOffset] = dest[destOffset + 1] = dest[destOffset + 2] = c;
}
getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
const scale = 255 / ((1 << bits) - 1);
let j = srcOffset,
q = destOffset;
for (let i = 0; i < count; ++i) {
const c = scale * src[j++];
dest[q++] = c;
dest[q++] = c;
dest[q++] = c;
q += alpha01;
}
}
getOutputLength(inputLength, alpha01) {
return inputLength * (3 + alpha01);
}
}
class DeviceRgbCS extends ColorSpace {
constructor() {
super("DeviceRGB", 3);
}
getRgbItem(src, srcOffset, dest, destOffset) {
dest[destOffset] = src[srcOffset] * 255;
dest[destOffset + 1] = src[srcOffset + 1] * 255;
dest[destOffset + 2] = src[srcOffset + 2] * 255;
}
getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
if (bits === 8 && alpha01 === 0) {
dest.set(src.subarray(srcOffset, srcOffset + count * 3), destOffset);
return;
}
const scale = 255 / ((1 << bits) - 1);
let j = srcOffset,
q = destOffset;
for (let i = 0; i < count; ++i) {
dest[q++] = scale * src[j++];
dest[q++] = scale * src[j++];
dest[q++] = scale * src[j++];
q += alpha01;
}
}
getOutputLength(inputLength, alpha01) {
return inputLength * (3 + alpha01) / 3 | 0;
}
isPassthrough(bits) {
return bits === 8;
}
}
class DeviceRgbaCS extends ColorSpace {
constructor() {
super("DeviceRGBA", 4);
}
getOutputLength(inputLength, _alpha01) {
return inputLength * 4;
}
isPassthrough(bits) {
return bits === 8;
}
fillRgb(dest, originalWidth, originalHeight, width, height, actualHeight, bpc, comps, alpha01) {
if (originalHeight !== height || originalWidth !== width) {
resizeRgbaImage(comps, dest, originalWidth, originalHeight, width, height, alpha01);
} else {
copyRgbaImage(comps, dest, alpha01);
}
}
}
class DeviceCmykCS extends ColorSpace {
constructor() {
super("DeviceCMYK", 4);
}
#toRgb(src, srcOffset, srcScale, dest, destOffset) {
const c = src[srcOffset] * srcScale;
const m = src[srcOffset + 1] * srcScale;
const y = src[srcOffset + 2] * srcScale;
const k = src[srcOffset + 3] * srcScale;
dest[destOffset] = 255 + c * (-4.387332384609988 * c + 54.48615194189176 * m + 18.82290502165302 * y + 212.25662451639585 * k + -285.2331026137004) + m * (1.7149763477362134 * m - 5.6096736904047315 * y + -17.873870861415444 * k - 5.497006427196366) + y * (-2.5217340131683033 * y - 21.248923337353073 * k + 17.5119270841813) + k * (-21.86122147463605 * k - 189.48180835922747);
dest[destOffset + 1] = 255 + c * (8.841041422036149 * c + 60.118027045597366 * m + 6.871425592049007 * y + 31.159100130055922 * k + -79.2970844816548) + m * (-15.310361306967817 * m + 17.575251261109482 * y + 131.35250912493976 * k - 190.9453302588951) + y * (4.444339102852739 * y + 9.8632861493405 * k - 24.86741582555878) + k * (-20.737325471181034 * k - 187.80453709719578);
dest[destOffset + 2] = 255 + c * (0.8842522430003296 * c + 8.078677503112928 * m + 30.89978309703729 * y - 0.23883238689178934 * k + -14.183576799673286) + m * (10.49593273432072 * m + 63.02378494754052 * y + 50.606957656360734 * k - 112.23884253719248) + y * (0.03296041114873217 * y + 115.60384449646641 * k + -193.58209356861505) + k * (-22.33816807309886 * k - 180.12613974708367);
}
getRgbItem(src, srcOffset, dest, destOffset) {
this.#toRgb(src, srcOffset, 1, dest, destOffset);
}
getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
const scale = 1 / ((1 << bits) - 1);
for (let i = 0; i < count; i++) {
this.#toRgb(src, srcOffset, scale, dest, destOffset);
srcOffset += 4;
destOffset += 3 + alpha01;
}
}
getOutputLength(inputLength, alpha01) {
return inputLength / 4 * (3 + alpha01) | 0;
}
}
class CalGrayCS extends ColorSpace {
constructor(whitePoint, blackPoint, gamma) {
super("CalGray", 1);
if (!whitePoint) {
throw new FormatError("WhitePoint missing - required for color space CalGray");
}
[this.XW, this.YW, this.ZW] = whitePoint;
[this.XB, this.YB, this.ZB] = blackPoint || [0, 0, 0];
this.G = gamma || 1;
if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) {
throw new FormatError(`Invalid WhitePoint components for ${this.name}, no fallback available`);
}
if (this.XB < 0 || this.YB < 0 || this.ZB < 0) {
info(`Invalid BlackPoint for ${this.name}, falling back to default.`);
this.XB = this.YB = this.ZB = 0;
}
if (this.XB !== 0 || this.YB !== 0 || this.ZB !== 0) {
warn(`${this.name}, BlackPoint: XB: ${this.XB}, YB: ${this.YB}, ` + `ZB: ${this.ZB}, only default values are supported.`);
}
if (this.G < 1) {
info(`Invalid Gamma: ${this.G} for ${this.name}, falling back to default.`);
this.G = 1;
}
}
#toRgb(src, srcOffset, dest, destOffset, scale) {
const A = src[srcOffset] * scale;
const AG = A ** this.G;
const L = this.YW * AG;
const val = Math.max(295.8 * L ** 0.3333333333333333 - 40.8, 0);
dest[destOffset] = val;
dest[destOffset + 1] = val;
dest[destOffset + 2] = val;
}
getRgbItem(src, srcOffset, dest, destOffset) {
this.#toRgb(src, srcOffset, dest, destOffset, 1);
}
getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
const scale = 1 / ((1 << bits) - 1);
for (let i = 0; i < count; ++i) {
this.#toRgb(src, srcOffset, dest, destOffset, scale);
srcOffset += 1;
destOffset += 3 + alpha01;
}
}
getOutputLength(inputLength, alpha01) {
return inputLength * (3 + alpha01);
}
}
class CalRGBCS extends ColorSpace {
static #BRADFORD_SCALE_MATRIX = new Float32Array([0.8951, 0.2664, -0.1614, -0.7502, 1.7135, 0.0367, 0.0389, -0.0685, 1.0296]);
static #BRADFORD_SCALE_INVERSE_MATRIX = new Float32Array([0.9869929, -0.1470543, 0.1599627, 0.4323053, 0.5183603, 0.0492912, -0.0085287, 0.0400428, 0.9684867]);
static #SRGB_D65_XYZ_TO_RGB_MATRIX = new Float32Array([3.2404542, -1.5371385, -0.4985314, -0.9692660, 1.8760108, 0.0415560, 0.0556434, -0.2040259, 1.0572252]);
static #FLAT_WHITEPOINT_MATRIX = new Float32Array([1, 1, 1]);
static #tempNormalizeMatrix = new Float32Array(3);
static #tempConvertMatrix1 = new Float32Array(3);
static #tempConvertMatrix2 = new Float32Array(3);
static #DECODE_L_CONSTANT = ((8 + 16) / 116) ** 3 / 8.0;
constructor(whitePoint, blackPoint, gamma, matrix) {
super("CalRGB", 3);
if (!whitePoint) {
throw new FormatError("WhitePoint missing - required for color space CalRGB");
}
const [XW, YW, ZW] = this.whitePoint = whitePoint;
const [XB, YB, ZB] = this.blackPoint = blackPoint || new Float32Array(3);
[this.GR, this.GG, this.GB] = gamma || new Float32Array([1, 1, 1]);
[this.MXA, this.MYA, this.MZA, this.MXB, this.MYB, this.MZB, this.MXC, this.MYC, this.MZC] = matrix || new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]);
if (XW < 0 || ZW < 0 || YW !== 1) {
throw new FormatError(`Invalid WhitePoint components for ${this.name}, no fallback available`);
}
if (XB < 0 || YB < 0 || ZB < 0) {
info(`Invalid BlackPoint for ${this.name} [${XB}, ${YB}, ${ZB}], ` + "falling back to default.");
this.blackPoint = new Float32Array(3);
}
if (this.GR < 0 || this.GG < 0 || this.GB < 0) {
info(`Invalid Gamma [${this.GR}, ${this.GG}, ${this.GB}] for ` + `${this.name}, falling back to default.`);
this.GR = this.GG = this.GB = 1;
}
}
#matrixProduct(a, b, result) {
result[0] = a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
result[1] = a[3] * b[0] + a[4] * b[1] + a[5] * b[2];
result[2] = a[6] * b[0] + a[7] * b[1] + a[8] * b[2];
}
#toFlat(sourceWhitePoint, LMS, result) {
result[0] = LMS[0] * 1 / sourceWhitePoint[0];
result[1] = LMS[1] * 1 / sourceWhitePoint[1];
result[2] = LMS[2] * 1 / sourceWhitePoint[2];
}
#toD65(sourceWhitePoint, LMS, result) {
const D65X = 0.95047;
const D65Y = 1;
const D65Z = 1.08883;
result[0] = LMS[0] * D65X / sourceWhitePoint[0];
result[1] = LMS[1] * D65Y / sourceWhitePoint[1];
result[2] = LMS[2] * D65Z / sourceWhitePoint[2];
}
#sRGBTransferFunction(color) {
if (color <= 0.0031308) {
return MathClamp(12.92 * color, 0, 1);
}
if (color >= 0.99554525) {
return 1;
}
return MathClamp((1 + 0.055) * color ** (1 / 2.4) - 0.055, 0, 1);
}
#decodeL(L) {
if (L < 0) {
return -this.#decodeL(-L);
}
if (L > 8.0) {
return ((L + 16) / 116) ** 3;
}
return L * CalRGBCS.#DECODE_L_CONSTANT;
}
#compensateBlackPoint(sourceBlackPoint, XYZ_Flat, result) {
if (sourceBlackPoint[0] === 0 && sourceBlackPoint[1] === 0 && sourceBlackPoint[2] === 0) {
result[0] = XYZ_Flat[0];
result[1] = XYZ_Flat[1];
result[2] = XYZ_Flat[2];
return;
}
const zeroDecodeL = this.#decodeL(0);
const X_DST = zeroDecodeL;
const X_SRC = this.#decodeL(sourceBlackPoint[0]);
const Y_DST = zeroDecodeL;
const Y_SRC = this.#decodeL(sourceBlackPoint[1]);
const Z_DST = zeroDecodeL;
const Z_SRC = this.#decodeL(sourceBlackPoint[2]);
const X_Scale = (1 - X_DST) / (1 - X_SRC);
const X_Offset = 1 - X_Scale;
const Y_Scale = (1 - Y_DST) / (1 - Y_SRC);
const Y_Offset = 1 - Y_Scale;
const Z_Scale = (1 - Z_DST) / (1 - Z_SRC);
const Z_Offset = 1 - Z_Scale;
result[0] = XYZ_Flat[0] * X_Scale + X_Offset;
result[1] = XYZ_Flat[1] * Y_Scale + Y_Offset;
result[2] = XYZ_Flat[2] * Z_Scale + Z_Offset;
}
#normalizeWhitePointToFlat(sourceWhitePoint, XYZ_In, result) {
if (sourceWhitePoint[0] === 1 && sourceWhitePoint[2] === 1) {
result[0] = XYZ_In[0];
result[1] = XYZ_In[1];
result[2] = XYZ_In[2];
return;
}
const LMS = result;
this.#matrixProduct(CalRGBCS.#BRADFORD_SCALE_MATRIX, XYZ_In, LMS);
const LMS_Flat = CalRGBCS.#tempNormalizeMatrix;
this.#toFlat(sourceWhitePoint, LMS, LMS_Flat);
this.#matrixProduct(CalRGBCS.#BRADFORD_SCALE_INVERSE_MATRIX, LMS_Flat, result);
}
#normalizeWhitePointToD65(sourceWhitePoint, XYZ_In, result) {
const LMS = result;
this.#matrixProduct(CalRGBCS.#BRADFORD_SCALE_MATRIX, XYZ_In, LMS);
const LMS_D65 = CalRGBCS.#tempNormalizeMatrix;
this.#toD65(sourceWhitePoint, LMS, LMS_D65);
this.#matrixProduct(CalRGBCS.#BRADFORD_SCALE_INVERSE_MATRIX, LMS_D65, result);
}
#toRgb(src, srcOffset, dest, destOffset, scale) {
const A = MathClamp(src[srcOffset] * scale, 0, 1);
const B = MathClamp(src[srcOffset + 1] * scale, 0, 1);
const C = MathClamp(src[srcOffset + 2] * scale, 0, 1);
const AGR = A === 1 ? 1 : A ** this.GR;
const BGG = B === 1 ? 1 : B ** this.GG;
const CGB = C === 1 ? 1 : C ** this.GB;
const X = this.MXA * AGR + this.MXB * BGG + this.MXC * CGB;
const Y = this.MYA * AGR + this.MYB * BGG + this.MYC * CGB;
const Z = this.MZA * AGR + this.MZB * BGG + this.MZC * CGB;
const XYZ = CalRGBCS.#tempConvertMatrix1;
XYZ[0] = X;
XYZ[1] = Y;
XYZ[2] = Z;
const XYZ_Flat = CalRGBCS.#tempConvertMatrix2;
this.#normalizeWhitePointToFlat(this.whitePoint, XYZ, XYZ_Flat);
const XYZ_Black = CalRGBCS.#tempConvertMatrix1;
this.#compensateBlackPoint(this.blackPoint, XYZ_Flat, XYZ_Black);
const XYZ_D65 = CalRGBCS.#tempConvertMatrix2;
this.#normalizeWhitePointToD65(CalRGBCS.#FLAT_WHITEPOINT_MATRIX, XYZ_Black, XYZ_D65);
const SRGB = CalRGBCS.#tempConvertMatrix1;
this.#matrixProduct(CalRGBCS.#SRGB_D65_XYZ_TO_RGB_MATRIX, XYZ_D65, SRGB);
dest[destOffset] = this.#sRGBTransferFunction(SRGB[0]) * 255;
dest[destOffset + 1] = this.#sRGBTransferFunction(SRGB[1]) * 255;
dest[destOffset + 2] = this.#sRGBTransferFunction(SRGB[2]) * 255;
}
getRgbItem(src, srcOffset, dest, destOffset) {
this.#toRgb(src, srcOffset, dest, destOffset, 1);
}
getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
const scale = 1 / ((1 << bits) - 1);
for (let i = 0; i < count; ++i) {
this.#toRgb(src, srcOffset, dest, destOffset, scale);
srcOffset += 3;
destOffset += 3 + alpha01;
}
}
getOutputLength(inputLength, alpha01) {
return inputLength * (3 + alpha01) / 3 | 0;
}
}
class LabCS extends ColorSpace {
constructor(whitePoint, blackPoint, range) {
super("Lab", 3);
if (!whitePoint) {
throw new FormatError("WhitePoint missing - required for color space Lab");
}
[this.XW, this.YW, this.ZW] = whitePoint;
[this.amin, this.amax, this.bmin, this.bmax] = range || [-100, 100, -100, 100];
[this.XB, this.YB, this.ZB] = blackPoint || [0, 0, 0];
if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) {
throw new FormatError("Invalid WhitePoint components, no fallback available");
}
if (this.XB < 0 || this.YB < 0 || this.ZB < 0) {
info("Invalid BlackPoint, falling back to default");
this.XB = this.YB = this.ZB = 0;
}
if (this.amin > this.amax || this.bmin > this.bmax) {
info("Invalid Range, falling back to defaults");
this.amin = -100;
this.amax = 100;
this.bmin = -100;
this.bmax = 100;
}
}
#fn_g(x) {
return x >= 6 / 29 ? x ** 3 : 108 / 841 * (x - 4 / 29);
}
#decode(value, high1, low2, high2) {
return low2 + value * (high2 - low2) / high1;
}
#toRgb(src, srcOffset, maxVal, dest, destOffset) {
let Ls = src[srcOffset];
let as = src[srcOffset + 1];
let bs = src[srcOffset + 2];
if (maxVal !== false) {
Ls = this.#decode(Ls, maxVal, 0, 100);
as = this.#decode(as, maxVal, this.amin, this.amax);
bs = this.#decode(bs, maxVal, this.bmin, this.bmax);
}
if (as > this.amax) {
as = this.amax;
} else if (as < this.amin) {
as = this.amin;
}
if (bs > this.bmax) {
bs = this.bmax;
} else if (bs < this.bmin) {
bs = this.bmin;
}
const M = (Ls + 16) / 116;
const L = M + as / 500;
const N = M - bs / 200;
const X = this.XW * this.#fn_g(L);
const Y = this.YW * this.#fn_g(M);
const Z = this.ZW * this.#fn_g(N);
let r, g, b;
if (this.ZW < 1) {
r = X * 3.1339 + Y * -1.617 + Z * -0.4906;
g = X * -0.9785 + Y * 1.916 + Z * 0.0333;
b = X * 0.072 + Y * -0.229 + Z * 1.4057;
} else {
r = X * 3.2406 + Y * -1.5372 + Z * -0.4986;
g = X * -0.9689 + Y * 1.8758 + Z * 0.0415;
b = X * 0.0557 + Y * -0.204 + Z * 1.057;
}
dest[destOffset] = Math.sqrt(r) * 255;
dest[destOffset + 1] = Math.sqrt(g) * 255;
dest[destOffset + 2] = Math.sqrt(b) * 255;
}
getRgbItem(src, srcOffset, dest, destOffset) {
this.#toRgb(src, srcOffset, false, dest, destOffset);
}
getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
const maxVal = (1 << bits) - 1;
for (let i = 0; i < count; i++) {
this.#toRgb(src, srcOffset, maxVal, dest, destOffset);
srcOffset += 3;
destOffset += 3 + alpha01;
}
}
getOutputLength(inputLength, alpha01) {
return inputLength * (3 + alpha01) / 3 | 0;
}
isDefaultDecode(decodeMap, bpc) {
return true;
}
get usesZeroToOneRange() {
return shadow(this, "usesZeroToOneRange", false);
}
}
;// ./src/core/icc_colorspace.js
function fetchSync(url) {
const xhr = new XMLHttpRequest();
xhr.open("GET", url, false);
xhr.responseType = "arraybuffer";
xhr.send(null);
return xhr.response;
}
class IccColorSpace extends ColorSpace {
#transformer;
#convertPixel;
static #useWasm = true;
static #wasmUrl = null;
static #finalizer = new FinalizationRegistry(transformer => {
qcms_drop_transformer(transformer);
});
constructor(iccProfile, name, numComps) {
if (!IccColorSpace.isUsable) {
throw new Error("No ICC color space support");
}
super(name, numComps);
let inType;
switch (numComps) {
case 1:
inType = DataType.Gray8;
this.#convertPixel = (src, srcOffset) => qcms_convert_one(this.#transformer, src[srcOffset] * 255);
break;
case 3:
inType = DataType.RGB8;
this.#convertPixel = (src, srcOffset) => qcms_convert_three(this.#transformer, src[srcOffset] * 255, src[srcOffset + 1] * 255, src[srcOffset + 2] * 255);
break;
case 4:
inType = DataType.CMYK;
this.#convertPixel = (src, srcOffset) => qcms_convert_four(this.#transformer, src[srcOffset] * 255, src[srcOffset + 1] * 255, src[srcOffset + 2] * 255, src[srcOffset + 3] * 255);
break;
default:
throw new Error(`Unsupported number of components: ${numComps}`);
}
this.#transformer = qcms_transformer_from_memory(iccProfile, inType, Intent.Perceptual);
if (!this.#transformer) {
throw new Error("Failed to create ICC color space");
}
IccColorSpace.#finalizer.register(this, this.#transformer);
}
getRgbItem(src, srcOffset, dest, destOffset) {
QCMS._destBuffer = dest.subarray(destOffset, destOffset + 3);
this.#convertPixel(src, srcOffset);
QCMS._destBuffer = null;
}
getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
src = src.subarray(srcOffset, srcOffset + count * this.numComps);
if (bits !== 8) {
const scale = 255 / ((1 << bits) - 1);
for (let i = 0, ii = src.length; i < ii; i++) {
src[i] *= scale;
}
}
QCMS._mustAddAlpha = alpha01 && dest.buffer === src.buffer;
QCMS._destBuffer = dest.subarray(destOffset, destOffset + count * (3 + alpha01));
qcms_convert_array(this.#transformer, src);
QCMS._mustAddAlpha = false;
QCMS._destBuffer = null;
}
getOutputLength(inputLength, alpha01) {
return inputLength / this.numComps * (3 + alpha01) | 0;
}
static setOptions({
useWasm,
useWorkerFetch,
wasmUrl
}) {
if (!useWorkerFetch) {
this.#useWasm = false;
return;
}
this.#useWasm = useWasm;
this.#wasmUrl = wasmUrl;
}
static get isUsable() {
let isUsable = false;
if (this.#useWasm) {
if (this.#wasmUrl) {
try {
this._module = QCMS._module = initSync({
module: fetchSync(`${this.#wasmUrl}qcms_bg.wasm`)
});
isUsable = !!this._module;
} catch (e) {
warn(`ICCBased color space: "${e}".`);
}
} else {
warn("No ICC color space support due to missing `wasmUrl` API option");
}
}
return shadow(this, "isUsable", isUsable);
}
}
class CmykICCBasedCS extends IccColorSpace {
static #iccUrl;
constructor() {
const iccProfile = new Uint8Array(fetchSync(`${CmykICCBasedCS.#iccUrl}CGATS001Compat-v2-micro.icc`));
super(iccProfile, "DeviceCMYK", 4);
}
static setOptions({
iccUrl
}) {
this.#iccUrl = iccUrl;
}
static get isUsable() {
let isUsable = false;
if (IccColorSpace.isUsable) {
if (this.#iccUrl) {
isUsable = true;
} else {
warn("No CMYK ICC profile support due to missing `iccUrl` API option");
}
}
return shadow(this, "isUsable", isUsable);
}
}
;// ./src/core/stream.js
class Stream extends BaseStream {
constructor(arrayBuffer, start, length, dict) {
super();
this.bytes = arrayBuffer instanceof Uint8Array ? arrayBuffer : new Uint8Array(arrayBuffer);
this.start = start || 0;
this.pos = this.start;
this.end = start + length || this.bytes.length;
this.dict = dict;
}
get length() {
return this.end - this.start;
}
get isEmpty() {
return this.length === 0;
}
getByte() {
if (this.pos >= this.end) {
return -1;
}
return this.bytes[this.pos++];
}
getBytes(length) {
const bytes = this.bytes;
const pos = this.pos;
const strEnd = this.end;
if (!length) {
return bytes.subarray(pos, strEnd);
}
let end = pos + length;
if (end > strEnd) {
end = strEnd;
}
this.pos = end;
return bytes.subarray(pos, end);
}
getByteRange(begin, end) {
if (begin < 0) {
begin = 0;
}
if (end > this.end) {
end = this.end;
}
return this.bytes.subarray(begin, end);
}
reset() {
this.pos = this.start;
}
moveStart() {
this.start = this.pos;
}
makeSubStream(start, length, dict = null) {
return new Stream(this.bytes.buffer, start, length, dict);
}
}
class StringStream extends Stream {
constructor(str) {
super(stringToBytes(str));
}
}
class NullStream extends Stream {
constructor() {
super(new Uint8Array(0));
}
}
;// ./src/core/chunked_stream.js
class ChunkedStream extends Stream {
constructor(length, chunkSize, manager) {
super(new Uint8Array(length), 0, length, null);
this.chunkSize = chunkSize;
this._loadedChunks = new Set();
this.numChunks = Math.ceil(length / chunkSize);
this.manager = manager;
this.progressiveDataLength = 0;
this.lastSuccessfulEnsureByteChunk = -1;
}
getMissingChunks() {
const chunks = [];
for (let chunk = 0, n = this.numChunks; chunk < n; ++chunk) {
if (!this._loadedChunks.has(chunk)) {
chunks.push(chunk);
}
}
return chunks;
}
get numChunksLoaded() {
return this._loadedChunks.size;
}
get isDataLoaded() {
return this.numChunksLoaded === this.numChunks;
}
onReceiveData(begin, chunk) {
const chunkSize = this.chunkSize;
if (begin % chunkSize !== 0) {
throw new Error(`Bad begin offset: ${begin}`);
}
const end = begin + chunk.byteLength;
if (end % chunkSize !== 0 && end !== this.bytes.length) {
throw new Error(`Bad end offset: ${end}`);
}
this.bytes.set(new Uint8Array(chunk), begin);
const beginChunk = Math.floor(begin / chunkSize);
const endChunk = Math.floor((end - 1) / chunkSize) + 1;
for (let curChunk = beginChunk; curChunk < endChunk; ++curChunk) {
this._loadedChunks.add(curChunk);
}
}
onReceiveProgressiveData(data) {
let position = this.progressiveDataLength;
const beginChunk = Math.floor(position / this.chunkSize);
this.bytes.set(new Uint8Array(data), position);
position += data.byteLength;
this.progressiveDataLength = position;
const endChunk = position >= this.end ? this.numChunks : Math.floor(position / this.chunkSize);
for (let curChunk = beginChunk; curChunk < endChunk; ++curChunk) {
this._loadedChunks.add(curChunk);
}
}
ensureByte(pos) {
if (pos < this.progressiveDataLength) {
return;
}
const chunk = Math.floor(pos / this.chunkSize);
if (chunk > this.numChunks) {
return;
}
if (chunk === this.lastSuccessfulEnsureByteChunk) {
return;
}
if (!this._loadedChunks.has(chunk)) {
throw new MissingDataException(pos, pos + 1);
}
this.lastSuccessfulEnsureByteChunk = chunk;
}
ensureRange(begin, end) {
if (begin >= end) {
return;
}
if (end <= this.progressiveDataLength) {
return;
}
const beginChunk = Math.floor(begin / this.chunkSize);
if (beginChunk > this.numChunks) {
return;
}
const endChunk = Math.min(Math.floor((end - 1) / this.chunkSize) + 1, this.numChunks);
for (let chunk = beginChunk; chunk < endChunk; ++chunk) {
if (!this._loadedChunks.has(chunk)) {
throw new MissingDataException(begin, end);
}
}
}
nextEmptyChunk(beginChunk) {
const numChunks = this.numChunks;
for (let i = 0; i < numChunks; ++i) {
const chunk = (beginChunk + i) % numChunks;
if (!this._loadedChunks.has(chunk)) {
return chunk;
}
}
return null;
}
hasChunk(chunk) {
return this._loadedChunks.has(chunk);
}
getByte() {
const pos = this.pos;
if (pos >= this.end) {
return -1;
}
if (pos >= this.progressiveDataLength) {
this.ensureByte(pos);
}
return this.bytes[this.pos++];
}
getBytes(length) {
const bytes = this.bytes;
const pos = this.pos;
const strEnd = this.end;
if (!length) {
if (strEnd > this.progressiveDataLength) {
this.ensureRange(pos, strEnd);
}
return bytes.subarray(pos, strEnd);
}
let end = pos + length;
if (end > strEnd) {
end = strEnd;
}
if (end > this.progressiveDataLength) {
this.ensureRange(pos, end);
}
this.pos = end;
return bytes.subarray(pos, end);
}
getByteRange(begin, end) {
if (begin < 0) {
begin = 0;
}
if (end > this.end) {
end = this.end;
}
if (end > this.progressiveDataLength) {
this.ensureRange(begin, end);
}
return this.bytes.subarray(begin, end);
}
makeSubStream(start, length, dict = null) {
if (length) {
if (start + length > this.progressiveDataLength) {
this.ensureRange(start, start + length);
}
} else if (start >= this.progressiveDataLength) {
this.ensureByte(start);
}
function ChunkedStreamSubstream() {}
ChunkedStreamSubstream.prototype = Object.create(this);
ChunkedStreamSubstream.prototype.getMissingChunks = function () {
const chunkSize = this.chunkSize;
const beginChunk = Math.floor(this.start / chunkSize);
const endChunk = Math.floor((this.end - 1) / chunkSize) + 1;
const missingChunks = [];
for (let chunk = beginChunk; chunk < endChunk; ++chunk) {
if (!this._loadedChunks.has(chunk)) {
missingChunks.push(chunk);
}
}
return missingChunks;
};
Object.defineProperty(ChunkedStreamSubstream.prototype, "isDataLoaded", {
get() {
if (this.numChunksLoaded === this.numChunks) {
return true;
}
return this.getMissingChunks().length === 0;
},
configurable: true
});
const subStream = new ChunkedStreamSubstream();
subStream.pos = subStream.start = start;
subStream.end = start + length || this.end;
subStream.dict = dict;
return subStream;
}
getBaseStreams() {
return [this];
}
}
class ChunkedStreamManager {
constructor(pdfNetworkStream, args) {
this.length = args.length;
this.chunkSize = args.rangeChunkSize;
this.stream = new ChunkedStream(this.length, this.chunkSize, this);
this.pdfNetworkStream = pdfNetworkStream;
this.disableAutoFetch = args.disableAutoFetch;
this.msgHandler = args.msgHandler;
this.currRequestId = 0;
this._chunksNeededByRequest = new Map();
this._requestsByChunk = new Map();
this._promisesByRequest = new Map();
this.progressiveDataLength = 0;
this.aborted = false;
this._loadedStreamCapability = Promise.withResolvers();
}
sendRequest(begin, end) {
const rangeReader = this.pdfNetworkStream.getRangeReader(begin, end);
if (!rangeReader.isStreamingSupported) {
rangeReader.onProgress = this.onProgress.bind(this);
}
let chunks = [],
loaded = 0;
return new Promise((resolve, reject) => {
const readChunk = ({
value,
done
}) => {
try {
if (done) {
const chunkData = arrayBuffersToBytes(chunks);
chunks = null;
resolve(chunkData);
return;
}
loaded += value.byteLength;
if (rangeReader.isStreamingSupported) {
this.onProgress({
loaded
});
}
chunks.push(value);
rangeReader.read().then(readChunk, reject);
} catch (e) {
reject(e);
}
};
rangeReader.read().then(readChunk, reject);
}).then(data => {
if (this.aborted) {
return;
}
this.onReceiveData({
chunk: data,
begin
});
});
}
requestAllChunks(noFetch = false) {
if (!noFetch) {
const missingChunks = this.stream.getMissingChunks();
this._requestChunks(missingChunks);
}
return this._loadedStreamCapability.promise;
}
_requestChunks(chunks) {
const requestId = this.currRequestId++;
const chunksNeeded = new Set();
this._chunksNeededByRequest.set(requestId, chunksNeeded);
for (const chunk of chunks) {
if (!this.stream.hasChunk(chunk)) {
chunksNeeded.add(chunk);
}
}
if (chunksNeeded.size === 0) {
return Promise.resolve();
}
const capability = Promise.withResolvers();
this._promisesByRequest.set(requestId, capability);
const chunksToRequest = [];
for (const chunk of chunksNeeded) {
let requestIds = this._requestsByChunk.get(chunk);
if (!requestIds) {
requestIds = [];
this._requestsByChunk.set(chunk, requestIds);
chunksToRequest.push(chunk);
}
requestIds.push(requestId);
}
if (chunksToRequest.length > 0) {
const groupedChunksToRequest = this.groupChunks(chunksToRequest);
for (const groupedChunk of groupedChunksToRequest) {
const begin = groupedChunk.beginChunk * this.chunkSize;
const end = Math.min(groupedChunk.endChunk * this.chunkSize, this.length);
this.sendRequest(begin, end).catch(capability.reject);
}
}
return capability.promise.catch(reason => {
if (this.aborted) {
return;
}
throw reason;
});
}
getStream() {
return this.stream;
}
requestRange(begin, end) {
end = Math.min(end, this.length);
const beginChunk = this.getBeginChunk(begin);
const endChunk = this.getEndChunk(end);
const chunks = [];
for (let chunk = beginChunk; chunk < endChunk; ++chunk) {
chunks.push(chunk);
}
return this._requestChunks(chunks);
}
requestRanges(ranges = []) {
const chunksToRequest = [];
for (const range of ranges) {
const beginChunk = this.getBeginChunk(range.begin);
const endChunk = this.getEndChunk(range.end);
for (let chunk = beginChunk; chunk < endChunk; ++chunk) {
if (!chunksToRequest.includes(chunk)) {
chunksToRequest.push(chunk);
}
}
}
chunksToRequest.sort((a, b) => a - b);
return this._requestChunks(chunksToRequest);
}
groupChunks(chunks) {
const groupedChunks = [];
let beginChunk = -1;
let prevChunk = -1;
for (let i = 0, ii = chunks.length; i < ii; ++i) {
const chunk = chunks[i];
if (beginChunk < 0) {
beginChunk = chunk;
}
if (prevChunk >= 0 && prevChunk + 1 !== chunk) {
groupedChunks.push({
beginChunk,
endChunk: prevChunk + 1
});
beginChunk = chunk;
}
if (i + 1 === chunks.length) {
groupedChunks.push({
beginChunk,
endChunk: chunk + 1
});
}
prevChunk = chunk;
}
return groupedChunks;
}
onProgress(args) {
this.msgHandler.send("DocProgress", {
loaded: this.stream.numChunksLoaded * this.chunkSize + args.loaded,
total: this.length
});
}
onReceiveData(args) {
const chunk = args.chunk;
const isProgressive = args.begin === undefined;
const begin = isProgressive ? this.progressiveDataLength : args.begin;
const end = begin + chunk.byteLength;
const beginChunk = Math.floor(begin / this.chunkSize);
const endChunk = end < this.length ? Math.floor(end / this.chunkSize) : Math.ceil(end / this.chunkSize);
if (isProgressive) {
this.stream.onReceiveProgressiveData(chunk);
this.progressiveDataLength = end;
} else {
this.stream.onReceiveData(begin, chunk);
}
if (this.stream.isDataLoaded) {
this._loadedStreamCapability.resolve(this.stream);
}
const loadedRequests = [];
for (let curChunk = beginChunk; curChunk < endChunk; ++curChunk) {
const requestIds = this._requestsByChunk.get(curChunk);
if (!requestIds) {
continue;
}
this._requestsByChunk.delete(curChunk);
for (const requestId of requestIds) {
const chunksNeeded = this._chunksNeededByRequest.get(requestId);
if (chunksNeeded.has(curChunk)) {
chunksNeeded.delete(curChunk);
}
if (chunksNeeded.size > 0) {
continue;
}
loadedRequests.push(requestId);
}
}
if (!this.disableAutoFetch && this._requestsByChunk.size === 0) {
let nextEmptyChunk;
if (this.stream.numChunksLoaded === 1) {
const lastChunk = this.stream.numChunks - 1;
if (!this.stream.hasChunk(lastChunk)) {
nextEmptyChunk = lastChunk;
}
} else {
nextEmptyChunk = this.stream.nextEmptyChunk(endChunk);
}
if (Number.isInteger(nextEmptyChunk)) {
this._requestChunks([nextEmptyChunk]);
}
}
for (const requestId of loadedRequests) {
const capability = this._promisesByRequest.get(requestId);
this._promisesByRequest.delete(requestId);
capability.resolve();
}
this.msgHandler.send("DocProgress", {
loaded: this.stream.numChunksLoaded * this.chunkSize,
total: this.length
});
}
onError(err) {
this._loadedStreamCapability.reject(err);
}
getBeginChunk(begin) {
return Math.floor(begin / this.chunkSize);
}
getEndChunk(end) {
return Math.floor((end - 1) / this.chunkSize) + 1;
}
abort(reason) {
this.aborted = true;
this.pdfNetworkStream?.cancelAllRequests(reason);
for (const capability of this._promisesByRequest.values()) {
capability.reject(reason);
}
}
}
;// ./src/shared/image_utils.js
function convertToRGBA(params) {
switch (params.kind) {
case ImageKind.GRAYSCALE_1BPP:
return convertBlackAndWhiteToRGBA(params);
case ImageKind.RGB_24BPP:
return convertRGBToRGBA(params);
}
return null;
}
function convertBlackAndWhiteToRGBA({
src,
srcPos = 0,
dest,
width,
height,
nonBlackColor = 0xffffffff,
inverseDecode = false
}) {
const black = FeatureTest.isLittleEndian ? 0xff000000 : 0x000000ff;
const [zeroMapping, oneMapping] = inverseDecode ? [nonBlackColor, black] : [black, nonBlackColor];
const widthInSource = width >> 3;
const widthRemainder = width & 7;
const srcLength = src.length;
dest = new Uint32Array(dest.buffer);
let destPos = 0;
for (let i = 0; i < height; i++) {
for (const max = srcPos + widthInSource; srcPos < max; srcPos++) {
const elem = srcPos < srcLength ? src[srcPos] : 255;
dest[destPos++] = elem & 0b10000000 ? oneMapping : zeroMapping;
dest[destPos++] = elem & 0b1000000 ? oneMapping : zeroMapping;
dest[destPos++] = elem & 0b100000 ? oneMapping : zeroMapping;
dest[destPos++] = elem & 0b10000 ? oneMapping : zeroMapping;
dest[destPos++] = elem & 0b1000 ? oneMapping : zeroMapping;
dest[destPos++] = elem & 0b100 ? oneMapping : zeroMapping;
dest[destPos++] = elem & 0b10 ? oneMapping : zeroMapping;
dest[destPos++] = elem & 0b1 ? oneMapping : zeroMapping;
}
if (widthRemainder === 0) {
continue;
}
const elem = srcPos < srcLength ? src[srcPos++] : 255;
for (let j = 0; j < widthRemainder; j++) {
dest[destPos++] = elem & 1 << 7 - j ? oneMapping : zeroMapping;
}
}
return {
srcPos,
destPos
};
}
function convertRGBToRGBA({
src,
srcPos = 0,
dest,
destPos = 0,
width,
height
}) {
let i = 0;
const len = width * height * 3;
const len32 = len >> 2;
const src32 = new Uint32Array(src.buffer, srcPos, len32);
if (FeatureTest.isLittleEndian) {
for (; i < len32 - 2; i += 3, destPos += 4) {
const s1 = src32[i];
const s2 = src32[i + 1];
const s3 = src32[i + 2];
dest[destPos] = s1 | 0xff000000;
dest[destPos + 1] = s1 >>> 24 | s2 << 8 | 0xff000000;
dest[destPos + 2] = s2 >>> 16 | s3 << 16 | 0xff000000;
dest[destPos + 3] = s3 >>> 8 | 0xff000000;
}
for (let j = i * 4, jj = srcPos + len; j < jj; j += 3) {
dest[destPos++] = src[j] | src[j + 1] << 8 | src[j + 2] << 16 | 0xff000000;
}
} else {
for (; i < len32 - 2; i += 3, destPos += 4) {
const s1 = src32[i];
const s2 = src32[i + 1];
const s3 = src32[i + 2];
dest[destPos] = s1 | 0xff;
dest[destPos + 1] = s1 << 24 | s2 >>> 8 | 0xff;
dest[destPos + 2] = s2 << 16 | s3 >>> 16 | 0xff;
dest[destPos + 3] = s3 << 8 | 0xff;
}
for (let j = i * 4, jj = srcPos + len; j < jj; j += 3) {
dest[destPos++] = src[j] << 24 | src[j + 1] << 16 | src[j + 2] << 8 | 0xff;
}
}
return {
srcPos: srcPos + len,
destPos
};
}
function grayToRGBA(src, dest) {
if (FeatureTest.isLittleEndian) {
for (let i = 0, ii = src.length; i < ii; i++) {
dest[i] = src[i] * 0x10101 | 0xff000000;
}
} else {
for (let i = 0, ii = src.length; i < ii; i++) {
dest[i] = src[i] * 0x1010100 | 0x000000ff;
}
}
}
;// ./src/core/image_resizer.js
const MIN_IMAGE_DIM = 2048;
const MAX_IMAGE_DIM = 65537;
const MAX_ERROR = 128;
class ImageResizer {
static #goodSquareLength = MIN_IMAGE_DIM;
static #isImageDecoderSupported = FeatureTest.isImageDecoderSupported;
constructor(imgData, isMask) {
this._imgData = imgData;
this._isMask = isMask;
}
static get canUseImageDecoder() {
return shadow(this, "canUseImageDecoder", this.#isImageDecoderSupported ? ImageDecoder.isTypeSupported("image/bmp") : Promise.resolve(false));
}
static needsToBeResized(width, height) {
if (width <= this.#goodSquareLength && height <= this.#goodSquareLength) {
return false;
}
const {
MAX_DIM
} = this;
if (width > MAX_DIM || height > MAX_DIM) {
return true;
}
const area = width * height;
if (this._hasMaxArea) {
return area > this.MAX_AREA;
}
if (area < this.#goodSquareLength ** 2) {
return false;
}
if (this._areGoodDims(width, height)) {
this.#goodSquareLength = Math.max(this.#goodSquareLength, Math.floor(Math.sqrt(width * height)));
return false;
}
this.#goodSquareLength = this._guessMax(this.#goodSquareLength, MAX_DIM, MAX_ERROR, 0);
const maxArea = this.MAX_AREA = this.#goodSquareLength ** 2;
return area > maxArea;
}
static get MAX_DIM() {
return shadow(this, "MAX_DIM", this._guessMax(MIN_IMAGE_DIM, MAX_IMAGE_DIM, 0, 1));
}
static get MAX_AREA() {
this._hasMaxArea = true;
return shadow(this, "MAX_AREA", this._guessMax(this.#goodSquareLength, this.MAX_DIM, MAX_ERROR, 0) ** 2);
}
static set MAX_AREA(area) {
if (area >= 0) {
this._hasMaxArea = true;
shadow(this, "MAX_AREA", area);
}
}
static setOptions({
canvasMaxAreaInBytes = -1,
isImageDecoderSupported = false
}) {
if (!this._hasMaxArea) {
this.MAX_AREA = canvasMaxAreaInBytes >> 2;
}
this.#isImageDecoderSupported = isImageDecoderSupported;
}
static _areGoodDims(width, height) {
try {
const canvas = new OffscreenCanvas(width, height);
const ctx = canvas.getContext("2d");
ctx.fillRect(0, 0, 1, 1);
const opacity = ctx.getImageData(0, 0, 1, 1).data[3];
canvas.width = canvas.height = 1;
return opacity !== 0;
} catch {
return false;
}
}
static _guessMax(start, end, tolerance, defaultHeight) {
while (start + tolerance + 1 < end) {
const middle = Math.floor((start + end) / 2);
const height = defaultHeight || middle;
if (this._areGoodDims(middle, height)) {
start = middle;
} else {
end = middle;
}
}
return start;
}
static async createImage(imgData, isMask = false) {
return new ImageResizer(imgData, isMask)._createImage();
}
async _createImage() {
const {
_imgData: imgData
} = this;
const {
width,
height
} = imgData;
if (width * height * 4 > MAX_INT_32) {
const result = this.#rescaleImageData();
if (result) {
return result;
}
}
const data = this._encodeBMP();
let decoder, imagePromise;
if (await ImageResizer.canUseImageDecoder) {
decoder = new ImageDecoder({
data,
type: "image/bmp",
preferAnimation: false,
transfer: [data.buffer]
});
imagePromise = decoder.decode().catch(reason => {
warn(`BMP image decoding failed: ${reason}`);
return createImageBitmap(new Blob([this._encodeBMP().buffer], {
type: "image/bmp"
}));
}).finally(() => {
decoder.close();
});
} else {
imagePromise = createImageBitmap(new Blob([data.buffer], {
type: "image/bmp"
}));
}
const {
MAX_AREA,
MAX_DIM
} = ImageResizer;
const minFactor = Math.max(width / MAX_DIM, height / MAX_DIM, Math.sqrt(width * height / MAX_AREA));
const firstFactor = Math.max(minFactor, 2);
const factor = Math.round(10 * (minFactor + 1.25)) / 10 / firstFactor;
const N = Math.floor(Math.log2(factor));
const steps = new Array(N + 2).fill(2);
steps[0] = firstFactor;
steps.splice(-1, 1, factor / (1 << N));
let newWidth = width;
let newHeight = height;
const result = await imagePromise;
let bitmap = result.image || result;
for (const step of steps) {
const prevWidth = newWidth;
const prevHeight = newHeight;
newWidth = Math.floor(newWidth / step) - 1;
newHeight = Math.floor(newHeight / step) - 1;
const canvas = new OffscreenCanvas(newWidth, newHeight);
const ctx = canvas.getContext("2d");
ctx.drawImage(bitmap, 0, 0, prevWidth, prevHeight, 0, 0, newWidth, newHeight);
bitmap.close();
bitmap = canvas.transferToImageBitmap();
}
imgData.data = null;
imgData.bitmap = bitmap;
imgData.width = newWidth;
imgData.height = newHeight;
return imgData;
}
#rescaleImageData() {
const {
_imgData: imgData
} = this;
const {
data,
width,
height,
kind
} = imgData;
const rgbaSize = width * height * 4;
const K = Math.ceil(Math.log2(rgbaSize / MAX_INT_32));
const newWidth = width >> K;
const newHeight = height >> K;
let rgbaData;
let maxHeight = height;
try {
rgbaData = new Uint8Array(rgbaSize);
} catch {
let n = Math.floor(Math.log2(rgbaSize + 1));
while (true) {
try {
rgbaData = new Uint8Array(2 ** n - 1);
break;
} catch {
n -= 1;
}
}
maxHeight = Math.floor((2 ** n - 1) / (width * 4));
const newSize = width * maxHeight * 4;
if (newSize < rgbaData.length) {
rgbaData = new Uint8Array(newSize);
}
}
const src32 = new Uint32Array(rgbaData.buffer);
const dest32 = new Uint32Array(newWidth * newHeight);
let srcPos = 0;
let newIndex = 0;
const step = Math.ceil(height / maxHeight);
const remainder = height % maxHeight === 0 ? height : height % maxHeight;
for (let k = 0; k < step; k++) {
const h = k < step - 1 ? maxHeight : remainder;
({
srcPos
} = convertToRGBA({
kind,
src: data,
dest: src32,
width,
height: h,
inverseDecode: this._isMask,
srcPos
}));
for (let i = 0, ii = h >> K; i < ii; i++) {
const buf = src32.subarray((i << K) * width);
for (let j = 0; j < newWidth; j++) {
dest32[newIndex++] = buf[j << K];
}
}
}
if (ImageResizer.needsToBeResized(newWidth, newHeight)) {
imgData.data = dest32;
imgData.width = newWidth;
imgData.height = newHeight;
imgData.kind = ImageKind.RGBA_32BPP;
return null;
}
const canvas = new OffscreenCanvas(newWidth, newHeight);
const ctx = canvas.getContext("2d", {
willReadFrequently: true
});
ctx.putImageData(new ImageData(new Uint8ClampedArray(dest32.buffer), newWidth, newHeight), 0, 0);
imgData.data = null;
imgData.bitmap = canvas.transferToImageBitmap();
imgData.width = newWidth;
imgData.height = newHeight;
return imgData;
}
_encodeBMP() {
const {
width,
height,
kind
} = this._imgData;
let data = this._imgData.data;
let bitPerPixel;
let colorTable = new Uint8Array(0);
let maskTable = colorTable;
let compression = 0;
switch (kind) {
case ImageKind.GRAYSCALE_1BPP:
{
bitPerPixel = 1;
colorTable = new Uint8Array(this._isMask ? [255, 255, 255, 255, 0, 0, 0, 0] : [0, 0, 0, 0, 255, 255, 255, 255]);
const rowLen = width + 7 >> 3;
const rowSize = rowLen + 3 & -4;
if (rowLen !== rowSize) {
const newData = new Uint8Array(rowSize * height);
let k = 0;
for (let i = 0, ii = height * rowLen; i < ii; i += rowLen, k += rowSize) {
newData.set(data.subarray(i, i + rowLen), k);
}
data = newData;
}
break;
}
case ImageKind.RGB_24BPP:
{
bitPerPixel = 24;
if (width & 3) {
const rowLen = 3 * width;
const rowSize = rowLen + 3 & -4;
const extraLen = rowSize - rowLen;
const newData = new Uint8Array(rowSize * height);
let k = 0;
for (let i = 0, ii = height * rowLen; i < ii; i += rowLen) {
const row = data.subarray(i, i + rowLen);
for (let j = 0; j < rowLen; j += 3) {
newData[k++] = row[j + 2];
newData[k++] = row[j + 1];
newData[k++] = row[j];
}
k += extraLen;
}
data = newData;
} else {
for (let i = 0, ii = data.length; i < ii; i += 3) {
const tmp = data[i];
data[i] = data[i + 2];
data[i + 2] = tmp;
}
}
break;
}
case ImageKind.RGBA_32BPP:
bitPerPixel = 32;
compression = 3;
maskTable = new Uint8Array(4 + 4 + 4 + 4 + 52);
const view = new DataView(maskTable.buffer);
if (FeatureTest.isLittleEndian) {
view.setUint32(0, 0x000000ff, true);
view.setUint32(4, 0x0000ff00, true);
view.setUint32(8, 0x00ff0000, true);
view.setUint32(12, 0xff000000, true);
} else {
view.setUint32(0, 0xff000000, true);
view.setUint32(4, 0x00ff0000, true);
view.setUint32(8, 0x0000ff00, true);
view.setUint32(12, 0x000000ff, true);
}
break;
default:
throw new Error("invalid format");
}
let i = 0;
const headerLength = 40 + maskTable.length;
const fileLength = 14 + headerLength + colorTable.length + data.length;
const bmpData = new Uint8Array(fileLength);
const view = new DataView(bmpData.buffer);
view.setUint16(i, 0x4d42, true);
i += 2;
view.setUint32(i, fileLength, true);
i += 4;
view.setUint32(i, 0, true);
i += 4;
view.setUint32(i, 14 + headerLength + colorTable.length, true);
i += 4;
view.setUint32(i, headerLength, true);
i += 4;
view.setInt32(i, width, true);
i += 4;
view.setInt32(i, -height, true);
i += 4;
view.setUint16(i, 1, true);
i += 2;
view.setUint16(i, bitPerPixel, true);
i += 2;
view.setUint32(i, compression, true);
i += 4;
view.setUint32(i, 0, true);
i += 4;
view.setInt32(i, 0, true);
i += 4;
view.setInt32(i, 0, true);
i += 4;
view.setUint32(i, colorTable.length / 4, true);
i += 4;
view.setUint32(i, 0, true);
i += 4;
bmpData.set(maskTable, i);
i += maskTable.length;
bmpData.set(colorTable, i);
i += colorTable.length;
bmpData.set(data, i);
return bmpData;
}
}
;// ./src/core/decode_stream.js
const emptyBuffer = new Uint8Array(0);
class DecodeStream extends BaseStream {
constructor(maybeMinBufferLength) {
super();
this._rawMinBufferLength = maybeMinBufferLength || 0;
this.pos = 0;
this.bufferLength = 0;
this.eof = false;
this.buffer = emptyBuffer;
this.minBufferLength = 512;
if (maybeMinBufferLength) {
while (this.minBufferLength < maybeMinBufferLength) {
this.minBufferLength *= 2;
}
}
}
get isEmpty() {
while (!this.eof && this.bufferLength === 0) {
this.readBlock();
}
return this.bufferLength === 0;
}
ensureBuffer(requested) {
const buffer = this.buffer;
if (requested <= buffer.byteLength) {
return buffer;
}
let size = this.minBufferLength;
while (size < requested) {
size *= 2;
}
const buffer2 = new Uint8Array(size);
buffer2.set(buffer);
return this.buffer = buffer2;
}
getByte() {
const pos = this.pos;
while (this.bufferLength <= pos) {
if (this.eof) {
return -1;
}
this.readBlock();
}
return this.buffer[this.pos++];
}
getBytes(length, decoderOptions = null) {
const pos = this.pos;
let end;
if (length) {
this.ensureBuffer(pos + length);
end = pos + length;
while (!this.eof && this.bufferLength < end) {
this.readBlock(decoderOptions);
}
const bufEnd = this.bufferLength;
if (end > bufEnd) {
end = bufEnd;
}
} else {
while (!this.eof) {
this.readBlock(decoderOptions);
}
end = this.bufferLength;
}
this.pos = end;
return this.buffer.subarray(pos, end);
}
async getImageData(length, decoderOptions) {
if (!this.canAsyncDecodeImageFromBuffer) {
if (this.isAsyncDecoder) {
return this.decodeImage(null, decoderOptions);
}
return this.getBytes(length, decoderOptions);
}
const data = await this.stream.asyncGetBytes();
return this.decodeImage(data, decoderOptions);
}
reset() {
this.pos = 0;
}
makeSubStream(start, length, dict = null) {
if (length === undefined) {
while (!this.eof) {
this.readBlock();
}
} else {
const end = start + length;
while (this.bufferLength <= end && !this.eof) {
this.readBlock();
}
}
return new Stream(this.buffer, start, length, dict);
}
getBaseStreams() {
return this.str ? this.str.getBaseStreams() : null;
}
}
class StreamsSequenceStream extends DecodeStream {
constructor(streams, onError = null) {
streams = streams.filter(s => s instanceof BaseStream);
let maybeLength = 0;
for (const stream of streams) {
maybeLength += stream instanceof DecodeStream ? stream._rawMinBufferLength : stream.length;
}
super(maybeLength);
this.streams = streams;
this._onError = onError;
}
readBlock() {
const streams = this.streams;
if (streams.length === 0) {
this.eof = true;
return;
}
const stream = streams.shift();
let chunk;
try {
chunk = stream.getBytes();
} catch (reason) {
if (this._onError) {
this._onError(reason, stream.dict?.objId);
return;
}
throw reason;
}
const bufferLength = this.bufferLength;
const newLength = bufferLength + chunk.length;
const buffer = this.ensureBuffer(newLength);
buffer.set(chunk, bufferLength);
this.bufferLength = newLength;
}
getBaseStreams() {
const baseStreamsBuf = [];
for (const stream of this.streams) {
const baseStreams = stream.getBaseStreams();
if (baseStreams) {
baseStreamsBuf.push(...baseStreams);
}
}
return baseStreamsBuf.length > 0 ? baseStreamsBuf : null;
}
}
;// ./src/core/colorspace_utils.js
class ColorSpaceUtils {
static parse({
cs,
xref,
resources = null,
pdfFunctionFactory,
globalColorSpaceCache,
localColorSpaceCache,
asyncIfNotCached = false
}) {
const options = {
xref,
resources,
pdfFunctionFactory,
globalColorSpaceCache,
localColorSpaceCache
};
let csName, csRef, parsedCS;
if (cs instanceof Ref) {
csRef = cs;
const cachedCS = globalColorSpaceCache.getByRef(csRef) || localColorSpaceCache.getByRef(csRef);
if (cachedCS) {
return cachedCS;
}
cs = xref.fetch(cs);
}
if (cs instanceof Name) {
csName = cs.name;
const cachedCS = localColorSpaceCache.getByName(csName);
if (cachedCS) {
return cachedCS;
}
}
try {
parsedCS = this.#parse(cs, options);
} catch (ex) {
if (asyncIfNotCached && !(ex instanceof MissingDataException)) {
return Promise.reject(ex);
}
throw ex;
}
if (csName || csRef) {
localColorSpaceCache.set(csName, csRef, parsedCS);
if (csRef) {
globalColorSpaceCache.set(null, csRef, parsedCS);
}
}
return asyncIfNotCached ? Promise.resolve(parsedCS) : parsedCS;
}
static #subParse(cs, options) {
const {
globalColorSpaceCache
} = options;
let csRef;
if (cs instanceof Ref) {
csRef = cs;
const cachedCS = globalColorSpaceCache.getByRef(csRef);
if (cachedCS) {
return cachedCS;
}
}
const parsedCS = this.#parse(cs, options);
if (csRef) {
globalColorSpaceCache.set(null, csRef, parsedCS);
}
return parsedCS;
}
static #parse(cs, options) {
const {
xref,
resources,
pdfFunctionFactory,
globalColorSpaceCache
} = options;
cs = xref.fetchIfRef(cs);
if (cs instanceof Name) {
switch (cs.name) {
case "G":
case "DeviceGray":
return this.gray;
case "RGB":
case "DeviceRGB":
return this.rgb;
case "DeviceRGBA":
return this.rgba;
case "CMYK":
case "DeviceCMYK":
return this.cmyk;
case "Pattern":
return new PatternCS(null);
default:
if (resources instanceof Dict) {
const colorSpaces = resources.get("ColorSpace");
if (colorSpaces instanceof Dict) {
const resourcesCS = colorSpaces.get(cs.name);
if (resourcesCS) {
if (resourcesCS instanceof Name) {
return this.#parse(resourcesCS, options);
}
cs = resourcesCS;
break;
}
}
}
warn(`Unrecognized ColorSpace: ${cs.name}`);
return this.gray;
}
}
if (Array.isArray(cs)) {
const mode = xref.fetchIfRef(cs[0]).name;
let params, numComps, baseCS, whitePoint, blackPoint, gamma;
switch (mode) {
case "G":
case "DeviceGray":
return this.gray;
case "RGB":
case "DeviceRGB":
return this.rgb;
case "CMYK":
case "DeviceCMYK":
return this.cmyk;
case "CalGray":
params = xref.fetchIfRef(cs[1]);
whitePoint = params.getArray("WhitePoint");
blackPoint = params.getArray("BlackPoint");
gamma = params.get("Gamma");
return new CalGrayCS(whitePoint, blackPoint, gamma);
case "CalRGB":
params = xref.fetchIfRef(cs[1]);
whitePoint = params.getArray("WhitePoint");
blackPoint = params.getArray("BlackPoint");
gamma = params.getArray("Gamma");
const matrix = params.getArray("Matrix");
return new CalRGBCS(whitePoint, blackPoint, gamma, matrix);
case "ICCBased":
const isRef = cs[1] instanceof Ref;
if (isRef) {
const cachedCS = globalColorSpaceCache.getByRef(cs[1]);
if (cachedCS) {
return cachedCS;
}
}
const stream = xref.fetchIfRef(cs[1]);
const dict = stream.dict;
numComps = dict.get("N");
if (IccColorSpace.isUsable) {
try {
const iccCS = new IccColorSpace(stream.getBytes(), "ICCBased", numComps);
if (isRef) {
globalColorSpaceCache.set(null, cs[1], iccCS);
}
return iccCS;
} catch (ex) {
if (ex instanceof MissingDataException) {
throw ex;
}
warn(`ICCBased color space (${cs[1]}): "${ex}".`);
}
}
const altRaw = dict.getRaw("Alternate");
if (altRaw) {
const altCS = this.#subParse(altRaw, options);
if (altCS.numComps === numComps) {
return altCS;
}
warn("ICCBased color space: Ignoring incorrect /Alternate entry.");
}
if (numComps === 1) {
return this.gray;
} else if (numComps === 3) {
return this.rgb;
} else if (numComps === 4) {
return this.cmyk;
}
break;
case "Pattern":
baseCS = cs[1] || null;
if (baseCS) {
baseCS = this.#subParse(baseCS, options);
}
return new PatternCS(baseCS);
case "I":
case "Indexed":
baseCS = this.#subParse(cs[1], options);
const hiVal = MathClamp(xref.fetchIfRef(cs[2]), 0, 255);
const lookup = xref.fetchIfRef(cs[3]);
return new IndexedCS(baseCS, hiVal, lookup);
case "Separation":
case "DeviceN":
const name = xref.fetchIfRef(cs[1]);
numComps = Array.isArray(name) ? name.length : 1;
baseCS = this.#subParse(cs[2], options);
const tintFn = pdfFunctionFactory.create(cs[3]);
return new AlternateCS(numComps, baseCS, tintFn);
case "Lab":
params = xref.fetchIfRef(cs[1]);
whitePoint = params.getArray("WhitePoint");
blackPoint = params.getArray("BlackPoint");
const range = params.getArray("Range");
return new LabCS(whitePoint, blackPoint, range);
default:
warn(`Unimplemented ColorSpace object: ${mode}`);
return this.gray;
}
}
warn(`Unrecognized ColorSpace object: ${cs}`);
return this.gray;
}
static get gray() {
return shadow(this, "gray", new DeviceGrayCS());
}
static get rgb() {
return shadow(this, "rgb", new DeviceRgbCS());
}
static get rgba() {
return shadow(this, "rgba", new DeviceRgbaCS());
}
static get cmyk() {
if (CmykICCBasedCS.isUsable) {
try {
return shadow(this, "cmyk", new CmykICCBasedCS());
} catch {
warn("CMYK fallback: DeviceCMYK");
}
}
return shadow(this, "cmyk", new DeviceCmykCS());
}
}
;// ./src/core/jpg.js
class JpegError extends BaseException {
constructor(msg) {
super(msg, "JpegError");
}
}
class DNLMarkerError extends BaseException {
constructor(message, scanLines) {
super(message, "DNLMarkerError");
this.scanLines = scanLines;
}
}
class EOIMarkerError extends BaseException {
constructor(msg) {
super(msg, "EOIMarkerError");
}
}
const dctZigZag = new Uint8Array([0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63]);
const dctCos1 = 4017;
const dctSin1 = 799;
const dctCos3 = 3406;
const dctSin3 = 2276;
const dctCos6 = 1567;
const dctSin6 = 3784;
const dctSqrt2 = 5793;
const dctSqrt1d2 = 2896;
function buildHuffmanTable(codeLengths, values) {
let k = 0,
i,
j,
length = 16;
while (length > 0 && !codeLengths[length - 1]) {
length--;
}
const code = [{
children: [],
index: 0
}];
let p = code[0],
q;
for (i = 0; i < length; i++) {
for (j = 0; j < codeLengths[i]; j++) {
p = code.pop();
p.children[p.index] = values[k];
while (p.index > 0) {
p = code.pop();
}
p.index++;
code.push(p);
while (code.length <= i) {
code.push(q = {
children: [],
index: 0
});
p.children[p.index] = q.children;
p = q;
}
k++;
}
if (i + 1 < length) {
code.push(q = {
children: [],
index: 0
});
p.children[p.index] = q.children;
p = q;
}
}
return code[0].children;
}
function getBlockBufferOffset(component, row, col) {
return 64 * ((component.blocksPerLine + 1) * row + col);
}
function decodeScan(data, offset, frame, components, resetInterval, spectralStart, spectralEnd, successivePrev, successive, parseDNLMarker = false) {
const mcusPerLine = frame.mcusPerLine;
const progressive = frame.progressive;
const startOffset = offset;
let bitsData = 0,
bitsCount = 0;
function readBit() {
if (bitsCount > 0) {
bitsCount--;
return bitsData >> bitsCount & 1;
}
bitsData = data[offset++];
if (bitsData === 0xff) {
const nextByte = data[offset++];
if (nextByte) {
if (nextByte === 0xdc && parseDNLMarker) {
offset += 2;
const scanLines = readUint16(data, offset);
offset += 2;
if (scanLines > 0 && scanLines !== frame.scanLines) {
throw new DNLMarkerError("Found DNL marker (0xFFDC) while parsing scan data", scanLines);
}
} else if (nextByte === 0xd9) {
if (parseDNLMarker) {
const maybeScanLines = blockRow * (frame.precision === 8 ? 8 : 0);
if (maybeScanLines > 0 && Math.round(frame.scanLines / maybeScanLines) >= 5) {
throw new DNLMarkerError("Found EOI marker (0xFFD9) while parsing scan data, " + "possibly caused by incorrect `scanLines` parameter", maybeScanLines);
}
}
throw new EOIMarkerError("Found EOI marker (0xFFD9) while parsing scan data");
}
throw new JpegError(`unexpected marker ${(bitsData << 8 | nextByte).toString(16)}`);
}
}
bitsCount = 7;
return bitsData >>> 7;
}
function decodeHuffman(tree) {
let node = tree;
while (true) {
node = node[readBit()];
switch (typeof node) {
case "number":
return node;
case "object":
continue;
}
throw new JpegError("invalid huffman sequence");
}
}
function receive(length) {
let n = 0;
while (length > 0) {
n = n << 1 | readBit();
length--;
}
return n;
}
function receiveAndExtend(length) {
if (length === 1) {
return readBit() === 1 ? 1 : -1;
}
const n = receive(length);
if (n >= 1 << length - 1) {
return n;
}
return n + (-1 << length) + 1;
}
function decodeBaseline(component, blockOffset) {
const t = decodeHuffman(component.huffmanTableDC);
const diff = t === 0 ? 0 : receiveAndExtend(t);
component.blockData[blockOffset] = component.pred += diff;
let k = 1;
while (k < 64) {
const rs = decodeHuffman(component.huffmanTableAC);
const s = rs & 15,
r = rs >> 4;
if (s === 0) {
if (r < 15) {
break;
}
k += 16;
continue;
}
k += r;
const z = dctZigZag[k];
component.blockData[blockOffset + z] = receiveAndExtend(s);
k++;
}
}
function decodeDCFirst(component, blockOffset) {
const t = decodeHuffman(component.huffmanTableDC);
const diff = t === 0 ? 0 : receiveAndExtend(t) << successive;
component.blockData[blockOffset] = component.pred += diff;
}
function decodeDCSuccessive(component, blockOffset) {
component.blockData[blockOffset] |= readBit() << successive;
}
let eobrun = 0;
function decodeACFirst(component, blockOffset) {
if (eobrun > 0) {
eobrun--;
return;
}
let k = spectralStart;
const e = spectralEnd;
while (k <= e) {
const rs = decodeHuffman(component.huffmanTableAC);
const s = rs & 15,
r = rs >> 4;
if (s === 0) {
if (r < 15) {
eobrun = receive(r) + (1 << r) - 1;
break;
}
k += 16;
continue;
}
k += r;
const z = dctZigZag[k];
component.blockData[blockOffset + z] = receiveAndExtend(s) * (1 << successive);
k++;
}
}
let successiveACState = 0,
successiveACNextValue;
function decodeACSuccessive(component, blockOffset) {
let k = spectralStart;
const e = spectralEnd;
let r = 0;
let s;
let rs;
while (k <= e) {
const offsetZ = blockOffset + dctZigZag[k];
const sign = component.blockData[offsetZ] < 0 ? -1 : 1;
switch (successiveACState) {
case 0:
rs = decodeHuffman(component.huffmanTableAC);
s = rs & 15;
r = rs >> 4;
if (s === 0) {
if (r < 15) {
eobrun = receive(r) + (1 << r);
successiveACState = 4;
} else {
r = 16;
successiveACState = 1;
}
} else {
if (s !== 1) {
throw new JpegError("invalid ACn encoding");
}
successiveACNextValue = receiveAndExtend(s);
successiveACState = r ? 2 : 3;
}
continue;
case 1:
case 2:
if (component.blockData[offsetZ]) {
component.blockData[offsetZ] += sign * (readBit() << successive);
} else {
r--;
if (r === 0) {
successiveACState = successiveACState === 2 ? 3 : 0;
}
}
break;
case 3:
if (component.blockData[offsetZ]) {
component.blockData[offsetZ] += sign * (readBit() << successive);
} else {
component.blockData[offsetZ] = successiveACNextValue << successive;
successiveACState = 0;
}
break;
case 4:
if (component.blockData[offsetZ]) {
component.blockData[offsetZ] += sign * (readBit() << successive);
}
break;
}
k++;
}
if (successiveACState === 4) {
eobrun--;
if (eobrun === 0) {
successiveACState = 0;
}
}
}
let blockRow = 0;
function decodeMcu(component, decode, mcu, row, col) {
const mcuRow = mcu / mcusPerLine | 0;
const mcuCol = mcu % mcusPerLine;
blockRow = mcuRow * component.v + row;
const blockCol = mcuCol * component.h + col;
const blockOffset = getBlockBufferOffset(component, blockRow, blockCol);
decode(component, blockOffset);
}
function decodeBlock(component, decode, mcu) {
blockRow = mcu / component.blocksPerLine | 0;
const blockCol = mcu % component.blocksPerLine;
const blockOffset = getBlockBufferOffset(component, blockRow, blockCol);
decode(component, blockOffset);
}
const componentsLength = components.length;
let component, i, j, k, n;
let decodeFn;
if (progressive) {
if (spectralStart === 0) {
decodeFn = successivePrev === 0 ? decodeDCFirst : decodeDCSuccessive;
} else {
decodeFn = successivePrev === 0 ? decodeACFirst : decodeACSuccessive;
}
} else {
decodeFn = decodeBaseline;
}
let mcu = 0,
fileMarker;
const mcuExpected = componentsLength === 1 ? components[0].blocksPerLine * components[0].blocksPerColumn : mcusPerLine * frame.mcusPerColumn;
let h, v;
while (mcu <= mcuExpected) {
const mcuToRead = resetInterval ? Math.min(mcuExpected - mcu, resetInterval) : mcuExpected;
if (mcuToRead > 0) {
for (i = 0; i < componentsLength; i++) {
components[i].pred = 0;
}
eobrun = 0;
if (componentsLength === 1) {
component = components[0];
for (n = 0; n < mcuToRead; n++) {
decodeBlock(component, decodeFn, mcu);
mcu++;
}
} else {
for (n = 0; n < mcuToRead; n++) {
for (i = 0; i < componentsLength; i++) {
component = components[i];
h = component.h;
v = component.v;
for (j = 0; j < v; j++) {
for (k = 0; k < h; k++) {
decodeMcu(component, decodeFn, mcu, j, k);
}
}
}
mcu++;
}
}
}
bitsCount = 0;
fileMarker = findNextFileMarker(data, offset);
if (!fileMarker) {
break;
}
if (fileMarker.invalid) {
const partialMsg = mcuToRead > 0 ? "unexpected" : "excessive";
warn(`decodeScan - ${partialMsg} MCU data, current marker is: ${fileMarker.invalid}`);
offset = fileMarker.offset;
}
if (fileMarker.marker >= 0xffd0 && fileMarker.marker <= 0xffd7) {
offset += 2;
} else {
break;
}
}
return offset - startOffset;
}
function quantizeAndInverse(component, blockBufferOffset, p) {
const qt = component.quantizationTable,
blockData = component.blockData;
let v0, v1, v2, v3, v4, v5, v6, v7;
let p0, p1, p2, p3, p4, p5, p6, p7;
let t;
if (!qt) {
throw new JpegError("missing required Quantization Table.");
}
for (let row = 0; row < 64; row += 8) {
p0 = blockData[blockBufferOffset + row];
p1 = blockData[blockBufferOffset + row + 1];
p2 = blockData[blockBufferOffset + row + 2];
p3 = blockData[blockBufferOffset + row + 3];
p4 = blockData[blockBufferOffset + row + 4];
p5 = blockData[blockBufferOffset + row + 5];
p6 = blockData[blockBufferOffset + row + 6];
p7 = blockData[blockBufferOffset + row + 7];
p0 *= qt[row];
if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) === 0) {
t = dctSqrt2 * p0 + 512 >> 10;
p[row] = t;
p[row + 1] = t;
p[row + 2] = t;
p[row + 3] = t;
p[row + 4] = t;
p[row + 5] = t;
p[row + 6] = t;
p[row + 7] = t;
continue;
}
p1 *= qt[row + 1];
p2 *= qt[row + 2];
p3 *= qt[row + 3];
p4 *= qt[row + 4];
p5 *= qt[row + 5];
p6 *= qt[row + 6];
p7 *= qt[row + 7];
v0 = dctSqrt2 * p0 + 128 >> 8;
v1 = dctSqrt2 * p4 + 128 >> 8;
v2 = p2;
v3 = p6;
v4 = dctSqrt1d2 * (p1 - p7) + 128 >> 8;
v7 = dctSqrt1d2 * (p1 + p7) + 128 >> 8;
v5 = p3 << 4;
v6 = p5 << 4;
v0 = v0 + v1 + 1 >> 1;
v1 = v0 - v1;
t = v2 * dctSin6 + v3 * dctCos6 + 128 >> 8;
v2 = v2 * dctCos6 - v3 * dctSin6 + 128 >> 8;
v3 = t;
v4 = v4 + v6 + 1 >> 1;
v6 = v4 - v6;
v7 = v7 + v5 + 1 >> 1;
v5 = v7 - v5;
v0 = v0 + v3 + 1 >> 1;
v3 = v0 - v3;
v1 = v1 + v2 + 1 >> 1;
v2 = v1 - v2;
t = v4 * dctSin3 + v7 * dctCos3 + 2048 >> 12;
v4 = v4 * dctCos3 - v7 * dctSin3 + 2048 >> 12;
v7 = t;
t = v5 * dctSin1 + v6 * dctCos1 + 2048 >> 12;
v5 = v5 * dctCos1 - v6 * dctSin1 + 2048 >> 12;
v6 = t;
p[row] = v0 + v7;
p[row + 7] = v0 - v7;
p[row + 1] = v1 + v6;
p[row + 6] = v1 - v6;
p[row + 2] = v2 + v5;
p[row + 5] = v2 - v5;
p[row + 3] = v3 + v4;
p[row + 4] = v3 - v4;
}
for (let col = 0; col < 8; ++col) {
p0 = p[col];
p1 = p[col + 8];
p2 = p[col + 16];
p3 = p[col + 24];
p4 = p[col + 32];
p5 = p[col + 40];
p6 = p[col + 48];
p7 = p[col + 56];
if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) === 0) {
t = dctSqrt2 * p0 + 8192 >> 14;
if (t < -2040) {
t = 0;
} else if (t >= 2024) {
t = 255;
} else {
t = t + 2056 >> 4;
}
blockData[blockBufferOffset + col] = t;
blockData[blockBufferOffset + col + 8] = t;
blockData[blockBufferOffset + col + 16] = t;
blockData[blockBufferOffset + col + 24] = t;
blockData[blockBufferOffset + col + 32] = t;
blockData[blockBufferOffset + col + 40] = t;
blockData[blockBufferOffset + col + 48] = t;
blockData[blockBufferOffset + col + 56] = t;
continue;
}
v0 = dctSqrt2 * p0 + 2048 >> 12;
v1 = dctSqrt2 * p4 + 2048 >> 12;
v2 = p2;
v3 = p6;
v4 = dctSqrt1d2 * (p1 - p7) + 2048 >> 12;
v7 = dctSqrt1d2 * (p1 + p7) + 2048 >> 12;
v5 = p3;
v6 = p5;
v0 = (v0 + v1 + 1 >> 1) + 4112;
v1 = v0 - v1;
t = v2 * dctSin6 + v3 * dctCos6 + 2048 >> 12;
v2 = v2 * dctCos6 - v3 * dctSin6 + 2048 >> 12;
v3 = t;
v4 = v4 + v6 + 1 >> 1;
v6 = v4 - v6;
v7 = v7 + v5 + 1 >> 1;
v5 = v7 - v5;
v0 = v0 + v3 + 1 >> 1;
v3 = v0 - v3;
v1 = v1 + v2 + 1 >> 1;
v2 = v1 - v2;
t = v4 * dctSin3 + v7 * dctCos3 + 2048 >> 12;
v4 = v4 * dctCos3 - v7 * dctSin3 + 2048 >> 12;
v7 = t;
t = v5 * dctSin1 + v6 * dctCos1 + 2048 >> 12;
v5 = v5 * dctCos1 - v6 * dctSin1 + 2048 >> 12;
v6 = t;
p0 = v0 + v7;
p7 = v0 - v7;
p1 = v1 + v6;
p6 = v1 - v6;
p2 = v2 + v5;
p5 = v2 - v5;
p3 = v3 + v4;
p4 = v3 - v4;
if (p0 < 16) {
p0 = 0;
} else if (p0 >= 4080) {
p0 = 255;
} else {
p0 >>= 4;
}
if (p1 < 16) {
p1 = 0;
} else if (p1 >= 4080) {
p1 = 255;
} else {
p1 >>= 4;
}
if (p2 < 16) {
p2 = 0;
} else if (p2 >= 4080) {
p2 = 255;
} else {
p2 >>= 4;
}
if (p3 < 16) {
p3 = 0;
} else if (p3 >= 4080) {
p3 = 255;
} else {
p3 >>= 4;
}
if (p4 < 16) {
p4 = 0;
} else if (p4 >= 4080) {
p4 = 255;
} else {
p4 >>= 4;
}
if (p5 < 16) {
p5 = 0;
} else if (p5 >= 4080) {
p5 = 255;
} else {
p5 >>= 4;
}
if (p6 < 16) {
p6 = 0;
} else if (p6 >= 4080) {
p6 = 255;
} else {
p6 >>= 4;
}
if (p7 < 16) {
p7 = 0;
} else if (p7 >= 4080) {
p7 = 255;
} else {
p7 >>= 4;
}
blockData[blockBufferOffset + col] = p0;
blockData[blockBufferOffset + col + 8] = p1;
blockData[blockBufferOffset + col + 16] = p2;
blockData[blockBufferOffset + col + 24] = p3;
blockData[blockBufferOffset + col + 32] = p4;
blockData[blockBufferOffset + col + 40] = p5;
blockData[blockBufferOffset + col + 48] = p6;
blockData[blockBufferOffset + col + 56] = p7;
}
}
function buildComponentData(frame, component) {
const blocksPerLine = component.blocksPerLine;
const blocksPerColumn = component.blocksPerColumn;
const computationBuffer = new Int16Array(64);
for (let blockRow = 0; blockRow < blocksPerColumn; blockRow++) {
for (let blockCol = 0; blockCol < blocksPerLine; blockCol++) {
const offset = getBlockBufferOffset(component, blockRow, blockCol);
quantizeAndInverse(component, offset, computationBuffer);
}
}
return component.blockData;
}
function findNextFileMarker(data, currentPos, startPos = currentPos) {
const maxPos = data.length - 1;
let newPos = startPos < currentPos ? startPos : currentPos;
if (currentPos >= maxPos) {
return null;
}
const currentMarker = readUint16(data, currentPos);
if (currentMarker >= 0xffc0 && currentMarker <= 0xfffe) {
return {
invalid: null,
marker: currentMarker,
offset: currentPos
};
}
let newMarker = readUint16(data, newPos);
while (!(newMarker >= 0xffc0 && newMarker <= 0xfffe)) {
if (++newPos >= maxPos) {
return null;
}
newMarker = readUint16(data, newPos);
}
return {
invalid: currentMarker.toString(16),
marker: newMarker,
offset: newPos
};
}
function prepareComponents(frame) {
const mcusPerLine = Math.ceil(frame.samplesPerLine / 8 / frame.maxH);
const mcusPerColumn = Math.ceil(frame.scanLines / 8 / frame.maxV);
for (const component of frame.components) {
const blocksPerLine = Math.ceil(Math.ceil(frame.samplesPerLine / 8) * component.h / frame.maxH);
const blocksPerColumn = Math.ceil(Math.ceil(frame.scanLines / 8) * component.v / frame.maxV);
const blocksPerLineForMcu = mcusPerLine * component.h;
const blocksPerColumnForMcu = mcusPerColumn * component.v;
const blocksBufferSize = 64 * blocksPerColumnForMcu * (blocksPerLineForMcu + 1);
component.blockData = new Int16Array(blocksBufferSize);
component.blocksPerLine = blocksPerLine;
component.blocksPerColumn = blocksPerColumn;
}
frame.mcusPerLine = mcusPerLine;
frame.mcusPerColumn = mcusPerColumn;
}
function readDataBlock(data, offset) {
const length = readUint16(data, offset);
offset += 2;
let endOffset = offset + length - 2;
const fileMarker = findNextFileMarker(data, endOffset, offset);
if (fileMarker?.invalid) {
warn("readDataBlock - incorrect length, current marker is: " + fileMarker.invalid);
endOffset = fileMarker.offset;
}
const array = data.subarray(offset, endOffset);
return {
appData: array,
oldOffset: offset,
newOffset: offset + array.length
};
}
function skipData(data, offset) {
const length = readUint16(data, offset);
offset += 2;
const endOffset = offset + length - 2;
const fileMarker = findNextFileMarker(data, endOffset, offset);
if (fileMarker?.invalid) {
return fileMarker.offset;
}
return endOffset;
}
class JpegImage {
constructor({
decodeTransform = null,
colorTransform = -1
} = {}) {
this._decodeTransform = decodeTransform;
this._colorTransform = colorTransform;
}
static canUseImageDecoder(data, colorTransform = -1) {
let exifOffsets = null;
let offset = 0;
let numComponents = null;
let fileMarker = readUint16(data, offset);
offset += 2;
if (fileMarker !== 0xffd8) {
throw new JpegError("SOI not found");
}
fileMarker = readUint16(data, offset);
offset += 2;
markerLoop: while (fileMarker !== 0xffd9) {
switch (fileMarker) {
case 0xffe1:
const {
appData,
oldOffset,
newOffset
} = readDataBlock(data, offset);
offset = newOffset;
if (appData[0] === 0x45 && appData[1] === 0x78 && appData[2] === 0x69 && appData[3] === 0x66 && appData[4] === 0 && appData[5] === 0) {
if (exifOffsets) {
throw new JpegError("Duplicate EXIF-blocks found.");
}
exifOffsets = {
exifStart: oldOffset + 6,
exifEnd: newOffset
};
}
fileMarker = readUint16(data, offset);
offset += 2;
continue;
case 0xffc0:
case 0xffc1:
case 0xffc2:
numComponents = data[offset + (2 + 1 + 2 + 2)];
break markerLoop;
case 0xffff:
if (data[offset] !== 0xff) {
offset--;
}
break;
}
offset = skipData(data, offset);
fileMarker = readUint16(data, offset);
offset += 2;
}
if (numComponents === 4) {
return null;
}
if (numComponents === 3 && colorTransform === 0) {
return null;
}
return exifOffsets || {};
}
parse(data, {
dnlScanLines = null
} = {}) {
let offset = 0;
let jfif = null;
let adobe = null;
let frame, resetInterval;
let numSOSMarkers = 0;
const quantizationTables = [];
const huffmanTablesAC = [],
huffmanTablesDC = [];
let fileMarker = readUint16(data, offset);
offset += 2;
if (fileMarker !== 0xffd8) {
throw new JpegError("SOI not found");
}
fileMarker = readUint16(data, offset);
offset += 2;
markerLoop: while (fileMarker !== 0xffd9) {
let i, j, l;
switch (fileMarker) {
case 0xffe0:
case 0xffe1:
case 0xffe2:
case 0xffe3:
case 0xffe4:
case 0xffe5:
case 0xffe6:
case 0xffe7:
case 0xffe8:
case 0xffe9:
case 0xffea:
case 0xffeb:
case 0xffec:
case 0xffed:
case 0xffee:
case 0xffef:
case 0xfffe:
const {
appData,
newOffset
} = readDataBlock(data, offset);
offset = newOffset;
if (fileMarker === 0xffe0) {
if (appData[0] === 0x4a && appData[1] === 0x46 && appData[2] === 0x49 && appData[3] === 0x46 && appData[4] === 0) {
jfif = {
version: {
major: appData[5],
minor: appData[6]
},
densityUnits: appData[7],
xDensity: appData[8] << 8 | appData[9],
yDensity: appData[10] << 8 | appData[11],
thumbWidth: appData[12],
thumbHeight: appData[13],
thumbData: appData.subarray(14, 14 + 3 * appData[12] * appData[13])
};
}
}
if (fileMarker === 0xffee) {
if (appData[0] === 0x41 && appData[1] === 0x64 && appData[2] === 0x6f && appData[3] === 0x62 && appData[4] === 0x65) {
adobe = {
version: appData[5] << 8 | appData[6],
flags0: appData[7] << 8 | appData[8],
flags1: appData[9] << 8 | appData[10],
transformCode: appData[11]
};
}
}
break;
case 0xffdb:
const quantizationTablesLength = readUint16(data, offset);
offset += 2;
const quantizationTablesEnd = quantizationTablesLength + offset - 2;
let z;
while (offset < quantizationTablesEnd) {
const quantizationTableSpec = data[offset++];
const tableData = new Uint16Array(64);
if (quantizationTableSpec >> 4 === 0) {
for (j = 0; j < 64; j++) {
z = dctZigZag[j];
tableData[z] = data[offset++];
}
} else if (quantizationTableSpec >> 4 === 1) {
for (j = 0; j < 64; j++) {
z = dctZigZag[j];
tableData[z] = readUint16(data, offset);
offset += 2;
}
} else {
throw new JpegError("DQT - invalid table spec");
}
quantizationTables[quantizationTableSpec & 15] = tableData;
}
break;
case 0xffc0:
case 0xffc1:
case 0xffc2:
if (frame) {
throw new JpegError("Only single frame JPEGs supported");
}
offset += 2;
frame = {};
frame.extended = fileMarker === 0xffc1;
frame.progressive = fileMarker === 0xffc2;
frame.precision = data[offset++];
const sofScanLines = readUint16(data, offset);
offset += 2;
frame.scanLines = dnlScanLines || sofScanLines;
frame.samplesPerLine = readUint16(data, offset);
offset += 2;
frame.components = [];
frame.componentIds = {};
const componentsCount = data[offset++];
let maxH = 0,
maxV = 0;
for (i = 0; i < componentsCount; i++) {
const componentId = data[offset];
const h = data[offset + 1] >> 4;
const v = data[offset + 1] & 15;
if (maxH < h) {
maxH = h;
}
if (maxV < v) {
maxV = v;
}
const qId = data[offset + 2];
l = frame.components.push({
h,
v,
quantizationId: qId,
quantizationTable: null
});
frame.componentIds[componentId] = l - 1;
offset += 3;
}
frame.maxH = maxH;
frame.maxV = maxV;
prepareComponents(frame);
break;
case 0xffc4:
const huffmanLength = readUint16(data, offset);
offset += 2;
for (i = 2; i < huffmanLength;) {
const huffmanTableSpec = data[offset++];
const codeLengths = new Uint8Array(16);
let codeLengthSum = 0;
for (j = 0; j < 16; j++, offset++) {
codeLengthSum += codeLengths[j] = data[offset];
}
const huffmanValues = new Uint8Array(codeLengthSum);
for (j = 0; j < codeLengthSum; j++, offset++) {
huffmanValues[j] = data[offset];
}
i += 17 + codeLengthSum;
(huffmanTableSpec >> 4 === 0 ? huffmanTablesDC : huffmanTablesAC)[huffmanTableSpec & 15] = buildHuffmanTable(codeLengths, huffmanValues);
}
break;
case 0xffdd:
offset += 2;
resetInterval = readUint16(data, offset);
offset += 2;
break;
case 0xffda:
const parseDNLMarker = ++numSOSMarkers === 1 && !dnlScanLines;
offset += 2;
const selectorsCount = data[offset++],
components = [];
for (i = 0; i < selectorsCount; i++) {
const index = data[offset++];
const componentIndex = frame.componentIds[index];
const component = frame.components[componentIndex];
component.index = index;
const tableSpec = data[offset++];
component.huffmanTableDC = huffmanTablesDC[tableSpec >> 4];
component.huffmanTableAC = huffmanTablesAC[tableSpec & 15];
components.push(component);
}
const spectralStart = data[offset++],
spectralEnd = data[offset++],
successiveApproximation = data[offset++];
try {
const processed = decodeScan(data, offset, frame, components, resetInterval, spectralStart, spectralEnd, successiveApproximation >> 4, successiveApproximation & 15, parseDNLMarker);
offset += processed;
} catch (ex) {
if (ex instanceof DNLMarkerError) {
warn(`${ex.message} -- attempting to re-parse the JPEG image.`);
return this.parse(data, {
dnlScanLines: ex.scanLines
});
} else if (ex instanceof EOIMarkerError) {
warn(`${ex.message} -- ignoring the rest of the image data.`);
break markerLoop;
}
throw ex;
}
break;
case 0xffdc:
offset += 4;
break;
case 0xffff:
if (data[offset] !== 0xff) {
offset--;
}
break;
default:
const nextFileMarker = findNextFileMarker(data, offset - 2, offset - 3);
if (nextFileMarker?.invalid) {
warn("JpegImage.parse - unexpected data, current marker is: " + nextFileMarker.invalid);
offset = nextFileMarker.offset;
break;
}
if (!nextFileMarker || offset >= data.length - 1) {
warn("JpegImage.parse - reached the end of the image data " + "without finding an EOI marker (0xFFD9).");
break markerLoop;
}
throw new JpegError("JpegImage.parse - unknown marker: " + fileMarker.toString(16));
}
fileMarker = readUint16(data, offset);
offset += 2;
}
if (!frame) {
throw new JpegError("JpegImage.parse - no frame data found.");
}
this.width = frame.samplesPerLine;
this.height = frame.scanLines;
this.jfif = jfif;
this.adobe = adobe;
this.components = [];
for (const component of frame.components) {
const quantizationTable = quantizationTables[component.quantizationId];
if (quantizationTable) {
component.quantizationTable = quantizationTable;
}
this.components.push({
index: component.index,
output: buildComponentData(frame, component),
scaleX: component.h / frame.maxH,
scaleY: component.v / frame.maxV,
blocksPerLine: component.blocksPerLine,
blocksPerColumn: component.blocksPerColumn
});
}
this.numComponents = this.components.length;
return undefined;
}
_getLinearizedBlockData(width, height, isSourcePDF = false) {
const scaleX = this.width / width,
scaleY = this.height / height;
let component, componentScaleX, componentScaleY, blocksPerScanline;
let x, y, i, j, k;
let index;
let offset = 0;
let output;
const numComponents = this.components.length;
const dataLength = width * height * numComponents;
const data = new Uint8ClampedArray(dataLength);
const xScaleBlockOffset = new Uint32Array(width);
const mask3LSB = 0xfffffff8;
let lastComponentScaleX;
for (i = 0; i < numComponents; i++) {
component = this.components[i];
componentScaleX = component.scaleX * scaleX;
componentScaleY = component.scaleY * scaleY;
offset = i;
output = component.output;
blocksPerScanline = component.blocksPerLine + 1 << 3;
if (componentScaleX !== lastComponentScaleX) {
for (x = 0; x < width; x++) {
j = 0 | x * componentScaleX;
xScaleBlockOffset[x] = (j & mask3LSB) << 3 | j & 7;
}
lastComponentScaleX = componentScaleX;
}
for (y = 0; y < height; y++) {
j = 0 | y * componentScaleY;
index = blocksPerScanline * (j & mask3LSB) | (j & 7) << 3;
for (x = 0; x < width; x++) {
data[offset] = output[index + xScaleBlockOffset[x]];
offset += numComponents;
}
}
}
let transform = this._decodeTransform;
if (!isSourcePDF && numComponents === 4 && !transform) {
transform = new Int32Array([-256, 255, -256, 255, -256, 255, -256, 255]);
}
if (transform) {
for (i = 0; i < dataLength;) {
for (j = 0, k = 0; j < numComponents; j++, i++, k += 2) {
data[i] = (data[i] * transform[k] >> 8) + transform[k + 1];
}
}
}
return data;
}
get _isColorConversionNeeded() {
if (this.adobe) {
return !!this.adobe.transformCode;
}
if (this.numComponents === 3) {
if (this._colorTransform === 0) {
return false;
} else if (this.components[0].index === 0x52 && this.components[1].index === 0x47 && this.components[2].index === 0x42) {
return false;
}
return true;
}
if (this._colorTransform === 1) {
return true;
}
return false;
}
_convertYccToRgb(data) {
let Y, Cb, Cr;
for (let i = 0, length = data.length; i < length; i += 3) {
Y = data[i];
Cb = data[i + 1];
Cr = data[i + 2];
data[i] = Y - 179.456 + 1.402 * Cr;
data[i + 1] = Y + 135.459 - 0.344 * Cb - 0.714 * Cr;
data[i + 2] = Y - 226.816 + 1.772 * Cb;
}
return data;
}
_convertYccToRgba(data, out) {
for (let i = 0, j = 0, length = data.length; i < length; i += 3, j += 4) {
const Y = data[i];
const Cb = data[i + 1];
const Cr = data[i + 2];
out[j] = Y - 179.456 + 1.402 * Cr;
out[j + 1] = Y + 135.459 - 0.344 * Cb - 0.714 * Cr;
out[j + 2] = Y - 226.816 + 1.772 * Cb;
out[j + 3] = 255;
}
return out;
}
_convertYcckToRgb(data) {
this._convertYcckToCmyk(data);
return this._convertCmykToRgb(data);
}
_convertYcckToRgba(data) {
this._convertYcckToCmyk(data);
return this._convertCmykToRgba(data);
}
_convertYcckToCmyk(data) {
let Y, Cb, Cr;
for (let i = 0, length = data.length; i < length; i += 4) {
Y = data[i];
Cb = data[i + 1];
Cr = data[i + 2];
data[i] = 434.456 - Y - 1.402 * Cr;
data[i + 1] = 119.541 - Y + 0.344 * Cb + 0.714 * Cr;
data[i + 2] = 481.816 - Y - 1.772 * Cb;
}
return data;
}
_convertCmykToRgb(data) {
const count = data.length / 4;
ColorSpaceUtils.cmyk.getRgbBuffer(data, 0, count, data, 0, 8, 0);
return data.subarray(0, count * 3);
}
_convertCmykToRgba(data) {
ColorSpaceUtils.cmyk.getRgbBuffer(data, 0, data.length / 4, data, 0, 8, 1);
if (ColorSpaceUtils.cmyk instanceof DeviceCmykCS) {
for (let i = 3, ii = data.length; i < ii; i += 4) {
data[i] = 255;
}
}
return data;
}
getData({
width,
height,
forceRGBA = false,
forceRGB = false,
isSourcePDF = false
}) {
if (this.numComponents > 4) {
throw new JpegError("Unsupported color mode");
}
const data = this._getLinearizedBlockData(width, height, isSourcePDF);
if (this.numComponents === 1 && (forceRGBA || forceRGB)) {
const len = data.length * (forceRGBA ? 4 : 3);
const rgbaData = new Uint8ClampedArray(len);
let offset = 0;
if (forceRGBA) {
grayToRGBA(data, new Uint32Array(rgbaData.buffer));
} else {
for (const grayColor of data) {
rgbaData[offset++] = grayColor;
rgbaData[offset++] = grayColor;
rgbaData[offset++] = grayColor;
}
}
return rgbaData;
} else if (this.numComponents === 3 && this._isColorConversionNeeded) {
if (forceRGBA) {
const rgbaData = new Uint8ClampedArray(data.length / 3 * 4);
return this._convertYccToRgba(data, rgbaData);
}
return this._convertYccToRgb(data);
} else if (this.numComponents === 4) {
if (this._isColorConversionNeeded) {
if (forceRGBA) {
return this._convertYcckToRgba(data);
}
if (forceRGB) {
return this._convertYcckToRgb(data);
}
return this._convertYcckToCmyk(data);
} else if (forceRGBA) {
return this._convertCmykToRgba(data);
} else if (forceRGB) {
return this._convertCmykToRgb(data);
}
}
return data;
}
}
;// ./src/core/jpeg_stream.js
class JpegStream extends DecodeStream {
static #isImageDecoderSupported = FeatureTest.isImageDecoderSupported;
constructor(stream, maybeLength, params) {
super(maybeLength);
this.stream = stream;
this.dict = stream.dict;
this.maybeLength = maybeLength;
this.params = params;
}
static get canUseImageDecoder() {
return shadow(this, "canUseImageDecoder", this.#isImageDecoderSupported ? ImageDecoder.isTypeSupported("image/jpeg") : Promise.resolve(false));
}
static setOptions({
isImageDecoderSupported = false
}) {
this.#isImageDecoderSupported = isImageDecoderSupported;
}
get bytes() {
return shadow(this, "bytes", this.stream.getBytes(this.maybeLength));
}
ensureBuffer(requested) {}
readBlock() {
this.decodeImage();
}
get jpegOptions() {
const jpegOptions = {
decodeTransform: undefined,
colorTransform: undefined
};
const decodeArr = this.dict.getArray("D", "Decode");
if ((this.forceRGBA || this.forceRGB) && Array.isArray(decodeArr)) {
const bitsPerComponent = this.dict.get("BPC", "BitsPerComponent") || 8;
const decodeArrLength = decodeArr.length;
const transform = new Int32Array(decodeArrLength);
let transformNeeded = false;
const maxValue = (1 << bitsPerComponent) - 1;
for (let i = 0; i < decodeArrLength; i += 2) {
transform[i] = (decodeArr[i + 1] - decodeArr[i]) * 256 | 0;
transform[i + 1] = decodeArr[i] * maxValue | 0;
if (transform[i] !== 256 || transform[i + 1] !== 0) {
transformNeeded = true;
}
}
if (transformNeeded) {
jpegOptions.decodeTransform = transform;
}
}
if (this.params instanceof Dict) {
const colorTransform = this.params.get("ColorTransform");
if (Number.isInteger(colorTransform)) {
jpegOptions.colorTransform = colorTransform;
}
}
return shadow(this, "jpegOptions", jpegOptions);
}
#skipUselessBytes(data) {
for (let i = 0, ii = data.length - 1; i < ii; i++) {
if (data[i] === 0xff && data[i + 1] === 0xd8) {
if (i > 0) {
data = data.subarray(i);
}
break;
}
}
return data;
}
decodeImage(bytes) {
if (this.eof) {
return this.buffer;
}
bytes = this.#skipUselessBytes(bytes || this.bytes);
const jpegImage = new JpegImage(this.jpegOptions);
jpegImage.parse(bytes);
const data = jpegImage.getData({
width: this.drawWidth,
height: this.drawHeight,
forceRGBA: this.forceRGBA,
forceRGB: this.forceRGB,
isSourcePDF: true
});
this.buffer = data;
this.bufferLength = data.length;
this.eof = true;
return this.buffer;
}
get canAsyncDecodeImageFromBuffer() {
return this.stream.isAsync;
}
async getTransferableImage() {
if (!(await JpegStream.canUseImageDecoder)) {
return null;
}
const jpegOptions = this.jpegOptions;
if (jpegOptions.decodeTransform) {
return null;
}
let decoder;
try {
const bytes = this.canAsyncDecodeImageFromBuffer && (await this.stream.asyncGetBytes()) || this.bytes;
if (!bytes) {
return null;
}
let data = this.#skipUselessBytes(bytes);
const useImageDecoder = JpegImage.canUseImageDecoder(data, jpegOptions.colorTransform);
if (!useImageDecoder) {
return null;
}
if (useImageDecoder.exifStart) {
data = data.slice();
data.fill(0x00, useImageDecoder.exifStart, useImageDecoder.exifEnd);
}
decoder = new ImageDecoder({
data,
type: "image/jpeg",
preferAnimation: false
});
return (await decoder.decode()).image;
} catch (reason) {
warn(`getTransferableImage - failed: "${reason}".`);
return null;
} finally {
decoder?.close();
}
}
}
;// ./external/openjpeg/openjpeg.js
var OpenJPEG = (() => {
var _scriptName = import.meta.url;
return async function (moduleArg = {}) {
var moduleRtn;
var Module = moduleArg;
var readyPromiseResolve, readyPromiseReject;
var readyPromise = new Promise((resolve, reject) => {
readyPromiseResolve = resolve;
readyPromiseReject = reject;
});
var ENVIRONMENT_IS_WEB = true;
var ENVIRONMENT_IS_WORKER = false;
var moduleOverrides = Object.assign({}, Module);
var arguments_ = [];
var thisProgram = "./this.program";
var quit_ = (status, toThrow) => {
throw toThrow;
};
var scriptDirectory = "";
function locateFile(path) {
if (Module["locateFile"]) {
return Module["locateFile"](path, scriptDirectory);
}
return scriptDirectory + path;
}
var readAsync, readBinary;
if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) {
if (ENVIRONMENT_IS_WORKER) {
scriptDirectory = self.location.href;
} else if (typeof document != "undefined" && document.currentScript) {
scriptDirectory = document.currentScript.src;
}
if (_scriptName) {
scriptDirectory = _scriptName;
}
if (scriptDirectory.startsWith("blob:")) {
scriptDirectory = "";
} else {
scriptDirectory = scriptDirectory.slice(0, scriptDirectory.replace(/[?#].*/, "").lastIndexOf("/") + 1);
}
readAsync = async url => {
var response = await fetch(url, {
credentials: "same-origin"
});
if (response.ok) {
return response.arrayBuffer();
}
throw new Error(response.status + " : " + response.url);
};
} else {}
var out = Module["print"] || console.log.bind(console);
var err = Module["printErr"] || console.error.bind(console);
Object.assign(Module, moduleOverrides);
moduleOverrides = null;
if (Module["arguments"]) arguments_ = Module["arguments"];
if (Module["thisProgram"]) thisProgram = Module["thisProgram"];
var wasmBinary = Module["wasmBinary"];
var wasmMemory;
var ABORT = false;
var EXITSTATUS;
var HEAP8, HEAPU8, HEAP16, HEAPU16, HEAP32, HEAPU32, HEAPF32, HEAP64, HEAPU64, HEAPF64;
var runtimeInitialized = false;
function updateMemoryViews() {
var b = wasmMemory.buffer;
Module["HEAP8"] = HEAP8 = new Int8Array(b);
Module["HEAP16"] = HEAP16 = new Int16Array(b);
Module["HEAPU8"] = HEAPU8 = new Uint8Array(b);
Module["HEAPU16"] = HEAPU16 = new Uint16Array(b);
Module["HEAP32"] = HEAP32 = new Int32Array(b);
Module["HEAPU32"] = HEAPU32 = new Uint32Array(b);
Module["HEAPF32"] = HEAPF32 = new Float32Array(b);
Module["HEAPF64"] = HEAPF64 = new Float64Array(b);
Module["HEAP64"] = HEAP64 = new BigInt64Array(b);
Module["HEAPU64"] = HEAPU64 = new BigUint64Array(b);
}
function preRun() {
if (Module["preRun"]) {
if (typeof Module["preRun"] == "function") Module["preRun"] = [Module["preRun"]];
while (Module["preRun"].length) {
addOnPreRun(Module["preRun"].shift());
}
}
callRuntimeCallbacks(onPreRuns);
}
function initRuntime() {
runtimeInitialized = true;
wasmExports["t"]();
}
function postRun() {
if (Module["postRun"]) {
if (typeof Module["postRun"] == "function") Module["postRun"] = [Module["postRun"]];
while (Module["postRun"].length) {
addOnPostRun(Module["postRun"].shift());
}
}
callRuntimeCallbacks(onPostRuns);
}
var runDependencies = 0;
var dependenciesFulfilled = null;
function addRunDependency(id) {
runDependencies++;
Module["monitorRunDependencies"]?.(runDependencies);
}
function removeRunDependency(id) {
runDependencies--;
Module["monitorRunDependencies"]?.(runDependencies);
if (runDependencies == 0) {
if (dependenciesFulfilled) {
var callback = dependenciesFulfilled;
dependenciesFulfilled = null;
callback();
}
}
}
function abort(what) {
Module["onAbort"]?.(what);
what = "Aborted(" + what + ")";
err(what);
ABORT = true;
what += ". Build with -sASSERTIONS for more info.";
var e = new WebAssembly.RuntimeError(what);
readyPromiseReject(e);
throw e;
}
var wasmBinaryFile;
function findWasmBinary() {
if (Module["locateFile"]) {
return locateFile("openjpeg.wasm");
}
return new URL("openjpeg.wasm", import.meta.url).href;
}
function getBinarySync(file) {
if (file == wasmBinaryFile && wasmBinary) {
return new Uint8Array(wasmBinary);
}
if (readBinary) {
return readBinary(file);
}
throw "both async and sync fetching of the wasm failed";
}
async function getWasmBinary(binaryFile) {
if (!wasmBinary) {
try {
var response = await readAsync(binaryFile);
return new Uint8Array(response);
} catch {}
}
return getBinarySync(binaryFile);
}
async function instantiateArrayBuffer(binaryFile, imports) {
try {
var binary = await getWasmBinary(binaryFile);
var instance = await WebAssembly.instantiate(binary, imports);
return instance;
} catch (reason) {
err(`failed to asynchronously prepare wasm: ${reason}`);
abort(reason);
}
}
async function instantiateAsync(binary, binaryFile, imports) {
if (!binary && typeof WebAssembly.instantiateStreaming == "function") {
try {
var response = fetch(binaryFile, {
credentials: "same-origin"
});
var instantiationResult = await WebAssembly.instantiateStreaming(response, imports);
return instantiationResult;
} catch (reason) {
err(`wasm streaming compile failed: ${reason}`);
err("falling back to ArrayBuffer instantiation");
}
}
return instantiateArrayBuffer(binaryFile, imports);
}
function getWasmImports() {
return {
a: wasmImports
};
}
async function createWasm() {
function receiveInstance(instance, module) {
wasmExports = instance.exports;
wasmMemory = wasmExports["s"];
updateMemoryViews();
removeRunDependency("wasm-instantiate");
return wasmExports;
}
addRunDependency("wasm-instantiate");
function receiveInstantiationResult(result) {
return receiveInstance(result["instance"]);
}
var info = getWasmImports();
if (Module["instantiateWasm"]) {
return new Promise((resolve, reject) => {
Module["instantiateWasm"](info, (mod, inst) => {
receiveInstance(mod, inst);
resolve(mod.exports);
});
});
}
wasmBinaryFile ??= findWasmBinary();
try {
var result = await instantiateAsync(wasmBinary, wasmBinaryFile, info);
var exports = receiveInstantiationResult(result);
return exports;
} catch (e) {
readyPromiseReject(e);
return Promise.reject(e);
}
}
class ExitStatus {
name = "ExitStatus";
constructor(status) {
this.message = `Program terminated with exit(${status})`;
this.status = status;
}
}
var callRuntimeCallbacks = callbacks => {
while (callbacks.length > 0) {
callbacks.shift()(Module);
}
};
var onPostRuns = [];
var addOnPostRun = cb => onPostRuns.unshift(cb);
var onPreRuns = [];
var addOnPreRun = cb => onPreRuns.unshift(cb);
var noExitRuntime = Module["noExitRuntime"] || true;
var __abort_js = () => abort("");
var runtimeKeepaliveCounter = 0;
var __emscripten_runtime_keepalive_clear = () => {
noExitRuntime = false;
runtimeKeepaliveCounter = 0;
};
var timers = {};
var handleException = e => {
if (e instanceof ExitStatus || e == "unwind") {
return EXITSTATUS;
}
quit_(1, e);
};
var keepRuntimeAlive = () => noExitRuntime || runtimeKeepaliveCounter > 0;
var _proc_exit = code => {
EXITSTATUS = code;
if (!keepRuntimeAlive()) {
Module["onExit"]?.(code);
ABORT = true;
}
quit_(code, new ExitStatus(code));
};
var exitJS = (status, implicit) => {
EXITSTATUS = status;
_proc_exit(status);
};
var _exit = exitJS;
var maybeExit = () => {
if (!keepRuntimeAlive()) {
try {
_exit(EXITSTATUS);
} catch (e) {
handleException(e);
}
}
};
var callUserCallback = func => {
if (ABORT) {
return;
}
try {
func();
maybeExit();
} catch (e) {
handleException(e);
}
};
var _emscripten_get_now = () => performance.now();
var __setitimer_js = (which, timeout_ms) => {
if (timers[which]) {
clearTimeout(timers[which].id);
delete timers[which];
}
if (!timeout_ms) return 0;
var id = setTimeout(() => {
delete timers[which];
callUserCallback(() => __emscripten_timeout(which, _emscripten_get_now()));
}, timeout_ms);
timers[which] = {
id,
timeout_ms
};
return 0;
};
function _copy_pixels_1(compG_ptr, nb_pixels) {
compG_ptr >>= 2;
const imageData = Module.imageData = new Uint8ClampedArray(nb_pixels);
const compG = HEAP32.subarray(compG_ptr, compG_ptr + nb_pixels);
imageData.set(compG);
}
function _copy_pixels_3(compR_ptr, compG_ptr, compB_ptr, nb_pixels) {
compR_ptr >>= 2;
compG_ptr >>= 2;
compB_ptr >>= 2;
const imageData = Module.imageData = new Uint8ClampedArray(nb_pixels * 3);
const compR = HEAP32.subarray(compR_ptr, compR_ptr + nb_pixels);
const compG = HEAP32.subarray(compG_ptr, compG_ptr + nb_pixels);
const compB = HEAP32.subarray(compB_ptr, compB_ptr + nb_pixels);
for (let i = 0; i < nb_pixels; i++) {
imageData[3 * i] = compR[i];
imageData[3 * i + 1] = compG[i];
imageData[3 * i + 2] = compB[i];
}
}
function _copy_pixels_4(compR_ptr, compG_ptr, compB_ptr, compA_ptr, nb_pixels) {
compR_ptr >>= 2;
compG_ptr >>= 2;
compB_ptr >>= 2;
compA_ptr >>= 2;
const imageData = Module.imageData = new Uint8ClampedArray(nb_pixels * 4);
const compR = HEAP32.subarray(compR_ptr, compR_ptr + nb_pixels);
const compG = HEAP32.subarray(compG_ptr, compG_ptr + nb_pixels);
const compB = HEAP32.subarray(compB_ptr, compB_ptr + nb_pixels);
const compA = HEAP32.subarray(compA_ptr, compA_ptr + nb_pixels);
for (let i = 0; i < nb_pixels; i++) {
imageData[4 * i] = compR[i];
imageData[4 * i + 1] = compG[i];
imageData[4 * i + 2] = compB[i];
imageData[4 * i + 3] = compA[i];
}
}
var getHeapMax = () => 2147483648;
var alignMemory = (size, alignment) => Math.ceil(size / alignment) * alignment;
var growMemory = size => {
var b = wasmMemory.buffer;
var pages = (size - b.byteLength + 65535) / 65536 | 0;
try {
wasmMemory.grow(pages);
updateMemoryViews();
return 1;
} catch (e) {}
};
var _emscripten_resize_heap = requestedSize => {
var oldSize = HEAPU8.length;
requestedSize >>>= 0;
var maxHeapSize = getHeapMax();
if (requestedSize > maxHeapSize) {
return false;
}
for (var cutDown = 1; cutDown <= 4; cutDown *= 2) {
var overGrownHeapSize = oldSize * (1 + .2 / cutDown);
overGrownHeapSize = Math.min(overGrownHeapSize, requestedSize + 100663296);
var newSize = Math.min(maxHeapSize, alignMemory(Math.max(requestedSize, overGrownHeapSize), 65536));
var replacement = growMemory(newSize);
if (replacement) {
return true;
}
}
return false;
};
var ENV = {};
var getExecutableName = () => thisProgram || "./this.program";
var getEnvStrings = () => {
if (!getEnvStrings.strings) {
var lang = (typeof navigator == "object" && navigator.languages && navigator.languages[0] || "C").replace("-", "_") + ".UTF-8";
var env = {
USER: "web_user",
LOGNAME: "web_user",
PATH: "/",
PWD: "/",
HOME: "/home/web_user",
LANG: lang,
_: getExecutableName()
};
for (var x in ENV) {
if (ENV[x] === undefined) delete env[x];else env[x] = ENV[x];
}
var strings = [];
for (var x in env) {
strings.push(`${x}=${env[x]}`);
}
getEnvStrings.strings = strings;
}
return getEnvStrings.strings;
};
var stringToAscii = (str, buffer) => {
for (var i = 0; i < str.length; ++i) {
HEAP8[buffer++] = str.charCodeAt(i);
}
HEAP8[buffer] = 0;
};
var _environ_get = (__environ, environ_buf) => {
var bufSize = 0;
getEnvStrings().forEach((string, i) => {
var ptr = environ_buf + bufSize;
HEAPU32[__environ + i * 4 >> 2] = ptr;
stringToAscii(string, ptr);
bufSize += string.length + 1;
});
return 0;
};
var _environ_sizes_get = (penviron_count, penviron_buf_size) => {
var strings = getEnvStrings();
HEAPU32[penviron_count >> 2] = strings.length;
var bufSize = 0;
strings.forEach(string => bufSize += string.length + 1);
HEAPU32[penviron_buf_size >> 2] = bufSize;
return 0;
};
var _fd_close = fd => 52;
var INT53_MAX = 9007199254740992;
var INT53_MIN = -9007199254740992;
var bigintToI53Checked = num => num < INT53_MIN || num > INT53_MAX ? NaN : Number(num);
function _fd_seek(fd, offset, whence, newOffset) {
offset = bigintToI53Checked(offset);
return 70;
}
var printCharBuffers = [null, [], []];
var UTF8Decoder = typeof TextDecoder != "undefined" ? new TextDecoder() : undefined;
var UTF8ArrayToString = (heapOrArray, idx = 0, maxBytesToRead = NaN) => {
var endIdx = idx + maxBytesToRead;
var endPtr = idx;
while (heapOrArray[endPtr] && !(endPtr >= endIdx)) ++endPtr;
if (endPtr - idx > 16 && heapOrArray.buffer && UTF8Decoder) {
return UTF8Decoder.decode(heapOrArray.subarray(idx, endPtr));
}
var str = "";
while (idx < endPtr) {
var u0 = heapOrArray[idx++];
if (!(u0 & 128)) {
str += String.fromCharCode(u0);
continue;
}
var u1 = heapOrArray[idx++] & 63;
if ((u0 & 224) == 192) {
str += String.fromCharCode((u0 & 31) << 6 | u1);
continue;
}
var u2 = heapOrArray[idx++] & 63;
if ((u0 & 240) == 224) {
u0 = (u0 & 15) << 12 | u1 << 6 | u2;
} else {
u0 = (u0 & 7) << 18 | u1 << 12 | u2 << 6 | heapOrArray[idx++] & 63;
}
if (u0 < 65536) {
str += String.fromCharCode(u0);
} else {
var ch = u0 - 65536;
str += String.fromCharCode(55296 | ch >> 10, 56320 | ch & 1023);
}
}
return str;
};
var printChar = (stream, curr) => {
var buffer = printCharBuffers[stream];
if (curr === 0 || curr === 10) {
(stream === 1 ? out : err)(UTF8ArrayToString(buffer));
buffer.length = 0;
} else {
buffer.push(curr);
}
};
var UTF8ToString = (ptr, maxBytesToRead) => ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead) : "";
var _fd_write = (fd, iov, iovcnt, pnum) => {
var num = 0;
for (var i = 0; i < iovcnt; i++) {
var ptr = HEAPU32[iov >> 2];
var len = HEAPU32[iov + 4 >> 2];
iov += 8;
for (var j = 0; j < len; j++) {
printChar(fd, HEAPU8[ptr + j]);
}
num += len;
}
HEAPU32[pnum >> 2] = num;
return 0;
};
function _gray_to_rgba(compG_ptr, nb_pixels) {
compG_ptr >>= 2;
const imageData = Module.imageData = new Uint8ClampedArray(nb_pixels * 4);
const compG = HEAP32.subarray(compG_ptr, compG_ptr + nb_pixels);
for (let i = 0; i < nb_pixels; i++) {
imageData[4 * i] = imageData[4 * i + 1] = imageData[4 * i + 2] = compG[i];
imageData[4 * i + 3] = 255;
}
}
function _graya_to_rgba(compG_ptr, compA_ptr, nb_pixels) {
compG_ptr >>= 2;
compA_ptr >>= 2;
const imageData = Module.imageData = new Uint8ClampedArray(nb_pixels * 4);
const compG = HEAP32.subarray(compG_ptr, compG_ptr + nb_pixels);
const compA = HEAP32.subarray(compA_ptr, compA_ptr + nb_pixels);
for (let i = 0; i < nb_pixels; i++) {
imageData[4 * i] = imageData[4 * i + 1] = imageData[4 * i + 2] = compG[i];
imageData[4 * i + 3] = compA[i];
}
}
function _jsPrintWarning(message_ptr) {
const message = UTF8ToString(message_ptr);
(Module.warn || console.warn)(`OpenJPEG: ${message}`);
}
function _rgb_to_rgba(compR_ptr, compG_ptr, compB_ptr, nb_pixels) {
compR_ptr >>= 2;
compG_ptr >>= 2;
compB_ptr >>= 2;
const imageData = Module.imageData = new Uint8ClampedArray(nb_pixels * 4);
const compR = HEAP32.subarray(compR_ptr, compR_ptr + nb_pixels);
const compG = HEAP32.subarray(compG_ptr, compG_ptr + nb_pixels);
const compB = HEAP32.subarray(compB_ptr, compB_ptr + nb_pixels);
for (let i = 0; i < nb_pixels; i++) {
imageData[4 * i] = compR[i];
imageData[4 * i + 1] = compG[i];
imageData[4 * i + 2] = compB[i];
imageData[4 * i + 3] = 255;
}
}
function _storeErrorMessage(message_ptr) {
const message = UTF8ToString(message_ptr);
if (!Module.errorMessages) {
Module.errorMessages = message;
} else {
Module.errorMessages += "\n" + message;
}
}
var wasmImports = {
l: __abort_js,
k: __emscripten_runtime_keepalive_clear,
m: __setitimer_js,
g: _copy_pixels_1,
f: _copy_pixels_3,
e: _copy_pixels_4,
n: _emscripten_resize_heap,
p: _environ_get,
q: _environ_sizes_get,
b: _fd_close,
o: _fd_seek,
c: _fd_write,
r: _gray_to_rgba,
i: _graya_to_rgba,
d: _jsPrintWarning,
j: _proc_exit,
h: _rgb_to_rgba,
a: _storeErrorMessage
};
var wasmExports = await createWasm();
var ___wasm_call_ctors = wasmExports["t"];
var _malloc = Module["_malloc"] = wasmExports["u"];
var _free = Module["_free"] = wasmExports["v"];
var _jp2_decode = Module["_jp2_decode"] = wasmExports["x"];
var __emscripten_timeout = wasmExports["y"];
function run() {
if (runDependencies > 0) {
dependenciesFulfilled = run;
return;
}
preRun();
if (runDependencies > 0) {
dependenciesFulfilled = run;
return;
}
function doRun() {
Module["calledRun"] = true;
if (ABORT) return;
initRuntime();
readyPromiseResolve(Module);
Module["onRuntimeInitialized"]?.();
postRun();
}
if (Module["setStatus"]) {
Module["setStatus"]("Running...");
setTimeout(() => {
setTimeout(() => Module["setStatus"](""), 1);
doRun();
}, 1);
} else {
doRun();
}
}
if (Module["preInit"]) {
if (typeof Module["preInit"] == "function") Module["preInit"] = [Module["preInit"]];
while (Module["preInit"].length > 0) {
Module["preInit"].pop()();
}
}
run();
moduleRtn = readyPromise;
return moduleRtn;
};
})();
/* harmony default export */ const openjpeg = (OpenJPEG);
;// ./src/core/jpx.js
class JpxError extends BaseException {
constructor(msg) {
super(msg, "JpxError");
}
}
class JpxImage {
static #buffer = null;
static #handler = null;
static #modulePromise = null;
static #useWasm = true;
static #useWorkerFetch = true;
static #wasmUrl = null;
static setOptions({
handler,
useWasm,
useWorkerFetch,
wasmUrl
}) {
this.#useWasm = useWasm;
this.#useWorkerFetch = useWorkerFetch;
this.#wasmUrl = wasmUrl;
if (!useWorkerFetch) {
this.#handler = handler;
}
}
static async #getJsModule(fallbackCallback) {
const path = `${this.#wasmUrl}openjpeg_nowasm_fallback.js`;
let instance = null;
try {
const mod = await import(
/*webpackIgnore: true*/
/*@vite-ignore*/
path);
instance = mod.default();
} catch (e) {
warn(`JpxImage#getJsModule: ${e}`);
}
fallbackCallback(instance);
}
static async #instantiateWasm(fallbackCallback, imports, successCallback) {
const filename = "openjpeg.wasm";
try {
if (!this.#buffer) {
if (this.#useWorkerFetch) {
this.#buffer = await fetchBinaryData(`${this.#wasmUrl}${filename}`);
} else {
this.#buffer = await this.#handler.sendWithPromise("FetchBinaryData", {
type: "wasmFactory",
filename
});
}
}
const results = await WebAssembly.instantiate(this.#buffer, imports);
return successCallback(results.instance);
} catch (reason) {
warn(`JpxImage#instantiateWasm: ${reason}`);
this.#getJsModule(fallbackCallback);
return null;
} finally {
this.#handler = null;
}
}
static async decode(bytes, {
numComponents = 4,
isIndexedColormap = false,
smaskInData = false
} = {}) {
if (!this.#modulePromise) {
const {
promise,
resolve
} = Promise.withResolvers();
const promises = [promise];
if (!this.#useWasm) {
this.#getJsModule(resolve);
} else {
promises.push(openjpeg({
warn: warn,
instantiateWasm: this.#instantiateWasm.bind(this, resolve)
}));
}
this.#modulePromise = Promise.race(promises);
}
const module = await this.#modulePromise;
if (!module) {
throw new JpxError("OpenJPEG failed to initialize");
}
let ptr;
try {
const size = bytes.length;
ptr = module._malloc(size);
module.HEAPU8.set(bytes, ptr);
const ret = module._jp2_decode(ptr, size, numComponents > 0 ? numComponents : 0, !!isIndexedColormap, !!smaskInData);
if (ret) {
const {
errorMessages
} = module;
if (errorMessages) {
delete module.errorMessages;
throw new JpxError(errorMessages);
}
throw new JpxError("Unknown error");
}
const {
imageData
} = module;
module.imageData = null;
return imageData;
} finally {
if (ptr) {
module._free(ptr);
}
}
}
static cleanup() {
this.#modulePromise = null;
}
static parseImageProperties(stream) {
let newByte = stream.getByte();
while (newByte >= 0) {
const oldByte = newByte;
newByte = stream.getByte();
const code = oldByte << 8 | newByte;
if (code === 0xff51) {
stream.skip(4);
const Xsiz = stream.getInt32() >>> 0;
const Ysiz = stream.getInt32() >>> 0;
const XOsiz = stream.getInt32() >>> 0;
const YOsiz = stream.getInt32() >>> 0;
stream.skip(16);
const Csiz = stream.getUint16();
return {
width: Xsiz - XOsiz,
height: Ysiz - YOsiz,
bitsPerComponent: 8,
componentsCount: Csiz
};
}
}
throw new JpxError("No size marker found in JPX stream");
}
}
;// ./src/core/binary_cmap.js
function hexToInt(a, size) {
let n = 0;
for (let i = 0; i <= size; i++) {
n = n << 8 | a[i];
}
return n >>> 0;
}
function hexToStr(a, size) {
if (size === 1) {
return String.fromCharCode(a[0], a[1]);
}
if (size === 3) {
return String.fromCharCode(a[0], a[1], a[2], a[3]);
}
return String.fromCharCode(...a.subarray(0, size + 1));
}
function addHex(a, b, size) {
let c = 0;
for (let i = size; i >= 0; i--) {
c += a[i] + b[i];
a[i] = c & 255;
c >>= 8;
}
}
function incHex(a, size) {
let c = 1;
for (let i = size; i >= 0 && c > 0; i--) {
c += a[i];
a[i] = c & 255;
c >>= 8;
}
}
const MAX_NUM_SIZE = 16;
const MAX_ENCODED_NUM_SIZE = 19;
class BinaryCMapStream {
constructor(data) {
this.buffer = data;
this.pos = 0;
this.end = data.length;
this.tmpBuf = new Uint8Array(MAX_ENCODED_NUM_SIZE);
}
readByte() {
if (this.pos >= this.end) {
return -1;
}
return this.buffer[this.pos++];
}
readNumber() {
let n = 0;
let last;
do {
const b = this.readByte();
if (b < 0) {
throw new FormatError("unexpected EOF in bcmap");
}
last = !(b & 0x80);
n = n << 7 | b & 0x7f;
} while (!last);
return n;
}
readSigned() {
const n = this.readNumber();
return n & 1 ? ~(n >>> 1) : n >>> 1;
}
readHex(num, size) {
num.set(this.buffer.subarray(this.pos, this.pos + size + 1));
this.pos += size + 1;
}
readHexNumber(num, size) {
let last;
const stack = this.tmpBuf;
let sp = 0;
do {
const b = this.readByte();
if (b < 0) {
throw new FormatError("unexpected EOF in bcmap");
}
last = !(b & 0x80);
stack[sp++] = b & 0x7f;
} while (!last);
let i = size,
buffer = 0,
bufferSize = 0;
while (i >= 0) {
while (bufferSize < 8 && stack.length > 0) {
buffer |= stack[--sp] << bufferSize;
bufferSize += 7;
}
num[i] = buffer & 255;
i--;
buffer >>= 8;
bufferSize -= 8;
}
}
readHexSigned(num, size) {
this.readHexNumber(num, size);
const sign = num[size] & 1 ? 255 : 0;
let c = 0;
for (let i = 0; i <= size; i++) {
c = (c & 1) << 8 | num[i];
num[i] = c >> 1 ^ sign;
}
}
readString() {
const len = this.readNumber(),
buf = new Array(len);
for (let i = 0; i < len; i++) {
buf[i] = this.readNumber();
}
return String.fromCharCode(...buf);
}
}
class BinaryCMapReader {
async process(data, cMap, extend) {
const stream = new BinaryCMapStream(data);
const header = stream.readByte();
cMap.vertical = !!(header & 1);
let useCMap = null;
const start = new Uint8Array(MAX_NUM_SIZE);
const end = new Uint8Array(MAX_NUM_SIZE);
const char = new Uint8Array(MAX_NUM_SIZE);
const charCode = new Uint8Array(MAX_NUM_SIZE);
const tmp = new Uint8Array(MAX_NUM_SIZE);
let code;
let b;
while ((b = stream.readByte()) >= 0) {
const type = b >> 5;
if (type === 7) {
switch (b & 0x1f) {
case 0:
stream.readString();
break;
case 1:
useCMap = stream.readString();
break;
}
continue;
}
const sequence = !!(b & 0x10);
const dataSize = b & 15;
if (dataSize + 1 > MAX_NUM_SIZE) {
throw new Error("BinaryCMapReader.process: Invalid dataSize.");
}
const ucs2DataSize = 1;
const subitemsCount = stream.readNumber();
switch (type) {
case 0:
stream.readHex(start, dataSize);
stream.readHexNumber(end, dataSize);
addHex(end, start, dataSize);
cMap.addCodespaceRange(dataSize + 1, hexToInt(start, dataSize), hexToInt(end, dataSize));
for (let i = 1; i < subitemsCount; i++) {
incHex(end, dataSize);
stream.readHexNumber(start, dataSize);
addHex(start, end, dataSize);
stream.readHexNumber(end, dataSize);
addHex(end, start, dataSize);
cMap.addCodespaceRange(dataSize + 1, hexToInt(start, dataSize), hexToInt(end, dataSize));
}
break;
case 1:
stream.readHex(start, dataSize);
stream.readHexNumber(end, dataSize);
addHex(end, start, dataSize);
stream.readNumber();
for (let i = 1; i < subitemsCount; i++) {
incHex(end, dataSize);
stream.readHexNumber(start, dataSize);
addHex(start, end, dataSize);
stream.readHexNumber(end, dataSize);
addHex(end, start, dataSize);
stream.readNumber();
}
break;
case 2:
stream.readHex(char, dataSize);
code = stream.readNumber();
cMap.mapOne(hexToInt(char, dataSize), code);
for (let i = 1; i < subitemsCount; i++) {
incHex(char, dataSize);
if (!sequence) {
stream.readHexNumber(tmp, dataSize);
addHex(char, tmp, dataSize);
}
code = stream.readSigned() + (code + 1);
cMap.mapOne(hexToInt(char, dataSize), code);
}
break;
case 3:
stream.readHex(start, dataSize);
stream.readHexNumber(end, dataSize);
addHex(end, start, dataSize);
code = stream.readNumber();
cMap.mapCidRange(hexToInt(start, dataSize), hexToInt(end, dataSize), code);
for (let i = 1; i < subitemsCount; i++) {
incHex(end, dataSize);
if (!sequence) {
stream.readHexNumber(start, dataSize);
addHex(start, end, dataSize);
} else {
start.set(end);
}
stream.readHexNumber(end, dataSize);
addHex(end, start, dataSize);
code = stream.readNumber();
cMap.mapCidRange(hexToInt(start, dataSize), hexToInt(end, dataSize), code);
}
break;
case 4:
stream.readHex(char, ucs2DataSize);
stream.readHex(charCode, dataSize);
cMap.mapOne(hexToInt(char, ucs2DataSize), hexToStr(charCode, dataSize));
for (let i = 1; i < subitemsCount; i++) {
incHex(char, ucs2DataSize);
if (!sequence) {
stream.readHexNumber(tmp, ucs2DataSize);
addHex(char, tmp, ucs2DataSize);
}
incHex(charCode, dataSize);
stream.readHexSigned(tmp, dataSize);
addHex(charCode, tmp, dataSize);
cMap.mapOne(hexToInt(char, ucs2DataSize), hexToStr(charCode, dataSize));
}
break;
case 5:
stream.readHex(start, ucs2DataSize);
stream.readHexNumber(end, ucs2DataSize);
addHex(end, start, ucs2DataSize);
stream.readHex(charCode, dataSize);
cMap.mapBfRange(hexToInt(start, ucs2DataSize), hexToInt(end, ucs2DataSize), hexToStr(charCode, dataSize));
for (let i = 1; i < subitemsCount; i++) {
incHex(end, ucs2DataSize);
if (!sequence) {
stream.readHexNumber(start, ucs2DataSize);
addHex(start, end, ucs2DataSize);
} else {
start.set(end);
}
stream.readHexNumber(end, ucs2DataSize);
addHex(end, start, ucs2DataSize);
stream.readHex(charCode, dataSize);
cMap.mapBfRange(hexToInt(start, ucs2DataSize), hexToInt(end, ucs2DataSize), hexToStr(charCode, dataSize));
}
break;
default:
throw new Error(`BinaryCMapReader.process - unknown type: ${type}`);
}
}
if (useCMap) {
return extend(useCMap);
}
return cMap;
}
}
;// ./src/core/ascii_85_stream.js
class Ascii85Stream extends DecodeStream {
constructor(str, maybeLength) {
if (maybeLength) {
maybeLength *= 0.8;
}
super(maybeLength);
this.str = str;
this.dict = str.dict;
this.input = new Uint8Array(5);
}
readBlock() {
const TILDA_CHAR = 0x7e;
const Z_LOWER_CHAR = 0x7a;
const EOF = -1;
const str = this.str;
let c = str.getByte();
while (isWhiteSpace(c)) {
c = str.getByte();
}
if (c === EOF || c === TILDA_CHAR) {
this.eof = true;
return;
}
const bufferLength = this.bufferLength;
let buffer, i;
if (c === Z_LOWER_CHAR) {
buffer = this.ensureBuffer(bufferLength + 4);
for (i = 0; i < 4; ++i) {
buffer[bufferLength + i] = 0;
}
this.bufferLength += 4;
} else {
const input = this.input;
input[0] = c;
for (i = 1; i < 5; ++i) {
c = str.getByte();
while (isWhiteSpace(c)) {
c = str.getByte();
}
input[i] = c;
if (c === EOF || c === TILDA_CHAR) {
break;
}
}
buffer = this.ensureBuffer(bufferLength + i - 1);
this.bufferLength += i - 1;
if (i < 5) {
for (; i < 5; ++i) {
input[i] = 0x21 + 84;
}
this.eof = true;
}
let t = 0;
for (i = 0; i < 5; ++i) {
t = t * 85 + (input[i] - 0x21);
}
for (i = 3; i >= 0; --i) {
buffer[bufferLength + i] = t & 0xff;
t >>= 8;
}
}
}
}
;// ./src/core/ascii_hex_stream.js
class AsciiHexStream extends DecodeStream {
constructor(str, maybeLength) {
if (maybeLength) {
maybeLength *= 0.5;
}
super(maybeLength);
this.str = str;
this.dict = str.dict;
this.firstDigit = -1;
}
readBlock() {
const UPSTREAM_BLOCK_SIZE = 8000;
const bytes = this.str.getBytes(UPSTREAM_BLOCK_SIZE);
if (!bytes.length) {
this.eof = true;
return;
}
const maxDecodeLength = bytes.length + 1 >> 1;
const buffer = this.ensureBuffer(this.bufferLength + maxDecodeLength);
let bufferLength = this.bufferLength;
let firstDigit = this.firstDigit;
for (const ch of bytes) {
let digit;
if (ch >= 0x30 && ch <= 0x39) {
digit = ch & 0x0f;
} else if (ch >= 0x41 && ch <= 0x46 || ch >= 0x61 && ch <= 0x66) {
digit = (ch & 0x0f) + 9;
} else if (ch === 0x3e) {
this.eof = true;
break;
} else {
continue;
}
if (firstDigit < 0) {
firstDigit = digit;
} else {
buffer[bufferLength++] = firstDigit << 4 | digit;
firstDigit = -1;
}
}
if (firstDigit >= 0 && this.eof) {
buffer[bufferLength++] = firstDigit << 4;
firstDigit = -1;
}
this.firstDigit = firstDigit;
this.bufferLength = bufferLength;
}
}
;// ./src/core/ccitt.js
const ccittEOL = -2;
const ccittEOF = -1;
const twoDimPass = 0;
const twoDimHoriz = 1;
const twoDimVert0 = 2;
const twoDimVertR1 = 3;
const twoDimVertL1 = 4;
const twoDimVertR2 = 5;
const twoDimVertL2 = 6;
const twoDimVertR3 = 7;
const twoDimVertL3 = 8;
const twoDimTable = [[-1, -1], [-1, -1], [7, twoDimVertL3], [7, twoDimVertR3], [6, twoDimVertL2], [6, twoDimVertL2], [6, twoDimVertR2], [6, twoDimVertR2], [4, twoDimPass], [4, twoDimPass], [4, twoDimPass], [4, twoDimPass], [4, twoDimPass], [4, twoDimPass], [4, twoDimPass], [4, twoDimPass], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0]];
const whiteTable1 = [[-1, -1], [12, ccittEOL], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [11, 1792], [11, 1792], [12, 1984], [12, 2048], [12, 2112], [12, 2176], [12, 2240], [12, 2304], [11, 1856], [11, 1856], [11, 1920], [11, 1920], [12, 2368], [12, 2432], [12, 2496], [12, 2560]];
const whiteTable2 = [[-1, -1], [-1, -1], [-1, -1], [-1, -1], [8, 29], [8, 29], [8, 30], [8, 30], [8, 45], [8, 45], [8, 46], [8, 46], [7, 22], [7, 22], [7, 22], [7, 22], [7, 23], [7, 23], [7, 23], [7, 23], [8, 47], [8, 47], [8, 48], [8, 48], [6, 13], [6, 13], [6, 13], [6, 13], [6, 13], [6, 13], [6, 13], [6, 13], [7, 20], [7, 20], [7, 20], [7, 20], [8, 33], [8, 33], [8, 34], [8, 34], [8, 35], [8, 35], [8, 36], [8, 36], [8, 37], [8, 37], [8, 38], [8, 38], [7, 19], [7, 19], [7, 19], [7, 19], [8, 31], [8, 31], [8, 32], [8, 32], [6, 1], [6, 1], [6, 1], [6, 1], [6, 1], [6, 1], [6, 1], [6, 1], [6, 12], [6, 12], [6, 12], [6, 12], [6, 12], [6, 12], [6, 12], [6, 12], [8, 53], [8, 53], [8, 54], [8, 54], [7, 26], [7, 26], [7, 26], [7, 26], [8, 39], [8, 39], [8, 40], [8, 40], [8, 41], [8, 41], [8, 42], [8, 42], [8, 43], [8, 43], [8, 44], [8, 44], [7, 21], [7, 21], [7, 21], [7, 21], [7, 28], [7, 28], [7, 28], [7, 28], [8, 61], [8, 61], [8, 62], [8, 62], [8, 63], [8, 63], [8, 0], [8, 0], [8, 320], [8, 320], [8, 384], [8, 384], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [7, 27], [7, 27], [7, 27], [7, 27], [8, 59], [8, 59], [8, 60], [8, 60], [9, 1472], [9, 1536], [9, 1600], [9, 1728], [7, 18], [7, 18], [7, 18], [7, 18], [7, 24], [7, 24], [7, 24], [7, 24], [8, 49], [8, 49], [8, 50], [8, 50], [8, 51], [8, 51], [8, 52], [8, 52], [7, 25], [7, 25], [7, 25], [7, 25], [8, 55], [8, 55], [8, 56], [8, 56], [8, 57], [8, 57], [8, 58], [8, 58], [6, 192], [6, 192], [6, 192], [6, 192], [6, 192], [6, 192], [6, 192], [6, 192], [6, 1664], [6, 1664], [6, 1664], [6, 1664], [6, 1664], [6, 1664], [6, 1664], [6, 1664], [8, 448], [8, 448], [8, 512], [8, 512], [9, 704], [9, 768], [8, 640], [8, 640], [8, 576], [8, 576], [9, 832], [9, 896], [9, 960], [9, 1024], [9, 1088], [9, 1152], [9, 1216], [9, 1280], [9, 1344], [9, 1408], [7, 256], [7, 256], [7, 256], [7, 256], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [6, 16], [6, 16], [6, 16], [6, 16], [6, 16], [6, 16], [6, 16], [6, 16], [6, 17], [6, 17], [6, 17], [6, 17], [6, 17], [6, 17], [6, 17], [6, 17], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [6, 14], [6, 14], [6, 14], [6, 14], [6, 14], [6, 14], [6, 14], [6, 14], [6, 15], [6, 15], [6, 15], [6, 15], [6, 15], [6, 15], [6, 15], [6, 15], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7]];
const blackTable1 = [[-1, -1], [-1, -1], [12, ccittEOL], [12, ccittEOL], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [11, 1792], [11, 1792], [11, 1792], [11, 1792], [12, 1984], [12, 1984], [12, 2048], [12, 2048], [12, 2112], [12, 2112], [12, 2176], [12, 2176], [12, 2240], [12, 2240], [12, 2304], [12, 2304], [11, 1856], [11, 1856], [11, 1856], [11, 1856], [11, 1920], [11, 1920], [11, 1920], [11, 1920], [12, 2368], [12, 2368], [12, 2432], [12, 2432], [12, 2496], [12, 2496], [12, 2560], [12, 2560], [10, 18], [10, 18], [10, 18], [10, 18], [10, 18], [10, 18], [10, 18], [10, 18], [12, 52], [12, 52], [13, 640], [13, 704], [13, 768], [13, 832], [12, 55], [12, 55], [12, 56], [12, 56], [13, 1280], [13, 1344], [13, 1408], [13, 1472], [12, 59], [12, 59], [12, 60], [12, 60], [13, 1536], [13, 1600], [11, 24], [11, 24], [11, 24], [11, 24], [11, 25], [11, 25], [11, 25], [11, 25], [13, 1664], [13, 1728], [12, 320], [12, 320], [12, 384], [12, 384], [12, 448], [12, 448], [13, 512], [13, 576], [12, 53], [12, 53], [12, 54], [12, 54], [13, 896], [13, 960], [13, 1024], [13, 1088], [13, 1152], [13, 1216], [10, 64], [10, 64], [10, 64], [10, 64], [10, 64], [10, 64], [10, 64], [10, 64]];
const blackTable2 = [[8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [11, 23], [11, 23], [12, 50], [12, 51], [12, 44], [12, 45], [12, 46], [12, 47], [12, 57], [12, 58], [12, 61], [12, 256], [10, 16], [10, 16], [10, 16], [10, 16], [10, 17], [10, 17], [10, 17], [10, 17], [12, 48], [12, 49], [12, 62], [12, 63], [12, 30], [12, 31], [12, 32], [12, 33], [12, 40], [12, 41], [11, 22], [11, 22], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [9, 15], [9, 15], [9, 15], [9, 15], [9, 15], [9, 15], [9, 15], [9, 15], [12, 128], [12, 192], [12, 26], [12, 27], [12, 28], [12, 29], [11, 19], [11, 19], [11, 20], [11, 20], [12, 34], [12, 35], [12, 36], [12, 37], [12, 38], [12, 39], [11, 21], [11, 21], [12, 42], [12, 43], [10, 0], [10, 0], [10, 0], [10, 0], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12]];
const blackTable3 = [[-1, -1], [-1, -1], [-1, -1], [-1, -1], [6, 9], [6, 8], [5, 7], [5, 7], [4, 6], [4, 6], [4, 6], [4, 6], [4, 5], [4, 5], [4, 5], [4, 5], [3, 1], [3, 1], [3, 1], [3, 1], [3, 1], [3, 1], [3, 1], [3, 1], [3, 4], [3, 4], [3, 4], [3, 4], [3, 4], [3, 4], [3, 4], [3, 4], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2]];
class CCITTFaxDecoder {
constructor(source, options = {}) {
if (typeof source?.next !== "function") {
throw new Error('CCITTFaxDecoder - invalid "source" parameter.');
}
this.source = source;
this.eof = false;
this.encoding = options.K || 0;
this.eoline = options.EndOfLine || false;
this.byteAlign = options.EncodedByteAlign || false;
this.columns = options.Columns || 1728;
this.rows = options.Rows || 0;
this.eoblock = options.EndOfBlock ?? true;
this.black = options.BlackIs1 || false;
this.codingLine = new Uint32Array(this.columns + 1);
this.refLine = new Uint32Array(this.columns + 2);
this.codingLine[0] = this.columns;
this.codingPos = 0;
this.row = 0;
this.nextLine2D = this.encoding < 0;
this.inputBits = 0;
this.inputBuf = 0;
this.outputBits = 0;
this.rowsDone = false;
let code1;
while ((code1 = this._lookBits(12)) === 0) {
this._eatBits(1);
}
if (code1 === 1) {
this._eatBits(12);
}
if (this.encoding > 0) {
this.nextLine2D = !this._lookBits(1);
this._eatBits(1);
}
}
readNextChar() {
if (this.eof) {
return -1;
}
const refLine = this.refLine;
const codingLine = this.codingLine;
const columns = this.columns;
let refPos, blackPixels, bits, i;
if (this.outputBits === 0) {
if (this.rowsDone) {
this.eof = true;
}
if (this.eof) {
return -1;
}
this.err = false;
let code1, code2, code3;
if (this.nextLine2D) {
for (i = 0; codingLine[i] < columns; ++i) {
refLine[i] = codingLine[i];
}
refLine[i++] = columns;
refLine[i] = columns;
codingLine[0] = 0;
this.codingPos = 0;
refPos = 0;
blackPixels = 0;
while (codingLine[this.codingPos] < columns) {
code1 = this._getTwoDimCode();
switch (code1) {
case twoDimPass:
this._addPixels(refLine[refPos + 1], blackPixels);
if (refLine[refPos + 1] < columns) {
refPos += 2;
}
break;
case twoDimHoriz:
code1 = code2 = 0;
if (blackPixels) {
do {
code1 += code3 = this._getBlackCode();
} while (code3 >= 64);
do {
code2 += code3 = this._getWhiteCode();
} while (code3 >= 64);
} else {
do {
code1 += code3 = this._getWhiteCode();
} while (code3 >= 64);
do {
code2 += code3 = this._getBlackCode();
} while (code3 >= 64);
}
this._addPixels(codingLine[this.codingPos] + code1, blackPixels);
if (codingLine[this.codingPos] < columns) {
this._addPixels(codingLine[this.codingPos] + code2, blackPixels ^ 1);
}
while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
refPos += 2;
}
break;
case twoDimVertR3:
this._addPixels(refLine[refPos] + 3, blackPixels);
blackPixels ^= 1;
if (codingLine[this.codingPos] < columns) {
++refPos;
while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
refPos += 2;
}
}
break;
case twoDimVertR2:
this._addPixels(refLine[refPos] + 2, blackPixels);
blackPixels ^= 1;
if (codingLine[this.codingPos] < columns) {
++refPos;
while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
refPos += 2;
}
}
break;
case twoDimVertR1:
this._addPixels(refLine[refPos] + 1, blackPixels);
blackPixels ^= 1;
if (codingLine[this.codingPos] < columns) {
++refPos;
while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
refPos += 2;
}
}
break;
case twoDimVert0:
this._addPixels(refLine[refPos], blackPixels);
blackPixels ^= 1;
if (codingLine[this.codingPos] < columns) {
++refPos;
while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
refPos += 2;
}
}
break;
case twoDimVertL3:
this._addPixelsNeg(refLine[refPos] - 3, blackPixels);
blackPixels ^= 1;
if (codingLine[this.codingPos] < columns) {
if (refPos > 0) {
--refPos;
} else {
++refPos;
}
while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
refPos += 2;
}
}
break;
case twoDimVertL2:
this._addPixelsNeg(refLine[refPos] - 2, blackPixels);
blackPixels ^= 1;
if (codingLine[this.codingPos] < columns) {
if (refPos > 0) {
--refPos;
} else {
++refPos;
}
while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
refPos += 2;
}
}
break;
case twoDimVertL1:
this._addPixelsNeg(refLine[refPos] - 1, blackPixels);
blackPixels ^= 1;
if (codingLine[this.codingPos] < columns) {
if (refPos > 0) {
--refPos;
} else {
++refPos;
}
while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
refPos += 2;
}
}
break;
case ccittEOF:
this._addPixels(columns, 0);
this.eof = true;
break;
default:
info("bad 2d code");
this._addPixels(columns, 0);
this.err = true;
}
}
} else {
codingLine[0] = 0;
this.codingPos = 0;
blackPixels = 0;
while (codingLine[this.codingPos] < columns) {
code1 = 0;
if (blackPixels) {
do {
code1 += code3 = this._getBlackCode();
} while (code3 >= 64);
} else {
do {
code1 += code3 = this._getWhiteCode();
} while (code3 >= 64);
}
this._addPixels(codingLine[this.codingPos] + code1, blackPixels);
blackPixels ^= 1;
}
}
let gotEOL = false;
if (this.byteAlign) {
this.inputBits &= ~7;
}
if (!this.eoblock && this.row === this.rows - 1) {
this.rowsDone = true;
} else {
code1 = this._lookBits(12);
if (this.eoline) {
while (code1 !== ccittEOF && code1 !== 1) {
this._eatBits(1);
code1 = this._lookBits(12);
}
} else {
while (code1 === 0) {
this._eatBits(1);
code1 = this._lookBits(12);
}
}
if (code1 === 1) {
this._eatBits(12);
gotEOL = true;
} else if (code1 === ccittEOF) {
this.eof = true;
}
}
if (!this.eof && this.encoding > 0 && !this.rowsDone) {
this.nextLine2D = !this._lookBits(1);
this._eatBits(1);
}
if (this.eoblock && gotEOL && this.byteAlign) {
code1 = this._lookBits(12);
if (code1 === 1) {
this._eatBits(12);
if (this.encoding > 0) {
this._lookBits(1);
this._eatBits(1);
}
if (this.encoding >= 0) {
for (i = 0; i < 4; ++i) {
code1 = this._lookBits(12);
if (code1 !== 1) {
info("bad rtc code: " + code1);
}
this._eatBits(12);
if (this.encoding > 0) {
this._lookBits(1);
this._eatBits(1);
}
}
}
this.eof = true;
}
} else if (this.err && this.eoline) {
while (true) {
code1 = this._lookBits(13);
if (code1 === ccittEOF) {
this.eof = true;
return -1;
}
if (code1 >> 1 === 1) {
break;
}
this._eatBits(1);
}
this._eatBits(12);
if (this.encoding > 0) {
this._eatBits(1);
this.nextLine2D = !(code1 & 1);
}
}
this.outputBits = codingLine[0] > 0 ? codingLine[this.codingPos = 0] : codingLine[this.codingPos = 1];
this.row++;
}
let c;
if (this.outputBits >= 8) {
c = this.codingPos & 1 ? 0 : 0xff;
this.outputBits -= 8;
if (this.outputBits === 0 && codingLine[this.codingPos] < columns) {
this.codingPos++;
this.outputBits = codingLine[this.codingPos] - codingLine[this.codingPos - 1];
}
} else {
bits = 8;
c = 0;
do {
if (typeof this.outputBits !== "number") {
throw new FormatError('Invalid /CCITTFaxDecode data, "outputBits" must be a number.');
}
if (this.outputBits > bits) {
c <<= bits;
if (!(this.codingPos & 1)) {
c |= 0xff >> 8 - bits;
}
this.outputBits -= bits;
bits = 0;
} else {
c <<= this.outputBits;
if (!(this.codingPos & 1)) {
c |= 0xff >> 8 - this.outputBits;
}
bits -= this.outputBits;
this.outputBits = 0;
if (codingLine[this.codingPos] < columns) {
this.codingPos++;
this.outputBits = codingLine[this.codingPos] - codingLine[this.codingPos - 1];
} else if (bits > 0) {
c <<= bits;
bits = 0;
}
}
} while (bits);
}
if (this.black) {
c ^= 0xff;
}
return c;
}
_addPixels(a1, blackPixels) {
const codingLine = this.codingLine;
let codingPos = this.codingPos;
if (a1 > codingLine[codingPos]) {
if (a1 > this.columns) {
info("row is wrong length");
this.err = true;
a1 = this.columns;
}
if (codingPos & 1 ^ blackPixels) {
++codingPos;
}
codingLine[codingPos] = a1;
}
this.codingPos = codingPos;
}
_addPixelsNeg(a1, blackPixels) {
const codingLine = this.codingLine;
let codingPos = this.codingPos;
if (a1 > codingLine[codingPos]) {
if (a1 > this.columns) {
info("row is wrong length");
this.err = true;
a1 = this.columns;
}
if (codingPos & 1 ^ blackPixels) {
++codingPos;
}
codingLine[codingPos] = a1;
} else if (a1 < codingLine[codingPos]) {
if (a1 < 0) {
info("invalid code");
this.err = true;
a1 = 0;
}
while (codingPos > 0 && a1 < codingLine[codingPos - 1]) {
--codingPos;
}
codingLine[codingPos] = a1;
}
this.codingPos = codingPos;
}
_findTableCode(start, end, table, limit) {
const limitValue = limit || 0;
for (let i = start; i <= end; ++i) {
let code = this._lookBits(i);
if (code === ccittEOF) {
return [true, 1, false];
}
if (i < end) {
code <<= end - i;
}
if (!limitValue || code >= limitValue) {
const p = table[code - limitValue];
if (p[0] === i) {
this._eatBits(i);
return [true, p[1], true];
}
}
}
return [false, 0, false];
}
_getTwoDimCode() {
let code = 0;
let p;
if (this.eoblock) {
code = this._lookBits(7);
p = twoDimTable[code];
if (p?.[0] > 0) {
this._eatBits(p[0]);
return p[1];
}
} else {
const result = this._findTableCode(1, 7, twoDimTable);
if (result[0] && result[2]) {
return result[1];
}
}
info("Bad two dim code");
return ccittEOF;
}
_getWhiteCode() {
let code = 0;
let p;
if (this.eoblock) {
code = this._lookBits(12);
if (code === ccittEOF) {
return 1;
}
p = code >> 5 === 0 ? whiteTable1[code] : whiteTable2[code >> 3];
if (p[0] > 0) {
this._eatBits(p[0]);
return p[1];
}
} else {
let result = this._findTableCode(1, 9, whiteTable2);
if (result[0]) {
return result[1];
}
result = this._findTableCode(11, 12, whiteTable1);
if (result[0]) {
return result[1];
}
}
info("bad white code");
this._eatBits(1);
return 1;
}
_getBlackCode() {
let code, p;
if (this.eoblock) {
code = this._lookBits(13);
if (code === ccittEOF) {
return 1;
}
if (code >> 7 === 0) {
p = blackTable1[code];
} else if (code >> 9 === 0 && code >> 7 !== 0) {
p = blackTable2[(code >> 1) - 64];
} else {
p = blackTable3[code >> 7];
}
if (p[0] > 0) {
this._eatBits(p[0]);
return p[1];
}
} else {
let result = this._findTableCode(2, 6, blackTable3);
if (result[0]) {
return result[1];
}
result = this._findTableCode(7, 12, blackTable2, 64);
if (result[0]) {
return result[1];
}
result = this._findTableCode(10, 13, blackTable1);
if (result[0]) {
return result[1];
}
}
info("bad black code");
this._eatBits(1);
return 1;
}
_lookBits(n) {
let c;
while (this.inputBits < n) {
if ((c = this.source.next()) === -1) {
if (this.inputBits === 0) {
return ccittEOF;
}
return this.inputBuf << n - this.inputBits & 0xffff >> 16 - n;
}
this.inputBuf = this.inputBuf << 8 | c;
this.inputBits += 8;
}
return this.inputBuf >> this.inputBits - n & 0xffff >> 16 - n;
}
_eatBits(n) {
if ((this.inputBits -= n) < 0) {
this.inputBits = 0;
}
}
}
;// ./src/core/ccitt_stream.js
class CCITTFaxStream extends DecodeStream {
constructor(str, maybeLength, params) {
super(maybeLength);
this.str = str;
this.dict = str.dict;
if (!(params instanceof Dict)) {
params = Dict.empty;
}
const source = {
next() {
return str.getByte();
}
};
this.ccittFaxDecoder = new CCITTFaxDecoder(source, {
K: params.get("K"),
EndOfLine: params.get("EndOfLine"),
EncodedByteAlign: params.get("EncodedByteAlign"),
Columns: params.get("Columns"),
Rows: params.get("Rows"),
EndOfBlock: params.get("EndOfBlock"),
BlackIs1: params.get("BlackIs1")
});
}
readBlock() {
while (!this.eof) {
const c = this.ccittFaxDecoder.readNextChar();
if (c === -1) {
this.eof = true;
return;
}
this.ensureBuffer(this.bufferLength + 1);
this.buffer[this.bufferLength++] = c;
}
}
}
;// ./src/core/flate_stream.js
const codeLenCodeMap = new Int32Array([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);
const lengthDecode = new Int32Array([0x00003, 0x00004, 0x00005, 0x00006, 0x00007, 0x00008, 0x00009, 0x0000a, 0x1000b, 0x1000d, 0x1000f, 0x10011, 0x20013, 0x20017, 0x2001b, 0x2001f, 0x30023, 0x3002b, 0x30033, 0x3003b, 0x40043, 0x40053, 0x40063, 0x40073, 0x50083, 0x500a3, 0x500c3, 0x500e3, 0x00102, 0x00102, 0x00102]);
const distDecode = new Int32Array([0x00001, 0x00002, 0x00003, 0x00004, 0x10005, 0x10007, 0x20009, 0x2000d, 0x30011, 0x30019, 0x40021, 0x40031, 0x50041, 0x50061, 0x60081, 0x600c1, 0x70101, 0x70181, 0x80201, 0x80301, 0x90401, 0x90601, 0xa0801, 0xa0c01, 0xb1001, 0xb1801, 0xc2001, 0xc3001, 0xd4001, 0xd6001]);
const fixedLitCodeTab = [new Int32Array([0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, 0x900c0, 0x70108, 0x80060, 0x80020, 0x900a0, 0x80000, 0x80080, 0x80040, 0x900e0, 0x70104, 0x80058, 0x80018, 0x90090, 0x70114, 0x80078, 0x80038, 0x900d0, 0x7010c, 0x80068, 0x80028, 0x900b0, 0x80008, 0x80088, 0x80048, 0x900f0, 0x70102, 0x80054, 0x80014, 0x8011c, 0x70112, 0x80074, 0x80034, 0x900c8, 0x7010a, 0x80064, 0x80024, 0x900a8, 0x80004, 0x80084, 0x80044, 0x900e8, 0x70106, 0x8005c, 0x8001c, 0x90098, 0x70116, 0x8007c, 0x8003c, 0x900d8, 0x7010e, 0x8006c, 0x8002c, 0x900b8, 0x8000c, 0x8008c, 0x8004c, 0x900f8, 0x70101, 0x80052, 0x80012, 0x8011a, 0x70111, 0x80072, 0x80032, 0x900c4, 0x70109, 0x80062, 0x80022, 0x900a4, 0x80002, 0x80082, 0x80042, 0x900e4, 0x70105, 0x8005a, 0x8001a, 0x90094, 0x70115, 0x8007a, 0x8003a, 0x900d4, 0x7010d, 0x8006a, 0x8002a, 0x900b4, 0x8000a, 0x8008a, 0x8004a, 0x900f4, 0x70103, 0x80056, 0x80016, 0x8011e, 0x70113, 0x80076, 0x80036, 0x900cc, 0x7010b, 0x80066, 0x80026, 0x900ac, 0x80006, 0x80086, 0x80046, 0x900ec, 0x70107, 0x8005e, 0x8001e, 0x9009c, 0x70117, 0x8007e, 0x8003e, 0x900dc, 0x7010f, 0x8006e, 0x8002e, 0x900bc, 0x8000e, 0x8008e, 0x8004e, 0x900fc, 0x70100, 0x80051, 0x80011, 0x80119, 0x70110, 0x80071, 0x80031, 0x900c2, 0x70108, 0x80061, 0x80021, 0x900a2, 0x80001, 0x80081, 0x80041, 0x900e2, 0x70104, 0x80059, 0x80019, 0x90092, 0x70114, 0x80079, 0x80039, 0x900d2, 0x7010c, 0x80069, 0x80029, 0x900b2, 0x80009, 0x80089, 0x80049, 0x900f2, 0x70102, 0x80055, 0x80015, 0x8011d, 0x70112, 0x80075, 0x80035, 0x900ca, 0x7010a, 0x80065, 0x80025, 0x900aa, 0x80005, 0x80085, 0x80045, 0x900ea, 0x70106, 0x8005d, 0x8001d, 0x9009a, 0x70116, 0x8007d, 0x8003d, 0x900da, 0x7010e, 0x8006d, 0x8002d, 0x900ba, 0x8000d, 0x8008d, 0x8004d, 0x900fa, 0x70101, 0x80053, 0x80013, 0x8011b, 0x70111, 0x80073, 0x80033, 0x900c6, 0x70109, 0x80063, 0x80023, 0x900a6, 0x80003, 0x80083, 0x80043, 0x900e6, 0x70105, 0x8005b, 0x8001b, 0x90096, 0x70115, 0x8007b, 0x8003b, 0x900d6, 0x7010d, 0x8006b, 0x8002b, 0x900b6, 0x8000b, 0x8008b, 0x8004b, 0x900f6, 0x70103, 0x80057, 0x80017, 0x8011f, 0x70113, 0x80077, 0x80037, 0x900ce, 0x7010b, 0x80067, 0x80027, 0x900ae, 0x80007, 0x80087, 0x80047, 0x900ee, 0x70107, 0x8005f, 0x8001f, 0x9009e, 0x70117, 0x8007f, 0x8003f, 0x900de, 0x7010f, 0x8006f, 0x8002f, 0x900be, 0x8000f, 0x8008f, 0x8004f, 0x900fe, 0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, 0x900c1, 0x70108, 0x80060, 0x80020, 0x900a1, 0x80000, 0x80080, 0x80040, 0x900e1, 0x70104, 0x80058, 0x80018, 0x90091, 0x70114, 0x80078, 0x80038, 0x900d1, 0x7010c, 0x80068, 0x80028, 0x900b1, 0x80008, 0x80088, 0x80048, 0x900f1, 0x70102, 0x80054, 0x80014, 0x8011c, 0x70112, 0x80074, 0x80034, 0x900c9, 0x7010a, 0x80064, 0x80024, 0x900a9, 0x80004, 0x80084, 0x80044, 0x900e9, 0x70106, 0x8005c, 0x8001c, 0x90099, 0x70116, 0x8007c, 0x8003c, 0x900d9, 0x7010e, 0x8006c, 0x8002c, 0x900b9, 0x8000c, 0x8008c, 0x8004c, 0x900f9, 0x70101, 0x80052, 0x80012, 0x8011a, 0x70111, 0x80072, 0x80032, 0x900c5, 0x70109, 0x80062, 0x80022, 0x900a5, 0x80002, 0x80082, 0x80042, 0x900e5, 0x70105, 0x8005a, 0x8001a, 0x90095, 0x70115, 0x8007a, 0x8003a, 0x900d5, 0x7010d, 0x8006a, 0x8002a, 0x900b5, 0x8000a, 0x8008a, 0x8004a, 0x900f5, 0x70103, 0x80056, 0x80016, 0x8011e, 0x70113, 0x80076, 0x80036, 0x900cd, 0x7010b, 0x80066, 0x80026, 0x900ad, 0x80006, 0x80086, 0x80046, 0x900ed, 0x70107, 0x8005e, 0x8001e, 0x9009d, 0x70117, 0x8007e, 0x8003e, 0x900dd, 0x7010f, 0x8006e, 0x8002e, 0x900bd, 0x8000e, 0x8008e, 0x8004e, 0x900fd, 0x70100, 0x80051, 0x80011, 0x80119, 0x70110, 0x80071, 0x80031, 0x900c3, 0x70108, 0x80061, 0x80021, 0x900a3, 0x80001, 0x80081, 0x80041, 0x900e3, 0x70104, 0x80059, 0x80019, 0x90093, 0x70114, 0x80079, 0x80039, 0x900d3, 0x7010c, 0x80069, 0x80029, 0x900b3, 0x80009, 0x80089, 0x80049, 0x900f3, 0x70102, 0x80055, 0x80015, 0x8011d, 0x70112, 0x80075, 0x80035, 0x900cb, 0x7010a, 0x80065, 0x80025, 0x900ab, 0x80005, 0x80085, 0x80045, 0x900eb, 0x70106, 0x8005d, 0x8001d, 0x9009b, 0x70116, 0x8007d, 0x8003d, 0x900db, 0x7010e, 0x8006d, 0x8002d, 0x900bb, 0x8000d, 0x8008d, 0x8004d, 0x900fb, 0x70101, 0x80053, 0x80013, 0x8011b, 0x70111, 0x80073, 0x80033, 0x900c7, 0x70109, 0x80063, 0x80023, 0x900a7, 0x80003, 0x80083, 0x80043, 0x900e7, 0x70105, 0x8005b, 0x8001b, 0x90097, 0x70115, 0x8007b, 0x8003b, 0x900d7, 0x7010d, 0x8006b, 0x8002b, 0x900b7, 0x8000b, 0x8008b, 0x8004b, 0x900f7, 0x70103, 0x80057, 0x80017, 0x8011f, 0x70113, 0x80077, 0x80037, 0x900cf, 0x7010b, 0x80067, 0x80027, 0x900af, 0x80007, 0x80087, 0x80047, 0x900ef, 0x70107, 0x8005f, 0x8001f, 0x9009f, 0x70117, 0x8007f, 0x8003f, 0x900df, 0x7010f, 0x8006f, 0x8002f, 0x900bf, 0x8000f, 0x8008f, 0x8004f, 0x900ff]), 9];
const fixedDistCodeTab = [new Int32Array([0x50000, 0x50010, 0x50008, 0x50018, 0x50004, 0x50014, 0x5000c, 0x5001c, 0x50002, 0x50012, 0x5000a, 0x5001a, 0x50006, 0x50016, 0x5000e, 0x00000, 0x50001, 0x50011, 0x50009, 0x50019, 0x50005, 0x50015, 0x5000d, 0x5001d, 0x50003, 0x50013, 0x5000b, 0x5001b, 0x50007, 0x50017, 0x5000f, 0x00000]), 5];
class FlateStream extends DecodeStream {
constructor(str, maybeLength) {
super(maybeLength);
this.str = str;
this.dict = str.dict;
const cmf = str.getByte();
const flg = str.getByte();
if (cmf === -1 || flg === -1) {
throw new FormatError(`Invalid header in flate stream: ${cmf}, ${flg}`);
}
if ((cmf & 0x0f) !== 0x08) {
throw new FormatError(`Unknown compression method in flate stream: ${cmf}, ${flg}`);
}
if (((cmf << 8) + flg) % 31 !== 0) {
throw new FormatError(`Bad FCHECK in flate stream: ${cmf}, ${flg}`);
}
if (flg & 0x20) {
throw new FormatError(`FDICT bit set in flate stream: ${cmf}, ${flg}`);
}
this.codeSize = 0;
this.codeBuf = 0;
}
async getImageData(length, _decoderOptions) {
const data = await this.asyncGetBytes();
return data?.subarray(0, length) || this.getBytes(length);
}
async asyncGetBytes() {
this.str.reset();
const bytes = this.str.getBytes();
try {
const {
readable,
writable
} = new DecompressionStream("deflate");
const writer = writable.getWriter();
await writer.ready;
writer.write(bytes).then(async () => {
await writer.ready;
await writer.close();
}).catch(() => {});
const chunks = [];
let totalLength = 0;
for await (const chunk of readable) {
chunks.push(chunk);
totalLength += chunk.byteLength;
}
const data = new Uint8Array(totalLength);
let offset = 0;
for (const chunk of chunks) {
data.set(chunk, offset);
offset += chunk.byteLength;
}
return data;
} catch {
this.str = new Stream(bytes, 2, bytes.length, this.str.dict);
this.reset();
return null;
}
}
get isAsync() {
return true;
}
getBits(bits) {
const str = this.str;
let codeSize = this.codeSize;
let codeBuf = this.codeBuf;
let b;
while (codeSize < bits) {
if ((b = str.getByte()) === -1) {
throw new FormatError("Bad encoding in flate stream");
}
codeBuf |= b << codeSize;
codeSize += 8;
}
b = codeBuf & (1 << bits) - 1;
this.codeBuf = codeBuf >> bits;
this.codeSize = codeSize -= bits;
return b;
}
getCode(table) {
const str = this.str;
const codes = table[0];
const maxLen = table[1];
let codeSize = this.codeSize;
let codeBuf = this.codeBuf;
let b;
while (codeSize < maxLen) {
if ((b = str.getByte()) === -1) {
break;
}
codeBuf |= b << codeSize;
codeSize += 8;
}
const code = codes[codeBuf & (1 << maxLen) - 1];
const codeLen = code >> 16;
const codeVal = code & 0xffff;
if (codeLen < 1 || codeSize < codeLen) {
throw new FormatError("Bad encoding in flate stream");
}
this.codeBuf = codeBuf >> codeLen;
this.codeSize = codeSize - codeLen;
return codeVal;
}
generateHuffmanTable(lengths) {
const n = lengths.length;
let maxLen = 0;
let i;
for (i = 0; i < n; ++i) {
if (lengths[i] > maxLen) {
maxLen = lengths[i];
}
}
const size = 1 << maxLen;
const codes = new Int32Array(size);
for (let len = 1, code = 0, skip = 2; len <= maxLen; ++len, code <<= 1, skip <<= 1) {
for (let val = 0; val < n; ++val) {
if (lengths[val] === len) {
let code2 = 0;
let t = code;
for (i = 0; i < len; ++i) {
code2 = code2 << 1 | t & 1;
t >>= 1;
}
for (i = code2; i < size; i += skip) {
codes[i] = len << 16 | val;
}
++code;
}
}
}
return [codes, maxLen];
}
#endsStreamOnError(err) {
info(err);
this.eof = true;
}
readBlock() {
let buffer, hdr, len;
const str = this.str;
try {
hdr = this.getBits(3);
} catch (ex) {
this.#endsStreamOnError(ex.message);
return;
}
if (hdr & 1) {
this.eof = true;
}
hdr >>= 1;
if (hdr === 0) {
let b;
if ((b = str.getByte()) === -1) {
this.#endsStreamOnError("Bad block header in flate stream");
return;
}
let blockLen = b;
if ((b = str.getByte()) === -1) {
this.#endsStreamOnError("Bad block header in flate stream");
return;
}
blockLen |= b << 8;
if ((b = str.getByte()) === -1) {
this.#endsStreamOnError("Bad block header in flate stream");
return;
}
let check = b;
if ((b = str.getByte()) === -1) {
this.#endsStreamOnError("Bad block header in flate stream");
return;
}
check |= b << 8;
if (check !== (~blockLen & 0xffff) && (blockLen !== 0 || check !== 0)) {
throw new FormatError("Bad uncompressed block length in flate stream");
}
this.codeBuf = 0;
this.codeSize = 0;
const bufferLength = this.bufferLength,
end = bufferLength + blockLen;
buffer = this.ensureBuffer(end);
this.bufferLength = end;
if (blockLen === 0) {
if (str.peekByte() === -1) {
this.eof = true;
}
} else {
const block = str.getBytes(blockLen);
buffer.set(block, bufferLength);
if (block.length < blockLen) {
this.eof = true;
}
}
return;
}
let litCodeTable;
let distCodeTable;
if (hdr === 1) {
litCodeTable = fixedLitCodeTab;
distCodeTable = fixedDistCodeTab;
} else if (hdr === 2) {
const numLitCodes = this.getBits(5) + 257;
const numDistCodes = this.getBits(5) + 1;
const numCodeLenCodes = this.getBits(4) + 4;
const codeLenCodeLengths = new Uint8Array(codeLenCodeMap.length);
let i;
for (i = 0; i < numCodeLenCodes; ++i) {
codeLenCodeLengths[codeLenCodeMap[i]] = this.getBits(3);
}
const codeLenCodeTab = this.generateHuffmanTable(codeLenCodeLengths);
len = 0;
i = 0;
const codes = numLitCodes + numDistCodes;
const codeLengths = new Uint8Array(codes);
let bitsLength, bitsOffset, what;
while (i < codes) {
const code = this.getCode(codeLenCodeTab);
if (code === 16) {
bitsLength = 2;
bitsOffset = 3;
what = len;
} else if (code === 17) {
bitsLength = 3;
bitsOffset = 3;
what = len = 0;
} else if (code === 18) {
bitsLength = 7;
bitsOffset = 11;
what = len = 0;
} else {
codeLengths[i++] = len = code;
continue;
}
let repeatLength = this.getBits(bitsLength) + bitsOffset;
while (repeatLength-- > 0) {
codeLengths[i++] = what;
}
}
litCodeTable = this.generateHuffmanTable(codeLengths.subarray(0, numLitCodes));
distCodeTable = this.generateHuffmanTable(codeLengths.subarray(numLitCodes, codes));
} else {
throw new FormatError("Unknown block type in flate stream");
}
buffer = this.buffer;
let limit = buffer ? buffer.length : 0;
let pos = this.bufferLength;
while (true) {
let code1 = this.getCode(litCodeTable);
if (code1 < 256) {
if (pos + 1 >= limit) {
buffer = this.ensureBuffer(pos + 1);
limit = buffer.length;
}
buffer[pos++] = code1;
continue;
}
if (code1 === 256) {
this.bufferLength = pos;
return;
}
code1 -= 257;
code1 = lengthDecode[code1];
let code2 = code1 >> 16;
if (code2 > 0) {
code2 = this.getBits(code2);
}
len = (code1 & 0xffff) + code2;
code1 = this.getCode(distCodeTable);
code1 = distDecode[code1];
code2 = code1 >> 16;
if (code2 > 0) {
code2 = this.getBits(code2);
}
const dist = (code1 & 0xffff) + code2;
if (pos + len >= limit) {
buffer = this.ensureBuffer(pos + len);
limit = buffer.length;
}
for (let k = 0; k < len; ++k, ++pos) {
buffer[pos] = buffer[pos - dist];
}
}
}
}
;// ./src/core/arithmetic_decoder.js
const QeTable = [{
qe: 0x5601,
nmps: 1,
nlps: 1,
switchFlag: 1
}, {
qe: 0x3401,
nmps: 2,
nlps: 6,
switchFlag: 0
}, {
qe: 0x1801,
nmps: 3,
nlps: 9,
switchFlag: 0
}, {
qe: 0x0ac1,
nmps: 4,
nlps: 12,
switchFlag: 0
}, {
qe: 0x0521,
nmps: 5,
nlps: 29,
switchFlag: 0
}, {
qe: 0x0221,
nmps: 38,
nlps: 33,
switchFlag: 0
}, {
qe: 0x5601,
nmps: 7,
nlps: 6,
switchFlag: 1
}, {
qe: 0x5401,
nmps: 8,
nlps: 14,
switchFlag: 0
}, {
qe: 0x4801,
nmps: 9,
nlps: 14,
switchFlag: 0
}, {
qe: 0x3801,
nmps: 10,
nlps: 14,
switchFlag: 0
}, {
qe: 0x3001,
nmps: 11,
nlps: 17,
switchFlag: 0
}, {
qe: 0x2401,
nmps: 12,
nlps: 18,
switchFlag: 0
}, {
qe: 0x1c01,
nmps: 13,
nlps: 20,
switchFlag: 0
}, {
qe: 0x1601,
nmps: 29,
nlps: 21,
switchFlag: 0
}, {
qe: 0x5601,
nmps: 15,
nlps: 14,
switchFlag: 1
}, {
qe: 0x5401,
nmps: 16,
nlps: 14,
switchFlag: 0
}, {
qe: 0x5101,
nmps: 17,
nlps: 15,
switchFlag: 0
}, {
qe: 0x4801,
nmps: 18,
nlps: 16,
switchFlag: 0
}, {
qe: 0x3801,
nmps: 19,
nlps: 17,
switchFlag: 0
}, {
qe: 0x3401,
nmps: 20,
nlps: 18,
switchFlag: 0
}, {
qe: 0x3001,
nmps: 21,
nlps: 19,
switchFlag: 0
}, {
qe: 0x2801,
nmps: 22,
nlps: 19,
switchFlag: 0
}, {
qe: 0x2401,
nmps: 23,
nlps: 20,
switchFlag: 0
}, {
qe: 0x2201,
nmps: 24,
nlps: 21,
switchFlag: 0
}, {
qe: 0x1c01,
nmps: 25,
nlps: 22,
switchFlag: 0
}, {
qe: 0x1801,
nmps: 26,
nlps: 23,
switchFlag: 0
}, {
qe: 0x1601,
nmps: 27,
nlps: 24,
switchFlag: 0
}, {
qe: 0x1401,
nmps: 28,
nlps: 25,
switchFlag: 0
}, {
qe: 0x1201,
nmps: 29,
nlps: 26,
switchFlag: 0
}, {
qe: 0x1101,
nmps: 30,
nlps: 27,
switchFlag: 0
}, {
qe: 0x0ac1,
nmps: 31,
nlps: 28,
switchFlag: 0
}, {
qe: 0x09c1,
nmps: 32,
nlps: 29,
switchFlag: 0
}, {
qe: 0x08a1,
nmps: 33,
nlps: 30,
switchFlag: 0
}, {
qe: 0x0521,
nmps: 34,
nlps: 31,
switchFlag: 0
}, {
qe: 0x0441,
nmps: 35,
nlps: 32,
switchFlag: 0
}, {
qe: 0x02a1,
nmps: 36,
nlps: 33,
switchFlag: 0
}, {
qe: 0x0221,
nmps: 37,
nlps: 34,
switchFlag: 0
}, {
qe: 0x0141,
nmps: 38,
nlps: 35,
switchFlag: 0
}, {
qe: 0x0111,
nmps: 39,
nlps: 36,
switchFlag: 0
}, {
qe: 0x0085,
nmps: 40,
nlps: 37,
switchFlag: 0
}, {
qe: 0x0049,
nmps: 41,
nlps: 38,
switchFlag: 0
}, {
qe: 0x0025,
nmps: 42,
nlps: 39,
switchFlag: 0
}, {
qe: 0x0015,
nmps: 43,
nlps: 40,
switchFlag: 0
}, {
qe: 0x0009,
nmps: 44,
nlps: 41,
switchFlag: 0
}, {
qe: 0x0005,
nmps: 45,
nlps: 42,
switchFlag: 0
}, {
qe: 0x0001,
nmps: 45,
nlps: 43,
switchFlag: 0
}, {
qe: 0x5601,
nmps: 46,
nlps: 46,
switchFlag: 0
}];
class ArithmeticDecoder {
constructor(data, start, end) {
this.data = data;
this.bp = start;
this.dataEnd = end;
this.chigh = data[start];
this.clow = 0;
this.byteIn();
this.chigh = this.chigh << 7 & 0xffff | this.clow >> 9 & 0x7f;
this.clow = this.clow << 7 & 0xffff;
this.ct -= 7;
this.a = 0x8000;
}
byteIn() {
const data = this.data;
let bp = this.bp;
if (data[bp] === 0xff) {
if (data[bp + 1] > 0x8f) {
this.clow += 0xff00;
this.ct = 8;
} else {
bp++;
this.clow += data[bp] << 9;
this.ct = 7;
this.bp = bp;
}
} else {
bp++;
this.clow += bp < this.dataEnd ? data[bp] << 8 : 0xff00;
this.ct = 8;
this.bp = bp;
}
if (this.clow > 0xffff) {
this.chigh += this.clow >> 16;
this.clow &= 0xffff;
}
}
readBit(contexts, pos) {
let cx_index = contexts[pos] >> 1,
cx_mps = contexts[pos] & 1;
const qeTableIcx = QeTable[cx_index];
const qeIcx = qeTableIcx.qe;
let d;
let a = this.a - qeIcx;
if (this.chigh < qeIcx) {
if (a < qeIcx) {
a = qeIcx;
d = cx_mps;
cx_index = qeTableIcx.nmps;
} else {
a = qeIcx;
d = 1 ^ cx_mps;
if (qeTableIcx.switchFlag === 1) {
cx_mps = d;
}
cx_index = qeTableIcx.nlps;
}
} else {
this.chigh -= qeIcx;
if ((a & 0x8000) !== 0) {
this.a = a;
return cx_mps;
}
if (a < qeIcx) {
d = 1 ^ cx_mps;
if (qeTableIcx.switchFlag === 1) {
cx_mps = d;
}
cx_index = qeTableIcx.nlps;
} else {
d = cx_mps;
cx_index = qeTableIcx.nmps;
}
}
do {
if (this.ct === 0) {
this.byteIn();
}
a <<= 1;
this.chigh = this.chigh << 1 & 0xffff | this.clow >> 15 & 1;
this.clow = this.clow << 1 & 0xffff;
this.ct--;
} while ((a & 0x8000) === 0);
this.a = a;
contexts[pos] = cx_index << 1 | cx_mps;
return d;
}
}
;// ./src/core/jbig2.js
class Jbig2Error extends BaseException {
constructor(msg) {
super(msg, "Jbig2Error");
}
}
class ContextCache {
getContexts(id) {
if (id in this) {
return this[id];
}
return this[id] = new Int8Array(1 << 16);
}
}
class DecodingContext {
constructor(data, start, end) {
this.data = data;
this.start = start;
this.end = end;
}
get decoder() {
const decoder = new ArithmeticDecoder(this.data, this.start, this.end);
return shadow(this, "decoder", decoder);
}
get contextCache() {
const cache = new ContextCache();
return shadow(this, "contextCache", cache);
}
}
function decodeInteger(contextCache, procedure, decoder) {
const contexts = contextCache.getContexts(procedure);
let prev = 1;
function readBits(length) {
let v = 0;
for (let i = 0; i < length; i++) {
const bit = decoder.readBit(contexts, prev);
prev = prev < 256 ? prev << 1 | bit : (prev << 1 | bit) & 511 | 256;
v = v << 1 | bit;
}
return v >>> 0;
}
const sign = readBits(1);
const value = readBits(1) ? readBits(1) ? readBits(1) ? readBits(1) ? readBits(1) ? readBits(32) + 4436 : readBits(12) + 340 : readBits(8) + 84 : readBits(6) + 20 : readBits(4) + 4 : readBits(2);
let signedValue;
if (sign === 0) {
signedValue = value;
} else if (value > 0) {
signedValue = -value;
}
if (signedValue >= MIN_INT_32 && signedValue <= MAX_INT_32) {
return signedValue;
}
return null;
}
function decodeIAID(contextCache, decoder, codeLength) {
const contexts = contextCache.getContexts("IAID");
let prev = 1;
for (let i = 0; i < codeLength; i++) {
const bit = decoder.readBit(contexts, prev);
prev = prev << 1 | bit;
}
if (codeLength < 31) {
return prev & (1 << codeLength) - 1;
}
return prev & 0x7fffffff;
}
const SegmentTypes = ["SymbolDictionary", null, null, null, "IntermediateTextRegion", null, "ImmediateTextRegion", "ImmediateLosslessTextRegion", null, null, null, null, null, null, null, null, "PatternDictionary", null, null, null, "IntermediateHalftoneRegion", null, "ImmediateHalftoneRegion", "ImmediateLosslessHalftoneRegion", null, null, null, null, null, null, null, null, null, null, null, null, "IntermediateGenericRegion", null, "ImmediateGenericRegion", "ImmediateLosslessGenericRegion", "IntermediateGenericRefinementRegion", null, "ImmediateGenericRefinementRegion", "ImmediateLosslessGenericRefinementRegion", null, null, null, null, "PageInformation", "EndOfPage", "EndOfStripe", "EndOfFile", "Profiles", "Tables", null, null, null, null, null, null, null, null, "Extension"];
const CodingTemplates = [[{
x: -1,
y: -2
}, {
x: 0,
y: -2
}, {
x: 1,
y: -2
}, {
x: -2,
y: -1
}, {
x: -1,
y: -1
}, {
x: 0,
y: -1
}, {
x: 1,
y: -1
}, {
x: 2,
y: -1
}, {
x: -4,
y: 0
}, {
x: -3,
y: 0
}, {
x: -2,
y: 0
}, {
x: -1,
y: 0
}], [{
x: -1,
y: -2
}, {
x: 0,
y: -2
}, {
x: 1,
y: -2
}, {
x: 2,
y: -2
}, {
x: -2,
y: -1
}, {
x: -1,
y: -1
}, {
x: 0,
y: -1
}, {
x: 1,
y: -1
}, {
x: 2,
y: -1
}, {
x: -3,
y: 0
}, {
x: -2,
y: 0
}, {
x: -1,
y: 0
}], [{
x: -1,
y: -2
}, {
x: 0,
y: -2
}, {
x: 1,
y: -2
}, {
x: -2,
y: -1
}, {
x: -1,
y: -1
}, {
x: 0,
y: -1
}, {
x: 1,
y: -1
}, {
x: -2,
y: 0
}, {
x: -1,
y: 0
}], [{
x: -3,
y: -1
}, {
x: -2,
y: -1
}, {
x: -1,
y: -1
}, {
x: 0,
y: -1
}, {
x: 1,
y: -1
}, {
x: -4,
y: 0
}, {
x: -3,
y: 0
}, {
x: -2,
y: 0
}, {
x: -1,
y: 0
}]];
const RefinementTemplates = [{
coding: [{
x: 0,
y: -1
}, {
x: 1,
y: -1
}, {
x: -1,
y: 0
}],
reference: [{
x: 0,
y: -1
}, {
x: 1,
y: -1
}, {
x: -1,
y: 0
}, {
x: 0,
y: 0
}, {
x: 1,
y: 0
}, {
x: -1,
y: 1
}, {
x: 0,
y: 1
}, {
x: 1,
y: 1
}]
}, {
coding: [{
x: -1,
y: -1
}, {
x: 0,
y: -1
}, {
x: 1,
y: -1
}, {
x: -1,
y: 0
}],
reference: [{
x: 0,
y: -1
}, {
x: -1,
y: 0
}, {
x: 0,
y: 0
}, {
x: 1,
y: 0
}, {
x: 0,
y: 1
}, {
x: 1,
y: 1
}]
}];
const ReusedContexts = [0x9b25, 0x0795, 0x00e5, 0x0195];
const RefinementReusedContexts = [0x0020, 0x0008];
function decodeBitmapTemplate0(width, height, decodingContext) {
const decoder = decodingContext.decoder;
const contexts = decodingContext.contextCache.getContexts("GB");
const bitmap = [];
let contextLabel, i, j, pixel, row, row1, row2;
const OLD_PIXEL_MASK = 0x7bf7;
for (i = 0; i < height; i++) {
row = bitmap[i] = new Uint8Array(width);
row1 = i < 1 ? row : bitmap[i - 1];
row2 = i < 2 ? row : bitmap[i - 2];
contextLabel = row2[0] << 13 | row2[1] << 12 | row2[2] << 11 | row1[0] << 7 | row1[1] << 6 | row1[2] << 5 | row1[3] << 4;
for (j = 0; j < width; j++) {
row[j] = pixel = decoder.readBit(contexts, contextLabel);
contextLabel = (contextLabel & OLD_PIXEL_MASK) << 1 | (j + 3 < width ? row2[j + 3] << 11 : 0) | (j + 4 < width ? row1[j + 4] << 4 : 0) | pixel;
}
}
return bitmap;
}
function decodeBitmap(mmr, width, height, templateIndex, prediction, skip, at, decodingContext) {
if (mmr) {
const input = new Reader(decodingContext.data, decodingContext.start, decodingContext.end);
return decodeMMRBitmap(input, width, height, false);
}
if (templateIndex === 0 && !skip && !prediction && at.length === 4 && at[0].x === 3 && at[0].y === -1 && at[1].x === -3 && at[1].y === -1 && at[2].x === 2 && at[2].y === -2 && at[3].x === -2 && at[3].y === -2) {
return decodeBitmapTemplate0(width, height, decodingContext);
}
const useskip = !!skip;
const template = CodingTemplates[templateIndex].concat(at);
template.sort((a, b) => a.y - b.y || a.x - b.x);
const templateLength = template.length;
const templateX = new Int8Array(templateLength);
const templateY = new Int8Array(templateLength);
const changingTemplateEntries = [];
let reuseMask = 0,
minX = 0,
maxX = 0,
minY = 0;
let c, k;
for (k = 0; k < templateLength; k++) {
templateX[k] = template[k].x;
templateY[k] = template[k].y;
minX = Math.min(minX, template[k].x);
maxX = Math.max(maxX, template[k].x);
minY = Math.min(minY, template[k].y);
if (k < templateLength - 1 && template[k].y === template[k + 1].y && template[k].x === template[k + 1].x - 1) {
reuseMask |= 1 << templateLength - 1 - k;
} else {
changingTemplateEntries.push(k);
}
}
const changingEntriesLength = changingTemplateEntries.length;
const changingTemplateX = new Int8Array(changingEntriesLength);
const changingTemplateY = new Int8Array(changingEntriesLength);
const changingTemplateBit = new Uint16Array(changingEntriesLength);
for (c = 0; c < changingEntriesLength; c++) {
k = changingTemplateEntries[c];
changingTemplateX[c] = template[k].x;
changingTemplateY[c] = template[k].y;
changingTemplateBit[c] = 1 << templateLength - 1 - k;
}
const sbb_left = -minX;
const sbb_top = -minY;
const sbb_right = width - maxX;
const pseudoPixelContext = ReusedContexts[templateIndex];
let row = new Uint8Array(width);
const bitmap = [];
const decoder = decodingContext.decoder;
const contexts = decodingContext.contextCache.getContexts("GB");
let ltp = 0,
j,
i0,
j0,
contextLabel = 0,
bit,
shift;
for (let i = 0; i < height; i++) {
if (prediction) {
const sltp = decoder.readBit(contexts, pseudoPixelContext);
ltp ^= sltp;
if (ltp) {
bitmap.push(row);
continue;
}
}
row = new Uint8Array(row);
bitmap.push(row);
for (j = 0; j < width; j++) {
if (useskip && skip[i][j]) {
row[j] = 0;
continue;
}
if (j >= sbb_left && j < sbb_right && i >= sbb_top) {
contextLabel = contextLabel << 1 & reuseMask;
for (k = 0; k < changingEntriesLength; k++) {
i0 = i + changingTemplateY[k];
j0 = j + changingTemplateX[k];
bit = bitmap[i0][j0];
if (bit) {
bit = changingTemplateBit[k];
contextLabel |= bit;
}
}
} else {
contextLabel = 0;
shift = templateLength - 1;
for (k = 0; k < templateLength; k++, shift--) {
j0 = j + templateX[k];
if (j0 >= 0 && j0 < width) {
i0 = i + templateY[k];
if (i0 >= 0) {
bit = bitmap[i0][j0];
if (bit) {
contextLabel |= bit << shift;
}
}
}
}
}
const pixel = decoder.readBit(contexts, contextLabel);
row[j] = pixel;
}
}
return bitmap;
}
function decodeRefinement(width, height, templateIndex, referenceBitmap, offsetX, offsetY, prediction, at, decodingContext) {
let codingTemplate = RefinementTemplates[templateIndex].coding;
if (templateIndex === 0) {
codingTemplate = codingTemplate.concat([at[0]]);
}
const codingTemplateLength = codingTemplate.length;
const codingTemplateX = new Int32Array(codingTemplateLength);
const codingTemplateY = new Int32Array(codingTemplateLength);
let k;
for (k = 0; k < codingTemplateLength; k++) {
codingTemplateX[k] = codingTemplate[k].x;
codingTemplateY[k] = codingTemplate[k].y;
}
let referenceTemplate = RefinementTemplates[templateIndex].reference;
if (templateIndex === 0) {
referenceTemplate = referenceTemplate.concat([at[1]]);
}
const referenceTemplateLength = referenceTemplate.length;
const referenceTemplateX = new Int32Array(referenceTemplateLength);
const referenceTemplateY = new Int32Array(referenceTemplateLength);
for (k = 0; k < referenceTemplateLength; k++) {
referenceTemplateX[k] = referenceTemplate[k].x;
referenceTemplateY[k] = referenceTemplate[k].y;
}
const referenceWidth = referenceBitmap[0].length;
const referenceHeight = referenceBitmap.length;
const pseudoPixelContext = RefinementReusedContexts[templateIndex];
const bitmap = [];
const decoder = decodingContext.decoder;
const contexts = decodingContext.contextCache.getContexts("GR");
let ltp = 0;
for (let i = 0; i < height; i++) {
if (prediction) {
const sltp = decoder.readBit(contexts, pseudoPixelContext);
ltp ^= sltp;
if (ltp) {
throw new Jbig2Error("prediction is not supported");
}
}
const row = new Uint8Array(width);
bitmap.push(row);
for (let j = 0; j < width; j++) {
let i0, j0;
let contextLabel = 0;
for (k = 0; k < codingTemplateLength; k++) {
i0 = i + codingTemplateY[k];
j0 = j + codingTemplateX[k];
if (i0 < 0 || j0 < 0 || j0 >= width) {
contextLabel <<= 1;
} else {
contextLabel = contextLabel << 1 | bitmap[i0][j0];
}
}
for (k = 0; k < referenceTemplateLength; k++) {
i0 = i + referenceTemplateY[k] - offsetY;
j0 = j + referenceTemplateX[k] - offsetX;
if (i0 < 0 || i0 >= referenceHeight || j0 < 0 || j0 >= referenceWidth) {
contextLabel <<= 1;
} else {
contextLabel = contextLabel << 1 | referenceBitmap[i0][j0];
}
}
const pixel = decoder.readBit(contexts, contextLabel);
row[j] = pixel;
}
}
return bitmap;
}
function decodeSymbolDictionary(huffman, refinement, symbols, numberOfNewSymbols, numberOfExportedSymbols, huffmanTables, templateIndex, at, refinementTemplateIndex, refinementAt, decodingContext, huffmanInput) {
if (huffman && refinement) {
throw new Jbig2Error("symbol refinement with Huffman is not supported");
}
const newSymbols = [];
let currentHeight = 0;
let symbolCodeLength = log2(symbols.length + numberOfNewSymbols);
const decoder = decodingContext.decoder;
const contextCache = decodingContext.contextCache;
let tableB1, symbolWidths;
if (huffman) {
tableB1 = getStandardTable(1);
symbolWidths = [];
symbolCodeLength = Math.max(symbolCodeLength, 1);
}
while (newSymbols.length < numberOfNewSymbols) {
const deltaHeight = huffman ? huffmanTables.tableDeltaHeight.decode(huffmanInput) : decodeInteger(contextCache, "IADH", decoder);
currentHeight += deltaHeight;
let currentWidth = 0,
totalWidth = 0;
const firstSymbol = huffman ? symbolWidths.length : 0;
while (true) {
const deltaWidth = huffman ? huffmanTables.tableDeltaWidth.decode(huffmanInput) : decodeInteger(contextCache, "IADW", decoder);
if (deltaWidth === null) {
break;
}
currentWidth += deltaWidth;
totalWidth += currentWidth;
let bitmap;
if (refinement) {
const numberOfInstances = decodeInteger(contextCache, "IAAI", decoder);
if (numberOfInstances > 1) {
bitmap = decodeTextRegion(huffman, refinement, currentWidth, currentHeight, 0, numberOfInstances, 1, symbols.concat(newSymbols), symbolCodeLength, 0, 0, 1, 0, huffmanTables, refinementTemplateIndex, refinementAt, decodingContext, 0, huffmanInput);
} else {
const symbolId = decodeIAID(contextCache, decoder, symbolCodeLength);
const rdx = decodeInteger(contextCache, "IARDX", decoder);
const rdy = decodeInteger(contextCache, "IARDY", decoder);
const symbol = symbolId < symbols.length ? symbols[symbolId] : newSymbols[symbolId - symbols.length];
bitmap = decodeRefinement(currentWidth, currentHeight, refinementTemplateIndex, symbol, rdx, rdy, false, refinementAt, decodingContext);
}
newSymbols.push(bitmap);
} else if (huffman) {
symbolWidths.push(currentWidth);
} else {
bitmap = decodeBitmap(false, currentWidth, currentHeight, templateIndex, false, null, at, decodingContext);
newSymbols.push(bitmap);
}
}
if (huffman && !refinement) {
const bitmapSize = huffmanTables.tableBitmapSize.decode(huffmanInput);
huffmanInput.byteAlign();
let collectiveBitmap;
if (bitmapSize === 0) {
collectiveBitmap = readUncompressedBitmap(huffmanInput, totalWidth, currentHeight);
} else {
const originalEnd = huffmanInput.end;
const bitmapEnd = huffmanInput.position + bitmapSize;
huffmanInput.end = bitmapEnd;
collectiveBitmap = decodeMMRBitmap(huffmanInput, totalWidth, currentHeight, false);
huffmanInput.end = originalEnd;
huffmanInput.position = bitmapEnd;
}
const numberOfSymbolsDecoded = symbolWidths.length;
if (firstSymbol === numberOfSymbolsDecoded - 1) {
newSymbols.push(collectiveBitmap);
} else {
let i,
y,
xMin = 0,
xMax,
bitmapWidth,
symbolBitmap;
for (i = firstSymbol; i < numberOfSymbolsDecoded; i++) {
bitmapWidth = symbolWidths[i];
xMax = xMin + bitmapWidth;
symbolBitmap = [];
for (y = 0; y < currentHeight; y++) {
symbolBitmap.push(collectiveBitmap[y].subarray(xMin, xMax));
}
newSymbols.push(symbolBitmap);
xMin = xMax;
}
}
}
}
const exportedSymbols = [],
flags = [];
let currentFlag = false,
i,
ii;
const totalSymbolsLength = symbols.length + numberOfNewSymbols;
while (flags.length < totalSymbolsLength) {
let runLength = huffman ? tableB1.decode(huffmanInput) : decodeInteger(contextCache, "IAEX", decoder);
while (runLength--) {
flags.push(currentFlag);
}
currentFlag = !currentFlag;
}
for (i = 0, ii = symbols.length; i < ii; i++) {
if (flags[i]) {
exportedSymbols.push(symbols[i]);
}
}
for (let j = 0; j < numberOfNewSymbols; i++, j++) {
if (flags[i]) {
exportedSymbols.push(newSymbols[j]);
}
}
return exportedSymbols;
}
function decodeTextRegion(huffman, refinement, width, height, defaultPixelValue, numberOfSymbolInstances, stripSize, inputSymbols, symbolCodeLength, transposed, dsOffset, referenceCorner, combinationOperator, huffmanTables, refinementTemplateIndex, refinementAt, decodingContext, logStripSize, huffmanInput) {
if (huffman && refinement) {
throw new Jbig2Error("refinement with Huffman is not supported");
}
const bitmap = [];
let i, row;
for (i = 0; i < height; i++) {
row = new Uint8Array(width);
if (defaultPixelValue) {
row.fill(defaultPixelValue);
}
bitmap.push(row);
}
const decoder = decodingContext.decoder;
const contextCache = decodingContext.contextCache;
let stripT = huffman ? -huffmanTables.tableDeltaT.decode(huffmanInput) : -decodeInteger(contextCache, "IADT", decoder);
let firstS = 0;
i = 0;
while (i < numberOfSymbolInstances) {
const deltaT = huffman ? huffmanTables.tableDeltaT.decode(huffmanInput) : decodeInteger(contextCache, "IADT", decoder);
stripT += deltaT;
const deltaFirstS = huffman ? huffmanTables.tableFirstS.decode(huffmanInput) : decodeInteger(contextCache, "IAFS", decoder);
firstS += deltaFirstS;
let currentS = firstS;
do {
let currentT = 0;
if (stripSize > 1) {
currentT = huffman ? huffmanInput.readBits(logStripSize) : decodeInteger(contextCache, "IAIT", decoder);
}
const t = stripSize * stripT + currentT;
const symbolId = huffman ? huffmanTables.symbolIDTable.decode(huffmanInput) : decodeIAID(contextCache, decoder, symbolCodeLength);
const applyRefinement = refinement && (huffman ? huffmanInput.readBit() : decodeInteger(contextCache, "IARI", decoder));
let symbolBitmap = inputSymbols[symbolId];
let symbolWidth = symbolBitmap[0].length;
let symbolHeight = symbolBitmap.length;
if (applyRefinement) {
const rdw = decodeInteger(contextCache, "IARDW", decoder);
const rdh = decodeInteger(contextCache, "IARDH", decoder);
const rdx = decodeInteger(contextCache, "IARDX", decoder);
const rdy = decodeInteger(contextCache, "IARDY", decoder);
symbolWidth += rdw;
symbolHeight += rdh;
symbolBitmap = decodeRefinement(symbolWidth, symbolHeight, refinementTemplateIndex, symbolBitmap, (rdw >> 1) + rdx, (rdh >> 1) + rdy, false, refinementAt, decodingContext);
}
let increment = 0;
if (!transposed) {
if (referenceCorner > 1) {
currentS += symbolWidth - 1;
} else {
increment = symbolWidth - 1;
}
} else if (!(referenceCorner & 1)) {
currentS += symbolHeight - 1;
} else {
increment = symbolHeight - 1;
}
const offsetT = t - (referenceCorner & 1 ? 0 : symbolHeight - 1);
const offsetS = currentS - (referenceCorner & 2 ? symbolWidth - 1 : 0);
let s2, t2, symbolRow;
if (transposed) {
for (s2 = 0; s2 < symbolHeight; s2++) {
row = bitmap[offsetS + s2];
if (!row) {
continue;
}
symbolRow = symbolBitmap[s2];
const maxWidth = Math.min(width - offsetT, symbolWidth);
switch (combinationOperator) {
case 0:
for (t2 = 0; t2 < maxWidth; t2++) {
row[offsetT + t2] |= symbolRow[t2];
}
break;
case 2:
for (t2 = 0; t2 < maxWidth; t2++) {
row[offsetT + t2] ^= symbolRow[t2];
}
break;
default:
throw new Jbig2Error(`operator ${combinationOperator} is not supported`);
}
}
} else {
for (t2 = 0; t2 < symbolHeight; t2++) {
row = bitmap[offsetT + t2];
if (!row) {
continue;
}
symbolRow = symbolBitmap[t2];
switch (combinationOperator) {
case 0:
for (s2 = 0; s2 < symbolWidth; s2++) {
row[offsetS + s2] |= symbolRow[s2];
}
break;
case 2:
for (s2 = 0; s2 < symbolWidth; s2++) {
row[offsetS + s2] ^= symbolRow[s2];
}
break;
default:
throw new Jbig2Error(`operator ${combinationOperator} is not supported`);
}
}
}
i++;
const deltaS = huffman ? huffmanTables.tableDeltaS.decode(huffmanInput) : decodeInteger(contextCache, "IADS", decoder);
if (deltaS === null) {
break;
}
currentS += increment + deltaS + dsOffset;
} while (true);
}
return bitmap;
}
function decodePatternDictionary(mmr, patternWidth, patternHeight, maxPatternIndex, template, decodingContext) {
const at = [];
if (!mmr) {
at.push({
x: -patternWidth,
y: 0
});
if (template === 0) {
at.push({
x: -3,
y: -1
}, {
x: 2,
y: -2
}, {
x: -2,
y: -2
});
}
}
const collectiveWidth = (maxPatternIndex + 1) * patternWidth;
const collectiveBitmap = decodeBitmap(mmr, collectiveWidth, patternHeight, template, false, null, at, decodingContext);
const patterns = [];
for (let i = 0; i <= maxPatternIndex; i++) {
const patternBitmap = [];
const xMin = patternWidth * i;
const xMax = xMin + patternWidth;
for (let y = 0; y < patternHeight; y++) {
patternBitmap.push(collectiveBitmap[y].subarray(xMin, xMax));
}
patterns.push(patternBitmap);
}
return patterns;
}
function decodeHalftoneRegion(mmr, patterns, template, regionWidth, regionHeight, defaultPixelValue, enableSkip, combinationOperator, gridWidth, gridHeight, gridOffsetX, gridOffsetY, gridVectorX, gridVectorY, decodingContext) {
const skip = null;
if (enableSkip) {
throw new Jbig2Error("skip is not supported");
}
if (combinationOperator !== 0) {
throw new Jbig2Error(`operator "${combinationOperator}" is not supported in halftone region`);
}
const regionBitmap = [];
let i, j, row;
for (i = 0; i < regionHeight; i++) {
row = new Uint8Array(regionWidth);
if (defaultPixelValue) {
row.fill(defaultPixelValue);
}
regionBitmap.push(row);
}
const numberOfPatterns = patterns.length;
const pattern0 = patterns[0];
const patternWidth = pattern0[0].length,
patternHeight = pattern0.length;
const bitsPerValue = log2(numberOfPatterns);
const at = [];
if (!mmr) {
at.push({
x: template <= 1 ? 3 : 2,
y: -1
});
if (template === 0) {
at.push({
x: -3,
y: -1
}, {
x: 2,
y: -2
}, {
x: -2,
y: -2
});
}
}
const grayScaleBitPlanes = [];
let mmrInput, bitmap;
if (mmr) {
mmrInput = new Reader(decodingContext.data, decodingContext.start, decodingContext.end);
}
for (i = bitsPerValue - 1; i >= 0; i--) {
if (mmr) {
bitmap = decodeMMRBitmap(mmrInput, gridWidth, gridHeight, true);
} else {
bitmap = decodeBitmap(false, gridWidth, gridHeight, template, false, skip, at, decodingContext);
}
grayScaleBitPlanes[i] = bitmap;
}
let mg, ng, bit, patternIndex, patternBitmap, x, y, patternRow, regionRow;
for (mg = 0; mg < gridHeight; mg++) {
for (ng = 0; ng < gridWidth; ng++) {
bit = 0;
patternIndex = 0;
for (j = bitsPerValue - 1; j >= 0; j--) {
bit ^= grayScaleBitPlanes[j][mg][ng];
patternIndex |= bit << j;
}
patternBitmap = patterns[patternIndex];
x = gridOffsetX + mg * gridVectorY + ng * gridVectorX >> 8;
y = gridOffsetY + mg * gridVectorX - ng * gridVectorY >> 8;
if (x >= 0 && x + patternWidth <= regionWidth && y >= 0 && y + patternHeight <= regionHeight) {
for (i = 0; i < patternHeight; i++) {
regionRow = regionBitmap[y + i];
patternRow = patternBitmap[i];
for (j = 0; j < patternWidth; j++) {
regionRow[x + j] |= patternRow[j];
}
}
} else {
let regionX, regionY;
for (i = 0; i < patternHeight; i++) {
regionY = y + i;
if (regionY < 0 || regionY >= regionHeight) {
continue;
}
regionRow = regionBitmap[regionY];
patternRow = patternBitmap[i];
for (j = 0; j < patternWidth; j++) {
regionX = x + j;
if (regionX >= 0 && regionX < regionWidth) {
regionRow[regionX] |= patternRow[j];
}
}
}
}
}
}
return regionBitmap;
}
function readSegmentHeader(data, start) {
const segmentHeader = {};
segmentHeader.number = readUint32(data, start);
const flags = data[start + 4];
const segmentType = flags & 0x3f;
if (!SegmentTypes[segmentType]) {
throw new Jbig2Error("invalid segment type: " + segmentType);
}
segmentHeader.type = segmentType;
segmentHeader.typeName = SegmentTypes[segmentType];
segmentHeader.deferredNonRetain = !!(flags & 0x80);
const pageAssociationFieldSize = !!(flags & 0x40);
const referredFlags = data[start + 5];
let referredToCount = referredFlags >> 5 & 7;
const retainBits = [referredFlags & 31];
let position = start + 6;
if (referredFlags === 7) {
referredToCount = readUint32(data, position - 1) & 0x1fffffff;
position += 3;
let bytes = referredToCount + 7 >> 3;
retainBits[0] = data[position++];
while (--bytes > 0) {
retainBits.push(data[position++]);
}
} else if (referredFlags === 5 || referredFlags === 6) {
throw new Jbig2Error("invalid referred-to flags");
}
segmentHeader.retainBits = retainBits;
let referredToSegmentNumberSize = 4;
if (segmentHeader.number <= 256) {
referredToSegmentNumberSize = 1;
} else if (segmentHeader.number <= 65536) {
referredToSegmentNumberSize = 2;
}
const referredTo = [];
let i, ii;
for (i = 0; i < referredToCount; i++) {
let number;
if (referredToSegmentNumberSize === 1) {
number = data[position];
} else if (referredToSegmentNumberSize === 2) {
number = readUint16(data, position);
} else {
number = readUint32(data, position);
}
referredTo.push(number);
position += referredToSegmentNumberSize;
}
segmentHeader.referredTo = referredTo;
if (!pageAssociationFieldSize) {
segmentHeader.pageAssociation = data[position++];
} else {
segmentHeader.pageAssociation = readUint32(data, position);
position += 4;
}
segmentHeader.length = readUint32(data, position);
position += 4;
if (segmentHeader.length === 0xffffffff) {
if (segmentType === 38) {
const genericRegionInfo = readRegionSegmentInformation(data, position);
const genericRegionSegmentFlags = data[position + RegionSegmentInformationFieldLength];
const genericRegionMmr = !!(genericRegionSegmentFlags & 1);
const searchPatternLength = 6;
const searchPattern = new Uint8Array(searchPatternLength);
if (!genericRegionMmr) {
searchPattern[0] = 0xff;
searchPattern[1] = 0xac;
}
searchPattern[2] = genericRegionInfo.height >>> 24 & 0xff;
searchPattern[3] = genericRegionInfo.height >> 16 & 0xff;
searchPattern[4] = genericRegionInfo.height >> 8 & 0xff;
searchPattern[5] = genericRegionInfo.height & 0xff;
for (i = position, ii = data.length; i < ii; i++) {
let j = 0;
while (j < searchPatternLength && searchPattern[j] === data[i + j]) {
j++;
}
if (j === searchPatternLength) {
segmentHeader.length = i + searchPatternLength;
break;
}
}
if (segmentHeader.length === 0xffffffff) {
throw new Jbig2Error("segment end was not found");
}
} else {
throw new Jbig2Error("invalid unknown segment length");
}
}
segmentHeader.headerEnd = position;
return segmentHeader;
}
function readSegments(header, data, start, end) {
const segments = [];
let position = start;
while (position < end) {
const segmentHeader = readSegmentHeader(data, position);
position = segmentHeader.headerEnd;
const segment = {
header: segmentHeader,
data
};
if (!header.randomAccess) {
segment.start = position;
position += segmentHeader.length;
segment.end = position;
}
segments.push(segment);
if (segmentHeader.type === 51) {
break;
}
}
if (header.randomAccess) {
for (let i = 0, ii = segments.length; i < ii; i++) {
segments[i].start = position;
position += segments[i].header.length;
segments[i].end = position;
}
}
return segments;
}
function readRegionSegmentInformation(data, start) {
return {
width: readUint32(data, start),
height: readUint32(data, start + 4),
x: readUint32(data, start + 8),
y: readUint32(data, start + 12),
combinationOperator: data[start + 16] & 7
};
}
const RegionSegmentInformationFieldLength = 17;
function processSegment(segment, visitor) {
const header = segment.header;
const data = segment.data,
end = segment.end;
let position = segment.start;
let args, at, i, atLength;
switch (header.type) {
case 0:
const dictionary = {};
const dictionaryFlags = readUint16(data, position);
dictionary.huffman = !!(dictionaryFlags & 1);
dictionary.refinement = !!(dictionaryFlags & 2);
dictionary.huffmanDHSelector = dictionaryFlags >> 2 & 3;
dictionary.huffmanDWSelector = dictionaryFlags >> 4 & 3;
dictionary.bitmapSizeSelector = dictionaryFlags >> 6 & 1;
dictionary.aggregationInstancesSelector = dictionaryFlags >> 7 & 1;
dictionary.bitmapCodingContextUsed = !!(dictionaryFlags & 256);
dictionary.bitmapCodingContextRetained = !!(dictionaryFlags & 512);
dictionary.template = dictionaryFlags >> 10 & 3;
dictionary.refinementTemplate = dictionaryFlags >> 12 & 1;
position += 2;
if (!dictionary.huffman) {
atLength = dictionary.template === 0 ? 4 : 1;
at = [];
for (i = 0; i < atLength; i++) {
at.push({
x: readInt8(data, position),
y: readInt8(data, position + 1)
});
position += 2;
}
dictionary.at = at;
}
if (dictionary.refinement && !dictionary.refinementTemplate) {
at = [];
for (i = 0; i < 2; i++) {
at.push({
x: readInt8(data, position),
y: readInt8(data, position + 1)
});
position += 2;
}
dictionary.refinementAt = at;
}
dictionary.numberOfExportedSymbols = readUint32(data, position);
position += 4;
dictionary.numberOfNewSymbols = readUint32(data, position);
position += 4;
args = [dictionary, header.number, header.referredTo, data, position, end];
break;
case 6:
case 7:
const textRegion = {};
textRegion.info = readRegionSegmentInformation(data, position);
position += RegionSegmentInformationFieldLength;
const textRegionSegmentFlags = readUint16(data, position);
position += 2;
textRegion.huffman = !!(textRegionSegmentFlags & 1);
textRegion.refinement = !!(textRegionSegmentFlags & 2);
textRegion.logStripSize = textRegionSegmentFlags >> 2 & 3;
textRegion.stripSize = 1 << textRegion.logStripSize;
textRegion.referenceCorner = textRegionSegmentFlags >> 4 & 3;
textRegion.transposed = !!(textRegionSegmentFlags & 64);
textRegion.combinationOperator = textRegionSegmentFlags >> 7 & 3;
textRegion.defaultPixelValue = textRegionSegmentFlags >> 9 & 1;
textRegion.dsOffset = textRegionSegmentFlags << 17 >> 27;
textRegion.refinementTemplate = textRegionSegmentFlags >> 15 & 1;
if (textRegion.huffman) {
const textRegionHuffmanFlags = readUint16(data, position);
position += 2;
textRegion.huffmanFS = textRegionHuffmanFlags & 3;
textRegion.huffmanDS = textRegionHuffmanFlags >> 2 & 3;
textRegion.huffmanDT = textRegionHuffmanFlags >> 4 & 3;
textRegion.huffmanRefinementDW = textRegionHuffmanFlags >> 6 & 3;
textRegion.huffmanRefinementDH = textRegionHuffmanFlags >> 8 & 3;
textRegion.huffmanRefinementDX = textRegionHuffmanFlags >> 10 & 3;
textRegion.huffmanRefinementDY = textRegionHuffmanFlags >> 12 & 3;
textRegion.huffmanRefinementSizeSelector = !!(textRegionHuffmanFlags & 0x4000);
}
if (textRegion.refinement && !textRegion.refinementTemplate) {
at = [];
for (i = 0; i < 2; i++) {
at.push({
x: readInt8(data, position),
y: readInt8(data, position + 1)
});
position += 2;
}
textRegion.refinementAt = at;
}
textRegion.numberOfSymbolInstances = readUint32(data, position);
position += 4;
args = [textRegion, header.referredTo, data, position, end];
break;
case 16:
const patternDictionary = {};
const patternDictionaryFlags = data[position++];
patternDictionary.mmr = !!(patternDictionaryFlags & 1);
patternDictionary.template = patternDictionaryFlags >> 1 & 3;
patternDictionary.patternWidth = data[position++];
patternDictionary.patternHeight = data[position++];
patternDictionary.maxPatternIndex = readUint32(data, position);
position += 4;
args = [patternDictionary, header.number, data, position, end];
break;
case 22:
case 23:
const halftoneRegion = {};
halftoneRegion.info = readRegionSegmentInformation(data, position);
position += RegionSegmentInformationFieldLength;
const halftoneRegionFlags = data[position++];
halftoneRegion.mmr = !!(halftoneRegionFlags & 1);
halftoneRegion.template = halftoneRegionFlags >> 1 & 3;
halftoneRegion.enableSkip = !!(halftoneRegionFlags & 8);
halftoneRegion.combinationOperator = halftoneRegionFlags >> 4 & 7;
halftoneRegion.defaultPixelValue = halftoneRegionFlags >> 7 & 1;
halftoneRegion.gridWidth = readUint32(data, position);
position += 4;
halftoneRegion.gridHeight = readUint32(data, position);
position += 4;
halftoneRegion.gridOffsetX = readUint32(data, position) & 0xffffffff;
position += 4;
halftoneRegion.gridOffsetY = readUint32(data, position) & 0xffffffff;
position += 4;
halftoneRegion.gridVectorX = readUint16(data, position);
position += 2;
halftoneRegion.gridVectorY = readUint16(data, position);
position += 2;
args = [halftoneRegion, header.referredTo, data, position, end];
break;
case 38:
case 39:
const genericRegion = {};
genericRegion.info = readRegionSegmentInformation(data, position);
position += RegionSegmentInformationFieldLength;
const genericRegionSegmentFlags = data[position++];
genericRegion.mmr = !!(genericRegionSegmentFlags & 1);
genericRegion.template = genericRegionSegmentFlags >> 1 & 3;
genericRegion.prediction = !!(genericRegionSegmentFlags & 8);
if (!genericRegion.mmr) {
atLength = genericRegion.template === 0 ? 4 : 1;
at = [];
for (i = 0; i < atLength; i++) {
at.push({
x: readInt8(data, position),
y: readInt8(data, position + 1)
});
position += 2;
}
genericRegion.at = at;
}
args = [genericRegion, data, position, end];
break;
case 48:
const pageInfo = {
width: readUint32(data, position),
height: readUint32(data, position + 4),
resolutionX: readUint32(data, position + 8),
resolutionY: readUint32(data, position + 12)
};
if (pageInfo.height === 0xffffffff) {
delete pageInfo.height;
}
const pageSegmentFlags = data[position + 16];
readUint16(data, position + 17);
pageInfo.lossless = !!(pageSegmentFlags & 1);
pageInfo.refinement = !!(pageSegmentFlags & 2);
pageInfo.defaultPixelValue = pageSegmentFlags >> 2 & 1;
pageInfo.combinationOperator = pageSegmentFlags >> 3 & 3;
pageInfo.requiresBuffer = !!(pageSegmentFlags & 32);
pageInfo.combinationOperatorOverride = !!(pageSegmentFlags & 64);
args = [pageInfo];
break;
case 49:
break;
case 50:
break;
case 51:
break;
case 53:
args = [header.number, data, position, end];
break;
case 62:
break;
default:
throw new Jbig2Error(`segment type ${header.typeName}(${header.type}) is not implemented`);
}
const callbackName = "on" + header.typeName;
if (callbackName in visitor) {
visitor[callbackName].apply(visitor, args);
}
}
function processSegments(segments, visitor) {
for (let i = 0, ii = segments.length; i < ii; i++) {
processSegment(segments[i], visitor);
}
}
function parseJbig2Chunks(chunks) {
const visitor = new SimpleSegmentVisitor();
for (let i = 0, ii = chunks.length; i < ii; i++) {
const chunk = chunks[i];
const segments = readSegments({}, chunk.data, chunk.start, chunk.end);
processSegments(segments, visitor);
}
return visitor.buffer;
}
function parseJbig2(data) {
throw new Error("Not implemented: parseJbig2");
}
class SimpleSegmentVisitor {
onPageInformation(info) {
this.currentPageInfo = info;
const rowSize = info.width + 7 >> 3;
const buffer = new Uint8ClampedArray(rowSize * info.height);
if (info.defaultPixelValue) {
buffer.fill(0xff);
}
this.buffer = buffer;
}
drawBitmap(regionInfo, bitmap) {
const pageInfo = this.currentPageInfo;
const width = regionInfo.width,
height = regionInfo.height;
const rowSize = pageInfo.width + 7 >> 3;
const combinationOperator = pageInfo.combinationOperatorOverride ? regionInfo.combinationOperator : pageInfo.combinationOperator;
const buffer = this.buffer;
const mask0 = 128 >> (regionInfo.x & 7);
let offset0 = regionInfo.y * rowSize + (regionInfo.x >> 3);
let i, j, mask, offset;
switch (combinationOperator) {
case 0:
for (i = 0; i < height; i++) {
mask = mask0;
offset = offset0;
for (j = 0; j < width; j++) {
if (bitmap[i][j]) {
buffer[offset] |= mask;
}
mask >>= 1;
if (!mask) {
mask = 128;
offset++;
}
}
offset0 += rowSize;
}
break;
case 2:
for (i = 0; i < height; i++) {
mask = mask0;
offset = offset0;
for (j = 0; j < width; j++) {
if (bitmap[i][j]) {
buffer[offset] ^= mask;
}
mask >>= 1;
if (!mask) {
mask = 128;
offset++;
}
}
offset0 += rowSize;
}
break;
default:
throw new Jbig2Error(`operator ${combinationOperator} is not supported`);
}
}
onImmediateGenericRegion(region, data, start, end) {
const regionInfo = region.info;
const decodingContext = new DecodingContext(data, start, end);
const bitmap = decodeBitmap(region.mmr, regionInfo.width, regionInfo.height, region.template, region.prediction, null, region.at, decodingContext);
this.drawBitmap(regionInfo, bitmap);
}
onImmediateLosslessGenericRegion() {
this.onImmediateGenericRegion(...arguments);
}
onSymbolDictionary(dictionary, currentSegment, referredSegments, data, start, end) {
let huffmanTables, huffmanInput;
if (dictionary.huffman) {
huffmanTables = getSymbolDictionaryHuffmanTables(dictionary, referredSegments, this.customTables);
huffmanInput = new Reader(data, start, end);
}
let symbols = this.symbols;
if (!symbols) {
this.symbols = symbols = {};
}
const inputSymbols = [];
for (const referredSegment of referredSegments) {
const referredSymbols = symbols[referredSegment];
if (referredSymbols) {
inputSymbols.push(...referredSymbols);
}
}
const decodingContext = new DecodingContext(data, start, end);
symbols[currentSegment] = decodeSymbolDictionary(dictionary.huffman, dictionary.refinement, inputSymbols, dictionary.numberOfNewSymbols, dictionary.numberOfExportedSymbols, huffmanTables, dictionary.template, dictionary.at, dictionary.refinementTemplate, dictionary.refinementAt, decodingContext, huffmanInput);
}
onImmediateTextRegion(region, referredSegments, data, start, end) {
const regionInfo = region.info;
let huffmanTables, huffmanInput;
const symbols = this.symbols;
const inputSymbols = [];
for (const referredSegment of referredSegments) {
const referredSymbols = symbols[referredSegment];
if (referredSymbols) {
inputSymbols.push(...referredSymbols);
}
}
const symbolCodeLength = log2(inputSymbols.length);
if (region.huffman) {
huffmanInput = new Reader(data, start, end);
huffmanTables = getTextRegionHuffmanTables(region, referredSegments, this.customTables, inputSymbols.length, huffmanInput);
}
const decodingContext = new DecodingContext(data, start, end);
const bitmap = decodeTextRegion(region.huffman, region.refinement, regionInfo.width, regionInfo.height, region.defaultPixelValue, region.numberOfSymbolInstances, region.stripSize, inputSymbols, symbolCodeLength, region.transposed, region.dsOffset, region.referenceCorner, region.combinationOperator, huffmanTables, region.refinementTemplate, region.refinementAt, decodingContext, region.logStripSize, huffmanInput);
this.drawBitmap(regionInfo, bitmap);
}
onImmediateLosslessTextRegion() {
this.onImmediateTextRegion(...arguments);
}
onPatternDictionary(dictionary, currentSegment, data, start, end) {
let patterns = this.patterns;
if (!patterns) {
this.patterns = patterns = {};
}
const decodingContext = new DecodingContext(data, start, end);
patterns[currentSegment] = decodePatternDictionary(dictionary.mmr, dictionary.patternWidth, dictionary.patternHeight, dictionary.maxPatternIndex, dictionary.template, decodingContext);
}
onImmediateHalftoneRegion(region, referredSegments, data, start, end) {
const patterns = this.patterns[referredSegments[0]];
const regionInfo = region.info;
const decodingContext = new DecodingContext(data, start, end);
const bitmap = decodeHalftoneRegion(region.mmr, patterns, region.template, regionInfo.width, regionInfo.height, region.defaultPixelValue, region.enableSkip, region.combinationOperator, region.gridWidth, region.gridHeight, region.gridOffsetX, region.gridOffsetY, region.gridVectorX, region.gridVectorY, decodingContext);
this.drawBitmap(regionInfo, bitmap);
}
onImmediateLosslessHalftoneRegion() {
this.onImmediateHalftoneRegion(...arguments);
}
onTables(currentSegment, data, start, end) {
let customTables = this.customTables;
if (!customTables) {
this.customTables = customTables = {};
}
customTables[currentSegment] = decodeTablesSegment(data, start, end);
}
}
class HuffmanLine {
constructor(lineData) {
if (lineData.length === 2) {
this.isOOB = true;
this.rangeLow = 0;
this.prefixLength = lineData[0];
this.rangeLength = 0;
this.prefixCode = lineData[1];
this.isLowerRange = false;
} else {
this.isOOB = false;
this.rangeLow = lineData[0];
this.prefixLength = lineData[1];
this.rangeLength = lineData[2];
this.prefixCode = lineData[3];
this.isLowerRange = lineData[4] === "lower";
}
}
}
class HuffmanTreeNode {
constructor(line) {
this.children = [];
if (line) {
this.isLeaf = true;
this.rangeLength = line.rangeLength;
this.rangeLow = line.rangeLow;
this.isLowerRange = line.isLowerRange;
this.isOOB = line.isOOB;
} else {
this.isLeaf = false;
}
}
buildTree(line, shift) {
const bit = line.prefixCode >> shift & 1;
if (shift <= 0) {
this.children[bit] = new HuffmanTreeNode(line);
} else {
let node = this.children[bit];
if (!node) {
this.children[bit] = node = new HuffmanTreeNode(null);
}
node.buildTree(line, shift - 1);
}
}
decodeNode(reader) {
if (this.isLeaf) {
if (this.isOOB) {
return null;
}
const htOffset = reader.readBits(this.rangeLength);
return this.rangeLow + (this.isLowerRange ? -htOffset : htOffset);
}
const node = this.children[reader.readBit()];
if (!node) {
throw new Jbig2Error("invalid Huffman data");
}
return node.decodeNode(reader);
}
}
class HuffmanTable {
constructor(lines, prefixCodesDone) {
if (!prefixCodesDone) {
this.assignPrefixCodes(lines);
}
this.rootNode = new HuffmanTreeNode(null);
for (let i = 0, ii = lines.length; i < ii; i++) {
const line = lines[i];
if (line.prefixLength > 0) {
this.rootNode.buildTree(line, line.prefixLength - 1);
}
}
}
decode(reader) {
return this.rootNode.decodeNode(reader);
}
assignPrefixCodes(lines) {
const linesLength = lines.length;
let prefixLengthMax = 0;
for (let i = 0; i < linesLength; i++) {
prefixLengthMax = Math.max(prefixLengthMax, lines[i].prefixLength);
}
const histogram = new Uint32Array(prefixLengthMax + 1);
for (let i = 0; i < linesLength; i++) {
histogram[lines[i].prefixLength]++;
}
let currentLength = 1,
firstCode = 0,
currentCode,
currentTemp,
line;
histogram[0] = 0;
while (currentLength <= prefixLengthMax) {
firstCode = firstCode + histogram[currentLength - 1] << 1;
currentCode = firstCode;
currentTemp = 0;
while (currentTemp < linesLength) {
line = lines[currentTemp];
if (line.prefixLength === currentLength) {
line.prefixCode = currentCode;
currentCode++;
}
currentTemp++;
}
currentLength++;
}
}
}
function decodeTablesSegment(data, start, end) {
const flags = data[start];
const lowestValue = readUint32(data, start + 1) & 0xffffffff;
const highestValue = readUint32(data, start + 5) & 0xffffffff;
const reader = new Reader(data, start + 9, end);
const prefixSizeBits = (flags >> 1 & 7) + 1;
const rangeSizeBits = (flags >> 4 & 7) + 1;
const lines = [];
let prefixLength,
rangeLength,
currentRangeLow = lowestValue;
do {
prefixLength = reader.readBits(prefixSizeBits);
rangeLength = reader.readBits(rangeSizeBits);
lines.push(new HuffmanLine([currentRangeLow, prefixLength, rangeLength, 0]));
currentRangeLow += 1 << rangeLength;
} while (currentRangeLow < highestValue);
prefixLength = reader.readBits(prefixSizeBits);
lines.push(new HuffmanLine([lowestValue - 1, prefixLength, 32, 0, "lower"]));
prefixLength = reader.readBits(prefixSizeBits);
lines.push(new HuffmanLine([highestValue, prefixLength, 32, 0]));
if (flags & 1) {
prefixLength = reader.readBits(prefixSizeBits);
lines.push(new HuffmanLine([prefixLength, 0]));
}
return new HuffmanTable(lines, false);
}
const standardTablesCache = {};
function getStandardTable(number) {
let table = standardTablesCache[number];
if (table) {
return table;
}
let lines;
switch (number) {
case 1:
lines = [[0, 1, 4, 0x0], [16, 2, 8, 0x2], [272, 3, 16, 0x6], [65808, 3, 32, 0x7]];
break;
case 2:
lines = [[0, 1, 0, 0x0], [1, 2, 0, 0x2], [2, 3, 0, 0x6], [3, 4, 3, 0xe], [11, 5, 6, 0x1e], [75, 6, 32, 0x3e], [6, 0x3f]];
break;
case 3:
lines = [[-256, 8, 8, 0xfe], [0, 1, 0, 0x0], [1, 2, 0, 0x2], [2, 3, 0, 0x6], [3, 4, 3, 0xe], [11, 5, 6, 0x1e], [-257, 8, 32, 0xff, "lower"], [75, 7, 32, 0x7e], [6, 0x3e]];
break;
case 4:
lines = [[1, 1, 0, 0x0], [2, 2, 0, 0x2], [3, 3, 0, 0x6], [4, 4, 3, 0xe], [12, 5, 6, 0x1e], [76, 5, 32, 0x1f]];
break;
case 5:
lines = [[-255, 7, 8, 0x7e], [1, 1, 0, 0x0], [2, 2, 0, 0x2], [3, 3, 0, 0x6], [4, 4, 3, 0xe], [12, 5, 6, 0x1e], [-256, 7, 32, 0x7f, "lower"], [76, 6, 32, 0x3e]];
break;
case 6:
lines = [[-2048, 5, 10, 0x1c], [-1024, 4, 9, 0x8], [-512, 4, 8, 0x9], [-256, 4, 7, 0xa], [-128, 5, 6, 0x1d], [-64, 5, 5, 0x1e], [-32, 4, 5, 0xb], [0, 2, 7, 0x0], [128, 3, 7, 0x2], [256, 3, 8, 0x3], [512, 4, 9, 0xc], [1024, 4, 10, 0xd], [-2049, 6, 32, 0x3e, "lower"], [2048, 6, 32, 0x3f]];
break;
case 7:
lines = [[-1024, 4, 9, 0x8], [-512, 3, 8, 0x0], [-256, 4, 7, 0x9], [-128, 5, 6, 0x1a], [-64, 5, 5, 0x1b], [-32, 4, 5, 0xa], [0, 4, 5, 0xb], [32, 5, 5, 0x1c], [64, 5, 6, 0x1d], [128, 4, 7, 0xc], [256, 3, 8, 0x1], [512, 3, 9, 0x2], [1024, 3, 10, 0x3], [-1025, 5, 32, 0x1e, "lower"], [2048, 5, 32, 0x1f]];
break;
case 8:
lines = [[-15, 8, 3, 0xfc], [-7, 9, 1, 0x1fc], [-5, 8, 1, 0xfd], [-3, 9, 0, 0x1fd], [-2, 7, 0, 0x7c], [-1, 4, 0, 0xa], [0, 2, 1, 0x0], [2, 5, 0, 0x1a], [3, 6, 0, 0x3a], [4, 3, 4, 0x4], [20, 6, 1, 0x3b], [22, 4, 4, 0xb], [38, 4, 5, 0xc], [70, 5, 6, 0x1b], [134, 5, 7, 0x1c], [262, 6, 7, 0x3c], [390, 7, 8, 0x7d], [646, 6, 10, 0x3d], [-16, 9, 32, 0x1fe, "lower"], [1670, 9, 32, 0x1ff], [2, 0x1]];
break;
case 9:
lines = [[-31, 8, 4, 0xfc], [-15, 9, 2, 0x1fc], [-11, 8, 2, 0xfd], [-7, 9, 1, 0x1fd], [-5, 7, 1, 0x7c], [-3, 4, 1, 0xa], [-1, 3, 1, 0x2], [1, 3, 1, 0x3], [3, 5, 1, 0x1a], [5, 6, 1, 0x3a], [7, 3, 5, 0x4], [39, 6, 2, 0x3b], [43, 4, 5, 0xb], [75, 4, 6, 0xc], [139, 5, 7, 0x1b], [267, 5, 8, 0x1c], [523, 6, 8, 0x3c], [779, 7, 9, 0x7d], [1291, 6, 11, 0x3d], [-32, 9, 32, 0x1fe, "lower"], [3339, 9, 32, 0x1ff], [2, 0x0]];
break;
case 10:
lines = [[-21, 7, 4, 0x7a], [-5, 8, 0, 0xfc], [-4, 7, 0, 0x7b], [-3, 5, 0, 0x18], [-2, 2, 2, 0x0], [2, 5, 0, 0x19], [3, 6, 0, 0x36], [4, 7, 0, 0x7c], [5, 8, 0, 0xfd], [6, 2, 6, 0x1], [70, 5, 5, 0x1a], [102, 6, 5, 0x37], [134, 6, 6, 0x38], [198, 6, 7, 0x39], [326, 6, 8, 0x3a], [582, 6, 9, 0x3b], [1094, 6, 10, 0x3c], [2118, 7, 11, 0x7d], [-22, 8, 32, 0xfe, "lower"], [4166, 8, 32, 0xff], [2, 0x2]];
break;
case 11:
lines = [[1, 1, 0, 0x0], [2, 2, 1, 0x2], [4, 4, 0, 0xc], [5, 4, 1, 0xd], [7, 5, 1, 0x1c], [9, 5, 2, 0x1d], [13, 6, 2, 0x3c], [17, 7, 2, 0x7a], [21, 7, 3, 0x7b], [29, 7, 4, 0x7c], [45, 7, 5, 0x7d], [77, 7, 6, 0x7e], [141, 7, 32, 0x7f]];
break;
case 12:
lines = [[1, 1, 0, 0x0], [2, 2, 0, 0x2], [3, 3, 1, 0x6], [5, 5, 0, 0x1c], [6, 5, 1, 0x1d], [8, 6, 1, 0x3c], [10, 7, 0, 0x7a], [11, 7, 1, 0x7b], [13, 7, 2, 0x7c], [17, 7, 3, 0x7d], [25, 7, 4, 0x7e], [41, 8, 5, 0xfe], [73, 8, 32, 0xff]];
break;
case 13:
lines = [[1, 1, 0, 0x0], [2, 3, 0, 0x4], [3, 4, 0, 0xc], [4, 5, 0, 0x1c], [5, 4, 1, 0xd], [7, 3, 3, 0x5], [15, 6, 1, 0x3a], [17, 6, 2, 0x3b], [21, 6, 3, 0x3c], [29, 6, 4, 0x3d], [45, 6, 5, 0x3e], [77, 7, 6, 0x7e], [141, 7, 32, 0x7f]];
break;
case 14:
lines = [[-2, 3, 0, 0x4], [-1, 3, 0, 0x5], [0, 1, 0, 0x0], [1, 3, 0, 0x6], [2, 3, 0, 0x7]];
break;
case 15:
lines = [[-24, 7, 4, 0x7c], [-8, 6, 2, 0x3c], [-4, 5, 1, 0x1c], [-2, 4, 0, 0xc], [-1, 3, 0, 0x4], [0, 1, 0, 0x0], [1, 3, 0, 0x5], [2, 4, 0, 0xd], [3, 5, 1, 0x1d], [5, 6, 2, 0x3d], [9, 7, 4, 0x7d], [-25, 7, 32, 0x7e, "lower"], [25, 7, 32, 0x7f]];
break;
default:
throw new Jbig2Error(`standard table B.${number} does not exist`);
}
for (let i = 0, ii = lines.length; i < ii; i++) {
lines[i] = new HuffmanLine(lines[i]);
}
table = new HuffmanTable(lines, true);
standardTablesCache[number] = table;
return table;
}
class Reader {
constructor(data, start, end) {
this.data = data;
this.start = start;
this.end = end;
this.position = start;
this.shift = -1;
this.currentByte = 0;
}
readBit() {
if (this.shift < 0) {
if (this.position >= this.end) {
throw new Jbig2Error("end of data while reading bit");
}
this.currentByte = this.data[this.position++];
this.shift = 7;
}
const bit = this.currentByte >> this.shift & 1;
this.shift--;
return bit;
}
readBits(numBits) {
let result = 0,
i;
for (i = numBits - 1; i >= 0; i--) {
result |= this.readBit() << i;
}
return result;
}
byteAlign() {
this.shift = -1;
}
next() {
if (this.position >= this.end) {
return -1;
}
return this.data[this.position++];
}
}
function getCustomHuffmanTable(index, referredTo, customTables) {
let currentIndex = 0;
for (let i = 0, ii = referredTo.length; i < ii; i++) {
const table = customTables[referredTo[i]];
if (table) {
if (index === currentIndex) {
return table;
}
currentIndex++;
}
}
throw new Jbig2Error("can't find custom Huffman table");
}
function getTextRegionHuffmanTables(textRegion, referredTo, customTables, numberOfSymbols, reader) {
const codes = [];
for (let i = 0; i <= 34; i++) {
const codeLength = reader.readBits(4);
codes.push(new HuffmanLine([i, codeLength, 0, 0]));
}
const runCodesTable = new HuffmanTable(codes, false);
codes.length = 0;
for (let i = 0; i < numberOfSymbols;) {
const codeLength = runCodesTable.decode(reader);
if (codeLength >= 32) {
let repeatedLength, numberOfRepeats, j;
switch (codeLength) {
case 32:
if (i === 0) {
throw new Jbig2Error("no previous value in symbol ID table");
}
numberOfRepeats = reader.readBits(2) + 3;
repeatedLength = codes[i - 1].prefixLength;
break;
case 33:
numberOfRepeats = reader.readBits(3) + 3;
repeatedLength = 0;
break;
case 34:
numberOfRepeats = reader.readBits(7) + 11;
repeatedLength = 0;
break;
default:
throw new Jbig2Error("invalid code length in symbol ID table");
}
for (j = 0; j < numberOfRepeats; j++) {
codes.push(new HuffmanLine([i, repeatedLength, 0, 0]));
i++;
}
} else {
codes.push(new HuffmanLine([i, codeLength, 0, 0]));
i++;
}
}
reader.byteAlign();
const symbolIDTable = new HuffmanTable(codes, false);
let customIndex = 0,
tableFirstS,
tableDeltaS,
tableDeltaT;
switch (textRegion.huffmanFS) {
case 0:
case 1:
tableFirstS = getStandardTable(textRegion.huffmanFS + 6);
break;
case 3:
tableFirstS = getCustomHuffmanTable(customIndex, referredTo, customTables);
customIndex++;
break;
default:
throw new Jbig2Error("invalid Huffman FS selector");
}
switch (textRegion.huffmanDS) {
case 0:
case 1:
case 2:
tableDeltaS = getStandardTable(textRegion.huffmanDS + 8);
break;
case 3:
tableDeltaS = getCustomHuffmanTable(customIndex, referredTo, customTables);
customIndex++;
break;
default:
throw new Jbig2Error("invalid Huffman DS selector");
}
switch (textRegion.huffmanDT) {
case 0:
case 1:
case 2:
tableDeltaT = getStandardTable(textRegion.huffmanDT + 11);
break;
case 3:
tableDeltaT = getCustomHuffmanTable(customIndex, referredTo, customTables);
customIndex++;
break;
default:
throw new Jbig2Error("invalid Huffman DT selector");
}
if (textRegion.refinement) {
throw new Jbig2Error("refinement with Huffman is not supported");
}
return {
symbolIDTable,
tableFirstS,
tableDeltaS,
tableDeltaT
};
}
function getSymbolDictionaryHuffmanTables(dictionary, referredTo, customTables) {
let customIndex = 0,
tableDeltaHeight,
tableDeltaWidth;
switch (dictionary.huffmanDHSelector) {
case 0:
case 1:
tableDeltaHeight = getStandardTable(dictionary.huffmanDHSelector + 4);
break;
case 3:
tableDeltaHeight = getCustomHuffmanTable(customIndex, referredTo, customTables);
customIndex++;
break;
default:
throw new Jbig2Error("invalid Huffman DH selector");
}
switch (dictionary.huffmanDWSelector) {
case 0:
case 1:
tableDeltaWidth = getStandardTable(dictionary.huffmanDWSelector + 2);
break;
case 3:
tableDeltaWidth = getCustomHuffmanTable(customIndex, referredTo, customTables);
customIndex++;
break;
default:
throw new Jbig2Error("invalid Huffman DW selector");
}
let tableBitmapSize, tableAggregateInstances;
if (dictionary.bitmapSizeSelector) {
tableBitmapSize = getCustomHuffmanTable(customIndex, referredTo, customTables);
customIndex++;
} else {
tableBitmapSize = getStandardTable(1);
}
if (dictionary.aggregationInstancesSelector) {
tableAggregateInstances = getCustomHuffmanTable(customIndex, referredTo, customTables);
} else {
tableAggregateInstances = getStandardTable(1);
}
return {
tableDeltaHeight,
tableDeltaWidth,
tableBitmapSize,
tableAggregateInstances
};
}
function readUncompressedBitmap(reader, width, height) {
const bitmap = [];
for (let y = 0; y < height; y++) {
const row = new Uint8Array(width);
bitmap.push(row);
for (let x = 0; x < width; x++) {
row[x] = reader.readBit();
}
reader.byteAlign();
}
return bitmap;
}
function decodeMMRBitmap(input, width, height, endOfBlock) {
const params = {
K: -1,
Columns: width,
Rows: height,
BlackIs1: true,
EndOfBlock: endOfBlock
};
const decoder = new CCITTFaxDecoder(input, params);
const bitmap = [];
let currentByte,
eof = false;
for (let y = 0; y < height; y++) {
const row = new Uint8Array(width);
bitmap.push(row);
let shift = -1;
for (let x = 0; x < width; x++) {
if (shift < 0) {
currentByte = decoder.readNextChar();
if (currentByte === -1) {
currentByte = 0;
eof = true;
}
shift = 7;
}
row[x] = currentByte >> shift & 1;
shift--;
}
}
if (endOfBlock && !eof) {
const lookForEOFLimit = 5;
for (let i = 0; i < lookForEOFLimit; i++) {
if (decoder.readNextChar() === -1) {
break;
}
}
}
return bitmap;
}
class Jbig2Image {
parseChunks(chunks) {
return parseJbig2Chunks(chunks);
}
parse(data) {
throw new Error("Not implemented: Jbig2Image.parse");
}
}
;// ./src/core/jbig2_stream.js
class Jbig2Stream extends DecodeStream {
constructor(stream, maybeLength, params) {
super(maybeLength);
this.stream = stream;
this.dict = stream.dict;
this.maybeLength = maybeLength;
this.params = params;
}
get bytes() {
return shadow(this, "bytes", this.stream.getBytes(this.maybeLength));
}
ensureBuffer(requested) {}
readBlock() {
this.decodeImage();
}
decodeImage(bytes) {
if (this.eof) {
return this.buffer;
}
bytes ||= this.bytes;
const jbig2Image = new Jbig2Image();
const chunks = [];
if (this.params instanceof Dict) {
const globalsStream = this.params.get("JBIG2Globals");
if (globalsStream instanceof BaseStream) {
const globals = globalsStream.getBytes();
chunks.push({
data: globals,
start: 0,
end: globals.length
});
}
}
chunks.push({
data: bytes,
start: 0,
end: bytes.length
});
const data = jbig2Image.parseChunks(chunks);
const dataLength = data.length;
for (let i = 0; i < dataLength; i++) {
data[i] ^= 0xff;
}
this.buffer = data;
this.bufferLength = dataLength;
this.eof = true;
return this.buffer;
}
get canAsyncDecodeImageFromBuffer() {
return this.stream.isAsync;
}
}
;// ./src/core/jpx_stream.js
class JpxStream extends DecodeStream {
constructor(stream, maybeLength, params) {
super(maybeLength);
this.stream = stream;
this.dict = stream.dict;
this.maybeLength = maybeLength;
this.params = params;
}
get bytes() {
return shadow(this, "bytes", this.stream.getBytes(this.maybeLength));
}
ensureBuffer(requested) {}
readBlock(decoderOptions) {
unreachable("JpxStream.readBlock");
}
get isAsyncDecoder() {
return true;
}
async decodeImage(bytes, decoderOptions) {
if (this.eof) {
return this.buffer;
}
bytes ||= this.bytes;
this.buffer = await JpxImage.decode(bytes, decoderOptions);
this.bufferLength = this.buffer.length;
this.eof = true;
return this.buffer;
}
get canAsyncDecodeImageFromBuffer() {
return this.stream.isAsync;
}
}
;// ./src/core/lzw_stream.js
class LZWStream extends DecodeStream {
constructor(str, maybeLength, earlyChange) {
super(maybeLength);
this.str = str;
this.dict = str.dict;
this.cachedData = 0;
this.bitsCached = 0;
const maxLzwDictionarySize = 4096;
const lzwState = {
earlyChange,
codeLength: 9,
nextCode: 258,
dictionaryValues: new Uint8Array(maxLzwDictionarySize),
dictionaryLengths: new Uint16Array(maxLzwDictionarySize),
dictionaryPrevCodes: new Uint16Array(maxLzwDictionarySize),
currentSequence: new Uint8Array(maxLzwDictionarySize),
currentSequenceLength: 0
};
for (let i = 0; i < 256; ++i) {
lzwState.dictionaryValues[i] = i;
lzwState.dictionaryLengths[i] = 1;
}
this.lzwState = lzwState;
}
readBits(n) {
let bitsCached = this.bitsCached;
let cachedData = this.cachedData;
while (bitsCached < n) {
const c = this.str.getByte();
if (c === -1) {
this.eof = true;
return null;
}
cachedData = cachedData << 8 | c;
bitsCached += 8;
}
this.bitsCached = bitsCached -= n;
this.cachedData = cachedData;
this.lastCode = null;
return cachedData >>> bitsCached & (1 << n) - 1;
}
readBlock() {
const blockSize = 512,
decodedSizeDelta = blockSize;
let estimatedDecodedSize = blockSize * 2;
let i, j, q;
const lzwState = this.lzwState;
if (!lzwState) {
return;
}
const earlyChange = lzwState.earlyChange;
let nextCode = lzwState.nextCode;
const dictionaryValues = lzwState.dictionaryValues;
const dictionaryLengths = lzwState.dictionaryLengths;
const dictionaryPrevCodes = lzwState.dictionaryPrevCodes;
let codeLength = lzwState.codeLength;
let prevCode = lzwState.prevCode;
const currentSequence = lzwState.currentSequence;
let currentSequenceLength = lzwState.currentSequenceLength;
let decodedLength = 0;
let currentBufferLength = this.bufferLength;
let buffer = this.ensureBuffer(this.bufferLength + estimatedDecodedSize);
for (i = 0; i < blockSize; i++) {
const code = this.readBits(codeLength);
const hasPrev = currentSequenceLength > 0;
if (code < 256) {
currentSequence[0] = code;
currentSequenceLength = 1;
} else if (code >= 258) {
if (code < nextCode) {
currentSequenceLength = dictionaryLengths[code];
for (j = currentSequenceLength - 1, q = code; j >= 0; j--) {
currentSequence[j] = dictionaryValues[q];
q = dictionaryPrevCodes[q];
}
} else {
currentSequence[currentSequenceLength++] = currentSequence[0];
}
} else if (code === 256) {
codeLength = 9;
nextCode = 258;
currentSequenceLength = 0;
continue;
} else {
this.eof = true;
delete this.lzwState;
break;
}
if (hasPrev) {
dictionaryPrevCodes[nextCode] = prevCode;
dictionaryLengths[nextCode] = dictionaryLengths[prevCode] + 1;
dictionaryValues[nextCode] = currentSequence[0];
nextCode++;
codeLength = nextCode + earlyChange & nextCode + earlyChange - 1 ? codeLength : Math.min(Math.log(nextCode + earlyChange) / 0.6931471805599453 + 1, 12) | 0;
}
prevCode = code;
decodedLength += currentSequenceLength;
if (estimatedDecodedSize < decodedLength) {
do {
estimatedDecodedSize += decodedSizeDelta;
} while (estimatedDecodedSize < decodedLength);
buffer = this.ensureBuffer(this.bufferLength + estimatedDecodedSize);
}
for (j = 0; j < currentSequenceLength; j++) {
buffer[currentBufferLength++] = currentSequence[j];
}
}
lzwState.nextCode = nextCode;
lzwState.codeLength = codeLength;
lzwState.prevCode = prevCode;
lzwState.currentSequenceLength = currentSequenceLength;
this.bufferLength = currentBufferLength;
}
}
;// ./src/core/predictor_stream.js
class PredictorStream extends DecodeStream {
constructor(str, maybeLength, params) {
super(maybeLength);
if (!(params instanceof Dict)) {
return str;
}
const predictor = this.predictor = params.get("Predictor") || 1;
if (predictor <= 1) {
return str;
}
if (predictor !== 2 && (predictor < 10 || predictor > 15)) {
throw new FormatError(`Unsupported predictor: ${predictor}`);
}
this.readBlock = predictor === 2 ? this.readBlockTiff : this.readBlockPng;
this.str = str;
this.dict = str.dict;
const colors = this.colors = params.get("Colors") || 1;
const bits = this.bits = params.get("BPC", "BitsPerComponent") || 8;
const columns = this.columns = params.get("Columns") || 1;
this.pixBytes = colors * bits + 7 >> 3;
this.rowBytes = columns * colors * bits + 7 >> 3;
return this;
}
readBlockTiff() {
const rowBytes = this.rowBytes;
const bufferLength = this.bufferLength;
const buffer = this.ensureBuffer(bufferLength + rowBytes);
const bits = this.bits;
const colors = this.colors;
const rawBytes = this.str.getBytes(rowBytes);
this.eof = !rawBytes.length;
if (this.eof) {
return;
}
let inbuf = 0,
outbuf = 0;
let inbits = 0,
outbits = 0;
let pos = bufferLength;
let i;
if (bits === 1 && colors === 1) {
for (i = 0; i < rowBytes; ++i) {
let c = rawBytes[i] ^ inbuf;
c ^= c >> 1;
c ^= c >> 2;
c ^= c >> 4;
inbuf = (c & 1) << 7;
buffer[pos++] = c;
}
} else if (bits === 8) {
for (i = 0; i < colors; ++i) {
buffer[pos++] = rawBytes[i];
}
for (; i < rowBytes; ++i) {
buffer[pos] = buffer[pos - colors] + rawBytes[i];
pos++;
}
} else if (bits === 16) {
const bytesPerPixel = colors * 2;
for (i = 0; i < bytesPerPixel; ++i) {
buffer[pos++] = rawBytes[i];
}
for (; i < rowBytes; i += 2) {
const sum = ((rawBytes[i] & 0xff) << 8) + (rawBytes[i + 1] & 0xff) + ((buffer[pos - bytesPerPixel] & 0xff) << 8) + (buffer[pos - bytesPerPixel + 1] & 0xff);
buffer[pos++] = sum >> 8 & 0xff;
buffer[pos++] = sum & 0xff;
}
} else {
const compArray = new Uint8Array(colors + 1);
const bitMask = (1 << bits) - 1;
let j = 0,
k = bufferLength;
const columns = this.columns;
for (i = 0; i < columns; ++i) {
for (let kk = 0; kk < colors; ++kk) {
if (inbits < bits) {
inbuf = inbuf << 8 | rawBytes[j++] & 0xff;
inbits += 8;
}
compArray[kk] = compArray[kk] + (inbuf >> inbits - bits) & bitMask;
inbits -= bits;
outbuf = outbuf << bits | compArray[kk];
outbits += bits;
if (outbits >= 8) {
buffer[k++] = outbuf >> outbits - 8 & 0xff;
outbits -= 8;
}
}
}
if (outbits > 0) {
buffer[k++] = (outbuf << 8 - outbits) + (inbuf & (1 << 8 - outbits) - 1);
}
}
this.bufferLength += rowBytes;
}
readBlockPng() {
const rowBytes = this.rowBytes;
const pixBytes = this.pixBytes;
const predictor = this.str.getByte();
const rawBytes = this.str.getBytes(rowBytes);
this.eof = !rawBytes.length;
if (this.eof) {
return;
}
const bufferLength = this.bufferLength;
const buffer = this.ensureBuffer(bufferLength + rowBytes);
let prevRow = buffer.subarray(bufferLength - rowBytes, bufferLength);
if (prevRow.length === 0) {
prevRow = new Uint8Array(rowBytes);
}
let i,
j = bufferLength,
up,
c;
switch (predictor) {
case 0:
for (i = 0; i < rowBytes; ++i) {
buffer[j++] = rawBytes[i];
}
break;
case 1:
for (i = 0; i < pixBytes; ++i) {
buffer[j++] = rawBytes[i];
}
for (; i < rowBytes; ++i) {
buffer[j] = buffer[j - pixBytes] + rawBytes[i] & 0xff;
j++;
}
break;
case 2:
for (i = 0; i < rowBytes; ++i) {
buffer[j++] = prevRow[i] + rawBytes[i] & 0xff;
}
break;
case 3:
for (i = 0; i < pixBytes; ++i) {
buffer[j++] = (prevRow[i] >> 1) + rawBytes[i];
}
for (; i < rowBytes; ++i) {
buffer[j] = (prevRow[i] + buffer[j - pixBytes] >> 1) + rawBytes[i] & 0xff;
j++;
}
break;
case 4:
for (i = 0; i < pixBytes; ++i) {
up = prevRow[i];
c = rawBytes[i];
buffer[j++] = up + c;
}
for (; i < rowBytes; ++i) {
up = prevRow[i];
const upLeft = prevRow[i - pixBytes];
const left = buffer[j - pixBytes];
const p = left + up - upLeft;
let pa = p - left;
if (pa < 0) {
pa = -pa;
}
let pb = p - up;
if (pb < 0) {
pb = -pb;
}
let pc = p - upLeft;
if (pc < 0) {
pc = -pc;
}
c = rawBytes[i];
if (pa <= pb && pa <= pc) {
buffer[j++] = left + c;
} else if (pb <= pc) {
buffer[j++] = up + c;
} else {
buffer[j++] = upLeft + c;
}
}
break;
default:
throw new FormatError(`Unsupported predictor: ${predictor}`);
}
this.bufferLength += rowBytes;
}
}
;// ./src/core/run_length_stream.js
class RunLengthStream extends DecodeStream {
constructor(str, maybeLength) {
super(maybeLength);
this.str = str;
this.dict = str.dict;
}
readBlock() {
const repeatHeader = this.str.getBytes(2);
if (!repeatHeader || repeatHeader.length < 2 || repeatHeader[0] === 128) {
this.eof = true;
return;
}
let buffer;
let bufferLength = this.bufferLength;
let n = repeatHeader[0];
if (n < 128) {
buffer = this.ensureBuffer(bufferLength + n + 1);
buffer[bufferLength++] = repeatHeader[1];
if (n > 0) {
const source = this.str.getBytes(n);
buffer.set(source, bufferLength);
bufferLength += n;
}
} else {
n = 257 - n;
buffer = this.ensureBuffer(bufferLength + n + 1);
buffer.fill(repeatHeader[1], bufferLength, bufferLength + n);
bufferLength += n;
}
this.bufferLength = bufferLength;
}
}
;// ./src/core/parser.js
const MAX_LENGTH_TO_CACHE = 1000;
function getInlineImageCacheKey(bytes) {
const strBuf = [],
ii = bytes.length;
let i = 0;
while (i < ii - 1) {
strBuf.push(bytes[i++] << 8 | bytes[i++]);
}
if (i < ii) {
strBuf.push(bytes[i]);
}
return ii + "_" + String.fromCharCode.apply(null, strBuf);
}
class Parser {
constructor({
lexer,
xref,
allowStreams = false,
recoveryMode = false
}) {
this.lexer = lexer;
this.xref = xref;
this.allowStreams = allowStreams;
this.recoveryMode = recoveryMode;
this.imageCache = Object.create(null);
this._imageId = 0;
this.refill();
}
refill() {
this.buf1 = this.lexer.getObj();
this.buf2 = this.lexer.getObj();
}
shift() {
if (this.buf2 instanceof Cmd && this.buf2.cmd === "ID") {
this.buf1 = this.buf2;
this.buf2 = null;
} else {
this.buf1 = this.buf2;
this.buf2 = this.lexer.getObj();
}
}
tryShift() {
try {
this.shift();
return true;
} catch (e) {
if (e instanceof MissingDataException) {
throw e;
}
return false;
}
}
getObj(cipherTransform = null) {
const buf1 = this.buf1;
this.shift();
if (buf1 instanceof Cmd) {
switch (buf1.cmd) {
case "BI":
return this.makeInlineImage(cipherTransform);
case "[":
const array = [];
while (!isCmd(this.buf1, "]") && this.buf1 !== EOF) {
array.push(this.getObj(cipherTransform));
}
if (this.buf1 === EOF) {
if (this.recoveryMode) {
return array;
}
throw new ParserEOFException("End of file inside array.");
}
this.shift();
return array;
case "<<":
const dict = new Dict(this.xref);
while (!isCmd(this.buf1, ">>") && this.buf1 !== EOF) {
if (!(this.buf1 instanceof Name)) {
info("Malformed dictionary: key must be a name object");
this.shift();
continue;
}
const key = this.buf1.name;
this.shift();
if (this.buf1 === EOF) {
break;
}
dict.set(key, this.getObj(cipherTransform));
}
if (this.buf1 === EOF) {
if (this.recoveryMode) {
return dict;
}
throw new ParserEOFException("End of file inside dictionary.");
}
if (isCmd(this.buf2, "stream")) {
return this.allowStreams ? this.makeStream(dict, cipherTransform) : dict;
}
this.shift();
return dict;
default:
return buf1;
}
}
if (Number.isInteger(buf1)) {
if (Number.isInteger(this.buf1) && isCmd(this.buf2, "R")) {
const ref = Ref.get(buf1, this.buf1);
this.shift();
this.shift();
return ref;
}
return buf1;
}
if (typeof buf1 === "string") {
if (cipherTransform) {
return cipherTransform.decryptString(buf1);
}
return buf1;
}
return buf1;
}
findDefaultInlineStreamEnd(stream) {
const E = 0x45,
I = 0x49,
SPACE = 0x20,
LF = 0xa,
CR = 0xd,
NUL = 0x0;
const {
knownCommands
} = this.lexer,
startPos = stream.pos,
n = 15;
let state = 0,
ch,
maybeEIPos;
while ((ch = stream.getByte()) !== -1) {
if (state === 0) {
state = ch === E ? 1 : 0;
} else if (state === 1) {
state = ch === I ? 2 : 0;
} else {
if (ch === SPACE || ch === LF || ch === CR) {
maybeEIPos = stream.pos;
const followingBytes = stream.peekBytes(n);
const ii = followingBytes.length;
if (ii === 0) {
break;
}
for (let i = 0; i < ii; i++) {
ch = followingBytes[i];
if (ch === NUL && followingBytes[i + 1] !== NUL) {
continue;
}
if (ch !== LF && ch !== CR && (ch < SPACE || ch > 0x7f)) {
state = 0;
break;
}
}
if (state !== 2) {
continue;
}
if (!knownCommands) {
warn("findDefaultInlineStreamEnd - `lexer.knownCommands` is undefined.");
continue;
}
const tmpLexer = new Lexer(new Stream(stream.peekBytes(5 * n)), knownCommands);
tmpLexer._hexStringWarn = () => {};
let numArgs = 0;
while (true) {
const nextObj = tmpLexer.getObj();
if (nextObj === EOF) {
state = 0;
break;
}
if (nextObj instanceof Cmd) {
const knownCommand = knownCommands[nextObj.cmd];
if (!knownCommand) {
state = 0;
break;
} else if (knownCommand.variableArgs ? numArgs <= knownCommand.numArgs : numArgs === knownCommand.numArgs) {
break;
}
numArgs = 0;
continue;
}
numArgs++;
}
if (state === 2) {
break;
}
} else {
state = 0;
}
}
}
if (ch === -1) {
warn("findDefaultInlineStreamEnd: " + "Reached the end of the stream without finding a valid EI marker");
if (maybeEIPos) {
warn('... trying to recover by using the last "EI" occurrence.');
stream.skip(-(stream.pos - maybeEIPos));
}
}
let endOffset = 4;
stream.skip(-endOffset);
ch = stream.peekByte();
stream.skip(endOffset);
if (!isWhiteSpace(ch)) {
endOffset--;
}
return stream.pos - endOffset - startPos;
}
findDCTDecodeInlineStreamEnd(stream) {
const startPos = stream.pos;
let foundEOI = false,
b,
markerLength;
while ((b = stream.getByte()) !== -1) {
if (b !== 0xff) {
continue;
}
switch (stream.getByte()) {
case 0x00:
break;
case 0xff:
stream.skip(-1);
break;
case 0xd9:
foundEOI = true;
break;
case 0xc0:
case 0xc1:
case 0xc2:
case 0xc3:
case 0xc5:
case 0xc6:
case 0xc7:
case 0xc9:
case 0xca:
case 0xcb:
case 0xcd:
case 0xce:
case 0xcf:
case 0xc4:
case 0xcc:
case 0xda:
case 0xdb:
case 0xdc:
case 0xdd:
case 0xde:
case 0xdf:
case 0xe0:
case 0xe1:
case 0xe2:
case 0xe3:
case 0xe4:
case 0xe5:
case 0xe6:
case 0xe7:
case 0xe8:
case 0xe9:
case 0xea:
case 0xeb:
case 0xec:
case 0xed:
case 0xee:
case 0xef:
case 0xfe:
markerLength = stream.getUint16();
if (markerLength > 2) {
stream.skip(markerLength - 2);
} else {
stream.skip(-2);
}
break;
}
if (foundEOI) {
break;
}
}
const length = stream.pos - startPos;
if (b === -1) {
warn("Inline DCTDecode image stream: " + "EOI marker not found, searching for /EI/ instead.");
stream.skip(-length);
return this.findDefaultInlineStreamEnd(stream);
}
this.inlineStreamSkipEI(stream);
return length;
}
findASCII85DecodeInlineStreamEnd(stream) {
const TILDE = 0x7e,
GT = 0x3e;
const startPos = stream.pos;
let ch;
while ((ch = stream.getByte()) !== -1) {
if (ch === TILDE) {
const tildePos = stream.pos;
ch = stream.peekByte();
while (isWhiteSpace(ch)) {
stream.skip();
ch = stream.peekByte();
}
if (ch === GT) {
stream.skip();
break;
}
if (stream.pos > tildePos) {
const maybeEI = stream.peekBytes(2);
if (maybeEI[0] === 0x45 && maybeEI[1] === 0x49) {
break;
}
}
}
}
const length = stream.pos - startPos;
if (ch === -1) {
warn("Inline ASCII85Decode image stream: " + "EOD marker not found, searching for /EI/ instead.");
stream.skip(-length);
return this.findDefaultInlineStreamEnd(stream);
}
this.inlineStreamSkipEI(stream);
return length;
}
findASCIIHexDecodeInlineStreamEnd(stream) {
const GT = 0x3e;
const startPos = stream.pos;
let ch;
while ((ch = stream.getByte()) !== -1) {
if (ch === GT) {
break;
}
}
const length = stream.pos - startPos;
if (ch === -1) {
warn("Inline ASCIIHexDecode image stream: " + "EOD marker not found, searching for /EI/ instead.");
stream.skip(-length);
return this.findDefaultInlineStreamEnd(stream);
}
this.inlineStreamSkipEI(stream);
return length;
}
inlineStreamSkipEI(stream) {
const E = 0x45,
I = 0x49;
let state = 0,
ch;
while ((ch = stream.getByte()) !== -1) {
if (state === 0) {
state = ch === E ? 1 : 0;
} else if (state === 1) {
state = ch === I ? 2 : 0;
} else if (state === 2) {
break;
}
}
}
makeInlineImage(cipherTransform) {
const lexer = this.lexer;
const stream = lexer.stream;
const dictMap = Object.create(null);
let dictLength;
while (!isCmd(this.buf1, "ID") && this.buf1 !== EOF) {
if (!(this.buf1 instanceof Name)) {
throw new FormatError("Dictionary key must be a name object");
}
const key = this.buf1.name;
this.shift();
if (this.buf1 === EOF) {
break;
}
dictMap[key] = this.getObj(cipherTransform);
}
if (lexer.beginInlineImagePos !== -1) {
dictLength = stream.pos - lexer.beginInlineImagePos;
}
const filter = this.xref.fetchIfRef(dictMap.F || dictMap.Filter);
let filterName;
if (filter instanceof Name) {
filterName = filter.name;
} else if (Array.isArray(filter)) {
const filterZero = this.xref.fetchIfRef(filter[0]);
if (filterZero instanceof Name) {
filterName = filterZero.name;
}
}
const startPos = stream.pos;
let length;
switch (filterName) {
case "DCT":
case "DCTDecode":
length = this.findDCTDecodeInlineStreamEnd(stream);
break;
case "A85":
case "ASCII85Decode":
length = this.findASCII85DecodeInlineStreamEnd(stream);
break;
case "AHx":
case "ASCIIHexDecode":
length = this.findASCIIHexDecodeInlineStreamEnd(stream);
break;
default:
length = this.findDefaultInlineStreamEnd(stream);
}
let cacheKey;
if (length < MAX_LENGTH_TO_CACHE && dictLength > 0) {
const initialStreamPos = stream.pos;
stream.pos = lexer.beginInlineImagePos;
cacheKey = getInlineImageCacheKey(stream.getBytes(dictLength + length));
stream.pos = initialStreamPos;
const cacheEntry = this.imageCache[cacheKey];
if (cacheEntry !== undefined) {
this.buf2 = Cmd.get("EI");
this.shift();
cacheEntry.reset();
return cacheEntry;
}
}
const dict = new Dict(this.xref);
for (const key in dictMap) {
dict.set(key, dictMap[key]);
}
let imageStream = stream.makeSubStream(startPos, length, dict);
if (cipherTransform) {
imageStream = cipherTransform.createStream(imageStream, length);
}
imageStream = this.filter(imageStream, dict, length);
imageStream.dict = dict;
if (cacheKey !== undefined) {
imageStream.cacheKey = `inline_img_${++this._imageId}`;
this.imageCache[cacheKey] = imageStream;
}
this.buf2 = Cmd.get("EI");
this.shift();
return imageStream;
}
#findStreamLength(startPos) {
const {
stream
} = this.lexer;
stream.pos = startPos;
const SCAN_BLOCK_LENGTH = 2048;
const signatureLength = "endstream".length;
const END_SIGNATURE = new Uint8Array([0x65, 0x6e, 0x64]);
const endLength = END_SIGNATURE.length;
const PARTIAL_SIGNATURE = [new Uint8Array([0x73, 0x74, 0x72, 0x65, 0x61, 0x6d]), new Uint8Array([0x73, 0x74, 0x65, 0x61, 0x6d]), new Uint8Array([0x73, 0x74, 0x72, 0x65, 0x61])];
const normalLength = signatureLength - endLength;
while (stream.pos < stream.end) {
const scanBytes = stream.peekBytes(SCAN_BLOCK_LENGTH);
const scanLength = scanBytes.length - signatureLength;
if (scanLength <= 0) {
break;
}
let pos = 0;
while (pos < scanLength) {
let j = 0;
while (j < endLength && scanBytes[pos + j] === END_SIGNATURE[j]) {
j++;
}
if (j >= endLength) {
let found = false;
for (const part of PARTIAL_SIGNATURE) {
const partLen = part.length;
let k = 0;
while (k < partLen && scanBytes[pos + j + k] === part[k]) {
k++;
}
if (k >= normalLength) {
found = true;
break;
}
if (k >= partLen) {
const lastByte = scanBytes[pos + j + k];
if (isWhiteSpace(lastByte)) {
info(`Found "${bytesToString([...END_SIGNATURE, ...part])}" when ` + "searching for endstream command.");
found = true;
}
break;
}
}
if (found) {
stream.pos += pos;
return stream.pos - startPos;
}
}
pos++;
}
stream.pos += scanLength;
}
return -1;
}
makeStream(dict, cipherTransform) {
const lexer = this.lexer;
let stream = lexer.stream;
lexer.skipToNextLine();
const startPos = stream.pos - 1;
let length = dict.get("Length");
if (!Number.isInteger(length)) {
info(`Bad length "${length && length.toString()}" in stream.`);
length = 0;
}
stream.pos = startPos + length;
lexer.nextChar();
if (this.tryShift() && isCmd(this.buf2, "endstream")) {
this.shift();
} else {
length = this.#findStreamLength(startPos);
if (length < 0) {
throw new FormatError("Missing endstream command.");
}
lexer.nextChar();
this.shift();
this.shift();
}
this.shift();
stream = stream.makeSubStream(startPos, length, dict);
if (cipherTransform) {
stream = cipherTransform.createStream(stream, length);
}
stream = this.filter(stream, dict, length);
stream.dict = dict;
return stream;
}
filter(stream, dict, length) {
let filter = dict.get("F", "Filter");
let params = dict.get("DP", "DecodeParms");
if (filter instanceof Name) {
if (Array.isArray(params)) {
warn("/DecodeParms should not be an Array, when /Filter is a Name.");
}
return this.makeFilter(stream, filter.name, length, params);
}
let maybeLength = length;
if (Array.isArray(filter)) {
const filterArray = filter;
const paramsArray = params;
for (let i = 0, ii = filterArray.length; i < ii; ++i) {
filter = this.xref.fetchIfRef(filterArray[i]);
if (!(filter instanceof Name)) {
throw new FormatError(`Bad filter name "${filter}"`);
}
params = null;
if (Array.isArray(paramsArray) && i in paramsArray) {
params = this.xref.fetchIfRef(paramsArray[i]);
}
stream = this.makeFilter(stream, filter.name, maybeLength, params);
maybeLength = null;
}
}
return stream;
}
makeFilter(stream, name, maybeLength, params) {
if (maybeLength === 0) {
warn(`Empty "${name}" stream.`);
return new NullStream();
}
try {
switch (name) {
case "Fl":
case "FlateDecode":
if (params) {
return new PredictorStream(new FlateStream(stream, maybeLength), maybeLength, params);
}
return new FlateStream(stream, maybeLength);
case "LZW":
case "LZWDecode":
let earlyChange = 1;
if (params) {
if (params.has("EarlyChange")) {
earlyChange = params.get("EarlyChange");
}
return new PredictorStream(new LZWStream(stream, maybeLength, earlyChange), maybeLength, params);
}
return new LZWStream(stream, maybeLength, earlyChange);
case "DCT":
case "DCTDecode":
return new JpegStream(stream, maybeLength, params);
case "JPX":
case "JPXDecode":
return new JpxStream(stream, maybeLength, params);
case "A85":
case "ASCII85Decode":
return new Ascii85Stream(stream, maybeLength);
case "AHx":
case "ASCIIHexDecode":
return new AsciiHexStream(stream, maybeLength);
case "CCF":
case "CCITTFaxDecode":
return new CCITTFaxStream(stream, maybeLength, params);
case "RL":
case "RunLengthDecode":
return new RunLengthStream(stream, maybeLength);
case "JBIG2Decode":
return new Jbig2Stream(stream, maybeLength, params);
}
warn(`Filter "${name}" is not supported.`);
return stream;
} catch (ex) {
if (ex instanceof MissingDataException) {
throw ex;
}
warn(`Invalid stream: "${ex}"`);
return new NullStream();
}
}
}
const specialChars = [1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 2, 0, 0, 2, 2, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
function toHexDigit(ch) {
if (ch >= 0x30 && ch <= 0x39) {
return ch & 0x0f;
}
if (ch >= 0x41 && ch <= 0x46 || ch >= 0x61 && ch <= 0x66) {
return (ch & 0x0f) + 9;
}
return -1;
}
class Lexer {
constructor(stream, knownCommands = null) {
this.stream = stream;
this.nextChar();
this.strBuf = [];
this.knownCommands = knownCommands;
this._hexStringNumWarn = 0;
this.beginInlineImagePos = -1;
}
nextChar() {
return this.currentChar = this.stream.getByte();
}
peekChar() {
return this.stream.peekByte();
}
getNumber() {
let ch = this.currentChar;
let eNotation = false;
let divideBy = 0;
let sign = 1;
if (ch === 0x2d) {
sign = -1;
ch = this.nextChar();
if (ch === 0x2d) {
ch = this.nextChar();
}
} else if (ch === 0x2b) {
ch = this.nextChar();
}
if (ch === 0x0a || ch === 0x0d) {
do {
ch = this.nextChar();
} while (ch === 0x0a || ch === 0x0d);
}
if (ch === 0x2e) {
divideBy = 10;
ch = this.nextChar();
}
if (ch < 0x30 || ch > 0x39) {
const msg = `Invalid number: ${String.fromCharCode(ch)} (charCode ${ch})`;
if (isWhiteSpace(ch) || ch === 0x28 || ch === 0x3c || ch === -1) {
info(`Lexer.getNumber - "${msg}".`);
return 0;
}
throw new FormatError(msg);
}
let baseValue = ch - 0x30;
let powerValue = 0;
let powerValueSign = 1;
while ((ch = this.nextChar()) >= 0) {
if (ch >= 0x30 && ch <= 0x39) {
const currentDigit = ch - 0x30;
if (eNotation) {
powerValue = powerValue * 10 + currentDigit;
} else {
if (divideBy !== 0) {
divideBy *= 10;
}
baseValue = baseValue * 10 + currentDigit;
}
} else if (ch === 0x2e) {
if (divideBy === 0) {
divideBy = 1;
} else {
break;
}
} else if (ch === 0x2d) {
warn("Badly formatted number: minus sign in the middle");
} else if (ch === 0x45 || ch === 0x65) {
ch = this.peekChar();
if (ch === 0x2b || ch === 0x2d) {
powerValueSign = ch === 0x2d ? -1 : 1;
this.nextChar();
} else if (ch < 0x30 || ch > 0x39) {
break;
}
eNotation = true;
} else {
break;
}
}
if (divideBy !== 0) {
baseValue /= divideBy;
}
if (eNotation) {
baseValue *= 10 ** (powerValueSign * powerValue);
}
return sign * baseValue;
}
getString() {
let numParen = 1;
let done = false;
const strBuf = this.strBuf;
strBuf.length = 0;
let ch = this.nextChar();
while (true) {
let charBuffered = false;
switch (ch | 0) {
case -1:
warn("Unterminated string");
done = true;
break;
case 0x28:
++numParen;
strBuf.push("(");
break;
case 0x29:
if (--numParen === 0) {
this.nextChar();
done = true;
} else {
strBuf.push(")");
}
break;
case 0x5c:
ch = this.nextChar();
switch (ch) {
case -1:
warn("Unterminated string");
done = true;
break;
case 0x6e:
strBuf.push("\n");
break;
case 0x72:
strBuf.push("\r");
break;
case 0x74:
strBuf.push("\t");
break;
case 0x62:
strBuf.push("\b");
break;
case 0x66:
strBuf.push("\f");
break;
case 0x5c:
case 0x28:
case 0x29:
strBuf.push(String.fromCharCode(ch));
break;
case 0x30:
case 0x31:
case 0x32:
case 0x33:
case 0x34:
case 0x35:
case 0x36:
case 0x37:
let x = ch & 0x0f;
ch = this.nextChar();
charBuffered = true;
if (ch >= 0x30 && ch <= 0x37) {
x = (x << 3) + (ch & 0x0f);
ch = this.nextChar();
if (ch >= 0x30 && ch <= 0x37) {
charBuffered = false;
x = (x << 3) + (ch & 0x0f);
}
}
strBuf.push(String.fromCharCode(x));
break;
case 0x0d:
if (this.peekChar() === 0x0a) {
this.nextChar();
}
break;
case 0x0a:
break;
default:
strBuf.push(String.fromCharCode(ch));
break;
}
break;
default:
strBuf.push(String.fromCharCode(ch));
break;
}
if (done) {
break;
}
if (!charBuffered) {
ch = this.nextChar();
}
}
return strBuf.join("");
}
getName() {
let ch, previousCh;
const strBuf = this.strBuf;
strBuf.length = 0;
while ((ch = this.nextChar()) >= 0 && !specialChars[ch]) {
if (ch === 0x23) {
ch = this.nextChar();
if (specialChars[ch]) {
warn("Lexer_getName: " + "NUMBER SIGN (#) should be followed by a hexadecimal number.");
strBuf.push("#");
break;
}
const x = toHexDigit(ch);
if (x !== -1) {
previousCh = ch;
ch = this.nextChar();
const x2 = toHexDigit(ch);
if (x2 === -1) {
warn(`Lexer_getName: Illegal digit (${String.fromCharCode(ch)}) ` + "in hexadecimal number.");
strBuf.push("#", String.fromCharCode(previousCh));
if (specialChars[ch]) {
break;
}
strBuf.push(String.fromCharCode(ch));
continue;
}
strBuf.push(String.fromCharCode(x << 4 | x2));
} else {
strBuf.push("#", String.fromCharCode(ch));
}
} else {
strBuf.push(String.fromCharCode(ch));
}
}
if (strBuf.length > 127) {
warn(`Name token is longer than allowed by the spec: ${strBuf.length}`);
}
return Name.get(strBuf.join(""));
}
_hexStringWarn(ch) {
const MAX_HEX_STRING_NUM_WARN = 5;
if (this._hexStringNumWarn++ === MAX_HEX_STRING_NUM_WARN) {
warn("getHexString - ignoring additional invalid characters.");
return;
}
if (this._hexStringNumWarn > MAX_HEX_STRING_NUM_WARN) {
return;
}
warn(`getHexString - ignoring invalid character: ${ch}`);
}
getHexString() {
const strBuf = this.strBuf;
strBuf.length = 0;
let ch = this.currentChar;
let firstDigit = -1,
digit = -1;
this._hexStringNumWarn = 0;
while (true) {
if (ch < 0) {
warn("Unterminated hex string");
break;
} else if (ch === 0x3e) {
this.nextChar();
break;
} else if (specialChars[ch] === 1) {
ch = this.nextChar();
continue;
} else {
digit = toHexDigit(ch);
if (digit === -1) {
this._hexStringWarn(ch);
} else if (firstDigit === -1) {
firstDigit = digit;
} else {
strBuf.push(String.fromCharCode(firstDigit << 4 | digit));
firstDigit = -1;
}
ch = this.nextChar();
}
}
if (firstDigit !== -1) {
strBuf.push(String.fromCharCode(firstDigit << 4));
}
return strBuf.join("");
}
getObj() {
let comment = false;
let ch = this.currentChar;
while (true) {
if (ch < 0) {
return EOF;
}
if (comment) {
if (ch === 0x0a || ch === 0x0d) {
comment = false;
}
} else if (ch === 0x25) {
comment = true;
} else if (specialChars[ch] !== 1) {
break;
}
ch = this.nextChar();
}
switch (ch | 0) {
case 0x30:
case 0x31:
case 0x32:
case 0x33:
case 0x34:
case 0x35:
case 0x36:
case 0x37:
case 0x38:
case 0x39:
case 0x2b:
case 0x2d:
case 0x2e:
return this.getNumber();
case 0x28:
return this.getString();
case 0x2f:
return this.getName();
case 0x5b:
this.nextChar();
return Cmd.get("[");
case 0x5d:
this.nextChar();
return Cmd.get("]");
case 0x3c:
ch = this.nextChar();
if (ch === 0x3c) {
this.nextChar();
return Cmd.get("<<");
}
return this.getHexString();
case 0x3e:
ch = this.nextChar();
if (ch === 0x3e) {
this.nextChar();
return Cmd.get(">>");
}
return Cmd.get(">");
case 0x7b:
this.nextChar();
return Cmd.get("{");
case 0x7d:
this.nextChar();
return Cmd.get("}");
case 0x29:
this.nextChar();
throw new FormatError(`Illegal character: ${ch}`);
}
let str = String.fromCharCode(ch);
if (ch < 0x20 || ch > 0x7f) {
const nextCh = this.peekChar();
if (nextCh >= 0x20 && nextCh <= 0x7f) {
this.nextChar();
return Cmd.get(str);
}
}
const knownCommands = this.knownCommands;
let knownCommandFound = knownCommands?.[str] !== undefined;
while ((ch = this.nextChar()) >= 0 && !specialChars[ch]) {
const possibleCommand = str + String.fromCharCode(ch);
if (knownCommandFound && knownCommands[possibleCommand] === undefined) {
break;
}
if (str.length === 128) {
throw new FormatError(`Command token too long: ${str.length}`);
}
str = possibleCommand;
knownCommandFound = knownCommands?.[str] !== undefined;
}
if (str === "true") {
return true;
}
if (str === "false") {
return false;
}
if (str === "null") {
return null;
}
if (str === "BI") {
this.beginInlineImagePos = this.stream.pos;
}
return Cmd.get(str);
}
skipToNextLine() {
let ch = this.currentChar;
while (ch >= 0) {
if (ch === 0x0d) {
ch = this.nextChar();
if (ch === 0x0a) {
this.nextChar();
}
break;
} else if (ch === 0x0a) {
this.nextChar();
break;
}
ch = this.nextChar();
}
}
}
class Linearization {
static create(stream) {
function getInt(linDict, name, allowZeroValue = false) {
const obj = linDict.get(name);
if (Number.isInteger(obj) && (allowZeroValue ? obj >= 0 : obj > 0)) {
return obj;
}
throw new Error(`The "${name}" parameter in the linearization ` + "dictionary is invalid.");
}
function getHints(linDict) {
const hints = linDict.get("H");
let hintsLength;
if (Array.isArray(hints) && ((hintsLength = hints.length) === 2 || hintsLength === 4)) {
for (let index = 0; index < hintsLength; index++) {
const hint = hints[index];
if (!(Number.isInteger(hint) && hint > 0)) {
throw new Error(`Hint (${index}) in the linearization dictionary is invalid.`);
}
}
return hints;
}
throw new Error("Hint array in the linearization dictionary is invalid.");
}
const parser = new Parser({
lexer: new Lexer(stream),
xref: null
});
const obj1 = parser.getObj();
const obj2 = parser.getObj();
const obj3 = parser.getObj();
const linDict = parser.getObj();
let obj, length;
if (!(Number.isInteger(obj1) && Number.isInteger(obj2) && isCmd(obj3, "obj") && linDict instanceof Dict && typeof (obj = linDict.get("Linearized")) === "number" && obj > 0)) {
return null;
} else if ((length = getInt(linDict, "L")) !== stream.length) {
throw new Error('The "L" parameter in the linearization dictionary ' + "does not equal the stream length.");
}
return {
length,
hints: getHints(linDict),
objectNumberFirst: getInt(linDict, "O"),
endFirst: getInt(linDict, "E"),
numPages: getInt(linDict, "N"),
mainXRefEntriesOffset: getInt(linDict, "T"),
pageFirst: linDict.has("P") ? getInt(linDict, "P", true) : 0
};
}
}
;// ./src/core/cmap.js
const BUILT_IN_CMAPS = ["Adobe-GB1-UCS2", "Adobe-CNS1-UCS2", "Adobe-Japan1-UCS2", "Adobe-Korea1-UCS2", "78-EUC-H", "78-EUC-V", "78-H", "78-RKSJ-H", "78-RKSJ-V", "78-V", "78ms-RKSJ-H", "78ms-RKSJ-V", "83pv-RKSJ-H", "90ms-RKSJ-H", "90ms-RKSJ-V", "90msp-RKSJ-H", "90msp-RKSJ-V", "90pv-RKSJ-H", "90pv-RKSJ-V", "Add-H", "Add-RKSJ-H", "Add-RKSJ-V", "Add-V", "Adobe-CNS1-0", "Adobe-CNS1-1", "Adobe-CNS1-2", "Adobe-CNS1-3", "Adobe-CNS1-4", "Adobe-CNS1-5", "Adobe-CNS1-6", "Adobe-GB1-0", "Adobe-GB1-1", "Adobe-GB1-2", "Adobe-GB1-3", "Adobe-GB1-4", "Adobe-GB1-5", "Adobe-Japan1-0", "Adobe-Japan1-1", "Adobe-Japan1-2", "Adobe-Japan1-3", "Adobe-Japan1-4", "Adobe-Japan1-5", "Adobe-Japan1-6", "Adobe-Korea1-0", "Adobe-Korea1-1", "Adobe-Korea1-2", "B5-H", "B5-V", "B5pc-H", "B5pc-V", "CNS-EUC-H", "CNS-EUC-V", "CNS1-H", "CNS1-V", "CNS2-H", "CNS2-V", "ETHK-B5-H", "ETHK-B5-V", "ETen-B5-H", "ETen-B5-V", "ETenms-B5-H", "ETenms-B5-V", "EUC-H", "EUC-V", "Ext-H", "Ext-RKSJ-H", "Ext-RKSJ-V", "Ext-V", "GB-EUC-H", "GB-EUC-V", "GB-H", "GB-V", "GBK-EUC-H", "GBK-EUC-V", "GBK2K-H", "GBK2K-V", "GBKp-EUC-H", "GBKp-EUC-V", "GBT-EUC-H", "GBT-EUC-V", "GBT-H", "GBT-V", "GBTpc-EUC-H", "GBTpc-EUC-V", "GBpc-EUC-H", "GBpc-EUC-V", "H", "HKdla-B5-H", "HKdla-B5-V", "HKdlb-B5-H", "HKdlb-B5-V", "HKgccs-B5-H", "HKgccs-B5-V", "HKm314-B5-H", "HKm314-B5-V", "HKm471-B5-H", "HKm471-B5-V", "HKscs-B5-H", "HKscs-B5-V", "Hankaku", "Hiragana", "KSC-EUC-H", "KSC-EUC-V", "KSC-H", "KSC-Johab-H", "KSC-Johab-V", "KSC-V", "KSCms-UHC-H", "KSCms-UHC-HW-H", "KSCms-UHC-HW-V", "KSCms-UHC-V", "KSCpc-EUC-H", "KSCpc-EUC-V", "Katakana", "NWP-H", "NWP-V", "RKSJ-H", "RKSJ-V", "Roman", "UniCNS-UCS2-H", "UniCNS-UCS2-V", "UniCNS-UTF16-H", "UniCNS-UTF16-V", "UniCNS-UTF32-H", "UniCNS-UTF32-V", "UniCNS-UTF8-H", "UniCNS-UTF8-V", "UniGB-UCS2-H", "UniGB-UCS2-V", "UniGB-UTF16-H", "UniGB-UTF16-V", "UniGB-UTF32-H", "UniGB-UTF32-V", "UniGB-UTF8-H", "UniGB-UTF8-V", "UniJIS-UCS2-H", "UniJIS-UCS2-HW-H", "UniJIS-UCS2-HW-V", "UniJIS-UCS2-V", "UniJIS-UTF16-H", "UniJIS-UTF16-V", "UniJIS-UTF32-H", "UniJIS-UTF32-V", "UniJIS-UTF8-H", "UniJIS-UTF8-V", "UniJIS2004-UTF16-H", "UniJIS2004-UTF16-V", "UniJIS2004-UTF32-H", "UniJIS2004-UTF32-V", "UniJIS2004-UTF8-H", "UniJIS2004-UTF8-V", "UniJISPro-UCS2-HW-V", "UniJISPro-UCS2-V", "UniJISPro-UTF8-V", "UniJISX0213-UTF32-H", "UniJISX0213-UTF32-V", "UniJISX02132004-UTF32-H", "UniJISX02132004-UTF32-V", "UniKS-UCS2-H", "UniKS-UCS2-V", "UniKS-UTF16-H", "UniKS-UTF16-V", "UniKS-UTF32-H", "UniKS-UTF32-V", "UniKS-UTF8-H", "UniKS-UTF8-V", "V", "WP-Symbol"];
const MAX_MAP_RANGE = 2 ** 24 - 1;
class CMap {
constructor(builtInCMap = false) {
this.codespaceRanges = [[], [], [], []];
this.numCodespaceRanges = 0;
this._map = [];
this.name = "";
this.vertical = false;
this.useCMap = null;
this.builtInCMap = builtInCMap;
}
addCodespaceRange(n, low, high) {
this.codespaceRanges[n - 1].push(low, high);
this.numCodespaceRanges++;
}
mapCidRange(low, high, dstLow) {
if (high - low > MAX_MAP_RANGE) {
throw new Error("mapCidRange - ignoring data above MAX_MAP_RANGE.");
}
while (low <= high) {
this._map[low++] = dstLow++;
}
}
mapBfRange(low, high, dstLow) {
if (high - low > MAX_MAP_RANGE) {
throw new Error("mapBfRange - ignoring data above MAX_MAP_RANGE.");
}
const lastByte = dstLow.length - 1;
while (low <= high) {
this._map[low++] = dstLow;
const nextCharCode = dstLow.charCodeAt(lastByte) + 1;
if (nextCharCode > 0xff) {
dstLow = dstLow.substring(0, lastByte - 1) + String.fromCharCode(dstLow.charCodeAt(lastByte - 1) + 1) + "\x00";
continue;
}
dstLow = dstLow.substring(0, lastByte) + String.fromCharCode(nextCharCode);
}
}
mapBfRangeToArray(low, high, array) {
if (high - low > MAX_MAP_RANGE) {
throw new Error("mapBfRangeToArray - ignoring data above MAX_MAP_RANGE.");
}
const ii = array.length;
let i = 0;
while (low <= high && i < ii) {
this._map[low] = array[i++];
++low;
}
}
mapOne(src, dst) {
this._map[src] = dst;
}
lookup(code) {
return this._map[code];
}
contains(code) {
return this._map[code] !== undefined;
}
forEach(callback) {
const map = this._map;
const length = map.length;
if (length <= 0x10000) {
for (let i = 0; i < length; i++) {
if (map[i] !== undefined) {
callback(i, map[i]);
}
}
} else {
for (const i in map) {
callback(i, map[i]);
}
}
}
charCodeOf(value) {
const map = this._map;
if (map.length <= 0x10000) {
return map.indexOf(value);
}
for (const charCode in map) {
if (map[charCode] === value) {
return charCode | 0;
}
}
return -1;
}
getMap() {
return this._map;
}
readCharCode(str, offset, out) {
let c = 0;
const codespaceRanges = this.codespaceRanges;
for (let n = 0, nn = codespaceRanges.length; n < nn; n++) {
c = (c << 8 | str.charCodeAt(offset + n)) >>> 0;
const codespaceRange = codespaceRanges[n];
for (let k = 0, kk = codespaceRange.length; k < kk;) {
const low = codespaceRange[k++];
const high = codespaceRange[k++];
if (c >= low && c <= high) {
out.charcode = c;
out.length = n + 1;
return;
}
}
}
out.charcode = 0;
out.length = 1;
}
getCharCodeLength(charCode) {
const codespaceRanges = this.codespaceRanges;
for (let n = 0, nn = codespaceRanges.length; n < nn; n++) {
const codespaceRange = codespaceRanges[n];
for (let k = 0, kk = codespaceRange.length; k < kk;) {
const low = codespaceRange[k++];
const high = codespaceRange[k++];
if (charCode >= low && charCode <= high) {
return n + 1;
}
}
}
return 1;
}
get length() {
return this._map.length;
}
get isIdentityCMap() {
if (!(this.name === "Identity-H" || this.name === "Identity-V")) {
return false;
}
if (this._map.length !== 0x10000) {
return false;
}
for (let i = 0; i < 0x10000; i++) {
if (this._map[i] !== i) {
return false;
}
}
return true;
}
}
class IdentityCMap extends CMap {
constructor(vertical, n) {
super();
this.vertical = vertical;
this.addCodespaceRange(n, 0, 0xffff);
}
mapCidRange(low, high, dstLow) {
unreachable("should not call mapCidRange");
}
mapBfRange(low, high, dstLow) {
unreachable("should not call mapBfRange");
}
mapBfRangeToArray(low, high, array) {
unreachable("should not call mapBfRangeToArray");
}
mapOne(src, dst) {
unreachable("should not call mapCidOne");
}
lookup(code) {
return Number.isInteger(code) && code <= 0xffff ? code : undefined;
}
contains(code) {
return Number.isInteger(code) && code <= 0xffff;
}
forEach(callback) {
for (let i = 0; i <= 0xffff; i++) {
callback(i, i);
}
}
charCodeOf(value) {
return Number.isInteger(value) && value <= 0xffff ? value : -1;
}
getMap() {
const map = new Array(0x10000);
for (let i = 0; i <= 0xffff; i++) {
map[i] = i;
}
return map;
}
get length() {
return 0x10000;
}
get isIdentityCMap() {
unreachable("should not access .isIdentityCMap");
}
}
function strToInt(str) {
let a = 0;
for (let i = 0; i < str.length; i++) {
a = a << 8 | str.charCodeAt(i);
}
return a >>> 0;
}
function expectString(obj) {
if (typeof obj !== "string") {
throw new FormatError("Malformed CMap: expected string.");
}
}
function expectInt(obj) {
if (!Number.isInteger(obj)) {
throw new FormatError("Malformed CMap: expected int.");
}
}
function parseBfChar(cMap, lexer) {
while (true) {
let obj = lexer.getObj();
if (obj === EOF) {
break;
}
if (isCmd(obj, "endbfchar")) {
return;
}
expectString(obj);
const src = strToInt(obj);
obj = lexer.getObj();
expectString(obj);
const dst = obj;
cMap.mapOne(src, dst);
}
}
function parseBfRange(cMap, lexer) {
while (true) {
let obj = lexer.getObj();
if (obj === EOF) {
break;
}
if (isCmd(obj, "endbfrange")) {
return;
}
expectString(obj);
const low = strToInt(obj);
obj = lexer.getObj();
expectString(obj);
const high = strToInt(obj);
obj = lexer.getObj();
if (Number.isInteger(obj) || typeof obj === "string") {
const dstLow = Number.isInteger(obj) ? String.fromCharCode(obj) : obj;
cMap.mapBfRange(low, high, dstLow);
} else if (isCmd(obj, "[")) {
obj = lexer.getObj();
const array = [];
while (!isCmd(obj, "]") && obj !== EOF) {
array.push(obj);
obj = lexer.getObj();
}
cMap.mapBfRangeToArray(low, high, array);
} else {
break;
}
}
throw new FormatError("Invalid bf range.");
}
function parseCidChar(cMap, lexer) {
while (true) {
let obj = lexer.getObj();
if (obj === EOF) {
break;
}
if (isCmd(obj, "endcidchar")) {
return;
}
expectString(obj);
const src = strToInt(obj);
obj = lexer.getObj();
expectInt(obj);
const dst = obj;
cMap.mapOne(src, dst);
}
}
function parseCidRange(cMap, lexer) {
while (true) {
let obj = lexer.getObj();
if (obj === EOF) {
break;
}
if (isCmd(obj, "endcidrange")) {
return;
}
expectString(obj);
const low = strToInt(obj);
obj = lexer.getObj();
expectString(obj);
const high = strToInt(obj);
obj = lexer.getObj();
expectInt(obj);
const dstLow = obj;
cMap.mapCidRange(low, high, dstLow);
}
}
function parseCodespaceRange(cMap, lexer) {
while (true) {
let obj = lexer.getObj();
if (obj === EOF) {
break;
}
if (isCmd(obj, "endcodespacerange")) {
return;
}
if (typeof obj !== "string") {
break;
}
const low = strToInt(obj);
obj = lexer.getObj();
if (typeof obj !== "string") {
break;
}
const high = strToInt(obj);
cMap.addCodespaceRange(obj.length, low, high);
}
throw new FormatError("Invalid codespace range.");
}
function parseWMode(cMap, lexer) {
const obj = lexer.getObj();
if (Number.isInteger(obj)) {
cMap.vertical = !!obj;
}
}
function parseCMapName(cMap, lexer) {
const obj = lexer.getObj();
if (obj instanceof Name) {
cMap.name = obj.name;
}
}
async function parseCMap(cMap, lexer, fetchBuiltInCMap, useCMap) {
let previous, embeddedUseCMap;
objLoop: while (true) {
try {
const obj = lexer.getObj();
if (obj === EOF) {
break;
} else if (obj instanceof Name) {
if (obj.name === "WMode") {
parseWMode(cMap, lexer);
} else if (obj.name === "CMapName") {
parseCMapName(cMap, lexer);
}
previous = obj;
} else if (obj instanceof Cmd) {
switch (obj.cmd) {
case "endcmap":
break objLoop;
case "usecmap":
if (previous instanceof Name) {
embeddedUseCMap = previous.name;
}
break;
case "begincodespacerange":
parseCodespaceRange(cMap, lexer);
break;
case "beginbfchar":
parseBfChar(cMap, lexer);
break;
case "begincidchar":
parseCidChar(cMap, lexer);
break;
case "beginbfrange":
parseBfRange(cMap, lexer);
break;
case "begincidrange":
parseCidRange(cMap, lexer);
break;
}
}
} catch (ex) {
if (ex instanceof MissingDataException) {
throw ex;
}
warn("Invalid cMap data: " + ex);
continue;
}
}
if (!useCMap && embeddedUseCMap) {
useCMap = embeddedUseCMap;
}
if (useCMap) {
return extendCMap(cMap, fetchBuiltInCMap, useCMap);
}
return cMap;
}
async function extendCMap(cMap, fetchBuiltInCMap, useCMap) {
cMap.useCMap = await createBuiltInCMap(useCMap, fetchBuiltInCMap);
if (cMap.numCodespaceRanges === 0) {
const useCodespaceRanges = cMap.useCMap.codespaceRanges;
for (let i = 0; i < useCodespaceRanges.length; i++) {
cMap.codespaceRanges[i] = useCodespaceRanges[i].slice();
}
cMap.numCodespaceRanges = cMap.useCMap.numCodespaceRanges;
}
cMap.useCMap.forEach(function (key, value) {
if (!cMap.contains(key)) {
cMap.mapOne(key, value);
}
});
return cMap;
}
async function createBuiltInCMap(name, fetchBuiltInCMap) {
if (name === "Identity-H") {
return new IdentityCMap(false, 2);
} else if (name === "Identity-V") {
return new IdentityCMap(true, 2);
}
if (!BUILT_IN_CMAPS.includes(name)) {
throw new Error("Unknown CMap name: " + name);
}
if (!fetchBuiltInCMap) {
throw new Error("Built-in CMap parameters are not provided.");
}
const {
cMapData,
isCompressed
} = await fetchBuiltInCMap(name);
const cMap = new CMap(true);
if (isCompressed) {
return new BinaryCMapReader().process(cMapData, cMap, useCMap => extendCMap(cMap, fetchBuiltInCMap, useCMap));
}
const lexer = new Lexer(new Stream(cMapData));
return parseCMap(cMap, lexer, fetchBuiltInCMap, null);
}
class CMapFactory {
static async create({
encoding,
fetchBuiltInCMap,
useCMap
}) {
if (encoding instanceof Name) {
return createBuiltInCMap(encoding.name, fetchBuiltInCMap);
} else if (encoding instanceof BaseStream) {
const parsedCMap = await parseCMap(new CMap(), new Lexer(encoding), fetchBuiltInCMap, useCMap);
if (parsedCMap.isIdentityCMap) {
return createBuiltInCMap(parsedCMap.name, fetchBuiltInCMap);
}
return parsedCMap;
}
throw new Error("Encoding required.");
}
}
;// ./src/core/charsets.js
const ISOAdobeCharset = [".notdef", "space", "exclam", "quotedbl", "numbersign", "dollar", "percent", "ampersand", "quoteright", "parenleft", "parenright", "asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", "less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum", "underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "braceleft", "bar", "braceright", "asciitilde", "exclamdown", "cent", "sterling", "fraction", "yen", "florin", "section", "currency", "quotesingle", "quotedblleft", "guillemotleft", "guilsinglleft", "guilsinglright", "fi", "fl", "endash", "dagger", "daggerdbl", "periodcentered", "paragraph", "bullet", "quotesinglbase", "quotedblbase", "quotedblright", "guillemotright", "ellipsis", "perthousand", "questiondown", "grave", "acute", "circumflex", "tilde", "macron", "breve", "dotaccent", "dieresis", "ring", "cedilla", "hungarumlaut", "ogonek", "caron", "emdash", "AE", "ordfeminine", "Lslash", "Oslash", "OE", "ordmasculine", "ae", "dotlessi", "lslash", "oslash", "oe", "germandbls", "onesuperior", "logicalnot", "mu", "trademark", "Eth", "onehalf", "plusminus", "Thorn", "onequarter", "divide", "brokenbar", "degree", "thorn", "threequarters", "twosuperior", "registered", "minus", "eth", "multiply", "threesuperior", "copyright", "Aacute", "Acircumflex", "Adieresis", "Agrave", "Aring", "Atilde", "Ccedilla", "Eacute", "Ecircumflex", "Edieresis", "Egrave", "Iacute", "Icircumflex", "Idieresis", "Igrave", "Ntilde", "Oacute", "Ocircumflex", "Odieresis", "Ograve", "Otilde", "Scaron", "Uacute", "Ucircumflex", "Udieresis", "Ugrave", "Yacute", "Ydieresis", "Zcaron", "aacute", "acircumflex", "adieresis", "agrave", "aring", "atilde", "ccedilla", "eacute", "ecircumflex", "edieresis", "egrave", "iacute", "icircumflex", "idieresis", "igrave", "ntilde", "oacute", "ocircumflex", "odieresis", "ograve", "otilde", "scaron", "uacute", "ucircumflex", "udieresis", "ugrave", "yacute", "ydieresis", "zcaron"];
const ExpertCharset = [".notdef", "space", "exclamsmall", "Hungarumlautsmall", "dollaroldstyle", "dollarsuperior", "ampersandsmall", "Acutesmall", "parenleftsuperior", "parenrightsuperior", "twodotenleader", "onedotenleader", "comma", "hyphen", "period", "fraction", "zerooldstyle", "oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle", "sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon", "semicolon", "commasuperior", "threequartersemdash", "periodsuperior", "questionsmall", "asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior", "lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior", "tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior", "parenrightinferior", "Circumflexsmall", "hyphensuperior", "Gravesmall", "Asmall", "Bsmall", "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall", "Ismall", "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall", "Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall", "Xsmall", "Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah", "Tildesmall", "exclamdownsmall", "centoldstyle", "Lslashsmall", "Scaronsmall", "Zcaronsmall", "Dieresissmall", "Brevesmall", "Caronsmall", "Dotaccentsmall", "Macronsmall", "figuredash", "hypheninferior", "Ogoneksmall", "Ringsmall", "Cedillasmall", "onequarter", "onehalf", "threequarters", "questiondownsmall", "oneeighth", "threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds", "zerosuperior", "onesuperior", "twosuperior", "threesuperior", "foursuperior", "fivesuperior", "sixsuperior", "sevensuperior", "eightsuperior", "ninesuperior", "zeroinferior", "oneinferior", "twoinferior", "threeinferior", "fourinferior", "fiveinferior", "sixinferior", "seveninferior", "eightinferior", "nineinferior", "centinferior", "dollarinferior", "periodinferior", "commainferior", "Agravesmall", "Aacutesmall", "Acircumflexsmall", "Atildesmall", "Adieresissmall", "Aringsmall", "AEsmall", "Ccedillasmall", "Egravesmall", "Eacutesmall", "Ecircumflexsmall", "Edieresissmall", "Igravesmall", "Iacutesmall", "Icircumflexsmall", "Idieresissmall", "Ethsmall", "Ntildesmall", "Ogravesmall", "Oacutesmall", "Ocircumflexsmall", "Otildesmall", "Odieresissmall", "OEsmall", "Oslashsmall", "Ugravesmall", "Uacutesmall", "Ucircumflexsmall", "Udieresissmall", "Yacutesmall", "Thornsmall", "Ydieresissmall"];
const ExpertSubsetCharset = [".notdef", "space", "dollaroldstyle", "dollarsuperior", "parenleftsuperior", "parenrightsuperior", "twodotenleader", "onedotenleader", "comma", "hyphen", "period", "fraction", "zerooldstyle", "oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle", "sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon", "semicolon", "commasuperior", "threequartersemdash", "periodsuperior", "asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior", "lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior", "tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior", "parenrightinferior", "hyphensuperior", "colonmonetary", "onefitted", "rupiah", "centoldstyle", "figuredash", "hypheninferior", "onequarter", "onehalf", "threequarters", "oneeighth", "threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds", "zerosuperior", "onesuperior", "twosuperior", "threesuperior", "foursuperior", "fivesuperior", "sixsuperior", "sevensuperior", "eightsuperior", "ninesuperior", "zeroinferior", "oneinferior", "twoinferior", "threeinferior", "fourinferior", "fiveinferior", "sixinferior", "seveninferior", "eightinferior", "nineinferior", "centinferior", "dollarinferior", "periodinferior", "commainferior"];
;// ./src/core/encodings.js
const ExpertEncoding = ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "space", "exclamsmall", "Hungarumlautsmall", "", "dollaroldstyle", "dollarsuperior", "ampersandsmall", "Acutesmall", "parenleftsuperior", "parenrightsuperior", "twodotenleader", "onedotenleader", "comma", "hyphen", "period", "fraction", "zerooldstyle", "oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle", "sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon", "semicolon", "commasuperior", "threequartersemdash", "periodsuperior", "questionsmall", "", "asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "", "", "", "isuperior", "", "", "lsuperior", "msuperior", "nsuperior", "osuperior", "", "", "rsuperior", "ssuperior", "tsuperior", "", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior", "", "parenrightinferior", "Circumflexsmall", "hyphensuperior", "Gravesmall", "Asmall", "Bsmall", "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall", "Ismall", "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall", "Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall", "Xsmall", "Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah", "Tildesmall", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "exclamdownsmall", "centoldstyle", "Lslashsmall", "", "", "Scaronsmall", "Zcaronsmall", "Dieresissmall", "Brevesmall", "Caronsmall", "", "Dotaccentsmall", "", "", "Macronsmall", "", "", "figuredash", "hypheninferior", "", "", "Ogoneksmall", "Ringsmall", "Cedillasmall", "", "", "", "onequarter", "onehalf", "threequarters", "questiondownsmall", "oneeighth", "threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds", "", "", "zerosuperior", "onesuperior", "twosuperior", "threesuperior", "foursuperior", "fivesuperior", "sixsuperior", "sevensuperior", "eightsuperior", "ninesuperior", "zeroinferior", "oneinferior", "twoinferior", "threeinferior", "fourinferior", "fiveinferior", "sixinferior", "seveninferior", "eightinferior", "nineinferior", "centinferior", "dollarinferior", "periodinferior", "commainferior", "Agravesmall", "Aacutesmall", "Acircumflexsmall", "Atildesmall", "Adieresissmall", "Aringsmall", "AEsmall", "Ccedillasmall", "Egravesmall", "Eacutesmall", "Ecircumflexsmall", "Edieresissmall", "Igravesmall", "Iacutesmall", "Icircumflexsmall", "Idieresissmall", "Ethsmall", "Ntildesmall", "Ogravesmall", "Oacutesmall", "Ocircumflexsmall", "Otildesmall", "Odieresissmall", "OEsmall", "Oslashsmall", "Ugravesmall", "Uacutesmall", "Ucircumflexsmall", "Udieresissmall", "Yacutesmall", "Thornsmall", "Ydieresissmall"];
const MacExpertEncoding = ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "space", "exclamsmall", "Hungarumlautsmall", "centoldstyle", "dollaroldstyle", "dollarsuperior", "ampersandsmall", "Acutesmall", "parenleftsuperior", "parenrightsuperior", "twodotenleader", "onedotenleader", "comma", "hyphen", "period", "fraction", "zerooldstyle", "oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle", "sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon", "semicolon", "", "threequartersemdash", "", "questionsmall", "", "", "", "", "Ethsmall", "", "", "onequarter", "onehalf", "threequarters", "oneeighth", "threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds", "", "", "", "", "", "", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior", "", "parenrightinferior", "Circumflexsmall", "hypheninferior", "Gravesmall", "Asmall", "Bsmall", "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall", "Ismall", "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall", "Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall", "Xsmall", "Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah", "Tildesmall", "", "", "asuperior", "centsuperior", "", "", "", "", "Aacutesmall", "Agravesmall", "Acircumflexsmall", "Adieresissmall", "Atildesmall", "Aringsmall", "Ccedillasmall", "Eacutesmall", "Egravesmall", "Ecircumflexsmall", "Edieresissmall", "Iacutesmall", "Igravesmall", "Icircumflexsmall", "Idieresissmall", "Ntildesmall", "Oacutesmall", "Ogravesmall", "Ocircumflexsmall", "Odieresissmall", "Otildesmall", "Uacutesmall", "Ugravesmall", "Ucircumflexsmall", "Udieresissmall", "", "eightsuperior", "fourinferior", "threeinferior", "sixinferior", "eightinferior", "seveninferior", "Scaronsmall", "", "centinferior", "twoinferior", "", "Dieresissmall", "", "Caronsmall", "osuperior", "fiveinferior", "", "commainferior", "periodinferior", "Yacutesmall", "", "dollarinferior", "", "", "Thornsmall", "", "nineinferior", "zeroinferior", "Zcaronsmall", "AEsmall", "Oslashsmall", "questiondownsmall", "oneinferior", "Lslashsmall", "", "", "", "", "", "", "Cedillasmall", "", "", "", "", "", "OEsmall", "figuredash", "hyphensuperior", "", "", "", "", "exclamdownsmall", "", "Ydieresissmall", "", "onesuperior", "twosuperior", "threesuperior", "foursuperior", "fivesuperior", "sixsuperior", "sevensuperior", "ninesuperior", "zerosuperior", "", "esuperior", "rsuperior", "tsuperior", "", "", "isuperior", "ssuperior", "dsuperior", "", "", "", "", "", "lsuperior", "Ogoneksmall", "Brevesmall", "Macronsmall", "bsuperior", "nsuperior", "msuperior", "commasuperior", "periodsuperior", "Dotaccentsmall", "Ringsmall", "", "", "", ""];
const MacRomanEncoding = ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "space", "exclam", "quotedbl", "numbersign", "dollar", "percent", "ampersand", "quotesingle", "parenleft", "parenright", "asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", "less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum", "underscore", "grave", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "braceleft", "bar", "braceright", "asciitilde", "", "Adieresis", "Aring", "Ccedilla", "Eacute", "Ntilde", "Odieresis", "Udieresis", "aacute", "agrave", "acircumflex", "adieresis", "atilde", "aring", "ccedilla", "eacute", "egrave", "ecircumflex", "edieresis", "iacute", "igrave", "icircumflex", "idieresis", "ntilde", "oacute", "ograve", "ocircumflex", "odieresis", "otilde", "uacute", "ugrave", "ucircumflex", "udieresis", "dagger", "degree", "cent", "sterling", "section", "bullet", "paragraph", "germandbls", "registered", "copyright", "trademark", "acute", "dieresis", "notequal", "AE", "Oslash", "infinity", "plusminus", "lessequal", "greaterequal", "yen", "mu", "partialdiff", "summation", "product", "pi", "integral", "ordfeminine", "ordmasculine", "Omega", "ae", "oslash", "questiondown", "exclamdown", "logicalnot", "radical", "florin", "approxequal", "Delta", "guillemotleft", "guillemotright", "ellipsis", "space", "Agrave", "Atilde", "Otilde", "OE", "oe", "endash", "emdash", "quotedblleft", "quotedblright", "quoteleft", "quoteright", "divide", "lozenge", "ydieresis", "Ydieresis", "fraction", "currency", "guilsinglleft", "guilsinglright", "fi", "fl", "daggerdbl", "periodcentered", "quotesinglbase", "quotedblbase", "perthousand", "Acircumflex", "Ecircumflex", "Aacute", "Edieresis", "Egrave", "Iacute", "Icircumflex", "Idieresis", "Igrave", "Oacute", "Ocircumflex", "apple", "Ograve", "Uacute", "Ucircumflex", "Ugrave", "dotlessi", "circumflex", "tilde", "macron", "breve", "dotaccent", "ring", "cedilla", "hungarumlaut", "ogonek", "caron"];
const StandardEncoding = ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "space", "exclam", "quotedbl", "numbersign", "dollar", "percent", "ampersand", "quoteright", "parenleft", "parenright", "asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", "less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum", "underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "braceleft", "bar", "braceright", "asciitilde", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "exclamdown", "cent", "sterling", "fraction", "yen", "florin", "section", "currency", "quotesingle", "quotedblleft", "guillemotleft", "guilsinglleft", "guilsinglright", "fi", "fl", "", "endash", "dagger", "daggerdbl", "periodcentered", "", "paragraph", "bullet", "quotesinglbase", "quotedblbase", "quotedblright", "guillemotright", "ellipsis", "perthousand", "", "questiondown", "", "grave", "acute", "circumflex", "tilde", "macron", "breve", "dotaccent", "dieresis", "", "ring", "cedilla", "", "hungarumlaut", "ogonek", "caron", "emdash", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "AE", "", "ordfeminine", "", "", "", "", "Lslash", "Oslash", "OE", "ordmasculine", "", "", "", "", "", "ae", "", "", "", "dotlessi", "", "", "lslash", "oslash", "oe", "germandbls", "", "", "", ""];
const WinAnsiEncoding = ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "space", "exclam", "quotedbl", "numbersign", "dollar", "percent", "ampersand", "quotesingle", "parenleft", "parenright", "asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", "less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum", "underscore", "grave", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "braceleft", "bar", "braceright", "asciitilde", "bullet", "Euro", "bullet", "quotesinglbase", "florin", "quotedblbase", "ellipsis", "dagger", "daggerdbl", "circumflex", "perthousand", "Scaron", "guilsinglleft", "OE", "bullet", "Zcaron", "bullet", "bullet", "quoteleft", "quoteright", "quotedblleft", "quotedblright", "bullet", "endash", "emdash", "tilde", "trademark", "scaron", "guilsinglright", "oe", "bullet", "zcaron", "Ydieresis", "space", "exclamdown", "cent", "sterling", "currency", "yen", "brokenbar", "section", "dieresis", "copyright", "ordfeminine", "guillemotleft", "logicalnot", "hyphen", "registered", "macron", "degree", "plusminus", "twosuperior", "threesuperior", "acute", "mu", "paragraph", "periodcentered", "cedilla", "onesuperior", "ordmasculine", "guillemotright", "onequarter", "onehalf", "threequarters", "questiondown", "Agrave", "Aacute", "Acircumflex", "Atilde", "Adieresis", "Aring", "AE", "Ccedilla", "Egrave", "Eacute", "Ecircumflex", "Edieresis", "Igrave", "Iacute", "Icircumflex", "Idieresis", "Eth", "Ntilde", "Ograve", "Oacute", "Ocircumflex", "Otilde", "Odieresis", "multiply", "Oslash", "Ugrave", "Uacute", "Ucircumflex", "Udieresis", "Yacute", "Thorn", "germandbls", "agrave", "aacute", "acircumflex", "atilde", "adieresis", "aring", "ae", "ccedilla", "egrave", "eacute", "ecircumflex", "edieresis", "igrave", "iacute", "icircumflex", "idieresis", "eth", "ntilde", "ograve", "oacute", "ocircumflex", "otilde", "odieresis", "divide", "oslash", "ugrave", "uacute", "ucircumflex", "udieresis", "yacute", "thorn", "ydieresis"];
const SymbolSetEncoding = ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "space", "exclam", "universal", "numbersign", "existential", "percent", "ampersand", "suchthat", "parenleft", "parenright", "asteriskmath", "plus", "comma", "minus", "period", "slash", "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", "less", "equal", "greater", "question", "congruent", "Alpha", "Beta", "Chi", "Delta", "Epsilon", "Phi", "Gamma", "Eta", "Iota", "theta1", "Kappa", "Lambda", "Mu", "Nu", "Omicron", "Pi", "Theta", "Rho", "Sigma", "Tau", "Upsilon", "sigma1", "Omega", "Xi", "Psi", "Zeta", "bracketleft", "therefore", "bracketright", "perpendicular", "underscore", "radicalex", "alpha", "beta", "chi", "delta", "epsilon", "phi", "gamma", "eta", "iota", "phi1", "kappa", "lambda", "mu", "nu", "omicron", "pi", "theta", "rho", "sigma", "tau", "upsilon", "omega1", "omega", "xi", "psi", "zeta", "braceleft", "bar", "braceright", "similar", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "Euro", "Upsilon1", "minute", "lessequal", "fraction", "infinity", "florin", "club", "diamond", "heart", "spade", "arrowboth", "arrowleft", "arrowup", "arrowright", "arrowdown", "degree", "plusminus", "second", "greaterequal", "multiply", "proportional", "partialdiff", "bullet", "divide", "notequal", "equivalence", "approxequal", "ellipsis", "arrowvertex", "arrowhorizex", "carriagereturn", "aleph", "Ifraktur", "Rfraktur", "weierstrass", "circlemultiply", "circleplus", "emptyset", "intersection", "union", "propersuperset", "reflexsuperset", "notsubset", "propersubset", "reflexsubset", "element", "notelement", "angle", "gradient", "registerserif", "copyrightserif", "trademarkserif", "product", "radical", "dotmath", "logicalnot", "logicaland", "logicalor", "arrowdblboth", "arrowdblleft", "arrowdblup", "arrowdblright", "arrowdbldown", "lozenge", "angleleft", "registersans", "copyrightsans", "trademarksans", "summation", "parenlefttp", "parenleftex", "parenleftbt", "bracketlefttp", "bracketleftex", "bracketleftbt", "bracelefttp", "braceleftmid", "braceleftbt", "braceex", "", "angleright", "integral", "integraltp", "integralex", "integralbt", "parenrighttp", "parenrightex", "parenrightbt", "bracketrighttp", "bracketrightex", "bracketrightbt", "bracerighttp", "bracerightmid", "bracerightbt", ""];
const ZapfDingbatsEncoding = ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "space", "a1", "a2", "a202", "a3", "a4", "a5", "a119", "a118", "a117", "a11", "a12", "a13", "a14", "a15", "a16", "a105", "a17", "a18", "a19", "a20", "a21", "a22", "a23", "a24", "a25", "a26", "a27", "a28", "a6", "a7", "a8", "a9", "a10", "a29", "a30", "a31", "a32", "a33", "a34", "a35", "a36", "a37", "a38", "a39", "a40", "a41", "a42", "a43", "a44", "a45", "a46", "a47", "a48", "a49", "a50", "a51", "a52", "a53", "a54", "a55", "a56", "a57", "a58", "a59", "a60", "a61", "a62", "a63", "a64", "a65", "a66", "a67", "a68", "a69", "a70", "a71", "a72", "a73", "a74", "a203", "a75", "a204", "a76", "a77", "a78", "a79", "a81", "a82", "a83", "a84", "a97", "a98", "a99", "a100", "", "a89", "a90", "a93", "a94", "a91", "a92", "a205", "a85", "a206", "a86", "a87", "a88", "a95", "a96", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "a101", "a102", "a103", "a104", "a106", "a107", "a108", "a112", "a111", "a110", "a109", "a120", "a121", "a122", "a123", "a124", "a125", "a126", "a127", "a128", "a129", "a130", "a131", "a132", "a133", "a134", "a135", "a136", "a137", "a138", "a139", "a140", "a141", "a142", "a143", "a144", "a145", "a146", "a147", "a148", "a149", "a150", "a151", "a152", "a153", "a154", "a155", "a156", "a157", "a158", "a159", "a160", "a161", "a163", "a164", "a196", "a165", "a192", "a166", "a167", "a168", "a169", "a170", "a171", "a172", "a173", "a162", "a174", "a175", "a176", "a177", "a178", "a179", "a193", "a180", "a199", "a181", "a200", "a182", "", "a201", "a183", "a184", "a197", "a185", "a194", "a198", "a186", "a195", "a187", "a188", "a189", "a190", "a191", ""];
function getEncoding(encodingName) {
switch (encodingName) {
case "WinAnsiEncoding":
return WinAnsiEncoding;
case "StandardEncoding":
return StandardEncoding;
case "MacRomanEncoding":
return MacRomanEncoding;
case "SymbolSetEncoding":
return SymbolSetEncoding;
case "ZapfDingbatsEncoding":
return ZapfDingbatsEncoding;
case "ExpertEncoding":
return ExpertEncoding;
case "MacExpertEncoding":
return MacExpertEncoding;
default:
return null;
}
}
;// ./src/core/cff_parser.js
const MAX_SUBR_NESTING = 10;
const CFFStandardStrings = [".notdef", "space", "exclam", "quotedbl", "numbersign", "dollar", "percent", "ampersand", "quoteright", "parenleft", "parenright", "asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", "less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum", "underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "braceleft", "bar", "braceright", "asciitilde", "exclamdown", "cent", "sterling", "fraction", "yen", "florin", "section", "currency", "quotesingle", "quotedblleft", "guillemotleft", "guilsinglleft", "guilsinglright", "fi", "fl", "endash", "dagger", "daggerdbl", "periodcentered", "paragraph", "bullet", "quotesinglbase", "quotedblbase", "quotedblright", "guillemotright", "ellipsis", "perthousand", "questiondown", "grave", "acute", "circumflex", "tilde", "macron", "breve", "dotaccent", "dieresis", "ring", "cedilla", "hungarumlaut", "ogonek", "caron", "emdash", "AE", "ordfeminine", "Lslash", "Oslash", "OE", "ordmasculine", "ae", "dotlessi", "lslash", "oslash", "oe", "germandbls", "onesuperior", "logicalnot", "mu", "trademark", "Eth", "onehalf", "plusminus", "Thorn", "onequarter", "divide", "brokenbar", "degree", "thorn", "threequarters", "twosuperior", "registered", "minus", "eth", "multiply", "threesuperior", "copyright", "Aacute", "Acircumflex", "Adieresis", "Agrave", "Aring", "Atilde", "Ccedilla", "Eacute", "Ecircumflex", "Edieresis", "Egrave", "Iacute", "Icircumflex", "Idieresis", "Igrave", "Ntilde", "Oacute", "Ocircumflex", "Odieresis", "Ograve", "Otilde", "Scaron", "Uacute", "Ucircumflex", "Udieresis", "Ugrave", "Yacute", "Ydieresis", "Zcaron", "aacute", "acircumflex", "adieresis", "agrave", "aring", "atilde", "ccedilla", "eacute", "ecircumflex", "edieresis", "egrave", "iacute", "icircumflex", "idieresis", "igrave", "ntilde", "oacute", "ocircumflex", "odieresis", "ograve", "otilde", "scaron", "uacute", "ucircumflex", "udieresis", "ugrave", "yacute", "ydieresis", "zcaron", "exclamsmall", "Hungarumlautsmall", "dollaroldstyle", "dollarsuperior", "ampersandsmall", "Acutesmall", "parenleftsuperior", "parenrightsuperior", "twodotenleader", "onedotenleader", "zerooldstyle", "oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle", "sevenoldstyle", "eightoldstyle", "nineoldstyle", "commasuperior", "threequartersemdash", "periodsuperior", "questionsmall", "asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior", "lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior", "tsuperior", "ff", "ffi", "ffl", "parenleftinferior", "parenrightinferior", "Circumflexsmall", "hyphensuperior", "Gravesmall", "Asmall", "Bsmall", "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall", "Ismall", "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall", "Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall", "Xsmall", "Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah", "Tildesmall", "exclamdownsmall", "centoldstyle", "Lslashsmall", "Scaronsmall", "Zcaronsmall", "Dieresissmall", "Brevesmall", "Caronsmall", "Dotaccentsmall", "Macronsmall", "figuredash", "hypheninferior", "Ogoneksmall", "Ringsmall", "Cedillasmall", "questiondownsmall", "oneeighth", "threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds", "zerosuperior", "foursuperior", "fivesuperior", "sixsuperior", "sevensuperior", "eightsuperior", "ninesuperior", "zeroinferior", "oneinferior", "twoinferior", "threeinferior", "fourinferior", "fiveinferior", "sixinferior", "seveninferior", "eightinferior", "nineinferior", "centinferior", "dollarinferior", "periodinferior", "commainferior", "Agravesmall", "Aacutesmall", "Acircumflexsmall", "Atildesmall", "Adieresissmall", "Aringsmall", "AEsmall", "Ccedillasmall", "Egravesmall", "Eacutesmall", "Ecircumflexsmall", "Edieresissmall", "Igravesmall", "Iacutesmall", "Icircumflexsmall", "Idieresissmall", "Ethsmall", "Ntildesmall", "Ogravesmall", "Oacutesmall", "Ocircumflexsmall", "Otildesmall", "Odieresissmall", "OEsmall", "Oslashsmall", "Ugravesmall", "Uacutesmall", "Ucircumflexsmall", "Udieresissmall", "Yacutesmall", "Thornsmall", "Ydieresissmall", "001.000", "001.001", "001.002", "001.003", "Black", "Bold", "Book", "Light", "Medium", "Regular", "Roman", "Semibold"];
const NUM_STANDARD_CFF_STRINGS = 391;
const CharstringValidationData = [null, {
id: "hstem",
min: 2,
stackClearing: true,
stem: true
}, null, {
id: "vstem",
min: 2,
stackClearing: true,
stem: true
}, {
id: "vmoveto",
min: 1,
stackClearing: true
}, {
id: "rlineto",
min: 2,
resetStack: true
}, {
id: "hlineto",
min: 1,
resetStack: true
}, {
id: "vlineto",
min: 1,
resetStack: true
}, {
id: "rrcurveto",
min: 6,
resetStack: true
}, null, {
id: "callsubr",
min: 1,
undefStack: true
}, {
id: "return",
min: 0,
undefStack: true
}, null, null, {
id: "endchar",
min: 0,
stackClearing: true
}, null, null, null, {
id: "hstemhm",
min: 2,
stackClearing: true,
stem: true
}, {
id: "hintmask",
min: 0,
stackClearing: true
}, {
id: "cntrmask",
min: 0,
stackClearing: true
}, {
id: "rmoveto",
min: 2,
stackClearing: true
}, {
id: "hmoveto",
min: 1,
stackClearing: true
}, {
id: "vstemhm",
min: 2,
stackClearing: true,
stem: true
}, {
id: "rcurveline",
min: 8,
resetStack: true
}, {
id: "rlinecurve",
min: 8,
resetStack: true
}, {
id: "vvcurveto",
min: 4,
resetStack: true
}, {
id: "hhcurveto",
min: 4,
resetStack: true
}, null, {
id: "callgsubr",
min: 1,
undefStack: true
}, {
id: "vhcurveto",
min: 4,
resetStack: true
}, {
id: "hvcurveto",
min: 4,
resetStack: true
}];
const CharstringValidationData12 = [null, null, null, {
id: "and",
min: 2,
stackDelta: -1
}, {
id: "or",
min: 2,
stackDelta: -1
}, {
id: "not",
min: 1,
stackDelta: 0
}, null, null, null, {
id: "abs",
min: 1,
stackDelta: 0
}, {
id: "add",
min: 2,
stackDelta: -1,
stackFn(stack, index) {
stack[index - 2] = stack[index - 2] + stack[index - 1];
}
}, {
id: "sub",
min: 2,
stackDelta: -1,
stackFn(stack, index) {
stack[index - 2] = stack[index - 2] - stack[index - 1];
}
}, {
id: "div",
min: 2,
stackDelta: -1,
stackFn(stack, index) {
stack[index - 2] = stack[index - 2] / stack[index - 1];
}
}, null, {
id: "neg",
min: 1,
stackDelta: 0,
stackFn(stack, index) {
stack[index - 1] = -stack[index - 1];
}
}, {
id: "eq",
min: 2,
stackDelta: -1
}, null, null, {
id: "drop",
min: 1,
stackDelta: -1
}, null, {
id: "put",
min: 2,
stackDelta: -2
}, {
id: "get",
min: 1,
stackDelta: 0
}, {
id: "ifelse",
min: 4,
stackDelta: -3
}, {
id: "random",
min: 0,
stackDelta: 1
}, {
id: "mul",
min: 2,
stackDelta: -1,
stackFn(stack, index) {
stack[index - 2] = stack[index - 2] * stack[index - 1];
}
}, null, {
id: "sqrt",
min: 1,
stackDelta: 0
}, {
id: "dup",
min: 1,
stackDelta: 1
}, {
id: "exch",
min: 2,
stackDelta: 0
}, {
id: "index",
min: 2,
stackDelta: 0
}, {
id: "roll",
min: 3,
stackDelta: -2
}, null, null, null, {
id: "hflex",
min: 7,
resetStack: true
}, {
id: "flex",
min: 13,
resetStack: true
}, {
id: "hflex1",
min: 9,
resetStack: true
}, {
id: "flex1",
min: 11,
resetStack: true
}];
class CFFParser {
constructor(file, properties, seacAnalysisEnabled) {
this.bytes = file.getBytes();
this.properties = properties;
this.seacAnalysisEnabled = !!seacAnalysisEnabled;
}
parse() {
const properties = this.properties;
const cff = new CFF();
this.cff = cff;
const header = this.parseHeader();
const nameIndex = this.parseIndex(header.endPos);
const topDictIndex = this.parseIndex(nameIndex.endPos);
const stringIndex = this.parseIndex(topDictIndex.endPos);
const globalSubrIndex = this.parseIndex(stringIndex.endPos);
const topDictParsed = this.parseDict(topDictIndex.obj.get(0));
const topDict = this.createDict(CFFTopDict, topDictParsed, cff.strings);
cff.header = header.obj;
cff.names = this.parseNameIndex(nameIndex.obj);
cff.strings = this.parseStringIndex(stringIndex.obj);
cff.topDict = topDict;
cff.globalSubrIndex = globalSubrIndex.obj;
this.parsePrivateDict(cff.topDict);
cff.isCIDFont = topDict.hasName("ROS");
const charStringOffset = topDict.getByName("CharStrings");
const charStringIndex = this.parseIndex(charStringOffset).obj;
const fontMatrix = topDict.getByName("FontMatrix");
if (fontMatrix) {
properties.fontMatrix = fontMatrix;
}
const fontBBox = topDict.getByName("FontBBox");
if (fontBBox) {
properties.ascent = Math.max(fontBBox[3], fontBBox[1]);
properties.descent = Math.min(fontBBox[1], fontBBox[3]);
properties.ascentScaled = true;
}
let charset, encoding;
if (cff.isCIDFont) {
const fdArrayIndex = this.parseIndex(topDict.getByName("FDArray")).obj;
for (let i = 0, ii = fdArrayIndex.count; i < ii; ++i) {
const dictRaw = fdArrayIndex.get(i);
const fontDict = this.createDict(CFFTopDict, this.parseDict(dictRaw), cff.strings);
this.parsePrivateDict(fontDict);
cff.fdArray.push(fontDict);
}
encoding = null;
charset = this.parseCharsets(topDict.getByName("charset"), charStringIndex.count, cff.strings, true);
cff.fdSelect = this.parseFDSelect(topDict.getByName("FDSelect"), charStringIndex.count);
} else {
charset = this.parseCharsets(topDict.getByName("charset"), charStringIndex.count, cff.strings, false);
encoding = this.parseEncoding(topDict.getByName("Encoding"), properties, cff.strings, charset.charset);
}
cff.charset = charset;
cff.encoding = encoding;
const charStringsAndSeacs = this.parseCharStrings({
charStrings: charStringIndex,
localSubrIndex: topDict.privateDict.subrsIndex,
globalSubrIndex: globalSubrIndex.obj,
fdSelect: cff.fdSelect,
fdArray: cff.fdArray,
privateDict: topDict.privateDict
});
cff.charStrings = charStringsAndSeacs.charStrings;
cff.seacs = charStringsAndSeacs.seacs;
cff.widths = charStringsAndSeacs.widths;
return cff;
}
parseHeader() {
let bytes = this.bytes;
const bytesLength = bytes.length;
let offset = 0;
while (offset < bytesLength && bytes[offset] !== 1) {
++offset;
}
if (offset >= bytesLength) {
throw new FormatError("Invalid CFF header");
}
if (offset !== 0) {
info("cff data is shifted");
bytes = bytes.subarray(offset);
this.bytes = bytes;
}
const major = bytes[0];
const minor = bytes[1];
const hdrSize = bytes[2];
const offSize = bytes[3];
const header = new CFFHeader(major, minor, hdrSize, offSize);
return {
obj: header,
endPos: hdrSize
};
}
parseDict(dict) {
let pos = 0;
function parseOperand() {
let value = dict[pos++];
if (value === 30) {
return parseFloatOperand();
} else if (value === 28) {
value = readInt16(dict, pos);
pos += 2;
return value;
} else if (value === 29) {
value = dict[pos++];
value = value << 8 | dict[pos++];
value = value << 8 | dict[pos++];
value = value << 8 | dict[pos++];
return value;
} else if (value >= 32 && value <= 246) {
return value - 139;
} else if (value >= 247 && value <= 250) {
return (value - 247) * 256 + dict[pos++] + 108;
} else if (value >= 251 && value <= 254) {
return -((value - 251) * 256) - dict[pos++] - 108;
}
warn('CFFParser_parseDict: "' + value + '" is a reserved command.');
return NaN;
}
function parseFloatOperand() {
let str = "";
const eof = 15;
const lookup = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ".", "E", "E-", null, "-"];
const length = dict.length;
while (pos < length) {
const b = dict[pos++];
const b1 = b >> 4;
const b2 = b & 15;
if (b1 === eof) {
break;
}
str += lookup[b1];
if (b2 === eof) {
break;
}
str += lookup[b2];
}
return parseFloat(str);
}
let operands = [];
const entries = [];
pos = 0;
const end = dict.length;
while (pos < end) {
let b = dict[pos];
if (b <= 21) {
if (b === 12) {
b = b << 8 | dict[++pos];
}
entries.push([b, operands]);
operands = [];
++pos;
} else {
operands.push(parseOperand());
}
}
return entries;
}
parseIndex(pos) {
const cffIndex = new CFFIndex();
const bytes = this.bytes;
const count = bytes[pos++] << 8 | bytes[pos++];
const offsets = [];
let end = pos;
let i, ii;
if (count !== 0) {
const offsetSize = bytes[pos++];
const startPos = pos + (count + 1) * offsetSize - 1;
for (i = 0, ii = count + 1; i < ii; ++i) {
let offset = 0;
for (let j = 0; j < offsetSize; ++j) {
offset <<= 8;
offset += bytes[pos++];
}
offsets.push(startPos + offset);
}
end = offsets[count];
}
for (i = 0, ii = offsets.length - 1; i < ii; ++i) {
const offsetStart = offsets[i];
const offsetEnd = offsets[i + 1];
cffIndex.add(bytes.subarray(offsetStart, offsetEnd));
}
return {
obj: cffIndex,
endPos: end
};
}
parseNameIndex(index) {
const names = [];
for (let i = 0, ii = index.count; i < ii; ++i) {
const name = index.get(i);
names.push(bytesToString(name));
}
return names;
}
parseStringIndex(index) {
const strings = new CFFStrings();
for (let i = 0, ii = index.count; i < ii; ++i) {
const data = index.get(i);
strings.add(bytesToString(data));
}
return strings;
}
createDict(Type, dict, strings) {
const cffDict = new Type(strings);
for (const [key, value] of dict) {
cffDict.setByKey(key, value);
}
return cffDict;
}
parseCharString(state, data, localSubrIndex, globalSubrIndex) {
if (!data || state.callDepth > MAX_SUBR_NESTING) {
return false;
}
let stackSize = state.stackSize;
const stack = state.stack;
let length = data.length;
for (let j = 0; j < length;) {
const value = data[j++];
let validationCommand = null;
if (value === 12) {
const q = data[j++];
if (q === 0) {
data[j - 2] = 139;
data[j - 1] = 22;
stackSize = 0;
} else {
validationCommand = CharstringValidationData12[q];
}
} else if (value === 28) {
stack[stackSize] = readInt16(data, j);
j += 2;
stackSize++;
} else if (value === 14) {
if (stackSize >= 4) {
stackSize -= 4;
if (this.seacAnalysisEnabled) {
state.seac = stack.slice(stackSize, stackSize + 4);
return false;
}
}
validationCommand = CharstringValidationData[value];
} else if (value >= 32 && value <= 246) {
stack[stackSize] = value - 139;
stackSize++;
} else if (value >= 247 && value <= 254) {
stack[stackSize] = value < 251 ? (value - 247 << 8) + data[j] + 108 : -(value - 251 << 8) - data[j] - 108;
j++;
stackSize++;
} else if (value === 255) {
stack[stackSize] = (data[j] << 24 | data[j + 1] << 16 | data[j + 2] << 8 | data[j + 3]) / 65536;
j += 4;
stackSize++;
} else if (value === 19 || value === 20) {
state.hints += stackSize >> 1;
if (state.hints === 0) {
data.copyWithin(j - 1, j, -1);
j -= 1;
length -= 1;
continue;
}
j += state.hints + 7 >> 3;
stackSize %= 2;
validationCommand = CharstringValidationData[value];
} else if (value === 10 || value === 29) {
const subrsIndex = value === 10 ? localSubrIndex : globalSubrIndex;
if (!subrsIndex) {
validationCommand = CharstringValidationData[value];
warn("Missing subrsIndex for " + validationCommand.id);
return false;
}
let bias = 32768;
if (subrsIndex.count < 1240) {
bias = 107;
} else if (subrsIndex.count < 33900) {
bias = 1131;
}
const subrNumber = stack[--stackSize] + bias;
if (subrNumber < 0 || subrNumber >= subrsIndex.count || isNaN(subrNumber)) {
validationCommand = CharstringValidationData[value];
warn("Out of bounds subrIndex for " + validationCommand.id);
return false;
}
state.stackSize = stackSize;
state.callDepth++;
const valid = this.parseCharString(state, subrsIndex.get(subrNumber), localSubrIndex, globalSubrIndex);
if (!valid) {
return false;
}
state.callDepth--;
stackSize = state.stackSize;
continue;
} else if (value === 11) {
state.stackSize = stackSize;
return true;
} else if (value === 0 && j === data.length) {
data[j - 1] = 14;
validationCommand = CharstringValidationData[14];
} else if (value === 9) {
data.copyWithin(j - 1, j, -1);
j -= 1;
length -= 1;
continue;
} else {
validationCommand = CharstringValidationData[value];
}
if (validationCommand) {
if (validationCommand.stem) {
state.hints += stackSize >> 1;
if (value === 3 || value === 23) {
state.hasVStems = true;
} else if (state.hasVStems && (value === 1 || value === 18)) {
warn("CFF stem hints are in wrong order");
data[j - 1] = value === 1 ? 3 : 23;
}
}
if ("min" in validationCommand) {
if (!state.undefStack && stackSize < validationCommand.min) {
warn("Not enough parameters for " + validationCommand.id + "; actual: " + stackSize + ", expected: " + validationCommand.min);
if (stackSize === 0) {
data[j - 1] = 14;
return true;
}
return false;
}
}
if (state.firstStackClearing && validationCommand.stackClearing) {
state.firstStackClearing = false;
stackSize -= validationCommand.min;
if (stackSize >= 2 && validationCommand.stem) {
stackSize %= 2;
} else if (stackSize > 1) {
warn("Found too many parameters for stack-clearing command");
}
if (stackSize > 0) {
state.width = stack[stackSize - 1];
}
}
if ("stackDelta" in validationCommand) {
if ("stackFn" in validationCommand) {
validationCommand.stackFn(stack, stackSize);
}
stackSize += validationCommand.stackDelta;
} else if (validationCommand.stackClearing) {
stackSize = 0;
} else if (validationCommand.resetStack) {
stackSize = 0;
state.undefStack = false;
} else if (validationCommand.undefStack) {
stackSize = 0;
state.undefStack = true;
state.firstStackClearing = false;
}
}
}
if (length < data.length) {
data.fill(14, length);
}
state.stackSize = stackSize;
return true;
}
parseCharStrings({
charStrings,
localSubrIndex,
globalSubrIndex,
fdSelect,
fdArray,
privateDict
}) {
const seacs = [];
const widths = [];
const count = charStrings.count;
for (let i = 0; i < count; i++) {
const charstring = charStrings.get(i);
const state = {
callDepth: 0,
stackSize: 0,
stack: [],
undefStack: true,
hints: 0,
firstStackClearing: true,
seac: null,
width: null,
hasVStems: false
};
let valid = true;
let localSubrToUse = null;
let privateDictToUse = privateDict;
if (fdSelect && fdArray.length) {
const fdIndex = fdSelect.getFDIndex(i);
if (fdIndex === -1) {
warn("Glyph index is not in fd select.");
valid = false;
}
if (fdIndex >= fdArray.length) {
warn("Invalid fd index for glyph index.");
valid = false;
}
if (valid) {
privateDictToUse = fdArray[fdIndex].privateDict;
localSubrToUse = privateDictToUse.subrsIndex;
}
} else if (localSubrIndex) {
localSubrToUse = localSubrIndex;
}
if (valid) {
valid = this.parseCharString(state, charstring, localSubrToUse, globalSubrIndex);
}
if (state.width !== null) {
const nominalWidth = privateDictToUse.getByName("nominalWidthX");
widths[i] = nominalWidth + state.width;
} else {
const defaultWidth = privateDictToUse.getByName("defaultWidthX");
widths[i] = defaultWidth;
}
if (state.seac !== null) {
seacs[i] = state.seac;
}
if (!valid) {
charStrings.set(i, new Uint8Array([14]));
}
}
return {
charStrings,
seacs,
widths
};
}
emptyPrivateDictionary(parentDict) {
const privateDict = this.createDict(CFFPrivateDict, [], parentDict.strings);
parentDict.setByKey(18, [0, 0]);
parentDict.privateDict = privateDict;
}
parsePrivateDict(parentDict) {
if (!parentDict.hasName("Private")) {
this.emptyPrivateDictionary(parentDict);
return;
}
const privateOffset = parentDict.getByName("Private");
if (!Array.isArray(privateOffset) || privateOffset.length !== 2) {
parentDict.removeByName("Private");
return;
}
const size = privateOffset[0];
const offset = privateOffset[1];
if (size === 0 || offset >= this.bytes.length) {
this.emptyPrivateDictionary(parentDict);
return;
}
const privateDictEnd = offset + size;
const dictData = this.bytes.subarray(offset, privateDictEnd);
const dict = this.parseDict(dictData);
const privateDict = this.createDict(CFFPrivateDict, dict, parentDict.strings);
parentDict.privateDict = privateDict;
if (privateDict.getByName("ExpansionFactor") === 0) {
privateDict.setByName("ExpansionFactor", 0.06);
}
if (!privateDict.getByName("Subrs")) {
return;
}
const subrsOffset = privateDict.getByName("Subrs");
const relativeOffset = offset + subrsOffset;
if (subrsOffset === 0 || relativeOffset >= this.bytes.length) {
this.emptyPrivateDictionary(parentDict);
return;
}
const subrsIndex = this.parseIndex(relativeOffset);
privateDict.subrsIndex = subrsIndex.obj;
}
parseCharsets(pos, length, strings, cid) {
if (pos === 0) {
return new CFFCharset(true, CFFCharsetPredefinedTypes.ISO_ADOBE, ISOAdobeCharset);
} else if (pos === 1) {
return new CFFCharset(true, CFFCharsetPredefinedTypes.EXPERT, ExpertCharset);
} else if (pos === 2) {
return new CFFCharset(true, CFFCharsetPredefinedTypes.EXPERT_SUBSET, ExpertSubsetCharset);
}
const bytes = this.bytes;
const start = pos;
const format = bytes[pos++];
const charset = [cid ? 0 : ".notdef"];
let id, count, i;
length -= 1;
switch (format) {
case 0:
for (i = 0; i < length; i++) {
id = bytes[pos++] << 8 | bytes[pos++];
charset.push(cid ? id : strings.get(id));
}
break;
case 1:
while (charset.length <= length) {
id = bytes[pos++] << 8 | bytes[pos++];
count = bytes[pos++];
for (i = 0; i <= count; i++) {
charset.push(cid ? id++ : strings.get(id++));
}
}
break;
case 2:
while (charset.length <= length) {
id = bytes[pos++] << 8 | bytes[pos++];
count = bytes[pos++] << 8 | bytes[pos++];
for (i = 0; i <= count; i++) {
charset.push(cid ? id++ : strings.get(id++));
}
}
break;
default:
throw new FormatError("Unknown charset format");
}
const end = pos;
const raw = bytes.subarray(start, end);
return new CFFCharset(false, format, charset, raw);
}
parseEncoding(pos, properties, strings, charset) {
const encoding = Object.create(null);
const bytes = this.bytes;
let predefined = false;
let format, i, ii;
let raw = null;
function readSupplement() {
const supplementsCount = bytes[pos++];
for (i = 0; i < supplementsCount; i++) {
const code = bytes[pos++];
const sid = (bytes[pos++] << 8) + (bytes[pos++] & 0xff);
encoding[code] = charset.indexOf(strings.get(sid));
}
}
if (pos === 0 || pos === 1) {
predefined = true;
format = pos;
const baseEncoding = pos ? ExpertEncoding : StandardEncoding;
for (i = 0, ii = charset.length; i < ii; i++) {
const index = baseEncoding.indexOf(charset[i]);
if (index !== -1) {
encoding[index] = i;
}
}
} else {
const dataStart = pos;
format = bytes[pos++];
switch (format & 0x7f) {
case 0:
const glyphsCount = bytes[pos++];
for (i = 1; i <= glyphsCount; i++) {
encoding[bytes[pos++]] = i;
}
break;
case 1:
const rangesCount = bytes[pos++];
let gid = 1;
for (i = 0; i < rangesCount; i++) {
const start = bytes[pos++];
const left = bytes[pos++];
for (let j = start; j <= start + left; j++) {
encoding[j] = gid++;
}
}
break;
default:
throw new FormatError(`Unknown encoding format: ${format} in CFF`);
}
const dataEnd = pos;
if (format & 0x80) {
bytes[dataStart] &= 0x7f;
readSupplement();
}
raw = bytes.subarray(dataStart, dataEnd);
}
format &= 0x7f;
return new CFFEncoding(predefined, format, encoding, raw);
}
parseFDSelect(pos, length) {
const bytes = this.bytes;
const format = bytes[pos++];
const fdSelect = [];
let i;
switch (format) {
case 0:
for (i = 0; i < length; ++i) {
const id = bytes[pos++];
fdSelect.push(id);
}
break;
case 3:
const rangesCount = bytes[pos++] << 8 | bytes[pos++];
for (i = 0; i < rangesCount; ++i) {
let first = bytes[pos++] << 8 | bytes[pos++];
if (i === 0 && first !== 0) {
warn("parseFDSelect: The first range must have a first GID of 0" + " -- trying to recover.");
first = 0;
}
const fdIndex = bytes[pos++];
const next = bytes[pos] << 8 | bytes[pos + 1];
for (let j = first; j < next; ++j) {
fdSelect.push(fdIndex);
}
}
pos += 2;
break;
default:
throw new FormatError(`parseFDSelect: Unknown format "${format}".`);
}
if (fdSelect.length !== length) {
throw new FormatError("parseFDSelect: Invalid font data.");
}
return new CFFFDSelect(format, fdSelect);
}
}
class CFF {
constructor() {
this.header = null;
this.names = [];
this.topDict = null;
this.strings = new CFFStrings();
this.globalSubrIndex = null;
this.encoding = null;
this.charset = null;
this.charStrings = null;
this.fdArray = [];
this.fdSelect = null;
this.isCIDFont = false;
}
duplicateFirstGlyph() {
if (this.charStrings.count >= 65535) {
warn("Not enough space in charstrings to duplicate first glyph.");
return;
}
const glyphZero = this.charStrings.get(0);
this.charStrings.add(glyphZero);
if (this.isCIDFont) {
this.fdSelect.fdSelect.push(this.fdSelect.fdSelect[0]);
}
}
hasGlyphId(id) {
if (id < 0 || id >= this.charStrings.count) {
return false;
}
const glyph = this.charStrings.get(id);
return glyph.length > 0;
}
}
class CFFHeader {
constructor(major, minor, hdrSize, offSize) {
this.major = major;
this.minor = minor;
this.hdrSize = hdrSize;
this.offSize = offSize;
}
}
class CFFStrings {
constructor() {
this.strings = [];
}
get(index) {
if (index >= 0 && index <= NUM_STANDARD_CFF_STRINGS - 1) {
return CFFStandardStrings[index];
}
if (index - NUM_STANDARD_CFF_STRINGS <= this.strings.length) {
return this.strings[index - NUM_STANDARD_CFF_STRINGS];
}
return CFFStandardStrings[0];
}
getSID(str) {
let index = CFFStandardStrings.indexOf(str);
if (index !== -1) {
return index;
}
index = this.strings.indexOf(str);
if (index !== -1) {
return index + NUM_STANDARD_CFF_STRINGS;
}
return -1;
}
add(value) {
this.strings.push(value);
}
get count() {
return this.strings.length;
}
}
class CFFIndex {
constructor() {
this.objects = [];
this.length = 0;
}
add(data) {
this.length += data.length;
this.objects.push(data);
}
set(index, data) {
this.length += data.length - this.objects[index].length;
this.objects[index] = data;
}
get(index) {
return this.objects[index];
}
get count() {
return this.objects.length;
}
}
class CFFDict {
constructor(tables, strings) {
this.keyToNameMap = tables.keyToNameMap;
this.nameToKeyMap = tables.nameToKeyMap;
this.defaults = tables.defaults;
this.types = tables.types;
this.opcodes = tables.opcodes;
this.order = tables.order;
this.strings = strings;
this.values = Object.create(null);
}
setByKey(key, value) {
if (!(key in this.keyToNameMap)) {
return false;
}
if (value.length === 0) {
return true;
}
for (const val of value) {
if (isNaN(val)) {
warn(`Invalid CFFDict value: "${value}" for key "${key}".`);
return true;
}
}
const type = this.types[key];
if (type === "num" || type === "sid" || type === "offset") {
value = value[0];
}
this.values[key] = value;
return true;
}
setByName(name, value) {
if (!(name in this.nameToKeyMap)) {
throw new FormatError(`Invalid dictionary name "${name}"`);
}
this.values[this.nameToKeyMap[name]] = value;
}
hasName(name) {
return this.nameToKeyMap[name] in this.values;
}
getByName(name) {
if (!(name in this.nameToKeyMap)) {
throw new FormatError(`Invalid dictionary name ${name}"`);
}
const key = this.nameToKeyMap[name];
if (!(key in this.values)) {
return this.defaults[key];
}
return this.values[key];
}
removeByName(name) {
delete this.values[this.nameToKeyMap[name]];
}
static createTables(layout) {
const tables = {
keyToNameMap: {},
nameToKeyMap: {},
defaults: {},
types: {},
opcodes: {},
order: []
};
for (const entry of layout) {
const key = Array.isArray(entry[0]) ? (entry[0][0] << 8) + entry[0][1] : entry[0];
tables.keyToNameMap[key] = entry[1];
tables.nameToKeyMap[entry[1]] = key;
tables.types[key] = entry[2];
tables.defaults[key] = entry[3];
tables.opcodes[key] = Array.isArray(entry[0]) ? entry[0] : [entry[0]];
tables.order.push(key);
}
return tables;
}
}
const CFFTopDictLayout = [[[12, 30], "ROS", ["sid", "sid", "num"], null], [[12, 20], "SyntheticBase", "num", null], [0, "version", "sid", null], [1, "Notice", "sid", null], [[12, 0], "Copyright", "sid", null], [2, "FullName", "sid", null], [3, "FamilyName", "sid", null], [4, "Weight", "sid", null], [[12, 1], "isFixedPitch", "num", 0], [[12, 2], "ItalicAngle", "num", 0], [[12, 3], "UnderlinePosition", "num", -100], [[12, 4], "UnderlineThickness", "num", 50], [[12, 5], "PaintType", "num", 0], [[12, 6], "CharstringType", "num", 2], [[12, 7], "FontMatrix", ["num", "num", "num", "num", "num", "num"], [0.001, 0, 0, 0.001, 0, 0]], [13, "UniqueID", "num", null], [5, "FontBBox", ["num", "num", "num", "num"], [0, 0, 0, 0]], [[12, 8], "StrokeWidth", "num", 0], [14, "XUID", "array", null], [15, "charset", "offset", 0], [16, "Encoding", "offset", 0], [17, "CharStrings", "offset", 0], [18, "Private", ["offset", "offset"], null], [[12, 21], "PostScript", "sid", null], [[12, 22], "BaseFontName", "sid", null], [[12, 23], "BaseFontBlend", "delta", null], [[12, 31], "CIDFontVersion", "num", 0], [[12, 32], "CIDFontRevision", "num", 0], [[12, 33], "CIDFontType", "num", 0], [[12, 34], "CIDCount", "num", 8720], [[12, 35], "UIDBase", "num", null], [[12, 37], "FDSelect", "offset", null], [[12, 36], "FDArray", "offset", null], [[12, 38], "FontName", "sid", null]];
class CFFTopDict extends CFFDict {
static get tables() {
return shadow(this, "tables", this.createTables(CFFTopDictLayout));
}
constructor(strings) {
super(CFFTopDict.tables, strings);
this.privateDict = null;
}
}
const CFFPrivateDictLayout = [[6, "BlueValues", "delta", null], [7, "OtherBlues", "delta", null], [8, "FamilyBlues", "delta", null], [9, "FamilyOtherBlues", "delta", null], [[12, 9], "BlueScale", "num", 0.039625], [[12, 10], "BlueShift", "num", 7], [[12, 11], "BlueFuzz", "num", 1], [10, "StdHW", "num", null], [11, "StdVW", "num", null], [[12, 12], "StemSnapH", "delta", null], [[12, 13], "StemSnapV", "delta", null], [[12, 14], "ForceBold", "num", 0], [[12, 17], "LanguageGroup", "num", 0], [[12, 18], "ExpansionFactor", "num", 0.06], [[12, 19], "initialRandomSeed", "num", 0], [20, "defaultWidthX", "num", 0], [21, "nominalWidthX", "num", 0], [19, "Subrs", "offset", null]];
class CFFPrivateDict extends CFFDict {
static get tables() {
return shadow(this, "tables", this.createTables(CFFPrivateDictLayout));
}
constructor(strings) {
super(CFFPrivateDict.tables, strings);
this.subrsIndex = null;
}
}
const CFFCharsetPredefinedTypes = {
ISO_ADOBE: 0,
EXPERT: 1,
EXPERT_SUBSET: 2
};
class CFFCharset {
constructor(predefined, format, charset, raw) {
this.predefined = predefined;
this.format = format;
this.charset = charset;
this.raw = raw;
}
}
class CFFEncoding {
constructor(predefined, format, encoding, raw) {
this.predefined = predefined;
this.format = format;
this.encoding = encoding;
this.raw = raw;
}
}
class CFFFDSelect {
constructor(format, fdSelect) {
this.format = format;
this.fdSelect = fdSelect;
}
getFDIndex(glyphIndex) {
if (glyphIndex < 0 || glyphIndex >= this.fdSelect.length) {
return -1;
}
return this.fdSelect[glyphIndex];
}
}
class CFFOffsetTracker {
constructor() {
this.offsets = Object.create(null);
}
isTracking(key) {
return key in this.offsets;
}
track(key, location) {
if (key in this.offsets) {
throw new FormatError(`Already tracking location of ${key}`);
}
this.offsets[key] = location;
}
offset(value) {
for (const key in this.offsets) {
this.offsets[key] += value;
}
}
setEntryLocation(key, values, output) {
if (!(key in this.offsets)) {
throw new FormatError(`Not tracking location of ${key}`);
}
const data = output.data;
const dataOffset = this.offsets[key];
const size = 5;
for (let i = 0, ii = values.length; i < ii; ++i) {
const offset0 = i * size + dataOffset;
const offset1 = offset0 + 1;
const offset2 = offset0 + 2;
const offset3 = offset0 + 3;
const offset4 = offset0 + 4;
if (data[offset0] !== 0x1d || data[offset1] !== 0 || data[offset2] !== 0 || data[offset3] !== 0 || data[offset4] !== 0) {
throw new FormatError("writing to an offset that is not empty");
}
const value = values[i];
data[offset0] = 0x1d;
data[offset1] = value >> 24 & 0xff;
data[offset2] = value >> 16 & 0xff;
data[offset3] = value >> 8 & 0xff;
data[offset4] = value & 0xff;
}
}
}
class CFFCompiler {
constructor(cff) {
this.cff = cff;
}
compile() {
const cff = this.cff;
const output = {
data: [],
length: 0,
add(data) {
try {
this.data.push(...data);
} catch {
this.data = this.data.concat(data);
}
this.length = this.data.length;
}
};
const header = this.compileHeader(cff.header);
output.add(header);
const nameIndex = this.compileNameIndex(cff.names);
output.add(nameIndex);
if (cff.isCIDFont) {
if (cff.topDict.hasName("FontMatrix")) {
const base = cff.topDict.getByName("FontMatrix");
cff.topDict.removeByName("FontMatrix");
for (const subDict of cff.fdArray) {
let matrix = base.slice(0);
if (subDict.hasName("FontMatrix")) {
matrix = Util.transform(matrix, subDict.getByName("FontMatrix"));
}
subDict.setByName("FontMatrix", matrix);
}
}
}
const xuid = cff.topDict.getByName("XUID");
if (xuid?.length > 16) {
cff.topDict.removeByName("XUID");
}
cff.topDict.setByName("charset", 0);
let compiled = this.compileTopDicts([cff.topDict], output.length, cff.isCIDFont);
output.add(compiled.output);
const topDictTracker = compiled.trackers[0];
const stringIndex = this.compileStringIndex(cff.strings.strings);
output.add(stringIndex);
const globalSubrIndex = this.compileIndex(cff.globalSubrIndex);
output.add(globalSubrIndex);
if (cff.encoding && cff.topDict.hasName("Encoding")) {
if (cff.encoding.predefined) {
topDictTracker.setEntryLocation("Encoding", [cff.encoding.format], output);
} else {
const encoding = this.compileEncoding(cff.encoding);
topDictTracker.setEntryLocation("Encoding", [output.length], output);
output.add(encoding);
}
}
const charset = this.compileCharset(cff.charset, cff.charStrings.count, cff.strings, cff.isCIDFont);
topDictTracker.setEntryLocation("charset", [output.length], output);
output.add(charset);
const charStrings = this.compileCharStrings(cff.charStrings);
topDictTracker.setEntryLocation("CharStrings", [output.length], output);
output.add(charStrings);
if (cff.isCIDFont) {
topDictTracker.setEntryLocation("FDSelect", [output.length], output);
const fdSelect = this.compileFDSelect(cff.fdSelect);
output.add(fdSelect);
compiled = this.compileTopDicts(cff.fdArray, output.length, true);
topDictTracker.setEntryLocation("FDArray", [output.length], output);
output.add(compiled.output);
const fontDictTrackers = compiled.trackers;
this.compilePrivateDicts(cff.fdArray, fontDictTrackers, output);
}
this.compilePrivateDicts([cff.topDict], [topDictTracker], output);
output.add([0]);
return output.data;
}
encodeNumber(value) {
if (Number.isInteger(value)) {
return this.encodeInteger(value);
}
return this.encodeFloat(value);
}
static get EncodeFloatRegExp() {
return shadow(this, "EncodeFloatRegExp", /\.(\d*?)(?:9{5,20}|0{5,20})\d{0,2}(?:e(.+)|$)/);
}
encodeFloat(num) {
let value = num.toString();
const m = CFFCompiler.EncodeFloatRegExp.exec(value);
if (m) {
const epsilon = parseFloat("1e" + ((m[2] ? +m[2] : 0) + m[1].length));
value = (Math.round(num * epsilon) / epsilon).toString();
}
let nibbles = "";
let i, ii;
for (i = 0, ii = value.length; i < ii; ++i) {
const a = value[i];
if (a === "e") {
nibbles += value[++i] === "-" ? "c" : "b";
} else if (a === ".") {
nibbles += "a";
} else if (a === "-") {
nibbles += "e";
} else {
nibbles += a;
}
}
nibbles += nibbles.length & 1 ? "f" : "ff";
const out = [30];
for (i = 0, ii = nibbles.length; i < ii; i += 2) {
out.push(parseInt(nibbles.substring(i, i + 2), 16));
}
return out;
}
encodeInteger(value) {
let code;
if (value >= -107 && value <= 107) {
code = [value + 139];
} else if (value >= 108 && value <= 1131) {
value -= 108;
code = [(value >> 8) + 247, value & 0xff];
} else if (value >= -1131 && value <= -108) {
value = -value - 108;
code = [(value >> 8) + 251, value & 0xff];
} else if (value >= -32768 && value <= 32767) {
code = [0x1c, value >> 8 & 0xff, value & 0xff];
} else {
code = [0x1d, value >> 24 & 0xff, value >> 16 & 0xff, value >> 8 & 0xff, value & 0xff];
}
return code;
}
compileHeader(header) {
return [header.major, header.minor, 4, header.offSize];
}
compileNameIndex(names) {
const nameIndex = new CFFIndex();
for (const name of names) {
const length = Math.min(name.length, 127);
let sanitizedName = new Array(length);
for (let j = 0; j < length; j++) {
let char = name[j];
if (char < "!" || char > "~" || char === "[" || char === "]" || char === "(" || char === ")" || char === "{" || char === "}" || char === "<" || char === ">" || char === "/" || char === "%") {
char = "_";
}
sanitizedName[j] = char;
}
sanitizedName = sanitizedName.join("");
if (sanitizedName === "") {
sanitizedName = "Bad_Font_Name";
}
nameIndex.add(stringToBytes(sanitizedName));
}
return this.compileIndex(nameIndex);
}
compileTopDicts(dicts, length, removeCidKeys) {
const fontDictTrackers = [];
let fdArrayIndex = new CFFIndex();
for (const fontDict of dicts) {
if (removeCidKeys) {
fontDict.removeByName("CIDFontVersion");
fontDict.removeByName("CIDFontRevision");
fontDict.removeByName("CIDFontType");
fontDict.removeByName("CIDCount");
fontDict.removeByName("UIDBase");
}
const fontDictTracker = new CFFOffsetTracker();
const fontDictData = this.compileDict(fontDict, fontDictTracker);
fontDictTrackers.push(fontDictTracker);
fdArrayIndex.add(fontDictData);
fontDictTracker.offset(length);
}
fdArrayIndex = this.compileIndex(fdArrayIndex, fontDictTrackers);
return {
trackers: fontDictTrackers,
output: fdArrayIndex
};
}
compilePrivateDicts(dicts, trackers, output) {
for (let i = 0, ii = dicts.length; i < ii; ++i) {
const fontDict = dicts[i];
const privateDict = fontDict.privateDict;
if (!privateDict || !fontDict.hasName("Private")) {
throw new FormatError("There must be a private dictionary.");
}
const privateDictTracker = new CFFOffsetTracker();
const privateDictData = this.compileDict(privateDict, privateDictTracker);
let outputLength = output.length;
privateDictTracker.offset(outputLength);
if (!privateDictData.length) {
outputLength = 0;
}
trackers[i].setEntryLocation("Private", [privateDictData.length, outputLength], output);
output.add(privateDictData);
if (privateDict.subrsIndex && privateDict.hasName("Subrs")) {
const subrs = this.compileIndex(privateDict.subrsIndex);
privateDictTracker.setEntryLocation("Subrs", [privateDictData.length], output);
output.add(subrs);
}
}
}
compileDict(dict, offsetTracker) {
const out = [];
for (const key of dict.order) {
if (!(key in dict.values)) {
continue;
}
let values = dict.values[key];
let types = dict.types[key];
if (!Array.isArray(types)) {
types = [types];
}
if (!Array.isArray(values)) {
values = [values];
}
if (values.length === 0) {
continue;
}
for (let j = 0, jj = types.length; j < jj; ++j) {
const type = types[j];
const value = values[j];
switch (type) {
case "num":
case "sid":
out.push(...this.encodeNumber(value));
break;
case "offset":
const name = dict.keyToNameMap[key];
if (!offsetTracker.isTracking(name)) {
offsetTracker.track(name, out.length);
}
out.push(0x1d, 0, 0, 0, 0);
break;
case "array":
case "delta":
out.push(...this.encodeNumber(value));
for (let k = 1, kk = values.length; k < kk; ++k) {
out.push(...this.encodeNumber(values[k]));
}
break;
default:
throw new FormatError(`Unknown data type of ${type}`);
}
}
out.push(...dict.opcodes[key]);
}
return out;
}
compileStringIndex(strings) {
const stringIndex = new CFFIndex();
for (const string of strings) {
stringIndex.add(stringToBytes(string));
}
return this.compileIndex(stringIndex);
}
compileCharStrings(charStrings) {
const charStringsIndex = new CFFIndex();
for (let i = 0; i < charStrings.count; i++) {
const glyph = charStrings.get(i);
if (glyph.length === 0) {
charStringsIndex.add(new Uint8Array([0x8b, 0x0e]));
continue;
}
charStringsIndex.add(glyph);
}
return this.compileIndex(charStringsIndex);
}
compileCharset(charset, numGlyphs, strings, isCIDFont) {
let out;
const numGlyphsLessNotDef = numGlyphs - 1;
if (isCIDFont) {
out = new Uint8Array([2, 0, 0, numGlyphsLessNotDef >> 8 & 0xff, numGlyphsLessNotDef & 0xff]);
} else {
const length = 1 + numGlyphsLessNotDef * 2;
out = new Uint8Array(length);
out[0] = 0;
let charsetIndex = 0;
const numCharsets = charset.charset.length;
let warned = false;
for (let i = 1; i < out.length; i += 2) {
let sid = 0;
if (charsetIndex < numCharsets) {
const name = charset.charset[charsetIndex++];
sid = strings.getSID(name);
if (sid === -1) {
sid = 0;
if (!warned) {
warned = true;
warn(`Couldn't find ${name} in CFF strings`);
}
}
}
out[i] = sid >> 8 & 0xff;
out[i + 1] = sid & 0xff;
}
}
return this.compileTypedArray(out);
}
compileEncoding(encoding) {
return this.compileTypedArray(encoding.raw);
}
compileFDSelect(fdSelect) {
const format = fdSelect.format;
let out, i;
switch (format) {
case 0:
out = new Uint8Array(1 + fdSelect.fdSelect.length);
out[0] = format;
for (i = 0; i < fdSelect.fdSelect.length; i++) {
out[i + 1] = fdSelect.fdSelect[i];
}
break;
case 3:
const start = 0;
let lastFD = fdSelect.fdSelect[0];
const ranges = [format, 0, 0, start >> 8 & 0xff, start & 0xff, lastFD];
for (i = 1; i < fdSelect.fdSelect.length; i++) {
const currentFD = fdSelect.fdSelect[i];
if (currentFD !== lastFD) {
ranges.push(i >> 8 & 0xff, i & 0xff, currentFD);
lastFD = currentFD;
}
}
const numRanges = (ranges.length - 3) / 3;
ranges[1] = numRanges >> 8 & 0xff;
ranges[2] = numRanges & 0xff;
ranges.push(i >> 8 & 0xff, i & 0xff);
out = new Uint8Array(ranges);
break;
}
return this.compileTypedArray(out);
}
compileTypedArray(data) {
return Array.from(data);
}
compileIndex(index, trackers = []) {
const objects = index.objects;
const count = objects.length;
if (count === 0) {
return [0, 0];
}
const data = [count >> 8 & 0xff, count & 0xff];
let lastOffset = 1,
i;
for (i = 0; i < count; ++i) {
lastOffset += objects[i].length;
}
let offsetSize;
if (lastOffset < 0x100) {
offsetSize = 1;
} else if (lastOffset < 0x10000) {
offsetSize = 2;
} else if (lastOffset < 0x1000000) {
offsetSize = 3;
} else {
offsetSize = 4;
}
data.push(offsetSize);
let relativeOffset = 1;
for (i = 0; i < count + 1; i++) {
if (offsetSize === 1) {
data.push(relativeOffset & 0xff);
} else if (offsetSize === 2) {
data.push(relativeOffset >> 8 & 0xff, relativeOffset & 0xff);
} else if (offsetSize === 3) {
data.push(relativeOffset >> 16 & 0xff, relativeOffset >> 8 & 0xff, relativeOffset & 0xff);
} else {
data.push(relativeOffset >>> 24 & 0xff, relativeOffset >> 16 & 0xff, relativeOffset >> 8 & 0xff, relativeOffset & 0xff);
}
if (objects[i]) {
relativeOffset += objects[i].length;
}
}
for (i = 0; i < count; i++) {
if (trackers[i]) {
trackers[i].offset(data.length);
}
data.push(...objects[i]);
}
return data;
}
}
;// ./src/core/glyphlist.js
const getGlyphsUnicode = getLookupTableFactory(function (t) {
t.A = 0x0041;
t.AE = 0x00c6;
t.AEacute = 0x01fc;
t.AEmacron = 0x01e2;
t.AEsmall = 0xf7e6;
t.Aacute = 0x00c1;
t.Aacutesmall = 0xf7e1;
t.Abreve = 0x0102;
t.Abreveacute = 0x1eae;
t.Abrevecyrillic = 0x04d0;
t.Abrevedotbelow = 0x1eb6;
t.Abrevegrave = 0x1eb0;
t.Abrevehookabove = 0x1eb2;
t.Abrevetilde = 0x1eb4;
t.Acaron = 0x01cd;
t.Acircle = 0x24b6;
t.Acircumflex = 0x00c2;
t.Acircumflexacute = 0x1ea4;
t.Acircumflexdotbelow = 0x1eac;
t.Acircumflexgrave = 0x1ea6;
t.Acircumflexhookabove = 0x1ea8;
t.Acircumflexsmall = 0xf7e2;
t.Acircumflextilde = 0x1eaa;
t.Acute = 0xf6c9;
t.Acutesmall = 0xf7b4;
t.Acyrillic = 0x0410;
t.Adblgrave = 0x0200;
t.Adieresis = 0x00c4;
t.Adieresiscyrillic = 0x04d2;
t.Adieresismacron = 0x01de;
t.Adieresissmall = 0xf7e4;
t.Adotbelow = 0x1ea0;
t.Adotmacron = 0x01e0;
t.Agrave = 0x00c0;
t.Agravesmall = 0xf7e0;
t.Ahookabove = 0x1ea2;
t.Aiecyrillic = 0x04d4;
t.Ainvertedbreve = 0x0202;
t.Alpha = 0x0391;
t.Alphatonos = 0x0386;
t.Amacron = 0x0100;
t.Amonospace = 0xff21;
t.Aogonek = 0x0104;
t.Aring = 0x00c5;
t.Aringacute = 0x01fa;
t.Aringbelow = 0x1e00;
t.Aringsmall = 0xf7e5;
t.Asmall = 0xf761;
t.Atilde = 0x00c3;
t.Atildesmall = 0xf7e3;
t.Aybarmenian = 0x0531;
t.B = 0x0042;
t.Bcircle = 0x24b7;
t.Bdotaccent = 0x1e02;
t.Bdotbelow = 0x1e04;
t.Becyrillic = 0x0411;
t.Benarmenian = 0x0532;
t.Beta = 0x0392;
t.Bhook = 0x0181;
t.Blinebelow = 0x1e06;
t.Bmonospace = 0xff22;
t.Brevesmall = 0xf6f4;
t.Bsmall = 0xf762;
t.Btopbar = 0x0182;
t.C = 0x0043;
t.Caarmenian = 0x053e;
t.Cacute = 0x0106;
t.Caron = 0xf6ca;
t.Caronsmall = 0xf6f5;
t.Ccaron = 0x010c;
t.Ccedilla = 0x00c7;
t.Ccedillaacute = 0x1e08;
t.Ccedillasmall = 0xf7e7;
t.Ccircle = 0x24b8;
t.Ccircumflex = 0x0108;
t.Cdot = 0x010a;
t.Cdotaccent = 0x010a;
t.Cedillasmall = 0xf7b8;
t.Chaarmenian = 0x0549;
t.Cheabkhasiancyrillic = 0x04bc;
t.Checyrillic = 0x0427;
t.Chedescenderabkhasiancyrillic = 0x04be;
t.Chedescendercyrillic = 0x04b6;
t.Chedieresiscyrillic = 0x04f4;
t.Cheharmenian = 0x0543;
t.Chekhakassiancyrillic = 0x04cb;
t.Cheverticalstrokecyrillic = 0x04b8;
t.Chi = 0x03a7;
t.Chook = 0x0187;
t.Circumflexsmall = 0xf6f6;
t.Cmonospace = 0xff23;
t.Coarmenian = 0x0551;
t.Csmall = 0xf763;
t.D = 0x0044;
t.DZ = 0x01f1;
t.DZcaron = 0x01c4;
t.Daarmenian = 0x0534;
t.Dafrican = 0x0189;
t.Dcaron = 0x010e;
t.Dcedilla = 0x1e10;
t.Dcircle = 0x24b9;
t.Dcircumflexbelow = 0x1e12;
t.Dcroat = 0x0110;
t.Ddotaccent = 0x1e0a;
t.Ddotbelow = 0x1e0c;
t.Decyrillic = 0x0414;
t.Deicoptic = 0x03ee;
t.Delta = 0x2206;
t.Deltagreek = 0x0394;
t.Dhook = 0x018a;
t.Dieresis = 0xf6cb;
t.DieresisAcute = 0xf6cc;
t.DieresisGrave = 0xf6cd;
t.Dieresissmall = 0xf7a8;
t.Digammagreek = 0x03dc;
t.Djecyrillic = 0x0402;
t.Dlinebelow = 0x1e0e;
t.Dmonospace = 0xff24;
t.Dotaccentsmall = 0xf6f7;
t.Dslash = 0x0110;
t.Dsmall = 0xf764;
t.Dtopbar = 0x018b;
t.Dz = 0x01f2;
t.Dzcaron = 0x01c5;
t.Dzeabkhasiancyrillic = 0x04e0;
t.Dzecyrillic = 0x0405;
t.Dzhecyrillic = 0x040f;
t.E = 0x0045;
t.Eacute = 0x00c9;
t.Eacutesmall = 0xf7e9;
t.Ebreve = 0x0114;
t.Ecaron = 0x011a;
t.Ecedillabreve = 0x1e1c;
t.Echarmenian = 0x0535;
t.Ecircle = 0x24ba;
t.Ecircumflex = 0x00ca;
t.Ecircumflexacute = 0x1ebe;
t.Ecircumflexbelow = 0x1e18;
t.Ecircumflexdotbelow = 0x1ec6;
t.Ecircumflexgrave = 0x1ec0;
t.Ecircumflexhookabove = 0x1ec2;
t.Ecircumflexsmall = 0xf7ea;
t.Ecircumflextilde = 0x1ec4;
t.Ecyrillic = 0x0404;
t.Edblgrave = 0x0204;
t.Edieresis = 0x00cb;
t.Edieresissmall = 0xf7eb;
t.Edot = 0x0116;
t.Edotaccent = 0x0116;
t.Edotbelow = 0x1eb8;
t.Efcyrillic = 0x0424;
t.Egrave = 0x00c8;
t.Egravesmall = 0xf7e8;
t.Eharmenian = 0x0537;
t.Ehookabove = 0x1eba;
t.Eightroman = 0x2167;
t.Einvertedbreve = 0x0206;
t.Eiotifiedcyrillic = 0x0464;
t.Elcyrillic = 0x041b;
t.Elevenroman = 0x216a;
t.Emacron = 0x0112;
t.Emacronacute = 0x1e16;
t.Emacrongrave = 0x1e14;
t.Emcyrillic = 0x041c;
t.Emonospace = 0xff25;
t.Encyrillic = 0x041d;
t.Endescendercyrillic = 0x04a2;
t.Eng = 0x014a;
t.Enghecyrillic = 0x04a4;
t.Enhookcyrillic = 0x04c7;
t.Eogonek = 0x0118;
t.Eopen = 0x0190;
t.Epsilon = 0x0395;
t.Epsilontonos = 0x0388;
t.Ercyrillic = 0x0420;
t.Ereversed = 0x018e;
t.Ereversedcyrillic = 0x042d;
t.Escyrillic = 0x0421;
t.Esdescendercyrillic = 0x04aa;
t.Esh = 0x01a9;
t.Esmall = 0xf765;
t.Eta = 0x0397;
t.Etarmenian = 0x0538;
t.Etatonos = 0x0389;
t.Eth = 0x00d0;
t.Ethsmall = 0xf7f0;
t.Etilde = 0x1ebc;
t.Etildebelow = 0x1e1a;
t.Euro = 0x20ac;
t.Ezh = 0x01b7;
t.Ezhcaron = 0x01ee;
t.Ezhreversed = 0x01b8;
t.F = 0x0046;
t.Fcircle = 0x24bb;
t.Fdotaccent = 0x1e1e;
t.Feharmenian = 0x0556;
t.Feicoptic = 0x03e4;
t.Fhook = 0x0191;
t.Fitacyrillic = 0x0472;
t.Fiveroman = 0x2164;
t.Fmonospace = 0xff26;
t.Fourroman = 0x2163;
t.Fsmall = 0xf766;
t.G = 0x0047;
t.GBsquare = 0x3387;
t.Gacute = 0x01f4;
t.Gamma = 0x0393;
t.Gammaafrican = 0x0194;
t.Gangiacoptic = 0x03ea;
t.Gbreve = 0x011e;
t.Gcaron = 0x01e6;
t.Gcedilla = 0x0122;
t.Gcircle = 0x24bc;
t.Gcircumflex = 0x011c;
t.Gcommaaccent = 0x0122;
t.Gdot = 0x0120;
t.Gdotaccent = 0x0120;
t.Gecyrillic = 0x0413;
t.Ghadarmenian = 0x0542;
t.Ghemiddlehookcyrillic = 0x0494;
t.Ghestrokecyrillic = 0x0492;
t.Gheupturncyrillic = 0x0490;
t.Ghook = 0x0193;
t.Gimarmenian = 0x0533;
t.Gjecyrillic = 0x0403;
t.Gmacron = 0x1e20;
t.Gmonospace = 0xff27;
t.Grave = 0xf6ce;
t.Gravesmall = 0xf760;
t.Gsmall = 0xf767;
t.Gsmallhook = 0x029b;
t.Gstroke = 0x01e4;
t.H = 0x0048;
t.H18533 = 0x25cf;
t.H18543 = 0x25aa;
t.H18551 = 0x25ab;
t.H22073 = 0x25a1;
t.HPsquare = 0x33cb;
t.Haabkhasiancyrillic = 0x04a8;
t.Hadescendercyrillic = 0x04b2;
t.Hardsigncyrillic = 0x042a;
t.Hbar = 0x0126;
t.Hbrevebelow = 0x1e2a;
t.Hcedilla = 0x1e28;
t.Hcircle = 0x24bd;
t.Hcircumflex = 0x0124;
t.Hdieresis = 0x1e26;
t.Hdotaccent = 0x1e22;
t.Hdotbelow = 0x1e24;
t.Hmonospace = 0xff28;
t.Hoarmenian = 0x0540;
t.Horicoptic = 0x03e8;
t.Hsmall = 0xf768;
t.Hungarumlaut = 0xf6cf;
t.Hungarumlautsmall = 0xf6f8;
t.Hzsquare = 0x3390;
t.I = 0x0049;
t.IAcyrillic = 0x042f;
t.IJ = 0x0132;
t.IUcyrillic = 0x042e;
t.Iacute = 0x00cd;
t.Iacutesmall = 0xf7ed;
t.Ibreve = 0x012c;
t.Icaron = 0x01cf;
t.Icircle = 0x24be;
t.Icircumflex = 0x00ce;
t.Icircumflexsmall = 0xf7ee;
t.Icyrillic = 0x0406;
t.Idblgrave = 0x0208;
t.Idieresis = 0x00cf;
t.Idieresisacute = 0x1e2e;
t.Idieresiscyrillic = 0x04e4;
t.Idieresissmall = 0xf7ef;
t.Idot = 0x0130;
t.Idotaccent = 0x0130;
t.Idotbelow = 0x1eca;
t.Iebrevecyrillic = 0x04d6;
t.Iecyrillic = 0x0415;
t.Ifraktur = 0x2111;
t.Igrave = 0x00cc;
t.Igravesmall = 0xf7ec;
t.Ihookabove = 0x1ec8;
t.Iicyrillic = 0x0418;
t.Iinvertedbreve = 0x020a;
t.Iishortcyrillic = 0x0419;
t.Imacron = 0x012a;
t.Imacroncyrillic = 0x04e2;
t.Imonospace = 0xff29;
t.Iniarmenian = 0x053b;
t.Iocyrillic = 0x0401;
t.Iogonek = 0x012e;
t.Iota = 0x0399;
t.Iotaafrican = 0x0196;
t.Iotadieresis = 0x03aa;
t.Iotatonos = 0x038a;
t.Ismall = 0xf769;
t.Istroke = 0x0197;
t.Itilde = 0x0128;
t.Itildebelow = 0x1e2c;
t.Izhitsacyrillic = 0x0474;
t.Izhitsadblgravecyrillic = 0x0476;
t.J = 0x004a;
t.Jaarmenian = 0x0541;
t.Jcircle = 0x24bf;
t.Jcircumflex = 0x0134;
t.Jecyrillic = 0x0408;
t.Jheharmenian = 0x054b;
t.Jmonospace = 0xff2a;
t.Jsmall = 0xf76a;
t.K = 0x004b;
t.KBsquare = 0x3385;
t.KKsquare = 0x33cd;
t.Kabashkircyrillic = 0x04a0;
t.Kacute = 0x1e30;
t.Kacyrillic = 0x041a;
t.Kadescendercyrillic = 0x049a;
t.Kahookcyrillic = 0x04c3;
t.Kappa = 0x039a;
t.Kastrokecyrillic = 0x049e;
t.Kaverticalstrokecyrillic = 0x049c;
t.Kcaron = 0x01e8;
t.Kcedilla = 0x0136;
t.Kcircle = 0x24c0;
t.Kcommaaccent = 0x0136;
t.Kdotbelow = 0x1e32;
t.Keharmenian = 0x0554;
t.Kenarmenian = 0x053f;
t.Khacyrillic = 0x0425;
t.Kheicoptic = 0x03e6;
t.Khook = 0x0198;
t.Kjecyrillic = 0x040c;
t.Klinebelow = 0x1e34;
t.Kmonospace = 0xff2b;
t.Koppacyrillic = 0x0480;
t.Koppagreek = 0x03de;
t.Ksicyrillic = 0x046e;
t.Ksmall = 0xf76b;
t.L = 0x004c;
t.LJ = 0x01c7;
t.LL = 0xf6bf;
t.Lacute = 0x0139;
t.Lambda = 0x039b;
t.Lcaron = 0x013d;
t.Lcedilla = 0x013b;
t.Lcircle = 0x24c1;
t.Lcircumflexbelow = 0x1e3c;
t.Lcommaaccent = 0x013b;
t.Ldot = 0x013f;
t.Ldotaccent = 0x013f;
t.Ldotbelow = 0x1e36;
t.Ldotbelowmacron = 0x1e38;
t.Liwnarmenian = 0x053c;
t.Lj = 0x01c8;
t.Ljecyrillic = 0x0409;
t.Llinebelow = 0x1e3a;
t.Lmonospace = 0xff2c;
t.Lslash = 0x0141;
t.Lslashsmall = 0xf6f9;
t.Lsmall = 0xf76c;
t.M = 0x004d;
t.MBsquare = 0x3386;
t.Macron = 0xf6d0;
t.Macronsmall = 0xf7af;
t.Macute = 0x1e3e;
t.Mcircle = 0x24c2;
t.Mdotaccent = 0x1e40;
t.Mdotbelow = 0x1e42;
t.Menarmenian = 0x0544;
t.Mmonospace = 0xff2d;
t.Msmall = 0xf76d;
t.Mturned = 0x019c;
t.Mu = 0x039c;
t.N = 0x004e;
t.NJ = 0x01ca;
t.Nacute = 0x0143;
t.Ncaron = 0x0147;
t.Ncedilla = 0x0145;
t.Ncircle = 0x24c3;
t.Ncircumflexbelow = 0x1e4a;
t.Ncommaaccent = 0x0145;
t.Ndotaccent = 0x1e44;
t.Ndotbelow = 0x1e46;
t.Nhookleft = 0x019d;
t.Nineroman = 0x2168;
t.Nj = 0x01cb;
t.Njecyrillic = 0x040a;
t.Nlinebelow = 0x1e48;
t.Nmonospace = 0xff2e;
t.Nowarmenian = 0x0546;
t.Nsmall = 0xf76e;
t.Ntilde = 0x00d1;
t.Ntildesmall = 0xf7f1;
t.Nu = 0x039d;
t.O = 0x004f;
t.OE = 0x0152;
t.OEsmall = 0xf6fa;
t.Oacute = 0x00d3;
t.Oacutesmall = 0xf7f3;
t.Obarredcyrillic = 0x04e8;
t.Obarreddieresiscyrillic = 0x04ea;
t.Obreve = 0x014e;
t.Ocaron = 0x01d1;
t.Ocenteredtilde = 0x019f;
t.Ocircle = 0x24c4;
t.Ocircumflex = 0x00d4;
t.Ocircumflexacute = 0x1ed0;
t.Ocircumflexdotbelow = 0x1ed8;
t.Ocircumflexgrave = 0x1ed2;
t.Ocircumflexhookabove = 0x1ed4;
t.Ocircumflexsmall = 0xf7f4;
t.Ocircumflextilde = 0x1ed6;
t.Ocyrillic = 0x041e;
t.Odblacute = 0x0150;
t.Odblgrave = 0x020c;
t.Odieresis = 0x00d6;
t.Odieresiscyrillic = 0x04e6;
t.Odieresissmall = 0xf7f6;
t.Odotbelow = 0x1ecc;
t.Ogoneksmall = 0xf6fb;
t.Ograve = 0x00d2;
t.Ogravesmall = 0xf7f2;
t.Oharmenian = 0x0555;
t.Ohm = 0x2126;
t.Ohookabove = 0x1ece;
t.Ohorn = 0x01a0;
t.Ohornacute = 0x1eda;
t.Ohorndotbelow = 0x1ee2;
t.Ohorngrave = 0x1edc;
t.Ohornhookabove = 0x1ede;
t.Ohorntilde = 0x1ee0;
t.Ohungarumlaut = 0x0150;
t.Oi = 0x01a2;
t.Oinvertedbreve = 0x020e;
t.Omacron = 0x014c;
t.Omacronacute = 0x1e52;
t.Omacrongrave = 0x1e50;
t.Omega = 0x2126;
t.Omegacyrillic = 0x0460;
t.Omegagreek = 0x03a9;
t.Omegaroundcyrillic = 0x047a;
t.Omegatitlocyrillic = 0x047c;
t.Omegatonos = 0x038f;
t.Omicron = 0x039f;
t.Omicrontonos = 0x038c;
t.Omonospace = 0xff2f;
t.Oneroman = 0x2160;
t.Oogonek = 0x01ea;
t.Oogonekmacron = 0x01ec;
t.Oopen = 0x0186;
t.Oslash = 0x00d8;
t.Oslashacute = 0x01fe;
t.Oslashsmall = 0xf7f8;
t.Osmall = 0xf76f;
t.Ostrokeacute = 0x01fe;
t.Otcyrillic = 0x047e;
t.Otilde = 0x00d5;
t.Otildeacute = 0x1e4c;
t.Otildedieresis = 0x1e4e;
t.Otildesmall = 0xf7f5;
t.P = 0x0050;
t.Pacute = 0x1e54;
t.Pcircle = 0x24c5;
t.Pdotaccent = 0x1e56;
t.Pecyrillic = 0x041f;
t.Peharmenian = 0x054a;
t.Pemiddlehookcyrillic = 0x04a6;
t.Phi = 0x03a6;
t.Phook = 0x01a4;
t.Pi = 0x03a0;
t.Piwrarmenian = 0x0553;
t.Pmonospace = 0xff30;
t.Psi = 0x03a8;
t.Psicyrillic = 0x0470;
t.Psmall = 0xf770;
t.Q = 0x0051;
t.Qcircle = 0x24c6;
t.Qmonospace = 0xff31;
t.Qsmall = 0xf771;
t.R = 0x0052;
t.Raarmenian = 0x054c;
t.Racute = 0x0154;
t.Rcaron = 0x0158;
t.Rcedilla = 0x0156;
t.Rcircle = 0x24c7;
t.Rcommaaccent = 0x0156;
t.Rdblgrave = 0x0210;
t.Rdotaccent = 0x1e58;
t.Rdotbelow = 0x1e5a;
t.Rdotbelowmacron = 0x1e5c;
t.Reharmenian = 0x0550;
t.Rfraktur = 0x211c;
t.Rho = 0x03a1;
t.Ringsmall = 0xf6fc;
t.Rinvertedbreve = 0x0212;
t.Rlinebelow = 0x1e5e;
t.Rmonospace = 0xff32;
t.Rsmall = 0xf772;
t.Rsmallinverted = 0x0281;
t.Rsmallinvertedsuperior = 0x02b6;
t.S = 0x0053;
t.SF010000 = 0x250c;
t.SF020000 = 0x2514;
t.SF030000 = 0x2510;
t.SF040000 = 0x2518;
t.SF050000 = 0x253c;
t.SF060000 = 0x252c;
t.SF070000 = 0x2534;
t.SF080000 = 0x251c;
t.SF090000 = 0x2524;
t.SF100000 = 0x2500;
t.SF110000 = 0x2502;
t.SF190000 = 0x2561;
t.SF200000 = 0x2562;
t.SF210000 = 0x2556;
t.SF220000 = 0x2555;
t.SF230000 = 0x2563;
t.SF240000 = 0x2551;
t.SF250000 = 0x2557;
t.SF260000 = 0x255d;
t.SF270000 = 0x255c;
t.SF280000 = 0x255b;
t.SF360000 = 0x255e;
t.SF370000 = 0x255f;
t.SF380000 = 0x255a;
t.SF390000 = 0x2554;
t.SF400000 = 0x2569;
t.SF410000 = 0x2566;
t.SF420000 = 0x2560;
t.SF430000 = 0x2550;
t.SF440000 = 0x256c;
t.SF450000 = 0x2567;
t.SF460000 = 0x2568;
t.SF470000 = 0x2564;
t.SF480000 = 0x2565;
t.SF490000 = 0x2559;
t.SF500000 = 0x2558;
t.SF510000 = 0x2552;
t.SF520000 = 0x2553;
t.SF530000 = 0x256b;
t.SF540000 = 0x256a;
t.Sacute = 0x015a;
t.Sacutedotaccent = 0x1e64;
t.Sampigreek = 0x03e0;
t.Scaron = 0x0160;
t.Scarondotaccent = 0x1e66;
t.Scaronsmall = 0xf6fd;
t.Scedilla = 0x015e;
t.Schwa = 0x018f;
t.Schwacyrillic = 0x04d8;
t.Schwadieresiscyrillic = 0x04da;
t.Scircle = 0x24c8;
t.Scircumflex = 0x015c;
t.Scommaaccent = 0x0218;
t.Sdotaccent = 0x1e60;
t.Sdotbelow = 0x1e62;
t.Sdotbelowdotaccent = 0x1e68;
t.Seharmenian = 0x054d;
t.Sevenroman = 0x2166;
t.Shaarmenian = 0x0547;
t.Shacyrillic = 0x0428;
t.Shchacyrillic = 0x0429;
t.Sheicoptic = 0x03e2;
t.Shhacyrillic = 0x04ba;
t.Shimacoptic = 0x03ec;
t.Sigma = 0x03a3;
t.Sixroman = 0x2165;
t.Smonospace = 0xff33;
t.Softsigncyrillic = 0x042c;
t.Ssmall = 0xf773;
t.Stigmagreek = 0x03da;
t.T = 0x0054;
t.Tau = 0x03a4;
t.Tbar = 0x0166;
t.Tcaron = 0x0164;
t.Tcedilla = 0x0162;
t.Tcircle = 0x24c9;
t.Tcircumflexbelow = 0x1e70;
t.Tcommaaccent = 0x0162;
t.Tdotaccent = 0x1e6a;
t.Tdotbelow = 0x1e6c;
t.Tecyrillic = 0x0422;
t.Tedescendercyrillic = 0x04ac;
t.Tenroman = 0x2169;
t.Tetsecyrillic = 0x04b4;
t.Theta = 0x0398;
t.Thook = 0x01ac;
t.Thorn = 0x00de;
t.Thornsmall = 0xf7fe;
t.Threeroman = 0x2162;
t.Tildesmall = 0xf6fe;
t.Tiwnarmenian = 0x054f;
t.Tlinebelow = 0x1e6e;
t.Tmonospace = 0xff34;
t.Toarmenian = 0x0539;
t.Tonefive = 0x01bc;
t.Tonesix = 0x0184;
t.Tonetwo = 0x01a7;
t.Tretroflexhook = 0x01ae;
t.Tsecyrillic = 0x0426;
t.Tshecyrillic = 0x040b;
t.Tsmall = 0xf774;
t.Twelveroman = 0x216b;
t.Tworoman = 0x2161;
t.U = 0x0055;
t.Uacute = 0x00da;
t.Uacutesmall = 0xf7fa;
t.Ubreve = 0x016c;
t.Ucaron = 0x01d3;
t.Ucircle = 0x24ca;
t.Ucircumflex = 0x00db;
t.Ucircumflexbelow = 0x1e76;
t.Ucircumflexsmall = 0xf7fb;
t.Ucyrillic = 0x0423;
t.Udblacute = 0x0170;
t.Udblgrave = 0x0214;
t.Udieresis = 0x00dc;
t.Udieresisacute = 0x01d7;
t.Udieresisbelow = 0x1e72;
t.Udieresiscaron = 0x01d9;
t.Udieresiscyrillic = 0x04f0;
t.Udieresisgrave = 0x01db;
t.Udieresismacron = 0x01d5;
t.Udieresissmall = 0xf7fc;
t.Udotbelow = 0x1ee4;
t.Ugrave = 0x00d9;
t.Ugravesmall = 0xf7f9;
t.Uhookabove = 0x1ee6;
t.Uhorn = 0x01af;
t.Uhornacute = 0x1ee8;
t.Uhorndotbelow = 0x1ef0;
t.Uhorngrave = 0x1eea;
t.Uhornhookabove = 0x1eec;
t.Uhorntilde = 0x1eee;
t.Uhungarumlaut = 0x0170;
t.Uhungarumlautcyrillic = 0x04f2;
t.Uinvertedbreve = 0x0216;
t.Ukcyrillic = 0x0478;
t.Umacron = 0x016a;
t.Umacroncyrillic = 0x04ee;
t.Umacrondieresis = 0x1e7a;
t.Umonospace = 0xff35;
t.Uogonek = 0x0172;
t.Upsilon = 0x03a5;
t.Upsilon1 = 0x03d2;
t.Upsilonacutehooksymbolgreek = 0x03d3;
t.Upsilonafrican = 0x01b1;
t.Upsilondieresis = 0x03ab;
t.Upsilondieresishooksymbolgreek = 0x03d4;
t.Upsilonhooksymbol = 0x03d2;
t.Upsilontonos = 0x038e;
t.Uring = 0x016e;
t.Ushortcyrillic = 0x040e;
t.Usmall = 0xf775;
t.Ustraightcyrillic = 0x04ae;
t.Ustraightstrokecyrillic = 0x04b0;
t.Utilde = 0x0168;
t.Utildeacute = 0x1e78;
t.Utildebelow = 0x1e74;
t.V = 0x0056;
t.Vcircle = 0x24cb;
t.Vdotbelow = 0x1e7e;
t.Vecyrillic = 0x0412;
t.Vewarmenian = 0x054e;
t.Vhook = 0x01b2;
t.Vmonospace = 0xff36;
t.Voarmenian = 0x0548;
t.Vsmall = 0xf776;
t.Vtilde = 0x1e7c;
t.W = 0x0057;
t.Wacute = 0x1e82;
t.Wcircle = 0x24cc;
t.Wcircumflex = 0x0174;
t.Wdieresis = 0x1e84;
t.Wdotaccent = 0x1e86;
t.Wdotbelow = 0x1e88;
t.Wgrave = 0x1e80;
t.Wmonospace = 0xff37;
t.Wsmall = 0xf777;
t.X = 0x0058;
t.Xcircle = 0x24cd;
t.Xdieresis = 0x1e8c;
t.Xdotaccent = 0x1e8a;
t.Xeharmenian = 0x053d;
t.Xi = 0x039e;
t.Xmonospace = 0xff38;
t.Xsmall = 0xf778;
t.Y = 0x0059;
t.Yacute = 0x00dd;
t.Yacutesmall = 0xf7fd;
t.Yatcyrillic = 0x0462;
t.Ycircle = 0x24ce;
t.Ycircumflex = 0x0176;
t.Ydieresis = 0x0178;
t.Ydieresissmall = 0xf7ff;
t.Ydotaccent = 0x1e8e;
t.Ydotbelow = 0x1ef4;
t.Yericyrillic = 0x042b;
t.Yerudieresiscyrillic = 0x04f8;
t.Ygrave = 0x1ef2;
t.Yhook = 0x01b3;
t.Yhookabove = 0x1ef6;
t.Yiarmenian = 0x0545;
t.Yicyrillic = 0x0407;
t.Yiwnarmenian = 0x0552;
t.Ymonospace = 0xff39;
t.Ysmall = 0xf779;
t.Ytilde = 0x1ef8;
t.Yusbigcyrillic = 0x046a;
t.Yusbigiotifiedcyrillic = 0x046c;
t.Yuslittlecyrillic = 0x0466;
t.Yuslittleiotifiedcyrillic = 0x0468;
t.Z = 0x005a;
t.Zaarmenian = 0x0536;
t.Zacute = 0x0179;
t.Zcaron = 0x017d;
t.Zcaronsmall = 0xf6ff;
t.Zcircle = 0x24cf;
t.Zcircumflex = 0x1e90;
t.Zdot = 0x017b;
t.Zdotaccent = 0x017b;
t.Zdotbelow = 0x1e92;
t.Zecyrillic = 0x0417;
t.Zedescendercyrillic = 0x0498;
t.Zedieresiscyrillic = 0x04de;
t.Zeta = 0x0396;
t.Zhearmenian = 0x053a;
t.Zhebrevecyrillic = 0x04c1;
t.Zhecyrillic = 0x0416;
t.Zhedescendercyrillic = 0x0496;
t.Zhedieresiscyrillic = 0x04dc;
t.Zlinebelow = 0x1e94;
t.Zmonospace = 0xff3a;
t.Zsmall = 0xf77a;
t.Zstroke = 0x01b5;
t.a = 0x0061;
t.aabengali = 0x0986;
t.aacute = 0x00e1;
t.aadeva = 0x0906;
t.aagujarati = 0x0a86;
t.aagurmukhi = 0x0a06;
t.aamatragurmukhi = 0x0a3e;
t.aarusquare = 0x3303;
t.aavowelsignbengali = 0x09be;
t.aavowelsigndeva = 0x093e;
t.aavowelsigngujarati = 0x0abe;
t.abbreviationmarkarmenian = 0x055f;
t.abbreviationsigndeva = 0x0970;
t.abengali = 0x0985;
t.abopomofo = 0x311a;
t.abreve = 0x0103;
t.abreveacute = 0x1eaf;
t.abrevecyrillic = 0x04d1;
t.abrevedotbelow = 0x1eb7;
t.abrevegrave = 0x1eb1;
t.abrevehookabove = 0x1eb3;
t.abrevetilde = 0x1eb5;
t.acaron = 0x01ce;
t.acircle = 0x24d0;
t.acircumflex = 0x00e2;
t.acircumflexacute = 0x1ea5;
t.acircumflexdotbelow = 0x1ead;
t.acircumflexgrave = 0x1ea7;
t.acircumflexhookabove = 0x1ea9;
t.acircumflextilde = 0x1eab;
t.acute = 0x00b4;
t.acutebelowcmb = 0x0317;
t.acutecmb = 0x0301;
t.acutecomb = 0x0301;
t.acutedeva = 0x0954;
t.acutelowmod = 0x02cf;
t.acutetonecmb = 0x0341;
t.acyrillic = 0x0430;
t.adblgrave = 0x0201;
t.addakgurmukhi = 0x0a71;
t.adeva = 0x0905;
t.adieresis = 0x00e4;
t.adieresiscyrillic = 0x04d3;
t.adieresismacron = 0x01df;
t.adotbelow = 0x1ea1;
t.adotmacron = 0x01e1;
t.ae = 0x00e6;
t.aeacute = 0x01fd;
t.aekorean = 0x3150;
t.aemacron = 0x01e3;
t.afii00208 = 0x2015;
t.afii08941 = 0x20a4;
t.afii10017 = 0x0410;
t.afii10018 = 0x0411;
t.afii10019 = 0x0412;
t.afii10020 = 0x0413;
t.afii10021 = 0x0414;
t.afii10022 = 0x0415;
t.afii10023 = 0x0401;
t.afii10024 = 0x0416;
t.afii10025 = 0x0417;
t.afii10026 = 0x0418;
t.afii10027 = 0x0419;
t.afii10028 = 0x041a;
t.afii10029 = 0x041b;
t.afii10030 = 0x041c;
t.afii10031 = 0x041d;
t.afii10032 = 0x041e;
t.afii10033 = 0x041f;
t.afii10034 = 0x0420;
t.afii10035 = 0x0421;
t.afii10036 = 0x0422;
t.afii10037 = 0x0423;
t.afii10038 = 0x0424;
t.afii10039 = 0x0425;
t.afii10040 = 0x0426;
t.afii10041 = 0x0427;
t.afii10042 = 0x0428;
t.afii10043 = 0x0429;
t.afii10044 = 0x042a;
t.afii10045 = 0x042b;
t.afii10046 = 0x042c;
t.afii10047 = 0x042d;
t.afii10048 = 0x042e;
t.afii10049 = 0x042f;
t.afii10050 = 0x0490;
t.afii10051 = 0x0402;
t.afii10052 = 0x0403;
t.afii10053 = 0x0404;
t.afii10054 = 0x0405;
t.afii10055 = 0x0406;
t.afii10056 = 0x0407;
t.afii10057 = 0x0408;
t.afii10058 = 0x0409;
t.afii10059 = 0x040a;
t.afii10060 = 0x040b;
t.afii10061 = 0x040c;
t.afii10062 = 0x040e;
t.afii10063 = 0xf6c4;
t.afii10064 = 0xf6c5;
t.afii10065 = 0x0430;
t.afii10066 = 0x0431;
t.afii10067 = 0x0432;
t.afii10068 = 0x0433;
t.afii10069 = 0x0434;
t.afii10070 = 0x0435;
t.afii10071 = 0x0451;
t.afii10072 = 0x0436;
t.afii10073 = 0x0437;
t.afii10074 = 0x0438;
t.afii10075 = 0x0439;
t.afii10076 = 0x043a;
t.afii10077 = 0x043b;
t.afii10078 = 0x043c;
t.afii10079 = 0x043d;
t.afii10080 = 0x043e;
t.afii10081 = 0x043f;
t.afii10082 = 0x0440;
t.afii10083 = 0x0441;
t.afii10084 = 0x0442;
t.afii10085 = 0x0443;
t.afii10086 = 0x0444;
t.afii10087 = 0x0445;
t.afii10088 = 0x0446;
t.afii10089 = 0x0447;
t.afii10090 = 0x0448;
t.afii10091 = 0x0449;
t.afii10092 = 0x044a;
t.afii10093 = 0x044b;
t.afii10094 = 0x044c;
t.afii10095 = 0x044d;
t.afii10096 = 0x044e;
t.afii10097 = 0x044f;
t.afii10098 = 0x0491;
t.afii10099 = 0x0452;
t.afii10100 = 0x0453;
t.afii10101 = 0x0454;
t.afii10102 = 0x0455;
t.afii10103 = 0x0456;
t.afii10104 = 0x0457;
t.afii10105 = 0x0458;
t.afii10106 = 0x0459;
t.afii10107 = 0x045a;
t.afii10108 = 0x045b;
t.afii10109 = 0x045c;
t.afii10110 = 0x045e;
t.afii10145 = 0x040f;
t.afii10146 = 0x0462;
t.afii10147 = 0x0472;
t.afii10148 = 0x0474;
t.afii10192 = 0xf6c6;
t.afii10193 = 0x045f;
t.afii10194 = 0x0463;
t.afii10195 = 0x0473;
t.afii10196 = 0x0475;
t.afii10831 = 0xf6c7;
t.afii10832 = 0xf6c8;
t.afii10846 = 0x04d9;
t.afii299 = 0x200e;
t.afii300 = 0x200f;
t.afii301 = 0x200d;
t.afii57381 = 0x066a;
t.afii57388 = 0x060c;
t.afii57392 = 0x0660;
t.afii57393 = 0x0661;
t.afii57394 = 0x0662;
t.afii57395 = 0x0663;
t.afii57396 = 0x0664;
t.afii57397 = 0x0665;
t.afii57398 = 0x0666;
t.afii57399 = 0x0667;
t.afii57400 = 0x0668;
t.afii57401 = 0x0669;
t.afii57403 = 0x061b;
t.afii57407 = 0x061f;
t.afii57409 = 0x0621;
t.afii57410 = 0x0622;
t.afii57411 = 0x0623;
t.afii57412 = 0x0624;
t.afii57413 = 0x0625;
t.afii57414 = 0x0626;
t.afii57415 = 0x0627;
t.afii57416 = 0x0628;
t.afii57417 = 0x0629;
t.afii57418 = 0x062a;
t.afii57419 = 0x062b;
t.afii57420 = 0x062c;
t.afii57421 = 0x062d;
t.afii57422 = 0x062e;
t.afii57423 = 0x062f;
t.afii57424 = 0x0630;
t.afii57425 = 0x0631;
t.afii57426 = 0x0632;
t.afii57427 = 0x0633;
t.afii57428 = 0x0634;
t.afii57429 = 0x0635;
t.afii57430 = 0x0636;
t.afii57431 = 0x0637;
t.afii57432 = 0x0638;
t.afii57433 = 0x0639;
t.afii57434 = 0x063a;
t.afii57440 = 0x0640;
t.afii57441 = 0x0641;
t.afii57442 = 0x0642;
t.afii57443 = 0x0643;
t.afii57444 = 0x0644;
t.afii57445 = 0x0645;
t.afii57446 = 0x0646;
t.afii57448 = 0x0648;
t.afii57449 = 0x0649;
t.afii57450 = 0x064a;
t.afii57451 = 0x064b;
t.afii57452 = 0x064c;
t.afii57453 = 0x064d;
t.afii57454 = 0x064e;
t.afii57455 = 0x064f;
t.afii57456 = 0x0650;
t.afii57457 = 0x0651;
t.afii57458 = 0x0652;
t.afii57470 = 0x0647;
t.afii57505 = 0x06a4;
t.afii57506 = 0x067e;
t.afii57507 = 0x0686;
t.afii57508 = 0x0698;
t.afii57509 = 0x06af;
t.afii57511 = 0x0679;
t.afii57512 = 0x0688;
t.afii57513 = 0x0691;
t.afii57514 = 0x06ba;
t.afii57519 = 0x06d2;
t.afii57534 = 0x06d5;
t.afii57636 = 0x20aa;
t.afii57645 = 0x05be;
t.afii57658 = 0x05c3;
t.afii57664 = 0x05d0;
t.afii57665 = 0x05d1;
t.afii57666 = 0x05d2;
t.afii57667 = 0x05d3;
t.afii57668 = 0x05d4;
t.afii57669 = 0x05d5;
t.afii57670 = 0x05d6;
t.afii57671 = 0x05d7;
t.afii57672 = 0x05d8;
t.afii57673 = 0x05d9;
t.afii57674 = 0x05da;
t.afii57675 = 0x05db;
t.afii57676 = 0x05dc;
t.afii57677 = 0x05dd;
t.afii57678 = 0x05de;
t.afii57679 = 0x05df;
t.afii57680 = 0x05e0;
t.afii57681 = 0x05e1;
t.afii57682 = 0x05e2;
t.afii57683 = 0x05e3;
t.afii57684 = 0x05e4;
t.afii57685 = 0x05e5;
t.afii57686 = 0x05e6;
t.afii57687 = 0x05e7;
t.afii57688 = 0x05e8;
t.afii57689 = 0x05e9;
t.afii57690 = 0x05ea;
t.afii57694 = 0xfb2a;
t.afii57695 = 0xfb2b;
t.afii57700 = 0xfb4b;
t.afii57705 = 0xfb1f;
t.afii57716 = 0x05f0;
t.afii57717 = 0x05f1;
t.afii57718 = 0x05f2;
t.afii57723 = 0xfb35;
t.afii57793 = 0x05b4;
t.afii57794 = 0x05b5;
t.afii57795 = 0x05b6;
t.afii57796 = 0x05bb;
t.afii57797 = 0x05b8;
t.afii57798 = 0x05b7;
t.afii57799 = 0x05b0;
t.afii57800 = 0x05b2;
t.afii57801 = 0x05b1;
t.afii57802 = 0x05b3;
t.afii57803 = 0x05c2;
t.afii57804 = 0x05c1;
t.afii57806 = 0x05b9;
t.afii57807 = 0x05bc;
t.afii57839 = 0x05bd;
t.afii57841 = 0x05bf;
t.afii57842 = 0x05c0;
t.afii57929 = 0x02bc;
t.afii61248 = 0x2105;
t.afii61289 = 0x2113;
t.afii61352 = 0x2116;
t.afii61573 = 0x202c;
t.afii61574 = 0x202d;
t.afii61575 = 0x202e;
t.afii61664 = 0x200c;
t.afii63167 = 0x066d;
t.afii64937 = 0x02bd;
t.agrave = 0x00e0;
t.agujarati = 0x0a85;
t.agurmukhi = 0x0a05;
t.ahiragana = 0x3042;
t.ahookabove = 0x1ea3;
t.aibengali = 0x0990;
t.aibopomofo = 0x311e;
t.aideva = 0x0910;
t.aiecyrillic = 0x04d5;
t.aigujarati = 0x0a90;
t.aigurmukhi = 0x0a10;
t.aimatragurmukhi = 0x0a48;
t.ainarabic = 0x0639;
t.ainfinalarabic = 0xfeca;
t.aininitialarabic = 0xfecb;
t.ainmedialarabic = 0xfecc;
t.ainvertedbreve = 0x0203;
t.aivowelsignbengali = 0x09c8;
t.aivowelsigndeva = 0x0948;
t.aivowelsigngujarati = 0x0ac8;
t.akatakana = 0x30a2;
t.akatakanahalfwidth = 0xff71;
t.akorean = 0x314f;
t.alef = 0x05d0;
t.alefarabic = 0x0627;
t.alefdageshhebrew = 0xfb30;
t.aleffinalarabic = 0xfe8e;
t.alefhamzaabovearabic = 0x0623;
t.alefhamzaabovefinalarabic = 0xfe84;
t.alefhamzabelowarabic = 0x0625;
t.alefhamzabelowfinalarabic = 0xfe88;
t.alefhebrew = 0x05d0;
t.aleflamedhebrew = 0xfb4f;
t.alefmaddaabovearabic = 0x0622;
t.alefmaddaabovefinalarabic = 0xfe82;
t.alefmaksuraarabic = 0x0649;
t.alefmaksurafinalarabic = 0xfef0;
t.alefmaksurainitialarabic = 0xfef3;
t.alefmaksuramedialarabic = 0xfef4;
t.alefpatahhebrew = 0xfb2e;
t.alefqamatshebrew = 0xfb2f;
t.aleph = 0x2135;
t.allequal = 0x224c;
t.alpha = 0x03b1;
t.alphatonos = 0x03ac;
t.amacron = 0x0101;
t.amonospace = 0xff41;
t.ampersand = 0x0026;
t.ampersandmonospace = 0xff06;
t.ampersandsmall = 0xf726;
t.amsquare = 0x33c2;
t.anbopomofo = 0x3122;
t.angbopomofo = 0x3124;
t.angbracketleft = 0x3008;
t.angbracketright = 0x3009;
t.angkhankhuthai = 0x0e5a;
t.angle = 0x2220;
t.anglebracketleft = 0x3008;
t.anglebracketleftvertical = 0xfe3f;
t.anglebracketright = 0x3009;
t.anglebracketrightvertical = 0xfe40;
t.angleleft = 0x2329;
t.angleright = 0x232a;
t.angstrom = 0x212b;
t.anoteleia = 0x0387;
t.anudattadeva = 0x0952;
t.anusvarabengali = 0x0982;
t.anusvaradeva = 0x0902;
t.anusvaragujarati = 0x0a82;
t.aogonek = 0x0105;
t.apaatosquare = 0x3300;
t.aparen = 0x249c;
t.apostrophearmenian = 0x055a;
t.apostrophemod = 0x02bc;
t.apple = 0xf8ff;
t.approaches = 0x2250;
t.approxequal = 0x2248;
t.approxequalorimage = 0x2252;
t.approximatelyequal = 0x2245;
t.araeaekorean = 0x318e;
t.araeakorean = 0x318d;
t.arc = 0x2312;
t.arighthalfring = 0x1e9a;
t.aring = 0x00e5;
t.aringacute = 0x01fb;
t.aringbelow = 0x1e01;
t.arrowboth = 0x2194;
t.arrowdashdown = 0x21e3;
t.arrowdashleft = 0x21e0;
t.arrowdashright = 0x21e2;
t.arrowdashup = 0x21e1;
t.arrowdblboth = 0x21d4;
t.arrowdbldown = 0x21d3;
t.arrowdblleft = 0x21d0;
t.arrowdblright = 0x21d2;
t.arrowdblup = 0x21d1;
t.arrowdown = 0x2193;
t.arrowdownleft = 0x2199;
t.arrowdownright = 0x2198;
t.arrowdownwhite = 0x21e9;
t.arrowheaddownmod = 0x02c5;
t.arrowheadleftmod = 0x02c2;
t.arrowheadrightmod = 0x02c3;
t.arrowheadupmod = 0x02c4;
t.arrowhorizex = 0xf8e7;
t.arrowleft = 0x2190;
t.arrowleftdbl = 0x21d0;
t.arrowleftdblstroke = 0x21cd;
t.arrowleftoverright = 0x21c6;
t.arrowleftwhite = 0x21e6;
t.arrowright = 0x2192;
t.arrowrightdblstroke = 0x21cf;
t.arrowrightheavy = 0x279e;
t.arrowrightoverleft = 0x21c4;
t.arrowrightwhite = 0x21e8;
t.arrowtableft = 0x21e4;
t.arrowtabright = 0x21e5;
t.arrowup = 0x2191;
t.arrowupdn = 0x2195;
t.arrowupdnbse = 0x21a8;
t.arrowupdownbase = 0x21a8;
t.arrowupleft = 0x2196;
t.arrowupleftofdown = 0x21c5;
t.arrowupright = 0x2197;
t.arrowupwhite = 0x21e7;
t.arrowvertex = 0xf8e6;
t.asciicircum = 0x005e;
t.asciicircummonospace = 0xff3e;
t.asciitilde = 0x007e;
t.asciitildemonospace = 0xff5e;
t.ascript = 0x0251;
t.ascriptturned = 0x0252;
t.asmallhiragana = 0x3041;
t.asmallkatakana = 0x30a1;
t.asmallkatakanahalfwidth = 0xff67;
t.asterisk = 0x002a;
t.asteriskaltonearabic = 0x066d;
t.asteriskarabic = 0x066d;
t.asteriskmath = 0x2217;
t.asteriskmonospace = 0xff0a;
t.asterisksmall = 0xfe61;
t.asterism = 0x2042;
t.asuperior = 0xf6e9;
t.asymptoticallyequal = 0x2243;
t.at = 0x0040;
t.atilde = 0x00e3;
t.atmonospace = 0xff20;
t.atsmall = 0xfe6b;
t.aturned = 0x0250;
t.aubengali = 0x0994;
t.aubopomofo = 0x3120;
t.audeva = 0x0914;
t.augujarati = 0x0a94;
t.augurmukhi = 0x0a14;
t.aulengthmarkbengali = 0x09d7;
t.aumatragurmukhi = 0x0a4c;
t.auvowelsignbengali = 0x09cc;
t.auvowelsigndeva = 0x094c;
t.auvowelsigngujarati = 0x0acc;
t.avagrahadeva = 0x093d;
t.aybarmenian = 0x0561;
t.ayin = 0x05e2;
t.ayinaltonehebrew = 0xfb20;
t.ayinhebrew = 0x05e2;
t.b = 0x0062;
t.babengali = 0x09ac;
t.backslash = 0x005c;
t.backslashmonospace = 0xff3c;
t.badeva = 0x092c;
t.bagujarati = 0x0aac;
t.bagurmukhi = 0x0a2c;
t.bahiragana = 0x3070;
t.bahtthai = 0x0e3f;
t.bakatakana = 0x30d0;
t.bar = 0x007c;
t.barmonospace = 0xff5c;
t.bbopomofo = 0x3105;
t.bcircle = 0x24d1;
t.bdotaccent = 0x1e03;
t.bdotbelow = 0x1e05;
t.beamedsixteenthnotes = 0x266c;
t.because = 0x2235;
t.becyrillic = 0x0431;
t.beharabic = 0x0628;
t.behfinalarabic = 0xfe90;
t.behinitialarabic = 0xfe91;
t.behiragana = 0x3079;
t.behmedialarabic = 0xfe92;
t.behmeeminitialarabic = 0xfc9f;
t.behmeemisolatedarabic = 0xfc08;
t.behnoonfinalarabic = 0xfc6d;
t.bekatakana = 0x30d9;
t.benarmenian = 0x0562;
t.bet = 0x05d1;
t.beta = 0x03b2;
t.betasymbolgreek = 0x03d0;
t.betdagesh = 0xfb31;
t.betdageshhebrew = 0xfb31;
t.bethebrew = 0x05d1;
t.betrafehebrew = 0xfb4c;
t.bhabengali = 0x09ad;
t.bhadeva = 0x092d;
t.bhagujarati = 0x0aad;
t.bhagurmukhi = 0x0a2d;
t.bhook = 0x0253;
t.bihiragana = 0x3073;
t.bikatakana = 0x30d3;
t.bilabialclick = 0x0298;
t.bindigurmukhi = 0x0a02;
t.birusquare = 0x3331;
t.blackcircle = 0x25cf;
t.blackdiamond = 0x25c6;
t.blackdownpointingtriangle = 0x25bc;
t.blackleftpointingpointer = 0x25c4;
t.blackleftpointingtriangle = 0x25c0;
t.blacklenticularbracketleft = 0x3010;
t.blacklenticularbracketleftvertical = 0xfe3b;
t.blacklenticularbracketright = 0x3011;
t.blacklenticularbracketrightvertical = 0xfe3c;
t.blacklowerlefttriangle = 0x25e3;
t.blacklowerrighttriangle = 0x25e2;
t.blackrectangle = 0x25ac;
t.blackrightpointingpointer = 0x25ba;
t.blackrightpointingtriangle = 0x25b6;
t.blacksmallsquare = 0x25aa;
t.blacksmilingface = 0x263b;
t.blacksquare = 0x25a0;
t.blackstar = 0x2605;
t.blackupperlefttriangle = 0x25e4;
t.blackupperrighttriangle = 0x25e5;
t.blackuppointingsmalltriangle = 0x25b4;
t.blackuppointingtriangle = 0x25b2;
t.blank = 0x2423;
t.blinebelow = 0x1e07;
t.block = 0x2588;
t.bmonospace = 0xff42;
t.bobaimaithai = 0x0e1a;
t.bohiragana = 0x307c;
t.bokatakana = 0x30dc;
t.bparen = 0x249d;
t.bqsquare = 0x33c3;
t.braceex = 0xf8f4;
t.braceleft = 0x007b;
t.braceleftbt = 0xf8f3;
t.braceleftmid = 0xf8f2;
t.braceleftmonospace = 0xff5b;
t.braceleftsmall = 0xfe5b;
t.bracelefttp = 0xf8f1;
t.braceleftvertical = 0xfe37;
t.braceright = 0x007d;
t.bracerightbt = 0xf8fe;
t.bracerightmid = 0xf8fd;
t.bracerightmonospace = 0xff5d;
t.bracerightsmall = 0xfe5c;
t.bracerighttp = 0xf8fc;
t.bracerightvertical = 0xfe38;
t.bracketleft = 0x005b;
t.bracketleftbt = 0xf8f0;
t.bracketleftex = 0xf8ef;
t.bracketleftmonospace = 0xff3b;
t.bracketlefttp = 0xf8ee;
t.bracketright = 0x005d;
t.bracketrightbt = 0xf8fb;
t.bracketrightex = 0xf8fa;
t.bracketrightmonospace = 0xff3d;
t.bracketrighttp = 0xf8f9;
t.breve = 0x02d8;
t.brevebelowcmb = 0x032e;
t.brevecmb = 0x0306;
t.breveinvertedbelowcmb = 0x032f;
t.breveinvertedcmb = 0x0311;
t.breveinverteddoublecmb = 0x0361;
t.bridgebelowcmb = 0x032a;
t.bridgeinvertedbelowcmb = 0x033a;
t.brokenbar = 0x00a6;
t.bstroke = 0x0180;
t.bsuperior = 0xf6ea;
t.btopbar = 0x0183;
t.buhiragana = 0x3076;
t.bukatakana = 0x30d6;
t.bullet = 0x2022;
t.bulletinverse = 0x25d8;
t.bulletoperator = 0x2219;
t.bullseye = 0x25ce;
t.c = 0x0063;
t.caarmenian = 0x056e;
t.cabengali = 0x099a;
t.cacute = 0x0107;
t.cadeva = 0x091a;
t.cagujarati = 0x0a9a;
t.cagurmukhi = 0x0a1a;
t.calsquare = 0x3388;
t.candrabindubengali = 0x0981;
t.candrabinducmb = 0x0310;
t.candrabindudeva = 0x0901;
t.candrabindugujarati = 0x0a81;
t.capslock = 0x21ea;
t.careof = 0x2105;
t.caron = 0x02c7;
t.caronbelowcmb = 0x032c;
t.caroncmb = 0x030c;
t.carriagereturn = 0x21b5;
t.cbopomofo = 0x3118;
t.ccaron = 0x010d;
t.ccedilla = 0x00e7;
t.ccedillaacute = 0x1e09;
t.ccircle = 0x24d2;
t.ccircumflex = 0x0109;
t.ccurl = 0x0255;
t.cdot = 0x010b;
t.cdotaccent = 0x010b;
t.cdsquare = 0x33c5;
t.cedilla = 0x00b8;
t.cedillacmb = 0x0327;
t.cent = 0x00a2;
t.centigrade = 0x2103;
t.centinferior = 0xf6df;
t.centmonospace = 0xffe0;
t.centoldstyle = 0xf7a2;
t.centsuperior = 0xf6e0;
t.chaarmenian = 0x0579;
t.chabengali = 0x099b;
t.chadeva = 0x091b;
t.chagujarati = 0x0a9b;
t.chagurmukhi = 0x0a1b;
t.chbopomofo = 0x3114;
t.cheabkhasiancyrillic = 0x04bd;
t.checkmark = 0x2713;
t.checyrillic = 0x0447;
t.chedescenderabkhasiancyrillic = 0x04bf;
t.chedescendercyrillic = 0x04b7;
t.chedieresiscyrillic = 0x04f5;
t.cheharmenian = 0x0573;
t.chekhakassiancyrillic = 0x04cc;
t.cheverticalstrokecyrillic = 0x04b9;
t.chi = 0x03c7;
t.chieuchacirclekorean = 0x3277;
t.chieuchaparenkorean = 0x3217;
t.chieuchcirclekorean = 0x3269;
t.chieuchkorean = 0x314a;
t.chieuchparenkorean = 0x3209;
t.chochangthai = 0x0e0a;
t.chochanthai = 0x0e08;
t.chochingthai = 0x0e09;
t.chochoethai = 0x0e0c;
t.chook = 0x0188;
t.cieucacirclekorean = 0x3276;
t.cieucaparenkorean = 0x3216;
t.cieuccirclekorean = 0x3268;
t.cieuckorean = 0x3148;
t.cieucparenkorean = 0x3208;
t.cieucuparenkorean = 0x321c;
t.circle = 0x25cb;
t.circlecopyrt = 0x00a9;
t.circlemultiply = 0x2297;
t.circleot = 0x2299;
t.circleplus = 0x2295;
t.circlepostalmark = 0x3036;
t.circlewithlefthalfblack = 0x25d0;
t.circlewithrighthalfblack = 0x25d1;
t.circumflex = 0x02c6;
t.circumflexbelowcmb = 0x032d;
t.circumflexcmb = 0x0302;
t.clear = 0x2327;
t.clickalveolar = 0x01c2;
t.clickdental = 0x01c0;
t.clicklateral = 0x01c1;
t.clickretroflex = 0x01c3;
t.club = 0x2663;
t.clubsuitblack = 0x2663;
t.clubsuitwhite = 0x2667;
t.cmcubedsquare = 0x33a4;
t.cmonospace = 0xff43;
t.cmsquaredsquare = 0x33a0;
t.coarmenian = 0x0581;
t.colon = 0x003a;
t.colonmonetary = 0x20a1;
t.colonmonospace = 0xff1a;
t.colonsign = 0x20a1;
t.colonsmall = 0xfe55;
t.colontriangularhalfmod = 0x02d1;
t.colontriangularmod = 0x02d0;
t.comma = 0x002c;
t.commaabovecmb = 0x0313;
t.commaaboverightcmb = 0x0315;
t.commaaccent = 0xf6c3;
t.commaarabic = 0x060c;
t.commaarmenian = 0x055d;
t.commainferior = 0xf6e1;
t.commamonospace = 0xff0c;
t.commareversedabovecmb = 0x0314;
t.commareversedmod = 0x02bd;
t.commasmall = 0xfe50;
t.commasuperior = 0xf6e2;
t.commaturnedabovecmb = 0x0312;
t.commaturnedmod = 0x02bb;
t.compass = 0x263c;
t.congruent = 0x2245;
t.contourintegral = 0x222e;
t.control = 0x2303;
t.controlACK = 0x0006;
t.controlBEL = 0x0007;
t.controlBS = 0x0008;
t.controlCAN = 0x0018;
t.controlCR = 0x000d;
t.controlDC1 = 0x0011;
t.controlDC2 = 0x0012;
t.controlDC3 = 0x0013;
t.controlDC4 = 0x0014;
t.controlDEL = 0x007f;
t.controlDLE = 0x0010;
t.controlEM = 0x0019;
t.controlENQ = 0x0005;
t.controlEOT = 0x0004;
t.controlESC = 0x001b;
t.controlETB = 0x0017;
t.controlETX = 0x0003;
t.controlFF = 0x000c;
t.controlFS = 0x001c;
t.controlGS = 0x001d;
t.controlHT = 0x0009;
t.controlLF = 0x000a;
t.controlNAK = 0x0015;
t.controlNULL = 0x0000;
t.controlRS = 0x001e;
t.controlSI = 0x000f;
t.controlSO = 0x000e;
t.controlSOT = 0x0002;
t.controlSTX = 0x0001;
t.controlSUB = 0x001a;
t.controlSYN = 0x0016;
t.controlUS = 0x001f;
t.controlVT = 0x000b;
t.copyright = 0x00a9;
t.copyrightsans = 0xf8e9;
t.copyrightserif = 0xf6d9;
t.cornerbracketleft = 0x300c;
t.cornerbracketlefthalfwidth = 0xff62;
t.cornerbracketleftvertical = 0xfe41;
t.cornerbracketright = 0x300d;
t.cornerbracketrighthalfwidth = 0xff63;
t.cornerbracketrightvertical = 0xfe42;
t.corporationsquare = 0x337f;
t.cosquare = 0x33c7;
t.coverkgsquare = 0x33c6;
t.cparen = 0x249e;
t.cruzeiro = 0x20a2;
t.cstretched = 0x0297;
t.curlyand = 0x22cf;
t.curlyor = 0x22ce;
t.currency = 0x00a4;
t.cyrBreve = 0xf6d1;
t.cyrFlex = 0xf6d2;
t.cyrbreve = 0xf6d4;
t.cyrflex = 0xf6d5;
t.d = 0x0064;
t.daarmenian = 0x0564;
t.dabengali = 0x09a6;
t.dadarabic = 0x0636;
t.dadeva = 0x0926;
t.dadfinalarabic = 0xfebe;
t.dadinitialarabic = 0xfebf;
t.dadmedialarabic = 0xfec0;
t.dagesh = 0x05bc;
t.dageshhebrew = 0x05bc;
t.dagger = 0x2020;
t.daggerdbl = 0x2021;
t.dagujarati = 0x0aa6;
t.dagurmukhi = 0x0a26;
t.dahiragana = 0x3060;
t.dakatakana = 0x30c0;
t.dalarabic = 0x062f;
t.dalet = 0x05d3;
t.daletdagesh = 0xfb33;
t.daletdageshhebrew = 0xfb33;
t.dalethebrew = 0x05d3;
t.dalfinalarabic = 0xfeaa;
t.dammaarabic = 0x064f;
t.dammalowarabic = 0x064f;
t.dammatanaltonearabic = 0x064c;
t.dammatanarabic = 0x064c;
t.danda = 0x0964;
t.dargahebrew = 0x05a7;
t.dargalefthebrew = 0x05a7;
t.dasiapneumatacyrilliccmb = 0x0485;
t.dblGrave = 0xf6d3;
t.dblanglebracketleft = 0x300a;
t.dblanglebracketleftvertical = 0xfe3d;
t.dblanglebracketright = 0x300b;
t.dblanglebracketrightvertical = 0xfe3e;
t.dblarchinvertedbelowcmb = 0x032b;
t.dblarrowleft = 0x21d4;
t.dblarrowright = 0x21d2;
t.dbldanda = 0x0965;
t.dblgrave = 0xf6d6;
t.dblgravecmb = 0x030f;
t.dblintegral = 0x222c;
t.dbllowline = 0x2017;
t.dbllowlinecmb = 0x0333;
t.dbloverlinecmb = 0x033f;
t.dblprimemod = 0x02ba;
t.dblverticalbar = 0x2016;
t.dblverticallineabovecmb = 0x030e;
t.dbopomofo = 0x3109;
t.dbsquare = 0x33c8;
t.dcaron = 0x010f;
t.dcedilla = 0x1e11;
t.dcircle = 0x24d3;
t.dcircumflexbelow = 0x1e13;
t.dcroat = 0x0111;
t.ddabengali = 0x09a1;
t.ddadeva = 0x0921;
t.ddagujarati = 0x0aa1;
t.ddagurmukhi = 0x0a21;
t.ddalarabic = 0x0688;
t.ddalfinalarabic = 0xfb89;
t.dddhadeva = 0x095c;
t.ddhabengali = 0x09a2;
t.ddhadeva = 0x0922;
t.ddhagujarati = 0x0aa2;
t.ddhagurmukhi = 0x0a22;
t.ddotaccent = 0x1e0b;
t.ddotbelow = 0x1e0d;
t.decimalseparatorarabic = 0x066b;
t.decimalseparatorpersian = 0x066b;
t.decyrillic = 0x0434;
t.degree = 0x00b0;
t.dehihebrew = 0x05ad;
t.dehiragana = 0x3067;
t.deicoptic = 0x03ef;
t.dekatakana = 0x30c7;
t.deleteleft = 0x232b;
t.deleteright = 0x2326;
t.delta = 0x03b4;
t.deltaturned = 0x018d;
t.denominatorminusonenumeratorbengali = 0x09f8;
t.dezh = 0x02a4;
t.dhabengali = 0x09a7;
t.dhadeva = 0x0927;
t.dhagujarati = 0x0aa7;
t.dhagurmukhi = 0x0a27;
t.dhook = 0x0257;
t.dialytikatonos = 0x0385;
t.dialytikatonoscmb = 0x0344;
t.diamond = 0x2666;
t.diamondsuitwhite = 0x2662;
t.dieresis = 0x00a8;
t.dieresisacute = 0xf6d7;
t.dieresisbelowcmb = 0x0324;
t.dieresiscmb = 0x0308;
t.dieresisgrave = 0xf6d8;
t.dieresistonos = 0x0385;
t.dihiragana = 0x3062;
t.dikatakana = 0x30c2;
t.dittomark = 0x3003;
t.divide = 0x00f7;
t.divides = 0x2223;
t.divisionslash = 0x2215;
t.djecyrillic = 0x0452;
t.dkshade = 0x2593;
t.dlinebelow = 0x1e0f;
t.dlsquare = 0x3397;
t.dmacron = 0x0111;
t.dmonospace = 0xff44;
t.dnblock = 0x2584;
t.dochadathai = 0x0e0e;
t.dodekthai = 0x0e14;
t.dohiragana = 0x3069;
t.dokatakana = 0x30c9;
t.dollar = 0x0024;
t.dollarinferior = 0xf6e3;
t.dollarmonospace = 0xff04;
t.dollaroldstyle = 0xf724;
t.dollarsmall = 0xfe69;
t.dollarsuperior = 0xf6e4;
t.dong = 0x20ab;
t.dorusquare = 0x3326;
t.dotaccent = 0x02d9;
t.dotaccentcmb = 0x0307;
t.dotbelowcmb = 0x0323;
t.dotbelowcomb = 0x0323;
t.dotkatakana = 0x30fb;
t.dotlessi = 0x0131;
t.dotlessj = 0xf6be;
t.dotlessjstrokehook = 0x0284;
t.dotmath = 0x22c5;
t.dottedcircle = 0x25cc;
t.doubleyodpatah = 0xfb1f;
t.doubleyodpatahhebrew = 0xfb1f;
t.downtackbelowcmb = 0x031e;
t.downtackmod = 0x02d5;
t.dparen = 0x249f;
t.dsuperior = 0xf6eb;
t.dtail = 0x0256;
t.dtopbar = 0x018c;
t.duhiragana = 0x3065;
t.dukatakana = 0x30c5;
t.dz = 0x01f3;
t.dzaltone = 0x02a3;
t.dzcaron = 0x01c6;
t.dzcurl = 0x02a5;
t.dzeabkhasiancyrillic = 0x04e1;
t.dzecyrillic = 0x0455;
t.dzhecyrillic = 0x045f;
t.e = 0x0065;
t.eacute = 0x00e9;
t.earth = 0x2641;
t.ebengali = 0x098f;
t.ebopomofo = 0x311c;
t.ebreve = 0x0115;
t.ecandradeva = 0x090d;
t.ecandragujarati = 0x0a8d;
t.ecandravowelsigndeva = 0x0945;
t.ecandravowelsigngujarati = 0x0ac5;
t.ecaron = 0x011b;
t.ecedillabreve = 0x1e1d;
t.echarmenian = 0x0565;
t.echyiwnarmenian = 0x0587;
t.ecircle = 0x24d4;
t.ecircumflex = 0x00ea;
t.ecircumflexacute = 0x1ebf;
t.ecircumflexbelow = 0x1e19;
t.ecircumflexdotbelow = 0x1ec7;
t.ecircumflexgrave = 0x1ec1;
t.ecircumflexhookabove = 0x1ec3;
t.ecircumflextilde = 0x1ec5;
t.ecyrillic = 0x0454;
t.edblgrave = 0x0205;
t.edeva = 0x090f;
t.edieresis = 0x00eb;
t.edot = 0x0117;
t.edotaccent = 0x0117;
t.edotbelow = 0x1eb9;
t.eegurmukhi = 0x0a0f;
t.eematragurmukhi = 0x0a47;
t.efcyrillic = 0x0444;
t.egrave = 0x00e8;
t.egujarati = 0x0a8f;
t.eharmenian = 0x0567;
t.ehbopomofo = 0x311d;
t.ehiragana = 0x3048;
t.ehookabove = 0x1ebb;
t.eibopomofo = 0x311f;
t.eight = 0x0038;
t.eightarabic = 0x0668;
t.eightbengali = 0x09ee;
t.eightcircle = 0x2467;
t.eightcircleinversesansserif = 0x2791;
t.eightdeva = 0x096e;
t.eighteencircle = 0x2471;
t.eighteenparen = 0x2485;
t.eighteenperiod = 0x2499;
t.eightgujarati = 0x0aee;
t.eightgurmukhi = 0x0a6e;
t.eighthackarabic = 0x0668;
t.eighthangzhou = 0x3028;
t.eighthnotebeamed = 0x266b;
t.eightideographicparen = 0x3227;
t.eightinferior = 0x2088;
t.eightmonospace = 0xff18;
t.eightoldstyle = 0xf738;
t.eightparen = 0x247b;
t.eightperiod = 0x248f;
t.eightpersian = 0x06f8;
t.eightroman = 0x2177;
t.eightsuperior = 0x2078;
t.eightthai = 0x0e58;
t.einvertedbreve = 0x0207;
t.eiotifiedcyrillic = 0x0465;
t.ekatakana = 0x30a8;
t.ekatakanahalfwidth = 0xff74;
t.ekonkargurmukhi = 0x0a74;
t.ekorean = 0x3154;
t.elcyrillic = 0x043b;
t.element = 0x2208;
t.elevencircle = 0x246a;
t.elevenparen = 0x247e;
t.elevenperiod = 0x2492;
t.elevenroman = 0x217a;
t.ellipsis = 0x2026;
t.ellipsisvertical = 0x22ee;
t.emacron = 0x0113;
t.emacronacute = 0x1e17;
t.emacrongrave = 0x1e15;
t.emcyrillic = 0x043c;
t.emdash = 0x2014;
t.emdashvertical = 0xfe31;
t.emonospace = 0xff45;
t.emphasismarkarmenian = 0x055b;
t.emptyset = 0x2205;
t.enbopomofo = 0x3123;
t.encyrillic = 0x043d;
t.endash = 0x2013;
t.endashvertical = 0xfe32;
t.endescendercyrillic = 0x04a3;
t.eng = 0x014b;
t.engbopomofo = 0x3125;
t.enghecyrillic = 0x04a5;
t.enhookcyrillic = 0x04c8;
t.enspace = 0x2002;
t.eogonek = 0x0119;
t.eokorean = 0x3153;
t.eopen = 0x025b;
t.eopenclosed = 0x029a;
t.eopenreversed = 0x025c;
t.eopenreversedclosed = 0x025e;
t.eopenreversedhook = 0x025d;
t.eparen = 0x24a0;
t.epsilon = 0x03b5;
t.epsilontonos = 0x03ad;
t.equal = 0x003d;
t.equalmonospace = 0xff1d;
t.equalsmall = 0xfe66;
t.equalsuperior = 0x207c;
t.equivalence = 0x2261;
t.erbopomofo = 0x3126;
t.ercyrillic = 0x0440;
t.ereversed = 0x0258;
t.ereversedcyrillic = 0x044d;
t.escyrillic = 0x0441;
t.esdescendercyrillic = 0x04ab;
t.esh = 0x0283;
t.eshcurl = 0x0286;
t.eshortdeva = 0x090e;
t.eshortvowelsigndeva = 0x0946;
t.eshreversedloop = 0x01aa;
t.eshsquatreversed = 0x0285;
t.esmallhiragana = 0x3047;
t.esmallkatakana = 0x30a7;
t.esmallkatakanahalfwidth = 0xff6a;
t.estimated = 0x212e;
t.esuperior = 0xf6ec;
t.eta = 0x03b7;
t.etarmenian = 0x0568;
t.etatonos = 0x03ae;
t.eth = 0x00f0;
t.etilde = 0x1ebd;
t.etildebelow = 0x1e1b;
t.etnahtafoukhhebrew = 0x0591;
t.etnahtafoukhlefthebrew = 0x0591;
t.etnahtahebrew = 0x0591;
t.etnahtalefthebrew = 0x0591;
t.eturned = 0x01dd;
t.eukorean = 0x3161;
t.euro = 0x20ac;
t.evowelsignbengali = 0x09c7;
t.evowelsigndeva = 0x0947;
t.evowelsigngujarati = 0x0ac7;
t.exclam = 0x0021;
t.exclamarmenian = 0x055c;
t.exclamdbl = 0x203c;
t.exclamdown = 0x00a1;
t.exclamdownsmall = 0xf7a1;
t.exclammonospace = 0xff01;
t.exclamsmall = 0xf721;
t.existential = 0x2203;
t.ezh = 0x0292;
t.ezhcaron = 0x01ef;
t.ezhcurl = 0x0293;
t.ezhreversed = 0x01b9;
t.ezhtail = 0x01ba;
t.f = 0x0066;
t.fadeva = 0x095e;
t.fagurmukhi = 0x0a5e;
t.fahrenheit = 0x2109;
t.fathaarabic = 0x064e;
t.fathalowarabic = 0x064e;
t.fathatanarabic = 0x064b;
t.fbopomofo = 0x3108;
t.fcircle = 0x24d5;
t.fdotaccent = 0x1e1f;
t.feharabic = 0x0641;
t.feharmenian = 0x0586;
t.fehfinalarabic = 0xfed2;
t.fehinitialarabic = 0xfed3;
t.fehmedialarabic = 0xfed4;
t.feicoptic = 0x03e5;
t.female = 0x2640;
t.ff = 0xfb00;
t.f_f = 0xfb00;
t.ffi = 0xfb03;
t.f_f_i = 0xfb03;
t.ffl = 0xfb04;
t.f_f_l = 0xfb04;
t.fi = 0xfb01;
t.f_i = 0xfb01;
t.fifteencircle = 0x246e;
t.fifteenparen = 0x2482;
t.fifteenperiod = 0x2496;
t.figuredash = 0x2012;
t.filledbox = 0x25a0;
t.filledrect = 0x25ac;
t.finalkaf = 0x05da;
t.finalkafdagesh = 0xfb3a;
t.finalkafdageshhebrew = 0xfb3a;
t.finalkafhebrew = 0x05da;
t.finalmem = 0x05dd;
t.finalmemhebrew = 0x05dd;
t.finalnun = 0x05df;
t.finalnunhebrew = 0x05df;
t.finalpe = 0x05e3;
t.finalpehebrew = 0x05e3;
t.finaltsadi = 0x05e5;
t.finaltsadihebrew = 0x05e5;
t.firsttonechinese = 0x02c9;
t.fisheye = 0x25c9;
t.fitacyrillic = 0x0473;
t.five = 0x0035;
t.fivearabic = 0x0665;
t.fivebengali = 0x09eb;
t.fivecircle = 0x2464;
t.fivecircleinversesansserif = 0x278e;
t.fivedeva = 0x096b;
t.fiveeighths = 0x215d;
t.fivegujarati = 0x0aeb;
t.fivegurmukhi = 0x0a6b;
t.fivehackarabic = 0x0665;
t.fivehangzhou = 0x3025;
t.fiveideographicparen = 0x3224;
t.fiveinferior = 0x2085;
t.fivemonospace = 0xff15;
t.fiveoldstyle = 0xf735;
t.fiveparen = 0x2478;
t.fiveperiod = 0x248c;
t.fivepersian = 0x06f5;
t.fiveroman = 0x2174;
t.fivesuperior = 0x2075;
t.fivethai = 0x0e55;
t.fl = 0xfb02;
t.f_l = 0xfb02;
t.florin = 0x0192;
t.fmonospace = 0xff46;
t.fmsquare = 0x3399;
t.fofanthai = 0x0e1f;
t.fofathai = 0x0e1d;
t.fongmanthai = 0x0e4f;
t.forall = 0x2200;
t.four = 0x0034;
t.fourarabic = 0x0664;
t.fourbengali = 0x09ea;
t.fourcircle = 0x2463;
t.fourcircleinversesansserif = 0x278d;
t.fourdeva = 0x096a;
t.fourgujarati = 0x0aea;
t.fourgurmukhi = 0x0a6a;
t.fourhackarabic = 0x0664;
t.fourhangzhou = 0x3024;
t.fourideographicparen = 0x3223;
t.fourinferior = 0x2084;
t.fourmonospace = 0xff14;
t.fournumeratorbengali = 0x09f7;
t.fouroldstyle = 0xf734;
t.fourparen = 0x2477;
t.fourperiod = 0x248b;
t.fourpersian = 0x06f4;
t.fourroman = 0x2173;
t.foursuperior = 0x2074;
t.fourteencircle = 0x246d;
t.fourteenparen = 0x2481;
t.fourteenperiod = 0x2495;
t.fourthai = 0x0e54;
t.fourthtonechinese = 0x02cb;
t.fparen = 0x24a1;
t.fraction = 0x2044;
t.franc = 0x20a3;
t.g = 0x0067;
t.gabengali = 0x0997;
t.gacute = 0x01f5;
t.gadeva = 0x0917;
t.gafarabic = 0x06af;
t.gaffinalarabic = 0xfb93;
t.gafinitialarabic = 0xfb94;
t.gafmedialarabic = 0xfb95;
t.gagujarati = 0x0a97;
t.gagurmukhi = 0x0a17;
t.gahiragana = 0x304c;
t.gakatakana = 0x30ac;
t.gamma = 0x03b3;
t.gammalatinsmall = 0x0263;
t.gammasuperior = 0x02e0;
t.gangiacoptic = 0x03eb;
t.gbopomofo = 0x310d;
t.gbreve = 0x011f;
t.gcaron = 0x01e7;
t.gcedilla = 0x0123;
t.gcircle = 0x24d6;
t.gcircumflex = 0x011d;
t.gcommaaccent = 0x0123;
t.gdot = 0x0121;
t.gdotaccent = 0x0121;
t.gecyrillic = 0x0433;
t.gehiragana = 0x3052;
t.gekatakana = 0x30b2;
t.geometricallyequal = 0x2251;
t.gereshaccenthebrew = 0x059c;
t.gereshhebrew = 0x05f3;
t.gereshmuqdamhebrew = 0x059d;
t.germandbls = 0x00df;
t.gershayimaccenthebrew = 0x059e;
t.gershayimhebrew = 0x05f4;
t.getamark = 0x3013;
t.ghabengali = 0x0998;
t.ghadarmenian = 0x0572;
t.ghadeva = 0x0918;
t.ghagujarati = 0x0a98;
t.ghagurmukhi = 0x0a18;
t.ghainarabic = 0x063a;
t.ghainfinalarabic = 0xfece;
t.ghaininitialarabic = 0xfecf;
t.ghainmedialarabic = 0xfed0;
t.ghemiddlehookcyrillic = 0x0495;
t.ghestrokecyrillic = 0x0493;
t.gheupturncyrillic = 0x0491;
t.ghhadeva = 0x095a;
t.ghhagurmukhi = 0x0a5a;
t.ghook = 0x0260;
t.ghzsquare = 0x3393;
t.gihiragana = 0x304e;
t.gikatakana = 0x30ae;
t.gimarmenian = 0x0563;
t.gimel = 0x05d2;
t.gimeldagesh = 0xfb32;
t.gimeldageshhebrew = 0xfb32;
t.gimelhebrew = 0x05d2;
t.gjecyrillic = 0x0453;
t.glottalinvertedstroke = 0x01be;
t.glottalstop = 0x0294;
t.glottalstopinverted = 0x0296;
t.glottalstopmod = 0x02c0;
t.glottalstopreversed = 0x0295;
t.glottalstopreversedmod = 0x02c1;
t.glottalstopreversedsuperior = 0x02e4;
t.glottalstopstroke = 0x02a1;
t.glottalstopstrokereversed = 0x02a2;
t.gmacron = 0x1e21;
t.gmonospace = 0xff47;
t.gohiragana = 0x3054;
t.gokatakana = 0x30b4;
t.gparen = 0x24a2;
t.gpasquare = 0x33ac;
t.gradient = 0x2207;
t.grave = 0x0060;
t.gravebelowcmb = 0x0316;
t.gravecmb = 0x0300;
t.gravecomb = 0x0300;
t.gravedeva = 0x0953;
t.gravelowmod = 0x02ce;
t.gravemonospace = 0xff40;
t.gravetonecmb = 0x0340;
t.greater = 0x003e;
t.greaterequal = 0x2265;
t.greaterequalorless = 0x22db;
t.greatermonospace = 0xff1e;
t.greaterorequivalent = 0x2273;
t.greaterorless = 0x2277;
t.greateroverequal = 0x2267;
t.greatersmall = 0xfe65;
t.gscript = 0x0261;
t.gstroke = 0x01e5;
t.guhiragana = 0x3050;
t.guillemotleft = 0x00ab;
t.guillemotright = 0x00bb;
t.guilsinglleft = 0x2039;
t.guilsinglright = 0x203a;
t.gukatakana = 0x30b0;
t.guramusquare = 0x3318;
t.gysquare = 0x33c9;
t.h = 0x0068;
t.haabkhasiancyrillic = 0x04a9;
t.haaltonearabic = 0x06c1;
t.habengali = 0x09b9;
t.hadescendercyrillic = 0x04b3;
t.hadeva = 0x0939;
t.hagujarati = 0x0ab9;
t.hagurmukhi = 0x0a39;
t.haharabic = 0x062d;
t.hahfinalarabic = 0xfea2;
t.hahinitialarabic = 0xfea3;
t.hahiragana = 0x306f;
t.hahmedialarabic = 0xfea4;
t.haitusquare = 0x332a;
t.hakatakana = 0x30cf;
t.hakatakanahalfwidth = 0xff8a;
t.halantgurmukhi = 0x0a4d;
t.hamzaarabic = 0x0621;
t.hamzalowarabic = 0x0621;
t.hangulfiller = 0x3164;
t.hardsigncyrillic = 0x044a;
t.harpoonleftbarbup = 0x21bc;
t.harpoonrightbarbup = 0x21c0;
t.hasquare = 0x33ca;
t.hatafpatah = 0x05b2;
t.hatafpatah16 = 0x05b2;
t.hatafpatah23 = 0x05b2;
t.hatafpatah2f = 0x05b2;
t.hatafpatahhebrew = 0x05b2;
t.hatafpatahnarrowhebrew = 0x05b2;
t.hatafpatahquarterhebrew = 0x05b2;
t.hatafpatahwidehebrew = 0x05b2;
t.hatafqamats = 0x05b3;
t.hatafqamats1b = 0x05b3;
t.hatafqamats28 = 0x05b3;
t.hatafqamats34 = 0x05b3;
t.hatafqamatshebrew = 0x05b3;
t.hatafqamatsnarrowhebrew = 0x05b3;
t.hatafqamatsquarterhebrew = 0x05b3;
t.hatafqamatswidehebrew = 0x05b3;
t.hatafsegol = 0x05b1;
t.hatafsegol17 = 0x05b1;
t.hatafsegol24 = 0x05b1;
t.hatafsegol30 = 0x05b1;
t.hatafsegolhebrew = 0x05b1;
t.hatafsegolnarrowhebrew = 0x05b1;
t.hatafsegolquarterhebrew = 0x05b1;
t.hatafsegolwidehebrew = 0x05b1;
t.hbar = 0x0127;
t.hbopomofo = 0x310f;
t.hbrevebelow = 0x1e2b;
t.hcedilla = 0x1e29;
t.hcircle = 0x24d7;
t.hcircumflex = 0x0125;
t.hdieresis = 0x1e27;
t.hdotaccent = 0x1e23;
t.hdotbelow = 0x1e25;
t.he = 0x05d4;
t.heart = 0x2665;
t.heartsuitblack = 0x2665;
t.heartsuitwhite = 0x2661;
t.hedagesh = 0xfb34;
t.hedageshhebrew = 0xfb34;
t.hehaltonearabic = 0x06c1;
t.heharabic = 0x0647;
t.hehebrew = 0x05d4;
t.hehfinalaltonearabic = 0xfba7;
t.hehfinalalttwoarabic = 0xfeea;
t.hehfinalarabic = 0xfeea;
t.hehhamzaabovefinalarabic = 0xfba5;
t.hehhamzaaboveisolatedarabic = 0xfba4;
t.hehinitialaltonearabic = 0xfba8;
t.hehinitialarabic = 0xfeeb;
t.hehiragana = 0x3078;
t.hehmedialaltonearabic = 0xfba9;
t.hehmedialarabic = 0xfeec;
t.heiseierasquare = 0x337b;
t.hekatakana = 0x30d8;
t.hekatakanahalfwidth = 0xff8d;
t.hekutaarusquare = 0x3336;
t.henghook = 0x0267;
t.herutusquare = 0x3339;
t.het = 0x05d7;
t.hethebrew = 0x05d7;
t.hhook = 0x0266;
t.hhooksuperior = 0x02b1;
t.hieuhacirclekorean = 0x327b;
t.hieuhaparenkorean = 0x321b;
t.hieuhcirclekorean = 0x326d;
t.hieuhkorean = 0x314e;
t.hieuhparenkorean = 0x320d;
t.hihiragana = 0x3072;
t.hikatakana = 0x30d2;
t.hikatakanahalfwidth = 0xff8b;
t.hiriq = 0x05b4;
t.hiriq14 = 0x05b4;
t.hiriq21 = 0x05b4;
t.hiriq2d = 0x05b4;
t.hiriqhebrew = 0x05b4;
t.hiriqnarrowhebrew = 0x05b4;
t.hiriqquarterhebrew = 0x05b4;
t.hiriqwidehebrew = 0x05b4;
t.hlinebelow = 0x1e96;
t.hmonospace = 0xff48;
t.hoarmenian = 0x0570;
t.hohipthai = 0x0e2b;
t.hohiragana = 0x307b;
t.hokatakana = 0x30db;
t.hokatakanahalfwidth = 0xff8e;
t.holam = 0x05b9;
t.holam19 = 0x05b9;
t.holam26 = 0x05b9;
t.holam32 = 0x05b9;
t.holamhebrew = 0x05b9;
t.holamnarrowhebrew = 0x05b9;
t.holamquarterhebrew = 0x05b9;
t.holamwidehebrew = 0x05b9;
t.honokhukthai = 0x0e2e;
t.hookabovecomb = 0x0309;
t.hookcmb = 0x0309;
t.hookpalatalizedbelowcmb = 0x0321;
t.hookretroflexbelowcmb = 0x0322;
t.hoonsquare = 0x3342;
t.horicoptic = 0x03e9;
t.horizontalbar = 0x2015;
t.horncmb = 0x031b;
t.hotsprings = 0x2668;
t.house = 0x2302;
t.hparen = 0x24a3;
t.hsuperior = 0x02b0;
t.hturned = 0x0265;
t.huhiragana = 0x3075;
t.huiitosquare = 0x3333;
t.hukatakana = 0x30d5;
t.hukatakanahalfwidth = 0xff8c;
t.hungarumlaut = 0x02dd;
t.hungarumlautcmb = 0x030b;
t.hv = 0x0195;
t.hyphen = 0x002d;
t.hypheninferior = 0xf6e5;
t.hyphenmonospace = 0xff0d;
t.hyphensmall = 0xfe63;
t.hyphensuperior = 0xf6e6;
t.hyphentwo = 0x2010;
t.i = 0x0069;
t.iacute = 0x00ed;
t.iacyrillic = 0x044f;
t.ibengali = 0x0987;
t.ibopomofo = 0x3127;
t.ibreve = 0x012d;
t.icaron = 0x01d0;
t.icircle = 0x24d8;
t.icircumflex = 0x00ee;
t.icyrillic = 0x0456;
t.idblgrave = 0x0209;
t.ideographearthcircle = 0x328f;
t.ideographfirecircle = 0x328b;
t.ideographicallianceparen = 0x323f;
t.ideographiccallparen = 0x323a;
t.ideographiccentrecircle = 0x32a5;
t.ideographicclose = 0x3006;
t.ideographiccomma = 0x3001;
t.ideographiccommaleft = 0xff64;
t.ideographiccongratulationparen = 0x3237;
t.ideographiccorrectcircle = 0x32a3;
t.ideographicearthparen = 0x322f;
t.ideographicenterpriseparen = 0x323d;
t.ideographicexcellentcircle = 0x329d;
t.ideographicfestivalparen = 0x3240;
t.ideographicfinancialcircle = 0x3296;
t.ideographicfinancialparen = 0x3236;
t.ideographicfireparen = 0x322b;
t.ideographichaveparen = 0x3232;
t.ideographichighcircle = 0x32a4;
t.ideographiciterationmark = 0x3005;
t.ideographiclaborcircle = 0x3298;
t.ideographiclaborparen = 0x3238;
t.ideographicleftcircle = 0x32a7;
t.ideographiclowcircle = 0x32a6;
t.ideographicmedicinecircle = 0x32a9;
t.ideographicmetalparen = 0x322e;
t.ideographicmoonparen = 0x322a;
t.ideographicnameparen = 0x3234;
t.ideographicperiod = 0x3002;
t.ideographicprintcircle = 0x329e;
t.ideographicreachparen = 0x3243;
t.ideographicrepresentparen = 0x3239;
t.ideographicresourceparen = 0x323e;
t.ideographicrightcircle = 0x32a8;
t.ideographicsecretcircle = 0x3299;
t.ideographicselfparen = 0x3242;
t.ideographicsocietyparen = 0x3233;
t.ideographicspace = 0x3000;
t.ideographicspecialparen = 0x3235;
t.ideographicstockparen = 0x3231;
t.ideographicstudyparen = 0x323b;
t.ideographicsunparen = 0x3230;
t.ideographicsuperviseparen = 0x323c;
t.ideographicwaterparen = 0x322c;
t.ideographicwoodparen = 0x322d;
t.ideographiczero = 0x3007;
t.ideographmetalcircle = 0x328e;
t.ideographmooncircle = 0x328a;
t.ideographnamecircle = 0x3294;
t.ideographsuncircle = 0x3290;
t.ideographwatercircle = 0x328c;
t.ideographwoodcircle = 0x328d;
t.ideva = 0x0907;
t.idieresis = 0x00ef;
t.idieresisacute = 0x1e2f;
t.idieresiscyrillic = 0x04e5;
t.idotbelow = 0x1ecb;
t.iebrevecyrillic = 0x04d7;
t.iecyrillic = 0x0435;
t.ieungacirclekorean = 0x3275;
t.ieungaparenkorean = 0x3215;
t.ieungcirclekorean = 0x3267;
t.ieungkorean = 0x3147;
t.ieungparenkorean = 0x3207;
t.igrave = 0x00ec;
t.igujarati = 0x0a87;
t.igurmukhi = 0x0a07;
t.ihiragana = 0x3044;
t.ihookabove = 0x1ec9;
t.iibengali = 0x0988;
t.iicyrillic = 0x0438;
t.iideva = 0x0908;
t.iigujarati = 0x0a88;
t.iigurmukhi = 0x0a08;
t.iimatragurmukhi = 0x0a40;
t.iinvertedbreve = 0x020b;
t.iishortcyrillic = 0x0439;
t.iivowelsignbengali = 0x09c0;
t.iivowelsigndeva = 0x0940;
t.iivowelsigngujarati = 0x0ac0;
t.ij = 0x0133;
t.ikatakana = 0x30a4;
t.ikatakanahalfwidth = 0xff72;
t.ikorean = 0x3163;
t.ilde = 0x02dc;
t.iluyhebrew = 0x05ac;
t.imacron = 0x012b;
t.imacroncyrillic = 0x04e3;
t.imageorapproximatelyequal = 0x2253;
t.imatragurmukhi = 0x0a3f;
t.imonospace = 0xff49;
t.increment = 0x2206;
t.infinity = 0x221e;
t.iniarmenian = 0x056b;
t.integral = 0x222b;
t.integralbottom = 0x2321;
t.integralbt = 0x2321;
t.integralex = 0xf8f5;
t.integraltop = 0x2320;
t.integraltp = 0x2320;
t.intersection = 0x2229;
t.intisquare = 0x3305;
t.invbullet = 0x25d8;
t.invcircle = 0x25d9;
t.invsmileface = 0x263b;
t.iocyrillic = 0x0451;
t.iogonek = 0x012f;
t.iota = 0x03b9;
t.iotadieresis = 0x03ca;
t.iotadieresistonos = 0x0390;
t.iotalatin = 0x0269;
t.iotatonos = 0x03af;
t.iparen = 0x24a4;
t.irigurmukhi = 0x0a72;
t.ismallhiragana = 0x3043;
t.ismallkatakana = 0x30a3;
t.ismallkatakanahalfwidth = 0xff68;
t.issharbengali = 0x09fa;
t.istroke = 0x0268;
t.isuperior = 0xf6ed;
t.iterationhiragana = 0x309d;
t.iterationkatakana = 0x30fd;
t.itilde = 0x0129;
t.itildebelow = 0x1e2d;
t.iubopomofo = 0x3129;
t.iucyrillic = 0x044e;
t.ivowelsignbengali = 0x09bf;
t.ivowelsigndeva = 0x093f;
t.ivowelsigngujarati = 0x0abf;
t.izhitsacyrillic = 0x0475;
t.izhitsadblgravecyrillic = 0x0477;
t.j = 0x006a;
t.jaarmenian = 0x0571;
t.jabengali = 0x099c;
t.jadeva = 0x091c;
t.jagujarati = 0x0a9c;
t.jagurmukhi = 0x0a1c;
t.jbopomofo = 0x3110;
t.jcaron = 0x01f0;
t.jcircle = 0x24d9;
t.jcircumflex = 0x0135;
t.jcrossedtail = 0x029d;
t.jdotlessstroke = 0x025f;
t.jecyrillic = 0x0458;
t.jeemarabic = 0x062c;
t.jeemfinalarabic = 0xfe9e;
t.jeeminitialarabic = 0xfe9f;
t.jeemmedialarabic = 0xfea0;
t.jeharabic = 0x0698;
t.jehfinalarabic = 0xfb8b;
t.jhabengali = 0x099d;
t.jhadeva = 0x091d;
t.jhagujarati = 0x0a9d;
t.jhagurmukhi = 0x0a1d;
t.jheharmenian = 0x057b;
t.jis = 0x3004;
t.jmonospace = 0xff4a;
t.jparen = 0x24a5;
t.jsuperior = 0x02b2;
t.k = 0x006b;
t.kabashkircyrillic = 0x04a1;
t.kabengali = 0x0995;
t.kacute = 0x1e31;
t.kacyrillic = 0x043a;
t.kadescendercyrillic = 0x049b;
t.kadeva = 0x0915;
t.kaf = 0x05db;
t.kafarabic = 0x0643;
t.kafdagesh = 0xfb3b;
t.kafdageshhebrew = 0xfb3b;
t.kaffinalarabic = 0xfeda;
t.kafhebrew = 0x05db;
t.kafinitialarabic = 0xfedb;
t.kafmedialarabic = 0xfedc;
t.kafrafehebrew = 0xfb4d;
t.kagujarati = 0x0a95;
t.kagurmukhi = 0x0a15;
t.kahiragana = 0x304b;
t.kahookcyrillic = 0x04c4;
t.kakatakana = 0x30ab;
t.kakatakanahalfwidth = 0xff76;
t.kappa = 0x03ba;
t.kappasymbolgreek = 0x03f0;
t.kapyeounmieumkorean = 0x3171;
t.kapyeounphieuphkorean = 0x3184;
t.kapyeounpieupkorean = 0x3178;
t.kapyeounssangpieupkorean = 0x3179;
t.karoriisquare = 0x330d;
t.kashidaautoarabic = 0x0640;
t.kashidaautonosidebearingarabic = 0x0640;
t.kasmallkatakana = 0x30f5;
t.kasquare = 0x3384;
t.kasraarabic = 0x0650;
t.kasratanarabic = 0x064d;
t.kastrokecyrillic = 0x049f;
t.katahiraprolongmarkhalfwidth = 0xff70;
t.kaverticalstrokecyrillic = 0x049d;
t.kbopomofo = 0x310e;
t.kcalsquare = 0x3389;
t.kcaron = 0x01e9;
t.kcedilla = 0x0137;
t.kcircle = 0x24da;
t.kcommaaccent = 0x0137;
t.kdotbelow = 0x1e33;
t.keharmenian = 0x0584;
t.kehiragana = 0x3051;
t.kekatakana = 0x30b1;
t.kekatakanahalfwidth = 0xff79;
t.kenarmenian = 0x056f;
t.kesmallkatakana = 0x30f6;
t.kgreenlandic = 0x0138;
t.khabengali = 0x0996;
t.khacyrillic = 0x0445;
t.khadeva = 0x0916;
t.khagujarati = 0x0a96;
t.khagurmukhi = 0x0a16;
t.khaharabic = 0x062e;
t.khahfinalarabic = 0xfea6;
t.khahinitialarabic = 0xfea7;
t.khahmedialarabic = 0xfea8;
t.kheicoptic = 0x03e7;
t.khhadeva = 0x0959;
t.khhagurmukhi = 0x0a59;
t.khieukhacirclekorean = 0x3278;
t.khieukhaparenkorean = 0x3218;
t.khieukhcirclekorean = 0x326a;
t.khieukhkorean = 0x314b;
t.khieukhparenkorean = 0x320a;
t.khokhaithai = 0x0e02;
t.khokhonthai = 0x0e05;
t.khokhuatthai = 0x0e03;
t.khokhwaithai = 0x0e04;
t.khomutthai = 0x0e5b;
t.khook = 0x0199;
t.khorakhangthai = 0x0e06;
t.khzsquare = 0x3391;
t.kihiragana = 0x304d;
t.kikatakana = 0x30ad;
t.kikatakanahalfwidth = 0xff77;
t.kiroguramusquare = 0x3315;
t.kiromeetorusquare = 0x3316;
t.kirosquare = 0x3314;
t.kiyeokacirclekorean = 0x326e;
t.kiyeokaparenkorean = 0x320e;
t.kiyeokcirclekorean = 0x3260;
t.kiyeokkorean = 0x3131;
t.kiyeokparenkorean = 0x3200;
t.kiyeoksioskorean = 0x3133;
t.kjecyrillic = 0x045c;
t.klinebelow = 0x1e35;
t.klsquare = 0x3398;
t.kmcubedsquare = 0x33a6;
t.kmonospace = 0xff4b;
t.kmsquaredsquare = 0x33a2;
t.kohiragana = 0x3053;
t.kohmsquare = 0x33c0;
t.kokaithai = 0x0e01;
t.kokatakana = 0x30b3;
t.kokatakanahalfwidth = 0xff7a;
t.kooposquare = 0x331e;
t.koppacyrillic = 0x0481;
t.koreanstandardsymbol = 0x327f;
t.koroniscmb = 0x0343;
t.kparen = 0x24a6;
t.kpasquare = 0x33aa;
t.ksicyrillic = 0x046f;
t.ktsquare = 0x33cf;
t.kturned = 0x029e;
t.kuhiragana = 0x304f;
t.kukatakana = 0x30af;
t.kukatakanahalfwidth = 0xff78;
t.kvsquare = 0x33b8;
t.kwsquare = 0x33be;
t.l = 0x006c;
t.labengali = 0x09b2;
t.lacute = 0x013a;
t.ladeva = 0x0932;
t.lagujarati = 0x0ab2;
t.lagurmukhi = 0x0a32;
t.lakkhangyaothai = 0x0e45;
t.lamaleffinalarabic = 0xfefc;
t.lamalefhamzaabovefinalarabic = 0xfef8;
t.lamalefhamzaaboveisolatedarabic = 0xfef7;
t.lamalefhamzabelowfinalarabic = 0xfefa;
t.lamalefhamzabelowisolatedarabic = 0xfef9;
t.lamalefisolatedarabic = 0xfefb;
t.lamalefmaddaabovefinalarabic = 0xfef6;
t.lamalefmaddaaboveisolatedarabic = 0xfef5;
t.lamarabic = 0x0644;
t.lambda = 0x03bb;
t.lambdastroke = 0x019b;
t.lamed = 0x05dc;
t.lameddagesh = 0xfb3c;
t.lameddageshhebrew = 0xfb3c;
t.lamedhebrew = 0x05dc;
t.lamfinalarabic = 0xfede;
t.lamhahinitialarabic = 0xfcca;
t.laminitialarabic = 0xfedf;
t.lamjeeminitialarabic = 0xfcc9;
t.lamkhahinitialarabic = 0xfccb;
t.lamlamhehisolatedarabic = 0xfdf2;
t.lammedialarabic = 0xfee0;
t.lammeemhahinitialarabic = 0xfd88;
t.lammeeminitialarabic = 0xfccc;
t.largecircle = 0x25ef;
t.lbar = 0x019a;
t.lbelt = 0x026c;
t.lbopomofo = 0x310c;
t.lcaron = 0x013e;
t.lcedilla = 0x013c;
t.lcircle = 0x24db;
t.lcircumflexbelow = 0x1e3d;
t.lcommaaccent = 0x013c;
t.ldot = 0x0140;
t.ldotaccent = 0x0140;
t.ldotbelow = 0x1e37;
t.ldotbelowmacron = 0x1e39;
t.leftangleabovecmb = 0x031a;
t.lefttackbelowcmb = 0x0318;
t.less = 0x003c;
t.lessequal = 0x2264;
t.lessequalorgreater = 0x22da;
t.lessmonospace = 0xff1c;
t.lessorequivalent = 0x2272;
t.lessorgreater = 0x2276;
t.lessoverequal = 0x2266;
t.lesssmall = 0xfe64;
t.lezh = 0x026e;
t.lfblock = 0x258c;
t.lhookretroflex = 0x026d;
t.lira = 0x20a4;
t.liwnarmenian = 0x056c;
t.lj = 0x01c9;
t.ljecyrillic = 0x0459;
t.ll = 0xf6c0;
t.lladeva = 0x0933;
t.llagujarati = 0x0ab3;
t.llinebelow = 0x1e3b;
t.llladeva = 0x0934;
t.llvocalicbengali = 0x09e1;
t.llvocalicdeva = 0x0961;
t.llvocalicvowelsignbengali = 0x09e3;
t.llvocalicvowelsigndeva = 0x0963;
t.lmiddletilde = 0x026b;
t.lmonospace = 0xff4c;
t.lmsquare = 0x33d0;
t.lochulathai = 0x0e2c;
t.logicaland = 0x2227;
t.logicalnot = 0x00ac;
t.logicalnotreversed = 0x2310;
t.logicalor = 0x2228;
t.lolingthai = 0x0e25;
t.longs = 0x017f;
t.lowlinecenterline = 0xfe4e;
t.lowlinecmb = 0x0332;
t.lowlinedashed = 0xfe4d;
t.lozenge = 0x25ca;
t.lparen = 0x24a7;
t.lslash = 0x0142;
t.lsquare = 0x2113;
t.lsuperior = 0xf6ee;
t.ltshade = 0x2591;
t.luthai = 0x0e26;
t.lvocalicbengali = 0x098c;
t.lvocalicdeva = 0x090c;
t.lvocalicvowelsignbengali = 0x09e2;
t.lvocalicvowelsigndeva = 0x0962;
t.lxsquare = 0x33d3;
t.m = 0x006d;
t.mabengali = 0x09ae;
t.macron = 0x00af;
t.macronbelowcmb = 0x0331;
t.macroncmb = 0x0304;
t.macronlowmod = 0x02cd;
t.macronmonospace = 0xffe3;
t.macute = 0x1e3f;
t.madeva = 0x092e;
t.magujarati = 0x0aae;
t.magurmukhi = 0x0a2e;
t.mahapakhhebrew = 0x05a4;
t.mahapakhlefthebrew = 0x05a4;
t.mahiragana = 0x307e;
t.maichattawalowleftthai = 0xf895;
t.maichattawalowrightthai = 0xf894;
t.maichattawathai = 0x0e4b;
t.maichattawaupperleftthai = 0xf893;
t.maieklowleftthai = 0xf88c;
t.maieklowrightthai = 0xf88b;
t.maiekthai = 0x0e48;
t.maiekupperleftthai = 0xf88a;
t.maihanakatleftthai = 0xf884;
t.maihanakatthai = 0x0e31;
t.maitaikhuleftthai = 0xf889;
t.maitaikhuthai = 0x0e47;
t.maitholowleftthai = 0xf88f;
t.maitholowrightthai = 0xf88e;
t.maithothai = 0x0e49;
t.maithoupperleftthai = 0xf88d;
t.maitrilowleftthai = 0xf892;
t.maitrilowrightthai = 0xf891;
t.maitrithai = 0x0e4a;
t.maitriupperleftthai = 0xf890;
t.maiyamokthai = 0x0e46;
t.makatakana = 0x30de;
t.makatakanahalfwidth = 0xff8f;
t.male = 0x2642;
t.mansyonsquare = 0x3347;
t.maqafhebrew = 0x05be;
t.mars = 0x2642;
t.masoracirclehebrew = 0x05af;
t.masquare = 0x3383;
t.mbopomofo = 0x3107;
t.mbsquare = 0x33d4;
t.mcircle = 0x24dc;
t.mcubedsquare = 0x33a5;
t.mdotaccent = 0x1e41;
t.mdotbelow = 0x1e43;
t.meemarabic = 0x0645;
t.meemfinalarabic = 0xfee2;
t.meeminitialarabic = 0xfee3;
t.meemmedialarabic = 0xfee4;
t.meemmeeminitialarabic = 0xfcd1;
t.meemmeemisolatedarabic = 0xfc48;
t.meetorusquare = 0x334d;
t.mehiragana = 0x3081;
t.meizierasquare = 0x337e;
t.mekatakana = 0x30e1;
t.mekatakanahalfwidth = 0xff92;
t.mem = 0x05de;
t.memdagesh = 0xfb3e;
t.memdageshhebrew = 0xfb3e;
t.memhebrew = 0x05de;
t.menarmenian = 0x0574;
t.merkhahebrew = 0x05a5;
t.merkhakefulahebrew = 0x05a6;
t.merkhakefulalefthebrew = 0x05a6;
t.merkhalefthebrew = 0x05a5;
t.mhook = 0x0271;
t.mhzsquare = 0x3392;
t.middledotkatakanahalfwidth = 0xff65;
t.middot = 0x00b7;
t.mieumacirclekorean = 0x3272;
t.mieumaparenkorean = 0x3212;
t.mieumcirclekorean = 0x3264;
t.mieumkorean = 0x3141;
t.mieumpansioskorean = 0x3170;
t.mieumparenkorean = 0x3204;
t.mieumpieupkorean = 0x316e;
t.mieumsioskorean = 0x316f;
t.mihiragana = 0x307f;
t.mikatakana = 0x30df;
t.mikatakanahalfwidth = 0xff90;
t.minus = 0x2212;
t.minusbelowcmb = 0x0320;
t.minuscircle = 0x2296;
t.minusmod = 0x02d7;
t.minusplus = 0x2213;
t.minute = 0x2032;
t.miribaarusquare = 0x334a;
t.mirisquare = 0x3349;
t.mlonglegturned = 0x0270;
t.mlsquare = 0x3396;
t.mmcubedsquare = 0x33a3;
t.mmonospace = 0xff4d;
t.mmsquaredsquare = 0x339f;
t.mohiragana = 0x3082;
t.mohmsquare = 0x33c1;
t.mokatakana = 0x30e2;
t.mokatakanahalfwidth = 0xff93;
t.molsquare = 0x33d6;
t.momathai = 0x0e21;
t.moverssquare = 0x33a7;
t.moverssquaredsquare = 0x33a8;
t.mparen = 0x24a8;
t.mpasquare = 0x33ab;
t.mssquare = 0x33b3;
t.msuperior = 0xf6ef;
t.mturned = 0x026f;
t.mu = 0x00b5;
t.mu1 = 0x00b5;
t.muasquare = 0x3382;
t.muchgreater = 0x226b;
t.muchless = 0x226a;
t.mufsquare = 0x338c;
t.mugreek = 0x03bc;
t.mugsquare = 0x338d;
t.muhiragana = 0x3080;
t.mukatakana = 0x30e0;
t.mukatakanahalfwidth = 0xff91;
t.mulsquare = 0x3395;
t.multiply = 0x00d7;
t.mumsquare = 0x339b;
t.munahhebrew = 0x05a3;
t.munahlefthebrew = 0x05a3;
t.musicalnote = 0x266a;
t.musicalnotedbl = 0x266b;
t.musicflatsign = 0x266d;
t.musicsharpsign = 0x266f;
t.mussquare = 0x33b2;
t.muvsquare = 0x33b6;
t.muwsquare = 0x33bc;
t.mvmegasquare = 0x33b9;
t.mvsquare = 0x33b7;
t.mwmegasquare = 0x33bf;
t.mwsquare = 0x33bd;
t.n = 0x006e;
t.nabengali = 0x09a8;
t.nabla = 0x2207;
t.nacute = 0x0144;
t.nadeva = 0x0928;
t.nagujarati = 0x0aa8;
t.nagurmukhi = 0x0a28;
t.nahiragana = 0x306a;
t.nakatakana = 0x30ca;
t.nakatakanahalfwidth = 0xff85;
t.napostrophe = 0x0149;
t.nasquare = 0x3381;
t.nbopomofo = 0x310b;
t.nbspace = 0x00a0;
t.ncaron = 0x0148;
t.ncedilla = 0x0146;
t.ncircle = 0x24dd;
t.ncircumflexbelow = 0x1e4b;
t.ncommaaccent = 0x0146;
t.ndotaccent = 0x1e45;
t.ndotbelow = 0x1e47;
t.nehiragana = 0x306d;
t.nekatakana = 0x30cd;
t.nekatakanahalfwidth = 0xff88;
t.newsheqelsign = 0x20aa;
t.nfsquare = 0x338b;
t.ngabengali = 0x0999;
t.ngadeva = 0x0919;
t.ngagujarati = 0x0a99;
t.ngagurmukhi = 0x0a19;
t.ngonguthai = 0x0e07;
t.nhiragana = 0x3093;
t.nhookleft = 0x0272;
t.nhookretroflex = 0x0273;
t.nieunacirclekorean = 0x326f;
t.nieunaparenkorean = 0x320f;
t.nieuncieuckorean = 0x3135;
t.nieuncirclekorean = 0x3261;
t.nieunhieuhkorean = 0x3136;
t.nieunkorean = 0x3134;
t.nieunpansioskorean = 0x3168;
t.nieunparenkorean = 0x3201;
t.nieunsioskorean = 0x3167;
t.nieuntikeutkorean = 0x3166;
t.nihiragana = 0x306b;
t.nikatakana = 0x30cb;
t.nikatakanahalfwidth = 0xff86;
t.nikhahitleftthai = 0xf899;
t.nikhahitthai = 0x0e4d;
t.nine = 0x0039;
t.ninearabic = 0x0669;
t.ninebengali = 0x09ef;
t.ninecircle = 0x2468;
t.ninecircleinversesansserif = 0x2792;
t.ninedeva = 0x096f;
t.ninegujarati = 0x0aef;
t.ninegurmukhi = 0x0a6f;
t.ninehackarabic = 0x0669;
t.ninehangzhou = 0x3029;
t.nineideographicparen = 0x3228;
t.nineinferior = 0x2089;
t.ninemonospace = 0xff19;
t.nineoldstyle = 0xf739;
t.nineparen = 0x247c;
t.nineperiod = 0x2490;
t.ninepersian = 0x06f9;
t.nineroman = 0x2178;
t.ninesuperior = 0x2079;
t.nineteencircle = 0x2472;
t.nineteenparen = 0x2486;
t.nineteenperiod = 0x249a;
t.ninethai = 0x0e59;
t.nj = 0x01cc;
t.njecyrillic = 0x045a;
t.nkatakana = 0x30f3;
t.nkatakanahalfwidth = 0xff9d;
t.nlegrightlong = 0x019e;
t.nlinebelow = 0x1e49;
t.nmonospace = 0xff4e;
t.nmsquare = 0x339a;
t.nnabengali = 0x09a3;
t.nnadeva = 0x0923;
t.nnagujarati = 0x0aa3;
t.nnagurmukhi = 0x0a23;
t.nnnadeva = 0x0929;
t.nohiragana = 0x306e;
t.nokatakana = 0x30ce;
t.nokatakanahalfwidth = 0xff89;
t.nonbreakingspace = 0x00a0;
t.nonenthai = 0x0e13;
t.nonuthai = 0x0e19;
t.noonarabic = 0x0646;
t.noonfinalarabic = 0xfee6;
t.noonghunnaarabic = 0x06ba;
t.noonghunnafinalarabic = 0xfb9f;
t.nooninitialarabic = 0xfee7;
t.noonjeeminitialarabic = 0xfcd2;
t.noonjeemisolatedarabic = 0xfc4b;
t.noonmedialarabic = 0xfee8;
t.noonmeeminitialarabic = 0xfcd5;
t.noonmeemisolatedarabic = 0xfc4e;
t.noonnoonfinalarabic = 0xfc8d;
t.notcontains = 0x220c;
t.notelement = 0x2209;
t.notelementof = 0x2209;
t.notequal = 0x2260;
t.notgreater = 0x226f;
t.notgreaternorequal = 0x2271;
t.notgreaternorless = 0x2279;
t.notidentical = 0x2262;
t.notless = 0x226e;
t.notlessnorequal = 0x2270;
t.notparallel = 0x2226;
t.notprecedes = 0x2280;
t.notsubset = 0x2284;
t.notsucceeds = 0x2281;
t.notsuperset = 0x2285;
t.nowarmenian = 0x0576;
t.nparen = 0x24a9;
t.nssquare = 0x33b1;
t.nsuperior = 0x207f;
t.ntilde = 0x00f1;
t.nu = 0x03bd;
t.nuhiragana = 0x306c;
t.nukatakana = 0x30cc;
t.nukatakanahalfwidth = 0xff87;
t.nuktabengali = 0x09bc;
t.nuktadeva = 0x093c;
t.nuktagujarati = 0x0abc;
t.nuktagurmukhi = 0x0a3c;
t.numbersign = 0x0023;
t.numbersignmonospace = 0xff03;
t.numbersignsmall = 0xfe5f;
t.numeralsigngreek = 0x0374;
t.numeralsignlowergreek = 0x0375;
t.numero = 0x2116;
t.nun = 0x05e0;
t.nundagesh = 0xfb40;
t.nundageshhebrew = 0xfb40;
t.nunhebrew = 0x05e0;
t.nvsquare = 0x33b5;
t.nwsquare = 0x33bb;
t.nyabengali = 0x099e;
t.nyadeva = 0x091e;
t.nyagujarati = 0x0a9e;
t.nyagurmukhi = 0x0a1e;
t.o = 0x006f;
t.oacute = 0x00f3;
t.oangthai = 0x0e2d;
t.obarred = 0x0275;
t.obarredcyrillic = 0x04e9;
t.obarreddieresiscyrillic = 0x04eb;
t.obengali = 0x0993;
t.obopomofo = 0x311b;
t.obreve = 0x014f;
t.ocandradeva = 0x0911;
t.ocandragujarati = 0x0a91;
t.ocandravowelsigndeva = 0x0949;
t.ocandravowelsigngujarati = 0x0ac9;
t.ocaron = 0x01d2;
t.ocircle = 0x24de;
t.ocircumflex = 0x00f4;
t.ocircumflexacute = 0x1ed1;
t.ocircumflexdotbelow = 0x1ed9;
t.ocircumflexgrave = 0x1ed3;
t.ocircumflexhookabove = 0x1ed5;
t.ocircumflextilde = 0x1ed7;
t.ocyrillic = 0x043e;
t.odblacute = 0x0151;
t.odblgrave = 0x020d;
t.odeva = 0x0913;
t.odieresis = 0x00f6;
t.odieresiscyrillic = 0x04e7;
t.odotbelow = 0x1ecd;
t.oe = 0x0153;
t.oekorean = 0x315a;
t.ogonek = 0x02db;
t.ogonekcmb = 0x0328;
t.ograve = 0x00f2;
t.ogujarati = 0x0a93;
t.oharmenian = 0x0585;
t.ohiragana = 0x304a;
t.ohookabove = 0x1ecf;
t.ohorn = 0x01a1;
t.ohornacute = 0x1edb;
t.ohorndotbelow = 0x1ee3;
t.ohorngrave = 0x1edd;
t.ohornhookabove = 0x1edf;
t.ohorntilde = 0x1ee1;
t.ohungarumlaut = 0x0151;
t.oi = 0x01a3;
t.oinvertedbreve = 0x020f;
t.okatakana = 0x30aa;
t.okatakanahalfwidth = 0xff75;
t.okorean = 0x3157;
t.olehebrew = 0x05ab;
t.omacron = 0x014d;
t.omacronacute = 0x1e53;
t.omacrongrave = 0x1e51;
t.omdeva = 0x0950;
t.omega = 0x03c9;
t.omega1 = 0x03d6;
t.omegacyrillic = 0x0461;
t.omegalatinclosed = 0x0277;
t.omegaroundcyrillic = 0x047b;
t.omegatitlocyrillic = 0x047d;
t.omegatonos = 0x03ce;
t.omgujarati = 0x0ad0;
t.omicron = 0x03bf;
t.omicrontonos = 0x03cc;
t.omonospace = 0xff4f;
t.one = 0x0031;
t.onearabic = 0x0661;
t.onebengali = 0x09e7;
t.onecircle = 0x2460;
t.onecircleinversesansserif = 0x278a;
t.onedeva = 0x0967;
t.onedotenleader = 0x2024;
t.oneeighth = 0x215b;
t.onefitted = 0xf6dc;
t.onegujarati = 0x0ae7;
t.onegurmukhi = 0x0a67;
t.onehackarabic = 0x0661;
t.onehalf = 0x00bd;
t.onehangzhou = 0x3021;
t.oneideographicparen = 0x3220;
t.oneinferior = 0x2081;
t.onemonospace = 0xff11;
t.onenumeratorbengali = 0x09f4;
t.oneoldstyle = 0xf731;
t.oneparen = 0x2474;
t.oneperiod = 0x2488;
t.onepersian = 0x06f1;
t.onequarter = 0x00bc;
t.oneroman = 0x2170;
t.onesuperior = 0x00b9;
t.onethai = 0x0e51;
t.onethird = 0x2153;
t.oogonek = 0x01eb;
t.oogonekmacron = 0x01ed;
t.oogurmukhi = 0x0a13;
t.oomatragurmukhi = 0x0a4b;
t.oopen = 0x0254;
t.oparen = 0x24aa;
t.openbullet = 0x25e6;
t.option = 0x2325;
t.ordfeminine = 0x00aa;
t.ordmasculine = 0x00ba;
t.orthogonal = 0x221f;
t.oshortdeva = 0x0912;
t.oshortvowelsigndeva = 0x094a;
t.oslash = 0x00f8;
t.oslashacute = 0x01ff;
t.osmallhiragana = 0x3049;
t.osmallkatakana = 0x30a9;
t.osmallkatakanahalfwidth = 0xff6b;
t.ostrokeacute = 0x01ff;
t.osuperior = 0xf6f0;
t.otcyrillic = 0x047f;
t.otilde = 0x00f5;
t.otildeacute = 0x1e4d;
t.otildedieresis = 0x1e4f;
t.oubopomofo = 0x3121;
t.overline = 0x203e;
t.overlinecenterline = 0xfe4a;
t.overlinecmb = 0x0305;
t.overlinedashed = 0xfe49;
t.overlinedblwavy = 0xfe4c;
t.overlinewavy = 0xfe4b;
t.overscore = 0x00af;
t.ovowelsignbengali = 0x09cb;
t.ovowelsigndeva = 0x094b;
t.ovowelsigngujarati = 0x0acb;
t.p = 0x0070;
t.paampssquare = 0x3380;
t.paasentosquare = 0x332b;
t.pabengali = 0x09aa;
t.pacute = 0x1e55;
t.padeva = 0x092a;
t.pagedown = 0x21df;
t.pageup = 0x21de;
t.pagujarati = 0x0aaa;
t.pagurmukhi = 0x0a2a;
t.pahiragana = 0x3071;
t.paiyannoithai = 0x0e2f;
t.pakatakana = 0x30d1;
t.palatalizationcyrilliccmb = 0x0484;
t.palochkacyrillic = 0x04c0;
t.pansioskorean = 0x317f;
t.paragraph = 0x00b6;
t.parallel = 0x2225;
t.parenleft = 0x0028;
t.parenleftaltonearabic = 0xfd3e;
t.parenleftbt = 0xf8ed;
t.parenleftex = 0xf8ec;
t.parenleftinferior = 0x208d;
t.parenleftmonospace = 0xff08;
t.parenleftsmall = 0xfe59;
t.parenleftsuperior = 0x207d;
t.parenlefttp = 0xf8eb;
t.parenleftvertical = 0xfe35;
t.parenright = 0x0029;
t.parenrightaltonearabic = 0xfd3f;
t.parenrightbt = 0xf8f8;
t.parenrightex = 0xf8f7;
t.parenrightinferior = 0x208e;
t.parenrightmonospace = 0xff09;
t.parenrightsmall = 0xfe5a;
t.parenrightsuperior = 0x207e;
t.parenrighttp = 0xf8f6;
t.parenrightvertical = 0xfe36;
t.partialdiff = 0x2202;
t.paseqhebrew = 0x05c0;
t.pashtahebrew = 0x0599;
t.pasquare = 0x33a9;
t.patah = 0x05b7;
t.patah11 = 0x05b7;
t.patah1d = 0x05b7;
t.patah2a = 0x05b7;
t.patahhebrew = 0x05b7;
t.patahnarrowhebrew = 0x05b7;
t.patahquarterhebrew = 0x05b7;
t.patahwidehebrew = 0x05b7;
t.pazerhebrew = 0x05a1;
t.pbopomofo = 0x3106;
t.pcircle = 0x24df;
t.pdotaccent = 0x1e57;
t.pe = 0x05e4;
t.pecyrillic = 0x043f;
t.pedagesh = 0xfb44;
t.pedageshhebrew = 0xfb44;
t.peezisquare = 0x333b;
t.pefinaldageshhebrew = 0xfb43;
t.peharabic = 0x067e;
t.peharmenian = 0x057a;
t.pehebrew = 0x05e4;
t.pehfinalarabic = 0xfb57;
t.pehinitialarabic = 0xfb58;
t.pehiragana = 0x307a;
t.pehmedialarabic = 0xfb59;
t.pekatakana = 0x30da;
t.pemiddlehookcyrillic = 0x04a7;
t.perafehebrew = 0xfb4e;
t.percent = 0x0025;
t.percentarabic = 0x066a;
t.percentmonospace = 0xff05;
t.percentsmall = 0xfe6a;
t.period = 0x002e;
t.periodarmenian = 0x0589;
t.periodcentered = 0x00b7;
t.periodhalfwidth = 0xff61;
t.periodinferior = 0xf6e7;
t.periodmonospace = 0xff0e;
t.periodsmall = 0xfe52;
t.periodsuperior = 0xf6e8;
t.perispomenigreekcmb = 0x0342;
t.perpendicular = 0x22a5;
t.perthousand = 0x2030;
t.peseta = 0x20a7;
t.pfsquare = 0x338a;
t.phabengali = 0x09ab;
t.phadeva = 0x092b;
t.phagujarati = 0x0aab;
t.phagurmukhi = 0x0a2b;
t.phi = 0x03c6;
t.phi1 = 0x03d5;
t.phieuphacirclekorean = 0x327a;
t.phieuphaparenkorean = 0x321a;
t.phieuphcirclekorean = 0x326c;
t.phieuphkorean = 0x314d;
t.phieuphparenkorean = 0x320c;
t.philatin = 0x0278;
t.phinthuthai = 0x0e3a;
t.phisymbolgreek = 0x03d5;
t.phook = 0x01a5;
t.phophanthai = 0x0e1e;
t.phophungthai = 0x0e1c;
t.phosamphaothai = 0x0e20;
t.pi = 0x03c0;
t.pieupacirclekorean = 0x3273;
t.pieupaparenkorean = 0x3213;
t.pieupcieuckorean = 0x3176;
t.pieupcirclekorean = 0x3265;
t.pieupkiyeokkorean = 0x3172;
t.pieupkorean = 0x3142;
t.pieupparenkorean = 0x3205;
t.pieupsioskiyeokkorean = 0x3174;
t.pieupsioskorean = 0x3144;
t.pieupsiostikeutkorean = 0x3175;
t.pieupthieuthkorean = 0x3177;
t.pieuptikeutkorean = 0x3173;
t.pihiragana = 0x3074;
t.pikatakana = 0x30d4;
t.pisymbolgreek = 0x03d6;
t.piwrarmenian = 0x0583;
t.planckover2pi = 0x210f;
t.planckover2pi1 = 0x210f;
t.plus = 0x002b;
t.plusbelowcmb = 0x031f;
t.pluscircle = 0x2295;
t.plusminus = 0x00b1;
t.plusmod = 0x02d6;
t.plusmonospace = 0xff0b;
t.plussmall = 0xfe62;
t.plussuperior = 0x207a;
t.pmonospace = 0xff50;
t.pmsquare = 0x33d8;
t.pohiragana = 0x307d;
t.pointingindexdownwhite = 0x261f;
t.pointingindexleftwhite = 0x261c;
t.pointingindexrightwhite = 0x261e;
t.pointingindexupwhite = 0x261d;
t.pokatakana = 0x30dd;
t.poplathai = 0x0e1b;
t.postalmark = 0x3012;
t.postalmarkface = 0x3020;
t.pparen = 0x24ab;
t.precedes = 0x227a;
t.prescription = 0x211e;
t.primemod = 0x02b9;
t.primereversed = 0x2035;
t.product = 0x220f;
t.projective = 0x2305;
t.prolongedkana = 0x30fc;
t.propellor = 0x2318;
t.propersubset = 0x2282;
t.propersuperset = 0x2283;
t.proportion = 0x2237;
t.proportional = 0x221d;
t.psi = 0x03c8;
t.psicyrillic = 0x0471;
t.psilipneumatacyrilliccmb = 0x0486;
t.pssquare = 0x33b0;
t.puhiragana = 0x3077;
t.pukatakana = 0x30d7;
t.pvsquare = 0x33b4;
t.pwsquare = 0x33ba;
t.q = 0x0071;
t.qadeva = 0x0958;
t.qadmahebrew = 0x05a8;
t.qafarabic = 0x0642;
t.qaffinalarabic = 0xfed6;
t.qafinitialarabic = 0xfed7;
t.qafmedialarabic = 0xfed8;
t.qamats = 0x05b8;
t.qamats10 = 0x05b8;
t.qamats1a = 0x05b8;
t.qamats1c = 0x05b8;
t.qamats27 = 0x05b8;
t.qamats29 = 0x05b8;
t.qamats33 = 0x05b8;
t.qamatsde = 0x05b8;
t.qamatshebrew = 0x05b8;
t.qamatsnarrowhebrew = 0x05b8;
t.qamatsqatanhebrew = 0x05b8;
t.qamatsqatannarrowhebrew = 0x05b8;
t.qamatsqatanquarterhebrew = 0x05b8;
t.qamatsqatanwidehebrew = 0x05b8;
t.qamatsquarterhebrew = 0x05b8;
t.qamatswidehebrew = 0x05b8;
t.qarneyparahebrew = 0x059f;
t.qbopomofo = 0x3111;
t.qcircle = 0x24e0;
t.qhook = 0x02a0;
t.qmonospace = 0xff51;
t.qof = 0x05e7;
t.qofdagesh = 0xfb47;
t.qofdageshhebrew = 0xfb47;
t.qofhebrew = 0x05e7;
t.qparen = 0x24ac;
t.quarternote = 0x2669;
t.qubuts = 0x05bb;
t.qubuts18 = 0x05bb;
t.qubuts25 = 0x05bb;
t.qubuts31 = 0x05bb;
t.qubutshebrew = 0x05bb;
t.qubutsnarrowhebrew = 0x05bb;
t.qubutsquarterhebrew = 0x05bb;
t.qubutswidehebrew = 0x05bb;
t.question = 0x003f;
t.questionarabic = 0x061f;
t.questionarmenian = 0x055e;
t.questiondown = 0x00bf;
t.questiondownsmall = 0xf7bf;
t.questiongreek = 0x037e;
t.questionmonospace = 0xff1f;
t.questionsmall = 0xf73f;
t.quotedbl = 0x0022;
t.quotedblbase = 0x201e;
t.quotedblleft = 0x201c;
t.quotedblmonospace = 0xff02;
t.quotedblprime = 0x301e;
t.quotedblprimereversed = 0x301d;
t.quotedblright = 0x201d;
t.quoteleft = 0x2018;
t.quoteleftreversed = 0x201b;
t.quotereversed = 0x201b;
t.quoteright = 0x2019;
t.quoterightn = 0x0149;
t.quotesinglbase = 0x201a;
t.quotesingle = 0x0027;
t.quotesinglemonospace = 0xff07;
t.r = 0x0072;
t.raarmenian = 0x057c;
t.rabengali = 0x09b0;
t.racute = 0x0155;
t.radeva = 0x0930;
t.radical = 0x221a;
t.radicalex = 0xf8e5;
t.radoverssquare = 0x33ae;
t.radoverssquaredsquare = 0x33af;
t.radsquare = 0x33ad;
t.rafe = 0x05bf;
t.rafehebrew = 0x05bf;
t.ragujarati = 0x0ab0;
t.ragurmukhi = 0x0a30;
t.rahiragana = 0x3089;
t.rakatakana = 0x30e9;
t.rakatakanahalfwidth = 0xff97;
t.ralowerdiagonalbengali = 0x09f1;
t.ramiddlediagonalbengali = 0x09f0;
t.ramshorn = 0x0264;
t.ratio = 0x2236;
t.rbopomofo = 0x3116;
t.rcaron = 0x0159;
t.rcedilla = 0x0157;
t.rcircle = 0x24e1;
t.rcommaaccent = 0x0157;
t.rdblgrave = 0x0211;
t.rdotaccent = 0x1e59;
t.rdotbelow = 0x1e5b;
t.rdotbelowmacron = 0x1e5d;
t.referencemark = 0x203b;
t.reflexsubset = 0x2286;
t.reflexsuperset = 0x2287;
t.registered = 0x00ae;
t.registersans = 0xf8e8;
t.registerserif = 0xf6da;
t.reharabic = 0x0631;
t.reharmenian = 0x0580;
t.rehfinalarabic = 0xfeae;
t.rehiragana = 0x308c;
t.rekatakana = 0x30ec;
t.rekatakanahalfwidth = 0xff9a;
t.resh = 0x05e8;
t.reshdageshhebrew = 0xfb48;
t.reshhebrew = 0x05e8;
t.reversedtilde = 0x223d;
t.reviahebrew = 0x0597;
t.reviamugrashhebrew = 0x0597;
t.revlogicalnot = 0x2310;
t.rfishhook = 0x027e;
t.rfishhookreversed = 0x027f;
t.rhabengali = 0x09dd;
t.rhadeva = 0x095d;
t.rho = 0x03c1;
t.rhook = 0x027d;
t.rhookturned = 0x027b;
t.rhookturnedsuperior = 0x02b5;
t.rhosymbolgreek = 0x03f1;
t.rhotichookmod = 0x02de;
t.rieulacirclekorean = 0x3271;
t.rieulaparenkorean = 0x3211;
t.rieulcirclekorean = 0x3263;
t.rieulhieuhkorean = 0x3140;
t.rieulkiyeokkorean = 0x313a;
t.rieulkiyeoksioskorean = 0x3169;
t.rieulkorean = 0x3139;
t.rieulmieumkorean = 0x313b;
t.rieulpansioskorean = 0x316c;
t.rieulparenkorean = 0x3203;
t.rieulphieuphkorean = 0x313f;
t.rieulpieupkorean = 0x313c;
t.rieulpieupsioskorean = 0x316b;
t.rieulsioskorean = 0x313d;
t.rieulthieuthkorean = 0x313e;
t.rieultikeutkorean = 0x316a;
t.rieulyeorinhieuhkorean = 0x316d;
t.rightangle = 0x221f;
t.righttackbelowcmb = 0x0319;
t.righttriangle = 0x22bf;
t.rihiragana = 0x308a;
t.rikatakana = 0x30ea;
t.rikatakanahalfwidth = 0xff98;
t.ring = 0x02da;
t.ringbelowcmb = 0x0325;
t.ringcmb = 0x030a;
t.ringhalfleft = 0x02bf;
t.ringhalfleftarmenian = 0x0559;
t.ringhalfleftbelowcmb = 0x031c;
t.ringhalfleftcentered = 0x02d3;
t.ringhalfright = 0x02be;
t.ringhalfrightbelowcmb = 0x0339;
t.ringhalfrightcentered = 0x02d2;
t.rinvertedbreve = 0x0213;
t.rittorusquare = 0x3351;
t.rlinebelow = 0x1e5f;
t.rlongleg = 0x027c;
t.rlonglegturned = 0x027a;
t.rmonospace = 0xff52;
t.rohiragana = 0x308d;
t.rokatakana = 0x30ed;
t.rokatakanahalfwidth = 0xff9b;
t.roruathai = 0x0e23;
t.rparen = 0x24ad;
t.rrabengali = 0x09dc;
t.rradeva = 0x0931;
t.rragurmukhi = 0x0a5c;
t.rreharabic = 0x0691;
t.rrehfinalarabic = 0xfb8d;
t.rrvocalicbengali = 0x09e0;
t.rrvocalicdeva = 0x0960;
t.rrvocalicgujarati = 0x0ae0;
t.rrvocalicvowelsignbengali = 0x09c4;
t.rrvocalicvowelsigndeva = 0x0944;
t.rrvocalicvowelsigngujarati = 0x0ac4;
t.rsuperior = 0xf6f1;
t.rtblock = 0x2590;
t.rturned = 0x0279;
t.rturnedsuperior = 0x02b4;
t.ruhiragana = 0x308b;
t.rukatakana = 0x30eb;
t.rukatakanahalfwidth = 0xff99;
t.rupeemarkbengali = 0x09f2;
t.rupeesignbengali = 0x09f3;
t.rupiah = 0xf6dd;
t.ruthai = 0x0e24;
t.rvocalicbengali = 0x098b;
t.rvocalicdeva = 0x090b;
t.rvocalicgujarati = 0x0a8b;
t.rvocalicvowelsignbengali = 0x09c3;
t.rvocalicvowelsigndeva = 0x0943;
t.rvocalicvowelsigngujarati = 0x0ac3;
t.s = 0x0073;
t.sabengali = 0x09b8;
t.sacute = 0x015b;
t.sacutedotaccent = 0x1e65;
t.sadarabic = 0x0635;
t.sadeva = 0x0938;
t.sadfinalarabic = 0xfeba;
t.sadinitialarabic = 0xfebb;
t.sadmedialarabic = 0xfebc;
t.sagujarati = 0x0ab8;
t.sagurmukhi = 0x0a38;
t.sahiragana = 0x3055;
t.sakatakana = 0x30b5;
t.sakatakanahalfwidth = 0xff7b;
t.sallallahoualayhewasallamarabic = 0xfdfa;
t.samekh = 0x05e1;
t.samekhdagesh = 0xfb41;
t.samekhdageshhebrew = 0xfb41;
t.samekhhebrew = 0x05e1;
t.saraaathai = 0x0e32;
t.saraaethai = 0x0e41;
t.saraaimaimalaithai = 0x0e44;
t.saraaimaimuanthai = 0x0e43;
t.saraamthai = 0x0e33;
t.saraathai = 0x0e30;
t.saraethai = 0x0e40;
t.saraiileftthai = 0xf886;
t.saraiithai = 0x0e35;
t.saraileftthai = 0xf885;
t.saraithai = 0x0e34;
t.saraothai = 0x0e42;
t.saraueeleftthai = 0xf888;
t.saraueethai = 0x0e37;
t.saraueleftthai = 0xf887;
t.sarauethai = 0x0e36;
t.sarauthai = 0x0e38;
t.sarauuthai = 0x0e39;
t.sbopomofo = 0x3119;
t.scaron = 0x0161;
t.scarondotaccent = 0x1e67;
t.scedilla = 0x015f;
t.schwa = 0x0259;
t.schwacyrillic = 0x04d9;
t.schwadieresiscyrillic = 0x04db;
t.schwahook = 0x025a;
t.scircle = 0x24e2;
t.scircumflex = 0x015d;
t.scommaaccent = 0x0219;
t.sdotaccent = 0x1e61;
t.sdotbelow = 0x1e63;
t.sdotbelowdotaccent = 0x1e69;
t.seagullbelowcmb = 0x033c;
t.second = 0x2033;
t.secondtonechinese = 0x02ca;
t.section = 0x00a7;
t.seenarabic = 0x0633;
t.seenfinalarabic = 0xfeb2;
t.seeninitialarabic = 0xfeb3;
t.seenmedialarabic = 0xfeb4;
t.segol = 0x05b6;
t.segol13 = 0x05b6;
t.segol1f = 0x05b6;
t.segol2c = 0x05b6;
t.segolhebrew = 0x05b6;
t.segolnarrowhebrew = 0x05b6;
t.segolquarterhebrew = 0x05b6;
t.segoltahebrew = 0x0592;
t.segolwidehebrew = 0x05b6;
t.seharmenian = 0x057d;
t.sehiragana = 0x305b;
t.sekatakana = 0x30bb;
t.sekatakanahalfwidth = 0xff7e;
t.semicolon = 0x003b;
t.semicolonarabic = 0x061b;
t.semicolonmonospace = 0xff1b;
t.semicolonsmall = 0xfe54;
t.semivoicedmarkkana = 0x309c;
t.semivoicedmarkkanahalfwidth = 0xff9f;
t.sentisquare = 0x3322;
t.sentosquare = 0x3323;
t.seven = 0x0037;
t.sevenarabic = 0x0667;
t.sevenbengali = 0x09ed;
t.sevencircle = 0x2466;
t.sevencircleinversesansserif = 0x2790;
t.sevendeva = 0x096d;
t.seveneighths = 0x215e;
t.sevengujarati = 0x0aed;
t.sevengurmukhi = 0x0a6d;
t.sevenhackarabic = 0x0667;
t.sevenhangzhou = 0x3027;
t.sevenideographicparen = 0x3226;
t.seveninferior = 0x2087;
t.sevenmonospace = 0xff17;
t.sevenoldstyle = 0xf737;
t.sevenparen = 0x247a;
t.sevenperiod = 0x248e;
t.sevenpersian = 0x06f7;
t.sevenroman = 0x2176;
t.sevensuperior = 0x2077;
t.seventeencircle = 0x2470;
t.seventeenparen = 0x2484;
t.seventeenperiod = 0x2498;
t.seventhai = 0x0e57;
t.sfthyphen = 0x00ad;
t.shaarmenian = 0x0577;
t.shabengali = 0x09b6;
t.shacyrillic = 0x0448;
t.shaddaarabic = 0x0651;
t.shaddadammaarabic = 0xfc61;
t.shaddadammatanarabic = 0xfc5e;
t.shaddafathaarabic = 0xfc60;
t.shaddakasraarabic = 0xfc62;
t.shaddakasratanarabic = 0xfc5f;
t.shade = 0x2592;
t.shadedark = 0x2593;
t.shadelight = 0x2591;
t.shademedium = 0x2592;
t.shadeva = 0x0936;
t.shagujarati = 0x0ab6;
t.shagurmukhi = 0x0a36;
t.shalshelethebrew = 0x0593;
t.shbopomofo = 0x3115;
t.shchacyrillic = 0x0449;
t.sheenarabic = 0x0634;
t.sheenfinalarabic = 0xfeb6;
t.sheeninitialarabic = 0xfeb7;
t.sheenmedialarabic = 0xfeb8;
t.sheicoptic = 0x03e3;
t.sheqel = 0x20aa;
t.sheqelhebrew = 0x20aa;
t.sheva = 0x05b0;
t.sheva115 = 0x05b0;
t.sheva15 = 0x05b0;
t.sheva22 = 0x05b0;
t.sheva2e = 0x05b0;
t.shevahebrew = 0x05b0;
t.shevanarrowhebrew = 0x05b0;
t.shevaquarterhebrew = 0x05b0;
t.shevawidehebrew = 0x05b0;
t.shhacyrillic = 0x04bb;
t.shimacoptic = 0x03ed;
t.shin = 0x05e9;
t.shindagesh = 0xfb49;
t.shindageshhebrew = 0xfb49;
t.shindageshshindot = 0xfb2c;
t.shindageshshindothebrew = 0xfb2c;
t.shindageshsindot = 0xfb2d;
t.shindageshsindothebrew = 0xfb2d;
t.shindothebrew = 0x05c1;
t.shinhebrew = 0x05e9;
t.shinshindot = 0xfb2a;
t.shinshindothebrew = 0xfb2a;
t.shinsindot = 0xfb2b;
t.shinsindothebrew = 0xfb2b;
t.shook = 0x0282;
t.sigma = 0x03c3;
t.sigma1 = 0x03c2;
t.sigmafinal = 0x03c2;
t.sigmalunatesymbolgreek = 0x03f2;
t.sihiragana = 0x3057;
t.sikatakana = 0x30b7;
t.sikatakanahalfwidth = 0xff7c;
t.siluqhebrew = 0x05bd;
t.siluqlefthebrew = 0x05bd;
t.similar = 0x223c;
t.sindothebrew = 0x05c2;
t.siosacirclekorean = 0x3274;
t.siosaparenkorean = 0x3214;
t.sioscieuckorean = 0x317e;
t.sioscirclekorean = 0x3266;
t.sioskiyeokkorean = 0x317a;
t.sioskorean = 0x3145;
t.siosnieunkorean = 0x317b;
t.siosparenkorean = 0x3206;
t.siospieupkorean = 0x317d;
t.siostikeutkorean = 0x317c;
t.six = 0x0036;
t.sixarabic = 0x0666;
t.sixbengali = 0x09ec;
t.sixcircle = 0x2465;
t.sixcircleinversesansserif = 0x278f;
t.sixdeva = 0x096c;
t.sixgujarati = 0x0aec;
t.sixgurmukhi = 0x0a6c;
t.sixhackarabic = 0x0666;
t.sixhangzhou = 0x3026;
t.sixideographicparen = 0x3225;
t.sixinferior = 0x2086;
t.sixmonospace = 0xff16;
t.sixoldstyle = 0xf736;
t.sixparen = 0x2479;
t.sixperiod = 0x248d;
t.sixpersian = 0x06f6;
t.sixroman = 0x2175;
t.sixsuperior = 0x2076;
t.sixteencircle = 0x246f;
t.sixteencurrencydenominatorbengali = 0x09f9;
t.sixteenparen = 0x2483;
t.sixteenperiod = 0x2497;
t.sixthai = 0x0e56;
t.slash = 0x002f;
t.slashmonospace = 0xff0f;
t.slong = 0x017f;
t.slongdotaccent = 0x1e9b;
t.smileface = 0x263a;
t.smonospace = 0xff53;
t.sofpasuqhebrew = 0x05c3;
t.softhyphen = 0x00ad;
t.softsigncyrillic = 0x044c;
t.sohiragana = 0x305d;
t.sokatakana = 0x30bd;
t.sokatakanahalfwidth = 0xff7f;
t.soliduslongoverlaycmb = 0x0338;
t.solidusshortoverlaycmb = 0x0337;
t.sorusithai = 0x0e29;
t.sosalathai = 0x0e28;
t.sosothai = 0x0e0b;
t.sosuathai = 0x0e2a;
t.space = 0x0020;
t.spacehackarabic = 0x0020;
t.spade = 0x2660;
t.spadesuitblack = 0x2660;
t.spadesuitwhite = 0x2664;
t.sparen = 0x24ae;
t.squarebelowcmb = 0x033b;
t.squarecc = 0x33c4;
t.squarecm = 0x339d;
t.squarediagonalcrosshatchfill = 0x25a9;
t.squarehorizontalfill = 0x25a4;
t.squarekg = 0x338f;
t.squarekm = 0x339e;
t.squarekmcapital = 0x33ce;
t.squareln = 0x33d1;
t.squarelog = 0x33d2;
t.squaremg = 0x338e;
t.squaremil = 0x33d5;
t.squaremm = 0x339c;
t.squaremsquared = 0x33a1;
t.squareorthogonalcrosshatchfill = 0x25a6;
t.squareupperlefttolowerrightfill = 0x25a7;
t.squareupperrighttolowerleftfill = 0x25a8;
t.squareverticalfill = 0x25a5;
t.squarewhitewithsmallblack = 0x25a3;
t.srsquare = 0x33db;
t.ssabengali = 0x09b7;
t.ssadeva = 0x0937;
t.ssagujarati = 0x0ab7;
t.ssangcieuckorean = 0x3149;
t.ssanghieuhkorean = 0x3185;
t.ssangieungkorean = 0x3180;
t.ssangkiyeokkorean = 0x3132;
t.ssangnieunkorean = 0x3165;
t.ssangpieupkorean = 0x3143;
t.ssangsioskorean = 0x3146;
t.ssangtikeutkorean = 0x3138;
t.ssuperior = 0xf6f2;
t.sterling = 0x00a3;
t.sterlingmonospace = 0xffe1;
t.strokelongoverlaycmb = 0x0336;
t.strokeshortoverlaycmb = 0x0335;
t.subset = 0x2282;
t.subsetnotequal = 0x228a;
t.subsetorequal = 0x2286;
t.succeeds = 0x227b;
t.suchthat = 0x220b;
t.suhiragana = 0x3059;
t.sukatakana = 0x30b9;
t.sukatakanahalfwidth = 0xff7d;
t.sukunarabic = 0x0652;
t.summation = 0x2211;
t.sun = 0x263c;
t.superset = 0x2283;
t.supersetnotequal = 0x228b;
t.supersetorequal = 0x2287;
t.svsquare = 0x33dc;
t.syouwaerasquare = 0x337c;
t.t = 0x0074;
t.tabengali = 0x09a4;
t.tackdown = 0x22a4;
t.tackleft = 0x22a3;
t.tadeva = 0x0924;
t.tagujarati = 0x0aa4;
t.tagurmukhi = 0x0a24;
t.taharabic = 0x0637;
t.tahfinalarabic = 0xfec2;
t.tahinitialarabic = 0xfec3;
t.tahiragana = 0x305f;
t.tahmedialarabic = 0xfec4;
t.taisyouerasquare = 0x337d;
t.takatakana = 0x30bf;
t.takatakanahalfwidth = 0xff80;
t.tatweelarabic = 0x0640;
t.tau = 0x03c4;
t.tav = 0x05ea;
t.tavdages = 0xfb4a;
t.tavdagesh = 0xfb4a;
t.tavdageshhebrew = 0xfb4a;
t.tavhebrew = 0x05ea;
t.tbar = 0x0167;
t.tbopomofo = 0x310a;
t.tcaron = 0x0165;
t.tccurl = 0x02a8;
t.tcedilla = 0x0163;
t.tcheharabic = 0x0686;
t.tchehfinalarabic = 0xfb7b;
t.tchehinitialarabic = 0xfb7c;
t.tchehmedialarabic = 0xfb7d;
t.tcircle = 0x24e3;
t.tcircumflexbelow = 0x1e71;
t.tcommaaccent = 0x0163;
t.tdieresis = 0x1e97;
t.tdotaccent = 0x1e6b;
t.tdotbelow = 0x1e6d;
t.tecyrillic = 0x0442;
t.tedescendercyrillic = 0x04ad;
t.teharabic = 0x062a;
t.tehfinalarabic = 0xfe96;
t.tehhahinitialarabic = 0xfca2;
t.tehhahisolatedarabic = 0xfc0c;
t.tehinitialarabic = 0xfe97;
t.tehiragana = 0x3066;
t.tehjeeminitialarabic = 0xfca1;
t.tehjeemisolatedarabic = 0xfc0b;
t.tehmarbutaarabic = 0x0629;
t.tehmarbutafinalarabic = 0xfe94;
t.tehmedialarabic = 0xfe98;
t.tehmeeminitialarabic = 0xfca4;
t.tehmeemisolatedarabic = 0xfc0e;
t.tehnoonfinalarabic = 0xfc73;
t.tekatakana = 0x30c6;
t.tekatakanahalfwidth = 0xff83;
t.telephone = 0x2121;
t.telephoneblack = 0x260e;
t.telishagedolahebrew = 0x05a0;
t.telishaqetanahebrew = 0x05a9;
t.tencircle = 0x2469;
t.tenideographicparen = 0x3229;
t.tenparen = 0x247d;
t.tenperiod = 0x2491;
t.tenroman = 0x2179;
t.tesh = 0x02a7;
t.tet = 0x05d8;
t.tetdagesh = 0xfb38;
t.tetdageshhebrew = 0xfb38;
t.tethebrew = 0x05d8;
t.tetsecyrillic = 0x04b5;
t.tevirhebrew = 0x059b;
t.tevirlefthebrew = 0x059b;
t.thabengali = 0x09a5;
t.thadeva = 0x0925;
t.thagujarati = 0x0aa5;
t.thagurmukhi = 0x0a25;
t.thalarabic = 0x0630;
t.thalfinalarabic = 0xfeac;
t.thanthakhatlowleftthai = 0xf898;
t.thanthakhatlowrightthai = 0xf897;
t.thanthakhatthai = 0x0e4c;
t.thanthakhatupperleftthai = 0xf896;
t.theharabic = 0x062b;
t.thehfinalarabic = 0xfe9a;
t.thehinitialarabic = 0xfe9b;
t.thehmedialarabic = 0xfe9c;
t.thereexists = 0x2203;
t.therefore = 0x2234;
t.theta = 0x03b8;
t.theta1 = 0x03d1;
t.thetasymbolgreek = 0x03d1;
t.thieuthacirclekorean = 0x3279;
t.thieuthaparenkorean = 0x3219;
t.thieuthcirclekorean = 0x326b;
t.thieuthkorean = 0x314c;
t.thieuthparenkorean = 0x320b;
t.thirteencircle = 0x246c;
t.thirteenparen = 0x2480;
t.thirteenperiod = 0x2494;
t.thonangmonthothai = 0x0e11;
t.thook = 0x01ad;
t.thophuthaothai = 0x0e12;
t.thorn = 0x00fe;
t.thothahanthai = 0x0e17;
t.thothanthai = 0x0e10;
t.thothongthai = 0x0e18;
t.thothungthai = 0x0e16;
t.thousandcyrillic = 0x0482;
t.thousandsseparatorarabic = 0x066c;
t.thousandsseparatorpersian = 0x066c;
t.three = 0x0033;
t.threearabic = 0x0663;
t.threebengali = 0x09e9;
t.threecircle = 0x2462;
t.threecircleinversesansserif = 0x278c;
t.threedeva = 0x0969;
t.threeeighths = 0x215c;
t.threegujarati = 0x0ae9;
t.threegurmukhi = 0x0a69;
t.threehackarabic = 0x0663;
t.threehangzhou = 0x3023;
t.threeideographicparen = 0x3222;
t.threeinferior = 0x2083;
t.threemonospace = 0xff13;
t.threenumeratorbengali = 0x09f6;
t.threeoldstyle = 0xf733;
t.threeparen = 0x2476;
t.threeperiod = 0x248a;
t.threepersian = 0x06f3;
t.threequarters = 0x00be;
t.threequartersemdash = 0xf6de;
t.threeroman = 0x2172;
t.threesuperior = 0x00b3;
t.threethai = 0x0e53;
t.thzsquare = 0x3394;
t.tihiragana = 0x3061;
t.tikatakana = 0x30c1;
t.tikatakanahalfwidth = 0xff81;
t.tikeutacirclekorean = 0x3270;
t.tikeutaparenkorean = 0x3210;
t.tikeutcirclekorean = 0x3262;
t.tikeutkorean = 0x3137;
t.tikeutparenkorean = 0x3202;
t.tilde = 0x02dc;
t.tildebelowcmb = 0x0330;
t.tildecmb = 0x0303;
t.tildecomb = 0x0303;
t.tildedoublecmb = 0x0360;
t.tildeoperator = 0x223c;
t.tildeoverlaycmb = 0x0334;
t.tildeverticalcmb = 0x033e;
t.timescircle = 0x2297;
t.tipehahebrew = 0x0596;
t.tipehalefthebrew = 0x0596;
t.tippigurmukhi = 0x0a70;
t.titlocyrilliccmb = 0x0483;
t.tiwnarmenian = 0x057f;
t.tlinebelow = 0x1e6f;
t.tmonospace = 0xff54;
t.toarmenian = 0x0569;
t.tohiragana = 0x3068;
t.tokatakana = 0x30c8;
t.tokatakanahalfwidth = 0xff84;
t.tonebarextrahighmod = 0x02e5;
t.tonebarextralowmod = 0x02e9;
t.tonebarhighmod = 0x02e6;
t.tonebarlowmod = 0x02e8;
t.tonebarmidmod = 0x02e7;
t.tonefive = 0x01bd;
t.tonesix = 0x0185;
t.tonetwo = 0x01a8;
t.tonos = 0x0384;
t.tonsquare = 0x3327;
t.topatakthai = 0x0e0f;
t.tortoiseshellbracketleft = 0x3014;
t.tortoiseshellbracketleftsmall = 0xfe5d;
t.tortoiseshellbracketleftvertical = 0xfe39;
t.tortoiseshellbracketright = 0x3015;
t.tortoiseshellbracketrightsmall = 0xfe5e;
t.tortoiseshellbracketrightvertical = 0xfe3a;
t.totaothai = 0x0e15;
t.tpalatalhook = 0x01ab;
t.tparen = 0x24af;
t.trademark = 0x2122;
t.trademarksans = 0xf8ea;
t.trademarkserif = 0xf6db;
t.tretroflexhook = 0x0288;
t.triagdn = 0x25bc;
t.triaglf = 0x25c4;
t.triagrt = 0x25ba;
t.triagup = 0x25b2;
t.ts = 0x02a6;
t.tsadi = 0x05e6;
t.tsadidagesh = 0xfb46;
t.tsadidageshhebrew = 0xfb46;
t.tsadihebrew = 0x05e6;
t.tsecyrillic = 0x0446;
t.tsere = 0x05b5;
t.tsere12 = 0x05b5;
t.tsere1e = 0x05b5;
t.tsere2b = 0x05b5;
t.tserehebrew = 0x05b5;
t.tserenarrowhebrew = 0x05b5;
t.tserequarterhebrew = 0x05b5;
t.tserewidehebrew = 0x05b5;
t.tshecyrillic = 0x045b;
t.tsuperior = 0xf6f3;
t.ttabengali = 0x099f;
t.ttadeva = 0x091f;
t.ttagujarati = 0x0a9f;
t.ttagurmukhi = 0x0a1f;
t.tteharabic = 0x0679;
t.ttehfinalarabic = 0xfb67;
t.ttehinitialarabic = 0xfb68;
t.ttehmedialarabic = 0xfb69;
t.tthabengali = 0x09a0;
t.tthadeva = 0x0920;
t.tthagujarati = 0x0aa0;
t.tthagurmukhi = 0x0a20;
t.tturned = 0x0287;
t.tuhiragana = 0x3064;
t.tukatakana = 0x30c4;
t.tukatakanahalfwidth = 0xff82;
t.tusmallhiragana = 0x3063;
t.tusmallkatakana = 0x30c3;
t.tusmallkatakanahalfwidth = 0xff6f;
t.twelvecircle = 0x246b;
t.twelveparen = 0x247f;
t.twelveperiod = 0x2493;
t.twelveroman = 0x217b;
t.twentycircle = 0x2473;
t.twentyhangzhou = 0x5344;
t.twentyparen = 0x2487;
t.twentyperiod = 0x249b;
t.two = 0x0032;
t.twoarabic = 0x0662;
t.twobengali = 0x09e8;
t.twocircle = 0x2461;
t.twocircleinversesansserif = 0x278b;
t.twodeva = 0x0968;
t.twodotenleader = 0x2025;
t.twodotleader = 0x2025;
t.twodotleadervertical = 0xfe30;
t.twogujarati = 0x0ae8;
t.twogurmukhi = 0x0a68;
t.twohackarabic = 0x0662;
t.twohangzhou = 0x3022;
t.twoideographicparen = 0x3221;
t.twoinferior = 0x2082;
t.twomonospace = 0xff12;
t.twonumeratorbengali = 0x09f5;
t.twooldstyle = 0xf732;
t.twoparen = 0x2475;
t.twoperiod = 0x2489;
t.twopersian = 0x06f2;
t.tworoman = 0x2171;
t.twostroke = 0x01bb;
t.twosuperior = 0x00b2;
t.twothai = 0x0e52;
t.twothirds = 0x2154;
t.u = 0x0075;
t.uacute = 0x00fa;
t.ubar = 0x0289;
t.ubengali = 0x0989;
t.ubopomofo = 0x3128;
t.ubreve = 0x016d;
t.ucaron = 0x01d4;
t.ucircle = 0x24e4;
t.ucircumflex = 0x00fb;
t.ucircumflexbelow = 0x1e77;
t.ucyrillic = 0x0443;
t.udattadeva = 0x0951;
t.udblacute = 0x0171;
t.udblgrave = 0x0215;
t.udeva = 0x0909;
t.udieresis = 0x00fc;
t.udieresisacute = 0x01d8;
t.udieresisbelow = 0x1e73;
t.udieresiscaron = 0x01da;
t.udieresiscyrillic = 0x04f1;
t.udieresisgrave = 0x01dc;
t.udieresismacron = 0x01d6;
t.udotbelow = 0x1ee5;
t.ugrave = 0x00f9;
t.ugujarati = 0x0a89;
t.ugurmukhi = 0x0a09;
t.uhiragana = 0x3046;
t.uhookabove = 0x1ee7;
t.uhorn = 0x01b0;
t.uhornacute = 0x1ee9;
t.uhorndotbelow = 0x1ef1;
t.uhorngrave = 0x1eeb;
t.uhornhookabove = 0x1eed;
t.uhorntilde = 0x1eef;
t.uhungarumlaut = 0x0171;
t.uhungarumlautcyrillic = 0x04f3;
t.uinvertedbreve = 0x0217;
t.ukatakana = 0x30a6;
t.ukatakanahalfwidth = 0xff73;
t.ukcyrillic = 0x0479;
t.ukorean = 0x315c;
t.umacron = 0x016b;
t.umacroncyrillic = 0x04ef;
t.umacrondieresis = 0x1e7b;
t.umatragurmukhi = 0x0a41;
t.umonospace = 0xff55;
t.underscore = 0x005f;
t.underscoredbl = 0x2017;
t.underscoremonospace = 0xff3f;
t.underscorevertical = 0xfe33;
t.underscorewavy = 0xfe4f;
t.union = 0x222a;
t.universal = 0x2200;
t.uogonek = 0x0173;
t.uparen = 0x24b0;
t.upblock = 0x2580;
t.upperdothebrew = 0x05c4;
t.upsilon = 0x03c5;
t.upsilondieresis = 0x03cb;
t.upsilondieresistonos = 0x03b0;
t.upsilonlatin = 0x028a;
t.upsilontonos = 0x03cd;
t.uptackbelowcmb = 0x031d;
t.uptackmod = 0x02d4;
t.uragurmukhi = 0x0a73;
t.uring = 0x016f;
t.ushortcyrillic = 0x045e;
t.usmallhiragana = 0x3045;
t.usmallkatakana = 0x30a5;
t.usmallkatakanahalfwidth = 0xff69;
t.ustraightcyrillic = 0x04af;
t.ustraightstrokecyrillic = 0x04b1;
t.utilde = 0x0169;
t.utildeacute = 0x1e79;
t.utildebelow = 0x1e75;
t.uubengali = 0x098a;
t.uudeva = 0x090a;
t.uugujarati = 0x0a8a;
t.uugurmukhi = 0x0a0a;
t.uumatragurmukhi = 0x0a42;
t.uuvowelsignbengali = 0x09c2;
t.uuvowelsigndeva = 0x0942;
t.uuvowelsigngujarati = 0x0ac2;
t.uvowelsignbengali = 0x09c1;
t.uvowelsigndeva = 0x0941;
t.uvowelsigngujarati = 0x0ac1;
t.v = 0x0076;
t.vadeva = 0x0935;
t.vagujarati = 0x0ab5;
t.vagurmukhi = 0x0a35;
t.vakatakana = 0x30f7;
t.vav = 0x05d5;
t.vavdagesh = 0xfb35;
t.vavdagesh65 = 0xfb35;
t.vavdageshhebrew = 0xfb35;
t.vavhebrew = 0x05d5;
t.vavholam = 0xfb4b;
t.vavholamhebrew = 0xfb4b;
t.vavvavhebrew = 0x05f0;
t.vavyodhebrew = 0x05f1;
t.vcircle = 0x24e5;
t.vdotbelow = 0x1e7f;
t.vecyrillic = 0x0432;
t.veharabic = 0x06a4;
t.vehfinalarabic = 0xfb6b;
t.vehinitialarabic = 0xfb6c;
t.vehmedialarabic = 0xfb6d;
t.vekatakana = 0x30f9;
t.venus = 0x2640;
t.verticalbar = 0x007c;
t.verticallineabovecmb = 0x030d;
t.verticallinebelowcmb = 0x0329;
t.verticallinelowmod = 0x02cc;
t.verticallinemod = 0x02c8;
t.vewarmenian = 0x057e;
t.vhook = 0x028b;
t.vikatakana = 0x30f8;
t.viramabengali = 0x09cd;
t.viramadeva = 0x094d;
t.viramagujarati = 0x0acd;
t.visargabengali = 0x0983;
t.visargadeva = 0x0903;
t.visargagujarati = 0x0a83;
t.vmonospace = 0xff56;
t.voarmenian = 0x0578;
t.voicediterationhiragana = 0x309e;
t.voicediterationkatakana = 0x30fe;
t.voicedmarkkana = 0x309b;
t.voicedmarkkanahalfwidth = 0xff9e;
t.vokatakana = 0x30fa;
t.vparen = 0x24b1;
t.vtilde = 0x1e7d;
t.vturned = 0x028c;
t.vuhiragana = 0x3094;
t.vukatakana = 0x30f4;
t.w = 0x0077;
t.wacute = 0x1e83;
t.waekorean = 0x3159;
t.wahiragana = 0x308f;
t.wakatakana = 0x30ef;
t.wakatakanahalfwidth = 0xff9c;
t.wakorean = 0x3158;
t.wasmallhiragana = 0x308e;
t.wasmallkatakana = 0x30ee;
t.wattosquare = 0x3357;
t.wavedash = 0x301c;
t.wavyunderscorevertical = 0xfe34;
t.wawarabic = 0x0648;
t.wawfinalarabic = 0xfeee;
t.wawhamzaabovearabic = 0x0624;
t.wawhamzaabovefinalarabic = 0xfe86;
t.wbsquare = 0x33dd;
t.wcircle = 0x24e6;
t.wcircumflex = 0x0175;
t.wdieresis = 0x1e85;
t.wdotaccent = 0x1e87;
t.wdotbelow = 0x1e89;
t.wehiragana = 0x3091;
t.weierstrass = 0x2118;
t.wekatakana = 0x30f1;
t.wekorean = 0x315e;
t.weokorean = 0x315d;
t.wgrave = 0x1e81;
t.whitebullet = 0x25e6;
t.whitecircle = 0x25cb;
t.whitecircleinverse = 0x25d9;
t.whitecornerbracketleft = 0x300e;
t.whitecornerbracketleftvertical = 0xfe43;
t.whitecornerbracketright = 0x300f;
t.whitecornerbracketrightvertical = 0xfe44;
t.whitediamond = 0x25c7;
t.whitediamondcontainingblacksmalldiamond = 0x25c8;
t.whitedownpointingsmalltriangle = 0x25bf;
t.whitedownpointingtriangle = 0x25bd;
t.whiteleftpointingsmalltriangle = 0x25c3;
t.whiteleftpointingtriangle = 0x25c1;
t.whitelenticularbracketleft = 0x3016;
t.whitelenticularbracketright = 0x3017;
t.whiterightpointingsmalltriangle = 0x25b9;
t.whiterightpointingtriangle = 0x25b7;
t.whitesmallsquare = 0x25ab;
t.whitesmilingface = 0x263a;
t.whitesquare = 0x25a1;
t.whitestar = 0x2606;
t.whitetelephone = 0x260f;
t.whitetortoiseshellbracketleft = 0x3018;
t.whitetortoiseshellbracketright = 0x3019;
t.whiteuppointingsmalltriangle = 0x25b5;
t.whiteuppointingtriangle = 0x25b3;
t.wihiragana = 0x3090;
t.wikatakana = 0x30f0;
t.wikorean = 0x315f;
t.wmonospace = 0xff57;
t.wohiragana = 0x3092;
t.wokatakana = 0x30f2;
t.wokatakanahalfwidth = 0xff66;
t.won = 0x20a9;
t.wonmonospace = 0xffe6;
t.wowaenthai = 0x0e27;
t.wparen = 0x24b2;
t.wring = 0x1e98;
t.wsuperior = 0x02b7;
t.wturned = 0x028d;
t.wynn = 0x01bf;
t.x = 0x0078;
t.xabovecmb = 0x033d;
t.xbopomofo = 0x3112;
t.xcircle = 0x24e7;
t.xdieresis = 0x1e8d;
t.xdotaccent = 0x1e8b;
t.xeharmenian = 0x056d;
t.xi = 0x03be;
t.xmonospace = 0xff58;
t.xparen = 0x24b3;
t.xsuperior = 0x02e3;
t.y = 0x0079;
t.yaadosquare = 0x334e;
t.yabengali = 0x09af;
t.yacute = 0x00fd;
t.yadeva = 0x092f;
t.yaekorean = 0x3152;
t.yagujarati = 0x0aaf;
t.yagurmukhi = 0x0a2f;
t.yahiragana = 0x3084;
t.yakatakana = 0x30e4;
t.yakatakanahalfwidth = 0xff94;
t.yakorean = 0x3151;
t.yamakkanthai = 0x0e4e;
t.yasmallhiragana = 0x3083;
t.yasmallkatakana = 0x30e3;
t.yasmallkatakanahalfwidth = 0xff6c;
t.yatcyrillic = 0x0463;
t.ycircle = 0x24e8;
t.ycircumflex = 0x0177;
t.ydieresis = 0x00ff;
t.ydotaccent = 0x1e8f;
t.ydotbelow = 0x1ef5;
t.yeharabic = 0x064a;
t.yehbarreearabic = 0x06d2;
t.yehbarreefinalarabic = 0xfbaf;
t.yehfinalarabic = 0xfef2;
t.yehhamzaabovearabic = 0x0626;
t.yehhamzaabovefinalarabic = 0xfe8a;
t.yehhamzaaboveinitialarabic = 0xfe8b;
t.yehhamzaabovemedialarabic = 0xfe8c;
t.yehinitialarabic = 0xfef3;
t.yehmedialarabic = 0xfef4;
t.yehmeeminitialarabic = 0xfcdd;
t.yehmeemisolatedarabic = 0xfc58;
t.yehnoonfinalarabic = 0xfc94;
t.yehthreedotsbelowarabic = 0x06d1;
t.yekorean = 0x3156;
t.yen = 0x00a5;
t.yenmonospace = 0xffe5;
t.yeokorean = 0x3155;
t.yeorinhieuhkorean = 0x3186;
t.yerahbenyomohebrew = 0x05aa;
t.yerahbenyomolefthebrew = 0x05aa;
t.yericyrillic = 0x044b;
t.yerudieresiscyrillic = 0x04f9;
t.yesieungkorean = 0x3181;
t.yesieungpansioskorean = 0x3183;
t.yesieungsioskorean = 0x3182;
t.yetivhebrew = 0x059a;
t.ygrave = 0x1ef3;
t.yhook = 0x01b4;
t.yhookabove = 0x1ef7;
t.yiarmenian = 0x0575;
t.yicyrillic = 0x0457;
t.yikorean = 0x3162;
t.yinyang = 0x262f;
t.yiwnarmenian = 0x0582;
t.ymonospace = 0xff59;
t.yod = 0x05d9;
t.yoddagesh = 0xfb39;
t.yoddageshhebrew = 0xfb39;
t.yodhebrew = 0x05d9;
t.yodyodhebrew = 0x05f2;
t.yodyodpatahhebrew = 0xfb1f;
t.yohiragana = 0x3088;
t.yoikorean = 0x3189;
t.yokatakana = 0x30e8;
t.yokatakanahalfwidth = 0xff96;
t.yokorean = 0x315b;
t.yosmallhiragana = 0x3087;
t.yosmallkatakana = 0x30e7;
t.yosmallkatakanahalfwidth = 0xff6e;
t.yotgreek = 0x03f3;
t.yoyaekorean = 0x3188;
t.yoyakorean = 0x3187;
t.yoyakthai = 0x0e22;
t.yoyingthai = 0x0e0d;
t.yparen = 0x24b4;
t.ypogegrammeni = 0x037a;
t.ypogegrammenigreekcmb = 0x0345;
t.yr = 0x01a6;
t.yring = 0x1e99;
t.ysuperior = 0x02b8;
t.ytilde = 0x1ef9;
t.yturned = 0x028e;
t.yuhiragana = 0x3086;
t.yuikorean = 0x318c;
t.yukatakana = 0x30e6;
t.yukatakanahalfwidth = 0xff95;
t.yukorean = 0x3160;
t.yusbigcyrillic = 0x046b;
t.yusbigiotifiedcyrillic = 0x046d;
t.yuslittlecyrillic = 0x0467;
t.yuslittleiotifiedcyrillic = 0x0469;
t.yusmallhiragana = 0x3085;
t.yusmallkatakana = 0x30e5;
t.yusmallkatakanahalfwidth = 0xff6d;
t.yuyekorean = 0x318b;
t.yuyeokorean = 0x318a;
t.yyabengali = 0x09df;
t.yyadeva = 0x095f;
t.z = 0x007a;
t.zaarmenian = 0x0566;
t.zacute = 0x017a;
t.zadeva = 0x095b;
t.zagurmukhi = 0x0a5b;
t.zaharabic = 0x0638;
t.zahfinalarabic = 0xfec6;
t.zahinitialarabic = 0xfec7;
t.zahiragana = 0x3056;
t.zahmedialarabic = 0xfec8;
t.zainarabic = 0x0632;
t.zainfinalarabic = 0xfeb0;
t.zakatakana = 0x30b6;
t.zaqefgadolhebrew = 0x0595;
t.zaqefqatanhebrew = 0x0594;
t.zarqahebrew = 0x0598;
t.zayin = 0x05d6;
t.zayindagesh = 0xfb36;
t.zayindageshhebrew = 0xfb36;
t.zayinhebrew = 0x05d6;
t.zbopomofo = 0x3117;
t.zcaron = 0x017e;
t.zcircle = 0x24e9;
t.zcircumflex = 0x1e91;
t.zcurl = 0x0291;
t.zdot = 0x017c;
t.zdotaccent = 0x017c;
t.zdotbelow = 0x1e93;
t.zecyrillic = 0x0437;
t.zedescendercyrillic = 0x0499;
t.zedieresiscyrillic = 0x04df;
t.zehiragana = 0x305c;
t.zekatakana = 0x30bc;
t.zero = 0x0030;
t.zeroarabic = 0x0660;
t.zerobengali = 0x09e6;
t.zerodeva = 0x0966;
t.zerogujarati = 0x0ae6;
t.zerogurmukhi = 0x0a66;
t.zerohackarabic = 0x0660;
t.zeroinferior = 0x2080;
t.zeromonospace = 0xff10;
t.zerooldstyle = 0xf730;
t.zeropersian = 0x06f0;
t.zerosuperior = 0x2070;
t.zerothai = 0x0e50;
t.zerowidthjoiner = 0xfeff;
t.zerowidthnonjoiner = 0x200c;
t.zerowidthspace = 0x200b;
t.zeta = 0x03b6;
t.zhbopomofo = 0x3113;
t.zhearmenian = 0x056a;
t.zhebrevecyrillic = 0x04c2;
t.zhecyrillic = 0x0436;
t.zhedescendercyrillic = 0x0497;
t.zhedieresiscyrillic = 0x04dd;
t.zihiragana = 0x3058;
t.zikatakana = 0x30b8;
t.zinorhebrew = 0x05ae;
t.zlinebelow = 0x1e95;
t.zmonospace = 0xff5a;
t.zohiragana = 0x305e;
t.zokatakana = 0x30be;
t.zparen = 0x24b5;
t.zretroflexhook = 0x0290;
t.zstroke = 0x01b6;
t.zuhiragana = 0x305a;
t.zukatakana = 0x30ba;
t[".notdef"] = 0x0000;
t.angbracketleftbig = 0x2329;
t.angbracketleftBig = 0x2329;
t.angbracketleftbigg = 0x2329;
t.angbracketleftBigg = 0x2329;
t.angbracketrightBig = 0x232a;
t.angbracketrightbig = 0x232a;
t.angbracketrightBigg = 0x232a;
t.angbracketrightbigg = 0x232a;
t.arrowhookleft = 0x21aa;
t.arrowhookright = 0x21a9;
t.arrowlefttophalf = 0x21bc;
t.arrowleftbothalf = 0x21bd;
t.arrownortheast = 0x2197;
t.arrownorthwest = 0x2196;
t.arrowrighttophalf = 0x21c0;
t.arrowrightbothalf = 0x21c1;
t.arrowsoutheast = 0x2198;
t.arrowsouthwest = 0x2199;
t.backslashbig = 0x2216;
t.backslashBig = 0x2216;
t.backslashBigg = 0x2216;
t.backslashbigg = 0x2216;
t.bardbl = 0x2016;
t.bracehtipdownleft = 0xfe37;
t.bracehtipdownright = 0xfe37;
t.bracehtipupleft = 0xfe38;
t.bracehtipupright = 0xfe38;
t.braceleftBig = 0x007b;
t.braceleftbig = 0x007b;
t.braceleftbigg = 0x007b;
t.braceleftBigg = 0x007b;
t.bracerightBig = 0x007d;
t.bracerightbig = 0x007d;
t.bracerightbigg = 0x007d;
t.bracerightBigg = 0x007d;
t.bracketleftbig = 0x005b;
t.bracketleftBig = 0x005b;
t.bracketleftbigg = 0x005b;
t.bracketleftBigg = 0x005b;
t.bracketrightBig = 0x005d;
t.bracketrightbig = 0x005d;
t.bracketrightbigg = 0x005d;
t.bracketrightBigg = 0x005d;
t.ceilingleftbig = 0x2308;
t.ceilingleftBig = 0x2308;
t.ceilingleftBigg = 0x2308;
t.ceilingleftbigg = 0x2308;
t.ceilingrightbig = 0x2309;
t.ceilingrightBig = 0x2309;
t.ceilingrightbigg = 0x2309;
t.ceilingrightBigg = 0x2309;
t.circledotdisplay = 0x2299;
t.circledottext = 0x2299;
t.circlemultiplydisplay = 0x2297;
t.circlemultiplytext = 0x2297;
t.circleplusdisplay = 0x2295;
t.circleplustext = 0x2295;
t.contintegraldisplay = 0x222e;
t.contintegraltext = 0x222e;
t.coproductdisplay = 0x2210;
t.coproducttext = 0x2210;
t.floorleftBig = 0x230a;
t.floorleftbig = 0x230a;
t.floorleftbigg = 0x230a;
t.floorleftBigg = 0x230a;
t.floorrightbig = 0x230b;
t.floorrightBig = 0x230b;
t.floorrightBigg = 0x230b;
t.floorrightbigg = 0x230b;
t.hatwide = 0x0302;
t.hatwider = 0x0302;
t.hatwidest = 0x0302;
t.intercal = 0x1d40;
t.integraldisplay = 0x222b;
t.integraltext = 0x222b;
t.intersectiondisplay = 0x22c2;
t.intersectiontext = 0x22c2;
t.logicalanddisplay = 0x2227;
t.logicalandtext = 0x2227;
t.logicalordisplay = 0x2228;
t.logicalortext = 0x2228;
t.parenleftBig = 0x0028;
t.parenleftbig = 0x0028;
t.parenleftBigg = 0x0028;
t.parenleftbigg = 0x0028;
t.parenrightBig = 0x0029;
t.parenrightbig = 0x0029;
t.parenrightBigg = 0x0029;
t.parenrightbigg = 0x0029;
t.prime = 0x2032;
t.productdisplay = 0x220f;
t.producttext = 0x220f;
t.radicalbig = 0x221a;
t.radicalBig = 0x221a;
t.radicalBigg = 0x221a;
t.radicalbigg = 0x221a;
t.radicalbt = 0x221a;
t.radicaltp = 0x221a;
t.radicalvertex = 0x221a;
t.slashbig = 0x002f;
t.slashBig = 0x002f;
t.slashBigg = 0x002f;
t.slashbigg = 0x002f;
t.summationdisplay = 0x2211;
t.summationtext = 0x2211;
t.tildewide = 0x02dc;
t.tildewider = 0x02dc;
t.tildewidest = 0x02dc;
t.uniondisplay = 0x22c3;
t.unionmultidisplay = 0x228e;
t.unionmultitext = 0x228e;
t.unionsqdisplay = 0x2294;
t.unionsqtext = 0x2294;
t.uniontext = 0x22c3;
t.vextenddouble = 0x2225;
t.vextendsingle = 0x2223;
});
const getDingbatsGlyphsUnicode = getLookupTableFactory(function (t) {
t.space = 0x0020;
t.a1 = 0x2701;
t.a2 = 0x2702;
t.a202 = 0x2703;
t.a3 = 0x2704;
t.a4 = 0x260e;
t.a5 = 0x2706;
t.a119 = 0x2707;
t.a118 = 0x2708;
t.a117 = 0x2709;
t.a11 = 0x261b;
t.a12 = 0x261e;
t.a13 = 0x270c;
t.a14 = 0x270d;
t.a15 = 0x270e;
t.a16 = 0x270f;
t.a105 = 0x2710;
t.a17 = 0x2711;
t.a18 = 0x2712;
t.a19 = 0x2713;
t.a20 = 0x2714;
t.a21 = 0x2715;
t.a22 = 0x2716;
t.a23 = 0x2717;
t.a24 = 0x2718;
t.a25 = 0x2719;
t.a26 = 0x271a;
t.a27 = 0x271b;
t.a28 = 0x271c;
t.a6 = 0x271d;
t.a7 = 0x271e;
t.a8 = 0x271f;
t.a9 = 0x2720;
t.a10 = 0x2721;
t.a29 = 0x2722;
t.a30 = 0x2723;
t.a31 = 0x2724;
t.a32 = 0x2725;
t.a33 = 0x2726;
t.a34 = 0x2727;
t.a35 = 0x2605;
t.a36 = 0x2729;
t.a37 = 0x272a;
t.a38 = 0x272b;
t.a39 = 0x272c;
t.a40 = 0x272d;
t.a41 = 0x272e;
t.a42 = 0x272f;
t.a43 = 0x2730;
t.a44 = 0x2731;
t.a45 = 0x2732;
t.a46 = 0x2733;
t.a47 = 0x2734;
t.a48 = 0x2735;
t.a49 = 0x2736;
t.a50 = 0x2737;
t.a51 = 0x2738;
t.a52 = 0x2739;
t.a53 = 0x273a;
t.a54 = 0x273b;
t.a55 = 0x273c;
t.a56 = 0x273d;
t.a57 = 0x273e;
t.a58 = 0x273f;
t.a59 = 0x2740;
t.a60 = 0x2741;
t.a61 = 0x2742;
t.a62 = 0x2743;
t.a63 = 0x2744;
t.a64 = 0x2745;
t.a65 = 0x2746;
t.a66 = 0x2747;
t.a67 = 0x2748;
t.a68 = 0x2749;
t.a69 = 0x274a;
t.a70 = 0x274b;
t.a71 = 0x25cf;
t.a72 = 0x274d;
t.a73 = 0x25a0;
t.a74 = 0x274f;
t.a203 = 0x2750;
t.a75 = 0x2751;
t.a204 = 0x2752;
t.a76 = 0x25b2;
t.a77 = 0x25bc;
t.a78 = 0x25c6;
t.a79 = 0x2756;
t.a81 = 0x25d7;
t.a82 = 0x2758;
t.a83 = 0x2759;
t.a84 = 0x275a;
t.a97 = 0x275b;
t.a98 = 0x275c;
t.a99 = 0x275d;
t.a100 = 0x275e;
t.a101 = 0x2761;
t.a102 = 0x2762;
t.a103 = 0x2763;
t.a104 = 0x2764;
t.a106 = 0x2765;
t.a107 = 0x2766;
t.a108 = 0x2767;
t.a112 = 0x2663;
t.a111 = 0x2666;
t.a110 = 0x2665;
t.a109 = 0x2660;
t.a120 = 0x2460;
t.a121 = 0x2461;
t.a122 = 0x2462;
t.a123 = 0x2463;
t.a124 = 0x2464;
t.a125 = 0x2465;
t.a126 = 0x2466;
t.a127 = 0x2467;
t.a128 = 0x2468;
t.a129 = 0x2469;
t.a130 = 0x2776;
t.a131 = 0x2777;
t.a132 = 0x2778;
t.a133 = 0x2779;
t.a134 = 0x277a;
t.a135 = 0x277b;
t.a136 = 0x277c;
t.a137 = 0x277d;
t.a138 = 0x277e;
t.a139 = 0x277f;
t.a140 = 0x2780;
t.a141 = 0x2781;
t.a142 = 0x2782;
t.a143 = 0x2783;
t.a144 = 0x2784;
t.a145 = 0x2785;
t.a146 = 0x2786;
t.a147 = 0x2787;
t.a148 = 0x2788;
t.a149 = 0x2789;
t.a150 = 0x278a;
t.a151 = 0x278b;
t.a152 = 0x278c;
t.a153 = 0x278d;
t.a154 = 0x278e;
t.a155 = 0x278f;
t.a156 = 0x2790;
t.a157 = 0x2791;
t.a158 = 0x2792;
t.a159 = 0x2793;
t.a160 = 0x2794;
t.a161 = 0x2192;
t.a163 = 0x2194;
t.a164 = 0x2195;
t.a196 = 0x2798;
t.a165 = 0x2799;
t.a192 = 0x279a;
t.a166 = 0x279b;
t.a167 = 0x279c;
t.a168 = 0x279d;
t.a169 = 0x279e;
t.a170 = 0x279f;
t.a171 = 0x27a0;
t.a172 = 0x27a1;
t.a173 = 0x27a2;
t.a162 = 0x27a3;
t.a174 = 0x27a4;
t.a175 = 0x27a5;
t.a176 = 0x27a6;
t.a177 = 0x27a7;
t.a178 = 0x27a8;
t.a179 = 0x27a9;
t.a193 = 0x27aa;
t.a180 = 0x27ab;
t.a199 = 0x27ac;
t.a181 = 0x27ad;
t.a200 = 0x27ae;
t.a182 = 0x27af;
t.a201 = 0x27b1;
t.a183 = 0x27b2;
t.a184 = 0x27b3;
t.a197 = 0x27b4;
t.a185 = 0x27b5;
t.a194 = 0x27b6;
t.a198 = 0x27b7;
t.a186 = 0x27b8;
t.a195 = 0x27b9;
t.a187 = 0x27ba;
t.a188 = 0x27bb;
t.a189 = 0x27bc;
t.a190 = 0x27bd;
t.a191 = 0x27be;
t.a89 = 0x2768;
t.a90 = 0x2769;
t.a93 = 0x276a;
t.a94 = 0x276b;
t.a91 = 0x276c;
t.a92 = 0x276d;
t.a205 = 0x276e;
t.a85 = 0x276f;
t.a206 = 0x2770;
t.a86 = 0x2771;
t.a87 = 0x2772;
t.a88 = 0x2773;
t.a95 = 0x2774;
t.a96 = 0x2775;
t[".notdef"] = 0x0000;
});
;// ./src/core/unicode.js
const getSpecialPUASymbols = getLookupTableFactory(function (t) {
t[63721] = 0x00a9;
t[63193] = 0x00a9;
t[63720] = 0x00ae;
t[63194] = 0x00ae;
t[63722] = 0x2122;
t[63195] = 0x2122;
t[63729] = 0x23a7;
t[63730] = 0x23a8;
t[63731] = 0x23a9;
t[63740] = 0x23ab;
t[63741] = 0x23ac;
t[63742] = 0x23ad;
t[63726] = 0x23a1;
t[63727] = 0x23a2;
t[63728] = 0x23a3;
t[63737] = 0x23a4;
t[63738] = 0x23a5;
t[63739] = 0x23a6;
t[63723] = 0x239b;
t[63724] = 0x239c;
t[63725] = 0x239d;
t[63734] = 0x239e;
t[63735] = 0x239f;
t[63736] = 0x23a0;
});
function mapSpecialUnicodeValues(code) {
if (code >= 0xfff0 && code <= 0xffff) {
return 0;
} else if (code >= 0xf600 && code <= 0xf8ff) {
return getSpecialPUASymbols()[code] || code;
} else if (code === 0x00ad) {
return 0x002d;
}
return code;
}
function getUnicodeForGlyph(name, glyphsUnicodeMap) {
let unicode = glyphsUnicodeMap[name];
if (unicode !== undefined) {
return unicode;
}
if (!name) {
return -1;
}
if (name[0] === "u") {
const nameLen = name.length;
let hexStr;
if (nameLen === 7 && name[1] === "n" && name[2] === "i") {
hexStr = name.substring(3);
} else if (nameLen >= 5 && nameLen <= 7) {
hexStr = name.substring(1);
} else {
return -1;
}
if (hexStr === hexStr.toUpperCase()) {
unicode = parseInt(hexStr, 16);
if (unicode >= 0) {
return unicode;
}
}
}
return -1;
}
const UnicodeRanges = [[0x0000, 0x007f], [0x0080, 0x00ff], [0x0100, 0x017f], [0x0180, 0x024f], [0x0250, 0x02af, 0x1d00, 0x1d7f, 0x1d80, 0x1dbf], [0x02b0, 0x02ff, 0xa700, 0xa71f], [0x0300, 0x036f, 0x1dc0, 0x1dff], [0x0370, 0x03ff], [0x2c80, 0x2cff], [0x0400, 0x04ff, 0x0500, 0x052f, 0x2de0, 0x2dff, 0xa640, 0xa69f], [0x0530, 0x058f], [0x0590, 0x05ff], [0xa500, 0xa63f], [0x0600, 0x06ff, 0x0750, 0x077f], [0x07c0, 0x07ff], [0x0900, 0x097f], [0x0980, 0x09ff], [0x0a00, 0x0a7f], [0x0a80, 0x0aff], [0x0b00, 0x0b7f], [0x0b80, 0x0bff], [0x0c00, 0x0c7f], [0x0c80, 0x0cff], [0x0d00, 0x0d7f], [0x0e00, 0x0e7f], [0x0e80, 0x0eff], [0x10a0, 0x10ff, 0x2d00, 0x2d2f], [0x1b00, 0x1b7f], [0x1100, 0x11ff], [0x1e00, 0x1eff, 0x2c60, 0x2c7f, 0xa720, 0xa7ff], [0x1f00, 0x1fff], [0x2000, 0x206f, 0x2e00, 0x2e7f], [0x2070, 0x209f], [0x20a0, 0x20cf], [0x20d0, 0x20ff], [0x2100, 0x214f], [0x2150, 0x218f], [0x2190, 0x21ff, 0x27f0, 0x27ff, 0x2900, 0x297f, 0x2b00, 0x2bff], [0x2200, 0x22ff, 0x2a00, 0x2aff, 0x27c0, 0x27ef, 0x2980, 0x29ff], [0x2300, 0x23ff], [0x2400, 0x243f], [0x2440, 0x245f], [0x2460, 0x24ff], [0x2500, 0x257f], [0x2580, 0x259f], [0x25a0, 0x25ff], [0x2600, 0x26ff], [0x2700, 0x27bf], [0x3000, 0x303f], [0x3040, 0x309f], [0x30a0, 0x30ff, 0x31f0, 0x31ff], [0x3100, 0x312f, 0x31a0, 0x31bf], [0x3130, 0x318f], [0xa840, 0xa87f], [0x3200, 0x32ff], [0x3300, 0x33ff], [0xac00, 0xd7af], [0xd800, 0xdfff], [0x10900, 0x1091f], [0x4e00, 0x9fff, 0x2e80, 0x2eff, 0x2f00, 0x2fdf, 0x2ff0, 0x2fff, 0x3400, 0x4dbf, 0x20000, 0x2a6df, 0x3190, 0x319f], [0xe000, 0xf8ff], [0x31c0, 0x31ef, 0xf900, 0xfaff, 0x2f800, 0x2fa1f], [0xfb00, 0xfb4f], [0xfb50, 0xfdff], [0xfe20, 0xfe2f], [0xfe10, 0xfe1f], [0xfe50, 0xfe6f], [0xfe70, 0xfeff], [0xff00, 0xffef], [0xfff0, 0xffff], [0x0f00, 0x0fff], [0x0700, 0x074f], [0x0780, 0x07bf], [0x0d80, 0x0dff], [0x1000, 0x109f], [0x1200, 0x137f, 0x1380, 0x139f, 0x2d80, 0x2ddf], [0x13a0, 0x13ff], [0x1400, 0x167f], [0x1680, 0x169f], [0x16a0, 0x16ff], [0x1780, 0x17ff], [0x1800, 0x18af], [0x2800, 0x28ff], [0xa000, 0xa48f], [0x1700, 0x171f, 0x1720, 0x173f, 0x1740, 0x175f, 0x1760, 0x177f], [0x10300, 0x1032f], [0x10330, 0x1034f], [0x10400, 0x1044f], [0x1d000, 0x1d0ff, 0x1d100, 0x1d1ff, 0x1d200, 0x1d24f], [0x1d400, 0x1d7ff], [0xff000, 0xffffd], [0xfe00, 0xfe0f, 0xe0100, 0xe01ef], [0xe0000, 0xe007f], [0x1900, 0x194f], [0x1950, 0x197f], [0x1980, 0x19df], [0x1a00, 0x1a1f], [0x2c00, 0x2c5f], [0x2d30, 0x2d7f], [0x4dc0, 0x4dff], [0xa800, 0xa82f], [0x10000, 0x1007f, 0x10080, 0x100ff, 0x10100, 0x1013f], [0x10140, 0x1018f], [0x10380, 0x1039f], [0x103a0, 0x103df], [0x10450, 0x1047f], [0x10480, 0x104af], [0x10800, 0x1083f], [0x10a00, 0x10a5f], [0x1d300, 0x1d35f], [0x12000, 0x123ff, 0x12400, 0x1247f], [0x1d360, 0x1d37f], [0x1b80, 0x1bbf], [0x1c00, 0x1c4f], [0x1c50, 0x1c7f], [0xa880, 0xa8df], [0xa900, 0xa92f], [0xa930, 0xa95f], [0xaa00, 0xaa5f], [0x10190, 0x101cf], [0x101d0, 0x101ff], [0x102a0, 0x102df, 0x10280, 0x1029f, 0x10920, 0x1093f], [0x1f030, 0x1f09f, 0x1f000, 0x1f02f]];
function getUnicodeRangeFor(value, lastPosition = -1) {
if (lastPosition !== -1) {
const range = UnicodeRanges[lastPosition];
for (let i = 0, ii = range.length; i < ii; i += 2) {
if (value >= range[i] && value <= range[i + 1]) {
return lastPosition;
}
}
}
for (let i = 0, ii = UnicodeRanges.length; i < ii; i++) {
const range = UnicodeRanges[i];
for (let j = 0, jj = range.length; j < jj; j += 2) {
if (value >= range[j] && value <= range[j + 1]) {
return i;
}
}
}
return -1;
}
const SpecialCharRegExp = new RegExp("^(\\s)|(\\p{Mn})|(\\p{Cf})$", "u");
const CategoryCache = new Map();
function getCharUnicodeCategory(char) {
const cachedCategory = CategoryCache.get(char);
if (cachedCategory) {
return cachedCategory;
}
const groups = char.match(SpecialCharRegExp);
const category = {
isWhitespace: !!groups?.[1],
isZeroWidthDiacritic: !!groups?.[2],
isInvisibleFormatMark: !!groups?.[3]
};
CategoryCache.set(char, category);
return category;
}
function clearUnicodeCaches() {
CategoryCache.clear();
}
;// ./src/core/fonts_utils.js
const SEAC_ANALYSIS_ENABLED = true;
const FontFlags = {
FixedPitch: 1,
Serif: 2,
Symbolic: 4,
Script: 8,
Nonsymbolic: 32,
Italic: 64,
AllCap: 65536,
SmallCap: 131072,
ForceBold: 262144
};
const MacStandardGlyphOrdering = [".notdef", ".null", "nonmarkingreturn", "space", "exclam", "quotedbl", "numbersign", "dollar", "percent", "ampersand", "quotesingle", "parenleft", "parenright", "asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", "less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum", "underscore", "grave", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "braceleft", "bar", "braceright", "asciitilde", "Adieresis", "Aring", "Ccedilla", "Eacute", "Ntilde", "Odieresis", "Udieresis", "aacute", "agrave", "acircumflex", "adieresis", "atilde", "aring", "ccedilla", "eacute", "egrave", "ecircumflex", "edieresis", "iacute", "igrave", "icircumflex", "idieresis", "ntilde", "oacute", "ograve", "ocircumflex", "odieresis", "otilde", "uacute", "ugrave", "ucircumflex", "udieresis", "dagger", "degree", "cent", "sterling", "section", "bullet", "paragraph", "germandbls", "registered", "copyright", "trademark", "acute", "dieresis", "notequal", "AE", "Oslash", "infinity", "plusminus", "lessequal", "greaterequal", "yen", "mu", "partialdiff", "summation", "product", "pi", "integral", "ordfeminine", "ordmasculine", "Omega", "ae", "oslash", "questiondown", "exclamdown", "logicalnot", "radical", "florin", "approxequal", "Delta", "guillemotleft", "guillemotright", "ellipsis", "nonbreakingspace", "Agrave", "Atilde", "Otilde", "OE", "oe", "endash", "emdash", "quotedblleft", "quotedblright", "quoteleft", "quoteright", "divide", "lozenge", "ydieresis", "Ydieresis", "fraction", "currency", "guilsinglleft", "guilsinglright", "fi", "fl", "daggerdbl", "periodcentered", "quotesinglbase", "quotedblbase", "perthousand", "Acircumflex", "Ecircumflex", "Aacute", "Edieresis", "Egrave", "Iacute", "Icircumflex", "Idieresis", "Igrave", "Oacute", "Ocircumflex", "apple", "Ograve", "Uacute", "Ucircumflex", "Ugrave", "dotlessi", "circumflex", "tilde", "macron", "breve", "dotaccent", "ring", "cedilla", "hungarumlaut", "ogonek", "caron", "Lslash", "lslash", "Scaron", "scaron", "Zcaron", "zcaron", "brokenbar", "Eth", "eth", "Yacute", "yacute", "Thorn", "thorn", "minus", "multiply", "onesuperior", "twosuperior", "threesuperior", "onehalf", "onequarter", "threequarters", "franc", "Gbreve", "gbreve", "Idotaccent", "Scedilla", "scedilla", "Cacute", "cacute", "Ccaron", "ccaron", "dcroat"];
function recoverGlyphName(name, glyphsUnicodeMap) {
if (glyphsUnicodeMap[name] !== undefined) {
return name;
}
const unicode = getUnicodeForGlyph(name, glyphsUnicodeMap);
if (unicode !== -1) {
for (const key in glyphsUnicodeMap) {
if (glyphsUnicodeMap[key] === unicode) {
return key;
}
}
}
info("Unable to recover a standard glyph name for: " + name);
return name;
}
function type1FontGlyphMapping(properties, builtInEncoding, glyphNames) {
const charCodeToGlyphId = Object.create(null);
let glyphId, charCode, baseEncoding;
const isSymbolicFont = !!(properties.flags & FontFlags.Symbolic);
if (properties.isInternalFont) {
baseEncoding = builtInEncoding;
for (charCode = 0; charCode < baseEncoding.length; charCode++) {
glyphId = glyphNames.indexOf(baseEncoding[charCode]);
charCodeToGlyphId[charCode] = glyphId >= 0 ? glyphId : 0;
}
} else if (properties.baseEncodingName) {
baseEncoding = getEncoding(properties.baseEncodingName);
for (charCode = 0; charCode < baseEncoding.length; charCode++) {
glyphId = glyphNames.indexOf(baseEncoding[charCode]);
charCodeToGlyphId[charCode] = glyphId >= 0 ? glyphId : 0;
}
} else if (isSymbolicFont) {
for (charCode in builtInEncoding) {
charCodeToGlyphId[charCode] = builtInEncoding[charCode];
}
} else {
baseEncoding = StandardEncoding;
for (charCode = 0; charCode < baseEncoding.length; charCode++) {
glyphId = glyphNames.indexOf(baseEncoding[charCode]);
charCodeToGlyphId[charCode] = glyphId >= 0 ? glyphId : 0;
}
}
const differences = properties.differences;
let glyphsUnicodeMap;
if (differences) {
for (charCode in differences) {
const glyphName = differences[charCode];
glyphId = glyphNames.indexOf(glyphName);
if (glyphId === -1) {
if (!glyphsUnicodeMap) {
glyphsUnicodeMap = getGlyphsUnicode();
}
const standardGlyphName = recoverGlyphName(glyphName, glyphsUnicodeMap);
if (standardGlyphName !== glyphName) {
glyphId = glyphNames.indexOf(standardGlyphName);
}
}
charCodeToGlyphId[charCode] = glyphId >= 0 ? glyphId : 0;
}
}
return charCodeToGlyphId;
}
function normalizeFontName(name) {
return name.replaceAll(/[,_]/g, "-").replaceAll(/\s/g, "");
}
const getVerticalPresentationForm = getLookupTableFactory(t => {
t[0x2013] = 0xfe32;
t[0x2014] = 0xfe31;
t[0x2025] = 0xfe30;
t[0x2026] = 0xfe19;
t[0x3001] = 0xfe11;
t[0x3002] = 0xfe12;
t[0x3008] = 0xfe3f;
t[0x3009] = 0xfe40;
t[0x300a] = 0xfe3d;
t[0x300b] = 0xfe3e;
t[0x300c] = 0xfe41;
t[0x300d] = 0xfe42;
t[0x300e] = 0xfe43;
t[0x300f] = 0xfe44;
t[0x3010] = 0xfe3b;
t[0x3011] = 0xfe3c;
t[0x3014] = 0xfe39;
t[0x3015] = 0xfe3a;
t[0x3016] = 0xfe17;
t[0x3017] = 0xfe18;
t[0xfe4f] = 0xfe34;
t[0xff01] = 0xfe15;
t[0xff08] = 0xfe35;
t[0xff09] = 0xfe36;
t[0xff0c] = 0xfe10;
t[0xff1a] = 0xfe13;
t[0xff1b] = 0xfe14;
t[0xff1f] = 0xfe16;
t[0xff3b] = 0xfe47;
t[0xff3d] = 0xfe48;
t[0xff3f] = 0xfe33;
t[0xff5b] = 0xfe37;
t[0xff5d] = 0xfe38;
});
;// ./src/core/standard_fonts.js
const getStdFontMap = getLookupTableFactory(function (t) {
t["Times-Roman"] = "Times-Roman";
t.Helvetica = "Helvetica";
t.Courier = "Courier";
t.Symbol = "Symbol";
t["Times-Bold"] = "Times-Bold";
t["Helvetica-Bold"] = "Helvetica-Bold";
t["Courier-Bold"] = "Courier-Bold";
t.ZapfDingbats = "ZapfDingbats";
t["Times-Italic"] = "Times-Italic";
t["Helvetica-Oblique"] = "Helvetica-Oblique";
t["Courier-Oblique"] = "Courier-Oblique";
t["Times-BoldItalic"] = "Times-BoldItalic";
t["Helvetica-BoldOblique"] = "Helvetica-BoldOblique";
t["Courier-BoldOblique"] = "Courier-BoldOblique";
t.ArialNarrow = "Helvetica";
t["ArialNarrow-Bold"] = "Helvetica-Bold";
t["ArialNarrow-BoldItalic"] = "Helvetica-BoldOblique";
t["ArialNarrow-Italic"] = "Helvetica-Oblique";
t.ArialBlack = "Helvetica";
t["ArialBlack-Bold"] = "Helvetica-Bold";
t["ArialBlack-BoldItalic"] = "Helvetica-BoldOblique";
t["ArialBlack-Italic"] = "Helvetica-Oblique";
t["Arial-Black"] = "Helvetica";
t["Arial-Black-Bold"] = "Helvetica-Bold";
t["Arial-Black-BoldItalic"] = "Helvetica-BoldOblique";
t["Arial-Black-Italic"] = "Helvetica-Oblique";
t.Arial = "Helvetica";
t["Arial-Bold"] = "Helvetica-Bold";
t["Arial-BoldItalic"] = "Helvetica-BoldOblique";
t["Arial-Italic"] = "Helvetica-Oblique";
t.ArialMT = "Helvetica";
t["Arial-BoldItalicMT"] = "Helvetica-BoldOblique";
t["Arial-BoldMT"] = "Helvetica-Bold";
t["Arial-ItalicMT"] = "Helvetica-Oblique";
t["Arial-BoldItalicMT-BoldItalic"] = "Helvetica-BoldOblique";
t["Arial-BoldMT-Bold"] = "Helvetica-Bold";
t["Arial-ItalicMT-Italic"] = "Helvetica-Oblique";
t.ArialUnicodeMS = "Helvetica";
t["ArialUnicodeMS-Bold"] = "Helvetica-Bold";
t["ArialUnicodeMS-BoldItalic"] = "Helvetica-BoldOblique";
t["ArialUnicodeMS-Italic"] = "Helvetica-Oblique";
t["Courier-BoldItalic"] = "Courier-BoldOblique";
t["Courier-Italic"] = "Courier-Oblique";
t.CourierNew = "Courier";
t["CourierNew-Bold"] = "Courier-Bold";
t["CourierNew-BoldItalic"] = "Courier-BoldOblique";
t["CourierNew-Italic"] = "Courier-Oblique";
t["CourierNewPS-BoldItalicMT"] = "Courier-BoldOblique";
t["CourierNewPS-BoldMT"] = "Courier-Bold";
t["CourierNewPS-ItalicMT"] = "Courier-Oblique";
t.CourierNewPSMT = "Courier";
t["Helvetica-BoldItalic"] = "Helvetica-BoldOblique";
t["Helvetica-Italic"] = "Helvetica-Oblique";
t["HelveticaLTStd-Bold"] = "Helvetica-Bold";
t["Symbol-Bold"] = "Symbol";
t["Symbol-BoldItalic"] = "Symbol";
t["Symbol-Italic"] = "Symbol";
t.TimesNewRoman = "Times-Roman";
t["TimesNewRoman-Bold"] = "Times-Bold";
t["TimesNewRoman-BoldItalic"] = "Times-BoldItalic";
t["TimesNewRoman-Italic"] = "Times-Italic";
t.TimesNewRomanPS = "Times-Roman";
t["TimesNewRomanPS-Bold"] = "Times-Bold";
t["TimesNewRomanPS-BoldItalic"] = "Times-BoldItalic";
t["TimesNewRomanPS-BoldItalicMT"] = "Times-BoldItalic";
t["TimesNewRomanPS-BoldMT"] = "Times-Bold";
t["TimesNewRomanPS-Italic"] = "Times-Italic";
t["TimesNewRomanPS-ItalicMT"] = "Times-Italic";
t.TimesNewRomanPSMT = "Times-Roman";
t["TimesNewRomanPSMT-Bold"] = "Times-Bold";
t["TimesNewRomanPSMT-BoldItalic"] = "Times-BoldItalic";
t["TimesNewRomanPSMT-Italic"] = "Times-Italic";
});
const getFontNameToFileMap = getLookupTableFactory(function (t) {
t.Courier = "FoxitFixed.pfb";
t["Courier-Bold"] = "FoxitFixedBold.pfb";
t["Courier-BoldOblique"] = "FoxitFixedBoldItalic.pfb";
t["Courier-Oblique"] = "FoxitFixedItalic.pfb";
t.Helvetica = "LiberationSans-Regular.ttf";
t["Helvetica-Bold"] = "LiberationSans-Bold.ttf";
t["Helvetica-BoldOblique"] = "LiberationSans-BoldItalic.ttf";
t["Helvetica-Oblique"] = "LiberationSans-Italic.ttf";
t["Times-Roman"] = "FoxitSerif.pfb";
t["Times-Bold"] = "FoxitSerifBold.pfb";
t["Times-BoldItalic"] = "FoxitSerifBoldItalic.pfb";
t["Times-Italic"] = "FoxitSerifItalic.pfb";
t.Symbol = "FoxitSymbol.pfb";
t.ZapfDingbats = "FoxitDingbats.pfb";
t["LiberationSans-Regular"] = "LiberationSans-Regular.ttf";
t["LiberationSans-Bold"] = "LiberationSans-Bold.ttf";
t["LiberationSans-Italic"] = "LiberationSans-Italic.ttf";
t["LiberationSans-BoldItalic"] = "LiberationSans-BoldItalic.ttf";
});
const getNonStdFontMap = getLookupTableFactory(function (t) {
t.Calibri = "Helvetica";
t["Calibri-Bold"] = "Helvetica-Bold";
t["Calibri-BoldItalic"] = "Helvetica-BoldOblique";
t["Calibri-Italic"] = "Helvetica-Oblique";
t.CenturyGothic = "Helvetica";
t["CenturyGothic-Bold"] = "Helvetica-Bold";
t["CenturyGothic-BoldItalic"] = "Helvetica-BoldOblique";
t["CenturyGothic-Italic"] = "Helvetica-Oblique";
t.ComicSansMS = "Comic Sans MS";
t["ComicSansMS-Bold"] = "Comic Sans MS-Bold";
t["ComicSansMS-BoldItalic"] = "Comic Sans MS-BoldItalic";
t["ComicSansMS-Italic"] = "Comic Sans MS-Italic";
t.GillSansMT = "Helvetica";
t["GillSansMT-Bold"] = "Helvetica-Bold";
t["GillSansMT-BoldItalic"] = "Helvetica-BoldOblique";
t["GillSansMT-Italic"] = "Helvetica-Oblique";
t.Impact = "Helvetica";
t["ItcSymbol-Bold"] = "Helvetica-Bold";
t["ItcSymbol-BoldItalic"] = "Helvetica-BoldOblique";
t["ItcSymbol-Book"] = "Helvetica";
t["ItcSymbol-BookItalic"] = "Helvetica-Oblique";
t["ItcSymbol-Medium"] = "Helvetica";
t["ItcSymbol-MediumItalic"] = "Helvetica-Oblique";
t.LucidaConsole = "Courier";
t["LucidaConsole-Bold"] = "Courier-Bold";
t["LucidaConsole-BoldItalic"] = "Courier-BoldOblique";
t["LucidaConsole-Italic"] = "Courier-Oblique";
t["LucidaSans-Demi"] = "Helvetica-Bold";
t["MS-Gothic"] = "MS Gothic";
t["MS-Gothic-Bold"] = "MS Gothic-Bold";
t["MS-Gothic-BoldItalic"] = "MS Gothic-BoldItalic";
t["MS-Gothic-Italic"] = "MS Gothic-Italic";
t["MS-Mincho"] = "MS Mincho";
t["MS-Mincho-Bold"] = "MS Mincho-Bold";
t["MS-Mincho-BoldItalic"] = "MS Mincho-BoldItalic";
t["MS-Mincho-Italic"] = "MS Mincho-Italic";
t["MS-PGothic"] = "MS PGothic";
t["MS-PGothic-Bold"] = "MS PGothic-Bold";
t["MS-PGothic-BoldItalic"] = "MS PGothic-BoldItalic";
t["MS-PGothic-Italic"] = "MS PGothic-Italic";
t["MS-PMincho"] = "MS PMincho";
t["MS-PMincho-Bold"] = "MS PMincho-Bold";
t["MS-PMincho-BoldItalic"] = "MS PMincho-BoldItalic";
t["MS-PMincho-Italic"] = "MS PMincho-Italic";
t.NuptialScript = "Times-Italic";
t.SegoeUISymbol = "Helvetica";
});
const getSerifFonts = getLookupTableFactory(function (t) {
t["Adobe Jenson"] = true;
t["Adobe Text"] = true;
t.Albertus = true;
t.Aldus = true;
t.Alexandria = true;
t.Algerian = true;
t["American Typewriter"] = true;
t.Antiqua = true;
t.Apex = true;
t.Arno = true;
t.Aster = true;
t.Aurora = true;
t.Baskerville = true;
t.Bell = true;
t.Bembo = true;
t["Bembo Schoolbook"] = true;
t.Benguiat = true;
t["Berkeley Old Style"] = true;
t["Bernhard Modern"] = true;
t["Berthold City"] = true;
t.Bodoni = true;
t["Bauer Bodoni"] = true;
t["Book Antiqua"] = true;
t.Bookman = true;
t["Bordeaux Roman"] = true;
t["Californian FB"] = true;
t.Calisto = true;
t.Calvert = true;
t.Capitals = true;
t.Cambria = true;
t.Cartier = true;
t.Caslon = true;
t.Catull = true;
t.Centaur = true;
t["Century Old Style"] = true;
t["Century Schoolbook"] = true;
t.Chaparral = true;
t["Charis SIL"] = true;
t.Cheltenham = true;
t["Cholla Slab"] = true;
t.Clarendon = true;
t.Clearface = true;
t.Cochin = true;
t.Colonna = true;
t["Computer Modern"] = true;
t["Concrete Roman"] = true;
t.Constantia = true;
t["Cooper Black"] = true;
t.Corona = true;
t.Ecotype = true;
t.Egyptienne = true;
t.Elephant = true;
t.Excelsior = true;
t.Fairfield = true;
t["FF Scala"] = true;
t.Folkard = true;
t.Footlight = true;
t.FreeSerif = true;
t["Friz Quadrata"] = true;
t.Garamond = true;
t.Gentium = true;
t.Georgia = true;
t.Gloucester = true;
t["Goudy Old Style"] = true;
t["Goudy Schoolbook"] = true;
t["Goudy Pro Font"] = true;
t.Granjon = true;
t["Guardian Egyptian"] = true;
t.Heather = true;
t.Hercules = true;
t["High Tower Text"] = true;
t.Hiroshige = true;
t["Hoefler Text"] = true;
t["Humana Serif"] = true;
t.Imprint = true;
t["Ionic No. 5"] = true;
t.Janson = true;
t.Joanna = true;
t.Korinna = true;
t.Lexicon = true;
t.LiberationSerif = true;
t["Liberation Serif"] = true;
t["Linux Libertine"] = true;
t.Literaturnaya = true;
t.Lucida = true;
t["Lucida Bright"] = true;
t.Melior = true;
t.Memphis = true;
t.Miller = true;
t.Minion = true;
t.Modern = true;
t["Mona Lisa"] = true;
t["Mrs Eaves"] = true;
t["MS Serif"] = true;
t["Museo Slab"] = true;
t["New York"] = true;
t["Nimbus Roman"] = true;
t["NPS Rawlinson Roadway"] = true;
t.NuptialScript = true;
t.Palatino = true;
t.Perpetua = true;
t.Plantin = true;
t["Plantin Schoolbook"] = true;
t.Playbill = true;
t["Poor Richard"] = true;
t["Rawlinson Roadway"] = true;
t.Renault = true;
t.Requiem = true;
t.Rockwell = true;
t.Roman = true;
t["Rotis Serif"] = true;
t.Sabon = true;
t.Scala = true;
t.Seagull = true;
t.Sistina = true;
t.Souvenir = true;
t.STIX = true;
t["Stone Informal"] = true;
t["Stone Serif"] = true;
t.Sylfaen = true;
t.Times = true;
t.Trajan = true;
t["Trinité"] = true;
t["Trump Mediaeval"] = true;
t.Utopia = true;
t["Vale Type"] = true;
t["Bitstream Vera"] = true;
t["Vera Serif"] = true;
t.Versailles = true;
t.Wanted = true;
t.Weiss = true;
t["Wide Latin"] = true;
t.Windsor = true;
t.XITS = true;
});
const getSymbolsFonts = getLookupTableFactory(function (t) {
t.Dingbats = true;
t.Symbol = true;
t.ZapfDingbats = true;
t.Wingdings = true;
t["Wingdings-Bold"] = true;
t["Wingdings-Regular"] = true;
});
const getGlyphMapForStandardFonts = getLookupTableFactory(function (t) {
t[2] = 10;
t[3] = 32;
t[4] = 33;
t[5] = 34;
t[6] = 35;
t[7] = 36;
t[8] = 37;
t[9] = 38;
t[10] = 39;
t[11] = 40;
t[12] = 41;
t[13] = 42;
t[14] = 43;
t[15] = 44;
t[16] = 45;
t[17] = 46;
t[18] = 47;
t[19] = 48;
t[20] = 49;
t[21] = 50;
t[22] = 51;
t[23] = 52;
t[24] = 53;
t[25] = 54;
t[26] = 55;
t[27] = 56;
t[28] = 57;
t[29] = 58;
t[30] = 894;
t[31] = 60;
t[32] = 61;
t[33] = 62;
t[34] = 63;
t[35] = 64;
t[36] = 65;
t[37] = 66;
t[38] = 67;
t[39] = 68;
t[40] = 69;
t[41] = 70;
t[42] = 71;
t[43] = 72;
t[44] = 73;
t[45] = 74;
t[46] = 75;
t[47] = 76;
t[48] = 77;
t[49] = 78;
t[50] = 79;
t[51] = 80;
t[52] = 81;
t[53] = 82;
t[54] = 83;
t[55] = 84;
t[56] = 85;
t[57] = 86;
t[58] = 87;
t[59] = 88;
t[60] = 89;
t[61] = 90;
t[62] = 91;
t[63] = 92;
t[64] = 93;
t[65] = 94;
t[66] = 95;
t[67] = 96;
t[68] = 97;
t[69] = 98;
t[70] = 99;
t[71] = 100;
t[72] = 101;
t[73] = 102;
t[74] = 103;
t[75] = 104;
t[76] = 105;
t[77] = 106;
t[78] = 107;
t[79] = 108;
t[80] = 109;
t[81] = 110;
t[82] = 111;
t[83] = 112;
t[84] = 113;
t[85] = 114;
t[86] = 115;
t[87] = 116;
t[88] = 117;
t[89] = 118;
t[90] = 119;
t[91] = 120;
t[92] = 121;
t[93] = 122;
t[94] = 123;
t[95] = 124;
t[96] = 125;
t[97] = 126;
t[98] = 196;
t[99] = 197;
t[100] = 199;
t[101] = 201;
t[102] = 209;
t[103] = 214;
t[104] = 220;
t[105] = 225;
t[106] = 224;
t[107] = 226;
t[108] = 228;
t[109] = 227;
t[110] = 229;
t[111] = 231;
t[112] = 233;
t[113] = 232;
t[114] = 234;
t[115] = 235;
t[116] = 237;
t[117] = 236;
t[118] = 238;
t[119] = 239;
t[120] = 241;
t[121] = 243;
t[122] = 242;
t[123] = 244;
t[124] = 246;
t[125] = 245;
t[126] = 250;
t[127] = 249;
t[128] = 251;
t[129] = 252;
t[130] = 8224;
t[131] = 176;
t[132] = 162;
t[133] = 163;
t[134] = 167;
t[135] = 8226;
t[136] = 182;
t[137] = 223;
t[138] = 174;
t[139] = 169;
t[140] = 8482;
t[141] = 180;
t[142] = 168;
t[143] = 8800;
t[144] = 198;
t[145] = 216;
t[146] = 8734;
t[147] = 177;
t[148] = 8804;
t[149] = 8805;
t[150] = 165;
t[151] = 181;
t[152] = 8706;
t[153] = 8721;
t[154] = 8719;
t[156] = 8747;
t[157] = 170;
t[158] = 186;
t[159] = 8486;
t[160] = 230;
t[161] = 248;
t[162] = 191;
t[163] = 161;
t[164] = 172;
t[165] = 8730;
t[166] = 402;
t[167] = 8776;
t[168] = 8710;
t[169] = 171;
t[170] = 187;
t[171] = 8230;
t[179] = 8220;
t[180] = 8221;
t[181] = 8216;
t[182] = 8217;
t[200] = 193;
t[203] = 205;
t[207] = 211;
t[210] = 218;
t[223] = 711;
t[224] = 321;
t[225] = 322;
t[226] = 352;
t[227] = 353;
t[228] = 381;
t[229] = 382;
t[233] = 221;
t[234] = 253;
t[252] = 263;
t[253] = 268;
t[254] = 269;
t[258] = 258;
t[260] = 260;
t[261] = 261;
t[265] = 280;
t[266] = 281;
t[267] = 282;
t[268] = 283;
t[269] = 313;
t[275] = 323;
t[276] = 324;
t[278] = 328;
t[283] = 344;
t[284] = 345;
t[285] = 346;
t[286] = 347;
t[292] = 367;
t[295] = 377;
t[296] = 378;
t[298] = 380;
t[305] = 963;
t[306] = 964;
t[307] = 966;
t[308] = 8215;
t[309] = 8252;
t[310] = 8319;
t[311] = 8359;
t[312] = 8592;
t[313] = 8593;
t[337] = 9552;
t[493] = 1039;
t[494] = 1040;
t[570] = 1040;
t[571] = 1041;
t[572] = 1042;
t[573] = 1043;
t[574] = 1044;
t[575] = 1045;
t[576] = 1046;
t[577] = 1047;
t[578] = 1048;
t[579] = 1049;
t[580] = 1050;
t[581] = 1051;
t[582] = 1052;
t[583] = 1053;
t[584] = 1054;
t[585] = 1055;
t[586] = 1056;
t[587] = 1057;
t[588] = 1058;
t[589] = 1059;
t[590] = 1060;
t[591] = 1061;
t[592] = 1062;
t[593] = 1063;
t[594] = 1064;
t[595] = 1065;
t[596] = 1066;
t[597] = 1067;
t[598] = 1068;
t[599] = 1069;
t[600] = 1070;
t[672] = 1488;
t[673] = 1489;
t[674] = 1490;
t[675] = 1491;
t[676] = 1492;
t[677] = 1493;
t[678] = 1494;
t[679] = 1495;
t[680] = 1496;
t[681] = 1497;
t[682] = 1498;
t[683] = 1499;
t[684] = 1500;
t[685] = 1501;
t[686] = 1502;
t[687] = 1503;
t[688] = 1504;
t[689] = 1505;
t[690] = 1506;
t[691] = 1507;
t[692] = 1508;
t[693] = 1509;
t[694] = 1510;
t[695] = 1511;
t[696] = 1512;
t[697] = 1513;
t[698] = 1514;
t[705] = 1524;
t[706] = 8362;
t[710] = 64288;
t[711] = 64298;
t[759] = 1617;
t[761] = 1776;
t[763] = 1778;
t[775] = 1652;
t[777] = 1764;
t[778] = 1780;
t[779] = 1781;
t[780] = 1782;
t[782] = 771;
t[783] = 64726;
t[786] = 8363;
t[788] = 8532;
t[790] = 768;
t[791] = 769;
t[792] = 768;
t[795] = 803;
t[797] = 64336;
t[798] = 64337;
t[799] = 64342;
t[800] = 64343;
t[801] = 64344;
t[802] = 64345;
t[803] = 64362;
t[804] = 64363;
t[805] = 64364;
t[2424] = 7821;
t[2425] = 7822;
t[2426] = 7823;
t[2427] = 7824;
t[2428] = 7825;
t[2429] = 7826;
t[2430] = 7827;
t[2433] = 7682;
t[2678] = 8045;
t[2679] = 8046;
t[2830] = 1552;
t[2838] = 686;
t[2840] = 751;
t[2842] = 753;
t[2843] = 754;
t[2844] = 755;
t[2846] = 757;
t[2856] = 767;
t[2857] = 848;
t[2858] = 849;
t[2862] = 853;
t[2863] = 854;
t[2864] = 855;
t[2865] = 861;
t[2866] = 862;
t[2906] = 7460;
t[2908] = 7462;
t[2909] = 7463;
t[2910] = 7464;
t[2912] = 7466;
t[2913] = 7467;
t[2914] = 7468;
t[2916] = 7470;
t[2917] = 7471;
t[2918] = 7472;
t[2920] = 7474;
t[2921] = 7475;
t[2922] = 7476;
t[2924] = 7478;
t[2925] = 7479;
t[2926] = 7480;
t[2928] = 7482;
t[2929] = 7483;
t[2930] = 7484;
t[2932] = 7486;
t[2933] = 7487;
t[2934] = 7488;
t[2936] = 7490;
t[2937] = 7491;
t[2938] = 7492;
t[2940] = 7494;
t[2941] = 7495;
t[2942] = 7496;
t[2944] = 7498;
t[2946] = 7500;
t[2948] = 7502;
t[2950] = 7504;
t[2951] = 7505;
t[2952] = 7506;
t[2954] = 7508;
t[2955] = 7509;
t[2956] = 7510;
t[2958] = 7512;
t[2959] = 7513;
t[2960] = 7514;
t[2962] = 7516;
t[2963] = 7517;
t[2964] = 7518;
t[2966] = 7520;
t[2967] = 7521;
t[2968] = 7522;
t[2970] = 7524;
t[2971] = 7525;
t[2972] = 7526;
t[2974] = 7528;
t[2975] = 7529;
t[2976] = 7530;
t[2978] = 1537;
t[2979] = 1538;
t[2980] = 1539;
t[2982] = 1549;
t[2983] = 1551;
t[2984] = 1552;
t[2986] = 1554;
t[2987] = 1555;
t[2988] = 1556;
t[2990] = 1623;
t[2991] = 1624;
t[2995] = 1775;
t[2999] = 1791;
t[3002] = 64290;
t[3003] = 64291;
t[3004] = 64292;
t[3006] = 64294;
t[3007] = 64295;
t[3008] = 64296;
t[3011] = 1900;
t[3014] = 8223;
t[3015] = 8244;
t[3017] = 7532;
t[3018] = 7533;
t[3019] = 7534;
t[3075] = 7590;
t[3076] = 7591;
t[3079] = 7594;
t[3080] = 7595;
t[3083] = 7598;
t[3084] = 7599;
t[3087] = 7602;
t[3088] = 7603;
t[3091] = 7606;
t[3092] = 7607;
t[3095] = 7610;
t[3096] = 7611;
t[3099] = 7614;
t[3100] = 7615;
t[3103] = 7618;
t[3104] = 7619;
t[3107] = 8337;
t[3108] = 8338;
t[3116] = 1884;
t[3119] = 1885;
t[3120] = 1885;
t[3123] = 1886;
t[3124] = 1886;
t[3127] = 1887;
t[3128] = 1887;
t[3131] = 1888;
t[3132] = 1888;
t[3135] = 1889;
t[3136] = 1889;
t[3139] = 1890;
t[3140] = 1890;
t[3143] = 1891;
t[3144] = 1891;
t[3147] = 1892;
t[3148] = 1892;
t[3153] = 580;
t[3154] = 581;
t[3157] = 584;
t[3158] = 585;
t[3161] = 588;
t[3162] = 589;
t[3165] = 891;
t[3166] = 892;
t[3169] = 1274;
t[3170] = 1275;
t[3173] = 1278;
t[3174] = 1279;
t[3181] = 7622;
t[3182] = 7623;
t[3282] = 11799;
t[3316] = 578;
t[3379] = 42785;
t[3393] = 1159;
t[3416] = 8377;
});
const getSupplementalGlyphMapForArialBlack = getLookupTableFactory(function (t) {
t[227] = 322;
t[264] = 261;
t[291] = 346;
});
const getSupplementalGlyphMapForCalibri = getLookupTableFactory(function (t) {
t[1] = 32;
t[4] = 65;
t[5] = 192;
t[6] = 193;
t[9] = 196;
t[17] = 66;
t[18] = 67;
t[21] = 268;
t[24] = 68;
t[28] = 69;
t[29] = 200;
t[30] = 201;
t[32] = 282;
t[38] = 70;
t[39] = 71;
t[44] = 72;
t[47] = 73;
t[48] = 204;
t[49] = 205;
t[58] = 74;
t[60] = 75;
t[62] = 76;
t[68] = 77;
t[69] = 78;
t[75] = 79;
t[76] = 210;
t[80] = 214;
t[87] = 80;
t[89] = 81;
t[90] = 82;
t[92] = 344;
t[94] = 83;
t[97] = 352;
t[100] = 84;
t[104] = 85;
t[109] = 220;
t[115] = 86;
t[116] = 87;
t[121] = 88;
t[122] = 89;
t[124] = 221;
t[127] = 90;
t[129] = 381;
t[258] = 97;
t[259] = 224;
t[260] = 225;
t[263] = 228;
t[268] = 261;
t[271] = 98;
t[272] = 99;
t[273] = 263;
t[275] = 269;
t[282] = 100;
t[286] = 101;
t[287] = 232;
t[288] = 233;
t[290] = 283;
t[295] = 281;
t[296] = 102;
t[336] = 103;
t[346] = 104;
t[349] = 105;
t[350] = 236;
t[351] = 237;
t[361] = 106;
t[364] = 107;
t[367] = 108;
t[371] = 322;
t[373] = 109;
t[374] = 110;
t[381] = 111;
t[382] = 242;
t[383] = 243;
t[386] = 246;
t[393] = 112;
t[395] = 113;
t[396] = 114;
t[398] = 345;
t[400] = 115;
t[401] = 347;
t[403] = 353;
t[410] = 116;
t[437] = 117;
t[442] = 252;
t[448] = 118;
t[449] = 119;
t[454] = 120;
t[455] = 121;
t[457] = 253;
t[460] = 122;
t[462] = 382;
t[463] = 380;
t[853] = 44;
t[855] = 58;
t[856] = 46;
t[876] = 47;
t[878] = 45;
t[882] = 45;
t[894] = 40;
t[895] = 41;
t[896] = 91;
t[897] = 93;
t[923] = 64;
t[940] = 163;
t[1004] = 48;
t[1005] = 49;
t[1006] = 50;
t[1007] = 51;
t[1008] = 52;
t[1009] = 53;
t[1010] = 54;
t[1011] = 55;
t[1012] = 56;
t[1013] = 57;
t[1081] = 37;
t[1085] = 43;
t[1086] = 45;
});
function getStandardFontName(name) {
const fontName = normalizeFontName(name);
const stdFontMap = getStdFontMap();
return stdFontMap[fontName];
}
function isKnownFontName(name) {
const fontName = normalizeFontName(name);
return !!(getStdFontMap()[fontName] || getNonStdFontMap()[fontName] || getSerifFonts()[fontName] || getSymbolsFonts()[fontName]);
}
;// ./src/core/to_unicode_map.js
class ToUnicodeMap {
constructor(cmap = []) {
this._map = cmap;
}
get length() {
return this._map.length;
}
forEach(callback) {
for (const charCode in this._map) {
callback(charCode, this._map[charCode].codePointAt(0));
}
}
has(i) {
return this._map[i] !== undefined;
}
get(i) {
return this._map[i];
}
charCodeOf(value) {
const map = this._map;
if (map.length <= 0x10000) {
return map.indexOf(value);
}
for (const charCode in map) {
if (map[charCode] === value) {
return charCode | 0;
}
}
return -1;
}
amend(map) {
for (const charCode in map) {
this._map[charCode] = map[charCode];
}
}
}
class IdentityToUnicodeMap {
constructor(firstChar, lastChar) {
this.firstChar = firstChar;
this.lastChar = lastChar;
}
get length() {
return this.lastChar + 1 - this.firstChar;
}
forEach(callback) {
for (let i = this.firstChar, ii = this.lastChar; i <= ii; i++) {
callback(i, i);
}
}
has(i) {
return this.firstChar <= i && i <= this.lastChar;
}
get(i) {
if (this.firstChar <= i && i <= this.lastChar) {
return String.fromCharCode(i);
}
return undefined;
}
charCodeOf(v) {
return Number.isInteger(v) && v >= this.firstChar && v <= this.lastChar ? v : -1;
}
amend(map) {
unreachable("Should not call amend()");
}
}
;// ./src/core/cff_font.js
class CFFFont {
constructor(file, properties) {
this.properties = properties;
const parser = new CFFParser(file, properties, SEAC_ANALYSIS_ENABLED);
this.cff = parser.parse();
this.cff.duplicateFirstGlyph();
const compiler = new CFFCompiler(this.cff);
this.seacs = this.cff.seacs;
try {
this.data = compiler.compile();
} catch {
warn("Failed to compile font " + properties.loadedName);
this.data = file;
}
this._createBuiltInEncoding();
}
get numGlyphs() {
return this.cff.charStrings.count;
}
getCharset() {
return this.cff.charset.charset;
}
getGlyphMapping() {
const cff = this.cff;
const properties = this.properties;
const {
cidToGidMap,
cMap
} = properties;
const charsets = cff.charset.charset;
let charCodeToGlyphId;
let glyphId;
if (properties.composite) {
let invCidToGidMap;
if (cidToGidMap?.length > 0) {
invCidToGidMap = Object.create(null);
for (let i = 0, ii = cidToGidMap.length; i < ii; i++) {
const gid = cidToGidMap[i];
if (gid !== undefined) {
invCidToGidMap[gid] = i;
}
}
}
charCodeToGlyphId = Object.create(null);
let charCode;
if (cff.isCIDFont) {
for (glyphId = 0; glyphId < charsets.length; glyphId++) {
const cid = charsets[glyphId];
charCode = cMap.charCodeOf(cid);
if (invCidToGidMap?.[charCode] !== undefined) {
charCode = invCidToGidMap[charCode];
}
charCodeToGlyphId[charCode] = glyphId;
}
} else {
for (glyphId = 0; glyphId < cff.charStrings.count; glyphId++) {
charCode = cMap.charCodeOf(glyphId);
charCodeToGlyphId[charCode] = glyphId;
}
}
return charCodeToGlyphId;
}
let encoding = cff.encoding ? cff.encoding.encoding : null;
if (properties.isInternalFont) {
encoding = properties.defaultEncoding;
}
charCodeToGlyphId = type1FontGlyphMapping(properties, encoding, charsets);
return charCodeToGlyphId;
}
hasGlyphId(id) {
return this.cff.hasGlyphId(id);
}
_createBuiltInEncoding() {
const {
charset,
encoding
} = this.cff;
if (!charset || !encoding) {
return;
}
const charsets = charset.charset,
encodings = encoding.encoding;
const map = [];
for (const charCode in encodings) {
const glyphId = encodings[charCode];
if (glyphId >= 0) {
const glyphName = charsets[glyphId];
if (glyphName) {
map[charCode] = glyphName;
}
}
}
if (map.length > 0) {
this.properties.builtInEncoding = map;
}
}
}
;// ./src/core/font_renderer.js
function getFloat214(data, offset) {
return readInt16(data, offset) / 16384;
}
function getSubroutineBias(subrs) {
const numSubrs = subrs.length;
let bias = 32768;
if (numSubrs < 1240) {
bias = 107;
} else if (numSubrs < 33900) {
bias = 1131;
}
return bias;
}
function parseCmap(data, start, end) {
const offset = readUint16(data, start + 2) === 1 ? readUint32(data, start + 8) : readUint32(data, start + 16);
const format = readUint16(data, start + offset);
let ranges, p, i;
if (format === 4) {
readUint16(data, start + offset + 2);
const segCount = readUint16(data, start + offset + 6) >> 1;
p = start + offset + 14;
ranges = [];
for (i = 0; i < segCount; i++, p += 2) {
ranges[i] = {
end: readUint16(data, p)
};
}
p += 2;
for (i = 0; i < segCount; i++, p += 2) {
ranges[i].start = readUint16(data, p);
}
for (i = 0; i < segCount; i++, p += 2) {
ranges[i].idDelta = readUint16(data, p);
}
for (i = 0; i < segCount; i++, p += 2) {
let idOffset = readUint16(data, p);
if (idOffset === 0) {
continue;
}
ranges[i].ids = [];
for (let j = 0, jj = ranges[i].end - ranges[i].start + 1; j < jj; j++) {
ranges[i].ids[j] = readUint16(data, p + idOffset);
idOffset += 2;
}
}
return ranges;
} else if (format === 12) {
const groups = readUint32(data, start + offset + 12);
p = start + offset + 16;
ranges = [];
for (i = 0; i < groups; i++) {
start = readUint32(data, p);
ranges.push({
start,
end: readUint32(data, p + 4),
idDelta: readUint32(data, p + 8) - start
});
p += 12;
}
return ranges;
}
throw new FormatError(`unsupported cmap: ${format}`);
}
function parseCff(data, start, end, seacAnalysisEnabled) {
const properties = {};
const parser = new CFFParser(new Stream(data, start, end - start), properties, seacAnalysisEnabled);
const cff = parser.parse();
return {
glyphs: cff.charStrings.objects,
subrs: cff.topDict.privateDict?.subrsIndex?.objects,
gsubrs: cff.globalSubrIndex?.objects,
isCFFCIDFont: cff.isCIDFont,
fdSelect: cff.fdSelect,
fdArray: cff.fdArray
};
}
function parseGlyfTable(glyf, loca, isGlyphLocationsLong) {
let itemSize, itemDecode;
if (isGlyphLocationsLong) {
itemSize = 4;
itemDecode = readUint32;
} else {
itemSize = 2;
itemDecode = (data, offset) => 2 * readUint16(data, offset);
}
const glyphs = [];
let startOffset = itemDecode(loca, 0);
for (let j = itemSize; j < loca.length; j += itemSize) {
const endOffset = itemDecode(loca, j);
glyphs.push(glyf.subarray(startOffset, endOffset));
startOffset = endOffset;
}
return glyphs;
}
function lookupCmap(ranges, unicode) {
const code = unicode.codePointAt(0);
let gid = 0,
l = 0,
r = ranges.length - 1;
while (l < r) {
const c = l + r + 1 >> 1;
if (code < ranges[c].start) {
r = c - 1;
} else {
l = c;
}
}
if (ranges[l].start <= code && code <= ranges[l].end) {
gid = ranges[l].idDelta + (ranges[l].ids ? ranges[l].ids[code - ranges[l].start] : code) & 0xffff;
}
return {
charCode: code,
glyphId: gid
};
}
function compileGlyf(code, cmds, font) {
function moveTo(x, y) {
if (firstPoint) {
cmds.add("L", firstPoint);
}
firstPoint = [x, y];
cmds.add("M", [x, y]);
}
function lineTo(x, y) {
cmds.add("L", [x, y]);
}
function quadraticCurveTo(xa, ya, x, y) {
cmds.add("Q", [xa, ya, x, y]);
}
let i = 0;
const numberOfContours = readInt16(code, i);
let flags;
let firstPoint = null;
let x = 0,
y = 0;
i += 10;
if (numberOfContours < 0) {
do {
flags = readUint16(code, i);
const glyphIndex = readUint16(code, i + 2);
i += 4;
let arg1, arg2;
if (flags & 0x01) {
if (flags & 0x02) {
arg1 = readInt16(code, i);
arg2 = readInt16(code, i + 2);
} else {
arg1 = readUint16(code, i);
arg2 = readUint16(code, i + 2);
}
i += 4;
} else if (flags & 0x02) {
arg1 = readInt8(code, i++);
arg2 = readInt8(code, i++);
} else {
arg1 = code[i++];
arg2 = code[i++];
}
if (flags & 0x02) {
x = arg1;
y = arg2;
} else {
x = 0;
y = 0;
}
let scaleX = 1,
scaleY = 1,
scale01 = 0,
scale10 = 0;
if (flags & 0x08) {
scaleX = scaleY = getFloat214(code, i);
i += 2;
} else if (flags & 0x40) {
scaleX = getFloat214(code, i);
scaleY = getFloat214(code, i + 2);
i += 4;
} else if (flags & 0x80) {
scaleX = getFloat214(code, i);
scale01 = getFloat214(code, i + 2);
scale10 = getFloat214(code, i + 4);
scaleY = getFloat214(code, i + 6);
i += 8;
}
const subglyph = font.glyphs[glyphIndex];
if (subglyph) {
cmds.save();
cmds.transform([scaleX, scale01, scale10, scaleY, x, y]);
if (!(flags & 0x02)) {}
compileGlyf(subglyph, cmds, font);
cmds.restore();
}
} while (flags & 0x20);
} else {
const endPtsOfContours = [];
let j, jj;
for (j = 0; j < numberOfContours; j++) {
endPtsOfContours.push(readUint16(code, i));
i += 2;
}
const instructionLength = readUint16(code, i);
i += 2 + instructionLength;
const numberOfPoints = endPtsOfContours.at(-1) + 1;
const points = [];
while (points.length < numberOfPoints) {
flags = code[i++];
let repeat = 1;
if (flags & 0x08) {
repeat += code[i++];
}
while (repeat-- > 0) {
points.push({
flags
});
}
}
for (j = 0; j < numberOfPoints; j++) {
switch (points[j].flags & 0x12) {
case 0x00:
x += readInt16(code, i);
i += 2;
break;
case 0x02:
x -= code[i++];
break;
case 0x12:
x += code[i++];
break;
}
points[j].x = x;
}
for (j = 0; j < numberOfPoints; j++) {
switch (points[j].flags & 0x24) {
case 0x00:
y += readInt16(code, i);
i += 2;
break;
case 0x04:
y -= code[i++];
break;
case 0x24:
y += code[i++];
break;
}
points[j].y = y;
}
let startPoint = 0;
for (i = 0; i < numberOfContours; i++) {
const endPoint = endPtsOfContours[i];
const contour = points.slice(startPoint, endPoint + 1);
if (contour[0].flags & 1) {
contour.push(contour[0]);
} else if (contour.at(-1).flags & 1) {
contour.unshift(contour.at(-1));
} else {
const p = {
flags: 1,
x: (contour[0].x + contour.at(-1).x) / 2,
y: (contour[0].y + contour.at(-1).y) / 2
};
contour.unshift(p);
contour.push(p);
}
moveTo(contour[0].x, contour[0].y);
for (j = 1, jj = contour.length; j < jj; j++) {
if (contour[j].flags & 1) {
lineTo(contour[j].x, contour[j].y);
} else if (contour[j + 1].flags & 1) {
quadraticCurveTo(contour[j].x, contour[j].y, contour[j + 1].x, contour[j + 1].y);
j++;
} else {
quadraticCurveTo(contour[j].x, contour[j].y, (contour[j].x + contour[j + 1].x) / 2, (contour[j].y + contour[j + 1].y) / 2);
}
}
startPoint = endPoint + 1;
}
}
}
function compileCharString(charStringCode, cmds, font, glyphId) {
function moveTo(x, y) {
if (firstPoint) {
cmds.add("L", firstPoint);
}
firstPoint = [x, y];
cmds.add("M", [x, y]);
}
function lineTo(x, y) {
cmds.add("L", [x, y]);
}
function bezierCurveTo(x1, y1, x2, y2, x, y) {
cmds.add("C", [x1, y1, x2, y2, x, y]);
}
const stack = [];
let x = 0,
y = 0;
let stems = 0;
let firstPoint = null;
function parse(code) {
let i = 0;
while (i < code.length) {
let stackClean = false;
let v = code[i++];
let xa, xb, ya, yb, y1, y2, y3, n, subrCode;
switch (v) {
case 1:
stems += stack.length >> 1;
stackClean = true;
break;
case 3:
stems += stack.length >> 1;
stackClean = true;
break;
case 4:
y += stack.pop();
moveTo(x, y);
stackClean = true;
break;
case 5:
while (stack.length > 0) {
x += stack.shift();
y += stack.shift();
lineTo(x, y);
}
break;
case 6:
while (stack.length > 0) {
x += stack.shift();
lineTo(x, y);
if (stack.length === 0) {
break;
}
y += stack.shift();
lineTo(x, y);
}
break;
case 7:
while (stack.length > 0) {
y += stack.shift();
lineTo(x, y);
if (stack.length === 0) {
break;
}
x += stack.shift();
lineTo(x, y);
}
break;
case 8:
while (stack.length > 0) {
xa = x + stack.shift();
ya = y + stack.shift();
xb = xa + stack.shift();
yb = ya + stack.shift();
x = xb + stack.shift();
y = yb + stack.shift();
bezierCurveTo(xa, ya, xb, yb, x, y);
}
break;
case 10:
n = stack.pop();
subrCode = null;
if (font.isCFFCIDFont) {
const fdIndex = font.fdSelect.getFDIndex(glyphId);
if (fdIndex >= 0 && fdIndex < font.fdArray.length) {
const fontDict = font.fdArray[fdIndex];
let subrs;
if (fontDict.privateDict?.subrsIndex) {
subrs = fontDict.privateDict.subrsIndex.objects;
}
if (subrs) {
n += getSubroutineBias(subrs);
subrCode = subrs[n];
}
} else {
warn("Invalid fd index for glyph index.");
}
} else {
subrCode = font.subrs[n + font.subrsBias];
}
if (subrCode) {
parse(subrCode);
}
break;
case 11:
return;
case 12:
v = code[i++];
switch (v) {
case 34:
xa = x + stack.shift();
xb = xa + stack.shift();
y1 = y + stack.shift();
x = xb + stack.shift();
bezierCurveTo(xa, y, xb, y1, x, y1);
xa = x + stack.shift();
xb = xa + stack.shift();
x = xb + stack.shift();
bezierCurveTo(xa, y1, xb, y, x, y);
break;
case 35:
xa = x + stack.shift();
ya = y + stack.shift();
xb = xa + stack.shift();
yb = ya + stack.shift();
x = xb + stack.shift();
y = yb + stack.shift();
bezierCurveTo(xa, ya, xb, yb, x, y);
xa = x + stack.shift();
ya = y + stack.shift();
xb = xa + stack.shift();
yb = ya + stack.shift();
x = xb + stack.shift();
y = yb + stack.shift();
bezierCurveTo(xa, ya, xb, yb, x, y);
stack.pop();
break;
case 36:
xa = x + stack.shift();
y1 = y + stack.shift();
xb = xa + stack.shift();
y2 = y1 + stack.shift();
x = xb + stack.shift();
bezierCurveTo(xa, y1, xb, y2, x, y2);
xa = x + stack.shift();
xb = xa + stack.shift();
y3 = y2 + stack.shift();
x = xb + stack.shift();
bezierCurveTo(xa, y2, xb, y3, x, y);
break;
case 37:
const x0 = x,
y0 = y;
xa = x + stack.shift();
ya = y + stack.shift();
xb = xa + stack.shift();
yb = ya + stack.shift();
x = xb + stack.shift();
y = yb + stack.shift();
bezierCurveTo(xa, ya, xb, yb, x, y);
xa = x + stack.shift();
ya = y + stack.shift();
xb = xa + stack.shift();
yb = ya + stack.shift();
x = xb;
y = yb;
if (Math.abs(x - x0) > Math.abs(y - y0)) {
x += stack.shift();
} else {
y += stack.shift();
}
bezierCurveTo(xa, ya, xb, yb, x, y);
break;
default:
throw new FormatError(`unknown operator: 12 ${v}`);
}
break;
case 14:
if (stack.length >= 4) {
const achar = stack.pop();
const bchar = stack.pop();
y = stack.pop();
x = stack.pop();
cmds.save();
cmds.translate(x, y);
let cmap = lookupCmap(font.cmap, String.fromCharCode(font.glyphNameMap[StandardEncoding[achar]]));
compileCharString(font.glyphs[cmap.glyphId], cmds, font, cmap.glyphId);
cmds.restore();
cmap = lookupCmap(font.cmap, String.fromCharCode(font.glyphNameMap[StandardEncoding[bchar]]));
compileCharString(font.glyphs[cmap.glyphId], cmds, font, cmap.glyphId);
}
return;
case 18:
stems += stack.length >> 1;
stackClean = true;
break;
case 19:
stems += stack.length >> 1;
i += stems + 7 >> 3;
stackClean = true;
break;
case 20:
stems += stack.length >> 1;
i += stems + 7 >> 3;
stackClean = true;
break;
case 21:
y += stack.pop();
x += stack.pop();
moveTo(x, y);
stackClean = true;
break;
case 22:
x += stack.pop();
moveTo(x, y);
stackClean = true;
break;
case 23:
stems += stack.length >> 1;
stackClean = true;
break;
case 24:
while (stack.length > 2) {
xa = x + stack.shift();
ya = y + stack.shift();
xb = xa + stack.shift();
yb = ya + stack.shift();
x = xb + stack.shift();
y = yb + stack.shift();
bezierCurveTo(xa, ya, xb, yb, x, y);
}
x += stack.shift();
y += stack.shift();
lineTo(x, y);
break;
case 25:
while (stack.length > 6) {
x += stack.shift();
y += stack.shift();
lineTo(x, y);
}
xa = x + stack.shift();
ya = y + stack.shift();
xb = xa + stack.shift();
yb = ya + stack.shift();
x = xb + stack.shift();
y = yb + stack.shift();
bezierCurveTo(xa, ya, xb, yb, x, y);
break;
case 26:
if (stack.length % 2) {
x += stack.shift();
}
while (stack.length > 0) {
xa = x;
ya = y + stack.shift();
xb = xa + stack.shift();
yb = ya + stack.shift();
x = xb;
y = yb + stack.shift();
bezierCurveTo(xa, ya, xb, yb, x, y);
}
break;
case 27:
if (stack.length % 2) {
y += stack.shift();
}
while (stack.length > 0) {
xa = x + stack.shift();
ya = y;
xb = xa + stack.shift();
yb = ya + stack.shift();
x = xb + stack.shift();
y = yb;
bezierCurveTo(xa, ya, xb, yb, x, y);
}
break;
case 28:
stack.push(readInt16(code, i));
i += 2;
break;
case 29:
n = stack.pop() + font.gsubrsBias;
subrCode = font.gsubrs[n];
if (subrCode) {
parse(subrCode);
}
break;
case 30:
while (stack.length > 0) {
xa = x;
ya = y + stack.shift();
xb = xa + stack.shift();
yb = ya + stack.shift();
x = xb + stack.shift();
y = yb + (stack.length === 1 ? stack.shift() : 0);
bezierCurveTo(xa, ya, xb, yb, x, y);
if (stack.length === 0) {
break;
}
xa = x + stack.shift();
ya = y;
xb = xa + stack.shift();
yb = ya + stack.shift();
y = yb + stack.shift();
x = xb + (stack.length === 1 ? stack.shift() : 0);
bezierCurveTo(xa, ya, xb, yb, x, y);
}
break;
case 31:
while (stack.length > 0) {
xa = x + stack.shift();
ya = y;
xb = xa + stack.shift();
yb = ya + stack.shift();
y = yb + stack.shift();
x = xb + (stack.length === 1 ? stack.shift() : 0);
bezierCurveTo(xa, ya, xb, yb, x, y);
if (stack.length === 0) {
break;
}
xa = x;
ya = y + stack.shift();
xb = xa + stack.shift();
yb = ya + stack.shift();
x = xb + stack.shift();
y = yb + (stack.length === 1 ? stack.shift() : 0);
bezierCurveTo(xa, ya, xb, yb, x, y);
}
break;
default:
if (v < 32) {
throw new FormatError(`unknown operator: ${v}`);
}
if (v < 247) {
stack.push(v - 139);
} else if (v < 251) {
stack.push((v - 247) * 256 + code[i++] + 108);
} else if (v < 255) {
stack.push(-(v - 251) * 256 - code[i++] - 108);
} else {
stack.push((code[i] << 24 | code[i + 1] << 16 | code[i + 2] << 8 | code[i + 3]) / 65536);
i += 4;
}
break;
}
if (stackClean) {
stack.length = 0;
}
}
}
parse(charStringCode);
}
const NOOP = "";
class Commands {
cmds = [];
transformStack = [];
currentTransform = [1, 0, 0, 1, 0, 0];
add(cmd, args) {
if (args) {
const [a, b, c, d, e, f] = this.currentTransform;
for (let i = 0, ii = args.length; i < ii; i += 2) {
const x = args[i];
const y = args[i + 1];
args[i] = a * x + c * y + e;
args[i + 1] = b * x + d * y + f;
}
this.cmds.push(`${cmd}${args.join(" ")}`);
} else {
this.cmds.push(cmd);
}
}
transform(transf) {
this.currentTransform = Util.transform(this.currentTransform, transf);
}
translate(x, y) {
this.transform([1, 0, 0, 1, x, y]);
}
save() {
this.transformStack.push(this.currentTransform.slice());
}
restore() {
this.currentTransform = this.transformStack.pop() || [1, 0, 0, 1, 0, 0];
}
getSVG() {
return this.cmds.join("");
}
}
class CompiledFont {
constructor(fontMatrix) {
this.fontMatrix = fontMatrix;
this.compiledGlyphs = Object.create(null);
this.compiledCharCodeToGlyphId = Object.create(null);
}
getPathJs(unicode) {
const {
charCode,
glyphId
} = lookupCmap(this.cmap, unicode);
let fn = this.compiledGlyphs[glyphId],
compileEx;
if (fn === undefined) {
try {
fn = this.compileGlyph(this.glyphs[glyphId], glyphId);
} catch (ex) {
fn = NOOP;
compileEx = ex;
}
this.compiledGlyphs[glyphId] = fn;
}
this.compiledCharCodeToGlyphId[charCode] ??= glyphId;
if (compileEx) {
throw compileEx;
}
return fn;
}
compileGlyph(code, glyphId) {
if (!code?.length || code[0] === 14) {
return NOOP;
}
let fontMatrix = this.fontMatrix;
if (this.isCFFCIDFont) {
const fdIndex = this.fdSelect.getFDIndex(glyphId);
if (fdIndex >= 0 && fdIndex < this.fdArray.length) {
const fontDict = this.fdArray[fdIndex];
fontMatrix = fontDict.getByName("FontMatrix") || FONT_IDENTITY_MATRIX;
} else {
warn("Invalid fd index for glyph index.");
}
}
assert(isNumberArray(fontMatrix, 6), "Expected a valid fontMatrix.");
const cmds = new Commands();
cmds.transform(fontMatrix.slice());
this.compileGlyphImpl(code, cmds, glyphId);
cmds.add("Z");
return cmds.getSVG();
}
compileGlyphImpl() {
unreachable("Children classes should implement this.");
}
hasBuiltPath(unicode) {
const {
charCode,
glyphId
} = lookupCmap(this.cmap, unicode);
return this.compiledGlyphs[glyphId] !== undefined && this.compiledCharCodeToGlyphId[charCode] !== undefined;
}
}
class TrueTypeCompiled extends CompiledFont {
constructor(glyphs, cmap, fontMatrix) {
super(fontMatrix || [0.000488, 0, 0, 0.000488, 0, 0]);
this.glyphs = glyphs;
this.cmap = cmap;
}
compileGlyphImpl(code, cmds) {
compileGlyf(code, cmds, this);
}
}
class Type2Compiled extends CompiledFont {
constructor(cffInfo, cmap, fontMatrix) {
super(fontMatrix || [0.001, 0, 0, 0.001, 0, 0]);
this.glyphs = cffInfo.glyphs;
this.gsubrs = cffInfo.gsubrs || [];
this.subrs = cffInfo.subrs || [];
this.cmap = cmap;
this.glyphNameMap = getGlyphsUnicode();
this.gsubrsBias = getSubroutineBias(this.gsubrs);
this.subrsBias = getSubroutineBias(this.subrs);
this.isCFFCIDFont = cffInfo.isCFFCIDFont;
this.fdSelect = cffInfo.fdSelect;
this.fdArray = cffInfo.fdArray;
}
compileGlyphImpl(code, cmds, glyphId) {
compileCharString(code, cmds, this, glyphId);
}
}
class FontRendererFactory {
static create(font, seacAnalysisEnabled) {
const data = new Uint8Array(font.data);
let cmap, glyf, loca, cff, indexToLocFormat, unitsPerEm;
const numTables = readUint16(data, 4);
for (let i = 0, p = 12; i < numTables; i++, p += 16) {
const tag = bytesToString(data.subarray(p, p + 4));
const offset = readUint32(data, p + 8);
const length = readUint32(data, p + 12);
switch (tag) {
case "cmap":
cmap = parseCmap(data, offset, offset + length);
break;
case "glyf":
glyf = data.subarray(offset, offset + length);
break;
case "loca":
loca = data.subarray(offset, offset + length);
break;
case "head":
unitsPerEm = readUint16(data, offset + 18);
indexToLocFormat = readUint16(data, offset + 50);
break;
case "CFF ":
cff = parseCff(data, offset, offset + length, seacAnalysisEnabled);
break;
}
}
if (glyf) {
const fontMatrix = !unitsPerEm ? font.fontMatrix : [1 / unitsPerEm, 0, 0, 1 / unitsPerEm, 0, 0];
return new TrueTypeCompiled(parseGlyfTable(glyf, loca, indexToLocFormat), cmap, fontMatrix);
}
return new Type2Compiled(cff, cmap, font.fontMatrix);
}
}
;// ./src/core/metrics.js
const getMetrics = getLookupTableFactory(function (t) {
t.Courier = 600;
t["Courier-Bold"] = 600;
t["Courier-BoldOblique"] = 600;
t["Courier-Oblique"] = 600;
t.Helvetica = getLookupTableFactory(function (t) {
t.space = 278;
t.exclam = 278;
t.quotedbl = 355;
t.numbersign = 556;
t.dollar = 556;
t.percent = 889;
t.ampersand = 667;
t.quoteright = 222;
t.parenleft = 333;
t.parenright = 333;
t.asterisk = 389;
t.plus = 584;
t.comma = 278;
t.hyphen = 333;
t.period = 278;
t.slash = 278;
t.zero = 556;
t.one = 556;
t.two = 556;
t.three = 556;
t.four = 556;
t.five = 556;
t.six = 556;
t.seven = 556;
t.eight = 556;
t.nine = 556;
t.colon = 278;
t.semicolon = 278;
t.less = 584;
t.equal = 584;
t.greater = 584;
t.question = 556;
t.at = 1015;
t.A = 667;
t.B = 667;
t.C = 722;
t.D = 722;
t.E = 667;
t.F = 611;
t.G = 778;
t.H = 722;
t.I = 278;
t.J = 500;
t.K = 667;
t.L = 556;
t.M = 833;
t.N = 722;
t.O = 778;
t.P = 667;
t.Q = 778;
t.R = 722;
t.S = 667;
t.T = 611;
t.U = 722;
t.V = 667;
t.W = 944;
t.X = 667;
t.Y = 667;
t.Z = 611;
t.bracketleft = 278;
t.backslash = 278;
t.bracketright = 278;
t.asciicircum = 469;
t.underscore = 556;
t.quoteleft = 222;
t.a = 556;
t.b = 556;
t.c = 500;
t.d = 556;
t.e = 556;
t.f = 278;
t.g = 556;
t.h = 556;
t.i = 222;
t.j = 222;
t.k = 500;
t.l = 222;
t.m = 833;
t.n = 556;
t.o = 556;
t.p = 556;
t.q = 556;
t.r = 333;
t.s = 500;
t.t = 278;
t.u = 556;
t.v = 500;
t.w = 722;
t.x = 500;
t.y = 500;
t.z = 500;
t.braceleft = 334;
t.bar = 260;
t.braceright = 334;
t.asciitilde = 584;
t.exclamdown = 333;
t.cent = 556;
t.sterling = 556;
t.fraction = 167;
t.yen = 556;
t.florin = 556;
t.section = 556;
t.currency = 556;
t.quotesingle = 191;
t.quotedblleft = 333;
t.guillemotleft = 556;
t.guilsinglleft = 333;
t.guilsinglright = 333;
t.fi = 500;
t.fl = 500;
t.endash = 556;
t.dagger = 556;
t.daggerdbl = 556;
t.periodcentered = 278;
t.paragraph = 537;
t.bullet = 350;
t.quotesinglbase = 222;
t.quotedblbase = 333;
t.quotedblright = 333;
t.guillemotright = 556;
t.ellipsis = 1000;
t.perthousand = 1000;
t.questiondown = 611;
t.grave = 333;
t.acute = 333;
t.circumflex = 333;
t.tilde = 333;
t.macron = 333;
t.breve = 333;
t.dotaccent = 333;
t.dieresis = 333;
t.ring = 333;
t.cedilla = 333;
t.hungarumlaut = 333;
t.ogonek = 333;
t.caron = 333;
t.emdash = 1000;
t.AE = 1000;
t.ordfeminine = 370;
t.Lslash = 556;
t.Oslash = 778;
t.OE = 1000;
t.ordmasculine = 365;
t.ae = 889;
t.dotlessi = 278;
t.lslash = 222;
t.oslash = 611;
t.oe = 944;
t.germandbls = 611;
t.Idieresis = 278;
t.eacute = 556;
t.abreve = 556;
t.uhungarumlaut = 556;
t.ecaron = 556;
t.Ydieresis = 667;
t.divide = 584;
t.Yacute = 667;
t.Acircumflex = 667;
t.aacute = 556;
t.Ucircumflex = 722;
t.yacute = 500;
t.scommaaccent = 500;
t.ecircumflex = 556;
t.Uring = 722;
t.Udieresis = 722;
t.aogonek = 556;
t.Uacute = 722;
t.uogonek = 556;
t.Edieresis = 667;
t.Dcroat = 722;
t.commaaccent = 250;
t.copyright = 737;
t.Emacron = 667;
t.ccaron = 500;
t.aring = 556;
t.Ncommaaccent = 722;
t.lacute = 222;
t.agrave = 556;
t.Tcommaaccent = 611;
t.Cacute = 722;
t.atilde = 556;
t.Edotaccent = 667;
t.scaron = 500;
t.scedilla = 500;
t.iacute = 278;
t.lozenge = 471;
t.Rcaron = 722;
t.Gcommaaccent = 778;
t.ucircumflex = 556;
t.acircumflex = 556;
t.Amacron = 667;
t.rcaron = 333;
t.ccedilla = 500;
t.Zdotaccent = 611;
t.Thorn = 667;
t.Omacron = 778;
t.Racute = 722;
t.Sacute = 667;
t.dcaron = 643;
t.Umacron = 722;
t.uring = 556;
t.threesuperior = 333;
t.Ograve = 778;
t.Agrave = 667;
t.Abreve = 667;
t.multiply = 584;
t.uacute = 556;
t.Tcaron = 611;
t.partialdiff = 476;
t.ydieresis = 500;
t.Nacute = 722;
t.icircumflex = 278;
t.Ecircumflex = 667;
t.adieresis = 556;
t.edieresis = 556;
t.cacute = 500;
t.nacute = 556;
t.umacron = 556;
t.Ncaron = 722;
t.Iacute = 278;
t.plusminus = 584;
t.brokenbar = 260;
t.registered = 737;
t.Gbreve = 778;
t.Idotaccent = 278;
t.summation = 600;
t.Egrave = 667;
t.racute = 333;
t.omacron = 556;
t.Zacute = 611;
t.Zcaron = 611;
t.greaterequal = 549;
t.Eth = 722;
t.Ccedilla = 722;
t.lcommaaccent = 222;
t.tcaron = 317;
t.eogonek = 556;
t.Uogonek = 722;
t.Aacute = 667;
t.Adieresis = 667;
t.egrave = 556;
t.zacute = 500;
t.iogonek = 222;
t.Oacute = 778;
t.oacute = 556;
t.amacron = 556;
t.sacute = 500;
t.idieresis = 278;
t.Ocircumflex = 778;
t.Ugrave = 722;
t.Delta = 612;
t.thorn = 556;
t.twosuperior = 333;
t.Odieresis = 778;
t.mu = 556;
t.igrave = 278;
t.ohungarumlaut = 556;
t.Eogonek = 667;
t.dcroat = 556;
t.threequarters = 834;
t.Scedilla = 667;
t.lcaron = 299;
t.Kcommaaccent = 667;
t.Lacute = 556;
t.trademark = 1000;
t.edotaccent = 556;
t.Igrave = 278;
t.Imacron = 278;
t.Lcaron = 556;
t.onehalf = 834;
t.lessequal = 549;
t.ocircumflex = 556;
t.ntilde = 556;
t.Uhungarumlaut = 722;
t.Eacute = 667;
t.emacron = 556;
t.gbreve = 556;
t.onequarter = 834;
t.Scaron = 667;
t.Scommaaccent = 667;
t.Ohungarumlaut = 778;
t.degree = 400;
t.ograve = 556;
t.Ccaron = 722;
t.ugrave = 556;
t.radical = 453;
t.Dcaron = 722;
t.rcommaaccent = 333;
t.Ntilde = 722;
t.otilde = 556;
t.Rcommaaccent = 722;
t.Lcommaaccent = 556;
t.Atilde = 667;
t.Aogonek = 667;
t.Aring = 667;
t.Otilde = 778;
t.zdotaccent = 500;
t.Ecaron = 667;
t.Iogonek = 278;
t.kcommaaccent = 500;
t.minus = 584;
t.Icircumflex = 278;
t.ncaron = 556;
t.tcommaaccent = 278;
t.logicalnot = 584;
t.odieresis = 556;
t.udieresis = 556;
t.notequal = 549;
t.gcommaaccent = 556;
t.eth = 556;
t.zcaron = 500;
t.ncommaaccent = 556;
t.onesuperior = 333;
t.imacron = 278;
t.Euro = 556;
});
t["Helvetica-Bold"] = getLookupTableFactory(function (t) {
t.space = 278;
t.exclam = 333;
t.quotedbl = 474;
t.numbersign = 556;
t.dollar = 556;
t.percent = 889;
t.ampersand = 722;
t.quoteright = 278;
t.parenleft = 333;
t.parenright = 333;
t.asterisk = 389;
t.plus = 584;
t.comma = 278;
t.hyphen = 333;
t.period = 278;
t.slash = 278;
t.zero = 556;
t.one = 556;
t.two = 556;
t.three = 556;
t.four = 556;
t.five = 556;
t.six = 556;
t.seven = 556;
t.eight = 556;
t.nine = 556;
t.colon = 333;
t.semicolon = 333;
t.less = 584;
t.equal = 584;
t.greater = 584;
t.question = 611;
t.at = 975;
t.A = 722;
t.B = 722;
t.C = 722;
t.D = 722;
t.E = 667;
t.F = 611;
t.G = 778;
t.H = 722;
t.I = 278;
t.J = 556;
t.K = 722;
t.L = 611;
t.M = 833;
t.N = 722;
t.O = 778;
t.P = 667;
t.Q = 778;
t.R = 722;
t.S = 667;
t.T = 611;
t.U = 722;
t.V = 667;
t.W = 944;
t.X = 667;
t.Y = 667;
t.Z = 611;
t.bracketleft = 333;
t.backslash = 278;
t.bracketright = 333;
t.asciicircum = 584;
t.underscore = 556;
t.quoteleft = 278;
t.a = 556;
t.b = 611;
t.c = 556;
t.d = 611;
t.e = 556;
t.f = 333;
t.g = 611;
t.h = 611;
t.i = 278;
t.j = 278;
t.k = 556;
t.l = 278;
t.m = 889;
t.n = 611;
t.o = 611;
t.p = 611;
t.q = 611;
t.r = 389;
t.s = 556;
t.t = 333;
t.u = 611;
t.v = 556;
t.w = 778;
t.x = 556;
t.y = 556;
t.z = 500;
t.braceleft = 389;
t.bar = 280;
t.braceright = 389;
t.asciitilde = 584;
t.exclamdown = 333;
t.cent = 556;
t.sterling = 556;
t.fraction = 167;
t.yen = 556;
t.florin = 556;
t.section = 556;
t.currency = 556;
t.quotesingle = 238;
t.quotedblleft = 500;
t.guillemotleft = 556;
t.guilsinglleft = 333;
t.guilsinglright = 333;
t.fi = 611;
t.fl = 611;
t.endash = 556;
t.dagger = 556;
t.daggerdbl = 556;
t.periodcentered = 278;
t.paragraph = 556;
t.bullet = 350;
t.quotesinglbase = 278;
t.quotedblbase = 500;
t.quotedblright = 500;
t.guillemotright = 556;
t.ellipsis = 1000;
t.perthousand = 1000;
t.questiondown = 611;
t.grave = 333;
t.acute = 333;
t.circumflex = 333;
t.tilde = 333;
t.macron = 333;
t.breve = 333;
t.dotaccent = 333;
t.dieresis = 333;
t.ring = 333;
t.cedilla = 333;
t.hungarumlaut = 333;
t.ogonek = 333;
t.caron = 333;
t.emdash = 1000;
t.AE = 1000;
t.ordfeminine = 370;
t.Lslash = 611;
t.Oslash = 778;
t.OE = 1000;
t.ordmasculine = 365;
t.ae = 889;
t.dotlessi = 278;
t.lslash = 278;
t.oslash = 611;
t.oe = 944;
t.germandbls = 611;
t.Idieresis = 278;
t.eacute = 556;
t.abreve = 556;
t.uhungarumlaut = 611;
t.ecaron = 556;
t.Ydieresis = 667;
t.divide = 584;
t.Yacute = 667;
t.Acircumflex = 722;
t.aacute = 556;
t.Ucircumflex = 722;
t.yacute = 556;
t.scommaaccent = 556;
t.ecircumflex = 556;
t.Uring = 722;
t.Udieresis = 722;
t.aogonek = 556;
t.Uacute = 722;
t.uogonek = 611;
t.Edieresis = 667;
t.Dcroat = 722;
t.commaaccent = 250;
t.copyright = 737;
t.Emacron = 667;
t.ccaron = 556;
t.aring = 556;
t.Ncommaaccent = 722;
t.lacute = 278;
t.agrave = 556;
t.Tcommaaccent = 611;
t.Cacute = 722;
t.atilde = 556;
t.Edotaccent = 667;
t.scaron = 556;
t.scedilla = 556;
t.iacute = 278;
t.lozenge = 494;
t.Rcaron = 722;
t.Gcommaaccent = 778;
t.ucircumflex = 611;
t.acircumflex = 556;
t.Amacron = 722;
t.rcaron = 389;
t.ccedilla = 556;
t.Zdotaccent = 611;
t.Thorn = 667;
t.Omacron = 778;
t.Racute = 722;
t.Sacute = 667;
t.dcaron = 743;
t.Umacron = 722;
t.uring = 611;
t.threesuperior = 333;
t.Ograve = 778;
t.Agrave = 722;
t.Abreve = 722;
t.multiply = 584;
t.uacute = 611;
t.Tcaron = 611;
t.partialdiff = 494;
t.ydieresis = 556;
t.Nacute = 722;
t.icircumflex = 278;
t.Ecircumflex = 667;
t.adieresis = 556;
t.edieresis = 556;
t.cacute = 556;
t.nacute = 611;
t.umacron = 611;
t.Ncaron = 722;
t.Iacute = 278;
t.plusminus = 584;
t.brokenbar = 280;
t.registered = 737;
t.Gbreve = 778;
t.Idotaccent = 278;
t.summation = 600;
t.Egrave = 667;
t.racute = 389;
t.omacron = 611;
t.Zacute = 611;
t.Zcaron = 611;
t.greaterequal = 549;
t.Eth = 722;
t.Ccedilla = 722;
t.lcommaaccent = 278;
t.tcaron = 389;
t.eogonek = 556;
t.Uogonek = 722;
t.Aacute = 722;
t.Adieresis = 722;
t.egrave = 556;
t.zacute = 500;
t.iogonek = 278;
t.Oacute = 778;
t.oacute = 611;
t.amacron = 556;
t.sacute = 556;
t.idieresis = 278;
t.Ocircumflex = 778;
t.Ugrave = 722;
t.Delta = 612;
t.thorn = 611;
t.twosuperior = 333;
t.Odieresis = 778;
t.mu = 611;
t.igrave = 278;
t.ohungarumlaut = 611;
t.Eogonek = 667;
t.dcroat = 611;
t.threequarters = 834;
t.Scedilla = 667;
t.lcaron = 400;
t.Kcommaaccent = 722;
t.Lacute = 611;
t.trademark = 1000;
t.edotaccent = 556;
t.Igrave = 278;
t.Imacron = 278;
t.Lcaron = 611;
t.onehalf = 834;
t.lessequal = 549;
t.ocircumflex = 611;
t.ntilde = 611;
t.Uhungarumlaut = 722;
t.Eacute = 667;
t.emacron = 556;
t.gbreve = 611;
t.onequarter = 834;
t.Scaron = 667;
t.Scommaaccent = 667;
t.Ohungarumlaut = 778;
t.degree = 400;
t.ograve = 611;
t.Ccaron = 722;
t.ugrave = 611;
t.radical = 549;
t.Dcaron = 722;
t.rcommaaccent = 389;
t.Ntilde = 722;
t.otilde = 611;
t.Rcommaaccent = 722;
t.Lcommaaccent = 611;
t.Atilde = 722;
t.Aogonek = 722;
t.Aring = 722;
t.Otilde = 778;
t.zdotaccent = 500;
t.Ecaron = 667;
t.Iogonek = 278;
t.kcommaaccent = 556;
t.minus = 584;
t.Icircumflex = 278;
t.ncaron = 611;
t.tcommaaccent = 333;
t.logicalnot = 584;
t.odieresis = 611;
t.udieresis = 611;
t.notequal = 549;
t.gcommaaccent = 611;
t.eth = 611;
t.zcaron = 500;
t.ncommaaccent = 611;
t.onesuperior = 333;
t.imacron = 278;
t.Euro = 556;
});
t["Helvetica-BoldOblique"] = getLookupTableFactory(function (t) {
t.space = 278;
t.exclam = 333;
t.quotedbl = 474;
t.numbersign = 556;
t.dollar = 556;
t.percent = 889;
t.ampersand = 722;
t.quoteright = 278;
t.parenleft = 333;
t.parenright = 333;
t.asterisk = 389;
t.plus = 584;
t.comma = 278;
t.hyphen = 333;
t.period = 278;
t.slash = 278;
t.zero = 556;
t.one = 556;
t.two = 556;
t.three = 556;
t.four = 556;
t.five = 556;
t.six = 556;
t.seven = 556;
t.eight = 556;
t.nine = 556;
t.colon = 333;
t.semicolon = 333;
t.less = 584;
t.equal = 584;
t.greater = 584;
t.question = 611;
t.at = 975;
t.A = 722;
t.B = 722;
t.C = 722;
t.D = 722;
t.E = 667;
t.F = 611;
t.G = 778;
t.H = 722;
t.I = 278;
t.J = 556;
t.K = 722;
t.L = 611;
t.M = 833;
t.N = 722;
t.O = 778;
t.P = 667;
t.Q = 778;
t.R = 722;
t.S = 667;
t.T = 611;
t.U = 722;
t.V = 667;
t.W = 944;
t.X = 667;
t.Y = 667;
t.Z = 611;
t.bracketleft = 333;
t.backslash = 278;
t.bracketright = 333;
t.asciicircum = 584;
t.underscore = 556;
t.quoteleft = 278;
t.a = 556;
t.b = 611;
t.c = 556;
t.d = 611;
t.e = 556;
t.f = 333;
t.g = 611;
t.h = 611;
t.i = 278;
t.j = 278;
t.k = 556;
t.l = 278;
t.m = 889;
t.n = 611;
t.o = 611;
t.p = 611;
t.q = 611;
t.r = 389;
t.s = 556;
t.t = 333;
t.u = 611;
t.v = 556;
t.w = 778;
t.x = 556;
t.y = 556;
t.z = 500;
t.braceleft = 389;
t.bar = 280;
t.braceright = 389;
t.asciitilde = 584;
t.exclamdown = 333;
t.cent = 556;
t.sterling = 556;
t.fraction = 167;
t.yen = 556;
t.florin = 556;
t.section = 556;
t.currency = 556;
t.quotesingle = 238;
t.quotedblleft = 500;
t.guillemotleft = 556;
t.guilsinglleft = 333;
t.guilsinglright = 333;
t.fi = 611;
t.fl = 611;
t.endash = 556;
t.dagger = 556;
t.daggerdbl = 556;
t.periodcentered = 278;
t.paragraph = 556;
t.bullet = 350;
t.quotesinglbase = 278;
t.quotedblbase = 500;
t.quotedblright = 500;
t.guillemotright = 556;
t.ellipsis = 1000;
t.perthousand = 1000;
t.questiondown = 611;
t.grave = 333;
t.acute = 333;
t.circumflex = 333;
t.tilde = 333;
t.macron = 333;
t.breve = 333;
t.dotaccent = 333;
t.dieresis = 333;
t.ring = 333;
t.cedilla = 333;
t.hungarumlaut = 333;
t.ogonek = 333;
t.caron = 333;
t.emdash = 1000;
t.AE = 1000;
t.ordfeminine = 370;
t.Lslash = 611;
t.Oslash = 778;
t.OE = 1000;
t.ordmasculine = 365;
t.ae = 889;
t.dotlessi = 278;
t.lslash = 278;
t.oslash = 611;
t.oe = 944;
t.germandbls = 611;
t.Idieresis = 278;
t.eacute = 556;
t.abreve = 556;
t.uhungarumlaut = 611;
t.ecaron = 556;
t.Ydieresis = 667;
t.divide = 584;
t.Yacute = 667;
t.Acircumflex = 722;
t.aacute = 556;
t.Ucircumflex = 722;
t.yacute = 556;
t.scommaaccent = 556;
t.ecircumflex = 556;
t.Uring = 722;
t.Udieresis = 722;
t.aogonek = 556;
t.Uacute = 722;
t.uogonek = 611;
t.Edieresis = 667;
t.Dcroat = 722;
t.commaaccent = 250;
t.copyright = 737;
t.Emacron = 667;
t.ccaron = 556;
t.aring = 556;
t.Ncommaaccent = 722;
t.lacute = 278;
t.agrave = 556;
t.Tcommaaccent = 611;
t.Cacute = 722;
t.atilde = 556;
t.Edotaccent = 667;
t.scaron = 556;
t.scedilla = 556;
t.iacute = 278;
t.lozenge = 494;
t.Rcaron = 722;
t.Gcommaaccent = 778;
t.ucircumflex = 611;
t.acircumflex = 556;
t.Amacron = 722;
t.rcaron = 389;
t.ccedilla = 556;
t.Zdotaccent = 611;
t.Thorn = 667;
t.Omacron = 778;
t.Racute = 722;
t.Sacute = 667;
t.dcaron = 743;
t.Umacron = 722;
t.uring = 611;
t.threesuperior = 333;
t.Ograve = 778;
t.Agrave = 722;
t.Abreve = 722;
t.multiply = 584;
t.uacute = 611;
t.Tcaron = 611;
t.partialdiff = 494;
t.ydieresis = 556;
t.Nacute = 722;
t.icircumflex = 278;
t.Ecircumflex = 667;
t.adieresis = 556;
t.edieresis = 556;
t.cacute = 556;
t.nacute = 611;
t.umacron = 611;
t.Ncaron = 722;
t.Iacute = 278;
t.plusminus = 584;
t.brokenbar = 280;
t.registered = 737;
t.Gbreve = 778;
t.Idotaccent = 278;
t.summation = 600;
t.Egrave = 667;
t.racute = 389;
t.omacron = 611;
t.Zacute = 611;
t.Zcaron = 611;
t.greaterequal = 549;
t.Eth = 722;
t.Ccedilla = 722;
t.lcommaaccent = 278;
t.tcaron = 389;
t.eogonek = 556;
t.Uogonek = 722;
t.Aacute = 722;
t.Adieresis = 722;
t.egrave = 556;
t.zacute = 500;
t.iogonek = 278;
t.Oacute = 778;
t.oacute = 611;
t.amacron = 556;
t.sacute = 556;
t.idieresis = 278;
t.Ocircumflex = 778;
t.Ugrave = 722;
t.Delta = 612;
t.thorn = 611;
t.twosuperior = 333;
t.Odieresis = 778;
t.mu = 611;
t.igrave = 278;
t.ohungarumlaut = 611;
t.Eogonek = 667;
t.dcroat = 611;
t.threequarters = 834;
t.Scedilla = 667;
t.lcaron = 400;
t.Kcommaaccent = 722;
t.Lacute = 611;
t.trademark = 1000;
t.edotaccent = 556;
t.Igrave = 278;
t.Imacron = 278;
t.Lcaron = 611;
t.onehalf = 834;
t.lessequal = 549;
t.ocircumflex = 611;
t.ntilde = 611;
t.Uhungarumlaut = 722;
t.Eacute = 667;
t.emacron = 556;
t.gbreve = 611;
t.onequarter = 834;
t.Scaron = 667;
t.Scommaaccent = 667;
t.Ohungarumlaut = 778;
t.degree = 400;
t.ograve = 611;
t.Ccaron = 722;
t.ugrave = 611;
t.radical = 549;
t.Dcaron = 722;
t.rcommaaccent = 389;
t.Ntilde = 722;
t.otilde = 611;
t.Rcommaaccent = 722;
t.Lcommaaccent = 611;
t.Atilde = 722;
t.Aogonek = 722;
t.Aring = 722;
t.Otilde = 778;
t.zdotaccent = 500;
t.Ecaron = 667;
t.Iogonek = 278;
t.kcommaaccent = 556;
t.minus = 584;
t.Icircumflex = 278;
t.ncaron = 611;
t.tcommaaccent = 333;
t.logicalnot = 584;
t.odieresis = 611;
t.udieresis = 611;
t.notequal = 549;
t.gcommaaccent = 611;
t.eth = 611;
t.zcaron = 500;
t.ncommaaccent = 611;
t.onesuperior = 333;
t.imacron = 278;
t.Euro = 556;
});
t["Helvetica-Oblique"] = getLookupTableFactory(function (t) {
t.space = 278;
t.exclam = 278;
t.quotedbl = 355;
t.numbersign = 556;
t.dollar = 556;
t.percent = 889;
t.ampersand = 667;
t.quoteright = 222;
t.parenleft = 333;
t.parenright = 333;
t.asterisk = 389;
t.plus = 584;
t.comma = 278;
t.hyphen = 333;
t.period = 278;
t.slash = 278;
t.zero = 556;
t.one = 556;
t.two = 556;
t.three = 556;
t.four = 556;
t.five = 556;
t.six = 556;
t.seven = 556;
t.eight = 556;
t.nine = 556;
t.colon = 278;
t.semicolon = 278;
t.less = 584;
t.equal = 584;
t.greater = 584;
t.question = 556;
t.at = 1015;
t.A = 667;
t.B = 667;
t.C = 722;
t.D = 722;
t.E = 667;
t.F = 611;
t.G = 778;
t.H = 722;
t.I = 278;
t.J = 500;
t.K = 667;
t.L = 556;
t.M = 833;
t.N = 722;
t.O = 778;
t.P = 667;
t.Q = 778;
t.R = 722;
t.S = 667;
t.T = 611;
t.U = 722;
t.V = 667;
t.W = 944;
t.X = 667;
t.Y = 667;
t.Z = 611;
t.bracketleft = 278;
t.backslash = 278;
t.bracketright = 278;
t.asciicircum = 469;
t.underscore = 556;
t.quoteleft = 222;
t.a = 556;
t.b = 556;
t.c = 500;
t.d = 556;
t.e = 556;
t.f = 278;
t.g = 556;
t.h = 556;
t.i = 222;
t.j = 222;
t.k = 500;
t.l = 222;
t.m = 833;
t.n = 556;
t.o = 556;
t.p = 556;
t.q = 556;
t.r = 333;
t.s = 500;
t.t = 278;
t.u = 556;
t.v = 500;
t.w = 722;
t.x = 500;
t.y = 500;
t.z = 500;
t.braceleft = 334;
t.bar = 260;
t.braceright = 334;
t.asciitilde = 584;
t.exclamdown = 333;
t.cent = 556;
t.sterling = 556;
t.fraction = 167;
t.yen = 556;
t.florin = 556;
t.section = 556;
t.currency = 556;
t.quotesingle = 191;
t.quotedblleft = 333;
t.guillemotleft = 556;
t.guilsinglleft = 333;
t.guilsinglright = 333;
t.fi = 500;
t.fl = 500;
t.endash = 556;
t.dagger = 556;
t.daggerdbl = 556;
t.periodcentered = 278;
t.paragraph = 537;
t.bullet = 350;
t.quotesinglbase = 222;
t.quotedblbase = 333;
t.quotedblright = 333;
t.guillemotright = 556;
t.ellipsis = 1000;
t.perthousand = 1000;
t.questiondown = 611;
t.grave = 333;
t.acute = 333;
t.circumflex = 333;
t.tilde = 333;
t.macron = 333;
t.breve = 333;
t.dotaccent = 333;
t.dieresis = 333;
t.ring = 333;
t.cedilla = 333;
t.hungarumlaut = 333;
t.ogonek = 333;
t.caron = 333;
t.emdash = 1000;
t.AE = 1000;
t.ordfeminine = 370;
t.Lslash = 556;
t.Oslash = 778;
t.OE = 1000;
t.ordmasculine = 365;
t.ae = 889;
t.dotlessi = 278;
t.lslash = 222;
t.oslash = 611;
t.oe = 944;
t.germandbls = 611;
t.Idieresis = 278;
t.eacute = 556;
t.abreve = 556;
t.uhungarumlaut = 556;
t.ecaron = 556;
t.Ydieresis = 667;
t.divide = 584;
t.Yacute = 667;
t.Acircumflex = 667;
t.aacute = 556;
t.Ucircumflex = 722;
t.yacute = 500;
t.scommaaccent = 500;
t.ecircumflex = 556;
t.Uring = 722;
t.Udieresis = 722;
t.aogonek = 556;
t.Uacute = 722;
t.uogonek = 556;
t.Edieresis = 667;
t.Dcroat = 722;
t.commaaccent = 250;
t.copyright = 737;
t.Emacron = 667;
t.ccaron = 500;
t.aring = 556;
t.Ncommaaccent = 722;
t.lacute = 222;
t.agrave = 556;
t.Tcommaaccent = 611;
t.Cacute = 722;
t.atilde = 556;
t.Edotaccent = 667;
t.scaron = 500;
t.scedilla = 500;
t.iacute = 278;
t.lozenge = 471;
t.Rcaron = 722;
t.Gcommaaccent = 778;
t.ucircumflex = 556;
t.acircumflex = 556;
t.Amacron = 667;
t.rcaron = 333;
t.ccedilla = 500;
t.Zdotaccent = 611;
t.Thorn = 667;
t.Omacron = 778;
t.Racute = 722;
t.Sacute = 667;
t.dcaron = 643;
t.Umacron = 722;
t.uring = 556;
t.threesuperior = 333;
t.Ograve = 778;
t.Agrave = 667;
t.Abreve = 667;
t.multiply = 584;
t.uacute = 556;
t.Tcaron = 611;
t.partialdiff = 476;
t.ydieresis = 500;
t.Nacute = 722;
t.icircumflex = 278;
t.Ecircumflex = 667;
t.adieresis = 556;
t.edieresis = 556;
t.cacute = 500;
t.nacute = 556;
t.umacron = 556;
t.Ncaron = 722;
t.Iacute = 278;
t.plusminus = 584;
t.brokenbar = 260;
t.registered = 737;
t.Gbreve = 778;
t.Idotaccent = 278;
t.summation = 600;
t.Egrave = 667;
t.racute = 333;
t.omacron = 556;
t.Zacute = 611;
t.Zcaron = 611;
t.greaterequal = 549;
t.Eth = 722;
t.Ccedilla = 722;
t.lcommaaccent = 222;
t.tcaron = 317;
t.eogonek = 556;
t.Uogonek = 722;
t.Aacute = 667;
t.Adieresis = 667;
t.egrave = 556;
t.zacute = 500;
t.iogonek = 222;
t.Oacute = 778;
t.oacute = 556;
t.amacron = 556;
t.sacute = 500;
t.idieresis = 278;
t.Ocircumflex = 778;
t.Ugrave = 722;
t.Delta = 612;
t.thorn = 556;
t.twosuperior = 333;
t.Odieresis = 778;
t.mu = 556;
t.igrave = 278;
t.ohungarumlaut = 556;
t.Eogonek = 667;
t.dcroat = 556;
t.threequarters = 834;
t.Scedilla = 667;
t.lcaron = 299;
t.Kcommaaccent = 667;
t.Lacute = 556;
t.trademark = 1000;
t.edotaccent = 556;
t.Igrave = 278;
t.Imacron = 278;
t.Lcaron = 556;
t.onehalf = 834;
t.lessequal = 549;
t.ocircumflex = 556;
t.ntilde = 556;
t.Uhungarumlaut = 722;
t.Eacute = 667;
t.emacron = 556;
t.gbreve = 556;
t.onequarter = 834;
t.Scaron = 667;
t.Scommaaccent = 667;
t.Ohungarumlaut = 778;
t.degree = 400;
t.ograve = 556;
t.Ccaron = 722;
t.ugrave = 556;
t.radical = 453;
t.Dcaron = 722;
t.rcommaaccent = 333;
t.Ntilde = 722;
t.otilde = 556;
t.Rcommaaccent = 722;
t.Lcommaaccent = 556;
t.Atilde = 667;
t.Aogonek = 667;
t.Aring = 667;
t.Otilde = 778;
t.zdotaccent = 500;
t.Ecaron = 667;
t.Iogonek = 278;
t.kcommaaccent = 500;
t.minus = 584;
t.Icircumflex = 278;
t.ncaron = 556;
t.tcommaaccent = 278;
t.logicalnot = 584;
t.odieresis = 556;
t.udieresis = 556;
t.notequal = 549;
t.gcommaaccent = 556;
t.eth = 556;
t.zcaron = 500;
t.ncommaaccent = 556;
t.onesuperior = 333;
t.imacron = 278;
t.Euro = 556;
});
t.Symbol = getLookupTableFactory(function (t) {
t.space = 250;
t.exclam = 333;
t.universal = 713;
t.numbersign = 500;
t.existential = 549;
t.percent = 833;
t.ampersand = 778;
t.suchthat = 439;
t.parenleft = 333;
t.parenright = 333;
t.asteriskmath = 500;
t.plus = 549;
t.comma = 250;
t.minus = 549;
t.period = 250;
t.slash = 278;
t.zero = 500;
t.one = 500;
t.two = 500;
t.three = 500;
t.four = 500;
t.five = 500;
t.six = 500;
t.seven = 500;
t.eight = 500;
t.nine = 500;
t.colon = 278;
t.semicolon = 278;
t.less = 549;
t.equal = 549;
t.greater = 549;
t.question = 444;
t.congruent = 549;
t.Alpha = 722;
t.Beta = 667;
t.Chi = 722;
t.Delta = 612;
t.Epsilon = 611;
t.Phi = 763;
t.Gamma = 603;
t.Eta = 722;
t.Iota = 333;
t.theta1 = 631;
t.Kappa = 722;
t.Lambda = 686;
t.Mu = 889;
t.Nu = 722;
t.Omicron = 722;
t.Pi = 768;
t.Theta = 741;
t.Rho = 556;
t.Sigma = 592;
t.Tau = 611;
t.Upsilon = 690;
t.sigma1 = 439;
t.Omega = 768;
t.Xi = 645;
t.Psi = 795;
t.Zeta = 611;
t.bracketleft = 333;
t.therefore = 863;
t.bracketright = 333;
t.perpendicular = 658;
t.underscore = 500;
t.radicalex = 500;
t.alpha = 631;
t.beta = 549;
t.chi = 549;
t.delta = 494;
t.epsilon = 439;
t.phi = 521;
t.gamma = 411;
t.eta = 603;
t.iota = 329;
t.phi1 = 603;
t.kappa = 549;
t.lambda = 549;
t.mu = 576;
t.nu = 521;
t.omicron = 549;
t.pi = 549;
t.theta = 521;
t.rho = 549;
t.sigma = 603;
t.tau = 439;
t.upsilon = 576;
t.omega1 = 713;
t.omega = 686;
t.xi = 493;
t.psi = 686;
t.zeta = 494;
t.braceleft = 480;
t.bar = 200;
t.braceright = 480;
t.similar = 549;
t.Euro = 750;
t.Upsilon1 = 620;
t.minute = 247;
t.lessequal = 549;
t.fraction = 167;
t.infinity = 713;
t.florin = 500;
t.club = 753;
t.diamond = 753;
t.heart = 753;
t.spade = 753;
t.arrowboth = 1042;
t.arrowleft = 987;
t.arrowup = 603;
t.arrowright = 987;
t.arrowdown = 603;
t.degree = 400;
t.plusminus = 549;
t.second = 411;
t.greaterequal = 549;
t.multiply = 549;
t.proportional = 713;
t.partialdiff = 494;
t.bullet = 460;
t.divide = 549;
t.notequal = 549;
t.equivalence = 549;
t.approxequal = 549;
t.ellipsis = 1000;
t.arrowvertex = 603;
t.arrowhorizex = 1000;
t.carriagereturn = 658;
t.aleph = 823;
t.Ifraktur = 686;
t.Rfraktur = 795;
t.weierstrass = 987;
t.circlemultiply = 768;
t.circleplus = 768;
t.emptyset = 823;
t.intersection = 768;
t.union = 768;
t.propersuperset = 713;
t.reflexsuperset = 713;
t.notsubset = 713;
t.propersubset = 713;
t.reflexsubset = 713;
t.element = 713;
t.notelement = 713;
t.angle = 768;
t.gradient = 713;
t.registerserif = 790;
t.copyrightserif = 790;
t.trademarkserif = 890;
t.product = 823;
t.radical = 549;
t.dotmath = 250;
t.logicalnot = 713;
t.logicaland = 603;
t.logicalor = 603;
t.arrowdblboth = 1042;
t.arrowdblleft = 987;
t.arrowdblup = 603;
t.arrowdblright = 987;
t.arrowdbldown = 603;
t.lozenge = 494;
t.angleleft = 329;
t.registersans = 790;
t.copyrightsans = 790;
t.trademarksans = 786;
t.summation = 713;
t.parenlefttp = 384;
t.parenleftex = 384;
t.parenleftbt = 384;
t.bracketlefttp = 384;
t.bracketleftex = 384;
t.bracketleftbt = 384;
t.bracelefttp = 494;
t.braceleftmid = 494;
t.braceleftbt = 494;
t.braceex = 494;
t.angleright = 329;
t.integral = 274;
t.integraltp = 686;
t.integralex = 686;
t.integralbt = 686;
t.parenrighttp = 384;
t.parenrightex = 384;
t.parenrightbt = 384;
t.bracketrighttp = 384;
t.bracketrightex = 384;
t.bracketrightbt = 384;
t.bracerighttp = 494;
t.bracerightmid = 494;
t.bracerightbt = 494;
t.apple = 790;
});
t["Times-Roman"] = getLookupTableFactory(function (t) {
t.space = 250;
t.exclam = 333;
t.quotedbl = 408;
t.numbersign = 500;
t.dollar = 500;
t.percent = 833;
t.ampersand = 778;
t.quoteright = 333;
t.parenleft = 333;
t.parenright = 333;
t.asterisk = 500;
t.plus = 564;
t.comma = 250;
t.hyphen = 333;
t.period = 250;
t.slash = 278;
t.zero = 500;
t.one = 500;
t.two = 500;
t.three = 500;
t.four = 500;
t.five = 500;
t.six = 500;
t.seven = 500;
t.eight = 500;
t.nine = 500;
t.colon = 278;
t.semicolon = 278;
t.less = 564;
t.equal = 564;
t.greater = 564;
t.question = 444;
t.at = 921;
t.A = 722;
t.B = 667;
t.C = 667;
t.D = 722;
t.E = 611;
t.F = 556;
t.G = 722;
t.H = 722;
t.I = 333;
t.J = 389;
t.K = 722;
t.L = 611;
t.M = 889;
t.N = 722;
t.O = 722;
t.P = 556;
t.Q = 722;
t.R = 667;
t.S = 556;
t.T = 611;
t.U = 722;
t.V = 722;
t.W = 944;
t.X = 722;
t.Y = 722;
t.Z = 611;
t.bracketleft = 333;
t.backslash = 278;
t.bracketright = 333;
t.asciicircum = 469;
t.underscore = 500;
t.quoteleft = 333;
t.a = 444;
t.b = 500;
t.c = 444;
t.d = 500;
t.e = 444;
t.f = 333;
t.g = 500;
t.h = 500;
t.i = 278;
t.j = 278;
t.k = 500;
t.l = 278;
t.m = 778;
t.n = 500;
t.o = 500;
t.p = 500;
t.q = 500;
t.r = 333;
t.s = 389;
t.t = 278;
t.u = 500;
t.v = 500;
t.w = 722;
t.x = 500;
t.y = 500;
t.z = 444;
t.braceleft = 480;
t.bar = 200;
t.braceright = 480;
t.asciitilde = 541;
t.exclamdown = 333;
t.cent = 500;
t.sterling = 500;
t.fraction = 167;
t.yen = 500;
t.florin = 500;
t.section = 500;
t.currency = 500;
t.quotesingle = 180;
t.quotedblleft = 444;
t.guillemotleft = 500;
t.guilsinglleft = 333;
t.guilsinglright = 333;
t.fi = 556;
t.fl = 556;
t.endash = 500;
t.dagger = 500;
t.daggerdbl = 500;
t.periodcentered = 250;
t.paragraph = 453;
t.bullet = 350;
t.quotesinglbase = 333;
t.quotedblbase = 444;
t.quotedblright = 444;
t.guillemotright = 500;
t.ellipsis = 1000;
t.perthousand = 1000;
t.questiondown = 444;
t.grave = 333;
t.acute = 333;
t.circumflex = 333;
t.tilde = 333;
t.macron = 333;
t.breve = 333;
t.dotaccent = 333;
t.dieresis = 333;
t.ring = 333;
t.cedilla = 333;
t.hungarumlaut = 333;
t.ogonek = 333;
t.caron = 333;
t.emdash = 1000;
t.AE = 889;
t.ordfeminine = 276;
t.Lslash = 611;
t.Oslash = 722;
t.OE = 889;
t.ordmasculine = 310;
t.ae = 667;
t.dotlessi = 278;
t.lslash = 278;
t.oslash = 500;
t.oe = 722;
t.germandbls = 500;
t.Idieresis = 333;
t.eacute = 444;
t.abreve = 444;
t.uhungarumlaut = 500;
t.ecaron = 444;
t.Ydieresis = 722;
t.divide = 564;
t.Yacute = 722;
t.Acircumflex = 722;
t.aacute = 444;
t.Ucircumflex = 722;
t.yacute = 500;
t.scommaaccent = 389;
t.ecircumflex = 444;
t.Uring = 722;
t.Udieresis = 722;
t.aogonek = 444;
t.Uacute = 722;
t.uogonek = 500;
t.Edieresis = 611;
t.Dcroat = 722;
t.commaaccent = 250;
t.copyright = 760;
t.Emacron = 611;
t.ccaron = 444;
t.aring = 444;
t.Ncommaaccent = 722;
t.lacute = 278;
t.agrave = 444;
t.Tcommaaccent = 611;
t.Cacute = 667;
t.atilde = 444;
t.Edotaccent = 611;
t.scaron = 389;
t.scedilla = 389;
t.iacute = 278;
t.lozenge = 471;
t.Rcaron = 667;
t.Gcommaaccent = 722;
t.ucircumflex = 500;
t.acircumflex = 444;
t.Amacron = 722;
t.rcaron = 333;
t.ccedilla = 444;
t.Zdotaccent = 611;
t.Thorn = 556;
t.Omacron = 722;
t.Racute = 667;
t.Sacute = 556;
t.dcaron = 588;
t.Umacron = 722;
t.uring = 500;
t.threesuperior = 300;
t.Ograve = 722;
t.Agrave = 722;
t.Abreve = 722;
t.multiply = 564;
t.uacute = 500;
t.Tcaron = 611;
t.partialdiff = 476;
t.ydieresis = 500;
t.Nacute = 722;
t.icircumflex = 278;
t.Ecircumflex = 611;
t.adieresis = 444;
t.edieresis = 444;
t.cacute = 444;
t.nacute = 500;
t.umacron = 500;
t.Ncaron = 722;
t.Iacute = 333;
t.plusminus = 564;
t.brokenbar = 200;
t.registered = 760;
t.Gbreve = 722;
t.Idotaccent = 333;
t.summation = 600;
t.Egrave = 611;
t.racute = 333;
t.omacron = 500;
t.Zacute = 611;
t.Zcaron = 611;
t.greaterequal = 549;
t.Eth = 722;
t.Ccedilla = 667;
t.lcommaaccent = 278;
t.tcaron = 326;
t.eogonek = 444;
t.Uogonek = 722;
t.Aacute = 722;
t.Adieresis = 722;
t.egrave = 444;
t.zacute = 444;
t.iogonek = 278;
t.Oacute = 722;
t.oacute = 500;
t.amacron = 444;
t.sacute = 389;
t.idieresis = 278;
t.Ocircumflex = 722;
t.Ugrave = 722;
t.Delta = 612;
t.thorn = 500;
t.twosuperior = 300;
t.Odieresis = 722;
t.mu = 500;
t.igrave = 278;
t.ohungarumlaut = 500;
t.Eogonek = 611;
t.dcroat = 500;
t.threequarters = 750;
t.Scedilla = 556;
t.lcaron = 344;
t.Kcommaaccent = 722;
t.Lacute = 611;
t.trademark = 980;
t.edotaccent = 444;
t.Igrave = 333;
t.Imacron = 333;
t.Lcaron = 611;
t.onehalf = 750;
t.lessequal = 549;
t.ocircumflex = 500;
t.ntilde = 500;
t.Uhungarumlaut = 722;
t.Eacute = 611;
t.emacron = 444;
t.gbreve = 500;
t.onequarter = 750;
t.Scaron = 556;
t.Scommaaccent = 556;
t.Ohungarumlaut = 722;
t.degree = 400;
t.ograve = 500;
t.Ccaron = 667;
t.ugrave = 500;
t.radical = 453;
t.Dcaron = 722;
t.rcommaaccent = 333;
t.Ntilde = 722;
t.otilde = 500;
t.Rcommaaccent = 667;
t.Lcommaaccent = 611;
t.Atilde = 722;
t.Aogonek = 722;
t.Aring = 722;
t.Otilde = 722;
t.zdotaccent = 444;
t.Ecaron = 611;
t.Iogonek = 333;
t.kcommaaccent = 500;
t.minus = 564;
t.Icircumflex = 333;
t.ncaron = 500;
t.tcommaaccent = 278;
t.logicalnot = 564;
t.odieresis = 500;
t.udieresis = 500;
t.notequal = 549;
t.gcommaaccent = 500;
t.eth = 500;
t.zcaron = 444;
t.ncommaaccent = 500;
t.onesuperior = 300;
t.imacron = 278;
t.Euro = 500;
});
t["Times-Bold"] = getLookupTableFactory(function (t) {
t.space = 250;
t.exclam = 333;
t.quotedbl = 555;
t.numbersign = 500;
t.dollar = 500;
t.percent = 1000;
t.ampersand = 833;
t.quoteright = 333;
t.parenleft = 333;
t.parenright = 333;
t.asterisk = 500;
t.plus = 570;
t.comma = 250;
t.hyphen = 333;
t.period = 250;
t.slash = 278;
t.zero = 500;
t.one = 500;
t.two = 500;
t.three = 500;
t.four = 500;
t.five = 500;
t.six = 500;
t.seven = 500;
t.eight = 500;
t.nine = 500;
t.colon = 333;
t.semicolon = 333;
t.less = 570;
t.equal = 570;
t.greater = 570;
t.question = 500;
t.at = 930;
t.A = 722;
t.B = 667;
t.C = 722;
t.D = 722;
t.E = 667;
t.F = 611;
t.G = 778;
t.H = 778;
t.I = 389;
t.J = 500;
t.K = 778;
t.L = 667;
t.M = 944;
t.N = 722;
t.O = 778;
t.P = 611;
t.Q = 778;
t.R = 722;
t.S = 556;
t.T = 667;
t.U = 722;
t.V = 722;
t.W = 1000;
t.X = 722;
t.Y = 722;
t.Z = 667;
t.bracketleft = 333;
t.backslash = 278;
t.bracketright = 333;
t.asciicircum = 581;
t.underscore = 500;
t.quoteleft = 333;
t.a = 500;
t.b = 556;
t.c = 444;
t.d = 556;
t.e = 444;
t.f = 333;
t.g = 500;
t.h = 556;
t.i = 278;
t.j = 333;
t.k = 556;
t.l = 278;
t.m = 833;
t.n = 556;
t.o = 500;
t.p = 556;
t.q = 556;
t.r = 444;
t.s = 389;
t.t = 333;
t.u = 556;
t.v = 500;
t.w = 722;
t.x = 500;
t.y = 500;
t.z = 444;
t.braceleft = 394;
t.bar = 220;
t.braceright = 394;
t.asciitilde = 520;
t.exclamdown = 333;
t.cent = 500;
t.sterling = 500;
t.fraction = 167;
t.yen = 500;
t.florin = 500;
t.section = 500;
t.currency = 500;
t.quotesingle = 278;
t.quotedblleft = 500;
t.guillemotleft = 500;
t.guilsinglleft = 333;
t.guilsinglright = 333;
t.fi = 556;
t.fl = 556;
t.endash = 500;
t.dagger = 500;
t.daggerdbl = 500;
t.periodcentered = 250;
t.paragraph = 540;
t.bullet = 350;
t.quotesinglbase = 333;
t.quotedblbase = 500;
t.quotedblright = 500;
t.guillemotright = 500;
t.ellipsis = 1000;
t.perthousand = 1000;
t.questiondown = 500;
t.grave = 333;
t.acute = 333;
t.circumflex = 333;
t.tilde = 333;
t.macron = 333;
t.breve = 333;
t.dotaccent = 333;
t.dieresis = 333;
t.ring = 333;
t.cedilla = 333;
t.hungarumlaut = 333;
t.ogonek = 333;
t.caron = 333;
t.emdash = 1000;
t.AE = 1000;
t.ordfeminine = 300;
t.Lslash = 667;
t.Oslash = 778;
t.OE = 1000;
t.ordmasculine = 330;
t.ae = 722;
t.dotlessi = 278;
t.lslash = 278;
t.oslash = 500;
t.oe = 722;
t.germandbls = 556;
t.Idieresis = 389;
t.eacute = 444;
t.abreve = 500;
t.uhungarumlaut = 556;
t.ecaron = 444;
t.Ydieresis = 722;
t.divide = 570;
t.Yacute = 722;
t.Acircumflex = 722;
t.aacute = 500;
t.Ucircumflex = 722;
t.yacute = 500;
t.scommaaccent = 389;
t.ecircumflex = 444;
t.Uring = 722;
t.Udieresis = 722;
t.aogonek = 500;
t.Uacute = 722;
t.uogonek = 556;
t.Edieresis = 667;
t.Dcroat = 722;
t.commaaccent = 250;
t.copyright = 747;
t.Emacron = 667;
t.ccaron = 444;
t.aring = 500;
t.Ncommaaccent = 722;
t.lacute = 278;
t.agrave = 500;
t.Tcommaaccent = 667;
t.Cacute = 722;
t.atilde = 500;
t.Edotaccent = 667;
t.scaron = 389;
t.scedilla = 389;
t.iacute = 278;
t.lozenge = 494;
t.Rcaron = 722;
t.Gcommaaccent = 778;
t.ucircumflex = 556;
t.acircumflex = 500;
t.Amacron = 722;
t.rcaron = 444;
t.ccedilla = 444;
t.Zdotaccent = 667;
t.Thorn = 611;
t.Omacron = 778;
t.Racute = 722;
t.Sacute = 556;
t.dcaron = 672;
t.Umacron = 722;
t.uring = 556;
t.threesuperior = 300;
t.Ograve = 778;
t.Agrave = 722;
t.Abreve = 722;
t.multiply = 570;
t.uacute = 556;
t.Tcaron = 667;
t.partialdiff = 494;
t.ydieresis = 500;
t.Nacute = 722;
t.icircumflex = 278;
t.Ecircumflex = 667;
t.adieresis = 500;
t.edieresis = 444;
t.cacute = 444;
t.nacute = 556;
t.umacron = 556;
t.Ncaron = 722;
t.Iacute = 389;
t.plusminus = 570;
t.brokenbar = 220;
t.registered = 747;
t.Gbreve = 778;
t.Idotaccent = 389;
t.summation = 600;
t.Egrave = 667;
t.racute = 444;
t.omacron = 500;
t.Zacute = 667;
t.Zcaron = 667;
t.greaterequal = 549;
t.Eth = 722;
t.Ccedilla = 722;
t.lcommaaccent = 278;
t.tcaron = 416;
t.eogonek = 444;
t.Uogonek = 722;
t.Aacute = 722;
t.Adieresis = 722;
t.egrave = 444;
t.zacute = 444;
t.iogonek = 278;
t.Oacute = 778;
t.oacute = 500;
t.amacron = 500;
t.sacute = 389;
t.idieresis = 278;
t.Ocircumflex = 778;
t.Ugrave = 722;
t.Delta = 612;
t.thorn = 556;
t.twosuperior = 300;
t.Odieresis = 778;
t.mu = 556;
t.igrave = 278;
t.ohungarumlaut = 500;
t.Eogonek = 667;
t.dcroat = 556;
t.threequarters = 750;
t.Scedilla = 556;
t.lcaron = 394;
t.Kcommaaccent = 778;
t.Lacute = 667;
t.trademark = 1000;
t.edotaccent = 444;
t.Igrave = 389;
t.Imacron = 389;
t.Lcaron = 667;
t.onehalf = 750;
t.lessequal = 549;
t.ocircumflex = 500;
t.ntilde = 556;
t.Uhungarumlaut = 722;
t.Eacute = 667;
t.emacron = 444;
t.gbreve = 500;
t.onequarter = 750;
t.Scaron = 556;
t.Scommaaccent = 556;
t.Ohungarumlaut = 778;
t.degree = 400;
t.ograve = 500;
t.Ccaron = 722;
t.ugrave = 556;
t.radical = 549;
t.Dcaron = 722;
t.rcommaaccent = 444;
t.Ntilde = 722;
t.otilde = 500;
t.Rcommaaccent = 722;
t.Lcommaaccent = 667;
t.Atilde = 722;
t.Aogonek = 722;
t.Aring = 722;
t.Otilde = 778;
t.zdotaccent = 444;
t.Ecaron = 667;
t.Iogonek = 389;
t.kcommaaccent = 556;
t.minus = 570;
t.Icircumflex = 389;
t.ncaron = 556;
t.tcommaaccent = 333;
t.logicalnot = 570;
t.odieresis = 500;
t.udieresis = 556;
t.notequal = 549;
t.gcommaaccent = 500;
t.eth = 500;
t.zcaron = 444;
t.ncommaaccent = 556;
t.onesuperior = 300;
t.imacron = 278;
t.Euro = 500;
});
t["Times-BoldItalic"] = getLookupTableFactory(function (t) {
t.space = 250;
t.exclam = 389;
t.quotedbl = 555;
t.numbersign = 500;
t.dollar = 500;
t.percent = 833;
t.ampersand = 778;
t.quoteright = 333;
t.parenleft = 333;
t.parenright = 333;
t.asterisk = 500;
t.plus = 570;
t.comma = 250;
t.hyphen = 333;
t.period = 250;
t.slash = 278;
t.zero = 500;
t.one = 500;
t.two = 500;
t.three = 500;
t.four = 500;
t.five = 500;
t.six = 500;
t.seven = 500;
t.eight = 500;
t.nine = 500;
t.colon = 333;
t.semicolon = 333;
t.less = 570;
t.equal = 570;
t.greater = 570;
t.question = 500;
t.at = 832;
t.A = 667;
t.B = 667;
t.C = 667;
t.D = 722;
t.E = 667;
t.F = 667;
t.G = 722;
t.H = 778;
t.I = 389;
t.J = 500;
t.K = 667;
t.L = 611;
t.M = 889;
t.N = 722;
t.O = 722;
t.P = 611;
t.Q = 722;
t.R = 667;
t.S = 556;
t.T = 611;
t.U = 722;
t.V = 667;
t.W = 889;
t.X = 667;
t.Y = 611;
t.Z = 611;
t.bracketleft = 333;
t.backslash = 278;
t.bracketright = 333;
t.asciicircum = 570;
t.underscore = 500;
t.quoteleft = 333;
t.a = 500;
t.b = 500;
t.c = 444;
t.d = 500;
t.e = 444;
t.f = 333;
t.g = 500;
t.h = 556;
t.i = 278;
t.j = 278;
t.k = 500;
t.l = 278;
t.m = 778;
t.n = 556;
t.o = 500;
t.p = 500;
t.q = 500;
t.r = 389;
t.s = 389;
t.t = 278;
t.u = 556;
t.v = 444;
t.w = 667;
t.x = 500;
t.y = 444;
t.z = 389;
t.braceleft = 348;
t.bar = 220;
t.braceright = 348;
t.asciitilde = 570;
t.exclamdown = 389;
t.cent = 500;
t.sterling = 500;
t.fraction = 167;
t.yen = 500;
t.florin = 500;
t.section = 500;
t.currency = 500;
t.quotesingle = 278;
t.quotedblleft = 500;
t.guillemotleft = 500;
t.guilsinglleft = 333;
t.guilsinglright = 333;
t.fi = 556;
t.fl = 556;
t.endash = 500;
t.dagger = 500;
t.daggerdbl = 500;
t.periodcentered = 250;
t.paragraph = 500;
t.bullet = 350;
t.quotesinglbase = 333;
t.quotedblbase = 500;
t.quotedblright = 500;
t.guillemotright = 500;
t.ellipsis = 1000;
t.perthousand = 1000;
t.questiondown = 500;
t.grave = 333;
t.acute = 333;
t.circumflex = 333;
t.tilde = 333;
t.macron = 333;
t.breve = 333;
t.dotaccent = 333;
t.dieresis = 333;
t.ring = 333;
t.cedilla = 333;
t.hungarumlaut = 333;
t.ogonek = 333;
t.caron = 333;
t.emdash = 1000;
t.AE = 944;
t.ordfeminine = 266;
t.Lslash = 611;
t.Oslash = 722;
t.OE = 944;
t.ordmasculine = 300;
t.ae = 722;
t.dotlessi = 278;
t.lslash = 278;
t.oslash = 500;
t.oe = 722;
t.germandbls = 500;
t.Idieresis = 389;
t.eacute = 444;
t.abreve = 500;
t.uhungarumlaut = 556;
t.ecaron = 444;
t.Ydieresis = 611;
t.divide = 570;
t.Yacute = 611;
t.Acircumflex = 667;
t.aacute = 500;
t.Ucircumflex = 722;
t.yacute = 444;
t.scommaaccent = 389;
t.ecircumflex = 444;
t.Uring = 722;
t.Udieresis = 722;
t.aogonek = 500;
t.Uacute = 722;
t.uogonek = 556;
t.Edieresis = 667;
t.Dcroat = 722;
t.commaaccent = 250;
t.copyright = 747;
t.Emacron = 667;
t.ccaron = 444;
t.aring = 500;
t.Ncommaaccent = 722;
t.lacute = 278;
t.agrave = 500;
t.Tcommaaccent = 611;
t.Cacute = 667;
t.atilde = 500;
t.Edotaccent = 667;
t.scaron = 389;
t.scedilla = 389;
t.iacute = 278;
t.lozenge = 494;
t.Rcaron = 667;
t.Gcommaaccent = 722;
t.ucircumflex = 556;
t.acircumflex = 500;
t.Amacron = 667;
t.rcaron = 389;
t.ccedilla = 444;
t.Zdotaccent = 611;
t.Thorn = 611;
t.Omacron = 722;
t.Racute = 667;
t.Sacute = 556;
t.dcaron = 608;
t.Umacron = 722;
t.uring = 556;
t.threesuperior = 300;
t.Ograve = 722;
t.Agrave = 667;
t.Abreve = 667;
t.multiply = 570;
t.uacute = 556;
t.Tcaron = 611;
t.partialdiff = 494;
t.ydieresis = 444;
t.Nacute = 722;
t.icircumflex = 278;
t.Ecircumflex = 667;
t.adieresis = 500;
t.edieresis = 444;
t.cacute = 444;
t.nacute = 556;
t.umacron = 556;
t.Ncaron = 722;
t.Iacute = 389;
t.plusminus = 570;
t.brokenbar = 220;
t.registered = 747;
t.Gbreve = 722;
t.Idotaccent = 389;
t.summation = 600;
t.Egrave = 667;
t.racute = 389;
t.omacron = 500;
t.Zacute = 611;
t.Zcaron = 611;
t.greaterequal = 549;
t.Eth = 722;
t.Ccedilla = 667;
t.lcommaaccent = 278;
t.tcaron = 366;
t.eogonek = 444;
t.Uogonek = 722;
t.Aacute = 667;
t.Adieresis = 667;
t.egrave = 444;
t.zacute = 389;
t.iogonek = 278;
t.Oacute = 722;
t.oacute = 500;
t.amacron = 500;
t.sacute = 389;
t.idieresis = 278;
t.Ocircumflex = 722;
t.Ugrave = 722;
t.Delta = 612;
t.thorn = 500;
t.twosuperior = 300;
t.Odieresis = 722;
t.mu = 576;
t.igrave = 278;
t.ohungarumlaut = 500;
t.Eogonek = 667;
t.dcroat = 500;
t.threequarters = 750;
t.Scedilla = 556;
t.lcaron = 382;
t.Kcommaaccent = 667;
t.Lacute = 611;
t.trademark = 1000;
t.edotaccent = 444;
t.Igrave = 389;
t.Imacron = 389;
t.Lcaron = 611;
t.onehalf = 750;
t.lessequal = 549;
t.ocircumflex = 500;
t.ntilde = 556;
t.Uhungarumlaut = 722;
t.Eacute = 667;
t.emacron = 444;
t.gbreve = 500;
t.onequarter = 750;
t.Scaron = 556;
t.Scommaaccent = 556;
t.Ohungarumlaut = 722;
t.degree = 400;
t.ograve = 500;
t.Ccaron = 667;
t.ugrave = 556;
t.radical = 549;
t.Dcaron = 722;
t.rcommaaccent = 389;
t.Ntilde = 722;
t.otilde = 500;
t.Rcommaaccent = 667;
t.Lcommaaccent = 611;
t.Atilde = 667;
t.Aogonek = 667;
t.Aring = 667;
t.Otilde = 722;
t.zdotaccent = 389;
t.Ecaron = 667;
t.Iogonek = 389;
t.kcommaaccent = 500;
t.minus = 606;
t.Icircumflex = 389;
t.ncaron = 556;
t.tcommaaccent = 278;
t.logicalnot = 606;
t.odieresis = 500;
t.udieresis = 556;
t.notequal = 549;
t.gcommaaccent = 500;
t.eth = 500;
t.zcaron = 389;
t.ncommaaccent = 556;
t.onesuperior = 300;
t.imacron = 278;
t.Euro = 500;
});
t["Times-Italic"] = getLookupTableFactory(function (t) {
t.space = 250;
t.exclam = 333;
t.quotedbl = 420;
t.numbersign = 500;
t.dollar = 500;
t.percent = 833;
t.ampersand = 778;
t.quoteright = 333;
t.parenleft = 333;
t.parenright = 333;
t.asterisk = 500;
t.plus = 675;
t.comma = 250;
t.hyphen = 333;
t.period = 250;
t.slash = 278;
t.zero = 500;
t.one = 500;
t.two = 500;
t.three = 500;
t.four = 500;
t.five = 500;
t.six = 500;
t.seven = 500;
t.eight = 500;
t.nine = 500;
t.colon = 333;
t.semicolon = 333;
t.less = 675;
t.equal = 675;
t.greater = 675;
t.question = 500;
t.at = 920;
t.A = 611;
t.B = 611;
t.C = 667;
t.D = 722;
t.E = 611;
t.F = 611;
t.G = 722;
t.H = 722;
t.I = 333;
t.J = 444;
t.K = 667;
t.L = 556;
t.M = 833;
t.N = 667;
t.O = 722;
t.P = 611;
t.Q = 722;
t.R = 611;
t.S = 500;
t.T = 556;
t.U = 722;
t.V = 611;
t.W = 833;
t.X = 611;
t.Y = 556;
t.Z = 556;
t.bracketleft = 389;
t.backslash = 278;
t.bracketright = 389;
t.asciicircum = 422;
t.underscore = 500;
t.quoteleft = 333;
t.a = 500;
t.b = 500;
t.c = 444;
t.d = 500;
t.e = 444;
t.f = 278;
t.g = 500;
t.h = 500;
t.i = 278;
t.j = 278;
t.k = 444;
t.l = 278;
t.m = 722;
t.n = 500;
t.o = 500;
t.p = 500;
t.q = 500;
t.r = 389;
t.s = 389;
t.t = 278;
t.u = 500;
t.v = 444;
t.w = 667;
t.x = 444;
t.y = 444;
t.z = 389;
t.braceleft = 400;
t.bar = 275;
t.braceright = 400;
t.asciitilde = 541;
t.exclamdown = 389;
t.cent = 500;
t.sterling = 500;
t.fraction = 167;
t.yen = 500;
t.florin = 500;
t.section = 500;
t.currency = 500;
t.quotesingle = 214;
t.quotedblleft = 556;
t.guillemotleft = 500;
t.guilsinglleft = 333;
t.guilsinglright = 333;
t.fi = 500;
t.fl = 500;
t.endash = 500;
t.dagger = 500;
t.daggerdbl = 500;
t.periodcentered = 250;
t.paragraph = 523;
t.bullet = 350;
t.quotesinglbase = 333;
t.quotedblbase = 556;
t.quotedblright = 556;
t.guillemotright = 500;
t.ellipsis = 889;
t.perthousand = 1000;
t.questiondown = 500;
t.grave = 333;
t.acute = 333;
t.circumflex = 333;
t.tilde = 333;
t.macron = 333;
t.breve = 333;
t.dotaccent = 333;
t.dieresis = 333;
t.ring = 333;
t.cedilla = 333;
t.hungarumlaut = 333;
t.ogonek = 333;
t.caron = 333;
t.emdash = 889;
t.AE = 889;
t.ordfeminine = 276;
t.Lslash = 556;
t.Oslash = 722;
t.OE = 944;
t.ordmasculine = 310;
t.ae = 667;
t.dotlessi = 278;
t.lslash = 278;
t.oslash = 500;
t.oe = 667;
t.germandbls = 500;
t.Idieresis = 333;
t.eacute = 444;
t.abreve = 500;
t.uhungarumlaut = 500;
t.ecaron = 444;
t.Ydieresis = 556;
t.divide = 675;
t.Yacute = 556;
t.Acircumflex = 611;
t.aacute = 500;
t.Ucircumflex = 722;
t.yacute = 444;
t.scommaaccent = 389;
t.ecircumflex = 444;
t.Uring = 722;
t.Udieresis = 722;
t.aogonek = 500;
t.Uacute = 722;
t.uogonek = 500;
t.Edieresis = 611;
t.Dcroat = 722;
t.commaaccent = 250;
t.copyright = 760;
t.Emacron = 611;
t.ccaron = 444;
t.aring = 500;
t.Ncommaaccent = 667;
t.lacute = 278;
t.agrave = 500;
t.Tcommaaccent = 556;
t.Cacute = 667;
t.atilde = 500;
t.Edotaccent = 611;
t.scaron = 389;
t.scedilla = 389;
t.iacute = 278;
t.lozenge = 471;
t.Rcaron = 611;
t.Gcommaaccent = 722;
t.ucircumflex = 500;
t.acircumflex = 500;
t.Amacron = 611;
t.rcaron = 389;
t.ccedilla = 444;
t.Zdotaccent = 556;
t.Thorn = 611;
t.Omacron = 722;
t.Racute = 611;
t.Sacute = 500;
t.dcaron = 544;
t.Umacron = 722;
t.uring = 500;
t.threesuperior = 300;
t.Ograve = 722;
t.Agrave = 611;
t.Abreve = 611;
t.multiply = 675;
t.uacute = 500;
t.Tcaron = 556;
t.partialdiff = 476;
t.ydieresis = 444;
t.Nacute = 667;
t.icircumflex = 278;
t.Ecircumflex = 611;
t.adieresis = 500;
t.edieresis = 444;
t.cacute = 444;
t.nacute = 500;
t.umacron = 500;
t.Ncaron = 667;
t.Iacute = 333;
t.plusminus = 675;
t.brokenbar = 275;
t.registered = 760;
t.Gbreve = 722;
t.Idotaccent = 333;
t.summation = 600;
t.Egrave = 611;
t.racute = 389;
t.omacron = 500;
t.Zacute = 556;
t.Zcaron = 556;
t.greaterequal = 549;
t.Eth = 722;
t.Ccedilla = 667;
t.lcommaaccent = 278;
t.tcaron = 300;
t.eogonek = 444;
t.Uogonek = 722;
t.Aacute = 611;
t.Adieresis = 611;
t.egrave = 444;
t.zacute = 389;
t.iogonek = 278;
t.Oacute = 722;
t.oacute = 500;
t.amacron = 500;
t.sacute = 389;
t.idieresis = 278;
t.Ocircumflex = 722;
t.Ugrave = 722;
t.Delta = 612;
t.thorn = 500;
t.twosuperior = 300;
t.Odieresis = 722;
t.mu = 500;
t.igrave = 278;
t.ohungarumlaut = 500;
t.Eogonek = 611;
t.dcroat = 500;
t.threequarters = 750;
t.Scedilla = 500;
t.lcaron = 300;
t.Kcommaaccent = 667;
t.Lacute = 556;
t.trademark = 980;
t.edotaccent = 444;
t.Igrave = 333;
t.Imacron = 333;
t.Lcaron = 611;
t.onehalf = 750;
t.lessequal = 549;
t.ocircumflex = 500;
t.ntilde = 500;
t.Uhungarumlaut = 722;
t.Eacute = 611;
t.emacron = 444;
t.gbreve = 500;
t.onequarter = 750;
t.Scaron = 500;
t.Scommaaccent = 500;
t.Ohungarumlaut = 722;
t.degree = 400;
t.ograve = 500;
t.Ccaron = 667;
t.ugrave = 500;
t.radical = 453;
t.Dcaron = 722;
t.rcommaaccent = 389;
t.Ntilde = 667;
t.otilde = 500;
t.Rcommaaccent = 611;
t.Lcommaaccent = 556;
t.Atilde = 611;
t.Aogonek = 611;
t.Aring = 611;
t.Otilde = 722;
t.zdotaccent = 389;
t.Ecaron = 611;
t.Iogonek = 333;
t.kcommaaccent = 444;
t.minus = 675;
t.Icircumflex = 333;
t.ncaron = 500;
t.tcommaaccent = 278;
t.logicalnot = 675;
t.odieresis = 500;
t.udieresis = 500;
t.notequal = 549;
t.gcommaaccent = 500;
t.eth = 500;
t.zcaron = 389;
t.ncommaaccent = 500;
t.onesuperior = 300;
t.imacron = 278;
t.Euro = 500;
});
t.ZapfDingbats = getLookupTableFactory(function (t) {
t.space = 278;
t.a1 = 974;
t.a2 = 961;
t.a202 = 974;
t.a3 = 980;
t.a4 = 719;
t.a5 = 789;
t.a119 = 790;
t.a118 = 791;
t.a117 = 690;
t.a11 = 960;
t.a12 = 939;
t.a13 = 549;
t.a14 = 855;
t.a15 = 911;
t.a16 = 933;
t.a105 = 911;
t.a17 = 945;
t.a18 = 974;
t.a19 = 755;
t.a20 = 846;
t.a21 = 762;
t.a22 = 761;
t.a23 = 571;
t.a24 = 677;
t.a25 = 763;
t.a26 = 760;
t.a27 = 759;
t.a28 = 754;
t.a6 = 494;
t.a7 = 552;
t.a8 = 537;
t.a9 = 577;
t.a10 = 692;
t.a29 = 786;
t.a30 = 788;
t.a31 = 788;
t.a32 = 790;
t.a33 = 793;
t.a34 = 794;
t.a35 = 816;
t.a36 = 823;
t.a37 = 789;
t.a38 = 841;
t.a39 = 823;
t.a40 = 833;
t.a41 = 816;
t.a42 = 831;
t.a43 = 923;
t.a44 = 744;
t.a45 = 723;
t.a46 = 749;
t.a47 = 790;
t.a48 = 792;
t.a49 = 695;
t.a50 = 776;
t.a51 = 768;
t.a52 = 792;
t.a53 = 759;
t.a54 = 707;
t.a55 = 708;
t.a56 = 682;
t.a57 = 701;
t.a58 = 826;
t.a59 = 815;
t.a60 = 789;
t.a61 = 789;
t.a62 = 707;
t.a63 = 687;
t.a64 = 696;
t.a65 = 689;
t.a66 = 786;
t.a67 = 787;
t.a68 = 713;
t.a69 = 791;
t.a70 = 785;
t.a71 = 791;
t.a72 = 873;
t.a73 = 761;
t.a74 = 762;
t.a203 = 762;
t.a75 = 759;
t.a204 = 759;
t.a76 = 892;
t.a77 = 892;
t.a78 = 788;
t.a79 = 784;
t.a81 = 438;
t.a82 = 138;
t.a83 = 277;
t.a84 = 415;
t.a97 = 392;
t.a98 = 392;
t.a99 = 668;
t.a100 = 668;
t.a89 = 390;
t.a90 = 390;
t.a93 = 317;
t.a94 = 317;
t.a91 = 276;
t.a92 = 276;
t.a205 = 509;
t.a85 = 509;
t.a206 = 410;
t.a86 = 410;
t.a87 = 234;
t.a88 = 234;
t.a95 = 334;
t.a96 = 334;
t.a101 = 732;
t.a102 = 544;
t.a103 = 544;
t.a104 = 910;
t.a106 = 667;
t.a107 = 760;
t.a108 = 760;
t.a112 = 776;
t.a111 = 595;
t.a110 = 694;
t.a109 = 626;
t.a120 = 788;
t.a121 = 788;
t.a122 = 788;
t.a123 = 788;
t.a124 = 788;
t.a125 = 788;
t.a126 = 788;
t.a127 = 788;
t.a128 = 788;
t.a129 = 788;
t.a130 = 788;
t.a131 = 788;
t.a132 = 788;
t.a133 = 788;
t.a134 = 788;
t.a135 = 788;
t.a136 = 788;
t.a137 = 788;
t.a138 = 788;
t.a139 = 788;
t.a140 = 788;
t.a141 = 788;
t.a142 = 788;
t.a143 = 788;
t.a144 = 788;
t.a145 = 788;
t.a146 = 788;
t.a147 = 788;
t.a148 = 788;
t.a149 = 788;
t.a150 = 788;
t.a151 = 788;
t.a152 = 788;
t.a153 = 788;
t.a154 = 788;
t.a155 = 788;
t.a156 = 788;
t.a157 = 788;
t.a158 = 788;
t.a159 = 788;
t.a160 = 894;
t.a161 = 838;
t.a163 = 1016;
t.a164 = 458;
t.a196 = 748;
t.a165 = 924;
t.a192 = 748;
t.a166 = 918;
t.a167 = 927;
t.a168 = 928;
t.a169 = 928;
t.a170 = 834;
t.a171 = 873;
t.a172 = 828;
t.a173 = 924;
t.a162 = 924;
t.a174 = 917;
t.a175 = 930;
t.a176 = 931;
t.a177 = 463;
t.a178 = 883;
t.a179 = 836;
t.a193 = 836;
t.a180 = 867;
t.a199 = 867;
t.a181 = 696;
t.a200 = 696;
t.a182 = 874;
t.a201 = 874;
t.a183 = 760;
t.a184 = 946;
t.a197 = 771;
t.a185 = 865;
t.a194 = 771;
t.a198 = 888;
t.a186 = 967;
t.a195 = 888;
t.a187 = 831;
t.a188 = 873;
t.a189 = 927;
t.a190 = 970;
t.a191 = 918;
});
});
const getFontBasicMetrics = getLookupTableFactory(function (t) {
t.Courier = {
ascent: 629,
descent: -157,
capHeight: 562,
xHeight: -426
};
t["Courier-Bold"] = {
ascent: 629,
descent: -157,
capHeight: 562,
xHeight: 439
};
t["Courier-Oblique"] = {
ascent: 629,
descent: -157,
capHeight: 562,
xHeight: 426
};
t["Courier-BoldOblique"] = {
ascent: 629,
descent: -157,
capHeight: 562,
xHeight: 426
};
t.Helvetica = {
ascent: 718,
descent: -207,
capHeight: 718,
xHeight: 523
};
t["Helvetica-Bold"] = {
ascent: 718,
descent: -207,
capHeight: 718,
xHeight: 532
};
t["Helvetica-Oblique"] = {
ascent: 718,
descent: -207,
capHeight: 718,
xHeight: 523
};
t["Helvetica-BoldOblique"] = {
ascent: 718,
descent: -207,
capHeight: 718,
xHeight: 532
};
t["Times-Roman"] = {
ascent: 683,
descent: -217,
capHeight: 662,
xHeight: 450
};
t["Times-Bold"] = {
ascent: 683,
descent: -217,
capHeight: 676,
xHeight: 461
};
t["Times-Italic"] = {
ascent: 683,
descent: -217,
capHeight: 653,
xHeight: 441
};
t["Times-BoldItalic"] = {
ascent: 683,
descent: -217,
capHeight: 669,
xHeight: 462
};
t.Symbol = {
ascent: Math.NaN,
descent: Math.NaN,
capHeight: Math.NaN,
xHeight: Math.NaN
};
t.ZapfDingbats = {
ascent: Math.NaN,
descent: Math.NaN,
capHeight: Math.NaN,
xHeight: Math.NaN
};
});
;// ./src/core/glyf.js
const ON_CURVE_POINT = 1 << 0;
const X_SHORT_VECTOR = 1 << 1;
const Y_SHORT_VECTOR = 1 << 2;
const REPEAT_FLAG = 1 << 3;
const X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR = 1 << 4;
const Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR = 1 << 5;
const OVERLAP_SIMPLE = 1 << 6;
const ARG_1_AND_2_ARE_WORDS = 1 << 0;
const ARGS_ARE_XY_VALUES = 1 << 1;
const WE_HAVE_A_SCALE = 1 << 3;
const MORE_COMPONENTS = 1 << 5;
const WE_HAVE_AN_X_AND_Y_SCALE = 1 << 6;
const WE_HAVE_A_TWO_BY_TWO = 1 << 7;
const WE_HAVE_INSTRUCTIONS = 1 << 8;
class GlyfTable {
constructor({
glyfTable,
isGlyphLocationsLong,
locaTable,
numGlyphs
}) {
this.glyphs = [];
const loca = new DataView(locaTable.buffer, locaTable.byteOffset, locaTable.byteLength);
const glyf = new DataView(glyfTable.buffer, glyfTable.byteOffset, glyfTable.byteLength);
const offsetSize = isGlyphLocationsLong ? 4 : 2;
let prev = isGlyphLocationsLong ? loca.getUint32(0) : 2 * loca.getUint16(0);
let pos = 0;
for (let i = 0; i < numGlyphs; i++) {
pos += offsetSize;
const next = isGlyphLocationsLong ? loca.getUint32(pos) : 2 * loca.getUint16(pos);
if (next === prev) {
this.glyphs.push(new Glyph({}));
continue;
}
const glyph = Glyph.parse(prev, glyf);
this.glyphs.push(glyph);
prev = next;
}
}
getSize() {
return Math.sumPrecise(this.glyphs.map(g => g.getSize() + 3 & ~3));
}
write() {
const totalSize = this.getSize();
const glyfTable = new DataView(new ArrayBuffer(totalSize));
const isLocationLong = totalSize > 0x1fffe;
const offsetSize = isLocationLong ? 4 : 2;
const locaTable = new DataView(new ArrayBuffer((this.glyphs.length + 1) * offsetSize));
if (isLocationLong) {
locaTable.setUint32(0, 0);
} else {
locaTable.setUint16(0, 0);
}
let pos = 0;
let locaIndex = 0;
for (const glyph of this.glyphs) {
pos += glyph.write(pos, glyfTable);
pos = pos + 3 & ~3;
locaIndex += offsetSize;
if (isLocationLong) {
locaTable.setUint32(locaIndex, pos);
} else {
locaTable.setUint16(locaIndex, pos >> 1);
}
}
return {
isLocationLong,
loca: new Uint8Array(locaTable.buffer),
glyf: new Uint8Array(glyfTable.buffer)
};
}
scale(factors) {
for (let i = 0, ii = this.glyphs.length; i < ii; i++) {
this.glyphs[i].scale(factors[i]);
}
}
}
class Glyph {
constructor({
header = null,
simple = null,
composites = null
}) {
this.header = header;
this.simple = simple;
this.composites = composites;
}
static parse(pos, glyf) {
const [read, header] = GlyphHeader.parse(pos, glyf);
pos += read;
if (header.numberOfContours < 0) {
const composites = [];
while (true) {
const [n, composite] = CompositeGlyph.parse(pos, glyf);
pos += n;
composites.push(composite);
if (!(composite.flags & MORE_COMPONENTS)) {
break;
}
}
return new Glyph({
header,
composites
});
}
const simple = SimpleGlyph.parse(pos, glyf, header.numberOfContours);
return new Glyph({
header,
simple
});
}
getSize() {
if (!this.header) {
return 0;
}
const size = this.simple ? this.simple.getSize() : Math.sumPrecise(this.composites.map(c => c.getSize()));
return this.header.getSize() + size;
}
write(pos, buf) {
if (!this.header) {
return 0;
}
const spos = pos;
pos += this.header.write(pos, buf);
if (this.simple) {
pos += this.simple.write(pos, buf);
} else {
for (const composite of this.composites) {
pos += composite.write(pos, buf);
}
}
return pos - spos;
}
scale(factor) {
if (!this.header) {
return;
}
const xMiddle = (this.header.xMin + this.header.xMax) / 2;
this.header.scale(xMiddle, factor);
if (this.simple) {
this.simple.scale(xMiddle, factor);
} else {
for (const composite of this.composites) {
composite.scale(xMiddle, factor);
}
}
}
}
class GlyphHeader {
constructor({
numberOfContours,
xMin,
yMin,
xMax,
yMax
}) {
this.numberOfContours = numberOfContours;
this.xMin = xMin;
this.yMin = yMin;
this.xMax = xMax;
this.yMax = yMax;
}
static parse(pos, glyf) {
return [10, new GlyphHeader({
numberOfContours: glyf.getInt16(pos),
xMin: glyf.getInt16(pos + 2),
yMin: glyf.getInt16(pos + 4),
xMax: glyf.getInt16(pos + 6),
yMax: glyf.getInt16(pos + 8)
})];
}
getSize() {
return 10;
}
write(pos, buf) {
buf.setInt16(pos, this.numberOfContours);
buf.setInt16(pos + 2, this.xMin);
buf.setInt16(pos + 4, this.yMin);
buf.setInt16(pos + 6, this.xMax);
buf.setInt16(pos + 8, this.yMax);
return 10;
}
scale(x, factor) {
this.xMin = Math.round(x + (this.xMin - x) * factor);
this.xMax = Math.round(x + (this.xMax - x) * factor);
}
}
class Contour {
constructor({
flags,
xCoordinates,
yCoordinates
}) {
this.xCoordinates = xCoordinates;
this.yCoordinates = yCoordinates;
this.flags = flags;
}
}
class SimpleGlyph {
constructor({
contours,
instructions
}) {
this.contours = contours;
this.instructions = instructions;
}
static parse(pos, glyf, numberOfContours) {
const endPtsOfContours = [];
for (let i = 0; i < numberOfContours; i++) {
const endPt = glyf.getUint16(pos);
pos += 2;
endPtsOfContours.push(endPt);
}
const numberOfPt = endPtsOfContours[numberOfContours - 1] + 1;
const instructionLength = glyf.getUint16(pos);
pos += 2;
const instructions = new Uint8Array(glyf).slice(pos, pos + instructionLength);
pos += instructionLength;
const flags = [];
for (let i = 0; i < numberOfPt; pos++, i++) {
let flag = glyf.getUint8(pos);
flags.push(flag);
if (flag & REPEAT_FLAG) {
const count = glyf.getUint8(++pos);
flag ^= REPEAT_FLAG;
for (let m = 0; m < count; m++) {
flags.push(flag);
}
i += count;
}
}
const allXCoordinates = [];
let xCoordinates = [];
let yCoordinates = [];
let pointFlags = [];
const contours = [];
let endPtsOfContoursIndex = 0;
let lastCoordinate = 0;
for (let i = 0; i < numberOfPt; i++) {
const flag = flags[i];
if (flag & X_SHORT_VECTOR) {
const x = glyf.getUint8(pos++);
lastCoordinate += flag & X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR ? x : -x;
xCoordinates.push(lastCoordinate);
} else if (flag & X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR) {
xCoordinates.push(lastCoordinate);
} else {
lastCoordinate += glyf.getInt16(pos);
pos += 2;
xCoordinates.push(lastCoordinate);
}
if (endPtsOfContours[endPtsOfContoursIndex] === i) {
endPtsOfContoursIndex++;
allXCoordinates.push(xCoordinates);
xCoordinates = [];
}
}
lastCoordinate = 0;
endPtsOfContoursIndex = 0;
for (let i = 0; i < numberOfPt; i++) {
const flag = flags[i];
if (flag & Y_SHORT_VECTOR) {
const y = glyf.getUint8(pos++);
lastCoordinate += flag & Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR ? y : -y;
yCoordinates.push(lastCoordinate);
} else if (flag & Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR) {
yCoordinates.push(lastCoordinate);
} else {
lastCoordinate += glyf.getInt16(pos);
pos += 2;
yCoordinates.push(lastCoordinate);
}
pointFlags.push(flag & ON_CURVE_POINT | flag & OVERLAP_SIMPLE);
if (endPtsOfContours[endPtsOfContoursIndex] === i) {
xCoordinates = allXCoordinates[endPtsOfContoursIndex];
endPtsOfContoursIndex++;
contours.push(new Contour({
flags: pointFlags,
xCoordinates,
yCoordinates
}));
yCoordinates = [];
pointFlags = [];
}
}
return new SimpleGlyph({
contours,
instructions
});
}
getSize() {
let size = this.contours.length * 2 + 2 + this.instructions.length;
let lastX = 0;
let lastY = 0;
for (const contour of this.contours) {
size += contour.flags.length;
for (let i = 0, ii = contour.xCoordinates.length; i < ii; i++) {
const x = contour.xCoordinates[i];
const y = contour.yCoordinates[i];
let abs = Math.abs(x - lastX);
if (abs > 255) {
size += 2;
} else if (abs > 0) {
size += 1;
}
lastX = x;
abs = Math.abs(y - lastY);
if (abs > 255) {
size += 2;
} else if (abs > 0) {
size += 1;
}
lastY = y;
}
}
return size;
}
write(pos, buf) {
const spos = pos;
const xCoordinates = [];
const yCoordinates = [];
const flags = [];
let lastX = 0;
let lastY = 0;
for (const contour of this.contours) {
for (let i = 0, ii = contour.xCoordinates.length; i < ii; i++) {
let flag = contour.flags[i];
const x = contour.xCoordinates[i];
let delta = x - lastX;
if (delta === 0) {
flag |= X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR;
xCoordinates.push(0);
} else {
const abs = Math.abs(delta);
if (abs <= 255) {
flag |= delta >= 0 ? X_SHORT_VECTOR | X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR : X_SHORT_VECTOR;
xCoordinates.push(abs);
} else {
xCoordinates.push(delta);
}
}
lastX = x;
const y = contour.yCoordinates[i];
delta = y - lastY;
if (delta === 0) {
flag |= Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR;
yCoordinates.push(0);
} else {
const abs = Math.abs(delta);
if (abs <= 255) {
flag |= delta >= 0 ? Y_SHORT_VECTOR | Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR : Y_SHORT_VECTOR;
yCoordinates.push(abs);
} else {
yCoordinates.push(delta);
}
}
lastY = y;
flags.push(flag);
}
buf.setUint16(pos, xCoordinates.length - 1);
pos += 2;
}
buf.setUint16(pos, this.instructions.length);
pos += 2;
if (this.instructions.length) {
new Uint8Array(buf.buffer, 0, buf.buffer.byteLength).set(this.instructions, pos);
pos += this.instructions.length;
}
for (const flag of flags) {
buf.setUint8(pos++, flag);
}
for (let i = 0, ii = xCoordinates.length; i < ii; i++) {
const x = xCoordinates[i];
const flag = flags[i];
if (flag & X_SHORT_VECTOR) {
buf.setUint8(pos++, x);
} else if (!(flag & X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR)) {
buf.setInt16(pos, x);
pos += 2;
}
}
for (let i = 0, ii = yCoordinates.length; i < ii; i++) {
const y = yCoordinates[i];
const flag = flags[i];
if (flag & Y_SHORT_VECTOR) {
buf.setUint8(pos++, y);
} else if (!(flag & Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR)) {
buf.setInt16(pos, y);
pos += 2;
}
}
return pos - spos;
}
scale(x, factor) {
for (const contour of this.contours) {
if (contour.xCoordinates.length === 0) {
continue;
}
for (let i = 0, ii = contour.xCoordinates.length; i < ii; i++) {
contour.xCoordinates[i] = Math.round(x + (contour.xCoordinates[i] - x) * factor);
}
}
}
}
class CompositeGlyph {
constructor({
flags,
glyphIndex,
argument1,
argument2,
transf,
instructions
}) {
this.flags = flags;
this.glyphIndex = glyphIndex;
this.argument1 = argument1;
this.argument2 = argument2;
this.transf = transf;
this.instructions = instructions;
}
static parse(pos, glyf) {
const spos = pos;
const transf = [];
let flags = glyf.getUint16(pos);
const glyphIndex = glyf.getUint16(pos + 2);
pos += 4;
let argument1, argument2;
if (flags & ARG_1_AND_2_ARE_WORDS) {
if (flags & ARGS_ARE_XY_VALUES) {
argument1 = glyf.getInt16(pos);
argument2 = glyf.getInt16(pos + 2);
} else {
argument1 = glyf.getUint16(pos);
argument2 = glyf.getUint16(pos + 2);
}
pos += 4;
flags ^= ARG_1_AND_2_ARE_WORDS;
} else {
if (flags & ARGS_ARE_XY_VALUES) {
argument1 = glyf.getInt8(pos);
argument2 = glyf.getInt8(pos + 1);
} else {
argument1 = glyf.getUint8(pos);
argument2 = glyf.getUint8(pos + 1);
}
pos += 2;
}
if (flags & WE_HAVE_A_SCALE) {
transf.push(glyf.getUint16(pos));
pos += 2;
} else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) {
transf.push(glyf.getUint16(pos), glyf.getUint16(pos + 2));
pos += 4;
} else if (flags & WE_HAVE_A_TWO_BY_TWO) {
transf.push(glyf.getUint16(pos), glyf.getUint16(pos + 2), glyf.getUint16(pos + 4), glyf.getUint16(pos + 6));
pos += 8;
}
let instructions = null;
if (flags & WE_HAVE_INSTRUCTIONS) {
const instructionLength = glyf.getUint16(pos);
pos += 2;
instructions = new Uint8Array(glyf).slice(pos, pos + instructionLength);
pos += instructionLength;
}
return [pos - spos, new CompositeGlyph({
flags,
glyphIndex,
argument1,
argument2,
transf,
instructions
})];
}
getSize() {
let size = 2 + 2 + this.transf.length * 2;
if (this.flags & WE_HAVE_INSTRUCTIONS) {
size += 2 + this.instructions.length;
}
size += 2;
if (this.flags & 2) {
if (!(this.argument1 >= -128 && this.argument1 <= 127 && this.argument2 >= -128 && this.argument2 <= 127)) {
size += 2;
}
} else if (!(this.argument1 >= 0 && this.argument1 <= 255 && this.argument2 >= 0 && this.argument2 <= 255)) {
size += 2;
}
return size;
}
write(pos, buf) {
const spos = pos;
if (this.flags & ARGS_ARE_XY_VALUES) {
if (!(this.argument1 >= -128 && this.argument1 <= 127 && this.argument2 >= -128 && this.argument2 <= 127)) {
this.flags |= ARG_1_AND_2_ARE_WORDS;
}
} else if (!(this.argument1 >= 0 && this.argument1 <= 255 && this.argument2 >= 0 && this.argument2 <= 255)) {
this.flags |= ARG_1_AND_2_ARE_WORDS;
}
buf.setUint16(pos, this.flags);
buf.setUint16(pos + 2, this.glyphIndex);
pos += 4;
if (this.flags & ARG_1_AND_2_ARE_WORDS) {
if (this.flags & ARGS_ARE_XY_VALUES) {
buf.setInt16(pos, this.argument1);
buf.setInt16(pos + 2, this.argument2);
} else {
buf.setUint16(pos, this.argument1);
buf.setUint16(pos + 2, this.argument2);
}
pos += 4;
} else {
buf.setUint8(pos, this.argument1);
buf.setUint8(pos + 1, this.argument2);
pos += 2;
}
if (this.flags & WE_HAVE_INSTRUCTIONS) {
buf.setUint16(pos, this.instructions.length);
pos += 2;
if (this.instructions.length) {
new Uint8Array(buf.buffer, 0, buf.buffer.byteLength).set(this.instructions, pos);
pos += this.instructions.length;
}
}
return pos - spos;
}
scale(x, factor) {}
}
;// ./src/core/opentype_file_builder.js
function writeInt16(dest, offset, num) {
dest[offset] = num >> 8 & 0xff;
dest[offset + 1] = num & 0xff;
}
function writeInt32(dest, offset, num) {
dest[offset] = num >> 24 & 0xff;
dest[offset + 1] = num >> 16 & 0xff;
dest[offset + 2] = num >> 8 & 0xff;
dest[offset + 3] = num & 0xff;
}
function writeData(dest, offset, data) {
if (data instanceof Uint8Array) {
dest.set(data, offset);
} else if (typeof data === "string") {
for (let i = 0, ii = data.length; i < ii; i++) {
dest[offset++] = data.charCodeAt(i) & 0xff;
}
} else {
for (const num of data) {
dest[offset++] = num & 0xff;
}
}
}
const OTF_HEADER_SIZE = 12;
const OTF_TABLE_ENTRY_SIZE = 16;
class OpenTypeFileBuilder {
constructor(sfnt) {
this.sfnt = sfnt;
this.tables = Object.create(null);
}
static getSearchParams(entriesCount, entrySize) {
let maxPower2 = 1,
log2 = 0;
while ((maxPower2 ^ entriesCount) > maxPower2) {
maxPower2 <<= 1;
log2++;
}
const searchRange = maxPower2 * entrySize;
return {
range: searchRange,
entry: log2,
rangeShift: entrySize * entriesCount - searchRange
};
}
toArray() {
let sfnt = this.sfnt;
const tables = this.tables;
const tablesNames = Object.keys(tables);
tablesNames.sort();
const numTables = tablesNames.length;
let i, j, jj, table, tableName;
let offset = OTF_HEADER_SIZE + numTables * OTF_TABLE_ENTRY_SIZE;
const tableOffsets = [offset];
for (i = 0; i < numTables; i++) {
table = tables[tablesNames[i]];
const paddedLength = (table.length + 3 & ~3) >>> 0;
offset += paddedLength;
tableOffsets.push(offset);
}
const file = new Uint8Array(offset);
for (i = 0; i < numTables; i++) {
table = tables[tablesNames[i]];
writeData(file, tableOffsets[i], table);
}
if (sfnt === "true") {
sfnt = string32(0x00010000);
}
file[0] = sfnt.charCodeAt(0) & 0xff;
file[1] = sfnt.charCodeAt(1) & 0xff;
file[2] = sfnt.charCodeAt(2) & 0xff;
file[3] = sfnt.charCodeAt(3) & 0xff;
writeInt16(file, 4, numTables);
const searchParams = OpenTypeFileBuilder.getSearchParams(numTables, 16);
writeInt16(file, 6, searchParams.range);
writeInt16(file, 8, searchParams.entry);
writeInt16(file, 10, searchParams.rangeShift);
offset = OTF_HEADER_SIZE;
for (i = 0; i < numTables; i++) {
tableName = tablesNames[i];
file[offset] = tableName.charCodeAt(0) & 0xff;
file[offset + 1] = tableName.charCodeAt(1) & 0xff;
file[offset + 2] = tableName.charCodeAt(2) & 0xff;
file[offset + 3] = tableName.charCodeAt(3) & 0xff;
let checksum = 0;
for (j = tableOffsets[i], jj = tableOffsets[i + 1]; j < jj; j += 4) {
const quad = readUint32(file, j);
checksum = checksum + quad >>> 0;
}
writeInt32(file, offset + 4, checksum);
writeInt32(file, offset + 8, tableOffsets[i]);
writeInt32(file, offset + 12, tables[tableName].length);
offset += OTF_TABLE_ENTRY_SIZE;
}
return file;
}
addTable(tag, data) {
if (tag in this.tables) {
throw new Error("Table " + tag + " already exists");
}
this.tables[tag] = data;
}
}
;// ./src/core/type1_parser.js
const HINTING_ENABLED = false;
const COMMAND_MAP = {
hstem: [1],
vstem: [3],
vmoveto: [4],
rlineto: [5],
hlineto: [6],
vlineto: [7],
rrcurveto: [8],
callsubr: [10],
flex: [12, 35],
drop: [12, 18],
endchar: [14],
rmoveto: [21],
hmoveto: [22],
vhcurveto: [30],
hvcurveto: [31]
};
class Type1CharString {
constructor() {
this.width = 0;
this.lsb = 0;
this.flexing = false;
this.output = [];
this.stack = [];
}
convert(encoded, subrs, seacAnalysisEnabled) {
const count = encoded.length;
let error = false;
let wx, sbx, subrNumber;
for (let i = 0; i < count; i++) {
let value = encoded[i];
if (value < 32) {
if (value === 12) {
value = (value << 8) + encoded[++i];
}
switch (value) {
case 1:
if (!HINTING_ENABLED) {
this.stack = [];
break;
}
error = this.executeCommand(2, COMMAND_MAP.hstem);
break;
case 3:
if (!HINTING_ENABLED) {
this.stack = [];
break;
}
error = this.executeCommand(2, COMMAND_MAP.vstem);
break;
case 4:
if (this.flexing) {
if (this.stack.length < 1) {
error = true;
break;
}
const dy = this.stack.pop();
this.stack.push(0, dy);
break;
}
error = this.executeCommand(1, COMMAND_MAP.vmoveto);
break;
case 5:
error = this.executeCommand(2, COMMAND_MAP.rlineto);
break;
case 6:
error = this.executeCommand(1, COMMAND_MAP.hlineto);
break;
case 7:
error = this.executeCommand(1, COMMAND_MAP.vlineto);
break;
case 8:
error = this.executeCommand(6, COMMAND_MAP.rrcurveto);
break;
case 9:
this.stack = [];
break;
case 10:
if (this.stack.length < 1) {
error = true;
break;
}
subrNumber = this.stack.pop();
if (!subrs[subrNumber]) {
error = true;
break;
}
error = this.convert(subrs[subrNumber], subrs, seacAnalysisEnabled);
break;
case 11:
return error;
case 13:
if (this.stack.length < 2) {
error = true;
break;
}
wx = this.stack.pop();
sbx = this.stack.pop();
this.lsb = sbx;
this.width = wx;
this.stack.push(wx, sbx);
error = this.executeCommand(2, COMMAND_MAP.hmoveto);
break;
case 14:
this.output.push(COMMAND_MAP.endchar[0]);
break;
case 21:
if (this.flexing) {
break;
}
error = this.executeCommand(2, COMMAND_MAP.rmoveto);
break;
case 22:
if (this.flexing) {
this.stack.push(0);
break;
}
error = this.executeCommand(1, COMMAND_MAP.hmoveto);
break;
case 30:
error = this.executeCommand(4, COMMAND_MAP.vhcurveto);
break;
case 31:
error = this.executeCommand(4, COMMAND_MAP.hvcurveto);
break;
case (12 << 8) + 0:
this.stack = [];
break;
case (12 << 8) + 1:
if (!HINTING_ENABLED) {
this.stack = [];
break;
}
error = this.executeCommand(2, COMMAND_MAP.vstem);
break;
case (12 << 8) + 2:
if (!HINTING_ENABLED) {
this.stack = [];
break;
}
error = this.executeCommand(2, COMMAND_MAP.hstem);
break;
case (12 << 8) + 6:
if (seacAnalysisEnabled) {
const asb = this.stack.at(-5);
this.seac = this.stack.splice(-4, 4);
this.seac[0] += this.lsb - asb;
error = this.executeCommand(0, COMMAND_MAP.endchar);
} else {
error = this.executeCommand(4, COMMAND_MAP.endchar);
}
break;
case (12 << 8) + 7:
if (this.stack.length < 4) {
error = true;
break;
}
this.stack.pop();
wx = this.stack.pop();
const sby = this.stack.pop();
sbx = this.stack.pop();
this.lsb = sbx;
this.width = wx;
this.stack.push(wx, sbx, sby);
error = this.executeCommand(3, COMMAND_MAP.rmoveto);
break;
case (12 << 8) + 12:
if (this.stack.length < 2) {
error = true;
break;
}
const num2 = this.stack.pop();
const num1 = this.stack.pop();
this.stack.push(num1 / num2);
break;
case (12 << 8) + 16:
if (this.stack.length < 2) {
error = true;
break;
}
subrNumber = this.stack.pop();
const numArgs = this.stack.pop();
if (subrNumber === 0 && numArgs === 3) {
const flexArgs = this.stack.splice(-17, 17);
this.stack.push(flexArgs[2] + flexArgs[0], flexArgs[3] + flexArgs[1], flexArgs[4], flexArgs[5], flexArgs[6], flexArgs[7], flexArgs[8], flexArgs[9], flexArgs[10], flexArgs[11], flexArgs[12], flexArgs[13], flexArgs[14]);
error = this.executeCommand(13, COMMAND_MAP.flex, true);
this.flexing = false;
this.stack.push(flexArgs[15], flexArgs[16]);
} else if (subrNumber === 1 && numArgs === 0) {
this.flexing = true;
}
break;
case (12 << 8) + 17:
break;
case (12 << 8) + 33:
this.stack = [];
break;
default:
warn('Unknown type 1 charstring command of "' + value + '"');
break;
}
if (error) {
break;
}
continue;
} else if (value <= 246) {
value -= 139;
} else if (value <= 250) {
value = (value - 247) * 256 + encoded[++i] + 108;
} else if (value <= 254) {
value = -((value - 251) * 256) - encoded[++i] - 108;
} else {
value = (encoded[++i] & 0xff) << 24 | (encoded[++i] & 0xff) << 16 | (encoded[++i] & 0xff) << 8 | (encoded[++i] & 0xff) << 0;
}
this.stack.push(value);
}
return error;
}
executeCommand(howManyArgs, command, keepStack) {
const stackLength = this.stack.length;
if (howManyArgs > stackLength) {
return true;
}
const start = stackLength - howManyArgs;
for (let i = start; i < stackLength; i++) {
let value = this.stack[i];
if (Number.isInteger(value)) {
this.output.push(28, value >> 8 & 0xff, value & 0xff);
} else {
value = 65536 * value | 0;
this.output.push(255, value >> 24 & 0xff, value >> 16 & 0xff, value >> 8 & 0xff, value & 0xff);
}
}
this.output.push(...command);
if (keepStack) {
this.stack.splice(start, howManyArgs);
} else {
this.stack.length = 0;
}
return false;
}
}
const EEXEC_ENCRYPT_KEY = 55665;
const CHAR_STRS_ENCRYPT_KEY = 4330;
function isHexDigit(code) {
return code >= 48 && code <= 57 || code >= 65 && code <= 70 || code >= 97 && code <= 102;
}
function decrypt(data, key, discardNumber) {
if (discardNumber >= data.length) {
return new Uint8Array(0);
}
const c1 = 52845,
c2 = 22719;
let r = key | 0,
i,
j;
for (i = 0; i < discardNumber; i++) {
r = (data[i] + r) * c1 + c2 & (1 << 16) - 1;
}
const count = data.length - discardNumber;
const decrypted = new Uint8Array(count);
for (i = discardNumber, j = 0; j < count; i++, j++) {
const value = data[i];
decrypted[j] = value ^ r >> 8;
r = (value + r) * c1 + c2 & (1 << 16) - 1;
}
return decrypted;
}
function decryptAscii(data, key, discardNumber) {
const c1 = 52845,
c2 = 22719;
let r = key | 0;
const count = data.length,
maybeLength = count >>> 1;
const decrypted = new Uint8Array(maybeLength);
let i, j;
for (i = 0, j = 0; i < count; i++) {
const digit1 = data[i];
if (!isHexDigit(digit1)) {
continue;
}
i++;
let digit2;
while (i < count && !isHexDigit(digit2 = data[i])) {
i++;
}
if (i < count) {
const value = parseInt(String.fromCharCode(digit1, digit2), 16);
decrypted[j++] = value ^ r >> 8;
r = (value + r) * c1 + c2 & (1 << 16) - 1;
}
}
return decrypted.slice(discardNumber, j);
}
function isSpecial(c) {
return c === 0x2f || c === 0x5b || c === 0x5d || c === 0x7b || c === 0x7d || c === 0x28 || c === 0x29;
}
class Type1Parser {
constructor(stream, encrypted, seacAnalysisEnabled) {
if (encrypted) {
const data = stream.getBytes();
const isBinary = !((isHexDigit(data[0]) || isWhiteSpace(data[0])) && isHexDigit(data[1]) && isHexDigit(data[2]) && isHexDigit(data[3]) && isHexDigit(data[4]) && isHexDigit(data[5]) && isHexDigit(data[6]) && isHexDigit(data[7]));
stream = new Stream(isBinary ? decrypt(data, EEXEC_ENCRYPT_KEY, 4) : decryptAscii(data, EEXEC_ENCRYPT_KEY, 4));
}
this.seacAnalysisEnabled = !!seacAnalysisEnabled;
this.stream = stream;
this.nextChar();
}
readNumberArray() {
this.getToken();
const array = [];
while (true) {
const token = this.getToken();
if (token === null || token === "]" || token === "}") {
break;
}
array.push(parseFloat(token || 0));
}
return array;
}
readNumber() {
const token = this.getToken();
return parseFloat(token || 0);
}
readInt() {
const token = this.getToken();
return parseInt(token || 0, 10) | 0;
}
readBoolean() {
const token = this.getToken();
return token === "true" ? 1 : 0;
}
nextChar() {
return this.currentChar = this.stream.getByte();
}
prevChar() {
this.stream.skip(-2);
return this.currentChar = this.stream.getByte();
}
getToken() {
let comment = false;
let ch = this.currentChar;
while (true) {
if (ch === -1) {
return null;
}
if (comment) {
if (ch === 0x0a || ch === 0x0d) {
comment = false;
}
} else if (ch === 0x25) {
comment = true;
} else if (!isWhiteSpace(ch)) {
break;
}
ch = this.nextChar();
}
if (isSpecial(ch)) {
this.nextChar();
return String.fromCharCode(ch);
}
let token = "";
do {
token += String.fromCharCode(ch);
ch = this.nextChar();
} while (ch >= 0 && !isWhiteSpace(ch) && !isSpecial(ch));
return token;
}
readCharStrings(bytes, lenIV) {
if (lenIV === -1) {
return bytes;
}
return decrypt(bytes, CHAR_STRS_ENCRYPT_KEY, lenIV);
}
extractFontProgram(properties) {
const stream = this.stream;
const subrs = [],
charstrings = [];
const privateData = Object.create(null);
privateData.lenIV = 4;
const program = {
subrs: [],
charstrings: [],
properties: {
privateData
}
};
let token, length, data, lenIV;
while ((token = this.getToken()) !== null) {
if (token !== "/") {
continue;
}
token = this.getToken();
switch (token) {
case "CharStrings":
this.getToken();
this.getToken();
this.getToken();
this.getToken();
while (true) {
token = this.getToken();
if (token === null || token === "end") {
break;
}
if (token !== "/") {
continue;
}
const glyph = this.getToken();
length = this.readInt();
this.getToken();
data = length > 0 ? stream.getBytes(length) : new Uint8Array(0);
lenIV = program.properties.privateData.lenIV;
const encoded = this.readCharStrings(data, lenIV);
this.nextChar();
token = this.getToken();
if (token === "noaccess") {
this.getToken();
} else if (token === "/") {
this.prevChar();
}
charstrings.push({
glyph,
encoded
});
}
break;
case "Subrs":
this.readInt();
this.getToken();
while (this.getToken() === "dup") {
const index = this.readInt();
length = this.readInt();
this.getToken();
data = length > 0 ? stream.getBytes(length) : new Uint8Array(0);
lenIV = program.properties.privateData.lenIV;
const encoded = this.readCharStrings(data, lenIV);
this.nextChar();
token = this.getToken();
if (token === "noaccess") {
this.getToken();
}
subrs[index] = encoded;
}
break;
case "BlueValues":
case "OtherBlues":
case "FamilyBlues":
case "FamilyOtherBlues":
const blueArray = this.readNumberArray();
if (blueArray.length > 0 && blueArray.length % 2 === 0 && HINTING_ENABLED) {
program.properties.privateData[token] = blueArray;
}
break;
case "StemSnapH":
case "StemSnapV":
program.properties.privateData[token] = this.readNumberArray();
break;
case "StdHW":
case "StdVW":
program.properties.privateData[token] = this.readNumberArray()[0];
break;
case "BlueShift":
case "lenIV":
case "BlueFuzz":
case "BlueScale":
case "LanguageGroup":
program.properties.privateData[token] = this.readNumber();
break;
case "ExpansionFactor":
program.properties.privateData[token] = this.readNumber() || 0.06;
break;
case "ForceBold":
program.properties.privateData[token] = this.readBoolean();
break;
}
}
for (const {
encoded,
glyph
} of charstrings) {
const charString = new Type1CharString();
const error = charString.convert(encoded, subrs, this.seacAnalysisEnabled);
let output = charString.output;
if (error) {
output = [14];
}
const charStringObject = {
glyphName: glyph,
charstring: output,
width: charString.width,
lsb: charString.lsb,
seac: charString.seac
};
if (glyph === ".notdef") {
program.charstrings.unshift(charStringObject);
} else {
program.charstrings.push(charStringObject);
}
if (properties.builtInEncoding) {
const index = properties.builtInEncoding.indexOf(glyph);
if (index > -1 && properties.widths[index] === undefined && index >= properties.firstChar && index <= properties.lastChar) {
properties.widths[index] = charString.width;
}
}
}
return program;
}
extractFontHeader(properties) {
let token;
while ((token = this.getToken()) !== null) {
if (token !== "/") {
continue;
}
token = this.getToken();
switch (token) {
case "FontMatrix":
const matrix = this.readNumberArray();
properties.fontMatrix = matrix;
break;
case "Encoding":
const encodingArg = this.getToken();
let encoding;
if (!/^\d+$/.test(encodingArg)) {
encoding = getEncoding(encodingArg);
} else {
encoding = [];
const size = parseInt(encodingArg, 10) | 0;
this.getToken();
for (let j = 0; j < size; j++) {
token = this.getToken();
while (token !== "dup" && token !== "def") {
token = this.getToken();
if (token === null) {
return;
}
}
if (token === "def") {
break;
}
const index = this.readInt();
this.getToken();
const glyph = this.getToken();
encoding[index] = glyph;
this.getToken();
}
}
properties.builtInEncoding = encoding;
break;
case "FontBBox":
const fontBBox = this.readNumberArray();
properties.ascent = Math.max(fontBBox[3], fontBBox[1]);
properties.descent = Math.min(fontBBox[1], fontBBox[3]);
properties.ascentScaled = true;
break;
}
}
}
}
;// ./src/core/type1_font.js
function findBlock(streamBytes, signature, startIndex) {
const streamBytesLength = streamBytes.length;
const signatureLength = signature.length;
const scanLength = streamBytesLength - signatureLength;
let i = startIndex,
found = false;
while (i < scanLength) {
let j = 0;
while (j < signatureLength && streamBytes[i + j] === signature[j]) {
j++;
}
if (j >= signatureLength) {
i += j;
while (i < streamBytesLength && isWhiteSpace(streamBytes[i])) {
i++;
}
found = true;
break;
}
i++;
}
return {
found,
length: i
};
}
function getHeaderBlock(stream, suggestedLength) {
const EEXEC_SIGNATURE = [0x65, 0x65, 0x78, 0x65, 0x63];
const streamStartPos = stream.pos;
let headerBytes, headerBytesLength, block;
try {
headerBytes = stream.getBytes(suggestedLength);
headerBytesLength = headerBytes.length;
} catch {}
if (headerBytesLength === suggestedLength) {
block = findBlock(headerBytes, EEXEC_SIGNATURE, suggestedLength - 2 * EEXEC_SIGNATURE.length);
if (block.found && block.length === suggestedLength) {
return {
stream: new Stream(headerBytes),
length: suggestedLength
};
}
}
warn('Invalid "Length1" property in Type1 font -- trying to recover.');
stream.pos = streamStartPos;
const SCAN_BLOCK_LENGTH = 2048;
let actualLength;
while (true) {
const scanBytes = stream.peekBytes(SCAN_BLOCK_LENGTH);
block = findBlock(scanBytes, EEXEC_SIGNATURE, 0);
if (block.length === 0) {
break;
}
stream.pos += block.length;
if (block.found) {
actualLength = stream.pos - streamStartPos;
break;
}
}
stream.pos = streamStartPos;
if (actualLength) {
return {
stream: new Stream(stream.getBytes(actualLength)),
length: actualLength
};
}
warn('Unable to recover "Length1" property in Type1 font -- using as is.');
return {
stream: new Stream(stream.getBytes(suggestedLength)),
length: suggestedLength
};
}
function getEexecBlock(stream, suggestedLength) {
const eexecBytes = stream.getBytes();
if (eexecBytes.length === 0) {
throw new FormatError("getEexecBlock - no font program found.");
}
return {
stream: new Stream(eexecBytes),
length: eexecBytes.length
};
}
class Type1Font {
constructor(name, file, properties) {
const PFB_HEADER_SIZE = 6;
let headerBlockLength = properties.length1;
let eexecBlockLength = properties.length2;
let pfbHeader = file.peekBytes(PFB_HEADER_SIZE);
const pfbHeaderPresent = pfbHeader[0] === 0x80 && pfbHeader[1] === 0x01;
if (pfbHeaderPresent) {
file.skip(PFB_HEADER_SIZE);
headerBlockLength = pfbHeader[5] << 24 | pfbHeader[4] << 16 | pfbHeader[3] << 8 | pfbHeader[2];
}
const headerBlock = getHeaderBlock(file, headerBlockLength);
const headerBlockParser = new Type1Parser(headerBlock.stream, false, SEAC_ANALYSIS_ENABLED);
headerBlockParser.extractFontHeader(properties);
if (pfbHeaderPresent) {
pfbHeader = file.getBytes(PFB_HEADER_SIZE);
eexecBlockLength = pfbHeader[5] << 24 | pfbHeader[4] << 16 | pfbHeader[3] << 8 | pfbHeader[2];
}
const eexecBlock = getEexecBlock(file, eexecBlockLength);
const eexecBlockParser = new Type1Parser(eexecBlock.stream, true, SEAC_ANALYSIS_ENABLED);
const data = eexecBlockParser.extractFontProgram(properties);
for (const key in data.properties) {
properties[key] = data.properties[key];
}
const charstrings = data.charstrings;
const type2Charstrings = this.getType2Charstrings(charstrings);
const subrs = this.getType2Subrs(data.subrs);
this.charstrings = charstrings;
this.data = this.wrap(name, type2Charstrings, this.charstrings, subrs, properties);
this.seacs = this.getSeacs(data.charstrings);
}
get numGlyphs() {
return this.charstrings.length + 1;
}
getCharset() {
const charset = [".notdef"];
for (const {
glyphName
} of this.charstrings) {
charset.push(glyphName);
}
return charset;
}
getGlyphMapping(properties) {
const charstrings = this.charstrings;
if (properties.composite) {
const charCodeToGlyphId = Object.create(null);
for (let glyphId = 0, charstringsLen = charstrings.length; glyphId < charstringsLen; glyphId++) {
const charCode = properties.cMap.charCodeOf(glyphId);
charCodeToGlyphId[charCode] = glyphId + 1;
}
return charCodeToGlyphId;
}
const glyphNames = [".notdef"];
let builtInEncoding, glyphId;
for (glyphId = 0; glyphId < charstrings.length; glyphId++) {
glyphNames.push(charstrings[glyphId].glyphName);
}
const encoding = properties.builtInEncoding;
if (encoding) {
builtInEncoding = Object.create(null);
for (const charCode in encoding) {
glyphId = glyphNames.indexOf(encoding[charCode]);
if (glyphId >= 0) {
builtInEncoding[charCode] = glyphId;
}
}
}
return type1FontGlyphMapping(properties, builtInEncoding, glyphNames);
}
hasGlyphId(id) {
if (id < 0 || id >= this.numGlyphs) {
return false;
}
if (id === 0) {
return true;
}
const glyph = this.charstrings[id - 1];
return glyph.charstring.length > 0;
}
getSeacs(charstrings) {
const seacMap = [];
for (let i = 0, ii = charstrings.length; i < ii; i++) {
const charstring = charstrings[i];
if (charstring.seac) {
seacMap[i + 1] = charstring.seac;
}
}
return seacMap;
}
getType2Charstrings(type1Charstrings) {
const type2Charstrings = [];
for (const type1Charstring of type1Charstrings) {
type2Charstrings.push(type1Charstring.charstring);
}
return type2Charstrings;
}
getType2Subrs(type1Subrs) {
let bias = 0;
const count = type1Subrs.length;
if (count < 1133) {
bias = 107;
} else if (count < 33769) {
bias = 1131;
} else {
bias = 32768;
}
const type2Subrs = [];
let i;
for (i = 0; i < bias; i++) {
type2Subrs.push([0x0b]);
}
for (i = 0; i < count; i++) {
type2Subrs.push(type1Subrs[i]);
}
return type2Subrs;
}
wrap(name, glyphs, charstrings, subrs, properties) {
const cff = new CFF();
cff.header = new CFFHeader(1, 0, 4, 4);
cff.names = [name];
const topDict = new CFFTopDict();
topDict.setByName("version", 391);
topDict.setByName("Notice", 392);
topDict.setByName("FullName", 393);
topDict.setByName("FamilyName", 394);
topDict.setByName("Weight", 395);
topDict.setByName("Encoding", null);
topDict.setByName("FontMatrix", properties.fontMatrix);
topDict.setByName("FontBBox", properties.bbox);
topDict.setByName("charset", null);
topDict.setByName("CharStrings", null);
topDict.setByName("Private", null);
cff.topDict = topDict;
const strings = new CFFStrings();
strings.add("Version 0.11");
strings.add("See original notice");
strings.add(name);
strings.add(name);
strings.add("Medium");
cff.strings = strings;
cff.globalSubrIndex = new CFFIndex();
const count = glyphs.length;
const charsetArray = [".notdef"];
let i, ii;
for (i = 0; i < count; i++) {
const glyphName = charstrings[i].glyphName;
const index = CFFStandardStrings.indexOf(glyphName);
if (index === -1) {
strings.add(glyphName);
}
charsetArray.push(glyphName);
}
cff.charset = new CFFCharset(false, 0, charsetArray);
const charStringsIndex = new CFFIndex();
charStringsIndex.add([0x8b, 0x0e]);
for (i = 0; i < count; i++) {
charStringsIndex.add(glyphs[i]);
}
cff.charStrings = charStringsIndex;
const privateDict = new CFFPrivateDict();
privateDict.setByName("Subrs", null);
const fields = ["BlueValues", "OtherBlues", "FamilyBlues", "FamilyOtherBlues", "StemSnapH", "StemSnapV", "BlueShift", "BlueFuzz", "BlueScale", "LanguageGroup", "ExpansionFactor", "ForceBold", "StdHW", "StdVW"];
for (i = 0, ii = fields.length; i < ii; i++) {
const field = fields[i];
if (!(field in properties.privateData)) {
continue;
}
const value = properties.privateData[field];
if (Array.isArray(value)) {
for (let j = value.length - 1; j > 0; j--) {
value[j] -= value[j - 1];
}
}
privateDict.setByName(field, value);
}
cff.topDict.privateDict = privateDict;
const subrIndex = new CFFIndex();
for (i = 0, ii = subrs.length; i < ii; i++) {
subrIndex.add(subrs[i]);
}
privateDict.subrsIndex = subrIndex;
const compiler = new CFFCompiler(cff);
return compiler.compile();
}
}
;// ./src/core/fonts.js
const PRIVATE_USE_AREAS = [[0xe000, 0xf8ff], [0x100000, 0x10fffd]];
const PDF_GLYPH_SPACE_UNITS = 1000;
const EXPORT_DATA_PROPERTIES = ["ascent", "bbox", "black", "bold", "charProcOperatorList", "cssFontInfo", "data", "defaultVMetrics", "defaultWidth", "descent", "disableFontFace", "fallbackName", "fontExtraProperties", "fontMatrix", "isInvalidPDFjsFont", "isType3Font", "italic", "loadedName", "mimetype", "missingFile", "name", "remeasure", "systemFontInfo", "vertical"];
const EXPORT_DATA_EXTRA_PROPERTIES = ["cMap", "composite", "defaultEncoding", "differences", "isMonospace", "isSerifFont", "isSymbolicFont", "seacMap", "subtype", "toFontChar", "toUnicode", "type", "vmetrics", "widths"];
function adjustWidths(properties) {
if (!properties.fontMatrix) {
return;
}
if (properties.fontMatrix[0] === FONT_IDENTITY_MATRIX[0]) {
return;
}
const scale = 0.001 / properties.fontMatrix[0];
const glyphsWidths = properties.widths;
for (const glyph in glyphsWidths) {
glyphsWidths[glyph] *= scale;
}
properties.defaultWidth *= scale;
}
function adjustTrueTypeToUnicode(properties, isSymbolicFont, nameRecords) {
if (properties.isInternalFont) {
return;
}
if (properties.hasIncludedToUnicodeMap) {
return;
}
if (properties.hasEncoding) {
return;
}
if (properties.toUnicode instanceof IdentityToUnicodeMap) {
return;
}
if (!isSymbolicFont) {
return;
}
if (nameRecords.length === 0) {
return;
}
if (properties.defaultEncoding === WinAnsiEncoding) {
return;
}
for (const r of nameRecords) {
if (!isWinNameRecord(r)) {
return;
}
}
const encoding = WinAnsiEncoding;
const toUnicode = [],
glyphsUnicodeMap = getGlyphsUnicode();
for (const charCode in encoding) {
const glyphName = encoding[charCode];
if (glyphName === "") {
continue;
}
const unicode = glyphsUnicodeMap[glyphName];
if (unicode === undefined) {
continue;
}
toUnicode[charCode] = String.fromCharCode(unicode);
}
if (toUnicode.length > 0) {
properties.toUnicode.amend(toUnicode);
}
}
function adjustType1ToUnicode(properties, builtInEncoding) {
if (properties.isInternalFont) {
return;
}
if (properties.hasIncludedToUnicodeMap) {
return;
}
if (builtInEncoding === properties.defaultEncoding) {
return;
}
if (properties.toUnicode instanceof IdentityToUnicodeMap) {
return;
}
const toUnicode = [],
glyphsUnicodeMap = getGlyphsUnicode();
for (const charCode in builtInEncoding) {
if (properties.hasEncoding) {
if (properties.baseEncodingName || properties.differences[charCode] !== undefined) {
continue;
}
}
const glyphName = builtInEncoding[charCode];
const unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap);
if (unicode !== -1) {
toUnicode[charCode] = String.fromCharCode(unicode);
}
}
if (toUnicode.length > 0) {
properties.toUnicode.amend(toUnicode);
}
}
function amendFallbackToUnicode(properties) {
if (!properties.fallbackToUnicode) {
return;
}
if (properties.toUnicode instanceof IdentityToUnicodeMap) {
return;
}
const toUnicode = [];
for (const charCode in properties.fallbackToUnicode) {
if (properties.toUnicode.has(charCode)) {
continue;
}
toUnicode[charCode] = properties.fallbackToUnicode[charCode];
}
if (toUnicode.length > 0) {
properties.toUnicode.amend(toUnicode);
}
}
class fonts_Glyph {
constructor(originalCharCode, fontChar, unicode, accent, width, vmetric, operatorListId, isSpace, isInFont) {
this.originalCharCode = originalCharCode;
this.fontChar = fontChar;
this.unicode = unicode;
this.accent = accent;
this.width = width;
this.vmetric = vmetric;
this.operatorListId = operatorListId;
this.isSpace = isSpace;
this.isInFont = isInFont;
}
get category() {
return shadow(this, "category", getCharUnicodeCategory(this.unicode), true);
}
}
function int16(b0, b1) {
return (b0 << 8) + b1;
}
function writeSignedInt16(bytes, index, value) {
bytes[index + 1] = value;
bytes[index] = value >>> 8;
}
function signedInt16(b0, b1) {
const value = (b0 << 8) + b1;
return value & 1 << 15 ? value - 0x10000 : value;
}
function writeUint32(bytes, index, value) {
bytes[index + 3] = value & 0xff;
bytes[index + 2] = value >>> 8;
bytes[index + 1] = value >>> 16;
bytes[index] = value >>> 24;
}
function int32(b0, b1, b2, b3) {
return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3;
}
function string16(value) {
return String.fromCharCode(value >> 8 & 0xff, value & 0xff);
}
function safeString16(value) {
if (value > 0x7fff) {
value = 0x7fff;
} else if (value < -0x8000) {
value = -0x8000;
}
return String.fromCharCode(value >> 8 & 0xff, value & 0xff);
}
function isTrueTypeFile(file) {
const header = file.peekBytes(4);
return readUint32(header, 0) === 0x00010000 || bytesToString(header) === "true";
}
function isTrueTypeCollectionFile(file) {
const header = file.peekBytes(4);
return bytesToString(header) === "ttcf";
}
function isOpenTypeFile(file) {
const header = file.peekBytes(4);
return bytesToString(header) === "OTTO";
}
function isType1File(file) {
const header = file.peekBytes(2);
if (header[0] === 0x25 && header[1] === 0x21) {
return true;
}
if (header[0] === 0x80 && header[1] === 0x01) {
return true;
}
return false;
}
function isCFFFile(file) {
const header = file.peekBytes(4);
if (header[0] >= 1 && header[3] >= 1 && header[3] <= 4) {
return true;
}
return false;
}
function getFontFileType(file, {
type,
subtype,
composite
}) {
let fileType, fileSubtype;
if (isTrueTypeFile(file) || isTrueTypeCollectionFile(file)) {
fileType = composite ? "CIDFontType2" : "TrueType";
} else if (isOpenTypeFile(file)) {
fileType = composite ? "CIDFontType2" : "OpenType";
} else if (isType1File(file)) {
if (composite) {
fileType = "CIDFontType0";
} else {
fileType = type === "MMType1" ? "MMType1" : "Type1";
}
} else if (isCFFFile(file)) {
if (composite) {
fileType = "CIDFontType0";
fileSubtype = "CIDFontType0C";
} else {
fileType = type === "MMType1" ? "MMType1" : "Type1";
fileSubtype = "Type1C";
}
} else {
warn("getFontFileType: Unable to detect correct font file Type/Subtype.");
fileType = type;
fileSubtype = subtype;
}
return [fileType, fileSubtype];
}
function applyStandardFontGlyphMap(map, glyphMap) {
for (const charCode in glyphMap) {
map[+charCode] = glyphMap[charCode];
}
}
function buildToFontChar(encoding, glyphsUnicodeMap, differences) {
const toFontChar = [];
let unicode;
for (let i = 0, ii = encoding.length; i < ii; i++) {
unicode = getUnicodeForGlyph(encoding[i], glyphsUnicodeMap);
if (unicode !== -1) {
toFontChar[i] = unicode;
}
}
for (const charCode in differences) {
unicode = getUnicodeForGlyph(differences[charCode], glyphsUnicodeMap);
if (unicode !== -1) {
toFontChar[+charCode] = unicode;
}
}
return toFontChar;
}
function isMacNameRecord(r) {
return r.platform === 1 && r.encoding === 0 && r.language === 0;
}
function isWinNameRecord(r) {
return r.platform === 3 && r.encoding === 1 && r.language === 0x409;
}
function convertCidString(charCode, cid, shouldThrow = false) {
switch (cid.length) {
case 1:
return cid.charCodeAt(0);
case 2:
return cid.charCodeAt(0) << 8 | cid.charCodeAt(1);
}
const msg = `Unsupported CID string (charCode ${charCode}): "${cid}".`;
if (shouldThrow) {
throw new FormatError(msg);
}
warn(msg);
return cid;
}
function adjustMapping(charCodeToGlyphId, hasGlyph, newGlyphZeroId, toUnicode) {
const newMap = Object.create(null);
const toUnicodeExtraMap = new Map();
const toFontChar = [];
const usedGlyphIds = new Set();
let privateUseAreaIndex = 0;
const privateUseOffetStart = PRIVATE_USE_AREAS[privateUseAreaIndex][0];
let nextAvailableFontCharCode = privateUseOffetStart;
let privateUseOffetEnd = PRIVATE_USE_AREAS[privateUseAreaIndex][1];
const isInPrivateArea = code => PRIVATE_USE_AREAS[0][0] <= code && code <= PRIVATE_USE_AREAS[0][1] || PRIVATE_USE_AREAS[1][0] <= code && code <= PRIVATE_USE_AREAS[1][1];
let LIGATURE_TO_UNICODE = null;
for (const originalCharCode in charCodeToGlyphId) {
let glyphId = charCodeToGlyphId[originalCharCode];
if (!hasGlyph(glyphId)) {
continue;
}
if (nextAvailableFontCharCode > privateUseOffetEnd) {
privateUseAreaIndex++;
if (privateUseAreaIndex >= PRIVATE_USE_AREAS.length) {
warn("Ran out of space in font private use area.");
break;
}
nextAvailableFontCharCode = PRIVATE_USE_AREAS[privateUseAreaIndex][0];
privateUseOffetEnd = PRIVATE_USE_AREAS[privateUseAreaIndex][1];
}
const fontCharCode = nextAvailableFontCharCode++;
if (glyphId === 0) {
glyphId = newGlyphZeroId;
}
let unicode = toUnicode.get(originalCharCode);
if (typeof unicode === "string") {
if (unicode.length === 1) {
unicode = unicode.codePointAt(0);
} else {
if (!LIGATURE_TO_UNICODE) {
LIGATURE_TO_UNICODE = new Map();
for (let i = 0xfb00; i <= 0xfb4f; i++) {
const normalized = String.fromCharCode(i).normalize("NFKD");
if (normalized.length > 1) {
LIGATURE_TO_UNICODE.set(normalized, i);
}
}
}
unicode = LIGATURE_TO_UNICODE.get(unicode) || unicode.codePointAt(0);
}
}
if (unicode && !isInPrivateArea(unicode) && !usedGlyphIds.has(glyphId)) {
toUnicodeExtraMap.set(unicode, glyphId);
usedGlyphIds.add(glyphId);
}
newMap[fontCharCode] = glyphId;
toFontChar[originalCharCode] = fontCharCode;
}
return {
toFontChar,
charCodeToGlyphId: newMap,
toUnicodeExtraMap,
nextAvailableFontCharCode
};
}
function getRanges(glyphs, toUnicodeExtraMap, numGlyphs) {
const codes = [];
for (const charCode in glyphs) {
if (glyphs[charCode] >= numGlyphs) {
continue;
}
codes.push({
fontCharCode: charCode | 0,
glyphId: glyphs[charCode]
});
}
if (toUnicodeExtraMap) {
for (const [unicode, glyphId] of toUnicodeExtraMap) {
if (glyphId >= numGlyphs) {
continue;
}
codes.push({
fontCharCode: unicode,
glyphId
});
}
}
if (codes.length === 0) {
codes.push({
fontCharCode: 0,
glyphId: 0
});
}
codes.sort((a, b) => a.fontCharCode - b.fontCharCode);
const ranges = [];
const length = codes.length;
for (let n = 0; n < length;) {
const start = codes[n].fontCharCode;
const codeIndices = [codes[n].glyphId];
++n;
let end = start;
while (n < length && end + 1 === codes[n].fontCharCode) {
codeIndices.push(codes[n].glyphId);
++end;
++n;
if (end === 0xffff) {
break;
}
}
ranges.push([start, end, codeIndices]);
}
return ranges;
}
function createCmapTable(glyphs, toUnicodeExtraMap, numGlyphs) {
const ranges = getRanges(glyphs, toUnicodeExtraMap, numGlyphs);
const numTables = ranges.at(-1)[1] > 0xffff ? 2 : 1;
let cmap = "\x00\x00" + string16(numTables) + "\x00\x03" + "\x00\x01" + string32(4 + numTables * 8);
let i, ii, j, jj;
for (i = ranges.length - 1; i >= 0; --i) {
if (ranges[i][0] <= 0xffff) {
break;
}
}
const bmpLength = i + 1;
if (ranges[i][0] < 0xffff && ranges[i][1] === 0xffff) {
ranges[i][1] = 0xfffe;
}
const trailingRangesCount = ranges[i][1] < 0xffff ? 1 : 0;
const segCount = bmpLength + trailingRangesCount;
const searchParams = OpenTypeFileBuilder.getSearchParams(segCount, 2);
let startCount = "";
let endCount = "";
let idDeltas = "";
let idRangeOffsets = "";
let glyphsIds = "";
let bias = 0;
let range, start, end, codes;
for (i = 0, ii = bmpLength; i < ii; i++) {
range = ranges[i];
start = range[0];
end = range[1];
startCount += string16(start);
endCount += string16(end);
codes = range[2];
let contiguous = true;
for (j = 1, jj = codes.length; j < jj; ++j) {
if (codes[j] !== codes[j - 1] + 1) {
contiguous = false;
break;
}
}
if (!contiguous) {
const offset = (segCount - i) * 2 + bias * 2;
bias += end - start + 1;
idDeltas += string16(0);
idRangeOffsets += string16(offset);
for (j = 0, jj = codes.length; j < jj; ++j) {
glyphsIds += string16(codes[j]);
}
} else {
const startCode = codes[0];
idDeltas += string16(startCode - start & 0xffff);
idRangeOffsets += string16(0);
}
}
if (trailingRangesCount > 0) {
endCount += "\xFF\xFF";
startCount += "\xFF\xFF";
idDeltas += "\x00\x01";
idRangeOffsets += "\x00\x00";
}
const format314 = "\x00\x00" + string16(2 * segCount) + string16(searchParams.range) + string16(searchParams.entry) + string16(searchParams.rangeShift) + endCount + "\x00\x00" + startCount + idDeltas + idRangeOffsets + glyphsIds;
let format31012 = "";
let header31012 = "";
if (numTables > 1) {
cmap += "\x00\x03" + "\x00\x0A" + string32(4 + numTables * 8 + 4 + format314.length);
format31012 = "";
for (i = 0, ii = ranges.length; i < ii; i++) {
range = ranges[i];
start = range[0];
codes = range[2];
let code = codes[0];
for (j = 1, jj = codes.length; j < jj; ++j) {
if (codes[j] !== codes[j - 1] + 1) {
end = range[0] + j - 1;
format31012 += string32(start) + string32(end) + string32(code);
start = end + 1;
code = codes[j];
}
}
format31012 += string32(start) + string32(range[1]) + string32(code);
}
header31012 = "\x00\x0C" + "\x00\x00" + string32(format31012.length + 16) + "\x00\x00\x00\x00" + string32(format31012.length / 12);
}
return cmap + "\x00\x04" + string16(format314.length + 4) + format314 + header31012 + format31012;
}
function validateOS2Table(os2, file) {
file.pos = (file.start || 0) + os2.offset;
const version = file.getUint16();
file.skip(60);
const selection = file.getUint16();
if (version < 4 && selection & 0x0300) {
return false;
}
const firstChar = file.getUint16();
const lastChar = file.getUint16();
if (firstChar > lastChar) {
return false;
}
file.skip(6);
const usWinAscent = file.getUint16();
if (usWinAscent === 0) {
return false;
}
os2.data[8] = os2.data[9] = 0;
return true;
}
function createOS2Table(properties, charstrings, override) {
override ||= {
unitsPerEm: 0,
yMax: 0,
yMin: 0,
ascent: 0,
descent: 0
};
let ulUnicodeRange1 = 0;
let ulUnicodeRange2 = 0;
let ulUnicodeRange3 = 0;
let ulUnicodeRange4 = 0;
let firstCharIndex = null;
let lastCharIndex = 0;
let position = -1;
if (charstrings) {
for (let code in charstrings) {
code |= 0;
if (firstCharIndex > code || !firstCharIndex) {
firstCharIndex = code;
}
if (lastCharIndex < code) {
lastCharIndex = code;
}
position = getUnicodeRangeFor(code, position);
if (position < 32) {
ulUnicodeRange1 |= 1 << position;
} else if (position < 64) {
ulUnicodeRange2 |= 1 << position - 32;
} else if (position < 96) {
ulUnicodeRange3 |= 1 << position - 64;
} else if (position < 123) {
ulUnicodeRange4 |= 1 << position - 96;
} else {
throw new FormatError("Unicode ranges Bits > 123 are reserved for internal usage");
}
}
if (lastCharIndex > 0xffff) {
lastCharIndex = 0xffff;
}
} else {
firstCharIndex = 0;
lastCharIndex = 255;
}
const bbox = properties.bbox || [0, 0, 0, 0];
const unitsPerEm = override.unitsPerEm || (properties.fontMatrix ? 1 / Math.max(...properties.fontMatrix.slice(0, 4).map(Math.abs)) : 1000);
const scale = properties.ascentScaled ? 1.0 : unitsPerEm / PDF_GLYPH_SPACE_UNITS;
const typoAscent = override.ascent || Math.round(scale * (properties.ascent || bbox[3]));
let typoDescent = override.descent || Math.round(scale * (properties.descent || bbox[1]));
if (typoDescent > 0 && properties.descent > 0 && bbox[1] < 0) {
typoDescent = -typoDescent;
}
const winAscent = override.yMax || typoAscent;
const winDescent = -override.yMin || -typoDescent;
return "\x00\x03" + "\x02\x24" + "\x01\xF4" + "\x00\x05" + "\x00\x00" + "\x02\x8A" + "\x02\xBB" + "\x00\x00" + "\x00\x8C" + "\x02\x8A" + "\x02\xBB" + "\x00\x00" + "\x01\xDF" + "\x00\x31" + "\x01\x02" + "\x00\x00" + "\x00\x00\x06" + String.fromCharCode(properties.fixedPitch ? 0x09 : 0x00) + "\x00\x00\x00\x00\x00\x00" + string32(ulUnicodeRange1) + string32(ulUnicodeRange2) + string32(ulUnicodeRange3) + string32(ulUnicodeRange4) + "\x2A\x32\x31\x2A" + string16(properties.italicAngle ? 1 : 0) + string16(firstCharIndex || properties.firstChar) + string16(lastCharIndex || properties.lastChar) + string16(typoAscent) + string16(typoDescent) + "\x00\x64" + string16(winAscent) + string16(winDescent) + "\x00\x00\x00\x00" + "\x00\x00\x00\x00" + string16(properties.xHeight) + string16(properties.capHeight) + string16(0) + string16(firstCharIndex || properties.firstChar) + "\x00\x03";
}
function createPostTable(properties) {
const angle = Math.floor(properties.italicAngle * 2 ** 16);
return "\x00\x03\x00\x00" + string32(angle) + "\x00\x00" + "\x00\x00" + string32(properties.fixedPitch ? 1 : 0) + "\x00\x00\x00\x00" + "\x00\x00\x00\x00" + "\x00\x00\x00\x00" + "\x00\x00\x00\x00";
}
function createPostscriptName(name) {
return name.replaceAll(/[^\x21-\x7E]|[[\](){}<>/%]/g, "").slice(0, 63);
}
function createNameTable(name, proto) {
if (!proto) {
proto = [[], []];
}
const strings = [proto[0][0] || "Original licence", proto[0][1] || name, proto[0][2] || "Unknown", proto[0][3] || "uniqueID", proto[0][4] || name, proto[0][5] || "Version 0.11", proto[0][6] || createPostscriptName(name), proto[0][7] || "Unknown", proto[0][8] || "Unknown", proto[0][9] || "Unknown"];
const stringsUnicode = [];
let i, ii, j, jj, str;
for (i = 0, ii = strings.length; i < ii; i++) {
str = proto[1][i] || strings[i];
const strBufUnicode = [];
for (j = 0, jj = str.length; j < jj; j++) {
strBufUnicode.push(string16(str.charCodeAt(j)));
}
stringsUnicode.push(strBufUnicode.join(""));
}
const names = [strings, stringsUnicode];
const platforms = ["\x00\x01", "\x00\x03"];
const encodings = ["\x00\x00", "\x00\x01"];
const languages = ["\x00\x00", "\x04\x09"];
const namesRecordCount = strings.length * platforms.length;
let nameTable = "\x00\x00" + string16(namesRecordCount) + string16(namesRecordCount * 12 + 6);
let strOffset = 0;
for (i = 0, ii = platforms.length; i < ii; i++) {
const strs = names[i];
for (j = 0, jj = strs.length; j < jj; j++) {
str = strs[j];
const nameRecord = platforms[i] + encodings[i] + languages[i] + string16(j) + string16(str.length) + string16(strOffset);
nameTable += nameRecord;
strOffset += str.length;
}
}
nameTable += strings.join("") + stringsUnicode.join("");
return nameTable;
}
class Font {
constructor(name, file, properties, evaluatorOptions) {
this.name = name;
this.psName = null;
this.mimetype = null;
this.disableFontFace = evaluatorOptions.disableFontFace;
this.fontExtraProperties = evaluatorOptions.fontExtraProperties;
this.loadedName = properties.loadedName;
this.isType3Font = properties.isType3Font;
this.missingFile = false;
this.cssFontInfo = properties.cssFontInfo;
this._charsCache = Object.create(null);
this._glyphCache = Object.create(null);
let isSerifFont = !!(properties.flags & FontFlags.Serif);
if (!isSerifFont && !properties.isSimulatedFlags) {
const baseName = name.replaceAll(/[,_]/g, "-").split("-", 1)[0],
serifFonts = getSerifFonts();
for (const namePart of baseName.split("+")) {
if (serifFonts[namePart]) {
isSerifFont = true;
break;
}
}
}
this.isSerifFont = isSerifFont;
this.isSymbolicFont = !!(properties.flags & FontFlags.Symbolic);
this.isMonospace = !!(properties.flags & FontFlags.FixedPitch);
let {
type,
subtype
} = properties;
this.type = type;
this.subtype = subtype;
this.systemFontInfo = properties.systemFontInfo;
const matches = name.match(/^InvalidPDFjsFont_(.*)_\d+$/);
this.isInvalidPDFjsFont = !!matches;
if (this.isInvalidPDFjsFont) {
this.fallbackName = matches[1];
} else if (this.isMonospace) {
this.fallbackName = "monospace";
} else if (this.isSerifFont) {
this.fallbackName = "serif";
} else {
this.fallbackName = "sans-serif";
}
if (this.systemFontInfo?.guessFallback) {
this.systemFontInfo.guessFallback = false;
this.systemFontInfo.css += `,${this.fallbackName}`;
}
this.differences = properties.differences;
this.widths = properties.widths;
this.defaultWidth = properties.defaultWidth;
this.composite = properties.composite;
this.cMap = properties.cMap;
this.capHeight = properties.capHeight / PDF_GLYPH_SPACE_UNITS;
this.ascent = properties.ascent / PDF_GLYPH_SPACE_UNITS;
this.descent = properties.descent / PDF_GLYPH_SPACE_UNITS;
this.lineHeight = this.ascent - this.descent;
this.fontMatrix = properties.fontMatrix;
this.bbox = properties.bbox;
this.defaultEncoding = properties.defaultEncoding;
this.toUnicode = properties.toUnicode;
this.toFontChar = [];
if (properties.type === "Type3") {
for (let charCode = 0; charCode < 256; charCode++) {
this.toFontChar[charCode] = this.differences[charCode] || properties.defaultEncoding[charCode];
}
return;
}
this.cidEncoding = properties.cidEncoding || "";
this.vertical = !!properties.vertical;
if (this.vertical) {
this.vmetrics = properties.vmetrics;
this.defaultVMetrics = properties.defaultVMetrics;
}
if (!file || file.isEmpty) {
if (file) {
warn('Font file is empty in "' + name + '" (' + this.loadedName + ")");
}
this.fallbackToSystemFont(properties);
return;
}
[type, subtype] = getFontFileType(file, properties);
if (type !== this.type || subtype !== this.subtype) {
info("Inconsistent font file Type/SubType, expected: " + `${this.type}/${this.subtype} but found: ${type}/${subtype}.`);
}
let data;
try {
switch (type) {
case "MMType1":
info("MMType1 font (" + name + "), falling back to Type1.");
case "Type1":
case "CIDFontType0":
this.mimetype = "font/opentype";
const cff = subtype === "Type1C" || subtype === "CIDFontType0C" ? new CFFFont(file, properties) : new Type1Font(name, file, properties);
adjustWidths(properties);
data = this.convert(name, cff, properties);
break;
case "OpenType":
case "TrueType":
case "CIDFontType2":
this.mimetype = "font/opentype";
data = this.checkAndRepair(name, file, properties);
if (this.isOpenType) {
adjustWidths(properties);
type = "OpenType";
}
break;
default:
throw new FormatError(`Font ${type} is not supported`);
}
} catch (e) {
warn(e);
this.fallbackToSystemFont(properties);
return;
}
amendFallbackToUnicode(properties);
this.data = data;
this.type = type;
this.subtype = subtype;
this.fontMatrix = properties.fontMatrix;
this.widths = properties.widths;
this.defaultWidth = properties.defaultWidth;
this.toUnicode = properties.toUnicode;
this.seacMap = properties.seacMap;
}
get renderer() {
const renderer = FontRendererFactory.create(this, SEAC_ANALYSIS_ENABLED);
return shadow(this, "renderer", renderer);
}
exportData() {
const exportDataProps = this.fontExtraProperties ? [...EXPORT_DATA_PROPERTIES, ...EXPORT_DATA_EXTRA_PROPERTIES] : EXPORT_DATA_PROPERTIES;
const data = Object.create(null);
for (const prop of exportDataProps) {
const value = this[prop];
if (value !== undefined) {
data[prop] = value;
}
}
return data;
}
fallbackToSystemFont(properties) {
this.missingFile = true;
const {
name,
type
} = this;
let fontName = normalizeFontName(name);
const stdFontMap = getStdFontMap(),
nonStdFontMap = getNonStdFontMap();
const isStandardFont = !!stdFontMap[fontName];
const isMappedToStandardFont = !!(nonStdFontMap[fontName] && stdFontMap[nonStdFontMap[fontName]]);
fontName = stdFontMap[fontName] || nonStdFontMap[fontName] || fontName;
const fontBasicMetricsMap = getFontBasicMetrics();
const metrics = fontBasicMetricsMap[fontName];
if (metrics) {
if (isNaN(this.ascent)) {
this.ascent = metrics.ascent / PDF_GLYPH_SPACE_UNITS;
}
if (isNaN(this.descent)) {
this.descent = metrics.descent / PDF_GLYPH_SPACE_UNITS;
}
if (isNaN(this.capHeight)) {
this.capHeight = metrics.capHeight / PDF_GLYPH_SPACE_UNITS;
}
}
this.bold = /bold/gi.test(fontName);
this.italic = /oblique|italic/gi.test(fontName);
this.black = /Black/g.test(name);
const isNarrow = /Narrow/g.test(name);
this.remeasure = (!isStandardFont || isNarrow) && Object.keys(this.widths).length > 0;
if ((isStandardFont || isMappedToStandardFont) && type === "CIDFontType2" && this.cidEncoding.startsWith("Identity-")) {
const cidToGidMap = properties.cidToGidMap;
const map = [];
applyStandardFontGlyphMap(map, getGlyphMapForStandardFonts());
if (/Arial-?Black/i.test(name)) {
applyStandardFontGlyphMap(map, getSupplementalGlyphMapForArialBlack());
} else if (/Calibri/i.test(name)) {
applyStandardFontGlyphMap(map, getSupplementalGlyphMapForCalibri());
}
if (cidToGidMap) {
for (const charCode in map) {
const cid = map[charCode];
if (cidToGidMap[cid] !== undefined) {
map[+charCode] = cidToGidMap[cid];
}
}
if (cidToGidMap.length !== this.toUnicode.length && properties.hasIncludedToUnicodeMap && this.toUnicode instanceof IdentityToUnicodeMap) {
this.toUnicode.forEach(function (charCode, unicodeCharCode) {
const cid = map[charCode];
if (cidToGidMap[cid] === undefined) {
map[+charCode] = unicodeCharCode;
}
});
}
}
if (!(this.toUnicode instanceof IdentityToUnicodeMap)) {
this.toUnicode.forEach(function (charCode, unicodeCharCode) {
map[+charCode] = unicodeCharCode;
});
}
this.toFontChar = map;
this.toUnicode = new ToUnicodeMap(map);
} else if (/Symbol/i.test(fontName)) {
this.toFontChar = buildToFontChar(SymbolSetEncoding, getGlyphsUnicode(), this.differences);
} else if (/Dingbats/i.test(fontName)) {
this.toFontChar = buildToFontChar(ZapfDingbatsEncoding, getDingbatsGlyphsUnicode(), this.differences);
} else if (isStandardFont || isMappedToStandardFont) {
const map = buildToFontChar(this.defaultEncoding, getGlyphsUnicode(), this.differences);
if (type === "CIDFontType2" && !this.cidEncoding.startsWith("Identity-") && !(this.toUnicode instanceof IdentityToUnicodeMap)) {
this.toUnicode.forEach(function (charCode, unicodeCharCode) {
map[+charCode] = unicodeCharCode;
});
}
this.toFontChar = map;
} else {
const glyphsUnicodeMap = getGlyphsUnicode();
const map = [];
this.toUnicode.forEach((charCode, unicodeCharCode) => {
if (!this.composite) {
const glyphName = this.differences[charCode] || this.defaultEncoding[charCode];
const unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap);
if (unicode !== -1) {
unicodeCharCode = unicode;
}
}
map[+charCode] = unicodeCharCode;
});
if (this.composite && this.toUnicode instanceof IdentityToUnicodeMap) {
if (/Tahoma|Verdana/i.test(name)) {
applyStandardFontGlyphMap(map, getGlyphMapForStandardFonts());
}
}
this.toFontChar = map;
}
amendFallbackToUnicode(properties);
this.loadedName = fontName.split("-", 1)[0];
}
checkAndRepair(name, font, properties) {
const VALID_TABLES = ["OS/2", "cmap", "head", "hhea", "hmtx", "maxp", "name", "post", "loca", "glyf", "fpgm", "prep", "cvt ", "CFF "];
function readTables(file, numTables) {
const tables = Object.create(null);
tables["OS/2"] = null;
tables.cmap = null;
tables.head = null;
tables.hhea = null;
tables.hmtx = null;
tables.maxp = null;
tables.name = null;
tables.post = null;
for (let i = 0; i < numTables; i++) {
const table = readTableEntry(file);
if (!VALID_TABLES.includes(table.tag)) {
continue;
}
if (table.length === 0) {
continue;
}
tables[table.tag] = table;
}
return tables;
}
function readTableEntry(file) {
const tag = file.getString(4);
const checksum = file.getInt32() >>> 0;
const offset = file.getInt32() >>> 0;
const length = file.getInt32() >>> 0;
const previousPosition = file.pos;
file.pos = file.start || 0;
file.skip(offset);
const data = file.getBytes(length);
file.pos = previousPosition;
if (tag === "head") {
data[8] = data[9] = data[10] = data[11] = 0;
data[17] |= 0x20;
}
return {
tag,
checksum,
length,
offset,
data
};
}
function readOpenTypeHeader(ttf) {
return {
version: ttf.getString(4),
numTables: ttf.getUint16(),
searchRange: ttf.getUint16(),
entrySelector: ttf.getUint16(),
rangeShift: ttf.getUint16()
};
}
function readTrueTypeCollectionHeader(ttc) {
const ttcTag = ttc.getString(4);
assert(ttcTag === "ttcf", "Must be a TrueType Collection font.");
const majorVersion = ttc.getUint16();
const minorVersion = ttc.getUint16();
const numFonts = ttc.getInt32() >>> 0;
const offsetTable = [];
for (let i = 0; i < numFonts; i++) {
offsetTable.push(ttc.getInt32() >>> 0);
}
const header = {
ttcTag,
majorVersion,
minorVersion,
numFonts,
offsetTable
};
switch (majorVersion) {
case 1:
return header;
case 2:
header.dsigTag = ttc.getInt32() >>> 0;
header.dsigLength = ttc.getInt32() >>> 0;
header.dsigOffset = ttc.getInt32() >>> 0;
return header;
}
throw new FormatError(`Invalid TrueType Collection majorVersion: ${majorVersion}.`);
}
function readTrueTypeCollectionData(ttc, fontName) {
const {
numFonts,
offsetTable
} = readTrueTypeCollectionHeader(ttc);
const fontNameParts = fontName.split("+");
let fallbackData;
for (let i = 0; i < numFonts; i++) {
ttc.pos = (ttc.start || 0) + offsetTable[i];
const potentialHeader = readOpenTypeHeader(ttc);
const potentialTables = readTables(ttc, potentialHeader.numTables);
if (!potentialTables.name) {
throw new FormatError('TrueType Collection font must contain a "name" table.');
}
const [nameTable] = readNameTable(potentialTables.name);
for (let j = 0, jj = nameTable.length; j < jj; j++) {
for (let k = 0, kk = nameTable[j].length; k < kk; k++) {
const nameEntry = nameTable[j][k]?.replaceAll(/\s/g, "");
if (!nameEntry) {
continue;
}
if (nameEntry === fontName) {
return {
header: potentialHeader,
tables: potentialTables
};
}
if (fontNameParts.length < 2) {
continue;
}
for (const part of fontNameParts) {
if (nameEntry === part) {
fallbackData = {
name: part,
header: potentialHeader,
tables: potentialTables
};
}
}
}
}
}
if (fallbackData) {
warn(`TrueType Collection does not contain "${fontName}" font, ` + `falling back to "${fallbackData.name}" font instead.`);
return {
header: fallbackData.header,
tables: fallbackData.tables
};
}
throw new FormatError(`TrueType Collection does not contain "${fontName}" font.`);
}
function readCmapTable(cmap, file, isSymbolicFont, hasEncoding) {
if (!cmap) {
warn("No cmap table available.");
return {
platformId: -1,
encodingId: -1,
mappings: [],
hasShortCmap: false
};
}
let segment;
let start = (file.start || 0) + cmap.offset;
file.pos = start;
file.skip(2);
const numTables = file.getUint16();
let potentialTable;
let canBreak = false;
for (let i = 0; i < numTables; i++) {
const platformId = file.getUint16();
const encodingId = file.getUint16();
const offset = file.getInt32() >>> 0;
let useTable = false;
if (potentialTable?.platformId === platformId && potentialTable?.encodingId === encodingId) {
continue;
}
if (platformId === 0 && (encodingId === 0 || encodingId === 1 || encodingId === 3)) {
useTable = true;
} else if (platformId === 1 && encodingId === 0) {
useTable = true;
} else if (platformId === 3 && encodingId === 1 && (hasEncoding || !potentialTable)) {
useTable = true;
if (!isSymbolicFont) {
canBreak = true;
}
} else if (isSymbolicFont && platformId === 3 && encodingId === 0) {
useTable = true;
let correctlySorted = true;
if (i < numTables - 1) {
const nextBytes = file.peekBytes(2),
nextPlatformId = int16(nextBytes[0], nextBytes[1]);
if (nextPlatformId < platformId) {
correctlySorted = false;
}
}
if (correctlySorted) {
canBreak = true;
}
}
if (useTable) {
potentialTable = {
platformId,
encodingId,
offset
};
}
if (canBreak) {
break;
}
}
if (potentialTable) {
file.pos = start + potentialTable.offset;
}
if (!potentialTable || file.peekByte() === -1) {
warn("Could not find a preferred cmap table.");
return {
platformId: -1,
encodingId: -1,
mappings: [],
hasShortCmap: false
};
}
const format = file.getUint16();
let hasShortCmap = false;
const mappings = [];
let j, glyphId;
if (format === 0) {
file.skip(2 + 2);
for (j = 0; j < 256; j++) {
const index = file.getByte();
if (!index) {
continue;
}
mappings.push({
charCode: j,
glyphId: index
});
}
hasShortCmap = true;
} else if (format === 2) {
file.skip(2 + 2);
const subHeaderKeys = [];
let maxSubHeaderKey = 0;
for (let i = 0; i < 256; i++) {
const subHeaderKey = file.getUint16() >> 3;
subHeaderKeys.push(subHeaderKey);
maxSubHeaderKey = Math.max(subHeaderKey, maxSubHeaderKey);
}
const subHeaders = [];
for (let i = 0; i <= maxSubHeaderKey; i++) {
subHeaders.push({
firstCode: file.getUint16(),
entryCount: file.getUint16(),
idDelta: signedInt16(file.getByte(), file.getByte()),
idRangePos: file.pos + file.getUint16()
});
}
for (let i = 0; i < 256; i++) {
if (subHeaderKeys[i] === 0) {
file.pos = subHeaders[0].idRangePos + 2 * i;
glyphId = file.getUint16();
mappings.push({
charCode: i,
glyphId
});
} else {
const s = subHeaders[subHeaderKeys[i]];
for (j = 0; j < s.entryCount; j++) {
const charCode = (i << 8) + j + s.firstCode;
file.pos = s.idRangePos + 2 * j;
glyphId = file.getUint16();
if (glyphId !== 0) {
glyphId = (glyphId + s.idDelta) % 65536;
}
mappings.push({
charCode,
glyphId
});
}
}
}
} else if (format === 4) {
file.skip(2 + 2);
const segCount = file.getUint16() >> 1;
file.skip(6);
const segments = [];
let segIndex;
for (segIndex = 0; segIndex < segCount; segIndex++) {
segments.push({
end: file.getUint16()
});
}
file.skip(2);
for (segIndex = 0; segIndex < segCount; segIndex++) {
segments[segIndex].start = file.getUint16();
}
for (segIndex = 0; segIndex < segCount; segIndex++) {
segments[segIndex].delta = file.getUint16();
}
let offsetsCount = 0,
offsetIndex;
for (segIndex = 0; segIndex < segCount; segIndex++) {
segment = segments[segIndex];
const rangeOffset = file.getUint16();
if (!rangeOffset) {
segment.offsetIndex = -1;
continue;
}
offsetIndex = (rangeOffset >> 1) - (segCount - segIndex);
segment.offsetIndex = offsetIndex;
offsetsCount = Math.max(offsetsCount, offsetIndex + segment.end - segment.start + 1);
}
const offsets = [];
for (j = 0; j < offsetsCount; j++) {
offsets.push(file.getUint16());
}
for (segIndex = 0; segIndex < segCount; segIndex++) {
segment = segments[segIndex];
start = segment.start;
const end = segment.end;
const delta = segment.delta;
offsetIndex = segment.offsetIndex;
for (j = start; j <= end; j++) {
if (j === 0xffff) {
continue;
}
glyphId = offsetIndex < 0 ? j : offsets[offsetIndex + j - start];
glyphId = glyphId + delta & 0xffff;
mappings.push({
charCode: j,
glyphId
});
}
}
} else if (format === 6) {
file.skip(2 + 2);
const firstCode = file.getUint16();
const entryCount = file.getUint16();
for (j = 0; j < entryCount; j++) {
glyphId = file.getUint16();
const charCode = firstCode + j;
mappings.push({
charCode,
glyphId
});
}
} else if (format === 12) {
file.skip(2 + 4 + 4);
const nGroups = file.getInt32() >>> 0;
for (j = 0; j < nGroups; j++) {
const startCharCode = file.getInt32() >>> 0;
const endCharCode = file.getInt32() >>> 0;
let glyphCode = file.getInt32() >>> 0;
for (let charCode = startCharCode; charCode <= endCharCode; charCode++) {
mappings.push({
charCode,
glyphId: glyphCode++
});
}
}
} else {
warn("cmap table has unsupported format: " + format);
return {
platformId: -1,
encodingId: -1,
mappings: [],
hasShortCmap: false
};
}
mappings.sort((a, b) => a.charCode - b.charCode);
const finalMappings = [],
seenCharCodes = new Set();
for (const map of mappings) {
const {
charCode
} = map;
if (seenCharCodes.has(charCode)) {
continue;
}
seenCharCodes.add(charCode);
finalMappings.push(map);
}
return {
platformId: potentialTable.platformId,
encodingId: potentialTable.encodingId,
mappings: finalMappings,
hasShortCmap
};
}
function sanitizeMetrics(file, header, metrics, headTable, numGlyphs, dupFirstEntry) {
if (!header) {
if (metrics) {
metrics.data = null;
}
return;
}
file.pos = (file.start || 0) + header.offset;
file.pos += 4;
file.pos += 2;
file.pos += 2;
file.pos += 2;
file.pos += 2;
file.pos += 2;
file.pos += 2;
file.pos += 2;
file.pos += 2;
file.pos += 2;
const caretOffset = file.getUint16();
file.pos += 8;
file.pos += 2;
let numOfMetrics = file.getUint16();
if (caretOffset !== 0) {
const macStyle = int16(headTable.data[44], headTable.data[45]);
if (!(macStyle & 2)) {
header.data[22] = 0;
header.data[23] = 0;
}
}
if (numOfMetrics > numGlyphs) {
info(`The numOfMetrics (${numOfMetrics}) should not be ` + `greater than the numGlyphs (${numGlyphs}).`);
numOfMetrics = numGlyphs;
header.data[34] = (numOfMetrics & 0xff00) >> 8;
header.data[35] = numOfMetrics & 0x00ff;
}
const numOfSidebearings = numGlyphs - numOfMetrics;
const numMissing = numOfSidebearings - (metrics.length - numOfMetrics * 4 >> 1);
if (numMissing > 0) {
const entries = new Uint8Array(metrics.length + numMissing * 2);
entries.set(metrics.data);
if (dupFirstEntry) {
entries[metrics.length] = metrics.data[2];
entries[metrics.length + 1] = metrics.data[3];
}
metrics.data = entries;
}
}
function sanitizeGlyph(source, sourceStart, sourceEnd, dest, destStart, hintsValid) {
const glyphProfile = {
length: 0,
sizeOfInstructions: 0
};
if (sourceStart < 0 || sourceStart >= source.length || sourceEnd > source.length || sourceEnd - sourceStart <= 12) {
return glyphProfile;
}
const glyf = source.subarray(sourceStart, sourceEnd);
const xMin = signedInt16(glyf[2], glyf[3]);
const yMin = signedInt16(glyf[4], glyf[5]);
const xMax = signedInt16(glyf[6], glyf[7]);
const yMax = signedInt16(glyf[8], glyf[9]);
if (xMin > xMax) {
writeSignedInt16(glyf, 2, xMax);
writeSignedInt16(glyf, 6, xMin);
}
if (yMin > yMax) {
writeSignedInt16(glyf, 4, yMax);
writeSignedInt16(glyf, 8, yMin);
}
const contoursCount = signedInt16(glyf[0], glyf[1]);
if (contoursCount < 0) {
if (contoursCount < -1) {
return glyphProfile;
}
dest.set(glyf, destStart);
glyphProfile.length = glyf.length;
return glyphProfile;
}
let i,
j = 10,
flagsCount = 0;
for (i = 0; i < contoursCount; i++) {
const endPoint = glyf[j] << 8 | glyf[j + 1];
flagsCount = endPoint + 1;
j += 2;
}
const instructionsStart = j;
const instructionsLength = glyf[j] << 8 | glyf[j + 1];
glyphProfile.sizeOfInstructions = instructionsLength;
j += 2 + instructionsLength;
const instructionsEnd = j;
let coordinatesLength = 0;
for (i = 0; i < flagsCount; i++) {
const flag = glyf[j++];
if (flag & 0xc0) {
glyf[j - 1] = flag & 0x3f;
}
let xLength = 2;
if (flag & 2) {
xLength = 1;
} else if (flag & 16) {
xLength = 0;
}
let yLength = 2;
if (flag & 4) {
yLength = 1;
} else if (flag & 32) {
yLength = 0;
}
const xyLength = xLength + yLength;
coordinatesLength += xyLength;
if (flag & 8) {
const repeat = glyf[j++];
if (repeat === 0) {
glyf[j - 1] ^= 8;
}
i += repeat;
coordinatesLength += repeat * xyLength;
}
}
if (coordinatesLength === 0) {
return glyphProfile;
}
let glyphDataLength = j + coordinatesLength;
if (glyphDataLength > glyf.length) {
return glyphProfile;
}
if (!hintsValid && instructionsLength > 0) {
dest.set(glyf.subarray(0, instructionsStart), destStart);
dest.set([0, 0], destStart + instructionsStart);
dest.set(glyf.subarray(instructionsEnd, glyphDataLength), destStart + instructionsStart + 2);
glyphDataLength -= instructionsLength;
if (glyf.length - glyphDataLength > 3) {
glyphDataLength = glyphDataLength + 3 & ~3;
}
glyphProfile.length = glyphDataLength;
return glyphProfile;
}
if (glyf.length - glyphDataLength > 3) {
glyphDataLength = glyphDataLength + 3 & ~3;
dest.set(glyf.subarray(0, glyphDataLength), destStart);
glyphProfile.length = glyphDataLength;
return glyphProfile;
}
dest.set(glyf, destStart);
glyphProfile.length = glyf.length;
return glyphProfile;
}
function sanitizeHead(head, numGlyphs, locaLength) {
const data = head.data;
const version = int32(data[0], data[1], data[2], data[3]);
if (version >> 16 !== 1) {
info("Attempting to fix invalid version in head table: " + version);
data[0] = 0;
data[1] = 1;
data[2] = 0;
data[3] = 0;
}
const indexToLocFormat = int16(data[50], data[51]);
if (indexToLocFormat < 0 || indexToLocFormat > 1) {
info("Attempting to fix invalid indexToLocFormat in head table: " + indexToLocFormat);
const numGlyphsPlusOne = numGlyphs + 1;
if (locaLength === numGlyphsPlusOne << 1) {
data[50] = 0;
data[51] = 0;
} else if (locaLength === numGlyphsPlusOne << 2) {
data[50] = 0;
data[51] = 1;
} else {
throw new FormatError("Could not fix indexToLocFormat: " + indexToLocFormat);
}
}
}
function sanitizeGlyphLocations(loca, glyf, numGlyphs, isGlyphLocationsLong, hintsValid, dupFirstEntry, maxSizeOfInstructions) {
let itemSize, itemDecode, itemEncode;
if (isGlyphLocationsLong) {
itemSize = 4;
itemDecode = function fontItemDecodeLong(data, offset) {
return data[offset] << 24 | data[offset + 1] << 16 | data[offset + 2] << 8 | data[offset + 3];
};
itemEncode = function fontItemEncodeLong(data, offset, value) {
data[offset] = value >>> 24 & 0xff;
data[offset + 1] = value >> 16 & 0xff;
data[offset + 2] = value >> 8 & 0xff;
data[offset + 3] = value & 0xff;
};
} else {
itemSize = 2;
itemDecode = function fontItemDecode(data, offset) {
return data[offset] << 9 | data[offset + 1] << 1;
};
itemEncode = function fontItemEncode(data, offset, value) {
data[offset] = value >> 9 & 0xff;
data[offset + 1] = value >> 1 & 0xff;
};
}
const numGlyphsOut = dupFirstEntry ? numGlyphs + 1 : numGlyphs;
const locaDataSize = itemSize * (1 + numGlyphsOut);
const locaData = new Uint8Array(locaDataSize);
locaData.set(loca.data.subarray(0, locaDataSize));
loca.data = locaData;
const oldGlyfData = glyf.data;
const oldGlyfDataLength = oldGlyfData.length;
const newGlyfData = new Uint8Array(oldGlyfDataLength);
let i, j;
const locaEntries = [];
for (i = 0, j = 0; i < numGlyphs + 1; i++, j += itemSize) {
let offset = itemDecode(locaData, j);
if (offset > oldGlyfDataLength) {
offset = oldGlyfDataLength;
}
locaEntries.push({
index: i,
offset,
endOffset: 0
});
}
locaEntries.sort((a, b) => a.offset - b.offset);
for (i = 0; i < numGlyphs; i++) {
locaEntries[i].endOffset = locaEntries[i + 1].offset;
}
locaEntries.sort((a, b) => a.index - b.index);
for (i = 0; i < numGlyphs; i++) {
const {
offset,
endOffset
} = locaEntries[i];
if (offset !== 0 || endOffset !== 0) {
break;
}
const nextOffset = locaEntries[i + 1].offset;
if (nextOffset === 0) {
continue;
}
locaEntries[i].endOffset = nextOffset;
break;
}
const last = locaEntries.at(-2);
if (last.offset !== 0 && last.endOffset === 0) {
last.endOffset = oldGlyfDataLength;
}
const missingGlyphs = Object.create(null);
let writeOffset = 0;
itemEncode(locaData, 0, writeOffset);
for (i = 0, j = itemSize; i < numGlyphs; i++, j += itemSize) {
const glyphProfile = sanitizeGlyph(oldGlyfData, locaEntries[i].offset, locaEntries[i].endOffset, newGlyfData, writeOffset, hintsValid);
const newLength = glyphProfile.length;
if (newLength === 0) {
missingGlyphs[i] = true;
}
if (glyphProfile.sizeOfInstructions > maxSizeOfInstructions) {
maxSizeOfInstructions = glyphProfile.sizeOfInstructions;
}
writeOffset += newLength;
itemEncode(locaData, j, writeOffset);
}
if (writeOffset === 0) {
const simpleGlyph = new Uint8Array([0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49, 0]);
for (i = 0, j = itemSize; i < numGlyphsOut; i++, j += itemSize) {
itemEncode(locaData, j, simpleGlyph.length);
}
glyf.data = simpleGlyph;
} else if (dupFirstEntry) {
const firstEntryLength = itemDecode(locaData, itemSize);
if (newGlyfData.length > firstEntryLength + writeOffset) {
glyf.data = newGlyfData.subarray(0, firstEntryLength + writeOffset);
} else {
glyf.data = new Uint8Array(firstEntryLength + writeOffset);
glyf.data.set(newGlyfData.subarray(0, writeOffset));
}
glyf.data.set(newGlyfData.subarray(0, firstEntryLength), writeOffset);
itemEncode(loca.data, locaData.length - itemSize, writeOffset + firstEntryLength);
} else {
glyf.data = newGlyfData.subarray(0, writeOffset);
}
return {
missingGlyphs,
maxSizeOfInstructions
};
}
function readPostScriptTable(post, propertiesObj, maxpNumGlyphs) {
const start = (font.start || 0) + post.offset;
font.pos = start;
const length = post.length,
end = start + length;
const version = font.getInt32();
font.skip(28);
let glyphNames;
let valid = true;
let i;
switch (version) {
case 0x00010000:
glyphNames = MacStandardGlyphOrdering;
break;
case 0x00020000:
const numGlyphs = font.getUint16();
if (numGlyphs !== maxpNumGlyphs) {
valid = false;
break;
}
const glyphNameIndexes = [];
for (i = 0; i < numGlyphs; ++i) {
const index = font.getUint16();
if (index >= 32768) {
valid = false;
break;
}
glyphNameIndexes.push(index);
}
if (!valid) {
break;
}
const customNames = [],
strBuf = [];
while (font.pos < end) {
const stringLength = font.getByte();
strBuf.length = stringLength;
for (i = 0; i < stringLength; ++i) {
strBuf[i] = String.fromCharCode(font.getByte());
}
customNames.push(strBuf.join(""));
}
glyphNames = [];
for (i = 0; i < numGlyphs; ++i) {
const j = glyphNameIndexes[i];
if (j < 258) {
glyphNames.push(MacStandardGlyphOrdering[j]);
continue;
}
glyphNames.push(customNames[j - 258]);
}
break;
case 0x00030000:
break;
default:
warn("Unknown/unsupported post table version " + version);
valid = false;
if (propertiesObj.defaultEncoding) {
glyphNames = propertiesObj.defaultEncoding;
}
break;
}
propertiesObj.glyphNames = glyphNames;
return valid;
}
function readNameTable(nameTable) {
const start = (font.start || 0) + nameTable.offset;
font.pos = start;
const names = [[], []],
records = [];
const length = nameTable.length,
end = start + length;
const format = font.getUint16();
const FORMAT_0_HEADER_LENGTH = 6;
if (format !== 0 || length < FORMAT_0_HEADER_LENGTH) {
return [names, records];
}
const numRecords = font.getUint16();
const stringsStart = font.getUint16();
const NAME_RECORD_LENGTH = 12;
let i, ii;
for (i = 0; i < numRecords && font.pos + NAME_RECORD_LENGTH <= end; i++) {
const r = {
platform: font.getUint16(),
encoding: font.getUint16(),
language: font.getUint16(),
name: font.getUint16(),
length: font.getUint16(),
offset: font.getUint16()
};
if (isMacNameRecord(r) || isWinNameRecord(r)) {
records.push(r);
}
}
for (i = 0, ii = records.length; i < ii; i++) {
const record = records[i];
if (record.length <= 0) {
continue;
}
const pos = start + stringsStart + record.offset;
if (pos + record.length > end) {
continue;
}
font.pos = pos;
const nameIndex = record.name;
if (record.encoding) {
let str = "";
for (let j = 0, jj = record.length; j < jj; j += 2) {
str += String.fromCharCode(font.getUint16());
}
names[1][nameIndex] = str;
} else {
names[0][nameIndex] = font.getString(record.length);
}
}
return [names, records];
}
const TTOpsStackDeltas = [0, 0, 0, 0, 0, 0, 0, 0, -2, -2, -2, -2, 0, 0, -2, -5, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, -1, 0, -1, -1, -1, -1, 1, -1, -999, 0, 1, 0, -1, -2, 0, -1, -2, -1, -1, 0, -1, -1, 0, 0, -999, -999, -1, -1, -1, -1, -2, -999, -2, -2, -999, 0, -2, -2, 0, 0, -2, 0, -2, 0, 0, 0, -2, -1, -1, 1, 1, 0, 0, -1, -1, -1, -1, -1, -1, -1, 0, 0, -1, 0, -1, -1, 0, -999, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2, -999, -999, -999, -999, -999, -1, -1, -2, -2, 0, 0, 0, 0, -1, -1, -999, -2, -2, 0, 0, -1, -2, -2, 0, 0, 0, -1, -1, -1, -2];
function sanitizeTTProgram(table, ttContext) {
let data = table.data;
let i = 0,
j,
n,
b,
funcId,
pc,
lastEndf = 0,
lastDeff = 0;
const stack = [];
const callstack = [];
const functionsCalled = [];
let tooComplexToFollowFunctions = ttContext.tooComplexToFollowFunctions;
let inFDEF = false,
ifLevel = 0,
inELSE = 0;
for (let ii = data.length; i < ii;) {
const op = data[i++];
if (op === 0x40) {
n = data[i++];
if (inFDEF || inELSE) {
i += n;
} else {
for (j = 0; j < n; j++) {
stack.push(data[i++]);
}
}
} else if (op === 0x41) {
n = data[i++];
if (inFDEF || inELSE) {
i += n * 2;
} else {
for (j = 0; j < n; j++) {
b = data[i++];
stack.push(b << 8 | data[i++]);
}
}
} else if ((op & 0xf8) === 0xb0) {
n = op - 0xb0 + 1;
if (inFDEF || inELSE) {
i += n;
} else {
for (j = 0; j < n; j++) {
stack.push(data[i++]);
}
}
} else if ((op & 0xf8) === 0xb8) {
n = op - 0xb8 + 1;
if (inFDEF || inELSE) {
i += n * 2;
} else {
for (j = 0; j < n; j++) {
b = data[i++];
stack.push(signedInt16(b, data[i++]));
}
}
} else if (op === 0x2b && !tooComplexToFollowFunctions) {
if (!inFDEF && !inELSE) {
funcId = stack.at(-1);
if (isNaN(funcId)) {
info("TT: CALL empty stack (or invalid entry).");
} else {
ttContext.functionsUsed[funcId] = true;
if (funcId in ttContext.functionsStackDeltas) {
const newStackLength = stack.length + ttContext.functionsStackDeltas[funcId];
if (newStackLength < 0) {
warn("TT: CALL invalid functions stack delta.");
ttContext.hintsValid = false;
return;
}
stack.length = newStackLength;
} else if (funcId in ttContext.functionsDefined && !functionsCalled.includes(funcId)) {
callstack.push({
data,
i,
stackTop: stack.length - 1
});
functionsCalled.push(funcId);
pc = ttContext.functionsDefined[funcId];
if (!pc) {
warn("TT: CALL non-existent function");
ttContext.hintsValid = false;
return;
}
data = pc.data;
i = pc.i;
}
}
}
} else if (op === 0x2c && !tooComplexToFollowFunctions) {
if (inFDEF || inELSE) {
warn("TT: nested FDEFs not allowed");
tooComplexToFollowFunctions = true;
}
inFDEF = true;
lastDeff = i;
funcId = stack.pop();
ttContext.functionsDefined[funcId] = {
data,
i
};
} else if (op === 0x2d) {
if (inFDEF) {
inFDEF = false;
lastEndf = i;
} else {
pc = callstack.pop();
if (!pc) {
warn("TT: ENDF bad stack");
ttContext.hintsValid = false;
return;
}
funcId = functionsCalled.pop();
data = pc.data;
i = pc.i;
ttContext.functionsStackDeltas[funcId] = stack.length - pc.stackTop;
}
} else if (op === 0x89) {
if (inFDEF || inELSE) {
warn("TT: nested IDEFs not allowed");
tooComplexToFollowFunctions = true;
}
inFDEF = true;
lastDeff = i;
} else if (op === 0x58) {
++ifLevel;
} else if (op === 0x1b) {
inELSE = ifLevel;
} else if (op === 0x59) {
if (inELSE === ifLevel) {
inELSE = 0;
}
--ifLevel;
} else if (op === 0x1c) {
if (!inFDEF && !inELSE) {
const offset = stack.at(-1);
if (offset > 0) {
i += offset - 1;
}
}
}
if (!inFDEF && !inELSE) {
let stackDelta = 0;
if (op <= 0x8e) {
stackDelta = TTOpsStackDeltas[op];
} else if (op >= 0xc0 && op <= 0xdf) {
stackDelta = -1;
} else if (op >= 0xe0) {
stackDelta = -2;
}
if (op >= 0x71 && op <= 0x75) {
n = stack.pop();
if (!isNaN(n)) {
stackDelta = -n * 2;
}
}
while (stackDelta < 0 && stack.length > 0) {
stack.pop();
stackDelta++;
}
while (stackDelta > 0) {
stack.push(NaN);
stackDelta--;
}
}
}
ttContext.tooComplexToFollowFunctions = tooComplexToFollowFunctions;
const content = [data];
if (i > data.length) {
content.push(new Uint8Array(i - data.length));
}
if (lastDeff > lastEndf) {
warn("TT: complementing a missing function tail");
content.push(new Uint8Array([0x22, 0x2d]));
}
foldTTTable(table, content);
}
function checkInvalidFunctions(ttContext, maxFunctionDefs) {
if (ttContext.tooComplexToFollowFunctions) {
return;
}
if (ttContext.functionsDefined.length > maxFunctionDefs) {
warn("TT: more functions defined than expected");
ttContext.hintsValid = false;
return;
}
for (let j = 0, jj = ttContext.functionsUsed.length; j < jj; j++) {
if (j > maxFunctionDefs) {
warn("TT: invalid function id: " + j);
ttContext.hintsValid = false;
return;
}
if (ttContext.functionsUsed[j] && !ttContext.functionsDefined[j]) {
warn("TT: undefined function: " + j);
ttContext.hintsValid = false;
return;
}
}
}
function foldTTTable(table, content) {
if (content.length > 1) {
let newLength = 0;
let j, jj;
for (j = 0, jj = content.length; j < jj; j++) {
newLength += content[j].length;
}
newLength = newLength + 3 & ~3;
const result = new Uint8Array(newLength);
let pos = 0;
for (j = 0, jj = content.length; j < jj; j++) {
result.set(content[j], pos);
pos += content[j].length;
}
table.data = result;
table.length = newLength;
}
}
function sanitizeTTPrograms(fpgm, prep, cvt, maxFunctionDefs) {
const ttContext = {
functionsDefined: [],
functionsUsed: [],
functionsStackDeltas: [],
tooComplexToFollowFunctions: false,
hintsValid: true
};
if (fpgm) {
sanitizeTTProgram(fpgm, ttContext);
}
if (prep) {
sanitizeTTProgram(prep, ttContext);
}
if (fpgm) {
checkInvalidFunctions(ttContext, maxFunctionDefs);
}
if (cvt && cvt.length & 1) {
const cvtData = new Uint8Array(cvt.length + 1);
cvtData.set(cvt.data);
cvt.data = cvtData;
}
return ttContext.hintsValid;
}
font = new Stream(new Uint8Array(font.getBytes()));
let header, tables;
if (isTrueTypeCollectionFile(font)) {
const ttcData = readTrueTypeCollectionData(font, this.name);
header = ttcData.header;
tables = ttcData.tables;
} else {
header = readOpenTypeHeader(font);
tables = readTables(font, header.numTables);
}
let cff, cffFile;
const isTrueType = !tables["CFF "];
if (!isTrueType) {
const isComposite = properties.composite && (properties.cidToGidMap?.length > 0 || !(properties.cMap instanceof IdentityCMap));
if (header.version === "OTTO" && !isComposite || !tables.head || !tables.hhea || !tables.maxp || !tables.post) {
cffFile = new Stream(tables["CFF "].data);
cff = new CFFFont(cffFile, properties);
adjustWidths(properties);
return this.convert(name, cff, properties);
}
delete tables.glyf;
delete tables.loca;
delete tables.fpgm;
delete tables.prep;
delete tables["cvt "];
this.isOpenType = true;
} else {
if (!tables.loca) {
throw new FormatError('Required "loca" table is not found');
}
if (!tables.glyf) {
warn('Required "glyf" table is not found -- trying to recover.');
tables.glyf = {
tag: "glyf",
data: new Uint8Array(0)
};
}
this.isOpenType = false;
}
if (!tables.maxp) {
throw new FormatError('Required "maxp" table is not found');
}
font.pos = (font.start || 0) + tables.maxp.offset;
let version = font.getInt32();
const numGlyphs = font.getUint16();
if (version !== 0x00010000 && version !== 0x00005000) {
if (tables.maxp.length === 6) {
version = 0x0005000;
} else if (tables.maxp.length >= 32) {
version = 0x00010000;
} else {
throw new FormatError(`"maxp" table has a wrong version number`);
}
writeUint32(tables.maxp.data, 0, version);
}
if (properties.scaleFactors?.length === numGlyphs && isTrueType) {
const {
scaleFactors
} = properties;
const isGlyphLocationsLong = int16(tables.head.data[50], tables.head.data[51]);
const glyphs = new GlyfTable({
glyfTable: tables.glyf.data,
isGlyphLocationsLong,
locaTable: tables.loca.data,
numGlyphs
});
glyphs.scale(scaleFactors);
const {
glyf,
loca,
isLocationLong
} = glyphs.write();
tables.glyf.data = glyf;
tables.loca.data = loca;
if (isLocationLong !== !!isGlyphLocationsLong) {
tables.head.data[50] = 0;
tables.head.data[51] = isLocationLong ? 1 : 0;
}
const metrics = tables.hmtx.data;
for (let i = 0; i < numGlyphs; i++) {
const j = 4 * i;
const advanceWidth = Math.round(scaleFactors[i] * int16(metrics[j], metrics[j + 1]));
metrics[j] = advanceWidth >> 8 & 0xff;
metrics[j + 1] = advanceWidth & 0xff;
const lsb = Math.round(scaleFactors[i] * signedInt16(metrics[j + 2], metrics[j + 3]));
writeSignedInt16(metrics, j + 2, lsb);
}
}
let numGlyphsOut = numGlyphs + 1;
let dupFirstEntry = true;
if (numGlyphsOut > 0xffff) {
dupFirstEntry = false;
numGlyphsOut = numGlyphs;
warn("Not enough space in glyfs to duplicate first glyph.");
}
let maxFunctionDefs = 0;
let maxSizeOfInstructions = 0;
if (version >= 0x00010000 && tables.maxp.length >= 32) {
font.pos += 8;
const maxZones = font.getUint16();
if (maxZones > 2) {
tables.maxp.data[14] = 0;
tables.maxp.data[15] = 2;
}
font.pos += 4;
maxFunctionDefs = font.getUint16();
font.pos += 4;
maxSizeOfInstructions = font.getUint16();
}
tables.maxp.data[4] = numGlyphsOut >> 8;
tables.maxp.data[5] = numGlyphsOut & 255;
const hintsValid = sanitizeTTPrograms(tables.fpgm, tables.prep, tables["cvt "], maxFunctionDefs);
if (!hintsValid) {
delete tables.fpgm;
delete tables.prep;
delete tables["cvt "];
}
sanitizeMetrics(font, tables.hhea, tables.hmtx, tables.head, numGlyphsOut, dupFirstEntry);
if (!tables.head) {
throw new FormatError('Required "head" table is not found');
}
sanitizeHead(tables.head, numGlyphs, isTrueType ? tables.loca.length : 0);
let missingGlyphs = Object.create(null);
if (isTrueType) {
const isGlyphLocationsLong = int16(tables.head.data[50], tables.head.data[51]);
const glyphsInfo = sanitizeGlyphLocations(tables.loca, tables.glyf, numGlyphs, isGlyphLocationsLong, hintsValid, dupFirstEntry, maxSizeOfInstructions);
missingGlyphs = glyphsInfo.missingGlyphs;
if (version >= 0x00010000 && tables.maxp.length >= 32) {
tables.maxp.data[26] = glyphsInfo.maxSizeOfInstructions >> 8;
tables.maxp.data[27] = glyphsInfo.maxSizeOfInstructions & 255;
}
}
if (!tables.hhea) {
throw new FormatError('Required "hhea" table is not found');
}
if (tables.hhea.data[10] === 0 && tables.hhea.data[11] === 0) {
tables.hhea.data[10] = 0xff;
tables.hhea.data[11] = 0xff;
}
const metricsOverride = {
unitsPerEm: int16(tables.head.data[18], tables.head.data[19]),
yMax: signedInt16(tables.head.data[42], tables.head.data[43]),
yMin: signedInt16(tables.head.data[38], tables.head.data[39]),
ascent: signedInt16(tables.hhea.data[4], tables.hhea.data[5]),
descent: signedInt16(tables.hhea.data[6], tables.hhea.data[7]),
lineGap: signedInt16(tables.hhea.data[8], tables.hhea.data[9])
};
this.ascent = metricsOverride.ascent / metricsOverride.unitsPerEm;
this.descent = metricsOverride.descent / metricsOverride.unitsPerEm;
this.lineGap = metricsOverride.lineGap / metricsOverride.unitsPerEm;
if (this.cssFontInfo?.lineHeight) {
this.lineHeight = this.cssFontInfo.metrics.lineHeight;
this.lineGap = this.cssFontInfo.metrics.lineGap;
} else {
this.lineHeight = this.ascent - this.descent + this.lineGap;
}
if (tables.post) {
readPostScriptTable(tables.post, properties, numGlyphs);
}
tables.post = {
tag: "post",
data: createPostTable(properties)
};
const charCodeToGlyphId = Object.create(null);
function hasGlyph(glyphId) {
return !missingGlyphs[glyphId];
}
if (properties.composite) {
const cidToGidMap = properties.cidToGidMap || [];
const isCidToGidMapEmpty = cidToGidMap.length === 0;
properties.cMap.forEach(function (charCode, cid) {
if (typeof cid === "string") {
cid = convertCidString(charCode, cid, true);
}
if (cid > 0xffff) {
throw new FormatError("Max size of CID is 65,535");
}
let glyphId = -1;
if (isCidToGidMapEmpty) {
glyphId = cid;
} else if (cidToGidMap[cid] !== undefined) {
glyphId = cidToGidMap[cid];
}
if (glyphId >= 0 && glyphId < numGlyphs && hasGlyph(glyphId)) {
charCodeToGlyphId[charCode] = glyphId;
}
});
} else {
const cmapTable = readCmapTable(tables.cmap, font, this.isSymbolicFont, properties.hasEncoding);
const cmapPlatformId = cmapTable.platformId;
const cmapEncodingId = cmapTable.encodingId;
const cmapMappings = cmapTable.mappings;
let baseEncoding = [],
forcePostTable = false;
if (properties.hasEncoding && (properties.baseEncodingName === "MacRomanEncoding" || properties.baseEncodingName === "WinAnsiEncoding")) {
baseEncoding = getEncoding(properties.baseEncodingName);
}
if (properties.hasEncoding && !this.isSymbolicFont && (cmapPlatformId === 3 && cmapEncodingId === 1 || cmapPlatformId === 1 && cmapEncodingId === 0)) {
const glyphsUnicodeMap = getGlyphsUnicode();
for (let charCode = 0; charCode < 256; charCode++) {
let glyphName;
if (this.differences[charCode] !== undefined) {
glyphName = this.differences[charCode];
} else if (baseEncoding.length && baseEncoding[charCode] !== "") {
glyphName = baseEncoding[charCode];
} else {
glyphName = StandardEncoding[charCode];
}
if (!glyphName) {
continue;
}
const standardGlyphName = recoverGlyphName(glyphName, glyphsUnicodeMap);
let unicodeOrCharCode;
if (cmapPlatformId === 3 && cmapEncodingId === 1) {
unicodeOrCharCode = glyphsUnicodeMap[standardGlyphName];
} else if (cmapPlatformId === 1 && cmapEncodingId === 0) {
unicodeOrCharCode = MacRomanEncoding.indexOf(standardGlyphName);
}
if (unicodeOrCharCode === undefined) {
if (!properties.glyphNames && properties.hasIncludedToUnicodeMap && !(this.toUnicode instanceof IdentityToUnicodeMap)) {
const unicode = this.toUnicode.get(charCode);
if (unicode) {
unicodeOrCharCode = unicode.codePointAt(0);
}
}
if (unicodeOrCharCode === undefined) {
continue;
}
}
for (const mapping of cmapMappings) {
if (mapping.charCode !== unicodeOrCharCode) {
continue;
}
charCodeToGlyphId[charCode] = mapping.glyphId;
break;
}
}
} else if (cmapPlatformId === 0) {
for (const mapping of cmapMappings) {
charCodeToGlyphId[mapping.charCode] = mapping.glyphId;
}
forcePostTable = true;
} else if (cmapPlatformId === 3 && cmapEncodingId === 0) {
for (const mapping of cmapMappings) {
let charCode = mapping.charCode;
if (charCode >= 0xf000 && charCode <= 0xf0ff) {
charCode &= 0xff;
}
charCodeToGlyphId[charCode] = mapping.glyphId;
}
} else {
for (const mapping of cmapMappings) {
charCodeToGlyphId[mapping.charCode] = mapping.glyphId;
}
}
if (properties.glyphNames && (baseEncoding.length || this.differences.length)) {
for (let i = 0; i < 256; ++i) {
if (!forcePostTable && charCodeToGlyphId[i] !== undefined) {
continue;
}
const glyphName = this.differences[i] || baseEncoding[i];
if (!glyphName) {
continue;
}
const glyphId = properties.glyphNames.indexOf(glyphName);
if (glyphId > 0 && hasGlyph(glyphId)) {
charCodeToGlyphId[i] = glyphId;
}
}
}
}
if (charCodeToGlyphId.length === 0) {
charCodeToGlyphId[0] = 0;
}
let glyphZeroId = numGlyphsOut - 1;
if (!dupFirstEntry) {
glyphZeroId = 0;
}
if (!properties.cssFontInfo) {
const newMapping = adjustMapping(charCodeToGlyphId, hasGlyph, glyphZeroId, this.toUnicode);
this.toFontChar = newMapping.toFontChar;
tables.cmap = {
tag: "cmap",
data: createCmapTable(newMapping.charCodeToGlyphId, newMapping.toUnicodeExtraMap, numGlyphsOut)
};
if (!tables["OS/2"] || !validateOS2Table(tables["OS/2"], font)) {
tables["OS/2"] = {
tag: "OS/2",
data: createOS2Table(properties, newMapping.charCodeToGlyphId, metricsOverride)
};
}
}
if (!isTrueType) {
try {
cffFile = new Stream(tables["CFF "].data);
const parser = new CFFParser(cffFile, properties, SEAC_ANALYSIS_ENABLED);
cff = parser.parse();
cff.duplicateFirstGlyph();
const compiler = new CFFCompiler(cff);
tables["CFF "].data = compiler.compile();
} catch {
warn("Failed to compile font " + properties.loadedName);
}
}
if (!tables.name) {
tables.name = {
tag: "name",
data: createNameTable(this.name)
};
} else {
const [namePrototype, nameRecords] = readNameTable(tables.name);
tables.name.data = createNameTable(name, namePrototype);
this.psName = namePrototype[0][6] || null;
if (!properties.composite) {
adjustTrueTypeToUnicode(properties, this.isSymbolicFont, nameRecords);
}
}
const builder = new OpenTypeFileBuilder(header.version);
for (const tableTag in tables) {
builder.addTable(tableTag, tables[tableTag].data);
}
return builder.toArray();
}
convert(fontName, font, properties) {
properties.fixedPitch = false;
if (properties.builtInEncoding) {
adjustType1ToUnicode(properties, properties.builtInEncoding);
}
let glyphZeroId = 1;
if (font instanceof CFFFont) {
glyphZeroId = font.numGlyphs - 1;
}
const mapping = font.getGlyphMapping(properties);
let newMapping = null;
let newCharCodeToGlyphId = mapping;
let toUnicodeExtraMap = null;
if (!properties.cssFontInfo) {
newMapping = adjustMapping(mapping, font.hasGlyphId.bind(font), glyphZeroId, this.toUnicode);
this.toFontChar = newMapping.toFontChar;
newCharCodeToGlyphId = newMapping.charCodeToGlyphId;
toUnicodeExtraMap = newMapping.toUnicodeExtraMap;
}
const numGlyphs = font.numGlyphs;
function getCharCodes(charCodeToGlyphId, glyphId) {
let charCodes = null;
for (const charCode in charCodeToGlyphId) {
if (glyphId === charCodeToGlyphId[charCode]) {
(charCodes ||= []).push(charCode | 0);
}
}
return charCodes;
}
function createCharCode(charCodeToGlyphId, glyphId) {
for (const charCode in charCodeToGlyphId) {
if (glyphId === charCodeToGlyphId[charCode]) {
return charCode | 0;
}
}
newMapping.charCodeToGlyphId[newMapping.nextAvailableFontCharCode] = glyphId;
return newMapping.nextAvailableFontCharCode++;
}
const seacs = font.seacs;
if (newMapping && SEAC_ANALYSIS_ENABLED && seacs?.length) {
const matrix = properties.fontMatrix || FONT_IDENTITY_MATRIX;
const charset = font.getCharset();
const seacMap = Object.create(null);
for (let glyphId in seacs) {
glyphId |= 0;
const seac = seacs[glyphId];
const baseGlyphName = StandardEncoding[seac[2]];
const accentGlyphName = StandardEncoding[seac[3]];
const baseGlyphId = charset.indexOf(baseGlyphName);
const accentGlyphId = charset.indexOf(accentGlyphName);
if (baseGlyphId < 0 || accentGlyphId < 0) {
continue;
}
const accentOffset = {
x: seac[0] * matrix[0] + seac[1] * matrix[2] + matrix[4],
y: seac[0] * matrix[1] + seac[1] * matrix[3] + matrix[5]
};
const charCodes = getCharCodes(mapping, glyphId);
if (!charCodes) {
continue;
}
for (const charCode of charCodes) {
const charCodeToGlyphId = newMapping.charCodeToGlyphId;
const baseFontCharCode = createCharCode(charCodeToGlyphId, baseGlyphId);
const accentFontCharCode = createCharCode(charCodeToGlyphId, accentGlyphId);
seacMap[charCode] = {
baseFontCharCode,
accentFontCharCode,
accentOffset
};
}
}
properties.seacMap = seacMap;
}
const unitsPerEm = properties.fontMatrix ? 1 / Math.max(...properties.fontMatrix.slice(0, 4).map(Math.abs)) : 1000;
const builder = new OpenTypeFileBuilder("\x4F\x54\x54\x4F");
builder.addTable("CFF ", font.data);
builder.addTable("OS/2", createOS2Table(properties, newCharCodeToGlyphId));
builder.addTable("cmap", createCmapTable(newCharCodeToGlyphId, toUnicodeExtraMap, numGlyphs));
builder.addTable("head", "\x00\x01\x00\x00" + "\x00\x00\x10\x00" + "\x00\x00\x00\x00" + "\x5F\x0F\x3C\xF5" + "\x00\x00" + safeString16(unitsPerEm) + "\x00\x00\x00\x00\x9e\x0b\x7e\x27" + "\x00\x00\x00\x00\x9e\x0b\x7e\x27" + "\x00\x00" + safeString16(properties.descent) + "\x0F\xFF" + safeString16(properties.ascent) + string16(properties.italicAngle ? 2 : 0) + "\x00\x11" + "\x00\x00" + "\x00\x00" + "\x00\x00");
builder.addTable("hhea", "\x00\x01\x00\x00" + safeString16(properties.ascent) + safeString16(properties.descent) + "\x00\x00" + "\xFF\xFF" + "\x00\x00" + "\x00\x00" + "\x00\x00" + safeString16(properties.capHeight) + safeString16(Math.tan(properties.italicAngle) * properties.xHeight) + "\x00\x00" + "\x00\x00" + "\x00\x00" + "\x00\x00" + "\x00\x00" + "\x00\x00" + string16(numGlyphs));
builder.addTable("hmtx", function fontFieldsHmtx() {
const charstrings = font.charstrings;
const cffWidths = font.cff ? font.cff.widths : null;
let hmtx = "\x00\x00\x00\x00";
for (let i = 1, ii = numGlyphs; i < ii; i++) {
let width = 0;
if (charstrings) {
const charstring = charstrings[i - 1];
width = "width" in charstring ? charstring.width : 0;
} else if (cffWidths) {
width = Math.ceil(cffWidths[i] || 0);
}
hmtx += string16(width) + string16(0);
}
return hmtx;
}());
builder.addTable("maxp", "\x00\x00\x50\x00" + string16(numGlyphs));
builder.addTable("name", createNameTable(fontName));
builder.addTable("post", createPostTable(properties));
return builder.toArray();
}
get _spaceWidth() {
const possibleSpaceReplacements = ["space", "minus", "one", "i", "I"];
let width;
for (const glyphName of possibleSpaceReplacements) {
if (glyphName in this.widths) {
width = this.widths[glyphName];
break;
}
const glyphsUnicodeMap = getGlyphsUnicode();
const glyphUnicode = glyphsUnicodeMap[glyphName];
let charcode = 0;
if (this.composite && this.cMap.contains(glyphUnicode)) {
charcode = this.cMap.lookup(glyphUnicode);
if (typeof charcode === "string") {
charcode = convertCidString(glyphUnicode, charcode);
}
}
if (!charcode && this.toUnicode) {
charcode = this.toUnicode.charCodeOf(glyphUnicode);
}
if (charcode <= 0) {
charcode = glyphUnicode;
}
width = this.widths[charcode];
if (width) {
break;
}
}
return shadow(this, "_spaceWidth", width || this.defaultWidth);
}
_charToGlyph(charcode, isSpace = false) {
let glyph = this._glyphCache[charcode];
if (glyph?.isSpace === isSpace) {
return glyph;
}
let fontCharCode, width, operatorListId;
let widthCode = charcode;
if (this.cMap?.contains(charcode)) {
widthCode = this.cMap.lookup(charcode);
if (typeof widthCode === "string") {
widthCode = convertCidString(charcode, widthCode);
}
}
width = this.widths[widthCode];
if (typeof width !== "number") {
width = this.defaultWidth;
}
const vmetric = this.vmetrics?.[widthCode];
let unicode = this.toUnicode.get(charcode) || charcode;
if (typeof unicode === "number") {
unicode = String.fromCharCode(unicode);
}
let isInFont = this.toFontChar[charcode] !== undefined;
fontCharCode = this.toFontChar[charcode] || charcode;
if (this.missingFile) {
const glyphName = this.differences[charcode] || this.defaultEncoding[charcode];
if ((glyphName === ".notdef" || glyphName === "") && this.type === "Type1") {
fontCharCode = 0x20;
if (glyphName === "") {
width ||= this._spaceWidth;
unicode = String.fromCharCode(fontCharCode);
}
}
fontCharCode = mapSpecialUnicodeValues(fontCharCode);
}
if (this.isType3Font) {
operatorListId = fontCharCode;
}
let accent = null;
if (this.seacMap?.[charcode]) {
isInFont = true;
const seac = this.seacMap[charcode];
fontCharCode = seac.baseFontCharCode;
accent = {
fontChar: String.fromCodePoint(seac.accentFontCharCode),
offset: seac.accentOffset
};
}
let fontChar = "";
if (typeof fontCharCode === "number") {
if (fontCharCode <= 0x10ffff) {
fontChar = String.fromCodePoint(fontCharCode);
} else {
warn(`charToGlyph - invalid fontCharCode: ${fontCharCode}`);
}
}
if (this.missingFile && this.vertical && fontChar.length === 1) {
const vertical = getVerticalPresentationForm()[fontChar.charCodeAt(0)];
if (vertical) {
fontChar = unicode = String.fromCharCode(vertical);
}
}
glyph = new fonts_Glyph(charcode, fontChar, unicode, accent, width, vmetric, operatorListId, isSpace, isInFont);
return this._glyphCache[charcode] = glyph;
}
charsToGlyphs(chars) {
let glyphs = this._charsCache[chars];
if (glyphs) {
return glyphs;
}
glyphs = [];
if (this.cMap) {
const c = Object.create(null),
ii = chars.length;
let i = 0;
while (i < ii) {
this.cMap.readCharCode(chars, i, c);
const {
charcode,
length
} = c;
i += length;
const glyph = this._charToGlyph(charcode, length === 1 && chars.charCodeAt(i - 1) === 0x20);
glyphs.push(glyph);
}
} else {
for (let i = 0, ii = chars.length; i < ii; ++i) {
const charcode = chars.charCodeAt(i);
const glyph = this._charToGlyph(charcode, charcode === 0x20);
glyphs.push(glyph);
}
}
return this._charsCache[chars] = glyphs;
}
getCharPositions(chars) {
const positions = [];
if (this.cMap) {
const c = Object.create(null);
let i = 0;
while (i < chars.length) {
this.cMap.readCharCode(chars, i, c);
const length = c.length;
positions.push([i, i + length]);
i += length;
}
} else {
for (let i = 0, ii = chars.length; i < ii; ++i) {
positions.push([i, i + 1]);
}
}
return positions;
}
get glyphCacheValues() {
return Object.values(this._glyphCache);
}
encodeString(str) {
const buffers = [];
const currentBuf = [];
const hasCurrentBufErrors = () => buffers.length % 2 === 1;
const getCharCode = this.toUnicode instanceof IdentityToUnicodeMap ? unicode => this.toUnicode.charCodeOf(unicode) : unicode => this.toUnicode.charCodeOf(String.fromCodePoint(unicode));
for (let i = 0, ii = str.length; i < ii; i++) {
const unicode = str.codePointAt(i);
if (unicode > 0xd7ff && (unicode < 0xe000 || unicode > 0xfffd)) {
i++;
}
if (this.toUnicode) {
const charCode = getCharCode(unicode);
if (charCode !== -1) {
if (hasCurrentBufErrors()) {
buffers.push(currentBuf.join(""));
currentBuf.length = 0;
}
const charCodeLength = this.cMap ? this.cMap.getCharCodeLength(charCode) : 1;
for (let j = charCodeLength - 1; j >= 0; j--) {
currentBuf.push(String.fromCharCode(charCode >> 8 * j & 0xff));
}
continue;
}
}
if (!hasCurrentBufErrors()) {
buffers.push(currentBuf.join(""));
currentBuf.length = 0;
}
currentBuf.push(String.fromCodePoint(unicode));
}
buffers.push(currentBuf.join(""));
return buffers;
}
}
class ErrorFont {
constructor(error) {
this.error = error;
this.loadedName = "g_font_error";
this.missingFile = true;
}
charsToGlyphs() {
return [];
}
encodeString(chars) {
return [chars];
}
exportData() {
return {
error: this.error
};
}
}
;// ./src/core/pattern.js
const ShadingType = {
FUNCTION_BASED: 1,
AXIAL: 2,
RADIAL: 3,
FREE_FORM_MESH: 4,
LATTICE_FORM_MESH: 5,
COONS_PATCH_MESH: 6,
TENSOR_PATCH_MESH: 7
};
class Pattern {
constructor() {
unreachable("Cannot initialize Pattern.");
}
static parseShading(shading, xref, res, pdfFunctionFactory, globalColorSpaceCache, localColorSpaceCache) {
const dict = shading instanceof BaseStream ? shading.dict : shading;
const type = dict.get("ShadingType");
try {
switch (type) {
case ShadingType.AXIAL:
case ShadingType.RADIAL:
return new RadialAxialShading(dict, xref, res, pdfFunctionFactory, globalColorSpaceCache, localColorSpaceCache);
case ShadingType.FREE_FORM_MESH:
case ShadingType.LATTICE_FORM_MESH:
case ShadingType.COONS_PATCH_MESH:
case ShadingType.TENSOR_PATCH_MESH:
return new MeshShading(shading, xref, res, pdfFunctionFactory, globalColorSpaceCache, localColorSpaceCache);
default:
throw new FormatError("Unsupported ShadingType: " + type);
}
} catch (ex) {
if (ex instanceof MissingDataException) {
throw ex;
}
warn(ex);
return new DummyShading();
}
}
}
class BaseShading {
static SMALL_NUMBER = 1e-6;
getIR() {
unreachable("Abstract method `getIR` called.");
}
}
class RadialAxialShading extends BaseShading {
constructor(dict, xref, resources, pdfFunctionFactory, globalColorSpaceCache, localColorSpaceCache) {
super();
this.shadingType = dict.get("ShadingType");
let coordsLen = 0;
if (this.shadingType === ShadingType.AXIAL) {
coordsLen = 4;
} else if (this.shadingType === ShadingType.RADIAL) {
coordsLen = 6;
}
this.coordsArr = dict.getArray("Coords");
if (!isNumberArray(this.coordsArr, coordsLen)) {
throw new FormatError("RadialAxialShading: Invalid /Coords array.");
}
const cs = ColorSpaceUtils.parse({
cs: dict.getRaw("CS") || dict.getRaw("ColorSpace"),
xref,
resources,
pdfFunctionFactory,
globalColorSpaceCache,
localColorSpaceCache
});
this.bbox = lookupNormalRect(dict.getArray("BBox"), null);
let t0 = 0.0,
t1 = 1.0;
const domainArr = dict.getArray("Domain");
if (isNumberArray(domainArr, 2)) {
[t0, t1] = domainArr;
}
let extendStart = false,
extendEnd = false;
const extendArr = dict.getArray("Extend");
if (isBooleanArray(extendArr, 2)) {
[extendStart, extendEnd] = extendArr;
}
if (this.shadingType === ShadingType.RADIAL && (!extendStart || !extendEnd)) {
const [x1, y1, r1, x2, y2, r2] = this.coordsArr;
const distance = Math.hypot(x1 - x2, y1 - y2);
if (r1 <= r2 + distance && r2 <= r1 + distance) {
warn("Unsupported radial gradient.");
}
}
this.extendStart = extendStart;
this.extendEnd = extendEnd;
const fnObj = dict.getRaw("Function");
const fn = pdfFunctionFactory.create(fnObj, true);
const NUMBER_OF_SAMPLES = 840;
const step = (t1 - t0) / NUMBER_OF_SAMPLES;
const colorStops = this.colorStops = [];
if (t0 >= t1 || step <= 0) {
info("Bad shading domain.");
return;
}
const color = new Float32Array(cs.numComps),
ratio = new Float32Array(1);
let rgbColor;
let iBase = 0;
ratio[0] = t0;
fn(ratio, 0, color, 0);
let rgbBase = cs.getRgb(color, 0);
const cssColorBase = Util.makeHexColor(rgbBase[0], rgbBase[1], rgbBase[2]);
colorStops.push([0, cssColorBase]);
let iPrev = 1;
ratio[0] = t0 + step;
fn(ratio, 0, color, 0);
let rgbPrev = cs.getRgb(color, 0);
let maxSlopeR = rgbPrev[0] - rgbBase[0] + 1;
let maxSlopeG = rgbPrev[1] - rgbBase[1] + 1;
let maxSlopeB = rgbPrev[2] - rgbBase[2] + 1;
let minSlopeR = rgbPrev[0] - rgbBase[0] - 1;
let minSlopeG = rgbPrev[1] - rgbBase[1] - 1;
let minSlopeB = rgbPrev[2] - rgbBase[2] - 1;
for (let i = 2; i < NUMBER_OF_SAMPLES; i++) {
ratio[0] = t0 + i * step;
fn(ratio, 0, color, 0);
rgbColor = cs.getRgb(color, 0);
const run = i - iBase;
maxSlopeR = Math.min(maxSlopeR, (rgbColor[0] - rgbBase[0] + 1) / run);
maxSlopeG = Math.min(maxSlopeG, (rgbColor[1] - rgbBase[1] + 1) / run);
maxSlopeB = Math.min(maxSlopeB, (rgbColor[2] - rgbBase[2] + 1) / run);
minSlopeR = Math.max(minSlopeR, (rgbColor[0] - rgbBase[0] - 1) / run);
minSlopeG = Math.max(minSlopeG, (rgbColor[1] - rgbBase[1] - 1) / run);
minSlopeB = Math.max(minSlopeB, (rgbColor[2] - rgbBase[2] - 1) / run);
const slopesExist = minSlopeR <= maxSlopeR && minSlopeG <= maxSlopeG && minSlopeB <= maxSlopeB;
if (!slopesExist) {
const cssColor = Util.makeHexColor(rgbPrev[0], rgbPrev[1], rgbPrev[2]);
colorStops.push([iPrev / NUMBER_OF_SAMPLES, cssColor]);
maxSlopeR = rgbColor[0] - rgbPrev[0] + 1;
maxSlopeG = rgbColor[1] - rgbPrev[1] + 1;
maxSlopeB = rgbColor[2] - rgbPrev[2] + 1;
minSlopeR = rgbColor[0] - rgbPrev[0] - 1;
minSlopeG = rgbColor[1] - rgbPrev[1] - 1;
minSlopeB = rgbColor[2] - rgbPrev[2] - 1;
iBase = iPrev;
rgbBase = rgbPrev;
}
iPrev = i;
rgbPrev = rgbColor;
}
const cssColor = Util.makeHexColor(rgbPrev[0], rgbPrev[1], rgbPrev[2]);
colorStops.push([1, cssColor]);
let background = "transparent";
if (dict.has("Background")) {
rgbColor = cs.getRgb(dict.get("Background"), 0);
background = Util.makeHexColor(rgbColor[0], rgbColor[1], rgbColor[2]);
}
if (!extendStart) {
colorStops.unshift([0, background]);
colorStops[1][0] += BaseShading.SMALL_NUMBER;
}
if (!extendEnd) {
colorStops.at(-1)[0] -= BaseShading.SMALL_NUMBER;
colorStops.push([1, background]);
}
this.colorStops = colorStops;
}
getIR() {
const {
coordsArr,
shadingType
} = this;
let type, p0, p1, r0, r1;
if (shadingType === ShadingType.AXIAL) {
p0 = [coordsArr[0], coordsArr[1]];
p1 = [coordsArr[2], coordsArr[3]];
r0 = null;
r1 = null;
type = "axial";
} else if (shadingType === ShadingType.RADIAL) {
p0 = [coordsArr[0], coordsArr[1]];
p1 = [coordsArr[3], coordsArr[4]];
r0 = coordsArr[2];
r1 = coordsArr[5];
type = "radial";
} else {
unreachable(`getPattern type unknown: ${shadingType}`);
}
return ["RadialAxial", type, this.bbox, this.colorStops, p0, p1, r0, r1];
}
}
class MeshStreamReader {
constructor(stream, context) {
this.stream = stream;
this.context = context;
this.buffer = 0;
this.bufferLength = 0;
const numComps = context.numComps;
this.tmpCompsBuf = new Float32Array(numComps);
const csNumComps = context.colorSpace.numComps;
this.tmpCsCompsBuf = context.colorFn ? new Float32Array(csNumComps) : this.tmpCompsBuf;
}
get hasData() {
if (this.stream.end) {
return this.stream.pos < this.stream.end;
}
if (this.bufferLength > 0) {
return true;
}
const nextByte = this.stream.getByte();
if (nextByte < 0) {
return false;
}
this.buffer = nextByte;
this.bufferLength = 8;
return true;
}
readBits(n) {
const {
stream
} = this;
let {
buffer,
bufferLength
} = this;
if (n === 32) {
if (bufferLength === 0) {
return stream.getInt32() >>> 0;
}
buffer = buffer << 24 | stream.getByte() << 16 | stream.getByte() << 8 | stream.getByte();
const nextByte = stream.getByte();
this.buffer = nextByte & (1 << bufferLength) - 1;
return (buffer << 8 - bufferLength | (nextByte & 0xff) >> bufferLength) >>> 0;
}
if (n === 8 && bufferLength === 0) {
return stream.getByte();
}
while (bufferLength < n) {
buffer = buffer << 8 | stream.getByte();
bufferLength += 8;
}
bufferLength -= n;
this.bufferLength = bufferLength;
this.buffer = buffer & (1 << bufferLength) - 1;
return buffer >> bufferLength;
}
align() {
this.buffer = 0;
this.bufferLength = 0;
}
readFlag() {
return this.readBits(this.context.bitsPerFlag);
}
readCoordinate() {
const {
bitsPerCoordinate,
decode
} = this.context;
const xi = this.readBits(bitsPerCoordinate);
const yi = this.readBits(bitsPerCoordinate);
const scale = bitsPerCoordinate < 32 ? 1 / ((1 << bitsPerCoordinate) - 1) : 2.3283064365386963e-10;
return [xi * scale * (decode[1] - decode[0]) + decode[0], yi * scale * (decode[3] - decode[2]) + decode[2]];
}
readComponents() {
const {
bitsPerComponent,
colorFn,
colorSpace,
decode,
numComps
} = this.context;
const scale = bitsPerComponent < 32 ? 1 / ((1 << bitsPerComponent) - 1) : 2.3283064365386963e-10;
const components = this.tmpCompsBuf;
for (let i = 0, j = 4; i < numComps; i++, j += 2) {
const ci = this.readBits(bitsPerComponent);
components[i] = ci * scale * (decode[j + 1] - decode[j]) + decode[j];
}
const color = this.tmpCsCompsBuf;
colorFn?.(components, 0, color, 0);
return colorSpace.getRgb(color, 0);
}
}
let bCache = Object.create(null);
function buildB(count) {
const lut = [];
for (let i = 0; i <= count; i++) {
const t = i / count,
t_ = 1 - t;
lut.push(new Float32Array([t_ ** 3, 3 * t * t_ ** 2, 3 * t ** 2 * t_, t ** 3]));
}
return lut;
}
function getB(count) {
return bCache[count] ||= buildB(count);
}
function clearPatternCaches() {
bCache = Object.create(null);
}
class MeshShading extends BaseShading {
static MIN_SPLIT_PATCH_CHUNKS_AMOUNT = 3;
static MAX_SPLIT_PATCH_CHUNKS_AMOUNT = 20;
static TRIANGLE_DENSITY = 20;
constructor(stream, xref, resources, pdfFunctionFactory, globalColorSpaceCache, localColorSpaceCache) {
super();
if (!(stream instanceof BaseStream)) {
throw new FormatError("Mesh data is not a stream");
}
const dict = stream.dict;
this.shadingType = dict.get("ShadingType");
this.bbox = lookupNormalRect(dict.getArray("BBox"), null);
const cs = ColorSpaceUtils.parse({
cs: dict.getRaw("CS") || dict.getRaw("ColorSpace"),
xref,
resources,
pdfFunctionFactory,
globalColorSpaceCache,
localColorSpaceCache
});
this.background = dict.has("Background") ? cs.getRgb(dict.get("Background"), 0) : null;
const fnObj = dict.getRaw("Function");
const fn = fnObj ? pdfFunctionFactory.create(fnObj, true) : null;
this.coords = [];
this.colors = [];
this.figures = [];
const decodeContext = {
bitsPerCoordinate: dict.get("BitsPerCoordinate"),
bitsPerComponent: dict.get("BitsPerComponent"),
bitsPerFlag: dict.get("BitsPerFlag"),
decode: dict.getArray("Decode"),
colorFn: fn,
colorSpace: cs,
numComps: fn ? 1 : cs.numComps
};
const reader = new MeshStreamReader(stream, decodeContext);
let patchMesh = false;
switch (this.shadingType) {
case ShadingType.FREE_FORM_MESH:
this._decodeType4Shading(reader);
break;
case ShadingType.LATTICE_FORM_MESH:
const verticesPerRow = dict.get("VerticesPerRow") | 0;
if (verticesPerRow < 2) {
throw new FormatError("Invalid VerticesPerRow");
}
this._decodeType5Shading(reader, verticesPerRow);
break;
case ShadingType.COONS_PATCH_MESH:
this._decodeType6Shading(reader);
patchMesh = true;
break;
case ShadingType.TENSOR_PATCH_MESH:
this._decodeType7Shading(reader);
patchMesh = true;
break;
default:
unreachable("Unsupported mesh type.");
break;
}
if (patchMesh) {
this._updateBounds();
for (let i = 0, ii = this.figures.length; i < ii; i++) {
this._buildFigureFromPatch(i);
}
}
this._updateBounds();
this._packData();
}
_decodeType4Shading(reader) {
const coords = this.coords;
const colors = this.colors;
const operators = [];
const ps = [];
let verticesLeft = 0;
while (reader.hasData) {
const f = reader.readFlag();
const coord = reader.readCoordinate();
const color = reader.readComponents();
if (verticesLeft === 0) {
if (!(0 <= f && f <= 2)) {
throw new FormatError("Unknown type4 flag");
}
switch (f) {
case 0:
verticesLeft = 3;
break;
case 1:
ps.push(ps.at(-2), ps.at(-1));
verticesLeft = 1;
break;
case 2:
ps.push(ps.at(-3), ps.at(-1));
verticesLeft = 1;
break;
}
operators.push(f);
}
ps.push(coords.length);
coords.push(coord);
colors.push(color);
verticesLeft--;
reader.align();
}
this.figures.push({
type: "triangles",
coords: new Int32Array(ps),
colors: new Int32Array(ps)
});
}
_decodeType5Shading(reader, verticesPerRow) {
const coords = this.coords;
const colors = this.colors;
const ps = [];
while (reader.hasData) {
const coord = reader.readCoordinate();
const color = reader.readComponents();
ps.push(coords.length);
coords.push(coord);
colors.push(color);
}
this.figures.push({
type: "lattice",
coords: new Int32Array(ps),
colors: new Int32Array(ps),
verticesPerRow
});
}
_decodeType6Shading(reader) {
const coords = this.coords;
const colors = this.colors;
const ps = new Int32Array(16);
const cs = new Int32Array(4);
while (reader.hasData) {
const f = reader.readFlag();
if (!(0 <= f && f <= 3)) {
throw new FormatError("Unknown type6 flag");
}
const pi = coords.length;
for (let i = 0, ii = f !== 0 ? 8 : 12; i < ii; i++) {
coords.push(reader.readCoordinate());
}
const ci = colors.length;
for (let i = 0, ii = f !== 0 ? 2 : 4; i < ii; i++) {
colors.push(reader.readComponents());
}
let tmp1, tmp2, tmp3, tmp4;
switch (f) {
case 0:
ps[12] = pi + 3;
ps[13] = pi + 4;
ps[14] = pi + 5;
ps[15] = pi + 6;
ps[8] = pi + 2;
ps[11] = pi + 7;
ps[4] = pi + 1;
ps[7] = pi + 8;
ps[0] = pi;
ps[1] = pi + 11;
ps[2] = pi + 10;
ps[3] = pi + 9;
cs[2] = ci + 1;
cs[3] = ci + 2;
cs[0] = ci;
cs[1] = ci + 3;
break;
case 1:
tmp1 = ps[12];
tmp2 = ps[13];
tmp3 = ps[14];
tmp4 = ps[15];
ps[12] = tmp4;
ps[13] = pi + 0;
ps[14] = pi + 1;
ps[15] = pi + 2;
ps[8] = tmp3;
ps[11] = pi + 3;
ps[4] = tmp2;
ps[7] = pi + 4;
ps[0] = tmp1;
ps[1] = pi + 7;
ps[2] = pi + 6;
ps[3] = pi + 5;
tmp1 = cs[2];
tmp2 = cs[3];
cs[2] = tmp2;
cs[3] = ci;
cs[0] = tmp1;
cs[1] = ci + 1;
break;
case 2:
tmp1 = ps[15];
tmp2 = ps[11];
ps[12] = ps[3];
ps[13] = pi + 0;
ps[14] = pi + 1;
ps[15] = pi + 2;
ps[8] = ps[7];
ps[11] = pi + 3;
ps[4] = tmp2;
ps[7] = pi + 4;
ps[0] = tmp1;
ps[1] = pi + 7;
ps[2] = pi + 6;
ps[3] = pi + 5;
tmp1 = cs[3];
cs[2] = cs[1];
cs[3] = ci;
cs[0] = tmp1;
cs[1] = ci + 1;
break;
case 3:
ps[12] = ps[0];
ps[13] = pi + 0;
ps[14] = pi + 1;
ps[15] = pi + 2;
ps[8] = ps[1];
ps[11] = pi + 3;
ps[4] = ps[2];
ps[7] = pi + 4;
ps[0] = ps[3];
ps[1] = pi + 7;
ps[2] = pi + 6;
ps[3] = pi + 5;
cs[2] = cs[0];
cs[3] = ci;
cs[0] = cs[1];
cs[1] = ci + 1;
break;
}
ps[5] = coords.length;
coords.push([(-4 * coords[ps[0]][0] - coords[ps[15]][0] + 6 * (coords[ps[4]][0] + coords[ps[1]][0]) - 2 * (coords[ps[12]][0] + coords[ps[3]][0]) + 3 * (coords[ps[13]][0] + coords[ps[7]][0])) / 9, (-4 * coords[ps[0]][1] - coords[ps[15]][1] + 6 * (coords[ps[4]][1] + coords[ps[1]][1]) - 2 * (coords[ps[12]][1] + coords[ps[3]][1]) + 3 * (coords[ps[13]][1] + coords[ps[7]][1])) / 9]);
ps[6] = coords.length;
coords.push([(-4 * coords[ps[3]][0] - coords[ps[12]][0] + 6 * (coords[ps[2]][0] + coords[ps[7]][0]) - 2 * (coords[ps[0]][0] + coords[ps[15]][0]) + 3 * (coords[ps[4]][0] + coords[ps[14]][0])) / 9, (-4 * coords[ps[3]][1] - coords[ps[12]][1] + 6 * (coords[ps[2]][1] + coords[ps[7]][1]) - 2 * (coords[ps[0]][1] + coords[ps[15]][1]) + 3 * (coords[ps[4]][1] + coords[ps[14]][1])) / 9]);
ps[9] = coords.length;
coords.push([(-4 * coords[ps[12]][0] - coords[ps[3]][0] + 6 * (coords[ps[8]][0] + coords[ps[13]][0]) - 2 * (coords[ps[0]][0] + coords[ps[15]][0]) + 3 * (coords[ps[11]][0] + coords[ps[1]][0])) / 9, (-4 * coords[ps[12]][1] - coords[ps[3]][1] + 6 * (coords[ps[8]][1] + coords[ps[13]][1]) - 2 * (coords[ps[0]][1] + coords[ps[15]][1]) + 3 * (coords[ps[11]][1] + coords[ps[1]][1])) / 9]);
ps[10] = coords.length;
coords.push([(-4 * coords[ps[15]][0] - coords[ps[0]][0] + 6 * (coords[ps[11]][0] + coords[ps[14]][0]) - 2 * (coords[ps[12]][0] + coords[ps[3]][0]) + 3 * (coords[ps[2]][0] + coords[ps[8]][0])) / 9, (-4 * coords[ps[15]][1] - coords[ps[0]][1] + 6 * (coords[ps[11]][1] + coords[ps[14]][1]) - 2 * (coords[ps[12]][1] + coords[ps[3]][1]) + 3 * (coords[ps[2]][1] + coords[ps[8]][1])) / 9]);
this.figures.push({
type: "patch",
coords: new Int32Array(ps),
colors: new Int32Array(cs)
});
}
}
_decodeType7Shading(reader) {
const coords = this.coords;
const colors = this.colors;
const ps = new Int32Array(16);
const cs = new Int32Array(4);
while (reader.hasData) {
const f = reader.readFlag();
if (!(0 <= f && f <= 3)) {
throw new FormatError("Unknown type7 flag");
}
const pi = coords.length;
for (let i = 0, ii = f !== 0 ? 12 : 16; i < ii; i++) {
coords.push(reader.readCoordinate());
}
const ci = colors.length;
for (let i = 0, ii = f !== 0 ? 2 : 4; i < ii; i++) {
colors.push(reader.readComponents());
}
let tmp1, tmp2, tmp3, tmp4;
switch (f) {
case 0:
ps[12] = pi + 3;
ps[13] = pi + 4;
ps[14] = pi + 5;
ps[15] = pi + 6;
ps[8] = pi + 2;
ps[9] = pi + 13;
ps[10] = pi + 14;
ps[11] = pi + 7;
ps[4] = pi + 1;
ps[5] = pi + 12;
ps[6] = pi + 15;
ps[7] = pi + 8;
ps[0] = pi;
ps[1] = pi + 11;
ps[2] = pi + 10;
ps[3] = pi + 9;
cs[2] = ci + 1;
cs[3] = ci + 2;
cs[0] = ci;
cs[1] = ci + 3;
break;
case 1:
tmp1 = ps[12];
tmp2 = ps[13];
tmp3 = ps[14];
tmp4 = ps[15];
ps[12] = tmp4;
ps[13] = pi + 0;
ps[14] = pi + 1;
ps[15] = pi + 2;
ps[8] = tmp3;
ps[9] = pi + 9;
ps[10] = pi + 10;
ps[11] = pi + 3;
ps[4] = tmp2;
ps[5] = pi + 8;
ps[6] = pi + 11;
ps[7] = pi + 4;
ps[0] = tmp1;
ps[1] = pi + 7;
ps[2] = pi + 6;
ps[3] = pi + 5;
tmp1 = cs[2];
tmp2 = cs[3];
cs[2] = tmp2;
cs[3] = ci;
cs[0] = tmp1;
cs[1] = ci + 1;
break;
case 2:
tmp1 = ps[15];
tmp2 = ps[11];
ps[12] = ps[3];
ps[13] = pi + 0;
ps[14] = pi + 1;
ps[15] = pi + 2;
ps[8] = ps[7];
ps[9] = pi + 9;
ps[10] = pi + 10;
ps[11] = pi + 3;
ps[4] = tmp2;
ps[5] = pi + 8;
ps[6] = pi + 11;
ps[7] = pi + 4;
ps[0] = tmp1;
ps[1] = pi + 7;
ps[2] = pi + 6;
ps[3] = pi + 5;
tmp1 = cs[3];
cs[2] = cs[1];
cs[3] = ci;
cs[0] = tmp1;
cs[1] = ci + 1;
break;
case 3:
ps[12] = ps[0];
ps[13] = pi + 0;
ps[14] = pi + 1;
ps[15] = pi + 2;
ps[8] = ps[1];
ps[9] = pi + 9;
ps[10] = pi + 10;
ps[11] = pi + 3;
ps[4] = ps[2];
ps[5] = pi + 8;
ps[6] = pi + 11;
ps[7] = pi + 4;
ps[0] = ps[3];
ps[1] = pi + 7;
ps[2] = pi + 6;
ps[3] = pi + 5;
cs[2] = cs[0];
cs[3] = ci;
cs[0] = cs[1];
cs[1] = ci + 1;
break;
}
this.figures.push({
type: "patch",
coords: new Int32Array(ps),
colors: new Int32Array(cs)
});
}
}
_buildFigureFromPatch(index) {
const figure = this.figures[index];
assert(figure.type === "patch", "Unexpected patch mesh figure");
const coords = this.coords,
colors = this.colors;
const pi = figure.coords;
const ci = figure.colors;
const figureMinX = Math.min(coords[pi[0]][0], coords[pi[3]][0], coords[pi[12]][0], coords[pi[15]][0]);
const figureMinY = Math.min(coords[pi[0]][1], coords[pi[3]][1], coords[pi[12]][1], coords[pi[15]][1]);
const figureMaxX = Math.max(coords[pi[0]][0], coords[pi[3]][0], coords[pi[12]][0], coords[pi[15]][0]);
const figureMaxY = Math.max(coords[pi[0]][1], coords[pi[3]][1], coords[pi[12]][1], coords[pi[15]][1]);
let splitXBy = Math.ceil((figureMaxX - figureMinX) * MeshShading.TRIANGLE_DENSITY / (this.bounds[2] - this.bounds[0]));
splitXBy = MathClamp(splitXBy, MeshShading.MIN_SPLIT_PATCH_CHUNKS_AMOUNT, MeshShading.MAX_SPLIT_PATCH_CHUNKS_AMOUNT);
let splitYBy = Math.ceil((figureMaxY - figureMinY) * MeshShading.TRIANGLE_DENSITY / (this.bounds[3] - this.bounds[1]));
splitYBy = MathClamp(splitYBy, MeshShading.MIN_SPLIT_PATCH_CHUNKS_AMOUNT, MeshShading.MAX_SPLIT_PATCH_CHUNKS_AMOUNT);
const verticesPerRow = splitXBy + 1;
const figureCoords = new Int32Array((splitYBy + 1) * verticesPerRow);
const figureColors = new Int32Array((splitYBy + 1) * verticesPerRow);
let k = 0;
const cl = new Uint8Array(3),
cr = new Uint8Array(3);
const c0 = colors[ci[0]],
c1 = colors[ci[1]],
c2 = colors[ci[2]],
c3 = colors[ci[3]];
const bRow = getB(splitYBy),
bCol = getB(splitXBy);
for (let row = 0; row <= splitYBy; row++) {
cl[0] = (c0[0] * (splitYBy - row) + c2[0] * row) / splitYBy | 0;
cl[1] = (c0[1] * (splitYBy - row) + c2[1] * row) / splitYBy | 0;
cl[2] = (c0[2] * (splitYBy - row) + c2[2] * row) / splitYBy | 0;
cr[0] = (c1[0] * (splitYBy - row) + c3[0] * row) / splitYBy | 0;
cr[1] = (c1[1] * (splitYBy - row) + c3[1] * row) / splitYBy | 0;
cr[2] = (c1[2] * (splitYBy - row) + c3[2] * row) / splitYBy | 0;
for (let col = 0; col <= splitXBy; col++, k++) {
if ((row === 0 || row === splitYBy) && (col === 0 || col === splitXBy)) {
continue;
}
let x = 0,
y = 0;
let q = 0;
for (let i = 0; i <= 3; i++) {
for (let j = 0; j <= 3; j++, q++) {
const m = bRow[row][i] * bCol[col][j];
x += coords[pi[q]][0] * m;
y += coords[pi[q]][1] * m;
}
}
figureCoords[k] = coords.length;
coords.push([x, y]);
figureColors[k] = colors.length;
const newColor = new Uint8Array(3);
newColor[0] = (cl[0] * (splitXBy - col) + cr[0] * col) / splitXBy | 0;
newColor[1] = (cl[1] * (splitXBy - col) + cr[1] * col) / splitXBy | 0;
newColor[2] = (cl[2] * (splitXBy - col) + cr[2] * col) / splitXBy | 0;
colors.push(newColor);
}
}
figureCoords[0] = pi[0];
figureColors[0] = ci[0];
figureCoords[splitXBy] = pi[3];
figureColors[splitXBy] = ci[1];
figureCoords[verticesPerRow * splitYBy] = pi[12];
figureColors[verticesPerRow * splitYBy] = ci[2];
figureCoords[verticesPerRow * splitYBy + splitXBy] = pi[15];
figureColors[verticesPerRow * splitYBy + splitXBy] = ci[3];
this.figures[index] = {
type: "lattice",
coords: figureCoords,
colors: figureColors,
verticesPerRow
};
}
_updateBounds() {
let minX = this.coords[0][0],
minY = this.coords[0][1],
maxX = minX,
maxY = minY;
for (let i = 1, ii = this.coords.length; i < ii; i++) {
const x = this.coords[i][0],
y = this.coords[i][1];
minX = minX > x ? x : minX;
minY = minY > y ? y : minY;
maxX = maxX < x ? x : maxX;
maxY = maxY < y ? y : maxY;
}
this.bounds = [minX, minY, maxX, maxY];
}
_packData() {
let i, ii, j, jj;
const coords = this.coords;
const coordsPacked = new Float32Array(coords.length * 2);
for (i = 0, j = 0, ii = coords.length; i < ii; i++) {
const xy = coords[i];
coordsPacked[j++] = xy[0];
coordsPacked[j++] = xy[1];
}
this.coords = coordsPacked;
const colors = this.colors;
const colorsPacked = new Uint8Array(colors.length * 3);
for (i = 0, j = 0, ii = colors.length; i < ii; i++) {
const c = colors[i];
colorsPacked[j++] = c[0];
colorsPacked[j++] = c[1];
colorsPacked[j++] = c[2];
}
this.colors = colorsPacked;
const figures = this.figures;
for (i = 0, ii = figures.length; i < ii; i++) {
const figure = figures[i],
ps = figure.coords,
cs = figure.colors;
for (j = 0, jj = ps.length; j < jj; j++) {
ps[j] *= 2;
cs[j] *= 3;
}
}
}
getIR() {
const {
bounds
} = this;
if (bounds[2] - bounds[0] === 0 || bounds[3] - bounds[1] === 0) {
throw new FormatError(`Invalid MeshShading bounds: [${bounds}].`);
}
return ["Mesh", this.shadingType, this.coords, this.colors, this.figures, bounds, this.bbox, this.background];
}
}
class DummyShading extends BaseShading {
getIR() {
return ["Dummy"];
}
}
function getTilingPatternIR(operatorList, dict, color) {
const matrix = lookupMatrix(dict.getArray("Matrix"), IDENTITY_MATRIX);
const bbox = lookupNormalRect(dict.getArray("BBox"), null);
if (!bbox || bbox[2] - bbox[0] === 0 || bbox[3] - bbox[1] === 0) {
throw new FormatError(`Invalid getTilingPatternIR /BBox array.`);
}
const xstep = dict.get("XStep");
if (typeof xstep !== "number") {
throw new FormatError(`Invalid getTilingPatternIR /XStep value.`);
}
const ystep = dict.get("YStep");
if (typeof ystep !== "number") {
throw new FormatError(`Invalid getTilingPatternIR /YStep value.`);
}
const paintType = dict.get("PaintType");
if (!Number.isInteger(paintType)) {
throw new FormatError(`Invalid getTilingPatternIR /PaintType value.`);
}
const tilingType = dict.get("TilingType");
if (!Number.isInteger(tilingType)) {
throw new FormatError(`Invalid getTilingPatternIR /TilingType value.`);
}
return ["TilingPattern", color, operatorList, matrix, bbox, xstep, ystep, paintType, tilingType];
}
;// ./src/core/calibri_factors.js
const CalibriBoldFactors = [1.3877, 1, 1, 1, 0.97801, 0.92482, 0.89552, 0.91133, 0.81988, 0.97566, 0.98152, 0.93548, 0.93548, 1.2798, 0.85284, 0.92794, 1, 0.96134, 1.54657, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.82845, 0.82845, 0.85284, 0.85284, 0.85284, 0.75859, 0.92138, 0.83908, 0.7762, 0.73293, 0.87289, 0.73133, 0.7514, 0.81921, 0.87356, 0.95958, 0.59526, 0.75727, 0.69225, 1.04924, 0.9121, 0.86943, 0.79795, 0.88198, 0.77958, 0.70864, 0.81055, 0.90399, 0.88653, 0.96017, 0.82577, 0.77892, 0.78257, 0.97507, 1.54657, 0.97507, 0.85284, 0.89552, 0.90176, 0.88762, 0.8785, 0.75241, 0.8785, 0.90518, 0.95015, 0.77618, 0.8785, 0.88401, 0.91916, 0.86304, 0.88401, 0.91488, 0.8785, 0.8801, 0.8785, 0.8785, 0.91343, 0.7173, 1.04106, 0.8785, 0.85075, 0.95794, 0.82616, 0.85162, 0.79492, 0.88331, 1.69808, 0.88331, 0.85284, 0.97801, 0.89552, 0.91133, 0.89552, 0.91133, 1.7801, 0.89552, 1.24487, 1.13254, 1.12401, 0.96839, 0.85284, 0.68787, 0.70645, 0.85592, 0.90747, 1.01466, 1.0088, 0.90323, 1, 1.07463, 1, 0.91056, 0.75806, 1.19118, 0.96839, 0.78864, 0.82845, 0.84133, 0.75859, 0.83908, 0.83908, 0.83908, 0.83908, 0.83908, 0.83908, 0.77539, 0.73293, 0.73133, 0.73133, 0.73133, 0.73133, 0.95958, 0.95958, 0.95958, 0.95958, 0.88506, 0.9121, 0.86943, 0.86943, 0.86943, 0.86943, 0.86943, 0.85284, 0.87508, 0.90399, 0.90399, 0.90399, 0.90399, 0.77892, 0.79795, 0.90807, 0.88762, 0.88762, 0.88762, 0.88762, 0.88762, 0.88762, 0.8715, 0.75241, 0.90518, 0.90518, 0.90518, 0.90518, 0.88401, 0.88401, 0.88401, 0.88401, 0.8785, 0.8785, 0.8801, 0.8801, 0.8801, 0.8801, 0.8801, 0.90747, 0.89049, 0.8785, 0.8785, 0.8785, 0.8785, 0.85162, 0.8785, 0.85162, 0.83908, 0.88762, 0.83908, 0.88762, 0.83908, 0.88762, 0.73293, 0.75241, 0.73293, 0.75241, 0.73293, 0.75241, 0.73293, 0.75241, 0.87289, 0.83016, 0.88506, 0.93125, 0.73133, 0.90518, 0.73133, 0.90518, 0.73133, 0.90518, 0.73133, 0.90518, 0.73133, 0.90518, 0.81921, 0.77618, 0.81921, 0.77618, 0.81921, 0.77618, 1, 1, 0.87356, 0.8785, 0.91075, 0.89608, 0.95958, 0.88401, 0.95958, 0.88401, 0.95958, 0.88401, 0.95958, 0.88401, 0.95958, 0.88401, 0.76229, 0.90167, 0.59526, 0.91916, 1, 1, 0.86304, 0.69225, 0.88401, 1, 1, 0.70424, 0.79468, 0.91926, 0.88175, 0.70823, 0.94903, 0.9121, 0.8785, 1, 1, 0.9121, 0.8785, 0.87802, 0.88656, 0.8785, 0.86943, 0.8801, 0.86943, 0.8801, 0.86943, 0.8801, 0.87402, 0.89291, 0.77958, 0.91343, 1, 1, 0.77958, 0.91343, 0.70864, 0.7173, 0.70864, 0.7173, 0.70864, 0.7173, 0.70864, 0.7173, 1, 1, 0.81055, 0.75841, 0.81055, 1.06452, 0.90399, 0.8785, 0.90399, 0.8785, 0.90399, 0.8785, 0.90399, 0.8785, 0.90399, 0.8785, 0.90399, 0.8785, 0.96017, 0.95794, 0.77892, 0.85162, 0.77892, 0.78257, 0.79492, 0.78257, 0.79492, 0.78257, 0.79492, 0.9297, 0.56892, 0.83908, 0.88762, 0.77539, 0.8715, 0.87508, 0.89049, 1, 1, 0.81055, 1.04106, 1.20528, 1.20528, 1, 1.15543, 0.70674, 0.98387, 0.94721, 1.33431, 1.45894, 0.95161, 1.06303, 0.83908, 0.80352, 0.57184, 0.6965, 0.56289, 0.82001, 0.56029, 0.81235, 1.02988, 0.83908, 0.7762, 0.68156, 0.80367, 0.73133, 0.78257, 0.87356, 0.86943, 0.95958, 0.75727, 0.89019, 1.04924, 0.9121, 0.7648, 0.86943, 0.87356, 0.79795, 0.78275, 0.81055, 0.77892, 0.9762, 0.82577, 0.99819, 0.84896, 0.95958, 0.77892, 0.96108, 1.01407, 0.89049, 1.02988, 0.94211, 0.96108, 0.8936, 0.84021, 0.87842, 0.96399, 0.79109, 0.89049, 1.00813, 1.02988, 0.86077, 0.87445, 0.92099, 0.84723, 0.86513, 0.8801, 0.75638, 0.85714, 0.78216, 0.79586, 0.87965, 0.94211, 0.97747, 0.78287, 0.97926, 0.84971, 1.02988, 0.94211, 0.8801, 0.94211, 0.84971, 0.73133, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.90264, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.90518, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.90548, 1, 1, 1, 1, 1, 1, 0.96017, 0.95794, 0.96017, 0.95794, 0.96017, 0.95794, 0.77892, 0.85162, 1, 1, 0.89552, 0.90527, 1, 0.90363, 0.92794, 0.92794, 0.92794, 0.92794, 0.87012, 0.87012, 0.87012, 0.89552, 0.89552, 1.42259, 0.71143, 1.06152, 1, 1, 1.03372, 1.03372, 0.97171, 1.4956, 2.2807, 0.93835, 0.83406, 0.91133, 0.84107, 0.91133, 1, 1, 1, 0.72021, 1, 1.23108, 0.83489, 0.88525, 0.88525, 0.81499, 0.90527, 1.81055, 0.90527, 1.81055, 1.31006, 1.53711, 0.94434, 1.08696, 1, 0.95018, 0.77192, 0.85284, 0.90747, 1.17534, 0.69825, 0.9716, 1.37077, 0.90747, 0.90747, 0.85356, 0.90747, 0.90747, 1.44947, 0.85284, 0.8941, 0.8941, 0.70572, 0.8, 0.70572, 0.70572, 0.70572, 0.70572, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.99862, 0.99862, 1, 1, 1, 1, 1, 1.08004, 0.91027, 1, 1, 1, 0.99862, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.90727, 0.90727, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
const CalibriBoldMetrics = {
lineHeight: 1.2207,
lineGap: 0.2207
};
const CalibriBoldItalicFactors = [1.3877, 1, 1, 1, 0.97801, 0.92482, 0.89552, 0.91133, 0.81988, 0.97566, 0.98152, 0.93548, 0.93548, 1.2798, 0.85284, 0.92794, 1, 0.96134, 1.56239, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.82845, 0.82845, 0.85284, 0.85284, 0.85284, 0.75859, 0.92138, 0.83908, 0.7762, 0.71805, 0.87289, 0.73133, 0.7514, 0.81921, 0.87356, 0.95958, 0.59526, 0.75727, 0.69225, 1.04924, 0.90872, 0.85938, 0.79795, 0.87068, 0.77958, 0.69766, 0.81055, 0.90399, 0.88653, 0.96068, 0.82577, 0.77892, 0.78257, 0.97507, 1.529, 0.97507, 0.85284, 0.89552, 0.90176, 0.94908, 0.86411, 0.74012, 0.86411, 0.88323, 0.95015, 0.86411, 0.86331, 0.88401, 0.91916, 0.86304, 0.88401, 0.9039, 0.86331, 0.86331, 0.86411, 0.86411, 0.90464, 0.70852, 1.04106, 0.86331, 0.84372, 0.95794, 0.82616, 0.84548, 0.79492, 0.88331, 1.69808, 0.88331, 0.85284, 0.97801, 0.89552, 0.91133, 0.89552, 0.91133, 1.7801, 0.89552, 1.24487, 1.13254, 1.19129, 0.96839, 0.85284, 0.68787, 0.70645, 0.85592, 0.90747, 1.01466, 1.0088, 0.90323, 1, 1.07463, 1, 0.91056, 0.75806, 1.19118, 0.96839, 0.78864, 0.82845, 0.84133, 0.75859, 0.83908, 0.83908, 0.83908, 0.83908, 0.83908, 0.83908, 0.77539, 0.71805, 0.73133, 0.73133, 0.73133, 0.73133, 0.95958, 0.95958, 0.95958, 0.95958, 0.88506, 0.90872, 0.85938, 0.85938, 0.85938, 0.85938, 0.85938, 0.85284, 0.87068, 0.90399, 0.90399, 0.90399, 0.90399, 0.77892, 0.79795, 0.90807, 0.94908, 0.94908, 0.94908, 0.94908, 0.94908, 0.94908, 0.85887, 0.74012, 0.88323, 0.88323, 0.88323, 0.88323, 0.88401, 0.88401, 0.88401, 0.88401, 0.8785, 0.86331, 0.86331, 0.86331, 0.86331, 0.86331, 0.86331, 0.90747, 0.89049, 0.86331, 0.86331, 0.86331, 0.86331, 0.84548, 0.86411, 0.84548, 0.83908, 0.94908, 0.83908, 0.94908, 0.83908, 0.94908, 0.71805, 0.74012, 0.71805, 0.74012, 0.71805, 0.74012, 0.71805, 0.74012, 0.87289, 0.79538, 0.88506, 0.92726, 0.73133, 0.88323, 0.73133, 0.88323, 0.73133, 0.88323, 0.73133, 0.88323, 0.73133, 0.88323, 0.81921, 0.86411, 0.81921, 0.86411, 0.81921, 0.86411, 1, 1, 0.87356, 0.86331, 0.91075, 0.8777, 0.95958, 0.88401, 0.95958, 0.88401, 0.95958, 0.88401, 0.95958, 0.88401, 0.95958, 0.88401, 0.76467, 0.90167, 0.59526, 0.91916, 1, 1, 0.86304, 0.69225, 0.88401, 1, 1, 0.70424, 0.77312, 0.91926, 0.88175, 0.70823, 0.94903, 0.90872, 0.86331, 1, 1, 0.90872, 0.86331, 0.86906, 0.88116, 0.86331, 0.85938, 0.86331, 0.85938, 0.86331, 0.85938, 0.86331, 0.87402, 0.86549, 0.77958, 0.90464, 1, 1, 0.77958, 0.90464, 0.69766, 0.70852, 0.69766, 0.70852, 0.69766, 0.70852, 0.69766, 0.70852, 1, 1, 0.81055, 0.75841, 0.81055, 1.06452, 0.90399, 0.86331, 0.90399, 0.86331, 0.90399, 0.86331, 0.90399, 0.86331, 0.90399, 0.86331, 0.90399, 0.86331, 0.96068, 0.95794, 0.77892, 0.84548, 0.77892, 0.78257, 0.79492, 0.78257, 0.79492, 0.78257, 0.79492, 0.9297, 0.56892, 0.83908, 0.94908, 0.77539, 0.85887, 0.87068, 0.89049, 1, 1, 0.81055, 1.04106, 1.20528, 1.20528, 1, 1.15543, 0.70088, 0.98387, 0.94721, 1.33431, 1.45894, 0.95161, 1.48387, 0.83908, 0.80352, 0.57118, 0.6965, 0.56347, 0.79179, 0.55853, 0.80346, 1.02988, 0.83908, 0.7762, 0.67174, 0.86036, 0.73133, 0.78257, 0.87356, 0.86441, 0.95958, 0.75727, 0.89019, 1.04924, 0.90872, 0.74889, 0.85938, 0.87891, 0.79795, 0.7957, 0.81055, 0.77892, 0.97447, 0.82577, 0.97466, 0.87179, 0.95958, 0.77892, 0.94252, 0.95612, 0.8753, 1.02988, 0.92733, 0.94252, 0.87411, 0.84021, 0.8728, 0.95612, 0.74081, 0.8753, 1.02189, 1.02988, 0.84814, 0.87445, 0.91822, 0.84723, 0.85668, 0.86331, 0.81344, 0.87581, 0.76422, 0.82046, 0.96057, 0.92733, 0.99375, 0.78022, 0.95452, 0.86015, 1.02988, 0.92733, 0.86331, 0.92733, 0.86015, 0.73133, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.90631, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.88323, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.85174, 1, 1, 1, 1, 1, 1, 0.96068, 0.95794, 0.96068, 0.95794, 0.96068, 0.95794, 0.77892, 0.84548, 1, 1, 0.89552, 0.90527, 1, 0.90363, 0.92794, 0.92794, 0.92794, 0.89807, 0.87012, 0.87012, 0.87012, 0.89552, 0.89552, 1.42259, 0.71094, 1.06152, 1, 1, 1.03372, 1.03372, 0.97171, 1.4956, 2.2807, 0.92972, 0.83406, 0.91133, 0.83326, 0.91133, 1, 1, 1, 0.72021, 1, 1.23108, 0.83489, 0.88525, 0.88525, 0.81499, 0.90616, 1.81055, 0.90527, 1.81055, 1.3107, 1.53711, 0.94434, 1.08696, 1, 0.95018, 0.77192, 0.85284, 0.90747, 1.17534, 0.69825, 0.9716, 1.37077, 0.90747, 0.90747, 0.85356, 0.90747, 0.90747, 1.44947, 0.85284, 0.8941, 0.8941, 0.70572, 0.8, 0.70572, 0.70572, 0.70572, 0.70572, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.99862, 0.99862, 1, 1, 1, 1, 1, 1.08004, 0.91027, 1, 1, 1, 0.99862, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.90727, 0.90727, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
const CalibriBoldItalicMetrics = {
lineHeight: 1.2207,
lineGap: 0.2207
};
const CalibriItalicFactors = [1.3877, 1, 1, 1, 1.17223, 1.1293, 0.89552, 0.91133, 0.80395, 1.02269, 1.15601, 0.91056, 0.91056, 1.2798, 0.85284, 0.89807, 1, 0.90861, 1.39543, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.96309, 0.96309, 0.85284, 0.85284, 0.85284, 0.83319, 0.88071, 0.8675, 0.81552, 0.72346, 0.85193, 0.73206, 0.7522, 0.81105, 0.86275, 0.90685, 0.6377, 0.77892, 0.75593, 1.02638, 0.89249, 0.84118, 0.77452, 0.85374, 0.75186, 0.67789, 0.79776, 0.88844, 0.85066, 0.94309, 0.77818, 0.7306, 0.76659, 1.10369, 1.38313, 1.10369, 1.06139, 0.89552, 0.8739, 0.9245, 0.9245, 0.83203, 0.9245, 0.85865, 1.09842, 0.9245, 0.9245, 1.03297, 1.07692, 0.90918, 1.03297, 0.94959, 0.9245, 0.92274, 0.9245, 0.9245, 1.02933, 0.77832, 1.20562, 0.9245, 0.8916, 0.98986, 0.86621, 0.89453, 0.79004, 0.94152, 1.77256, 0.94152, 0.85284, 0.97801, 0.89552, 0.91133, 0.89552, 0.91133, 1.91729, 0.89552, 1.17889, 1.13254, 1.16359, 0.92098, 0.85284, 0.68787, 0.71353, 0.84737, 0.90747, 1.0088, 1.0044, 0.87683, 1, 1.09091, 1, 0.92229, 0.739, 1.15642, 0.92098, 0.76288, 0.80504, 0.80972, 0.75859, 0.8675, 0.8675, 0.8675, 0.8675, 0.8675, 0.8675, 0.76318, 0.72346, 0.73206, 0.73206, 0.73206, 0.73206, 0.90685, 0.90685, 0.90685, 0.90685, 0.86477, 0.89249, 0.84118, 0.84118, 0.84118, 0.84118, 0.84118, 0.85284, 0.84557, 0.88844, 0.88844, 0.88844, 0.88844, 0.7306, 0.77452, 0.86331, 0.9245, 0.9245, 0.9245, 0.9245, 0.9245, 0.9245, 0.84843, 0.83203, 0.85865, 0.85865, 0.85865, 0.85865, 0.82601, 0.82601, 0.82601, 0.82601, 0.94469, 0.9245, 0.92274, 0.92274, 0.92274, 0.92274, 0.92274, 0.90747, 0.86651, 0.9245, 0.9245, 0.9245, 0.9245, 0.89453, 0.9245, 0.89453, 0.8675, 0.9245, 0.8675, 0.9245, 0.8675, 0.9245, 0.72346, 0.83203, 0.72346, 0.83203, 0.72346, 0.83203, 0.72346, 0.83203, 0.85193, 0.8875, 0.86477, 0.99034, 0.73206, 0.85865, 0.73206, 0.85865, 0.73206, 0.85865, 0.73206, 0.85865, 0.73206, 0.85865, 0.81105, 0.9245, 0.81105, 0.9245, 0.81105, 0.9245, 1, 1, 0.86275, 0.9245, 0.90872, 0.93591, 0.90685, 0.82601, 0.90685, 0.82601, 0.90685, 0.82601, 0.90685, 1.03297, 0.90685, 0.82601, 0.77896, 1.05611, 0.6377, 1.07692, 1, 1, 0.90918, 0.75593, 1.03297, 1, 1, 0.76032, 0.9375, 0.98156, 0.93407, 0.77261, 1.11429, 0.89249, 0.9245, 1, 1, 0.89249, 0.9245, 0.92534, 0.86698, 0.9245, 0.84118, 0.92274, 0.84118, 0.92274, 0.84118, 0.92274, 0.8667, 0.86291, 0.75186, 1.02933, 1, 1, 0.75186, 1.02933, 0.67789, 0.77832, 0.67789, 0.77832, 0.67789, 0.77832, 0.67789, 0.77832, 1, 1, 0.79776, 0.97655, 0.79776, 1.23023, 0.88844, 0.9245, 0.88844, 0.9245, 0.88844, 0.9245, 0.88844, 0.9245, 0.88844, 0.9245, 0.88844, 0.9245, 0.94309, 0.98986, 0.7306, 0.89453, 0.7306, 0.76659, 0.79004, 0.76659, 0.79004, 0.76659, 0.79004, 1.09231, 0.54873, 0.8675, 0.9245, 0.76318, 0.84843, 0.84557, 0.86651, 1, 1, 0.79776, 1.20562, 1.18622, 1.18622, 1, 1.1437, 0.67009, 0.96334, 0.93695, 1.35191, 1.40909, 0.95161, 1.48387, 0.8675, 0.90861, 0.6192, 0.7363, 0.64824, 0.82411, 0.56321, 0.85696, 1.23516, 0.8675, 0.81552, 0.7286, 0.84134, 0.73206, 0.76659, 0.86275, 0.84369, 0.90685, 0.77892, 0.85871, 1.02638, 0.89249, 0.75828, 0.84118, 0.85984, 0.77452, 0.76466, 0.79776, 0.7306, 0.90782, 0.77818, 0.903, 0.87291, 0.90685, 0.7306, 0.99058, 1.03667, 0.94635, 1.23516, 0.9849, 0.99058, 0.92393, 0.8916, 0.942, 1.03667, 0.75026, 0.94635, 1.0297, 1.23516, 0.90918, 0.94048, 0.98217, 0.89746, 0.84153, 0.92274, 0.82507, 0.88832, 0.84438, 0.88178, 1.03525, 0.9849, 1.00225, 0.78086, 0.97248, 0.89404, 1.23516, 0.9849, 0.92274, 0.9849, 0.89404, 0.73206, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.89693, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.85865, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.90933, 1, 1, 1, 1, 1, 1, 0.94309, 0.98986, 0.94309, 0.98986, 0.94309, 0.98986, 0.7306, 0.89453, 1, 1, 0.89552, 0.90527, 1, 0.90186, 1.12308, 1.12308, 1.12308, 1.12308, 1.2566, 1.2566, 1.2566, 0.89552, 0.89552, 1.42259, 0.68994, 1.03809, 1, 1, 1.0176, 1.0176, 1.11523, 1.4956, 2.01462, 0.97858, 0.82616, 0.91133, 0.83437, 0.91133, 1, 1, 1, 0.70508, 1, 1.23108, 0.79801, 0.84426, 0.84426, 0.774, 0.90572, 1.81055, 0.90749, 1.81055, 1.28809, 1.55469, 0.94434, 1.07806, 1, 0.97094, 0.7589, 0.85284, 0.90747, 1.19658, 0.69825, 0.97622, 1.33512, 0.90747, 0.90747, 0.85284, 0.90747, 0.90747, 1.44947, 0.85284, 0.8941, 0.8941, 0.70572, 0.8, 0.70572, 0.70572, 0.70572, 0.70572, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.99862, 0.99862, 1, 1, 1, 1, 1, 1.0336, 0.91027, 1, 1, 1, 0.99862, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.05859, 1.05859, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
const CalibriItalicMetrics = {
lineHeight: 1.2207,
lineGap: 0.2207
};
const CalibriRegularFactors = [1.3877, 1, 1, 1, 1.17223, 1.1293, 0.89552, 0.91133, 0.80395, 1.02269, 1.15601, 0.91056, 0.91056, 1.2798, 0.85284, 0.89807, 1, 0.90861, 1.39016, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.96309, 0.96309, 0.85284, 0.85284, 0.85284, 0.83319, 0.88071, 0.8675, 0.81552, 0.73834, 0.85193, 0.73206, 0.7522, 0.81105, 0.86275, 0.90685, 0.6377, 0.77892, 0.75593, 1.02638, 0.89385, 0.85122, 0.77452, 0.86503, 0.75186, 0.68887, 0.79776, 0.88844, 0.85066, 0.94258, 0.77818, 0.7306, 0.76659, 1.10369, 1.39016, 1.10369, 1.06139, 0.89552, 0.8739, 0.86128, 0.94469, 0.8457, 0.94469, 0.89464, 1.09842, 0.84636, 0.94469, 1.03297, 1.07692, 0.90918, 1.03297, 0.95897, 0.94469, 0.9482, 0.94469, 0.94469, 1.04692, 0.78223, 1.20562, 0.94469, 0.90332, 0.98986, 0.86621, 0.90527, 0.79004, 0.94152, 1.77256, 0.94152, 0.85284, 0.97801, 0.89552, 0.91133, 0.89552, 0.91133, 1.91729, 0.89552, 1.17889, 1.13254, 1.08707, 0.92098, 0.85284, 0.68787, 0.71353, 0.84737, 0.90747, 1.0088, 1.0044, 0.87683, 1, 1.09091, 1, 0.92229, 0.739, 1.15642, 0.92098, 0.76288, 0.80504, 0.80972, 0.75859, 0.8675, 0.8675, 0.8675, 0.8675, 0.8675, 0.8675, 0.76318, 0.73834, 0.73206, 0.73206, 0.73206, 0.73206, 0.90685, 0.90685, 0.90685, 0.90685, 0.86477, 0.89385, 0.85122, 0.85122, 0.85122, 0.85122, 0.85122, 0.85284, 0.85311, 0.88844, 0.88844, 0.88844, 0.88844, 0.7306, 0.77452, 0.86331, 0.86128, 0.86128, 0.86128, 0.86128, 0.86128, 0.86128, 0.8693, 0.8457, 0.89464, 0.89464, 0.89464, 0.89464, 0.82601, 0.82601, 0.82601, 0.82601, 0.94469, 0.94469, 0.9482, 0.9482, 0.9482, 0.9482, 0.9482, 0.90747, 0.86651, 0.94469, 0.94469, 0.94469, 0.94469, 0.90527, 0.94469, 0.90527, 0.8675, 0.86128, 0.8675, 0.86128, 0.8675, 0.86128, 0.73834, 0.8457, 0.73834, 0.8457, 0.73834, 0.8457, 0.73834, 0.8457, 0.85193, 0.92454, 0.86477, 0.9921, 0.73206, 0.89464, 0.73206, 0.89464, 0.73206, 0.89464, 0.73206, 0.89464, 0.73206, 0.89464, 0.81105, 0.84636, 0.81105, 0.84636, 0.81105, 0.84636, 1, 1, 0.86275, 0.94469, 0.90872, 0.95786, 0.90685, 0.82601, 0.90685, 0.82601, 0.90685, 0.82601, 0.90685, 1.03297, 0.90685, 0.82601, 0.77741, 1.05611, 0.6377, 1.07692, 1, 1, 0.90918, 0.75593, 1.03297, 1, 1, 0.76032, 0.90452, 0.98156, 1.11842, 0.77261, 1.11429, 0.89385, 0.94469, 1, 1, 0.89385, 0.94469, 0.95877, 0.86901, 0.94469, 0.85122, 0.9482, 0.85122, 0.9482, 0.85122, 0.9482, 0.8667, 0.90016, 0.75186, 1.04692, 1, 1, 0.75186, 1.04692, 0.68887, 0.78223, 0.68887, 0.78223, 0.68887, 0.78223, 0.68887, 0.78223, 1, 1, 0.79776, 0.92188, 0.79776, 1.23023, 0.88844, 0.94469, 0.88844, 0.94469, 0.88844, 0.94469, 0.88844, 0.94469, 0.88844, 0.94469, 0.88844, 0.94469, 0.94258, 0.98986, 0.7306, 0.90527, 0.7306, 0.76659, 0.79004, 0.76659, 0.79004, 0.76659, 0.79004, 1.09231, 0.54873, 0.8675, 0.86128, 0.76318, 0.8693, 0.85311, 0.86651, 1, 1, 0.79776, 1.20562, 1.18622, 1.18622, 1, 1.1437, 0.67742, 0.96334, 0.93695, 1.35191, 1.40909, 0.95161, 1.48387, 0.86686, 0.90861, 0.62267, 0.74359, 0.65649, 0.85498, 0.56963, 0.88254, 1.23516, 0.8675, 0.81552, 0.75443, 0.84503, 0.73206, 0.76659, 0.86275, 0.85122, 0.90685, 0.77892, 0.85746, 1.02638, 0.89385, 0.75657, 0.85122, 0.86275, 0.77452, 0.74171, 0.79776, 0.7306, 0.95165, 0.77818, 0.89772, 0.88831, 0.90685, 0.7306, 0.98142, 1.02191, 0.96576, 1.23516, 0.99018, 0.98142, 0.9236, 0.89258, 0.94035, 1.02191, 0.78848, 0.96576, 0.9561, 1.23516, 0.90918, 0.92578, 0.95424, 0.89746, 0.83969, 0.9482, 0.80113, 0.89442, 0.85208, 0.86155, 0.98022, 0.99018, 1.00452, 0.81209, 0.99247, 0.89181, 1.23516, 0.99018, 0.9482, 0.99018, 0.89181, 0.73206, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.88844, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.89464, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.96766, 1, 1, 1, 1, 1, 1, 0.94258, 0.98986, 0.94258, 0.98986, 0.94258, 0.98986, 0.7306, 0.90527, 1, 1, 0.89552, 0.90527, 1, 0.90186, 1.12308, 1.12308, 1.12308, 1.12308, 1.2566, 1.2566, 1.2566, 0.89552, 0.89552, 1.42259, 0.69043, 1.03809, 1, 1, 1.0176, 1.0176, 1.11523, 1.4956, 2.01462, 0.99331, 0.82616, 0.91133, 0.84286, 0.91133, 1, 1, 1, 0.70508, 1, 1.23108, 0.79801, 0.84426, 0.84426, 0.774, 0.90527, 1.81055, 0.90527, 1.81055, 1.28809, 1.55469, 0.94434, 1.07806, 1, 0.97094, 0.7589, 0.85284, 0.90747, 1.19658, 0.69825, 0.97622, 1.33512, 0.90747, 0.90747, 0.85356, 0.90747, 0.90747, 1.44947, 0.85284, 0.8941, 0.8941, 0.70572, 0.8, 0.70572, 0.70572, 0.70572, 0.70572, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.99862, 0.99862, 1, 1, 1, 1, 1, 1.0336, 0.91027, 1, 1, 1, 0.99862, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.05859, 1.05859, 1, 1, 1, 1.07185, 0.99413, 0.96334, 1.08065, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
const CalibriRegularMetrics = {
lineHeight: 1.2207,
lineGap: 0.2207
};
;// ./src/core/helvetica_factors.js
const HelveticaBoldFactors = [0.76116, 1, 1, 1.0006, 0.99998, 0.99974, 0.99973, 0.99973, 0.99982, 0.99977, 1.00087, 0.99998, 0.99998, 0.99959, 1.00003, 1.0006, 0.99998, 1.0006, 1.0006, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99998, 1, 1.00003, 1.00003, 1.00003, 1.00026, 0.9999, 0.99977, 0.99977, 0.99977, 0.99977, 1.00001, 1.00026, 1.00022, 0.99977, 1.0006, 0.99973, 0.99977, 1.00026, 0.99999, 0.99977, 1.00022, 1.00001, 1.00022, 0.99977, 1.00001, 1.00026, 0.99977, 1.00001, 1.00016, 1.00001, 1.00001, 1.00026, 0.99998, 1.0006, 0.99998, 1.00003, 0.99973, 0.99998, 0.99973, 1.00026, 0.99973, 1.00026, 0.99973, 0.99998, 1.00026, 1.00026, 1.0006, 1.0006, 0.99973, 1.0006, 0.99982, 1.00026, 1.00026, 1.00026, 1.00026, 0.99959, 0.99973, 0.99998, 1.00026, 0.99973, 1.00022, 0.99973, 0.99973, 1, 0.99959, 1.00077, 0.99959, 1.00003, 0.99998, 0.99973, 0.99973, 0.99973, 0.99973, 1.00077, 0.99973, 0.99998, 1.00025, 0.99968, 0.99973, 1.00003, 1.00025, 0.60299, 1.00024, 1.06409, 1, 1, 0.99998, 1, 0.99973, 1.0006, 0.99998, 1, 0.99936, 0.99973, 1.00002, 1.00002, 1.00002, 1.00026, 0.99977, 0.99977, 0.99977, 0.99977, 0.99977, 0.99977, 1, 0.99977, 1.00001, 1.00001, 1.00001, 1.00001, 1.0006, 1.0006, 1.0006, 1.0006, 0.99977, 0.99977, 1.00022, 1.00022, 1.00022, 1.00022, 1.00022, 1.00003, 1.00022, 0.99977, 0.99977, 0.99977, 0.99977, 1.00001, 1.00001, 1.00026, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99982, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 1.0006, 1.0006, 1.0006, 1.0006, 1.00026, 1.00026, 1.00026, 1.00026, 1.00026, 1.00026, 1.00026, 1.06409, 1.00026, 1.00026, 1.00026, 1.00026, 1.00026, 0.99973, 1.00026, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 1.03374, 0.99977, 1.00026, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00022, 1.00026, 1.00022, 1.00026, 1.00022, 1.00026, 1.00022, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.00042, 0.99973, 0.99973, 1.0006, 0.99977, 0.99973, 0.99973, 1.00026, 1.0006, 1.00026, 1.0006, 1.00026, 1.03828, 1.00026, 0.99999, 1.00026, 1.0006, 0.99977, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 0.9993, 0.9998, 1.00026, 1.00022, 1.00026, 1.00022, 1.00026, 1.00022, 1.00026, 1, 1.00016, 0.99977, 0.99959, 0.99977, 0.99959, 0.99977, 0.99959, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00026, 0.99998, 1.00026, 0.8121, 1.00026, 0.99998, 0.99977, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 1.00016, 1.00022, 1.00001, 0.99973, 1.00001, 1.00026, 1, 1.00026, 1, 1.00026, 1, 1.0006, 0.99973, 0.99977, 0.99973, 1, 0.99982, 1.00022, 1.00026, 1.00001, 0.99973, 1.00026, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 1.00034, 0.99977, 1, 0.99997, 1.00026, 1.00078, 1.00036, 0.99973, 1.00013, 1.0006, 0.99977, 0.99977, 0.99988, 0.85148, 1.00001, 1.00026, 0.99977, 1.00022, 1.0006, 0.99977, 1.00001, 0.99999, 0.99977, 1.00069, 1.00022, 0.99977, 1.00001, 0.99984, 1.00026, 1.00001, 1.00024, 1.00001, 0.9999, 1, 1.0006, 1.00001, 1.00041, 0.99962, 1.00026, 1.0006, 0.99995, 1.00041, 0.99942, 0.99973, 0.99927, 1.00082, 0.99902, 1.00026, 1.00087, 1.0006, 1.00069, 0.99973, 0.99867, 0.99973, 0.9993, 1.00026, 1.00049, 1.00056, 1, 0.99988, 0.99935, 0.99995, 0.99954, 1.00055, 0.99945, 1.00032, 1.0006, 0.99995, 1.00026, 0.99995, 1.00032, 1.00001, 1.00008, 0.99971, 1.00019, 0.9994, 1.00001, 1.0006, 1.00044, 0.99973, 1.00023, 1.00047, 1, 0.99942, 0.99561, 0.99989, 1.00035, 0.99977, 1.00035, 0.99977, 1.00019, 0.99944, 1.00001, 1.00021, 0.99926, 1.00035, 1.00035, 0.99942, 1.00048, 0.99999, 0.99977, 1.00022, 1.00035, 1.00001, 0.99977, 1.00026, 0.99989, 1.00057, 1.00001, 0.99936, 1.00052, 1.00012, 0.99996, 1.00043, 1, 1.00035, 0.9994, 0.99976, 1.00035, 0.99973, 1.00052, 1.00041, 1.00119, 1.00037, 0.99973, 1.00002, 0.99986, 1.00041, 1.00041, 0.99902, 0.9996, 1.00034, 0.99999, 1.00026, 0.99999, 1.00026, 0.99973, 1.00052, 0.99973, 1, 0.99973, 1.00041, 1.00075, 0.9994, 1.0003, 0.99999, 1, 1.00041, 0.99955, 1, 0.99915, 0.99973, 0.99973, 1.00026, 1.00119, 0.99955, 0.99973, 1.0006, 0.99911, 1.0006, 1.00026, 0.99972, 1.00026, 0.99902, 1.00041, 0.99973, 0.99999, 1, 1, 1.00038, 1.0005, 1.00016, 1.00022, 1.00016, 1.00022, 1.00016, 1.00022, 1.00001, 0.99973, 1, 1, 0.99973, 1, 1, 0.99955, 1.0006, 1.0006, 1.0006, 1.0006, 1, 1, 1, 0.99973, 0.99973, 0.99972, 1, 1, 1.00106, 0.99999, 0.99998, 0.99998, 0.99999, 0.99998, 1.66475, 1, 0.99973, 0.99973, 1.00023, 0.99973, 0.99971, 1.00047, 1.00023, 1, 0.99991, 0.99984, 1.00002, 1.00002, 1.00002, 1.00002, 1, 1, 1, 1, 1, 1, 1, 0.99972, 1, 1.20985, 1.39713, 1.00003, 1.00031, 1.00015, 1, 0.99561, 1.00027, 1.00031, 1.00031, 0.99915, 1.00031, 1.00031, 0.99999, 1.00003, 0.99999, 0.99999, 1.41144, 1.6, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.40579, 1.40579, 1.36625, 0.99999, 1, 0.99861, 0.99861, 1, 1.00026, 1.00026, 1.00026, 1.00026, 0.99972, 0.99999, 0.99999, 0.99999, 0.99999, 1.40483, 1, 0.99977, 1.00054, 1, 1, 0.99953, 0.99962, 1.00042, 0.9995, 1, 1, 1, 1, 1, 1, 1, 1, 0.99998, 0.99998, 0.99998, 0.99998, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
const HelveticaBoldMetrics = {
lineHeight: 1.2,
lineGap: 0.2
};
const HelveticaBoldItalicFactors = [0.76116, 1, 1, 1.0006, 0.99998, 0.99974, 0.99973, 0.99973, 0.99982, 0.99977, 1.00087, 0.99998, 0.99998, 0.99959, 1.00003, 1.0006, 0.99998, 1.0006, 1.0006, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99998, 1, 1.00003, 1.00003, 1.00003, 1.00026, 0.9999, 0.99977, 0.99977, 0.99977, 0.99977, 1.00001, 1.00026, 1.00022, 0.99977, 1.0006, 0.99973, 0.99977, 1.00026, 0.99999, 0.99977, 1.00022, 1.00001, 1.00022, 0.99977, 1.00001, 1.00026, 0.99977, 1.00001, 1.00016, 1.00001, 1.00001, 1.00026, 0.99998, 1.0006, 0.99998, 1.00003, 0.99973, 0.99998, 0.99973, 1.00026, 0.99973, 1.00026, 0.99973, 0.99998, 1.00026, 1.00026, 1.0006, 1.0006, 0.99973, 1.0006, 0.99982, 1.00026, 1.00026, 1.00026, 1.00026, 0.99959, 0.99973, 0.99998, 1.00026, 0.99973, 1.00022, 0.99973, 0.99973, 1, 0.99959, 1.00077, 0.99959, 1.00003, 0.99998, 0.99973, 0.99973, 0.99973, 0.99973, 1.00077, 0.99973, 0.99998, 1.00025, 0.99968, 0.99973, 1.00003, 1.00025, 0.60299, 1.00024, 1.06409, 1, 1, 0.99998, 1, 0.99973, 1.0006, 0.99998, 1, 0.99936, 0.99973, 1.00002, 1.00002, 1.00002, 1.00026, 0.99977, 0.99977, 0.99977, 0.99977, 0.99977, 0.99977, 1, 0.99977, 1.00001, 1.00001, 1.00001, 1.00001, 1.0006, 1.0006, 1.0006, 1.0006, 0.99977, 0.99977, 1.00022, 1.00022, 1.00022, 1.00022, 1.00022, 1.00003, 1.00022, 0.99977, 0.99977, 0.99977, 0.99977, 1.00001, 1.00001, 1.00026, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99982, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 1.0006, 1.0006, 1.0006, 1.0006, 1.00026, 1.00026, 1.00026, 1.00026, 1.00026, 1.00026, 1.00026, 1.06409, 1.00026, 1.00026, 1.00026, 1.00026, 1.00026, 0.99973, 1.00026, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 1.0044, 0.99977, 1.00026, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00022, 1.00026, 1.00022, 1.00026, 1.00022, 1.00026, 1.00022, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 0.99971, 0.99973, 0.99973, 1.0006, 0.99977, 0.99973, 0.99973, 1.00026, 1.0006, 1.00026, 1.0006, 1.00026, 1.01011, 1.00026, 0.99999, 1.00026, 1.0006, 0.99977, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 0.9993, 0.9998, 1.00026, 1.00022, 1.00026, 1.00022, 1.00026, 1.00022, 1.00026, 1, 1.00016, 0.99977, 0.99959, 0.99977, 0.99959, 0.99977, 0.99959, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00026, 0.99998, 1.00026, 0.8121, 1.00026, 0.99998, 0.99977, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 1.00016, 1.00022, 1.00001, 0.99973, 1.00001, 1.00026, 1, 1.00026, 1, 1.00026, 1, 1.0006, 0.99973, 0.99977, 0.99973, 1, 0.99982, 1.00022, 1.00026, 1.00001, 0.99973, 1.00026, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99977, 1, 1, 1.00026, 0.99969, 0.99972, 0.99981, 0.9998, 1.0006, 0.99977, 0.99977, 1.00022, 0.91155, 1.00001, 1.00026, 0.99977, 1.00022, 1.0006, 0.99977, 1.00001, 0.99999, 0.99977, 0.99966, 1.00022, 1.00032, 1.00001, 0.99944, 1.00026, 1.00001, 0.99968, 1.00001, 1.00047, 1, 1.0006, 1.00001, 0.99981, 1.00101, 1.00026, 1.0006, 0.99948, 0.99981, 1.00064, 0.99973, 0.99942, 1.00101, 1.00061, 1.00026, 1.00069, 1.0006, 1.00014, 0.99973, 1.01322, 0.99973, 1.00065, 1.00026, 1.00012, 0.99923, 1, 1.00064, 1.00076, 0.99948, 1.00055, 1.00063, 1.00007, 0.99943, 1.0006, 0.99948, 1.00026, 0.99948, 0.99943, 1.00001, 1.00001, 1.00029, 1.00038, 1.00035, 1.00001, 1.0006, 1.0006, 0.99973, 0.99978, 1.00001, 1.00057, 0.99989, 0.99967, 0.99964, 0.99967, 0.99977, 0.99999, 0.99977, 1.00038, 0.99977, 1.00001, 0.99973, 1.00066, 0.99967, 0.99967, 1.00041, 0.99998, 0.99999, 0.99977, 1.00022, 0.99967, 1.00001, 0.99977, 1.00026, 0.99964, 1.00031, 1.00001, 0.99999, 0.99999, 1, 1.00023, 1, 1, 0.99999, 1.00035, 1.00001, 0.99999, 0.99973, 0.99977, 0.99999, 1.00058, 0.99973, 0.99973, 0.99955, 0.9995, 1.00026, 1.00026, 1.00032, 0.99989, 1.00034, 0.99999, 1.00026, 1.00026, 1.00026, 0.99973, 0.45998, 0.99973, 1.00026, 0.99973, 1.00001, 0.99999, 0.99982, 0.99994, 0.99996, 1, 1.00042, 1.00044, 1.00029, 1.00023, 0.99973, 0.99973, 1.00026, 0.99949, 1.00002, 0.99973, 1.0006, 1.0006, 1.0006, 0.99975, 1.00026, 1.00026, 1.00032, 0.98685, 0.99973, 1.00026, 1, 1, 0.99966, 1.00044, 1.00016, 1.00022, 1.00016, 1.00022, 1.00016, 1.00022, 1.00001, 0.99973, 1, 1, 0.99973, 1, 1, 0.99955, 1.0006, 1.0006, 1.0006, 1.0006, 1, 1, 1, 0.99973, 0.99973, 0.99972, 1, 1, 1.00106, 0.99999, 0.99998, 0.99998, 0.99999, 0.99998, 1.66475, 1, 0.99973, 0.99973, 1, 0.99973, 0.99971, 0.99978, 1, 1, 0.99991, 0.99984, 1.00002, 1.00002, 1.00002, 1.00002, 1.00098, 1, 1, 1, 1.00049, 1, 1, 0.99972, 1, 1.20985, 1.39713, 1.00003, 1.00031, 1.00015, 1, 0.99561, 1.00027, 1.00031, 1.00031, 0.99915, 1.00031, 1.00031, 0.99999, 1.00003, 0.99999, 0.99999, 1.41144, 1.6, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.40579, 1.40579, 1.36625, 0.99999, 1, 0.99861, 0.99861, 1, 1.00026, 1.00026, 1.00026, 1.00026, 0.99972, 0.99999, 0.99999, 0.99999, 0.99999, 1.40483, 1, 0.99977, 1.00054, 1, 1, 0.99953, 0.99962, 1.00042, 0.9995, 1, 1, 1, 1, 1, 1, 1, 1, 0.99998, 0.99998, 0.99998, 0.99998, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
const HelveticaBoldItalicMetrics = {
lineHeight: 1.35,
lineGap: 0.2
};
const HelveticaItalicFactors = [0.76116, 1, 1, 1.0006, 1.0006, 1.00006, 0.99973, 0.99973, 0.99982, 1.00001, 1.00043, 0.99998, 0.99998, 0.99959, 1.00003, 1.0006, 0.99998, 1.0006, 1.0006, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 1.0006, 1, 1.00003, 1.00003, 1.00003, 0.99973, 0.99987, 1.00001, 1.00001, 0.99977, 0.99977, 1.00001, 1.00026, 1.00022, 0.99977, 1.0006, 1, 1.00001, 0.99973, 0.99999, 0.99977, 1.00022, 1.00001, 1.00022, 0.99977, 1.00001, 1.00026, 0.99977, 1.00001, 1.00016, 1.00001, 1.00001, 1.00026, 1.0006, 1.0006, 1.0006, 0.99949, 0.99973, 0.99998, 0.99973, 0.99973, 1, 0.99973, 0.99973, 1.0006, 0.99973, 0.99973, 0.99924, 0.99924, 1, 0.99924, 0.99999, 0.99973, 0.99973, 0.99973, 0.99973, 0.99998, 1, 1.0006, 0.99973, 1, 0.99977, 1, 1, 1, 1.00005, 1.0009, 1.00005, 1.00003, 0.99998, 0.99973, 0.99973, 0.99973, 0.99973, 1.0009, 0.99973, 0.99998, 1.00025, 0.99968, 0.99973, 1.00003, 1.00025, 0.60299, 1.00024, 1.06409, 1, 1, 0.99998, 1, 0.9998, 1.0006, 0.99998, 1, 0.99936, 0.99973, 1.00002, 1.00002, 1.00002, 1.00026, 1.00001, 1.00001, 1.00001, 1.00001, 1.00001, 1.00001, 1, 0.99977, 1.00001, 1.00001, 1.00001, 1.00001, 1.0006, 1.0006, 1.0006, 1.0006, 0.99977, 0.99977, 1.00022, 1.00022, 1.00022, 1.00022, 1.00022, 1.00003, 1.00022, 0.99977, 0.99977, 0.99977, 0.99977, 1.00001, 1.00001, 1.00026, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99982, 1, 0.99973, 0.99973, 0.99973, 0.99973, 1.0006, 1.0006, 1.0006, 1.0006, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 1.06409, 1.00026, 0.99973, 0.99973, 0.99973, 0.99973, 1, 0.99973, 1, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 0.99977, 1, 0.99977, 1, 0.99977, 1, 0.99977, 1, 0.99977, 1.0288, 0.99977, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00022, 0.99973, 1.00022, 0.99973, 1.00022, 0.99973, 1.00022, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 0.99924, 1.0006, 1.0006, 0.99946, 1.00034, 1, 0.99924, 1.00001, 1, 1, 0.99973, 0.99924, 0.99973, 0.99924, 0.99973, 1.06311, 0.99973, 1.00024, 0.99973, 0.99924, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 1.00041, 0.9998, 0.99973, 1.00022, 0.99973, 1.00022, 0.99973, 1.00022, 0.99973, 1, 1.00016, 0.99977, 0.99998, 0.99977, 0.99998, 0.99977, 0.99998, 1.00001, 1, 1.00001, 1, 1.00001, 1, 1.00001, 1, 1.00026, 1.0006, 1.00026, 0.89547, 1.00026, 1.0006, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 1.00016, 0.99977, 1.00001, 1, 1.00001, 1.00026, 1, 1.00026, 1, 1.00026, 1, 0.99924, 0.99973, 1.00001, 0.99973, 1, 0.99982, 1.00022, 1.00026, 1.00001, 1, 1.00026, 1.0006, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 1.00001, 1, 1.00054, 0.99977, 1.00084, 1.00007, 0.99973, 1.00013, 0.99924, 1.00001, 1.00001, 0.99945, 0.91221, 1.00001, 1.00026, 0.99977, 1.00022, 1.0006, 1.00001, 1.00001, 0.99999, 0.99977, 0.99933, 1.00022, 1.00054, 1.00001, 1.00065, 1.00026, 1.00001, 1.0001, 1.00001, 1.00052, 1, 1.0006, 1.00001, 0.99945, 0.99897, 0.99968, 0.99924, 1.00036, 0.99945, 0.99949, 1, 1.0006, 0.99897, 0.99918, 0.99968, 0.99911, 0.99924, 1, 0.99962, 1.01487, 1, 1.0005, 0.99973, 1.00012, 1.00043, 1, 0.99995, 0.99994, 1.00036, 0.99947, 1.00019, 1.00063, 1.00025, 0.99924, 1.00036, 0.99973, 1.00036, 1.00025, 1.00001, 1.00001, 1.00027, 1.0001, 1.00068, 1.00001, 1.0006, 1.0006, 1, 1.00008, 0.99957, 0.99972, 0.9994, 0.99954, 0.99975, 1.00051, 1.00001, 1.00019, 1.00001, 1.0001, 0.99986, 1.00001, 1.00001, 1.00038, 0.99954, 0.99954, 0.9994, 1.00066, 0.99999, 0.99977, 1.00022, 1.00054, 1.00001, 0.99977, 1.00026, 0.99975, 1.0001, 1.00001, 0.99993, 0.9995, 0.99955, 1.00016, 0.99978, 0.99974, 1.00019, 1.00022, 0.99955, 1.00053, 0.99973, 1.00089, 1.00005, 0.99967, 1.00048, 0.99973, 1.00002, 1.00034, 0.99973, 0.99973, 0.99964, 1.00006, 1.00066, 0.99947, 0.99973, 0.98894, 0.99973, 1, 0.44898, 1, 0.99946, 1, 1.00039, 1.00082, 0.99991, 0.99991, 0.99985, 1.00022, 1.00023, 1.00061, 1.00006, 0.99966, 0.99973, 0.99973, 0.99973, 1.00019, 1.0008, 1, 0.99924, 0.99924, 0.99924, 0.99983, 1.00044, 0.99973, 0.99964, 0.98332, 1, 0.99973, 1, 1, 0.99962, 0.99895, 1.00016, 0.99977, 1.00016, 0.99977, 1.00016, 0.99977, 1.00001, 1, 1, 1, 0.99973, 1, 1, 0.99955, 0.99924, 0.99924, 0.99924, 0.99924, 0.99998, 0.99998, 0.99998, 0.99973, 0.99973, 0.99972, 1, 1, 1.00267, 0.99999, 0.99998, 0.99998, 1, 0.99998, 1.66475, 1, 0.99973, 0.99973, 1.00023, 0.99973, 1.00423, 0.99925, 0.99999, 1, 0.99991, 0.99984, 1.00002, 1.00002, 1.00002, 1.00002, 1.00049, 1, 1.00245, 1, 1, 1, 1, 0.96329, 1, 1.20985, 1.39713, 1.00003, 0.8254, 1.00015, 1, 1.00035, 1.00027, 1.00031, 1.00031, 1.00003, 1.00031, 1.00031, 0.99999, 1.00003, 0.99999, 0.99999, 1.41144, 1.6, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.40579, 1.40579, 1.36625, 0.99999, 1, 0.99861, 0.99861, 1, 1.00026, 1.00026, 1.00026, 1.00026, 0.95317, 0.99999, 0.99999, 0.99999, 0.99999, 1.40483, 1, 0.99977, 1.00054, 1, 1, 0.99953, 0.99962, 1.00042, 0.9995, 1, 1, 1, 1, 1, 1, 1, 1, 0.99998, 0.99998, 0.99998, 0.99998, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
const HelveticaItalicMetrics = {
lineHeight: 1.35,
lineGap: 0.2
};
const HelveticaRegularFactors = [0.76116, 1, 1, 1.0006, 1.0006, 1.00006, 0.99973, 0.99973, 0.99982, 1.00001, 1.00043, 0.99998, 0.99998, 0.99959, 1.00003, 1.0006, 0.99998, 1.0006, 1.0006, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 1.0006, 1, 1.00003, 1.00003, 1.00003, 0.99973, 0.99987, 1.00001, 1.00001, 0.99977, 0.99977, 1.00001, 1.00026, 1.00022, 0.99977, 1.0006, 1, 1.00001, 0.99973, 0.99999, 0.99977, 1.00022, 1.00001, 1.00022, 0.99977, 1.00001, 1.00026, 0.99977, 1.00001, 1.00016, 1.00001, 1.00001, 1.00026, 1.0006, 1.0006, 1.0006, 0.99949, 0.99973, 0.99998, 0.99973, 0.99973, 1, 0.99973, 0.99973, 1.0006, 0.99973, 0.99973, 0.99924, 0.99924, 1, 0.99924, 0.99999, 0.99973, 0.99973, 0.99973, 0.99973, 0.99998, 1, 1.0006, 0.99973, 1, 0.99977, 1, 1, 1, 1.00005, 1.0009, 1.00005, 1.00003, 0.99998, 0.99973, 0.99973, 0.99973, 0.99973, 1.0009, 0.99973, 0.99998, 1.00025, 0.99968, 0.99973, 1.00003, 1.00025, 0.60299, 1.00024, 1.06409, 1, 1, 0.99998, 1, 0.9998, 1.0006, 0.99998, 1, 0.99936, 0.99973, 1.00002, 1.00002, 1.00002, 1.00026, 1.00001, 1.00001, 1.00001, 1.00001, 1.00001, 1.00001, 1, 0.99977, 1.00001, 1.00001, 1.00001, 1.00001, 1.0006, 1.0006, 1.0006, 1.0006, 0.99977, 0.99977, 1.00022, 1.00022, 1.00022, 1.00022, 1.00022, 1.00003, 1.00022, 0.99977, 0.99977, 0.99977, 0.99977, 1.00001, 1.00001, 1.00026, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99982, 1, 0.99973, 0.99973, 0.99973, 0.99973, 1.0006, 1.0006, 1.0006, 1.0006, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 1.06409, 1.00026, 0.99973, 0.99973, 0.99973, 0.99973, 1, 0.99973, 1, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 0.99977, 1, 0.99977, 1, 0.99977, 1, 0.99977, 1, 0.99977, 1.04596, 0.99977, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00022, 0.99973, 1.00022, 0.99973, 1.00022, 0.99973, 1.00022, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 0.99924, 1.0006, 1.0006, 1.00019, 1.00034, 1, 0.99924, 1.00001, 1, 1, 0.99973, 0.99924, 0.99973, 0.99924, 0.99973, 1.02572, 0.99973, 1.00005, 0.99973, 0.99924, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99999, 0.9998, 0.99973, 1.00022, 0.99973, 1.00022, 0.99973, 1.00022, 0.99973, 1, 1.00016, 0.99977, 0.99998, 0.99977, 0.99998, 0.99977, 0.99998, 1.00001, 1, 1.00001, 1, 1.00001, 1, 1.00001, 1, 1.00026, 1.0006, 1.00026, 0.84533, 1.00026, 1.0006, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 1.00016, 0.99977, 1.00001, 1, 1.00001, 1.00026, 1, 1.00026, 1, 1.00026, 1, 0.99924, 0.99973, 1.00001, 0.99973, 1, 0.99982, 1.00022, 1.00026, 1.00001, 1, 1.00026, 1.0006, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99928, 1, 0.99977, 1.00013, 1.00055, 0.99947, 0.99945, 0.99941, 0.99924, 1.00001, 1.00001, 1.0004, 0.91621, 1.00001, 1.00026, 0.99977, 1.00022, 1.0006, 1.00001, 1.00005, 0.99999, 0.99977, 1.00015, 1.00022, 0.99977, 1.00001, 0.99973, 1.00026, 1.00001, 1.00019, 1.00001, 0.99946, 1, 1.0006, 1.00001, 0.99978, 1.00045, 0.99973, 0.99924, 1.00023, 0.99978, 0.99966, 1, 1.00065, 1.00045, 1.00019, 0.99973, 0.99973, 0.99924, 1, 1, 0.96499, 1, 1.00055, 0.99973, 1.00008, 1.00027, 1, 0.9997, 0.99995, 1.00023, 0.99933, 1.00019, 1.00015, 1.00031, 0.99924, 1.00023, 0.99973, 1.00023, 1.00031, 1.00001, 0.99928, 1.00029, 1.00092, 1.00035, 1.00001, 1.0006, 1.0006, 1, 0.99988, 0.99975, 1, 1.00082, 0.99561, 0.9996, 1.00035, 1.00001, 0.99962, 1.00001, 1.00092, 0.99964, 1.00001, 0.99963, 0.99999, 1.00035, 1.00035, 1.00082, 0.99962, 0.99999, 0.99977, 1.00022, 1.00035, 1.00001, 0.99977, 1.00026, 0.9996, 0.99967, 1.00001, 1.00034, 1.00074, 1.00054, 1.00053, 1.00063, 0.99971, 0.99962, 1.00035, 0.99975, 0.99977, 0.99973, 1.00043, 0.99953, 1.0007, 0.99915, 0.99973, 1.00008, 0.99892, 1.00073, 1.00073, 1.00114, 0.99915, 1.00073, 0.99955, 0.99973, 1.00092, 0.99973, 1, 0.99998, 1, 1.0003, 1, 1.00043, 1.00001, 0.99969, 1.0003, 1, 1.00035, 1.00001, 0.9995, 1, 1.00092, 0.99973, 0.99973, 0.99973, 1.0007, 0.9995, 1, 0.99924, 1.0006, 0.99924, 0.99972, 1.00062, 0.99973, 1.00114, 1.00073, 1, 0.99955, 1, 1, 1.00047, 0.99968, 1.00016, 0.99977, 1.00016, 0.99977, 1.00016, 0.99977, 1.00001, 1, 1, 1, 0.99973, 1, 1, 0.99955, 0.99924, 0.99924, 0.99924, 0.99924, 0.99998, 0.99998, 0.99998, 0.99973, 0.99973, 0.99972, 1, 1, 1.00267, 0.99999, 0.99998, 0.99998, 1, 0.99998, 1.66475, 1, 0.99973, 0.99973, 1.00023, 0.99973, 0.99971, 0.99925, 1.00023, 1, 0.99991, 0.99984, 1.00002, 1.00002, 1.00002, 1.00002, 1, 1, 1, 1, 1, 1, 1, 0.96329, 1, 1.20985, 1.39713, 1.00003, 0.8254, 1.00015, 1, 1.00035, 1.00027, 1.00031, 1.00031, 0.99915, 1.00031, 1.00031, 0.99999, 1.00003, 0.99999, 0.99999, 1.41144, 1.6, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.40579, 1.40579, 1.36625, 0.99999, 1, 0.99861, 0.99861, 1, 1.00026, 1.00026, 1.00026, 1.00026, 0.95317, 0.99999, 0.99999, 0.99999, 0.99999, 1.40483, 1, 0.99977, 1.00054, 1, 1, 0.99953, 0.99962, 1.00042, 0.9995, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
const HelveticaRegularMetrics = {
lineHeight: 1.2,
lineGap: 0.2
};
;// ./src/core/liberationsans_widths.js
const LiberationSansBoldWidths = [365, 0, 333, 278, 333, 474, 556, 556, 889, 722, 238, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 333, 333, 584, 584, 584, 611, 975, 722, 722, 722, 722, 667, 611, 778, 722, 278, 556, 722, 611, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 333, 278, 333, 584, 556, 333, 556, 611, 556, 611, 556, 333, 611, 611, 278, 278, 556, 278, 889, 611, 611, 611, 611, 389, 556, 333, 611, 556, 778, 556, 556, 500, 389, 280, 389, 584, 333, 556, 556, 556, 556, 280, 556, 333, 737, 370, 556, 584, 737, 552, 400, 549, 333, 333, 333, 576, 556, 278, 333, 333, 365, 556, 834, 834, 834, 611, 722, 722, 722, 722, 722, 722, 1000, 722, 667, 667, 667, 667, 278, 278, 278, 278, 722, 722, 778, 778, 778, 778, 778, 584, 778, 722, 722, 722, 722, 667, 667, 611, 556, 556, 556, 556, 556, 556, 889, 556, 556, 556, 556, 556, 278, 278, 278, 278, 611, 611, 611, 611, 611, 611, 611, 549, 611, 611, 611, 611, 611, 556, 611, 556, 722, 556, 722, 556, 722, 556, 722, 556, 722, 556, 722, 556, 722, 556, 722, 719, 722, 611, 667, 556, 667, 556, 667, 556, 667, 556, 667, 556, 778, 611, 778, 611, 778, 611, 778, 611, 722, 611, 722, 611, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 785, 556, 556, 278, 722, 556, 556, 611, 278, 611, 278, 611, 385, 611, 479, 611, 278, 722, 611, 722, 611, 722, 611, 708, 723, 611, 778, 611, 778, 611, 778, 611, 1000, 944, 722, 389, 722, 389, 722, 389, 667, 556, 667, 556, 667, 556, 667, 556, 611, 333, 611, 479, 611, 333, 722, 611, 722, 611, 722, 611, 722, 611, 722, 611, 722, 611, 944, 778, 667, 556, 667, 611, 500, 611, 500, 611, 500, 278, 556, 722, 556, 1000, 889, 778, 611, 667, 556, 611, 333, 333, 333, 333, 333, 333, 333, 333, 333, 333, 333, 465, 722, 333, 853, 906, 474, 825, 927, 838, 278, 722, 722, 601, 719, 667, 611, 722, 778, 278, 722, 667, 833, 722, 644, 778, 722, 667, 600, 611, 667, 821, 667, 809, 802, 278, 667, 615, 451, 611, 278, 582, 615, 610, 556, 606, 475, 460, 611, 541, 278, 558, 556, 612, 556, 445, 611, 766, 619, 520, 684, 446, 582, 715, 576, 753, 845, 278, 582, 611, 582, 845, 667, 669, 885, 567, 711, 667, 278, 276, 556, 1094, 1062, 875, 610, 722, 622, 719, 722, 719, 722, 567, 712, 667, 904, 626, 719, 719, 610, 702, 833, 722, 778, 719, 667, 722, 611, 622, 854, 667, 730, 703, 1005, 1019, 870, 979, 719, 711, 1031, 719, 556, 618, 615, 417, 635, 556, 709, 497, 615, 615, 500, 635, 740, 604, 611, 604, 611, 556, 490, 556, 875, 556, 615, 581, 833, 844, 729, 854, 615, 552, 854, 583, 556, 556, 611, 417, 552, 556, 278, 281, 278, 969, 906, 611, 500, 615, 556, 604, 778, 611, 487, 447, 944, 778, 944, 778, 944, 778, 667, 556, 333, 333, 556, 1000, 1000, 552, 278, 278, 278, 278, 500, 500, 500, 556, 556, 350, 1000, 1000, 240, 479, 333, 333, 604, 333, 167, 396, 556, 556, 1094, 556, 885, 489, 1115, 1000, 768, 600, 834, 834, 834, 834, 1000, 500, 1000, 500, 1000, 500, 500, 494, 612, 823, 713, 584, 549, 713, 979, 722, 274, 549, 549, 583, 549, 549, 604, 584, 604, 604, 708, 625, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 729, 604, 604, 354, 354, 1000, 990, 990, 990, 990, 494, 604, 604, 604, 604, 354, 1021, 1052, 917, 750, 750, 531, 656, 594, 510, 500, 750, 750, 611, 611, 333, 333, 333, 333, 333, 333, 333, 333, 222, 222, 333, 333, 333, 333, 333, 333, 333, 333];
const LiberationSansBoldMapping = [-1, -1, -1, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 402, 506, 507, 508, 509, 510, 511, 536, 537, 538, 539, 710, 711, 713, 728, 729, 730, 731, 732, 733, 900, 901, 902, 903, 904, 905, 906, 908, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, 1040, 1041, 1042, 1043, 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1081, 1082, 1083, 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, 1109, 1110, 1111, 1112, 1113, 1114, 1115, 1116, 1117, 1118, 1119, 1138, 1139, 1168, 1169, 7808, 7809, 7810, 7811, 7812, 7813, 7922, 7923, 8208, 8209, 8211, 8212, 8213, 8215, 8216, 8217, 8218, 8219, 8220, 8221, 8222, 8224, 8225, 8226, 8230, 8240, 8242, 8243, 8249, 8250, 8252, 8254, 8260, 8319, 8355, 8356, 8359, 8364, 8453, 8467, 8470, 8482, 8486, 8494, 8539, 8540, 8541, 8542, 8592, 8593, 8594, 8595, 8596, 8597, 8616, 8706, 8710, 8719, 8721, 8722, 8730, 8734, 8735, 8745, 8747, 8776, 8800, 8801, 8804, 8805, 8962, 8976, 8992, 8993, 9472, 9474, 9484, 9488, 9492, 9496, 9500, 9508, 9516, 9524, 9532, 9552, 9553, 9554, 9555, 9556, 9557, 9558, 9559, 9560, 9561, 9562, 9563, 9564, 9565, 9566, 9567, 9568, 9569, 9570, 9571, 9572, 9573, 9574, 9575, 9576, 9577, 9578, 9579, 9580, 9600, 9604, 9608, 9612, 9616, 9617, 9618, 9619, 9632, 9633, 9642, 9643, 9644, 9650, 9658, 9660, 9668, 9674, 9675, 9679, 9688, 9689, 9702, 9786, 9787, 9788, 9792, 9794, 9824, 9827, 9829, 9830, 9834, 9835, 9836, 61441, 61442, 61445, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1];
const LiberationSansBoldItalicWidths = [365, 0, 333, 278, 333, 474, 556, 556, 889, 722, 238, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 333, 333, 584, 584, 584, 611, 975, 722, 722, 722, 722, 667, 611, 778, 722, 278, 556, 722, 611, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 333, 278, 333, 584, 556, 333, 556, 611, 556, 611, 556, 333, 611, 611, 278, 278, 556, 278, 889, 611, 611, 611, 611, 389, 556, 333, 611, 556, 778, 556, 556, 500, 389, 280, 389, 584, 333, 556, 556, 556, 556, 280, 556, 333, 737, 370, 556, 584, 737, 552, 400, 549, 333, 333, 333, 576, 556, 278, 333, 333, 365, 556, 834, 834, 834, 611, 722, 722, 722, 722, 722, 722, 1000, 722, 667, 667, 667, 667, 278, 278, 278, 278, 722, 722, 778, 778, 778, 778, 778, 584, 778, 722, 722, 722, 722, 667, 667, 611, 556, 556, 556, 556, 556, 556, 889, 556, 556, 556, 556, 556, 278, 278, 278, 278, 611, 611, 611, 611, 611, 611, 611, 549, 611, 611, 611, 611, 611, 556, 611, 556, 722, 556, 722, 556, 722, 556, 722, 556, 722, 556, 722, 556, 722, 556, 722, 740, 722, 611, 667, 556, 667, 556, 667, 556, 667, 556, 667, 556, 778, 611, 778, 611, 778, 611, 778, 611, 722, 611, 722, 611, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 782, 556, 556, 278, 722, 556, 556, 611, 278, 611, 278, 611, 396, 611, 479, 611, 278, 722, 611, 722, 611, 722, 611, 708, 723, 611, 778, 611, 778, 611, 778, 611, 1000, 944, 722, 389, 722, 389, 722, 389, 667, 556, 667, 556, 667, 556, 667, 556, 611, 333, 611, 479, 611, 333, 722, 611, 722, 611, 722, 611, 722, 611, 722, 611, 722, 611, 944, 778, 667, 556, 667, 611, 500, 611, 500, 611, 500, 278, 556, 722, 556, 1000, 889, 778, 611, 667, 556, 611, 333, 333, 333, 333, 333, 333, 333, 333, 333, 333, 333, 333, 722, 333, 854, 906, 473, 844, 930, 847, 278, 722, 722, 610, 671, 667, 611, 722, 778, 278, 722, 667, 833, 722, 657, 778, 718, 667, 590, 611, 667, 822, 667, 829, 781, 278, 667, 620, 479, 611, 278, 591, 620, 621, 556, 610, 479, 492, 611, 558, 278, 566, 556, 603, 556, 450, 611, 712, 605, 532, 664, 409, 591, 704, 578, 773, 834, 278, 591, 611, 591, 834, 667, 667, 886, 614, 719, 667, 278, 278, 556, 1094, 1042, 854, 622, 719, 677, 719, 722, 708, 722, 614, 722, 667, 927, 643, 719, 719, 615, 687, 833, 722, 778, 719, 667, 722, 611, 677, 781, 667, 729, 708, 979, 989, 854, 1000, 708, 719, 1042, 729, 556, 619, 604, 534, 618, 556, 736, 510, 611, 611, 507, 622, 740, 604, 611, 611, 611, 556, 889, 556, 885, 556, 646, 583, 889, 935, 707, 854, 594, 552, 865, 589, 556, 556, 611, 469, 563, 556, 278, 278, 278, 969, 906, 611, 507, 619, 556, 611, 778, 611, 575, 467, 944, 778, 944, 778, 944, 778, 667, 556, 333, 333, 556, 1000, 1000, 552, 278, 278, 278, 278, 500, 500, 500, 556, 556, 350, 1000, 1000, 240, 479, 333, 333, 604, 333, 167, 396, 556, 556, 1104, 556, 885, 516, 1146, 1000, 768, 600, 834, 834, 834, 834, 999, 500, 1000, 500, 1000, 500, 500, 494, 612, 823, 713, 584, 549, 713, 979, 722, 274, 549, 549, 583, 549, 549, 604, 584, 604, 604, 708, 625, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 729, 604, 604, 354, 354, 1000, 990, 990, 990, 990, 494, 604, 604, 604, 604, 354, 1021, 1052, 917, 750, 750, 531, 656, 594, 510, 500, 750, 750, 611, 611, 333, 333, 333, 333, 333, 333, 333, 333, 222, 222, 333, 333, 333, 333, 333, 333, 333, 333];
const LiberationSansBoldItalicMapping = [-1, -1, -1, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 402, 506, 507, 508, 509, 510, 511, 536, 537, 538, 539, 710, 711, 713, 728, 729, 730, 731, 732, 733, 900, 901, 902, 903, 904, 905, 906, 908, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, 1040, 1041, 1042, 1043, 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1081, 1082, 1083, 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, 1109, 1110, 1111, 1112, 1113, 1114, 1115, 1116, 1117, 1118, 1119, 1138, 1139, 1168, 1169, 7808, 7809, 7810, 7811, 7812, 7813, 7922, 7923, 8208, 8209, 8211, 8212, 8213, 8215, 8216, 8217, 8218, 8219, 8220, 8221, 8222, 8224, 8225, 8226, 8230, 8240, 8242, 8243, 8249, 8250, 8252, 8254, 8260, 8319, 8355, 8356, 8359, 8364, 8453, 8467, 8470, 8482, 8486, 8494, 8539, 8540, 8541, 8542, 8592, 8593, 8594, 8595, 8596, 8597, 8616, 8706, 8710, 8719, 8721, 8722, 8730, 8734, 8735, 8745, 8747, 8776, 8800, 8801, 8804, 8805, 8962, 8976, 8992, 8993, 9472, 9474, 9484, 9488, 9492, 9496, 9500, 9508, 9516, 9524, 9532, 9552, 9553, 9554, 9555, 9556, 9557, 9558, 9559, 9560, 9561, 9562, 9563, 9564, 9565, 9566, 9567, 9568, 9569, 9570, 9571, 9572, 9573, 9574, 9575, 9576, 9577, 9578, 9579, 9580, 9600, 9604, 9608, 9612, 9616, 9617, 9618, 9619, 9632, 9633, 9642, 9643, 9644, 9650, 9658, 9660, 9668, 9674, 9675, 9679, 9688, 9689, 9702, 9786, 9787, 9788, 9792, 9794, 9824, 9827, 9829, 9830, 9834, 9835, 9836, 61441, 61442, 61445, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1];
const LiberationSansItalicWidths = [365, 0, 333, 278, 278, 355, 556, 556, 889, 667, 191, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 278, 278, 584, 584, 584, 556, 1015, 667, 667, 722, 722, 667, 611, 778, 722, 278, 500, 667, 556, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 278, 278, 278, 469, 556, 333, 556, 556, 500, 556, 556, 278, 556, 556, 222, 222, 500, 222, 833, 556, 556, 556, 556, 333, 500, 278, 556, 500, 722, 500, 500, 500, 334, 260, 334, 584, 333, 556, 556, 556, 556, 260, 556, 333, 737, 370, 556, 584, 737, 552, 400, 549, 333, 333, 333, 576, 537, 278, 333, 333, 365, 556, 834, 834, 834, 611, 667, 667, 667, 667, 667, 667, 1000, 722, 667, 667, 667, 667, 278, 278, 278, 278, 722, 722, 778, 778, 778, 778, 778, 584, 778, 722, 722, 722, 722, 667, 667, 611, 556, 556, 556, 556, 556, 556, 889, 500, 556, 556, 556, 556, 278, 278, 278, 278, 556, 556, 556, 556, 556, 556, 556, 549, 611, 556, 556, 556, 556, 500, 556, 500, 667, 556, 667, 556, 667, 556, 722, 500, 722, 500, 722, 500, 722, 500, 722, 625, 722, 556, 667, 556, 667, 556, 667, 556, 667, 556, 667, 556, 778, 556, 778, 556, 778, 556, 778, 556, 722, 556, 722, 556, 278, 278, 278, 278, 278, 278, 278, 222, 278, 278, 733, 444, 500, 222, 667, 500, 500, 556, 222, 556, 222, 556, 281, 556, 400, 556, 222, 722, 556, 722, 556, 722, 556, 615, 723, 556, 778, 556, 778, 556, 778, 556, 1000, 944, 722, 333, 722, 333, 722, 333, 667, 500, 667, 500, 667, 500, 667, 500, 611, 278, 611, 354, 611, 278, 722, 556, 722, 556, 722, 556, 722, 556, 722, 556, 722, 556, 944, 722, 667, 500, 667, 611, 500, 611, 500, 611, 500, 222, 556, 667, 556, 1000, 889, 778, 611, 667, 500, 611, 278, 333, 333, 333, 333, 333, 333, 333, 333, 333, 333, 333, 667, 278, 789, 846, 389, 794, 865, 775, 222, 667, 667, 570, 671, 667, 611, 722, 778, 278, 667, 667, 833, 722, 648, 778, 725, 667, 600, 611, 667, 837, 667, 831, 761, 278, 667, 570, 439, 555, 222, 550, 570, 571, 500, 556, 439, 463, 555, 542, 222, 500, 492, 548, 500, 447, 556, 670, 573, 486, 603, 374, 550, 652, 546, 728, 779, 222, 550, 556, 550, 779, 667, 667, 843, 544, 708, 667, 278, 278, 500, 1066, 982, 844, 589, 715, 639, 724, 667, 651, 667, 544, 704, 667, 917, 614, 715, 715, 589, 686, 833, 722, 778, 725, 667, 722, 611, 639, 795, 667, 727, 673, 920, 923, 805, 886, 651, 694, 1022, 682, 556, 562, 522, 493, 553, 556, 688, 465, 556, 556, 472, 564, 686, 550, 556, 556, 556, 500, 833, 500, 835, 500, 572, 518, 830, 851, 621, 736, 526, 492, 752, 534, 556, 556, 556, 378, 496, 500, 222, 222, 222, 910, 828, 556, 472, 565, 500, 556, 778, 556, 492, 339, 944, 722, 944, 722, 944, 722, 667, 500, 333, 333, 556, 1000, 1000, 552, 222, 222, 222, 222, 333, 333, 333, 556, 556, 350, 1000, 1000, 188, 354, 333, 333, 500, 333, 167, 365, 556, 556, 1094, 556, 885, 323, 1083, 1000, 768, 600, 834, 834, 834, 834, 1000, 500, 998, 500, 1000, 500, 500, 494, 612, 823, 713, 584, 549, 713, 979, 719, 274, 549, 549, 584, 549, 549, 604, 584, 604, 604, 708, 625, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 729, 604, 604, 354, 354, 1000, 990, 990, 990, 990, 494, 604, 604, 604, 604, 354, 1021, 1052, 917, 750, 750, 531, 656, 594, 510, 500, 750, 750, 500, 500, 333, 333, 333, 333, 333, 333, 333, 333, 222, 222, 294, 294, 324, 324, 316, 328, 398, 285];
const LiberationSansItalicMapping = [-1, -1, -1, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 402, 506, 507, 508, 509, 510, 511, 536, 537, 538, 539, 710, 711, 713, 728, 729, 730, 731, 732, 733, 900, 901, 902, 903, 904, 905, 906, 908, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, 1040, 1041, 1042, 1043, 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1081, 1082, 1083, 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, 1109, 1110, 1111, 1112, 1113, 1114, 1115, 1116, 1117, 1118, 1119, 1138, 1139, 1168, 1169, 7808, 7809, 7810, 7811, 7812, 7813, 7922, 7923, 8208, 8209, 8211, 8212, 8213, 8215, 8216, 8217, 8218, 8219, 8220, 8221, 8222, 8224, 8225, 8226, 8230, 8240, 8242, 8243, 8249, 8250, 8252, 8254, 8260, 8319, 8355, 8356, 8359, 8364, 8453, 8467, 8470, 8482, 8486, 8494, 8539, 8540, 8541, 8542, 8592, 8593, 8594, 8595, 8596, 8597, 8616, 8706, 8710, 8719, 8721, 8722, 8730, 8734, 8735, 8745, 8747, 8776, 8800, 8801, 8804, 8805, 8962, 8976, 8992, 8993, 9472, 9474, 9484, 9488, 9492, 9496, 9500, 9508, 9516, 9524, 9532, 9552, 9553, 9554, 9555, 9556, 9557, 9558, 9559, 9560, 9561, 9562, 9563, 9564, 9565, 9566, 9567, 9568, 9569, 9570, 9571, 9572, 9573, 9574, 9575, 9576, 9577, 9578, 9579, 9580, 9600, 9604, 9608, 9612, 9616, 9617, 9618, 9619, 9632, 9633, 9642, 9643, 9644, 9650, 9658, 9660, 9668, 9674, 9675, 9679, 9688, 9689, 9702, 9786, 9787, 9788, 9792, 9794, 9824, 9827, 9829, 9830, 9834, 9835, 9836, 61441, 61442, 61445, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1];
const LiberationSansRegularWidths = [365, 0, 333, 278, 278, 355, 556, 556, 889, 667, 191, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 278, 278, 584, 584, 584, 556, 1015, 667, 667, 722, 722, 667, 611, 778, 722, 278, 500, 667, 556, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 278, 278, 278, 469, 556, 333, 556, 556, 500, 556, 556, 278, 556, 556, 222, 222, 500, 222, 833, 556, 556, 556, 556, 333, 500, 278, 556, 500, 722, 500, 500, 500, 334, 260, 334, 584, 333, 556, 556, 556, 556, 260, 556, 333, 737, 370, 556, 584, 737, 552, 400, 549, 333, 333, 333, 576, 537, 278, 333, 333, 365, 556, 834, 834, 834, 611, 667, 667, 667, 667, 667, 667, 1000, 722, 667, 667, 667, 667, 278, 278, 278, 278, 722, 722, 778, 778, 778, 778, 778, 584, 778, 722, 722, 722, 722, 667, 667, 611, 556, 556, 556, 556, 556, 556, 889, 500, 556, 556, 556, 556, 278, 278, 278, 278, 556, 556, 556, 556, 556, 556, 556, 549, 611, 556, 556, 556, 556, 500, 556, 500, 667, 556, 667, 556, 667, 556, 722, 500, 722, 500, 722, 500, 722, 500, 722, 615, 722, 556, 667, 556, 667, 556, 667, 556, 667, 556, 667, 556, 778, 556, 778, 556, 778, 556, 778, 556, 722, 556, 722, 556, 278, 278, 278, 278, 278, 278, 278, 222, 278, 278, 735, 444, 500, 222, 667, 500, 500, 556, 222, 556, 222, 556, 292, 556, 334, 556, 222, 722, 556, 722, 556, 722, 556, 604, 723, 556, 778, 556, 778, 556, 778, 556, 1000, 944, 722, 333, 722, 333, 722, 333, 667, 500, 667, 500, 667, 500, 667, 500, 611, 278, 611, 375, 611, 278, 722, 556, 722, 556, 722, 556, 722, 556, 722, 556, 722, 556, 944, 722, 667, 500, 667, 611, 500, 611, 500, 611, 500, 222, 556, 667, 556, 1000, 889, 778, 611, 667, 500, 611, 278, 333, 333, 333, 333, 333, 333, 333, 333, 333, 333, 333, 667, 278, 784, 838, 384, 774, 855, 752, 222, 667, 667, 551, 668, 667, 611, 722, 778, 278, 667, 668, 833, 722, 650, 778, 722, 667, 618, 611, 667, 798, 667, 835, 748, 278, 667, 578, 446, 556, 222, 547, 578, 575, 500, 557, 446, 441, 556, 556, 222, 500, 500, 576, 500, 448, 556, 690, 569, 482, 617, 395, 547, 648, 525, 713, 781, 222, 547, 556, 547, 781, 667, 667, 865, 542, 719, 667, 278, 278, 500, 1057, 1010, 854, 583, 722, 635, 719, 667, 656, 667, 542, 677, 667, 923, 604, 719, 719, 583, 656, 833, 722, 778, 719, 667, 722, 611, 635, 760, 667, 740, 667, 917, 938, 792, 885, 656, 719, 1010, 722, 556, 573, 531, 365, 583, 556, 669, 458, 559, 559, 438, 583, 688, 552, 556, 542, 556, 500, 458, 500, 823, 500, 573, 521, 802, 823, 625, 719, 521, 510, 750, 542, 556, 556, 556, 365, 510, 500, 222, 278, 222, 906, 812, 556, 438, 559, 500, 552, 778, 556, 489, 411, 944, 722, 944, 722, 944, 722, 667, 500, 333, 333, 556, 1000, 1000, 552, 222, 222, 222, 222, 333, 333, 333, 556, 556, 350, 1000, 1000, 188, 354, 333, 333, 500, 333, 167, 365, 556, 556, 1094, 556, 885, 323, 1073, 1000, 768, 600, 834, 834, 834, 834, 1000, 500, 1000, 500, 1000, 500, 500, 494, 612, 823, 713, 584, 549, 713, 979, 719, 274, 549, 549, 583, 549, 549, 604, 584, 604, 604, 708, 625, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 729, 604, 604, 354, 354, 1000, 990, 990, 990, 990, 494, 604, 604, 604, 604, 354, 1021, 1052, 917, 750, 750, 531, 656, 594, 510, 500, 750, 750, 500, 500, 333, 333, 333, 333, 333, 333, 333, 333, 222, 222, 294, 294, 324, 324, 316, 328, 398, 285];
const LiberationSansRegularMapping = [-1, -1, -1, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 402, 506, 507, 508, 509, 510, 511, 536, 537, 538, 539, 710, 711, 713, 728, 729, 730, 731, 732, 733, 900, 901, 902, 903, 904, 905, 906, 908, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, 1040, 1041, 1042, 1043, 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1081, 1082, 1083, 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, 1109, 1110, 1111, 1112, 1113, 1114, 1115, 1116, 1117, 1118, 1119, 1138, 1139, 1168, 1169, 7808, 7809, 7810, 7811, 7812, 7813, 7922, 7923, 8208, 8209, 8211, 8212, 8213, 8215, 8216, 8217, 8218, 8219, 8220, 8221, 8222, 8224, 8225, 8226, 8230, 8240, 8242, 8243, 8249, 8250, 8252, 8254, 8260, 8319, 8355, 8356, 8359, 8364, 8453, 8467, 8470, 8482, 8486, 8494, 8539, 8540, 8541, 8542, 8592, 8593, 8594, 8595, 8596, 8597, 8616, 8706, 8710, 8719, 8721, 8722, 8730, 8734, 8735, 8745, 8747, 8776, 8800, 8801, 8804, 8805, 8962, 8976, 8992, 8993, 9472, 9474, 9484, 9488, 9492, 9496, 9500, 9508, 9516, 9524, 9532, 9552, 9553, 9554, 9555, 9556, 9557, 9558, 9559, 9560, 9561, 9562, 9563, 9564, 9565, 9566, 9567, 9568, 9569, 9570, 9571, 9572, 9573, 9574, 9575, 9576, 9577, 9578, 9579, 9580, 9600, 9604, 9608, 9612, 9616, 9617, 9618, 9619, 9632, 9633, 9642, 9643, 9644, 9650, 9658, 9660, 9668, 9674, 9675, 9679, 9688, 9689, 9702, 9786, 9787, 9788, 9792, 9794, 9824, 9827, 9829, 9830, 9834, 9835, 9836, 61441, 61442, 61445, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1];
;// ./src/core/myriadpro_factors.js
const MyriadProBoldFactors = [1.36898, 1, 1, 0.72706, 0.80479, 0.83734, 0.98894, 0.99793, 0.9897, 0.93884, 0.86209, 0.94292, 0.94292, 1.16661, 1.02058, 0.93582, 0.96694, 0.93582, 1.19137, 0.99793, 0.99793, 0.99793, 0.99793, 0.99793, 0.99793, 0.99793, 0.99793, 0.99793, 0.99793, 0.78076, 0.78076, 1.02058, 1.02058, 1.02058, 0.72851, 0.78966, 0.90838, 0.83637, 0.82391, 0.96376, 0.80061, 0.86275, 0.8768, 0.95407, 1.0258, 0.73901, 0.85022, 0.83655, 1.0156, 0.95546, 0.92179, 0.87107, 0.92179, 0.82114, 0.8096, 0.89713, 0.94438, 0.95353, 0.94083, 0.91905, 0.90406, 0.9446, 0.94292, 1.18777, 0.94292, 1.02058, 0.89903, 0.90088, 0.94938, 0.97898, 0.81093, 0.97571, 0.94938, 1.024, 0.9577, 0.95933, 0.98621, 1.0474, 0.97455, 0.98981, 0.9672, 0.95933, 0.9446, 0.97898, 0.97407, 0.97646, 0.78036, 1.10208, 0.95442, 0.95298, 0.97579, 0.9332, 0.94039, 0.938, 0.80687, 1.01149, 0.80687, 1.02058, 0.80479, 0.99793, 0.99793, 0.99793, 0.99793, 1.01149, 1.00872, 0.90088, 0.91882, 1.0213, 0.8361, 1.02058, 0.62295, 0.54324, 0.89022, 1.08595, 1, 1, 0.90088, 1, 0.97455, 0.93582, 0.90088, 1, 1.05686, 0.8361, 0.99642, 0.99642, 0.99642, 0.72851, 0.90838, 0.90838, 0.90838, 0.90838, 0.90838, 0.90838, 0.868, 0.82391, 0.80061, 0.80061, 0.80061, 0.80061, 1.0258, 1.0258, 1.0258, 1.0258, 0.97484, 0.95546, 0.92179, 0.92179, 0.92179, 0.92179, 0.92179, 1.02058, 0.92179, 0.94438, 0.94438, 0.94438, 0.94438, 0.90406, 0.86958, 0.98225, 0.94938, 0.94938, 0.94938, 0.94938, 0.94938, 0.94938, 0.9031, 0.81093, 0.94938, 0.94938, 0.94938, 0.94938, 0.98621, 0.98621, 0.98621, 0.98621, 0.93969, 0.95933, 0.9446, 0.9446, 0.9446, 0.9446, 0.9446, 1.08595, 0.9446, 0.95442, 0.95442, 0.95442, 0.95442, 0.94039, 0.97898, 0.94039, 0.90838, 0.94938, 0.90838, 0.94938, 0.90838, 0.94938, 0.82391, 0.81093, 0.82391, 0.81093, 0.82391, 0.81093, 0.82391, 0.81093, 0.96376, 0.84313, 0.97484, 0.97571, 0.80061, 0.94938, 0.80061, 0.94938, 0.80061, 0.94938, 0.80061, 0.94938, 0.80061, 0.94938, 0.8768, 0.9577, 0.8768, 0.9577, 0.8768, 0.9577, 1, 1, 0.95407, 0.95933, 0.97069, 0.95933, 1.0258, 0.98621, 1.0258, 0.98621, 1.0258, 0.98621, 1.0258, 0.98621, 1.0258, 0.98621, 0.887, 1.01591, 0.73901, 1.0474, 1, 1, 0.97455, 0.83655, 0.98981, 1, 1, 0.83655, 0.73977, 0.83655, 0.73903, 0.84638, 1.033, 0.95546, 0.95933, 1, 1, 0.95546, 0.95933, 0.8271, 0.95417, 0.95933, 0.92179, 0.9446, 0.92179, 0.9446, 0.92179, 0.9446, 0.936, 0.91964, 0.82114, 0.97646, 1, 1, 0.82114, 0.97646, 0.8096, 0.78036, 0.8096, 0.78036, 1, 1, 0.8096, 0.78036, 1, 1, 0.89713, 0.77452, 0.89713, 1.10208, 0.94438, 0.95442, 0.94438, 0.95442, 0.94438, 0.95442, 0.94438, 0.95442, 0.94438, 0.95442, 0.94438, 0.95442, 0.94083, 0.97579, 0.90406, 0.94039, 0.90406, 0.9446, 0.938, 0.9446, 0.938, 0.9446, 0.938, 1, 0.99793, 0.90838, 0.94938, 0.868, 0.9031, 0.92179, 0.9446, 1, 1, 0.89713, 1.10208, 0.90088, 0.90088, 0.90088, 0.90088, 0.90088, 0.90088, 0.90088, 0.90088, 0.90088, 0.90989, 0.9358, 0.91945, 0.83181, 0.75261, 0.87992, 0.82976, 0.96034, 0.83689, 0.97268, 1.0078, 0.90838, 0.83637, 0.8019, 0.90157, 0.80061, 0.9446, 0.95407, 0.92436, 1.0258, 0.85022, 0.97153, 1.0156, 0.95546, 0.89192, 0.92179, 0.92361, 0.87107, 0.96318, 0.89713, 0.93704, 0.95638, 0.91905, 0.91709, 0.92796, 1.0258, 0.93704, 0.94836, 1.0373, 0.95933, 1.0078, 0.95871, 0.94836, 0.96174, 0.92601, 0.9498, 0.98607, 0.95776, 0.95933, 1.05453, 1.0078, 0.98275, 0.9314, 0.95617, 0.91701, 1.05993, 0.9446, 0.78367, 0.9553, 1, 0.86832, 1.0128, 0.95871, 0.99394, 0.87548, 0.96361, 0.86774, 1.0078, 0.95871, 0.9446, 0.95871, 0.86774, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.94083, 0.97579, 0.94083, 0.97579, 0.94083, 0.97579, 0.90406, 0.94039, 0.96694, 1, 0.89903, 1, 1, 1, 0.93582, 0.93582, 0.93582, 1, 0.908, 0.908, 0.918, 0.94219, 0.94219, 0.96544, 1, 1.285, 1, 1, 0.81079, 0.81079, 1, 1, 0.74854, 1, 1, 1, 1, 0.99793, 1, 1, 1, 0.65, 1, 1.36145, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.17173, 1, 0.80535, 0.76169, 1.02058, 1.0732, 1.05486, 1, 1, 1.30692, 1.08595, 1.08595, 1, 1.08595, 1.08595, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.16161, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
const MyriadProBoldMetrics = {
lineHeight: 1.2,
lineGap: 0.2
};
const MyriadProBoldItalicFactors = [1.36898, 1, 1, 0.66227, 0.80779, 0.81625, 0.97276, 0.97276, 0.97733, 0.92222, 0.83266, 0.94292, 0.94292, 1.16148, 1.02058, 0.93582, 0.96694, 0.93582, 1.17337, 0.97276, 0.97276, 0.97276, 0.97276, 0.97276, 0.97276, 0.97276, 0.97276, 0.97276, 0.97276, 0.78076, 0.78076, 1.02058, 1.02058, 1.02058, 0.71541, 0.76813, 0.85576, 0.80591, 0.80729, 0.94299, 0.77512, 0.83655, 0.86523, 0.92222, 0.98621, 0.71743, 0.81698, 0.79726, 0.98558, 0.92222, 0.90637, 0.83809, 0.90637, 0.80729, 0.76463, 0.86275, 0.90699, 0.91605, 0.9154, 0.85308, 0.85458, 0.90531, 0.94292, 1.21296, 0.94292, 1.02058, 0.89903, 1.18616, 0.99613, 0.91677, 0.78216, 0.91677, 0.90083, 0.98796, 0.9135, 0.92168, 0.95381, 0.98981, 0.95298, 0.95381, 0.93459, 0.92168, 0.91513, 0.92004, 0.91677, 0.95077, 0.748, 1.04502, 0.91677, 0.92061, 0.94236, 0.89544, 0.89364, 0.9, 0.80687, 0.8578, 0.80687, 1.02058, 0.80779, 0.97276, 0.97276, 0.97276, 0.97276, 0.8578, 0.99973, 1.18616, 0.91339, 1.08074, 0.82891, 1.02058, 0.55509, 0.71526, 0.89022, 1.08595, 1, 1, 1.18616, 1, 0.96736, 0.93582, 1.18616, 1, 1.04864, 0.82711, 0.99043, 0.99043, 0.99043, 0.71541, 0.85576, 0.85576, 0.85576, 0.85576, 0.85576, 0.85576, 0.845, 0.80729, 0.77512, 0.77512, 0.77512, 0.77512, 0.98621, 0.98621, 0.98621, 0.98621, 0.95961, 0.92222, 0.90637, 0.90637, 0.90637, 0.90637, 0.90637, 1.02058, 0.90251, 0.90699, 0.90699, 0.90699, 0.90699, 0.85458, 0.83659, 0.94951, 0.99613, 0.99613, 0.99613, 0.99613, 0.99613, 0.99613, 0.85811, 0.78216, 0.90083, 0.90083, 0.90083, 0.90083, 0.95381, 0.95381, 0.95381, 0.95381, 0.9135, 0.92168, 0.91513, 0.91513, 0.91513, 0.91513, 0.91513, 1.08595, 0.91677, 0.91677, 0.91677, 0.91677, 0.91677, 0.89364, 0.92332, 0.89364, 0.85576, 0.99613, 0.85576, 0.99613, 0.85576, 0.99613, 0.80729, 0.78216, 0.80729, 0.78216, 0.80729, 0.78216, 0.80729, 0.78216, 0.94299, 0.76783, 0.95961, 0.91677, 0.77512, 0.90083, 0.77512, 0.90083, 0.77512, 0.90083, 0.77512, 0.90083, 0.77512, 0.90083, 0.86523, 0.9135, 0.86523, 0.9135, 0.86523, 0.9135, 1, 1, 0.92222, 0.92168, 0.92222, 0.92168, 0.98621, 0.95381, 0.98621, 0.95381, 0.98621, 0.95381, 0.98621, 0.95381, 0.98621, 0.95381, 0.86036, 0.97096, 0.71743, 0.98981, 1, 1, 0.95298, 0.79726, 0.95381, 1, 1, 0.79726, 0.6894, 0.79726, 0.74321, 0.81691, 1.0006, 0.92222, 0.92168, 1, 1, 0.92222, 0.92168, 0.79464, 0.92098, 0.92168, 0.90637, 0.91513, 0.90637, 0.91513, 0.90637, 0.91513, 0.909, 0.87514, 0.80729, 0.95077, 1, 1, 0.80729, 0.95077, 0.76463, 0.748, 0.76463, 0.748, 1, 1, 0.76463, 0.748, 1, 1, 0.86275, 0.72651, 0.86275, 1.04502, 0.90699, 0.91677, 0.90699, 0.91677, 0.90699, 0.91677, 0.90699, 0.91677, 0.90699, 0.91677, 0.90699, 0.91677, 0.9154, 0.94236, 0.85458, 0.89364, 0.85458, 0.90531, 0.9, 0.90531, 0.9, 0.90531, 0.9, 1, 0.97276, 0.85576, 0.99613, 0.845, 0.85811, 0.90251, 0.91677, 1, 1, 0.86275, 1.04502, 1.18616, 1.18616, 1.18616, 1.18616, 1.18616, 1.18616, 1.18616, 1.18616, 1.18616, 1.00899, 1.30628, 0.85576, 0.80178, 0.66862, 0.7927, 0.69323, 0.88127, 0.72459, 0.89711, 0.95381, 0.85576, 0.80591, 0.7805, 0.94729, 0.77512, 0.90531, 0.92222, 0.90637, 0.98621, 0.81698, 0.92655, 0.98558, 0.92222, 0.85359, 0.90637, 0.90976, 0.83809, 0.94523, 0.86275, 0.83509, 0.93157, 0.85308, 0.83392, 0.92346, 0.98621, 0.83509, 0.92886, 0.91324, 0.92168, 0.95381, 0.90646, 0.92886, 0.90557, 0.86847, 0.90276, 0.91324, 0.86842, 0.92168, 0.99531, 0.95381, 0.9224, 0.85408, 0.92699, 0.86847, 1.0051, 0.91513, 0.80487, 0.93481, 1, 0.88159, 1.05214, 0.90646, 0.97355, 0.81539, 0.89398, 0.85923, 0.95381, 0.90646, 0.91513, 0.90646, 0.85923, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.9154, 0.94236, 0.9154, 0.94236, 0.9154, 0.94236, 0.85458, 0.89364, 0.96694, 1, 0.89903, 1, 1, 1, 0.91782, 0.91782, 0.91782, 1, 0.896, 0.896, 0.896, 0.9332, 0.9332, 0.95973, 1, 1.26, 1, 1, 0.80479, 0.80178, 1, 1, 0.85633, 1, 1, 1, 1, 0.97276, 1, 1, 1, 0.698, 1, 1.36145, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.14542, 1, 0.79199, 0.78694, 1.02058, 1.03493, 1.05486, 1, 1, 1.23026, 1.08595, 1.08595, 1, 1.08595, 1.08595, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.20006, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
const MyriadProBoldItalicMetrics = {
lineHeight: 1.2,
lineGap: 0.2
};
const MyriadProItalicFactors = [1.36898, 1, 1, 0.65507, 0.84943, 0.85639, 0.88465, 0.88465, 0.86936, 0.88307, 0.86948, 0.85283, 0.85283, 1.06383, 1.02058, 0.75945, 0.9219, 0.75945, 1.17337, 0.88465, 0.88465, 0.88465, 0.88465, 0.88465, 0.88465, 0.88465, 0.88465, 0.88465, 0.88465, 0.75945, 0.75945, 1.02058, 1.02058, 1.02058, 0.69046, 0.70926, 0.85158, 0.77812, 0.76852, 0.89591, 0.70466, 0.76125, 0.80094, 0.86822, 0.83864, 0.728, 0.77212, 0.79475, 0.93637, 0.87514, 0.8588, 0.76013, 0.8588, 0.72421, 0.69866, 0.77598, 0.85991, 0.80811, 0.87832, 0.78112, 0.77512, 0.8562, 1.0222, 1.18417, 1.0222, 1.27014, 0.89903, 1.15012, 0.93859, 0.94399, 0.846, 0.94399, 0.81453, 1.0186, 0.94219, 0.96017, 1.03075, 1.02175, 0.912, 1.03075, 0.96998, 0.96017, 0.93859, 0.94399, 0.94399, 0.95493, 0.746, 1.12658, 0.94578, 0.91, 0.979, 0.882, 0.882, 0.83, 0.85034, 0.83537, 0.85034, 1.02058, 0.70869, 0.88465, 0.88465, 0.88465, 0.88465, 0.83537, 0.90083, 1.15012, 0.9161, 0.94565, 0.73541, 1.02058, 0.53609, 0.69353, 0.79519, 1.08595, 1, 1, 1.15012, 1, 0.91974, 0.75945, 1.15012, 1, 0.9446, 0.73361, 0.9005, 0.9005, 0.9005, 0.62864, 0.85158, 0.85158, 0.85158, 0.85158, 0.85158, 0.85158, 0.773, 0.76852, 0.70466, 0.70466, 0.70466, 0.70466, 0.83864, 0.83864, 0.83864, 0.83864, 0.90561, 0.87514, 0.8588, 0.8588, 0.8588, 0.8588, 0.8588, 1.02058, 0.85751, 0.85991, 0.85991, 0.85991, 0.85991, 0.77512, 0.76013, 0.88075, 0.93859, 0.93859, 0.93859, 0.93859, 0.93859, 0.93859, 0.8075, 0.846, 0.81453, 0.81453, 0.81453, 0.81453, 0.82424, 0.82424, 0.82424, 0.82424, 0.9278, 0.96017, 0.93859, 0.93859, 0.93859, 0.93859, 0.93859, 1.08595, 0.8562, 0.94578, 0.94578, 0.94578, 0.94578, 0.882, 0.94578, 0.882, 0.85158, 0.93859, 0.85158, 0.93859, 0.85158, 0.93859, 0.76852, 0.846, 0.76852, 0.846, 0.76852, 0.846, 0.76852, 0.846, 0.89591, 0.8544, 0.90561, 0.94399, 0.70466, 0.81453, 0.70466, 0.81453, 0.70466, 0.81453, 0.70466, 0.81453, 0.70466, 0.81453, 0.80094, 0.94219, 0.80094, 0.94219, 0.80094, 0.94219, 1, 1, 0.86822, 0.96017, 0.86822, 0.96017, 0.83864, 0.82424, 0.83864, 0.82424, 0.83864, 0.82424, 0.83864, 1.03075, 0.83864, 0.82424, 0.81402, 1.02738, 0.728, 1.02175, 1, 1, 0.912, 0.79475, 1.03075, 1, 1, 0.79475, 0.83911, 0.79475, 0.66266, 0.80553, 1.06676, 0.87514, 0.96017, 1, 1, 0.87514, 0.96017, 0.86865, 0.87396, 0.96017, 0.8588, 0.93859, 0.8588, 0.93859, 0.8588, 0.93859, 0.867, 0.84759, 0.72421, 0.95493, 1, 1, 0.72421, 0.95493, 0.69866, 0.746, 0.69866, 0.746, 1, 1, 0.69866, 0.746, 1, 1, 0.77598, 0.88417, 0.77598, 1.12658, 0.85991, 0.94578, 0.85991, 0.94578, 0.85991, 0.94578, 0.85991, 0.94578, 0.85991, 0.94578, 0.85991, 0.94578, 0.87832, 0.979, 0.77512, 0.882, 0.77512, 0.8562, 0.83, 0.8562, 0.83, 0.8562, 0.83, 1, 0.88465, 0.85158, 0.93859, 0.773, 0.8075, 0.85751, 0.8562, 1, 1, 0.77598, 1.12658, 1.15012, 1.15012, 1.15012, 1.15012, 1.15012, 1.15313, 1.15012, 1.15012, 1.15012, 1.08106, 1.03901, 0.85158, 0.77025, 0.62264, 0.7646, 0.65351, 0.86026, 0.69461, 0.89947, 1.03075, 0.85158, 0.77812, 0.76449, 0.88836, 0.70466, 0.8562, 0.86822, 0.8588, 0.83864, 0.77212, 0.85308, 0.93637, 0.87514, 0.82352, 0.8588, 0.85701, 0.76013, 0.89058, 0.77598, 0.8156, 0.82565, 0.78112, 0.77899, 0.89386, 0.83864, 0.8156, 0.9486, 0.92388, 0.96186, 1.03075, 0.91123, 0.9486, 0.93298, 0.878, 0.93942, 0.92388, 0.84596, 0.96186, 0.95119, 1.03075, 0.922, 0.88787, 0.95829, 0.88, 0.93559, 0.93859, 0.78815, 0.93758, 1, 0.89217, 1.03737, 0.91123, 0.93969, 0.77487, 0.85769, 0.86799, 1.03075, 0.91123, 0.93859, 0.91123, 0.86799, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.87832, 0.979, 0.87832, 0.979, 0.87832, 0.979, 0.77512, 0.882, 0.9219, 1, 0.89903, 1, 1, 1, 0.87321, 0.87321, 0.87321, 1, 1.027, 1.027, 1.027, 0.86847, 0.86847, 0.79121, 1, 1.124, 1, 1, 0.73572, 0.73572, 1, 1, 0.85034, 1, 1, 1, 1, 0.88465, 1, 1, 1, 0.669, 1, 1.36145, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.04828, 1, 0.74948, 0.75187, 1.02058, 0.98391, 1.02119, 1, 1, 1.06233, 1.08595, 1.08595, 1, 1.08595, 1.08595, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.05233, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
const MyriadProItalicMetrics = {
lineHeight: 1.2,
lineGap: 0.2
};
const MyriadProRegularFactors = [1.36898, 1, 1, 0.76305, 0.82784, 0.94935, 0.89364, 0.92241, 0.89073, 0.90706, 0.98472, 0.85283, 0.85283, 1.0664, 1.02058, 0.74505, 0.9219, 0.74505, 1.23456, 0.92241, 0.92241, 0.92241, 0.92241, 0.92241, 0.92241, 0.92241, 0.92241, 0.92241, 0.92241, 0.74505, 0.74505, 1.02058, 1.02058, 1.02058, 0.73002, 0.72601, 0.91755, 0.8126, 0.80314, 0.92222, 0.73764, 0.79726, 0.83051, 0.90284, 0.86023, 0.74, 0.8126, 0.84869, 0.96518, 0.91115, 0.8858, 0.79761, 0.8858, 0.74498, 0.73914, 0.81363, 0.89591, 0.83659, 0.89633, 0.85608, 0.8111, 0.90531, 1.0222, 1.22736, 1.0222, 1.27014, 0.89903, 0.90088, 0.86667, 1.0231, 0.896, 1.01411, 0.90083, 1.05099, 1.00512, 0.99793, 1.05326, 1.09377, 0.938, 1.06226, 1.00119, 0.99793, 0.98714, 1.0231, 1.01231, 0.98196, 0.792, 1.19137, 0.99074, 0.962, 1.01915, 0.926, 0.942, 0.856, 0.85034, 0.92006, 0.85034, 1.02058, 0.69067, 0.92241, 0.92241, 0.92241, 0.92241, 0.92006, 0.9332, 0.90088, 0.91882, 0.93484, 0.75339, 1.02058, 0.56866, 0.54324, 0.79519, 1.08595, 1, 1, 0.90088, 1, 0.95325, 0.74505, 0.90088, 1, 0.97198, 0.75339, 0.91009, 0.91009, 0.91009, 0.66466, 0.91755, 0.91755, 0.91755, 0.91755, 0.91755, 0.91755, 0.788, 0.80314, 0.73764, 0.73764, 0.73764, 0.73764, 0.86023, 0.86023, 0.86023, 0.86023, 0.92915, 0.91115, 0.8858, 0.8858, 0.8858, 0.8858, 0.8858, 1.02058, 0.8858, 0.89591, 0.89591, 0.89591, 0.89591, 0.8111, 0.79611, 0.89713, 0.86667, 0.86667, 0.86667, 0.86667, 0.86667, 0.86667, 0.86936, 0.896, 0.90083, 0.90083, 0.90083, 0.90083, 0.84224, 0.84224, 0.84224, 0.84224, 0.97276, 0.99793, 0.98714, 0.98714, 0.98714, 0.98714, 0.98714, 1.08595, 0.89876, 0.99074, 0.99074, 0.99074, 0.99074, 0.942, 1.0231, 0.942, 0.91755, 0.86667, 0.91755, 0.86667, 0.91755, 0.86667, 0.80314, 0.896, 0.80314, 0.896, 0.80314, 0.896, 0.80314, 0.896, 0.92222, 0.93372, 0.92915, 1.01411, 0.73764, 0.90083, 0.73764, 0.90083, 0.73764, 0.90083, 0.73764, 0.90083, 0.73764, 0.90083, 0.83051, 1.00512, 0.83051, 1.00512, 0.83051, 1.00512, 1, 1, 0.90284, 0.99793, 0.90976, 0.99793, 0.86023, 0.84224, 0.86023, 0.84224, 0.86023, 0.84224, 0.86023, 1.05326, 0.86023, 0.84224, 0.82873, 1.07469, 0.74, 1.09377, 1, 1, 0.938, 0.84869, 1.06226, 1, 1, 0.84869, 0.83704, 0.84869, 0.81441, 0.85588, 1.08927, 0.91115, 0.99793, 1, 1, 0.91115, 0.99793, 0.91887, 0.90991, 0.99793, 0.8858, 0.98714, 0.8858, 0.98714, 0.8858, 0.98714, 0.894, 0.91434, 0.74498, 0.98196, 1, 1, 0.74498, 0.98196, 0.73914, 0.792, 0.73914, 0.792, 1, 1, 0.73914, 0.792, 1, 1, 0.81363, 0.904, 0.81363, 1.19137, 0.89591, 0.99074, 0.89591, 0.99074, 0.89591, 0.99074, 0.89591, 0.99074, 0.89591, 0.99074, 0.89591, 0.99074, 0.89633, 1.01915, 0.8111, 0.942, 0.8111, 0.90531, 0.856, 0.90531, 0.856, 0.90531, 0.856, 1, 0.92241, 0.91755, 0.86667, 0.788, 0.86936, 0.8858, 0.89876, 1, 1, 0.81363, 1.19137, 0.90088, 0.90088, 0.90088, 0.90088, 0.90088, 0.90088, 0.90088, 0.90088, 0.90088, 0.90388, 1.03901, 0.92138, 0.78105, 0.7154, 0.86169, 0.80513, 0.94007, 0.82528, 0.98612, 1.06226, 0.91755, 0.8126, 0.81884, 0.92819, 0.73764, 0.90531, 0.90284, 0.8858, 0.86023, 0.8126, 0.91172, 0.96518, 0.91115, 0.83089, 0.8858, 0.87791, 0.79761, 0.89297, 0.81363, 0.88157, 0.89992, 0.85608, 0.81992, 0.94307, 0.86023, 0.88157, 0.95308, 0.98699, 0.99793, 1.06226, 0.95817, 0.95308, 0.97358, 0.928, 0.98088, 0.98699, 0.92761, 0.99793, 0.96017, 1.06226, 0.986, 0.944, 0.95978, 0.938, 0.96705, 0.98714, 0.80442, 0.98972, 1, 0.89762, 1.04552, 0.95817, 0.99007, 0.87064, 0.91879, 0.88888, 1.06226, 0.95817, 0.98714, 0.95817, 0.88888, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.89633, 1.01915, 0.89633, 1.01915, 0.89633, 1.01915, 0.8111, 0.942, 0.9219, 1, 0.89903, 1, 1, 1, 0.93173, 0.93173, 0.93173, 1, 1.06304, 1.06304, 1.06904, 0.89903, 0.89903, 0.80549, 1, 1.156, 1, 1, 0.76575, 0.76575, 1, 1, 0.72458, 1, 1, 1, 1, 0.92241, 1, 1, 1, 0.619, 1, 1.36145, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.07257, 1, 0.74705, 0.71119, 1.02058, 1.024, 1.02119, 1, 1, 1.1536, 1.08595, 1.08595, 1, 1.08595, 1.08595, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.05638, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
const MyriadProRegularMetrics = {
lineHeight: 1.2,
lineGap: 0.2
};
;// ./src/core/segoeui_factors.js
const SegoeuiBoldFactors = [1.76738, 1, 1, 0.99297, 0.9824, 1.04016, 1.06497, 1.03424, 0.97529, 1.17647, 1.23203, 1.1085, 1.1085, 1.16939, 1.2107, 0.9754, 1.21408, 0.9754, 1.59578, 1.03424, 1.03424, 1.03424, 1.03424, 1.03424, 1.03424, 1.03424, 1.03424, 1.03424, 1.03424, 0.81378, 0.81378, 1.2107, 1.2107, 1.2107, 0.71703, 0.97847, 0.97363, 0.88776, 0.8641, 1.02096, 0.79795, 0.85132, 0.914, 1.06085, 1.1406, 0.8007, 0.89858, 0.83693, 1.14889, 1.09398, 0.97489, 0.92094, 0.97489, 0.90399, 0.84041, 0.95923, 1.00135, 1, 1.06467, 0.98243, 0.90996, 0.99361, 1.1085, 1.56942, 1.1085, 1.2107, 0.74627, 0.94282, 0.96752, 1.01519, 0.86304, 1.01359, 0.97278, 1.15103, 1.01359, 0.98561, 1.02285, 1.02285, 1.00527, 1.02285, 1.0302, 0.99041, 1.0008, 1.01519, 1.01359, 1.02258, 0.79104, 1.16862, 0.99041, 0.97454, 1.02511, 0.99298, 0.96752, 0.95801, 0.94856, 1.16579, 0.94856, 1.2107, 0.9824, 1.03424, 1.03424, 1, 1.03424, 1.16579, 0.8727, 1.3871, 1.18622, 1.10818, 1.04478, 1.2107, 1.18622, 0.75155, 0.94994, 1.28826, 1.21408, 1.21408, 0.91056, 1, 0.91572, 0.9754, 0.64663, 1.18328, 1.24866, 1.04478, 1.14169, 1.15749, 1.17389, 0.71703, 0.97363, 0.97363, 0.97363, 0.97363, 0.97363, 0.97363, 0.93506, 0.8641, 0.79795, 0.79795, 0.79795, 0.79795, 1.1406, 1.1406, 1.1406, 1.1406, 1.02096, 1.09398, 0.97426, 0.97426, 0.97426, 0.97426, 0.97426, 1.2107, 0.97489, 1.00135, 1.00135, 1.00135, 1.00135, 0.90996, 0.92094, 1.02798, 0.96752, 0.96752, 0.96752, 0.96752, 0.96752, 0.96752, 0.93136, 0.86304, 0.97278, 0.97278, 0.97278, 0.97278, 1.02285, 1.02285, 1.02285, 1.02285, 0.97122, 0.99041, 1, 1, 1, 1, 1, 1.28826, 1.0008, 0.99041, 0.99041, 0.99041, 0.99041, 0.96752, 1.01519, 0.96752, 0.97363, 0.96752, 0.97363, 0.96752, 0.97363, 0.96752, 0.8641, 0.86304, 0.8641, 0.86304, 0.8641, 0.86304, 0.8641, 0.86304, 1.02096, 1.03057, 1.02096, 1.03517, 0.79795, 0.97278, 0.79795, 0.97278, 0.79795, 0.97278, 0.79795, 0.97278, 0.79795, 0.97278, 0.914, 1.01359, 0.914, 1.01359, 0.914, 1.01359, 1, 1, 1.06085, 0.98561, 1.06085, 1.00879, 1.1406, 1.02285, 1.1406, 1.02285, 1.1406, 1.02285, 1.1406, 1.02285, 1.1406, 1.02285, 0.97138, 1.08692, 0.8007, 1.02285, 1, 1, 1.00527, 0.83693, 1.02285, 1, 1, 0.83693, 0.9455, 0.83693, 0.90418, 0.83693, 1.13005, 1.09398, 0.99041, 1, 1, 1.09398, 0.99041, 0.96692, 1.09251, 0.99041, 0.97489, 1.0008, 0.97489, 1.0008, 0.97489, 1.0008, 0.93994, 0.97931, 0.90399, 1.02258, 1, 1, 0.90399, 1.02258, 0.84041, 0.79104, 0.84041, 0.79104, 0.84041, 0.79104, 0.84041, 0.79104, 1, 1, 0.95923, 1.07034, 0.95923, 1.16862, 1.00135, 0.99041, 1.00135, 0.99041, 1.00135, 0.99041, 1.00135, 0.99041, 1.00135, 0.99041, 1.00135, 0.99041, 1.06467, 1.02511, 0.90996, 0.96752, 0.90996, 0.99361, 0.95801, 0.99361, 0.95801, 0.99361, 0.95801, 1.07733, 1.03424, 0.97363, 0.96752, 0.93506, 0.93136, 0.97489, 1.0008, 1, 1, 0.95923, 1.16862, 1.15103, 1.15103, 1.01173, 1.03959, 0.75953, 0.81378, 0.79912, 1.15103, 1.21994, 0.95161, 0.87815, 1.01149, 0.81525, 0.7676, 0.98167, 1.01134, 1.02546, 0.84097, 1.03089, 1.18102, 0.97363, 0.88776, 0.85134, 0.97826, 0.79795, 0.99361, 1.06085, 0.97489, 1.1406, 0.89858, 1.0388, 1.14889, 1.09398, 0.86039, 0.97489, 1.0595, 0.92094, 0.94793, 0.95923, 0.90996, 0.99346, 0.98243, 1.02112, 0.95493, 1.1406, 0.90996, 1.03574, 1.02597, 1.0008, 1.18102, 1.06628, 1.03574, 1.0192, 1.01932, 1.00886, 0.97531, 1.0106, 1.0008, 1.13189, 1.18102, 1.02277, 0.98683, 1.0016, 0.99561, 1.07237, 1.0008, 0.90434, 0.99921, 0.93803, 0.8965, 1.23085, 1.06628, 1.04983, 0.96268, 1.0499, 0.98439, 1.18102, 1.06628, 1.0008, 1.06628, 0.98439, 0.79795, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.09466, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.97278, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.02065, 1, 1, 1, 1, 1, 1, 1.06467, 1.02511, 1.06467, 1.02511, 1.06467, 1.02511, 0.90996, 0.96752, 1, 1.21408, 0.89903, 1, 1, 0.75155, 1.04394, 1.04394, 1.04394, 1.04394, 0.98633, 0.98633, 0.98633, 0.73047, 0.73047, 1.20642, 0.91211, 1.25635, 1.222, 1.02956, 1.03372, 1.03372, 0.96039, 1.24633, 1, 1.12454, 0.93503, 1.03424, 1.19687, 1.03424, 1, 1, 1, 0.771, 1, 1, 1.15749, 1.15749, 1.15749, 1.10948, 0.86279, 0.94434, 0.86279, 0.94434, 0.86182, 1, 1, 1.16897, 1, 0.96085, 0.90137, 1.2107, 1.18416, 1.13973, 0.69825, 0.9716, 2.10339, 1.29004, 1.29004, 1.21172, 1.29004, 1.29004, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.42603, 1, 0.99862, 0.99862, 1, 0.87025, 0.87025, 0.87025, 0.87025, 1.18874, 1.42603, 1, 1.42603, 1.42603, 0.99862, 1, 1, 1, 1, 1, 1.2886, 1.04315, 1.15296, 1.34163, 1, 1, 1, 1.09193, 1.09193, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
const SegoeuiBoldMetrics = {
lineHeight: 1.33008,
lineGap: 0
};
const SegoeuiBoldItalicFactors = [1.76738, 1, 1, 0.98946, 1.03959, 1.04016, 1.02809, 1.036, 0.97639, 1.10953, 1.23203, 1.11144, 1.11144, 1.16939, 1.21237, 0.9754, 1.21261, 0.9754, 1.59754, 1.036, 1.036, 1.036, 1.036, 1.036, 1.036, 1.036, 1.036, 1.036, 1.036, 0.81378, 0.81378, 1.21237, 1.21237, 1.21237, 0.73541, 0.97847, 0.97363, 0.89723, 0.87897, 1.0426, 0.79429, 0.85292, 0.91149, 1.05815, 1.1406, 0.79631, 0.90128, 0.83853, 1.04396, 1.10615, 0.97552, 0.94436, 0.97552, 0.88641, 0.80527, 0.96083, 1.00135, 1, 1.06777, 0.9817, 0.91142, 0.99361, 1.11144, 1.57293, 1.11144, 1.21237, 0.74627, 1.31818, 1.06585, 0.97042, 0.83055, 0.97042, 0.93503, 1.1261, 0.97042, 0.97922, 1.14236, 0.94552, 1.01054, 1.14236, 1.02471, 0.97922, 0.94165, 0.97042, 0.97042, 1.0276, 0.78929, 1.1261, 0.97922, 0.95874, 1.02197, 0.98507, 0.96752, 0.97168, 0.95107, 1.16579, 0.95107, 1.21237, 1.03959, 1.036, 1.036, 1, 1.036, 1.16579, 0.87357, 1.31818, 1.18754, 1.26781, 1.05356, 1.21237, 1.18622, 0.79487, 0.94994, 1.29004, 1.24047, 1.24047, 1.31818, 1, 0.91484, 0.9754, 1.31818, 1.1349, 1.24866, 1.05356, 1.13934, 1.15574, 1.17389, 0.73541, 0.97363, 0.97363, 0.97363, 0.97363, 0.97363, 0.97363, 0.94385, 0.87897, 0.79429, 0.79429, 0.79429, 0.79429, 1.1406, 1.1406, 1.1406, 1.1406, 1.0426, 1.10615, 0.97552, 0.97552, 0.97552, 0.97552, 0.97552, 1.21237, 0.97552, 1.00135, 1.00135, 1.00135, 1.00135, 0.91142, 0.94436, 0.98721, 1.06585, 1.06585, 1.06585, 1.06585, 1.06585, 1.06585, 0.96705, 0.83055, 0.93503, 0.93503, 0.93503, 0.93503, 1.14236, 1.14236, 1.14236, 1.14236, 0.93125, 0.97922, 0.94165, 0.94165, 0.94165, 0.94165, 0.94165, 1.29004, 0.94165, 0.97922, 0.97922, 0.97922, 0.97922, 0.96752, 0.97042, 0.96752, 0.97363, 1.06585, 0.97363, 1.06585, 0.97363, 1.06585, 0.87897, 0.83055, 0.87897, 0.83055, 0.87897, 0.83055, 0.87897, 0.83055, 1.0426, 1.0033, 1.0426, 0.97042, 0.79429, 0.93503, 0.79429, 0.93503, 0.79429, 0.93503, 0.79429, 0.93503, 0.79429, 0.93503, 0.91149, 0.97042, 0.91149, 0.97042, 0.91149, 0.97042, 1, 1, 1.05815, 0.97922, 1.05815, 0.97922, 1.1406, 1.14236, 1.1406, 1.14236, 1.1406, 1.14236, 1.1406, 1.14236, 1.1406, 1.14236, 0.97441, 1.04302, 0.79631, 1.01582, 1, 1, 1.01054, 0.83853, 1.14236, 1, 1, 0.83853, 1.09125, 0.83853, 0.90418, 0.83853, 1.19508, 1.10615, 0.97922, 1, 1, 1.10615, 0.97922, 1.01034, 1.10466, 0.97922, 0.97552, 0.94165, 0.97552, 0.94165, 0.97552, 0.94165, 0.91602, 0.91981, 0.88641, 1.0276, 1, 1, 0.88641, 1.0276, 0.80527, 0.78929, 0.80527, 0.78929, 0.80527, 0.78929, 0.80527, 0.78929, 1, 1, 0.96083, 1.05403, 0.95923, 1.16862, 1.00135, 0.97922, 1.00135, 0.97922, 1.00135, 0.97922, 1.00135, 0.97922, 1.00135, 0.97922, 1.00135, 0.97922, 1.06777, 1.02197, 0.91142, 0.96752, 0.91142, 0.99361, 0.97168, 0.99361, 0.97168, 0.99361, 0.97168, 1.23199, 1.036, 0.97363, 1.06585, 0.94385, 0.96705, 0.97552, 0.94165, 1, 1, 0.96083, 1.1261, 1.31818, 1.31818, 1.31818, 1.31818, 1.31818, 1.31818, 1.31818, 1.31818, 1.31818, 0.95161, 1.27126, 1.00811, 0.83284, 0.77702, 0.99137, 0.95253, 1.0347, 0.86142, 1.07205, 1.14236, 0.97363, 0.89723, 0.86869, 1.09818, 0.79429, 0.99361, 1.05815, 0.97552, 1.1406, 0.90128, 1.06662, 1.04396, 1.10615, 0.84918, 0.97552, 1.04694, 0.94436, 0.98015, 0.96083, 0.91142, 1.00356, 0.9817, 1.01945, 0.98999, 1.1406, 0.91142, 1.04961, 0.9898, 1.00639, 1.14236, 1.07514, 1.04961, 0.99607, 1.02897, 1.008, 0.9898, 0.95134, 1.00639, 1.11121, 1.14236, 1.00518, 0.97981, 1.02186, 1, 1.08578, 0.94165, 0.99314, 0.98387, 0.93028, 0.93377, 1.35125, 1.07514, 1.10687, 0.93491, 1.04232, 1.00351, 1.14236, 1.07514, 0.94165, 1.07514, 1.00351, 0.79429, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.09097, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.93503, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.96609, 1, 1, 1, 1, 1, 1, 1.06777, 1.02197, 1.06777, 1.02197, 1.06777, 1.02197, 0.91142, 0.96752, 1, 1.21261, 0.89903, 1, 1, 0.75155, 1.04745, 1.04745, 1.04745, 1.04394, 0.98633, 0.98633, 0.98633, 0.72959, 0.72959, 1.20502, 0.91406, 1.26514, 1.222, 1.02956, 1.03372, 1.03372, 0.96039, 1.24633, 1, 1.09125, 0.93327, 1.03336, 1.16541, 1.036, 1, 1, 1, 0.771, 1, 1, 1.15574, 1.15574, 1.15574, 1.15574, 0.86364, 0.94434, 0.86279, 0.94434, 0.86224, 1, 1, 1.16798, 1, 0.96085, 0.90068, 1.21237, 1.18416, 1.13904, 0.69825, 0.9716, 2.10339, 1.29004, 1.29004, 1.21339, 1.29004, 1.29004, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.42603, 1, 0.99862, 0.99862, 1, 0.87025, 0.87025, 0.87025, 0.87025, 1.18775, 1.42603, 1, 1.42603, 1.42603, 0.99862, 1, 1, 1, 1, 1, 1.2886, 1.04315, 1.15296, 1.34163, 1, 1, 1, 1.13269, 1.13269, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
const SegoeuiBoldItalicMetrics = {
lineHeight: 1.33008,
lineGap: 0
};
const SegoeuiItalicFactors = [1.76738, 1, 1, 0.98946, 1.14763, 1.05365, 1.06234, 0.96927, 0.92586, 1.15373, 1.18414, 0.91349, 0.91349, 1.07403, 1.17308, 0.78383, 1.20088, 0.78383, 1.42531, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.78383, 0.78383, 1.17308, 1.17308, 1.17308, 0.77349, 0.94565, 0.94729, 0.85944, 0.88506, 0.9858, 0.74817, 0.80016, 0.88449, 0.98039, 0.95782, 0.69238, 0.89898, 0.83231, 0.98183, 1.03989, 0.96924, 0.86237, 0.96924, 0.80595, 0.74524, 0.86091, 0.95402, 0.94143, 0.98448, 0.8858, 0.83089, 0.93285, 1.0949, 1.39016, 1.0949, 1.45994, 0.74627, 1.04839, 0.97454, 0.97454, 0.87207, 0.97454, 0.87533, 1.06151, 0.97454, 1.00176, 1.16484, 1.08132, 0.98047, 1.16484, 1.02989, 1.01054, 0.96225, 0.97454, 0.97454, 1.06598, 0.79004, 1.16344, 1.00351, 0.94629, 0.9973, 0.91016, 0.96777, 0.9043, 0.91082, 0.92481, 0.91082, 1.17308, 0.95748, 0.96927, 0.96927, 1, 0.96927, 0.92481, 0.80597, 1.04839, 1.23393, 1.1781, 0.9245, 1.17308, 1.20808, 0.63218, 0.94261, 1.24822, 1.09971, 1.09971, 1.04839, 1, 0.85273, 0.78032, 1.04839, 1.09971, 1.22326, 0.9245, 1.09836, 1.13525, 1.15222, 0.70424, 0.94729, 0.94729, 0.94729, 0.94729, 0.94729, 0.94729, 0.85498, 0.88506, 0.74817, 0.74817, 0.74817, 0.74817, 0.95782, 0.95782, 0.95782, 0.95782, 0.9858, 1.03989, 0.96924, 0.96924, 0.96924, 0.96924, 0.96924, 1.17308, 0.96924, 0.95402, 0.95402, 0.95402, 0.95402, 0.83089, 0.86237, 0.88409, 0.97454, 0.97454, 0.97454, 0.97454, 0.97454, 0.97454, 0.92916, 0.87207, 0.87533, 0.87533, 0.87533, 0.87533, 0.93146, 0.93146, 0.93146, 0.93146, 0.93854, 1.01054, 0.96225, 0.96225, 0.96225, 0.96225, 0.96225, 1.24822, 0.8761, 1.00351, 1.00351, 1.00351, 1.00351, 0.96777, 0.97454, 0.96777, 0.94729, 0.97454, 0.94729, 0.97454, 0.94729, 0.97454, 0.88506, 0.87207, 0.88506, 0.87207, 0.88506, 0.87207, 0.88506, 0.87207, 0.9858, 0.95391, 0.9858, 0.97454, 0.74817, 0.87533, 0.74817, 0.87533, 0.74817, 0.87533, 0.74817, 0.87533, 0.74817, 0.87533, 0.88449, 0.97454, 0.88449, 0.97454, 0.88449, 0.97454, 1, 1, 0.98039, 1.00176, 0.98039, 1.00176, 0.95782, 0.93146, 0.95782, 0.93146, 0.95782, 0.93146, 0.95782, 1.16484, 0.95782, 0.93146, 0.84421, 1.12761, 0.69238, 1.08132, 1, 1, 0.98047, 0.83231, 1.16484, 1, 1, 0.84723, 1.04861, 0.84723, 0.78755, 0.83231, 1.23736, 1.03989, 1.01054, 1, 1, 1.03989, 1.01054, 0.9857, 1.03849, 1.01054, 0.96924, 0.96225, 0.96924, 0.96225, 0.96924, 0.96225, 0.92383, 0.90171, 0.80595, 1.06598, 1, 1, 0.80595, 1.06598, 0.74524, 0.79004, 0.74524, 0.79004, 0.74524, 0.79004, 0.74524, 0.79004, 1, 1, 0.86091, 1.02759, 0.85771, 1.16344, 0.95402, 1.00351, 0.95402, 1.00351, 0.95402, 1.00351, 0.95402, 1.00351, 0.95402, 1.00351, 0.95402, 1.00351, 0.98448, 0.9973, 0.83089, 0.96777, 0.83089, 0.93285, 0.9043, 0.93285, 0.9043, 0.93285, 0.9043, 1.31868, 0.96927, 0.94729, 0.97454, 0.85498, 0.92916, 0.96924, 0.8761, 1, 1, 0.86091, 1.16344, 1.04839, 1.04839, 1.04839, 1.04839, 1.04839, 1.04839, 1.04839, 1.04839, 1.04839, 0.81965, 0.81965, 0.94729, 0.78032, 0.71022, 0.90883, 0.84171, 0.99877, 0.77596, 1.05734, 1.2, 0.94729, 0.85944, 0.82791, 0.9607, 0.74817, 0.93285, 0.98039, 0.96924, 0.95782, 0.89898, 0.98316, 0.98183, 1.03989, 0.78614, 0.96924, 0.97642, 0.86237, 0.86075, 0.86091, 0.83089, 0.90082, 0.8858, 0.97296, 1.01284, 0.95782, 0.83089, 1.0976, 1.04, 1.03342, 1.2, 1.0675, 1.0976, 0.98205, 1.03809, 1.05097, 1.04, 0.95364, 1.03342, 1.05401, 1.2, 1.02148, 1.0119, 1.04724, 1.0127, 1.02732, 0.96225, 0.8965, 0.97783, 0.93574, 0.94818, 1.30679, 1.0675, 1.11826, 0.99821, 1.0557, 1.0326, 1.2, 1.0675, 0.96225, 1.0675, 1.0326, 0.74817, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.03754, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.87533, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.98705, 1, 1, 1, 1, 1, 1, 0.98448, 0.9973, 0.98448, 0.9973, 0.98448, 0.9973, 0.83089, 0.96777, 1, 1.20088, 0.89903, 1, 1, 0.75155, 0.94945, 0.94945, 0.94945, 0.94945, 1.12317, 1.12317, 1.12317, 0.67603, 0.67603, 1.15621, 0.73584, 1.21191, 1.22135, 1.06483, 0.94868, 0.94868, 0.95996, 1.24633, 1, 1.07497, 0.87709, 0.96927, 1.01473, 0.96927, 1, 1, 1, 0.77295, 1, 1, 1.09836, 1.09836, 1.09836, 1.01522, 0.86321, 0.94434, 0.8649, 0.94434, 0.86182, 1, 1, 1.083, 1, 0.91578, 0.86438, 1.17308, 1.18416, 1.14589, 0.69825, 0.97622, 1.96791, 1.24822, 1.24822, 1.17308, 1.24822, 1.24822, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.42603, 1, 0.99862, 0.99862, 1, 0.87025, 0.87025, 0.87025, 0.87025, 1.17984, 1.42603, 1, 1.42603, 1.42603, 0.99862, 1, 1, 1, 1, 1, 1.2886, 1.04315, 1.15296, 1.34163, 1, 1, 1, 1.10742, 1.10742, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
const SegoeuiItalicMetrics = {
lineHeight: 1.33008,
lineGap: 0
};
const SegoeuiRegularFactors = [1.76738, 1, 1, 0.98594, 1.02285, 1.10454, 1.06234, 0.96927, 0.92037, 1.19985, 1.2046, 0.90616, 0.90616, 1.07152, 1.1714, 0.78032, 1.20088, 0.78032, 1.40246, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.78032, 0.78032, 1.1714, 1.1714, 1.1714, 0.80597, 0.94084, 0.96706, 0.85944, 0.85734, 0.97093, 0.75842, 0.79936, 0.88198, 0.9831, 0.95782, 0.71387, 0.86969, 0.84636, 1.07796, 1.03584, 0.96924, 0.83968, 0.96924, 0.82826, 0.79649, 0.85771, 0.95132, 0.93119, 0.98965, 0.88433, 0.8287, 0.93365, 1.08612, 1.3638, 1.08612, 1.45786, 0.74627, 0.80499, 0.91484, 1.05707, 0.92383, 1.05882, 0.9403, 1.12654, 1.05882, 1.01756, 1.09011, 1.09011, 0.99414, 1.09011, 1.034, 1.01756, 1.05356, 1.05707, 1.05882, 1.04399, 0.84863, 1.21968, 1.01756, 0.95801, 1.00068, 0.91797, 0.96777, 0.9043, 0.90351, 0.92105, 0.90351, 1.1714, 0.85337, 0.96927, 0.96927, 0.99912, 0.96927, 0.92105, 0.80597, 1.2434, 1.20808, 1.05937, 0.90957, 1.1714, 1.20808, 0.75155, 0.94261, 1.24644, 1.09971, 1.09971, 0.84751, 1, 0.85273, 0.78032, 0.61584, 1.05425, 1.17914, 0.90957, 1.08665, 1.11593, 1.14169, 0.73381, 0.96706, 0.96706, 0.96706, 0.96706, 0.96706, 0.96706, 0.86035, 0.85734, 0.75842, 0.75842, 0.75842, 0.75842, 0.95782, 0.95782, 0.95782, 0.95782, 0.97093, 1.03584, 0.96924, 0.96924, 0.96924, 0.96924, 0.96924, 1.1714, 0.96924, 0.95132, 0.95132, 0.95132, 0.95132, 0.8287, 0.83968, 0.89049, 0.91484, 0.91484, 0.91484, 0.91484, 0.91484, 0.91484, 0.93575, 0.92383, 0.9403, 0.9403, 0.9403, 0.9403, 0.8717, 0.8717, 0.8717, 0.8717, 1.00527, 1.01756, 1.05356, 1.05356, 1.05356, 1.05356, 1.05356, 1.24644, 0.95923, 1.01756, 1.01756, 1.01756, 1.01756, 0.96777, 1.05707, 0.96777, 0.96706, 0.91484, 0.96706, 0.91484, 0.96706, 0.91484, 0.85734, 0.92383, 0.85734, 0.92383, 0.85734, 0.92383, 0.85734, 0.92383, 0.97093, 1.0969, 0.97093, 1.05882, 0.75842, 0.9403, 0.75842, 0.9403, 0.75842, 0.9403, 0.75842, 0.9403, 0.75842, 0.9403, 0.88198, 1.05882, 0.88198, 1.05882, 0.88198, 1.05882, 1, 1, 0.9831, 1.01756, 0.9831, 1.01756, 0.95782, 0.8717, 0.95782, 0.8717, 0.95782, 0.8717, 0.95782, 1.09011, 0.95782, 0.8717, 0.84784, 1.11551, 0.71387, 1.09011, 1, 1, 0.99414, 0.84636, 1.09011, 1, 1, 0.84636, 1.0536, 0.84636, 0.94298, 0.84636, 1.23297, 1.03584, 1.01756, 1, 1, 1.03584, 1.01756, 1.00323, 1.03444, 1.01756, 0.96924, 1.05356, 0.96924, 1.05356, 0.96924, 1.05356, 0.93066, 0.98293, 0.82826, 1.04399, 1, 1, 0.82826, 1.04399, 0.79649, 0.84863, 0.79649, 0.84863, 0.79649, 0.84863, 0.79649, 0.84863, 1, 1, 0.85771, 1.17318, 0.85771, 1.21968, 0.95132, 1.01756, 0.95132, 1.01756, 0.95132, 1.01756, 0.95132, 1.01756, 0.95132, 1.01756, 0.95132, 1.01756, 0.98965, 1.00068, 0.8287, 0.96777, 0.8287, 0.93365, 0.9043, 0.93365, 0.9043, 0.93365, 0.9043, 1.08571, 0.96927, 0.96706, 0.91484, 0.86035, 0.93575, 0.96924, 0.95923, 1, 1, 0.85771, 1.21968, 1.11437, 1.11437, 0.93109, 0.91202, 0.60411, 0.84164, 0.55572, 1.01173, 0.97361, 0.81818, 0.81818, 0.96635, 0.78032, 0.72727, 0.92366, 0.98601, 1.03405, 0.77968, 1.09799, 1.2, 0.96706, 0.85944, 0.85638, 0.96491, 0.75842, 0.93365, 0.9831, 0.96924, 0.95782, 0.86969, 0.94152, 1.07796, 1.03584, 0.78437, 0.96924, 0.98715, 0.83968, 0.83491, 0.85771, 0.8287, 0.94492, 0.88433, 0.9287, 1.0098, 0.95782, 0.8287, 1.0625, 0.98248, 1.03424, 1.2, 1.01071, 1.0625, 0.95246, 1.03809, 1.04912, 0.98248, 1.00221, 1.03424, 1.05443, 1.2, 1.04785, 0.99609, 1.00169, 1.05176, 0.99346, 1.05356, 0.9087, 1.03004, 0.95542, 0.93117, 1.23362, 1.01071, 1.07831, 1.02512, 1.05205, 1.03502, 1.2, 1.01071, 1.05356, 1.01071, 1.03502, 0.75842, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.03719, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.9403, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.04021, 1, 1, 1, 1, 1, 1, 0.98965, 1.00068, 0.98965, 1.00068, 0.98965, 1.00068, 0.8287, 0.96777, 1, 1.20088, 0.89903, 1, 1, 0.75155, 1.03077, 1.03077, 1.03077, 1.03077, 1.13196, 1.13196, 1.13196, 0.67428, 0.67428, 1.16039, 0.73291, 1.20996, 1.22135, 1.06483, 0.94868, 0.94868, 0.95996, 1.24633, 1, 1.07497, 0.87796, 0.96927, 1.01518, 0.96927, 1, 1, 1, 0.77295, 1, 1, 1.10539, 1.10539, 1.11358, 1.06967, 0.86279, 0.94434, 0.86279, 0.94434, 0.86182, 1, 1, 1.083, 1, 0.91578, 0.86507, 1.1714, 1.18416, 1.14589, 0.69825, 0.97622, 1.9697, 1.24822, 1.24822, 1.17238, 1.24822, 1.24822, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.42603, 1, 0.99862, 0.99862, 1, 0.87025, 0.87025, 0.87025, 0.87025, 1.18083, 1.42603, 1, 1.42603, 1.42603, 0.99862, 1, 1, 1, 1, 1, 1.2886, 1.04315, 1.15296, 1.34163, 1, 1, 1, 1.10938, 1.10938, 1, 1, 1, 1.05425, 1.09971, 1.09971, 1.09971, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
const SegoeuiRegularMetrics = {
lineHeight: 1.33008,
lineGap: 0
};
;// ./src/core/xfa_fonts.js
const getXFAFontMap = getLookupTableFactory(function (t) {
t["MyriadPro-Regular"] = t["PdfJS-Fallback-Regular"] = {
name: "LiberationSans-Regular",
factors: MyriadProRegularFactors,
baseWidths: LiberationSansRegularWidths,
baseMapping: LiberationSansRegularMapping,
metrics: MyriadProRegularMetrics
};
t["MyriadPro-Bold"] = t["PdfJS-Fallback-Bold"] = {
name: "LiberationSans-Bold",
factors: MyriadProBoldFactors,
baseWidths: LiberationSansBoldWidths,
baseMapping: LiberationSansBoldMapping,
metrics: MyriadProBoldMetrics
};
t["MyriadPro-It"] = t["MyriadPro-Italic"] = t["PdfJS-Fallback-Italic"] = {
name: "LiberationSans-Italic",
factors: MyriadProItalicFactors,
baseWidths: LiberationSansItalicWidths,
baseMapping: LiberationSansItalicMapping,
metrics: MyriadProItalicMetrics
};
t["MyriadPro-BoldIt"] = t["MyriadPro-BoldItalic"] = t["PdfJS-Fallback-BoldItalic"] = {
name: "LiberationSans-BoldItalic",
factors: MyriadProBoldItalicFactors,
baseWidths: LiberationSansBoldItalicWidths,
baseMapping: LiberationSansBoldItalicMapping,
metrics: MyriadProBoldItalicMetrics
};
t.ArialMT = t.Arial = t["Arial-Regular"] = {
name: "LiberationSans-Regular",
baseWidths: LiberationSansRegularWidths,
baseMapping: LiberationSansRegularMapping
};
t["Arial-BoldMT"] = t["Arial-Bold"] = {
name: "LiberationSans-Bold",
baseWidths: LiberationSansBoldWidths,
baseMapping: LiberationSansBoldMapping
};
t["Arial-ItalicMT"] = t["Arial-Italic"] = {
name: "LiberationSans-Italic",
baseWidths: LiberationSansItalicWidths,
baseMapping: LiberationSansItalicMapping
};
t["Arial-BoldItalicMT"] = t["Arial-BoldItalic"] = {
name: "LiberationSans-BoldItalic",
baseWidths: LiberationSansBoldItalicWidths,
baseMapping: LiberationSansBoldItalicMapping
};
t["Calibri-Regular"] = {
name: "LiberationSans-Regular",
factors: CalibriRegularFactors,
baseWidths: LiberationSansRegularWidths,
baseMapping: LiberationSansRegularMapping,
metrics: CalibriRegularMetrics
};
t["Calibri-Bold"] = {
name: "LiberationSans-Bold",
factors: CalibriBoldFactors,
baseWidths: LiberationSansBoldWidths,
baseMapping: LiberationSansBoldMapping,
metrics: CalibriBoldMetrics
};
t["Calibri-Italic"] = {
name: "LiberationSans-Italic",
factors: CalibriItalicFactors,
baseWidths: LiberationSansItalicWidths,
baseMapping: LiberationSansItalicMapping,
metrics: CalibriItalicMetrics
};
t["Calibri-BoldItalic"] = {
name: "LiberationSans-BoldItalic",
factors: CalibriBoldItalicFactors,
baseWidths: LiberationSansBoldItalicWidths,
baseMapping: LiberationSansBoldItalicMapping,
metrics: CalibriBoldItalicMetrics
};
t["Segoeui-Regular"] = {
name: "LiberationSans-Regular",
factors: SegoeuiRegularFactors,
baseWidths: LiberationSansRegularWidths,
baseMapping: LiberationSansRegularMapping,
metrics: SegoeuiRegularMetrics
};
t["Segoeui-Bold"] = {
name: "LiberationSans-Bold",
factors: SegoeuiBoldFactors,
baseWidths: LiberationSansBoldWidths,
baseMapping: LiberationSansBoldMapping,
metrics: SegoeuiBoldMetrics
};
t["Segoeui-Italic"] = {
name: "LiberationSans-Italic",
factors: SegoeuiItalicFactors,
baseWidths: LiberationSansItalicWidths,
baseMapping: LiberationSansItalicMapping,
metrics: SegoeuiItalicMetrics
};
t["Segoeui-BoldItalic"] = {
name: "LiberationSans-BoldItalic",
factors: SegoeuiBoldItalicFactors,
baseWidths: LiberationSansBoldItalicWidths,
baseMapping: LiberationSansBoldItalicMapping,
metrics: SegoeuiBoldItalicMetrics
};
t["Helvetica-Regular"] = t.Helvetica = {
name: "LiberationSans-Regular",
factors: HelveticaRegularFactors,
baseWidths: LiberationSansRegularWidths,
baseMapping: LiberationSansRegularMapping,
metrics: HelveticaRegularMetrics
};
t["Helvetica-Bold"] = {
name: "LiberationSans-Bold",
factors: HelveticaBoldFactors,
baseWidths: LiberationSansBoldWidths,
baseMapping: LiberationSansBoldMapping,
metrics: HelveticaBoldMetrics
};
t["Helvetica-Italic"] = {
name: "LiberationSans-Italic",
factors: HelveticaItalicFactors,
baseWidths: LiberationSansItalicWidths,
baseMapping: LiberationSansItalicMapping,
metrics: HelveticaItalicMetrics
};
t["Helvetica-BoldItalic"] = {
name: "LiberationSans-BoldItalic",
factors: HelveticaBoldItalicFactors,
baseWidths: LiberationSansBoldItalicWidths,
baseMapping: LiberationSansBoldItalicMapping,
metrics: HelveticaBoldItalicMetrics
};
});
function getXfaFontName(name) {
const fontName = normalizeFontName(name);
const fontMap = getXFAFontMap();
return fontMap[fontName];
}
function getXfaFontWidths(name) {
const info = getXfaFontName(name);
if (!info) {
return null;
}
const {
baseWidths,
baseMapping,
factors
} = info;
const rescaledBaseWidths = !factors ? baseWidths : baseWidths.map((w, i) => w * factors[i]);
let currentCode = -2;
let currentArray;
const newWidths = [];
for (const [unicode, glyphIndex] of baseMapping.map((charUnicode, index) => [charUnicode, index]).sort(([unicode1], [unicode2]) => unicode1 - unicode2)) {
if (unicode === -1) {
continue;
}
if (unicode === currentCode + 1) {
currentArray.push(rescaledBaseWidths[glyphIndex]);
currentCode += 1;
} else {
currentCode = unicode;
currentArray = [rescaledBaseWidths[glyphIndex]];
newWidths.push(unicode, currentArray);
}
}
return newWidths;
}
function getXfaFontDict(name) {
const widths = getXfaFontWidths(name);
const dict = new Dict(null);
dict.set("BaseFont", Name.get(name));
dict.set("Type", Name.get("Font"));
dict.set("Subtype", Name.get("CIDFontType2"));
dict.set("Encoding", Name.get("Identity-H"));
dict.set("CIDToGIDMap", Name.get("Identity"));
dict.set("W", widths);
dict.set("FirstChar", widths[0]);
dict.set("LastChar", widths.at(-2) + widths.at(-1).length - 1);
const descriptor = new Dict(null);
dict.set("FontDescriptor", descriptor);
const systemInfo = new Dict(null);
systemInfo.set("Ordering", "Identity");
systemInfo.set("Registry", "Adobe");
systemInfo.set("Supplement", 0);
dict.set("CIDSystemInfo", systemInfo);
return dict;
}
;// ./src/core/ps_parser.js
class PostScriptParser {
constructor(lexer) {
this.lexer = lexer;
this.operators = [];
this.token = null;
this.prev = null;
}
nextToken() {
this.prev = this.token;
this.token = this.lexer.getToken();
}
accept(type) {
if (this.token.type === type) {
this.nextToken();
return true;
}
return false;
}
expect(type) {
if (this.accept(type)) {
return true;
}
throw new FormatError(`Unexpected symbol: found ${this.token.type} expected ${type}.`);
}
parse() {
this.nextToken();
this.expect(PostScriptTokenTypes.LBRACE);
this.parseBlock();
this.expect(PostScriptTokenTypes.RBRACE);
return this.operators;
}
parseBlock() {
while (true) {
if (this.accept(PostScriptTokenTypes.NUMBER)) {
this.operators.push(this.prev.value);
} else if (this.accept(PostScriptTokenTypes.OPERATOR)) {
this.operators.push(this.prev.value);
} else if (this.accept(PostScriptTokenTypes.LBRACE)) {
this.parseCondition();
} else {
return;
}
}
}
parseCondition() {
const conditionLocation = this.operators.length;
this.operators.push(null, null);
this.parseBlock();
this.expect(PostScriptTokenTypes.RBRACE);
if (this.accept(PostScriptTokenTypes.IF)) {
this.operators[conditionLocation] = this.operators.length;
this.operators[conditionLocation + 1] = "jz";
} else if (this.accept(PostScriptTokenTypes.LBRACE)) {
const jumpLocation = this.operators.length;
this.operators.push(null, null);
const endOfTrue = this.operators.length;
this.parseBlock();
this.expect(PostScriptTokenTypes.RBRACE);
this.expect(PostScriptTokenTypes.IFELSE);
this.operators[jumpLocation] = this.operators.length;
this.operators[jumpLocation + 1] = "j";
this.operators[conditionLocation] = endOfTrue;
this.operators[conditionLocation + 1] = "jz";
} else {
throw new FormatError("PS Function: error parsing conditional.");
}
}
}
const PostScriptTokenTypes = {
LBRACE: 0,
RBRACE: 1,
NUMBER: 2,
OPERATOR: 3,
IF: 4,
IFELSE: 5
};
class PostScriptToken {
static get opCache() {
return shadow(this, "opCache", Object.create(null));
}
constructor(type, value) {
this.type = type;
this.value = value;
}
static getOperator(op) {
return PostScriptToken.opCache[op] ||= new PostScriptToken(PostScriptTokenTypes.OPERATOR, op);
}
static get LBRACE() {
return shadow(this, "LBRACE", new PostScriptToken(PostScriptTokenTypes.LBRACE, "{"));
}
static get RBRACE() {
return shadow(this, "RBRACE", new PostScriptToken(PostScriptTokenTypes.RBRACE, "}"));
}
static get IF() {
return shadow(this, "IF", new PostScriptToken(PostScriptTokenTypes.IF, "IF"));
}
static get IFELSE() {
return shadow(this, "IFELSE", new PostScriptToken(PostScriptTokenTypes.IFELSE, "IFELSE"));
}
}
class PostScriptLexer {
constructor(stream) {
this.stream = stream;
this.nextChar();
this.strBuf = [];
}
nextChar() {
return this.currentChar = this.stream.getByte();
}
getToken() {
let comment = false;
let ch = this.currentChar;
while (true) {
if (ch < 0) {
return EOF;
}
if (comment) {
if (ch === 0x0a || ch === 0x0d) {
comment = false;
}
} else if (ch === 0x25) {
comment = true;
} else if (!isWhiteSpace(ch)) {
break;
}
ch = this.nextChar();
}
switch (ch | 0) {
case 0x30:
case 0x31:
case 0x32:
case 0x33:
case 0x34:
case 0x35:
case 0x36:
case 0x37:
case 0x38:
case 0x39:
case 0x2b:
case 0x2d:
case 0x2e:
return new PostScriptToken(PostScriptTokenTypes.NUMBER, this.getNumber());
case 0x7b:
this.nextChar();
return PostScriptToken.LBRACE;
case 0x7d:
this.nextChar();
return PostScriptToken.RBRACE;
}
const strBuf = this.strBuf;
strBuf.length = 0;
strBuf[0] = String.fromCharCode(ch);
while ((ch = this.nextChar()) >= 0 && (ch >= 0x41 && ch <= 0x5a || ch >= 0x61 && ch <= 0x7a)) {
strBuf.push(String.fromCharCode(ch));
}
const str = strBuf.join("");
switch (str.toLowerCase()) {
case "if":
return PostScriptToken.IF;
case "ifelse":
return PostScriptToken.IFELSE;
default:
return PostScriptToken.getOperator(str);
}
}
getNumber() {
let ch = this.currentChar;
const strBuf = this.strBuf;
strBuf.length = 0;
strBuf[0] = String.fromCharCode(ch);
while ((ch = this.nextChar()) >= 0) {
if (ch >= 0x30 && ch <= 0x39 || ch === 0x2d || ch === 0x2e) {
strBuf.push(String.fromCharCode(ch));
} else {
break;
}
}
const value = parseFloat(strBuf.join(""));
if (isNaN(value)) {
throw new FormatError(`Invalid floating point number: ${value}`);
}
return value;
}
}
;// ./src/core/image_utils.js
class BaseLocalCache {
constructor(options) {
this._onlyRefs = options?.onlyRefs === true;
if (!this._onlyRefs) {
this._nameRefMap = new Map();
this._imageMap = new Map();
}
this._imageCache = new RefSetCache();
}
getByName(name) {
if (this._onlyRefs) {
unreachable("Should not call `getByName` method.");
}
const ref = this._nameRefMap.get(name);
if (ref) {
return this.getByRef(ref);
}
return this._imageMap.get(name) || null;
}
getByRef(ref) {
return this._imageCache.get(ref) || null;
}
set(name, ref, data) {
unreachable("Abstract method `set` called.");
}
}
class LocalImageCache extends BaseLocalCache {
set(name, ref = null, data) {
if (typeof name !== "string") {
throw new Error('LocalImageCache.set - expected "name" argument.');
}
if (ref) {
if (this._imageCache.has(ref)) {
return;
}
this._nameRefMap.set(name, ref);
this._imageCache.put(ref, data);
return;
}
if (this._imageMap.has(name)) {
return;
}
this._imageMap.set(name, data);
}
}
class LocalColorSpaceCache extends BaseLocalCache {
set(name = null, ref = null, data) {
if (typeof name !== "string" && !ref) {
throw new Error('LocalColorSpaceCache.set - expected "name" and/or "ref" argument.');
}
if (ref) {
if (this._imageCache.has(ref)) {
return;
}
if (name !== null) {
this._nameRefMap.set(name, ref);
}
this._imageCache.put(ref, data);
return;
}
if (this._imageMap.has(name)) {
return;
}
this._imageMap.set(name, data);
}
}
class LocalFunctionCache extends BaseLocalCache {
constructor(options) {
super({
onlyRefs: true
});
}
set(name = null, ref, data) {
if (!ref) {
throw new Error('LocalFunctionCache.set - expected "ref" argument.');
}
if (this._imageCache.has(ref)) {
return;
}
this._imageCache.put(ref, data);
}
}
class LocalGStateCache extends BaseLocalCache {
set(name, ref = null, data) {
if (typeof name !== "string") {
throw new Error('LocalGStateCache.set - expected "name" argument.');
}
if (ref) {
if (this._imageCache.has(ref)) {
return;
}
this._nameRefMap.set(name, ref);
this._imageCache.put(ref, data);
return;
}
if (this._imageMap.has(name)) {
return;
}
this._imageMap.set(name, data);
}
}
class LocalTilingPatternCache extends BaseLocalCache {
constructor(options) {
super({
onlyRefs: true
});
}
set(name = null, ref, data) {
if (!ref) {
throw new Error('LocalTilingPatternCache.set - expected "ref" argument.');
}
if (this._imageCache.has(ref)) {
return;
}
this._imageCache.put(ref, data);
}
}
class RegionalImageCache extends BaseLocalCache {
constructor(options) {
super({
onlyRefs: true
});
}
set(name = null, ref, data) {
if (!ref) {
throw new Error('RegionalImageCache.set - expected "ref" argument.');
}
if (this._imageCache.has(ref)) {
return;
}
this._imageCache.put(ref, data);
}
}
class GlobalColorSpaceCache extends BaseLocalCache {
constructor(options) {
super({
onlyRefs: true
});
}
set(name = null, ref, data) {
if (!ref) {
throw new Error('GlobalColorSpaceCache.set - expected "ref" argument.');
}
if (this._imageCache.has(ref)) {
return;
}
this._imageCache.put(ref, data);
}
clear() {
this._imageCache.clear();
}
}
class GlobalImageCache {
static NUM_PAGES_THRESHOLD = 2;
static MIN_IMAGES_TO_CACHE = 10;
static MAX_BYTE_SIZE = 5e7;
#decodeFailedSet = new RefSet();
constructor() {
this._refCache = new RefSetCache();
this._imageCache = new RefSetCache();
}
get #byteSize() {
let byteSize = 0;
for (const imageData of this._imageCache) {
byteSize += imageData.byteSize;
}
return byteSize;
}
get #cacheLimitReached() {
if (this._imageCache.size < GlobalImageCache.MIN_IMAGES_TO_CACHE) {
return false;
}
if (this.#byteSize < GlobalImageCache.MAX_BYTE_SIZE) {
return false;
}
return true;
}
shouldCache(ref, pageIndex) {
let pageIndexSet = this._refCache.get(ref);
if (!pageIndexSet) {
pageIndexSet = new Set();
this._refCache.put(ref, pageIndexSet);
}
pageIndexSet.add(pageIndex);
if (pageIndexSet.size < GlobalImageCache.NUM_PAGES_THRESHOLD) {
return false;
}
if (!this._imageCache.has(ref) && this.#cacheLimitReached) {
return false;
}
return true;
}
addDecodeFailed(ref) {
this.#decodeFailedSet.put(ref);
}
hasDecodeFailed(ref) {
return this.#decodeFailedSet.has(ref);
}
addByteSize(ref, byteSize) {
const imageData = this._imageCache.get(ref);
if (!imageData) {
return;
}
if (imageData.byteSize) {
return;
}
imageData.byteSize = byteSize;
}
getData(ref, pageIndex) {
const pageIndexSet = this._refCache.get(ref);
if (!pageIndexSet) {
return null;
}
if (pageIndexSet.size < GlobalImageCache.NUM_PAGES_THRESHOLD) {
return null;
}
const imageData = this._imageCache.get(ref);
if (!imageData) {
return null;
}
pageIndexSet.add(pageIndex);
return imageData;
}
setData(ref, data) {
if (!this._refCache.has(ref)) {
throw new Error('GlobalImageCache.setData - expected "shouldCache" to have been called.');
}
if (this._imageCache.has(ref)) {
return;
}
if (this.#cacheLimitReached) {
warn("GlobalImageCache.setData - cache limit reached.");
return;
}
this._imageCache.put(ref, data);
}
clear(onlyData = false) {
if (!onlyData) {
this.#decodeFailedSet.clear();
this._refCache.clear();
}
this._imageCache.clear();
}
}
;// ./src/core/function.js
class PDFFunctionFactory {
constructor({
xref,
isEvalSupported = true
}) {
this.xref = xref;
this.isEvalSupported = isEvalSupported !== false;
}
create(fn, parseArray = false) {
let fnRef, parsedFn;
if (fn instanceof Ref) {
fnRef = fn;
} else if (fn instanceof Dict) {
fnRef = fn.objId;
} else if (fn instanceof BaseStream) {
fnRef = fn.dict?.objId;
}
if (fnRef) {
const cachedFn = this._localFunctionCache.getByRef(fnRef);
if (cachedFn) {
return cachedFn;
}
}
const fnObj = this.xref.fetchIfRef(fn);
if (Array.isArray(fnObj)) {
if (!parseArray) {
throw new Error('PDFFunctionFactory.create - expected "parseArray" argument.');
}
parsedFn = PDFFunction.parseArray(this, fnObj);
} else {
parsedFn = PDFFunction.parse(this, fnObj);
}
if (fnRef) {
this._localFunctionCache.set(null, fnRef, parsedFn);
}
return parsedFn;
}
get _localFunctionCache() {
return shadow(this, "_localFunctionCache", new LocalFunctionCache());
}
}
function toNumberArray(arr) {
if (!Array.isArray(arr)) {
return null;
}
if (!isNumberArray(arr, null)) {
return arr.map(x => +x);
}
return arr;
}
class PDFFunction {
static getSampleArray(size, outputSize, bps, stream) {
let i, ii;
let length = 1;
for (i = 0, ii = size.length; i < ii; i++) {
length *= size[i];
}
length *= outputSize;
const array = new Array(length);
let codeSize = 0;
let codeBuf = 0;
const sampleMul = 1.0 / (2.0 ** bps - 1);
const strBytes = stream.getBytes((length * bps + 7) / 8);
let strIdx = 0;
for (i = 0; i < length; i++) {
while (codeSize < bps) {
codeBuf <<= 8;
codeBuf |= strBytes[strIdx++];
codeSize += 8;
}
codeSize -= bps;
array[i] = (codeBuf >> codeSize) * sampleMul;
codeBuf &= (1 << codeSize) - 1;
}
return array;
}
static parse(factory, fn) {
const dict = fn.dict || fn;
const typeNum = dict.get("FunctionType");
switch (typeNum) {
case 0:
return this.constructSampled(factory, fn, dict);
case 1:
break;
case 2:
return this.constructInterpolated(factory, dict);
case 3:
return this.constructStiched(factory, dict);
case 4:
return this.constructPostScript(factory, fn, dict);
}
throw new FormatError("Unknown type of function");
}
static parseArray(factory, fnObj) {
const {
xref
} = factory;
const fnArray = [];
for (const fn of fnObj) {
fnArray.push(this.parse(factory, xref.fetchIfRef(fn)));
}
return function (src, srcOffset, dest, destOffset) {
for (let i = 0, ii = fnArray.length; i < ii; i++) {
fnArray[i](src, srcOffset, dest, destOffset + i);
}
};
}
static constructSampled(factory, fn, dict) {
function toMultiArray(arr) {
const inputLength = arr.length;
const out = [];
let index = 0;
for (let i = 0; i < inputLength; i += 2) {
out[index++] = [arr[i], arr[i + 1]];
}
return out;
}
function interpolate(x, xmin, xmax, ymin, ymax) {
return ymin + (x - xmin) * ((ymax - ymin) / (xmax - xmin));
}
let domain = toNumberArray(dict.getArray("Domain"));
let range = toNumberArray(dict.getArray("Range"));
if (!domain || !range) {
throw new FormatError("No domain or range");
}
const inputSize = domain.length / 2;
const outputSize = range.length / 2;
domain = toMultiArray(domain);
range = toMultiArray(range);
const size = toNumberArray(dict.getArray("Size"));
const bps = dict.get("BitsPerSample");
const order = dict.get("Order") || 1;
if (order !== 1) {
info("No support for cubic spline interpolation: " + order);
}
let encode = toNumberArray(dict.getArray("Encode"));
if (!encode) {
encode = [];
for (let i = 0; i < inputSize; ++i) {
encode.push([0, size[i] - 1]);
}
} else {
encode = toMultiArray(encode);
}
let decode = toNumberArray(dict.getArray("Decode"));
decode = !decode ? range : toMultiArray(decode);
const samples = this.getSampleArray(size, outputSize, bps, fn);
return function constructSampledFn(src, srcOffset, dest, destOffset) {
const cubeVertices = 1 << inputSize;
const cubeN = new Float64Array(cubeVertices).fill(1);
const cubeVertex = new Uint32Array(cubeVertices);
let i, j;
let k = outputSize,
pos = 1;
for (i = 0; i < inputSize; ++i) {
const domain_2i = domain[i][0];
const domain_2i_1 = domain[i][1];
const xi = MathClamp(src[srcOffset + i], domain_2i, domain_2i_1);
let e = interpolate(xi, domain_2i, domain_2i_1, encode[i][0], encode[i][1]);
const size_i = size[i];
e = MathClamp(e, 0, size_i - 1);
const e0 = e < size_i - 1 ? Math.floor(e) : e - 1;
const n0 = e0 + 1 - e;
const n1 = e - e0;
const offset0 = e0 * k;
const offset1 = offset0 + k;
for (j = 0; j < cubeVertices; j++) {
if (j & pos) {
cubeN[j] *= n1;
cubeVertex[j] += offset1;
} else {
cubeN[j] *= n0;
cubeVertex[j] += offset0;
}
}
k *= size_i;
pos <<= 1;
}
for (j = 0; j < outputSize; ++j) {
let rj = 0;
for (i = 0; i < cubeVertices; i++) {
rj += samples[cubeVertex[i] + j] * cubeN[i];
}
rj = interpolate(rj, 0, 1, decode[j][0], decode[j][1]);
dest[destOffset + j] = MathClamp(rj, range[j][0], range[j][1]);
}
};
}
static constructInterpolated(factory, dict) {
const c0 = toNumberArray(dict.getArray("C0")) || [0];
const c1 = toNumberArray(dict.getArray("C1")) || [1];
const n = dict.get("N");
const diff = [];
for (let i = 0, ii = c0.length; i < ii; ++i) {
diff.push(c1[i] - c0[i]);
}
const length = diff.length;
return function constructInterpolatedFn(src, srcOffset, dest, destOffset) {
const x = n === 1 ? src[srcOffset] : src[srcOffset] ** n;
for (let j = 0; j < length; ++j) {
dest[destOffset + j] = c0[j] + x * diff[j];
}
};
}
static constructStiched(factory, dict) {
const domain = toNumberArray(dict.getArray("Domain"));
if (!domain) {
throw new FormatError("No domain");
}
const inputSize = domain.length / 2;
if (inputSize !== 1) {
throw new FormatError("Bad domain for stiched function");
}
const {
xref
} = factory;
const fns = [];
for (const fn of dict.get("Functions")) {
fns.push(this.parse(factory, xref.fetchIfRef(fn)));
}
const bounds = toNumberArray(dict.getArray("Bounds"));
const encode = toNumberArray(dict.getArray("Encode"));
const tmpBuf = new Float32Array(1);
return function constructStichedFn(src, srcOffset, dest, destOffset) {
const v = MathClamp(src[srcOffset], domain[0], domain[1]);
const length = bounds.length;
let i;
for (i = 0; i < length; ++i) {
if (v < bounds[i]) {
break;
}
}
let dmin = domain[0];
if (i > 0) {
dmin = bounds[i - 1];
}
let dmax = domain[1];
if (i < bounds.length) {
dmax = bounds[i];
}
const rmin = encode[2 * i];
const rmax = encode[2 * i + 1];
tmpBuf[0] = dmin === dmax ? rmin : rmin + (v - dmin) * (rmax - rmin) / (dmax - dmin);
fns[i](tmpBuf, 0, dest, destOffset);
};
}
static constructPostScript(factory, fn, dict) {
const domain = toNumberArray(dict.getArray("Domain"));
const range = toNumberArray(dict.getArray("Range"));
if (!domain) {
throw new FormatError("No domain.");
}
if (!range) {
throw new FormatError("No range.");
}
const lexer = new PostScriptLexer(fn);
const parser = new PostScriptParser(lexer);
const code = parser.parse();
if (factory.isEvalSupported && FeatureTest.isEvalSupported) {
const compiled = new PostScriptCompiler().compile(code, domain, range);
if (compiled) {
return new Function("src", "srcOffset", "dest", "destOffset", compiled);
}
}
info("Unable to compile PS function");
const numOutputs = range.length >> 1;
const numInputs = domain.length >> 1;
const evaluator = new PostScriptEvaluator(code);
const cache = Object.create(null);
const MAX_CACHE_SIZE = 2048 * 4;
let cache_available = MAX_CACHE_SIZE;
const tmpBuf = new Float32Array(numInputs);
return function constructPostScriptFn(src, srcOffset, dest, destOffset) {
let i, value;
let key = "";
const input = tmpBuf;
for (i = 0; i < numInputs; i++) {
value = src[srcOffset + i];
input[i] = value;
key += value + "_";
}
const cachedValue = cache[key];
if (cachedValue !== undefined) {
dest.set(cachedValue, destOffset);
return;
}
const output = new Float32Array(numOutputs);
const stack = evaluator.execute(input);
const stackIndex = stack.length - numOutputs;
for (i = 0; i < numOutputs; i++) {
value = stack[stackIndex + i];
let bound = range[i * 2];
if (value < bound) {
value = bound;
} else {
bound = range[i * 2 + 1];
if (value > bound) {
value = bound;
}
}
output[i] = value;
}
if (cache_available > 0) {
cache_available--;
cache[key] = output;
}
dest.set(output, destOffset);
};
}
}
function isPDFFunction(v) {
let fnDict;
if (v instanceof Dict) {
fnDict = v;
} else if (v instanceof BaseStream) {
fnDict = v.dict;
} else {
return false;
}
return fnDict.has("FunctionType");
}
class PostScriptStack {
static MAX_STACK_SIZE = 100;
constructor(initialStack) {
this.stack = initialStack ? Array.from(initialStack) : [];
}
push(value) {
if (this.stack.length >= PostScriptStack.MAX_STACK_SIZE) {
throw new Error("PostScript function stack overflow.");
}
this.stack.push(value);
}
pop() {
if (this.stack.length <= 0) {
throw new Error("PostScript function stack underflow.");
}
return this.stack.pop();
}
copy(n) {
if (this.stack.length + n >= PostScriptStack.MAX_STACK_SIZE) {
throw new Error("PostScript function stack overflow.");
}
const stack = this.stack;
for (let i = stack.length - n, j = n - 1; j >= 0; j--, i++) {
stack.push(stack[i]);
}
}
index(n) {
this.push(this.stack[this.stack.length - n - 1]);
}
roll(n, p) {
const stack = this.stack;
const l = stack.length - n;
const r = stack.length - 1;
const c = l + (p - Math.floor(p / n) * n);
for (let i = l, j = r; i < j; i++, j--) {
const t = stack[i];
stack[i] = stack[j];
stack[j] = t;
}
for (let i = l, j = c - 1; i < j; i++, j--) {
const t = stack[i];
stack[i] = stack[j];
stack[j] = t;
}
for (let i = c, j = r; i < j; i++, j--) {
const t = stack[i];
stack[i] = stack[j];
stack[j] = t;
}
}
}
class PostScriptEvaluator {
constructor(operators) {
this.operators = operators;
}
execute(initialStack) {
const stack = new PostScriptStack(initialStack);
let counter = 0;
const operators = this.operators;
const length = operators.length;
let operator, a, b;
while (counter < length) {
operator = operators[counter++];
if (typeof operator === "number") {
stack.push(operator);
continue;
}
switch (operator) {
case "jz":
b = stack.pop();
a = stack.pop();
if (!a) {
counter = b;
}
break;
case "j":
a = stack.pop();
counter = a;
break;
case "abs":
a = stack.pop();
stack.push(Math.abs(a));
break;
case "add":
b = stack.pop();
a = stack.pop();
stack.push(a + b);
break;
case "and":
b = stack.pop();
a = stack.pop();
if (typeof a === "boolean" && typeof b === "boolean") {
stack.push(a && b);
} else {
stack.push(a & b);
}
break;
case "atan":
b = stack.pop();
a = stack.pop();
a = Math.atan2(a, b) / Math.PI * 180;
if (a < 0) {
a += 360;
}
stack.push(a);
break;
case "bitshift":
b = stack.pop();
a = stack.pop();
if (a > 0) {
stack.push(a << b);
} else {
stack.push(a >> b);
}
break;
case "ceiling":
a = stack.pop();
stack.push(Math.ceil(a));
break;
case "copy":
a = stack.pop();
stack.copy(a);
break;
case "cos":
a = stack.pop();
stack.push(Math.cos(a % 360 / 180 * Math.PI));
break;
case "cvi":
a = stack.pop() | 0;
stack.push(a);
break;
case "cvr":
break;
case "div":
b = stack.pop();
a = stack.pop();
stack.push(a / b);
break;
case "dup":
stack.copy(1);
break;
case "eq":
b = stack.pop();
a = stack.pop();
stack.push(a === b);
break;
case "exch":
stack.roll(2, 1);
break;
case "exp":
b = stack.pop();
a = stack.pop();
stack.push(a ** b);
break;
case "false":
stack.push(false);
break;
case "floor":
a = stack.pop();
stack.push(Math.floor(a));
break;
case "ge":
b = stack.pop();
a = stack.pop();
stack.push(a >= b);
break;
case "gt":
b = stack.pop();
a = stack.pop();
stack.push(a > b);
break;
case "idiv":
b = stack.pop();
a = stack.pop();
stack.push(a / b | 0);
break;
case "index":
a = stack.pop();
stack.index(a);
break;
case "le":
b = stack.pop();
a = stack.pop();
stack.push(a <= b);
break;
case "ln":
a = stack.pop();
stack.push(Math.log(a));
break;
case "log":
a = stack.pop();
stack.push(Math.log10(a));
break;
case "lt":
b = stack.pop();
a = stack.pop();
stack.push(a < b);
break;
case "mod":
b = stack.pop();
a = stack.pop();
stack.push(a % b);
break;
case "mul":
b = stack.pop();
a = stack.pop();
stack.push(a * b);
break;
case "ne":
b = stack.pop();
a = stack.pop();
stack.push(a !== b);
break;
case "neg":
a = stack.pop();
stack.push(-a);
break;
case "not":
a = stack.pop();
if (typeof a === "boolean") {
stack.push(!a);
} else {
stack.push(~a);
}
break;
case "or":
b = stack.pop();
a = stack.pop();
if (typeof a === "boolean" && typeof b === "boolean") {
stack.push(a || b);
} else {
stack.push(a | b);
}
break;
case "pop":
stack.pop();
break;
case "roll":
b = stack.pop();
a = stack.pop();
stack.roll(a, b);
break;
case "round":
a = stack.pop();
stack.push(Math.round(a));
break;
case "sin":
a = stack.pop();
stack.push(Math.sin(a % 360 / 180 * Math.PI));
break;
case "sqrt":
a = stack.pop();
stack.push(Math.sqrt(a));
break;
case "sub":
b = stack.pop();
a = stack.pop();
stack.push(a - b);
break;
case "true":
stack.push(true);
break;
case "truncate":
a = stack.pop();
a = a < 0 ? Math.ceil(a) : Math.floor(a);
stack.push(a);
break;
case "xor":
b = stack.pop();
a = stack.pop();
if (typeof a === "boolean" && typeof b === "boolean") {
stack.push(a !== b);
} else {
stack.push(a ^ b);
}
break;
default:
throw new FormatError(`Unknown operator ${operator}`);
}
}
return stack.stack;
}
}
class AstNode {
constructor(type) {
this.type = type;
}
visit(visitor) {
unreachable("abstract method");
}
}
class AstArgument extends AstNode {
constructor(index, min, max) {
super("args");
this.index = index;
this.min = min;
this.max = max;
}
visit(visitor) {
visitor.visitArgument(this);
}
}
class AstLiteral extends AstNode {
constructor(number) {
super("literal");
this.number = number;
this.min = number;
this.max = number;
}
visit(visitor) {
visitor.visitLiteral(this);
}
}
class AstBinaryOperation extends AstNode {
constructor(op, arg1, arg2, min, max) {
super("binary");
this.op = op;
this.arg1 = arg1;
this.arg2 = arg2;
this.min = min;
this.max = max;
}
visit(visitor) {
visitor.visitBinaryOperation(this);
}
}
class AstMin extends AstNode {
constructor(arg, max) {
super("max");
this.arg = arg;
this.min = arg.min;
this.max = max;
}
visit(visitor) {
visitor.visitMin(this);
}
}
class AstVariable extends AstNode {
constructor(index, min, max) {
super("var");
this.index = index;
this.min = min;
this.max = max;
}
visit(visitor) {
visitor.visitVariable(this);
}
}
class AstVariableDefinition extends AstNode {
constructor(variable, arg) {
super("definition");
this.variable = variable;
this.arg = arg;
}
visit(visitor) {
visitor.visitVariableDefinition(this);
}
}
class ExpressionBuilderVisitor {
constructor() {
this.parts = [];
}
visitArgument(arg) {
this.parts.push("Math.max(", arg.min, ", Math.min(", arg.max, ", src[srcOffset + ", arg.index, "]))");
}
visitVariable(variable) {
this.parts.push("v", variable.index);
}
visitLiteral(literal) {
this.parts.push(literal.number);
}
visitBinaryOperation(operation) {
this.parts.push("(");
operation.arg1.visit(this);
this.parts.push(" ", operation.op, " ");
operation.arg2.visit(this);
this.parts.push(")");
}
visitVariableDefinition(definition) {
this.parts.push("var ");
definition.variable.visit(this);
this.parts.push(" = ");
definition.arg.visit(this);
this.parts.push(";");
}
visitMin(max) {
this.parts.push("Math.min(");
max.arg.visit(this);
this.parts.push(", ", max.max, ")");
}
toString() {
return this.parts.join("");
}
}
function buildAddOperation(num1, num2) {
if (num2.type === "literal" && num2.number === 0) {
return num1;
}
if (num1.type === "literal" && num1.number === 0) {
return num2;
}
if (num2.type === "literal" && num1.type === "literal") {
return new AstLiteral(num1.number + num2.number);
}
return new AstBinaryOperation("+", num1, num2, num1.min + num2.min, num1.max + num2.max);
}
function buildMulOperation(num1, num2) {
if (num2.type === "literal") {
if (num2.number === 0) {
return new AstLiteral(0);
} else if (num2.number === 1) {
return num1;
} else if (num1.type === "literal") {
return new AstLiteral(num1.number * num2.number);
}
}
if (num1.type === "literal") {
if (num1.number === 0) {
return new AstLiteral(0);
} else if (num1.number === 1) {
return num2;
}
}
const min = Math.min(num1.min * num2.min, num1.min * num2.max, num1.max * num2.min, num1.max * num2.max);
const max = Math.max(num1.min * num2.min, num1.min * num2.max, num1.max * num2.min, num1.max * num2.max);
return new AstBinaryOperation("*", num1, num2, min, max);
}
function buildSubOperation(num1, num2) {
if (num2.type === "literal") {
if (num2.number === 0) {
return num1;
} else if (num1.type === "literal") {
return new AstLiteral(num1.number - num2.number);
}
}
if (num2.type === "binary" && num2.op === "-" && num1.type === "literal" && num1.number === 1 && num2.arg1.type === "literal" && num2.arg1.number === 1) {
return num2.arg2;
}
return new AstBinaryOperation("-", num1, num2, num1.min - num2.max, num1.max - num2.min);
}
function buildMinOperation(num1, max) {
if (num1.min >= max) {
return new AstLiteral(max);
} else if (num1.max <= max) {
return num1;
}
return new AstMin(num1, max);
}
class PostScriptCompiler {
compile(code, domain, range) {
const stack = [];
const instructions = [];
const inputSize = domain.length >> 1,
outputSize = range.length >> 1;
let lastRegister = 0;
let n, j;
let num1, num2, ast1, ast2, tmpVar, item;
for (let i = 0; i < inputSize; i++) {
stack.push(new AstArgument(i, domain[i * 2], domain[i * 2 + 1]));
}
for (let i = 0, ii = code.length; i < ii; i++) {
item = code[i];
if (typeof item === "number") {
stack.push(new AstLiteral(item));
continue;
}
switch (item) {
case "add":
if (stack.length < 2) {
return null;
}
num2 = stack.pop();
num1 = stack.pop();
stack.push(buildAddOperation(num1, num2));
break;
case "cvr":
if (stack.length < 1) {
return null;
}
break;
case "mul":
if (stack.length < 2) {
return null;
}
num2 = stack.pop();
num1 = stack.pop();
stack.push(buildMulOperation(num1, num2));
break;
case "sub":
if (stack.length < 2) {
return null;
}
num2 = stack.pop();
num1 = stack.pop();
stack.push(buildSubOperation(num1, num2));
break;
case "exch":
if (stack.length < 2) {
return null;
}
ast1 = stack.pop();
ast2 = stack.pop();
stack.push(ast1, ast2);
break;
case "pop":
if (stack.length < 1) {
return null;
}
stack.pop();
break;
case "index":
if (stack.length < 1) {
return null;
}
num1 = stack.pop();
if (num1.type !== "literal") {
return null;
}
n = num1.number;
if (n < 0 || !Number.isInteger(n) || stack.length < n) {
return null;
}
ast1 = stack[stack.length - n - 1];
if (ast1.type === "literal" || ast1.type === "var") {
stack.push(ast1);
break;
}
tmpVar = new AstVariable(lastRegister++, ast1.min, ast1.max);
stack[stack.length - n - 1] = tmpVar;
stack.push(tmpVar);
instructions.push(new AstVariableDefinition(tmpVar, ast1));
break;
case "dup":
if (stack.length < 1) {
return null;
}
if (typeof code[i + 1] === "number" && code[i + 2] === "gt" && code[i + 3] === i + 7 && code[i + 4] === "jz" && code[i + 5] === "pop" && code[i + 6] === code[i + 1]) {
num1 = stack.pop();
stack.push(buildMinOperation(num1, code[i + 1]));
i += 6;
break;
}
ast1 = stack.at(-1);
if (ast1.type === "literal" || ast1.type === "var") {
stack.push(ast1);
break;
}
tmpVar = new AstVariable(lastRegister++, ast1.min, ast1.max);
stack[stack.length - 1] = tmpVar;
stack.push(tmpVar);
instructions.push(new AstVariableDefinition(tmpVar, ast1));
break;
case "roll":
if (stack.length < 2) {
return null;
}
num2 = stack.pop();
num1 = stack.pop();
if (num2.type !== "literal" || num1.type !== "literal") {
return null;
}
j = num2.number;
n = num1.number;
if (n <= 0 || !Number.isInteger(n) || !Number.isInteger(j) || stack.length < n) {
return null;
}
j = (j % n + n) % n;
if (j === 0) {
break;
}
stack.push(...stack.splice(stack.length - n, n - j));
break;
default:
return null;
}
}
if (stack.length !== outputSize) {
return null;
}
const result = [];
for (const instruction of instructions) {
const statementBuilder = new ExpressionBuilderVisitor();
instruction.visit(statementBuilder);
result.push(statementBuilder.toString());
}
for (let i = 0, ii = stack.length; i < ii; i++) {
const expr = stack[i],
statementBuilder = new ExpressionBuilderVisitor();
expr.visit(statementBuilder);
const min = range[i * 2],
max = range[i * 2 + 1];
const out = [statementBuilder.toString()];
if (min > expr.min) {
out.unshift("Math.max(", min, ", ");
out.push(")");
}
if (max < expr.max) {
out.unshift("Math.min(", max, ", ");
out.push(")");
}
out.unshift("dest[destOffset + ", i, "] = ");
out.push(";");
result.push(out.join(""));
}
return result.join("\n");
}
}
;// ./src/core/bidi.js
const baseTypes = ["BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "S", "B", "S", "WS", "B", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "B", "B", "B", "S", "WS", "ON", "ON", "ET", "ET", "ET", "ON", "ON", "ON", "ON", "ON", "ES", "CS", "ES", "CS", "CS", "EN", "EN", "EN", "EN", "EN", "EN", "EN", "EN", "EN", "EN", "CS", "ON", "ON", "ON", "ON", "ON", "ON", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "ON", "ON", "ON", "ON", "ON", "ON", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "ON", "ON", "ON", "ON", "BN", "BN", "BN", "BN", "BN", "BN", "B", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "CS", "ON", "ET", "ET", "ET", "ET", "ON", "ON", "ON", "ON", "L", "ON", "ON", "BN", "ON", "ON", "ET", "ET", "EN", "EN", "ON", "L", "ON", "ON", "ON", "EN", "L", "ON", "ON", "ON", "ON", "ON", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "ON", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "ON", "L", "L", "L", "L", "L", "L", "L", "L"];
const arabicTypes = ["AN", "AN", "AN", "AN", "AN", "AN", "ON", "ON", "AL", "ET", "ET", "AL", "CS", "AL", "ON", "ON", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "AL", "AL", "", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "AN", "AN", "AN", "AN", "AN", "AN", "AN", "AN", "AN", "AN", "ET", "AN", "AN", "AL", "AL", "AL", "NSM", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "AN", "ON", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "AL", "AL", "NSM", "NSM", "ON", "NSM", "NSM", "NSM", "NSM", "AL", "AL", "EN", "EN", "EN", "EN", "EN", "EN", "EN", "EN", "EN", "EN", "AL", "AL", "AL", "AL", "AL", "AL"];
function isOdd(i) {
return (i & 1) !== 0;
}
function isEven(i) {
return (i & 1) === 0;
}
function findUnequal(arr, start, value) {
let j, jj;
for (j = start, jj = arr.length; j < jj; ++j) {
if (arr[j] !== value) {
return j;
}
}
return j;
}
function reverseValues(arr, start, end) {
for (let i = start, j = end - 1; i < j; ++i, --j) {
const temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
function createBidiText(str, isLTR, vertical = false) {
let dir = "ltr";
if (vertical) {
dir = "ttb";
} else if (!isLTR) {
dir = "rtl";
}
return {
str,
dir
};
}
const chars = [];
const types = [];
function bidi(str, startLevel = -1, vertical = false) {
let isLTR = true;
const strLength = str.length;
if (strLength === 0 || vertical) {
return createBidiText(str, isLTR, vertical);
}
chars.length = strLength;
types.length = strLength;
let numBidi = 0;
let i, ii;
for (i = 0; i < strLength; ++i) {
chars[i] = str.charAt(i);
const charCode = str.charCodeAt(i);
let charType = "L";
if (charCode <= 0x00ff) {
charType = baseTypes[charCode];
} else if (0x0590 <= charCode && charCode <= 0x05f4) {
charType = "R";
} else if (0x0600 <= charCode && charCode <= 0x06ff) {
charType = arabicTypes[charCode & 0xff];
if (!charType) {
warn("Bidi: invalid Unicode character " + charCode.toString(16));
}
} else if (0x0700 <= charCode && charCode <= 0x08ac || 0xfb50 <= charCode && charCode <= 0xfdff || 0xfe70 <= charCode && charCode <= 0xfeff) {
charType = "AL";
}
if (charType === "R" || charType === "AL" || charType === "AN") {
numBidi++;
}
types[i] = charType;
}
if (numBidi === 0) {
isLTR = true;
return createBidiText(str, isLTR);
}
if (startLevel === -1) {
if (numBidi / strLength < 0.3 && strLength > 4) {
isLTR = true;
startLevel = 0;
} else {
isLTR = false;
startLevel = 1;
}
}
const levels = [];
for (i = 0; i < strLength; ++i) {
levels[i] = startLevel;
}
const e = isOdd(startLevel) ? "R" : "L";
const sor = e;
const eor = sor;
let lastType = sor;
for (i = 0; i < strLength; ++i) {
if (types[i] === "NSM") {
types[i] = lastType;
} else {
lastType = types[i];
}
}
lastType = sor;
let t;
for (i = 0; i < strLength; ++i) {
t = types[i];
if (t === "EN") {
types[i] = lastType === "AL" ? "AN" : "EN";
} else if (t === "R" || t === "L" || t === "AL") {
lastType = t;
}
}
for (i = 0; i < strLength; ++i) {
t = types[i];
if (t === "AL") {
types[i] = "R";
}
}
for (i = 1; i < strLength - 1; ++i) {
if (types[i] === "ES" && types[i - 1] === "EN" && types[i + 1] === "EN") {
types[i] = "EN";
}
if (types[i] === "CS" && (types[i - 1] === "EN" || types[i - 1] === "AN") && types[i + 1] === types[i - 1]) {
types[i] = types[i - 1];
}
}
for (i = 0; i < strLength; ++i) {
if (types[i] === "EN") {
for (let j = i - 1; j >= 0; --j) {
if (types[j] !== "ET") {
break;
}
types[j] = "EN";
}
for (let j = i + 1; j < strLength; ++j) {
if (types[j] !== "ET") {
break;
}
types[j] = "EN";
}
}
}
for (i = 0; i < strLength; ++i) {
t = types[i];
if (t === "WS" || t === "ES" || t === "ET" || t === "CS") {
types[i] = "ON";
}
}
lastType = sor;
for (i = 0; i < strLength; ++i) {
t = types[i];
if (t === "EN") {
types[i] = lastType === "L" ? "L" : "EN";
} else if (t === "R" || t === "L") {
lastType = t;
}
}
for (i = 0; i < strLength; ++i) {
if (types[i] === "ON") {
const end = findUnequal(types, i + 1, "ON");
let before = sor;
if (i > 0) {
before = types[i - 1];
}
let after = eor;
if (end + 1 < strLength) {
after = types[end + 1];
}
if (before !== "L") {
before = "R";
}
if (after !== "L") {
after = "R";
}
if (before === after) {
types.fill(before, i, end);
}
i = end - 1;
}
}
for (i = 0; i < strLength; ++i) {
if (types[i] === "ON") {
types[i] = e;
}
}
for (i = 0; i < strLength; ++i) {
t = types[i];
if (isEven(levels[i])) {
if (t === "R") {
levels[i] += 1;
} else if (t === "AN" || t === "EN") {
levels[i] += 2;
}
} else if (t === "L" || t === "AN" || t === "EN") {
levels[i] += 1;
}
}
let highestLevel = -1;
let lowestOddLevel = 99;
let level;
for (i = 0, ii = levels.length; i < ii; ++i) {
level = levels[i];
if (highestLevel < level) {
highestLevel = level;
}
if (lowestOddLevel > level && isOdd(level)) {
lowestOddLevel = level;
}
}
for (level = highestLevel; level >= lowestOddLevel; --level) {
let start = -1;
for (i = 0, ii = levels.length; i < ii; ++i) {
if (levels[i] < level) {
if (start >= 0) {
reverseValues(chars, start, i);
start = -1;
}
} else if (start < 0) {
start = i;
}
}
if (start >= 0) {
reverseValues(chars, start, levels.length);
}
}
for (i = 0, ii = chars.length; i < ii; ++i) {
const ch = chars[i];
if (ch === "<" || ch === ">") {
chars[i] = "";
}
}
return createBidiText(chars.join(""), isLTR);
}
;// ./src/core/font_substitutions.js
const NORMAL = {
style: "normal",
weight: "normal"
};
const BOLD = {
style: "normal",
weight: "bold"
};
const ITALIC = {
style: "italic",
weight: "normal"
};
const BOLDITALIC = {
style: "italic",
weight: "bold"
};
const substitutionMap = new Map([["Times-Roman", {
local: ["Times New Roman", "Times-Roman", "Times", "Liberation Serif", "Nimbus Roman", "Nimbus Roman L", "Tinos", "Thorndale", "TeX Gyre Termes", "FreeSerif", "Linux Libertine O", "Libertinus Serif", "DejaVu Serif", "Bitstream Vera Serif", "Ubuntu"],
style: NORMAL,
ultimate: "serif"
}], ["Times-Bold", {
alias: "Times-Roman",
style: BOLD,
ultimate: "serif"
}], ["Times-Italic", {
alias: "Times-Roman",
style: ITALIC,
ultimate: "serif"
}], ["Times-BoldItalic", {
alias: "Times-Roman",
style: BOLDITALIC,
ultimate: "serif"
}], ["Helvetica", {
local: ["Helvetica", "Helvetica Neue", "Arial", "Arial Nova", "Liberation Sans", "Arimo", "Nimbus Sans", "Nimbus Sans L", "A030", "TeX Gyre Heros", "FreeSans", "DejaVu Sans", "Albany", "Bitstream Vera Sans", "Arial Unicode MS", "Microsoft Sans Serif", "Apple Symbols", "Cantarell"],
path: "LiberationSans-Regular.ttf",
style: NORMAL,
ultimate: "sans-serif"
}], ["Helvetica-Bold", {
alias: "Helvetica",
path: "LiberationSans-Bold.ttf",
style: BOLD,
ultimate: "sans-serif"
}], ["Helvetica-Oblique", {
alias: "Helvetica",
path: "LiberationSans-Italic.ttf",
style: ITALIC,
ultimate: "sans-serif"
}], ["Helvetica-BoldOblique", {
alias: "Helvetica",
path: "LiberationSans-BoldItalic.ttf",
style: BOLDITALIC,
ultimate: "sans-serif"
}], ["Courier", {
local: ["Courier", "Courier New", "Liberation Mono", "Nimbus Mono", "Nimbus Mono L", "Cousine", "Cumberland", "TeX Gyre Cursor", "FreeMono", "Linux Libertine Mono O", "Libertinus Mono"],
style: NORMAL,
ultimate: "monospace"
}], ["Courier-Bold", {
alias: "Courier",
style: BOLD,
ultimate: "monospace"
}], ["Courier-Oblique", {
alias: "Courier",
style: ITALIC,
ultimate: "monospace"
}], ["Courier-BoldOblique", {
alias: "Courier",
style: BOLDITALIC,
ultimate: "monospace"
}], ["ArialBlack", {
local: ["Arial Black"],
style: {
style: "normal",
weight: "900"
},
fallback: "Helvetica-Bold"
}], ["ArialBlack-Bold", {
alias: "ArialBlack"
}], ["ArialBlack-Italic", {
alias: "ArialBlack",
style: {
style: "italic",
weight: "900"
},
fallback: "Helvetica-BoldOblique"
}], ["ArialBlack-BoldItalic", {
alias: "ArialBlack-Italic"
}], ["ArialNarrow", {
local: ["Arial Narrow", "Liberation Sans Narrow", "Helvetica Condensed", "Nimbus Sans Narrow", "TeX Gyre Heros Cn"],
style: NORMAL,
fallback: "Helvetica"
}], ["ArialNarrow-Bold", {
alias: "ArialNarrow",
style: BOLD,
fallback: "Helvetica-Bold"
}], ["ArialNarrow-Italic", {
alias: "ArialNarrow",
style: ITALIC,
fallback: "Helvetica-Oblique"
}], ["ArialNarrow-BoldItalic", {
alias: "ArialNarrow",
style: BOLDITALIC,
fallback: "Helvetica-BoldOblique"
}], ["Calibri", {
local: ["Calibri", "Carlito"],
style: NORMAL,
fallback: "Helvetica"
}], ["Calibri-Bold", {
alias: "Calibri",
style: BOLD,
fallback: "Helvetica-Bold"
}], ["Calibri-Italic", {
alias: "Calibri",
style: ITALIC,
fallback: "Helvetica-Oblique"
}], ["Calibri-BoldItalic", {
alias: "Calibri",
style: BOLDITALIC,
fallback: "Helvetica-BoldOblique"
}], ["Wingdings", {
local: ["Wingdings", "URW Dingbats"],
style: NORMAL
}], ["Wingdings-Regular", {
alias: "Wingdings"
}], ["Wingdings-Bold", {
alias: "Wingdings"
}]]);
const fontAliases = new Map([["Arial-Black", "ArialBlack"]]);
function getStyleToAppend(style) {
switch (style) {
case BOLD:
return "Bold";
case ITALIC:
return "Italic";
case BOLDITALIC:
return "Bold Italic";
default:
if (style?.weight === "bold") {
return "Bold";
}
if (style?.style === "italic") {
return "Italic";
}
}
return "";
}
function getFamilyName(str) {
const keywords = new Set(["thin", "extralight", "ultralight", "demilight", "semilight", "light", "book", "regular", "normal", "medium", "demibold", "semibold", "bold", "extrabold", "ultrabold", "black", "heavy", "extrablack", "ultrablack", "roman", "italic", "oblique", "ultracondensed", "extracondensed", "condensed", "semicondensed", "normal", "semiexpanded", "expanded", "extraexpanded", "ultraexpanded", "bolditalic"]);
return str.split(/[- ,+]+/g).filter(tok => !keywords.has(tok.toLowerCase())).join(" ");
}
function generateFont({
alias,
local,
path,
fallback,
style,
ultimate
}, src, localFontPath, useFallback = true, usePath = true, append = "") {
const result = {
style: null,
ultimate: null
};
if (local) {
const extra = append ? ` ${append}` : "";
for (const name of local) {
src.push(`local(${name}${extra})`);
}
}
if (alias) {
const substitution = substitutionMap.get(alias);
const aliasAppend = append || getStyleToAppend(style);
Object.assign(result, generateFont(substitution, src, localFontPath, useFallback && !fallback, usePath && !path, aliasAppend));
}
if (style) {
result.style = style;
}
if (ultimate) {
result.ultimate = ultimate;
}
if (useFallback && fallback) {
const fallbackInfo = substitutionMap.get(fallback);
const {
ultimate: fallbackUltimate
} = generateFont(fallbackInfo, src, localFontPath, useFallback, usePath && !path, append);
result.ultimate ||= fallbackUltimate;
}
if (usePath && path && localFontPath) {
src.push(`url(${localFontPath}${path})`);
}
return result;
}
function getFontSubstitution(systemFontCache, idFactory, localFontPath, baseFontName, standardFontName, type) {
if (baseFontName.startsWith("InvalidPDFjsFont_")) {
return null;
}
if ((type === "TrueType" || type === "Type1") && /^[A-Z]{6}\+/.test(baseFontName)) {
baseFontName = baseFontName.slice(7);
}
baseFontName = normalizeFontName(baseFontName);
const key = baseFontName;
let substitutionInfo = systemFontCache.get(key);
if (substitutionInfo) {
return substitutionInfo;
}
let substitution = substitutionMap.get(baseFontName);
if (!substitution) {
for (const [alias, subst] of fontAliases) {
if (baseFontName.startsWith(alias)) {
baseFontName = `${subst}${baseFontName.substring(alias.length)}`;
substitution = substitutionMap.get(baseFontName);
break;
}
}
}
let mustAddBaseFont = false;
if (!substitution) {
substitution = substitutionMap.get(standardFontName);
mustAddBaseFont = true;
}
const loadedName = `${idFactory.getDocId()}_s${idFactory.createFontId()}`;
if (!substitution) {
if (!validateFontName(baseFontName)) {
warn(`Cannot substitute the font because of its name: ${baseFontName}`);
systemFontCache.set(key, null);
return null;
}
const bold = /bold/gi.test(baseFontName);
const italic = /oblique|italic/gi.test(baseFontName);
const style = bold && italic && BOLDITALIC || bold && BOLD || italic && ITALIC || NORMAL;
substitutionInfo = {
css: `"${getFamilyName(baseFontName)}",${loadedName}`,
guessFallback: true,
loadedName,
baseFontName,
src: `local(${baseFontName})`,
style
};
systemFontCache.set(key, substitutionInfo);
return substitutionInfo;
}
const src = [];
if (mustAddBaseFont && validateFontName(baseFontName)) {
src.push(`local(${baseFontName})`);
}
const {
style,
ultimate
} = generateFont(substitution, src, localFontPath);
const guessFallback = ultimate === null;
const fallback = guessFallback ? "" : `,${ultimate}`;
substitutionInfo = {
css: `"${getFamilyName(baseFontName)}",${loadedName}${fallback}`,
guessFallback,
loadedName,
baseFontName,
src: src.join(","),
style
};
systemFontCache.set(key, substitutionInfo);
return substitutionInfo;
}
;// ./src/shared/murmurhash3.js
const SEED = 0xc3d2e1f0;
const MASK_HIGH = 0xffff0000;
const MASK_LOW = 0xffff;
class MurmurHash3_64 {
constructor(seed) {
this.h1 = seed ? seed & 0xffffffff : SEED;
this.h2 = seed ? seed & 0xffffffff : SEED;
}
update(input) {
let data, length;
if (typeof input === "string") {
data = new Uint8Array(input.length * 2);
length = 0;
for (let i = 0, ii = input.length; i < ii; i++) {
const code = input.charCodeAt(i);
if (code <= 0xff) {
data[length++] = code;
} else {
data[length++] = code >>> 8;
data[length++] = code & 0xff;
}
}
} else if (ArrayBuffer.isView(input)) {
data = input.slice();
length = data.byteLength;
} else {
throw new Error("Invalid data format, must be a string or TypedArray.");
}
const blockCounts = length >> 2;
const tailLength = length - blockCounts * 4;
const dataUint32 = new Uint32Array(data.buffer, 0, blockCounts);
let k1 = 0,
k2 = 0;
let h1 = this.h1,
h2 = this.h2;
const C1 = 0xcc9e2d51,
C2 = 0x1b873593;
const C1_LOW = C1 & MASK_LOW,
C2_LOW = C2 & MASK_LOW;
for (let i = 0; i < blockCounts; i++) {
if (i & 1) {
k1 = dataUint32[i];
k1 = k1 * C1 & MASK_HIGH | k1 * C1_LOW & MASK_LOW;
k1 = k1 << 15 | k1 >>> 17;
k1 = k1 * C2 & MASK_HIGH | k1 * C2_LOW & MASK_LOW;
h1 ^= k1;
h1 = h1 << 13 | h1 >>> 19;
h1 = h1 * 5 + 0xe6546b64;
} else {
k2 = dataUint32[i];
k2 = k2 * C1 & MASK_HIGH | k2 * C1_LOW & MASK_LOW;
k2 = k2 << 15 | k2 >>> 17;
k2 = k2 * C2 & MASK_HIGH | k2 * C2_LOW & MASK_LOW;
h2 ^= k2;
h2 = h2 << 13 | h2 >>> 19;
h2 = h2 * 5 + 0xe6546b64;
}
}
k1 = 0;
switch (tailLength) {
case 3:
k1 ^= data[blockCounts * 4 + 2] << 16;
case 2:
k1 ^= data[blockCounts * 4 + 1] << 8;
case 1:
k1 ^= data[blockCounts * 4];
k1 = k1 * C1 & MASK_HIGH | k1 * C1_LOW & MASK_LOW;
k1 = k1 << 15 | k1 >>> 17;
k1 = k1 * C2 & MASK_HIGH | k1 * C2_LOW & MASK_LOW;
if (blockCounts & 1) {
h1 ^= k1;
} else {
h2 ^= k1;
}
}
this.h1 = h1;
this.h2 = h2;
}
hexdigest() {
let h1 = this.h1,
h2 = this.h2;
h1 ^= h2 >>> 1;
h1 = h1 * 0xed558ccd & MASK_HIGH | h1 * 0x8ccd & MASK_LOW;
h2 = h2 * 0xff51afd7 & MASK_HIGH | ((h2 << 16 | h1 >>> 16) * 0xafd7ed55 & MASK_HIGH) >>> 16;
h1 ^= h2 >>> 1;
h1 = h1 * 0x1a85ec53 & MASK_HIGH | h1 * 0xec53 & MASK_LOW;
h2 = h2 * 0xc4ceb9fe & MASK_HIGH | ((h2 << 16 | h1 >>> 16) * 0xb9fe1a85 & MASK_HIGH) >>> 16;
h1 ^= h2 >>> 1;
return (h1 >>> 0).toString(16).padStart(8, "0") + (h2 >>> 0).toString(16).padStart(8, "0");
}
}
;// ./src/core/operator_list.js
function addState(parentState, pattern, checkFn, iterateFn, processFn) {
let state = parentState;
for (let i = 0, ii = pattern.length - 1; i < ii; i++) {
const item = pattern[i];
state = state[item] ||= [];
}
state[pattern.at(-1)] = {
checkFn,
iterateFn,
processFn
};
}
const InitialState = [];
addState(InitialState, [OPS.save, OPS.transform, OPS.paintInlineImageXObject, OPS.restore], null, function iterateInlineImageGroup(context, i) {
const fnArray = context.fnArray;
const iFirstSave = context.iCurr - 3;
const pos = (i - iFirstSave) % 4;
switch (pos) {
case 0:
return fnArray[i] === OPS.save;
case 1:
return fnArray[i] === OPS.transform;
case 2:
return fnArray[i] === OPS.paintInlineImageXObject;
case 3:
return fnArray[i] === OPS.restore;
}
throw new Error(`iterateInlineImageGroup - invalid pos: ${pos}`);
}, function foundInlineImageGroup(context, i) {
const MIN_IMAGES_IN_INLINE_IMAGES_BLOCK = 10;
const MAX_IMAGES_IN_INLINE_IMAGES_BLOCK = 200;
const MAX_WIDTH = 1000;
const IMAGE_PADDING = 1;
const fnArray = context.fnArray,
argsArray = context.argsArray;
const curr = context.iCurr;
const iFirstSave = curr - 3;
const iFirstTransform = curr - 2;
const iFirstPIIXO = curr - 1;
const count = Math.min(Math.floor((i - iFirstSave) / 4), MAX_IMAGES_IN_INLINE_IMAGES_BLOCK);
if (count < MIN_IMAGES_IN_INLINE_IMAGES_BLOCK) {
return i - (i - iFirstSave) % 4;
}
let maxX = 0;
const map = [];
let maxLineHeight = 0;
let currentX = IMAGE_PADDING,
currentY = IMAGE_PADDING;
for (let q = 0; q < count; q++) {
const transform = argsArray[iFirstTransform + (q << 2)];
const img = argsArray[iFirstPIIXO + (q << 2)][0];
if (currentX + img.width > MAX_WIDTH) {
maxX = Math.max(maxX, currentX);
currentY += maxLineHeight + 2 * IMAGE_PADDING;
currentX = 0;
maxLineHeight = 0;
}
map.push({
transform,
x: currentX,
y: currentY,
w: img.width,
h: img.height
});
currentX += img.width + 2 * IMAGE_PADDING;
maxLineHeight = Math.max(maxLineHeight, img.height);
}
const imgWidth = Math.max(maxX, currentX) + IMAGE_PADDING;
const imgHeight = currentY + maxLineHeight + IMAGE_PADDING;
const imgData = new Uint8Array(imgWidth * imgHeight * 4);
const imgRowSize = imgWidth << 2;
for (let q = 0; q < count; q++) {
const data = argsArray[iFirstPIIXO + (q << 2)][0].data;
const rowSize = map[q].w << 2;
let dataOffset = 0;
let offset = map[q].x + map[q].y * imgWidth << 2;
imgData.set(data.subarray(0, rowSize), offset - imgRowSize);
for (let k = 0, kk = map[q].h; k < kk; k++) {
imgData.set(data.subarray(dataOffset, dataOffset + rowSize), offset);
dataOffset += rowSize;
offset += imgRowSize;
}
imgData.set(data.subarray(dataOffset - rowSize, dataOffset), offset);
while (offset >= 0) {
data[offset - 4] = data[offset];
data[offset - 3] = data[offset + 1];
data[offset - 2] = data[offset + 2];
data[offset - 1] = data[offset + 3];
data[offset + rowSize] = data[offset + rowSize - 4];
data[offset + rowSize + 1] = data[offset + rowSize - 3];
data[offset + rowSize + 2] = data[offset + rowSize - 2];
data[offset + rowSize + 3] = data[offset + rowSize - 1];
offset -= imgRowSize;
}
}
const img = {
width: imgWidth,
height: imgHeight
};
if (context.isOffscreenCanvasSupported) {
const canvas = new OffscreenCanvas(imgWidth, imgHeight);
const ctx = canvas.getContext("2d");
ctx.putImageData(new ImageData(new Uint8ClampedArray(imgData.buffer), imgWidth, imgHeight), 0, 0);
img.bitmap = canvas.transferToImageBitmap();
img.data = null;
} else {
img.kind = ImageKind.RGBA_32BPP;
img.data = imgData;
}
fnArray.splice(iFirstSave, count * 4, OPS.paintInlineImageXObjectGroup);
argsArray.splice(iFirstSave, count * 4, [img, map]);
return iFirstSave + 1;
});
addState(InitialState, [OPS.save, OPS.transform, OPS.paintImageMaskXObject, OPS.restore], null, function iterateImageMaskGroup(context, i) {
const fnArray = context.fnArray;
const iFirstSave = context.iCurr - 3;
const pos = (i - iFirstSave) % 4;
switch (pos) {
case 0:
return fnArray[i] === OPS.save;
case 1:
return fnArray[i] === OPS.transform;
case 2:
return fnArray[i] === OPS.paintImageMaskXObject;
case 3:
return fnArray[i] === OPS.restore;
}
throw new Error(`iterateImageMaskGroup - invalid pos: ${pos}`);
}, function foundImageMaskGroup(context, i) {
const MIN_IMAGES_IN_MASKS_BLOCK = 10;
const MAX_IMAGES_IN_MASKS_BLOCK = 100;
const MAX_SAME_IMAGES_IN_MASKS_BLOCK = 1000;
const fnArray = context.fnArray,
argsArray = context.argsArray;
const curr = context.iCurr;
const iFirstSave = curr - 3;
const iFirstTransform = curr - 2;
const iFirstPIMXO = curr - 1;
let count = Math.floor((i - iFirstSave) / 4);
if (count < MIN_IMAGES_IN_MASKS_BLOCK) {
return i - (i - iFirstSave) % 4;
}
let isSameImage = false;
let iTransform, transformArgs;
const firstPIMXOArg0 = argsArray[iFirstPIMXO][0];
const firstTransformArg0 = argsArray[iFirstTransform][0],
firstTransformArg1 = argsArray[iFirstTransform][1],
firstTransformArg2 = argsArray[iFirstTransform][2],
firstTransformArg3 = argsArray[iFirstTransform][3];
if (firstTransformArg1 === firstTransformArg2) {
isSameImage = true;
iTransform = iFirstTransform + 4;
let iPIMXO = iFirstPIMXO + 4;
for (let q = 1; q < count; q++, iTransform += 4, iPIMXO += 4) {
transformArgs = argsArray[iTransform];
if (argsArray[iPIMXO][0] !== firstPIMXOArg0 || transformArgs[0] !== firstTransformArg0 || transformArgs[1] !== firstTransformArg1 || transformArgs[2] !== firstTransformArg2 || transformArgs[3] !== firstTransformArg3) {
if (q < MIN_IMAGES_IN_MASKS_BLOCK) {
isSameImage = false;
} else {
count = q;
}
break;
}
}
}
if (isSameImage) {
count = Math.min(count, MAX_SAME_IMAGES_IN_MASKS_BLOCK);
const positions = new Float32Array(count * 2);
iTransform = iFirstTransform;
for (let q = 0; q < count; q++, iTransform += 4) {
transformArgs = argsArray[iTransform];
positions[q << 1] = transformArgs[4];
positions[(q << 1) + 1] = transformArgs[5];
}
fnArray.splice(iFirstSave, count * 4, OPS.paintImageMaskXObjectRepeat);
argsArray.splice(iFirstSave, count * 4, [firstPIMXOArg0, firstTransformArg0, firstTransformArg1, firstTransformArg2, firstTransformArg3, positions]);
} else {
count = Math.min(count, MAX_IMAGES_IN_MASKS_BLOCK);
const images = [];
for (let q = 0; q < count; q++) {
transformArgs = argsArray[iFirstTransform + (q << 2)];
const maskParams = argsArray[iFirstPIMXO + (q << 2)][0];
images.push({
data: maskParams.data,
width: maskParams.width,
height: maskParams.height,
interpolate: maskParams.interpolate,
count: maskParams.count,
transform: transformArgs
});
}
fnArray.splice(iFirstSave, count * 4, OPS.paintImageMaskXObjectGroup);
argsArray.splice(iFirstSave, count * 4, [images]);
}
return iFirstSave + 1;
});
addState(InitialState, [OPS.save, OPS.transform, OPS.paintImageXObject, OPS.restore], function (context) {
const argsArray = context.argsArray;
const iFirstTransform = context.iCurr - 2;
return argsArray[iFirstTransform][1] === 0 && argsArray[iFirstTransform][2] === 0;
}, function iterateImageGroup(context, i) {
const fnArray = context.fnArray,
argsArray = context.argsArray;
const iFirstSave = context.iCurr - 3;
const pos = (i - iFirstSave) % 4;
switch (pos) {
case 0:
return fnArray[i] === OPS.save;
case 1:
if (fnArray[i] !== OPS.transform) {
return false;
}
const iFirstTransform = context.iCurr - 2;
const firstTransformArg0 = argsArray[iFirstTransform][0];
const firstTransformArg3 = argsArray[iFirstTransform][3];
if (argsArray[i][0] !== firstTransformArg0 || argsArray[i][1] !== 0 || argsArray[i][2] !== 0 || argsArray[i][3] !== firstTransformArg3) {
return false;
}
return true;
case 2:
if (fnArray[i] !== OPS.paintImageXObject) {
return false;
}
const iFirstPIXO = context.iCurr - 1;
const firstPIXOArg0 = argsArray[iFirstPIXO][0];
if (argsArray[i][0] !== firstPIXOArg0) {
return false;
}
return true;
case 3:
return fnArray[i] === OPS.restore;
}
throw new Error(`iterateImageGroup - invalid pos: ${pos}`);
}, function (context, i) {
const MIN_IMAGES_IN_BLOCK = 3;
const MAX_IMAGES_IN_BLOCK = 1000;
const fnArray = context.fnArray,
argsArray = context.argsArray;
const curr = context.iCurr;
const iFirstSave = curr - 3;
const iFirstTransform = curr - 2;
const iFirstPIXO = curr - 1;
const firstPIXOArg0 = argsArray[iFirstPIXO][0];
const firstTransformArg0 = argsArray[iFirstTransform][0];
const firstTransformArg3 = argsArray[iFirstTransform][3];
const count = Math.min(Math.floor((i - iFirstSave) / 4), MAX_IMAGES_IN_BLOCK);
if (count < MIN_IMAGES_IN_BLOCK) {
return i - (i - iFirstSave) % 4;
}
const positions = new Float32Array(count * 2);
let iTransform = iFirstTransform;
for (let q = 0; q < count; q++, iTransform += 4) {
const transformArgs = argsArray[iTransform];
positions[q << 1] = transformArgs[4];
positions[(q << 1) + 1] = transformArgs[5];
}
const args = [firstPIXOArg0, firstTransformArg0, firstTransformArg3, positions];
fnArray.splice(iFirstSave, count * 4, OPS.paintImageXObjectRepeat);
argsArray.splice(iFirstSave, count * 4, args);
return iFirstSave + 1;
});
addState(InitialState, [OPS.beginText, OPS.setFont, OPS.setTextMatrix, OPS.showText, OPS.endText], null, function iterateShowTextGroup(context, i) {
const fnArray = context.fnArray,
argsArray = context.argsArray;
const iFirstSave = context.iCurr - 4;
const pos = (i - iFirstSave) % 5;
switch (pos) {
case 0:
return fnArray[i] === OPS.beginText;
case 1:
return fnArray[i] === OPS.setFont;
case 2:
return fnArray[i] === OPS.setTextMatrix;
case 3:
if (fnArray[i] !== OPS.showText) {
return false;
}
const iFirstSetFont = context.iCurr - 3;
const firstSetFontArg0 = argsArray[iFirstSetFont][0];
const firstSetFontArg1 = argsArray[iFirstSetFont][1];
if (argsArray[i][0] !== firstSetFontArg0 || argsArray[i][1] !== firstSetFontArg1) {
return false;
}
return true;
case 4:
return fnArray[i] === OPS.endText;
}
throw new Error(`iterateShowTextGroup - invalid pos: ${pos}`);
}, function (context, i) {
const MIN_CHARS_IN_BLOCK = 3;
const MAX_CHARS_IN_BLOCK = 1000;
const fnArray = context.fnArray,
argsArray = context.argsArray;
const curr = context.iCurr;
const iFirstBeginText = curr - 4;
const iFirstSetFont = curr - 3;
const iFirstSetTextMatrix = curr - 2;
const iFirstShowText = curr - 1;
const iFirstEndText = curr;
const firstSetFontArg0 = argsArray[iFirstSetFont][0];
const firstSetFontArg1 = argsArray[iFirstSetFont][1];
let count = Math.min(Math.floor((i - iFirstBeginText) / 5), MAX_CHARS_IN_BLOCK);
if (count < MIN_CHARS_IN_BLOCK) {
return i - (i - iFirstBeginText) % 5;
}
let iFirst = iFirstBeginText;
if (iFirstBeginText >= 4 && fnArray[iFirstBeginText - 4] === fnArray[iFirstSetFont] && fnArray[iFirstBeginText - 3] === fnArray[iFirstSetTextMatrix] && fnArray[iFirstBeginText - 2] === fnArray[iFirstShowText] && fnArray[iFirstBeginText - 1] === fnArray[iFirstEndText] && argsArray[iFirstBeginText - 4][0] === firstSetFontArg0 && argsArray[iFirstBeginText - 4][1] === firstSetFontArg1) {
count++;
iFirst -= 5;
}
let iEndText = iFirst + 4;
for (let q = 1; q < count; q++) {
fnArray.splice(iEndText, 3);
argsArray.splice(iEndText, 3);
iEndText += 2;
}
return iEndText + 1;
});
class NullOptimizer {
constructor(queue) {
this.queue = queue;
}
_optimize() {}
push(fn, args) {
this.queue.fnArray.push(fn);
this.queue.argsArray.push(args);
this._optimize();
}
flush() {}
reset() {}
}
class QueueOptimizer extends NullOptimizer {
constructor(queue) {
super(queue);
this.state = null;
this.context = {
iCurr: 0,
fnArray: queue.fnArray,
argsArray: queue.argsArray,
isOffscreenCanvasSupported: false
};
this.match = null;
this.lastProcessed = 0;
}
set isOffscreenCanvasSupported(value) {
this.context.isOffscreenCanvasSupported = value;
}
_optimize() {
const fnArray = this.queue.fnArray;
let i = this.lastProcessed,
ii = fnArray.length;
let state = this.state;
let match = this.match;
if (!state && !match && i + 1 === ii && !InitialState[fnArray[i]]) {
this.lastProcessed = ii;
return;
}
const context = this.context;
while (i < ii) {
if (match) {
const iterate = (0, match.iterateFn)(context, i);
if (iterate) {
i++;
continue;
}
i = (0, match.processFn)(context, i + 1);
ii = fnArray.length;
match = null;
state = null;
if (i >= ii) {
break;
}
}
state = (state || InitialState)[fnArray[i]];
if (!state || Array.isArray(state)) {
i++;
continue;
}
context.iCurr = i;
i++;
if (state.checkFn && !(0, state.checkFn)(context)) {
state = null;
continue;
}
match = state;
state = null;
}
this.state = state;
this.match = match;
this.lastProcessed = i;
}
flush() {
while (this.match) {
const length = this.queue.fnArray.length;
this.lastProcessed = (0, this.match.processFn)(this.context, length);
this.match = null;
this.state = null;
this._optimize();
}
}
reset() {
this.state = null;
this.match = null;
this.lastProcessed = 0;
}
}
class OperatorList {
static CHUNK_SIZE = 1000;
static CHUNK_SIZE_ABOUT = this.CHUNK_SIZE - 5;
constructor(intent = 0, streamSink) {
this._streamSink = streamSink;
this.fnArray = [];
this.argsArray = [];
this.optimizer = streamSink && !(intent & RenderingIntentFlag.OPLIST) ? new QueueOptimizer(this) : new NullOptimizer(this);
this.dependencies = new Set();
this._totalLength = 0;
this.weight = 0;
this._resolved = streamSink ? null : Promise.resolve();
}
set isOffscreenCanvasSupported(value) {
this.optimizer.isOffscreenCanvasSupported = value;
}
get length() {
return this.argsArray.length;
}
get ready() {
return this._resolved || this._streamSink.ready;
}
get totalLength() {
return this._totalLength + this.length;
}
addOp(fn, args) {
this.optimizer.push(fn, args);
this.weight++;
if (this._streamSink) {
if (this.weight >= OperatorList.CHUNK_SIZE) {
this.flush();
} else if (this.weight >= OperatorList.CHUNK_SIZE_ABOUT && (fn === OPS.restore || fn === OPS.endText)) {
this.flush();
}
}
}
addImageOps(fn, args, optionalContent, hasMask = false) {
if (hasMask) {
this.addOp(OPS.save);
this.addOp(OPS.setGState, [[["SMask", false]]]);
}
if (optionalContent !== undefined) {
this.addOp(OPS.beginMarkedContentProps, ["OC", optionalContent]);
}
this.addOp(fn, args);
if (optionalContent !== undefined) {
this.addOp(OPS.endMarkedContent, []);
}
if (hasMask) {
this.addOp(OPS.restore);
}
}
addDependency(dependency) {
if (this.dependencies.has(dependency)) {
return;
}
this.dependencies.add(dependency);
this.addOp(OPS.dependency, [dependency]);
}
addDependencies(dependencies) {
for (const dependency of dependencies) {
this.addDependency(dependency);
}
}
addOpList(opList) {
if (!(opList instanceof OperatorList)) {
warn('addOpList - ignoring invalid "opList" parameter.');
return;
}
for (const dependency of opList.dependencies) {
this.dependencies.add(dependency);
}
for (let i = 0, ii = opList.length; i < ii; i++) {
this.addOp(opList.fnArray[i], opList.argsArray[i]);
}
}
getIR() {
return {
fnArray: this.fnArray,
argsArray: this.argsArray,
length: this.length
};
}
get _transfers() {
const transfers = [];
const {
fnArray,
argsArray,
length
} = this;
for (let i = 0; i < length; i++) {
switch (fnArray[i]) {
case OPS.paintInlineImageXObject:
case OPS.paintInlineImageXObjectGroup:
case OPS.paintImageMaskXObject:
const arg = argsArray[i][0];
if (!arg.cached && arg.data?.buffer instanceof ArrayBuffer) {
transfers.push(arg.data.buffer);
}
break;
case OPS.constructPath:
const [, [data], minMax] = argsArray[i];
if (data) {
transfers.push(data.buffer, minMax.buffer);
}
break;
}
}
return transfers;
}
flush(lastChunk = false, separateAnnots = null) {
this.optimizer.flush();
const length = this.length;
this._totalLength += length;
this._streamSink.enqueue({
fnArray: this.fnArray,
argsArray: this.argsArray,
lastChunk,
separateAnnots,
length
}, 1, this._transfers);
this.dependencies.clear();
this.fnArray.length = 0;
this.argsArray.length = 0;
this.weight = 0;
this.optimizer.reset();
}
}
;// ./src/core/image.js
function resizeImageMask(src, bpc, w1, h1, w2, h2) {
const length = w2 * h2;
let dest;
if (bpc <= 8) {
dest = new Uint8Array(length);
} else if (bpc <= 16) {
dest = new Uint16Array(length);
} else {
dest = new Uint32Array(length);
}
const xRatio = w1 / w2;
const yRatio = h1 / h2;
let i,
j,
py,
newIndex = 0,
oldIndex;
const xScaled = new Uint16Array(w2);
const w1Scanline = w1;
for (i = 0; i < w2; i++) {
xScaled[i] = Math.floor(i * xRatio);
}
for (i = 0; i < h2; i++) {
py = Math.floor(i * yRatio) * w1Scanline;
for (j = 0; j < w2; j++) {
oldIndex = py + xScaled[j];
dest[newIndex++] = src[oldIndex];
}
}
return dest;
}
class PDFImage {
constructor({
xref,
res,
image,
isInline = false,
smask = null,
mask = null,
isMask = false,
pdfFunctionFactory,
globalColorSpaceCache,
localColorSpaceCache
}) {
this.image = image;
const dict = image.dict;
const filter = dict.get("F", "Filter");
let filterName;
if (filter instanceof Name) {
filterName = filter.name;
} else if (Array.isArray(filter)) {
const filterZero = xref.fetchIfRef(filter[0]);
if (filterZero instanceof Name) {
filterName = filterZero.name;
}
}
switch (filterName) {
case "JPXDecode":
({
width: image.width,
height: image.height,
componentsCount: image.numComps,
bitsPerComponent: image.bitsPerComponent
} = JpxImage.parseImageProperties(image.stream));
image.stream.reset();
this.jpxDecoderOptions = {
numComponents: 0,
isIndexedColormap: false,
smaskInData: dict.has("SMaskInData")
};
break;
case "JBIG2Decode":
image.bitsPerComponent = 1;
image.numComps = 1;
break;
}
let width = dict.get("W", "Width");
let height = dict.get("H", "Height");
if (Number.isInteger(image.width) && image.width > 0 && Number.isInteger(image.height) && image.height > 0 && (image.width !== width || image.height !== height)) {
warn("PDFImage - using the Width/Height of the image data, " + "rather than the image dictionary.");
width = image.width;
height = image.height;
} else {
const validWidth = typeof width === "number" && width > 0,
validHeight = typeof height === "number" && height > 0;
if (!validWidth || !validHeight) {
if (!image.fallbackDims) {
throw new FormatError(`Invalid image width: ${width} or height: ${height}`);
}
warn("PDFImage - using the Width/Height of the parent image, for SMask/Mask data.");
if (!validWidth) {
width = image.fallbackDims.width;
}
if (!validHeight) {
height = image.fallbackDims.height;
}
}
}
this.width = width;
this.height = height;
this.interpolate = dict.get("I", "Interpolate");
this.imageMask = dict.get("IM", "ImageMask") || false;
this.matte = dict.get("Matte") || false;
let bitsPerComponent = image.bitsPerComponent;
if (!bitsPerComponent) {
bitsPerComponent = dict.get("BPC", "BitsPerComponent");
if (!bitsPerComponent) {
if (this.imageMask) {
bitsPerComponent = 1;
} else {
throw new FormatError(`Bits per component missing in image: ${this.imageMask}`);
}
}
}
this.bpc = bitsPerComponent;
if (!this.imageMask) {
let colorSpace = dict.getRaw("CS") || dict.getRaw("ColorSpace");
const hasColorSpace = !!colorSpace;
if (!hasColorSpace) {
if (this.jpxDecoderOptions) {
colorSpace = Name.get("DeviceRGBA");
} else {
switch (image.numComps) {
case 1:
colorSpace = Name.get("DeviceGray");
break;
case 3:
colorSpace = Name.get("DeviceRGB");
break;
case 4:
colorSpace = Name.get("DeviceCMYK");
break;
default:
throw new Error(`Images with ${image.numComps} color components not supported.`);
}
}
} else if (this.jpxDecoderOptions?.smaskInData) {
colorSpace = Name.get("DeviceRGBA");
}
this.colorSpace = ColorSpaceUtils.parse({
cs: colorSpace,
xref,
resources: isInline ? res : null,
pdfFunctionFactory,
globalColorSpaceCache,
localColorSpaceCache
});
this.numComps = this.colorSpace.numComps;
if (this.jpxDecoderOptions) {
this.jpxDecoderOptions.numComponents = hasColorSpace ? this.numComps : 0;
this.jpxDecoderOptions.isIndexedColormap = this.colorSpace.name === "Indexed";
}
}
this.decode = dict.getArray("D", "Decode");
this.needsDecode = false;
if (this.decode && (this.colorSpace && !this.colorSpace.isDefaultDecode(this.decode, bitsPerComponent) || isMask && !ColorSpace.isDefaultDecode(this.decode, 1))) {
this.needsDecode = true;
const max = (1 << bitsPerComponent) - 1;
this.decodeCoefficients = [];
this.decodeAddends = [];
const isIndexed = this.colorSpace?.name === "Indexed";
for (let i = 0, j = 0; i < this.decode.length; i += 2, ++j) {
const dmin = this.decode[i];
const dmax = this.decode[i + 1];
this.decodeCoefficients[j] = isIndexed ? (dmax - dmin) / max : dmax - dmin;
this.decodeAddends[j] = isIndexed ? dmin : max * dmin;
}
}
if (smask) {
smask.fallbackDims ??= {
width,
height
};
this.smask = new PDFImage({
xref,
res,
image: smask,
isInline,
pdfFunctionFactory,
globalColorSpaceCache,
localColorSpaceCache
});
} else if (mask) {
if (mask instanceof BaseStream) {
const maskDict = mask.dict,
imageMask = maskDict.get("IM", "ImageMask");
if (!imageMask) {
warn("Ignoring /Mask in image without /ImageMask.");
} else {
mask.fallbackDims ??= {
width,
height
};
this.mask = new PDFImage({
xref,
res,
image: mask,
isInline,
isMask: true,
pdfFunctionFactory,
globalColorSpaceCache,
localColorSpaceCache
});
}
} else {
this.mask = mask;
}
}
}
static async buildImage({
xref,
res,
image,
isInline = false,
pdfFunctionFactory,
globalColorSpaceCache,
localColorSpaceCache
}) {
const imageData = image;
let smaskData = null;
let maskData = null;
const smask = image.dict.get("SMask");
const mask = image.dict.get("Mask");
if (smask) {
if (smask instanceof BaseStream) {
smaskData = smask;
} else {
warn("Unsupported /SMask format.");
}
} else if (mask) {
if (mask instanceof BaseStream || Array.isArray(mask)) {
maskData = mask;
} else {
warn("Unsupported /Mask format.");
}
}
return new PDFImage({
xref,
res,
image: imageData,
isInline,
smask: smaskData,
mask: maskData,
pdfFunctionFactory,
globalColorSpaceCache,
localColorSpaceCache
});
}
static createRawMask({
imgArray,
width,
height,
imageIsFromDecodeStream,
inverseDecode,
interpolate
}) {
const computedLength = (width + 7 >> 3) * height;
const actualLength = imgArray.byteLength;
const haveFullData = computedLength === actualLength;
let data, i;
if (imageIsFromDecodeStream && (!inverseDecode || haveFullData)) {
data = imgArray;
} else if (!inverseDecode) {
data = new Uint8Array(imgArray);
} else {
data = new Uint8Array(computedLength);
data.set(imgArray);
data.fill(0xff, actualLength);
}
if (inverseDecode) {
for (i = 0; i < actualLength; i++) {
data[i] ^= 0xff;
}
}
return {
data,
width,
height,
interpolate
};
}
static async createMask({
imgArray,
width,
height,
imageIsFromDecodeStream,
inverseDecode,
interpolate,
isOffscreenCanvasSupported = false
}) {
const isSingleOpaquePixel = width === 1 && height === 1 && inverseDecode === (imgArray.length === 0 || !!(imgArray[0] & 128));
if (isSingleOpaquePixel) {
return {
isSingleOpaquePixel
};
}
if (isOffscreenCanvasSupported) {
if (ImageResizer.needsToBeResized(width, height)) {
const data = new Uint8ClampedArray(width * height * 4);
convertBlackAndWhiteToRGBA({
src: imgArray,
dest: data,
width,
height,
nonBlackColor: 0,
inverseDecode
});
return ImageResizer.createImage({
kind: ImageKind.RGBA_32BPP,
data,
width,
height,
interpolate
});
}
const canvas = new OffscreenCanvas(width, height);
const ctx = canvas.getContext("2d");
const imgData = ctx.createImageData(width, height);
convertBlackAndWhiteToRGBA({
src: imgArray,
dest: imgData.data,
width,
height,
nonBlackColor: 0,
inverseDecode
});
ctx.putImageData(imgData, 0, 0);
const bitmap = canvas.transferToImageBitmap();
return {
data: null,
width,
height,
interpolate,
bitmap
};
}
return this.createRawMask({
imgArray,
width,
height,
inverseDecode,
imageIsFromDecodeStream,
interpolate
});
}
get drawWidth() {
return Math.max(this.width, this.smask?.width || 0, this.mask?.width || 0);
}
get drawHeight() {
return Math.max(this.height, this.smask?.height || 0, this.mask?.height || 0);
}
decodeBuffer(buffer) {
const bpc = this.bpc;
const numComps = this.numComps;
const decodeAddends = this.decodeAddends;
const decodeCoefficients = this.decodeCoefficients;
const max = (1 << bpc) - 1;
let i, ii;
if (bpc === 1) {
for (i = 0, ii = buffer.length; i < ii; i++) {
buffer[i] = +!buffer[i];
}
return;
}
let index = 0;
for (i = 0, ii = this.width * this.height; i < ii; i++) {
for (let j = 0; j < numComps; j++) {
buffer[index] = MathClamp(decodeAddends[j] + buffer[index] * decodeCoefficients[j], 0, max);
index++;
}
}
}
getComponents(buffer) {
const bpc = this.bpc;
if (bpc === 8) {
return buffer;
}
const width = this.width;
const height = this.height;
const numComps = this.numComps;
const length = width * height * numComps;
let bufferPos = 0;
let output;
if (bpc <= 8) {
output = new Uint8Array(length);
} else if (bpc <= 16) {
output = new Uint16Array(length);
} else {
output = new Uint32Array(length);
}
const rowComps = width * numComps;
const max = (1 << bpc) - 1;
let i = 0,
ii,
buf;
if (bpc === 1) {
let mask, loop1End, loop2End;
for (let j = 0; j < height; j++) {
loop1End = i + (rowComps & ~7);
loop2End = i + rowComps;
while (i < loop1End) {
buf = buffer[bufferPos++];
output[i] = buf >> 7 & 1;
output[i + 1] = buf >> 6 & 1;
output[i + 2] = buf >> 5 & 1;
output[i + 3] = buf >> 4 & 1;
output[i + 4] = buf >> 3 & 1;
output[i + 5] = buf >> 2 & 1;
output[i + 6] = buf >> 1 & 1;
output[i + 7] = buf & 1;
i += 8;
}
if (i < loop2End) {
buf = buffer[bufferPos++];
mask = 128;
while (i < loop2End) {
output[i++] = +!!(buf & mask);
mask >>= 1;
}
}
}
} else {
let bits = 0;
buf = 0;
for (i = 0, ii = length; i < ii; ++i) {
if (i % rowComps === 0) {
buf = 0;
bits = 0;
}
while (bits < bpc) {
buf = buf << 8 | buffer[bufferPos++];
bits += 8;
}
const remainingBits = bits - bpc;
let value = buf >> remainingBits;
if (value < 0) {
value = 0;
} else if (value > max) {
value = max;
}
output[i] = value;
buf &= (1 << remainingBits) - 1;
bits = remainingBits;
}
}
return output;
}
async fillOpacity(rgbaBuf, width, height, actualHeight, image) {
const smask = this.smask;
const mask = this.mask;
let alphaBuf, sw, sh, i, ii, j;
if (smask) {
sw = smask.width;
sh = smask.height;
alphaBuf = new Uint8ClampedArray(sw * sh);
await smask.fillGrayBuffer(alphaBuf);
if (sw !== width || sh !== height) {
alphaBuf = resizeImageMask(alphaBuf, smask.bpc, sw, sh, width, height);
}
} else if (mask) {
if (mask instanceof PDFImage) {
sw = mask.width;
sh = mask.height;
alphaBuf = new Uint8ClampedArray(sw * sh);
mask.numComps = 1;
await mask.fillGrayBuffer(alphaBuf);
for (i = 0, ii = sw * sh; i < ii; ++i) {
alphaBuf[i] = 255 - alphaBuf[i];
}
if (sw !== width || sh !== height) {
alphaBuf = resizeImageMask(alphaBuf, mask.bpc, sw, sh, width, height);
}
} else if (Array.isArray(mask)) {
alphaBuf = new Uint8ClampedArray(width * height);
const numComps = this.numComps;
for (i = 0, ii = width * height; i < ii; ++i) {
let opacity = 0;
const imageOffset = i * numComps;
for (j = 0; j < numComps; ++j) {
const color = image[imageOffset + j];
const maskOffset = j * 2;
if (color < mask[maskOffset] || color > mask[maskOffset + 1]) {
opacity = 255;
break;
}
}
alphaBuf[i] = opacity;
}
} else {
throw new FormatError("Unknown mask format.");
}
}
if (alphaBuf) {
for (i = 0, j = 3, ii = width * actualHeight; i < ii; ++i, j += 4) {
rgbaBuf[j] = alphaBuf[i];
}
} else {
for (i = 0, j = 3, ii = width * actualHeight; i < ii; ++i, j += 4) {
rgbaBuf[j] = 255;
}
}
}
undoPreblend(buffer, width, height) {
const matte = this.smask?.matte;
if (!matte) {
return;
}
const matteRgb = this.colorSpace.getRgb(matte, 0);
const matteR = matteRgb[0];
const matteG = matteRgb[1];
const matteB = matteRgb[2];
const length = width * height * 4;
for (let i = 0; i < length; i += 4) {
const alpha = buffer[i + 3];
if (alpha === 0) {
buffer[i] = 255;
buffer[i + 1] = 255;
buffer[i + 2] = 255;
continue;
}
const k = 255 / alpha;
buffer[i] = (buffer[i] - matteR) * k + matteR;
buffer[i + 1] = (buffer[i + 1] - matteG) * k + matteG;
buffer[i + 2] = (buffer[i + 2] - matteB) * k + matteB;
}
}
async createImageData(forceRGBA = false, isOffscreenCanvasSupported = false) {
const drawWidth = this.drawWidth;
const drawHeight = this.drawHeight;
const imgData = {
width: drawWidth,
height: drawHeight,
interpolate: this.interpolate,
kind: 0,
data: null
};
const numComps = this.numComps;
const originalWidth = this.width;
const originalHeight = this.height;
const bpc = this.bpc;
const rowBytes = originalWidth * numComps * bpc + 7 >> 3;
const mustBeResized = isOffscreenCanvasSupported && ImageResizer.needsToBeResized(drawWidth, drawHeight);
if (!this.smask && !this.mask && this.colorSpace.name === "DeviceRGBA") {
imgData.kind = ImageKind.RGBA_32BPP;
const imgArray = imgData.data = await this.getImageBytes(originalHeight * originalWidth * 4, {});
if (isOffscreenCanvasSupported) {
if (!mustBeResized) {
return this.createBitmap(ImageKind.RGBA_32BPP, drawWidth, drawHeight, imgArray);
}
return ImageResizer.createImage(imgData, false);
}
return imgData;
}
if (!forceRGBA) {
let kind;
if (this.colorSpace.name === "DeviceGray" && bpc === 1) {
kind = ImageKind.GRAYSCALE_1BPP;
} else if (this.colorSpace.name === "DeviceRGB" && bpc === 8 && !this.needsDecode) {
kind = ImageKind.RGB_24BPP;
}
if (kind && !this.smask && !this.mask && drawWidth === originalWidth && drawHeight === originalHeight) {
const image = await this.#getImage(originalWidth, originalHeight);
if (image) {
return image;
}
const data = await this.getImageBytes(originalHeight * rowBytes, {});
if (isOffscreenCanvasSupported) {
if (mustBeResized) {
return ImageResizer.createImage({
data,
kind,
width: drawWidth,
height: drawHeight,
interpolate: this.interpolate
}, this.needsDecode);
}
return this.createBitmap(kind, originalWidth, originalHeight, data);
}
imgData.kind = kind;
imgData.data = data;
if (this.needsDecode) {
assert(kind === ImageKind.GRAYSCALE_1BPP, "PDFImage.createImageData: The image must be grayscale.");
const buffer = imgData.data;
for (let i = 0, ii = buffer.length; i < ii; i++) {
buffer[i] ^= 0xff;
}
}
return imgData;
}
if (this.image instanceof JpegStream && !this.smask && !this.mask && !this.needsDecode) {
let imageLength = originalHeight * rowBytes;
if (isOffscreenCanvasSupported && !mustBeResized) {
let isHandled = false;
switch (this.colorSpace.name) {
case "DeviceGray":
imageLength *= 4;
isHandled = true;
break;
case "DeviceRGB":
imageLength = imageLength / 3 * 4;
isHandled = true;
break;
case "DeviceCMYK":
isHandled = true;
break;
}
if (isHandled) {
const image = await this.#getImage(drawWidth, drawHeight);
if (image) {
return image;
}
const rgba = await this.getImageBytes(imageLength, {
drawWidth,
drawHeight,
forceRGBA: true
});
return this.createBitmap(ImageKind.RGBA_32BPP, drawWidth, drawHeight, rgba);
}
} else {
switch (this.colorSpace.name) {
case "DeviceGray":
imageLength *= 3;
case "DeviceRGB":
case "DeviceCMYK":
imgData.kind = ImageKind.RGB_24BPP;
imgData.data = await this.getImageBytes(imageLength, {
drawWidth,
drawHeight,
forceRGB: true
});
if (mustBeResized) {
return ImageResizer.createImage(imgData);
}
return imgData;
}
}
}
}
const imgArray = await this.getImageBytes(originalHeight * rowBytes, {
internal: true
});
const actualHeight = 0 | imgArray.length / rowBytes * drawHeight / originalHeight;
const comps = this.getComponents(imgArray);
let alpha01, maybeUndoPreblend;
let canvas, ctx, canvasImgData, data;
if (isOffscreenCanvasSupported && !mustBeResized) {
canvas = new OffscreenCanvas(drawWidth, drawHeight);
ctx = canvas.getContext("2d");
canvasImgData = ctx.createImageData(drawWidth, drawHeight);
data = canvasImgData.data;
}
imgData.kind = ImageKind.RGBA_32BPP;
if (!forceRGBA && !this.smask && !this.mask) {
if (!isOffscreenCanvasSupported || mustBeResized) {
imgData.kind = ImageKind.RGB_24BPP;
data = new Uint8ClampedArray(drawWidth * drawHeight * 3);
alpha01 = 0;
} else {
const arr = new Uint32Array(data.buffer);
arr.fill(FeatureTest.isLittleEndian ? 0xff000000 : 0x000000ff);
alpha01 = 1;
}
maybeUndoPreblend = false;
} else {
if (!isOffscreenCanvasSupported || mustBeResized) {
data = new Uint8ClampedArray(drawWidth * drawHeight * 4);
}
alpha01 = 1;
maybeUndoPreblend = true;
await this.fillOpacity(data, drawWidth, drawHeight, actualHeight, comps);
}
if (this.needsDecode) {
this.decodeBuffer(comps);
}
this.colorSpace.fillRgb(data, originalWidth, originalHeight, drawWidth, drawHeight, actualHeight, bpc, comps, alpha01);
if (maybeUndoPreblend) {
this.undoPreblend(data, drawWidth, actualHeight);
}
if (isOffscreenCanvasSupported && !mustBeResized) {
ctx.putImageData(canvasImgData, 0, 0);
const bitmap = canvas.transferToImageBitmap();
return {
data: null,
width: drawWidth,
height: drawHeight,
bitmap,
interpolate: this.interpolate
};
}
imgData.data = data;
if (mustBeResized) {
return ImageResizer.createImage(imgData);
}
return imgData;
}
async fillGrayBuffer(buffer) {
const numComps = this.numComps;
if (numComps !== 1) {
throw new FormatError(`Reading gray scale from a color image: ${numComps}`);
}
const width = this.width;
const height = this.height;
const bpc = this.bpc;
const rowBytes = width * numComps * bpc + 7 >> 3;
const imgArray = await this.getImageBytes(height * rowBytes, {
internal: true
});
const comps = this.getComponents(imgArray);
let i, length;
if (bpc === 1) {
length = width * height;
if (this.needsDecode) {
for (i = 0; i < length; ++i) {
buffer[i] = comps[i] - 1 & 255;
}
} else {
for (i = 0; i < length; ++i) {
buffer[i] = -comps[i] & 255;
}
}
return;
}
if (this.needsDecode) {
this.decodeBuffer(comps);
}
length = width * height;
const scale = 255 / ((1 << bpc) - 1);
for (i = 0; i < length; ++i) {
buffer[i] = scale * comps[i];
}
}
createBitmap(kind, width, height, src) {
const canvas = new OffscreenCanvas(width, height);
const ctx = canvas.getContext("2d");
let imgData;
if (kind === ImageKind.RGBA_32BPP) {
imgData = new ImageData(src, width, height);
} else {
imgData = ctx.createImageData(width, height);
convertToRGBA({
kind,
src,
dest: new Uint32Array(imgData.data.buffer),
width,
height,
inverseDecode: this.needsDecode
});
}
ctx.putImageData(imgData, 0, 0);
const bitmap = canvas.transferToImageBitmap();
return {
data: null,
width,
height,
bitmap,
interpolate: this.interpolate
};
}
async #getImage(width, height) {
const bitmap = await this.image.getTransferableImage();
if (!bitmap) {
return null;
}
return {
data: null,
width,
height,
bitmap,
interpolate: this.interpolate
};
}
async getImageBytes(length, {
drawWidth,
drawHeight,
forceRGBA = false,
forceRGB = false,
internal = false
}) {
this.image.reset();
this.image.drawWidth = drawWidth || this.width;
this.image.drawHeight = drawHeight || this.height;
this.image.forceRGBA = !!forceRGBA;
this.image.forceRGB = !!forceRGB;
const imageBytes = await this.image.getImageData(length, this.jpxDecoderOptions);
if (internal || this.image instanceof DecodeStream) {
return imageBytes;
}
assert(imageBytes instanceof Uint8Array, 'PDFImage.getImageBytes: Unsupported "imageBytes" type.');
return new Uint8Array(imageBytes);
}
}
;// ./src/core/evaluator.js
const DefaultPartialEvaluatorOptions = Object.freeze({
maxImageSize: -1,
disableFontFace: false,
ignoreErrors: false,
isEvalSupported: true,
isOffscreenCanvasSupported: false,
isImageDecoderSupported: false,
canvasMaxAreaInBytes: -1,
fontExtraProperties: false,
useSystemFonts: true,
useWasm: true,
useWorkerFetch: true,
cMapUrl: null,
iccUrl: null,
standardFontDataUrl: null,
wasmUrl: null
});
const PatternType = {
TILING: 1,
SHADING: 2
};
const TEXT_CHUNK_BATCH_SIZE = 10;
const deferred = Promise.resolve();
function normalizeBlendMode(value, parsingArray = false) {
if (Array.isArray(value)) {
for (const val of value) {
const maybeBM = normalizeBlendMode(val, true);
if (maybeBM) {
return maybeBM;
}
}
warn(`Unsupported blend mode Array: ${value}`);
return "source-over";
}
if (!(value instanceof Name)) {
if (parsingArray) {
return null;
}
return "source-over";
}
switch (value.name) {
case "Normal":
case "Compatible":
return "source-over";
case "Multiply":
return "multiply";
case "Screen":
return "screen";
case "Overlay":
return "overlay";
case "Darken":
return "darken";
case "Lighten":
return "lighten";
case "ColorDodge":
return "color-dodge";
case "ColorBurn":
return "color-burn";
case "HardLight":
return "hard-light";
case "SoftLight":
return "soft-light";
case "Difference":
return "difference";
case "Exclusion":
return "exclusion";
case "Hue":
return "hue";
case "Saturation":
return "saturation";
case "Color":
return "color";
case "Luminosity":
return "luminosity";
}
if (parsingArray) {
return null;
}
warn(`Unsupported blend mode: ${value.name}`);
return "source-over";
}
function addLocallyCachedImageOps(opList, data) {
if (data.objId) {
opList.addDependency(data.objId);
}
opList.addImageOps(data.fn, data.args, data.optionalContent, data.hasMask);
if (data.fn === OPS.paintImageMaskXObject && data.args[0]?.count > 0) {
data.args[0].count++;
}
}
class TimeSlotManager {
static TIME_SLOT_DURATION_MS = 20;
static CHECK_TIME_EVERY = 100;
constructor() {
this.reset();
}
check() {
if (++this.checked < TimeSlotManager.CHECK_TIME_EVERY) {
return false;
}
this.checked = 0;
return this.endTime <= Date.now();
}
reset() {
this.endTime = Date.now() + TimeSlotManager.TIME_SLOT_DURATION_MS;
this.checked = 0;
}
}
class PartialEvaluator {
constructor({
xref,
handler,
pageIndex,
idFactory,
fontCache,
builtInCMapCache,
standardFontDataCache,
globalColorSpaceCache,
globalImageCache,
systemFontCache,
options = null
}) {
this.xref = xref;
this.handler = handler;
this.pageIndex = pageIndex;
this.idFactory = idFactory;
this.fontCache = fontCache;
this.builtInCMapCache = builtInCMapCache;
this.standardFontDataCache = standardFontDataCache;
this.globalColorSpaceCache = globalColorSpaceCache;
this.globalImageCache = globalImageCache;
this.systemFontCache = systemFontCache;
this.options = options || DefaultPartialEvaluatorOptions;
this.type3FontRefs = null;
this._regionalImageCache = new RegionalImageCache();
this._fetchBuiltInCMapBound = this.fetchBuiltInCMap.bind(this);
}
get _pdfFunctionFactory() {
const pdfFunctionFactory = new PDFFunctionFactory({
xref: this.xref,
isEvalSupported: this.options.isEvalSupported
});
return shadow(this, "_pdfFunctionFactory", pdfFunctionFactory);
}
get parsingType3Font() {
return !!this.type3FontRefs;
}
clone(newOptions = null) {
const newEvaluator = Object.create(this);
newEvaluator.options = Object.assign(Object.create(null), this.options, newOptions);
return newEvaluator;
}
hasBlendModes(resources, nonBlendModesSet) {
if (!(resources instanceof Dict)) {
return false;
}
if (resources.objId && nonBlendModesSet.has(resources.objId)) {
return false;
}
const processed = new RefSet(nonBlendModesSet);
if (resources.objId) {
processed.put(resources.objId);
}
const nodes = [resources],
xref = this.xref;
while (nodes.length) {
const node = nodes.shift();
const graphicStates = node.get("ExtGState");
if (graphicStates instanceof Dict) {
for (let graphicState of graphicStates.getRawValues()) {
if (graphicState instanceof Ref) {
if (processed.has(graphicState)) {
continue;
}
try {
graphicState = xref.fetch(graphicState);
} catch (ex) {
processed.put(graphicState);
info(`hasBlendModes - ignoring ExtGState: "${ex}".`);
continue;
}
}
if (!(graphicState instanceof Dict)) {
continue;
}
if (graphicState.objId) {
processed.put(graphicState.objId);
}
const bm = graphicState.get("BM");
if (bm instanceof Name) {
if (bm.name !== "Normal") {
return true;
}
continue;
}
if (bm !== undefined && Array.isArray(bm)) {
for (const element of bm) {
if (element instanceof Name && element.name !== "Normal") {
return true;
}
}
}
}
}
const xObjects = node.get("XObject");
if (!(xObjects instanceof Dict)) {
continue;
}
for (let xObject of xObjects.getRawValues()) {
if (xObject instanceof Ref) {
if (processed.has(xObject)) {
continue;
}
try {
xObject = xref.fetch(xObject);
} catch (ex) {
processed.put(xObject);
info(`hasBlendModes - ignoring XObject: "${ex}".`);
continue;
}
}
if (!(xObject instanceof BaseStream)) {
continue;
}
if (xObject.dict.objId) {
processed.put(xObject.dict.objId);
}
const xResources = xObject.dict.get("Resources");
if (!(xResources instanceof Dict)) {
continue;
}
if (xResources.objId && processed.has(xResources.objId)) {
continue;
}
nodes.push(xResources);
if (xResources.objId) {
processed.put(xResources.objId);
}
}
}
for (const ref of processed) {
nonBlendModesSet.put(ref);
}
return false;
}
async fetchBuiltInCMap(name) {
const cachedData = this.builtInCMapCache.get(name);
if (cachedData) {
return cachedData;
}
let data;
if (this.options.useWorkerFetch) {
data = {
cMapData: await fetchBinaryData(`${this.options.cMapUrl}${name}.bcmap`),
isCompressed: true
};
} else {
data = await this.handler.sendWithPromise("FetchBinaryData", {
type: "cMapReaderFactory",
name
});
}
this.builtInCMapCache.set(name, data);
return data;
}
async fetchStandardFontData(name) {
const cachedData = this.standardFontDataCache.get(name);
if (cachedData) {
return new Stream(cachedData);
}
if (this.options.useSystemFonts && name !== "Symbol" && name !== "ZapfDingbats") {
return null;
}
const standardFontNameToFileName = getFontNameToFileMap(),
filename = standardFontNameToFileName[name];
let data;
try {
if (this.options.useWorkerFetch) {
data = await fetchBinaryData(`${this.options.standardFontDataUrl}${filename}`);
} else {
data = await this.handler.sendWithPromise("FetchBinaryData", {
type: "standardFontDataFactory",
filename
});
}
} catch (ex) {
warn(ex);
return null;
}
this.standardFontDataCache.set(name, data);
return new Stream(data);
}
async buildFormXObject(resources, xobj, smask, operatorList, task, initialState, localColorSpaceCache) {
const dict = xobj.dict;
const matrix = lookupMatrix(dict.getArray("Matrix"), null);
const bbox = lookupNormalRect(dict.getArray("BBox"), null);
let optionalContent, groupOptions;
if (dict.has("OC")) {
optionalContent = await this.parseMarkedContentProps(dict.get("OC"), resources);
}
if (optionalContent !== undefined) {
operatorList.addOp(OPS.beginMarkedContentProps, ["OC", optionalContent]);
}
const group = dict.get("Group");
if (group) {
groupOptions = {
matrix,
bbox,
smask,
isolated: false,
knockout: false
};
const groupSubtype = group.get("S");
let colorSpace = null;
if (isName(groupSubtype, "Transparency")) {
groupOptions.isolated = group.get("I") || false;
groupOptions.knockout = group.get("K") || false;
if (group.has("CS")) {
const cs = this._getColorSpace(group.getRaw("CS"), resources, localColorSpaceCache);
colorSpace = cs instanceof ColorSpace ? cs : await this._handleColorSpace(cs);
}
}
if (smask?.backdrop) {
colorSpace ||= ColorSpaceUtils.rgb;
smask.backdrop = colorSpace.getRgb(smask.backdrop, 0);
}
operatorList.addOp(OPS.beginGroup, [groupOptions]);
}
const args = group ? [matrix, null] : [matrix, bbox];
operatorList.addOp(OPS.paintFormXObjectBegin, args);
await this.getOperatorList({
stream: xobj,
task,
resources: dict.get("Resources") || resources,
operatorList,
initialState
});
operatorList.addOp(OPS.paintFormXObjectEnd, []);
if (group) {
operatorList.addOp(OPS.endGroup, [groupOptions]);
}
if (optionalContent !== undefined) {
operatorList.addOp(OPS.endMarkedContent, []);
}
}
_sendImgData(objId, imgData, cacheGlobally = false) {
const transfers = imgData ? [imgData.bitmap || imgData.data.buffer] : null;
if (this.parsingType3Font || cacheGlobally) {
return this.handler.send("commonobj", [objId, "Image", imgData], transfers);
}
return this.handler.send("obj", [objId, this.pageIndex, "Image", imgData], transfers);
}
async buildPaintImageXObject({
resources,
image,
isInline = false,
operatorList,
cacheKey,
localImageCache,
localColorSpaceCache
}) {
const dict = image.dict;
const imageRef = dict.objId;
const w = dict.get("W", "Width");
const h = dict.get("H", "Height");
if (!(w && typeof w === "number") || !(h && typeof h === "number")) {
warn("Image dimensions are missing, or not numbers.");
return;
}
const maxImageSize = this.options.maxImageSize;
if (maxImageSize !== -1 && w * h > maxImageSize) {
const msg = "Image exceeded maximum allowed size and was removed.";
if (this.options.ignoreErrors) {
warn(msg);
return;
}
throw new Error(msg);
}
let optionalContent;
if (dict.has("OC")) {
optionalContent = await this.parseMarkedContentProps(dict.get("OC"), resources);
}
const imageMask = dict.get("IM", "ImageMask") || false;
let imgData, fn, args;
if (imageMask) {
const interpolate = dict.get("I", "Interpolate");
const bitStrideLength = w + 7 >> 3;
const imgArray = image.getBytes(bitStrideLength * h);
const decode = dict.getArray("D", "Decode");
if (this.parsingType3Font) {
imgData = PDFImage.createRawMask({
imgArray,
width: w,
height: h,
imageIsFromDecodeStream: image instanceof DecodeStream,
inverseDecode: decode?.[0] > 0,
interpolate
});
imgData.cached = !!cacheKey;
fn = OPS.paintImageMaskXObject;
args = [imgData];
operatorList.addImageOps(fn, args, optionalContent);
if (cacheKey) {
const cacheData = {
fn,
args,
optionalContent
};
localImageCache.set(cacheKey, imageRef, cacheData);
if (imageRef) {
this._regionalImageCache.set(null, imageRef, cacheData);
}
}
return;
}
imgData = await PDFImage.createMask({
imgArray,
width: w,
height: h,
imageIsFromDecodeStream: image instanceof DecodeStream,
inverseDecode: decode?.[0] > 0,
interpolate,
isOffscreenCanvasSupported: this.options.isOffscreenCanvasSupported
});
if (imgData.isSingleOpaquePixel) {
fn = OPS.paintSolidColorImageMask;
args = [];
operatorList.addImageOps(fn, args, optionalContent);
if (cacheKey) {
const cacheData = {
fn,
args,
optionalContent
};
localImageCache.set(cacheKey, imageRef, cacheData);
if (imageRef) {
this._regionalImageCache.set(null, imageRef, cacheData);
}
}
return;
}
const objId = `mask_${this.idFactory.createObjId()}`;
operatorList.addDependency(objId);
imgData.dataLen = imgData.bitmap ? imgData.width * imgData.height * 4 : imgData.data.length;
this._sendImgData(objId, imgData);
fn = OPS.paintImageMaskXObject;
args = [{
data: objId,
width: imgData.width,
height: imgData.height,
interpolate: imgData.interpolate,
count: 1
}];
operatorList.addImageOps(fn, args, optionalContent);
if (cacheKey) {
const cacheData = {
objId,
fn,
args,
optionalContent
};
localImageCache.set(cacheKey, imageRef, cacheData);
if (imageRef) {
this._regionalImageCache.set(null, imageRef, cacheData);
}
}
return;
}
const SMALL_IMAGE_DIMENSIONS = 200;
const hasMask = dict.has("SMask") || dict.has("Mask");
if (isInline && w + h < SMALL_IMAGE_DIMENSIONS && !hasMask) {
try {
const imageObj = new PDFImage({
xref: this.xref,
res: resources,
image,
isInline,
pdfFunctionFactory: this._pdfFunctionFactory,
globalColorSpaceCache: this.globalColorSpaceCache,
localColorSpaceCache
});
imgData = await imageObj.createImageData(true, false);
operatorList.isOffscreenCanvasSupported = this.options.isOffscreenCanvasSupported;
operatorList.addImageOps(OPS.paintInlineImageXObject, [imgData], optionalContent);
} catch (reason) {
const msg = `Unable to decode inline image: "${reason}".`;
if (!this.options.ignoreErrors) {
throw new Error(msg);
}
warn(msg);
}
return;
}
let objId = `img_${this.idFactory.createObjId()}`,
cacheGlobally = false;
if (this.parsingType3Font) {
objId = `${this.idFactory.getDocId()}_type3_${objId}`;
} else if (cacheKey && imageRef) {
cacheGlobally = this.globalImageCache.shouldCache(imageRef, this.pageIndex);
if (cacheGlobally) {
assert(!isInline, "Cannot cache an inline image globally.");
objId = `${this.idFactory.getDocId()}_${objId}`;
}
}
operatorList.addDependency(objId);
fn = OPS.paintImageXObject;
args = [objId, w, h];
operatorList.addImageOps(fn, args, optionalContent, hasMask);
if (cacheGlobally) {
if (this.globalImageCache.hasDecodeFailed(imageRef)) {
this.globalImageCache.setData(imageRef, {
objId,
fn,
args,
optionalContent,
hasMask,
byteSize: 0
});
this._sendImgData(objId, null, cacheGlobally);
return;
}
if (w * h > 250000 || hasMask) {
const localLength = await this.handler.sendWithPromise("commonobj", [objId, "CopyLocalImage", {
imageRef
}]);
if (localLength) {
this.globalImageCache.setData(imageRef, {
objId,
fn,
args,
optionalContent,
hasMask,
byteSize: 0
});
this.globalImageCache.addByteSize(imageRef, localLength);
return;
}
}
}
PDFImage.buildImage({
xref: this.xref,
res: resources,
image,
isInline,
pdfFunctionFactory: this._pdfFunctionFactory,
globalColorSpaceCache: this.globalColorSpaceCache,
localColorSpaceCache
}).then(async imageObj => {
imgData = await imageObj.createImageData(false, this.options.isOffscreenCanvasSupported);
imgData.dataLen = imgData.bitmap ? imgData.width * imgData.height * 4 : imgData.data.length;
imgData.ref = imageRef;
if (cacheGlobally) {
this.globalImageCache.addByteSize(imageRef, imgData.dataLen);
}
return this._sendImgData(objId, imgData, cacheGlobally);
}).catch(reason => {
warn(`Unable to decode image "${objId}": "${reason}".`);
if (imageRef) {
this.globalImageCache.addDecodeFailed(imageRef);
}
return this._sendImgData(objId, null, cacheGlobally);
});
if (cacheKey) {
const cacheData = {
objId,
fn,
args,
optionalContent,
hasMask
};
localImageCache.set(cacheKey, imageRef, cacheData);
if (imageRef) {
this._regionalImageCache.set(null, imageRef, cacheData);
if (cacheGlobally) {
this.globalImageCache.setData(imageRef, {
objId,
fn,
args,
optionalContent,
hasMask,
byteSize: 0
});
}
}
}
}
handleSMask(smask, resources, operatorList, task, stateManager, localColorSpaceCache) {
const smaskContent = smask.get("G");
const smaskOptions = {
subtype: smask.get("S").name,
backdrop: smask.get("BC")
};
const transferObj = smask.get("TR");
if (isPDFFunction(transferObj)) {
const transferFn = this._pdfFunctionFactory.create(transferObj);
const transferMap = new Uint8Array(256);
const tmp = new Float32Array(1);
for (let i = 0; i < 256; i++) {
tmp[0] = i / 255;
transferFn(tmp, 0, tmp, 0);
transferMap[i] = tmp[0] * 255 | 0;
}
smaskOptions.transferMap = transferMap;
}
return this.buildFormXObject(resources, smaskContent, smaskOptions, operatorList, task, stateManager.state.clone({
newPath: true
}), localColorSpaceCache);
}
handleTransferFunction(tr) {
let transferArray;
if (Array.isArray(tr)) {
transferArray = tr;
} else if (isPDFFunction(tr)) {
transferArray = [tr];
} else {
return null;
}
const transferMaps = [];
let numFns = 0,
numEffectfulFns = 0;
for (const entry of transferArray) {
const transferObj = this.xref.fetchIfRef(entry);
numFns++;
if (isName(transferObj, "Identity")) {
transferMaps.push(null);
continue;
} else if (!isPDFFunction(transferObj)) {
return null;
}
const transferFn = this._pdfFunctionFactory.create(transferObj);
const transferMap = new Uint8Array(256),
tmp = new Float32Array(1);
for (let j = 0; j < 256; j++) {
tmp[0] = j / 255;
transferFn(tmp, 0, tmp, 0);
transferMap[j] = tmp[0] * 255 | 0;
}
transferMaps.push(transferMap);
numEffectfulFns++;
}
if (!(numFns === 1 || numFns === 4)) {
return null;
}
if (numEffectfulFns === 0) {
return null;
}
return transferMaps;
}
handleTilingType(fn, color, resources, pattern, patternDict, operatorList, task, localTilingPatternCache) {
const tilingOpList = new OperatorList();
const patternResources = Dict.merge({
xref: this.xref,
dictArray: [patternDict.get("Resources"), resources]
});
return this.getOperatorList({
stream: pattern,
task,
resources: patternResources,
operatorList: tilingOpList
}).then(function () {
const operatorListIR = tilingOpList.getIR();
const tilingPatternIR = getTilingPatternIR(operatorListIR, patternDict, color);
operatorList.addDependencies(tilingOpList.dependencies);
operatorList.addOp(fn, tilingPatternIR);
if (patternDict.objId) {
localTilingPatternCache.set(null, patternDict.objId, {
operatorListIR,
dict: patternDict
});
}
}).catch(reason => {
if (reason instanceof AbortException) {
return;
}
if (this.options.ignoreErrors) {
warn(`handleTilingType - ignoring pattern: "${reason}".`);
return;
}
throw reason;
});
}
async handleSetFont(resources, fontArgs, fontRef, operatorList, task, state, fallbackFontDict = null, cssFontInfo = null) {
const fontName = fontArgs?.[0] instanceof Name ? fontArgs[0].name : null;
const translated = await this.loadFont(fontName, fontRef, resources, task, fallbackFontDict, cssFontInfo);
if (translated.font.isType3Font) {
operatorList.addDependencies(translated.type3Dependencies);
}
state.font = translated.font;
translated.send(this.handler);
return translated.loadedName;
}
handleText(chars, state) {
const font = state.font;
const glyphs = font.charsToGlyphs(chars);
if (font.data) {
const isAddToPathSet = !!(state.textRenderingMode & TextRenderingMode.ADD_TO_PATH_FLAG);
if (isAddToPathSet || state.fillColorSpace.name === "Pattern" || font.disableFontFace) {
PartialEvaluator.buildFontPaths(font, glyphs, this.handler, this.options);
}
}
return glyphs;
}
ensureStateFont(state) {
if (state.font) {
return;
}
const reason = new FormatError("Missing setFont (Tf) operator before text rendering operator.");
if (this.options.ignoreErrors) {
warn(`ensureStateFont: "${reason}".`);
return;
}
throw reason;
}
async setGState({
resources,
gState,
operatorList,
cacheKey,
task,
stateManager,
localGStateCache,
localColorSpaceCache
}) {
const gStateRef = gState.objId;
let isSimpleGState = true;
const gStateObj = [];
let promise = Promise.resolve();
for (const [key, value] of gState) {
switch (key) {
case "Type":
break;
case "LW":
if (typeof value !== "number") {
warn(`Invalid LW (line width): ${value}`);
break;
}
gStateObj.push([key, Math.abs(value)]);
break;
case "LC":
case "LJ":
case "ML":
case "D":
case "RI":
case "FL":
case "CA":
case "ca":
gStateObj.push([key, value]);
break;
case "Font":
isSimpleGState = false;
promise = promise.then(() => this.handleSetFont(resources, null, value[0], operatorList, task, stateManager.state).then(function (loadedName) {
operatorList.addDependency(loadedName);
gStateObj.push([key, [loadedName, value[1]]]);
}));
break;
case "BM":
gStateObj.push([key, normalizeBlendMode(value)]);
break;
case "SMask":
if (isName(value, "None")) {
gStateObj.push([key, false]);
break;
}
if (value instanceof Dict) {
isSimpleGState = false;
promise = promise.then(() => this.handleSMask(value, resources, operatorList, task, stateManager, localColorSpaceCache));
gStateObj.push([key, true]);
} else {
warn("Unsupported SMask type");
}
break;
case "TR":
const transferMaps = this.handleTransferFunction(value);
gStateObj.push([key, transferMaps]);
break;
case "OP":
case "op":
case "OPM":
case "BG":
case "BG2":
case "UCR":
case "UCR2":
case "TR2":
case "HT":
case "SM":
case "SA":
case "AIS":
case "TK":
info("graphic state operator " + key);
break;
default:
info("Unknown graphic state operator " + key);
break;
}
}
await promise;
if (gStateObj.length > 0) {
operatorList.addOp(OPS.setGState, [gStateObj]);
}
if (isSimpleGState) {
localGStateCache.set(cacheKey, gStateRef, gStateObj);
}
}
loadFont(fontName, font, resources, task, fallbackFontDict = null, cssFontInfo = null) {
const errorFont = async () => new TranslatedFont({
loadedName: "g_font_error",
font: new ErrorFont(`Font "${fontName}" is not available.`),
dict: font
});
let fontRef;
if (font) {
if (font instanceof Ref) {
fontRef = font;
}
} else {
const fontRes = resources.get("Font");
if (fontRes) {
fontRef = fontRes.getRaw(fontName);
}
}
if (fontRef) {
if (this.type3FontRefs?.has(fontRef)) {
return errorFont();
}
if (this.fontCache.has(fontRef)) {
return this.fontCache.get(fontRef);
}
try {
font = this.xref.fetchIfRef(fontRef);
} catch (ex) {
warn(`loadFont - lookup failed: "${ex}".`);
}
}
if (!(font instanceof Dict)) {
if (!this.options.ignoreErrors && !this.parsingType3Font) {
warn(`Font "${fontName}" is not available.`);
return errorFont();
}
warn(`Font "${fontName}" is not available -- attempting to fallback to a default font.`);
font = fallbackFontDict || PartialEvaluator.fallbackFontDict;
}
if (font.cacheKey && this.fontCache.has(font.cacheKey)) {
return this.fontCache.get(font.cacheKey);
}
const {
promise,
resolve
} = Promise.withResolvers();
let preEvaluatedFont;
try {
preEvaluatedFont = this.preEvaluateFont(font);
preEvaluatedFont.cssFontInfo = cssFontInfo;
} catch (reason) {
warn(`loadFont - preEvaluateFont failed: "${reason}".`);
return errorFont();
}
const {
descriptor,
hash
} = preEvaluatedFont;
const fontRefIsRef = fontRef instanceof Ref;
let fontID;
if (hash && descriptor instanceof Dict) {
const fontAliases = descriptor.fontAliases ||= Object.create(null);
if (fontAliases[hash]) {
const aliasFontRef = fontAliases[hash].aliasRef;
if (fontRefIsRef && aliasFontRef && this.fontCache.has(aliasFontRef)) {
this.fontCache.putAlias(fontRef, aliasFontRef);
return this.fontCache.get(fontRef);
}
} else {
fontAliases[hash] = {
fontID: this.idFactory.createFontId()
};
}
if (fontRefIsRef) {
fontAliases[hash].aliasRef = fontRef;
}
fontID = fontAliases[hash].fontID;
} else {
fontID = this.idFactory.createFontId();
}
assert(fontID?.startsWith("f"), 'The "fontID" must be (correctly) defined.');
if (fontRefIsRef) {
this.fontCache.put(fontRef, promise);
} else {
font.cacheKey = `cacheKey_${fontID}`;
this.fontCache.put(font.cacheKey, promise);
}
font.loadedName = `${this.idFactory.getDocId()}_${fontID}`;
this.translateFont(preEvaluatedFont).then(async translatedFont => {
const translated = new TranslatedFont({
loadedName: font.loadedName,
font: translatedFont,
dict: font
});
if (translatedFont.isType3Font) {
try {
await translated.loadType3Data(this, resources, task);
} catch (reason) {
throw new Error(`Type3 font load error: ${reason}`);
}
}
resolve(translated);
}).catch(reason => {
warn(`loadFont - translateFont failed: "${reason}".`);
resolve(new TranslatedFont({
loadedName: font.loadedName,
font: new ErrorFont(reason?.message),
dict: font
}));
});
return promise;
}
buildPath(fn, args, state) {
const {
pathMinMax: minMax,
pathBuffer
} = state;
switch (fn | 0) {
case OPS.rectangle:
{
const x = state.currentPointX = args[0];
const y = state.currentPointY = args[1];
const width = args[2];
const height = args[3];
const xw = x + width;
const yh = y + height;
if (width === 0 || height === 0) {
pathBuffer.push(DrawOPS.moveTo, x, y, DrawOPS.lineTo, xw, yh, DrawOPS.closePath);
} else {
pathBuffer.push(DrawOPS.moveTo, x, y, DrawOPS.lineTo, xw, y, DrawOPS.lineTo, xw, yh, DrawOPS.lineTo, x, yh, DrawOPS.closePath);
}
Util.rectBoundingBox(x, y, xw, yh, minMax);
break;
}
case OPS.moveTo:
{
const x = state.currentPointX = args[0];
const y = state.currentPointY = args[1];
pathBuffer.push(DrawOPS.moveTo, x, y);
Util.pointBoundingBox(x, y, minMax);
break;
}
case OPS.lineTo:
{
const x = state.currentPointX = args[0];
const y = state.currentPointY = args[1];
pathBuffer.push(DrawOPS.lineTo, x, y);
Util.pointBoundingBox(x, y, minMax);
break;
}
case OPS.curveTo:
{
const startX = state.currentPointX;
const startY = state.currentPointY;
const [x1, y1, x2, y2, x, y] = args;
state.currentPointX = x;
state.currentPointY = y;
pathBuffer.push(DrawOPS.curveTo, x1, y1, x2, y2, x, y);
Util.bezierBoundingBox(startX, startY, x1, y1, x2, y2, x, y, minMax);
break;
}
case OPS.curveTo2:
{
const startX = state.currentPointX;
const startY = state.currentPointY;
const [x1, y1, x, y] = args;
state.currentPointX = x;
state.currentPointY = y;
pathBuffer.push(DrawOPS.curveTo, startX, startY, x1, y1, x, y);
Util.bezierBoundingBox(startX, startY, startX, startY, x1, y1, x, y, minMax);
break;
}
case OPS.curveTo3:
{
const startX = state.currentPointX;
const startY = state.currentPointY;
const [x1, y1, x, y] = args;
state.currentPointX = x;
state.currentPointY = y;
pathBuffer.push(DrawOPS.curveTo, x1, y1, x, y, x, y);
Util.bezierBoundingBox(startX, startY, x1, y1, x, y, x, y, minMax);
break;
}
case OPS.closePath:
pathBuffer.push(DrawOPS.closePath);
break;
}
}
_getColorSpace(cs, resources, localColorSpaceCache) {
return ColorSpaceUtils.parse({
cs,
xref: this.xref,
resources,
pdfFunctionFactory: this._pdfFunctionFactory,
globalColorSpaceCache: this.globalColorSpaceCache,
localColorSpaceCache,
asyncIfNotCached: true
});
}
async _handleColorSpace(csPromise) {
try {
return await csPromise;
} catch (ex) {
if (ex instanceof AbortException) {
return null;
}
if (this.options.ignoreErrors) {
warn(`_handleColorSpace - ignoring ColorSpace: "${ex}".`);
return null;
}
throw ex;
}
}
parseShading({
shading,
resources,
localColorSpaceCache,
localShadingPatternCache
}) {
let id = localShadingPatternCache.get(shading);
if (id) {
return id;
}
let patternIR;
try {
const shadingFill = Pattern.parseShading(shading, this.xref, resources, this._pdfFunctionFactory, this.globalColorSpaceCache, localColorSpaceCache);
patternIR = shadingFill.getIR();
} catch (reason) {
if (reason instanceof AbortException) {
return null;
}
if (this.options.ignoreErrors) {
warn(`parseShading - ignoring shading: "${reason}".`);
localShadingPatternCache.set(shading, null);
return null;
}
throw reason;
}
id = `pattern_${this.idFactory.createObjId()}`;
if (this.parsingType3Font) {
id = `${this.idFactory.getDocId()}_type3_${id}`;
}
localShadingPatternCache.set(shading, id);
if (this.parsingType3Font) {
this.handler.send("commonobj", [id, "Pattern", patternIR]);
} else {
this.handler.send("obj", [id, this.pageIndex, "Pattern", patternIR]);
}
return id;
}
handleColorN(operatorList, fn, args, cs, patterns, resources, task, localColorSpaceCache, localTilingPatternCache, localShadingPatternCache) {
const patternName = args.pop();
if (patternName instanceof Name) {
const rawPattern = patterns.getRaw(patternName.name);
const localTilingPattern = rawPattern instanceof Ref && localTilingPatternCache.getByRef(rawPattern);
if (localTilingPattern) {
try {
const color = cs.base ? cs.base.getRgb(args, 0) : null;
const tilingPatternIR = getTilingPatternIR(localTilingPattern.operatorListIR, localTilingPattern.dict, color);
operatorList.addOp(fn, tilingPatternIR);
return undefined;
} catch {}
}
const pattern = this.xref.fetchIfRef(rawPattern);
if (pattern) {
const dict = pattern instanceof BaseStream ? pattern.dict : pattern;
const typeNum = dict.get("PatternType");
if (typeNum === PatternType.TILING) {
const color = cs.base ? cs.base.getRgb(args, 0) : null;
return this.handleTilingType(fn, color, resources, pattern, dict, operatorList, task, localTilingPatternCache);
} else if (typeNum === PatternType.SHADING) {
const shading = dict.get("Shading");
const objId = this.parseShading({
shading,
resources,
localColorSpaceCache,
localShadingPatternCache
});
if (objId) {
const matrix = lookupMatrix(dict.getArray("Matrix"), null);
operatorList.addOp(fn, ["Shading", objId, matrix]);
}
return undefined;
}
throw new FormatError(`Unknown PatternType: ${typeNum}`);
}
}
throw new FormatError(`Unknown PatternName: ${patternName}`);
}
_parseVisibilityExpression(array, nestingCounter, currentResult) {
const MAX_NESTING = 10;
if (++nestingCounter > MAX_NESTING) {
warn("Visibility expression is too deeply nested");
return;
}
const length = array.length;
const operator = this.xref.fetchIfRef(array[0]);
if (length < 2 || !(operator instanceof Name)) {
warn("Invalid visibility expression");
return;
}
switch (operator.name) {
case "And":
case "Or":
case "Not":
currentResult.push(operator.name);
break;
default:
warn(`Invalid operator ${operator.name} in visibility expression`);
return;
}
for (let i = 1; i < length; i++) {
const raw = array[i];
const object = this.xref.fetchIfRef(raw);
if (Array.isArray(object)) {
const nestedResult = [];
currentResult.push(nestedResult);
this._parseVisibilityExpression(object, nestingCounter, nestedResult);
} else if (raw instanceof Ref) {
currentResult.push(raw.toString());
}
}
}
async parseMarkedContentProps(contentProperties, resources) {
let optionalContent;
if (contentProperties instanceof Name) {
const properties = resources.get("Properties");
optionalContent = properties.get(contentProperties.name);
} else if (contentProperties instanceof Dict) {
optionalContent = contentProperties;
} else {
throw new FormatError("Optional content properties malformed.");
}
const optionalContentType = optionalContent.get("Type")?.name;
if (optionalContentType === "OCG") {
return {
type: optionalContentType,
id: optionalContent.objId
};
} else if (optionalContentType === "OCMD") {
const expression = optionalContent.get("VE");
if (Array.isArray(expression)) {
const result = [];
this._parseVisibilityExpression(expression, 0, result);
if (result.length > 0) {
return {
type: "OCMD",
expression: result
};
}
}
const optionalContentGroups = optionalContent.get("OCGs");
if (Array.isArray(optionalContentGroups) || optionalContentGroups instanceof Dict) {
const groupIds = [];
if (Array.isArray(optionalContentGroups)) {
for (const ocg of optionalContentGroups) {
groupIds.push(ocg.toString());
}
} else {
groupIds.push(optionalContentGroups.objId);
}
return {
type: optionalContentType,
ids: groupIds,
policy: optionalContent.get("P") instanceof Name ? optionalContent.get("P").name : null,
expression: null
};
} else if (optionalContentGroups instanceof Ref) {
return {
type: optionalContentType,
id: optionalContentGroups.toString()
};
}
}
return null;
}
getOperatorList({
stream,
task,
resources,
operatorList,
initialState = null,
fallbackFontDict = null
}) {
resources ||= Dict.empty;
initialState ||= new EvalState();
if (!operatorList) {
throw new Error('getOperatorList: missing "operatorList" parameter');
}
const self = this;
const xref = this.xref;
const localImageCache = new LocalImageCache();
const localColorSpaceCache = new LocalColorSpaceCache();
const localGStateCache = new LocalGStateCache();
const localTilingPatternCache = new LocalTilingPatternCache();
const localShadingPatternCache = new Map();
const xobjs = resources.get("XObject") || Dict.empty;
const patterns = resources.get("Pattern") || Dict.empty;
const stateManager = new StateManager(initialState);
const preprocessor = new EvaluatorPreprocessor(stream, xref, stateManager);
const timeSlotManager = new TimeSlotManager();
function closePendingRestoreOPS(argument) {
for (let i = 0, ii = preprocessor.savedStatesDepth; i < ii; i++) {
operatorList.addOp(OPS.restore, []);
}
}
return new Promise(function promiseBody(resolve, reject) {
const next = function (promise) {
Promise.all([promise, operatorList.ready]).then(function () {
try {
promiseBody(resolve, reject);
} catch (ex) {
reject(ex);
}
}, reject);
};
task.ensureNotTerminated();
timeSlotManager.reset();
const operation = {};
let stop, i, ii, cs, name, isValidName;
while (!(stop = timeSlotManager.check())) {
operation.args = null;
if (!preprocessor.read(operation)) {
break;
}
let args = operation.args;
let fn = operation.fn;
switch (fn | 0) {
case OPS.paintXObject:
isValidName = args[0] instanceof Name;
name = args[0].name;
if (isValidName) {
const localImage = localImageCache.getByName(name);
if (localImage) {
addLocallyCachedImageOps(operatorList, localImage);
args = null;
continue;
}
}
next(new Promise(function (resolveXObject, rejectXObject) {
if (!isValidName) {
throw new FormatError("XObject must be referred to by name.");
}
let xobj = xobjs.getRaw(name);
if (xobj instanceof Ref) {
const localImage = localImageCache.getByRef(xobj) || self._regionalImageCache.getByRef(xobj);
if (localImage) {
addLocallyCachedImageOps(operatorList, localImage);
resolveXObject();
return;
}
const globalImage = self.globalImageCache.getData(xobj, self.pageIndex);
if (globalImage) {
operatorList.addDependency(globalImage.objId);
operatorList.addImageOps(globalImage.fn, globalImage.args, globalImage.optionalContent, globalImage.hasMask);
resolveXObject();
return;
}
xobj = xref.fetch(xobj);
}
if (!(xobj instanceof BaseStream)) {
throw new FormatError("XObject should be a stream");
}
const type = xobj.dict.get("Subtype");
if (!(type instanceof Name)) {
throw new FormatError("XObject should have a Name subtype");
}
if (type.name === "Form") {
stateManager.save();
self.buildFormXObject(resources, xobj, null, operatorList, task, stateManager.state.clone({
newPath: true
}), localColorSpaceCache).then(function () {
stateManager.restore();
resolveXObject();
}, rejectXObject);
return;
} else if (type.name === "Image") {
self.buildPaintImageXObject({
resources,
image: xobj,
operatorList,
cacheKey: name,
localImageCache,
localColorSpaceCache
}).then(resolveXObject, rejectXObject);
return;
} else if (type.name === "PS") {
info("Ignored XObject subtype PS");
} else {
throw new FormatError(`Unhandled XObject subtype ${type.name}`);
}
resolveXObject();
}).catch(function (reason) {
if (reason instanceof AbortException) {
return;
}
if (self.options.ignoreErrors) {
warn(`getOperatorList - ignoring XObject: "${reason}".`);
return;
}
throw reason;
}));
return;
case OPS.setFont:
const fontSize = args[1];
next(self.handleSetFont(resources, args, null, operatorList, task, stateManager.state, fallbackFontDict).then(function (loadedName) {
operatorList.addDependency(loadedName);
operatorList.addOp(OPS.setFont, [loadedName, fontSize]);
}));
return;
case OPS.endInlineImage:
const cacheKey = args[0].cacheKey;
if (cacheKey) {
const localImage = localImageCache.getByName(cacheKey);
if (localImage) {
addLocallyCachedImageOps(operatorList, localImage);
args = null;
continue;
}
}
next(self.buildPaintImageXObject({
resources,
image: args[0],
isInline: true,
operatorList,
cacheKey,
localImageCache,
localColorSpaceCache
}));
return;
case OPS.showText:
if (!stateManager.state.font) {
self.ensureStateFont(stateManager.state);
continue;
}
args[0] = self.handleText(args[0], stateManager.state);
break;
case OPS.showSpacedText:
if (!stateManager.state.font) {
self.ensureStateFont(stateManager.state);
continue;
}
const combinedGlyphs = [],
state = stateManager.state;
for (const arrItem of args[0]) {
if (typeof arrItem === "string") {
combinedGlyphs.push(...self.handleText(arrItem, state));
} else if (typeof arrItem === "number") {
combinedGlyphs.push(arrItem);
}
}
args[0] = combinedGlyphs;
fn = OPS.showText;
break;
case OPS.nextLineShowText:
if (!stateManager.state.font) {
self.ensureStateFont(stateManager.state);
continue;
}
operatorList.addOp(OPS.nextLine);
args[0] = self.handleText(args[0], stateManager.state);
fn = OPS.showText;
break;
case OPS.nextLineSetSpacingShowText:
if (!stateManager.state.font) {
self.ensureStateFont(stateManager.state);
continue;
}
operatorList.addOp(OPS.nextLine);
operatorList.addOp(OPS.setWordSpacing, [args.shift()]);
operatorList.addOp(OPS.setCharSpacing, [args.shift()]);
args[0] = self.handleText(args[0], stateManager.state);
fn = OPS.showText;
break;
case OPS.setTextRenderingMode:
stateManager.state.textRenderingMode = args[0];
break;
case OPS.setFillColorSpace:
{
const fillCS = self._getColorSpace(args[0], resources, localColorSpaceCache);
if (fillCS instanceof ColorSpace) {
stateManager.state.fillColorSpace = fillCS;
continue;
}
next(self._handleColorSpace(fillCS).then(colorSpace => {
stateManager.state.fillColorSpace = colorSpace || ColorSpaceUtils.gray;
}));
return;
}
case OPS.setStrokeColorSpace:
{
const strokeCS = self._getColorSpace(args[0], resources, localColorSpaceCache);
if (strokeCS instanceof ColorSpace) {
stateManager.state.strokeColorSpace = strokeCS;
continue;
}
next(self._handleColorSpace(strokeCS).then(colorSpace => {
stateManager.state.strokeColorSpace = colorSpace || ColorSpaceUtils.gray;
}));
return;
}
case OPS.setFillColor:
cs = stateManager.state.fillColorSpace;
args = cs.getRgb(args, 0);
fn = OPS.setFillRGBColor;
break;
case OPS.setStrokeColor:
cs = stateManager.state.strokeColorSpace;
args = cs.getRgb(args, 0);
fn = OPS.setStrokeRGBColor;
break;
case OPS.setFillGray:
stateManager.state.fillColorSpace = ColorSpaceUtils.gray;
args = ColorSpaceUtils.gray.getRgb(args, 0);
fn = OPS.setFillRGBColor;
break;
case OPS.setStrokeGray:
stateManager.state.strokeColorSpace = ColorSpaceUtils.gray;
args = ColorSpaceUtils.gray.getRgb(args, 0);
fn = OPS.setStrokeRGBColor;
break;
case OPS.setFillCMYKColor:
stateManager.state.fillColorSpace = ColorSpaceUtils.cmyk;
args = ColorSpaceUtils.cmyk.getRgb(args, 0);
fn = OPS.setFillRGBColor;
break;
case OPS.setStrokeCMYKColor:
stateManager.state.strokeColorSpace = ColorSpaceUtils.cmyk;
args = ColorSpaceUtils.cmyk.getRgb(args, 0);
fn = OPS.setStrokeRGBColor;
break;
case OPS.setFillRGBColor:
stateManager.state.fillColorSpace = ColorSpaceUtils.rgb;
args = ColorSpaceUtils.rgb.getRgb(args, 0);
break;
case OPS.setStrokeRGBColor:
stateManager.state.strokeColorSpace = ColorSpaceUtils.rgb;
args = ColorSpaceUtils.rgb.getRgb(args, 0);
break;
case OPS.setFillColorN:
cs = stateManager.state.patternFillColorSpace;
if (!cs) {
if (isNumberArray(args, null)) {
args = ColorSpaceUtils.gray.getRgb(args, 0);
fn = OPS.setFillRGBColor;
break;
}
args = [];
fn = OPS.setFillTransparent;
break;
}
if (cs.name === "Pattern") {
next(self.handleColorN(operatorList, OPS.setFillColorN, args, cs, patterns, resources, task, localColorSpaceCache, localTilingPatternCache, localShadingPatternCache));
return;
}
args = cs.getRgb(args, 0);
fn = OPS.setFillRGBColor;
break;
case OPS.setStrokeColorN:
cs = stateManager.state.patternStrokeColorSpace;
if (!cs) {
if (isNumberArray(args, null)) {
args = ColorSpaceUtils.gray.getRgb(args, 0);
fn = OPS.setStrokeRGBColor;
break;
}
args = [];
fn = OPS.setStrokeTransparent;
break;
}
if (cs.name === "Pattern") {
next(self.handleColorN(operatorList, OPS.setStrokeColorN, args, cs, patterns, resources, task, localColorSpaceCache, localTilingPatternCache, localShadingPatternCache));
return;
}
args = cs.getRgb(args, 0);
fn = OPS.setStrokeRGBColor;
break;
case OPS.shadingFill:
let shading;
try {
const shadingRes = resources.get("Shading");
if (!shadingRes) {
throw new FormatError("No shading resource found");
}
shading = shadingRes.get(args[0].name);
if (!shading) {
throw new FormatError("No shading object found");
}
} catch (reason) {
if (reason instanceof AbortException) {
continue;
}
if (self.options.ignoreErrors) {
warn(`getOperatorList - ignoring Shading: "${reason}".`);
continue;
}
throw reason;
}
const patternId = self.parseShading({
shading,
resources,
localColorSpaceCache,
localShadingPatternCache
});
if (!patternId) {
continue;
}
args = [patternId];
fn = OPS.shadingFill;
break;
case OPS.setGState:
isValidName = args[0] instanceof Name;
name = args[0].name;
if (isValidName) {
const localGStateObj = localGStateCache.getByName(name);
if (localGStateObj) {
if (localGStateObj.length > 0) {
operatorList.addOp(OPS.setGState, [localGStateObj]);
}
args = null;
continue;
}
}
next(new Promise(function (resolveGState, rejectGState) {
if (!isValidName) {
throw new FormatError("GState must be referred to by name.");
}
const extGState = resources.get("ExtGState");
if (!(extGState instanceof Dict)) {
throw new FormatError("ExtGState should be a dictionary.");
}
const gState = extGState.get(name);
if (!(gState instanceof Dict)) {
throw new FormatError("GState should be a dictionary.");
}
self.setGState({
resources,
gState,
operatorList,
cacheKey: name,
task,
stateManager,
localGStateCache,
localColorSpaceCache
}).then(resolveGState, rejectGState);
}).catch(function (reason) {
if (reason instanceof AbortException) {
return;
}
if (self.options.ignoreErrors) {
warn(`getOperatorList - ignoring ExtGState: "${reason}".`);
return;
}
throw reason;
}));
return;
case OPS.setLineWidth:
{
const [thickness] = args;
if (typeof thickness !== "number") {
warn(`Invalid setLineWidth: ${thickness}`);
continue;
}
args[0] = Math.abs(thickness);
break;
}
case OPS.moveTo:
case OPS.lineTo:
case OPS.curveTo:
case OPS.curveTo2:
case OPS.curveTo3:
case OPS.closePath:
case OPS.rectangle:
self.buildPath(fn, args, stateManager.state);
continue;
case OPS.stroke:
case OPS.closeStroke:
case OPS.fill:
case OPS.eoFill:
case OPS.fillStroke:
case OPS.eoFillStroke:
case OPS.closeFillStroke:
case OPS.closeEOFillStroke:
case OPS.endPath:
{
const {
state: {
pathBuffer,
pathMinMax
}
} = stateManager;
if (fn === OPS.closeStroke || fn === OPS.closeFillStroke || fn === OPS.closeEOFillStroke) {
pathBuffer.push(DrawOPS.closePath);
}
if (pathBuffer.length === 0) {
operatorList.addOp(OPS.constructPath, [fn, [null], null]);
} else {
operatorList.addOp(OPS.constructPath, [fn, [new Float32Array(pathBuffer)], pathMinMax.slice()]);
pathBuffer.length = 0;
pathMinMax.set([Infinity, Infinity, -Infinity, -Infinity], 0);
}
continue;
}
case OPS.markPoint:
case OPS.markPointProps:
case OPS.beginCompat:
case OPS.endCompat:
continue;
case OPS.beginMarkedContentProps:
if (!(args[0] instanceof Name)) {
warn(`Expected name for beginMarkedContentProps arg0=${args[0]}`);
operatorList.addOp(OPS.beginMarkedContentProps, ["OC", null]);
continue;
}
if (args[0].name === "OC") {
next(self.parseMarkedContentProps(args[1], resources).then(data => {
operatorList.addOp(OPS.beginMarkedContentProps, ["OC", data]);
}).catch(reason => {
if (reason instanceof AbortException) {
return;
}
if (self.options.ignoreErrors) {
warn(`getOperatorList - ignoring beginMarkedContentProps: "${reason}".`);
operatorList.addOp(OPS.beginMarkedContentProps, ["OC", null]);
return;
}
throw reason;
}));
return;
}
args = [args[0].name, args[1] instanceof Dict ? args[1].get("MCID") : null];
break;
case OPS.beginMarkedContent:
case OPS.endMarkedContent:
default:
if (args !== null) {
for (i = 0, ii = args.length; i < ii; i++) {
if (args[i] instanceof Dict) {
break;
}
}
if (i < ii) {
warn("getOperatorList - ignoring operator: " + fn);
continue;
}
}
}
operatorList.addOp(fn, args);
}
if (stop) {
next(deferred);
return;
}
closePendingRestoreOPS();
resolve();
}).catch(reason => {
if (reason instanceof AbortException) {
return;
}
if (this.options.ignoreErrors) {
warn(`getOperatorList - ignoring errors during "${task.name}" ` + `task: "${reason}".`);
closePendingRestoreOPS();
return;
}
throw reason;
});
}
getTextContent({
stream,
task,
resources,
stateManager = null,
includeMarkedContent = false,
sink,
seenStyles = new Set(),
viewBox,
lang = null,
markedContentData = null,
disableNormalization = false,
keepWhiteSpace = false
}) {
resources ||= Dict.empty;
stateManager ||= new StateManager(new TextState());
if (includeMarkedContent) {
markedContentData ||= {
level: 0
};
}
const textContent = {
items: [],
styles: Object.create(null),
lang
};
const textContentItem = {
initialized: false,
str: [],
totalWidth: 0,
totalHeight: 0,
width: 0,
height: 0,
vertical: false,
prevTransform: null,
textAdvanceScale: 0,
spaceInFlowMin: 0,
spaceInFlowMax: 0,
trackingSpaceMin: Infinity,
negativeSpaceMax: -Infinity,
notASpace: -Infinity,
transform: null,
fontName: null,
hasEOL: false
};
const twoLastChars = [" ", " "];
let twoLastCharsPos = 0;
function saveLastChar(char) {
const nextPos = (twoLastCharsPos + 1) % 2;
const ret = twoLastChars[twoLastCharsPos] !== " " && twoLastChars[nextPos] === " ";
twoLastChars[twoLastCharsPos] = char;
twoLastCharsPos = nextPos;
return !keepWhiteSpace && ret;
}
function shouldAddWhitepsace() {
return !keepWhiteSpace && twoLastChars[twoLastCharsPos] !== " " && twoLastChars[(twoLastCharsPos + 1) % 2] === " ";
}
function resetLastChars() {
twoLastChars[0] = twoLastChars[1] = " ";
twoLastCharsPos = 0;
}
const TRACKING_SPACE_FACTOR = 0.102;
const NOT_A_SPACE_FACTOR = 0.03;
const NEGATIVE_SPACE_FACTOR = -0.2;
const SPACE_IN_FLOW_MIN_FACTOR = 0.102;
const SPACE_IN_FLOW_MAX_FACTOR = 0.6;
const VERTICAL_SHIFT_RATIO = 0.25;
const self = this;
const xref = this.xref;
const showSpacedTextBuffer = [];
let xobjs = null;
const emptyXObjectCache = new LocalImageCache();
const emptyGStateCache = new LocalGStateCache();
const preprocessor = new EvaluatorPreprocessor(stream, xref, stateManager);
let textState;
function pushWhitespace({
width = 0,
height = 0,
transform = textContentItem.prevTransform,
fontName = textContentItem.fontName
}) {
textContent.items.push({
str: " ",
dir: "ltr",
width,
height,
transform,
fontName,
hasEOL: false
});
}
function getCurrentTextTransform() {
const font = textState.font;
const tsm = [textState.fontSize * textState.textHScale, 0, 0, textState.fontSize, 0, textState.textRise];
if (font.isType3Font && (textState.fontSize <= 1 || font.isCharBBox) && !isArrayEqual(textState.fontMatrix, FONT_IDENTITY_MATRIX)) {
const glyphHeight = font.bbox[3] - font.bbox[1];
if (glyphHeight > 0) {
tsm[3] *= glyphHeight * textState.fontMatrix[3];
}
}
return Util.transform(textState.ctm, Util.transform(textState.textMatrix, tsm));
}
function ensureTextContentItem() {
if (textContentItem.initialized) {
return textContentItem;
}
const {
font,
loadedName
} = textState;
if (!seenStyles.has(loadedName)) {
seenStyles.add(loadedName);
textContent.styles[loadedName] = {
fontFamily: font.fallbackName,
ascent: font.ascent,
descent: font.descent,
vertical: font.vertical
};
if (self.options.fontExtraProperties && font.systemFontInfo) {
const style = textContent.styles[loadedName];
style.fontSubstitution = font.systemFontInfo.css;
style.fontSubstitutionLoadedName = font.systemFontInfo.loadedName;
}
}
textContentItem.fontName = loadedName;
const trm = textContentItem.transform = getCurrentTextTransform();
if (!font.vertical) {
textContentItem.width = textContentItem.totalWidth = 0;
textContentItem.height = textContentItem.totalHeight = Math.hypot(trm[2], trm[3]);
textContentItem.vertical = false;
} else {
textContentItem.width = textContentItem.totalWidth = Math.hypot(trm[0], trm[1]);
textContentItem.height = textContentItem.totalHeight = 0;
textContentItem.vertical = true;
}
const scaleLineX = Math.hypot(textState.textLineMatrix[0], textState.textLineMatrix[1]);
const scaleCtmX = Math.hypot(textState.ctm[0], textState.ctm[1]);
textContentItem.textAdvanceScale = scaleCtmX * scaleLineX;
const {
fontSize
} = textState;
textContentItem.trackingSpaceMin = fontSize * TRACKING_SPACE_FACTOR;
textContentItem.notASpace = fontSize * NOT_A_SPACE_FACTOR;
textContentItem.negativeSpaceMax = fontSize * NEGATIVE_SPACE_FACTOR;
textContentItem.spaceInFlowMin = fontSize * SPACE_IN_FLOW_MIN_FACTOR;
textContentItem.spaceInFlowMax = fontSize * SPACE_IN_FLOW_MAX_FACTOR;
textContentItem.hasEOL = false;
textContentItem.initialized = true;
return textContentItem;
}
function updateAdvanceScale() {
if (!textContentItem.initialized) {
return;
}
const scaleLineX = Math.hypot(textState.textLineMatrix[0], textState.textLineMatrix[1]);
const scaleCtmX = Math.hypot(textState.ctm[0], textState.ctm[1]);
const scaleFactor = scaleCtmX * scaleLineX;
if (scaleFactor === textContentItem.textAdvanceScale) {
return;
}
if (!textContentItem.vertical) {
textContentItem.totalWidth += textContentItem.width * textContentItem.textAdvanceScale;
textContentItem.width = 0;
} else {
textContentItem.totalHeight += textContentItem.height * textContentItem.textAdvanceScale;
textContentItem.height = 0;
}
textContentItem.textAdvanceScale = scaleFactor;
}
function runBidiTransform(textChunk) {
let text = textChunk.str.join("");
if (!disableNormalization) {
text = normalizeUnicode(text);
}
const bidiResult = bidi(text, -1, textChunk.vertical);
return {
str: bidiResult.str,
dir: bidiResult.dir,
width: Math.abs(textChunk.totalWidth),
height: Math.abs(textChunk.totalHeight),
transform: textChunk.transform,
fontName: textChunk.fontName,
hasEOL: textChunk.hasEOL
};
}
async function handleSetFont(fontName, fontRef) {
const translated = await self.loadFont(fontName, fontRef, resources, task);
textState.loadedName = translated.loadedName;
textState.font = translated.font;
textState.fontMatrix = translated.font.fontMatrix || FONT_IDENTITY_MATRIX;
}
function applyInverseRotation(x, y, matrix) {
const scale = Math.hypot(matrix[0], matrix[1]);
return [(matrix[0] * x + matrix[1] * y) / scale, (matrix[2] * x + matrix[3] * y) / scale];
}
function compareWithLastPosition(glyphWidth) {
const currentTransform = getCurrentTextTransform();
let posX = currentTransform[4];
let posY = currentTransform[5];
if (textState.font?.vertical) {
if (posX < viewBox[0] || posX > viewBox[2] || posY + glyphWidth < viewBox[1] || posY > viewBox[3]) {
return false;
}
} else if (posX + glyphWidth < viewBox[0] || posX > viewBox[2] || posY < viewBox[1] || posY > viewBox[3]) {
return false;
}
if (!textState.font || !textContentItem.prevTransform) {
return true;
}
let lastPosX = textContentItem.prevTransform[4];
let lastPosY = textContentItem.prevTransform[5];
if (lastPosX === posX && lastPosY === posY) {
return true;
}
let rotate = -1;
if (currentTransform[0] && currentTransform[1] === 0 && currentTransform[2] === 0) {
rotate = currentTransform[0] > 0 ? 0 : 180;
} else if (currentTransform[1] && currentTransform[0] === 0 && currentTransform[3] === 0) {
rotate = currentTransform[1] > 0 ? 90 : 270;
}
switch (rotate) {
case 0:
break;
case 90:
[posX, posY] = [posY, posX];
[lastPosX, lastPosY] = [lastPosY, lastPosX];
break;
case 180:
[posX, posY, lastPosX, lastPosY] = [-posX, -posY, -lastPosX, -lastPosY];
break;
case 270:
[posX, posY] = [-posY, -posX];
[lastPosX, lastPosY] = [-lastPosY, -lastPosX];
break;
default:
[posX, posY] = applyInverseRotation(posX, posY, currentTransform);
[lastPosX, lastPosY] = applyInverseRotation(lastPosX, lastPosY, textContentItem.prevTransform);
}
if (textState.font.vertical) {
const advanceY = (lastPosY - posY) / textContentItem.textAdvanceScale;
const advanceX = posX - lastPosX;
const textOrientation = Math.sign(textContentItem.height);
if (advanceY < textOrientation * textContentItem.negativeSpaceMax) {
if (Math.abs(advanceX) > 0.5 * textContentItem.width) {
appendEOL();
return true;
}
resetLastChars();
flushTextContentItem();
return true;
}
if (Math.abs(advanceX) > textContentItem.width) {
appendEOL();
return true;
}
if (advanceY <= textOrientation * textContentItem.notASpace) {
resetLastChars();
}
if (advanceY <= textOrientation * textContentItem.trackingSpaceMin) {
if (shouldAddWhitepsace()) {
resetLastChars();
flushTextContentItem();
pushWhitespace({
height: Math.abs(advanceY)
});
} else {
textContentItem.height += advanceY;
}
} else if (!addFakeSpaces(advanceY, textContentItem.prevTransform, textOrientation)) {
if (textContentItem.str.length === 0) {
resetLastChars();
pushWhitespace({
height: Math.abs(advanceY)
});
} else {
textContentItem.height += advanceY;
}
}
if (Math.abs(advanceX) > textContentItem.width * VERTICAL_SHIFT_RATIO) {
flushTextContentItem();
}
return true;
}
const advanceX = (posX - lastPosX) / textContentItem.textAdvanceScale;
const advanceY = posY - lastPosY;
const textOrientation = Math.sign(textContentItem.width);
if (advanceX < textOrientation * textContentItem.negativeSpaceMax) {
if (Math.abs(advanceY) > 0.5 * textContentItem.height) {
appendEOL();
return true;
}
resetLastChars();
flushTextContentItem();
return true;
}
if (Math.abs(advanceY) > textContentItem.height) {
appendEOL();
return true;
}
if (advanceX <= textOrientation * textContentItem.notASpace) {
resetLastChars();
}
if (advanceX <= textOrientation * textContentItem.trackingSpaceMin) {
if (shouldAddWhitepsace()) {
resetLastChars();
flushTextContentItem();
pushWhitespace({
width: Math.abs(advanceX)
});
} else {
textContentItem.width += advanceX;
}
} else if (!addFakeSpaces(advanceX, textContentItem.prevTransform, textOrientation)) {
if (textContentItem.str.length === 0) {
resetLastChars();
pushWhitespace({
width: Math.abs(advanceX)
});
} else {
textContentItem.width += advanceX;
}
}
if (Math.abs(advanceY) > textContentItem.height * VERTICAL_SHIFT_RATIO) {
flushTextContentItem();
}
return true;
}
function buildTextContentItem({
chars,
extraSpacing
}) {
const font = textState.font;
if (!chars) {
const charSpacing = textState.charSpacing + extraSpacing;
if (charSpacing) {
if (!font.vertical) {
textState.translateTextMatrix(charSpacing * textState.textHScale, 0);
} else {
textState.translateTextMatrix(0, -charSpacing);
}
}
if (keepWhiteSpace) {
compareWithLastPosition(0);
}
return;
}
const glyphs = font.charsToGlyphs(chars);
const scale = textState.fontMatrix[0] * textState.fontSize;
for (let i = 0, ii = glyphs.length; i < ii; i++) {
const glyph = glyphs[i];
const {
category
} = glyph;
if (category.isInvisibleFormatMark) {
continue;
}
let charSpacing = textState.charSpacing + (i + 1 === ii ? extraSpacing : 0);
let glyphWidth = glyph.width;
if (font.vertical) {
glyphWidth = glyph.vmetric ? glyph.vmetric[0] : -glyphWidth;
}
let scaledDim = glyphWidth * scale;
if (!keepWhiteSpace && category.isWhitespace) {
if (!font.vertical) {
charSpacing += scaledDim + textState.wordSpacing;
textState.translateTextMatrix(charSpacing * textState.textHScale, 0);
} else {
charSpacing += -scaledDim + textState.wordSpacing;
textState.translateTextMatrix(0, -charSpacing);
}
saveLastChar(" ");
continue;
}
if (!category.isZeroWidthDiacritic && !compareWithLastPosition(scaledDim)) {
if (!font.vertical) {
textState.translateTextMatrix(scaledDim * textState.textHScale, 0);
} else {
textState.translateTextMatrix(0, scaledDim);
}
continue;
}
const textChunk = ensureTextContentItem();
if (category.isZeroWidthDiacritic) {
scaledDim = 0;
}
if (!font.vertical) {
scaledDim *= textState.textHScale;
textState.translateTextMatrix(scaledDim, 0);
textChunk.width += scaledDim;
} else {
textState.translateTextMatrix(0, scaledDim);
scaledDim = Math.abs(scaledDim);
textChunk.height += scaledDim;
}
if (scaledDim) {
textChunk.prevTransform = getCurrentTextTransform();
}
const glyphUnicode = glyph.unicode;
if (saveLastChar(glyphUnicode)) {
textChunk.str.push(" ");
}
textChunk.str.push(glyphUnicode);
if (charSpacing) {
if (!font.vertical) {
textState.translateTextMatrix(charSpacing * textState.textHScale, 0);
} else {
textState.translateTextMatrix(0, -charSpacing);
}
}
}
}
function appendEOL() {
resetLastChars();
if (textContentItem.initialized) {
textContentItem.hasEOL = true;
flushTextContentItem();
} else {
textContent.items.push({
str: "",
dir: "ltr",
width: 0,
height: 0,
transform: getCurrentTextTransform(),
fontName: textState.loadedName,
hasEOL: true
});
}
}
function addFakeSpaces(width, transf, textOrientation) {
if (textOrientation * textContentItem.spaceInFlowMin <= width && width <= textOrientation * textContentItem.spaceInFlowMax) {
if (textContentItem.initialized) {
resetLastChars();
textContentItem.str.push(" ");
}
return false;
}
const fontName = textContentItem.fontName;
let height = 0;
if (textContentItem.vertical) {
height = width;
width = 0;
}
flushTextContentItem();
resetLastChars();
pushWhitespace({
width: Math.abs(width),
height: Math.abs(height),
transform: transf || getCurrentTextTransform(),
fontName
});
return true;
}
function flushTextContentItem() {
if (!textContentItem.initialized || !textContentItem.str) {
return;
}
if (!textContentItem.vertical) {
textContentItem.totalWidth += textContentItem.width * textContentItem.textAdvanceScale;
} else {
textContentItem.totalHeight += textContentItem.height * textContentItem.textAdvanceScale;
}
textContent.items.push(runBidiTransform(textContentItem));
textContentItem.initialized = false;
textContentItem.str.length = 0;
}
function enqueueChunk(batch = false) {
const length = textContent.items.length;
if (length === 0) {
return;
}
if (batch && length < TEXT_CHUNK_BATCH_SIZE) {
return;
}
sink.enqueue(textContent, length);
textContent.items = [];
textContent.styles = Object.create(null);
}
const timeSlotManager = new TimeSlotManager();
return new Promise(function promiseBody(resolve, reject) {
const next = function (promise) {
enqueueChunk(true);
Promise.all([promise, sink.ready]).then(function () {
try {
promiseBody(resolve, reject);
} catch (ex) {
reject(ex);
}
}, reject);
};
task.ensureNotTerminated();
timeSlotManager.reset();
const operation = {};
let stop,
name,
isValidName,
args = [];
while (!(stop = timeSlotManager.check())) {
args.length = 0;
operation.args = args;
if (!preprocessor.read(operation)) {
break;
}
const previousState = textState;
textState = stateManager.state;
const fn = operation.fn;
args = operation.args;
switch (fn | 0) {
case OPS.setFont:
const fontNameArg = args[0].name,
fontSizeArg = args[1];
if (textState.font && fontNameArg === textState.fontName && fontSizeArg === textState.fontSize) {
break;
}
flushTextContentItem();
textState.fontName = fontNameArg;
textState.fontSize = fontSizeArg;
next(handleSetFont(fontNameArg, null));
return;
case OPS.setTextRise:
textState.textRise = args[0];
break;
case OPS.setHScale:
textState.textHScale = args[0] / 100;
break;
case OPS.setLeading:
textState.leading = args[0];
break;
case OPS.moveText:
textState.translateTextLineMatrix(args[0], args[1]);
textState.textMatrix = textState.textLineMatrix.slice();
break;
case OPS.setLeadingMoveText:
textState.leading = -args[1];
textState.translateTextLineMatrix(args[0], args[1]);
textState.textMatrix = textState.textLineMatrix.slice();
break;
case OPS.nextLine:
textState.carriageReturn();
break;
case OPS.setTextMatrix:
textState.setTextMatrix(args[0], args[1], args[2], args[3], args[4], args[5]);
textState.setTextLineMatrix(args[0], args[1], args[2], args[3], args[4], args[5]);
updateAdvanceScale();
break;
case OPS.setCharSpacing:
textState.charSpacing = args[0];
break;
case OPS.setWordSpacing:
textState.wordSpacing = args[0];
break;
case OPS.beginText:
textState.textMatrix = IDENTITY_MATRIX.slice();
textState.textLineMatrix = IDENTITY_MATRIX.slice();
break;
case OPS.showSpacedText:
if (!stateManager.state.font) {
self.ensureStateFont(stateManager.state);
continue;
}
const spaceFactor = (textState.font.vertical ? 1 : -1) * textState.fontSize / 1000;
const elements = args[0];
for (let i = 0, ii = elements.length; i < ii; i++) {
const item = elements[i];
if (typeof item === "string") {
showSpacedTextBuffer.push(item);
} else if (typeof item === "number" && item !== 0) {
const str = showSpacedTextBuffer.join("");
showSpacedTextBuffer.length = 0;
buildTextContentItem({
chars: str,
extraSpacing: item * spaceFactor
});
}
}
if (showSpacedTextBuffer.length > 0) {
const str = showSpacedTextBuffer.join("");
showSpacedTextBuffer.length = 0;
buildTextContentItem({
chars: str,
extraSpacing: 0
});
}
break;
case OPS.showText:
if (!stateManager.state.font) {
self.ensureStateFont(stateManager.state);
continue;
}
buildTextContentItem({
chars: args[0],
extraSpacing: 0
});
break;
case OPS.nextLineShowText:
if (!stateManager.state.font) {
self.ensureStateFont(stateManager.state);
continue;
}
textState.carriageReturn();
buildTextContentItem({
chars: args[0],
extraSpacing: 0
});
break;
case OPS.nextLineSetSpacingShowText:
if (!stateManager.state.font) {
self.ensureStateFont(stateManager.state);
continue;
}
textState.wordSpacing = args[0];
textState.charSpacing = args[1];
textState.carriageReturn();
buildTextContentItem({
chars: args[2],
extraSpacing: 0
});
break;
case OPS.paintXObject:
flushTextContentItem();
xobjs ??= resources.get("XObject") || Dict.empty;
isValidName = args[0] instanceof Name;
name = args[0].name;
if (isValidName && emptyXObjectCache.getByName(name)) {
break;
}
next(new Promise(function (resolveXObject, rejectXObject) {
if (!isValidName) {
throw new FormatError("XObject must be referred to by name.");
}
let xobj = xobjs.getRaw(name);
if (xobj instanceof Ref) {
if (emptyXObjectCache.getByRef(xobj)) {
resolveXObject();
return;
}
const globalImage = self.globalImageCache.getData(xobj, self.pageIndex);
if (globalImage) {
resolveXObject();
return;
}
xobj = xref.fetch(xobj);
}
if (!(xobj instanceof BaseStream)) {
throw new FormatError("XObject should be a stream");
}
const type = xobj.dict.get("Subtype");
if (!(type instanceof Name)) {
throw new FormatError("XObject should have a Name subtype");
}
if (type.name !== "Form") {
emptyXObjectCache.set(name, xobj.dict.objId, true);
resolveXObject();
return;
}
const currentState = stateManager.state.clone();
const xObjStateManager = new StateManager(currentState);
const matrix = lookupMatrix(xobj.dict.getArray("Matrix"), null);
if (matrix) {
xObjStateManager.transform(matrix);
}
enqueueChunk();
const sinkWrapper = {
enqueueInvoked: false,
enqueue(chunk, size) {
this.enqueueInvoked = true;
sink.enqueue(chunk, size);
},
get desiredSize() {
return sink.desiredSize;
},
get ready() {
return sink.ready;
}
};
self.getTextContent({
stream: xobj,
task,
resources: xobj.dict.get("Resources") || resources,
stateManager: xObjStateManager,
includeMarkedContent,
sink: sinkWrapper,
seenStyles,
viewBox,
lang,
markedContentData,
disableNormalization,
keepWhiteSpace
}).then(function () {
if (!sinkWrapper.enqueueInvoked) {
emptyXObjectCache.set(name, xobj.dict.objId, true);
}
resolveXObject();
}, rejectXObject);
}).catch(function (reason) {
if (reason instanceof AbortException) {
return;
}
if (self.options.ignoreErrors) {
warn(`getTextContent - ignoring XObject: "${reason}".`);
return;
}
throw reason;
}));
return;
case OPS.setGState:
isValidName = args[0] instanceof Name;
name = args[0].name;
if (isValidName && emptyGStateCache.getByName(name)) {
break;
}
next(new Promise(function (resolveGState, rejectGState) {
if (!isValidName) {
throw new FormatError("GState must be referred to by name.");
}
const extGState = resources.get("ExtGState");
if (!(extGState instanceof Dict)) {
throw new FormatError("ExtGState should be a dictionary.");
}
const gState = extGState.get(name);
if (!(gState instanceof Dict)) {
throw new FormatError("GState should be a dictionary.");
}
const gStateFont = gState.get("Font");
if (!gStateFont) {
emptyGStateCache.set(name, gState.objId, true);
resolveGState();
return;
}
flushTextContentItem();
textState.fontName = null;
textState.fontSize = gStateFont[1];
handleSetFont(null, gStateFont[0]).then(resolveGState, rejectGState);
}).catch(function (reason) {
if (reason instanceof AbortException) {
return;
}
if (self.options.ignoreErrors) {
warn(`getTextContent - ignoring ExtGState: "${reason}".`);
return;
}
throw reason;
}));
return;
case OPS.beginMarkedContent:
flushTextContentItem();
if (includeMarkedContent) {
markedContentData.level++;
textContent.items.push({
type: "beginMarkedContent",
tag: args[0] instanceof Name ? args[0].name : null
});
}
break;
case OPS.beginMarkedContentProps:
flushTextContentItem();
if (includeMarkedContent) {
markedContentData.level++;
let mcid = null;
if (args[1] instanceof Dict) {
mcid = args[1].get("MCID");
}
textContent.items.push({
type: "beginMarkedContentProps",
id: Number.isInteger(mcid) ? `${self.idFactory.getPageObjId()}_mc${mcid}` : null,
tag: args[0] instanceof Name ? args[0].name : null
});
}
break;
case OPS.endMarkedContent:
flushTextContentItem();
if (includeMarkedContent) {
if (markedContentData.level === 0) {
break;
}
markedContentData.level--;
textContent.items.push({
type: "endMarkedContent"
});
}
break;
case OPS.restore:
if (previousState && (previousState.font !== textState.font || previousState.fontSize !== textState.fontSize || previousState.fontName !== textState.fontName)) {
flushTextContentItem();
}
break;
}
if (textContent.items.length >= sink.desiredSize) {
stop = true;
break;
}
}
if (stop) {
next(deferred);
return;
}
flushTextContentItem();
enqueueChunk();
resolve();
}).catch(reason => {
if (reason instanceof AbortException) {
return;
}
if (this.options.ignoreErrors) {
warn(`getTextContent - ignoring errors during "${task.name}" ` + `task: "${reason}".`);
flushTextContentItem();
enqueueChunk();
return;
}
throw reason;
});
}
async extractDataStructures(dict, properties) {
const xref = this.xref;
let cidToGidBytes;
const toUnicodePromise = this.readToUnicode(properties.toUnicode);
if (properties.composite) {
const cidSystemInfo = dict.get("CIDSystemInfo");
if (cidSystemInfo instanceof Dict) {
properties.cidSystemInfo = {
registry: stringToPDFString(cidSystemInfo.get("Registry")),
ordering: stringToPDFString(cidSystemInfo.get("Ordering")),
supplement: cidSystemInfo.get("Supplement")
};
}
try {
const cidToGidMap = dict.get("CIDToGIDMap");
if (cidToGidMap instanceof BaseStream) {
cidToGidBytes = cidToGidMap.getBytes();
}
} catch (ex) {
if (!this.options.ignoreErrors) {
throw ex;
}
warn(`extractDataStructures - ignoring CIDToGIDMap data: "${ex}".`);
}
}
const differences = [];
let baseEncodingName = null;
let encoding;
if (dict.has("Encoding")) {
encoding = dict.get("Encoding");
if (encoding instanceof Dict) {
baseEncodingName = encoding.get("BaseEncoding");
baseEncodingName = baseEncodingName instanceof Name ? baseEncodingName.name : null;
if (encoding.has("Differences")) {
const diffEncoding = encoding.get("Differences");
let index = 0;
for (const entry of diffEncoding) {
const data = xref.fetchIfRef(entry);
if (typeof data === "number") {
index = data;
} else if (data instanceof Name) {
differences[index++] = data.name;
} else {
throw new FormatError(`Invalid entry in 'Differences' array: ${data}`);
}
}
}
} else if (encoding instanceof Name) {
baseEncodingName = encoding.name;
} else {
const msg = "Encoding is not a Name nor a Dict";
if (!this.options.ignoreErrors) {
throw new FormatError(msg);
}
warn(msg);
}
if (baseEncodingName !== "MacRomanEncoding" && baseEncodingName !== "MacExpertEncoding" && baseEncodingName !== "WinAnsiEncoding") {
baseEncodingName = null;
}
}
const nonEmbeddedFont = !properties.file || properties.isInternalFont,
isSymbolsFontName = getSymbolsFonts()[properties.name];
if (baseEncodingName && nonEmbeddedFont && isSymbolsFontName) {
baseEncodingName = null;
}
if (baseEncodingName) {
properties.defaultEncoding = getEncoding(baseEncodingName);
} else {
const isSymbolicFont = !!(properties.flags & FontFlags.Symbolic);
const isNonsymbolicFont = !!(properties.flags & FontFlags.Nonsymbolic);
encoding = StandardEncoding;
if (properties.type === "TrueType" && !isNonsymbolicFont) {
encoding = WinAnsiEncoding;
}
if (isSymbolicFont || isSymbolsFontName) {
encoding = MacRomanEncoding;
if (nonEmbeddedFont) {
if (/Symbol/i.test(properties.name)) {
encoding = SymbolSetEncoding;
} else if (/Dingbats/i.test(properties.name)) {
encoding = ZapfDingbatsEncoding;
} else if (/Wingdings/i.test(properties.name)) {
encoding = WinAnsiEncoding;
}
}
}
properties.defaultEncoding = encoding;
}
properties.differences = differences;
properties.baseEncodingName = baseEncodingName;
properties.hasEncoding = !!baseEncodingName || differences.length > 0;
properties.dict = dict;
properties.toUnicode = await toUnicodePromise;
const builtToUnicode = await this.buildToUnicode(properties);
properties.toUnicode = builtToUnicode;
if (cidToGidBytes) {
properties.cidToGidMap = this.readCidToGidMap(cidToGidBytes, builtToUnicode);
}
return properties;
}
_simpleFontToUnicode(properties, forceGlyphs = false) {
assert(!properties.composite, "Must be a simple font.");
const toUnicode = [];
const encoding = properties.defaultEncoding.slice();
const baseEncodingName = properties.baseEncodingName;
const differences = properties.differences;
for (const charcode in differences) {
const glyphName = differences[charcode];
if (glyphName === ".notdef") {
continue;
}
encoding[charcode] = glyphName;
}
const glyphsUnicodeMap = getGlyphsUnicode();
for (const charcode in encoding) {
let glyphName = encoding[charcode];
if (glyphName === "") {
continue;
}
let unicode = glyphsUnicodeMap[glyphName];
if (unicode !== undefined) {
toUnicode[charcode] = String.fromCharCode(unicode);
continue;
}
let code = 0;
switch (glyphName[0]) {
case "G":
if (glyphName.length === 3) {
code = parseInt(glyphName.substring(1), 16);
}
break;
case "g":
if (glyphName.length === 5) {
code = parseInt(glyphName.substring(1), 16);
}
break;
case "C":
case "c":
if (glyphName.length >= 3 && glyphName.length <= 4) {
const codeStr = glyphName.substring(1);
if (forceGlyphs) {
code = parseInt(codeStr, 16);
break;
}
code = +codeStr;
if (Number.isNaN(code) && Number.isInteger(parseInt(codeStr, 16))) {
return this._simpleFontToUnicode(properties, true);
}
}
break;
case "u":
unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap);
if (unicode !== -1) {
code = unicode;
}
break;
default:
switch (glyphName) {
case "f_h":
case "f_t":
case "T_h":
toUnicode[charcode] = glyphName.replaceAll("_", "");
continue;
}
break;
}
if (code > 0 && code <= 0x10ffff && Number.isInteger(code)) {
if (baseEncodingName && code === +charcode) {
const baseEncoding = getEncoding(baseEncodingName);
if (baseEncoding && (glyphName = baseEncoding[charcode])) {
toUnicode[charcode] = String.fromCharCode(glyphsUnicodeMap[glyphName]);
continue;
}
}
toUnicode[charcode] = String.fromCodePoint(code);
}
}
return toUnicode;
}
async buildToUnicode(properties) {
properties.hasIncludedToUnicodeMap = properties.toUnicode?.length > 0;
if (properties.hasIncludedToUnicodeMap) {
if (!properties.composite && properties.hasEncoding) {
properties.fallbackToUnicode = this._simpleFontToUnicode(properties);
}
return properties.toUnicode;
}
if (!properties.composite) {
return new ToUnicodeMap(this._simpleFontToUnicode(properties));
}
if (properties.composite && (properties.cMap.builtInCMap && !(properties.cMap instanceof IdentityCMap) || properties.cidSystemInfo?.registry === "Adobe" && (properties.cidSystemInfo.ordering === "GB1" || properties.cidSystemInfo.ordering === "CNS1" || properties.cidSystemInfo.ordering === "Japan1" || properties.cidSystemInfo.ordering === "Korea1"))) {
const {
registry,
ordering
} = properties.cidSystemInfo;
const ucs2CMapName = Name.get(`${registry}-${ordering}-UCS2`);
const ucs2CMap = await CMapFactory.create({
encoding: ucs2CMapName,
fetchBuiltInCMap: this._fetchBuiltInCMapBound,
useCMap: null
});
const toUnicode = [],
buf = [];
properties.cMap.forEach(function (charcode, cid) {
if (cid > 0xffff) {
throw new FormatError("Max size of CID is 65,535");
}
const ucs2 = ucs2CMap.lookup(cid);
if (ucs2) {
buf.length = 0;
for (let i = 0, ii = ucs2.length; i < ii; i += 2) {
buf.push((ucs2.charCodeAt(i) << 8) + ucs2.charCodeAt(i + 1));
}
toUnicode[charcode] = String.fromCharCode(...buf);
}
});
return new ToUnicodeMap(toUnicode);
}
return new IdentityToUnicodeMap(properties.firstChar, properties.lastChar);
}
async readToUnicode(cmapObj) {
if (!cmapObj) {
return null;
}
if (cmapObj instanceof Name) {
const cmap = await CMapFactory.create({
encoding: cmapObj,
fetchBuiltInCMap: this._fetchBuiltInCMapBound,
useCMap: null
});
if (cmap instanceof IdentityCMap) {
return new IdentityToUnicodeMap(0, 0xffff);
}
return new ToUnicodeMap(cmap.getMap());
}
if (cmapObj instanceof BaseStream) {
try {
const cmap = await CMapFactory.create({
encoding: cmapObj,
fetchBuiltInCMap: this._fetchBuiltInCMapBound,
useCMap: null
});
if (cmap instanceof IdentityCMap) {
return new IdentityToUnicodeMap(0, 0xffff);
}
const map = new Array(cmap.length);
cmap.forEach(function (charCode, token) {
if (typeof token === "number") {
map[charCode] = String.fromCodePoint(token);
return;
}
if (token.length % 2 !== 0) {
token = "\u0000" + token;
}
const str = [];
for (let k = 0; k < token.length; k += 2) {
const w1 = token.charCodeAt(k) << 8 | token.charCodeAt(k + 1);
if ((w1 & 0xf800) !== 0xd800) {
str.push(w1);
continue;
}
k += 2;
const w2 = token.charCodeAt(k) << 8 | token.charCodeAt(k + 1);
str.push(((w1 & 0x3ff) << 10) + (w2 & 0x3ff) + 0x10000);
}
map[charCode] = String.fromCodePoint(...str);
});
return new ToUnicodeMap(map);
} catch (reason) {
if (reason instanceof AbortException) {
return null;
}
if (this.options.ignoreErrors) {
warn(`readToUnicode - ignoring ToUnicode data: "${reason}".`);
return null;
}
throw reason;
}
}
return null;
}
readCidToGidMap(glyphsData, toUnicode) {
const result = [];
for (let j = 0, jj = glyphsData.length; j < jj; j++) {
const glyphID = glyphsData[j++] << 8 | glyphsData[j];
const code = j >> 1;
if (glyphID === 0 && !toUnicode.has(code)) {
continue;
}
result[code] = glyphID;
}
return result;
}
extractWidths(dict, descriptor, properties) {
const xref = this.xref;
let glyphsWidths = [];
let defaultWidth = 0;
const glyphsVMetrics = [];
let defaultVMetrics;
if (properties.composite) {
const dw = dict.get("DW");
defaultWidth = typeof dw === "number" ? Math.ceil(dw) : 1000;
const widths = dict.get("W");
if (Array.isArray(widths)) {
for (let i = 0, ii = widths.length; i < ii; i++) {
let start = xref.fetchIfRef(widths[i++]);
if (!Number.isInteger(start)) {
break;
}
const code = xref.fetchIfRef(widths[i]);
if (Array.isArray(code)) {
for (const c of code) {
const width = xref.fetchIfRef(c);
if (typeof width === "number") {
glyphsWidths[start] = width;
}
start++;
}
} else if (Number.isInteger(code)) {
const width = xref.fetchIfRef(widths[++i]);
if (typeof width !== "number") {
continue;
}
for (let j = start; j <= code; j++) {
glyphsWidths[j] = width;
}
} else {
break;
}
}
}
if (properties.vertical) {
const dw2 = dict.getArray("DW2");
let vmetrics = isNumberArray(dw2, 2) ? dw2 : [880, -1000];
defaultVMetrics = [vmetrics[1], defaultWidth * 0.5, vmetrics[0]];
vmetrics = dict.get("W2");
if (Array.isArray(vmetrics)) {
for (let i = 0, ii = vmetrics.length; i < ii; i++) {
let start = xref.fetchIfRef(vmetrics[i++]);
if (!Number.isInteger(start)) {
break;
}
const code = xref.fetchIfRef(vmetrics[i]);
if (Array.isArray(code)) {
for (let j = 0, jj = code.length; j < jj; j++) {
const vmetric = [xref.fetchIfRef(code[j++]), xref.fetchIfRef(code[j++]), xref.fetchIfRef(code[j])];
if (isNumberArray(vmetric, null)) {
glyphsVMetrics[start] = vmetric;
}
start++;
}
} else if (Number.isInteger(code)) {
const vmetric = [xref.fetchIfRef(vmetrics[++i]), xref.fetchIfRef(vmetrics[++i]), xref.fetchIfRef(vmetrics[++i])];
if (!isNumberArray(vmetric, null)) {
continue;
}
for (let j = start; j <= code; j++) {
glyphsVMetrics[j] = vmetric;
}
} else {
break;
}
}
}
}
} else {
const widths = dict.get("Widths");
if (Array.isArray(widths)) {
let j = properties.firstChar;
for (const w of widths) {
const width = xref.fetchIfRef(w);
if (typeof width === "number") {
glyphsWidths[j] = width;
}
j++;
}
const missingWidth = descriptor.get("MissingWidth");
defaultWidth = typeof missingWidth === "number" ? missingWidth : 0;
} else {
const baseFontName = dict.get("BaseFont");
if (baseFontName instanceof Name) {
const metrics = this.getBaseFontMetrics(baseFontName.name);
glyphsWidths = this.buildCharCodeToWidth(metrics.widths, properties);
defaultWidth = metrics.defaultWidth;
}
}
}
let isMonospace = true;
let firstWidth = defaultWidth;
for (const glyph in glyphsWidths) {
const glyphWidth = glyphsWidths[glyph];
if (!glyphWidth) {
continue;
}
if (!firstWidth) {
firstWidth = glyphWidth;
continue;
}
if (firstWidth !== glyphWidth) {
isMonospace = false;
break;
}
}
if (isMonospace) {
properties.flags |= FontFlags.FixedPitch;
} else {
properties.flags &= ~FontFlags.FixedPitch;
}
properties.defaultWidth = defaultWidth;
properties.widths = glyphsWidths;
properties.defaultVMetrics = defaultVMetrics;
properties.vmetrics = glyphsVMetrics;
}
isSerifFont(baseFontName) {
const fontNameWoStyle = baseFontName.split("-", 1)[0];
return fontNameWoStyle in getSerifFonts() || /serif/gi.test(fontNameWoStyle);
}
getBaseFontMetrics(name) {
let defaultWidth = 0;
let widths = Object.create(null);
let monospace = false;
const stdFontMap = getStdFontMap();
let lookupName = stdFontMap[name] || name;
const Metrics = getMetrics();
if (!(lookupName in Metrics)) {
lookupName = this.isSerifFont(name) ? "Times-Roman" : "Helvetica";
}
const glyphWidths = Metrics[lookupName];
if (typeof glyphWidths === "number") {
defaultWidth = glyphWidths;
monospace = true;
} else {
widths = glyphWidths();
}
return {
defaultWidth,
monospace,
widths
};
}
buildCharCodeToWidth(widthsByGlyphName, properties) {
const widths = Object.create(null);
const differences = properties.differences;
const encoding = properties.defaultEncoding;
for (let charCode = 0; charCode < 256; charCode++) {
if (charCode in differences && widthsByGlyphName[differences[charCode]]) {
widths[charCode] = widthsByGlyphName[differences[charCode]];
continue;
}
if (charCode in encoding && widthsByGlyphName[encoding[charCode]]) {
widths[charCode] = widthsByGlyphName[encoding[charCode]];
continue;
}
}
return widths;
}
preEvaluateFont(dict) {
const baseDict = dict;
let type = dict.get("Subtype");
if (!(type instanceof Name)) {
throw new FormatError("invalid font Subtype");
}
let composite = false;
let hash;
if (type.name === "Type0") {
const df = dict.get("DescendantFonts");
if (!df) {
throw new FormatError("Descendant fonts are not specified");
}
dict = Array.isArray(df) ? this.xref.fetchIfRef(df[0]) : df;
if (!(dict instanceof Dict)) {
throw new FormatError("Descendant font is not a dictionary.");
}
type = dict.get("Subtype");
if (!(type instanceof Name)) {
throw new FormatError("invalid font Subtype");
}
composite = true;
}
let firstChar = dict.get("FirstChar");
if (!Number.isInteger(firstChar)) {
firstChar = 0;
}
let lastChar = dict.get("LastChar");
if (!Number.isInteger(lastChar)) {
lastChar = composite ? 0xffff : 0xff;
}
const descriptor = dict.get("FontDescriptor");
const toUnicode = dict.get("ToUnicode") || baseDict.get("ToUnicode");
if (descriptor) {
hash = new MurmurHash3_64();
const encoding = baseDict.getRaw("Encoding");
if (encoding instanceof Name) {
hash.update(encoding.name);
} else if (encoding instanceof Ref) {
hash.update(encoding.toString());
} else if (encoding instanceof Dict) {
for (const entry of encoding.getRawValues()) {
if (entry instanceof Name) {
hash.update(entry.name);
} else if (entry instanceof Ref) {
hash.update(entry.toString());
} else if (Array.isArray(entry)) {
const diffLength = entry.length,
diffBuf = new Array(diffLength);
for (let j = 0; j < diffLength; j++) {
const diffEntry = entry[j];
if (diffEntry instanceof Name) {
diffBuf[j] = diffEntry.name;
} else if (typeof diffEntry === "number" || diffEntry instanceof Ref) {
diffBuf[j] = diffEntry.toString();
}
}
hash.update(diffBuf.join());
}
}
}
hash.update(`${firstChar}-${lastChar}`);
if (toUnicode instanceof BaseStream) {
const stream = toUnicode.str || toUnicode;
const uint8array = stream.buffer ? new Uint8Array(stream.buffer.buffer, 0, stream.bufferLength) : new Uint8Array(stream.bytes.buffer, stream.start, stream.end - stream.start);
hash.update(uint8array);
} else if (toUnicode instanceof Name) {
hash.update(toUnicode.name);
}
const widths = dict.get("Widths") || baseDict.get("Widths");
if (Array.isArray(widths)) {
const widthsBuf = [];
for (const entry of widths) {
if (typeof entry === "number" || entry instanceof Ref) {
widthsBuf.push(entry.toString());
}
}
hash.update(widthsBuf.join());
}
if (composite) {
hash.update("compositeFont");
const compositeWidths = dict.get("W") || baseDict.get("W");
if (Array.isArray(compositeWidths)) {
const widthsBuf = [];
for (const entry of compositeWidths) {
if (typeof entry === "number" || entry instanceof Ref) {
widthsBuf.push(entry.toString());
} else if (Array.isArray(entry)) {
const subWidthsBuf = [];
for (const element of entry) {
if (typeof element === "number" || element instanceof Ref) {
subWidthsBuf.push(element.toString());
}
}
widthsBuf.push(`[${subWidthsBuf.join()}]`);
}
}
hash.update(widthsBuf.join());
}
const cidToGidMap = dict.getRaw("CIDToGIDMap") || baseDict.getRaw("CIDToGIDMap");
if (cidToGidMap instanceof Name) {
hash.update(cidToGidMap.name);
} else if (cidToGidMap instanceof Ref) {
hash.update(cidToGidMap.toString());
} else if (cidToGidMap instanceof BaseStream) {
hash.update(cidToGidMap.peekBytes());
}
}
}
return {
descriptor,
dict,
baseDict,
composite,
type: type.name,
firstChar,
lastChar,
toUnicode,
hash: hash ? hash.hexdigest() : ""
};
}
async translateFont({
descriptor,
dict,
baseDict,
composite,
type,
firstChar,
lastChar,
toUnicode,
cssFontInfo
}) {
const isType3Font = type === "Type3";
if (!descriptor) {
if (isType3Font) {
const bbox = lookupNormalRect(dict.getArray("FontBBox"), [0, 0, 0, 0]);
descriptor = new Dict(null);
descriptor.set("FontName", Name.get(type));
descriptor.set("FontBBox", bbox);
} else {
let baseFontName = dict.get("BaseFont");
if (!(baseFontName instanceof Name)) {
throw new FormatError("Base font is not specified");
}
baseFontName = baseFontName.name.replaceAll(/[,_]/g, "-");
const metrics = this.getBaseFontMetrics(baseFontName);
const fontNameWoStyle = baseFontName.split("-", 1)[0];
const flags = (this.isSerifFont(fontNameWoStyle) ? FontFlags.Serif : 0) | (metrics.monospace ? FontFlags.FixedPitch : 0) | (getSymbolsFonts()[fontNameWoStyle] ? FontFlags.Symbolic : FontFlags.Nonsymbolic);
const properties = {
type,
name: baseFontName,
loadedName: baseDict.loadedName,
systemFontInfo: null,
widths: metrics.widths,
defaultWidth: metrics.defaultWidth,
isSimulatedFlags: true,
flags,
firstChar,
lastChar,
toUnicode,
xHeight: 0,
capHeight: 0,
italicAngle: 0,
isType3Font
};
const widths = dict.get("Widths");
const standardFontName = getStandardFontName(baseFontName);
let file = null;
if (standardFontName) {
file = await this.fetchStandardFontData(standardFontName);
properties.isInternalFont = !!file;
}
if (!properties.isInternalFont && this.options.useSystemFonts) {
properties.systemFontInfo = getFontSubstitution(this.systemFontCache, this.idFactory, this.options.standardFontDataUrl, baseFontName, standardFontName, type);
}
const newProperties = await this.extractDataStructures(dict, properties);
if (Array.isArray(widths)) {
const glyphWidths = [];
let j = firstChar;
for (const w of widths) {
const width = this.xref.fetchIfRef(w);
if (typeof width === "number") {
glyphWidths[j] = width;
}
j++;
}
newProperties.widths = glyphWidths;
} else {
newProperties.widths = this.buildCharCodeToWidth(metrics.widths, newProperties);
}
return new Font(baseFontName, file, newProperties, this.options);
}
}
let fontName = descriptor.get("FontName");
let baseFont = dict.get("BaseFont");
if (typeof fontName === "string") {
fontName = Name.get(fontName);
}
if (typeof baseFont === "string") {
baseFont = Name.get(baseFont);
}
const fontNameStr = fontName?.name;
const baseFontStr = baseFont?.name;
if (!isType3Font && fontNameStr !== baseFontStr) {
info(`The FontDescriptor's FontName is "${fontNameStr}" but ` + `should be the same as the Font's BaseFont "${baseFontStr}".`);
if (fontNameStr && baseFontStr && (baseFontStr.startsWith(fontNameStr) || !isKnownFontName(fontNameStr) && isKnownFontName(baseFontStr))) {
fontName = null;
}
}
fontName ||= baseFont;
if (!(fontName instanceof Name)) {
throw new FormatError("invalid font name");
}
let fontFile, subtype, length1, length2, length3;
try {
fontFile = descriptor.get("FontFile", "FontFile2", "FontFile3");
if (fontFile) {
if (!(fontFile instanceof BaseStream)) {
throw new FormatError("FontFile should be a stream");
} else if (fontFile.isEmpty) {
throw new FormatError("FontFile is empty");
}
}
} catch (ex) {
if (!this.options.ignoreErrors) {
throw ex;
}
warn(`translateFont - fetching "${fontName.name}" font file: "${ex}".`);
fontFile = null;
}
let isInternalFont = false;
let glyphScaleFactors = null;
let systemFontInfo = null;
if (fontFile) {
if (fontFile.dict) {
const subtypeEntry = fontFile.dict.get("Subtype");
if (subtypeEntry instanceof Name) {
subtype = subtypeEntry.name;
}
length1 = fontFile.dict.get("Length1");
length2 = fontFile.dict.get("Length2");
length3 = fontFile.dict.get("Length3");
}
} else if (cssFontInfo) {
const standardFontName = getXfaFontName(fontName.name);
if (standardFontName) {
cssFontInfo.fontFamily = `${cssFontInfo.fontFamily}-PdfJS-XFA`;
cssFontInfo.metrics = standardFontName.metrics || null;
glyphScaleFactors = standardFontName.factors || null;
fontFile = await this.fetchStandardFontData(standardFontName.name);
isInternalFont = !!fontFile;
baseDict = dict = getXfaFontDict(fontName.name);
composite = true;
}
} else if (!isType3Font) {
const standardFontName = getStandardFontName(fontName.name);
if (standardFontName) {
fontFile = await this.fetchStandardFontData(standardFontName);
isInternalFont = !!fontFile;
}
if (!isInternalFont && this.options.useSystemFonts) {
systemFontInfo = getFontSubstitution(this.systemFontCache, this.idFactory, this.options.standardFontDataUrl, fontName.name, standardFontName, type);
}
}
const fontMatrix = lookupMatrix(dict.getArray("FontMatrix"), FONT_IDENTITY_MATRIX);
const bbox = lookupNormalRect(descriptor.getArray("FontBBox") || dict.getArray("FontBBox"), undefined);
let ascent = descriptor.get("Ascent");
if (typeof ascent !== "number") {
ascent = undefined;
}
let descent = descriptor.get("Descent");
if (typeof descent !== "number") {
descent = undefined;
}
let xHeight = descriptor.get("XHeight");
if (typeof xHeight !== "number") {
xHeight = 0;
}
let capHeight = descriptor.get("CapHeight");
if (typeof capHeight !== "number") {
capHeight = 0;
}
let flags = descriptor.get("Flags");
if (!Number.isInteger(flags)) {
flags = 0;
}
let italicAngle = descriptor.get("ItalicAngle");
if (typeof italicAngle !== "number") {
italicAngle = 0;
}
const properties = {
type,
name: fontName.name,
subtype,
file: fontFile,
length1,
length2,
length3,
isInternalFont,
loadedName: baseDict.loadedName,
composite,
fixedPitch: false,
fontMatrix,
firstChar,
lastChar,
toUnicode,
bbox,
ascent,
descent,
xHeight,
capHeight,
flags,
italicAngle,
isType3Font,
cssFontInfo,
scaleFactors: glyphScaleFactors,
systemFontInfo
};
if (composite) {
const cidEncoding = baseDict.get("Encoding");
if (cidEncoding instanceof Name) {
properties.cidEncoding = cidEncoding.name;
}
const cMap = await CMapFactory.create({
encoding: cidEncoding,
fetchBuiltInCMap: this._fetchBuiltInCMapBound,
useCMap: null
});
properties.cMap = cMap;
properties.vertical = properties.cMap.vertical;
}
const newProperties = await this.extractDataStructures(dict, properties);
this.extractWidths(dict, descriptor, newProperties);
return new Font(fontName.name, fontFile, newProperties, this.options);
}
static buildFontPaths(font, glyphs, handler, evaluatorOptions) {
function buildPath(fontChar) {
const glyphName = `${font.loadedName}_path_${fontChar}`;
try {
if (font.renderer.hasBuiltPath(fontChar)) {
return;
}
handler.send("commonobj", [glyphName, "FontPath", font.renderer.getPathJs(fontChar)]);
} catch (reason) {
if (evaluatorOptions.ignoreErrors) {
warn(`buildFontPaths - ignoring ${glyphName} glyph: "${reason}".`);
return;
}
throw reason;
}
}
for (const glyph of glyphs) {
buildPath(glyph.fontChar);
const accent = glyph.accent;
if (accent?.fontChar) {
buildPath(accent.fontChar);
}
}
}
static get fallbackFontDict() {
const dict = new Dict();
dict.set("BaseFont", Name.get("Helvetica"));
dict.set("Type", Name.get("FallbackType"));
dict.set("Subtype", Name.get("FallbackType"));
dict.set("Encoding", Name.get("WinAnsiEncoding"));
return shadow(this, "fallbackFontDict", dict);
}
}
class TranslatedFont {
#sent = false;
#type3Loaded = null;
constructor({
loadedName,
font,
dict
}) {
this.loadedName = loadedName;
this.font = font;
this.dict = dict;
this.type3Dependencies = font.isType3Font ? new Set() : null;
}
send(handler) {
if (this.#sent) {
return;
}
this.#sent = true;
handler.send("commonobj", [this.loadedName, "Font", this.font.exportData()]);
}
fallback(handler, evaluatorOptions) {
if (!this.font.data) {
return;
}
this.font.disableFontFace = true;
PartialEvaluator.buildFontPaths(this.font, this.font.glyphCacheValues, handler, evaluatorOptions);
}
loadType3Data(evaluator, resources, task) {
if (this.#type3Loaded) {
return this.#type3Loaded;
}
const {
font,
type3Dependencies
} = this;
assert(font.isType3Font, "Must be a Type3 font.");
const type3Evaluator = evaluator.clone({
ignoreErrors: false
});
const type3FontRefs = new RefSet(evaluator.type3FontRefs);
if (this.dict.objId && !type3FontRefs.has(this.dict.objId)) {
type3FontRefs.put(this.dict.objId);
}
type3Evaluator.type3FontRefs = type3FontRefs;
let loadCharProcsPromise = Promise.resolve();
const charProcs = this.dict.get("CharProcs");
const fontResources = this.dict.get("Resources") || resources;
const charProcOperatorList = Object.create(null);
const fontBBox = Util.normalizeRect(font.bbox || [0, 0, 0, 0]),
width = fontBBox[2] - fontBBox[0],
height = fontBBox[3] - fontBBox[1];
const fontBBoxSize = Math.hypot(width, height);
for (const key of charProcs.getKeys()) {
loadCharProcsPromise = loadCharProcsPromise.then(() => {
const glyphStream = charProcs.get(key);
const operatorList = new OperatorList();
return type3Evaluator.getOperatorList({
stream: glyphStream,
task,
resources: fontResources,
operatorList
}).then(() => {
switch (operatorList.fnArray[0]) {
case OPS.setCharWidthAndBounds:
this.#removeType3ColorOperators(operatorList, fontBBoxSize);
break;
case OPS.setCharWidth:
if (!fontBBoxSize) {
this.#guessType3FontBBox(operatorList);
}
break;
}
charProcOperatorList[key] = operatorList.getIR();
for (const dependency of operatorList.dependencies) {
type3Dependencies.add(dependency);
}
}).catch(function (reason) {
warn(`Type3 font resource "${key}" is not available.`);
const dummyOperatorList = new OperatorList();
charProcOperatorList[key] = dummyOperatorList.getIR();
});
});
}
this.#type3Loaded = loadCharProcsPromise.then(() => {
font.charProcOperatorList = charProcOperatorList;
if (this._bbox) {
font.isCharBBox = true;
font.bbox = this._bbox;
}
});
return this.#type3Loaded;
}
#removeType3ColorOperators(operatorList, fontBBoxSize = NaN) {
const charBBox = Util.normalizeRect(operatorList.argsArray[0].slice(2)),
width = charBBox[2] - charBBox[0],
height = charBBox[3] - charBBox[1];
const charBBoxSize = Math.hypot(width, height);
if (width === 0 || height === 0) {
operatorList.fnArray.splice(0, 1);
operatorList.argsArray.splice(0, 1);
} else if (fontBBoxSize === 0 || Math.round(charBBoxSize / fontBBoxSize) >= 10) {
this._bbox ??= [Infinity, Infinity, -Infinity, -Infinity];
Util.rectBoundingBox(...charBBox, this._bbox);
}
let i = 0,
ii = operatorList.length;
while (i < ii) {
switch (operatorList.fnArray[i]) {
case OPS.setCharWidthAndBounds:
break;
case OPS.setStrokeColorSpace:
case OPS.setFillColorSpace:
case OPS.setStrokeColor:
case OPS.setStrokeColorN:
case OPS.setFillColor:
case OPS.setFillColorN:
case OPS.setStrokeGray:
case OPS.setFillGray:
case OPS.setStrokeRGBColor:
case OPS.setFillRGBColor:
case OPS.setStrokeCMYKColor:
case OPS.setFillCMYKColor:
case OPS.shadingFill:
case OPS.setRenderingIntent:
operatorList.fnArray.splice(i, 1);
operatorList.argsArray.splice(i, 1);
ii--;
continue;
case OPS.setGState:
const [gStateObj] = operatorList.argsArray[i];
let j = 0,
jj = gStateObj.length;
while (j < jj) {
const [gStateKey] = gStateObj[j];
switch (gStateKey) {
case "TR":
case "TR2":
case "HT":
case "BG":
case "BG2":
case "UCR":
case "UCR2":
gStateObj.splice(j, 1);
jj--;
continue;
}
j++;
}
break;
}
i++;
}
}
#guessType3FontBBox(operatorList) {
let i = 1;
const ii = operatorList.length;
while (i < ii) {
switch (operatorList.fnArray[i]) {
case OPS.constructPath:
const minMax = operatorList.argsArray[i][2];
this._bbox ??= [Infinity, Infinity, -Infinity, -Infinity];
Util.rectBoundingBox(...minMax, this._bbox);
break;
}
i++;
}
}
}
class StateManager {
constructor(initialState = new EvalState()) {
this.state = initialState;
this.stateStack = [];
}
save() {
const old = this.state;
this.stateStack.push(this.state);
this.state = old.clone();
}
restore() {
const prev = this.stateStack.pop();
if (prev) {
this.state = prev;
}
}
transform(args) {
this.state.ctm = Util.transform(this.state.ctm, args);
}
}
class TextState {
constructor() {
this.ctm = new Float32Array(IDENTITY_MATRIX);
this.fontName = null;
this.fontSize = 0;
this.loadedName = null;
this.font = null;
this.fontMatrix = FONT_IDENTITY_MATRIX;
this.textMatrix = IDENTITY_MATRIX.slice();
this.textLineMatrix = IDENTITY_MATRIX.slice();
this.charSpacing = 0;
this.wordSpacing = 0;
this.leading = 0;
this.textHScale = 1;
this.textRise = 0;
}
setTextMatrix(a, b, c, d, e, f) {
const m = this.textMatrix;
m[0] = a;
m[1] = b;
m[2] = c;
m[3] = d;
m[4] = e;
m[5] = f;
}
setTextLineMatrix(a, b, c, d, e, f) {
const m = this.textLineMatrix;
m[0] = a;
m[1] = b;
m[2] = c;
m[3] = d;
m[4] = e;
m[5] = f;
}
translateTextMatrix(x, y) {
const m = this.textMatrix;
m[4] = m[0] * x + m[2] * y + m[4];
m[5] = m[1] * x + m[3] * y + m[5];
}
translateTextLineMatrix(x, y) {
const m = this.textLineMatrix;
m[4] = m[0] * x + m[2] * y + m[4];
m[5] = m[1] * x + m[3] * y + m[5];
}
carriageReturn() {
this.translateTextLineMatrix(0, -this.leading);
this.textMatrix = this.textLineMatrix.slice();
}
clone() {
const clone = Object.create(this);
clone.textMatrix = this.textMatrix.slice();
clone.textLineMatrix = this.textLineMatrix.slice();
clone.fontMatrix = this.fontMatrix.slice();
return clone;
}
}
class EvalState {
constructor() {
this.ctm = new Float32Array(IDENTITY_MATRIX);
this.font = null;
this.textRenderingMode = TextRenderingMode.FILL;
this._fillColorSpace = this._strokeColorSpace = ColorSpaceUtils.gray;
this.patternFillColorSpace = null;
this.patternStrokeColorSpace = null;
this.currentPointX = this.currentPointY = 0;
this.pathMinMax = new Float32Array([Infinity, Infinity, -Infinity, -Infinity]);
this.pathBuffer = [];
}
get fillColorSpace() {
return this._fillColorSpace;
}
set fillColorSpace(colorSpace) {
this._fillColorSpace = this.patternFillColorSpace = colorSpace;
}
get strokeColorSpace() {
return this._strokeColorSpace;
}
set strokeColorSpace(colorSpace) {
this._strokeColorSpace = this.patternStrokeColorSpace = colorSpace;
}
clone({
newPath = false
} = {}) {
const clone = Object.create(this);
if (newPath) {
clone.pathBuffer = [];
clone.pathMinMax = new Float32Array([Infinity, Infinity, -Infinity, -Infinity]);
}
return clone;
}
}
class EvaluatorPreprocessor {
static get opMap() {
return shadow(this, "opMap", Object.assign(Object.create(null), {
w: {
id: OPS.setLineWidth,
numArgs: 1,
variableArgs: false
},
J: {
id: OPS.setLineCap,
numArgs: 1,
variableArgs: false
},
j: {
id: OPS.setLineJoin,
numArgs: 1,
variableArgs: false
},
M: {
id: OPS.setMiterLimit,
numArgs: 1,
variableArgs: false
},
d: {
id: OPS.setDash,
numArgs: 2,
variableArgs: false
},
ri: {
id: OPS.setRenderingIntent,
numArgs: 1,
variableArgs: false
},
i: {
id: OPS.setFlatness,
numArgs: 1,
variableArgs: false
},
gs: {
id: OPS.setGState,
numArgs: 1,
variableArgs: false
},
q: {
id: OPS.save,
numArgs: 0,
variableArgs: false
},
Q: {
id: OPS.restore,
numArgs: 0,
variableArgs: false
},
cm: {
id: OPS.transform,
numArgs: 6,
variableArgs: false
},
m: {
id: OPS.moveTo,
numArgs: 2,
variableArgs: false
},
l: {
id: OPS.lineTo,
numArgs: 2,
variableArgs: false
},
c: {
id: OPS.curveTo,
numArgs: 6,
variableArgs: false
},
v: {
id: OPS.curveTo2,
numArgs: 4,
variableArgs: false
},
y: {
id: OPS.curveTo3,
numArgs: 4,
variableArgs: false
},
h: {
id: OPS.closePath,
numArgs: 0,
variableArgs: false
},
re: {
id: OPS.rectangle,
numArgs: 4,
variableArgs: false
},
S: {
id: OPS.stroke,
numArgs: 0,
variableArgs: false
},
s: {
id: OPS.closeStroke,
numArgs: 0,
variableArgs: false
},
f: {
id: OPS.fill,
numArgs: 0,
variableArgs: false
},
F: {
id: OPS.fill,
numArgs: 0,
variableArgs: false
},
"f*": {
id: OPS.eoFill,
numArgs: 0,
variableArgs: false
},
B: {
id: OPS.fillStroke,
numArgs: 0,
variableArgs: false
},
"B*": {
id: OPS.eoFillStroke,
numArgs: 0,
variableArgs: false
},
b: {
id: OPS.closeFillStroke,
numArgs: 0,
variableArgs: false
},
"b*": {
id: OPS.closeEOFillStroke,
numArgs: 0,
variableArgs: false
},
n: {
id: OPS.endPath,
numArgs: 0,
variableArgs: false
},
W: {
id: OPS.clip,
numArgs: 0,
variableArgs: false
},
"W*": {
id: OPS.eoClip,
numArgs: 0,
variableArgs: false
},
BT: {
id: OPS.beginText,
numArgs: 0,
variableArgs: false
},
ET: {
id: OPS.endText,
numArgs: 0,
variableArgs: false
},
Tc: {
id: OPS.setCharSpacing,
numArgs: 1,
variableArgs: false
},
Tw: {
id: OPS.setWordSpacing,
numArgs: 1,
variableArgs: false
},
Tz: {
id: OPS.setHScale,
numArgs: 1,
variableArgs: false
},
TL: {
id: OPS.setLeading,
numArgs: 1,
variableArgs: false
},
Tf: {
id: OPS.setFont,
numArgs: 2,
variableArgs: false
},
Tr: {
id: OPS.setTextRenderingMode,
numArgs: 1,
variableArgs: false
},
Ts: {
id: OPS.setTextRise,
numArgs: 1,
variableArgs: false
},
Td: {
id: OPS.moveText,
numArgs: 2,
variableArgs: false
},
TD: {
id: OPS.setLeadingMoveText,
numArgs: 2,
variableArgs: false
},
Tm: {
id: OPS.setTextMatrix,
numArgs: 6,
variableArgs: false
},
"T*": {
id: OPS.nextLine,
numArgs: 0,
variableArgs: false
},
Tj: {
id: OPS.showText,
numArgs: 1,
variableArgs: false
},
TJ: {
id: OPS.showSpacedText,
numArgs: 1,
variableArgs: false
},
"'": {
id: OPS.nextLineShowText,
numArgs: 1,
variableArgs: false
},
'"': {
id: OPS.nextLineSetSpacingShowText,
numArgs: 3,
variableArgs: false
},
d0: {
id: OPS.setCharWidth,
numArgs: 2,
variableArgs: false
},
d1: {
id: OPS.setCharWidthAndBounds,
numArgs: 6,
variableArgs: false
},
CS: {
id: OPS.setStrokeColorSpace,
numArgs: 1,
variableArgs: false
},
cs: {
id: OPS.setFillColorSpace,
numArgs: 1,
variableArgs: false
},
SC: {
id: OPS.setStrokeColor,
numArgs: 4,
variableArgs: true
},
SCN: {
id: OPS.setStrokeColorN,
numArgs: 33,
variableArgs: true
},
sc: {
id: OPS.setFillColor,
numArgs: 4,
variableArgs: true
},
scn: {
id: OPS.setFillColorN,
numArgs: 33,
variableArgs: true
},
G: {
id: OPS.setStrokeGray,
numArgs: 1,
variableArgs: false
},
g: {
id: OPS.setFillGray,
numArgs: 1,
variableArgs: false
},
RG: {
id: OPS.setStrokeRGBColor,
numArgs: 3,
variableArgs: false
},
rg: {
id: OPS.setFillRGBColor,
numArgs: 3,
variableArgs: false
},
K: {
id: OPS.setStrokeCMYKColor,
numArgs: 4,
variableArgs: false
},
k: {
id: OPS.setFillCMYKColor,
numArgs: 4,
variableArgs: false
},
sh: {
id: OPS.shadingFill,
numArgs: 1,
variableArgs: false
},
BI: {
id: OPS.beginInlineImage,
numArgs: 0,
variableArgs: false
},
ID: {
id: OPS.beginImageData,
numArgs: 0,
variableArgs: false
},
EI: {
id: OPS.endInlineImage,
numArgs: 1,
variableArgs: false
},
Do: {
id: OPS.paintXObject,
numArgs: 1,
variableArgs: false
},
MP: {
id: OPS.markPoint,
numArgs: 1,
variableArgs: false
},
DP: {
id: OPS.markPointProps,
numArgs: 2,
variableArgs: false
},
BMC: {
id: OPS.beginMarkedContent,
numArgs: 1,
variableArgs: false
},
BDC: {
id: OPS.beginMarkedContentProps,
numArgs: 2,
variableArgs: false
},
EMC: {
id: OPS.endMarkedContent,
numArgs: 0,
variableArgs: false
},
BX: {
id: OPS.beginCompat,
numArgs: 0,
variableArgs: false
},
EX: {
id: OPS.endCompat,
numArgs: 0,
variableArgs: false
},
BM: null,
BD: null,
true: null,
fa: null,
fal: null,
fals: null,
false: null,
nu: null,
nul: null,
null: null
}));
}
static MAX_INVALID_PATH_OPS = 10;
constructor(stream, xref, stateManager = new StateManager()) {
this.parser = new Parser({
lexer: new Lexer(stream, EvaluatorPreprocessor.opMap),
xref
});
this.stateManager = stateManager;
this.nonProcessedArgs = [];
this._isPathOp = false;
this._numInvalidPathOPS = 0;
}
get savedStatesDepth() {
return this.stateManager.stateStack.length;
}
read(operation) {
let args = operation.args;
while (true) {
const obj = this.parser.getObj();
if (obj instanceof Cmd) {
const cmd = obj.cmd;
const opSpec = EvaluatorPreprocessor.opMap[cmd];
if (!opSpec) {
warn(`Unknown command "${cmd}".`);
continue;
}
const fn = opSpec.id;
const numArgs = opSpec.numArgs;
let argsLength = args !== null ? args.length : 0;
if (!this._isPathOp) {
this._numInvalidPathOPS = 0;
}
this._isPathOp = fn >= OPS.moveTo && fn <= OPS.endPath;
if (!opSpec.variableArgs) {
if (argsLength !== numArgs) {
const nonProcessedArgs = this.nonProcessedArgs;
while (argsLength > numArgs) {
nonProcessedArgs.push(args.shift());
argsLength--;
}
while (argsLength < numArgs && nonProcessedArgs.length !== 0) {
if (args === null) {
args = [];
}
args.unshift(nonProcessedArgs.pop());
argsLength++;
}
}
if (argsLength < numArgs) {
const partialMsg = `command ${cmd}: expected ${numArgs} args, ` + `but received ${argsLength} args.`;
if (this._isPathOp && ++this._numInvalidPathOPS > EvaluatorPreprocessor.MAX_INVALID_PATH_OPS) {
throw new FormatError(`Invalid ${partialMsg}`);
}
warn(`Skipping ${partialMsg}`);
if (args !== null) {
args.length = 0;
}
continue;
}
} else if (argsLength > numArgs) {
info(`Command ${cmd}: expected [0, ${numArgs}] args, ` + `but received ${argsLength} args.`);
}
this.preprocessCommand(fn, args);
operation.fn = fn;
operation.args = args;
return true;
}
if (obj === EOF) {
return false;
}
if (obj !== null) {
if (args === null) {
args = [];
}
args.push(obj);
if (args.length > 33) {
throw new FormatError("Too many arguments");
}
}
}
}
preprocessCommand(fn, args) {
switch (fn | 0) {
case OPS.save:
this.stateManager.save();
break;
case OPS.restore:
this.stateManager.restore();
break;
case OPS.transform:
this.stateManager.transform(args);
break;
}
}
}
;// ./src/core/default_appearance.js
class DefaultAppearanceEvaluator extends EvaluatorPreprocessor {
constructor(str) {
super(new StringStream(str));
}
parse() {
const operation = {
fn: 0,
args: []
};
const result = {
fontSize: 0,
fontName: "",
fontColor: new Uint8ClampedArray(3)
};
try {
while (true) {
operation.args.length = 0;
if (!this.read(operation)) {
break;
}
if (this.savedStatesDepth !== 0) {
continue;
}
const {
fn,
args
} = operation;
switch (fn | 0) {
case OPS.setFont:
const [fontName, fontSize] = args;
if (fontName instanceof Name) {
result.fontName = fontName.name;
}
if (typeof fontSize === "number" && fontSize > 0) {
result.fontSize = fontSize;
}
break;
case OPS.setFillRGBColor:
ColorSpaceUtils.rgb.getRgbItem(args, 0, result.fontColor, 0);
break;
case OPS.setFillGray:
ColorSpaceUtils.gray.getRgbItem(args, 0, result.fontColor, 0);
break;
case OPS.setFillCMYKColor:
ColorSpaceUtils.cmyk.getRgbItem(args, 0, result.fontColor, 0);
break;
}
}
} catch (reason) {
warn(`parseDefaultAppearance - ignoring errors: "${reason}".`);
}
return result;
}
}
function parseDefaultAppearance(str) {
return new DefaultAppearanceEvaluator(str).parse();
}
class AppearanceStreamEvaluator extends EvaluatorPreprocessor {
constructor(stream, evaluatorOptions, xref, globalColorSpaceCache) {
super(stream);
this.stream = stream;
this.evaluatorOptions = evaluatorOptions;
this.xref = xref;
this.globalColorSpaceCache = globalColorSpaceCache;
this.resources = stream.dict?.get("Resources");
}
parse() {
const operation = {
fn: 0,
args: []
};
let result = {
scaleFactor: 1,
fontSize: 0,
fontName: "",
fontColor: new Uint8ClampedArray(3),
fillColorSpace: ColorSpaceUtils.gray
};
let breakLoop = false;
const stack = [];
try {
while (true) {
operation.args.length = 0;
if (breakLoop || !this.read(operation)) {
break;
}
const {
fn,
args
} = operation;
switch (fn | 0) {
case OPS.save:
stack.push({
scaleFactor: result.scaleFactor,
fontSize: result.fontSize,
fontName: result.fontName,
fontColor: result.fontColor.slice(),
fillColorSpace: result.fillColorSpace
});
break;
case OPS.restore:
result = stack.pop() || result;
break;
case OPS.setTextMatrix:
result.scaleFactor *= Math.hypot(args[0], args[1]);
break;
case OPS.setFont:
const [fontName, fontSize] = args;
if (fontName instanceof Name) {
result.fontName = fontName.name;
}
if (typeof fontSize === "number" && fontSize > 0) {
result.fontSize = fontSize * result.scaleFactor;
}
break;
case OPS.setFillColorSpace:
result.fillColorSpace = ColorSpaceUtils.parse({
cs: args[0],
xref: this.xref,
resources: this.resources,
pdfFunctionFactory: this._pdfFunctionFactory,
globalColorSpaceCache: this.globalColorSpaceCache,
localColorSpaceCache: this._localColorSpaceCache
});
break;
case OPS.setFillColor:
const cs = result.fillColorSpace;
cs.getRgbItem(args, 0, result.fontColor, 0);
break;
case OPS.setFillRGBColor:
ColorSpaceUtils.rgb.getRgbItem(args, 0, result.fontColor, 0);
break;
case OPS.setFillGray:
ColorSpaceUtils.gray.getRgbItem(args, 0, result.fontColor, 0);
break;
case OPS.setFillCMYKColor:
ColorSpaceUtils.cmyk.getRgbItem(args, 0, result.fontColor, 0);
break;
case OPS.showText:
case OPS.showSpacedText:
case OPS.nextLineShowText:
case OPS.nextLineSetSpacingShowText:
breakLoop = true;
break;
}
}
} catch (reason) {
warn(`parseAppearanceStream - ignoring errors: "${reason}".`);
}
this.stream.reset();
delete result.scaleFactor;
delete result.fillColorSpace;
return result;
}
get _localColorSpaceCache() {
return shadow(this, "_localColorSpaceCache", new LocalColorSpaceCache());
}
get _pdfFunctionFactory() {
const pdfFunctionFactory = new PDFFunctionFactory({
xref: this.xref,
isEvalSupported: this.evaluatorOptions.isEvalSupported
});
return shadow(this, "_pdfFunctionFactory", pdfFunctionFactory);
}
}
function parseAppearanceStream(stream, evaluatorOptions, xref, globalColorSpaceCache) {
return new AppearanceStreamEvaluator(stream, evaluatorOptions, xref, globalColorSpaceCache).parse();
}
function getPdfColor(color, isFill) {
if (color[0] === color[1] && color[1] === color[2]) {
const gray = color[0] / 255;
return `${numberToString(gray)} ${isFill ? "g" : "G"}`;
}
return Array.from(color, c => numberToString(c / 255)).join(" ") + ` ${isFill ? "rg" : "RG"}`;
}
function createDefaultAppearance({
fontSize,
fontName,
fontColor
}) {
return `/${escapePDFName(fontName)} ${fontSize} Tf ${getPdfColor(fontColor, true)}`;
}
class FakeUnicodeFont {
constructor(xref, fontFamily) {
this.xref = xref;
this.widths = null;
this.firstChar = Infinity;
this.lastChar = -Infinity;
this.fontFamily = fontFamily;
const canvas = new OffscreenCanvas(1, 1);
this.ctxMeasure = canvas.getContext("2d", {
willReadFrequently: true
});
if (!FakeUnicodeFont._fontNameId) {
FakeUnicodeFont._fontNameId = 1;
}
this.fontName = Name.get(`InvalidPDFjsFont_${fontFamily}_${FakeUnicodeFont._fontNameId++}`);
}
get fontDescriptorRef() {
if (!FakeUnicodeFont._fontDescriptorRef) {
const fontDescriptor = new Dict(this.xref);
fontDescriptor.set("Type", Name.get("FontDescriptor"));
fontDescriptor.set("FontName", this.fontName);
fontDescriptor.set("FontFamily", "MyriadPro Regular");
fontDescriptor.set("FontBBox", [0, 0, 0, 0]);
fontDescriptor.set("FontStretch", Name.get("Normal"));
fontDescriptor.set("FontWeight", 400);
fontDescriptor.set("ItalicAngle", 0);
FakeUnicodeFont._fontDescriptorRef = this.xref.getNewPersistentRef(fontDescriptor);
}
return FakeUnicodeFont._fontDescriptorRef;
}
get descendantFontRef() {
const descendantFont = new Dict(this.xref);
descendantFont.set("BaseFont", this.fontName);
descendantFont.set("Type", Name.get("Font"));
descendantFont.set("Subtype", Name.get("CIDFontType0"));
descendantFont.set("CIDToGIDMap", Name.get("Identity"));
descendantFont.set("FirstChar", this.firstChar);
descendantFont.set("LastChar", this.lastChar);
descendantFont.set("FontDescriptor", this.fontDescriptorRef);
descendantFont.set("DW", 1000);
const widths = [];
const chars = [...this.widths.entries()].sort();
let currentChar = null;
let currentWidths = null;
for (const [char, width] of chars) {
if (!currentChar) {
currentChar = char;
currentWidths = [width];
continue;
}
if (char === currentChar + currentWidths.length) {
currentWidths.push(width);
} else {
widths.push(currentChar, currentWidths);
currentChar = char;
currentWidths = [width];
}
}
if (currentChar) {
widths.push(currentChar, currentWidths);
}
descendantFont.set("W", widths);
const cidSystemInfo = new Dict(this.xref);
cidSystemInfo.set("Ordering", "Identity");
cidSystemInfo.set("Registry", "Adobe");
cidSystemInfo.set("Supplement", 0);
descendantFont.set("CIDSystemInfo", cidSystemInfo);
return this.xref.getNewPersistentRef(descendantFont);
}
get baseFontRef() {
const baseFont = new Dict(this.xref);
baseFont.set("BaseFont", this.fontName);
baseFont.set("Type", Name.get("Font"));
baseFont.set("Subtype", Name.get("Type0"));
baseFont.set("Encoding", Name.get("Identity-H"));
baseFont.set("DescendantFonts", [this.descendantFontRef]);
baseFont.set("ToUnicode", Name.get("Identity-H"));
return this.xref.getNewPersistentRef(baseFont);
}
get resources() {
const resources = new Dict(this.xref);
const font = new Dict(this.xref);
font.set(this.fontName.name, this.baseFontRef);
resources.set("Font", font);
return resources;
}
_createContext() {
this.widths = new Map();
this.ctxMeasure.font = `1000px ${this.fontFamily}`;
return this.ctxMeasure;
}
createFontResources(text) {
const ctx = this._createContext();
for (const line of text.split(/\r\n?|\n/)) {
for (const char of line.split("")) {
const code = char.charCodeAt(0);
if (this.widths.has(code)) {
continue;
}
const metrics = ctx.measureText(char);
const width = Math.ceil(metrics.width);
this.widths.set(code, width);
this.firstChar = Math.min(code, this.firstChar);
this.lastChar = Math.max(code, this.lastChar);
}
}
return this.resources;
}
static getFirstPositionInfo(rect, rotation, fontSize) {
const [x1, y1, x2, y2] = rect;
let w = x2 - x1;
let h = y2 - y1;
if (rotation % 180 !== 0) {
[w, h] = [h, w];
}
const lineHeight = LINE_FACTOR * fontSize;
const lineDescent = LINE_DESCENT_FACTOR * fontSize;
return {
coords: [0, h + lineDescent - lineHeight],
bbox: [0, 0, w, h],
matrix: rotation !== 0 ? getRotationMatrix(rotation, h, lineHeight) : undefined
};
}
createAppearance(text, rect, rotation, fontSize, bgColor, strokeAlpha) {
const ctx = this._createContext();
const lines = [];
let maxWidth = -Infinity;
for (const line of text.split(/\r\n?|\n/)) {
lines.push(line);
const lineWidth = ctx.measureText(line).width;
maxWidth = Math.max(maxWidth, lineWidth);
for (const code of codePointIter(line)) {
const char = String.fromCodePoint(code);
let width = this.widths.get(code);
if (width === undefined) {
const metrics = ctx.measureText(char);
width = Math.ceil(metrics.width);
this.widths.set(code, width);
this.firstChar = Math.min(code, this.firstChar);
this.lastChar = Math.max(code, this.lastChar);
}
}
}
maxWidth *= fontSize / 1000;
const [x1, y1, x2, y2] = rect;
let w = x2 - x1;
let h = y2 - y1;
if (rotation % 180 !== 0) {
[w, h] = [h, w];
}
let hscale = 1;
if (maxWidth > w) {
hscale = w / maxWidth;
}
let vscale = 1;
const lineHeight = LINE_FACTOR * fontSize;
const lineDescent = LINE_DESCENT_FACTOR * fontSize;
const maxHeight = lineHeight * lines.length;
if (maxHeight > h) {
vscale = h / maxHeight;
}
const fscale = Math.min(hscale, vscale);
const newFontSize = fontSize * fscale;
const buffer = ["q", `0 0 ${numberToString(w)} ${numberToString(h)} re W n`, `BT`, `1 0 0 1 0 ${numberToString(h + lineDescent)} Tm 0 Tc ${getPdfColor(bgColor, true)}`, `/${this.fontName.name} ${numberToString(newFontSize)} Tf`];
const {
resources
} = this;
strokeAlpha = typeof strokeAlpha === "number" && strokeAlpha >= 0 && strokeAlpha <= 1 ? strokeAlpha : 1;
if (strokeAlpha !== 1) {
buffer.push("/R0 gs");
const extGState = new Dict(this.xref);
const r0 = new Dict(this.xref);
r0.set("ca", strokeAlpha);
r0.set("CA", strokeAlpha);
r0.set("Type", Name.get("ExtGState"));
extGState.set("R0", r0);
resources.set("ExtGState", extGState);
}
const vShift = numberToString(lineHeight);
for (const line of lines) {
buffer.push(`0 -${vShift} Td <${stringToUTF16HexString(line)}> Tj`);
}
buffer.push("ET", "Q");
const appearance = buffer.join("\n");
const appearanceStreamDict = new Dict(this.xref);
appearanceStreamDict.set("Subtype", Name.get("Form"));
appearanceStreamDict.set("Type", Name.get("XObject"));
appearanceStreamDict.set("BBox", [0, 0, w, h]);
appearanceStreamDict.set("Length", appearance.length);
appearanceStreamDict.set("Resources", resources);
if (rotation) {
const matrix = getRotationMatrix(rotation, w, h);
appearanceStreamDict.set("Matrix", matrix);
}
const ap = new StringStream(appearance);
ap.dict = appearanceStreamDict;
return ap;
}
}
;// ./src/core/name_number_tree.js
class NameOrNumberTree {
constructor(root, xref, type) {
this.root = root;
this.xref = xref;
this._type = type;
}
getAll() {
const map = new Map();
if (!this.root) {
return map;
}
const xref = this.xref;
const processed = new RefSet();
processed.put(this.root);
const queue = [this.root];
while (queue.length > 0) {
const obj = xref.fetchIfRef(queue.shift());
if (!(obj instanceof Dict)) {
continue;
}
if (obj.has("Kids")) {
const kids = obj.get("Kids");
if (!Array.isArray(kids)) {
continue;
}
for (const kid of kids) {
if (processed.has(kid)) {
throw new FormatError(`Duplicate entry in "${this._type}" tree.`);
}
queue.push(kid);
processed.put(kid);
}
continue;
}
const entries = obj.get(this._type);
if (!Array.isArray(entries)) {
continue;
}
for (let i = 0, ii = entries.length; i < ii; i += 2) {
map.set(xref.fetchIfRef(entries[i]), xref.fetchIfRef(entries[i + 1]));
}
}
return map;
}
getRaw(key) {
if (!this.root) {
return null;
}
const xref = this.xref;
let kidsOrEntries = xref.fetchIfRef(this.root);
let loopCount = 0;
const MAX_LEVELS = 10;
while (kidsOrEntries.has("Kids")) {
if (++loopCount > MAX_LEVELS) {
warn(`Search depth limit reached for "${this._type}" tree.`);
return null;
}
const kids = kidsOrEntries.get("Kids");
if (!Array.isArray(kids)) {
return null;
}
let l = 0,
r = kids.length - 1;
while (l <= r) {
const m = l + r >> 1;
const kid = xref.fetchIfRef(kids[m]);
const limits = kid.get("Limits");
if (key < xref.fetchIfRef(limits[0])) {
r = m - 1;
} else if (key > xref.fetchIfRef(limits[1])) {
l = m + 1;
} else {
kidsOrEntries = kid;
break;
}
}
if (l > r) {
return null;
}
}
const entries = kidsOrEntries.get(this._type);
if (Array.isArray(entries)) {
let l = 0,
r = entries.length - 2;
while (l <= r) {
const tmp = l + r >> 1,
m = tmp + (tmp & 1);
const currentKey = xref.fetchIfRef(entries[m]);
if (key < currentKey) {
r = m - 2;
} else if (key > currentKey) {
l = m + 2;
} else {
return entries[m + 1];
}
}
}
return null;
}
get(key) {
return this.xref.fetchIfRef(this.getRaw(key));
}
}
class NameTree extends NameOrNumberTree {
constructor(root, xref) {
super(root, xref, "Names");
}
}
class NumberTree extends NameOrNumberTree {
constructor(root, xref) {
super(root, xref, "Nums");
}
}
;// ./src/core/cleanup_helper.js
function clearGlobalCaches() {
clearPatternCaches();
clearPrimitiveCaches();
clearUnicodeCaches();
JpxImage.cleanup();
}
;// ./src/core/file_spec.js
function pickPlatformItem(dict) {
if (!(dict instanceof Dict)) {
return null;
}
if (dict.has("UF")) {
return dict.get("UF");
} else if (dict.has("F")) {
return dict.get("F");
} else if (dict.has("Unix")) {
return dict.get("Unix");
} else if (dict.has("Mac")) {
return dict.get("Mac");
} else if (dict.has("DOS")) {
return dict.get("DOS");
}
return null;
}
function stripPath(str) {
return str.substring(str.lastIndexOf("/") + 1);
}
class FileSpec {
#contentAvailable = false;
constructor(root, xref, skipContent = false) {
if (!(root instanceof Dict)) {
return;
}
this.xref = xref;
this.root = root;
if (root.has("FS")) {
this.fs = root.get("FS");
}
if (root.has("RF")) {
warn("Related file specifications are not supported");
}
if (!skipContent) {
if (root.has("EF")) {
this.#contentAvailable = true;
} else {
warn("Non-embedded file specifications are not supported");
}
}
}
get filename() {
let filename = "";
const item = pickPlatformItem(this.root);
if (item && typeof item === "string") {
filename = stringToPDFString(item).replaceAll("\\\\", "\\").replaceAll("\\/", "/").replaceAll("\\", "/");
}
return shadow(this, "filename", filename || "unnamed");
}
get content() {
if (!this.#contentAvailable) {
return null;
}
this._contentRef ||= pickPlatformItem(this.root?.get("EF"));
let content = null;
if (this._contentRef) {
const fileObj = this.xref.fetchIfRef(this._contentRef);
if (fileObj instanceof BaseStream) {
content = fileObj.getBytes();
} else {
warn("Embedded file specification points to non-existing/invalid content");
}
} else {
warn("Embedded file specification does not have any content");
}
return content;
}
get description() {
let description = "";
const desc = this.root?.get("Desc");
if (desc && typeof desc === "string") {
description = stringToPDFString(desc);
}
return shadow(this, "description", description);
}
get serializable() {
return {
rawFilename: this.filename,
filename: stripPath(this.filename),
content: this.content,
description: this.description
};
}
}
;// ./src/core/xml_parser.js
const XMLParserErrorCode = {
NoError: 0,
EndOfDocument: -1,
UnterminatedCdat: -2,
UnterminatedXmlDeclaration: -3,
UnterminatedDoctypeDeclaration: -4,
UnterminatedComment: -5,
MalformedElement: -6,
OutOfMemory: -7,
UnterminatedAttributeValue: -8,
UnterminatedElement: -9,
ElementNeverBegun: -10
};
function isWhitespace(s, index) {
const ch = s[index];
return ch === " " || ch === "\n" || ch === "\r" || ch === "\t";
}
function isWhitespaceString(s) {
for (let i = 0, ii = s.length; i < ii; i++) {
if (!isWhitespace(s, i)) {
return false;
}
}
return true;
}
class XMLParserBase {
_resolveEntities(s) {
return s.replaceAll(/&([^;]+);/g, (all, entity) => {
if (entity.substring(0, 2) === "#x") {
return String.fromCodePoint(parseInt(entity.substring(2), 16));
} else if (entity.substring(0, 1) === "#") {
return String.fromCodePoint(parseInt(entity.substring(1), 10));
}
switch (entity) {
case "lt":
return "<";
case "gt":
return ">";
case "amp":
return "&";
case "quot":
return '"';
case "apos":
return "'";
}
return this.onResolveEntity(entity);
});
}
_parseContent(s, start) {
const attributes = [];
let pos = start;
function skipWs() {
while (pos < s.length && isWhitespace(s, pos)) {
++pos;
}
}
while (pos < s.length && !isWhitespace(s, pos) && s[pos] !== ">" && s[pos] !== "/") {
++pos;
}
const name = s.substring(start, pos);
skipWs();
while (pos < s.length && s[pos] !== ">" && s[pos] !== "/" && s[pos] !== "?") {
skipWs();
let attrName = "",
attrValue = "";
while (pos < s.length && !isWhitespace(s, pos) && s[pos] !== "=") {
attrName += s[pos];
++pos;
}
skipWs();
if (s[pos] !== "=") {
return null;
}
++pos;
skipWs();
const attrEndChar = s[pos];
if (attrEndChar !== '"' && attrEndChar !== "'") {
return null;
}
const attrEndIndex = s.indexOf(attrEndChar, ++pos);
if (attrEndIndex < 0) {
return null;
}
attrValue = s.substring(pos, attrEndIndex);
attributes.push({
name: attrName,
value: this._resolveEntities(attrValue)
});
pos = attrEndIndex + 1;
skipWs();
}
return {
name,
attributes,
parsed: pos - start
};
}
_parseProcessingInstruction(s, start) {
let pos = start;
function skipWs() {
while (pos < s.length && isWhitespace(s, pos)) {
++pos;
}
}
while (pos < s.length && !isWhitespace(s, pos) && s[pos] !== ">" && s[pos] !== "?" && s[pos] !== "/") {
++pos;
}
const name = s.substring(start, pos);
skipWs();
const attrStart = pos;
while (pos < s.length && (s[pos] !== "?" || s[pos + 1] !== ">")) {
++pos;
}
const value = s.substring(attrStart, pos);
return {
name,
value,
parsed: pos - start
};
}
parseXml(s) {
let i = 0;
while (i < s.length) {
const ch = s[i];
let j = i;
if (ch === "<") {
++j;
const ch2 = s[j];
let q;
switch (ch2) {
case "/":
++j;
q = s.indexOf(">", j);
if (q < 0) {
this.onError(XMLParserErrorCode.UnterminatedElement);
return;
}
this.onEndElement(s.substring(j, q));
j = q + 1;
break;
case "?":
++j;
const pi = this._parseProcessingInstruction(s, j);
if (s.substring(j + pi.parsed, j + pi.parsed + 2) !== "?>") {
this.onError(XMLParserErrorCode.UnterminatedXmlDeclaration);
return;
}
this.onPi(pi.name, pi.value);
j += pi.parsed + 2;
break;
case "!":
if (s.substring(j + 1, j + 3) === "--") {
q = s.indexOf("-->", j + 3);
if (q < 0) {
this.onError(XMLParserErrorCode.UnterminatedComment);
return;
}
this.onComment(s.substring(j + 3, q));
j = q + 3;
} else if (s.substring(j + 1, j + 8) === "[CDATA[") {
q = s.indexOf("]]>", j + 8);
if (q < 0) {
this.onError(XMLParserErrorCode.UnterminatedCdat);
return;
}
this.onCdata(s.substring(j + 8, q));
j = q + 3;
} else if (s.substring(j + 1, j + 8) === "DOCTYPE") {
const q2 = s.indexOf("[", j + 8);
let complexDoctype = false;
q = s.indexOf(">", j + 8);
if (q < 0) {
this.onError(XMLParserErrorCode.UnterminatedDoctypeDeclaration);
return;
}
if (q2 > 0 && q > q2) {
q = s.indexOf("]>", j + 8);
if (q < 0) {
this.onError(XMLParserErrorCode.UnterminatedDoctypeDeclaration);
return;
}
complexDoctype = true;
}
const doctypeContent = s.substring(j + 8, q + (complexDoctype ? 1 : 0));
this.onDoctype(doctypeContent);
j = q + (complexDoctype ? 2 : 1);
} else {
this.onError(XMLParserErrorCode.MalformedElement);
return;
}
break;
default:
const content = this._parseContent(s, j);
if (content === null) {
this.onError(XMLParserErrorCode.MalformedElement);
return;
}
let isClosed = false;
if (s.substring(j + content.parsed, j + content.parsed + 2) === "/>") {
isClosed = true;
} else if (s.substring(j + content.parsed, j + content.parsed + 1) !== ">") {
this.onError(XMLParserErrorCode.UnterminatedElement);
return;
}
this.onBeginElement(content.name, content.attributes, isClosed);
j += content.parsed + (isClosed ? 2 : 1);
break;
}
} else {
while (j < s.length && s[j] !== "<") {
j++;
}
const text = s.substring(i, j);
this.onText(this._resolveEntities(text));
}
i = j;
}
}
onResolveEntity(name) {
return `&${name};`;
}
onPi(name, value) {}
onComment(text) {}
onCdata(text) {}
onDoctype(doctypeContent) {}
onText(text) {}
onBeginElement(name, attributes, isEmpty) {}
onEndElement(name) {}
onError(code) {}
}
class SimpleDOMNode {
constructor(nodeName, nodeValue) {
this.nodeName = nodeName;
this.nodeValue = nodeValue;
Object.defineProperty(this, "parentNode", {
value: null,
writable: true
});
}
get firstChild() {
return this.childNodes?.[0];
}
get nextSibling() {
const childNodes = this.parentNode.childNodes;
if (!childNodes) {
return undefined;
}
const index = childNodes.indexOf(this);
if (index === -1) {
return undefined;
}
return childNodes[index + 1];
}
get textContent() {
if (!this.childNodes) {
return this.nodeValue || "";
}
return this.childNodes.map(child => child.textContent).join("");
}
get children() {
return this.childNodes || [];
}
hasChildNodes() {
return this.childNodes?.length > 0;
}
searchNode(paths, pos) {
if (pos >= paths.length) {
return this;
}
const component = paths[pos];
if (component.name.startsWith("#") && pos < paths.length - 1) {
return this.searchNode(paths, pos + 1);
}
const stack = [];
let node = this;
while (true) {
if (component.name === node.nodeName) {
if (component.pos === 0) {
const res = node.searchNode(paths, pos + 1);
if (res !== null) {
return res;
}
} else if (stack.length === 0) {
return null;
} else {
const [parent] = stack.pop();
let siblingPos = 0;
for (const child of parent.childNodes) {
if (component.name === child.nodeName) {
if (siblingPos === component.pos) {
return child.searchNode(paths, pos + 1);
}
siblingPos++;
}
}
return node.searchNode(paths, pos + 1);
}
}
if (node.childNodes?.length > 0) {
stack.push([node, 0]);
node = node.childNodes[0];
} else if (stack.length === 0) {
return null;
} else {
while (stack.length !== 0) {
const [parent, currentPos] = stack.pop();
const newPos = currentPos + 1;
if (newPos < parent.childNodes.length) {
stack.push([parent, newPos]);
node = parent.childNodes[newPos];
break;
}
}
if (stack.length === 0) {
return null;
}
}
}
}
dump(buffer) {
if (this.nodeName === "#text") {
buffer.push(encodeToXmlString(this.nodeValue));
return;
}
buffer.push(`<${this.nodeName}`);
if (this.attributes) {
for (const attribute of this.attributes) {
buffer.push(` ${attribute.name}="${encodeToXmlString(attribute.value)}"`);
}
}
if (this.hasChildNodes()) {
buffer.push(">");
for (const child of this.childNodes) {
child.dump(buffer);
}
buffer.push(`${this.nodeName}>`);
} else if (this.nodeValue) {
buffer.push(`>${encodeToXmlString(this.nodeValue)}${this.nodeName}>`);
} else {
buffer.push("/>");
}
}
}
class SimpleXMLParser extends XMLParserBase {
constructor({
hasAttributes = false,
lowerCaseName = false
}) {
super();
this._currentFragment = null;
this._stack = null;
this._errorCode = XMLParserErrorCode.NoError;
this._hasAttributes = hasAttributes;
this._lowerCaseName = lowerCaseName;
}
parseFromString(data) {
this._currentFragment = [];
this._stack = [];
this._errorCode = XMLParserErrorCode.NoError;
this.parseXml(data);
if (this._errorCode !== XMLParserErrorCode.NoError) {
return undefined;
}
const [documentElement] = this._currentFragment;
if (!documentElement) {
return undefined;
}
return {
documentElement
};
}
onText(text) {
if (isWhitespaceString(text)) {
return;
}
const node = new SimpleDOMNode("#text", text);
this._currentFragment.push(node);
}
onCdata(text) {
const node = new SimpleDOMNode("#text", text);
this._currentFragment.push(node);
}
onBeginElement(name, attributes, isEmpty) {
if (this._lowerCaseName) {
name = name.toLowerCase();
}
const node = new SimpleDOMNode(name);
node.childNodes = [];
if (this._hasAttributes) {
node.attributes = attributes;
}
this._currentFragment.push(node);
if (isEmpty) {
return;
}
this._stack.push(this._currentFragment);
this._currentFragment = node.childNodes;
}
onEndElement(name) {
this._currentFragment = this._stack.pop() || [];
const lastElement = this._currentFragment.at(-1);
if (!lastElement) {
return null;
}
for (const childNode of lastElement.childNodes) {
childNode.parentNode = lastElement;
}
return lastElement;
}
onError(code) {
this._errorCode = code;
}
}
;// ./src/core/metadata_parser.js
class MetadataParser {
constructor(data) {
data = this._repair(data);
const parser = new SimpleXMLParser({
lowerCaseName: true
});
const xmlDocument = parser.parseFromString(data);
this._metadataMap = new Map();
this._data = data;
if (xmlDocument) {
this._parse(xmlDocument);
}
}
_repair(data) {
return data.replace(/^[^<]+/, "").replaceAll(/>\\376\\377([^<]+)/g, function (all, codes) {
const bytes = codes.replaceAll(/\\([0-3])([0-7])([0-7])/g, function (code, d1, d2, d3) {
return String.fromCharCode(d1 * 64 + d2 * 8 + d3 * 1);
}).replaceAll(/&(amp|apos|gt|lt|quot);/g, function (str, name) {
switch (name) {
case "amp":
return "&";
case "apos":
return "'";
case "gt":
return ">";
case "lt":
return "<";
case "quot":
return '"';
}
throw new Error(`_repair: ${name} isn't defined.`);
});
const charBuf = [">"];
for (let i = 0, ii = bytes.length; i < ii; i += 2) {
const code = bytes.charCodeAt(i) * 256 + bytes.charCodeAt(i + 1);
if (code >= 32 && code < 127 && code !== 60 && code !== 62 && code !== 38) {
charBuf.push(String.fromCharCode(code));
} else {
charBuf.push("" + (0x10000 + code).toString(16).substring(1) + ";");
}
}
return charBuf.join("");
});
}
_getSequence(entry) {
const name = entry.nodeName;
if (name !== "rdf:bag" && name !== "rdf:seq" && name !== "rdf:alt") {
return null;
}
return entry.childNodes.filter(node => node.nodeName === "rdf:li");
}
_parseArray(entry) {
if (!entry.hasChildNodes()) {
return;
}
const [seqNode] = entry.childNodes;
const sequence = this._getSequence(seqNode) || [];
this._metadataMap.set(entry.nodeName, sequence.map(node => node.textContent.trim()));
}
_parse(xmlDocument) {
let rdf = xmlDocument.documentElement;
if (rdf.nodeName !== "rdf:rdf") {
rdf = rdf.firstChild;
while (rdf && rdf.nodeName !== "rdf:rdf") {
rdf = rdf.nextSibling;
}
}
if (!rdf || rdf.nodeName !== "rdf:rdf" || !rdf.hasChildNodes()) {
return;
}
for (const desc of rdf.childNodes) {
if (desc.nodeName !== "rdf:description") {
continue;
}
for (const entry of desc.childNodes) {
const name = entry.nodeName;
switch (name) {
case "#text":
continue;
case "dc:creator":
case "dc:subject":
this._parseArray(entry);
continue;
}
this._metadataMap.set(name, entry.textContent.trim());
}
}
}
get serializable() {
return {
parsedData: this._metadataMap,
rawData: this._data
};
}
}
;// ./src/core/struct_tree.js
const MAX_DEPTH = 40;
const StructElementType = {
PAGE_CONTENT: 1,
STREAM_CONTENT: 2,
OBJECT: 3,
ANNOTATION: 4,
ELEMENT: 5
};
class StructTreeRoot {
constructor(xref, rootDict, rootRef) {
this.xref = xref;
this.dict = rootDict;
this.ref = rootRef instanceof Ref ? rootRef : null;
this.roleMap = new Map();
this.structParentIds = null;
}
init() {
this.readRoleMap();
}
#addIdToPage(pageRef, id, type) {
if (!(pageRef instanceof Ref) || id < 0) {
return;
}
this.structParentIds ||= new RefSetCache();
let ids = this.structParentIds.get(pageRef);
if (!ids) {
ids = [];
this.structParentIds.put(pageRef, ids);
}
ids.push([id, type]);
}
addAnnotationIdToPage(pageRef, id) {
this.#addIdToPage(pageRef, id, StructElementType.ANNOTATION);
}
readRoleMap() {
const roleMapDict = this.dict.get("RoleMap");
if (!(roleMapDict instanceof Dict)) {
return;
}
for (const [key, value] of roleMapDict) {
if (value instanceof Name) {
this.roleMap.set(key, value.name);
}
}
}
static async canCreateStructureTree({
catalogRef,
pdfManager,
newAnnotationsByPage
}) {
if (!(catalogRef instanceof Ref)) {
warn("Cannot save the struct tree: no catalog reference.");
return false;
}
let nextKey = 0;
let hasNothingToUpdate = true;
for (const [pageIndex, elements] of newAnnotationsByPage) {
const {
ref: pageRef
} = await pdfManager.getPage(pageIndex);
if (!(pageRef instanceof Ref)) {
warn(`Cannot save the struct tree: page ${pageIndex} has no ref.`);
hasNothingToUpdate = true;
break;
}
for (const element of elements) {
if (element.accessibilityData?.type) {
element.parentTreeId = nextKey++;
hasNothingToUpdate = false;
}
}
}
if (hasNothingToUpdate) {
for (const elements of newAnnotationsByPage.values()) {
for (const element of elements) {
delete element.parentTreeId;
}
}
return false;
}
return true;
}
static async createStructureTree({
newAnnotationsByPage,
xref,
catalogRef,
pdfManager,
changes
}) {
const root = pdfManager.catalog.cloneDict();
const cache = new RefSetCache();
cache.put(catalogRef, root);
const structTreeRootRef = xref.getNewTemporaryRef();
root.set("StructTreeRoot", structTreeRootRef);
const structTreeRoot = new Dict(xref);
structTreeRoot.set("Type", Name.get("StructTreeRoot"));
const parentTreeRef = xref.getNewTemporaryRef();
structTreeRoot.set("ParentTree", parentTreeRef);
const kids = [];
structTreeRoot.set("K", kids);
cache.put(structTreeRootRef, structTreeRoot);
const parentTree = new Dict(xref);
const nums = [];
parentTree.set("Nums", nums);
const nextKey = await this.#writeKids({
newAnnotationsByPage,
structTreeRootRef,
structTreeRoot: null,
kids,
nums,
xref,
pdfManager,
changes,
cache
});
structTreeRoot.set("ParentTreeNextKey", nextKey);
cache.put(parentTreeRef, parentTree);
for (const [ref, obj] of cache.items()) {
changes.put(ref, {
data: obj
});
}
}
async canUpdateStructTree({
pdfManager,
newAnnotationsByPage
}) {
if (!this.ref) {
warn("Cannot update the struct tree: no root reference.");
return false;
}
let nextKey = this.dict.get("ParentTreeNextKey");
if (!Number.isInteger(nextKey) || nextKey < 0) {
warn("Cannot update the struct tree: invalid next key.");
return false;
}
const parentTree = this.dict.get("ParentTree");
if (!(parentTree instanceof Dict)) {
warn("Cannot update the struct tree: ParentTree isn't a dict.");
return false;
}
const nums = parentTree.get("Nums");
if (!Array.isArray(nums)) {
warn("Cannot update the struct tree: nums isn't an array.");
return false;
}
const numberTree = new NumberTree(parentTree, this.xref);
for (const pageIndex of newAnnotationsByPage.keys()) {
const {
pageDict
} = await pdfManager.getPage(pageIndex);
if (!pageDict.has("StructParents")) {
continue;
}
const id = pageDict.get("StructParents");
if (!Number.isInteger(id) || !Array.isArray(numberTree.get(id))) {
warn(`Cannot save the struct tree: page ${pageIndex} has a wrong id.`);
return false;
}
}
let hasNothingToUpdate = true;
for (const [pageIndex, elements] of newAnnotationsByPage) {
const {
pageDict
} = await pdfManager.getPage(pageIndex);
StructTreeRoot.#collectParents({
elements,
xref: this.xref,
pageDict,
numberTree
});
for (const element of elements) {
if (element.accessibilityData?.type) {
if (!(element.accessibilityData.structParent >= 0)) {
element.parentTreeId = nextKey++;
}
hasNothingToUpdate = false;
}
}
}
if (hasNothingToUpdate) {
for (const elements of newAnnotationsByPage.values()) {
for (const element of elements) {
delete element.parentTreeId;
delete element.structTreeParent;
}
}
return false;
}
return true;
}
async updateStructureTree({
newAnnotationsByPage,
pdfManager,
changes
}) {
const {
ref: structTreeRootRef,
xref
} = this;
const structTreeRoot = this.dict.clone();
const cache = new RefSetCache();
cache.put(structTreeRootRef, structTreeRoot);
let parentTreeRef = structTreeRoot.getRaw("ParentTree");
let parentTree;
if (parentTreeRef instanceof Ref) {
parentTree = xref.fetch(parentTreeRef);
} else {
parentTree = parentTreeRef;
parentTreeRef = xref.getNewTemporaryRef();
structTreeRoot.set("ParentTree", parentTreeRef);
}
parentTree = parentTree.clone();
cache.put(parentTreeRef, parentTree);
let nums = parentTree.getRaw("Nums");
let numsRef = null;
if (nums instanceof Ref) {
numsRef = nums;
nums = xref.fetch(numsRef);
}
nums = nums.slice();
if (!numsRef) {
parentTree.set("Nums", nums);
}
const newNextKey = await StructTreeRoot.#writeKids({
newAnnotationsByPage,
structTreeRootRef,
structTreeRoot: this,
kids: null,
nums,
xref,
pdfManager,
changes,
cache
});
if (newNextKey === -1) {
return;
}
structTreeRoot.set("ParentTreeNextKey", newNextKey);
if (numsRef) {
cache.put(numsRef, nums);
}
for (const [ref, obj] of cache.items()) {
changes.put(ref, {
data: obj
});
}
}
static async #writeKids({
newAnnotationsByPage,
structTreeRootRef,
structTreeRoot,
kids,
nums,
xref,
pdfManager,
changes,
cache
}) {
const objr = Name.get("OBJR");
let nextKey = -1;
let structTreePageObjs;
for (const [pageIndex, elements] of newAnnotationsByPage) {
const page = await pdfManager.getPage(pageIndex);
const {
ref: pageRef
} = page;
const isPageRef = pageRef instanceof Ref;
for (const {
accessibilityData,
ref,
parentTreeId,
structTreeParent
} of elements) {
if (!accessibilityData?.type) {
continue;
}
const {
structParent
} = accessibilityData;
if (structTreeRoot && Number.isInteger(structParent) && structParent >= 0) {
let objs = (structTreePageObjs ||= new Map()).get(pageIndex);
if (objs === undefined) {
const structTreePage = new StructTreePage(structTreeRoot, page.pageDict);
objs = structTreePage.collectObjects(pageRef);
structTreePageObjs.set(pageIndex, objs);
}
const objRef = objs?.get(structParent);
if (objRef) {
const tagDict = xref.fetch(objRef).clone();
StructTreeRoot.#writeProperties(tagDict, accessibilityData);
changes.put(objRef, {
data: tagDict
});
continue;
}
}
nextKey = Math.max(nextKey, parentTreeId);
const tagRef = xref.getNewTemporaryRef();
const tagDict = new Dict(xref);
StructTreeRoot.#writeProperties(tagDict, accessibilityData);
await this.#updateParentTag({
structTreeParent,
tagDict,
newTagRef: tagRef,
structTreeRootRef,
fallbackKids: kids,
xref,
cache
});
const objDict = new Dict(xref);
tagDict.set("K", objDict);
objDict.set("Type", objr);
if (isPageRef) {
objDict.set("Pg", pageRef);
}
objDict.set("Obj", ref);
cache.put(tagRef, tagDict);
nums.push(parentTreeId, tagRef);
}
}
return nextKey + 1;
}
static #writeProperties(tagDict, {
type,
title,
lang,
alt,
expanded,
actualText
}) {
tagDict.set("S", Name.get(type));
if (title) {
tagDict.set("T", stringToAsciiOrUTF16BE(title));
}
if (lang) {
tagDict.set("Lang", stringToAsciiOrUTF16BE(lang));
}
if (alt) {
tagDict.set("Alt", stringToAsciiOrUTF16BE(alt));
}
if (expanded) {
tagDict.set("E", stringToAsciiOrUTF16BE(expanded));
}
if (actualText) {
tagDict.set("ActualText", stringToAsciiOrUTF16BE(actualText));
}
}
static #collectParents({
elements,
xref,
pageDict,
numberTree
}) {
const idToElements = new Map();
for (const element of elements) {
if (element.structTreeParentId) {
const id = parseInt(element.structTreeParentId.split("_mc")[1], 10);
let elems = idToElements.get(id);
if (!elems) {
elems = [];
idToElements.set(id, elems);
}
elems.push(element);
}
}
const id = pageDict.get("StructParents");
if (!Number.isInteger(id)) {
return;
}
const parentArray = numberTree.get(id);
const updateElement = (kid, pageKid, kidRef) => {
const elems = idToElements.get(kid);
if (elems) {
const parentRef = pageKid.getRaw("P");
const parentDict = xref.fetchIfRef(parentRef);
if (parentRef instanceof Ref && parentDict instanceof Dict) {
const params = {
ref: kidRef,
dict: pageKid
};
for (const element of elems) {
element.structTreeParent = params;
}
}
return true;
}
return false;
};
for (const kidRef of parentArray) {
if (!(kidRef instanceof Ref)) {
continue;
}
const pageKid = xref.fetch(kidRef);
const k = pageKid.get("K");
if (Number.isInteger(k)) {
updateElement(k, pageKid, kidRef);
continue;
}
if (!Array.isArray(k)) {
continue;
}
for (let kid of k) {
kid = xref.fetchIfRef(kid);
if (Number.isInteger(kid) && updateElement(kid, pageKid, kidRef)) {
break;
}
if (!(kid instanceof Dict)) {
continue;
}
if (!isName(kid.get("Type"), "MCR")) {
break;
}
const mcid = kid.get("MCID");
if (Number.isInteger(mcid) && updateElement(mcid, pageKid, kidRef)) {
break;
}
}
}
}
static async #updateParentTag({
structTreeParent,
tagDict,
newTagRef,
structTreeRootRef,
fallbackKids,
xref,
cache
}) {
let ref = null;
let parentRef;
if (structTreeParent) {
({
ref
} = structTreeParent);
parentRef = structTreeParent.dict.getRaw("P") || structTreeRootRef;
} else {
parentRef = structTreeRootRef;
}
tagDict.set("P", parentRef);
const parentDict = xref.fetchIfRef(parentRef);
if (!parentDict) {
fallbackKids.push(newTagRef);
return;
}
let cachedParentDict = cache.get(parentRef);
if (!cachedParentDict) {
cachedParentDict = parentDict.clone();
cache.put(parentRef, cachedParentDict);
}
const parentKidsRaw = cachedParentDict.getRaw("K");
let cachedParentKids = parentKidsRaw instanceof Ref ? cache.get(parentKidsRaw) : null;
if (!cachedParentKids) {
cachedParentKids = xref.fetchIfRef(parentKidsRaw);
cachedParentKids = Array.isArray(cachedParentKids) ? cachedParentKids.slice() : [parentKidsRaw];
const parentKidsRef = xref.getNewTemporaryRef();
cachedParentDict.set("K", parentKidsRef);
cache.put(parentKidsRef, cachedParentKids);
}
const index = cachedParentKids.indexOf(ref);
cachedParentKids.splice(index >= 0 ? index + 1 : cachedParentKids.length, 0, newTagRef);
}
}
class StructElementNode {
constructor(tree, dict) {
this.tree = tree;
this.xref = tree.xref;
this.dict = dict;
this.kids = [];
this.parseKids();
}
get role() {
const nameObj = this.dict.get("S");
const name = nameObj instanceof Name ? nameObj.name : "";
const {
root
} = this.tree;
return root.roleMap.get(name) ?? name;
}
parseKids() {
let pageObjId = null;
const objRef = this.dict.getRaw("Pg");
if (objRef instanceof Ref) {
pageObjId = objRef.toString();
}
const kids = this.dict.get("K");
if (Array.isArray(kids)) {
for (const kid of kids) {
const element = this.parseKid(pageObjId, this.xref.fetchIfRef(kid));
if (element) {
this.kids.push(element);
}
}
} else {
const element = this.parseKid(pageObjId, kids);
if (element) {
this.kids.push(element);
}
}
}
parseKid(pageObjId, kid) {
if (Number.isInteger(kid)) {
if (this.tree.pageDict.objId !== pageObjId) {
return null;
}
return new StructElement({
type: StructElementType.PAGE_CONTENT,
mcid: kid,
pageObjId
});
}
if (!(kid instanceof Dict)) {
return null;
}
const pageRef = kid.getRaw("Pg");
if (pageRef instanceof Ref) {
pageObjId = pageRef.toString();
}
const type = kid.get("Type") instanceof Name ? kid.get("Type").name : null;
if (type === "MCR") {
if (this.tree.pageDict.objId !== pageObjId) {
return null;
}
const kidRef = kid.getRaw("Stm");
return new StructElement({
type: StructElementType.STREAM_CONTENT,
refObjId: kidRef instanceof Ref ? kidRef.toString() : null,
pageObjId,
mcid: kid.get("MCID")
});
}
if (type === "OBJR") {
if (this.tree.pageDict.objId !== pageObjId) {
return null;
}
const kidRef = kid.getRaw("Obj");
return new StructElement({
type: StructElementType.OBJECT,
refObjId: kidRef instanceof Ref ? kidRef.toString() : null,
pageObjId
});
}
return new StructElement({
type: StructElementType.ELEMENT,
dict: kid
});
}
}
class StructElement {
constructor({
type,
dict = null,
mcid = null,
pageObjId = null,
refObjId = null
}) {
this.type = type;
this.dict = dict;
this.mcid = mcid;
this.pageObjId = pageObjId;
this.refObjId = refObjId;
this.parentNode = null;
}
}
class StructTreePage {
constructor(structTreeRoot, pageDict) {
this.root = structTreeRoot;
this.xref = structTreeRoot?.xref ?? null;
this.rootDict = structTreeRoot?.dict ?? null;
this.pageDict = pageDict;
this.nodes = [];
}
collectObjects(pageRef) {
if (!this.root || !this.rootDict || !(pageRef instanceof Ref)) {
return null;
}
const parentTree = this.rootDict.get("ParentTree");
if (!parentTree) {
return null;
}
const ids = this.root.structParentIds?.get(pageRef);
if (!ids) {
return null;
}
const map = new Map();
const numberTree = new NumberTree(parentTree, this.xref);
for (const [elemId] of ids) {
const obj = numberTree.getRaw(elemId);
if (obj instanceof Ref) {
map.set(elemId, obj);
}
}
return map;
}
parse(pageRef) {
if (!this.root || !this.rootDict || !(pageRef instanceof Ref)) {
return;
}
const parentTree = this.rootDict.get("ParentTree");
if (!parentTree) {
return;
}
const id = this.pageDict.get("StructParents");
const ids = this.root.structParentIds?.get(pageRef);
if (!Number.isInteger(id) && !ids) {
return;
}
const map = new Map();
const numberTree = new NumberTree(parentTree, this.xref);
if (Number.isInteger(id)) {
const parentArray = numberTree.get(id);
if (Array.isArray(parentArray)) {
for (const ref of parentArray) {
if (ref instanceof Ref) {
this.addNode(this.xref.fetch(ref), map);
}
}
}
}
if (!ids) {
return;
}
for (const [elemId, type] of ids) {
const obj = numberTree.get(elemId);
if (obj) {
const elem = this.addNode(this.xref.fetchIfRef(obj), map);
if (elem?.kids?.length === 1 && elem.kids[0].type === StructElementType.OBJECT) {
elem.kids[0].type = type;
}
}
}
}
addNode(dict, map, level = 0) {
if (level > MAX_DEPTH) {
warn("StructTree MAX_DEPTH reached.");
return null;
}
if (!(dict instanceof Dict)) {
return null;
}
if (map.has(dict)) {
return map.get(dict);
}
const element = new StructElementNode(this, dict);
map.set(dict, element);
const parent = dict.get("P");
if (!parent || isName(parent.get("Type"), "StructTreeRoot")) {
if (!this.addTopLevelNode(dict, element)) {
map.delete(dict);
}
return element;
}
const parentNode = this.addNode(parent, map, level + 1);
if (!parentNode) {
return element;
}
let save = false;
for (const kid of parentNode.kids) {
if (kid.type === StructElementType.ELEMENT && kid.dict === dict) {
kid.parentNode = element;
save = true;
}
}
if (!save) {
map.delete(dict);
}
return element;
}
addTopLevelNode(dict, element) {
const obj = this.rootDict.get("K");
if (!obj) {
return false;
}
if (obj instanceof Dict) {
if (obj.objId !== dict.objId) {
return false;
}
this.nodes[0] = element;
return true;
}
if (!Array.isArray(obj)) {
return true;
}
let save = false;
for (let i = 0; i < obj.length; i++) {
const kidRef = obj[i];
if (kidRef?.toString() === dict.objId) {
this.nodes[i] = element;
save = true;
}
}
return save;
}
get serializable() {
function nodeToSerializable(node, parent, level = 0) {
if (level > MAX_DEPTH) {
warn("StructTree too deep to be fully serialized.");
return;
}
const obj = Object.create(null);
obj.role = node.role;
obj.children = [];
parent.children.push(obj);
let alt = node.dict.get("Alt");
if (typeof alt !== "string") {
alt = node.dict.get("ActualText");
}
if (typeof alt === "string") {
obj.alt = stringToPDFString(alt);
}
const a = node.dict.get("A");
if (a instanceof Dict) {
const bbox = lookupNormalRect(a.getArray("BBox"), null);
if (bbox) {
obj.bbox = bbox;
} else {
const width = a.get("Width");
const height = a.get("Height");
if (typeof width === "number" && width > 0 && typeof height === "number" && height > 0) {
obj.bbox = [0, 0, width, height];
}
}
}
const lang = node.dict.get("Lang");
if (typeof lang === "string") {
obj.lang = stringToPDFString(lang);
}
for (const kid of node.kids) {
const kidElement = kid.type === StructElementType.ELEMENT ? kid.parentNode : null;
if (kidElement) {
nodeToSerializable(kidElement, obj, level + 1);
continue;
} else if (kid.type === StructElementType.PAGE_CONTENT || kid.type === StructElementType.STREAM_CONTENT) {
obj.children.push({
type: "content",
id: `p${kid.pageObjId}_mc${kid.mcid}`
});
} else if (kid.type === StructElementType.OBJECT) {
obj.children.push({
type: "object",
id: kid.refObjId
});
} else if (kid.type === StructElementType.ANNOTATION) {
obj.children.push({
type: "annotation",
id: `${AnnotationPrefix}${kid.refObjId}`
});
}
}
}
const root = Object.create(null);
root.children = [];
root.role = "Root";
for (const child of this.nodes) {
if (!child) {
continue;
}
nodeToSerializable(child, root);
}
return root;
}
}
;// ./src/core/catalog.js
const isRef = v => v instanceof Ref;
const isValidExplicitDest = _isValidExplicitDest.bind(null, isRef, isName);
function fetchDest(dest) {
if (dest instanceof Dict) {
dest = dest.get("D");
}
return isValidExplicitDest(dest) ? dest : null;
}
function fetchRemoteDest(action) {
let dest = action.get("D");
if (dest) {
if (dest instanceof Name) {
dest = dest.name;
}
if (typeof dest === "string") {
return stringToPDFString(dest);
} else if (isValidExplicitDest(dest)) {
return JSON.stringify(dest);
}
}
return null;
}
class Catalog {
constructor(pdfManager, xref) {
this.pdfManager = pdfManager;
this.xref = xref;
this._catDict = xref.getCatalogObj();
if (!(this._catDict instanceof Dict)) {
throw new FormatError("Catalog object is not a dictionary.");
}
this.toplevelPagesDict;
this._actualNumPages = null;
this.fontCache = new RefSetCache();
this.builtInCMapCache = new Map();
this.standardFontDataCache = new Map();
this.globalColorSpaceCache = new GlobalColorSpaceCache();
this.globalImageCache = new GlobalImageCache();
this.pageKidsCountCache = new RefSetCache();
this.pageIndexCache = new RefSetCache();
this.pageDictCache = new RefSetCache();
this.nonBlendModesSet = new RefSet();
this.systemFontCache = new Map();
}
cloneDict() {
return this._catDict.clone();
}
get version() {
const version = this._catDict.get("Version");
if (version instanceof Name) {
if (PDF_VERSION_REGEXP.test(version.name)) {
return shadow(this, "version", version.name);
}
warn(`Invalid PDF catalog version: ${version.name}`);
}
return shadow(this, "version", null);
}
get lang() {
const lang = this._catDict.get("Lang");
return shadow(this, "lang", lang && typeof lang === "string" ? stringToPDFString(lang) : null);
}
get needsRendering() {
const needsRendering = this._catDict.get("NeedsRendering");
return shadow(this, "needsRendering", typeof needsRendering === "boolean" ? needsRendering : false);
}
get collection() {
let collection = null;
try {
const obj = this._catDict.get("Collection");
if (obj instanceof Dict && obj.size > 0) {
collection = obj;
}
} catch (ex) {
if (ex instanceof MissingDataException) {
throw ex;
}
info("Cannot fetch Collection entry; assuming no collection is present.");
}
return shadow(this, "collection", collection);
}
get acroForm() {
let acroForm = null;
try {
const obj = this._catDict.get("AcroForm");
if (obj instanceof Dict && obj.size > 0) {
acroForm = obj;
}
} catch (ex) {
if (ex instanceof MissingDataException) {
throw ex;
}
info("Cannot fetch AcroForm entry; assuming no forms are present.");
}
return shadow(this, "acroForm", acroForm);
}
get acroFormRef() {
const value = this._catDict.getRaw("AcroForm");
return shadow(this, "acroFormRef", value instanceof Ref ? value : null);
}
get metadata() {
const streamRef = this._catDict.getRaw("Metadata");
if (!(streamRef instanceof Ref)) {
return shadow(this, "metadata", null);
}
let metadata = null;
try {
const stream = this.xref.fetch(streamRef, !this.xref.encrypt?.encryptMetadata);
if (stream instanceof BaseStream && stream.dict instanceof Dict) {
const type = stream.dict.get("Type");
const subtype = stream.dict.get("Subtype");
if (isName(type, "Metadata") && isName(subtype, "XML")) {
const data = stringToUTF8String(stream.getString());
if (data) {
metadata = new MetadataParser(data).serializable;
}
}
}
} catch (ex) {
if (ex instanceof MissingDataException) {
throw ex;
}
info(`Skipping invalid Metadata: "${ex}".`);
}
return shadow(this, "metadata", metadata);
}
get markInfo() {
let markInfo = null;
try {
markInfo = this._readMarkInfo();
} catch (ex) {
if (ex instanceof MissingDataException) {
throw ex;
}
warn("Unable to read mark info.");
}
return shadow(this, "markInfo", markInfo);
}
_readMarkInfo() {
const obj = this._catDict.get("MarkInfo");
if (!(obj instanceof Dict)) {
return null;
}
const markInfo = {
Marked: false,
UserProperties: false,
Suspects: false
};
for (const key in markInfo) {
const value = obj.get(key);
if (typeof value === "boolean") {
markInfo[key] = value;
}
}
return markInfo;
}
get structTreeRoot() {
let structTree = null;
try {
structTree = this.#readStructTreeRoot();
} catch (ex) {
if (ex instanceof MissingDataException) {
throw ex;
}
warn("Unable read to structTreeRoot info.");
}
return shadow(this, "structTreeRoot", structTree);
}
#readStructTreeRoot() {
const rawObj = this._catDict.getRaw("StructTreeRoot");
const obj = this.xref.fetchIfRef(rawObj);
if (!(obj instanceof Dict)) {
return null;
}
const root = new StructTreeRoot(this.xref, obj, rawObj);
root.init();
return root;
}
get toplevelPagesDict() {
const pagesObj = this._catDict.get("Pages");
if (!(pagesObj instanceof Dict)) {
throw new FormatError("Invalid top-level pages dictionary.");
}
return shadow(this, "toplevelPagesDict", pagesObj);
}
get documentOutline() {
let obj = null;
try {
obj = this._readDocumentOutline();
} catch (ex) {
if (ex instanceof MissingDataException) {
throw ex;
}
warn("Unable to read document outline.");
}
return shadow(this, "documentOutline", obj);
}
_readDocumentOutline() {
let obj = this._catDict.get("Outlines");
if (!(obj instanceof Dict)) {
return null;
}
obj = obj.getRaw("First");
if (!(obj instanceof Ref)) {
return null;
}
const root = {
items: []
};
const queue = [{
obj,
parent: root
}];
const processed = new RefSet();
processed.put(obj);
const xref = this.xref,
blackColor = new Uint8ClampedArray(3);
while (queue.length > 0) {
const i = queue.shift();
const outlineDict = xref.fetchIfRef(i.obj);
if (outlineDict === null) {
continue;
}
if (!outlineDict.has("Title")) {
warn("Invalid outline item encountered.");
}
const data = {
url: null,
dest: null,
action: null
};
Catalog.parseDestDictionary({
destDict: outlineDict,
resultObj: data,
docBaseUrl: this.baseUrl,
docAttachments: this.attachments
});
const title = outlineDict.get("Title");
const flags = outlineDict.get("F") || 0;
const color = outlineDict.getArray("C");
const count = outlineDict.get("Count");
let rgbColor = blackColor;
if (isNumberArray(color, 3) && (color[0] !== 0 || color[1] !== 0 || color[2] !== 0)) {
rgbColor = ColorSpaceUtils.rgb.getRgb(color, 0);
}
const outlineItem = {
action: data.action,
attachment: data.attachment,
dest: data.dest,
url: data.url,
unsafeUrl: data.unsafeUrl,
newWindow: data.newWindow,
setOCGState: data.setOCGState,
title: typeof title === "string" ? stringToPDFString(title) : "",
color: rgbColor,
count: Number.isInteger(count) ? count : undefined,
bold: !!(flags & 2),
italic: !!(flags & 1),
items: []
};
i.parent.items.push(outlineItem);
obj = outlineDict.getRaw("First");
if (obj instanceof Ref && !processed.has(obj)) {
queue.push({
obj,
parent: outlineItem
});
processed.put(obj);
}
obj = outlineDict.getRaw("Next");
if (obj instanceof Ref && !processed.has(obj)) {
queue.push({
obj,
parent: i.parent
});
processed.put(obj);
}
}
return root.items.length > 0 ? root.items : null;
}
get permissions() {
let permissions = null;
try {
permissions = this._readPermissions();
} catch (ex) {
if (ex instanceof MissingDataException) {
throw ex;
}
warn("Unable to read permissions.");
}
return shadow(this, "permissions", permissions);
}
_readPermissions() {
const encrypt = this.xref.trailer.get("Encrypt");
if (!(encrypt instanceof Dict)) {
return null;
}
let flags = encrypt.get("P");
if (typeof flags !== "number") {
return null;
}
flags += 2 ** 32;
const permissions = [];
for (const key in PermissionFlag) {
const value = PermissionFlag[key];
if (flags & value) {
permissions.push(value);
}
}
return permissions;
}
get optionalContentConfig() {
let config = null;
try {
const properties = this._catDict.get("OCProperties");
if (!properties) {
return shadow(this, "optionalContentConfig", null);
}
const defaultConfig = properties.get("D");
if (!defaultConfig) {
return shadow(this, "optionalContentConfig", null);
}
const groupsData = properties.get("OCGs");
if (!Array.isArray(groupsData)) {
return shadow(this, "optionalContentConfig", null);
}
const groupRefCache = new RefSetCache();
for (const groupRef of groupsData) {
if (!(groupRef instanceof Ref) || groupRefCache.has(groupRef)) {
continue;
}
groupRefCache.put(groupRef, this.#readOptionalContentGroup(groupRef));
}
config = this.#readOptionalContentConfig(defaultConfig, groupRefCache);
} catch (ex) {
if (ex instanceof MissingDataException) {
throw ex;
}
warn(`Unable to read optional content config: ${ex}`);
}
return shadow(this, "optionalContentConfig", config);
}
#readOptionalContentGroup(groupRef) {
const group = this.xref.fetch(groupRef);
const obj = {
id: groupRef.toString(),
name: null,
intent: null,
usage: {
print: null,
view: null
},
rbGroups: []
};
const name = group.get("Name");
if (typeof name === "string") {
obj.name = stringToPDFString(name);
}
let intent = group.getArray("Intent");
if (!Array.isArray(intent)) {
intent = [intent];
}
if (intent.every(i => i instanceof Name)) {
obj.intent = intent.map(i => i.name);
}
const usage = group.get("Usage");
if (!(usage instanceof Dict)) {
return obj;
}
const usageObj = obj.usage;
const print = usage.get("Print");
if (print instanceof Dict) {
const printState = print.get("PrintState");
if (printState instanceof Name) {
switch (printState.name) {
case "ON":
case "OFF":
usageObj.print = {
printState: printState.name
};
}
}
}
const view = usage.get("View");
if (view instanceof Dict) {
const viewState = view.get("ViewState");
if (viewState instanceof Name) {
switch (viewState.name) {
case "ON":
case "OFF":
usageObj.view = {
viewState: viewState.name
};
}
}
}
return obj;
}
#readOptionalContentConfig(config, groupRefCache) {
function parseOnOff(refs) {
const onParsed = [];
if (Array.isArray(refs)) {
for (const value of refs) {
if (value instanceof Ref && groupRefCache.has(value)) {
onParsed.push(value.toString());
}
}
}
return onParsed;
}
function parseOrder(refs, nestedLevels = 0) {
if (!Array.isArray(refs)) {
return null;
}
const order = [];
for (const value of refs) {
if (value instanceof Ref && groupRefCache.has(value)) {
parsedOrderRefs.put(value);
order.push(value.toString());
continue;
}
const nestedOrder = parseNestedOrder(value, nestedLevels);
if (nestedOrder) {
order.push(nestedOrder);
}
}
if (nestedLevels > 0) {
return order;
}
const hiddenGroups = [];
for (const [groupRef] of groupRefCache.items()) {
if (parsedOrderRefs.has(groupRef)) {
continue;
}
hiddenGroups.push(groupRef.toString());
}
if (hiddenGroups.length) {
order.push({
name: null,
order: hiddenGroups
});
}
return order;
}
function parseNestedOrder(ref, nestedLevels) {
if (++nestedLevels > MAX_NESTED_LEVELS) {
warn("parseNestedOrder - reached MAX_NESTED_LEVELS.");
return null;
}
const value = xref.fetchIfRef(ref);
if (!Array.isArray(value)) {
return null;
}
const nestedName = xref.fetchIfRef(value[0]);
if (typeof nestedName !== "string") {
return null;
}
const nestedOrder = parseOrder(value.slice(1), nestedLevels);
if (!nestedOrder?.length) {
return null;
}
return {
name: stringToPDFString(nestedName),
order: nestedOrder
};
}
function parseRBGroups(rbGroups) {
if (!Array.isArray(rbGroups)) {
return;
}
for (const value of rbGroups) {
const rbGroup = xref.fetchIfRef(value);
if (!Array.isArray(rbGroup) || !rbGroup.length) {
continue;
}
const parsedRbGroup = new Set();
for (const ref of rbGroup) {
if (ref instanceof Ref && groupRefCache.has(ref) && !parsedRbGroup.has(ref.toString())) {
parsedRbGroup.add(ref.toString());
groupRefCache.get(ref).rbGroups.push(parsedRbGroup);
}
}
}
}
const xref = this.xref,
parsedOrderRefs = new RefSet(),
MAX_NESTED_LEVELS = 10;
parseRBGroups(config.get("RBGroups"));
return {
name: typeof config.get("Name") === "string" ? stringToPDFString(config.get("Name")) : null,
creator: typeof config.get("Creator") === "string" ? stringToPDFString(config.get("Creator")) : null,
baseState: config.get("BaseState") instanceof Name ? config.get("BaseState").name : null,
on: parseOnOff(config.get("ON")),
off: parseOnOff(config.get("OFF")),
order: parseOrder(config.get("Order")),
groups: [...groupRefCache]
};
}
setActualNumPages(num = null) {
this._actualNumPages = num;
}
get hasActualNumPages() {
return this._actualNumPages !== null;
}
get _pagesCount() {
const obj = this.toplevelPagesDict.get("Count");
if (!Number.isInteger(obj)) {
throw new FormatError("Page count in top-level pages dictionary is not an integer.");
}
return shadow(this, "_pagesCount", obj);
}
get numPages() {
return this.hasActualNumPages ? this._actualNumPages : this._pagesCount;
}
get destinations() {
const rawDests = this.#readDests(),
dests = Object.create(null);
for (const obj of rawDests) {
if (obj instanceof NameTree) {
for (const [key, value] of obj.getAll()) {
const dest = fetchDest(value);
if (dest) {
dests[stringToPDFString(key)] = dest;
}
}
} else if (obj instanceof Dict) {
for (const [key, value] of obj) {
const dest = fetchDest(value);
if (dest) {
dests[key] ||= dest;
}
}
}
}
return shadow(this, "destinations", dests);
}
getDestination(id) {
const rawDests = this.#readDests();
for (const obj of rawDests) {
if (obj instanceof NameTree || obj instanceof Dict) {
const dest = fetchDest(obj.get(id));
if (dest) {
return dest;
}
}
}
if (rawDests[0] instanceof NameTree) {
const dest = this.destinations[id];
if (dest) {
warn(`Found "${id}" at an incorrect position in the NameTree.`);
return dest;
}
}
return null;
}
#readDests() {
const obj = this._catDict.get("Names");
const rawDests = [];
if (obj?.has("Dests")) {
rawDests.push(new NameTree(obj.getRaw("Dests"), this.xref));
}
if (this._catDict.has("Dests")) {
rawDests.push(this._catDict.get("Dests"));
}
return rawDests;
}
get pageLabels() {
let obj = null;
try {
obj = this._readPageLabels();
} catch (ex) {
if (ex instanceof MissingDataException) {
throw ex;
}
warn("Unable to read page labels.");
}
return shadow(this, "pageLabels", obj);
}
_readPageLabels() {
const obj = this._catDict.getRaw("PageLabels");
if (!obj) {
return null;
}
const pageLabels = new Array(this.numPages);
let style = null,
prefix = "";
const numberTree = new NumberTree(obj, this.xref);
const nums = numberTree.getAll();
let currentLabel = "",
currentIndex = 1;
for (let i = 0, ii = this.numPages; i < ii; i++) {
const labelDict = nums.get(i);
if (labelDict !== undefined) {
if (!(labelDict instanceof Dict)) {
throw new FormatError("PageLabel is not a dictionary.");
}
if (labelDict.has("Type") && !isName(labelDict.get("Type"), "PageLabel")) {
throw new FormatError("Invalid type in PageLabel dictionary.");
}
if (labelDict.has("S")) {
const s = labelDict.get("S");
if (!(s instanceof Name)) {
throw new FormatError("Invalid style in PageLabel dictionary.");
}
style = s.name;
} else {
style = null;
}
if (labelDict.has("P")) {
const p = labelDict.get("P");
if (typeof p !== "string") {
throw new FormatError("Invalid prefix in PageLabel dictionary.");
}
prefix = stringToPDFString(p);
} else {
prefix = "";
}
if (labelDict.has("St")) {
const st = labelDict.get("St");
if (!(Number.isInteger(st) && st >= 1)) {
throw new FormatError("Invalid start in PageLabel dictionary.");
}
currentIndex = st;
} else {
currentIndex = 1;
}
}
switch (style) {
case "D":
currentLabel = currentIndex;
break;
case "R":
case "r":
currentLabel = toRomanNumerals(currentIndex, style === "r");
break;
case "A":
case "a":
const LIMIT = 26;
const A_UPPER_CASE = 0x41,
A_LOWER_CASE = 0x61;
const baseCharCode = style === "a" ? A_LOWER_CASE : A_UPPER_CASE;
const letterIndex = currentIndex - 1;
const character = String.fromCharCode(baseCharCode + letterIndex % LIMIT);
currentLabel = character.repeat(Math.floor(letterIndex / LIMIT) + 1);
break;
default:
if (style) {
throw new FormatError(`Invalid style "${style}" in PageLabel dictionary.`);
}
currentLabel = "";
}
pageLabels[i] = prefix + currentLabel;
currentIndex++;
}
return pageLabels;
}
get pageLayout() {
const obj = this._catDict.get("PageLayout");
let pageLayout = "";
if (obj instanceof Name) {
switch (obj.name) {
case "SinglePage":
case "OneColumn":
case "TwoColumnLeft":
case "TwoColumnRight":
case "TwoPageLeft":
case "TwoPageRight":
pageLayout = obj.name;
}
}
return shadow(this, "pageLayout", pageLayout);
}
get pageMode() {
const obj = this._catDict.get("PageMode");
let pageMode = "UseNone";
if (obj instanceof Name) {
switch (obj.name) {
case "UseNone":
case "UseOutlines":
case "UseThumbs":
case "FullScreen":
case "UseOC":
case "UseAttachments":
pageMode = obj.name;
}
}
return shadow(this, "pageMode", pageMode);
}
get viewerPreferences() {
const obj = this._catDict.get("ViewerPreferences");
if (!(obj instanceof Dict)) {
return shadow(this, "viewerPreferences", null);
}
let prefs = null;
for (const [key, value] of obj) {
let prefValue;
switch (key) {
case "HideToolbar":
case "HideMenubar":
case "HideWindowUI":
case "FitWindow":
case "CenterWindow":
case "DisplayDocTitle":
case "PickTrayByPDFSize":
if (typeof value === "boolean") {
prefValue = value;
}
break;
case "NonFullScreenPageMode":
if (value instanceof Name) {
switch (value.name) {
case "UseNone":
case "UseOutlines":
case "UseThumbs":
case "UseOC":
prefValue = value.name;
break;
default:
prefValue = "UseNone";
}
}
break;
case "Direction":
if (value instanceof Name) {
switch (value.name) {
case "L2R":
case "R2L":
prefValue = value.name;
break;
default:
prefValue = "L2R";
}
}
break;
case "ViewArea":
case "ViewClip":
case "PrintArea":
case "PrintClip":
if (value instanceof Name) {
switch (value.name) {
case "MediaBox":
case "CropBox":
case "BleedBox":
case "TrimBox":
case "ArtBox":
prefValue = value.name;
break;
default:
prefValue = "CropBox";
}
}
break;
case "PrintScaling":
if (value instanceof Name) {
switch (value.name) {
case "None":
case "AppDefault":
prefValue = value.name;
break;
default:
prefValue = "AppDefault";
}
}
break;
case "Duplex":
if (value instanceof Name) {
switch (value.name) {
case "Simplex":
case "DuplexFlipShortEdge":
case "DuplexFlipLongEdge":
prefValue = value.name;
break;
default:
prefValue = "None";
}
}
break;
case "PrintPageRange":
if (Array.isArray(value) && value.length % 2 === 0) {
const isValid = value.every((page, i, arr) => Number.isInteger(page) && page > 0 && (i === 0 || page >= arr[i - 1]) && page <= this.numPages);
if (isValid) {
prefValue = value;
}
}
break;
case "NumCopies":
if (Number.isInteger(value) && value > 0) {
prefValue = value;
}
break;
default:
warn(`Ignoring non-standard key in ViewerPreferences: ${key}.`);
continue;
}
if (prefValue === undefined) {
warn(`Bad value, for key "${key}", in ViewerPreferences: ${value}.`);
continue;
}
if (!prefs) {
prefs = Object.create(null);
}
prefs[key] = prefValue;
}
return shadow(this, "viewerPreferences", prefs);
}
get openAction() {
const obj = this._catDict.get("OpenAction");
const openAction = Object.create(null);
if (obj instanceof Dict) {
const destDict = new Dict(this.xref);
destDict.set("A", obj);
const resultObj = {
url: null,
dest: null,
action: null
};
Catalog.parseDestDictionary({
destDict,
resultObj
});
if (Array.isArray(resultObj.dest)) {
openAction.dest = resultObj.dest;
} else if (resultObj.action) {
openAction.action = resultObj.action;
}
} else if (Array.isArray(obj)) {
openAction.dest = obj;
}
return shadow(this, "openAction", objectSize(openAction) > 0 ? openAction : null);
}
get attachments() {
const obj = this._catDict.get("Names");
let attachments = null;
if (obj instanceof Dict && obj.has("EmbeddedFiles")) {
const nameTree = new NameTree(obj.getRaw("EmbeddedFiles"), this.xref);
for (const [key, value] of nameTree.getAll()) {
const fs = new FileSpec(value, this.xref);
if (!attachments) {
attachments = Object.create(null);
}
attachments[stringToPDFString(key)] = fs.serializable;
}
}
return shadow(this, "attachments", attachments);
}
get xfaImages() {
const obj = this._catDict.get("Names");
let xfaImages = null;
if (obj instanceof Dict && obj.has("XFAImages")) {
const nameTree = new NameTree(obj.getRaw("XFAImages"), this.xref);
for (const [key, value] of nameTree.getAll()) {
if (!xfaImages) {
xfaImages = new Dict(this.xref);
}
xfaImages.set(stringToPDFString(key), value);
}
}
return shadow(this, "xfaImages", xfaImages);
}
_collectJavaScript() {
const obj = this._catDict.get("Names");
let javaScript = null;
function appendIfJavaScriptDict(name, jsDict) {
if (!(jsDict instanceof Dict)) {
return;
}
if (!isName(jsDict.get("S"), "JavaScript")) {
return;
}
let js = jsDict.get("JS");
if (js instanceof BaseStream) {
js = js.getString();
} else if (typeof js !== "string") {
return;
}
js = stringToPDFString(js).replaceAll("\x00", "");
if (js) {
(javaScript ||= new Map()).set(name, js);
}
}
if (obj instanceof Dict && obj.has("JavaScript")) {
const nameTree = new NameTree(obj.getRaw("JavaScript"), this.xref);
for (const [key, value] of nameTree.getAll()) {
appendIfJavaScriptDict(stringToPDFString(key), value);
}
}
const openAction = this._catDict.get("OpenAction");
if (openAction) {
appendIfJavaScriptDict("OpenAction", openAction);
}
return javaScript;
}
get jsActions() {
const javaScript = this._collectJavaScript();
let actions = collectActions(this.xref, this._catDict, DocumentActionEventType);
if (javaScript) {
actions ||= Object.create(null);
for (const [key, val] of javaScript) {
if (key in actions) {
actions[key].push(val);
} else {
actions[key] = [val];
}
}
}
return shadow(this, "jsActions", actions);
}
async cleanup(manuallyTriggered = false) {
clearGlobalCaches();
this.globalColorSpaceCache.clear();
this.globalImageCache.clear(manuallyTriggered);
this.pageKidsCountCache.clear();
this.pageIndexCache.clear();
this.pageDictCache.clear();
this.nonBlendModesSet.clear();
for (const {
dict
} of await Promise.all(this.fontCache)) {
delete dict.cacheKey;
}
this.fontCache.clear();
this.builtInCMapCache.clear();
this.standardFontDataCache.clear();
this.systemFontCache.clear();
}
async getPageDict(pageIndex) {
const nodesToVisit = [this.toplevelPagesDict];
const visitedNodes = new RefSet();
const pagesRef = this._catDict.getRaw("Pages");
if (pagesRef instanceof Ref) {
visitedNodes.put(pagesRef);
}
const xref = this.xref,
pageKidsCountCache = this.pageKidsCountCache,
pageIndexCache = this.pageIndexCache,
pageDictCache = this.pageDictCache;
let currentPageIndex = 0;
while (nodesToVisit.length) {
const currentNode = nodesToVisit.pop();
if (currentNode instanceof Ref) {
const count = pageKidsCountCache.get(currentNode);
if (count >= 0 && currentPageIndex + count <= pageIndex) {
currentPageIndex += count;
continue;
}
if (visitedNodes.has(currentNode)) {
throw new FormatError("Pages tree contains circular reference.");
}
visitedNodes.put(currentNode);
const obj = await (pageDictCache.get(currentNode) || xref.fetchAsync(currentNode));
if (obj instanceof Dict) {
let type = obj.getRaw("Type");
if (type instanceof Ref) {
type = await xref.fetchAsync(type);
}
if (isName(type, "Page") || !obj.has("Kids")) {
if (!pageKidsCountCache.has(currentNode)) {
pageKidsCountCache.put(currentNode, 1);
}
if (!pageIndexCache.has(currentNode)) {
pageIndexCache.put(currentNode, currentPageIndex);
}
if (currentPageIndex === pageIndex) {
return [obj, currentNode];
}
currentPageIndex++;
continue;
}
}
nodesToVisit.push(obj);
continue;
}
if (!(currentNode instanceof Dict)) {
throw new FormatError("Page dictionary kid reference points to wrong type of object.");
}
const {
objId
} = currentNode;
let count = currentNode.getRaw("Count");
if (count instanceof Ref) {
count = await xref.fetchAsync(count);
}
if (Number.isInteger(count) && count >= 0) {
if (objId && !pageKidsCountCache.has(objId)) {
pageKidsCountCache.put(objId, count);
}
if (currentPageIndex + count <= pageIndex) {
currentPageIndex += count;
continue;
}
}
let kids = currentNode.getRaw("Kids");
if (kids instanceof Ref) {
kids = await xref.fetchAsync(kids);
}
if (!Array.isArray(kids)) {
let type = currentNode.getRaw("Type");
if (type instanceof Ref) {
type = await xref.fetchAsync(type);
}
if (isName(type, "Page") || !currentNode.has("Kids")) {
if (currentPageIndex === pageIndex) {
return [currentNode, null];
}
currentPageIndex++;
continue;
}
throw new FormatError("Page dictionary kids object is not an array.");
}
for (let last = kids.length - 1; last >= 0; last--) {
const lastKid = kids[last];
nodesToVisit.push(lastKid);
if (currentNode === this.toplevelPagesDict && lastKid instanceof Ref && !pageDictCache.has(lastKid)) {
pageDictCache.put(lastKid, xref.fetchAsync(lastKid));
}
}
}
throw new Error(`Page index ${pageIndex} not found.`);
}
async getAllPageDicts(recoveryMode = false) {
const {
ignoreErrors
} = this.pdfManager.evaluatorOptions;
const queue = [{
currentNode: this.toplevelPagesDict,
posInKids: 0
}];
const visitedNodes = new RefSet();
const pagesRef = this._catDict.getRaw("Pages");
if (pagesRef instanceof Ref) {
visitedNodes.put(pagesRef);
}
const map = new Map(),
xref = this.xref,
pageIndexCache = this.pageIndexCache;
let pageIndex = 0;
function addPageDict(pageDict, pageRef) {
if (pageRef && !pageIndexCache.has(pageRef)) {
pageIndexCache.put(pageRef, pageIndex);
}
map.set(pageIndex++, [pageDict, pageRef]);
}
function addPageError(error) {
if (error instanceof XRefEntryException && !recoveryMode) {
throw error;
}
if (recoveryMode && ignoreErrors && pageIndex === 0) {
warn(`getAllPageDicts - Skipping invalid first page: "${error}".`);
error = Dict.empty;
}
map.set(pageIndex++, [error, null]);
}
while (queue.length > 0) {
const queueItem = queue.at(-1);
const {
currentNode,
posInKids
} = queueItem;
let kids = currentNode.getRaw("Kids");
if (kids instanceof Ref) {
try {
kids = await xref.fetchAsync(kids);
} catch (ex) {
addPageError(ex);
break;
}
}
if (!Array.isArray(kids)) {
addPageError(new FormatError("Page dictionary kids object is not an array."));
break;
}
if (posInKids >= kids.length) {
queue.pop();
continue;
}
const kidObj = kids[posInKids];
let obj;
if (kidObj instanceof Ref) {
if (visitedNodes.has(kidObj)) {
addPageError(new FormatError("Pages tree contains circular reference."));
break;
}
visitedNodes.put(kidObj);
try {
obj = await xref.fetchAsync(kidObj);
} catch (ex) {
addPageError(ex);
break;
}
} else {
obj = kidObj;
}
if (!(obj instanceof Dict)) {
addPageError(new FormatError("Page dictionary kid reference points to wrong type of object."));
break;
}
let type = obj.getRaw("Type");
if (type instanceof Ref) {
try {
type = await xref.fetchAsync(type);
} catch (ex) {
addPageError(ex);
break;
}
}
if (isName(type, "Page") || !obj.has("Kids")) {
addPageDict(obj, kidObj instanceof Ref ? kidObj : null);
} else {
queue.push({
currentNode: obj,
posInKids: 0
});
}
queueItem.posInKids++;
}
return map;
}
getPageIndex(pageRef) {
const cachedPageIndex = this.pageIndexCache.get(pageRef);
if (cachedPageIndex !== undefined) {
return Promise.resolve(cachedPageIndex);
}
const xref = this.xref;
function pagesBeforeRef(kidRef) {
let total = 0,
parentRef;
return xref.fetchAsync(kidRef).then(function (node) {
if (isRefsEqual(kidRef, pageRef) && !isDict(node, "Page") && !(node instanceof Dict && !node.has("Type") && node.has("Contents"))) {
throw new FormatError("The reference does not point to a /Page dictionary.");
}
if (!node) {
return null;
}
if (!(node instanceof Dict)) {
throw new FormatError("Node must be a dictionary.");
}
parentRef = node.getRaw("Parent");
return node.getAsync("Parent");
}).then(function (parent) {
if (!parent) {
return null;
}
if (!(parent instanceof Dict)) {
throw new FormatError("Parent must be a dictionary.");
}
return parent.getAsync("Kids");
}).then(function (kids) {
if (!kids) {
return null;
}
const kidPromises = [];
let found = false;
for (const kid of kids) {
if (!(kid instanceof Ref)) {
throw new FormatError("Kid must be a reference.");
}
if (isRefsEqual(kid, kidRef)) {
found = true;
break;
}
kidPromises.push(xref.fetchAsync(kid).then(function (obj) {
if (!(obj instanceof Dict)) {
throw new FormatError("Kid node must be a dictionary.");
}
if (obj.has("Count")) {
total += obj.get("Count");
} else {
total++;
}
}));
}
if (!found) {
throw new FormatError("Kid reference not found in parent's kids.");
}
return Promise.all(kidPromises).then(() => [total, parentRef]);
});
}
let total = 0;
const next = ref => pagesBeforeRef(ref).then(args => {
if (!args) {
this.pageIndexCache.put(pageRef, total);
return total;
}
const [count, parentRef] = args;
total += count;
return next(parentRef);
});
return next(pageRef);
}
get baseUrl() {
const uri = this._catDict.get("URI");
if (uri instanceof Dict) {
const base = uri.get("Base");
if (typeof base === "string") {
const absoluteUrl = createValidAbsoluteUrl(base, null, {
tryConvertEncoding: true
});
if (absoluteUrl) {
return shadow(this, "baseUrl", absoluteUrl.href);
}
}
}
return shadow(this, "baseUrl", this.pdfManager.docBaseUrl);
}
static parseDestDictionary({
destDict,
resultObj,
docBaseUrl = null,
docAttachments = null
}) {
if (!(destDict instanceof Dict)) {
warn("parseDestDictionary: `destDict` must be a dictionary.");
return;
}
let action = destDict.get("A"),
url,
dest;
if (!(action instanceof Dict)) {
if (destDict.has("Dest")) {
action = destDict.get("Dest");
} else {
action = destDict.get("AA");
if (action instanceof Dict) {
if (action.has("D")) {
action = action.get("D");
} else if (action.has("U")) {
action = action.get("U");
}
}
}
}
if (action instanceof Dict) {
const actionType = action.get("S");
if (!(actionType instanceof Name)) {
warn("parseDestDictionary: Invalid type in Action dictionary.");
return;
}
const actionName = actionType.name;
switch (actionName) {
case "ResetForm":
const flags = action.get("Flags");
const include = ((typeof flags === "number" ? flags : 0) & 1) === 0;
const fields = [];
const refs = [];
for (const obj of action.get("Fields") || []) {
if (obj instanceof Ref) {
refs.push(obj.toString());
} else if (typeof obj === "string") {
fields.push(stringToPDFString(obj));
}
}
resultObj.resetForm = {
fields,
refs,
include
};
break;
case "URI":
url = action.get("URI");
if (url instanceof Name) {
url = "/" + url.name;
}
break;
case "GoTo":
dest = action.get("D");
break;
case "Launch":
case "GoToR":
const urlDict = action.get("F");
if (urlDict instanceof Dict) {
const fs = new FileSpec(urlDict, null, true);
const {
rawFilename
} = fs.serializable;
url = rawFilename;
} else if (typeof urlDict === "string") {
url = urlDict;
}
const remoteDest = fetchRemoteDest(action);
if (remoteDest && typeof url === "string") {
url = url.split("#", 1)[0] + "#" + remoteDest;
}
const newWindow = action.get("NewWindow");
if (typeof newWindow === "boolean") {
resultObj.newWindow = newWindow;
}
break;
case "GoToE":
const target = action.get("T");
let attachment;
if (docAttachments && target instanceof Dict) {
const relationship = target.get("R");
const name = target.get("N");
if (isName(relationship, "C") && typeof name === "string") {
attachment = docAttachments[stringToPDFString(name)];
}
}
if (attachment) {
resultObj.attachment = attachment;
const attachmentDest = fetchRemoteDest(action);
if (attachmentDest) {
resultObj.attachmentDest = attachmentDest;
}
} else {
warn(`parseDestDictionary - unimplemented "GoToE" action.`);
}
break;
case "Named":
const namedAction = action.get("N");
if (namedAction instanceof Name) {
resultObj.action = namedAction.name;
}
break;
case "SetOCGState":
const state = action.get("State");
const preserveRB = action.get("PreserveRB");
if (!Array.isArray(state) || state.length === 0) {
break;
}
const stateArr = [];
for (const elem of state) {
if (elem instanceof Name) {
switch (elem.name) {
case "ON":
case "OFF":
case "Toggle":
stateArr.push(elem.name);
break;
}
} else if (elem instanceof Ref) {
stateArr.push(elem.toString());
}
}
if (stateArr.length !== state.length) {
break;
}
resultObj.setOCGState = {
state: stateArr,
preserveRB: typeof preserveRB === "boolean" ? preserveRB : true
};
break;
case "JavaScript":
const jsAction = action.get("JS");
let js;
if (jsAction instanceof BaseStream) {
js = jsAction.getString();
} else if (typeof jsAction === "string") {
js = jsAction;
}
const jsURL = js && recoverJsURL(stringToPDFString(js));
if (jsURL) {
url = jsURL.url;
resultObj.newWindow = jsURL.newWindow;
break;
}
default:
if (actionName === "JavaScript" || actionName === "SubmitForm") {
break;
}
warn(`parseDestDictionary - unsupported action: "${actionName}".`);
break;
}
} else if (destDict.has("Dest")) {
dest = destDict.get("Dest");
}
if (typeof url === "string") {
const absoluteUrl = createValidAbsoluteUrl(url, docBaseUrl, {
addDefaultProtocol: true,
tryConvertEncoding: true
});
if (absoluteUrl) {
resultObj.url = absoluteUrl.href;
}
resultObj.unsafeUrl = url;
}
if (dest) {
if (dest instanceof Name) {
dest = dest.name;
}
if (typeof dest === "string") {
resultObj.dest = stringToPDFString(dest);
} else if (isValidExplicitDest(dest)) {
resultObj.dest = dest;
}
}
}
}
;// ./src/core/object_loader.js
function mayHaveChildren(value) {
return value instanceof Ref || value instanceof Dict || value instanceof BaseStream || Array.isArray(value);
}
function addChildren(node, nodesToVisit) {
if (node instanceof Dict) {
node = node.getRawValues();
} else if (node instanceof BaseStream) {
node = node.dict.getRawValues();
} else if (!Array.isArray(node)) {
return;
}
for (const rawValue of node) {
if (mayHaveChildren(rawValue)) {
nodesToVisit.push(rawValue);
}
}
}
class ObjectLoader {
constructor(dict, keys, xref) {
this.dict = dict;
this.keys = keys;
this.xref = xref;
this.refSet = null;
}
async load() {
if (this.xref.stream.isDataLoaded) {
return undefined;
}
const {
keys,
dict
} = this;
this.refSet = new RefSet();
const nodesToVisit = [];
for (const key of keys) {
const rawValue = dict.getRaw(key);
if (rawValue !== undefined) {
nodesToVisit.push(rawValue);
}
}
return this._walk(nodesToVisit);
}
async _walk(nodesToVisit) {
const nodesToRevisit = [];
const pendingRequests = [];
while (nodesToVisit.length) {
let currentNode = nodesToVisit.pop();
if (currentNode instanceof Ref) {
if (this.refSet.has(currentNode)) {
continue;
}
try {
this.refSet.put(currentNode);
currentNode = this.xref.fetch(currentNode);
} catch (ex) {
if (!(ex instanceof MissingDataException)) {
warn(`ObjectLoader._walk - requesting all data: "${ex}".`);
this.refSet = null;
const {
manager
} = this.xref.stream;
return manager.requestAllChunks();
}
nodesToRevisit.push(currentNode);
pendingRequests.push({
begin: ex.begin,
end: ex.end
});
}
}
if (currentNode instanceof BaseStream) {
const baseStreams = currentNode.getBaseStreams();
if (baseStreams) {
let foundMissingData = false;
for (const stream of baseStreams) {
if (stream.isDataLoaded) {
continue;
}
foundMissingData = true;
pendingRequests.push({
begin: stream.start,
end: stream.end
});
}
if (foundMissingData) {
nodesToRevisit.push(currentNode);
}
}
}
addChildren(currentNode, nodesToVisit);
}
if (pendingRequests.length) {
await this.xref.stream.manager.requestRanges(pendingRequests);
for (const node of nodesToRevisit) {
if (node instanceof Ref) {
this.refSet.remove(node);
}
}
return this._walk(nodesToRevisit);
}
this.refSet = null;
return undefined;
}
}
;// ./src/core/xfa/symbol_utils.js
const $acceptWhitespace = Symbol();
const $addHTML = Symbol();
const $appendChild = Symbol();
const $childrenToHTML = Symbol();
const $clean = Symbol();
const $cleanPage = Symbol();
const $cleanup = Symbol();
const $clone = Symbol();
const $consumed = Symbol();
const $content = Symbol("content");
const $data = Symbol("data");
const $dump = Symbol();
const $extra = Symbol("extra");
const $finalize = Symbol();
const $flushHTML = Symbol();
const $getAttributeIt = Symbol();
const $getAttributes = Symbol();
const $getAvailableSpace = Symbol();
const $getChildrenByClass = Symbol();
const $getChildrenByName = Symbol();
const $getChildrenByNameIt = Symbol();
const $getDataValue = Symbol();
const $getExtra = Symbol();
const $getRealChildrenByNameIt = Symbol();
const $getChildren = Symbol();
const $getContainedChildren = Symbol();
const $getNextPage = Symbol();
const $getSubformParent = Symbol();
const $getParent = Symbol();
const $getTemplateRoot = Symbol();
const $globalData = Symbol();
const $hasSettableValue = Symbol();
const $ids = Symbol();
const $indexOf = Symbol();
const $insertAt = Symbol();
const $isCDATAXml = Symbol();
const $isBindable = Symbol();
const $isDataValue = Symbol();
const $isDescendent = Symbol();
const $isNsAgnostic = Symbol();
const $isSplittable = Symbol();
const $isThereMoreWidth = Symbol();
const $isTransparent = Symbol();
const $isUsable = Symbol();
const $lastAttribute = Symbol();
const $namespaceId = Symbol("namespaceId");
const $nodeName = Symbol("nodeName");
const $nsAttributes = Symbol();
const $onChild = Symbol();
const $onChildCheck = Symbol();
const $onText = Symbol();
const $pushGlyphs = Symbol();
const $popPara = Symbol();
const $pushPara = Symbol();
const $removeChild = Symbol();
const $root = Symbol("root");
const $resolvePrototypes = Symbol();
const $searchNode = Symbol();
const $setId = Symbol();
const $setSetAttributes = Symbol();
const $setValue = Symbol();
const $tabIndex = Symbol();
const $text = Symbol();
const $toPages = Symbol();
const $toHTML = Symbol();
const $toString = Symbol();
const $toStyle = Symbol();
const $uid = Symbol("uid");
;// ./src/core/xfa/namespaces.js
const $buildXFAObject = Symbol();
const NamespaceIds = {
config: {
id: 0,
check: ns => ns.startsWith("http://www.xfa.org/schema/xci/")
},
connectionSet: {
id: 1,
check: ns => ns.startsWith("http://www.xfa.org/schema/xfa-connection-set/")
},
datasets: {
id: 2,
check: ns => ns.startsWith("http://www.xfa.org/schema/xfa-data/")
},
form: {
id: 3,
check: ns => ns.startsWith("http://www.xfa.org/schema/xfa-form/")
},
localeSet: {
id: 4,
check: ns => ns.startsWith("http://www.xfa.org/schema/xfa-locale-set/")
},
pdf: {
id: 5,
check: ns => ns === "http://ns.adobe.com/xdp/pdf/"
},
signature: {
id: 6,
check: ns => ns === "http://www.w3.org/2000/09/xmldsig#"
},
sourceSet: {
id: 7,
check: ns => ns.startsWith("http://www.xfa.org/schema/xfa-source-set/")
},
stylesheet: {
id: 8,
check: ns => ns === "http://www.w3.org/1999/XSL/Transform"
},
template: {
id: 9,
check: ns => ns.startsWith("http://www.xfa.org/schema/xfa-template/")
},
xdc: {
id: 10,
check: ns => ns.startsWith("http://www.xfa.org/schema/xdc/")
},
xdp: {
id: 11,
check: ns => ns === "http://ns.adobe.com/xdp/"
},
xfdf: {
id: 12,
check: ns => ns === "http://ns.adobe.com/xfdf/"
},
xhtml: {
id: 13,
check: ns => ns === "http://www.w3.org/1999/xhtml"
},
xmpmeta: {
id: 14,
check: ns => ns === "http://ns.adobe.com/xmpmeta/"
}
};
;// ./src/core/xfa/utils.js
const dimConverters = {
pt: x => x,
cm: x => x / 2.54 * 72,
mm: x => x / (10 * 2.54) * 72,
in: x => x * 72,
px: x => x
};
const measurementPattern = /([+-]?\d+\.?\d*)(.*)/;
function stripQuotes(str) {
if (str.startsWith("'") || str.startsWith('"')) {
return str.slice(1, -1);
}
return str;
}
function getInteger({
data,
defaultValue,
validate
}) {
if (!data) {
return defaultValue;
}
data = data.trim();
const n = parseInt(data, 10);
if (!isNaN(n) && validate(n)) {
return n;
}
return defaultValue;
}
function getFloat({
data,
defaultValue,
validate
}) {
if (!data) {
return defaultValue;
}
data = data.trim();
const n = parseFloat(data);
if (!isNaN(n) && validate(n)) {
return n;
}
return defaultValue;
}
function getKeyword({
data,
defaultValue,
validate
}) {
if (!data) {
return defaultValue;
}
data = data.trim();
if (validate(data)) {
return data;
}
return defaultValue;
}
function getStringOption(data, options) {
return getKeyword({
data,
defaultValue: options[0],
validate: k => options.includes(k)
});
}
function getMeasurement(str, def = "0") {
def ||= "0";
if (!str) {
return getMeasurement(def);
}
const match = str.trim().match(measurementPattern);
if (!match) {
return getMeasurement(def);
}
const [, valueStr, unit] = match;
const value = parseFloat(valueStr);
if (isNaN(value)) {
return getMeasurement(def);
}
if (value === 0) {
return 0;
}
const conv = dimConverters[unit];
if (conv) {
return conv(value);
}
return value;
}
function getRatio(data) {
if (!data) {
return {
num: 1,
den: 1
};
}
const ratio = data.split(":", 2).map(x => parseFloat(x.trim())).filter(x => !isNaN(x));
if (ratio.length === 1) {
ratio.push(1);
}
if (ratio.length === 0) {
return {
num: 1,
den: 1
};
}
const [num, den] = ratio;
return {
num,
den
};
}
function getRelevant(data) {
if (!data) {
return [];
}
return data.trim().split(/\s+/).map(e => ({
excluded: e[0] === "-",
viewname: e.substring(1)
}));
}
function getColor(data, def = [0, 0, 0]) {
let [r, g, b] = def;
if (!data) {
return {
r,
g,
b
};
}
const color = data.split(",", 3).map(c => MathClamp(parseInt(c.trim(), 10), 0, 255)).map(c => isNaN(c) ? 0 : c);
if (color.length < 3) {
return {
r,
g,
b
};
}
[r, g, b] = color;
return {
r,
g,
b
};
}
function getBBox(data) {
const def = -1;
if (!data) {
return {
x: def,
y: def,
width: def,
height: def
};
}
const bbox = data.split(",", 4).map(m => getMeasurement(m.trim(), "-1"));
if (bbox.length < 4 || bbox[2] < 0 || bbox[3] < 0) {
return {
x: def,
y: def,
width: def,
height: def
};
}
const [x, y, width, height] = bbox;
return {
x,
y,
width,
height
};
}
class HTMLResult {
static get FAILURE() {
return shadow(this, "FAILURE", new HTMLResult(false, null, null, null));
}
static get EMPTY() {
return shadow(this, "EMPTY", new HTMLResult(true, null, null, null));
}
constructor(success, html, bbox, breakNode) {
this.success = success;
this.html = html;
this.bbox = bbox;
this.breakNode = breakNode;
}
isBreak() {
return !!this.breakNode;
}
static breakNode(node) {
return new HTMLResult(false, null, null, node);
}
static success(html, bbox = null) {
return new HTMLResult(true, html, bbox, null);
}
}
;// ./src/core/xfa/fonts.js
class FontFinder {
constructor(pdfFonts) {
this.fonts = new Map();
this.cache = new Map();
this.warned = new Set();
this.defaultFont = null;
this.add(pdfFonts);
}
add(pdfFonts, reallyMissingFonts = null) {
for (const pdfFont of pdfFonts) {
this.addPdfFont(pdfFont);
}
for (const pdfFont of this.fonts.values()) {
if (!pdfFont.regular) {
pdfFont.regular = pdfFont.italic || pdfFont.bold || pdfFont.bolditalic;
}
}
if (!reallyMissingFonts || reallyMissingFonts.size === 0) {
return;
}
const myriad = this.fonts.get("PdfJS-Fallback-PdfJS-XFA");
for (const missing of reallyMissingFonts) {
this.fonts.set(missing, myriad);
}
}
addPdfFont(pdfFont) {
const cssFontInfo = pdfFont.cssFontInfo;
const name = cssFontInfo.fontFamily;
let font = this.fonts.get(name);
if (!font) {
font = Object.create(null);
this.fonts.set(name, font);
if (!this.defaultFont) {
this.defaultFont = font;
}
}
let property = "";
const fontWeight = parseFloat(cssFontInfo.fontWeight);
if (parseFloat(cssFontInfo.italicAngle) !== 0) {
property = fontWeight >= 700 ? "bolditalic" : "italic";
} else if (fontWeight >= 700) {
property = "bold";
}
if (!property) {
if (pdfFont.name.includes("Bold") || pdfFont.psName?.includes("Bold")) {
property = "bold";
}
if (pdfFont.name.includes("Italic") || pdfFont.name.endsWith("It") || pdfFont.psName?.includes("Italic") || pdfFont.psName?.endsWith("It")) {
property += "italic";
}
}
if (!property) {
property = "regular";
}
font[property] = pdfFont;
}
getDefault() {
return this.defaultFont;
}
find(fontName, mustWarn = true) {
let font = this.fonts.get(fontName) || this.cache.get(fontName);
if (font) {
return font;
}
const pattern = /,|-|_| |bolditalic|bold|italic|regular|it/gi;
let name = fontName.replaceAll(pattern, "");
font = this.fonts.get(name);
if (font) {
this.cache.set(fontName, font);
return font;
}
name = name.toLowerCase();
const maybe = [];
for (const [family, pdfFont] of this.fonts.entries()) {
if (family.replaceAll(pattern, "").toLowerCase().startsWith(name)) {
maybe.push(pdfFont);
}
}
if (maybe.length === 0) {
for (const [, pdfFont] of this.fonts.entries()) {
if (pdfFont.regular.name?.replaceAll(pattern, "").toLowerCase().startsWith(name)) {
maybe.push(pdfFont);
}
}
}
if (maybe.length === 0) {
name = name.replaceAll(/psmt|mt/gi, "");
for (const [family, pdfFont] of this.fonts.entries()) {
if (family.replaceAll(pattern, "").toLowerCase().startsWith(name)) {
maybe.push(pdfFont);
}
}
}
if (maybe.length === 0) {
for (const pdfFont of this.fonts.values()) {
if (pdfFont.regular.name?.replaceAll(pattern, "").toLowerCase().startsWith(name)) {
maybe.push(pdfFont);
}
}
}
if (maybe.length >= 1) {
if (maybe.length !== 1 && mustWarn) {
warn(`XFA - Too many choices to guess the correct font: ${fontName}`);
}
this.cache.set(fontName, maybe[0]);
return maybe[0];
}
if (mustWarn && !this.warned.has(fontName)) {
this.warned.add(fontName);
warn(`XFA - Cannot find the font: ${fontName}`);
}
return null;
}
}
function selectFont(xfaFont, typeface) {
if (xfaFont.posture === "italic") {
if (xfaFont.weight === "bold") {
return typeface.bolditalic;
}
return typeface.italic;
} else if (xfaFont.weight === "bold") {
return typeface.bold;
}
return typeface.regular;
}
function fonts_getMetrics(xfaFont, real = false) {
let pdfFont = null;
if (xfaFont) {
const name = stripQuotes(xfaFont.typeface);
const typeface = xfaFont[$globalData].fontFinder.find(name);
pdfFont = selectFont(xfaFont, typeface);
}
if (!pdfFont) {
return {
lineHeight: 12,
lineGap: 2,
lineNoGap: 10
};
}
const size = xfaFont.size || 10;
const lineHeight = pdfFont.lineHeight ? Math.max(real ? 0 : 1.2, pdfFont.lineHeight) : 1.2;
const lineGap = pdfFont.lineGap === undefined ? 0.2 : pdfFont.lineGap;
return {
lineHeight: lineHeight * size,
lineGap: lineGap * size,
lineNoGap: Math.max(1, lineHeight - lineGap) * size
};
}
;// ./src/core/xfa/text.js
const WIDTH_FACTOR = 1.02;
class FontInfo {
constructor(xfaFont, margin, lineHeight, fontFinder) {
this.lineHeight = lineHeight;
this.paraMargin = margin || {
top: 0,
bottom: 0,
left: 0,
right: 0
};
if (!xfaFont) {
[this.pdfFont, this.xfaFont] = this.defaultFont(fontFinder);
return;
}
this.xfaFont = {
typeface: xfaFont.typeface,
posture: xfaFont.posture,
weight: xfaFont.weight,
size: xfaFont.size,
letterSpacing: xfaFont.letterSpacing
};
const typeface = fontFinder.find(xfaFont.typeface);
if (!typeface) {
[this.pdfFont, this.xfaFont] = this.defaultFont(fontFinder);
return;
}
this.pdfFont = selectFont(xfaFont, typeface);
if (!this.pdfFont) {
[this.pdfFont, this.xfaFont] = this.defaultFont(fontFinder);
}
}
defaultFont(fontFinder) {
const font = fontFinder.find("Helvetica", false) || fontFinder.find("Myriad Pro", false) || fontFinder.find("Arial", false) || fontFinder.getDefault();
if (font?.regular) {
const pdfFont = font.regular;
const info = pdfFont.cssFontInfo;
const xfaFont = {
typeface: info.fontFamily,
posture: "normal",
weight: "normal",
size: 10,
letterSpacing: 0
};
return [pdfFont, xfaFont];
}
const xfaFont = {
typeface: "Courier",
posture: "normal",
weight: "normal",
size: 10,
letterSpacing: 0
};
return [null, xfaFont];
}
}
class FontSelector {
constructor(defaultXfaFont, defaultParaMargin, defaultLineHeight, fontFinder) {
this.fontFinder = fontFinder;
this.stack = [new FontInfo(defaultXfaFont, defaultParaMargin, defaultLineHeight, fontFinder)];
}
pushData(xfaFont, margin, lineHeight) {
const lastFont = this.stack.at(-1);
for (const name of ["typeface", "posture", "weight", "size", "letterSpacing"]) {
if (!xfaFont[name]) {
xfaFont[name] = lastFont.xfaFont[name];
}
}
for (const name of ["top", "bottom", "left", "right"]) {
if (isNaN(margin[name])) {
margin[name] = lastFont.paraMargin[name];
}
}
const fontInfo = new FontInfo(xfaFont, margin, lineHeight || lastFont.lineHeight, this.fontFinder);
if (!fontInfo.pdfFont) {
fontInfo.pdfFont = lastFont.pdfFont;
}
this.stack.push(fontInfo);
}
popFont() {
this.stack.pop();
}
topFont() {
return this.stack.at(-1);
}
}
class TextMeasure {
constructor(defaultXfaFont, defaultParaMargin, defaultLineHeight, fonts) {
this.glyphs = [];
this.fontSelector = new FontSelector(defaultXfaFont, defaultParaMargin, defaultLineHeight, fonts);
this.extraHeight = 0;
}
pushData(xfaFont, margin, lineHeight) {
this.fontSelector.pushData(xfaFont, margin, lineHeight);
}
popFont(xfaFont) {
return this.fontSelector.popFont();
}
addPara() {
const lastFont = this.fontSelector.topFont();
this.extraHeight += lastFont.paraMargin.top + lastFont.paraMargin.bottom;
}
addString(str) {
if (!str) {
return;
}
const lastFont = this.fontSelector.topFont();
const fontSize = lastFont.xfaFont.size;
if (lastFont.pdfFont) {
const letterSpacing = lastFont.xfaFont.letterSpacing;
const pdfFont = lastFont.pdfFont;
const fontLineHeight = pdfFont.lineHeight || 1.2;
const lineHeight = lastFont.lineHeight || Math.max(1.2, fontLineHeight) * fontSize;
const lineGap = pdfFont.lineGap === undefined ? 0.2 : pdfFont.lineGap;
const noGap = fontLineHeight - lineGap;
const firstLineHeight = Math.max(1, noGap) * fontSize;
const scale = fontSize / 1000;
const fallbackWidth = pdfFont.defaultWidth || pdfFont.charsToGlyphs(" ")[0].width;
for (const line of str.split(/[\u2029\n]/)) {
const encodedLine = pdfFont.encodeString(line).join("");
const glyphs = pdfFont.charsToGlyphs(encodedLine);
for (const glyph of glyphs) {
const width = glyph.width || fallbackWidth;
this.glyphs.push([width * scale + letterSpacing, lineHeight, firstLineHeight, glyph.unicode, false]);
}
this.glyphs.push([0, 0, 0, "\n", true]);
}
this.glyphs.pop();
return;
}
for (const line of str.split(/[\u2029\n]/)) {
for (const char of line.split("")) {
this.glyphs.push([fontSize, 1.2 * fontSize, fontSize, char, false]);
}
this.glyphs.push([0, 0, 0, "\n", true]);
}
this.glyphs.pop();
}
compute(maxWidth) {
let lastSpacePos = -1,
lastSpaceWidth = 0,
width = 0,
height = 0,
currentLineWidth = 0,
currentLineHeight = 0;
let isBroken = false;
let isFirstLine = true;
for (let i = 0, ii = this.glyphs.length; i < ii; i++) {
const [glyphWidth, lineHeight, firstLineHeight, char, isEOL] = this.glyphs[i];
const isSpace = char === " ";
const glyphHeight = isFirstLine ? firstLineHeight : lineHeight;
if (isEOL) {
width = Math.max(width, currentLineWidth);
currentLineWidth = 0;
height += currentLineHeight;
currentLineHeight = glyphHeight;
lastSpacePos = -1;
lastSpaceWidth = 0;
isFirstLine = false;
continue;
}
if (isSpace) {
if (currentLineWidth + glyphWidth > maxWidth) {
width = Math.max(width, currentLineWidth);
currentLineWidth = 0;
height += currentLineHeight;
currentLineHeight = glyphHeight;
lastSpacePos = -1;
lastSpaceWidth = 0;
isBroken = true;
isFirstLine = false;
} else {
currentLineHeight = Math.max(glyphHeight, currentLineHeight);
lastSpaceWidth = currentLineWidth;
currentLineWidth += glyphWidth;
lastSpacePos = i;
}
continue;
}
if (currentLineWidth + glyphWidth > maxWidth) {
height += currentLineHeight;
currentLineHeight = glyphHeight;
if (lastSpacePos !== -1) {
i = lastSpacePos;
width = Math.max(width, lastSpaceWidth);
currentLineWidth = 0;
lastSpacePos = -1;
lastSpaceWidth = 0;
} else {
width = Math.max(width, currentLineWidth);
currentLineWidth = glyphWidth;
}
isBroken = true;
isFirstLine = false;
continue;
}
currentLineWidth += glyphWidth;
currentLineHeight = Math.max(glyphHeight, currentLineHeight);
}
width = Math.max(width, currentLineWidth);
height += currentLineHeight + this.extraHeight;
return {
width: WIDTH_FACTOR * width,
height,
isBroken
};
}
}
;// ./src/core/xfa/som.js
const namePattern = /^[^.[]+/;
const indexPattern = /^[^\]]+/;
const operators = {
dot: 0,
dotDot: 1,
dotHash: 2,
dotBracket: 3,
dotParen: 4
};
const shortcuts = new Map([["$data", (root, current) => root.datasets ? root.datasets.data : root], ["$record", (root, current) => (root.datasets ? root.datasets.data : root)[$getChildren]()[0]], ["$template", (root, current) => root.template], ["$connectionSet", (root, current) => root.connectionSet], ["$form", (root, current) => root.form], ["$layout", (root, current) => root.layout], ["$host", (root, current) => root.host], ["$dataWindow", (root, current) => root.dataWindow], ["$event", (root, current) => root.event], ["!", (root, current) => root.datasets], ["$xfa", (root, current) => root], ["xfa", (root, current) => root], ["$", (root, current) => current]]);
const somCache = new WeakMap();
function parseIndex(index) {
index = index.trim();
if (index === "*") {
return Infinity;
}
return parseInt(index, 10) || 0;
}
function parseExpression(expr, dotDotAllowed, noExpr = true) {
let match = expr.match(namePattern);
if (!match) {
return null;
}
let [name] = match;
const parsed = [{
name,
cacheName: "." + name,
index: 0,
js: null,
formCalc: null,
operator: operators.dot
}];
let pos = name.length;
while (pos < expr.length) {
const spos = pos;
const char = expr.charAt(pos++);
if (char === "[") {
match = expr.slice(pos).match(indexPattern);
if (!match) {
warn("XFA - Invalid index in SOM expression");
return null;
}
parsed.at(-1).index = parseIndex(match[0]);
pos += match[0].length + 1;
continue;
}
let operator;
switch (expr.charAt(pos)) {
case ".":
if (!dotDotAllowed) {
return null;
}
pos++;
operator = operators.dotDot;
break;
case "#":
pos++;
operator = operators.dotHash;
break;
case "[":
if (noExpr) {
warn("XFA - SOM expression contains a FormCalc subexpression which is not supported for now.");
return null;
}
operator = operators.dotBracket;
break;
case "(":
if (noExpr) {
warn("XFA - SOM expression contains a JavaScript subexpression which is not supported for now.");
return null;
}
operator = operators.dotParen;
break;
default:
operator = operators.dot;
break;
}
match = expr.slice(pos).match(namePattern);
if (!match) {
break;
}
[name] = match;
pos += name.length;
parsed.push({
name,
cacheName: expr.slice(spos, pos),
operator,
index: 0,
js: null,
formCalc: null
});
}
return parsed;
}
function searchNode(root, container, expr, dotDotAllowed = true, useCache = true) {
const parsed = parseExpression(expr, dotDotAllowed);
if (!parsed) {
return null;
}
const fn = shortcuts.get(parsed[0].name);
let i = 0;
let isQualified;
if (fn) {
isQualified = true;
root = [fn(root, container)];
i = 1;
} else {
isQualified = container === null;
root = [container || root];
}
for (let ii = parsed.length; i < ii; i++) {
const {
name,
cacheName,
operator,
index
} = parsed[i];
const nodes = [];
for (const node of root) {
if (!node.isXFAObject) {
continue;
}
let children, cached;
if (useCache) {
cached = somCache.get(node);
if (!cached) {
cached = new Map();
somCache.set(node, cached);
}
children = cached.get(cacheName);
}
if (!children) {
switch (operator) {
case operators.dot:
children = node[$getChildrenByName](name, false);
break;
case operators.dotDot:
children = node[$getChildrenByName](name, true);
break;
case operators.dotHash:
children = node[$getChildrenByClass](name);
children = children.isXFAObjectArray ? children.children : [children];
break;
default:
break;
}
if (useCache) {
cached.set(cacheName, children);
}
}
if (children.length > 0) {
nodes.push(children);
}
}
if (nodes.length === 0 && !isQualified && i === 0) {
const parent = container[$getParent]();
container = parent;
if (!container) {
return null;
}
i = -1;
root = [container];
continue;
}
root = isFinite(index) ? nodes.filter(node => index < node.length).map(node => node[index]) : nodes.flat();
}
if (root.length === 0) {
return null;
}
return root;
}
function createDataNode(root, container, expr) {
const parsed = parseExpression(expr);
if (!parsed) {
return null;
}
if (parsed.some(x => x.operator === operators.dotDot)) {
return null;
}
const fn = shortcuts.get(parsed[0].name);
let i = 0;
if (fn) {
root = fn(root, container);
i = 1;
} else {
root = container || root;
}
for (let ii = parsed.length; i < ii; i++) {
const {
name,
operator,
index
} = parsed[i];
if (!isFinite(index)) {
parsed[i].index = 0;
return root.createNodes(parsed.slice(i));
}
let children;
switch (operator) {
case operators.dot:
children = root[$getChildrenByName](name, false);
break;
case operators.dotDot:
children = root[$getChildrenByName](name, true);
break;
case operators.dotHash:
children = root[$getChildrenByClass](name);
children = children.isXFAObjectArray ? children.children : [children];
break;
default:
break;
}
if (children.length === 0) {
return root.createNodes(parsed.slice(i));
}
if (index < children.length) {
const child = children[index];
if (!child.isXFAObject) {
warn(`XFA - Cannot create a node.`);
return null;
}
root = child;
} else {
parsed[i].index = index - children.length;
return root.createNodes(parsed.slice(i));
}
}
return null;
}
;// ./src/core/xfa/xfa_object.js
const _applyPrototype = Symbol();
const _attributes = Symbol();
const _attributeNames = Symbol();
const _children = Symbol("_children");
const _cloneAttribute = Symbol();
const _dataValue = Symbol();
const _defaultValue = Symbol();
const _filteredChildrenGenerator = Symbol();
const _getPrototype = Symbol();
const _getUnsetAttributes = Symbol();
const _hasChildren = Symbol();
const _max = Symbol();
const _options = Symbol();
const _parent = Symbol("parent");
const _resolvePrototypesHelper = Symbol();
const _setAttributes = Symbol();
const _validator = Symbol();
let uid = 0;
const NS_DATASETS = NamespaceIds.datasets.id;
class XFAObject {
constructor(nsId, name, hasChildren = false) {
this[$namespaceId] = nsId;
this[$nodeName] = name;
this[_hasChildren] = hasChildren;
this[_parent] = null;
this[_children] = [];
this[$uid] = `${name}${uid++}`;
this[$globalData] = null;
}
get isXFAObject() {
return true;
}
get isXFAObjectArray() {
return false;
}
createNodes(path) {
let root = this,
node = null;
for (const {
name,
index
} of path) {
for (let i = 0, ii = isFinite(index) ? index : 0; i <= ii; i++) {
const nsId = root[$namespaceId] === NS_DATASETS ? -1 : root[$namespaceId];
node = new XmlObject(nsId, name);
root[$appendChild](node);
}
root = node;
}
return node;
}
[$onChild](child) {
if (!this[_hasChildren] || !this[$onChildCheck](child)) {
return false;
}
const name = child[$nodeName];
const node = this[name];
if (node instanceof XFAObjectArray) {
if (node.push(child)) {
this[$appendChild](child);
return true;
}
} else {
if (node !== null) {
this[$removeChild](node);
}
this[name] = child;
this[$appendChild](child);
return true;
}
let id = "";
if (this.id) {
id = ` (id: ${this.id})`;
} else if (this.name) {
id = ` (name: ${this.name} ${this.h.value})`;
}
warn(`XFA - node "${this[$nodeName]}"${id} has already enough "${name}"!`);
return false;
}
[$onChildCheck](child) {
return this.hasOwnProperty(child[$nodeName]) && child[$namespaceId] === this[$namespaceId];
}
[$isNsAgnostic]() {
return false;
}
[$acceptWhitespace]() {
return false;
}
[$isCDATAXml]() {
return false;
}
[$isBindable]() {
return false;
}
[$popPara]() {
if (this.para) {
this[$getTemplateRoot]()[$extra].paraStack.pop();
}
}
[$pushPara]() {
this[$getTemplateRoot]()[$extra].paraStack.push(this.para);
}
[$setId](ids) {
if (this.id && this[$namespaceId] === NamespaceIds.template.id) {
ids.set(this.id, this);
}
}
[$getTemplateRoot]() {
return this[$globalData].template;
}
[$isSplittable]() {
return false;
}
[$isThereMoreWidth]() {
return false;
}
[$appendChild](child) {
child[_parent] = this;
this[_children].push(child);
if (!child[$globalData] && this[$globalData]) {
child[$globalData] = this[$globalData];
}
}
[$removeChild](child) {
const i = this[_children].indexOf(child);
this[_children].splice(i, 1);
}
[$hasSettableValue]() {
return this.hasOwnProperty("value");
}
[$setValue](_) {}
[$onText](_) {}
[$finalize]() {}
[$clean](builder) {
delete this[_hasChildren];
if (this[$cleanup]) {
builder.clean(this[$cleanup]);
delete this[$cleanup];
}
}
[$indexOf](child) {
return this[_children].indexOf(child);
}
[$insertAt](i, child) {
child[_parent] = this;
this[_children].splice(i, 0, child);
if (!child[$globalData] && this[$globalData]) {
child[$globalData] = this[$globalData];
}
}
[$isTransparent]() {
return !this.name;
}
[$lastAttribute]() {
return "";
}
[$text]() {
if (this[_children].length === 0) {
return this[$content];
}
return this[_children].map(c => c[$text]()).join("");
}
get [_attributeNames]() {
const proto = Object.getPrototypeOf(this);
if (!proto._attributes) {
const attributes = proto._attributes = new Set();
for (const name of Object.getOwnPropertyNames(this)) {
if (this[name] === null || this[name] instanceof XFAObject || this[name] instanceof XFAObjectArray) {
break;
}
attributes.add(name);
}
}
return shadow(this, _attributeNames, proto._attributes);
}
[$isDescendent](parent) {
let node = this;
while (node) {
if (node === parent) {
return true;
}
node = node[$getParent]();
}
return false;
}
[$getParent]() {
return this[_parent];
}
[$getSubformParent]() {
return this[$getParent]();
}
[$getChildren](name = null) {
if (!name) {
return this[_children];
}
return this[name];
}
[$dump]() {
const dumped = Object.create(null);
if (this[$content]) {
dumped.$content = this[$content];
}
for (const name of Object.getOwnPropertyNames(this)) {
const value = this[name];
if (value === null) {
continue;
}
if (value instanceof XFAObject) {
dumped[name] = value[$dump]();
} else if (value instanceof XFAObjectArray) {
if (!value.isEmpty()) {
dumped[name] = value.dump();
}
} else {
dumped[name] = value;
}
}
return dumped;
}
[$toStyle]() {
return null;
}
[$toHTML]() {
return HTMLResult.EMPTY;
}
*[$getContainedChildren]() {
for (const node of this[$getChildren]()) {
yield node;
}
}
*[_filteredChildrenGenerator](filter, include) {
for (const node of this[$getContainedChildren]()) {
if (!filter || include === filter.has(node[$nodeName])) {
const availableSpace = this[$getAvailableSpace]();
const res = node[$toHTML](availableSpace);
if (!res.success) {
this[$extra].failingNode = node;
}
yield res;
}
}
}
[$flushHTML]() {
return null;
}
[$addHTML](html, bbox) {
this[$extra].children.push(html);
}
[$getAvailableSpace]() {}
[$childrenToHTML]({
filter = null,
include = true
}) {
if (!this[$extra].generator) {
this[$extra].generator = this[_filteredChildrenGenerator](filter, include);
} else {
const availableSpace = this[$getAvailableSpace]();
const res = this[$extra].failingNode[$toHTML](availableSpace);
if (!res.success) {
return res;
}
if (res.html) {
this[$addHTML](res.html, res.bbox);
}
delete this[$extra].failingNode;
}
while (true) {
const gen = this[$extra].generator.next();
if (gen.done) {
break;
}
const res = gen.value;
if (!res.success) {
return res;
}
if (res.html) {
this[$addHTML](res.html, res.bbox);
}
}
this[$extra].generator = null;
return HTMLResult.EMPTY;
}
[$setSetAttributes](attributes) {
this[_setAttributes] = new Set(Object.keys(attributes));
}
[_getUnsetAttributes](protoAttributes) {
const allAttr = this[_attributeNames];
const setAttr = this[_setAttributes];
return [...protoAttributes].filter(x => allAttr.has(x) && !setAttr.has(x));
}
[$resolvePrototypes](ids, ancestors = new Set()) {
for (const child of this[_children]) {
child[_resolvePrototypesHelper](ids, ancestors);
}
}
[_resolvePrototypesHelper](ids, ancestors) {
const proto = this[_getPrototype](ids, ancestors);
if (proto) {
this[_applyPrototype](proto, ids, ancestors);
} else {
this[$resolvePrototypes](ids, ancestors);
}
}
[_getPrototype](ids, ancestors) {
const {
use,
usehref
} = this;
if (!use && !usehref) {
return null;
}
let proto = null;
let somExpression = null;
let id = null;
let ref = use;
if (usehref) {
ref = usehref;
if (usehref.startsWith("#som(") && usehref.endsWith(")")) {
somExpression = usehref.slice("#som(".length, -1);
} else if (usehref.startsWith(".#som(") && usehref.endsWith(")")) {
somExpression = usehref.slice(".#som(".length, -1);
} else if (usehref.startsWith("#")) {
id = usehref.slice(1);
} else if (usehref.startsWith(".#")) {
id = usehref.slice(2);
}
} else if (use.startsWith("#")) {
id = use.slice(1);
} else {
somExpression = use;
}
this.use = this.usehref = "";
if (id) {
proto = ids.get(id);
} else {
proto = searchNode(ids.get($root), this, somExpression, true, false);
if (proto) {
proto = proto[0];
}
}
if (!proto) {
warn(`XFA - Invalid prototype reference: ${ref}.`);
return null;
}
if (proto[$nodeName] !== this[$nodeName]) {
warn(`XFA - Incompatible prototype: ${proto[$nodeName]} !== ${this[$nodeName]}.`);
return null;
}
if (ancestors.has(proto)) {
warn(`XFA - Cycle detected in prototypes use.`);
return null;
}
ancestors.add(proto);
const protoProto = proto[_getPrototype](ids, ancestors);
if (protoProto) {
proto[_applyPrototype](protoProto, ids, ancestors);
}
proto[$resolvePrototypes](ids, ancestors);
ancestors.delete(proto);
return proto;
}
[_applyPrototype](proto, ids, ancestors) {
if (ancestors.has(proto)) {
warn(`XFA - Cycle detected in prototypes use.`);
return;
}
if (!this[$content] && proto[$content]) {
this[$content] = proto[$content];
}
const newAncestors = new Set(ancestors);
newAncestors.add(proto);
for (const unsetAttrName of this[_getUnsetAttributes](proto[_setAttributes])) {
this[unsetAttrName] = proto[unsetAttrName];
if (this[_setAttributes]) {
this[_setAttributes].add(unsetAttrName);
}
}
for (const name of Object.getOwnPropertyNames(this)) {
if (this[_attributeNames].has(name)) {
continue;
}
const value = this[name];
const protoValue = proto[name];
if (value instanceof XFAObjectArray) {
for (const child of value[_children]) {
child[_resolvePrototypesHelper](ids, ancestors);
}
for (let i = value[_children].length, ii = protoValue[_children].length; i < ii; i++) {
const child = proto[_children][i][$clone]();
if (value.push(child)) {
child[_parent] = this;
this[_children].push(child);
child[_resolvePrototypesHelper](ids, ancestors);
} else {
break;
}
}
continue;
}
if (value !== null) {
value[$resolvePrototypes](ids, ancestors);
if (protoValue) {
value[_applyPrototype](protoValue, ids, ancestors);
}
continue;
}
if (protoValue !== null) {
const child = protoValue[$clone]();
child[_parent] = this;
this[name] = child;
this[_children].push(child);
child[_resolvePrototypesHelper](ids, ancestors);
}
}
}
static [_cloneAttribute](obj) {
if (Array.isArray(obj)) {
return obj.map(x => XFAObject[_cloneAttribute](x));
}
if (typeof obj === "object" && obj !== null) {
return Object.assign({}, obj);
}
return obj;
}
[$clone]() {
const clone = Object.create(Object.getPrototypeOf(this));
for (const $symbol of Object.getOwnPropertySymbols(this)) {
try {
clone[$symbol] = this[$symbol];
} catch {
shadow(clone, $symbol, this[$symbol]);
}
}
clone[$uid] = `${clone[$nodeName]}${uid++}`;
clone[_children] = [];
for (const name of Object.getOwnPropertyNames(this)) {
if (this[_attributeNames].has(name)) {
clone[name] = XFAObject[_cloneAttribute](this[name]);
continue;
}
const value = this[name];
clone[name] = value instanceof XFAObjectArray ? new XFAObjectArray(value[_max]) : null;
}
for (const child of this[_children]) {
const name = child[$nodeName];
const clonedChild = child[$clone]();
clone[_children].push(clonedChild);
clonedChild[_parent] = clone;
if (clone[name] === null) {
clone[name] = clonedChild;
} else {
clone[name][_children].push(clonedChild);
}
}
return clone;
}
[$getChildren](name = null) {
if (!name) {
return this[_children];
}
return this[_children].filter(c => c[$nodeName] === name);
}
[$getChildrenByClass](name) {
return this[name];
}
[$getChildrenByName](name, allTransparent, first = true) {
return Array.from(this[$getChildrenByNameIt](name, allTransparent, first));
}
*[$getChildrenByNameIt](name, allTransparent, first = true) {
if (name === "parent") {
yield this[_parent];
return;
}
for (const child of this[_children]) {
if (child[$nodeName] === name) {
yield child;
}
if (child.name === name) {
yield child;
}
if (allTransparent || child[$isTransparent]()) {
yield* child[$getChildrenByNameIt](name, allTransparent, false);
}
}
if (first && this[_attributeNames].has(name)) {
yield new XFAAttribute(this, name, this[name]);
}
}
}
class XFAObjectArray {
constructor(max = Infinity) {
this[_max] = max;
this[_children] = [];
}
get isXFAObject() {
return false;
}
get isXFAObjectArray() {
return true;
}
push(child) {
const len = this[_children].length;
if (len <= this[_max]) {
this[_children].push(child);
return true;
}
warn(`XFA - node "${child[$nodeName]}" accepts no more than ${this[_max]} children`);
return false;
}
isEmpty() {
return this[_children].length === 0;
}
dump() {
return this[_children].length === 1 ? this[_children][0][$dump]() : this[_children].map(x => x[$dump]());
}
[$clone]() {
const clone = new XFAObjectArray(this[_max]);
clone[_children] = this[_children].map(c => c[$clone]());
return clone;
}
get children() {
return this[_children];
}
clear() {
this[_children].length = 0;
}
}
class XFAAttribute {
constructor(node, name, value) {
this[_parent] = node;
this[$nodeName] = name;
this[$content] = value;
this[$consumed] = false;
this[$uid] = `attribute${uid++}`;
}
[$getParent]() {
return this[_parent];
}
[$isDataValue]() {
return true;
}
[$getDataValue]() {
return this[$content].trim();
}
[$setValue](value) {
value = value.value || "";
this[$content] = value.toString();
}
[$text]() {
return this[$content];
}
[$isDescendent](parent) {
return this[_parent] === parent || this[_parent][$isDescendent](parent);
}
}
class XmlObject extends XFAObject {
constructor(nsId, name, attributes = {}) {
super(nsId, name);
this[$content] = "";
this[_dataValue] = null;
if (name !== "#text") {
const map = new Map();
this[_attributes] = map;
for (const [attrName, value] of Object.entries(attributes)) {
map.set(attrName, new XFAAttribute(this, attrName, value));
}
if (attributes.hasOwnProperty($nsAttributes)) {
const dataNode = attributes[$nsAttributes].xfa.dataNode;
if (dataNode !== undefined) {
if (dataNode === "dataGroup") {
this[_dataValue] = false;
} else if (dataNode === "dataValue") {
this[_dataValue] = true;
}
}
}
}
this[$consumed] = false;
}
[$toString](buf) {
const tagName = this[$nodeName];
if (tagName === "#text") {
buf.push(encodeToXmlString(this[$content]));
return;
}
const utf8TagName = utf8StringToString(tagName);
const prefix = this[$namespaceId] === NS_DATASETS ? "xfa:" : "";
buf.push(`<${prefix}${utf8TagName}`);
for (const [name, value] of this[_attributes].entries()) {
const utf8Name = utf8StringToString(name);
buf.push(` ${utf8Name}="${encodeToXmlString(value[$content])}"`);
}
if (this[_dataValue] !== null) {
if (this[_dataValue]) {
buf.push(` xfa:dataNode="dataValue"`);
} else {
buf.push(` xfa:dataNode="dataGroup"`);
}
}
if (!this[$content] && this[_children].length === 0) {
buf.push("/>");
return;
}
buf.push(">");
if (this[$content]) {
if (typeof this[$content] === "string") {
buf.push(encodeToXmlString(this[$content]));
} else {
this[$content][$toString](buf);
}
} else {
for (const child of this[_children]) {
child[$toString](buf);
}
}
buf.push(`${prefix}${utf8TagName}>`);
}
[$onChild](child) {
if (this[$content]) {
const node = new XmlObject(this[$namespaceId], "#text");
this[$appendChild](node);
node[$content] = this[$content];
this[$content] = "";
}
this[$appendChild](child);
return true;
}
[$onText](str) {
this[$content] += str;
}
[$finalize]() {
if (this[$content] && this[_children].length > 0) {
const node = new XmlObject(this[$namespaceId], "#text");
this[$appendChild](node);
node[$content] = this[$content];
delete this[$content];
}
}
[$toHTML]() {
if (this[$nodeName] === "#text") {
return HTMLResult.success({
name: "#text",
value: this[$content]
});
}
return HTMLResult.EMPTY;
}
[$getChildren](name = null) {
if (!name) {
return this[_children];
}
return this[_children].filter(c => c[$nodeName] === name);
}
[$getAttributes]() {
return this[_attributes];
}
[$getChildrenByClass](name) {
const value = this[_attributes].get(name);
if (value !== undefined) {
return value;
}
return this[$getChildren](name);
}
*[$getChildrenByNameIt](name, allTransparent) {
const value = this[_attributes].get(name);
if (value) {
yield value;
}
for (const child of this[_children]) {
if (child[$nodeName] === name) {
yield child;
}
if (allTransparent) {
yield* child[$getChildrenByNameIt](name, allTransparent);
}
}
}
*[$getAttributeIt](name, skipConsumed) {
const value = this[_attributes].get(name);
if (value && (!skipConsumed || !value[$consumed])) {
yield value;
}
for (const child of this[_children]) {
yield* child[$getAttributeIt](name, skipConsumed);
}
}
*[$getRealChildrenByNameIt](name, allTransparent, skipConsumed) {
for (const child of this[_children]) {
if (child[$nodeName] === name && (!skipConsumed || !child[$consumed])) {
yield child;
}
if (allTransparent) {
yield* child[$getRealChildrenByNameIt](name, allTransparent, skipConsumed);
}
}
}
[$isDataValue]() {
if (this[_dataValue] === null) {
return this[_children].length === 0 || this[_children][0][$namespaceId] === NamespaceIds.xhtml.id;
}
return this[_dataValue];
}
[$getDataValue]() {
if (this[_dataValue] === null) {
if (this[_children].length === 0) {
return this[$content].trim();
}
if (this[_children][0][$namespaceId] === NamespaceIds.xhtml.id) {
return this[_children][0][$text]().trim();
}
return null;
}
return this[$content].trim();
}
[$setValue](value) {
value = value.value || "";
this[$content] = value.toString();
}
[$dump](hasNS = false) {
const dumped = Object.create(null);
if (hasNS) {
dumped.$ns = this[$namespaceId];
}
if (this[$content]) {
dumped.$content = this[$content];
}
dumped.$name = this[$nodeName];
dumped.children = [];
for (const child of this[_children]) {
dumped.children.push(child[$dump](hasNS));
}
dumped.attributes = Object.create(null);
for (const [name, value] of this[_attributes]) {
dumped.attributes[name] = value[$content];
}
return dumped;
}
}
class ContentObject extends XFAObject {
constructor(nsId, name) {
super(nsId, name);
this[$content] = "";
}
[$onText](text) {
this[$content] += text;
}
[$finalize]() {}
}
class OptionObject extends ContentObject {
constructor(nsId, name, options) {
super(nsId, name);
this[_options] = options;
}
[$finalize]() {
this[$content] = getKeyword({
data: this[$content],
defaultValue: this[_options][0],
validate: k => this[_options].includes(k)
});
}
[$clean](builder) {
super[$clean](builder);
delete this[_options];
}
}
class StringObject extends ContentObject {
[$finalize]() {
this[$content] = this[$content].trim();
}
}
class IntegerObject extends ContentObject {
constructor(nsId, name, defaultValue, validator) {
super(nsId, name);
this[_defaultValue] = defaultValue;
this[_validator] = validator;
}
[$finalize]() {
this[$content] = getInteger({
data: this[$content],
defaultValue: this[_defaultValue],
validate: this[_validator]
});
}
[$clean](builder) {
super[$clean](builder);
delete this[_defaultValue];
delete this[_validator];
}
}
class Option01 extends IntegerObject {
constructor(nsId, name) {
super(nsId, name, 0, n => n === 1);
}
}
class Option10 extends IntegerObject {
constructor(nsId, name) {
super(nsId, name, 1, n => n === 0);
}
}
;// ./src/core/xfa/html_utils.js
function measureToString(m) {
if (typeof m === "string") {
return "0px";
}
return Number.isInteger(m) ? `${m}px` : `${m.toFixed(2)}px`;
}
const converters = {
anchorType(node, style) {
const parent = node[$getSubformParent]();
if (!parent || parent.layout && parent.layout !== "position") {
return;
}
if (!("transform" in style)) {
style.transform = "";
}
switch (node.anchorType) {
case "bottomCenter":
style.transform += "translate(-50%, -100%)";
break;
case "bottomLeft":
style.transform += "translate(0,-100%)";
break;
case "bottomRight":
style.transform += "translate(-100%,-100%)";
break;
case "middleCenter":
style.transform += "translate(-50%,-50%)";
break;
case "middleLeft":
style.transform += "translate(0,-50%)";
break;
case "middleRight":
style.transform += "translate(-100%,-50%)";
break;
case "topCenter":
style.transform += "translate(-50%,0)";
break;
case "topRight":
style.transform += "translate(-100%,0)";
break;
}
},
dimensions(node, style) {
const parent = node[$getSubformParent]();
let width = node.w;
const height = node.h;
if (parent.layout?.includes("row")) {
const extra = parent[$extra];
const colSpan = node.colSpan;
let w;
if (colSpan === -1) {
w = Math.sumPrecise(extra.columnWidths.slice(extra.currentColumn));
extra.currentColumn = 0;
} else {
w = Math.sumPrecise(extra.columnWidths.slice(extra.currentColumn, extra.currentColumn + colSpan));
extra.currentColumn = (extra.currentColumn + node.colSpan) % extra.columnWidths.length;
}
if (!isNaN(w)) {
width = node.w = w;
}
}
style.width = width !== "" ? measureToString(width) : "auto";
style.height = height !== "" ? measureToString(height) : "auto";
},
position(node, style) {
const parent = node[$getSubformParent]();
if (parent?.layout && parent.layout !== "position") {
return;
}
style.position = "absolute";
style.left = measureToString(node.x);
style.top = measureToString(node.y);
},
rotate(node, style) {
if (node.rotate) {
if (!("transform" in style)) {
style.transform = "";
}
style.transform += `rotate(-${node.rotate}deg)`;
style.transformOrigin = "top left";
}
},
presence(node, style) {
switch (node.presence) {
case "invisible":
style.visibility = "hidden";
break;
case "hidden":
case "inactive":
style.display = "none";
break;
}
},
hAlign(node, style) {
if (node[$nodeName] === "para") {
switch (node.hAlign) {
case "justifyAll":
style.textAlign = "justify-all";
break;
case "radix":
style.textAlign = "left";
break;
default:
style.textAlign = node.hAlign;
}
} else {
switch (node.hAlign) {
case "left":
style.alignSelf = "start";
break;
case "center":
style.alignSelf = "center";
break;
case "right":
style.alignSelf = "end";
break;
}
}
},
margin(node, style) {
if (node.margin) {
style.margin = node.margin[$toStyle]().margin;
}
}
};
function setMinMaxDimensions(node, style) {
const parent = node[$getSubformParent]();
if (parent.layout === "position") {
if (node.minW > 0) {
style.minWidth = measureToString(node.minW);
}
if (node.maxW > 0) {
style.maxWidth = measureToString(node.maxW);
}
if (node.minH > 0) {
style.minHeight = measureToString(node.minH);
}
if (node.maxH > 0) {
style.maxHeight = measureToString(node.maxH);
}
}
}
function layoutText(text, xfaFont, margin, lineHeight, fontFinder, width) {
const measure = new TextMeasure(xfaFont, margin, lineHeight, fontFinder);
if (typeof text === "string") {
measure.addString(text);
} else {
text[$pushGlyphs](measure);
}
return measure.compute(width);
}
function layoutNode(node, availableSpace) {
let height = null;
let width = null;
let isBroken = false;
if ((!node.w || !node.h) && node.value) {
let marginH = 0;
let marginV = 0;
if (node.margin) {
marginH = node.margin.leftInset + node.margin.rightInset;
marginV = node.margin.topInset + node.margin.bottomInset;
}
let lineHeight = null;
let margin = null;
if (node.para) {
margin = Object.create(null);
lineHeight = node.para.lineHeight === "" ? null : node.para.lineHeight;
margin.top = node.para.spaceAbove === "" ? 0 : node.para.spaceAbove;
margin.bottom = node.para.spaceBelow === "" ? 0 : node.para.spaceBelow;
margin.left = node.para.marginLeft === "" ? 0 : node.para.marginLeft;
margin.right = node.para.marginRight === "" ? 0 : node.para.marginRight;
}
let font = node.font;
if (!font) {
const root = node[$getTemplateRoot]();
let parent = node[$getParent]();
while (parent && parent !== root) {
if (parent.font) {
font = parent.font;
break;
}
parent = parent[$getParent]();
}
}
const maxWidth = (node.w || availableSpace.width) - marginH;
const fontFinder = node[$globalData].fontFinder;
if (node.value.exData && node.value.exData[$content] && node.value.exData.contentType === "text/html") {
const res = layoutText(node.value.exData[$content], font, margin, lineHeight, fontFinder, maxWidth);
width = res.width;
height = res.height;
isBroken = res.isBroken;
} else {
const text = node.value[$text]();
if (text) {
const res = layoutText(text, font, margin, lineHeight, fontFinder, maxWidth);
width = res.width;
height = res.height;
isBroken = res.isBroken;
}
}
if (width !== null && !node.w) {
width += marginH;
}
if (height !== null && !node.h) {
height += marginV;
}
}
return {
w: width,
h: height,
isBroken
};
}
function computeBbox(node, html, availableSpace) {
let bbox;
if (node.w !== "" && node.h !== "") {
bbox = [node.x, node.y, node.w, node.h];
} else {
if (!availableSpace) {
return null;
}
let width = node.w;
if (width === "") {
if (node.maxW === 0) {
const parent = node[$getSubformParent]();
width = parent.layout === "position" && parent.w !== "" ? 0 : node.minW;
} else {
width = Math.min(node.maxW, availableSpace.width);
}
html.attributes.style.width = measureToString(width);
}
let height = node.h;
if (height === "") {
if (node.maxH === 0) {
const parent = node[$getSubformParent]();
height = parent.layout === "position" && parent.h !== "" ? 0 : node.minH;
} else {
height = Math.min(node.maxH, availableSpace.height);
}
html.attributes.style.height = measureToString(height);
}
bbox = [node.x, node.y, width, height];
}
return bbox;
}
function fixDimensions(node) {
const parent = node[$getSubformParent]();
if (parent.layout?.includes("row")) {
const extra = parent[$extra];
const colSpan = node.colSpan;
let width;
if (colSpan === -1) {
width = Math.sumPrecise(extra.columnWidths.slice(extra.currentColumn));
} else {
width = Math.sumPrecise(extra.columnWidths.slice(extra.currentColumn, extra.currentColumn + colSpan));
}
if (!isNaN(width)) {
node.w = width;
}
}
if (parent.layout && parent.layout !== "position") {
node.x = node.y = 0;
}
if (node.layout === "table") {
if (node.w === "" && Array.isArray(node.columnWidths)) {
node.w = Math.sumPrecise(node.columnWidths);
}
}
}
function layoutClass(node) {
switch (node.layout) {
case "position":
return "xfaPosition";
case "lr-tb":
return "xfaLrTb";
case "rl-row":
return "xfaRlRow";
case "rl-tb":
return "xfaRlTb";
case "row":
return "xfaRow";
case "table":
return "xfaTable";
case "tb":
return "xfaTb";
default:
return "xfaPosition";
}
}
function toStyle(node, ...names) {
const style = Object.create(null);
for (const name of names) {
const value = node[name];
if (value === null) {
continue;
}
if (converters.hasOwnProperty(name)) {
converters[name](node, style);
continue;
}
if (value instanceof XFAObject) {
const newStyle = value[$toStyle]();
if (newStyle) {
Object.assign(style, newStyle);
} else {
warn(`(DEBUG) - XFA - style for ${name} not implemented yet`);
}
}
}
return style;
}
function createWrapper(node, html) {
const {
attributes
} = html;
const {
style
} = attributes;
const wrapper = {
name: "div",
attributes: {
class: ["xfaWrapper"],
style: Object.create(null)
},
children: []
};
attributes.class.push("xfaWrapped");
if (node.border) {
const {
widths,
insets
} = node.border[$extra];
let width, height;
let top = insets[0];
let left = insets[3];
const insetsH = insets[0] + insets[2];
const insetsW = insets[1] + insets[3];
switch (node.border.hand) {
case "even":
top -= widths[0] / 2;
left -= widths[3] / 2;
width = `calc(100% + ${(widths[1] + widths[3]) / 2 - insetsW}px)`;
height = `calc(100% + ${(widths[0] + widths[2]) / 2 - insetsH}px)`;
break;
case "left":
top -= widths[0];
left -= widths[3];
width = `calc(100% + ${widths[1] + widths[3] - insetsW}px)`;
height = `calc(100% + ${widths[0] + widths[2] - insetsH}px)`;
break;
case "right":
width = insetsW ? `calc(100% - ${insetsW}px)` : "100%";
height = insetsH ? `calc(100% - ${insetsH}px)` : "100%";
break;
}
const classNames = ["xfaBorder"];
if (isPrintOnly(node.border)) {
classNames.push("xfaPrintOnly");
}
const border = {
name: "div",
attributes: {
class: classNames,
style: {
top: `${top}px`,
left: `${left}px`,
width,
height
}
},
children: []
};
for (const key of ["border", "borderWidth", "borderColor", "borderRadius", "borderStyle"]) {
if (style[key] !== undefined) {
border.attributes.style[key] = style[key];
delete style[key];
}
}
wrapper.children.push(border, html);
} else {
wrapper.children.push(html);
}
for (const key of ["background", "backgroundClip", "top", "left", "width", "height", "minWidth", "minHeight", "maxWidth", "maxHeight", "transform", "transformOrigin", "visibility"]) {
if (style[key] !== undefined) {
wrapper.attributes.style[key] = style[key];
delete style[key];
}
}
wrapper.attributes.style.position = style.position === "absolute" ? "absolute" : "relative";
delete style.position;
if (style.alignSelf) {
wrapper.attributes.style.alignSelf = style.alignSelf;
delete style.alignSelf;
}
return wrapper;
}
function fixTextIndent(styles) {
const indent = getMeasurement(styles.textIndent, "0px");
if (indent >= 0) {
return;
}
const align = styles.textAlign === "right" ? "right" : "left";
const name = "padding" + (align === "left" ? "Left" : "Right");
const padding = getMeasurement(styles[name], "0px");
styles[name] = `${padding - indent}px`;
}
function setAccess(node, classNames) {
switch (node.access) {
case "nonInteractive":
classNames.push("xfaNonInteractive");
break;
case "readOnly":
classNames.push("xfaReadOnly");
break;
case "protected":
classNames.push("xfaDisabled");
break;
}
}
function isPrintOnly(node) {
return node.relevant.length > 0 && !node.relevant[0].excluded && node.relevant[0].viewname === "print";
}
function getCurrentPara(node) {
const stack = node[$getTemplateRoot]()[$extra].paraStack;
return stack.length ? stack.at(-1) : null;
}
function setPara(node, nodeStyle, value) {
if (value.attributes.class?.includes("xfaRich")) {
if (nodeStyle) {
if (node.h === "") {
nodeStyle.height = "auto";
}
if (node.w === "") {
nodeStyle.width = "auto";
}
}
const para = getCurrentPara(node);
if (para) {
const valueStyle = value.attributes.style;
valueStyle.display = "flex";
valueStyle.flexDirection = "column";
switch (para.vAlign) {
case "top":
valueStyle.justifyContent = "start";
break;
case "bottom":
valueStyle.justifyContent = "end";
break;
case "middle":
valueStyle.justifyContent = "center";
break;
}
const paraStyle = para[$toStyle]();
for (const [key, val] of Object.entries(paraStyle)) {
if (!(key in valueStyle)) {
valueStyle[key] = val;
}
}
}
}
}
function setFontFamily(xfaFont, node, fontFinder, style) {
if (!fontFinder) {
delete style.fontFamily;
return;
}
const name = stripQuotes(xfaFont.typeface);
style.fontFamily = `"${name}"`;
const typeface = fontFinder.find(name);
if (typeface) {
const {
fontFamily
} = typeface.regular.cssFontInfo;
if (fontFamily !== name) {
style.fontFamily = `"${fontFamily}"`;
}
const para = getCurrentPara(node);
if (para && para.lineHeight !== "") {
return;
}
if (style.lineHeight) {
return;
}
const pdfFont = selectFont(xfaFont, typeface);
if (pdfFont) {
style.lineHeight = Math.max(1.2, pdfFont.lineHeight);
}
}
}
function fixURL(str) {
const absoluteUrl = createValidAbsoluteUrl(str, null, {
addDefaultProtocol: true,
tryConvertEncoding: true
});
return absoluteUrl ? absoluteUrl.href : null;
}
;// ./src/core/xfa/layout.js
function createLine(node, children) {
return {
name: "div",
attributes: {
class: [node.layout === "lr-tb" ? "xfaLr" : "xfaRl"]
},
children
};
}
function flushHTML(node) {
if (!node[$extra]) {
return null;
}
const attributes = node[$extra].attributes;
const html = {
name: "div",
attributes,
children: node[$extra].children
};
if (node[$extra].failingNode) {
const htmlFromFailing = node[$extra].failingNode[$flushHTML]();
if (htmlFromFailing) {
if (node.layout.endsWith("-tb")) {
html.children.push(createLine(node, [htmlFromFailing]));
} else {
html.children.push(htmlFromFailing);
}
}
}
if (html.children.length === 0) {
return null;
}
return html;
}
function addHTML(node, html, bbox) {
const extra = node[$extra];
const availableSpace = extra.availableSpace;
const [x, y, w, h] = bbox;
switch (node.layout) {
case "position":
{
extra.width = Math.max(extra.width, x + w);
extra.height = Math.max(extra.height, y + h);
extra.children.push(html);
break;
}
case "lr-tb":
case "rl-tb":
if (!extra.line || extra.attempt === 1) {
extra.line = createLine(node, []);
extra.children.push(extra.line);
extra.numberInLine = 0;
}
extra.numberInLine += 1;
extra.line.children.push(html);
if (extra.attempt === 0) {
extra.currentWidth += w;
extra.height = Math.max(extra.height, extra.prevHeight + h);
} else {
extra.currentWidth = w;
extra.prevHeight = extra.height;
extra.height += h;
extra.attempt = 0;
}
extra.width = Math.max(extra.width, extra.currentWidth);
break;
case "rl-row":
case "row":
{
extra.children.push(html);
extra.width += w;
extra.height = Math.max(extra.height, h);
const height = measureToString(extra.height);
for (const child of extra.children) {
child.attributes.style.height = height;
}
break;
}
case "table":
{
extra.width = MathClamp(w, extra.width, availableSpace.width);
extra.height += h;
extra.children.push(html);
break;
}
case "tb":
{
extra.width = MathClamp(w, extra.width, availableSpace.width);
extra.height += h;
extra.children.push(html);
break;
}
}
}
function getAvailableSpace(node) {
const availableSpace = node[$extra].availableSpace;
const marginV = node.margin ? node.margin.topInset + node.margin.bottomInset : 0;
const marginH = node.margin ? node.margin.leftInset + node.margin.rightInset : 0;
switch (node.layout) {
case "lr-tb":
case "rl-tb":
if (node[$extra].attempt === 0) {
return {
width: availableSpace.width - marginH - node[$extra].currentWidth,
height: availableSpace.height - marginV - node[$extra].prevHeight
};
}
return {
width: availableSpace.width - marginH,
height: availableSpace.height - marginV - node[$extra].height
};
case "rl-row":
case "row":
const width = Math.sumPrecise(node[$extra].columnWidths.slice(node[$extra].currentColumn));
return {
width,
height: availableSpace.height - marginH
};
case "table":
case "tb":
return {
width: availableSpace.width - marginH,
height: availableSpace.height - marginV - node[$extra].height
};
case "position":
default:
return availableSpace;
}
}
function getTransformedBBox(node) {
let w = node.w === "" ? NaN : node.w;
let h = node.h === "" ? NaN : node.h;
let [centerX, centerY] = [0, 0];
switch (node.anchorType || "") {
case "bottomCenter":
[centerX, centerY] = [w / 2, h];
break;
case "bottomLeft":
[centerX, centerY] = [0, h];
break;
case "bottomRight":
[centerX, centerY] = [w, h];
break;
case "middleCenter":
[centerX, centerY] = [w / 2, h / 2];
break;
case "middleLeft":
[centerX, centerY] = [0, h / 2];
break;
case "middleRight":
[centerX, centerY] = [w, h / 2];
break;
case "topCenter":
[centerX, centerY] = [w / 2, 0];
break;
case "topRight":
[centerX, centerY] = [w, 0];
break;
}
let x, y;
switch (node.rotate || 0) {
case 0:
[x, y] = [-centerX, -centerY];
break;
case 90:
[x, y] = [-centerY, centerX];
[w, h] = [h, -w];
break;
case 180:
[x, y] = [centerX, centerY];
[w, h] = [-w, -h];
break;
case 270:
[x, y] = [centerY, -centerX];
[w, h] = [-h, w];
break;
}
return [node.x + x + Math.min(0, w), node.y + y + Math.min(0, h), Math.abs(w), Math.abs(h)];
}
function checkDimensions(node, space) {
if (node[$getTemplateRoot]()[$extra].firstUnsplittable === null) {
return true;
}
if (node.w === 0 || node.h === 0) {
return true;
}
const ERROR = 2;
const parent = node[$getSubformParent]();
const attempt = parent[$extra]?.attempt || 0;
const [, y, w, h] = getTransformedBBox(node);
switch (parent.layout) {
case "lr-tb":
case "rl-tb":
if (attempt === 0) {
if (!node[$getTemplateRoot]()[$extra].noLayoutFailure) {
if (node.h !== "" && Math.round(h - space.height) > ERROR) {
return false;
}
if (node.w !== "") {
if (Math.round(w - space.width) <= ERROR) {
return true;
}
if (parent[$extra].numberInLine === 0) {
return space.height > ERROR;
}
return false;
}
return space.width > ERROR;
}
if (node.w !== "") {
return Math.round(w - space.width) <= ERROR;
}
return space.width > ERROR;
}
if (node[$getTemplateRoot]()[$extra].noLayoutFailure) {
return true;
}
if (node.h !== "" && Math.round(h - space.height) > ERROR) {
return false;
}
if (node.w === "" || Math.round(w - space.width) <= ERROR) {
return space.height > ERROR;
}
if (parent[$isThereMoreWidth]()) {
return false;
}
return space.height > ERROR;
case "table":
case "tb":
if (node[$getTemplateRoot]()[$extra].noLayoutFailure) {
return true;
}
if (node.h !== "" && !node[$isSplittable]()) {
return Math.round(h - space.height) <= ERROR;
}
if (node.w === "" || Math.round(w - space.width) <= ERROR) {
return space.height > ERROR;
}
if (parent[$isThereMoreWidth]()) {
return false;
}
return space.height > ERROR;
case "position":
if (node[$getTemplateRoot]()[$extra].noLayoutFailure) {
return true;
}
if (node.h === "" || Math.round(h + y - space.height) <= ERROR) {
return true;
}
const area = node[$getTemplateRoot]()[$extra].currentContentArea;
return h + y > area.h;
case "rl-row":
case "row":
if (node[$getTemplateRoot]()[$extra].noLayoutFailure) {
return true;
}
if (node.h !== "") {
return Math.round(h - space.height) <= ERROR;
}
return true;
default:
return true;
}
}
;// ./src/core/xfa/template.js
const TEMPLATE_NS_ID = NamespaceIds.template.id;
const SVG_NS = "http://www.w3.org/2000/svg";
const MAX_ATTEMPTS_FOR_LRTB_LAYOUT = 2;
const MAX_EMPTY_PAGES = 3;
const DEFAULT_TAB_INDEX = 5000;
const HEADING_PATTERN = /^H(\d+)$/;
const MIMES = new Set(["image/gif", "image/jpeg", "image/jpg", "image/pjpeg", "image/png", "image/apng", "image/x-png", "image/bmp", "image/x-ms-bmp", "image/tiff", "image/tif", "application/octet-stream"]);
const IMAGES_HEADERS = [[[0x42, 0x4d], "image/bmp"], [[0xff, 0xd8, 0xff], "image/jpeg"], [[0x49, 0x49, 0x2a, 0x00], "image/tiff"], [[0x4d, 0x4d, 0x00, 0x2a], "image/tiff"], [[0x47, 0x49, 0x46, 0x38, 0x39, 0x61], "image/gif"], [[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a], "image/png"]];
function getBorderDims(node) {
if (!node || !node.border) {
return {
w: 0,
h: 0
};
}
const borderExtra = node.border[$getExtra]();
if (!borderExtra) {
return {
w: 0,
h: 0
};
}
return {
w: borderExtra.widths[0] + borderExtra.widths[2] + borderExtra.insets[0] + borderExtra.insets[2],
h: borderExtra.widths[1] + borderExtra.widths[3] + borderExtra.insets[1] + borderExtra.insets[3]
};
}
function hasMargin(node) {
return node.margin && (node.margin.topInset || node.margin.rightInset || node.margin.bottomInset || node.margin.leftInset);
}
function _setValue(templateNode, value) {
if (!templateNode.value) {
const nodeValue = new Value({});
templateNode[$appendChild](nodeValue);
templateNode.value = nodeValue;
}
templateNode.value[$setValue](value);
}
function* getContainedChildren(node) {
for (const child of node[$getChildren]()) {
if (child instanceof SubformSet) {
yield* child[$getContainedChildren]();
continue;
}
yield child;
}
}
function isRequired(node) {
return node.validate?.nullTest === "error";
}
function setTabIndex(node) {
while (node) {
if (!node.traversal) {
node[$tabIndex] = node[$getParent]()[$tabIndex];
return;
}
if (node[$tabIndex]) {
return;
}
let next = null;
for (const child of node.traversal[$getChildren]()) {
if (child.operation === "next") {
next = child;
break;
}
}
if (!next || !next.ref) {
node[$tabIndex] = node[$getParent]()[$tabIndex];
return;
}
const root = node[$getTemplateRoot]();
node[$tabIndex] = ++root[$tabIndex];
const ref = root[$searchNode](next.ref, node);
if (!ref) {
return;
}
node = ref[0];
}
}
function applyAssist(obj, attributes) {
const assist = obj.assist;
if (assist) {
const assistTitle = assist[$toHTML]();
if (assistTitle) {
attributes.title = assistTitle;
}
const role = assist.role;
const match = role.match(HEADING_PATTERN);
if (match) {
const ariaRole = "heading";
const ariaLevel = match[1];
attributes.role = ariaRole;
attributes["aria-level"] = ariaLevel;
}
}
if (obj.layout === "table") {
attributes.role = "table";
} else if (obj.layout === "row") {
attributes.role = "row";
} else {
const parent = obj[$getParent]();
if (parent.layout === "row") {
attributes.role = parent.assist?.role === "TH" ? "columnheader" : "cell";
}
}
}
function ariaLabel(obj) {
if (!obj.assist) {
return null;
}
const assist = obj.assist;
if (assist.speak && assist.speak[$content] !== "") {
return assist.speak[$content];
}
if (assist.toolTip) {
return assist.toolTip[$content];
}
return null;
}
function valueToHtml(value) {
return HTMLResult.success({
name: "div",
attributes: {
class: ["xfaRich"],
style: Object.create(null)
},
children: [{
name: "span",
attributes: {
style: Object.create(null)
},
value
}]
});
}
function setFirstUnsplittable(node) {
const root = node[$getTemplateRoot]();
if (root[$extra].firstUnsplittable === null) {
root[$extra].firstUnsplittable = node;
root[$extra].noLayoutFailure = true;
}
}
function unsetFirstUnsplittable(node) {
const root = node[$getTemplateRoot]();
if (root[$extra].firstUnsplittable === node) {
root[$extra].noLayoutFailure = false;
}
}
function handleBreak(node) {
if (node[$extra]) {
return false;
}
node[$extra] = Object.create(null);
if (node.targetType === "auto") {
return false;
}
const root = node[$getTemplateRoot]();
let target = null;
if (node.target) {
target = root[$searchNode](node.target, node[$getParent]());
if (!target) {
return false;
}
target = target[0];
}
const {
currentPageArea,
currentContentArea
} = root[$extra];
if (node.targetType === "pageArea") {
if (!(target instanceof PageArea)) {
target = null;
}
if (node.startNew) {
node[$extra].target = target || currentPageArea;
return true;
} else if (target && target !== currentPageArea) {
node[$extra].target = target;
return true;
}
return false;
}
if (!(target instanceof ContentArea)) {
target = null;
}
const pageArea = target && target[$getParent]();
let index;
let nextPageArea = pageArea;
if (node.startNew) {
if (target) {
const contentAreas = pageArea.contentArea.children;
const indexForCurrent = contentAreas.indexOf(currentContentArea);
const indexForTarget = contentAreas.indexOf(target);
if (indexForCurrent !== -1 && indexForCurrent < indexForTarget) {
nextPageArea = null;
}
index = indexForTarget - 1;
} else {
index = currentPageArea.contentArea.children.indexOf(currentContentArea);
}
} else if (target && target !== currentContentArea) {
const contentAreas = pageArea.contentArea.children;
index = contentAreas.indexOf(target) - 1;
nextPageArea = pageArea === currentPageArea ? null : pageArea;
} else {
return false;
}
node[$extra].target = nextPageArea;
node[$extra].index = index;
return true;
}
function handleOverflow(node, extraNode, space) {
const root = node[$getTemplateRoot]();
const saved = root[$extra].noLayoutFailure;
const savedMethod = extraNode[$getSubformParent];
extraNode[$getSubformParent] = () => node;
root[$extra].noLayoutFailure = true;
const res = extraNode[$toHTML](space);
node[$addHTML](res.html, res.bbox);
root[$extra].noLayoutFailure = saved;
extraNode[$getSubformParent] = savedMethod;
}
class AppearanceFilter extends StringObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "appearanceFilter");
this.id = attributes.id || "";
this.type = getStringOption(attributes.type, ["optional", "required"]);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
}
class Arc extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "arc", true);
this.circular = getInteger({
data: attributes.circular,
defaultValue: 0,
validate: x => x === 1
});
this.hand = getStringOption(attributes.hand, ["even", "left", "right"]);
this.id = attributes.id || "";
this.startAngle = getFloat({
data: attributes.startAngle,
defaultValue: 0,
validate: x => true
});
this.sweepAngle = getFloat({
data: attributes.sweepAngle,
defaultValue: 360,
validate: x => true
});
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.edge = null;
this.fill = null;
}
[$toHTML]() {
const edge = this.edge || new Edge({});
const edgeStyle = edge[$toStyle]();
const style = Object.create(null);
if (this.fill?.presence === "visible") {
Object.assign(style, this.fill[$toStyle]());
} else {
style.fill = "transparent";
}
style.strokeWidth = measureToString(edge.presence === "visible" ? edge.thickness : 0);
style.stroke = edgeStyle.color;
let arc;
const attributes = {
xmlns: SVG_NS,
style: {
width: "100%",
height: "100%",
overflow: "visible"
}
};
if (this.sweepAngle === 360) {
arc = {
name: "ellipse",
attributes: {
xmlns: SVG_NS,
cx: "50%",
cy: "50%",
rx: "50%",
ry: "50%",
style
}
};
} else {
const startAngle = this.startAngle * Math.PI / 180;
const sweepAngle = this.sweepAngle * Math.PI / 180;
const largeArc = this.sweepAngle > 180 ? 1 : 0;
const [x1, y1, x2, y2] = [50 * (1 + Math.cos(startAngle)), 50 * (1 - Math.sin(startAngle)), 50 * (1 + Math.cos(startAngle + sweepAngle)), 50 * (1 - Math.sin(startAngle + sweepAngle))];
arc = {
name: "path",
attributes: {
xmlns: SVG_NS,
d: `M ${x1} ${y1} A 50 50 0 ${largeArc} 0 ${x2} ${y2}`,
vectorEffect: "non-scaling-stroke",
style
}
};
Object.assign(attributes, {
viewBox: "0 0 100 100",
preserveAspectRatio: "none"
});
}
const svg = {
name: "svg",
children: [arc],
attributes
};
const parent = this[$getParent]()[$getParent]();
if (hasMargin(parent)) {
return HTMLResult.success({
name: "div",
attributes: {
style: {
display: "inline",
width: "100%",
height: "100%"
}
},
children: [svg]
});
}
svg.attributes.style.position = "absolute";
return HTMLResult.success(svg);
}
}
class Area extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "area", true);
this.colSpan = getInteger({
data: attributes.colSpan,
defaultValue: 1,
validate: n => n >= 1 || n === -1
});
this.id = attributes.id || "";
this.name = attributes.name || "";
this.relevant = getRelevant(attributes.relevant);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.x = getMeasurement(attributes.x, "0pt");
this.y = getMeasurement(attributes.y, "0pt");
this.desc = null;
this.extras = null;
this.area = new XFAObjectArray();
this.draw = new XFAObjectArray();
this.exObject = new XFAObjectArray();
this.exclGroup = new XFAObjectArray();
this.field = new XFAObjectArray();
this.subform = new XFAObjectArray();
this.subformSet = new XFAObjectArray();
}
*[$getContainedChildren]() {
yield* getContainedChildren(this);
}
[$isTransparent]() {
return true;
}
[$isBindable]() {
return true;
}
[$addHTML](html, bbox) {
const [x, y, w, h] = bbox;
this[$extra].width = Math.max(this[$extra].width, x + w);
this[$extra].height = Math.max(this[$extra].height, y + h);
this[$extra].children.push(html);
}
[$getAvailableSpace]() {
return this[$extra].availableSpace;
}
[$toHTML](availableSpace) {
const style = toStyle(this, "position");
const attributes = {
style,
id: this[$uid],
class: ["xfaArea"]
};
if (isPrintOnly(this)) {
attributes.class.push("xfaPrintOnly");
}
if (this.name) {
attributes.xfaName = this.name;
}
const children = [];
this[$extra] = {
children,
width: 0,
height: 0,
availableSpace
};
const result = this[$childrenToHTML]({
filter: new Set(["area", "draw", "field", "exclGroup", "subform", "subformSet"]),
include: true
});
if (!result.success) {
if (result.isBreak()) {
return result;
}
delete this[$extra];
return HTMLResult.FAILURE;
}
style.width = measureToString(this[$extra].width);
style.height = measureToString(this[$extra].height);
const html = {
name: "div",
attributes,
children
};
const bbox = [this.x, this.y, this[$extra].width, this[$extra].height];
delete this[$extra];
return HTMLResult.success(html, bbox);
}
}
class Assist extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "assist", true);
this.id = attributes.id || "";
this.role = attributes.role || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.speak = null;
this.toolTip = null;
}
[$toHTML]() {
return this.toolTip?.[$content] || null;
}
}
class Barcode extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "barcode", true);
this.charEncoding = getKeyword({
data: attributes.charEncoding ? attributes.charEncoding.toLowerCase() : "",
defaultValue: "",
validate: k => ["utf-8", "big-five", "fontspecific", "gbk", "gb-18030", "gb-2312", "ksc-5601", "none", "shift-jis", "ucs-2", "utf-16"].includes(k) || k.match(/iso-8859-\d{2}/)
});
this.checksum = getStringOption(attributes.checksum, ["none", "1mod10", "1mod10_1mod11", "2mod10", "auto"]);
this.dataColumnCount = getInteger({
data: attributes.dataColumnCount,
defaultValue: -1,
validate: x => x >= 0
});
this.dataLength = getInteger({
data: attributes.dataLength,
defaultValue: -1,
validate: x => x >= 0
});
this.dataPrep = getStringOption(attributes.dataPrep, ["none", "flateCompress"]);
this.dataRowCount = getInteger({
data: attributes.dataRowCount,
defaultValue: -1,
validate: x => x >= 0
});
this.endChar = attributes.endChar || "";
this.errorCorrectionLevel = getInteger({
data: attributes.errorCorrectionLevel,
defaultValue: -1,
validate: x => x >= 0 && x <= 8
});
this.id = attributes.id || "";
this.moduleHeight = getMeasurement(attributes.moduleHeight, "5mm");
this.moduleWidth = getMeasurement(attributes.moduleWidth, "0.25mm");
this.printCheckDigit = getInteger({
data: attributes.printCheckDigit,
defaultValue: 0,
validate: x => x === 1
});
this.rowColumnRatio = getRatio(attributes.rowColumnRatio);
this.startChar = attributes.startChar || "";
this.textLocation = getStringOption(attributes.textLocation, ["below", "above", "aboveEmbedded", "belowEmbedded", "none"]);
this.truncate = getInteger({
data: attributes.truncate,
defaultValue: 0,
validate: x => x === 1
});
this.type = getStringOption(attributes.type ? attributes.type.toLowerCase() : "", ["aztec", "codabar", "code2of5industrial", "code2of5interleaved", "code2of5matrix", "code2of5standard", "code3of9", "code3of9extended", "code11", "code49", "code93", "code128", "code128a", "code128b", "code128c", "code128sscc", "datamatrix", "ean8", "ean8add2", "ean8add5", "ean13", "ean13add2", "ean13add5", "ean13pwcd", "fim", "logmars", "maxicode", "msi", "pdf417", "pdf417macro", "plessey", "postauscust2", "postauscust3", "postausreplypaid", "postausstandard", "postukrm4scc", "postusdpbc", "postusimb", "postusstandard", "postus5zip", "qrcode", "rfid", "rss14", "rss14expanded", "rss14limited", "rss14stacked", "rss14stackedomni", "rss14truncated", "telepen", "ucc128", "ucc128random", "ucc128sscc", "upca", "upcaadd2", "upcaadd5", "upcapwcd", "upce", "upceadd2", "upceadd5", "upcean2", "upcean5", "upsmaxicode"]);
this.upsMode = getStringOption(attributes.upsMode, ["usCarrier", "internationalCarrier", "secureSymbol", "standardSymbol"]);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.wideNarrowRatio = getRatio(attributes.wideNarrowRatio);
this.encrypt = null;
this.extras = null;
}
}
class Bind extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "bind", true);
this.match = getStringOption(attributes.match, ["once", "dataRef", "global", "none"]);
this.ref = attributes.ref || "";
this.picture = null;
}
}
class BindItems extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "bindItems");
this.connection = attributes.connection || "";
this.labelRef = attributes.labelRef || "";
this.ref = attributes.ref || "";
this.valueRef = attributes.valueRef || "";
}
}
class Bookend extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "bookend");
this.id = attributes.id || "";
this.leader = attributes.leader || "";
this.trailer = attributes.trailer || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
}
class BooleanElement extends Option01 {
constructor(attributes) {
super(TEMPLATE_NS_ID, "boolean");
this.id = attributes.id || "";
this.name = attributes.name || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
[$toHTML](availableSpace) {
return valueToHtml(this[$content] === 1 ? "1" : "0");
}
}
class Border extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "border", true);
this.break = getStringOption(attributes.break, ["close", "open"]);
this.hand = getStringOption(attributes.hand, ["even", "left", "right"]);
this.id = attributes.id || "";
this.presence = getStringOption(attributes.presence, ["visible", "hidden", "inactive", "invisible"]);
this.relevant = getRelevant(attributes.relevant);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.corner = new XFAObjectArray(4);
this.edge = new XFAObjectArray(4);
this.extras = null;
this.fill = null;
this.margin = null;
}
[$getExtra]() {
if (!this[$extra]) {
const edges = this.edge.children.slice();
if (edges.length < 4) {
const defaultEdge = edges.at(-1) || new Edge({});
for (let i = edges.length; i < 4; i++) {
edges.push(defaultEdge);
}
}
const widths = edges.map(edge => edge.thickness);
const insets = [0, 0, 0, 0];
if (this.margin) {
insets[0] = this.margin.topInset;
insets[1] = this.margin.rightInset;
insets[2] = this.margin.bottomInset;
insets[3] = this.margin.leftInset;
}
this[$extra] = {
widths,
insets,
edges
};
}
return this[$extra];
}
[$toStyle]() {
const {
edges
} = this[$getExtra]();
const edgeStyles = edges.map(node => {
const style = node[$toStyle]();
style.color ||= "#000000";
return style;
});
const style = Object.create(null);
if (this.margin) {
Object.assign(style, this.margin[$toStyle]());
}
if (this.fill?.presence === "visible") {
Object.assign(style, this.fill[$toStyle]());
}
if (this.corner.children.some(node => node.radius !== 0)) {
const cornerStyles = this.corner.children.map(node => node[$toStyle]());
if (cornerStyles.length === 2 || cornerStyles.length === 3) {
const last = cornerStyles.at(-1);
for (let i = cornerStyles.length; i < 4; i++) {
cornerStyles.push(last);
}
}
style.borderRadius = cornerStyles.map(s => s.radius).join(" ");
}
switch (this.presence) {
case "invisible":
case "hidden":
style.borderStyle = "";
break;
case "inactive":
style.borderStyle = "none";
break;
default:
style.borderStyle = edgeStyles.map(s => s.style).join(" ");
break;
}
style.borderWidth = edgeStyles.map(s => s.width).join(" ");
style.borderColor = edgeStyles.map(s => s.color).join(" ");
return style;
}
}
class Break extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "break", true);
this.after = getStringOption(attributes.after, ["auto", "contentArea", "pageArea", "pageEven", "pageOdd"]);
this.afterTarget = attributes.afterTarget || "";
this.before = getStringOption(attributes.before, ["auto", "contentArea", "pageArea", "pageEven", "pageOdd"]);
this.beforeTarget = attributes.beforeTarget || "";
this.bookendLeader = attributes.bookendLeader || "";
this.bookendTrailer = attributes.bookendTrailer || "";
this.id = attributes.id || "";
this.overflowLeader = attributes.overflowLeader || "";
this.overflowTarget = attributes.overflowTarget || "";
this.overflowTrailer = attributes.overflowTrailer || "";
this.startNew = getInteger({
data: attributes.startNew,
defaultValue: 0,
validate: x => x === 1
});
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.extras = null;
}
}
class BreakAfter extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "breakAfter", true);
this.id = attributes.id || "";
this.leader = attributes.leader || "";
this.startNew = getInteger({
data: attributes.startNew,
defaultValue: 0,
validate: x => x === 1
});
this.target = attributes.target || "";
this.targetType = getStringOption(attributes.targetType, ["auto", "contentArea", "pageArea"]);
this.trailer = attributes.trailer || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.script = null;
}
}
class BreakBefore extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "breakBefore", true);
this.id = attributes.id || "";
this.leader = attributes.leader || "";
this.startNew = getInteger({
data: attributes.startNew,
defaultValue: 0,
validate: x => x === 1
});
this.target = attributes.target || "";
this.targetType = getStringOption(attributes.targetType, ["auto", "contentArea", "pageArea"]);
this.trailer = attributes.trailer || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.script = null;
}
[$toHTML](availableSpace) {
this[$extra] = {};
return HTMLResult.FAILURE;
}
}
class Button extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "button", true);
this.highlight = getStringOption(attributes.highlight, ["inverted", "none", "outline", "push"]);
this.id = attributes.id || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.extras = null;
}
[$toHTML](availableSpace) {
const parent = this[$getParent]();
const grandpa = parent[$getParent]();
const htmlButton = {
name: "button",
attributes: {
id: this[$uid],
class: ["xfaButton"],
style: {}
},
children: []
};
for (const event of grandpa.event.children) {
if (event.activity !== "click" || !event.script) {
continue;
}
const jsURL = recoverJsURL(event.script[$content]);
if (!jsURL) {
continue;
}
const href = fixURL(jsURL.url);
if (!href) {
continue;
}
htmlButton.children.push({
name: "a",
attributes: {
id: "link" + this[$uid],
href,
newWindow: jsURL.newWindow,
class: ["xfaLink"],
style: {}
},
children: []
});
}
return HTMLResult.success(htmlButton);
}
}
class Calculate extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "calculate", true);
this.id = attributes.id || "";
this.override = getStringOption(attributes.override, ["disabled", "error", "ignore", "warning"]);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.extras = null;
this.message = null;
this.script = null;
}
}
class Caption extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "caption", true);
this.id = attributes.id || "";
this.placement = getStringOption(attributes.placement, ["left", "bottom", "inline", "right", "top"]);
this.presence = getStringOption(attributes.presence, ["visible", "hidden", "inactive", "invisible"]);
this.reserve = Math.ceil(getMeasurement(attributes.reserve));
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.extras = null;
this.font = null;
this.margin = null;
this.para = null;
this.value = null;
}
[$setValue](value) {
_setValue(this, value);
}
[$getExtra](availableSpace) {
if (!this[$extra]) {
let {
width,
height
} = availableSpace;
switch (this.placement) {
case "left":
case "right":
case "inline":
width = this.reserve <= 0 ? width : this.reserve;
break;
case "top":
case "bottom":
height = this.reserve <= 0 ? height : this.reserve;
break;
}
this[$extra] = layoutNode(this, {
width,
height
});
}
return this[$extra];
}
[$toHTML](availableSpace) {
if (!this.value) {
return HTMLResult.EMPTY;
}
this[$pushPara]();
const value = this.value[$toHTML](availableSpace).html;
if (!value) {
this[$popPara]();
return HTMLResult.EMPTY;
}
const savedReserve = this.reserve;
if (this.reserve <= 0) {
const {
w,
h
} = this[$getExtra](availableSpace);
switch (this.placement) {
case "left":
case "right":
case "inline":
this.reserve = w;
break;
case "top":
case "bottom":
this.reserve = h;
break;
}
}
const children = [];
if (typeof value === "string") {
children.push({
name: "#text",
value
});
} else {
children.push(value);
}
const style = toStyle(this, "font", "margin", "visibility");
switch (this.placement) {
case "left":
case "right":
if (this.reserve > 0) {
style.width = measureToString(this.reserve);
}
break;
case "top":
case "bottom":
if (this.reserve > 0) {
style.height = measureToString(this.reserve);
}
break;
}
setPara(this, null, value);
this[$popPara]();
this.reserve = savedReserve;
return HTMLResult.success({
name: "div",
attributes: {
style,
class: ["xfaCaption"]
},
children
});
}
}
class Certificate extends StringObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "certificate");
this.id = attributes.id || "";
this.name = attributes.name || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
}
class Certificates extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "certificates", true);
this.credentialServerPolicy = getStringOption(attributes.credentialServerPolicy, ["optional", "required"]);
this.id = attributes.id || "";
this.url = attributes.url || "";
this.urlPolicy = attributes.urlPolicy || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.encryption = null;
this.issuers = null;
this.keyUsage = null;
this.oids = null;
this.signing = null;
this.subjectDNs = null;
}
}
class CheckButton extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "checkButton", true);
this.id = attributes.id || "";
this.mark = getStringOption(attributes.mark, ["default", "check", "circle", "cross", "diamond", "square", "star"]);
this.shape = getStringOption(attributes.shape, ["square", "round"]);
this.size = getMeasurement(attributes.size, "10pt");
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.border = null;
this.extras = null;
this.margin = null;
}
[$toHTML](availableSpace) {
const style = toStyle("margin");
const size = measureToString(this.size);
style.width = style.height = size;
let type;
let className;
let groupId;
const field = this[$getParent]()[$getParent]();
const items = field.items.children.length && field.items.children[0][$toHTML]().html || [];
const exportedValue = {
on: (items[0] !== undefined ? items[0] : "on").toString(),
off: (items[1] !== undefined ? items[1] : "off").toString()
};
const value = field.value?.[$text]() || "off";
const checked = value === exportedValue.on || undefined;
const container = field[$getSubformParent]();
const fieldId = field[$uid];
let dataId;
if (container instanceof ExclGroup) {
groupId = container[$uid];
type = "radio";
className = "xfaRadio";
dataId = container[$data]?.[$uid] || container[$uid];
} else {
type = "checkbox";
className = "xfaCheckbox";
dataId = field[$data]?.[$uid] || field[$uid];
}
const input = {
name: "input",
attributes: {
class: [className],
style,
fieldId,
dataId,
type,
checked,
xfaOn: exportedValue.on,
xfaOff: exportedValue.off,
"aria-label": ariaLabel(field),
"aria-required": false
}
};
if (groupId) {
input.attributes.name = groupId;
}
if (isRequired(field)) {
input.attributes["aria-required"] = true;
input.attributes.required = true;
}
return HTMLResult.success({
name: "label",
attributes: {
class: ["xfaLabel"]
},
children: [input]
});
}
}
class ChoiceList extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "choiceList", true);
this.commitOn = getStringOption(attributes.commitOn, ["select", "exit"]);
this.id = attributes.id || "";
this.open = getStringOption(attributes.open, ["userControl", "always", "multiSelect", "onEntry"]);
this.textEntry = getInteger({
data: attributes.textEntry,
defaultValue: 0,
validate: x => x === 1
});
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.border = null;
this.extras = null;
this.margin = null;
}
[$toHTML](availableSpace) {
const style = toStyle(this, "border", "margin");
const ui = this[$getParent]();
const field = ui[$getParent]();
const fontSize = field.font?.size || 10;
const optionStyle = {
fontSize: `calc(${fontSize}px * var(--total-scale-factor))`
};
const children = [];
if (field.items.children.length > 0) {
const items = field.items;
let displayedIndex = 0;
let saveIndex = 0;
if (items.children.length === 2) {
displayedIndex = items.children[0].save;
saveIndex = 1 - displayedIndex;
}
const displayed = items.children[displayedIndex][$toHTML]().html;
const values = items.children[saveIndex][$toHTML]().html;
let selected = false;
const value = field.value?.[$text]() || "";
for (let i = 0, ii = displayed.length; i < ii; i++) {
const option = {
name: "option",
attributes: {
value: values[i] || displayed[i],
style: optionStyle
},
value: displayed[i]
};
if (values[i] === value) {
option.attributes.selected = selected = true;
}
children.push(option);
}
if (!selected) {
children.splice(0, 0, {
name: "option",
attributes: {
hidden: true,
selected: true
},
value: " "
});
}
}
const selectAttributes = {
class: ["xfaSelect"],
fieldId: field[$uid],
dataId: field[$data]?.[$uid] || field[$uid],
style,
"aria-label": ariaLabel(field),
"aria-required": false
};
if (isRequired(field)) {
selectAttributes["aria-required"] = true;
selectAttributes.required = true;
}
if (this.open === "multiSelect") {
selectAttributes.multiple = true;
}
return HTMLResult.success({
name: "label",
attributes: {
class: ["xfaLabel"]
},
children: [{
name: "select",
children,
attributes: selectAttributes
}]
});
}
}
class Color extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "color", true);
this.cSpace = getStringOption(attributes.cSpace, ["SRGB"]);
this.id = attributes.id || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.value = attributes.value ? getColor(attributes.value) : "";
this.extras = null;
}
[$hasSettableValue]() {
return false;
}
[$toStyle]() {
return this.value ? Util.makeHexColor(this.value.r, this.value.g, this.value.b) : null;
}
}
class Comb extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "comb");
this.id = attributes.id || "";
this.numberOfCells = getInteger({
data: attributes.numberOfCells,
defaultValue: 0,
validate: x => x >= 0
});
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
}
class Connect extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "connect", true);
this.connection = attributes.connection || "";
this.id = attributes.id || "";
this.ref = attributes.ref || "";
this.usage = getStringOption(attributes.usage, ["exportAndImport", "exportOnly", "importOnly"]);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.picture = null;
}
}
class ContentArea extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "contentArea", true);
this.h = getMeasurement(attributes.h);
this.id = attributes.id || "";
this.name = attributes.name || "";
this.relevant = getRelevant(attributes.relevant);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.w = getMeasurement(attributes.w);
this.x = getMeasurement(attributes.x, "0pt");
this.y = getMeasurement(attributes.y, "0pt");
this.desc = null;
this.extras = null;
}
[$toHTML](availableSpace) {
const left = measureToString(this.x);
const top = measureToString(this.y);
const style = {
left,
top,
width: measureToString(this.w),
height: measureToString(this.h)
};
const classNames = ["xfaContentarea"];
if (isPrintOnly(this)) {
classNames.push("xfaPrintOnly");
}
return HTMLResult.success({
name: "div",
children: [],
attributes: {
style,
class: classNames,
id: this[$uid]
}
});
}
}
class Corner extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "corner", true);
this.id = attributes.id || "";
this.inverted = getInteger({
data: attributes.inverted,
defaultValue: 0,
validate: x => x === 1
});
this.join = getStringOption(attributes.join, ["square", "round"]);
this.presence = getStringOption(attributes.presence, ["visible", "hidden", "inactive", "invisible"]);
this.radius = getMeasurement(attributes.radius);
this.stroke = getStringOption(attributes.stroke, ["solid", "dashDot", "dashDotDot", "dashed", "dotted", "embossed", "etched", "lowered", "raised"]);
this.thickness = getMeasurement(attributes.thickness, "0.5pt");
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.color = null;
this.extras = null;
}
[$toStyle]() {
const style = toStyle(this, "visibility");
style.radius = measureToString(this.join === "square" ? 0 : this.radius);
return style;
}
}
class DateElement extends ContentObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "date");
this.id = attributes.id || "";
this.name = attributes.name || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
[$finalize]() {
const date = this[$content].trim();
this[$content] = date ? new Date(date) : null;
}
[$toHTML](availableSpace) {
return valueToHtml(this[$content] ? this[$content].toString() : "");
}
}
class DateTime extends ContentObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "dateTime");
this.id = attributes.id || "";
this.name = attributes.name || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
[$finalize]() {
const date = this[$content].trim();
this[$content] = date ? new Date(date) : null;
}
[$toHTML](availableSpace) {
return valueToHtml(this[$content] ? this[$content].toString() : "");
}
}
class DateTimeEdit extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "dateTimeEdit", true);
this.hScrollPolicy = getStringOption(attributes.hScrollPolicy, ["auto", "off", "on"]);
this.id = attributes.id || "";
this.picker = getStringOption(attributes.picker, ["host", "none"]);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.border = null;
this.comb = null;
this.extras = null;
this.margin = null;
}
[$toHTML](availableSpace) {
const style = toStyle(this, "border", "font", "margin");
const field = this[$getParent]()[$getParent]();
const html = {
name: "input",
attributes: {
type: "text",
fieldId: field[$uid],
dataId: field[$data]?.[$uid] || field[$uid],
class: ["xfaTextfield"],
style,
"aria-label": ariaLabel(field),
"aria-required": false
}
};
if (isRequired(field)) {
html.attributes["aria-required"] = true;
html.attributes.required = true;
}
return HTMLResult.success({
name: "label",
attributes: {
class: ["xfaLabel"]
},
children: [html]
});
}
}
class Decimal extends ContentObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "decimal");
this.fracDigits = getInteger({
data: attributes.fracDigits,
defaultValue: 2,
validate: x => true
});
this.id = attributes.id || "";
this.leadDigits = getInteger({
data: attributes.leadDigits,
defaultValue: -1,
validate: x => true
});
this.name = attributes.name || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
[$finalize]() {
const number = parseFloat(this[$content].trim());
this[$content] = isNaN(number) ? null : number;
}
[$toHTML](availableSpace) {
return valueToHtml(this[$content] !== null ? this[$content].toString() : "");
}
}
class DefaultUi extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "defaultUi", true);
this.id = attributes.id || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.extras = null;
}
}
class Desc extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "desc", true);
this.id = attributes.id || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.boolean = new XFAObjectArray();
this.date = new XFAObjectArray();
this.dateTime = new XFAObjectArray();
this.decimal = new XFAObjectArray();
this.exData = new XFAObjectArray();
this.float = new XFAObjectArray();
this.image = new XFAObjectArray();
this.integer = new XFAObjectArray();
this.text = new XFAObjectArray();
this.time = new XFAObjectArray();
}
}
class DigestMethod extends OptionObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "digestMethod", ["", "SHA1", "SHA256", "SHA512", "RIPEMD160"]);
this.id = attributes.id || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
}
class DigestMethods extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "digestMethods", true);
this.id = attributes.id || "";
this.type = getStringOption(attributes.type, ["optional", "required"]);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.digestMethod = new XFAObjectArray();
}
}
class Draw extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "draw", true);
this.anchorType = getStringOption(attributes.anchorType, ["topLeft", "bottomCenter", "bottomLeft", "bottomRight", "middleCenter", "middleLeft", "middleRight", "topCenter", "topRight"]);
this.colSpan = getInteger({
data: attributes.colSpan,
defaultValue: 1,
validate: n => n >= 1 || n === -1
});
this.h = attributes.h ? getMeasurement(attributes.h) : "";
this.hAlign = getStringOption(attributes.hAlign, ["left", "center", "justify", "justifyAll", "radix", "right"]);
this.id = attributes.id || "";
this.locale = attributes.locale || "";
this.maxH = getMeasurement(attributes.maxH, "0pt");
this.maxW = getMeasurement(attributes.maxW, "0pt");
this.minH = getMeasurement(attributes.minH, "0pt");
this.minW = getMeasurement(attributes.minW, "0pt");
this.name = attributes.name || "";
this.presence = getStringOption(attributes.presence, ["visible", "hidden", "inactive", "invisible"]);
this.relevant = getRelevant(attributes.relevant);
this.rotate = getInteger({
data: attributes.rotate,
defaultValue: 0,
validate: x => x % 90 === 0
});
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.w = attributes.w ? getMeasurement(attributes.w) : "";
this.x = getMeasurement(attributes.x, "0pt");
this.y = getMeasurement(attributes.y, "0pt");
this.assist = null;
this.border = null;
this.caption = null;
this.desc = null;
this.extras = null;
this.font = null;
this.keep = null;
this.margin = null;
this.para = null;
this.traversal = null;
this.ui = null;
this.value = null;
this.setProperty = new XFAObjectArray();
}
[$setValue](value) {
_setValue(this, value);
}
[$toHTML](availableSpace) {
setTabIndex(this);
if (this.presence === "hidden" || this.presence === "inactive") {
return HTMLResult.EMPTY;
}
fixDimensions(this);
this[$pushPara]();
const savedW = this.w;
const savedH = this.h;
const {
w,
h,
isBroken
} = layoutNode(this, availableSpace);
if (w && this.w === "") {
if (isBroken && this[$getSubformParent]()[$isThereMoreWidth]()) {
this[$popPara]();
return HTMLResult.FAILURE;
}
this.w = w;
}
if (h && this.h === "") {
this.h = h;
}
setFirstUnsplittable(this);
if (!checkDimensions(this, availableSpace)) {
this.w = savedW;
this.h = savedH;
this[$popPara]();
return HTMLResult.FAILURE;
}
unsetFirstUnsplittable(this);
const style = toStyle(this, "font", "hAlign", "dimensions", "position", "presence", "rotate", "anchorType", "border", "margin");
setMinMaxDimensions(this, style);
if (style.margin) {
style.padding = style.margin;
delete style.margin;
}
const classNames = ["xfaDraw"];
if (this.font) {
classNames.push("xfaFont");
}
if (isPrintOnly(this)) {
classNames.push("xfaPrintOnly");
}
const attributes = {
style,
id: this[$uid],
class: classNames
};
if (this.name) {
attributes.xfaName = this.name;
}
const html = {
name: "div",
attributes,
children: []
};
applyAssist(this, attributes);
const bbox = computeBbox(this, html, availableSpace);
const value = this.value ? this.value[$toHTML](availableSpace).html : null;
if (value === null) {
this.w = savedW;
this.h = savedH;
this[$popPara]();
return HTMLResult.success(createWrapper(this, html), bbox);
}
html.children.push(value);
setPara(this, style, value);
this.w = savedW;
this.h = savedH;
this[$popPara]();
return HTMLResult.success(createWrapper(this, html), bbox);
}
}
class Edge extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "edge", true);
this.cap = getStringOption(attributes.cap, ["square", "butt", "round"]);
this.id = attributes.id || "";
this.presence = getStringOption(attributes.presence, ["visible", "hidden", "inactive", "invisible"]);
this.stroke = getStringOption(attributes.stroke, ["solid", "dashDot", "dashDotDot", "dashed", "dotted", "embossed", "etched", "lowered", "raised"]);
this.thickness = getMeasurement(attributes.thickness, "0.5pt");
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.color = null;
this.extras = null;
}
[$toStyle]() {
const style = toStyle(this, "visibility");
Object.assign(style, {
linecap: this.cap,
width: measureToString(this.thickness),
color: this.color ? this.color[$toStyle]() : "#000000",
style: ""
});
if (this.presence !== "visible") {
style.style = "none";
} else {
switch (this.stroke) {
case "solid":
style.style = "solid";
break;
case "dashDot":
style.style = "dashed";
break;
case "dashDotDot":
style.style = "dashed";
break;
case "dashed":
style.style = "dashed";
break;
case "dotted":
style.style = "dotted";
break;
case "embossed":
style.style = "ridge";
break;
case "etched":
style.style = "groove";
break;
case "lowered":
style.style = "inset";
break;
case "raised":
style.style = "outset";
break;
}
}
return style;
}
}
class Encoding extends OptionObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "encoding", ["adbe.x509.rsa_sha1", "adbe.pkcs7.detached", "adbe.pkcs7.sha1"]);
this.id = attributes.id || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
}
class Encodings extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "encodings", true);
this.id = attributes.id || "";
this.type = getStringOption(attributes.type, ["optional", "required"]);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.encoding = new XFAObjectArray();
}
}
class Encrypt extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "encrypt", true);
this.id = attributes.id || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.certificate = null;
}
}
class EncryptData extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "encryptData", true);
this.id = attributes.id || "";
this.operation = getStringOption(attributes.operation, ["encrypt", "decrypt"]);
this.target = attributes.target || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.filter = null;
this.manifest = null;
}
}
class Encryption extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "encryption", true);
this.id = attributes.id || "";
this.type = getStringOption(attributes.type, ["optional", "required"]);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.certificate = new XFAObjectArray();
}
}
class EncryptionMethod extends OptionObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "encryptionMethod", ["", "AES256-CBC", "TRIPLEDES-CBC", "AES128-CBC", "AES192-CBC"]);
this.id = attributes.id || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
}
class EncryptionMethods extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "encryptionMethods", true);
this.id = attributes.id || "";
this.type = getStringOption(attributes.type, ["optional", "required"]);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.encryptionMethod = new XFAObjectArray();
}
}
class Event extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "event", true);
this.activity = getStringOption(attributes.activity, ["click", "change", "docClose", "docReady", "enter", "exit", "full", "indexChange", "initialize", "mouseDown", "mouseEnter", "mouseExit", "mouseUp", "postExecute", "postOpen", "postPrint", "postSave", "postSign", "postSubmit", "preExecute", "preOpen", "prePrint", "preSave", "preSign", "preSubmit", "ready", "validationState"]);
this.id = attributes.id || "";
this.listen = getStringOption(attributes.listen, ["refOnly", "refAndDescendents"]);
this.name = attributes.name || "";
this.ref = attributes.ref || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.extras = null;
this.encryptData = null;
this.execute = null;
this.script = null;
this.signData = null;
this.submit = null;
}
}
class ExData extends ContentObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "exData");
this.contentType = attributes.contentType || "";
this.href = attributes.href || "";
this.id = attributes.id || "";
this.maxLength = getInteger({
data: attributes.maxLength,
defaultValue: -1,
validate: x => x >= -1
});
this.name = attributes.name || "";
this.rid = attributes.rid || "";
this.transferEncoding = getStringOption(attributes.transferEncoding, ["none", "base64", "package"]);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
[$isCDATAXml]() {
return this.contentType === "text/html";
}
[$onChild](child) {
if (this.contentType === "text/html" && child[$namespaceId] === NamespaceIds.xhtml.id) {
this[$content] = child;
return true;
}
if (this.contentType === "text/xml") {
this[$content] = child;
return true;
}
return false;
}
[$toHTML](availableSpace) {
if (this.contentType !== "text/html" || !this[$content]) {
return HTMLResult.EMPTY;
}
return this[$content][$toHTML](availableSpace);
}
}
class ExObject extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "exObject", true);
this.archive = attributes.archive || "";
this.classId = attributes.classId || "";
this.codeBase = attributes.codeBase || "";
this.codeType = attributes.codeType || "";
this.id = attributes.id || "";
this.name = attributes.name || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.extras = null;
this.boolean = new XFAObjectArray();
this.date = new XFAObjectArray();
this.dateTime = new XFAObjectArray();
this.decimal = new XFAObjectArray();
this.exData = new XFAObjectArray();
this.exObject = new XFAObjectArray();
this.float = new XFAObjectArray();
this.image = new XFAObjectArray();
this.integer = new XFAObjectArray();
this.text = new XFAObjectArray();
this.time = new XFAObjectArray();
}
}
class ExclGroup extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "exclGroup", true);
this.access = getStringOption(attributes.access, ["open", "nonInteractive", "protected", "readOnly"]);
this.accessKey = attributes.accessKey || "";
this.anchorType = getStringOption(attributes.anchorType, ["topLeft", "bottomCenter", "bottomLeft", "bottomRight", "middleCenter", "middleLeft", "middleRight", "topCenter", "topRight"]);
this.colSpan = getInteger({
data: attributes.colSpan,
defaultValue: 1,
validate: n => n >= 1 || n === -1
});
this.h = attributes.h ? getMeasurement(attributes.h) : "";
this.hAlign = getStringOption(attributes.hAlign, ["left", "center", "justify", "justifyAll", "radix", "right"]);
this.id = attributes.id || "";
this.layout = getStringOption(attributes.layout, ["position", "lr-tb", "rl-row", "rl-tb", "row", "table", "tb"]);
this.maxH = getMeasurement(attributes.maxH, "0pt");
this.maxW = getMeasurement(attributes.maxW, "0pt");
this.minH = getMeasurement(attributes.minH, "0pt");
this.minW = getMeasurement(attributes.minW, "0pt");
this.name = attributes.name || "";
this.presence = getStringOption(attributes.presence, ["visible", "hidden", "inactive", "invisible"]);
this.relevant = getRelevant(attributes.relevant);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.w = attributes.w ? getMeasurement(attributes.w) : "";
this.x = getMeasurement(attributes.x, "0pt");
this.y = getMeasurement(attributes.y, "0pt");
this.assist = null;
this.bind = null;
this.border = null;
this.calculate = null;
this.caption = null;
this.desc = null;
this.extras = null;
this.margin = null;
this.para = null;
this.traversal = null;
this.validate = null;
this.connect = new XFAObjectArray();
this.event = new XFAObjectArray();
this.field = new XFAObjectArray();
this.setProperty = new XFAObjectArray();
}
[$isBindable]() {
return true;
}
[$hasSettableValue]() {
return true;
}
[$setValue](value) {
for (const field of this.field.children) {
if (!field.value) {
const nodeValue = new Value({});
field[$appendChild](nodeValue);
field.value = nodeValue;
}
field.value[$setValue](value);
}
}
[$isThereMoreWidth]() {
return this.layout.endsWith("-tb") && this[$extra].attempt === 0 && this[$extra].numberInLine > 0 || this[$getParent]()[$isThereMoreWidth]();
}
[$isSplittable]() {
const parent = this[$getSubformParent]();
if (!parent[$isSplittable]()) {
return false;
}
if (this[$extra]._isSplittable !== undefined) {
return this[$extra]._isSplittable;
}
if (this.layout === "position" || this.layout.includes("row")) {
this[$extra]._isSplittable = false;
return false;
}
if (parent.layout?.endsWith("-tb") && parent[$extra].numberInLine !== 0) {
return false;
}
this[$extra]._isSplittable = true;
return true;
}
[$flushHTML]() {
return flushHTML(this);
}
[$addHTML](html, bbox) {
addHTML(this, html, bbox);
}
[$getAvailableSpace]() {
return getAvailableSpace(this);
}
[$toHTML](availableSpace) {
setTabIndex(this);
if (this.presence === "hidden" || this.presence === "inactive" || this.h === 0 || this.w === 0) {
return HTMLResult.EMPTY;
}
fixDimensions(this);
const children = [];
const attributes = {
id: this[$uid],
class: []
};
setAccess(this, attributes.class);
if (!this[$extra]) {
this[$extra] = Object.create(null);
}
Object.assign(this[$extra], {
children,
attributes,
attempt: 0,
line: null,
numberInLine: 0,
availableSpace: {
width: Math.min(this.w || Infinity, availableSpace.width),
height: Math.min(this.h || Infinity, availableSpace.height)
},
width: 0,
height: 0,
prevHeight: 0,
currentWidth: 0
});
const isSplittable = this[$isSplittable]();
if (!isSplittable) {
setFirstUnsplittable(this);
}
if (!checkDimensions(this, availableSpace)) {
return HTMLResult.FAILURE;
}
const filter = new Set(["field"]);
if (this.layout.includes("row")) {
const columnWidths = this[$getSubformParent]().columnWidths;
if (Array.isArray(columnWidths) && columnWidths.length > 0) {
this[$extra].columnWidths = columnWidths;
this[$extra].currentColumn = 0;
}
}
const style = toStyle(this, "anchorType", "dimensions", "position", "presence", "border", "margin", "hAlign");
const classNames = ["xfaExclgroup"];
const cl = layoutClass(this);
if (cl) {
classNames.push(cl);
}
if (isPrintOnly(this)) {
classNames.push("xfaPrintOnly");
}
attributes.style = style;
attributes.class = classNames;
if (this.name) {
attributes.xfaName = this.name;
}
this[$pushPara]();
const isLrTb = this.layout === "lr-tb" || this.layout === "rl-tb";
const maxRun = isLrTb ? MAX_ATTEMPTS_FOR_LRTB_LAYOUT : 1;
for (; this[$extra].attempt < maxRun; this[$extra].attempt++) {
if (isLrTb && this[$extra].attempt === MAX_ATTEMPTS_FOR_LRTB_LAYOUT - 1) {
this[$extra].numberInLine = 0;
}
const result = this[$childrenToHTML]({
filter,
include: true
});
if (result.success) {
break;
}
if (result.isBreak()) {
this[$popPara]();
return result;
}
if (isLrTb && this[$extra].attempt === 0 && this[$extra].numberInLine === 0 && !this[$getTemplateRoot]()[$extra].noLayoutFailure) {
this[$extra].attempt = maxRun;
break;
}
}
this[$popPara]();
if (!isSplittable) {
unsetFirstUnsplittable(this);
}
if (this[$extra].attempt === maxRun) {
if (!isSplittable) {
delete this[$extra];
}
return HTMLResult.FAILURE;
}
let marginH = 0;
let marginV = 0;
if (this.margin) {
marginH = this.margin.leftInset + this.margin.rightInset;
marginV = this.margin.topInset + this.margin.bottomInset;
}
const width = Math.max(this[$extra].width + marginH, this.w || 0);
const height = Math.max(this[$extra].height + marginV, this.h || 0);
const bbox = [this.x, this.y, width, height];
if (this.w === "") {
style.width = measureToString(width);
}
if (this.h === "") {
style.height = measureToString(height);
}
const html = {
name: "div",
attributes,
children
};
applyAssist(this, attributes);
delete this[$extra];
return HTMLResult.success(createWrapper(this, html), bbox);
}
}
class Execute extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "execute");
this.connection = attributes.connection || "";
this.executeType = getStringOption(attributes.executeType, ["import", "remerge"]);
this.id = attributes.id || "";
this.runAt = getStringOption(attributes.runAt, ["client", "both", "server"]);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
}
class Extras extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "extras", true);
this.id = attributes.id || "";
this.name = attributes.name || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.boolean = new XFAObjectArray();
this.date = new XFAObjectArray();
this.dateTime = new XFAObjectArray();
this.decimal = new XFAObjectArray();
this.exData = new XFAObjectArray();
this.extras = new XFAObjectArray();
this.float = new XFAObjectArray();
this.image = new XFAObjectArray();
this.integer = new XFAObjectArray();
this.text = new XFAObjectArray();
this.time = new XFAObjectArray();
}
}
class Field extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "field", true);
this.access = getStringOption(attributes.access, ["open", "nonInteractive", "protected", "readOnly"]);
this.accessKey = attributes.accessKey || "";
this.anchorType = getStringOption(attributes.anchorType, ["topLeft", "bottomCenter", "bottomLeft", "bottomRight", "middleCenter", "middleLeft", "middleRight", "topCenter", "topRight"]);
this.colSpan = getInteger({
data: attributes.colSpan,
defaultValue: 1,
validate: n => n >= 1 || n === -1
});
this.h = attributes.h ? getMeasurement(attributes.h) : "";
this.hAlign = getStringOption(attributes.hAlign, ["left", "center", "justify", "justifyAll", "radix", "right"]);
this.id = attributes.id || "";
this.locale = attributes.locale || "";
this.maxH = getMeasurement(attributes.maxH, "0pt");
this.maxW = getMeasurement(attributes.maxW, "0pt");
this.minH = getMeasurement(attributes.minH, "0pt");
this.minW = getMeasurement(attributes.minW, "0pt");
this.name = attributes.name || "";
this.presence = getStringOption(attributes.presence, ["visible", "hidden", "inactive", "invisible"]);
this.relevant = getRelevant(attributes.relevant);
this.rotate = getInteger({
data: attributes.rotate,
defaultValue: 0,
validate: x => x % 90 === 0
});
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.w = attributes.w ? getMeasurement(attributes.w) : "";
this.x = getMeasurement(attributes.x, "0pt");
this.y = getMeasurement(attributes.y, "0pt");
this.assist = null;
this.bind = null;
this.border = null;
this.calculate = null;
this.caption = null;
this.desc = null;
this.extras = null;
this.font = null;
this.format = null;
this.items = new XFAObjectArray(2);
this.keep = null;
this.margin = null;
this.para = null;
this.traversal = null;
this.ui = null;
this.validate = null;
this.value = null;
this.bindItems = new XFAObjectArray();
this.connect = new XFAObjectArray();
this.event = new XFAObjectArray();
this.setProperty = new XFAObjectArray();
}
[$isBindable]() {
return true;
}
[$setValue](value) {
_setValue(this, value);
}
[$toHTML](availableSpace) {
setTabIndex(this);
if (!this.ui) {
this.ui = new Ui({});
this.ui[$globalData] = this[$globalData];
this[$appendChild](this.ui);
let node;
switch (this.items.children.length) {
case 0:
node = new TextEdit({});
this.ui.textEdit = node;
break;
case 1:
node = new CheckButton({});
this.ui.checkButton = node;
break;
case 2:
node = new ChoiceList({});
this.ui.choiceList = node;
break;
}
this.ui[$appendChild](node);
}
if (!this.ui || this.presence === "hidden" || this.presence === "inactive" || this.h === 0 || this.w === 0) {
return HTMLResult.EMPTY;
}
if (this.caption) {
delete this.caption[$extra];
}
this[$pushPara]();
const caption = this.caption ? this.caption[$toHTML](availableSpace).html : null;
const savedW = this.w;
const savedH = this.h;
let marginH = 0;
let marginV = 0;
if (this.margin) {
marginH = this.margin.leftInset + this.margin.rightInset;
marginV = this.margin.topInset + this.margin.bottomInset;
}
let borderDims = null;
if (this.w === "" || this.h === "") {
let width = null;
let height = null;
let uiW = 0;
let uiH = 0;
if (this.ui.checkButton) {
uiW = uiH = this.ui.checkButton.size;
} else {
const {
w,
h
} = layoutNode(this, availableSpace);
if (w !== null) {
uiW = w;
uiH = h;
} else {
uiH = fonts_getMetrics(this.font, true).lineNoGap;
}
}
borderDims = getBorderDims(this.ui[$getExtra]());
uiW += borderDims.w;
uiH += borderDims.h;
if (this.caption) {
const {
w,
h,
isBroken
} = this.caption[$getExtra](availableSpace);
if (isBroken && this[$getSubformParent]()[$isThereMoreWidth]()) {
this[$popPara]();
return HTMLResult.FAILURE;
}
width = w;
height = h;
switch (this.caption.placement) {
case "left":
case "right":
case "inline":
width += uiW;
break;
case "top":
case "bottom":
height += uiH;
break;
}
} else {
width = uiW;
height = uiH;
}
if (width && this.w === "") {
width += marginH;
this.w = Math.min(this.maxW <= 0 ? Infinity : this.maxW, this.minW + 1 < width ? width : this.minW);
}
if (height && this.h === "") {
height += marginV;
this.h = Math.min(this.maxH <= 0 ? Infinity : this.maxH, this.minH + 1 < height ? height : this.minH);
}
}
this[$popPara]();
fixDimensions(this);
setFirstUnsplittable(this);
if (!checkDimensions(this, availableSpace)) {
this.w = savedW;
this.h = savedH;
this[$popPara]();
return HTMLResult.FAILURE;
}
unsetFirstUnsplittable(this);
const style = toStyle(this, "font", "dimensions", "position", "rotate", "anchorType", "presence", "margin", "hAlign");
setMinMaxDimensions(this, style);
const classNames = ["xfaField"];
if (this.font) {
classNames.push("xfaFont");
}
if (isPrintOnly(this)) {
classNames.push("xfaPrintOnly");
}
const attributes = {
style,
id: this[$uid],
class: classNames
};
if (style.margin) {
style.padding = style.margin;
delete style.margin;
}
setAccess(this, classNames);
if (this.name) {
attributes.xfaName = this.name;
}
const children = [];
const html = {
name: "div",
attributes,
children
};
applyAssist(this, attributes);
const borderStyle = this.border ? this.border[$toStyle]() : null;
const bbox = computeBbox(this, html, availableSpace);
const ui = this.ui[$toHTML]().html;
if (!ui) {
Object.assign(style, borderStyle);
return HTMLResult.success(createWrapper(this, html), bbox);
}
if (this[$tabIndex]) {
if (ui.children?.[0]) {
ui.children[0].attributes.tabindex = this[$tabIndex];
} else {
ui.attributes.tabindex = this[$tabIndex];
}
}
if (!ui.attributes.style) {
ui.attributes.style = Object.create(null);
}
let aElement = null;
if (this.ui.button) {
if (ui.children.length === 1) {
[aElement] = ui.children.splice(0, 1);
}
Object.assign(ui.attributes.style, borderStyle);
} else {
Object.assign(style, borderStyle);
}
children.push(ui);
if (this.value) {
if (this.ui.imageEdit) {
ui.children.push(this.value[$toHTML]().html);
} else if (!this.ui.button) {
let value = "";
if (this.value.exData) {
value = this.value.exData[$text]();
} else if (this.value.text) {
value = this.value.text[$getExtra]();
} else {
const htmlValue = this.value[$toHTML]().html;
if (htmlValue !== null) {
value = htmlValue.children[0].value;
}
}
if (this.ui.textEdit && this.value.text?.maxChars) {
ui.children[0].attributes.maxLength = this.value.text.maxChars;
}
if (value) {
if (this.ui.numericEdit) {
value = parseFloat(value);
value = isNaN(value) ? "" : value.toString();
}
if (ui.children[0].name === "textarea") {
ui.children[0].attributes.textContent = value;
} else {
ui.children[0].attributes.value = value;
}
}
}
}
if (!this.ui.imageEdit && ui.children?.[0] && this.h) {
borderDims = borderDims || getBorderDims(this.ui[$getExtra]());
let captionHeight = 0;
if (this.caption && ["top", "bottom"].includes(this.caption.placement)) {
captionHeight = this.caption.reserve;
if (captionHeight <= 0) {
captionHeight = this.caption[$getExtra](availableSpace).h;
}
const inputHeight = this.h - captionHeight - marginV - borderDims.h;
ui.children[0].attributes.style.height = measureToString(inputHeight);
} else {
ui.children[0].attributes.style.height = "100%";
}
}
if (aElement) {
ui.children.push(aElement);
}
if (!caption) {
if (ui.attributes.class) {
ui.attributes.class.push("xfaLeft");
}
this.w = savedW;
this.h = savedH;
return HTMLResult.success(createWrapper(this, html), bbox);
}
if (this.ui.button) {
if (style.padding) {
delete style.padding;
}
if (caption.name === "div") {
caption.name = "span";
}
ui.children.push(caption);
return HTMLResult.success(html, bbox);
} else if (this.ui.checkButton) {
caption.attributes.class[0] = "xfaCaptionForCheckButton";
}
if (!ui.attributes.class) {
ui.attributes.class = [];
}
ui.children.splice(0, 0, caption);
switch (this.caption.placement) {
case "left":
ui.attributes.class.push("xfaLeft");
break;
case "right":
ui.attributes.class.push("xfaRight");
break;
case "top":
ui.attributes.class.push("xfaTop");
break;
case "bottom":
ui.attributes.class.push("xfaBottom");
break;
case "inline":
ui.attributes.class.push("xfaLeft");
break;
}
this.w = savedW;
this.h = savedH;
return HTMLResult.success(createWrapper(this, html), bbox);
}
}
class Fill extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "fill", true);
this.id = attributes.id || "";
this.presence = getStringOption(attributes.presence, ["visible", "hidden", "inactive", "invisible"]);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.color = null;
this.extras = null;
this.linear = null;
this.pattern = null;
this.radial = null;
this.solid = null;
this.stipple = null;
}
[$toStyle]() {
const parent = this[$getParent]();
const grandpa = parent[$getParent]();
const ggrandpa = grandpa[$getParent]();
const style = Object.create(null);
let propName = "color";
let altPropName = propName;
if (parent instanceof Border) {
propName = "background-color";
altPropName = "background";
if (ggrandpa instanceof Ui) {
style.backgroundColor = "white";
}
}
if (parent instanceof Rectangle || parent instanceof Arc) {
propName = altPropName = "fill";
style.fill = "white";
}
for (const name of Object.getOwnPropertyNames(this)) {
if (name === "extras" || name === "color") {
continue;
}
const obj = this[name];
if (!(obj instanceof XFAObject)) {
continue;
}
const color = obj[$toStyle](this.color);
if (color) {
style[color.startsWith("#") ? propName : altPropName] = color;
}
return style;
}
if (this.color?.value) {
const color = this.color[$toStyle]();
style[color.startsWith("#") ? propName : altPropName] = color;
}
return style;
}
}
class Filter extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "filter", true);
this.addRevocationInfo = getStringOption(attributes.addRevocationInfo, ["", "required", "optional", "none"]);
this.id = attributes.id || "";
this.name = attributes.name || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.version = getInteger({
data: this.version,
defaultValue: 5,
validate: x => x >= 1 && x <= 5
});
this.appearanceFilter = null;
this.certificates = null;
this.digestMethods = null;
this.encodings = null;
this.encryptionMethods = null;
this.handler = null;
this.lockDocument = null;
this.mdp = null;
this.reasons = null;
this.timeStamp = null;
}
}
class Float extends ContentObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "float");
this.id = attributes.id || "";
this.name = attributes.name || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
[$finalize]() {
const number = parseFloat(this[$content].trim());
this[$content] = isNaN(number) ? null : number;
}
[$toHTML](availableSpace) {
return valueToHtml(this[$content] !== null ? this[$content].toString() : "");
}
}
class template_Font extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "font", true);
this.baselineShift = getMeasurement(attributes.baselineShift);
this.fontHorizontalScale = getFloat({
data: attributes.fontHorizontalScale,
defaultValue: 100,
validate: x => x >= 0
});
this.fontVerticalScale = getFloat({
data: attributes.fontVerticalScale,
defaultValue: 100,
validate: x => x >= 0
});
this.id = attributes.id || "";
this.kerningMode = getStringOption(attributes.kerningMode, ["none", "pair"]);
this.letterSpacing = getMeasurement(attributes.letterSpacing, "0");
this.lineThrough = getInteger({
data: attributes.lineThrough,
defaultValue: 0,
validate: x => x === 1 || x === 2
});
this.lineThroughPeriod = getStringOption(attributes.lineThroughPeriod, ["all", "word"]);
this.overline = getInteger({
data: attributes.overline,
defaultValue: 0,
validate: x => x === 1 || x === 2
});
this.overlinePeriod = getStringOption(attributes.overlinePeriod, ["all", "word"]);
this.posture = getStringOption(attributes.posture, ["normal", "italic"]);
this.size = getMeasurement(attributes.size, "10pt");
this.typeface = attributes.typeface || "Courier";
this.underline = getInteger({
data: attributes.underline,
defaultValue: 0,
validate: x => x === 1 || x === 2
});
this.underlinePeriod = getStringOption(attributes.underlinePeriod, ["all", "word"]);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.weight = getStringOption(attributes.weight, ["normal", "bold"]);
this.extras = null;
this.fill = null;
}
[$clean](builder) {
super[$clean](builder);
this[$globalData].usedTypefaces.add(this.typeface);
}
[$toStyle]() {
const style = toStyle(this, "fill");
const color = style.color;
if (color) {
if (color === "#000000") {
delete style.color;
} else if (!color.startsWith("#")) {
style.background = color;
style.backgroundClip = "text";
style.color = "transparent";
}
}
if (this.baselineShift) {
style.verticalAlign = measureToString(this.baselineShift);
}
style.fontKerning = this.kerningMode === "none" ? "none" : "normal";
style.letterSpacing = measureToString(this.letterSpacing);
if (this.lineThrough !== 0) {
style.textDecoration = "line-through";
if (this.lineThrough === 2) {
style.textDecorationStyle = "double";
}
}
if (this.overline !== 0) {
style.textDecoration = "overline";
if (this.overline === 2) {
style.textDecorationStyle = "double";
}
}
style.fontStyle = this.posture;
style.fontSize = measureToString(0.99 * this.size);
setFontFamily(this, this, this[$globalData].fontFinder, style);
if (this.underline !== 0) {
style.textDecoration = "underline";
if (this.underline === 2) {
style.textDecorationStyle = "double";
}
}
style.fontWeight = this.weight;
return style;
}
}
class Format extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "format", true);
this.id = attributes.id || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.extras = null;
this.picture = null;
}
}
class Handler extends StringObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "handler");
this.id = attributes.id || "";
this.type = getStringOption(attributes.type, ["optional", "required"]);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
}
class Hyphenation extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "hyphenation");
this.excludeAllCaps = getInteger({
data: attributes.excludeAllCaps,
defaultValue: 0,
validate: x => x === 1
});
this.excludeInitialCap = getInteger({
data: attributes.excludeInitialCap,
defaultValue: 0,
validate: x => x === 1
});
this.hyphenate = getInteger({
data: attributes.hyphenate,
defaultValue: 0,
validate: x => x === 1
});
this.id = attributes.id || "";
this.pushCharacterCount = getInteger({
data: attributes.pushCharacterCount,
defaultValue: 3,
validate: x => x >= 0
});
this.remainCharacterCount = getInteger({
data: attributes.remainCharacterCount,
defaultValue: 3,
validate: x => x >= 0
});
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.wordCharacterCount = getInteger({
data: attributes.wordCharacterCount,
defaultValue: 7,
validate: x => x >= 0
});
}
}
class Image extends StringObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "image");
this.aspect = getStringOption(attributes.aspect, ["fit", "actual", "height", "none", "width"]);
this.contentType = attributes.contentType || "";
this.href = attributes.href || "";
this.id = attributes.id || "";
this.name = attributes.name || "";
this.transferEncoding = getStringOption(attributes.transferEncoding, ["base64", "none", "package"]);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
[$toHTML]() {
if (this.contentType && !MIMES.has(this.contentType.toLowerCase())) {
return HTMLResult.EMPTY;
}
let buffer = this[$globalData].images && this[$globalData].images.get(this.href);
if (!buffer && (this.href || !this[$content])) {
return HTMLResult.EMPTY;
}
if (!buffer && this.transferEncoding === "base64") {
buffer = fromBase64Util(this[$content]);
}
if (!buffer) {
return HTMLResult.EMPTY;
}
if (!this.contentType) {
for (const [header, type] of IMAGES_HEADERS) {
if (buffer.length > header.length && header.every((x, i) => x === buffer[i])) {
this.contentType = type;
break;
}
}
if (!this.contentType) {
return HTMLResult.EMPTY;
}
}
const blob = new Blob([buffer], {
type: this.contentType
});
let style;
switch (this.aspect) {
case "fit":
case "actual":
break;
case "height":
style = {
height: "100%",
objectFit: "fill"
};
break;
case "none":
style = {
width: "100%",
height: "100%",
objectFit: "fill"
};
break;
case "width":
style = {
width: "100%",
objectFit: "fill"
};
break;
}
const parent = this[$getParent]();
return HTMLResult.success({
name: "img",
attributes: {
class: ["xfaImage"],
style,
src: URL.createObjectURL(blob),
alt: parent ? ariaLabel(parent[$getParent]()) : null
}
});
}
}
class ImageEdit extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "imageEdit", true);
this.data = getStringOption(attributes.data, ["link", "embed"]);
this.id = attributes.id || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.border = null;
this.extras = null;
this.margin = null;
}
[$toHTML](availableSpace) {
if (this.data === "embed") {
return HTMLResult.success({
name: "div",
children: [],
attributes: {}
});
}
return HTMLResult.EMPTY;
}
}
class Integer extends ContentObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "integer");
this.id = attributes.id || "";
this.name = attributes.name || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
[$finalize]() {
const number = parseInt(this[$content].trim(), 10);
this[$content] = isNaN(number) ? null : number;
}
[$toHTML](availableSpace) {
return valueToHtml(this[$content] !== null ? this[$content].toString() : "");
}
}
class Issuers extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "issuers", true);
this.id = attributes.id || "";
this.type = getStringOption(attributes.type, ["optional", "required"]);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.certificate = new XFAObjectArray();
}
}
class Items extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "items", true);
this.id = attributes.id || "";
this.name = attributes.name || "";
this.presence = getStringOption(attributes.presence, ["visible", "hidden", "inactive", "invisible"]);
this.ref = attributes.ref || "";
this.save = getInteger({
data: attributes.save,
defaultValue: 0,
validate: x => x === 1
});
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.boolean = new XFAObjectArray();
this.date = new XFAObjectArray();
this.dateTime = new XFAObjectArray();
this.decimal = new XFAObjectArray();
this.exData = new XFAObjectArray();
this.float = new XFAObjectArray();
this.image = new XFAObjectArray();
this.integer = new XFAObjectArray();
this.text = new XFAObjectArray();
this.time = new XFAObjectArray();
}
[$toHTML]() {
const output = [];
for (const child of this[$getChildren]()) {
output.push(child[$text]());
}
return HTMLResult.success(output);
}
}
class Keep extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "keep", true);
this.id = attributes.id || "";
const options = ["none", "contentArea", "pageArea"];
this.intact = getStringOption(attributes.intact, options);
this.next = getStringOption(attributes.next, options);
this.previous = getStringOption(attributes.previous, options);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.extras = null;
}
}
class KeyUsage extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "keyUsage");
const options = ["", "yes", "no"];
this.crlSign = getStringOption(attributes.crlSign, options);
this.dataEncipherment = getStringOption(attributes.dataEncipherment, options);
this.decipherOnly = getStringOption(attributes.decipherOnly, options);
this.digitalSignature = getStringOption(attributes.digitalSignature, options);
this.encipherOnly = getStringOption(attributes.encipherOnly, options);
this.id = attributes.id || "";
this.keyAgreement = getStringOption(attributes.keyAgreement, options);
this.keyCertSign = getStringOption(attributes.keyCertSign, options);
this.keyEncipherment = getStringOption(attributes.keyEncipherment, options);
this.nonRepudiation = getStringOption(attributes.nonRepudiation, options);
this.type = getStringOption(attributes.type, ["optional", "required"]);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
}
class Line extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "line", true);
this.hand = getStringOption(attributes.hand, ["even", "left", "right"]);
this.id = attributes.id || "";
this.slope = getStringOption(attributes.slope, ["\\", "/"]);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.edge = null;
}
[$toHTML]() {
const parent = this[$getParent]()[$getParent]();
const edge = this.edge || new Edge({});
const edgeStyle = edge[$toStyle]();
const style = Object.create(null);
const thickness = edge.presence === "visible" ? edge.thickness : 0;
style.strokeWidth = measureToString(thickness);
style.stroke = edgeStyle.color;
let x1, y1, x2, y2;
let width = "100%";
let height = "100%";
if (parent.w <= thickness) {
[x1, y1, x2, y2] = ["50%", 0, "50%", "100%"];
width = style.strokeWidth;
} else if (parent.h <= thickness) {
[x1, y1, x2, y2] = [0, "50%", "100%", "50%"];
height = style.strokeWidth;
} else if (this.slope === "\\") {
[x1, y1, x2, y2] = [0, 0, "100%", "100%"];
} else {
[x1, y1, x2, y2] = [0, "100%", "100%", 0];
}
const line = {
name: "line",
attributes: {
xmlns: SVG_NS,
x1,
y1,
x2,
y2,
style
}
};
const svg = {
name: "svg",
children: [line],
attributes: {
xmlns: SVG_NS,
width,
height,
style: {
overflow: "visible"
}
}
};
if (hasMargin(parent)) {
return HTMLResult.success({
name: "div",
attributes: {
style: {
display: "inline",
width: "100%",
height: "100%"
}
},
children: [svg]
});
}
svg.attributes.style.position = "absolute";
return HTMLResult.success(svg);
}
}
class Linear extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "linear", true);
this.id = attributes.id || "";
this.type = getStringOption(attributes.type, ["toRight", "toBottom", "toLeft", "toTop"]);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.color = null;
this.extras = null;
}
[$toStyle](startColor) {
startColor = startColor ? startColor[$toStyle]() : "#FFFFFF";
const transf = this.type.replace(/([RBLT])/, " $1").toLowerCase();
const endColor = this.color ? this.color[$toStyle]() : "#000000";
return `linear-gradient(${transf}, ${startColor}, ${endColor})`;
}
}
class LockDocument extends ContentObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "lockDocument");
this.id = attributes.id || "";
this.type = getStringOption(attributes.type, ["optional", "required"]);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
[$finalize]() {
this[$content] = getStringOption(this[$content], ["auto", "0", "1"]);
}
}
class Manifest extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "manifest", true);
this.action = getStringOption(attributes.action, ["include", "all", "exclude"]);
this.id = attributes.id || "";
this.name = attributes.name || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.extras = null;
this.ref = new XFAObjectArray();
}
}
class Margin extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "margin", true);
this.bottomInset = getMeasurement(attributes.bottomInset, "0");
this.id = attributes.id || "";
this.leftInset = getMeasurement(attributes.leftInset, "0");
this.rightInset = getMeasurement(attributes.rightInset, "0");
this.topInset = getMeasurement(attributes.topInset, "0");
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.extras = null;
}
[$toStyle]() {
return {
margin: measureToString(this.topInset) + " " + measureToString(this.rightInset) + " " + measureToString(this.bottomInset) + " " + measureToString(this.leftInset)
};
}
}
class Mdp extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "mdp");
this.id = attributes.id || "";
this.permissions = getInteger({
data: attributes.permissions,
defaultValue: 2,
validate: x => x === 1 || x === 3
});
this.signatureType = getStringOption(attributes.signatureType, ["filler", "author"]);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
}
class Medium extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "medium");
this.id = attributes.id || "";
this.imagingBBox = getBBox(attributes.imagingBBox);
this.long = getMeasurement(attributes.long);
this.orientation = getStringOption(attributes.orientation, ["portrait", "landscape"]);
this.short = getMeasurement(attributes.short);
this.stock = attributes.stock || "";
this.trayIn = getStringOption(attributes.trayIn, ["auto", "delegate", "pageFront"]);
this.trayOut = getStringOption(attributes.trayOut, ["auto", "delegate"]);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
}
class Message extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "message", true);
this.id = attributes.id || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.text = new XFAObjectArray();
}
}
class NumericEdit extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "numericEdit", true);
this.hScrollPolicy = getStringOption(attributes.hScrollPolicy, ["auto", "off", "on"]);
this.id = attributes.id || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.border = null;
this.comb = null;
this.extras = null;
this.margin = null;
}
[$toHTML](availableSpace) {
const style = toStyle(this, "border", "font", "margin");
const field = this[$getParent]()[$getParent]();
const html = {
name: "input",
attributes: {
type: "text",
fieldId: field[$uid],
dataId: field[$data]?.[$uid] || field[$uid],
class: ["xfaTextfield"],
style,
"aria-label": ariaLabel(field),
"aria-required": false
}
};
if (isRequired(field)) {
html.attributes["aria-required"] = true;
html.attributes.required = true;
}
return HTMLResult.success({
name: "label",
attributes: {
class: ["xfaLabel"]
},
children: [html]
});
}
}
class Occur extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "occur", true);
this.id = attributes.id || "";
this.initial = attributes.initial !== "" ? getInteger({
data: attributes.initial,
defaultValue: "",
validate: x => true
}) : "";
this.max = attributes.max !== "" ? getInteger({
data: attributes.max,
defaultValue: 1,
validate: x => true
}) : "";
this.min = attributes.min !== "" ? getInteger({
data: attributes.min,
defaultValue: 1,
validate: x => true
}) : "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.extras = null;
}
[$clean]() {
const parent = this[$getParent]();
const originalMin = this.min;
if (this.min === "") {
this.min = parent instanceof PageArea || parent instanceof PageSet ? 0 : 1;
}
if (this.max === "") {
if (originalMin === "") {
this.max = parent instanceof PageArea || parent instanceof PageSet ? -1 : 1;
} else {
this.max = this.min;
}
}
if (this.max !== -1 && this.max < this.min) {
this.max = this.min;
}
if (this.initial === "") {
this.initial = parent instanceof Template ? 1 : this.min;
}
}
}
class Oid extends StringObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "oid");
this.id = attributes.id || "";
this.name = attributes.name || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
}
class Oids extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "oids", true);
this.id = attributes.id || "";
this.type = getStringOption(attributes.type, ["optional", "required"]);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.oid = new XFAObjectArray();
}
}
class Overflow extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "overflow");
this.id = attributes.id || "";
this.leader = attributes.leader || "";
this.target = attributes.target || "";
this.trailer = attributes.trailer || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
[$getExtra]() {
if (!this[$extra]) {
const parent = this[$getParent]();
const root = this[$getTemplateRoot]();
const target = root[$searchNode](this.target, parent);
const leader = root[$searchNode](this.leader, parent);
const trailer = root[$searchNode](this.trailer, parent);
this[$extra] = {
target: target?.[0] || null,
leader: leader?.[0] || null,
trailer: trailer?.[0] || null,
addLeader: false,
addTrailer: false
};
}
return this[$extra];
}
}
class PageArea extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "pageArea", true);
this.blankOrNotBlank = getStringOption(attributes.blankOrNotBlank, ["any", "blank", "notBlank"]);
this.id = attributes.id || "";
this.initialNumber = getInteger({
data: attributes.initialNumber,
defaultValue: 1,
validate: x => true
});
this.name = attributes.name || "";
this.numbered = getInteger({
data: attributes.numbered,
defaultValue: 1,
validate: x => true
});
this.oddOrEven = getStringOption(attributes.oddOrEven, ["any", "even", "odd"]);
this.pagePosition = getStringOption(attributes.pagePosition, ["any", "first", "last", "only", "rest"]);
this.relevant = getRelevant(attributes.relevant);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.desc = null;
this.extras = null;
this.medium = null;
this.occur = null;
this.area = new XFAObjectArray();
this.contentArea = new XFAObjectArray();
this.draw = new XFAObjectArray();
this.exclGroup = new XFAObjectArray();
this.field = new XFAObjectArray();
this.subform = new XFAObjectArray();
}
[$isUsable]() {
if (!this[$extra]) {
this[$extra] = {
numberOfUse: 0
};
return true;
}
return !this.occur || this.occur.max === -1 || this[$extra].numberOfUse < this.occur.max;
}
[$cleanPage]() {
delete this[$extra];
}
[$getNextPage]() {
if (!this[$extra]) {
this[$extra] = {
numberOfUse: 0
};
}
const parent = this[$getParent]();
if (parent.relation === "orderedOccurrence") {
if (this[$isUsable]()) {
this[$extra].numberOfUse += 1;
return this;
}
}
return parent[$getNextPage]();
}
[$getAvailableSpace]() {
return this[$extra].space || {
width: 0,
height: 0
};
}
[$toHTML]() {
if (!this[$extra]) {
this[$extra] = {
numberOfUse: 1
};
}
const children = [];
this[$extra].children = children;
const style = Object.create(null);
if (this.medium && this.medium.short && this.medium.long) {
style.width = measureToString(this.medium.short);
style.height = measureToString(this.medium.long);
this[$extra].space = {
width: this.medium.short,
height: this.medium.long
};
if (this.medium.orientation === "landscape") {
const x = style.width;
style.width = style.height;
style.height = x;
this[$extra].space = {
width: this.medium.long,
height: this.medium.short
};
}
} else {
warn("XFA - No medium specified in pageArea: please file a bug.");
}
this[$childrenToHTML]({
filter: new Set(["area", "draw", "field", "subform"]),
include: true
});
this[$childrenToHTML]({
filter: new Set(["contentArea"]),
include: true
});
return HTMLResult.success({
name: "div",
children,
attributes: {
class: ["xfaPage"],
id: this[$uid],
style,
xfaName: this.name
}
});
}
}
class PageSet extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "pageSet", true);
this.duplexImposition = getStringOption(attributes.duplexImposition, ["longEdge", "shortEdge"]);
this.id = attributes.id || "";
this.name = attributes.name || "";
this.relation = getStringOption(attributes.relation, ["orderedOccurrence", "duplexPaginated", "simplexPaginated"]);
this.relevant = getRelevant(attributes.relevant);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.extras = null;
this.occur = null;
this.pageArea = new XFAObjectArray();
this.pageSet = new XFAObjectArray();
}
[$cleanPage]() {
for (const page of this.pageArea.children) {
page[$cleanPage]();
}
for (const page of this.pageSet.children) {
page[$cleanPage]();
}
}
[$isUsable]() {
return !this.occur || this.occur.max === -1 || this[$extra].numberOfUse < this.occur.max;
}
[$getNextPage]() {
if (!this[$extra]) {
this[$extra] = {
numberOfUse: 1,
pageIndex: -1,
pageSetIndex: -1
};
}
if (this.relation === "orderedOccurrence") {
if (this[$extra].pageIndex + 1 < this.pageArea.children.length) {
this[$extra].pageIndex += 1;
const pageArea = this.pageArea.children[this[$extra].pageIndex];
return pageArea[$getNextPage]();
}
if (this[$extra].pageSetIndex + 1 < this.pageSet.children.length) {
this[$extra].pageSetIndex += 1;
return this.pageSet.children[this[$extra].pageSetIndex][$getNextPage]();
}
if (this[$isUsable]()) {
this[$extra].numberOfUse += 1;
this[$extra].pageIndex = -1;
this[$extra].pageSetIndex = -1;
return this[$getNextPage]();
}
const parent = this[$getParent]();
if (parent instanceof PageSet) {
return parent[$getNextPage]();
}
this[$cleanPage]();
return this[$getNextPage]();
}
const pageNumber = this[$getTemplateRoot]()[$extra].pageNumber;
const parity = pageNumber % 2 === 0 ? "even" : "odd";
const position = pageNumber === 0 ? "first" : "rest";
let page = this.pageArea.children.find(p => p.oddOrEven === parity && p.pagePosition === position);
if (page) {
return page;
}
page = this.pageArea.children.find(p => p.oddOrEven === "any" && p.pagePosition === position);
if (page) {
return page;
}
page = this.pageArea.children.find(p => p.oddOrEven === "any" && p.pagePosition === "any");
if (page) {
return page;
}
return this.pageArea.children[0];
}
}
class Para extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "para", true);
this.hAlign = getStringOption(attributes.hAlign, ["left", "center", "justify", "justifyAll", "radix", "right"]);
this.id = attributes.id || "";
this.lineHeight = attributes.lineHeight ? getMeasurement(attributes.lineHeight, "0pt") : "";
this.marginLeft = attributes.marginLeft ? getMeasurement(attributes.marginLeft, "0pt") : "";
this.marginRight = attributes.marginRight ? getMeasurement(attributes.marginRight, "0pt") : "";
this.orphans = getInteger({
data: attributes.orphans,
defaultValue: 0,
validate: x => x >= 0
});
this.preserve = attributes.preserve || "";
this.radixOffset = attributes.radixOffset ? getMeasurement(attributes.radixOffset, "0pt") : "";
this.spaceAbove = attributes.spaceAbove ? getMeasurement(attributes.spaceAbove, "0pt") : "";
this.spaceBelow = attributes.spaceBelow ? getMeasurement(attributes.spaceBelow, "0pt") : "";
this.tabDefault = attributes.tabDefault ? getMeasurement(this.tabDefault) : "";
this.tabStops = (attributes.tabStops || "").trim().split(/\s+/).map((x, i) => i % 2 === 1 ? getMeasurement(x) : x);
this.textIndent = attributes.textIndent ? getMeasurement(attributes.textIndent, "0pt") : "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.vAlign = getStringOption(attributes.vAlign, ["top", "bottom", "middle"]);
this.widows = getInteger({
data: attributes.widows,
defaultValue: 0,
validate: x => x >= 0
});
this.hyphenation = null;
}
[$toStyle]() {
const style = toStyle(this, "hAlign");
if (this.marginLeft !== "") {
style.paddingLeft = measureToString(this.marginLeft);
}
if (this.marginRight !== "") {
style.paddingRight = measureToString(this.marginRight);
}
if (this.spaceAbove !== "") {
style.paddingTop = measureToString(this.spaceAbove);
}
if (this.spaceBelow !== "") {
style.paddingBottom = measureToString(this.spaceBelow);
}
if (this.textIndent !== "") {
style.textIndent = measureToString(this.textIndent);
fixTextIndent(style);
}
if (this.lineHeight > 0) {
style.lineHeight = measureToString(this.lineHeight);
}
if (this.tabDefault !== "") {
style.tabSize = measureToString(this.tabDefault);
}
if (this.tabStops.length > 0) {}
if (this.hyphenatation) {
Object.assign(style, this.hyphenatation[$toStyle]());
}
return style;
}
}
class PasswordEdit extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "passwordEdit", true);
this.hScrollPolicy = getStringOption(attributes.hScrollPolicy, ["auto", "off", "on"]);
this.id = attributes.id || "";
this.passwordChar = attributes.passwordChar || "*";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.border = null;
this.extras = null;
this.margin = null;
}
}
class template_Pattern extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "pattern", true);
this.id = attributes.id || "";
this.type = getStringOption(attributes.type, ["crossHatch", "crossDiagonal", "diagonalLeft", "diagonalRight", "horizontal", "vertical"]);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.color = null;
this.extras = null;
}
[$toStyle](startColor) {
startColor = startColor ? startColor[$toStyle]() : "#FFFFFF";
const endColor = this.color ? this.color[$toStyle]() : "#000000";
const width = 5;
const cmd = "repeating-linear-gradient";
const colors = `${startColor},${startColor} ${width}px,${endColor} ${width}px,${endColor} ${2 * width}px`;
switch (this.type) {
case "crossHatch":
return `${cmd}(to top,${colors}) ${cmd}(to right,${colors})`;
case "crossDiagonal":
return `${cmd}(45deg,${colors}) ${cmd}(-45deg,${colors})`;
case "diagonalLeft":
return `${cmd}(45deg,${colors})`;
case "diagonalRight":
return `${cmd}(-45deg,${colors})`;
case "horizontal":
return `${cmd}(to top,${colors})`;
case "vertical":
return `${cmd}(to right,${colors})`;
}
return "";
}
}
class Picture extends StringObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "picture");
this.id = attributes.id || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
}
class Proto extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "proto", true);
this.appearanceFilter = new XFAObjectArray();
this.arc = new XFAObjectArray();
this.area = new XFAObjectArray();
this.assist = new XFAObjectArray();
this.barcode = new XFAObjectArray();
this.bindItems = new XFAObjectArray();
this.bookend = new XFAObjectArray();
this.boolean = new XFAObjectArray();
this.border = new XFAObjectArray();
this.break = new XFAObjectArray();
this.breakAfter = new XFAObjectArray();
this.breakBefore = new XFAObjectArray();
this.button = new XFAObjectArray();
this.calculate = new XFAObjectArray();
this.caption = new XFAObjectArray();
this.certificate = new XFAObjectArray();
this.certificates = new XFAObjectArray();
this.checkButton = new XFAObjectArray();
this.choiceList = new XFAObjectArray();
this.color = new XFAObjectArray();
this.comb = new XFAObjectArray();
this.connect = new XFAObjectArray();
this.contentArea = new XFAObjectArray();
this.corner = new XFAObjectArray();
this.date = new XFAObjectArray();
this.dateTime = new XFAObjectArray();
this.dateTimeEdit = new XFAObjectArray();
this.decimal = new XFAObjectArray();
this.defaultUi = new XFAObjectArray();
this.desc = new XFAObjectArray();
this.digestMethod = new XFAObjectArray();
this.digestMethods = new XFAObjectArray();
this.draw = new XFAObjectArray();
this.edge = new XFAObjectArray();
this.encoding = new XFAObjectArray();
this.encodings = new XFAObjectArray();
this.encrypt = new XFAObjectArray();
this.encryptData = new XFAObjectArray();
this.encryption = new XFAObjectArray();
this.encryptionMethod = new XFAObjectArray();
this.encryptionMethods = new XFAObjectArray();
this.event = new XFAObjectArray();
this.exData = new XFAObjectArray();
this.exObject = new XFAObjectArray();
this.exclGroup = new XFAObjectArray();
this.execute = new XFAObjectArray();
this.extras = new XFAObjectArray();
this.field = new XFAObjectArray();
this.fill = new XFAObjectArray();
this.filter = new XFAObjectArray();
this.float = new XFAObjectArray();
this.font = new XFAObjectArray();
this.format = new XFAObjectArray();
this.handler = new XFAObjectArray();
this.hyphenation = new XFAObjectArray();
this.image = new XFAObjectArray();
this.imageEdit = new XFAObjectArray();
this.integer = new XFAObjectArray();
this.issuers = new XFAObjectArray();
this.items = new XFAObjectArray();
this.keep = new XFAObjectArray();
this.keyUsage = new XFAObjectArray();
this.line = new XFAObjectArray();
this.linear = new XFAObjectArray();
this.lockDocument = new XFAObjectArray();
this.manifest = new XFAObjectArray();
this.margin = new XFAObjectArray();
this.mdp = new XFAObjectArray();
this.medium = new XFAObjectArray();
this.message = new XFAObjectArray();
this.numericEdit = new XFAObjectArray();
this.occur = new XFAObjectArray();
this.oid = new XFAObjectArray();
this.oids = new XFAObjectArray();
this.overflow = new XFAObjectArray();
this.pageArea = new XFAObjectArray();
this.pageSet = new XFAObjectArray();
this.para = new XFAObjectArray();
this.passwordEdit = new XFAObjectArray();
this.pattern = new XFAObjectArray();
this.picture = new XFAObjectArray();
this.radial = new XFAObjectArray();
this.reason = new XFAObjectArray();
this.reasons = new XFAObjectArray();
this.rectangle = new XFAObjectArray();
this.ref = new XFAObjectArray();
this.script = new XFAObjectArray();
this.setProperty = new XFAObjectArray();
this.signData = new XFAObjectArray();
this.signature = new XFAObjectArray();
this.signing = new XFAObjectArray();
this.solid = new XFAObjectArray();
this.speak = new XFAObjectArray();
this.stipple = new XFAObjectArray();
this.subform = new XFAObjectArray();
this.subformSet = new XFAObjectArray();
this.subjectDN = new XFAObjectArray();
this.subjectDNs = new XFAObjectArray();
this.submit = new XFAObjectArray();
this.text = new XFAObjectArray();
this.textEdit = new XFAObjectArray();
this.time = new XFAObjectArray();
this.timeStamp = new XFAObjectArray();
this.toolTip = new XFAObjectArray();
this.traversal = new XFAObjectArray();
this.traverse = new XFAObjectArray();
this.ui = new XFAObjectArray();
this.validate = new XFAObjectArray();
this.value = new XFAObjectArray();
this.variables = new XFAObjectArray();
}
}
class Radial extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "radial", true);
this.id = attributes.id || "";
this.type = getStringOption(attributes.type, ["toEdge", "toCenter"]);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.color = null;
this.extras = null;
}
[$toStyle](startColor) {
startColor = startColor ? startColor[$toStyle]() : "#FFFFFF";
const endColor = this.color ? this.color[$toStyle]() : "#000000";
const colors = this.type === "toEdge" ? `${startColor},${endColor}` : `${endColor},${startColor}`;
return `radial-gradient(circle at center, ${colors})`;
}
}
class Reason extends StringObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "reason");
this.id = attributes.id || "";
this.name = attributes.name || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
}
class Reasons extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "reasons", true);
this.id = attributes.id || "";
this.type = getStringOption(attributes.type, ["optional", "required"]);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.reason = new XFAObjectArray();
}
}
class Rectangle extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "rectangle", true);
this.hand = getStringOption(attributes.hand, ["even", "left", "right"]);
this.id = attributes.id || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.corner = new XFAObjectArray(4);
this.edge = new XFAObjectArray(4);
this.fill = null;
}
[$toHTML]() {
const edge = this.edge.children.length ? this.edge.children[0] : new Edge({});
const edgeStyle = edge[$toStyle]();
const style = Object.create(null);
if (this.fill?.presence === "visible") {
Object.assign(style, this.fill[$toStyle]());
} else {
style.fill = "transparent";
}
style.strokeWidth = measureToString(edge.presence === "visible" ? edge.thickness : 0);
style.stroke = edgeStyle.color;
const corner = this.corner.children.length ? this.corner.children[0] : new Corner({});
const cornerStyle = corner[$toStyle]();
const rect = {
name: "rect",
attributes: {
xmlns: SVG_NS,
width: "100%",
height: "100%",
x: 0,
y: 0,
rx: cornerStyle.radius,
ry: cornerStyle.radius,
style
}
};
const svg = {
name: "svg",
children: [rect],
attributes: {
xmlns: SVG_NS,
style: {
overflow: "visible"
},
width: "100%",
height: "100%"
}
};
const parent = this[$getParent]()[$getParent]();
if (hasMargin(parent)) {
return HTMLResult.success({
name: "div",
attributes: {
style: {
display: "inline",
width: "100%",
height: "100%"
}
},
children: [svg]
});
}
svg.attributes.style.position = "absolute";
return HTMLResult.success(svg);
}
}
class RefElement extends StringObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "ref");
this.id = attributes.id || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
}
class Script extends StringObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "script");
this.binding = attributes.binding || "";
this.contentType = attributes.contentType || "";
this.id = attributes.id || "";
this.name = attributes.name || "";
this.runAt = getStringOption(attributes.runAt, ["client", "both", "server"]);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
}
class SetProperty extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "setProperty");
this.connection = attributes.connection || "";
this.ref = attributes.ref || "";
this.target = attributes.target || "";
}
}
class SignData extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "signData", true);
this.id = attributes.id || "";
this.operation = getStringOption(attributes.operation, ["sign", "clear", "verify"]);
this.ref = attributes.ref || "";
this.target = attributes.target || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.filter = null;
this.manifest = null;
}
}
class Signature extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "signature", true);
this.id = attributes.id || "";
this.type = getStringOption(attributes.type, ["PDF1.3", "PDF1.6"]);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.border = null;
this.extras = null;
this.filter = null;
this.manifest = null;
this.margin = null;
}
}
class Signing extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "signing", true);
this.id = attributes.id || "";
this.type = getStringOption(attributes.type, ["optional", "required"]);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.certificate = new XFAObjectArray();
}
}
class Solid extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "solid", true);
this.id = attributes.id || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.extras = null;
}
[$toStyle](startColor) {
return startColor ? startColor[$toStyle]() : "#FFFFFF";
}
}
class Speak extends StringObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "speak");
this.disable = getInteger({
data: attributes.disable,
defaultValue: 0,
validate: x => x === 1
});
this.id = attributes.id || "";
this.priority = getStringOption(attributes.priority, ["custom", "caption", "name", "toolTip"]);
this.rid = attributes.rid || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
}
class Stipple extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "stipple", true);
this.id = attributes.id || "";
this.rate = getInteger({
data: attributes.rate,
defaultValue: 50,
validate: x => x >= 0 && x <= 100
});
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.color = null;
this.extras = null;
}
[$toStyle](bgColor) {
const alpha = this.rate / 100;
return Util.makeHexColor(Math.round(bgColor.value.r * (1 - alpha) + this.value.r * alpha), Math.round(bgColor.value.g * (1 - alpha) + this.value.g * alpha), Math.round(bgColor.value.b * (1 - alpha) + this.value.b * alpha));
}
}
class Subform extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "subform", true);
this.access = getStringOption(attributes.access, ["open", "nonInteractive", "protected", "readOnly"]);
this.allowMacro = getInteger({
data: attributes.allowMacro,
defaultValue: 0,
validate: x => x === 1
});
this.anchorType = getStringOption(attributes.anchorType, ["topLeft", "bottomCenter", "bottomLeft", "bottomRight", "middleCenter", "middleLeft", "middleRight", "topCenter", "topRight"]);
this.colSpan = getInteger({
data: attributes.colSpan,
defaultValue: 1,
validate: n => n >= 1 || n === -1
});
this.columnWidths = (attributes.columnWidths || "").trim().split(/\s+/).map(x => x === "-1" ? -1 : getMeasurement(x));
this.h = attributes.h ? getMeasurement(attributes.h) : "";
this.hAlign = getStringOption(attributes.hAlign, ["left", "center", "justify", "justifyAll", "radix", "right"]);
this.id = attributes.id || "";
this.layout = getStringOption(attributes.layout, ["position", "lr-tb", "rl-row", "rl-tb", "row", "table", "tb"]);
this.locale = attributes.locale || "";
this.maxH = getMeasurement(attributes.maxH, "0pt");
this.maxW = getMeasurement(attributes.maxW, "0pt");
this.mergeMode = getStringOption(attributes.mergeMode, ["consumeData", "matchTemplate"]);
this.minH = getMeasurement(attributes.minH, "0pt");
this.minW = getMeasurement(attributes.minW, "0pt");
this.name = attributes.name || "";
this.presence = getStringOption(attributes.presence, ["visible", "hidden", "inactive", "invisible"]);
this.relevant = getRelevant(attributes.relevant);
this.restoreState = getStringOption(attributes.restoreState, ["manual", "auto"]);
this.scope = getStringOption(attributes.scope, ["name", "none"]);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.w = attributes.w ? getMeasurement(attributes.w) : "";
this.x = getMeasurement(attributes.x, "0pt");
this.y = getMeasurement(attributes.y, "0pt");
this.assist = null;
this.bind = null;
this.bookend = null;
this.border = null;
this.break = null;
this.calculate = null;
this.desc = null;
this.extras = null;
this.keep = null;
this.margin = null;
this.occur = null;
this.overflow = null;
this.pageSet = null;
this.para = null;
this.traversal = null;
this.validate = null;
this.variables = null;
this.area = new XFAObjectArray();
this.breakAfter = new XFAObjectArray();
this.breakBefore = new XFAObjectArray();
this.connect = new XFAObjectArray();
this.draw = new XFAObjectArray();
this.event = new XFAObjectArray();
this.exObject = new XFAObjectArray();
this.exclGroup = new XFAObjectArray();
this.field = new XFAObjectArray();
this.proto = new XFAObjectArray();
this.setProperty = new XFAObjectArray();
this.subform = new XFAObjectArray();
this.subformSet = new XFAObjectArray();
}
[$getSubformParent]() {
const parent = this[$getParent]();
if (parent instanceof SubformSet) {
return parent[$getSubformParent]();
}
return parent;
}
[$isBindable]() {
return true;
}
[$isThereMoreWidth]() {
return this.layout.endsWith("-tb") && this[$extra].attempt === 0 && this[$extra].numberInLine > 0 || this[$getParent]()[$isThereMoreWidth]();
}
*[$getContainedChildren]() {
yield* getContainedChildren(this);
}
[$flushHTML]() {
return flushHTML(this);
}
[$addHTML](html, bbox) {
addHTML(this, html, bbox);
}
[$getAvailableSpace]() {
return getAvailableSpace(this);
}
[$isSplittable]() {
const parent = this[$getSubformParent]();
if (!parent[$isSplittable]()) {
return false;
}
if (this[$extra]._isSplittable !== undefined) {
return this[$extra]._isSplittable;
}
if (this.layout === "position" || this.layout.includes("row")) {
this[$extra]._isSplittable = false;
return false;
}
if (this.keep && this.keep.intact !== "none") {
this[$extra]._isSplittable = false;
return false;
}
if (parent.layout?.endsWith("-tb") && parent[$extra].numberInLine !== 0) {
return false;
}
this[$extra]._isSplittable = true;
return true;
}
[$toHTML](availableSpace) {
setTabIndex(this);
if (this.break) {
if (this.break.after !== "auto" || this.break.afterTarget !== "") {
const node = new BreakAfter({
targetType: this.break.after,
target: this.break.afterTarget,
startNew: this.break.startNew.toString()
});
node[$globalData] = this[$globalData];
this[$appendChild](node);
this.breakAfter.push(node);
}
if (this.break.before !== "auto" || this.break.beforeTarget !== "") {
const node = new BreakBefore({
targetType: this.break.before,
target: this.break.beforeTarget,
startNew: this.break.startNew.toString()
});
node[$globalData] = this[$globalData];
this[$appendChild](node);
this.breakBefore.push(node);
}
if (this.break.overflowTarget !== "") {
const node = new Overflow({
target: this.break.overflowTarget,
leader: this.break.overflowLeader,
trailer: this.break.overflowTrailer
});
node[$globalData] = this[$globalData];
this[$appendChild](node);
this.overflow.push(node);
}
this[$removeChild](this.break);
this.break = null;
}
if (this.presence === "hidden" || this.presence === "inactive") {
return HTMLResult.EMPTY;
}
if (this.breakBefore.children.length > 1 || this.breakAfter.children.length > 1) {
warn("XFA - Several breakBefore or breakAfter in subforms: please file a bug.");
}
if (this.breakBefore.children.length >= 1) {
const breakBefore = this.breakBefore.children[0];
if (handleBreak(breakBefore)) {
return HTMLResult.breakNode(breakBefore);
}
}
if (this[$extra]?.afterBreakAfter) {
return HTMLResult.EMPTY;
}
fixDimensions(this);
const children = [];
const attributes = {
id: this[$uid],
class: []
};
setAccess(this, attributes.class);
if (!this[$extra]) {
this[$extra] = Object.create(null);
}
Object.assign(this[$extra], {
children,
line: null,
attributes,
attempt: 0,
numberInLine: 0,
availableSpace: {
width: Math.min(this.w || Infinity, availableSpace.width),
height: Math.min(this.h || Infinity, availableSpace.height)
},
width: 0,
height: 0,
prevHeight: 0,
currentWidth: 0
});
const root = this[$getTemplateRoot]();
const savedNoLayoutFailure = root[$extra].noLayoutFailure;
const isSplittable = this[$isSplittable]();
if (!isSplittable) {
setFirstUnsplittable(this);
}
if (!checkDimensions(this, availableSpace)) {
return HTMLResult.FAILURE;
}
const filter = new Set(["area", "draw", "exclGroup", "field", "subform", "subformSet"]);
if (this.layout.includes("row")) {
const columnWidths = this[$getSubformParent]().columnWidths;
if (Array.isArray(columnWidths) && columnWidths.length > 0) {
this[$extra].columnWidths = columnWidths;
this[$extra].currentColumn = 0;
}
}
const style = toStyle(this, "anchorType", "dimensions", "position", "presence", "border", "margin", "hAlign");
const classNames = ["xfaSubform"];
const cl = layoutClass(this);
if (cl) {
classNames.push(cl);
}
attributes.style = style;
attributes.class = classNames;
if (this.name) {
attributes.xfaName = this.name;
}
if (this.overflow) {
const overflowExtra = this.overflow[$getExtra]();
if (overflowExtra.addLeader) {
overflowExtra.addLeader = false;
handleOverflow(this, overflowExtra.leader, availableSpace);
}
}
this[$pushPara]();
const isLrTb = this.layout === "lr-tb" || this.layout === "rl-tb";
const maxRun = isLrTb ? MAX_ATTEMPTS_FOR_LRTB_LAYOUT : 1;
for (; this[$extra].attempt < maxRun; this[$extra].attempt++) {
if (isLrTb && this[$extra].attempt === MAX_ATTEMPTS_FOR_LRTB_LAYOUT - 1) {
this[$extra].numberInLine = 0;
}
const result = this[$childrenToHTML]({
filter,
include: true
});
if (result.success) {
break;
}
if (result.isBreak()) {
this[$popPara]();
return result;
}
if (isLrTb && this[$extra].attempt === 0 && this[$extra].numberInLine === 0 && !root[$extra].noLayoutFailure) {
this[$extra].attempt = maxRun;
break;
}
}
this[$popPara]();
if (!isSplittable) {
unsetFirstUnsplittable(this);
}
root[$extra].noLayoutFailure = savedNoLayoutFailure;
if (this[$extra].attempt === maxRun) {
if (this.overflow) {
this[$getTemplateRoot]()[$extra].overflowNode = this.overflow;
}
if (!isSplittable) {
delete this[$extra];
}
return HTMLResult.FAILURE;
}
if (this.overflow) {
const overflowExtra = this.overflow[$getExtra]();
if (overflowExtra.addTrailer) {
overflowExtra.addTrailer = false;
handleOverflow(this, overflowExtra.trailer, availableSpace);
}
}
let marginH = 0;
let marginV = 0;
if (this.margin) {
marginH = this.margin.leftInset + this.margin.rightInset;
marginV = this.margin.topInset + this.margin.bottomInset;
}
const width = Math.max(this[$extra].width + marginH, this.w || 0);
const height = Math.max(this[$extra].height + marginV, this.h || 0);
const bbox = [this.x, this.y, width, height];
if (this.w === "") {
style.width = measureToString(width);
}
if (this.h === "") {
style.height = measureToString(height);
}
if ((style.width === "0px" || style.height === "0px") && children.length === 0) {
return HTMLResult.EMPTY;
}
const html = {
name: "div",
attributes,
children
};
applyAssist(this, attributes);
const result = HTMLResult.success(createWrapper(this, html), bbox);
if (this.breakAfter.children.length >= 1) {
const breakAfter = this.breakAfter.children[0];
if (handleBreak(breakAfter)) {
this[$extra].afterBreakAfter = result;
return HTMLResult.breakNode(breakAfter);
}
}
delete this[$extra];
return result;
}
}
class SubformSet extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "subformSet", true);
this.id = attributes.id || "";
this.name = attributes.name || "";
this.relation = getStringOption(attributes.relation, ["ordered", "choice", "unordered"]);
this.relevant = getRelevant(attributes.relevant);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.bookend = null;
this.break = null;
this.desc = null;
this.extras = null;
this.occur = null;
this.overflow = null;
this.breakAfter = new XFAObjectArray();
this.breakBefore = new XFAObjectArray();
this.subform = new XFAObjectArray();
this.subformSet = new XFAObjectArray();
}
*[$getContainedChildren]() {
yield* getContainedChildren(this);
}
[$getSubformParent]() {
let parent = this[$getParent]();
while (!(parent instanceof Subform)) {
parent = parent[$getParent]();
}
return parent;
}
[$isBindable]() {
return true;
}
}
class SubjectDN extends ContentObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "subjectDN");
this.delimiter = attributes.delimiter || ",";
this.id = attributes.id || "";
this.name = attributes.name || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
[$finalize]() {
this[$content] = new Map(this[$content].split(this.delimiter).map(kv => {
kv = kv.split("=", 2);
kv[0] = kv[0].trim();
return kv;
}));
}
}
class SubjectDNs extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "subjectDNs", true);
this.id = attributes.id || "";
this.type = getStringOption(attributes.type, ["optional", "required"]);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.subjectDN = new XFAObjectArray();
}
}
class Submit extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "submit", true);
this.embedPDF = getInteger({
data: attributes.embedPDF,
defaultValue: 0,
validate: x => x === 1
});
this.format = getStringOption(attributes.format, ["xdp", "formdata", "pdf", "urlencoded", "xfd", "xml"]);
this.id = attributes.id || "";
this.target = attributes.target || "";
this.textEncoding = getKeyword({
data: attributes.textEncoding ? attributes.textEncoding.toLowerCase() : "",
defaultValue: "",
validate: k => ["utf-8", "big-five", "fontspecific", "gbk", "gb-18030", "gb-2312", "ksc-5601", "none", "shift-jis", "ucs-2", "utf-16"].includes(k) || k.match(/iso-8859-\d{2}/)
});
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.xdpContent = attributes.xdpContent || "";
this.encrypt = null;
this.encryptData = new XFAObjectArray();
this.signData = new XFAObjectArray();
}
}
class Template extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "template", true);
this.baseProfile = getStringOption(attributes.baseProfile, ["full", "interactiveForms"]);
this.extras = null;
this.subform = new XFAObjectArray();
}
[$finalize]() {
if (this.subform.children.length === 0) {
warn("XFA - No subforms in template node.");
}
if (this.subform.children.length >= 2) {
warn("XFA - Several subforms in template node: please file a bug.");
}
this[$tabIndex] = DEFAULT_TAB_INDEX;
}
[$isSplittable]() {
return true;
}
[$searchNode](expr, container) {
if (expr.startsWith("#")) {
return [this[$ids].get(expr.slice(1))];
}
return searchNode(this, container, expr, true, true);
}
*[$toPages]() {
if (!this.subform.children.length) {
return HTMLResult.success({
name: "div",
children: []
});
}
this[$extra] = {
overflowNode: null,
firstUnsplittable: null,
currentContentArea: null,
currentPageArea: null,
noLayoutFailure: false,
pageNumber: 1,
pagePosition: "first",
oddOrEven: "odd",
blankOrNotBlank: "nonBlank",
paraStack: []
};
const root = this.subform.children[0];
root.pageSet[$cleanPage]();
const pageAreas = root.pageSet.pageArea.children;
const mainHtml = {
name: "div",
children: []
};
let pageArea = null;
let breakBefore = null;
let breakBeforeTarget = null;
if (root.breakBefore.children.length >= 1) {
breakBefore = root.breakBefore.children[0];
breakBeforeTarget = breakBefore.target;
} else if (root.subform.children.length >= 1 && root.subform.children[0].breakBefore.children.length >= 1) {
breakBefore = root.subform.children[0].breakBefore.children[0];
breakBeforeTarget = breakBefore.target;
} else if (root.break?.beforeTarget) {
breakBefore = root.break;
breakBeforeTarget = breakBefore.beforeTarget;
} else if (root.subform.children.length >= 1 && root.subform.children[0].break?.beforeTarget) {
breakBefore = root.subform.children[0].break;
breakBeforeTarget = breakBefore.beforeTarget;
}
if (breakBefore) {
const target = this[$searchNode](breakBeforeTarget, breakBefore[$getParent]());
if (target instanceof PageArea) {
pageArea = target;
breakBefore[$extra] = {};
}
}
if (!pageArea) {
pageArea = pageAreas[0];
}
pageArea[$extra] = {
numberOfUse: 1
};
const pageAreaParent = pageArea[$getParent]();
pageAreaParent[$extra] = {
numberOfUse: 1,
pageIndex: pageAreaParent.pageArea.children.indexOf(pageArea),
pageSetIndex: 0
};
let targetPageArea;
let leader = null;
let trailer = null;
let hasSomething = true;
let hasSomethingCounter = 0;
let startIndex = 0;
while (true) {
if (!hasSomething) {
mainHtml.children.pop();
if (++hasSomethingCounter === MAX_EMPTY_PAGES) {
warn("XFA - Something goes wrong: please file a bug.");
return mainHtml;
}
} else {
hasSomethingCounter = 0;
}
targetPageArea = null;
this[$extra].currentPageArea = pageArea;
const page = pageArea[$toHTML]().html;
mainHtml.children.push(page);
if (leader) {
this[$extra].noLayoutFailure = true;
page.children.push(leader[$toHTML](pageArea[$extra].space).html);
leader = null;
}
if (trailer) {
this[$extra].noLayoutFailure = true;
page.children.push(trailer[$toHTML](pageArea[$extra].space).html);
trailer = null;
}
const contentAreas = pageArea.contentArea.children;
const htmlContentAreas = page.children.filter(node => node.attributes.class.includes("xfaContentarea"));
hasSomething = false;
this[$extra].firstUnsplittable = null;
this[$extra].noLayoutFailure = false;
const flush = index => {
const html = root[$flushHTML]();
if (html) {
hasSomething ||= html.children?.length > 0;
htmlContentAreas[index].children.push(html);
}
};
for (let i = startIndex, ii = contentAreas.length; i < ii; i++) {
const contentArea = this[$extra].currentContentArea = contentAreas[i];
const space = {
width: contentArea.w,
height: contentArea.h
};
startIndex = 0;
if (leader) {
htmlContentAreas[i].children.push(leader[$toHTML](space).html);
leader = null;
}
if (trailer) {
htmlContentAreas[i].children.push(trailer[$toHTML](space).html);
trailer = null;
}
const html = root[$toHTML](space);
if (html.success) {
if (html.html) {
hasSomething ||= html.html.children?.length > 0;
htmlContentAreas[i].children.push(html.html);
} else if (!hasSomething && mainHtml.children.length > 1) {
mainHtml.children.pop();
}
return mainHtml;
}
if (html.isBreak()) {
const node = html.breakNode;
flush(i);
if (node.targetType === "auto") {
continue;
}
if (node.leader) {
leader = this[$searchNode](node.leader, node[$getParent]());
leader = leader ? leader[0] : null;
}
if (node.trailer) {
trailer = this[$searchNode](node.trailer, node[$getParent]());
trailer = trailer ? trailer[0] : null;
}
if (node.targetType === "pageArea") {
targetPageArea = node[$extra].target;
i = Infinity;
} else if (!node[$extra].target) {
i = node[$extra].index;
} else {
targetPageArea = node[$extra].target;
startIndex = node[$extra].index + 1;
i = Infinity;
}
continue;
}
if (this[$extra].overflowNode) {
const node = this[$extra].overflowNode;
this[$extra].overflowNode = null;
const overflowExtra = node[$getExtra]();
const target = overflowExtra.target;
overflowExtra.addLeader = overflowExtra.leader !== null;
overflowExtra.addTrailer = overflowExtra.trailer !== null;
flush(i);
const currentIndex = i;
i = Infinity;
if (target instanceof PageArea) {
targetPageArea = target;
} else if (target instanceof ContentArea) {
const index = contentAreas.indexOf(target);
if (index !== -1) {
if (index > currentIndex) {
i = index - 1;
} else {
startIndex = index;
}
} else {
targetPageArea = target[$getParent]();
startIndex = targetPageArea.contentArea.children.indexOf(target);
}
}
continue;
}
flush(i);
}
this[$extra].pageNumber += 1;
if (targetPageArea) {
if (targetPageArea[$isUsable]()) {
targetPageArea[$extra].numberOfUse += 1;
} else {
targetPageArea = null;
}
}
pageArea = targetPageArea || pageArea[$getNextPage]();
yield null;
}
}
}
class Text extends ContentObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "text");
this.id = attributes.id || "";
this.maxChars = getInteger({
data: attributes.maxChars,
defaultValue: 0,
validate: x => x >= 0
});
this.name = attributes.name || "";
this.rid = attributes.rid || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
[$acceptWhitespace]() {
return true;
}
[$onChild](child) {
if (child[$namespaceId] === NamespaceIds.xhtml.id) {
this[$content] = child;
return true;
}
warn(`XFA - Invalid content in Text: ${child[$nodeName]}.`);
return false;
}
[$onText](str) {
if (this[$content] instanceof XFAObject) {
return;
}
super[$onText](str);
}
[$finalize]() {
if (typeof this[$content] === "string") {
this[$content] = this[$content].replaceAll("\r\n", "\n");
}
}
[$getExtra]() {
if (typeof this[$content] === "string") {
return this[$content].split(/[\u2029\u2028\n]/).filter(line => !!line).join("\n");
}
return this[$content][$text]();
}
[$toHTML](availableSpace) {
if (typeof this[$content] === "string") {
const html = valueToHtml(this[$content]).html;
if (this[$content].includes("\u2029")) {
html.name = "div";
html.children = [];
this[$content].split("\u2029").map(para => para.split(/[\u2028\n]/).flatMap(line => [{
name: "span",
value: line
}, {
name: "br"
}])).forEach(lines => {
html.children.push({
name: "p",
children: lines
});
});
} else if (/[\u2028\n]/.test(this[$content])) {
html.name = "div";
html.children = [];
this[$content].split(/[\u2028\n]/).forEach(line => {
html.children.push({
name: "span",
value: line
}, {
name: "br"
});
});
}
return HTMLResult.success(html);
}
return this[$content][$toHTML](availableSpace);
}
}
class TextEdit extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "textEdit", true);
this.allowRichText = getInteger({
data: attributes.allowRichText,
defaultValue: 0,
validate: x => x === 1
});
this.hScrollPolicy = getStringOption(attributes.hScrollPolicy, ["auto", "off", "on"]);
this.id = attributes.id || "";
this.multiLine = getInteger({
data: attributes.multiLine,
defaultValue: "",
validate: x => x === 0 || x === 1
});
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.vScrollPolicy = getStringOption(attributes.vScrollPolicy, ["auto", "off", "on"]);
this.border = null;
this.comb = null;
this.extras = null;
this.margin = null;
}
[$toHTML](availableSpace) {
const style = toStyle(this, "border", "font", "margin");
let html;
const field = this[$getParent]()[$getParent]();
if (this.multiLine === "") {
this.multiLine = field instanceof Draw ? 1 : 0;
}
if (this.multiLine === 1) {
html = {
name: "textarea",
attributes: {
dataId: field[$data]?.[$uid] || field[$uid],
fieldId: field[$uid],
class: ["xfaTextfield"],
style,
"aria-label": ariaLabel(field),
"aria-required": false
}
};
} else {
html = {
name: "input",
attributes: {
type: "text",
dataId: field[$data]?.[$uid] || field[$uid],
fieldId: field[$uid],
class: ["xfaTextfield"],
style,
"aria-label": ariaLabel(field),
"aria-required": false
}
};
}
if (isRequired(field)) {
html.attributes["aria-required"] = true;
html.attributes.required = true;
}
return HTMLResult.success({
name: "label",
attributes: {
class: ["xfaLabel"]
},
children: [html]
});
}
}
class Time extends StringObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "time");
this.id = attributes.id || "";
this.name = attributes.name || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
[$finalize]() {
const date = this[$content].trim();
this[$content] = date ? new Date(date) : null;
}
[$toHTML](availableSpace) {
return valueToHtml(this[$content] ? this[$content].toString() : "");
}
}
class TimeStamp extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "timeStamp");
this.id = attributes.id || "";
this.server = attributes.server || "";
this.type = getStringOption(attributes.type, ["optional", "required"]);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
}
class ToolTip extends StringObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "toolTip");
this.id = attributes.id || "";
this.rid = attributes.rid || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
}
class Traversal extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "traversal", true);
this.id = attributes.id || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.extras = null;
this.traverse = new XFAObjectArray();
}
}
class Traverse extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "traverse", true);
this.id = attributes.id || "";
this.operation = getStringOption(attributes.operation, ["next", "back", "down", "first", "left", "right", "up"]);
this.ref = attributes.ref || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.extras = null;
this.script = null;
}
get name() {
return this.operation;
}
[$isTransparent]() {
return false;
}
}
class Ui extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "ui", true);
this.id = attributes.id || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.extras = null;
this.picture = null;
this.barcode = null;
this.button = null;
this.checkButton = null;
this.choiceList = null;
this.dateTimeEdit = null;
this.defaultUi = null;
this.imageEdit = null;
this.numericEdit = null;
this.passwordEdit = null;
this.signature = null;
this.textEdit = null;
}
[$getExtra]() {
if (this[$extra] === undefined) {
for (const name of Object.getOwnPropertyNames(this)) {
if (name === "extras" || name === "picture") {
continue;
}
const obj = this[name];
if (!(obj instanceof XFAObject)) {
continue;
}
this[$extra] = obj;
return obj;
}
this[$extra] = null;
}
return this[$extra];
}
[$toHTML](availableSpace) {
const obj = this[$getExtra]();
if (obj) {
return obj[$toHTML](availableSpace);
}
return HTMLResult.EMPTY;
}
}
class Validate extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "validate", true);
this.formatTest = getStringOption(attributes.formatTest, ["warning", "disabled", "error"]);
this.id = attributes.id || "";
this.nullTest = getStringOption(attributes.nullTest, ["disabled", "error", "warning"]);
this.scriptTest = getStringOption(attributes.scriptTest, ["error", "disabled", "warning"]);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.extras = null;
this.message = null;
this.picture = null;
this.script = null;
}
}
class Value extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "value", true);
this.id = attributes.id || "";
this.override = getInteger({
data: attributes.override,
defaultValue: 0,
validate: x => x === 1
});
this.relevant = getRelevant(attributes.relevant);
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.arc = null;
this.boolean = null;
this.date = null;
this.dateTime = null;
this.decimal = null;
this.exData = null;
this.float = null;
this.image = null;
this.integer = null;
this.line = null;
this.rectangle = null;
this.text = null;
this.time = null;
}
[$setValue](value) {
const parent = this[$getParent]();
if (parent instanceof Field) {
if (parent.ui?.imageEdit) {
if (!this.image) {
this.image = new Image({});
this[$appendChild](this.image);
}
this.image[$content] = value[$content];
return;
}
}
const valueName = value[$nodeName];
if (this[valueName] !== null) {
this[valueName][$content] = value[$content];
return;
}
for (const name of Object.getOwnPropertyNames(this)) {
const obj = this[name];
if (obj instanceof XFAObject) {
this[name] = null;
this[$removeChild](obj);
}
}
this[value[$nodeName]] = value;
this[$appendChild](value);
}
[$text]() {
if (this.exData) {
if (typeof this.exData[$content] === "string") {
return this.exData[$content].trim();
}
return this.exData[$content][$text]().trim();
}
for (const name of Object.getOwnPropertyNames(this)) {
if (name === "image") {
continue;
}
const obj = this[name];
if (obj instanceof XFAObject) {
return (obj[$content] || "").toString().trim();
}
}
return null;
}
[$toHTML](availableSpace) {
for (const name of Object.getOwnPropertyNames(this)) {
const obj = this[name];
if (!(obj instanceof XFAObject)) {
continue;
}
return obj[$toHTML](availableSpace);
}
return HTMLResult.EMPTY;
}
}
class Variables extends XFAObject {
constructor(attributes) {
super(TEMPLATE_NS_ID, "variables", true);
this.id = attributes.id || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
this.boolean = new XFAObjectArray();
this.date = new XFAObjectArray();
this.dateTime = new XFAObjectArray();
this.decimal = new XFAObjectArray();
this.exData = new XFAObjectArray();
this.float = new XFAObjectArray();
this.image = new XFAObjectArray();
this.integer = new XFAObjectArray();
this.manifest = new XFAObjectArray();
this.script = new XFAObjectArray();
this.text = new XFAObjectArray();
this.time = new XFAObjectArray();
}
[$isTransparent]() {
return true;
}
}
class TemplateNamespace {
static [$buildXFAObject](name, attributes) {
if (TemplateNamespace.hasOwnProperty(name)) {
const node = TemplateNamespace[name](attributes);
node[$setSetAttributes](attributes);
return node;
}
return undefined;
}
static appearanceFilter(attrs) {
return new AppearanceFilter(attrs);
}
static arc(attrs) {
return new Arc(attrs);
}
static area(attrs) {
return new Area(attrs);
}
static assist(attrs) {
return new Assist(attrs);
}
static barcode(attrs) {
return new Barcode(attrs);
}
static bind(attrs) {
return new Bind(attrs);
}
static bindItems(attrs) {
return new BindItems(attrs);
}
static bookend(attrs) {
return new Bookend(attrs);
}
static boolean(attrs) {
return new BooleanElement(attrs);
}
static border(attrs) {
return new Border(attrs);
}
static break(attrs) {
return new Break(attrs);
}
static breakAfter(attrs) {
return new BreakAfter(attrs);
}
static breakBefore(attrs) {
return new BreakBefore(attrs);
}
static button(attrs) {
return new Button(attrs);
}
static calculate(attrs) {
return new Calculate(attrs);
}
static caption(attrs) {
return new Caption(attrs);
}
static certificate(attrs) {
return new Certificate(attrs);
}
static certificates(attrs) {
return new Certificates(attrs);
}
static checkButton(attrs) {
return new CheckButton(attrs);
}
static choiceList(attrs) {
return new ChoiceList(attrs);
}
static color(attrs) {
return new Color(attrs);
}
static comb(attrs) {
return new Comb(attrs);
}
static connect(attrs) {
return new Connect(attrs);
}
static contentArea(attrs) {
return new ContentArea(attrs);
}
static corner(attrs) {
return new Corner(attrs);
}
static date(attrs) {
return new DateElement(attrs);
}
static dateTime(attrs) {
return new DateTime(attrs);
}
static dateTimeEdit(attrs) {
return new DateTimeEdit(attrs);
}
static decimal(attrs) {
return new Decimal(attrs);
}
static defaultUi(attrs) {
return new DefaultUi(attrs);
}
static desc(attrs) {
return new Desc(attrs);
}
static digestMethod(attrs) {
return new DigestMethod(attrs);
}
static digestMethods(attrs) {
return new DigestMethods(attrs);
}
static draw(attrs) {
return new Draw(attrs);
}
static edge(attrs) {
return new Edge(attrs);
}
static encoding(attrs) {
return new Encoding(attrs);
}
static encodings(attrs) {
return new Encodings(attrs);
}
static encrypt(attrs) {
return new Encrypt(attrs);
}
static encryptData(attrs) {
return new EncryptData(attrs);
}
static encryption(attrs) {
return new Encryption(attrs);
}
static encryptionMethod(attrs) {
return new EncryptionMethod(attrs);
}
static encryptionMethods(attrs) {
return new EncryptionMethods(attrs);
}
static event(attrs) {
return new Event(attrs);
}
static exData(attrs) {
return new ExData(attrs);
}
static exObject(attrs) {
return new ExObject(attrs);
}
static exclGroup(attrs) {
return new ExclGroup(attrs);
}
static execute(attrs) {
return new Execute(attrs);
}
static extras(attrs) {
return new Extras(attrs);
}
static field(attrs) {
return new Field(attrs);
}
static fill(attrs) {
return new Fill(attrs);
}
static filter(attrs) {
return new Filter(attrs);
}
static float(attrs) {
return new Float(attrs);
}
static font(attrs) {
return new template_Font(attrs);
}
static format(attrs) {
return new Format(attrs);
}
static handler(attrs) {
return new Handler(attrs);
}
static hyphenation(attrs) {
return new Hyphenation(attrs);
}
static image(attrs) {
return new Image(attrs);
}
static imageEdit(attrs) {
return new ImageEdit(attrs);
}
static integer(attrs) {
return new Integer(attrs);
}
static issuers(attrs) {
return new Issuers(attrs);
}
static items(attrs) {
return new Items(attrs);
}
static keep(attrs) {
return new Keep(attrs);
}
static keyUsage(attrs) {
return new KeyUsage(attrs);
}
static line(attrs) {
return new Line(attrs);
}
static linear(attrs) {
return new Linear(attrs);
}
static lockDocument(attrs) {
return new LockDocument(attrs);
}
static manifest(attrs) {
return new Manifest(attrs);
}
static margin(attrs) {
return new Margin(attrs);
}
static mdp(attrs) {
return new Mdp(attrs);
}
static medium(attrs) {
return new Medium(attrs);
}
static message(attrs) {
return new Message(attrs);
}
static numericEdit(attrs) {
return new NumericEdit(attrs);
}
static occur(attrs) {
return new Occur(attrs);
}
static oid(attrs) {
return new Oid(attrs);
}
static oids(attrs) {
return new Oids(attrs);
}
static overflow(attrs) {
return new Overflow(attrs);
}
static pageArea(attrs) {
return new PageArea(attrs);
}
static pageSet(attrs) {
return new PageSet(attrs);
}
static para(attrs) {
return new Para(attrs);
}
static passwordEdit(attrs) {
return new PasswordEdit(attrs);
}
static pattern(attrs) {
return new template_Pattern(attrs);
}
static picture(attrs) {
return new Picture(attrs);
}
static proto(attrs) {
return new Proto(attrs);
}
static radial(attrs) {
return new Radial(attrs);
}
static reason(attrs) {
return new Reason(attrs);
}
static reasons(attrs) {
return new Reasons(attrs);
}
static rectangle(attrs) {
return new Rectangle(attrs);
}
static ref(attrs) {
return new RefElement(attrs);
}
static script(attrs) {
return new Script(attrs);
}
static setProperty(attrs) {
return new SetProperty(attrs);
}
static signData(attrs) {
return new SignData(attrs);
}
static signature(attrs) {
return new Signature(attrs);
}
static signing(attrs) {
return new Signing(attrs);
}
static solid(attrs) {
return new Solid(attrs);
}
static speak(attrs) {
return new Speak(attrs);
}
static stipple(attrs) {
return new Stipple(attrs);
}
static subform(attrs) {
return new Subform(attrs);
}
static subformSet(attrs) {
return new SubformSet(attrs);
}
static subjectDN(attrs) {
return new SubjectDN(attrs);
}
static subjectDNs(attrs) {
return new SubjectDNs(attrs);
}
static submit(attrs) {
return new Submit(attrs);
}
static template(attrs) {
return new Template(attrs);
}
static text(attrs) {
return new Text(attrs);
}
static textEdit(attrs) {
return new TextEdit(attrs);
}
static time(attrs) {
return new Time(attrs);
}
static timeStamp(attrs) {
return new TimeStamp(attrs);
}
static toolTip(attrs) {
return new ToolTip(attrs);
}
static traversal(attrs) {
return new Traversal(attrs);
}
static traverse(attrs) {
return new Traverse(attrs);
}
static ui(attrs) {
return new Ui(attrs);
}
static validate(attrs) {
return new Validate(attrs);
}
static value(attrs) {
return new Value(attrs);
}
static variables(attrs) {
return new Variables(attrs);
}
}
;// ./src/core/xfa/bind.js
const bind_NS_DATASETS = NamespaceIds.datasets.id;
function createText(content) {
const node = new Text({});
node[$content] = content;
return node;
}
class Binder {
constructor(root) {
this.root = root;
this.datasets = root.datasets;
this.data = root.datasets?.data || new XmlObject(NamespaceIds.datasets.id, "data");
this.emptyMerge = this.data[$getChildren]().length === 0;
this.root.form = this.form = root.template[$clone]();
}
_isConsumeData() {
return !this.emptyMerge && this._mergeMode;
}
_isMatchTemplate() {
return !this._isConsumeData();
}
bind() {
this._bindElement(this.form, this.data);
return this.form;
}
getData() {
return this.data;
}
_bindValue(formNode, data, picture) {
formNode[$data] = data;
if (formNode[$hasSettableValue]()) {
if (data[$isDataValue]()) {
const value = data[$getDataValue]();
formNode[$setValue](createText(value));
} else if (formNode instanceof Field && formNode.ui?.choiceList?.open === "multiSelect") {
const value = data[$getChildren]().map(child => child[$content].trim()).join("\n");
formNode[$setValue](createText(value));
} else if (this._isConsumeData()) {
warn(`XFA - Nodes haven't the same type.`);
}
} else if (!data[$isDataValue]() || this._isMatchTemplate()) {
this._bindElement(formNode, data);
} else {
warn(`XFA - Nodes haven't the same type.`);
}
}
_findDataByNameToConsume(name, isValue, dataNode, global) {
if (!name) {
return null;
}
let generator, match;
for (let i = 0; i < 3; i++) {
generator = dataNode[$getRealChildrenByNameIt](name, false, true);
while (true) {
match = generator.next().value;
if (!match) {
break;
}
if (isValue === match[$isDataValue]()) {
return match;
}
}
if (dataNode[$namespaceId] === NamespaceIds.datasets.id && dataNode[$nodeName] === "data") {
break;
}
dataNode = dataNode[$getParent]();
}
if (!global) {
return null;
}
generator = this.data[$getRealChildrenByNameIt](name, true, false);
match = generator.next().value;
if (match) {
return match;
}
generator = this.data[$getAttributeIt](name, true);
match = generator.next().value;
if (match?.[$isDataValue]()) {
return match;
}
return null;
}
_setProperties(formNode, dataNode) {
if (!formNode.hasOwnProperty("setProperty")) {
return;
}
for (const {
ref,
target,
connection
} of formNode.setProperty.children) {
if (connection) {
continue;
}
if (!ref) {
continue;
}
const nodes = searchNode(this.root, dataNode, ref, false, false);
if (!nodes) {
warn(`XFA - Invalid reference: ${ref}.`);
continue;
}
const [node] = nodes;
if (!node[$isDescendent](this.data)) {
warn(`XFA - Invalid node: must be a data node.`);
continue;
}
const targetNodes = searchNode(this.root, formNode, target, false, false);
if (!targetNodes) {
warn(`XFA - Invalid target: ${target}.`);
continue;
}
const [targetNode] = targetNodes;
if (!targetNode[$isDescendent](formNode)) {
warn(`XFA - Invalid target: must be a property or subproperty.`);
continue;
}
const targetParent = targetNode[$getParent]();
if (targetNode instanceof SetProperty || targetParent instanceof SetProperty) {
warn(`XFA - Invalid target: cannot be a setProperty or one of its properties.`);
continue;
}
if (targetNode instanceof BindItems || targetParent instanceof BindItems) {
warn(`XFA - Invalid target: cannot be a bindItems or one of its properties.`);
continue;
}
const content = node[$text]();
const name = targetNode[$nodeName];
if (targetNode instanceof XFAAttribute) {
const attrs = Object.create(null);
attrs[name] = content;
const obj = Reflect.construct(Object.getPrototypeOf(targetParent).constructor, [attrs]);
targetParent[name] = obj[name];
continue;
}
if (!targetNode.hasOwnProperty($content)) {
warn(`XFA - Invalid node to use in setProperty`);
continue;
}
targetNode[$data] = node;
targetNode[$content] = content;
targetNode[$finalize]();
}
}
_bindItems(formNode, dataNode) {
if (!formNode.hasOwnProperty("items") || !formNode.hasOwnProperty("bindItems") || formNode.bindItems.isEmpty()) {
return;
}
for (const item of formNode.items.children) {
formNode[$removeChild](item);
}
formNode.items.clear();
const labels = new Items({});
const values = new Items({});
formNode[$appendChild](labels);
formNode.items.push(labels);
formNode[$appendChild](values);
formNode.items.push(values);
for (const {
ref,
labelRef,
valueRef,
connection
} of formNode.bindItems.children) {
if (connection) {
continue;
}
if (!ref) {
continue;
}
const nodes = searchNode(this.root, dataNode, ref, false, false);
if (!nodes) {
warn(`XFA - Invalid reference: ${ref}.`);
continue;
}
for (const node of nodes) {
if (!node[$isDescendent](this.datasets)) {
warn(`XFA - Invalid ref (${ref}): must be a datasets child.`);
continue;
}
const labelNodes = searchNode(this.root, node, labelRef, true, false);
if (!labelNodes) {
warn(`XFA - Invalid label: ${labelRef}.`);
continue;
}
const [labelNode] = labelNodes;
if (!labelNode[$isDescendent](this.datasets)) {
warn(`XFA - Invalid label: must be a datasets child.`);
continue;
}
const valueNodes = searchNode(this.root, node, valueRef, true, false);
if (!valueNodes) {
warn(`XFA - Invalid value: ${valueRef}.`);
continue;
}
const [valueNode] = valueNodes;
if (!valueNode[$isDescendent](this.datasets)) {
warn(`XFA - Invalid value: must be a datasets child.`);
continue;
}
const label = createText(labelNode[$text]());
const value = createText(valueNode[$text]());
labels[$appendChild](label);
labels.text.push(label);
values[$appendChild](value);
values.text.push(value);
}
}
}
_bindOccurrences(formNode, matches, picture) {
let baseClone;
if (matches.length > 1) {
baseClone = formNode[$clone]();
baseClone[$removeChild](baseClone.occur);
baseClone.occur = null;
}
this._bindValue(formNode, matches[0], picture);
this._setProperties(formNode, matches[0]);
this._bindItems(formNode, matches[0]);
if (matches.length === 1) {
return;
}
const parent = formNode[$getParent]();
const name = formNode[$nodeName];
const pos = parent[$indexOf](formNode);
for (let i = 1, ii = matches.length; i < ii; i++) {
const match = matches[i];
const clone = baseClone[$clone]();
parent[name].push(clone);
parent[$insertAt](pos + i, clone);
this._bindValue(clone, match, picture);
this._setProperties(clone, match);
this._bindItems(clone, match);
}
}
_createOccurrences(formNode) {
if (!this.emptyMerge) {
return;
}
const {
occur
} = formNode;
if (!occur || occur.initial <= 1) {
return;
}
const parent = formNode[$getParent]();
const name = formNode[$nodeName];
if (!(parent[name] instanceof XFAObjectArray)) {
return;
}
let currentNumber;
if (formNode.name) {
currentNumber = parent[name].children.filter(e => e.name === formNode.name).length;
} else {
currentNumber = parent[name].children.length;
}
const pos = parent[$indexOf](formNode) + 1;
const ii = occur.initial - currentNumber;
if (ii) {
const nodeClone = formNode[$clone]();
nodeClone[$removeChild](nodeClone.occur);
nodeClone.occur = null;
parent[name].push(nodeClone);
parent[$insertAt](pos, nodeClone);
for (let i = 1; i < ii; i++) {
const clone = nodeClone[$clone]();
parent[name].push(clone);
parent[$insertAt](pos + i, clone);
}
}
}
_getOccurInfo(formNode) {
const {
name,
occur
} = formNode;
if (!occur || !name) {
return [1, 1];
}
const max = occur.max === -1 ? Infinity : occur.max;
return [occur.min, max];
}
_setAndBind(formNode, dataNode) {
this._setProperties(formNode, dataNode);
this._bindItems(formNode, dataNode);
this._bindElement(formNode, dataNode);
}
_bindElement(formNode, dataNode) {
const uselessNodes = [];
this._createOccurrences(formNode);
for (const child of formNode[$getChildren]()) {
if (child[$data]) {
continue;
}
if (this._mergeMode === undefined && child[$nodeName] === "subform") {
this._mergeMode = child.mergeMode === "consumeData";
const dataChildren = dataNode[$getChildren]();
if (dataChildren.length > 0) {
this._bindOccurrences(child, [dataChildren[0]], null);
} else if (this.emptyMerge) {
const nsId = dataNode[$namespaceId] === bind_NS_DATASETS ? -1 : dataNode[$namespaceId];
const dataChild = child[$data] = new XmlObject(nsId, child.name || "root");
dataNode[$appendChild](dataChild);
this._bindElement(child, dataChild);
}
continue;
}
if (!child[$isBindable]()) {
continue;
}
let global = false;
let picture = null;
let ref = null;
let match = null;
if (child.bind) {
switch (child.bind.match) {
case "none":
this._setAndBind(child, dataNode);
continue;
case "global":
global = true;
break;
case "dataRef":
if (!child.bind.ref) {
warn(`XFA - ref is empty in node ${child[$nodeName]}.`);
this._setAndBind(child, dataNode);
continue;
}
ref = child.bind.ref;
break;
default:
break;
}
if (child.bind.picture) {
picture = child.bind.picture[$content];
}
}
const [min, max] = this._getOccurInfo(child);
if (ref) {
match = searchNode(this.root, dataNode, ref, true, false);
if (match === null) {
match = createDataNode(this.data, dataNode, ref);
if (!match) {
continue;
}
if (this._isConsumeData()) {
match[$consumed] = true;
}
this._setAndBind(child, match);
continue;
} else {
if (this._isConsumeData()) {
match = match.filter(node => !node[$consumed]);
}
if (match.length > max) {
match = match.slice(0, max);
} else if (match.length === 0) {
match = null;
}
if (match && this._isConsumeData()) {
match.forEach(node => {
node[$consumed] = true;
});
}
}
} else {
if (!child.name) {
this._setAndBind(child, dataNode);
continue;
}
if (this._isConsumeData()) {
const matches = [];
while (matches.length < max) {
const found = this._findDataByNameToConsume(child.name, child[$hasSettableValue](), dataNode, global);
if (!found) {
break;
}
found[$consumed] = true;
matches.push(found);
}
match = matches.length > 0 ? matches : null;
} else {
match = dataNode[$getRealChildrenByNameIt](child.name, false, this.emptyMerge).next().value;
if (!match) {
if (min === 0) {
uselessNodes.push(child);
continue;
}
const nsId = dataNode[$namespaceId] === bind_NS_DATASETS ? -1 : dataNode[$namespaceId];
match = child[$data] = new XmlObject(nsId, child.name);
if (this.emptyMerge) {
match[$consumed] = true;
}
dataNode[$appendChild](match);
this._setAndBind(child, match);
continue;
}
if (this.emptyMerge) {
match[$consumed] = true;
}
match = [match];
}
}
if (match) {
this._bindOccurrences(child, match, picture);
} else if (min > 0) {
this._setAndBind(child, dataNode);
} else {
uselessNodes.push(child);
}
}
uselessNodes.forEach(node => node[$getParent]()[$removeChild](node));
}
}
;// ./src/core/xfa/data.js
class DataHandler {
constructor(root, data) {
this.data = data;
this.dataset = root.datasets || null;
}
serialize(storage) {
const stack = [[-1, this.data[$getChildren]()]];
while (stack.length > 0) {
const last = stack.at(-1);
const [i, children] = last;
if (i + 1 === children.length) {
stack.pop();
continue;
}
const child = children[++last[0]];
const storageEntry = storage.get(child[$uid]);
if (storageEntry) {
child[$setValue](storageEntry);
} else {
const attributes = child[$getAttributes]();
for (const value of attributes.values()) {
const entry = storage.get(value[$uid]);
if (entry) {
value[$setValue](entry);
break;
}
}
}
const nodes = child[$getChildren]();
if (nodes.length > 0) {
stack.push([-1, nodes]);
}
}
const buf = [``];
if (this.dataset) {
for (const child of this.dataset[$getChildren]()) {
if (child[$nodeName] !== "data") {
child[$toString](buf);
}
}
}
this.data[$toString](buf);
buf.push(" ");
return buf.join("");
}
}
;// ./src/core/xfa/config.js
const CONFIG_NS_ID = NamespaceIds.config.id;
class Acrobat extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "acrobat", true);
this.acrobat7 = null;
this.autoSave = null;
this.common = null;
this.validate = null;
this.validateApprovalSignatures = null;
this.submitUrl = new XFAObjectArray();
}
}
class Acrobat7 extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "acrobat7", true);
this.dynamicRender = null;
}
}
class ADBE_JSConsole extends OptionObject {
constructor(attributes) {
super(CONFIG_NS_ID, "ADBE_JSConsole", ["delegate", "Enable", "Disable"]);
}
}
class ADBE_JSDebugger extends OptionObject {
constructor(attributes) {
super(CONFIG_NS_ID, "ADBE_JSDebugger", ["delegate", "Enable", "Disable"]);
}
}
class AddSilentPrint extends Option01 {
constructor(attributes) {
super(CONFIG_NS_ID, "addSilentPrint");
}
}
class AddViewerPreferences extends Option01 {
constructor(attributes) {
super(CONFIG_NS_ID, "addViewerPreferences");
}
}
class AdjustData extends Option10 {
constructor(attributes) {
super(CONFIG_NS_ID, "adjustData");
}
}
class AdobeExtensionLevel extends IntegerObject {
constructor(attributes) {
super(CONFIG_NS_ID, "adobeExtensionLevel", 0, n => n >= 1 && n <= 8);
}
}
class Agent extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "agent", true);
this.name = attributes.name ? attributes.name.trim() : "";
this.common = new XFAObjectArray();
}
}
class AlwaysEmbed extends ContentObject {
constructor(attributes) {
super(CONFIG_NS_ID, "alwaysEmbed");
}
}
class Amd extends StringObject {
constructor(attributes) {
super(CONFIG_NS_ID, "amd");
}
}
class config_Area extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "area");
this.level = getInteger({
data: attributes.level,
defaultValue: 0,
validate: n => n >= 1 && n <= 3
});
this.name = getStringOption(attributes.name, ["", "barcode", "coreinit", "deviceDriver", "font", "general", "layout", "merge", "script", "signature", "sourceSet", "templateCache"]);
}
}
class Attributes extends OptionObject {
constructor(attributes) {
super(CONFIG_NS_ID, "attributes", ["preserve", "delegate", "ignore"]);
}
}
class AutoSave extends OptionObject {
constructor(attributes) {
super(CONFIG_NS_ID, "autoSave", ["disabled", "enabled"]);
}
}
class Base extends StringObject {
constructor(attributes) {
super(CONFIG_NS_ID, "base");
}
}
class BatchOutput extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "batchOutput");
this.format = getStringOption(attributes.format, ["none", "concat", "zip", "zipCompress"]);
}
}
class BehaviorOverride extends ContentObject {
constructor(attributes) {
super(CONFIG_NS_ID, "behaviorOverride");
}
[$finalize]() {
this[$content] = new Map(this[$content].trim().split(/\s+/).filter(x => x.includes(":")).map(x => x.split(":", 2)));
}
}
class Cache extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "cache", true);
this.templateCache = null;
}
}
class Change extends Option01 {
constructor(attributes) {
super(CONFIG_NS_ID, "change");
}
}
class Common extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "common", true);
this.data = null;
this.locale = null;
this.localeSet = null;
this.messaging = null;
this.suppressBanner = null;
this.template = null;
this.validationMessaging = null;
this.versionControl = null;
this.log = new XFAObjectArray();
}
}
class Compress extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "compress");
this.scope = getStringOption(attributes.scope, ["imageOnly", "document"]);
}
}
class CompressLogicalStructure extends Option01 {
constructor(attributes) {
super(CONFIG_NS_ID, "compressLogicalStructure");
}
}
class CompressObjectStream extends Option10 {
constructor(attributes) {
super(CONFIG_NS_ID, "compressObjectStream");
}
}
class Compression extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "compression", true);
this.compressLogicalStructure = null;
this.compressObjectStream = null;
this.level = null;
this.type = null;
}
}
class Config extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "config", true);
this.acrobat = null;
this.present = null;
this.trace = null;
this.agent = new XFAObjectArray();
}
}
class Conformance extends OptionObject {
constructor(attributes) {
super(CONFIG_NS_ID, "conformance", ["A", "B"]);
}
}
class ContentCopy extends Option01 {
constructor(attributes) {
super(CONFIG_NS_ID, "contentCopy");
}
}
class Copies extends IntegerObject {
constructor(attributes) {
super(CONFIG_NS_ID, "copies", 1, n => n >= 1);
}
}
class Creator extends StringObject {
constructor(attributes) {
super(CONFIG_NS_ID, "creator");
}
}
class CurrentPage extends IntegerObject {
constructor(attributes) {
super(CONFIG_NS_ID, "currentPage", 0, n => n >= 0);
}
}
class Data extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "data", true);
this.adjustData = null;
this.attributes = null;
this.incrementalLoad = null;
this.outputXSL = null;
this.range = null;
this.record = null;
this.startNode = null;
this.uri = null;
this.window = null;
this.xsl = null;
this.excludeNS = new XFAObjectArray();
this.transform = new XFAObjectArray();
}
}
class Debug extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "debug", true);
this.uri = null;
}
}
class DefaultTypeface extends ContentObject {
constructor(attributes) {
super(CONFIG_NS_ID, "defaultTypeface");
this.writingScript = getStringOption(attributes.writingScript, ["*", "Arabic", "Cyrillic", "EastEuropeanRoman", "Greek", "Hebrew", "Japanese", "Korean", "Roman", "SimplifiedChinese", "Thai", "TraditionalChinese", "Vietnamese"]);
}
}
class Destination extends OptionObject {
constructor(attributes) {
super(CONFIG_NS_ID, "destination", ["pdf", "pcl", "ps", "webClient", "zpl"]);
}
}
class DocumentAssembly extends Option01 {
constructor(attributes) {
super(CONFIG_NS_ID, "documentAssembly");
}
}
class Driver extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "driver", true);
this.name = attributes.name ? attributes.name.trim() : "";
this.fontInfo = null;
this.xdc = null;
}
}
class DuplexOption extends OptionObject {
constructor(attributes) {
super(CONFIG_NS_ID, "duplexOption", ["simplex", "duplexFlipLongEdge", "duplexFlipShortEdge"]);
}
}
class DynamicRender extends OptionObject {
constructor(attributes) {
super(CONFIG_NS_ID, "dynamicRender", ["forbidden", "required"]);
}
}
class Embed extends Option01 {
constructor(attributes) {
super(CONFIG_NS_ID, "embed");
}
}
class config_Encrypt extends Option01 {
constructor(attributes) {
super(CONFIG_NS_ID, "encrypt");
}
}
class config_Encryption extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "encryption", true);
this.encrypt = null;
this.encryptionLevel = null;
this.permissions = null;
}
}
class EncryptionLevel extends OptionObject {
constructor(attributes) {
super(CONFIG_NS_ID, "encryptionLevel", ["40bit", "128bit"]);
}
}
class Enforce extends StringObject {
constructor(attributes) {
super(CONFIG_NS_ID, "enforce");
}
}
class Equate extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "equate");
this.force = getInteger({
data: attributes.force,
defaultValue: 1,
validate: n => n === 0
});
this.from = attributes.from || "";
this.to = attributes.to || "";
}
}
class EquateRange extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "equateRange");
this.from = attributes.from || "";
this.to = attributes.to || "";
this._unicodeRange = attributes.unicodeRange || "";
}
get unicodeRange() {
const ranges = [];
const unicodeRegex = /U\+([0-9a-fA-F]+)/;
const unicodeRange = this._unicodeRange;
for (let range of unicodeRange.split(",").map(x => x.trim()).filter(x => !!x)) {
range = range.split("-", 2).map(x => {
const found = x.match(unicodeRegex);
if (!found) {
return 0;
}
return parseInt(found[1], 16);
});
if (range.length === 1) {
range.push(range[0]);
}
ranges.push(range);
}
return shadow(this, "unicodeRange", ranges);
}
}
class Exclude extends ContentObject {
constructor(attributes) {
super(CONFIG_NS_ID, "exclude");
}
[$finalize]() {
this[$content] = this[$content].trim().split(/\s+/).filter(x => x && ["calculate", "close", "enter", "exit", "initialize", "ready", "validate"].includes(x));
}
}
class ExcludeNS extends StringObject {
constructor(attributes) {
super(CONFIG_NS_ID, "excludeNS");
}
}
class FlipLabel extends OptionObject {
constructor(attributes) {
super(CONFIG_NS_ID, "flipLabel", ["usePrinterSetting", "on", "off"]);
}
}
class config_FontInfo extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "fontInfo", true);
this.embed = null;
this.map = null;
this.subsetBelow = null;
this.alwaysEmbed = new XFAObjectArray();
this.defaultTypeface = new XFAObjectArray();
this.neverEmbed = new XFAObjectArray();
}
}
class FormFieldFilling extends Option01 {
constructor(attributes) {
super(CONFIG_NS_ID, "formFieldFilling");
}
}
class GroupParent extends StringObject {
constructor(attributes) {
super(CONFIG_NS_ID, "groupParent");
}
}
class IfEmpty extends OptionObject {
constructor(attributes) {
super(CONFIG_NS_ID, "ifEmpty", ["dataValue", "dataGroup", "ignore", "remove"]);
}
}
class IncludeXDPContent extends StringObject {
constructor(attributes) {
super(CONFIG_NS_ID, "includeXDPContent");
}
}
class IncrementalLoad extends OptionObject {
constructor(attributes) {
super(CONFIG_NS_ID, "incrementalLoad", ["none", "forwardOnly"]);
}
}
class IncrementalMerge extends Option01 {
constructor(attributes) {
super(CONFIG_NS_ID, "incrementalMerge");
}
}
class Interactive extends Option01 {
constructor(attributes) {
super(CONFIG_NS_ID, "interactive");
}
}
class Jog extends OptionObject {
constructor(attributes) {
super(CONFIG_NS_ID, "jog", ["usePrinterSetting", "none", "pageSet"]);
}
}
class LabelPrinter extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "labelPrinter", true);
this.name = getStringOption(attributes.name, ["zpl", "dpl", "ipl", "tcpl"]);
this.batchOutput = null;
this.flipLabel = null;
this.fontInfo = null;
this.xdc = null;
}
}
class Layout extends OptionObject {
constructor(attributes) {
super(CONFIG_NS_ID, "layout", ["paginate", "panel"]);
}
}
class Level extends IntegerObject {
constructor(attributes) {
super(CONFIG_NS_ID, "level", 0, n => n > 0);
}
}
class Linearized extends Option01 {
constructor(attributes) {
super(CONFIG_NS_ID, "linearized");
}
}
class Locale extends StringObject {
constructor(attributes) {
super(CONFIG_NS_ID, "locale");
}
}
class LocaleSet extends StringObject {
constructor(attributes) {
super(CONFIG_NS_ID, "localeSet");
}
}
class Log extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "log", true);
this.mode = null;
this.threshold = null;
this.to = null;
this.uri = null;
}
}
class MapElement extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "map", true);
this.equate = new XFAObjectArray();
this.equateRange = new XFAObjectArray();
}
}
class MediumInfo extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "mediumInfo", true);
this.map = null;
}
}
class config_Message extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "message", true);
this.msgId = null;
this.severity = null;
}
}
class Messaging extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "messaging", true);
this.message = new XFAObjectArray();
}
}
class Mode extends OptionObject {
constructor(attributes) {
super(CONFIG_NS_ID, "mode", ["append", "overwrite"]);
}
}
class ModifyAnnots extends Option01 {
constructor(attributes) {
super(CONFIG_NS_ID, "modifyAnnots");
}
}
class MsgId extends IntegerObject {
constructor(attributes) {
super(CONFIG_NS_ID, "msgId", 1, n => n >= 1);
}
}
class NameAttr extends StringObject {
constructor(attributes) {
super(CONFIG_NS_ID, "nameAttr");
}
}
class NeverEmbed extends ContentObject {
constructor(attributes) {
super(CONFIG_NS_ID, "neverEmbed");
}
}
class NumberOfCopies extends IntegerObject {
constructor(attributes) {
super(CONFIG_NS_ID, "numberOfCopies", null, n => n >= 2 && n <= 5);
}
}
class OpenAction extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "openAction", true);
this.destination = null;
}
}
class Output extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "output", true);
this.to = null;
this.type = null;
this.uri = null;
}
}
class OutputBin extends StringObject {
constructor(attributes) {
super(CONFIG_NS_ID, "outputBin");
}
}
class OutputXSL extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "outputXSL", true);
this.uri = null;
}
}
class Overprint extends OptionObject {
constructor(attributes) {
super(CONFIG_NS_ID, "overprint", ["none", "both", "draw", "field"]);
}
}
class Packets extends StringObject {
constructor(attributes) {
super(CONFIG_NS_ID, "packets");
}
[$finalize]() {
if (this[$content] === "*") {
return;
}
this[$content] = this[$content].trim().split(/\s+/).filter(x => ["config", "datasets", "template", "xfdf", "xslt"].includes(x));
}
}
class PageOffset extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "pageOffset");
this.x = getInteger({
data: attributes.x,
defaultValue: "useXDCSetting",
validate: n => true
});
this.y = getInteger({
data: attributes.y,
defaultValue: "useXDCSetting",
validate: n => true
});
}
}
class PageRange extends StringObject {
constructor(attributes) {
super(CONFIG_NS_ID, "pageRange");
}
[$finalize]() {
const numbers = this[$content].trim().split(/\s+/).map(x => parseInt(x, 10));
const ranges = [];
for (let i = 0, ii = numbers.length; i < ii; i += 2) {
ranges.push(numbers.slice(i, i + 2));
}
this[$content] = ranges;
}
}
class Pagination extends OptionObject {
constructor(attributes) {
super(CONFIG_NS_ID, "pagination", ["simplex", "duplexShortEdge", "duplexLongEdge"]);
}
}
class PaginationOverride extends OptionObject {
constructor(attributes) {
super(CONFIG_NS_ID, "paginationOverride", ["none", "forceDuplex", "forceDuplexLongEdge", "forceDuplexShortEdge", "forceSimplex"]);
}
}
class Part extends IntegerObject {
constructor(attributes) {
super(CONFIG_NS_ID, "part", 1, n => false);
}
}
class Pcl extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "pcl", true);
this.name = attributes.name || "";
this.batchOutput = null;
this.fontInfo = null;
this.jog = null;
this.mediumInfo = null;
this.outputBin = null;
this.pageOffset = null;
this.staple = null;
this.xdc = null;
}
}
class Pdf extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "pdf", true);
this.name = attributes.name || "";
this.adobeExtensionLevel = null;
this.batchOutput = null;
this.compression = null;
this.creator = null;
this.encryption = null;
this.fontInfo = null;
this.interactive = null;
this.linearized = null;
this.openAction = null;
this.pdfa = null;
this.producer = null;
this.renderPolicy = null;
this.scriptModel = null;
this.silentPrint = null;
this.submitFormat = null;
this.tagged = null;
this.version = null;
this.viewerPreferences = null;
this.xdc = null;
}
}
class Pdfa extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "pdfa", true);
this.amd = null;
this.conformance = null;
this.includeXDPContent = null;
this.part = null;
}
}
class Permissions extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "permissions", true);
this.accessibleContent = null;
this.change = null;
this.contentCopy = null;
this.documentAssembly = null;
this.formFieldFilling = null;
this.modifyAnnots = null;
this.plaintextMetadata = null;
this.print = null;
this.printHighQuality = null;
}
}
class PickTrayByPDFSize extends Option01 {
constructor(attributes) {
super(CONFIG_NS_ID, "pickTrayByPDFSize");
}
}
class config_Picture extends StringObject {
constructor(attributes) {
super(CONFIG_NS_ID, "picture");
}
}
class PlaintextMetadata extends Option01 {
constructor(attributes) {
super(CONFIG_NS_ID, "plaintextMetadata");
}
}
class Presence extends OptionObject {
constructor(attributes) {
super(CONFIG_NS_ID, "presence", ["preserve", "dissolve", "dissolveStructure", "ignore", "remove"]);
}
}
class Present extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "present", true);
this.behaviorOverride = null;
this.cache = null;
this.common = null;
this.copies = null;
this.destination = null;
this.incrementalMerge = null;
this.layout = null;
this.output = null;
this.overprint = null;
this.pagination = null;
this.paginationOverride = null;
this.script = null;
this.validate = null;
this.xdp = null;
this.driver = new XFAObjectArray();
this.labelPrinter = new XFAObjectArray();
this.pcl = new XFAObjectArray();
this.pdf = new XFAObjectArray();
this.ps = new XFAObjectArray();
this.submitUrl = new XFAObjectArray();
this.webClient = new XFAObjectArray();
this.zpl = new XFAObjectArray();
}
}
class Print extends Option01 {
constructor(attributes) {
super(CONFIG_NS_ID, "print");
}
}
class PrintHighQuality extends Option01 {
constructor(attributes) {
super(CONFIG_NS_ID, "printHighQuality");
}
}
class PrintScaling extends OptionObject {
constructor(attributes) {
super(CONFIG_NS_ID, "printScaling", ["appdefault", "noScaling"]);
}
}
class PrinterName extends StringObject {
constructor(attributes) {
super(CONFIG_NS_ID, "printerName");
}
}
class Producer extends StringObject {
constructor(attributes) {
super(CONFIG_NS_ID, "producer");
}
}
class Ps extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "ps", true);
this.name = attributes.name || "";
this.batchOutput = null;
this.fontInfo = null;
this.jog = null;
this.mediumInfo = null;
this.outputBin = null;
this.staple = null;
this.xdc = null;
}
}
class Range extends ContentObject {
constructor(attributes) {
super(CONFIG_NS_ID, "range");
}
[$finalize]() {
this[$content] = this[$content].split(",", 2).map(range => range.split("-").map(x => parseInt(x.trim(), 10))).filter(range => range.every(x => !isNaN(x))).map(range => {
if (range.length === 1) {
range.push(range[0]);
}
return range;
});
}
}
class Record extends ContentObject {
constructor(attributes) {
super(CONFIG_NS_ID, "record");
}
[$finalize]() {
this[$content] = this[$content].trim();
const n = parseInt(this[$content], 10);
if (!isNaN(n) && n >= 0) {
this[$content] = n;
}
}
}
class Relevant extends ContentObject {
constructor(attributes) {
super(CONFIG_NS_ID, "relevant");
}
[$finalize]() {
this[$content] = this[$content].trim().split(/\s+/);
}
}
class Rename extends ContentObject {
constructor(attributes) {
super(CONFIG_NS_ID, "rename");
}
[$finalize]() {
this[$content] = this[$content].trim();
if (this[$content].toLowerCase().startsWith("xml") || new RegExp("[\\p{L}_][\\p{L}\\d._\\p{M}-]*", "u").test(this[$content])) {
warn("XFA - Rename: invalid XFA name");
}
}
}
class RenderPolicy extends OptionObject {
constructor(attributes) {
super(CONFIG_NS_ID, "renderPolicy", ["server", "client"]);
}
}
class RunScripts extends OptionObject {
constructor(attributes) {
super(CONFIG_NS_ID, "runScripts", ["both", "client", "none", "server"]);
}
}
class config_Script extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "script", true);
this.currentPage = null;
this.exclude = null;
this.runScripts = null;
}
}
class ScriptModel extends OptionObject {
constructor(attributes) {
super(CONFIG_NS_ID, "scriptModel", ["XFA", "none"]);
}
}
class Severity extends OptionObject {
constructor(attributes) {
super(CONFIG_NS_ID, "severity", ["ignore", "error", "information", "trace", "warning"]);
}
}
class SilentPrint extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "silentPrint", true);
this.addSilentPrint = null;
this.printerName = null;
}
}
class Staple extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "staple");
this.mode = getStringOption(attributes.mode, ["usePrinterSetting", "on", "off"]);
}
}
class StartNode extends StringObject {
constructor(attributes) {
super(CONFIG_NS_ID, "startNode");
}
}
class StartPage extends IntegerObject {
constructor(attributes) {
super(CONFIG_NS_ID, "startPage", 0, n => true);
}
}
class SubmitFormat extends OptionObject {
constructor(attributes) {
super(CONFIG_NS_ID, "submitFormat", ["html", "delegate", "fdf", "xml", "pdf"]);
}
}
class SubmitUrl extends StringObject {
constructor(attributes) {
super(CONFIG_NS_ID, "submitUrl");
}
}
class SubsetBelow extends IntegerObject {
constructor(attributes) {
super(CONFIG_NS_ID, "subsetBelow", 100, n => n >= 0 && n <= 100);
}
}
class SuppressBanner extends Option01 {
constructor(attributes) {
super(CONFIG_NS_ID, "suppressBanner");
}
}
class Tagged extends Option01 {
constructor(attributes) {
super(CONFIG_NS_ID, "tagged");
}
}
class config_Template extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "template", true);
this.base = null;
this.relevant = null;
this.startPage = null;
this.uri = null;
this.xsl = null;
}
}
class Threshold extends OptionObject {
constructor(attributes) {
super(CONFIG_NS_ID, "threshold", ["trace", "error", "information", "warning"]);
}
}
class To extends OptionObject {
constructor(attributes) {
super(CONFIG_NS_ID, "to", ["null", "memory", "stderr", "stdout", "system", "uri"]);
}
}
class TemplateCache extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "templateCache");
this.maxEntries = getInteger({
data: attributes.maxEntries,
defaultValue: 5,
validate: n => n >= 0
});
}
}
class Trace extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "trace", true);
this.area = new XFAObjectArray();
}
}
class Transform extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "transform", true);
this.groupParent = null;
this.ifEmpty = null;
this.nameAttr = null;
this.picture = null;
this.presence = null;
this.rename = null;
this.whitespace = null;
}
}
class Type extends OptionObject {
constructor(attributes) {
super(CONFIG_NS_ID, "type", ["none", "ascii85", "asciiHex", "ccittfax", "flate", "lzw", "runLength", "native", "xdp", "mergedXDP"]);
}
}
class Uri extends StringObject {
constructor(attributes) {
super(CONFIG_NS_ID, "uri");
}
}
class config_Validate extends OptionObject {
constructor(attributes) {
super(CONFIG_NS_ID, "validate", ["preSubmit", "prePrint", "preExecute", "preSave"]);
}
}
class ValidateApprovalSignatures extends ContentObject {
constructor(attributes) {
super(CONFIG_NS_ID, "validateApprovalSignatures");
}
[$finalize]() {
this[$content] = this[$content].trim().split(/\s+/).filter(x => ["docReady", "postSign"].includes(x));
}
}
class ValidationMessaging extends OptionObject {
constructor(attributes) {
super(CONFIG_NS_ID, "validationMessaging", ["allMessagesIndividually", "allMessagesTogether", "firstMessageOnly", "noMessages"]);
}
}
class Version extends OptionObject {
constructor(attributes) {
super(CONFIG_NS_ID, "version", ["1.7", "1.6", "1.5", "1.4", "1.3", "1.2"]);
}
}
class VersionControl extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "VersionControl");
this.outputBelow = getStringOption(attributes.outputBelow, ["warn", "error", "update"]);
this.sourceAbove = getStringOption(attributes.sourceAbove, ["warn", "error"]);
this.sourceBelow = getStringOption(attributes.sourceBelow, ["update", "maintain"]);
}
}
class ViewerPreferences extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "viewerPreferences", true);
this.ADBE_JSConsole = null;
this.ADBE_JSDebugger = null;
this.addViewerPreferences = null;
this.duplexOption = null;
this.enforce = null;
this.numberOfCopies = null;
this.pageRange = null;
this.pickTrayByPDFSize = null;
this.printScaling = null;
}
}
class WebClient extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "webClient", true);
this.name = attributes.name ? attributes.name.trim() : "";
this.fontInfo = null;
this.xdc = null;
}
}
class Whitespace extends OptionObject {
constructor(attributes) {
super(CONFIG_NS_ID, "whitespace", ["preserve", "ltrim", "normalize", "rtrim", "trim"]);
}
}
class Window extends ContentObject {
constructor(attributes) {
super(CONFIG_NS_ID, "window");
}
[$finalize]() {
const pair = this[$content].split(",", 2).map(x => parseInt(x.trim(), 10));
if (pair.some(x => isNaN(x))) {
this[$content] = [0, 0];
return;
}
if (pair.length === 1) {
pair.push(pair[0]);
}
this[$content] = pair;
}
}
class Xdc extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "xdc", true);
this.uri = new XFAObjectArray();
this.xsl = new XFAObjectArray();
}
}
class Xdp extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "xdp", true);
this.packets = null;
}
}
class Xsl extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "xsl", true);
this.debug = null;
this.uri = null;
}
}
class Zpl extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "zpl", true);
this.name = attributes.name ? attributes.name.trim() : "";
this.batchOutput = null;
this.flipLabel = null;
this.fontInfo = null;
this.xdc = null;
}
}
class ConfigNamespace {
static [$buildXFAObject](name, attributes) {
if (ConfigNamespace.hasOwnProperty(name)) {
return ConfigNamespace[name](attributes);
}
return undefined;
}
static acrobat(attrs) {
return new Acrobat(attrs);
}
static acrobat7(attrs) {
return new Acrobat7(attrs);
}
static ADBE_JSConsole(attrs) {
return new ADBE_JSConsole(attrs);
}
static ADBE_JSDebugger(attrs) {
return new ADBE_JSDebugger(attrs);
}
static addSilentPrint(attrs) {
return new AddSilentPrint(attrs);
}
static addViewerPreferences(attrs) {
return new AddViewerPreferences(attrs);
}
static adjustData(attrs) {
return new AdjustData(attrs);
}
static adobeExtensionLevel(attrs) {
return new AdobeExtensionLevel(attrs);
}
static agent(attrs) {
return new Agent(attrs);
}
static alwaysEmbed(attrs) {
return new AlwaysEmbed(attrs);
}
static amd(attrs) {
return new Amd(attrs);
}
static area(attrs) {
return new config_Area(attrs);
}
static attributes(attrs) {
return new Attributes(attrs);
}
static autoSave(attrs) {
return new AutoSave(attrs);
}
static base(attrs) {
return new Base(attrs);
}
static batchOutput(attrs) {
return new BatchOutput(attrs);
}
static behaviorOverride(attrs) {
return new BehaviorOverride(attrs);
}
static cache(attrs) {
return new Cache(attrs);
}
static change(attrs) {
return new Change(attrs);
}
static common(attrs) {
return new Common(attrs);
}
static compress(attrs) {
return new Compress(attrs);
}
static compressLogicalStructure(attrs) {
return new CompressLogicalStructure(attrs);
}
static compressObjectStream(attrs) {
return new CompressObjectStream(attrs);
}
static compression(attrs) {
return new Compression(attrs);
}
static config(attrs) {
return new Config(attrs);
}
static conformance(attrs) {
return new Conformance(attrs);
}
static contentCopy(attrs) {
return new ContentCopy(attrs);
}
static copies(attrs) {
return new Copies(attrs);
}
static creator(attrs) {
return new Creator(attrs);
}
static currentPage(attrs) {
return new CurrentPage(attrs);
}
static data(attrs) {
return new Data(attrs);
}
static debug(attrs) {
return new Debug(attrs);
}
static defaultTypeface(attrs) {
return new DefaultTypeface(attrs);
}
static destination(attrs) {
return new Destination(attrs);
}
static documentAssembly(attrs) {
return new DocumentAssembly(attrs);
}
static driver(attrs) {
return new Driver(attrs);
}
static duplexOption(attrs) {
return new DuplexOption(attrs);
}
static dynamicRender(attrs) {
return new DynamicRender(attrs);
}
static embed(attrs) {
return new Embed(attrs);
}
static encrypt(attrs) {
return new config_Encrypt(attrs);
}
static encryption(attrs) {
return new config_Encryption(attrs);
}
static encryptionLevel(attrs) {
return new EncryptionLevel(attrs);
}
static enforce(attrs) {
return new Enforce(attrs);
}
static equate(attrs) {
return new Equate(attrs);
}
static equateRange(attrs) {
return new EquateRange(attrs);
}
static exclude(attrs) {
return new Exclude(attrs);
}
static excludeNS(attrs) {
return new ExcludeNS(attrs);
}
static flipLabel(attrs) {
return new FlipLabel(attrs);
}
static fontInfo(attrs) {
return new config_FontInfo(attrs);
}
static formFieldFilling(attrs) {
return new FormFieldFilling(attrs);
}
static groupParent(attrs) {
return new GroupParent(attrs);
}
static ifEmpty(attrs) {
return new IfEmpty(attrs);
}
static includeXDPContent(attrs) {
return new IncludeXDPContent(attrs);
}
static incrementalLoad(attrs) {
return new IncrementalLoad(attrs);
}
static incrementalMerge(attrs) {
return new IncrementalMerge(attrs);
}
static interactive(attrs) {
return new Interactive(attrs);
}
static jog(attrs) {
return new Jog(attrs);
}
static labelPrinter(attrs) {
return new LabelPrinter(attrs);
}
static layout(attrs) {
return new Layout(attrs);
}
static level(attrs) {
return new Level(attrs);
}
static linearized(attrs) {
return new Linearized(attrs);
}
static locale(attrs) {
return new Locale(attrs);
}
static localeSet(attrs) {
return new LocaleSet(attrs);
}
static log(attrs) {
return new Log(attrs);
}
static map(attrs) {
return new MapElement(attrs);
}
static mediumInfo(attrs) {
return new MediumInfo(attrs);
}
static message(attrs) {
return new config_Message(attrs);
}
static messaging(attrs) {
return new Messaging(attrs);
}
static mode(attrs) {
return new Mode(attrs);
}
static modifyAnnots(attrs) {
return new ModifyAnnots(attrs);
}
static msgId(attrs) {
return new MsgId(attrs);
}
static nameAttr(attrs) {
return new NameAttr(attrs);
}
static neverEmbed(attrs) {
return new NeverEmbed(attrs);
}
static numberOfCopies(attrs) {
return new NumberOfCopies(attrs);
}
static openAction(attrs) {
return new OpenAction(attrs);
}
static output(attrs) {
return new Output(attrs);
}
static outputBin(attrs) {
return new OutputBin(attrs);
}
static outputXSL(attrs) {
return new OutputXSL(attrs);
}
static overprint(attrs) {
return new Overprint(attrs);
}
static packets(attrs) {
return new Packets(attrs);
}
static pageOffset(attrs) {
return new PageOffset(attrs);
}
static pageRange(attrs) {
return new PageRange(attrs);
}
static pagination(attrs) {
return new Pagination(attrs);
}
static paginationOverride(attrs) {
return new PaginationOverride(attrs);
}
static part(attrs) {
return new Part(attrs);
}
static pcl(attrs) {
return new Pcl(attrs);
}
static pdf(attrs) {
return new Pdf(attrs);
}
static pdfa(attrs) {
return new Pdfa(attrs);
}
static permissions(attrs) {
return new Permissions(attrs);
}
static pickTrayByPDFSize(attrs) {
return new PickTrayByPDFSize(attrs);
}
static picture(attrs) {
return new config_Picture(attrs);
}
static plaintextMetadata(attrs) {
return new PlaintextMetadata(attrs);
}
static presence(attrs) {
return new Presence(attrs);
}
static present(attrs) {
return new Present(attrs);
}
static print(attrs) {
return new Print(attrs);
}
static printHighQuality(attrs) {
return new PrintHighQuality(attrs);
}
static printScaling(attrs) {
return new PrintScaling(attrs);
}
static printerName(attrs) {
return new PrinterName(attrs);
}
static producer(attrs) {
return new Producer(attrs);
}
static ps(attrs) {
return new Ps(attrs);
}
static range(attrs) {
return new Range(attrs);
}
static record(attrs) {
return new Record(attrs);
}
static relevant(attrs) {
return new Relevant(attrs);
}
static rename(attrs) {
return new Rename(attrs);
}
static renderPolicy(attrs) {
return new RenderPolicy(attrs);
}
static runScripts(attrs) {
return new RunScripts(attrs);
}
static script(attrs) {
return new config_Script(attrs);
}
static scriptModel(attrs) {
return new ScriptModel(attrs);
}
static severity(attrs) {
return new Severity(attrs);
}
static silentPrint(attrs) {
return new SilentPrint(attrs);
}
static staple(attrs) {
return new Staple(attrs);
}
static startNode(attrs) {
return new StartNode(attrs);
}
static startPage(attrs) {
return new StartPage(attrs);
}
static submitFormat(attrs) {
return new SubmitFormat(attrs);
}
static submitUrl(attrs) {
return new SubmitUrl(attrs);
}
static subsetBelow(attrs) {
return new SubsetBelow(attrs);
}
static suppressBanner(attrs) {
return new SuppressBanner(attrs);
}
static tagged(attrs) {
return new Tagged(attrs);
}
static template(attrs) {
return new config_Template(attrs);
}
static templateCache(attrs) {
return new TemplateCache(attrs);
}
static threshold(attrs) {
return new Threshold(attrs);
}
static to(attrs) {
return new To(attrs);
}
static trace(attrs) {
return new Trace(attrs);
}
static transform(attrs) {
return new Transform(attrs);
}
static type(attrs) {
return new Type(attrs);
}
static uri(attrs) {
return new Uri(attrs);
}
static validate(attrs) {
return new config_Validate(attrs);
}
static validateApprovalSignatures(attrs) {
return new ValidateApprovalSignatures(attrs);
}
static validationMessaging(attrs) {
return new ValidationMessaging(attrs);
}
static version(attrs) {
return new Version(attrs);
}
static versionControl(attrs) {
return new VersionControl(attrs);
}
static viewerPreferences(attrs) {
return new ViewerPreferences(attrs);
}
static webClient(attrs) {
return new WebClient(attrs);
}
static whitespace(attrs) {
return new Whitespace(attrs);
}
static window(attrs) {
return new Window(attrs);
}
static xdc(attrs) {
return new Xdc(attrs);
}
static xdp(attrs) {
return new Xdp(attrs);
}
static xsl(attrs) {
return new Xsl(attrs);
}
static zpl(attrs) {
return new Zpl(attrs);
}
}
;// ./src/core/xfa/connection_set.js
const CONNECTION_SET_NS_ID = NamespaceIds.connectionSet.id;
class ConnectionSet extends XFAObject {
constructor(attributes) {
super(CONNECTION_SET_NS_ID, "connectionSet", true);
this.wsdlConnection = new XFAObjectArray();
this.xmlConnection = new XFAObjectArray();
this.xsdConnection = new XFAObjectArray();
}
}
class EffectiveInputPolicy extends XFAObject {
constructor(attributes) {
super(CONNECTION_SET_NS_ID, "effectiveInputPolicy");
this.id = attributes.id || "";
this.name = attributes.name || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
}
class EffectiveOutputPolicy extends XFAObject {
constructor(attributes) {
super(CONNECTION_SET_NS_ID, "effectiveOutputPolicy");
this.id = attributes.id || "";
this.name = attributes.name || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
}
class Operation extends StringObject {
constructor(attributes) {
super(CONNECTION_SET_NS_ID, "operation");
this.id = attributes.id || "";
this.input = attributes.input || "";
this.name = attributes.name || "";
this.output = attributes.output || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
}
class RootElement extends StringObject {
constructor(attributes) {
super(CONNECTION_SET_NS_ID, "rootElement");
this.id = attributes.id || "";
this.name = attributes.name || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
}
class SoapAction extends StringObject {
constructor(attributes) {
super(CONNECTION_SET_NS_ID, "soapAction");
this.id = attributes.id || "";
this.name = attributes.name || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
}
class SoapAddress extends StringObject {
constructor(attributes) {
super(CONNECTION_SET_NS_ID, "soapAddress");
this.id = attributes.id || "";
this.name = attributes.name || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
}
class connection_set_Uri extends StringObject {
constructor(attributes) {
super(CONNECTION_SET_NS_ID, "uri");
this.id = attributes.id || "";
this.name = attributes.name || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
}
class WsdlAddress extends StringObject {
constructor(attributes) {
super(CONNECTION_SET_NS_ID, "wsdlAddress");
this.id = attributes.id || "";
this.name = attributes.name || "";
this.use = attributes.use || "";
this.usehref = attributes.usehref || "";
}
}
class WsdlConnection extends XFAObject {
constructor(attributes) {
super(CONNECTION_SET_NS_ID, "wsdlConnection", true);
this.dataDescription = attributes.dataDescription || "";
this.name = attributes.name || "";
this.effectiveInputPolicy = null;
this.effectiveOutputPolicy = null;
this.operation = null;
this.soapAction = null;
this.soapAddress = null;
this.wsdlAddress = null;
}
}
class XmlConnection extends XFAObject {
constructor(attributes) {
super(CONNECTION_SET_NS_ID, "xmlConnection", true);
this.dataDescription = attributes.dataDescription || "";
this.name = attributes.name || "";
this.uri = null;
}
}
class XsdConnection extends XFAObject {
constructor(attributes) {
super(CONNECTION_SET_NS_ID, "xsdConnection", true);
this.dataDescription = attributes.dataDescription || "";
this.name = attributes.name || "";
this.rootElement = null;
this.uri = null;
}
}
class ConnectionSetNamespace {
static [$buildXFAObject](name, attributes) {
if (ConnectionSetNamespace.hasOwnProperty(name)) {
return ConnectionSetNamespace[name](attributes);
}
return undefined;
}
static connectionSet(attrs) {
return new ConnectionSet(attrs);
}
static effectiveInputPolicy(attrs) {
return new EffectiveInputPolicy(attrs);
}
static effectiveOutputPolicy(attrs) {
return new EffectiveOutputPolicy(attrs);
}
static operation(attrs) {
return new Operation(attrs);
}
static rootElement(attrs) {
return new RootElement(attrs);
}
static soapAction(attrs) {
return new SoapAction(attrs);
}
static soapAddress(attrs) {
return new SoapAddress(attrs);
}
static uri(attrs) {
return new connection_set_Uri(attrs);
}
static wsdlAddress(attrs) {
return new WsdlAddress(attrs);
}
static wsdlConnection(attrs) {
return new WsdlConnection(attrs);
}
static xmlConnection(attrs) {
return new XmlConnection(attrs);
}
static xsdConnection(attrs) {
return new XsdConnection(attrs);
}
}
;// ./src/core/xfa/datasets.js
const DATASETS_NS_ID = NamespaceIds.datasets.id;
class datasets_Data extends XmlObject {
constructor(attributes) {
super(DATASETS_NS_ID, "data", attributes);
}
[$isNsAgnostic]() {
return true;
}
}
class Datasets extends XFAObject {
constructor(attributes) {
super(DATASETS_NS_ID, "datasets", true);
this.data = null;
this.Signature = null;
}
[$onChild](child) {
const name = child[$nodeName];
if (name === "data" && child[$namespaceId] === DATASETS_NS_ID || name === "Signature" && child[$namespaceId] === NamespaceIds.signature.id) {
this[name] = child;
}
this[$appendChild](child);
}
}
class DatasetsNamespace {
static [$buildXFAObject](name, attributes) {
if (DatasetsNamespace.hasOwnProperty(name)) {
return DatasetsNamespace[name](attributes);
}
return undefined;
}
static datasets(attributes) {
return new Datasets(attributes);
}
static data(attributes) {
return new datasets_Data(attributes);
}
}
;// ./src/core/xfa/locale_set.js
const LOCALE_SET_NS_ID = NamespaceIds.localeSet.id;
class CalendarSymbols extends XFAObject {
constructor(attributes) {
super(LOCALE_SET_NS_ID, "calendarSymbols", true);
this.name = "gregorian";
this.dayNames = new XFAObjectArray(2);
this.eraNames = null;
this.meridiemNames = null;
this.monthNames = new XFAObjectArray(2);
}
}
class CurrencySymbol extends StringObject {
constructor(attributes) {
super(LOCALE_SET_NS_ID, "currencySymbol");
this.name = getStringOption(attributes.name, ["symbol", "isoname", "decimal"]);
}
}
class CurrencySymbols extends XFAObject {
constructor(attributes) {
super(LOCALE_SET_NS_ID, "currencySymbols", true);
this.currencySymbol = new XFAObjectArray(3);
}
}
class DatePattern extends StringObject {
constructor(attributes) {
super(LOCALE_SET_NS_ID, "datePattern");
this.name = getStringOption(attributes.name, ["full", "long", "med", "short"]);
}
}
class DatePatterns extends XFAObject {
constructor(attributes) {
super(LOCALE_SET_NS_ID, "datePatterns", true);
this.datePattern = new XFAObjectArray(4);
}
}
class DateTimeSymbols extends ContentObject {
constructor(attributes) {
super(LOCALE_SET_NS_ID, "dateTimeSymbols");
}
}
class Day extends StringObject {
constructor(attributes) {
super(LOCALE_SET_NS_ID, "day");
}
}
class DayNames extends XFAObject {
constructor(attributes) {
super(LOCALE_SET_NS_ID, "dayNames", true);
this.abbr = getInteger({
data: attributes.abbr,
defaultValue: 0,
validate: x => x === 1
});
this.day = new XFAObjectArray(7);
}
}
class Era extends StringObject {
constructor(attributes) {
super(LOCALE_SET_NS_ID, "era");
}
}
class EraNames extends XFAObject {
constructor(attributes) {
super(LOCALE_SET_NS_ID, "eraNames", true);
this.era = new XFAObjectArray(2);
}
}
class locale_set_Locale extends XFAObject {
constructor(attributes) {
super(LOCALE_SET_NS_ID, "locale", true);
this.desc = attributes.desc || "";
this.name = "isoname";
this.calendarSymbols = null;
this.currencySymbols = null;
this.datePatterns = null;
this.dateTimeSymbols = null;
this.numberPatterns = null;
this.numberSymbols = null;
this.timePatterns = null;
this.typeFaces = null;
}
}
class locale_set_LocaleSet extends XFAObject {
constructor(attributes) {
super(LOCALE_SET_NS_ID, "localeSet", true);
this.locale = new XFAObjectArray();
}
}
class Meridiem extends StringObject {
constructor(attributes) {
super(LOCALE_SET_NS_ID, "meridiem");
}
}
class MeridiemNames extends XFAObject {
constructor(attributes) {
super(LOCALE_SET_NS_ID, "meridiemNames", true);
this.meridiem = new XFAObjectArray(2);
}
}
class Month extends StringObject {
constructor(attributes) {
super(LOCALE_SET_NS_ID, "month");
}
}
class MonthNames extends XFAObject {
constructor(attributes) {
super(LOCALE_SET_NS_ID, "monthNames", true);
this.abbr = getInteger({
data: attributes.abbr,
defaultValue: 0,
validate: x => x === 1
});
this.month = new XFAObjectArray(12);
}
}
class NumberPattern extends StringObject {
constructor(attributes) {
super(LOCALE_SET_NS_ID, "numberPattern");
this.name = getStringOption(attributes.name, ["full", "long", "med", "short"]);
}
}
class NumberPatterns extends XFAObject {
constructor(attributes) {
super(LOCALE_SET_NS_ID, "numberPatterns", true);
this.numberPattern = new XFAObjectArray(4);
}
}
class NumberSymbol extends StringObject {
constructor(attributes) {
super(LOCALE_SET_NS_ID, "numberSymbol");
this.name = getStringOption(attributes.name, ["decimal", "grouping", "percent", "minus", "zero"]);
}
}
class NumberSymbols extends XFAObject {
constructor(attributes) {
super(LOCALE_SET_NS_ID, "numberSymbols", true);
this.numberSymbol = new XFAObjectArray(5);
}
}
class TimePattern extends StringObject {
constructor(attributes) {
super(LOCALE_SET_NS_ID, "timePattern");
this.name = getStringOption(attributes.name, ["full", "long", "med", "short"]);
}
}
class TimePatterns extends XFAObject {
constructor(attributes) {
super(LOCALE_SET_NS_ID, "timePatterns", true);
this.timePattern = new XFAObjectArray(4);
}
}
class TypeFace extends XFAObject {
constructor(attributes) {
super(LOCALE_SET_NS_ID, "typeFace", true);
this.name = attributes.name | "";
}
}
class TypeFaces extends XFAObject {
constructor(attributes) {
super(LOCALE_SET_NS_ID, "typeFaces", true);
this.typeFace = new XFAObjectArray();
}
}
class LocaleSetNamespace {
static [$buildXFAObject](name, attributes) {
if (LocaleSetNamespace.hasOwnProperty(name)) {
return LocaleSetNamespace[name](attributes);
}
return undefined;
}
static calendarSymbols(attrs) {
return new CalendarSymbols(attrs);
}
static currencySymbol(attrs) {
return new CurrencySymbol(attrs);
}
static currencySymbols(attrs) {
return new CurrencySymbols(attrs);
}
static datePattern(attrs) {
return new DatePattern(attrs);
}
static datePatterns(attrs) {
return new DatePatterns(attrs);
}
static dateTimeSymbols(attrs) {
return new DateTimeSymbols(attrs);
}
static day(attrs) {
return new Day(attrs);
}
static dayNames(attrs) {
return new DayNames(attrs);
}
static era(attrs) {
return new Era(attrs);
}
static eraNames(attrs) {
return new EraNames(attrs);
}
static locale(attrs) {
return new locale_set_Locale(attrs);
}
static localeSet(attrs) {
return new locale_set_LocaleSet(attrs);
}
static meridiem(attrs) {
return new Meridiem(attrs);
}
static meridiemNames(attrs) {
return new MeridiemNames(attrs);
}
static month(attrs) {
return new Month(attrs);
}
static monthNames(attrs) {
return new MonthNames(attrs);
}
static numberPattern(attrs) {
return new NumberPattern(attrs);
}
static numberPatterns(attrs) {
return new NumberPatterns(attrs);
}
static numberSymbol(attrs) {
return new NumberSymbol(attrs);
}
static numberSymbols(attrs) {
return new NumberSymbols(attrs);
}
static timePattern(attrs) {
return new TimePattern(attrs);
}
static timePatterns(attrs) {
return new TimePatterns(attrs);
}
static typeFace(attrs) {
return new TypeFace(attrs);
}
static typeFaces(attrs) {
return new TypeFaces(attrs);
}
}
;// ./src/core/xfa/signature.js
const SIGNATURE_NS_ID = NamespaceIds.signature.id;
class signature_Signature extends XFAObject {
constructor(attributes) {
super(SIGNATURE_NS_ID, "signature", true);
}
}
class SignatureNamespace {
static [$buildXFAObject](name, attributes) {
if (SignatureNamespace.hasOwnProperty(name)) {
return SignatureNamespace[name](attributes);
}
return undefined;
}
static signature(attributes) {
return new signature_Signature(attributes);
}
}
;// ./src/core/xfa/stylesheet.js
const STYLESHEET_NS_ID = NamespaceIds.stylesheet.id;
class Stylesheet extends XFAObject {
constructor(attributes) {
super(STYLESHEET_NS_ID, "stylesheet", true);
}
}
class StylesheetNamespace {
static [$buildXFAObject](name, attributes) {
if (StylesheetNamespace.hasOwnProperty(name)) {
return StylesheetNamespace[name](attributes);
}
return undefined;
}
static stylesheet(attributes) {
return new Stylesheet(attributes);
}
}
;// ./src/core/xfa/xdp.js
const XDP_NS_ID = NamespaceIds.xdp.id;
class xdp_Xdp extends XFAObject {
constructor(attributes) {
super(XDP_NS_ID, "xdp", true);
this.uuid = attributes.uuid || "";
this.timeStamp = attributes.timeStamp || "";
this.config = null;
this.connectionSet = null;
this.datasets = null;
this.localeSet = null;
this.stylesheet = new XFAObjectArray();
this.template = null;
}
[$onChildCheck](child) {
const ns = NamespaceIds[child[$nodeName]];
return ns && child[$namespaceId] === ns.id;
}
}
class XdpNamespace {
static [$buildXFAObject](name, attributes) {
if (XdpNamespace.hasOwnProperty(name)) {
return XdpNamespace[name](attributes);
}
return undefined;
}
static xdp(attributes) {
return new xdp_Xdp(attributes);
}
}
;// ./src/core/xfa/xhtml.js
const XHTML_NS_ID = NamespaceIds.xhtml.id;
const $richText = Symbol();
const VALID_STYLES = new Set(["color", "font", "font-family", "font-size", "font-stretch", "font-style", "font-weight", "margin", "margin-bottom", "margin-left", "margin-right", "margin-top", "letter-spacing", "line-height", "orphans", "page-break-after", "page-break-before", "page-break-inside", "tab-interval", "tab-stop", "text-align", "text-decoration", "text-indent", "vertical-align", "widows", "kerning-mode", "xfa-font-horizontal-scale", "xfa-font-vertical-scale", "xfa-spacerun", "xfa-tab-stops"]);
const StyleMapping = new Map([["page-break-after", "breakAfter"], ["page-break-before", "breakBefore"], ["page-break-inside", "breakInside"], ["kerning-mode", value => value === "none" ? "none" : "normal"], ["xfa-font-horizontal-scale", value => `scaleX(${Math.max(0, parseInt(value) / 100).toFixed(2)})`], ["xfa-font-vertical-scale", value => `scaleY(${Math.max(0, parseInt(value) / 100).toFixed(2)})`], ["xfa-spacerun", ""], ["xfa-tab-stops", ""], ["font-size", (value, original) => {
value = original.fontSize = Math.abs(getMeasurement(value));
return measureToString(0.99 * value);
}], ["letter-spacing", value => measureToString(getMeasurement(value))], ["line-height", value => measureToString(getMeasurement(value))], ["margin", value => measureToString(getMeasurement(value))], ["margin-bottom", value => measureToString(getMeasurement(value))], ["margin-left", value => measureToString(getMeasurement(value))], ["margin-right", value => measureToString(getMeasurement(value))], ["margin-top", value => measureToString(getMeasurement(value))], ["text-indent", value => measureToString(getMeasurement(value))], ["font-family", value => value], ["vertical-align", value => measureToString(getMeasurement(value))]]);
const spacesRegExp = /\s+/g;
const crlfRegExp = /[\r\n]+/g;
const crlfForRichTextRegExp = /\r\n?/g;
function mapStyle(styleStr, node, richText) {
const style = Object.create(null);
if (!styleStr) {
return style;
}
const original = Object.create(null);
for (const [key, value] of styleStr.split(";").map(s => s.split(":", 2))) {
const mapping = StyleMapping.get(key);
if (mapping === "") {
continue;
}
let newValue = value;
if (mapping) {
newValue = typeof mapping === "string" ? mapping : mapping(value, original);
}
if (key.endsWith("scale")) {
style.transform = style.transform ? `${style[key]} ${newValue}` : newValue;
} else {
style[key.replaceAll(/-([a-zA-Z])/g, (_, x) => x.toUpperCase())] = newValue;
}
}
if (style.fontFamily) {
setFontFamily({
typeface: style.fontFamily,
weight: style.fontWeight || "normal",
posture: style.fontStyle || "normal",
size: original.fontSize || 0
}, node, node[$globalData].fontFinder, style);
}
if (richText && style.verticalAlign && style.verticalAlign !== "0px" && style.fontSize) {
const SUB_SUPER_SCRIPT_FACTOR = 0.583;
const VERTICAL_FACTOR = 0.333;
const fontSize = getMeasurement(style.fontSize);
style.fontSize = measureToString(fontSize * SUB_SUPER_SCRIPT_FACTOR);
style.verticalAlign = measureToString(Math.sign(getMeasurement(style.verticalAlign)) * fontSize * VERTICAL_FACTOR);
}
if (richText && style.fontSize) {
style.fontSize = `calc(${style.fontSize} * var(--total-scale-factor))`;
}
fixTextIndent(style);
return style;
}
function checkStyle(node) {
if (!node.style) {
return "";
}
return node.style.split(";").filter(s => !!s.trim()).map(s => s.split(":", 2).map(t => t.trim())).filter(([key, value]) => {
if (key === "font-family") {
node[$globalData].usedTypefaces.add(value);
}
return VALID_STYLES.has(key);
}).map(kv => kv.join(":")).join(";");
}
const NoWhites = new Set(["body", "html"]);
class XhtmlObject extends XmlObject {
constructor(attributes, name) {
super(XHTML_NS_ID, name);
this[$richText] = false;
this.style = attributes.style || "";
}
[$clean](builder) {
super[$clean](builder);
this.style = checkStyle(this);
}
[$acceptWhitespace]() {
return !NoWhites.has(this[$nodeName]);
}
[$onText](str, richText = false) {
if (!richText) {
str = str.replaceAll(crlfRegExp, "");
if (!this.style.includes("xfa-spacerun:yes")) {
str = str.replaceAll(spacesRegExp, " ");
}
} else {
this[$richText] = true;
}
if (str) {
this[$content] += str;
}
}
[$pushGlyphs](measure, mustPop = true) {
const xfaFont = Object.create(null);
const margin = {
top: NaN,
bottom: NaN,
left: NaN,
right: NaN
};
let lineHeight = null;
for (const [key, value] of this.style.split(";").map(s => s.split(":", 2))) {
switch (key) {
case "font-family":
xfaFont.typeface = stripQuotes(value);
break;
case "font-size":
xfaFont.size = getMeasurement(value);
break;
case "font-weight":
xfaFont.weight = value;
break;
case "font-style":
xfaFont.posture = value;
break;
case "letter-spacing":
xfaFont.letterSpacing = getMeasurement(value);
break;
case "margin":
const values = value.split(/ \t/).map(x => getMeasurement(x));
switch (values.length) {
case 1:
margin.top = margin.bottom = margin.left = margin.right = values[0];
break;
case 2:
margin.top = margin.bottom = values[0];
margin.left = margin.right = values[1];
break;
case 3:
margin.top = values[0];
margin.bottom = values[2];
margin.left = margin.right = values[1];
break;
case 4:
margin.top = values[0];
margin.left = values[1];
margin.bottom = values[2];
margin.right = values[3];
break;
}
break;
case "margin-top":
margin.top = getMeasurement(value);
break;
case "margin-bottom":
margin.bottom = getMeasurement(value);
break;
case "margin-left":
margin.left = getMeasurement(value);
break;
case "margin-right":
margin.right = getMeasurement(value);
break;
case "line-height":
lineHeight = getMeasurement(value);
break;
}
}
measure.pushData(xfaFont, margin, lineHeight);
if (this[$content]) {
measure.addString(this[$content]);
} else {
for (const child of this[$getChildren]()) {
if (child[$nodeName] === "#text") {
measure.addString(child[$content]);
continue;
}
child[$pushGlyphs](measure);
}
}
if (mustPop) {
measure.popFont();
}
}
[$toHTML](availableSpace) {
const children = [];
this[$extra] = {
children
};
this[$childrenToHTML]({});
if (children.length === 0 && !this[$content]) {
return HTMLResult.EMPTY;
}
let value;
if (this[$richText]) {
value = this[$content] ? this[$content].replaceAll(crlfForRichTextRegExp, "\n") : undefined;
} else {
value = this[$content] || undefined;
}
return HTMLResult.success({
name: this[$nodeName],
attributes: {
href: this.href,
style: mapStyle(this.style, this, this[$richText])
},
children,
value
});
}
}
class A extends XhtmlObject {
constructor(attributes) {
super(attributes, "a");
this.href = fixURL(attributes.href) || "";
}
}
class B extends XhtmlObject {
constructor(attributes) {
super(attributes, "b");
}
[$pushGlyphs](measure) {
measure.pushFont({
weight: "bold"
});
super[$pushGlyphs](measure);
measure.popFont();
}
}
class Body extends XhtmlObject {
constructor(attributes) {
super(attributes, "body");
}
[$toHTML](availableSpace) {
const res = super[$toHTML](availableSpace);
const {
html
} = res;
if (!html) {
return HTMLResult.EMPTY;
}
html.name = "div";
html.attributes.class = ["xfaRich"];
return res;
}
}
class Br extends XhtmlObject {
constructor(attributes) {
super(attributes, "br");
}
[$text]() {
return "\n";
}
[$pushGlyphs](measure) {
measure.addString("\n");
}
[$toHTML](availableSpace) {
return HTMLResult.success({
name: "br"
});
}
}
class Html extends XhtmlObject {
constructor(attributes) {
super(attributes, "html");
}
[$toHTML](availableSpace) {
const children = [];
this[$extra] = {
children
};
this[$childrenToHTML]({});
if (children.length === 0) {
return HTMLResult.success({
name: "div",
attributes: {
class: ["xfaRich"],
style: {}
},
value: this[$content] || ""
});
}
if (children.length === 1) {
const child = children[0];
if (child.attributes?.class.includes("xfaRich")) {
return HTMLResult.success(child);
}
}
return HTMLResult.success({
name: "div",
attributes: {
class: ["xfaRich"],
style: {}
},
children
});
}
}
class I extends XhtmlObject {
constructor(attributes) {
super(attributes, "i");
}
[$pushGlyphs](measure) {
measure.pushFont({
posture: "italic"
});
super[$pushGlyphs](measure);
measure.popFont();
}
}
class Li extends XhtmlObject {
constructor(attributes) {
super(attributes, "li");
}
}
class Ol extends XhtmlObject {
constructor(attributes) {
super(attributes, "ol");
}
}
class P extends XhtmlObject {
constructor(attributes) {
super(attributes, "p");
}
[$pushGlyphs](measure) {
super[$pushGlyphs](measure, false);
measure.addString("\n");
measure.addPara();
measure.popFont();
}
[$text]() {
const siblings = this[$getParent]()[$getChildren]();
if (siblings.at(-1) === this) {
return super[$text]();
}
return super[$text]() + "\n";
}
}
class Span extends XhtmlObject {
constructor(attributes) {
super(attributes, "span");
}
}
class Sub extends XhtmlObject {
constructor(attributes) {
super(attributes, "sub");
}
}
class Sup extends XhtmlObject {
constructor(attributes) {
super(attributes, "sup");
}
}
class Ul extends XhtmlObject {
constructor(attributes) {
super(attributes, "ul");
}
}
class XhtmlNamespace {
static [$buildXFAObject](name, attributes) {
if (XhtmlNamespace.hasOwnProperty(name)) {
return XhtmlNamespace[name](attributes);
}
return undefined;
}
static a(attributes) {
return new A(attributes);
}
static b(attributes) {
return new B(attributes);
}
static body(attributes) {
return new Body(attributes);
}
static br(attributes) {
return new Br(attributes);
}
static html(attributes) {
return new Html(attributes);
}
static i(attributes) {
return new I(attributes);
}
static li(attributes) {
return new Li(attributes);
}
static ol(attributes) {
return new Ol(attributes);
}
static p(attributes) {
return new P(attributes);
}
static span(attributes) {
return new Span(attributes);
}
static sub(attributes) {
return new Sub(attributes);
}
static sup(attributes) {
return new Sup(attributes);
}
static ul(attributes) {
return new Ul(attributes);
}
}
;// ./src/core/xfa/setup.js
const NamespaceSetUp = {
config: ConfigNamespace,
connection: ConnectionSetNamespace,
datasets: DatasetsNamespace,
localeSet: LocaleSetNamespace,
signature: SignatureNamespace,
stylesheet: StylesheetNamespace,
template: TemplateNamespace,
xdp: XdpNamespace,
xhtml: XhtmlNamespace
};
;// ./src/core/xfa/unknown.js
class UnknownNamespace {
constructor(nsId) {
this.namespaceId = nsId;
}
[$buildXFAObject](name, attributes) {
return new XmlObject(this.namespaceId, name, attributes);
}
}
;// ./src/core/xfa/builder.js
class Root extends XFAObject {
constructor(ids) {
super(-1, "root", Object.create(null));
this.element = null;
this[$ids] = ids;
}
[$onChild](child) {
this.element = child;
return true;
}
[$finalize]() {
super[$finalize]();
if (this.element.template instanceof Template) {
this[$ids].set($root, this.element);
this.element.template[$resolvePrototypes](this[$ids]);
this.element.template[$ids] = this[$ids];
}
}
}
class Empty extends XFAObject {
constructor() {
super(-1, "", Object.create(null));
}
[$onChild](_) {
return false;
}
}
class Builder {
constructor(rootNameSpace = null) {
this._namespaceStack = [];
this._nsAgnosticLevel = 0;
this._namespacePrefixes = new Map();
this._namespaces = new Map();
this._nextNsId = Math.max(...Object.values(NamespaceIds).map(({
id
}) => id));
this._currentNamespace = rootNameSpace || new UnknownNamespace(++this._nextNsId);
}
buildRoot(ids) {
return new Root(ids);
}
build({
nsPrefix,
name,
attributes,
namespace,
prefixes
}) {
const hasNamespaceDef = namespace !== null;
if (hasNamespaceDef) {
this._namespaceStack.push(this._currentNamespace);
this._currentNamespace = this._searchNamespace(namespace);
}
if (prefixes) {
this._addNamespacePrefix(prefixes);
}
if (attributes.hasOwnProperty($nsAttributes)) {
const dataTemplate = NamespaceSetUp.datasets;
const nsAttrs = attributes[$nsAttributes];
let xfaAttrs = null;
for (const [ns, attrs] of Object.entries(nsAttrs)) {
const nsToUse = this._getNamespaceToUse(ns);
if (nsToUse === dataTemplate) {
xfaAttrs = {
xfa: attrs
};
break;
}
}
if (xfaAttrs) {
attributes[$nsAttributes] = xfaAttrs;
} else {
delete attributes[$nsAttributes];
}
}
const namespaceToUse = this._getNamespaceToUse(nsPrefix);
const node = namespaceToUse?.[$buildXFAObject](name, attributes) || new Empty();
if (node[$isNsAgnostic]()) {
this._nsAgnosticLevel++;
}
if (hasNamespaceDef || prefixes || node[$isNsAgnostic]()) {
node[$cleanup] = {
hasNamespace: hasNamespaceDef,
prefixes,
nsAgnostic: node[$isNsAgnostic]()
};
}
return node;
}
isNsAgnostic() {
return this._nsAgnosticLevel > 0;
}
_searchNamespace(nsName) {
let ns = this._namespaces.get(nsName);
if (ns) {
return ns;
}
for (const [name, {
check
}] of Object.entries(NamespaceIds)) {
if (check(nsName)) {
ns = NamespaceSetUp[name];
if (ns) {
this._namespaces.set(nsName, ns);
return ns;
}
break;
}
}
ns = new UnknownNamespace(++this._nextNsId);
this._namespaces.set(nsName, ns);
return ns;
}
_addNamespacePrefix(prefixes) {
for (const {
prefix,
value
} of prefixes) {
const namespace = this._searchNamespace(value);
let prefixStack = this._namespacePrefixes.get(prefix);
if (!prefixStack) {
prefixStack = [];
this._namespacePrefixes.set(prefix, prefixStack);
}
prefixStack.push(namespace);
}
}
_getNamespaceToUse(prefix) {
if (!prefix) {
return this._currentNamespace;
}
const prefixStack = this._namespacePrefixes.get(prefix);
if (prefixStack?.length > 0) {
return prefixStack.at(-1);
}
warn(`Unknown namespace prefix: ${prefix}.`);
return null;
}
clean(data) {
const {
hasNamespace,
prefixes,
nsAgnostic
} = data;
if (hasNamespace) {
this._currentNamespace = this._namespaceStack.pop();
}
if (prefixes) {
prefixes.forEach(({
prefix
}) => {
this._namespacePrefixes.get(prefix).pop();
});
}
if (nsAgnostic) {
this._nsAgnosticLevel--;
}
}
}
;// ./src/core/xfa/parser.js
class XFAParser extends XMLParserBase {
constructor(rootNameSpace = null, richText = false) {
super();
this._builder = new Builder(rootNameSpace);
this._stack = [];
this._globalData = {
usedTypefaces: new Set()
};
this._ids = new Map();
this._current = this._builder.buildRoot(this._ids);
this._errorCode = XMLParserErrorCode.NoError;
this._whiteRegex = /^\s+$/;
this._nbsps = /\xa0+/g;
this._richText = richText;
}
parse(data) {
this.parseXml(data);
if (this._errorCode !== XMLParserErrorCode.NoError) {
return undefined;
}
this._current[$finalize]();
return this._current.element;
}
onText(text) {
text = text.replace(this._nbsps, match => match.slice(1) + " ");
if (this._richText || this._current[$acceptWhitespace]()) {
this._current[$onText](text, this._richText);
return;
}
if (this._whiteRegex.test(text)) {
return;
}
this._current[$onText](text.trim());
}
onCdata(text) {
this._current[$onText](text);
}
_mkAttributes(attributes, tagName) {
let namespace = null;
let prefixes = null;
const attributeObj = Object.create({});
for (const {
name,
value
} of attributes) {
if (name === "xmlns") {
if (!namespace) {
namespace = value;
} else {
warn(`XFA - multiple namespace definition in <${tagName}>`);
}
} else if (name.startsWith("xmlns:")) {
const prefix = name.substring("xmlns:".length);
if (!prefixes) {
prefixes = [];
}
prefixes.push({
prefix,
value
});
} else {
const i = name.indexOf(":");
if (i === -1) {
attributeObj[name] = value;
} else {
let nsAttrs = attributeObj[$nsAttributes];
if (!nsAttrs) {
nsAttrs = attributeObj[$nsAttributes] = Object.create(null);
}
const [ns, attrName] = [name.slice(0, i), name.slice(i + 1)];
const attrs = nsAttrs[ns] ||= Object.create(null);
attrs[attrName] = value;
}
}
}
return [namespace, prefixes, attributeObj];
}
_getNameAndPrefix(name, nsAgnostic) {
const i = name.indexOf(":");
if (i === -1) {
return [name, null];
}
return [name.substring(i + 1), nsAgnostic ? "" : name.substring(0, i)];
}
onBeginElement(tagName, attributes, isEmpty) {
const [namespace, prefixes, attributesObj] = this._mkAttributes(attributes, tagName);
const [name, nsPrefix] = this._getNameAndPrefix(tagName, this._builder.isNsAgnostic());
const node = this._builder.build({
nsPrefix,
name,
attributes: attributesObj,
namespace,
prefixes
});
node[$globalData] = this._globalData;
if (isEmpty) {
node[$finalize]();
if (this._current[$onChild](node)) {
node[$setId](this._ids);
}
node[$clean](this._builder);
return;
}
this._stack.push(this._current);
this._current = node;
}
onEndElement(name) {
const node = this._current;
if (node[$isCDATAXml]() && typeof node[$content] === "string") {
const parser = new XFAParser();
parser._globalData = this._globalData;
const root = parser.parse(node[$content]);
node[$content] = null;
node[$onChild](root);
}
node[$finalize]();
this._current = this._stack.pop();
if (this._current[$onChild](node)) {
node[$setId](this._ids);
}
node[$clean](this._builder);
}
onError(code) {
this._errorCode = code;
}
}
;// ./src/core/xfa/factory.js
class XFAFactory {
constructor(data) {
try {
this.root = new XFAParser().parse(XFAFactory._createDocument(data));
const binder = new Binder(this.root);
this.form = binder.bind();
this.dataHandler = new DataHandler(this.root, binder.getData());
this.form[$globalData].template = this.form;
} catch (e) {
warn(`XFA - an error occurred during parsing and binding: ${e}`);
}
}
isValid() {
return this.root && this.form;
}
_createPagesHelper() {
const iterator = this.form[$toPages]();
return new Promise((resolve, reject) => {
const nextIteration = () => {
try {
const value = iterator.next();
if (value.done) {
resolve(value.value);
} else {
setTimeout(nextIteration, 0);
}
} catch (e) {
reject(e);
}
};
setTimeout(nextIteration, 0);
});
}
async _createPages() {
try {
this.pages = await this._createPagesHelper();
this.dims = this.pages.children.map(c => {
const {
width,
height
} = c.attributes.style;
return [0, 0, parseInt(width), parseInt(height)];
});
} catch (e) {
warn(`XFA - an error occurred during layout: ${e}`);
}
}
getBoundingBox(pageIndex) {
return this.dims[pageIndex];
}
async getNumPages() {
if (!this.pages) {
await this._createPages();
}
return this.dims.length;
}
setImages(images) {
this.form[$globalData].images = images;
}
setFonts(fonts) {
this.form[$globalData].fontFinder = new FontFinder(fonts);
const missingFonts = [];
for (let typeface of this.form[$globalData].usedTypefaces) {
typeface = stripQuotes(typeface);
const font = this.form[$globalData].fontFinder.find(typeface);
if (!font) {
missingFonts.push(typeface);
}
}
if (missingFonts.length > 0) {
return missingFonts;
}
return null;
}
appendFonts(fonts, reallyMissingFonts) {
this.form[$globalData].fontFinder.add(fonts, reallyMissingFonts);
}
async getPages() {
if (!this.pages) {
await this._createPages();
}
const pages = this.pages;
this.pages = null;
return pages;
}
serializeData(storage) {
return this.dataHandler.serialize(storage);
}
static _createDocument(data) {
if (!data["/xdp:xdp"]) {
return data["xdp:xdp"];
}
return Object.values(data).join("");
}
static getRichTextAsHtml(rc) {
if (!rc || typeof rc !== "string") {
return null;
}
try {
let root = new XFAParser(XhtmlNamespace, true).parse(rc);
if (!["body", "xhtml"].includes(root[$nodeName])) {
const newRoot = XhtmlNamespace.body({});
newRoot[$appendChild](root);
root = newRoot;
}
const result = root[$toHTML]();
if (!result.success) {
return null;
}
const {
html
} = result;
const {
attributes
} = html;
if (attributes) {
if (attributes.class) {
attributes.class = attributes.class.filter(attr => !attr.startsWith("xfa"));
}
attributes.dir = "auto";
}
return {
html,
str: root[$text]()
};
} catch (e) {
warn(`XFA - an error occurred during parsing of rich text: ${e}`);
}
return null;
}
}
;// ./src/core/annotation.js
class AnnotationFactory {
static createGlobals(pdfManager) {
return Promise.all([pdfManager.ensureCatalog("acroForm"), pdfManager.ensureDoc("xfaDatasets"), pdfManager.ensureCatalog("structTreeRoot"), pdfManager.ensureCatalog("baseUrl"), pdfManager.ensureCatalog("attachments"), pdfManager.ensureCatalog("globalColorSpaceCache")]).then(([acroForm, xfaDatasets, structTreeRoot, baseUrl, attachments, globalColorSpaceCache]) => ({
pdfManager,
acroForm: acroForm instanceof Dict ? acroForm : Dict.empty,
xfaDatasets,
structTreeRoot,
baseUrl,
attachments,
globalColorSpaceCache
}), reason => {
warn(`createGlobals: "${reason}".`);
return null;
});
}
static async create(xref, ref, annotationGlobals, idFactory, collectFields, orphanFields, pageRef) {
const pageIndex = collectFields ? await this._getPageIndex(xref, ref, annotationGlobals.pdfManager) : null;
return annotationGlobals.pdfManager.ensure(this, "_create", [xref, ref, annotationGlobals, idFactory, collectFields, orphanFields, pageIndex, pageRef]);
}
static _create(xref, ref, annotationGlobals, idFactory, collectFields = false, orphanFields = null, pageIndex = null, pageRef = null) {
const dict = xref.fetchIfRef(ref);
if (!(dict instanceof Dict)) {
return undefined;
}
const {
acroForm,
pdfManager
} = annotationGlobals;
const id = ref instanceof Ref ? ref.toString() : `annot_${idFactory.createObjId()}`;
let subtype = dict.get("Subtype");
subtype = subtype instanceof Name ? subtype.name : null;
const parameters = {
xref,
ref,
dict,
subtype,
id,
annotationGlobals,
collectFields,
orphanFields,
needAppearances: !collectFields && acroForm.get("NeedAppearances") === true,
pageIndex,
evaluatorOptions: pdfManager.evaluatorOptions,
pageRef
};
switch (subtype) {
case "Link":
return new LinkAnnotation(parameters);
case "Text":
return new TextAnnotation(parameters);
case "Widget":
let fieldType = getInheritableProperty({
dict,
key: "FT"
});
fieldType = fieldType instanceof Name ? fieldType.name : null;
switch (fieldType) {
case "Tx":
return new TextWidgetAnnotation(parameters);
case "Btn":
return new ButtonWidgetAnnotation(parameters);
case "Ch":
return new ChoiceWidgetAnnotation(parameters);
case "Sig":
return new SignatureWidgetAnnotation(parameters);
}
warn(`Unimplemented widget field type "${fieldType}", ` + "falling back to base field type.");
return new WidgetAnnotation(parameters);
case "Popup":
return new PopupAnnotation(parameters);
case "FreeText":
return new FreeTextAnnotation(parameters);
case "Line":
return new LineAnnotation(parameters);
case "Square":
return new SquareAnnotation(parameters);
case "Circle":
return new CircleAnnotation(parameters);
case "PolyLine":
return new PolylineAnnotation(parameters);
case "Polygon":
return new PolygonAnnotation(parameters);
case "Caret":
return new CaretAnnotation(parameters);
case "Ink":
return new InkAnnotation(parameters);
case "Highlight":
return new HighlightAnnotation(parameters);
case "Underline":
return new UnderlineAnnotation(parameters);
case "Squiggly":
return new SquigglyAnnotation(parameters);
case "StrikeOut":
return new StrikeOutAnnotation(parameters);
case "Stamp":
return new StampAnnotation(parameters);
case "FileAttachment":
return new FileAttachmentAnnotation(parameters);
default:
if (!collectFields) {
if (!subtype) {
warn("Annotation is missing the required /Subtype.");
} else {
warn(`Unimplemented annotation type "${subtype}", ` + "falling back to base annotation.");
}
}
return new Annotation(parameters);
}
}
static async _getPageIndex(xref, ref, pdfManager) {
try {
const annotDict = await xref.fetchIfRefAsync(ref);
if (!(annotDict instanceof Dict)) {
return -1;
}
const pageRef = annotDict.getRaw("P");
if (pageRef instanceof Ref) {
try {
const pageIndex = await pdfManager.ensureCatalog("getPageIndex", [pageRef]);
return pageIndex;
} catch (ex) {
info(`_getPageIndex -- not a valid page reference: "${ex}".`);
}
}
if (annotDict.has("Kids")) {
return -1;
}
const numPages = await pdfManager.ensureDoc("numPages");
for (let pageIndex = 0; pageIndex < numPages; pageIndex++) {
const page = await pdfManager.getPage(pageIndex);
const annotations = await pdfManager.ensure(page, "annotations");
for (const annotRef of annotations) {
if (annotRef instanceof Ref && isRefsEqual(annotRef, ref)) {
return pageIndex;
}
}
}
} catch (ex) {
warn(`_getPageIndex: "${ex}".`);
}
return -1;
}
static generateImages(annotations, xref, isOffscreenCanvasSupported) {
if (!isOffscreenCanvasSupported) {
warn("generateImages: OffscreenCanvas is not supported, cannot save or print some annotations with images.");
return null;
}
let imagePromises;
for (const {
bitmapId,
bitmap
} of annotations) {
if (!bitmap) {
continue;
}
imagePromises ||= new Map();
imagePromises.set(bitmapId, StampAnnotation.createImage(bitmap, xref));
}
return imagePromises;
}
static async saveNewAnnotations(evaluator, task, annotations, imagePromises, changes) {
const xref = evaluator.xref;
let baseFontRef;
const promises = [];
const {
isOffscreenCanvasSupported
} = evaluator.options;
for (const annotation of annotations) {
if (annotation.deleted) {
continue;
}
switch (annotation.annotationType) {
case AnnotationEditorType.FREETEXT:
if (!baseFontRef) {
const baseFont = new Dict(xref);
baseFont.set("BaseFont", Name.get("Helvetica"));
baseFont.set("Type", Name.get("Font"));
baseFont.set("Subtype", Name.get("Type1"));
baseFont.set("Encoding", Name.get("WinAnsiEncoding"));
baseFontRef = xref.getNewTemporaryRef();
changes.put(baseFontRef, {
data: baseFont
});
}
promises.push(FreeTextAnnotation.createNewAnnotation(xref, annotation, changes, {
evaluator,
task,
baseFontRef
}));
break;
case AnnotationEditorType.HIGHLIGHT:
if (annotation.quadPoints) {
promises.push(HighlightAnnotation.createNewAnnotation(xref, annotation, changes));
} else {
promises.push(InkAnnotation.createNewAnnotation(xref, annotation, changes));
}
break;
case AnnotationEditorType.INK:
promises.push(InkAnnotation.createNewAnnotation(xref, annotation, changes));
break;
case AnnotationEditorType.STAMP:
const image = isOffscreenCanvasSupported ? await imagePromises?.get(annotation.bitmapId) : null;
if (image?.imageStream) {
const {
imageStream,
smaskStream
} = image;
if (smaskStream) {
const smaskRef = xref.getNewTemporaryRef();
changes.put(smaskRef, {
data: smaskStream
});
imageStream.dict.set("SMask", smaskRef);
}
const imageRef = image.imageRef = xref.getNewTemporaryRef();
changes.put(imageRef, {
data: imageStream
});
image.imageStream = image.smaskStream = null;
}
promises.push(StampAnnotation.createNewAnnotation(xref, annotation, changes, {
image
}));
break;
case AnnotationEditorType.SIGNATURE:
promises.push(StampAnnotation.createNewAnnotation(xref, annotation, changes, {}));
break;
}
}
return {
annotations: await Promise.all(promises)
};
}
static async printNewAnnotations(annotationGlobals, evaluator, task, annotations, imagePromises) {
if (!annotations) {
return null;
}
const {
options,
xref
} = evaluator;
const promises = [];
for (const annotation of annotations) {
if (annotation.deleted) {
continue;
}
switch (annotation.annotationType) {
case AnnotationEditorType.FREETEXT:
promises.push(FreeTextAnnotation.createNewPrintAnnotation(annotationGlobals, xref, annotation, {
evaluator,
task,
evaluatorOptions: options
}));
break;
case AnnotationEditorType.HIGHLIGHT:
if (annotation.quadPoints) {
promises.push(HighlightAnnotation.createNewPrintAnnotation(annotationGlobals, xref, annotation, {
evaluatorOptions: options
}));
} else {
promises.push(InkAnnotation.createNewPrintAnnotation(annotationGlobals, xref, annotation, {
evaluatorOptions: options
}));
}
break;
case AnnotationEditorType.INK:
promises.push(InkAnnotation.createNewPrintAnnotation(annotationGlobals, xref, annotation, {
evaluatorOptions: options
}));
break;
case AnnotationEditorType.STAMP:
const image = options.isOffscreenCanvasSupported ? await imagePromises?.get(annotation.bitmapId) : null;
if (image?.imageStream) {
const {
imageStream,
smaskStream
} = image;
if (smaskStream) {
imageStream.dict.set("SMask", smaskStream);
}
image.imageRef = new JpegStream(imageStream, imageStream.length);
image.imageStream = image.smaskStream = null;
}
promises.push(StampAnnotation.createNewPrintAnnotation(annotationGlobals, xref, annotation, {
image,
evaluatorOptions: options
}));
break;
case AnnotationEditorType.SIGNATURE:
promises.push(StampAnnotation.createNewPrintAnnotation(annotationGlobals, xref, annotation, {
evaluatorOptions: options
}));
break;
}
}
return Promise.all(promises);
}
}
function getRgbColor(color, defaultColor = new Uint8ClampedArray(3)) {
if (!Array.isArray(color)) {
return defaultColor;
}
const rgbColor = defaultColor || new Uint8ClampedArray(3);
switch (color.length) {
case 0:
return null;
case 1:
ColorSpaceUtils.gray.getRgbItem(color, 0, rgbColor, 0);
return rgbColor;
case 3:
ColorSpaceUtils.rgb.getRgbItem(color, 0, rgbColor, 0);
return rgbColor;
case 4:
ColorSpaceUtils.cmyk.getRgbItem(color, 0, rgbColor, 0);
return rgbColor;
default:
return defaultColor;
}
}
function getPdfColorArray(color) {
return Array.from(color, c => c / 255);
}
function getQuadPoints(dict, rect) {
const quadPoints = dict.getArray("QuadPoints");
if (!isNumberArray(quadPoints, null) || quadPoints.length === 0 || quadPoints.length % 8 > 0) {
return null;
}
const newQuadPoints = new Float32Array(quadPoints.length);
for (let i = 0, ii = quadPoints.length; i < ii; i += 8) {
const [x1, y1, x2, y2, x3, y3, x4, y4] = quadPoints.slice(i, i + 8);
const minX = Math.min(x1, x2, x3, x4);
const maxX = Math.max(x1, x2, x3, x4);
const minY = Math.min(y1, y2, y3, y4);
const maxY = Math.max(y1, y2, y3, y4);
if (rect !== null && (minX < rect[0] || maxX > rect[2] || minY < rect[1] || maxY > rect[3])) {
return null;
}
newQuadPoints.set([minX, maxY, maxX, maxY, minX, minY, maxX, minY], i);
}
return newQuadPoints;
}
function getTransformMatrix(rect, bbox, matrix) {
const [minX, minY, maxX, maxY] = Util.getAxialAlignedBoundingBox(bbox, matrix);
if (minX === maxX || minY === maxY) {
return [1, 0, 0, 1, rect[0], rect[1]];
}
const xRatio = (rect[2] - rect[0]) / (maxX - minX);
const yRatio = (rect[3] - rect[1]) / (maxY - minY);
return [xRatio, 0, 0, yRatio, rect[0] - minX * xRatio, rect[1] - minY * yRatio];
}
class Annotation {
constructor(params) {
const {
dict,
xref,
annotationGlobals,
ref,
orphanFields
} = params;
const parentRef = orphanFields?.get(ref);
if (parentRef) {
dict.set("Parent", parentRef);
}
this.setTitle(dict.get("T"));
this.setContents(dict.get("Contents"));
this.setModificationDate(dict.get("M"));
this.setFlags(dict.get("F"));
this.setRectangle(dict.getArray("Rect"));
this.setColor(dict.getArray("C"));
this.setBorderStyle(dict);
this.setAppearance(dict);
this.setOptionalContent(dict);
const MK = dict.get("MK");
this.setBorderAndBackgroundColors(MK);
this.setRotation(MK, dict);
this.ref = params.ref instanceof Ref ? params.ref : null;
this._streams = [];
if (this.appearance) {
this._streams.push(this.appearance);
}
const isLocked = !!(this.flags & AnnotationFlag.LOCKED);
const isContentLocked = !!(this.flags & AnnotationFlag.LOCKEDCONTENTS);
this.data = {
annotationFlags: this.flags,
borderStyle: this.borderStyle,
color: this.color,
backgroundColor: this.backgroundColor,
borderColor: this.borderColor,
rotation: this.rotation,
contentsObj: this._contents,
hasAppearance: !!this.appearance,
id: params.id,
modificationDate: this.modificationDate,
rect: this.rectangle,
subtype: params.subtype,
hasOwnCanvas: false,
noRotate: !!(this.flags & AnnotationFlag.NOROTATE),
noHTML: isLocked && isContentLocked,
isEditable: false,
structParent: -1
};
if (annotationGlobals.structTreeRoot) {
let structParent = dict.get("StructParent");
this.data.structParent = structParent = Number.isInteger(structParent) && structParent >= 0 ? structParent : -1;
annotationGlobals.structTreeRoot.addAnnotationIdToPage(params.pageRef, structParent);
}
if (params.collectFields) {
const kids = dict.get("Kids");
if (Array.isArray(kids)) {
const kidIds = [];
for (const kid of kids) {
if (kid instanceof Ref) {
kidIds.push(kid.toString());
}
}
if (kidIds.length !== 0) {
this.data.kidIds = kidIds;
}
}
this.data.actions = collectActions(xref, dict, AnnotationActionEventType);
this.data.fieldName = this._constructFieldName(dict);
this.data.pageIndex = params.pageIndex;
}
const it = dict.get("IT");
if (it instanceof Name) {
this.data.it = it.name;
}
this._isOffscreenCanvasSupported = params.evaluatorOptions.isOffscreenCanvasSupported;
this._fallbackFontDict = null;
this._needAppearances = false;
}
_hasFlag(flags, flag) {
return !!(flags & flag);
}
_buildFlags(noView, noPrint) {
let {
flags
} = this;
if (noView === undefined) {
if (noPrint === undefined) {
return undefined;
}
if (noPrint) {
return flags & ~AnnotationFlag.PRINT;
}
return flags & ~AnnotationFlag.HIDDEN | AnnotationFlag.PRINT;
}
if (noView) {
flags |= AnnotationFlag.PRINT;
if (noPrint) {
return flags & ~AnnotationFlag.NOVIEW | AnnotationFlag.HIDDEN;
}
return flags & ~AnnotationFlag.HIDDEN | AnnotationFlag.NOVIEW;
}
flags &= ~(AnnotationFlag.HIDDEN | AnnotationFlag.NOVIEW);
if (noPrint) {
return flags & ~AnnotationFlag.PRINT;
}
return flags | AnnotationFlag.PRINT;
}
_isViewable(flags) {
return !this._hasFlag(flags, AnnotationFlag.INVISIBLE) && !this._hasFlag(flags, AnnotationFlag.NOVIEW);
}
_isPrintable(flags) {
return this._hasFlag(flags, AnnotationFlag.PRINT) && !this._hasFlag(flags, AnnotationFlag.HIDDEN) && !this._hasFlag(flags, AnnotationFlag.INVISIBLE);
}
mustBeViewed(annotationStorage, _renderForms) {
const noView = annotationStorage?.get(this.data.id)?.noView;
if (noView !== undefined) {
return !noView;
}
return this.viewable && !this._hasFlag(this.flags, AnnotationFlag.HIDDEN);
}
mustBePrinted(annotationStorage) {
const noPrint = annotationStorage?.get(this.data.id)?.noPrint;
if (noPrint !== undefined) {
return !noPrint;
}
return this.printable;
}
mustBeViewedWhenEditing(isEditing, modifiedIds = null) {
return isEditing ? !this.data.isEditable : !modifiedIds?.has(this.data.id);
}
get viewable() {
if (this.data.quadPoints === null) {
return false;
}
if (this.flags === 0) {
return true;
}
return this._isViewable(this.flags);
}
get printable() {
if (this.data.quadPoints === null) {
return false;
}
if (this.flags === 0) {
return false;
}
return this._isPrintable(this.flags);
}
_parseStringHelper(data) {
const str = typeof data === "string" ? stringToPDFString(data) : "";
const dir = str && bidi(str).dir === "rtl" ? "rtl" : "ltr";
return {
str,
dir
};
}
setDefaultAppearance(params) {
const {
dict,
annotationGlobals
} = params;
const defaultAppearance = getInheritableProperty({
dict,
key: "DA"
}) || annotationGlobals.acroForm.get("DA");
this._defaultAppearance = typeof defaultAppearance === "string" ? defaultAppearance : "";
this.data.defaultAppearanceData = parseDefaultAppearance(this._defaultAppearance);
}
setTitle(title) {
this._title = this._parseStringHelper(title);
}
setContents(contents) {
this._contents = this._parseStringHelper(contents);
}
setModificationDate(modificationDate) {
this.modificationDate = typeof modificationDate === "string" ? modificationDate : null;
}
setFlags(flags) {
this.flags = Number.isInteger(flags) && flags > 0 ? flags : 0;
if (this.flags & AnnotationFlag.INVISIBLE && this.constructor.name !== "Annotation") {
this.flags ^= AnnotationFlag.INVISIBLE;
}
}
hasFlag(flag) {
return this._hasFlag(this.flags, flag);
}
setRectangle(rectangle) {
this.rectangle = lookupNormalRect(rectangle, [0, 0, 0, 0]);
}
setColor(color) {
this.color = getRgbColor(color);
}
setLineEndings(lineEndings) {
this.lineEndings = ["None", "None"];
if (Array.isArray(lineEndings) && lineEndings.length === 2) {
for (let i = 0; i < 2; i++) {
const obj = lineEndings[i];
if (obj instanceof Name) {
switch (obj.name) {
case "None":
continue;
case "Square":
case "Circle":
case "Diamond":
case "OpenArrow":
case "ClosedArrow":
case "Butt":
case "ROpenArrow":
case "RClosedArrow":
case "Slash":
this.lineEndings[i] = obj.name;
continue;
}
}
warn(`Ignoring invalid lineEnding: ${obj}`);
}
}
}
setRotation(mk, dict) {
this.rotation = 0;
let angle = mk instanceof Dict ? mk.get("R") || 0 : dict.get("Rotate") || 0;
if (Number.isInteger(angle) && angle !== 0) {
angle %= 360;
if (angle < 0) {
angle += 360;
}
if (angle % 90 === 0) {
this.rotation = angle;
}
}
}
setBorderAndBackgroundColors(mk) {
if (mk instanceof Dict) {
this.borderColor = getRgbColor(mk.getArray("BC"), null);
this.backgroundColor = getRgbColor(mk.getArray("BG"), null);
} else {
this.borderColor = this.backgroundColor = null;
}
}
setBorderStyle(borderStyle) {
this.borderStyle = new AnnotationBorderStyle();
if (!(borderStyle instanceof Dict)) {
return;
}
if (borderStyle.has("BS")) {
const dict = borderStyle.get("BS");
if (dict instanceof Dict) {
const dictType = dict.get("Type");
if (!dictType || isName(dictType, "Border")) {
this.borderStyle.setWidth(dict.get("W"), this.rectangle);
this.borderStyle.setStyle(dict.get("S"));
this.borderStyle.setDashArray(dict.getArray("D"));
}
}
} else if (borderStyle.has("Border")) {
const array = borderStyle.getArray("Border");
if (Array.isArray(array) && array.length >= 3) {
this.borderStyle.setHorizontalCornerRadius(array[0]);
this.borderStyle.setVerticalCornerRadius(array[1]);
this.borderStyle.setWidth(array[2], this.rectangle);
if (array.length === 4) {
this.borderStyle.setDashArray(array[3], true);
}
}
} else {
this.borderStyle.setWidth(0);
}
}
setAppearance(dict) {
this.appearance = null;
const appearanceStates = dict.get("AP");
if (!(appearanceStates instanceof Dict)) {
return;
}
const normalAppearanceState = appearanceStates.get("N");
if (normalAppearanceState instanceof BaseStream) {
this.appearance = normalAppearanceState;
return;
}
if (!(normalAppearanceState instanceof Dict)) {
return;
}
const as = dict.get("AS");
if (!(as instanceof Name) || !normalAppearanceState.has(as.name)) {
return;
}
const appearance = normalAppearanceState.get(as.name);
if (appearance instanceof BaseStream) {
this.appearance = appearance;
}
}
setOptionalContent(dict) {
this.oc = null;
const oc = dict.get("OC");
if (oc instanceof Name) {
warn("setOptionalContent: Support for /Name-entry is not implemented.");
} else if (oc instanceof Dict) {
this.oc = oc;
}
}
loadResources(keys, appearance) {
return appearance.dict.getAsync("Resources").then(resources => {
if (!resources) {
return undefined;
}
const objectLoader = new ObjectLoader(resources, keys, resources.xref);
return objectLoader.load().then(() => resources);
});
}
async getOperatorList(evaluator, task, intent, annotationStorage) {
const {
hasOwnCanvas,
id,
rect
} = this.data;
let appearance = this.appearance;
const isUsingOwnCanvas = !!(hasOwnCanvas && intent & RenderingIntentFlag.DISPLAY);
if (isUsingOwnCanvas && (this.width === 0 || this.height === 0)) {
this.data.hasOwnCanvas = false;
return {
opList: new OperatorList(),
separateForm: false,
separateCanvas: false
};
}
if (!appearance) {
if (!isUsingOwnCanvas) {
return {
opList: new OperatorList(),
separateForm: false,
separateCanvas: false
};
}
appearance = new StringStream("");
appearance.dict = new Dict();
}
const appearanceDict = appearance.dict;
const resources = await this.loadResources(["ExtGState", "ColorSpace", "Pattern", "Shading", "XObject", "Font"], appearance);
const bbox = lookupRect(appearanceDict.getArray("BBox"), [0, 0, 1, 1]);
const matrix = lookupMatrix(appearanceDict.getArray("Matrix"), IDENTITY_MATRIX);
const transform = getTransformMatrix(rect, bbox, matrix);
const opList = new OperatorList();
let optionalContent;
if (this.oc) {
optionalContent = await evaluator.parseMarkedContentProps(this.oc, null);
}
if (optionalContent !== undefined) {
opList.addOp(OPS.beginMarkedContentProps, ["OC", optionalContent]);
}
opList.addOp(OPS.beginAnnotation, [id, rect, transform, matrix, isUsingOwnCanvas]);
await evaluator.getOperatorList({
stream: appearance,
task,
resources,
operatorList: opList,
fallbackFontDict: this._fallbackFontDict
});
opList.addOp(OPS.endAnnotation, []);
if (optionalContent !== undefined) {
opList.addOp(OPS.endMarkedContent, []);
}
this.reset();
return {
opList,
separateForm: false,
separateCanvas: isUsingOwnCanvas
};
}
async save(evaluator, task, annotationStorage, changes) {
return null;
}
get hasTextContent() {
return false;
}
async extractTextContent(evaluator, task, viewBox) {
if (!this.appearance) {
return;
}
const resources = await this.loadResources(["ExtGState", "Font", "Properties", "XObject"], this.appearance);
const text = [];
const buffer = [];
let firstPosition = null;
const sink = {
desiredSize: Math.Infinity,
ready: true,
enqueue(chunk, size) {
for (const item of chunk.items) {
if (item.str === undefined) {
continue;
}
firstPosition ||= item.transform.slice(-2);
buffer.push(item.str);
if (item.hasEOL) {
text.push(buffer.join("").trimEnd());
buffer.length = 0;
}
}
}
};
await evaluator.getTextContent({
stream: this.appearance,
task,
resources,
includeMarkedContent: true,
keepWhiteSpace: true,
sink,
viewBox
});
this.reset();
if (buffer.length) {
text.push(buffer.join("").trimEnd());
}
if (text.length > 1 || text[0]) {
const appearanceDict = this.appearance.dict;
const bbox = lookupRect(appearanceDict.getArray("BBox"), null);
const matrix = lookupMatrix(appearanceDict.getArray("Matrix"), null);
this.data.textPosition = this._transformPoint(firstPosition, bbox, matrix);
this.data.textContent = text;
}
}
_transformPoint(coords, bbox, matrix) {
const {
rect
} = this.data;
bbox ||= [0, 0, 1, 1];
matrix ||= [1, 0, 0, 1, 0, 0];
const transform = getTransformMatrix(rect, bbox, matrix);
transform[4] -= rect[0];
transform[5] -= rect[1];
coords = Util.applyTransform(coords, transform);
return Util.applyTransform(coords, matrix);
}
getFieldObject() {
if (this.data.kidIds) {
return {
id: this.data.id,
actions: this.data.actions,
name: this.data.fieldName,
strokeColor: this.data.borderColor,
fillColor: this.data.backgroundColor,
type: "",
kidIds: this.data.kidIds,
page: this.data.pageIndex,
rotation: this.rotation
};
}
return null;
}
reset() {
for (const stream of this._streams) {
stream.reset();
}
}
_constructFieldName(dict) {
if (!dict.has("T") && !dict.has("Parent")) {
warn("Unknown field name, falling back to empty field name.");
return "";
}
if (!dict.has("Parent")) {
return stringToPDFString(dict.get("T"));
}
const fieldName = [];
if (dict.has("T")) {
fieldName.unshift(stringToPDFString(dict.get("T")));
}
let loopDict = dict;
const visited = new RefSet();
if (dict.objId) {
visited.put(dict.objId);
}
while (loopDict.has("Parent")) {
loopDict = loopDict.get("Parent");
if (!(loopDict instanceof Dict) || loopDict.objId && visited.has(loopDict.objId)) {
break;
}
if (loopDict.objId) {
visited.put(loopDict.objId);
}
if (loopDict.has("T")) {
fieldName.unshift(stringToPDFString(loopDict.get("T")));
}
}
return fieldName.join(".");
}
get width() {
return this.data.rect[2] - this.data.rect[0];
}
get height() {
return this.data.rect[3] - this.data.rect[1];
}
}
class AnnotationBorderStyle {
constructor() {
this.width = 1;
this.rawWidth = 1;
this.style = AnnotationBorderStyleType.SOLID;
this.dashArray = [3];
this.horizontalCornerRadius = 0;
this.verticalCornerRadius = 0;
}
setWidth(width, rect = [0, 0, 0, 0]) {
if (width instanceof Name) {
this.width = 0;
return;
}
if (typeof width === "number") {
if (width > 0) {
this.rawWidth = width;
const maxWidth = (rect[2] - rect[0]) / 2;
const maxHeight = (rect[3] - rect[1]) / 2;
if (maxWidth > 0 && maxHeight > 0 && (width > maxWidth || width > maxHeight)) {
warn(`AnnotationBorderStyle.setWidth - ignoring width: ${width}`);
width = 1;
}
}
this.width = width;
}
}
setStyle(style) {
if (!(style instanceof Name)) {
return;
}
switch (style.name) {
case "S":
this.style = AnnotationBorderStyleType.SOLID;
break;
case "D":
this.style = AnnotationBorderStyleType.DASHED;
break;
case "B":
this.style = AnnotationBorderStyleType.BEVELED;
break;
case "I":
this.style = AnnotationBorderStyleType.INSET;
break;
case "U":
this.style = AnnotationBorderStyleType.UNDERLINE;
break;
default:
break;
}
}
setDashArray(dashArray, forceStyle = false) {
if (Array.isArray(dashArray)) {
let isValid = true;
let allZeros = true;
for (const element of dashArray) {
const validNumber = +element >= 0;
if (!validNumber) {
isValid = false;
break;
} else if (element > 0) {
allZeros = false;
}
}
if (dashArray.length === 0 || isValid && !allZeros) {
this.dashArray = dashArray;
if (forceStyle) {
this.setStyle(Name.get("D"));
}
} else {
this.width = 0;
}
} else if (dashArray) {
this.width = 0;
}
}
setHorizontalCornerRadius(radius) {
if (Number.isInteger(radius)) {
this.horizontalCornerRadius = radius;
}
}
setVerticalCornerRadius(radius) {
if (Number.isInteger(radius)) {
this.verticalCornerRadius = radius;
}
}
}
class MarkupAnnotation extends Annotation {
constructor(params) {
super(params);
const {
dict
} = params;
if (dict.has("IRT")) {
const rawIRT = dict.getRaw("IRT");
this.data.inReplyTo = rawIRT instanceof Ref ? rawIRT.toString() : null;
const rt = dict.get("RT");
this.data.replyType = rt instanceof Name ? rt.name : AnnotationReplyType.REPLY;
}
let popupRef = null;
if (this.data.replyType === AnnotationReplyType.GROUP) {
const parent = dict.get("IRT");
this.setTitle(parent.get("T"));
this.data.titleObj = this._title;
this.setContents(parent.get("Contents"));
this.data.contentsObj = this._contents;
if (!parent.has("CreationDate")) {
this.data.creationDate = null;
} else {
this.setCreationDate(parent.get("CreationDate"));
this.data.creationDate = this.creationDate;
}
if (!parent.has("M")) {
this.data.modificationDate = null;
} else {
this.setModificationDate(parent.get("M"));
this.data.modificationDate = this.modificationDate;
}
popupRef = parent.getRaw("Popup");
if (!parent.has("C")) {
this.data.color = null;
} else {
this.setColor(parent.getArray("C"));
this.data.color = this.color;
}
} else {
this.data.titleObj = this._title;
this.setCreationDate(dict.get("CreationDate"));
this.data.creationDate = this.creationDate;
popupRef = dict.getRaw("Popup");
if (!dict.has("C")) {
this.data.color = null;
}
}
this.data.popupRef = popupRef instanceof Ref ? popupRef.toString() : null;
if (dict.has("RC")) {
this.data.richText = XFAFactory.getRichTextAsHtml(dict.get("RC"));
}
}
setCreationDate(creationDate) {
this.creationDate = typeof creationDate === "string" ? creationDate : null;
}
_setDefaultAppearance({
xref,
extra,
strokeColor,
fillColor,
blendMode,
strokeAlpha,
fillAlpha,
pointsCallback
}) {
const bbox = this.data.rect = [Infinity, Infinity, -Infinity, -Infinity];
const buffer = ["q"];
if (extra) {
buffer.push(extra);
}
if (strokeColor) {
buffer.push(`${strokeColor[0]} ${strokeColor[1]} ${strokeColor[2]} RG`);
}
if (fillColor) {
buffer.push(`${fillColor[0]} ${fillColor[1]} ${fillColor[2]} rg`);
}
const pointsArray = this.data.quadPoints || Float32Array.from([this.rectangle[0], this.rectangle[3], this.rectangle[2], this.rectangle[3], this.rectangle[0], this.rectangle[1], this.rectangle[2], this.rectangle[1]]);
for (let i = 0, ii = pointsArray.length; i < ii; i += 8) {
const points = pointsCallback(buffer, pointsArray.subarray(i, i + 8));
Util.rectBoundingBox(...points, bbox);
}
buffer.push("Q");
const formDict = new Dict(xref);
const appearanceStreamDict = new Dict(xref);
appearanceStreamDict.set("Subtype", Name.get("Form"));
const appearanceStream = new StringStream(buffer.join(" "));
appearanceStream.dict = appearanceStreamDict;
formDict.set("Fm0", appearanceStream);
const gsDict = new Dict(xref);
if (blendMode) {
gsDict.set("BM", Name.get(blendMode));
}
if (typeof strokeAlpha === "number") {
gsDict.set("CA", strokeAlpha);
}
if (typeof fillAlpha === "number") {
gsDict.set("ca", fillAlpha);
}
const stateDict = new Dict(xref);
stateDict.set("GS0", gsDict);
const resources = new Dict(xref);
resources.set("ExtGState", stateDict);
resources.set("XObject", formDict);
const appearanceDict = new Dict(xref);
appearanceDict.set("Resources", resources);
appearanceDict.set("BBox", bbox);
this.appearance = new StringStream("/GS0 gs /Fm0 Do");
this.appearance.dict = appearanceDict;
this._streams.push(this.appearance, appearanceStream);
}
static async createNewAnnotation(xref, annotation, changes, params) {
const annotationRef = annotation.ref ||= xref.getNewTemporaryRef();
const ap = await this.createNewAppearanceStream(annotation, xref, params);
let annotationDict;
if (ap) {
const apRef = xref.getNewTemporaryRef();
annotationDict = this.createNewDict(annotation, xref, {
apRef
});
changes.put(apRef, {
data: ap
});
} else {
annotationDict = this.createNewDict(annotation, xref, {});
}
if (Number.isInteger(annotation.parentTreeId)) {
annotationDict.set("StructParent", annotation.parentTreeId);
}
changes.put(annotationRef, {
data: annotationDict
});
return {
ref: annotationRef
};
}
static async createNewPrintAnnotation(annotationGlobals, xref, annotation, params) {
const ap = await this.createNewAppearanceStream(annotation, xref, params);
const annotationDict = this.createNewDict(annotation, xref, ap ? {
ap
} : {});
const newAnnotation = new this.prototype.constructor({
dict: annotationDict,
xref,
annotationGlobals,
evaluatorOptions: params.evaluatorOptions
});
if (annotation.ref) {
newAnnotation.ref = newAnnotation.refToReplace = annotation.ref;
}
return newAnnotation;
}
}
class WidgetAnnotation extends Annotation {
constructor(params) {
super(params);
const {
dict,
xref,
annotationGlobals
} = params;
const data = this.data;
this._needAppearances = params.needAppearances;
data.annotationType = AnnotationType.WIDGET;
if (data.fieldName === undefined) {
data.fieldName = this._constructFieldName(dict);
}
if (data.actions === undefined) {
data.actions = collectActions(xref, dict, AnnotationActionEventType);
}
let fieldValue = getInheritableProperty({
dict,
key: "V",
getArray: true
});
data.fieldValue = this._decodeFormValue(fieldValue);
const defaultFieldValue = getInheritableProperty({
dict,
key: "DV",
getArray: true
});
data.defaultFieldValue = this._decodeFormValue(defaultFieldValue);
if (fieldValue === undefined && annotationGlobals.xfaDatasets) {
const path = this._title.str;
if (path) {
this._hasValueFromXFA = true;
data.fieldValue = fieldValue = annotationGlobals.xfaDatasets.getValue(path);
}
}
if (fieldValue === undefined && data.defaultFieldValue !== null) {
data.fieldValue = data.defaultFieldValue;
}
data.alternativeText = stringToPDFString(dict.get("TU") || "");
this.setDefaultAppearance(params);
data.hasAppearance ||= this._needAppearances && data.fieldValue !== undefined && data.fieldValue !== null;
const fieldType = getInheritableProperty({
dict,
key: "FT"
});
data.fieldType = fieldType instanceof Name ? fieldType.name : null;
const localResources = getInheritableProperty({
dict,
key: "DR"
});
const acroFormResources = annotationGlobals.acroForm.get("DR");
const appearanceResources = this.appearance?.dict.get("Resources");
this._fieldResources = {
localResources,
acroFormResources,
appearanceResources,
mergedResources: Dict.merge({
xref,
dictArray: [localResources, appearanceResources, acroFormResources],
mergeSubDicts: true
})
};
data.fieldFlags = getInheritableProperty({
dict,
key: "Ff"
});
if (!Number.isInteger(data.fieldFlags) || data.fieldFlags < 0) {
data.fieldFlags = 0;
}
data.password = this.hasFieldFlag(AnnotationFieldFlag.PASSWORD);
data.readOnly = this.hasFieldFlag(AnnotationFieldFlag.READONLY);
data.required = this.hasFieldFlag(AnnotationFieldFlag.REQUIRED);
data.hidden = this._hasFlag(data.annotationFlags, AnnotationFlag.HIDDEN) || this._hasFlag(data.annotationFlags, AnnotationFlag.NOVIEW);
}
_decodeFormValue(formValue) {
if (Array.isArray(formValue)) {
return formValue.filter(item => typeof item === "string").map(item => stringToPDFString(item));
} else if (formValue instanceof Name) {
return stringToPDFString(formValue.name);
} else if (typeof formValue === "string") {
return stringToPDFString(formValue);
}
return null;
}
hasFieldFlag(flag) {
return !!(this.data.fieldFlags & flag);
}
_isViewable(flags) {
return true;
}
mustBeViewed(annotationStorage, renderForms) {
if (renderForms) {
return this.viewable;
}
return super.mustBeViewed(annotationStorage, renderForms) && !this._hasFlag(this.flags, AnnotationFlag.NOVIEW);
}
getRotationMatrix(annotationStorage) {
let rotation = annotationStorage?.get(this.data.id)?.rotation;
if (rotation === undefined) {
rotation = this.rotation;
}
return rotation === 0 ? IDENTITY_MATRIX : getRotationMatrix(rotation, this.width, this.height);
}
getBorderAndBackgroundAppearances(annotationStorage) {
let rotation = annotationStorage?.get(this.data.id)?.rotation;
if (rotation === undefined) {
rotation = this.rotation;
}
if (!this.backgroundColor && !this.borderColor) {
return "";
}
const rect = rotation === 0 || rotation === 180 ? `0 0 ${this.width} ${this.height} re` : `0 0 ${this.height} ${this.width} re`;
let str = "";
if (this.backgroundColor) {
str = `${getPdfColor(this.backgroundColor, true)} ${rect} f `;
}
if (this.borderColor) {
const borderWidth = this.borderStyle.width || 1;
str += `${borderWidth} w ${getPdfColor(this.borderColor, false)} ${rect} S `;
}
return str;
}
async getOperatorList(evaluator, task, intent, annotationStorage) {
if (intent & RenderingIntentFlag.ANNOTATIONS_FORMS && !(this instanceof SignatureWidgetAnnotation) && !this.data.noHTML && !this.data.hasOwnCanvas) {
return {
opList: new OperatorList(),
separateForm: true,
separateCanvas: false
};
}
if (!this._hasText) {
return super.getOperatorList(evaluator, task, intent, annotationStorage);
}
const content = await this._getAppearance(evaluator, task, intent, annotationStorage);
if (this.appearance && content === null) {
return super.getOperatorList(evaluator, task, intent, annotationStorage);
}
const opList = new OperatorList();
if (!this._defaultAppearance || content === null) {
return {
opList,
separateForm: false,
separateCanvas: false
};
}
const isUsingOwnCanvas = !!(this.data.hasOwnCanvas && intent & RenderingIntentFlag.DISPLAY);
const matrix = [1, 0, 0, 1, 0, 0];
const bbox = [0, 0, this.width, this.height];
const transform = getTransformMatrix(this.data.rect, bbox, matrix);
let optionalContent;
if (this.oc) {
optionalContent = await evaluator.parseMarkedContentProps(this.oc, null);
}
if (optionalContent !== undefined) {
opList.addOp(OPS.beginMarkedContentProps, ["OC", optionalContent]);
}
opList.addOp(OPS.beginAnnotation, [this.data.id, this.data.rect, transform, this.getRotationMatrix(annotationStorage), isUsingOwnCanvas]);
const stream = new StringStream(content);
await evaluator.getOperatorList({
stream,
task,
resources: this._fieldResources.mergedResources,
operatorList: opList
});
opList.addOp(OPS.endAnnotation, []);
if (optionalContent !== undefined) {
opList.addOp(OPS.endMarkedContent, []);
}
return {
opList,
separateForm: false,
separateCanvas: isUsingOwnCanvas
};
}
_getMKDict(rotation) {
const mk = new Dict(null);
if (rotation) {
mk.set("R", rotation);
}
if (this.borderColor) {
mk.set("BC", getPdfColorArray(this.borderColor));
}
if (this.backgroundColor) {
mk.set("BG", getPdfColorArray(this.backgroundColor));
}
return mk.size > 0 ? mk : null;
}
amendSavedDict(annotationStorage, dict) {}
setValue(dict, value, xref, changes) {
const {
dict: parentDict,
ref: parentRef
} = getParentToUpdate(dict, this.ref, xref);
if (!parentDict) {
dict.set("V", value);
} else if (!changes.has(parentRef)) {
const newParentDict = parentDict.clone();
newParentDict.set("V", value);
changes.put(parentRef, {
data: newParentDict
});
return newParentDict;
}
return null;
}
async save(evaluator, task, annotationStorage, changes) {
const storageEntry = annotationStorage?.get(this.data.id);
const flags = this._buildFlags(storageEntry?.noView, storageEntry?.noPrint);
let value = storageEntry?.value,
rotation = storageEntry?.rotation;
if (value === this.data.fieldValue || value === undefined) {
if (!this._hasValueFromXFA && rotation === undefined && flags === undefined) {
return;
}
value ||= this.data.fieldValue;
}
if (rotation === undefined && !this._hasValueFromXFA && Array.isArray(value) && Array.isArray(this.data.fieldValue) && isArrayEqual(value, this.data.fieldValue) && flags === undefined) {
return;
}
if (rotation === undefined) {
rotation = this.rotation;
}
let appearance = null;
if (!this._needAppearances) {
appearance = await this._getAppearance(evaluator, task, RenderingIntentFlag.SAVE, annotationStorage);
if (appearance === null && flags === undefined) {
return;
}
} else {}
let needAppearances = false;
if (appearance?.needAppearances) {
needAppearances = true;
appearance = null;
}
const {
xref
} = evaluator;
const originalDict = xref.fetchIfRef(this.ref);
if (!(originalDict instanceof Dict)) {
return;
}
const dict = new Dict(xref);
for (const key of originalDict.getKeys()) {
if (key !== "AP") {
dict.set(key, originalDict.getRaw(key));
}
}
if (flags !== undefined) {
dict.set("F", flags);
if (appearance === null && !needAppearances) {
const ap = originalDict.getRaw("AP");
if (ap) {
dict.set("AP", ap);
}
}
}
const xfa = {
path: this.data.fieldName,
value
};
const newParentDict = this.setValue(dict, Array.isArray(value) ? value.map(stringToAsciiOrUTF16BE) : stringToAsciiOrUTF16BE(value), xref, changes);
this.amendSavedDict(annotationStorage, newParentDict || dict);
const maybeMK = this._getMKDict(rotation);
if (maybeMK) {
dict.set("MK", maybeMK);
}
changes.put(this.ref, {
data: dict,
xfa,
needAppearances
});
if (appearance !== null) {
const newRef = xref.getNewTemporaryRef();
const AP = new Dict(xref);
dict.set("AP", AP);
AP.set("N", newRef);
const resources = this._getSaveFieldResources(xref);
const appearanceStream = new StringStream(appearance);
const appearanceDict = appearanceStream.dict = new Dict(xref);
appearanceDict.set("Subtype", Name.get("Form"));
appearanceDict.set("Resources", resources);
appearanceDict.set("BBox", [0, 0, this.width, this.height]);
const rotationMatrix = this.getRotationMatrix(annotationStorage);
if (rotationMatrix !== IDENTITY_MATRIX) {
appearanceDict.set("Matrix", rotationMatrix);
}
changes.put(newRef, {
data: appearanceStream,
xfa: null,
needAppearances: false
});
}
dict.set("M", `D:${getModificationDate()}`);
}
async _getAppearance(evaluator, task, intent, annotationStorage) {
if (this.data.password) {
return null;
}
const storageEntry = annotationStorage?.get(this.data.id);
let value, rotation;
if (storageEntry) {
value = storageEntry.formattedValue || storageEntry.value;
rotation = storageEntry.rotation;
}
if (rotation === undefined && value === undefined && !this._needAppearances) {
if (!this._hasValueFromXFA || this.appearance) {
return null;
}
}
const colors = this.getBorderAndBackgroundAppearances(annotationStorage);
if (value === undefined) {
value = this.data.fieldValue;
if (!value) {
return `/Tx BMC q ${colors}Q EMC`;
}
}
if (Array.isArray(value) && value.length === 1) {
value = value[0];
}
assert(typeof value === "string", "Expected `value` to be a string.");
value = value.trimEnd();
if (this.data.combo) {
const option = this.data.options.find(({
exportValue
}) => value === exportValue);
value = option?.displayValue || value;
}
if (value === "") {
return `/Tx BMC q ${colors}Q EMC`;
}
if (rotation === undefined) {
rotation = this.rotation;
}
let lineCount = -1;
let lines;
if (this.data.multiLine) {
lines = value.split(/\r\n?|\n/).map(line => line.normalize("NFC"));
lineCount = lines.length;
} else {
lines = [value.replace(/\r\n?|\n/, "").normalize("NFC")];
}
const defaultPadding = 1;
const defaultHPadding = 2;
let {
width: totalWidth,
height: totalHeight
} = this;
if (rotation === 90 || rotation === 270) {
[totalWidth, totalHeight] = [totalHeight, totalWidth];
}
if (!this._defaultAppearance) {
this.data.defaultAppearanceData = parseDefaultAppearance(this._defaultAppearance = "/Helvetica 0 Tf 0 g");
}
let font = await WidgetAnnotation._getFontData(evaluator, task, this.data.defaultAppearanceData, this._fieldResources.mergedResources);
let defaultAppearance, fontSize, lineHeight;
const encodedLines = [];
let encodingError = false;
for (const line of lines) {
const encodedString = font.encodeString(line);
if (encodedString.length > 1) {
encodingError = true;
}
encodedLines.push(encodedString.join(""));
}
if (encodingError && intent & RenderingIntentFlag.SAVE) {
return {
needAppearances: true
};
}
if (encodingError && this._isOffscreenCanvasSupported) {
const fontFamily = this.data.comb ? "monospace" : "sans-serif";
const fakeUnicodeFont = new FakeUnicodeFont(evaluator.xref, fontFamily);
const resources = fakeUnicodeFont.createFontResources(lines.join(""));
const newFont = resources.getRaw("Font");
if (this._fieldResources.mergedResources.has("Font")) {
const oldFont = this._fieldResources.mergedResources.get("Font");
for (const key of newFont.getKeys()) {
oldFont.set(key, newFont.getRaw(key));
}
} else {
this._fieldResources.mergedResources.set("Font", newFont);
}
const fontName = fakeUnicodeFont.fontName.name;
font = await WidgetAnnotation._getFontData(evaluator, task, {
fontName,
fontSize: 0
}, resources);
for (let i = 0, ii = encodedLines.length; i < ii; i++) {
encodedLines[i] = stringToUTF16String(lines[i]);
}
const savedDefaultAppearance = Object.assign(Object.create(null), this.data.defaultAppearanceData);
this.data.defaultAppearanceData.fontSize = 0;
this.data.defaultAppearanceData.fontName = fontName;
[defaultAppearance, fontSize, lineHeight] = this._computeFontSize(totalHeight - 2 * defaultPadding, totalWidth - 2 * defaultHPadding, value, font, lineCount);
this.data.defaultAppearanceData = savedDefaultAppearance;
} else {
if (!this._isOffscreenCanvasSupported) {
warn("_getAppearance: OffscreenCanvas is not supported, annotation may not render correctly.");
}
[defaultAppearance, fontSize, lineHeight] = this._computeFontSize(totalHeight - 2 * defaultPadding, totalWidth - 2 * defaultHPadding, value, font, lineCount);
}
let descent = font.descent;
if (isNaN(descent)) {
descent = BASELINE_FACTOR * lineHeight;
} else {
descent = Math.max(BASELINE_FACTOR * lineHeight, Math.abs(descent) * fontSize);
}
const defaultVPadding = Math.min(Math.floor((totalHeight - fontSize) / 2), defaultPadding);
const alignment = this.data.textAlignment;
if (this.data.multiLine) {
return this._getMultilineAppearance(defaultAppearance, encodedLines, font, fontSize, totalWidth, totalHeight, alignment, defaultHPadding, defaultVPadding, descent, lineHeight, annotationStorage);
}
if (this.data.comb) {
return this._getCombAppearance(defaultAppearance, font, encodedLines[0], fontSize, totalWidth, totalHeight, defaultHPadding, defaultVPadding, descent, lineHeight, annotationStorage);
}
const bottomPadding = defaultVPadding + descent;
if (alignment === 0 || alignment > 2) {
return `/Tx BMC q ${colors}BT ` + defaultAppearance + ` 1 0 0 1 ${numberToString(defaultHPadding)} ${numberToString(bottomPadding)} Tm (${escapeString(encodedLines[0])}) Tj` + " ET Q EMC";
}
const prevInfo = {
shift: 0
};
const renderedText = this._renderText(encodedLines[0], font, fontSize, totalWidth, alignment, prevInfo, defaultHPadding, bottomPadding);
return `/Tx BMC q ${colors}BT ` + defaultAppearance + ` 1 0 0 1 0 0 Tm ${renderedText}` + " ET Q EMC";
}
static async _getFontData(evaluator, task, appearanceData, resources) {
const operatorList = new OperatorList();
const initialState = {
font: null,
clone() {
return this;
}
};
const {
fontName,
fontSize
} = appearanceData;
await evaluator.handleSetFont(resources, [fontName && Name.get(fontName), fontSize], null, operatorList, task, initialState, null);
return initialState.font;
}
_getTextWidth(text, font) {
return Math.sumPrecise(font.charsToGlyphs(text).map(g => g.width)) / 1000;
}
_computeFontSize(height, width, text, font, lineCount) {
let {
fontSize
} = this.data.defaultAppearanceData;
let lineHeight = (fontSize || 12) * LINE_FACTOR,
numberOfLines = Math.round(height / lineHeight);
if (!fontSize) {
const roundWithTwoDigits = x => Math.floor(x * 100) / 100;
if (lineCount === -1) {
const textWidth = this._getTextWidth(text, font);
fontSize = roundWithTwoDigits(Math.min(height / LINE_FACTOR, width / textWidth));
numberOfLines = 1;
} else {
const lines = text.split(/\r\n?|\n/);
const cachedLines = [];
for (const line of lines) {
const encoded = font.encodeString(line).join("");
const glyphs = font.charsToGlyphs(encoded);
const positions = font.getCharPositions(encoded);
cachedLines.push({
line: encoded,
glyphs,
positions
});
}
const isTooBig = fsize => {
let totalHeight = 0;
for (const cache of cachedLines) {
const chunks = this._splitLine(null, font, fsize, width, cache);
totalHeight += chunks.length * fsize;
if (totalHeight > height) {
return true;
}
}
return false;
};
numberOfLines = Math.max(numberOfLines, lineCount);
while (true) {
lineHeight = height / numberOfLines;
fontSize = roundWithTwoDigits(lineHeight / LINE_FACTOR);
if (isTooBig(fontSize)) {
numberOfLines++;
continue;
}
break;
}
}
const {
fontName,
fontColor
} = this.data.defaultAppearanceData;
this._defaultAppearance = createDefaultAppearance({
fontSize,
fontName,
fontColor
});
}
return [this._defaultAppearance, fontSize, height / numberOfLines];
}
_renderText(text, font, fontSize, totalWidth, alignment, prevInfo, hPadding, vPadding) {
let shift;
if (alignment === 1) {
const width = this._getTextWidth(text, font) * fontSize;
shift = (totalWidth - width) / 2;
} else if (alignment === 2) {
const width = this._getTextWidth(text, font) * fontSize;
shift = totalWidth - width - hPadding;
} else {
shift = hPadding;
}
const shiftStr = numberToString(shift - prevInfo.shift);
prevInfo.shift = shift;
vPadding = numberToString(vPadding);
return `${shiftStr} ${vPadding} Td (${escapeString(text)}) Tj`;
}
_getSaveFieldResources(xref) {
const {
localResources,
appearanceResources,
acroFormResources
} = this._fieldResources;
const fontName = this.data.defaultAppearanceData?.fontName;
if (!fontName) {
return localResources || Dict.empty;
}
for (const resources of [localResources, appearanceResources]) {
if (resources instanceof Dict) {
const localFont = resources.get("Font");
if (localFont instanceof Dict && localFont.has(fontName)) {
return resources;
}
}
}
if (acroFormResources instanceof Dict) {
const acroFormFont = acroFormResources.get("Font");
if (acroFormFont instanceof Dict && acroFormFont.has(fontName)) {
const subFontDict = new Dict(xref);
subFontDict.set(fontName, acroFormFont.getRaw(fontName));
const subResourcesDict = new Dict(xref);
subResourcesDict.set("Font", subFontDict);
return Dict.merge({
xref,
dictArray: [subResourcesDict, localResources],
mergeSubDicts: true
});
}
}
return localResources || Dict.empty;
}
getFieldObject() {
return null;
}
}
class TextWidgetAnnotation extends WidgetAnnotation {
constructor(params) {
super(params);
const {
dict
} = params;
if (dict.has("PMD")) {
this.flags |= AnnotationFlag.HIDDEN;
this.data.hidden = true;
warn("Barcodes are not supported");
}
this.data.hasOwnCanvas = this.data.readOnly && !this.data.noHTML;
this._hasText = true;
if (typeof this.data.fieldValue !== "string") {
this.data.fieldValue = "";
}
let alignment = getInheritableProperty({
dict,
key: "Q"
});
if (!Number.isInteger(alignment) || alignment < 0 || alignment > 2) {
alignment = null;
}
this.data.textAlignment = alignment;
let maximumLength = getInheritableProperty({
dict,
key: "MaxLen"
});
if (!Number.isInteger(maximumLength) || maximumLength < 0) {
maximumLength = 0;
}
this.data.maxLen = maximumLength;
this.data.multiLine = this.hasFieldFlag(AnnotationFieldFlag.MULTILINE);
this.data.comb = this.hasFieldFlag(AnnotationFieldFlag.COMB) && !this.data.multiLine && !this.data.password && !this.hasFieldFlag(AnnotationFieldFlag.FILESELECT) && this.data.maxLen !== 0;
this.data.doNotScroll = this.hasFieldFlag(AnnotationFieldFlag.DONOTSCROLL);
}
get hasTextContent() {
return !!this.appearance && !this._needAppearances;
}
_getCombAppearance(defaultAppearance, font, text, fontSize, width, height, hPadding, vPadding, descent, lineHeight, annotationStorage) {
const combWidth = width / this.data.maxLen;
const colors = this.getBorderAndBackgroundAppearances(annotationStorage);
const buf = [];
const positions = font.getCharPositions(text);
for (const [start, end] of positions) {
buf.push(`(${escapeString(text.substring(start, end))}) Tj`);
}
const renderedComb = buf.join(` ${numberToString(combWidth)} 0 Td `);
return `/Tx BMC q ${colors}BT ` + defaultAppearance + ` 1 0 0 1 ${numberToString(hPadding)} ${numberToString(vPadding + descent)} Tm ${renderedComb}` + " ET Q EMC";
}
_getMultilineAppearance(defaultAppearance, lines, font, fontSize, width, height, alignment, hPadding, vPadding, descent, lineHeight, annotationStorage) {
const buf = [];
const totalWidth = width - 2 * hPadding;
const prevInfo = {
shift: 0
};
for (let i = 0, ii = lines.length; i < ii; i++) {
const line = lines[i];
const chunks = this._splitLine(line, font, fontSize, totalWidth);
for (let j = 0, jj = chunks.length; j < jj; j++) {
const chunk = chunks[j];
const vShift = i === 0 && j === 0 ? -vPadding - (lineHeight - descent) : -lineHeight;
buf.push(this._renderText(chunk, font, fontSize, width, alignment, prevInfo, hPadding, vShift));
}
}
const colors = this.getBorderAndBackgroundAppearances(annotationStorage);
const renderedText = buf.join("\n");
return `/Tx BMC q ${colors}BT ` + defaultAppearance + ` 1 0 0 1 0 ${numberToString(height)} Tm ${renderedText}` + " ET Q EMC";
}
_splitLine(line, font, fontSize, width, cache = {}) {
line = cache.line || line;
const glyphs = cache.glyphs || font.charsToGlyphs(line);
if (glyphs.length <= 1) {
return [line];
}
const positions = cache.positions || font.getCharPositions(line);
const scale = fontSize / 1000;
const chunks = [];
let lastSpacePosInStringStart = -1,
lastSpacePosInStringEnd = -1,
lastSpacePos = -1,
startChunk = 0,
currentWidth = 0;
for (let i = 0, ii = glyphs.length; i < ii; i++) {
const [start, end] = positions[i];
const glyph = glyphs[i];
const glyphWidth = glyph.width * scale;
if (glyph.unicode === " ") {
if (currentWidth + glyphWidth > width) {
chunks.push(line.substring(startChunk, start));
startChunk = start;
currentWidth = glyphWidth;
lastSpacePosInStringStart = -1;
lastSpacePos = -1;
} else {
currentWidth += glyphWidth;
lastSpacePosInStringStart = start;
lastSpacePosInStringEnd = end;
lastSpacePos = i;
}
} else if (currentWidth + glyphWidth > width) {
if (lastSpacePosInStringStart !== -1) {
chunks.push(line.substring(startChunk, lastSpacePosInStringEnd));
startChunk = lastSpacePosInStringEnd;
i = lastSpacePos + 1;
lastSpacePosInStringStart = -1;
currentWidth = 0;
} else {
chunks.push(line.substring(startChunk, start));
startChunk = start;
currentWidth = glyphWidth;
}
} else {
currentWidth += glyphWidth;
}
}
if (startChunk < line.length) {
chunks.push(line.substring(startChunk, line.length));
}
return chunks;
}
async extractTextContent(evaluator, task, viewBox) {
await super.extractTextContent(evaluator, task, viewBox);
const text = this.data.textContent;
if (!text) {
return;
}
const allText = text.join("\n");
if (allText === this.data.fieldValue) {
return;
}
const regex = allText.replaceAll(/([.*+?^${}()|[\]\\])|(\s+)/g, (_m, p1) => p1 ? `\\${p1}` : "\\s+");
if (new RegExp(`^\\s*${regex}\\s*$`).test(this.data.fieldValue)) {
this.data.textContent = this.data.fieldValue.split("\n");
}
}
getFieldObject() {
return {
id: this.data.id,
value: this.data.fieldValue,
defaultValue: this.data.defaultFieldValue || "",
multiline: this.data.multiLine,
password: this.data.password,
charLimit: this.data.maxLen,
comb: this.data.comb,
editable: !this.data.readOnly,
hidden: this.data.hidden,
name: this.data.fieldName,
rect: this.data.rect,
actions: this.data.actions,
page: this.data.pageIndex,
strokeColor: this.data.borderColor,
fillColor: this.data.backgroundColor,
rotation: this.rotation,
type: "text"
};
}
}
class ButtonWidgetAnnotation extends WidgetAnnotation {
constructor(params) {
super(params);
this.checkedAppearance = null;
this.uncheckedAppearance = null;
const isRadio = this.hasFieldFlag(AnnotationFieldFlag.RADIO),
isPushButton = this.hasFieldFlag(AnnotationFieldFlag.PUSHBUTTON);
this.data.checkBox = !isRadio && !isPushButton;
this.data.radioButton = isRadio && !isPushButton;
this.data.pushButton = isPushButton;
this.data.isTooltipOnly = false;
if (this.data.checkBox) {
this._processCheckBox(params);
} else if (this.data.radioButton) {
this._processRadioButton(params);
} else if (this.data.pushButton) {
this.data.hasOwnCanvas = true;
this.data.noHTML = false;
this._processPushButton(params);
} else {
warn("Invalid field flags for button widget annotation");
}
}
async getOperatorList(evaluator, task, intent, annotationStorage) {
if (this.data.pushButton) {
return super.getOperatorList(evaluator, task, intent, false, annotationStorage);
}
let value = null;
let rotation = null;
if (annotationStorage) {
const storageEntry = annotationStorage.get(this.data.id);
value = storageEntry ? storageEntry.value : null;
rotation = storageEntry ? storageEntry.rotation : null;
}
if (value === null && this.appearance) {
return super.getOperatorList(evaluator, task, intent, annotationStorage);
}
if (value === null || value === undefined) {
value = this.data.checkBox ? this.data.fieldValue === this.data.exportValue : this.data.fieldValue === this.data.buttonValue;
}
const appearance = value ? this.checkedAppearance : this.uncheckedAppearance;
if (appearance) {
const savedAppearance = this.appearance;
const savedMatrix = lookupMatrix(appearance.dict.getArray("Matrix"), IDENTITY_MATRIX);
if (rotation) {
appearance.dict.set("Matrix", this.getRotationMatrix(annotationStorage));
}
this.appearance = appearance;
const operatorList = super.getOperatorList(evaluator, task, intent, annotationStorage);
this.appearance = savedAppearance;
appearance.dict.set("Matrix", savedMatrix);
return operatorList;
}
return {
opList: new OperatorList(),
separateForm: false,
separateCanvas: false
};
}
async save(evaluator, task, annotationStorage, changes) {
if (this.data.checkBox) {
this._saveCheckbox(evaluator, task, annotationStorage, changes);
return;
}
if (this.data.radioButton) {
this._saveRadioButton(evaluator, task, annotationStorage, changes);
}
}
async _saveCheckbox(evaluator, task, annotationStorage, changes) {
if (!annotationStorage) {
return;
}
const storageEntry = annotationStorage.get(this.data.id);
const flags = this._buildFlags(storageEntry?.noView, storageEntry?.noPrint);
let rotation = storageEntry?.rotation,
value = storageEntry?.value;
if (rotation === undefined && flags === undefined) {
if (value === undefined) {
return;
}
const defaultValue = this.data.fieldValue === this.data.exportValue;
if (defaultValue === value) {
return;
}
}
let dict = evaluator.xref.fetchIfRef(this.ref);
if (!(dict instanceof Dict)) {
return;
}
dict = dict.clone();
if (rotation === undefined) {
rotation = this.rotation;
}
if (value === undefined) {
value = this.data.fieldValue === this.data.exportValue;
}
const xfa = {
path: this.data.fieldName,
value: value ? this.data.exportValue : ""
};
const name = Name.get(value ? this.data.exportValue : "Off");
this.setValue(dict, name, evaluator.xref, changes);
dict.set("AS", name);
dict.set("M", `D:${getModificationDate()}`);
if (flags !== undefined) {
dict.set("F", flags);
}
const maybeMK = this._getMKDict(rotation);
if (maybeMK) {
dict.set("MK", maybeMK);
}
changes.put(this.ref, {
data: dict,
xfa,
needAppearances: false
});
}
async _saveRadioButton(evaluator, task, annotationStorage, changes) {
if (!annotationStorage) {
return;
}
const storageEntry = annotationStorage.get(this.data.id);
const flags = this._buildFlags(storageEntry?.noView, storageEntry?.noPrint);
let rotation = storageEntry?.rotation,
value = storageEntry?.value;
if (rotation === undefined && flags === undefined) {
if (value === undefined) {
return;
}
const defaultValue = this.data.fieldValue === this.data.buttonValue;
if (defaultValue === value) {
return;
}
}
let dict = evaluator.xref.fetchIfRef(this.ref);
if (!(dict instanceof Dict)) {
return;
}
dict = dict.clone();
if (value === undefined) {
value = this.data.fieldValue === this.data.buttonValue;
}
if (rotation === undefined) {
rotation = this.rotation;
}
const xfa = {
path: this.data.fieldName,
value: value ? this.data.buttonValue : ""
};
const name = Name.get(value ? this.data.buttonValue : "Off");
if (value) {
this.setValue(dict, name, evaluator.xref, changes);
}
dict.set("AS", name);
dict.set("M", `D:${getModificationDate()}`);
if (flags !== undefined) {
dict.set("F", flags);
}
const maybeMK = this._getMKDict(rotation);
if (maybeMK) {
dict.set("MK", maybeMK);
}
changes.put(this.ref, {
data: dict,
xfa,
needAppearances: false
});
}
_getDefaultCheckedAppearance(params, type) {
const {
width,
height
} = this;
const bbox = [0, 0, width, height];
const FONT_RATIO = 0.8;
const fontSize = Math.min(width, height) * FONT_RATIO;
let metrics, char;
if (type === "check") {
metrics = {
width: 0.755 * fontSize,
height: 0.705 * fontSize
};
char = "\x33";
} else if (type === "disc") {
metrics = {
width: 0.791 * fontSize,
height: 0.705 * fontSize
};
char = "\x6C";
} else {
unreachable(`_getDefaultCheckedAppearance - unsupported type: ${type}`);
}
const xShift = numberToString((width - metrics.width) / 2);
const yShift = numberToString((height - metrics.height) / 2);
const appearance = `q BT /PdfJsZaDb ${fontSize} Tf 0 g ${xShift} ${yShift} Td (${char}) Tj ET Q`;
const appearanceStreamDict = new Dict(params.xref);
appearanceStreamDict.set("FormType", 1);
appearanceStreamDict.set("Subtype", Name.get("Form"));
appearanceStreamDict.set("Type", Name.get("XObject"));
appearanceStreamDict.set("BBox", bbox);
appearanceStreamDict.set("Matrix", [1, 0, 0, 1, 0, 0]);
appearanceStreamDict.set("Length", appearance.length);
const resources = new Dict(params.xref);
const font = new Dict(params.xref);
font.set("PdfJsZaDb", this.fallbackFontDict);
resources.set("Font", font);
appearanceStreamDict.set("Resources", resources);
this.checkedAppearance = new StringStream(appearance);
this.checkedAppearance.dict = appearanceStreamDict;
this._streams.push(this.checkedAppearance);
}
_processCheckBox(params) {
const customAppearance = params.dict.get("AP");
if (!(customAppearance instanceof Dict)) {
return;
}
const normalAppearance = customAppearance.get("N");
if (!(normalAppearance instanceof Dict)) {
return;
}
const asValue = this._decodeFormValue(params.dict.get("AS"));
if (typeof asValue === "string") {
this.data.fieldValue = asValue;
}
const yes = this.data.fieldValue !== null && this.data.fieldValue !== "Off" ? this.data.fieldValue : "Yes";
const exportValues = normalAppearance.getKeys();
if (exportValues.length === 0) {
exportValues.push("Off", yes);
} else if (exportValues.length === 1) {
if (exportValues[0] === "Off") {
exportValues.push(yes);
} else {
exportValues.unshift("Off");
}
} else if (exportValues.includes(yes)) {
exportValues.length = 0;
exportValues.push("Off", yes);
} else {
const otherYes = exportValues.find(v => v !== "Off");
exportValues.length = 0;
exportValues.push("Off", otherYes);
}
if (!exportValues.includes(this.data.fieldValue)) {
this.data.fieldValue = "Off";
}
this.data.exportValue = exportValues[1];
const checkedAppearance = normalAppearance.get(this.data.exportValue);
this.checkedAppearance = checkedAppearance instanceof BaseStream ? checkedAppearance : null;
const uncheckedAppearance = normalAppearance.get("Off");
this.uncheckedAppearance = uncheckedAppearance instanceof BaseStream ? uncheckedAppearance : null;
if (this.checkedAppearance) {
this._streams.push(this.checkedAppearance);
} else {
this._getDefaultCheckedAppearance(params, "check");
}
if (this.uncheckedAppearance) {
this._streams.push(this.uncheckedAppearance);
}
this._fallbackFontDict = this.fallbackFontDict;
if (this.data.defaultFieldValue === null) {
this.data.defaultFieldValue = "Off";
}
}
_processRadioButton(params) {
this.data.buttonValue = null;
const fieldParent = params.dict.get("Parent");
if (fieldParent instanceof Dict) {
this.parent = params.dict.getRaw("Parent");
const fieldParentValue = fieldParent.get("V");
if (fieldParentValue instanceof Name) {
this.data.fieldValue = this._decodeFormValue(fieldParentValue);
}
}
const appearanceStates = params.dict.get("AP");
if (!(appearanceStates instanceof Dict)) {
return;
}
const normalAppearance = appearanceStates.get("N");
if (!(normalAppearance instanceof Dict)) {
return;
}
for (const key of normalAppearance.getKeys()) {
if (key !== "Off") {
this.data.buttonValue = this._decodeFormValue(key);
break;
}
}
const checkedAppearance = normalAppearance.get(this.data.buttonValue);
this.checkedAppearance = checkedAppearance instanceof BaseStream ? checkedAppearance : null;
const uncheckedAppearance = normalAppearance.get("Off");
this.uncheckedAppearance = uncheckedAppearance instanceof BaseStream ? uncheckedAppearance : null;
if (this.checkedAppearance) {
this._streams.push(this.checkedAppearance);
} else {
this._getDefaultCheckedAppearance(params, "disc");
}
if (this.uncheckedAppearance) {
this._streams.push(this.uncheckedAppearance);
}
this._fallbackFontDict = this.fallbackFontDict;
if (this.data.defaultFieldValue === null) {
this.data.defaultFieldValue = "Off";
}
}
_processPushButton(params) {
const {
dict,
annotationGlobals
} = params;
if (!dict.has("A") && !dict.has("AA") && !this.data.alternativeText) {
warn("Push buttons without action dictionaries are not supported");
return;
}
this.data.isTooltipOnly = !dict.has("A") && !dict.has("AA");
Catalog.parseDestDictionary({
destDict: dict,
resultObj: this.data,
docBaseUrl: annotationGlobals.baseUrl,
docAttachments: annotationGlobals.attachments
});
}
getFieldObject() {
let type = "button";
let exportValues;
if (this.data.checkBox) {
type = "checkbox";
exportValues = this.data.exportValue;
} else if (this.data.radioButton) {
type = "radiobutton";
exportValues = this.data.buttonValue;
}
return {
id: this.data.id,
value: this.data.fieldValue || "Off",
defaultValue: this.data.defaultFieldValue,
exportValues,
editable: !this.data.readOnly,
name: this.data.fieldName,
rect: this.data.rect,
hidden: this.data.hidden,
actions: this.data.actions,
page: this.data.pageIndex,
strokeColor: this.data.borderColor,
fillColor: this.data.backgroundColor,
rotation: this.rotation,
type
};
}
get fallbackFontDict() {
const dict = new Dict();
dict.set("BaseFont", Name.get("ZapfDingbats"));
dict.set("Type", Name.get("FallbackType"));
dict.set("Subtype", Name.get("FallbackType"));
dict.set("Encoding", Name.get("ZapfDingbatsEncoding"));
return shadow(this, "fallbackFontDict", dict);
}
}
class ChoiceWidgetAnnotation extends WidgetAnnotation {
constructor(params) {
super(params);
const {
dict,
xref
} = params;
this.indices = dict.getArray("I");
this.hasIndices = Array.isArray(this.indices) && this.indices.length > 0;
this.data.options = [];
const options = getInheritableProperty({
dict,
key: "Opt"
});
if (Array.isArray(options)) {
for (let i = 0, ii = options.length; i < ii; i++) {
const option = xref.fetchIfRef(options[i]);
const isOptionArray = Array.isArray(option);
this.data.options[i] = {
exportValue: this._decodeFormValue(isOptionArray ? xref.fetchIfRef(option[0]) : option),
displayValue: this._decodeFormValue(isOptionArray ? xref.fetchIfRef(option[1]) : option)
};
}
}
if (!this.hasIndices) {
if (typeof this.data.fieldValue === "string") {
this.data.fieldValue = [this.data.fieldValue];
} else if (!this.data.fieldValue) {
this.data.fieldValue = [];
}
} else {
this.data.fieldValue = [];
const ii = this.data.options.length;
for (const i of this.indices) {
if (Number.isInteger(i) && i >= 0 && i < ii) {
this.data.fieldValue.push(this.data.options[i].exportValue);
}
}
}
if (this.data.options.length === 0 && this.data.fieldValue.length > 0) {
this.data.options = this.data.fieldValue.map(value => ({
exportValue: value,
displayValue: value
}));
}
this.data.combo = this.hasFieldFlag(AnnotationFieldFlag.COMBO);
this.data.multiSelect = this.hasFieldFlag(AnnotationFieldFlag.MULTISELECT);
this._hasText = true;
}
getFieldObject() {
const type = this.data.combo ? "combobox" : "listbox";
const value = this.data.fieldValue.length > 0 ? this.data.fieldValue[0] : null;
return {
id: this.data.id,
value,
defaultValue: this.data.defaultFieldValue,
editable: !this.data.readOnly,
name: this.data.fieldName,
rect: this.data.rect,
numItems: this.data.fieldValue.length,
multipleSelection: this.data.multiSelect,
hidden: this.data.hidden,
actions: this.data.actions,
items: this.data.options,
page: this.data.pageIndex,
strokeColor: this.data.borderColor,
fillColor: this.data.backgroundColor,
rotation: this.rotation,
type
};
}
amendSavedDict(annotationStorage, dict) {
if (!this.hasIndices) {
return;
}
let values = annotationStorage?.get(this.data.id)?.value;
if (!Array.isArray(values)) {
values = [values];
}
const indices = [];
const {
options
} = this.data;
for (let i = 0, j = 0, ii = options.length; i < ii; i++) {
if (options[i].exportValue === values[j]) {
indices.push(i);
j += 1;
}
}
dict.set("I", indices);
}
async _getAppearance(evaluator, task, intent, annotationStorage) {
if (this.data.combo) {
return super._getAppearance(evaluator, task, intent, annotationStorage);
}
let exportedValue, rotation;
const storageEntry = annotationStorage?.get(this.data.id);
if (storageEntry) {
rotation = storageEntry.rotation;
exportedValue = storageEntry.value;
}
if (rotation === undefined && exportedValue === undefined && !this._needAppearances) {
return null;
}
if (exportedValue === undefined) {
exportedValue = this.data.fieldValue;
} else if (!Array.isArray(exportedValue)) {
exportedValue = [exportedValue];
}
const defaultPadding = 1;
const defaultHPadding = 2;
let {
width: totalWidth,
height: totalHeight
} = this;
if (rotation === 90 || rotation === 270) {
[totalWidth, totalHeight] = [totalHeight, totalWidth];
}
const lineCount = this.data.options.length;
const valueIndices = [];
for (let i = 0; i < lineCount; i++) {
const {
exportValue
} = this.data.options[i];
if (exportedValue.includes(exportValue)) {
valueIndices.push(i);
}
}
if (!this._defaultAppearance) {
this.data.defaultAppearanceData = parseDefaultAppearance(this._defaultAppearance = "/Helvetica 0 Tf 0 g");
}
const font = await WidgetAnnotation._getFontData(evaluator, task, this.data.defaultAppearanceData, this._fieldResources.mergedResources);
let defaultAppearance;
let {
fontSize
} = this.data.defaultAppearanceData;
if (!fontSize) {
const lineHeight = (totalHeight - defaultPadding) / lineCount;
let lineWidth = -1;
let value;
for (const {
displayValue
} of this.data.options) {
const width = this._getTextWidth(displayValue, font);
if (width > lineWidth) {
lineWidth = width;
value = displayValue;
}
}
[defaultAppearance, fontSize] = this._computeFontSize(lineHeight, totalWidth - 2 * defaultHPadding, value, font, -1);
} else {
defaultAppearance = this._defaultAppearance;
}
const lineHeight = fontSize * LINE_FACTOR;
const vPadding = (lineHeight - fontSize) / 2;
const numberOfVisibleLines = Math.floor(totalHeight / lineHeight);
let firstIndex = 0;
if (valueIndices.length > 0) {
const minIndex = Math.min(...valueIndices);
const maxIndex = Math.max(...valueIndices);
firstIndex = Math.max(0, maxIndex - numberOfVisibleLines + 1);
if (firstIndex > minIndex) {
firstIndex = minIndex;
}
}
const end = Math.min(firstIndex + numberOfVisibleLines + 1, lineCount);
const buf = ["/Tx BMC q", `1 1 ${totalWidth} ${totalHeight} re W n`];
if (valueIndices.length) {
buf.push("0.600006 0.756866 0.854904 rg");
for (const index of valueIndices) {
if (firstIndex <= index && index < end) {
buf.push(`1 ${totalHeight - (index - firstIndex + 1) * lineHeight} ${totalWidth} ${lineHeight} re f`);
}
}
}
buf.push("BT", defaultAppearance, `1 0 0 1 0 ${totalHeight} Tm`);
const prevInfo = {
shift: 0
};
for (let i = firstIndex; i < end; i++) {
const {
displayValue
} = this.data.options[i];
const vpadding = i === firstIndex ? vPadding : 0;
buf.push(this._renderText(displayValue, font, fontSize, totalWidth, 0, prevInfo, defaultHPadding, -lineHeight + vpadding));
}
buf.push("ET Q EMC");
return buf.join("\n");
}
}
class SignatureWidgetAnnotation extends WidgetAnnotation {
constructor(params) {
super(params);
this.data.fieldValue = null;
this.data.hasOwnCanvas = this.data.noRotate;
this.data.noHTML = !this.data.hasOwnCanvas;
}
getFieldObject() {
return {
id: this.data.id,
value: null,
page: this.data.pageIndex,
type: "signature"
};
}
}
class TextAnnotation extends MarkupAnnotation {
constructor(params) {
const DEFAULT_ICON_SIZE = 22;
super(params);
this.data.noRotate = true;
this.data.hasOwnCanvas = this.data.noRotate;
this.data.noHTML = false;
const {
dict
} = params;
this.data.annotationType = AnnotationType.TEXT;
if (this.data.hasAppearance) {
this.data.name = "NoIcon";
} else {
this.data.rect[1] = this.data.rect[3] - DEFAULT_ICON_SIZE;
this.data.rect[2] = this.data.rect[0] + DEFAULT_ICON_SIZE;
this.data.name = dict.has("Name") ? dict.get("Name").name : "Note";
}
if (dict.has("State")) {
this.data.state = dict.get("State") || null;
this.data.stateModel = dict.get("StateModel") || null;
} else {
this.data.state = null;
this.data.stateModel = null;
}
}
}
class LinkAnnotation extends Annotation {
constructor(params) {
super(params);
const {
dict,
annotationGlobals
} = params;
this.data.annotationType = AnnotationType.LINK;
this.data.noHTML = false;
const quadPoints = getQuadPoints(dict, this.rectangle);
if (quadPoints) {
this.data.quadPoints = quadPoints;
}
this.data.borderColor ||= this.data.color;
Catalog.parseDestDictionary({
destDict: dict,
resultObj: this.data,
docBaseUrl: annotationGlobals.baseUrl,
docAttachments: annotationGlobals.attachments
});
}
}
class PopupAnnotation extends Annotation {
constructor(params) {
super(params);
const {
dict
} = params;
this.data.annotationType = AnnotationType.POPUP;
this.data.noHTML = false;
if (this.width === 0 || this.height === 0) {
this.data.rect = null;
}
let parentItem = dict.get("Parent");
if (!parentItem) {
warn("Popup annotation has a missing or invalid parent annotation.");
return;
}
this.data.parentRect = lookupNormalRect(parentItem.getArray("Rect"), null);
const rt = parentItem.get("RT");
if (isName(rt, AnnotationReplyType.GROUP)) {
parentItem = parentItem.get("IRT");
}
if (!parentItem.has("M")) {
this.data.modificationDate = null;
} else {
this.setModificationDate(parentItem.get("M"));
this.data.modificationDate = this.modificationDate;
}
if (!parentItem.has("C")) {
this.data.color = null;
} else {
this.setColor(parentItem.getArray("C"));
this.data.color = this.color;
}
if (!this.viewable) {
const parentFlags = parentItem.get("F");
if (this._isViewable(parentFlags)) {
this.setFlags(parentFlags);
}
}
this.setTitle(parentItem.get("T"));
this.data.titleObj = this._title;
this.setContents(parentItem.get("Contents"));
this.data.contentsObj = this._contents;
if (parentItem.has("RC")) {
this.data.richText = XFAFactory.getRichTextAsHtml(parentItem.get("RC"));
}
this.data.open = !!dict.get("Open");
}
}
class FreeTextAnnotation extends MarkupAnnotation {
constructor(params) {
super(params);
this.data.hasOwnCanvas = this.data.noRotate;
this.data.isEditable = !this.data.noHTML;
this.data.noHTML = false;
const {
annotationGlobals,
evaluatorOptions,
xref
} = params;
this.data.annotationType = AnnotationType.FREETEXT;
this.setDefaultAppearance(params);
this._hasAppearance = !!this.appearance;
if (this._hasAppearance) {
const {
fontColor,
fontSize
} = parseAppearanceStream(this.appearance, evaluatorOptions, xref, annotationGlobals.globalColorSpaceCache);
this.data.defaultAppearanceData.fontColor = fontColor;
this.data.defaultAppearanceData.fontSize = fontSize || 10;
} else {
this.data.defaultAppearanceData.fontSize ||= 10;
const {
fontColor,
fontSize
} = this.data.defaultAppearanceData;
if (this._contents.str) {
this.data.textContent = this._contents.str.split(/\r\n?|\n/).map(line => line.trimEnd());
const {
coords,
bbox,
matrix
} = FakeUnicodeFont.getFirstPositionInfo(this.rectangle, this.rotation, fontSize);
this.data.textPosition = this._transformPoint(coords, bbox, matrix);
}
if (this._isOffscreenCanvasSupported) {
const strokeAlpha = params.dict.get("CA");
const fakeUnicodeFont = new FakeUnicodeFont(xref, "sans-serif");
this.appearance = fakeUnicodeFont.createAppearance(this._contents.str, this.rectangle, this.rotation, fontSize, fontColor, strokeAlpha);
this._streams.push(this.appearance);
} else {
warn("FreeTextAnnotation: OffscreenCanvas is not supported, annotation may not render correctly.");
}
}
}
get hasTextContent() {
return this._hasAppearance;
}
static createNewDict(annotation, xref, {
apRef,
ap
}) {
const {
color,
fontSize,
oldAnnotation,
rect,
rotation,
user,
value
} = annotation;
const freetext = oldAnnotation || new Dict(xref);
freetext.set("Type", Name.get("Annot"));
freetext.set("Subtype", Name.get("FreeText"));
if (oldAnnotation) {
freetext.set("M", `D:${getModificationDate()}`);
freetext.delete("RC");
} else {
freetext.set("CreationDate", `D:${getModificationDate()}`);
}
freetext.set("Rect", rect);
const da = `/Helv ${fontSize} Tf ${getPdfColor(color, true)}`;
freetext.set("DA", da);
freetext.set("Contents", stringToAsciiOrUTF16BE(value));
freetext.set("F", 4);
freetext.set("Border", [0, 0, 0]);
freetext.set("Rotate", rotation);
if (user) {
freetext.set("T", stringToAsciiOrUTF16BE(user));
}
if (apRef || ap) {
const n = new Dict(xref);
freetext.set("AP", n);
if (apRef) {
n.set("N", apRef);
} else {
n.set("N", ap);
}
}
return freetext;
}
static async createNewAppearanceStream(annotation, xref, params) {
const {
baseFontRef,
evaluator,
task
} = params;
const {
color,
fontSize,
rect,
rotation,
value
} = annotation;
const resources = new Dict(xref);
const font = new Dict(xref);
if (baseFontRef) {
font.set("Helv", baseFontRef);
} else {
const baseFont = new Dict(xref);
baseFont.set("BaseFont", Name.get("Helvetica"));
baseFont.set("Type", Name.get("Font"));
baseFont.set("Subtype", Name.get("Type1"));
baseFont.set("Encoding", Name.get("WinAnsiEncoding"));
font.set("Helv", baseFont);
}
resources.set("Font", font);
const helv = await WidgetAnnotation._getFontData(evaluator, task, {
fontName: "Helv",
fontSize
}, resources);
const [x1, y1, x2, y2] = rect;
let w = x2 - x1;
let h = y2 - y1;
if (rotation % 180 !== 0) {
[w, h] = [h, w];
}
const lines = value.split("\n");
const scale = fontSize / 1000;
let totalWidth = -Infinity;
const encodedLines = [];
for (let line of lines) {
const encoded = helv.encodeString(line);
if (encoded.length > 1) {
return null;
}
line = encoded.join("");
encodedLines.push(line);
let lineWidth = 0;
const glyphs = helv.charsToGlyphs(line);
for (const glyph of glyphs) {
lineWidth += glyph.width * scale;
}
totalWidth = Math.max(totalWidth, lineWidth);
}
let hscale = 1;
if (totalWidth > w) {
hscale = w / totalWidth;
}
let vscale = 1;
const lineHeight = LINE_FACTOR * fontSize;
const lineAscent = (LINE_FACTOR - LINE_DESCENT_FACTOR) * fontSize;
const totalHeight = lineHeight * lines.length;
if (totalHeight > h) {
vscale = h / totalHeight;
}
const fscale = Math.min(hscale, vscale);
const newFontSize = fontSize * fscale;
let firstPoint, clipBox, matrix;
switch (rotation) {
case 0:
matrix = [1, 0, 0, 1];
clipBox = [rect[0], rect[1], w, h];
firstPoint = [rect[0], rect[3] - lineAscent];
break;
case 90:
matrix = [0, 1, -1, 0];
clipBox = [rect[1], -rect[2], w, h];
firstPoint = [rect[1], -rect[0] - lineAscent];
break;
case 180:
matrix = [-1, 0, 0, -1];
clipBox = [-rect[2], -rect[3], w, h];
firstPoint = [-rect[2], -rect[1] - lineAscent];
break;
case 270:
matrix = [0, -1, 1, 0];
clipBox = [-rect[3], rect[0], w, h];
firstPoint = [-rect[3], rect[2] - lineAscent];
break;
}
const buffer = ["q", `${matrix.join(" ")} 0 0 cm`, `${clipBox.join(" ")} re W n`, `BT`, `${getPdfColor(color, true)}`, `0 Tc /Helv ${numberToString(newFontSize)} Tf`];
buffer.push(`${firstPoint.join(" ")} Td (${escapeString(encodedLines[0])}) Tj`);
const vShift = numberToString(lineHeight);
for (let i = 1, ii = encodedLines.length; i < ii; i++) {
const line = encodedLines[i];
buffer.push(`0 -${vShift} Td (${escapeString(line)}) Tj`);
}
buffer.push("ET", "Q");
const appearance = buffer.join("\n");
const appearanceStreamDict = new Dict(xref);
appearanceStreamDict.set("FormType", 1);
appearanceStreamDict.set("Subtype", Name.get("Form"));
appearanceStreamDict.set("Type", Name.get("XObject"));
appearanceStreamDict.set("BBox", rect);
appearanceStreamDict.set("Resources", resources);
appearanceStreamDict.set("Matrix", [1, 0, 0, 1, -rect[0], -rect[1]]);
const ap = new StringStream(appearance);
ap.dict = appearanceStreamDict;
return ap;
}
}
class LineAnnotation extends MarkupAnnotation {
constructor(params) {
super(params);
const {
dict,
xref
} = params;
this.data.annotationType = AnnotationType.LINE;
this.data.hasOwnCanvas = this.data.noRotate;
this.data.noHTML = false;
const lineCoordinates = lookupRect(dict.getArray("L"), [0, 0, 0, 0]);
this.data.lineCoordinates = Util.normalizeRect(lineCoordinates);
this.setLineEndings(dict.getArray("LE"));
this.data.lineEndings = this.lineEndings;
if (!this.appearance) {
const strokeColor = this.color ? getPdfColorArray(this.color) : [0, 0, 0];
const strokeAlpha = dict.get("CA");
const interiorColor = getRgbColor(dict.getArray("IC"), null);
const fillColor = interiorColor ? getPdfColorArray(interiorColor) : null;
const fillAlpha = fillColor ? strokeAlpha : null;
const borderWidth = this.borderStyle.width || 1,
borderAdjust = 2 * borderWidth;
const bbox = [this.data.lineCoordinates[0] - borderAdjust, this.data.lineCoordinates[1] - borderAdjust, this.data.lineCoordinates[2] + borderAdjust, this.data.lineCoordinates[3] + borderAdjust];
if (!Util.intersect(this.rectangle, bbox)) {
this.rectangle = bbox;
}
this._setDefaultAppearance({
xref,
extra: `${borderWidth} w`,
strokeColor,
fillColor,
strokeAlpha,
fillAlpha,
pointsCallback: (buffer, points) => {
buffer.push(`${lineCoordinates[0]} ${lineCoordinates[1]} m`, `${lineCoordinates[2]} ${lineCoordinates[3]} l`, "S");
return [points[0] - borderWidth, points[7] - borderWidth, points[2] + borderWidth, points[3] + borderWidth];
}
});
}
}
}
class SquareAnnotation extends MarkupAnnotation {
constructor(params) {
super(params);
const {
dict,
xref
} = params;
this.data.annotationType = AnnotationType.SQUARE;
this.data.hasOwnCanvas = this.data.noRotate;
this.data.noHTML = false;
if (!this.appearance) {
const strokeColor = this.color ? getPdfColorArray(this.color) : [0, 0, 0];
const strokeAlpha = dict.get("CA");
const interiorColor = getRgbColor(dict.getArray("IC"), null);
const fillColor = interiorColor ? getPdfColorArray(interiorColor) : null;
const fillAlpha = fillColor ? strokeAlpha : null;
if (this.borderStyle.width === 0 && !fillColor) {
return;
}
this._setDefaultAppearance({
xref,
extra: `${this.borderStyle.width} w`,
strokeColor,
fillColor,
strokeAlpha,
fillAlpha,
pointsCallback: (buffer, points) => {
const x = points[4] + this.borderStyle.width / 2;
const y = points[5] + this.borderStyle.width / 2;
const width = points[6] - points[4] - this.borderStyle.width;
const height = points[3] - points[7] - this.borderStyle.width;
buffer.push(`${x} ${y} ${width} ${height} re`);
if (fillColor) {
buffer.push("B");
} else {
buffer.push("S");
}
return [points[0], points[7], points[2], points[3]];
}
});
}
}
}
class CircleAnnotation extends MarkupAnnotation {
constructor(params) {
super(params);
const {
dict,
xref
} = params;
this.data.annotationType = AnnotationType.CIRCLE;
if (!this.appearance) {
const strokeColor = this.color ? getPdfColorArray(this.color) : [0, 0, 0];
const strokeAlpha = dict.get("CA");
const interiorColor = getRgbColor(dict.getArray("IC"), null);
const fillColor = interiorColor ? getPdfColorArray(interiorColor) : null;
const fillAlpha = fillColor ? strokeAlpha : null;
if (this.borderStyle.width === 0 && !fillColor) {
return;
}
const controlPointsDistance = 4 / 3 * Math.tan(Math.PI / (2 * 4));
this._setDefaultAppearance({
xref,
extra: `${this.borderStyle.width} w`,
strokeColor,
fillColor,
strokeAlpha,
fillAlpha,
pointsCallback: (buffer, points) => {
const x0 = points[0] + this.borderStyle.width / 2;
const y0 = points[1] - this.borderStyle.width / 2;
const x1 = points[6] - this.borderStyle.width / 2;
const y1 = points[7] + this.borderStyle.width / 2;
const xMid = x0 + (x1 - x0) / 2;
const yMid = y0 + (y1 - y0) / 2;
const xOffset = (x1 - x0) / 2 * controlPointsDistance;
const yOffset = (y1 - y0) / 2 * controlPointsDistance;
buffer.push(`${xMid} ${y1} m`, `${xMid + xOffset} ${y1} ${x1} ${yMid + yOffset} ${x1} ${yMid} c`, `${x1} ${yMid - yOffset} ${xMid + xOffset} ${y0} ${xMid} ${y0} c`, `${xMid - xOffset} ${y0} ${x0} ${yMid - yOffset} ${x0} ${yMid} c`, `${x0} ${yMid + yOffset} ${xMid - xOffset} ${y1} ${xMid} ${y1} c`, "h");
if (fillColor) {
buffer.push("B");
} else {
buffer.push("S");
}
return [points[0], points[7], points[2], points[3]];
}
});
}
}
}
class PolylineAnnotation extends MarkupAnnotation {
constructor(params) {
super(params);
const {
dict,
xref
} = params;
this.data.annotationType = AnnotationType.POLYLINE;
this.data.hasOwnCanvas = this.data.noRotate;
this.data.noHTML = false;
this.data.vertices = null;
if (!(this instanceof PolygonAnnotation)) {
this.setLineEndings(dict.getArray("LE"));
this.data.lineEndings = this.lineEndings;
}
const rawVertices = dict.getArray("Vertices");
if (!isNumberArray(rawVertices, null)) {
return;
}
const vertices = this.data.vertices = Float32Array.from(rawVertices);
if (!this.appearance) {
const strokeColor = this.color ? getPdfColorArray(this.color) : [0, 0, 0];
const strokeAlpha = dict.get("CA");
const borderWidth = this.borderStyle.width || 1,
borderAdjust = 2 * borderWidth;
const bbox = [Infinity, Infinity, -Infinity, -Infinity];
for (let i = 0, ii = vertices.length; i < ii; i += 2) {
Util.rectBoundingBox(vertices[i] - borderAdjust, vertices[i + 1] - borderAdjust, vertices[i] + borderAdjust, vertices[i + 1] + borderAdjust, bbox);
}
if (!Util.intersect(this.rectangle, bbox)) {
this.rectangle = bbox;
}
this._setDefaultAppearance({
xref,
extra: `${borderWidth} w`,
strokeColor,
strokeAlpha,
pointsCallback: (buffer, points) => {
for (let i = 0, ii = vertices.length; i < ii; i += 2) {
buffer.push(`${vertices[i]} ${vertices[i + 1]} ${i === 0 ? "m" : "l"}`);
}
buffer.push("S");
return [points[0], points[7], points[2], points[3]];
}
});
}
}
}
class PolygonAnnotation extends PolylineAnnotation {
constructor(params) {
super(params);
this.data.annotationType = AnnotationType.POLYGON;
}
}
class CaretAnnotation extends MarkupAnnotation {
constructor(params) {
super(params);
this.data.annotationType = AnnotationType.CARET;
}
}
class InkAnnotation extends MarkupAnnotation {
constructor(params) {
super(params);
this.data.hasOwnCanvas = this.data.noRotate;
this.data.noHTML = false;
const {
dict,
xref
} = params;
this.data.annotationType = AnnotationType.INK;
this.data.inkLists = [];
this.data.isEditable = !this.data.noHTML;
this.data.noHTML = false;
this.data.opacity = dict.get("CA") || 1;
const rawInkLists = dict.getArray("InkList");
if (!Array.isArray(rawInkLists)) {
return;
}
for (let i = 0, ii = rawInkLists.length; i < ii; ++i) {
if (!Array.isArray(rawInkLists[i])) {
continue;
}
const inkList = new Float32Array(rawInkLists[i].length);
this.data.inkLists.push(inkList);
for (let j = 0, jj = rawInkLists[i].length; j < jj; j += 2) {
const x = xref.fetchIfRef(rawInkLists[i][j]),
y = xref.fetchIfRef(rawInkLists[i][j + 1]);
if (typeof x === "number" && typeof y === "number") {
inkList[j] = x;
inkList[j + 1] = y;
}
}
}
if (!this.appearance) {
const strokeColor = this.color ? getPdfColorArray(this.color) : [0, 0, 0];
const strokeAlpha = dict.get("CA");
const borderWidth = this.borderStyle.width || 1,
borderAdjust = 2 * borderWidth;
const bbox = [Infinity, Infinity, -Infinity, -Infinity];
for (const inkList of this.data.inkLists) {
for (let i = 0, ii = inkList.length; i < ii; i += 2) {
Util.rectBoundingBox(inkList[i] - borderAdjust, inkList[i + 1] - borderAdjust, inkList[i] + borderAdjust, inkList[i + 1] + borderAdjust, bbox);
}
}
if (!Util.intersect(this.rectangle, bbox)) {
this.rectangle = bbox;
}
this._setDefaultAppearance({
xref,
extra: `${borderWidth} w`,
strokeColor,
strokeAlpha,
pointsCallback: (buffer, points) => {
for (const inkList of this.data.inkLists) {
for (let i = 0, ii = inkList.length; i < ii; i += 2) {
buffer.push(`${inkList[i]} ${inkList[i + 1]} ${i === 0 ? "m" : "l"}`);
}
buffer.push("S");
}
return [points[0], points[7], points[2], points[3]];
}
});
}
}
static createNewDict(annotation, xref, {
apRef,
ap
}) {
const {
oldAnnotation,
color,
opacity,
paths,
outlines,
rect,
rotation,
thickness,
user
} = annotation;
const ink = oldAnnotation || new Dict(xref);
ink.set("Type", Name.get("Annot"));
ink.set("Subtype", Name.get("Ink"));
ink.set(oldAnnotation ? "M" : "CreationDate", `D:${getModificationDate()}`);
ink.set("Rect", rect);
ink.set("InkList", outlines?.points || paths.points);
ink.set("F", 4);
ink.set("Rotate", rotation);
if (user) {
ink.set("T", stringToAsciiOrUTF16BE(user));
}
if (outlines) {
ink.set("IT", Name.get("InkHighlight"));
}
const bs = new Dict(xref);
ink.set("BS", bs);
bs.set("W", thickness);
ink.set("C", getPdfColorArray(color));
ink.set("CA", opacity);
const n = new Dict(xref);
ink.set("AP", n);
if (apRef) {
n.set("N", apRef);
} else {
n.set("N", ap);
}
return ink;
}
static async createNewAppearanceStream(annotation, xref, params) {
if (annotation.outlines) {
return this.createNewAppearanceStreamForHighlight(annotation, xref, params);
}
const {
color,
rect,
paths,
thickness,
opacity
} = annotation;
const appearanceBuffer = [`${thickness} w 1 J 1 j`, `${getPdfColor(color, false)}`];
if (opacity !== 1) {
appearanceBuffer.push("/R0 gs");
}
for (const outline of paths.lines) {
appearanceBuffer.push(`${numberToString(outline[4])} ${numberToString(outline[5])} m`);
for (let i = 6, ii = outline.length; i < ii; i += 6) {
if (isNaN(outline[i])) {
appearanceBuffer.push(`${numberToString(outline[i + 4])} ${numberToString(outline[i + 5])} l`);
} else {
const [c1x, c1y, c2x, c2y, x, y] = outline.slice(i, i + 6);
appearanceBuffer.push([c1x, c1y, c2x, c2y, x, y].map(numberToString).join(" ") + " c");
}
}
if (outline.length === 6) {
appearanceBuffer.push(`${numberToString(outline[4])} ${numberToString(outline[5])} l`);
}
}
appearanceBuffer.push("S");
const appearance = appearanceBuffer.join("\n");
const appearanceStreamDict = new Dict(xref);
appearanceStreamDict.set("FormType", 1);
appearanceStreamDict.set("Subtype", Name.get("Form"));
appearanceStreamDict.set("Type", Name.get("XObject"));
appearanceStreamDict.set("BBox", rect);
appearanceStreamDict.set("Length", appearance.length);
if (opacity !== 1) {
const resources = new Dict(xref);
const extGState = new Dict(xref);
const r0 = new Dict(xref);
r0.set("CA", opacity);
r0.set("Type", Name.get("ExtGState"));
extGState.set("R0", r0);
resources.set("ExtGState", extGState);
appearanceStreamDict.set("Resources", resources);
}
const ap = new StringStream(appearance);
ap.dict = appearanceStreamDict;
return ap;
}
static async createNewAppearanceStreamForHighlight(annotation, xref, params) {
const {
color,
rect,
outlines: {
outline
},
opacity
} = annotation;
const appearanceBuffer = [`${getPdfColor(color, true)}`, "/R0 gs"];
appearanceBuffer.push(`${numberToString(outline[4])} ${numberToString(outline[5])} m`);
for (let i = 6, ii = outline.length; i < ii; i += 6) {
if (isNaN(outline[i])) {
appearanceBuffer.push(`${numberToString(outline[i + 4])} ${numberToString(outline[i + 5])} l`);
} else {
const [c1x, c1y, c2x, c2y, x, y] = outline.slice(i, i + 6);
appearanceBuffer.push([c1x, c1y, c2x, c2y, x, y].map(numberToString).join(" ") + " c");
}
}
appearanceBuffer.push("h f");
const appearance = appearanceBuffer.join("\n");
const appearanceStreamDict = new Dict(xref);
appearanceStreamDict.set("FormType", 1);
appearanceStreamDict.set("Subtype", Name.get("Form"));
appearanceStreamDict.set("Type", Name.get("XObject"));
appearanceStreamDict.set("BBox", rect);
appearanceStreamDict.set("Length", appearance.length);
const resources = new Dict(xref);
const extGState = new Dict(xref);
resources.set("ExtGState", extGState);
appearanceStreamDict.set("Resources", resources);
const r0 = new Dict(xref);
extGState.set("R0", r0);
r0.set("BM", Name.get("Multiply"));
if (opacity !== 1) {
r0.set("ca", opacity);
r0.set("Type", Name.get("ExtGState"));
}
const ap = new StringStream(appearance);
ap.dict = appearanceStreamDict;
return ap;
}
}
class HighlightAnnotation extends MarkupAnnotation {
constructor(params) {
super(params);
const {
dict,
xref
} = params;
this.data.annotationType = AnnotationType.HIGHLIGHT;
this.data.isEditable = !this.data.noHTML;
this.data.noHTML = false;
this.data.opacity = dict.get("CA") || 1;
const quadPoints = this.data.quadPoints = getQuadPoints(dict, null);
if (quadPoints) {
const resources = this.appearance?.dict.get("Resources");
if (!this.appearance || !resources?.has("ExtGState")) {
if (this.appearance) {
warn("HighlightAnnotation - ignoring built-in appearance stream.");
}
const fillColor = this.color ? getPdfColorArray(this.color) : [1, 1, 0];
const fillAlpha = dict.get("CA");
this._setDefaultAppearance({
xref,
fillColor,
blendMode: "Multiply",
fillAlpha,
pointsCallback: (buffer, points) => {
buffer.push(`${points[0]} ${points[1]} m`, `${points[2]} ${points[3]} l`, `${points[6]} ${points[7]} l`, `${points[4]} ${points[5]} l`, "f");
return [points[0], points[7], points[2], points[3]];
}
});
}
} else {
this.data.popupRef = null;
}
}
static createNewDict(annotation, xref, {
apRef,
ap
}) {
const {
color,
oldAnnotation,
opacity,
rect,
rotation,
user,
quadPoints
} = annotation;
const highlight = oldAnnotation || new Dict(xref);
highlight.set("Type", Name.get("Annot"));
highlight.set("Subtype", Name.get("Highlight"));
highlight.set(oldAnnotation ? "M" : "CreationDate", `D:${getModificationDate()}`);
highlight.set("CreationDate", `D:${getModificationDate()}`);
highlight.set("Rect", rect);
highlight.set("F", 4);
highlight.set("Border", [0, 0, 0]);
highlight.set("Rotate", rotation);
highlight.set("QuadPoints", quadPoints);
highlight.set("C", getPdfColorArray(color));
highlight.set("CA", opacity);
if (user) {
highlight.set("T", stringToAsciiOrUTF16BE(user));
}
if (apRef || ap) {
const n = new Dict(xref);
highlight.set("AP", n);
n.set("N", apRef || ap);
}
return highlight;
}
static async createNewAppearanceStream(annotation, xref, params) {
const {
color,
rect,
outlines,
opacity
} = annotation;
const appearanceBuffer = [`${getPdfColor(color, true)}`, "/R0 gs"];
const buffer = [];
for (const outline of outlines) {
buffer.length = 0;
buffer.push(`${numberToString(outline[0])} ${numberToString(outline[1])} m`);
for (let i = 2, ii = outline.length; i < ii; i += 2) {
buffer.push(`${numberToString(outline[i])} ${numberToString(outline[i + 1])} l`);
}
buffer.push("h");
appearanceBuffer.push(buffer.join("\n"));
}
appearanceBuffer.push("f*");
const appearance = appearanceBuffer.join("\n");
const appearanceStreamDict = new Dict(xref);
appearanceStreamDict.set("FormType", 1);
appearanceStreamDict.set("Subtype", Name.get("Form"));
appearanceStreamDict.set("Type", Name.get("XObject"));
appearanceStreamDict.set("BBox", rect);
appearanceStreamDict.set("Length", appearance.length);
const resources = new Dict(xref);
const extGState = new Dict(xref);
resources.set("ExtGState", extGState);
appearanceStreamDict.set("Resources", resources);
const r0 = new Dict(xref);
extGState.set("R0", r0);
r0.set("BM", Name.get("Multiply"));
if (opacity !== 1) {
r0.set("ca", opacity);
r0.set("Type", Name.get("ExtGState"));
}
const ap = new StringStream(appearance);
ap.dict = appearanceStreamDict;
return ap;
}
}
class UnderlineAnnotation extends MarkupAnnotation {
constructor(params) {
super(params);
const {
dict,
xref
} = params;
this.data.annotationType = AnnotationType.UNDERLINE;
const quadPoints = this.data.quadPoints = getQuadPoints(dict, null);
if (quadPoints) {
if (!this.appearance) {
const strokeColor = this.color ? getPdfColorArray(this.color) : [0, 0, 0];
const strokeAlpha = dict.get("CA");
this._setDefaultAppearance({
xref,
extra: "[] 0 d 0.571 w",
strokeColor,
strokeAlpha,
pointsCallback: (buffer, points) => {
buffer.push(`${points[4]} ${points[5] + 1.3} m`, `${points[6]} ${points[7] + 1.3} l`, "S");
return [points[0], points[7], points[2], points[3]];
}
});
}
} else {
this.data.popupRef = null;
}
}
}
class SquigglyAnnotation extends MarkupAnnotation {
constructor(params) {
super(params);
const {
dict,
xref
} = params;
this.data.annotationType = AnnotationType.SQUIGGLY;
const quadPoints = this.data.quadPoints = getQuadPoints(dict, null);
if (quadPoints) {
if (!this.appearance) {
const strokeColor = this.color ? getPdfColorArray(this.color) : [0, 0, 0];
const strokeAlpha = dict.get("CA");
this._setDefaultAppearance({
xref,
extra: "[] 0 d 1 w",
strokeColor,
strokeAlpha,
pointsCallback: (buffer, points) => {
const dy = (points[1] - points[5]) / 6;
let shift = dy;
let x = points[4];
const y = points[5];
const xEnd = points[6];
buffer.push(`${x} ${y + shift} m`);
do {
x += 2;
shift = shift === 0 ? dy : 0;
buffer.push(`${x} ${y + shift} l`);
} while (x < xEnd);
buffer.push("S");
return [points[4], y - 2 * dy, xEnd, y + 2 * dy];
}
});
}
} else {
this.data.popupRef = null;
}
}
}
class StrikeOutAnnotation extends MarkupAnnotation {
constructor(params) {
super(params);
const {
dict,
xref
} = params;
this.data.annotationType = AnnotationType.STRIKEOUT;
const quadPoints = this.data.quadPoints = getQuadPoints(dict, null);
if (quadPoints) {
if (!this.appearance) {
const strokeColor = this.color ? getPdfColorArray(this.color) : [0, 0, 0];
const strokeAlpha = dict.get("CA");
this._setDefaultAppearance({
xref,
extra: "[] 0 d 1 w",
strokeColor,
strokeAlpha,
pointsCallback: (buffer, points) => {
buffer.push(`${(points[0] + points[4]) / 2} ` + `${(points[1] + points[5]) / 2} m`, `${(points[2] + points[6]) / 2} ` + `${(points[3] + points[7]) / 2} l`, "S");
return [points[0], points[7], points[2], points[3]];
}
});
}
} else {
this.data.popupRef = null;
}
}
}
class StampAnnotation extends MarkupAnnotation {
#savedHasOwnCanvas = null;
constructor(params) {
super(params);
this.data.annotationType = AnnotationType.STAMP;
this.data.hasOwnCanvas = this.data.noRotate;
this.data.isEditable = !this.data.noHTML;
this.data.noHTML = false;
}
mustBeViewedWhenEditing(isEditing, modifiedIds = null) {
if (isEditing) {
if (!this.data.isEditable) {
return true;
}
this.#savedHasOwnCanvas ??= this.data.hasOwnCanvas;
this.data.hasOwnCanvas = true;
return true;
}
if (this.#savedHasOwnCanvas !== null) {
this.data.hasOwnCanvas = this.#savedHasOwnCanvas;
this.#savedHasOwnCanvas = null;
}
return !modifiedIds?.has(this.data.id);
}
static async createImage(bitmap, xref) {
const {
width,
height
} = bitmap;
const canvas = new OffscreenCanvas(width, height);
const ctx = canvas.getContext("2d", {
alpha: true
});
ctx.drawImage(bitmap, 0, 0);
const data = ctx.getImageData(0, 0, width, height).data;
const buf32 = new Uint32Array(data.buffer);
const hasAlpha = buf32.some(FeatureTest.isLittleEndian ? x => x >>> 24 !== 0xff : x => (x & 0xff) !== 0xff);
if (hasAlpha) {
ctx.fillStyle = "white";
ctx.fillRect(0, 0, width, height);
ctx.drawImage(bitmap, 0, 0);
}
const jpegBufferPromise = canvas.convertToBlob({
type: "image/jpeg",
quality: 1
}).then(blob => blob.arrayBuffer());
const xobjectName = Name.get("XObject");
const imageName = Name.get("Image");
const image = new Dict(xref);
image.set("Type", xobjectName);
image.set("Subtype", imageName);
image.set("BitsPerComponent", 8);
image.set("ColorSpace", Name.get("DeviceRGB"));
image.set("Filter", Name.get("DCTDecode"));
image.set("BBox", [0, 0, width, height]);
image.set("Width", width);
image.set("Height", height);
let smaskStream = null;
if (hasAlpha) {
const alphaBuffer = new Uint8Array(buf32.length);
if (FeatureTest.isLittleEndian) {
for (let i = 0, ii = buf32.length; i < ii; i++) {
alphaBuffer[i] = buf32[i] >>> 24;
}
} else {
for (let i = 0, ii = buf32.length; i < ii; i++) {
alphaBuffer[i] = buf32[i] & 0xff;
}
}
const smask = new Dict(xref);
smask.set("Type", xobjectName);
smask.set("Subtype", imageName);
smask.set("BitsPerComponent", 8);
smask.set("ColorSpace", Name.get("DeviceGray"));
smask.set("Width", width);
smask.set("Height", height);
smaskStream = new Stream(alphaBuffer, 0, 0, smask);
}
const imageStream = new Stream(await jpegBufferPromise, 0, 0, image);
return {
imageStream,
smaskStream,
width,
height
};
}
static createNewDict(annotation, xref, {
apRef,
ap
}) {
const {
oldAnnotation,
rect,
rotation,
user
} = annotation;
const stamp = oldAnnotation || new Dict(xref);
stamp.set("Type", Name.get("Annot"));
stamp.set("Subtype", Name.get("Stamp"));
stamp.set(oldAnnotation ? "M" : "CreationDate", `D:${getModificationDate()}`);
stamp.set("Rect", rect);
stamp.set("F", 4);
stamp.set("Border", [0, 0, 0]);
stamp.set("Rotate", rotation);
if (user) {
stamp.set("T", stringToAsciiOrUTF16BE(user));
}
if (apRef || ap) {
const n = new Dict(xref);
stamp.set("AP", n);
if (apRef) {
n.set("N", apRef);
} else {
n.set("N", ap);
}
}
return stamp;
}
static async #createNewAppearanceStreamForDrawing(annotation, xref) {
const {
areContours,
color,
rect,
lines,
thickness
} = annotation;
const appearanceBuffer = [`${thickness} w 1 J 1 j`, `${getPdfColor(color, areContours)}`];
for (const line of lines) {
appearanceBuffer.push(`${numberToString(line[4])} ${numberToString(line[5])} m`);
for (let i = 6, ii = line.length; i < ii; i += 6) {
if (isNaN(line[i])) {
appearanceBuffer.push(`${numberToString(line[i + 4])} ${numberToString(line[i + 5])} l`);
} else {
const [c1x, c1y, c2x, c2y, x, y] = line.slice(i, i + 6);
appearanceBuffer.push([c1x, c1y, c2x, c2y, x, y].map(numberToString).join(" ") + " c");
}
}
if (line.length === 6) {
appearanceBuffer.push(`${numberToString(line[4])} ${numberToString(line[5])} l`);
}
}
appearanceBuffer.push(areContours ? "F" : "S");
const appearance = appearanceBuffer.join("\n");
const appearanceStreamDict = new Dict(xref);
appearanceStreamDict.set("FormType", 1);
appearanceStreamDict.set("Subtype", Name.get("Form"));
appearanceStreamDict.set("Type", Name.get("XObject"));
appearanceStreamDict.set("BBox", rect);
appearanceStreamDict.set("Length", appearance.length);
const ap = new StringStream(appearance);
ap.dict = appearanceStreamDict;
return ap;
}
static async createNewAppearanceStream(annotation, xref, params) {
if (annotation.oldAnnotation) {
return null;
}
if (annotation.isSignature) {
return this.#createNewAppearanceStreamForDrawing(annotation, xref);
}
const {
rotation
} = annotation;
const {
imageRef,
width,
height
} = params.image;
const resources = new Dict(xref);
const xobject = new Dict(xref);
resources.set("XObject", xobject);
xobject.set("Im0", imageRef);
const appearance = `q ${width} 0 0 ${height} 0 0 cm /Im0 Do Q`;
const appearanceStreamDict = new Dict(xref);
appearanceStreamDict.set("FormType", 1);
appearanceStreamDict.set("Subtype", Name.get("Form"));
appearanceStreamDict.set("Type", Name.get("XObject"));
appearanceStreamDict.set("BBox", [0, 0, width, height]);
appearanceStreamDict.set("Resources", resources);
if (rotation) {
const matrix = getRotationMatrix(rotation, width, height);
appearanceStreamDict.set("Matrix", matrix);
}
const ap = new StringStream(appearance);
ap.dict = appearanceStreamDict;
return ap;
}
}
class FileAttachmentAnnotation extends MarkupAnnotation {
constructor(params) {
super(params);
const {
dict,
xref
} = params;
const file = new FileSpec(dict.get("FS"), xref);
this.data.annotationType = AnnotationType.FILEATTACHMENT;
this.data.hasOwnCanvas = this.data.noRotate;
this.data.noHTML = false;
this.data.file = file.serializable;
const name = dict.get("Name");
this.data.name = name instanceof Name ? stringToPDFString(name.name) : "PushPin";
const fillAlpha = dict.get("ca");
this.data.fillAlpha = typeof fillAlpha === "number" && fillAlpha >= 0 && fillAlpha <= 1 ? fillAlpha : null;
}
}
;// ./src/core/calculate_md5.js
const PARAMS = {
get r() {
return shadow(this, "r", new Uint8Array([7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21]));
},
get k() {
return shadow(this, "k", new Int32Array([-680876936, -389564586, 606105819, -1044525330, -176418897, 1200080426, -1473231341, -45705983, 1770035416, -1958414417, -42063, -1990404162, 1804603682, -40341101, -1502002290, 1236535329, -165796510, -1069501632, 643717713, -373897302, -701558691, 38016083, -660478335, -405537848, 568446438, -1019803690, -187363961, 1163531501, -1444681467, -51403784, 1735328473, -1926607734, -378558, -2022574463, 1839030562, -35309556, -1530992060, 1272893353, -155497632, -1094730640, 681279174, -358537222, -722521979, 76029189, -640364487, -421815835, 530742520, -995338651, -198630844, 1126891415, -1416354905, -57434055, 1700485571, -1894986606, -1051523, -2054922799, 1873313359, -30611744, -1560198380, 1309151649, -145523070, -1120210379, 718787259, -343485551]));
}
};
function calculateMD5(data, offset, length) {
let h0 = 1732584193,
h1 = -271733879,
h2 = -1732584194,
h3 = 271733878;
const paddedLength = length + 72 & ~63;
const padded = new Uint8Array(paddedLength);
let i, j;
for (i = 0; i < length; ++i) {
padded[i] = data[offset++];
}
padded[i++] = 0x80;
const n = paddedLength - 8;
if (i < n) {
i = n;
}
padded[i++] = length << 3 & 0xff;
padded[i++] = length >> 5 & 0xff;
padded[i++] = length >> 13 & 0xff;
padded[i++] = length >> 21 & 0xff;
padded[i++] = length >>> 29 & 0xff;
i += 3;
const w = new Int32Array(16);
const {
k,
r
} = PARAMS;
for (i = 0; i < paddedLength;) {
for (j = 0; j < 16; ++j, i += 4) {
w[j] = padded[i] | padded[i + 1] << 8 | padded[i + 2] << 16 | padded[i + 3] << 24;
}
let a = h0,
b = h1,
c = h2,
d = h3,
f,
g;
for (j = 0; j < 64; ++j) {
if (j < 16) {
f = b & c | ~b & d;
g = j;
} else if (j < 32) {
f = d & b | ~d & c;
g = 5 * j + 1 & 15;
} else if (j < 48) {
f = b ^ c ^ d;
g = 3 * j + 5 & 15;
} else {
f = c ^ (b | ~d);
g = 7 * j & 15;
}
const tmp = d,
rotateArg = a + f + k[j] + w[g] | 0,
rotate = r[j];
d = c;
c = b;
b = b + (rotateArg << rotate | rotateArg >>> 32 - rotate) | 0;
a = tmp;
}
h0 = h0 + a | 0;
h1 = h1 + b | 0;
h2 = h2 + c | 0;
h3 = h3 + d | 0;
}
return new Uint8Array([h0 & 0xFF, h0 >> 8 & 0xFF, h0 >> 16 & 0xFF, h0 >>> 24 & 0xFF, h1 & 0xFF, h1 >> 8 & 0xFF, h1 >> 16 & 0xFF, h1 >>> 24 & 0xFF, h2 & 0xFF, h2 >> 8 & 0xFF, h2 >> 16 & 0xFF, h2 >>> 24 & 0xFF, h3 & 0xFF, h3 >> 8 & 0xFF, h3 >> 16 & 0xFF, h3 >>> 24 & 0xFF]);
}
;// ./src/core/dataset_reader.js
function decodeString(str) {
try {
return stringToUTF8String(str);
} catch (ex) {
warn(`UTF-8 decoding failed: "${ex}".`);
return str;
}
}
class DatasetXMLParser extends SimpleXMLParser {
constructor(options) {
super(options);
this.node = null;
}
onEndElement(name) {
const node = super.onEndElement(name);
if (node && name === "xfa:datasets") {
this.node = node;
throw new Error("Aborting DatasetXMLParser.");
}
}
}
class DatasetReader {
constructor(data) {
if (data.datasets) {
this.node = new SimpleXMLParser({
hasAttributes: true
}).parseFromString(data.datasets).documentElement;
} else {
const parser = new DatasetXMLParser({
hasAttributes: true
});
try {
parser.parseFromString(data["xdp:xdp"]);
} catch {}
this.node = parser.node;
}
}
getValue(path) {
if (!this.node || !path) {
return "";
}
const node = this.node.searchNode(parseXFAPath(path), 0);
if (!node) {
return "";
}
const first = node.firstChild;
if (first?.nodeName === "value") {
return node.children.map(child => decodeString(child.textContent));
}
return decodeString(node.textContent);
}
}
;// ./src/core/calculate_sha_other.js
class Word64 {
constructor(highInteger, lowInteger) {
this.high = highInteger | 0;
this.low = lowInteger | 0;
}
and(word) {
this.high &= word.high;
this.low &= word.low;
}
xor(word) {
this.high ^= word.high;
this.low ^= word.low;
}
shiftRight(places) {
if (places >= 32) {
this.low = this.high >>> places - 32 | 0;
this.high = 0;
} else {
this.low = this.low >>> places | this.high << 32 - places;
this.high = this.high >>> places | 0;
}
}
rotateRight(places) {
let low, high;
if (places & 32) {
high = this.low;
low = this.high;
} else {
low = this.low;
high = this.high;
}
places &= 31;
this.low = low >>> places | high << 32 - places;
this.high = high >>> places | low << 32 - places;
}
not() {
this.high = ~this.high;
this.low = ~this.low;
}
add(word) {
const lowAdd = (this.low >>> 0) + (word.low >>> 0);
let highAdd = (this.high >>> 0) + (word.high >>> 0);
if (lowAdd > 0xffffffff) {
highAdd += 1;
}
this.low = lowAdd | 0;
this.high = highAdd | 0;
}
copyTo(bytes, offset) {
bytes[offset] = this.high >>> 24 & 0xff;
bytes[offset + 1] = this.high >> 16 & 0xff;
bytes[offset + 2] = this.high >> 8 & 0xff;
bytes[offset + 3] = this.high & 0xff;
bytes[offset + 4] = this.low >>> 24 & 0xff;
bytes[offset + 5] = this.low >> 16 & 0xff;
bytes[offset + 6] = this.low >> 8 & 0xff;
bytes[offset + 7] = this.low & 0xff;
}
assign(word) {
this.high = word.high;
this.low = word.low;
}
}
const calculate_sha_other_PARAMS = {
get k() {
return shadow(this, "k", [new Word64(0x428a2f98, 0xd728ae22), new Word64(0x71374491, 0x23ef65cd), new Word64(0xb5c0fbcf, 0xec4d3b2f), new Word64(0xe9b5dba5, 0x8189dbbc), new Word64(0x3956c25b, 0xf348b538), new Word64(0x59f111f1, 0xb605d019), new Word64(0x923f82a4, 0xaf194f9b), new Word64(0xab1c5ed5, 0xda6d8118), new Word64(0xd807aa98, 0xa3030242), new Word64(0x12835b01, 0x45706fbe), new Word64(0x243185be, 0x4ee4b28c), new Word64(0x550c7dc3, 0xd5ffb4e2), new Word64(0x72be5d74, 0xf27b896f), new Word64(0x80deb1fe, 0x3b1696b1), new Word64(0x9bdc06a7, 0x25c71235), new Word64(0xc19bf174, 0xcf692694), new Word64(0xe49b69c1, 0x9ef14ad2), new Word64(0xefbe4786, 0x384f25e3), new Word64(0x0fc19dc6, 0x8b8cd5b5), new Word64(0x240ca1cc, 0x77ac9c65), new Word64(0x2de92c6f, 0x592b0275), new Word64(0x4a7484aa, 0x6ea6e483), new Word64(0x5cb0a9dc, 0xbd41fbd4), new Word64(0x76f988da, 0x831153b5), new Word64(0x983e5152, 0xee66dfab), new Word64(0xa831c66d, 0x2db43210), new Word64(0xb00327c8, 0x98fb213f), new Word64(0xbf597fc7, 0xbeef0ee4), new Word64(0xc6e00bf3, 0x3da88fc2), new Word64(0xd5a79147, 0x930aa725), new Word64(0x06ca6351, 0xe003826f), new Word64(0x14292967, 0x0a0e6e70), new Word64(0x27b70a85, 0x46d22ffc), new Word64(0x2e1b2138, 0x5c26c926), new Word64(0x4d2c6dfc, 0x5ac42aed), new Word64(0x53380d13, 0x9d95b3df), new Word64(0x650a7354, 0x8baf63de), new Word64(0x766a0abb, 0x3c77b2a8), new Word64(0x81c2c92e, 0x47edaee6), new Word64(0x92722c85, 0x1482353b), new Word64(0xa2bfe8a1, 0x4cf10364), new Word64(0xa81a664b, 0xbc423001), new Word64(0xc24b8b70, 0xd0f89791), new Word64(0xc76c51a3, 0x0654be30), new Word64(0xd192e819, 0xd6ef5218), new Word64(0xd6990624, 0x5565a910), new Word64(0xf40e3585, 0x5771202a), new Word64(0x106aa070, 0x32bbd1b8), new Word64(0x19a4c116, 0xb8d2d0c8), new Word64(0x1e376c08, 0x5141ab53), new Word64(0x2748774c, 0xdf8eeb99), new Word64(0x34b0bcb5, 0xe19b48a8), new Word64(0x391c0cb3, 0xc5c95a63), new Word64(0x4ed8aa4a, 0xe3418acb), new Word64(0x5b9cca4f, 0x7763e373), new Word64(0x682e6ff3, 0xd6b2b8a3), new Word64(0x748f82ee, 0x5defb2fc), new Word64(0x78a5636f, 0x43172f60), new Word64(0x84c87814, 0xa1f0ab72), new Word64(0x8cc70208, 0x1a6439ec), new Word64(0x90befffa, 0x23631e28), new Word64(0xa4506ceb, 0xde82bde9), new Word64(0xbef9a3f7, 0xb2c67915), new Word64(0xc67178f2, 0xe372532b), new Word64(0xca273ece, 0xea26619c), new Word64(0xd186b8c7, 0x21c0c207), new Word64(0xeada7dd6, 0xcde0eb1e), new Word64(0xf57d4f7f, 0xee6ed178), new Word64(0x06f067aa, 0x72176fba), new Word64(0x0a637dc5, 0xa2c898a6), new Word64(0x113f9804, 0xbef90dae), new Word64(0x1b710b35, 0x131c471b), new Word64(0x28db77f5, 0x23047d84), new Word64(0x32caab7b, 0x40c72493), new Word64(0x3c9ebe0a, 0x15c9bebc), new Word64(0x431d67c4, 0x9c100d4c), new Word64(0x4cc5d4be, 0xcb3e42b6), new Word64(0x597f299c, 0xfc657e2a), new Word64(0x5fcb6fab, 0x3ad6faec), new Word64(0x6c44198c, 0x4a475817)]);
}
};
function ch(result, x, y, z, tmp) {
result.assign(x);
result.and(y);
tmp.assign(x);
tmp.not();
tmp.and(z);
result.xor(tmp);
}
function maj(result, x, y, z, tmp) {
result.assign(x);
result.and(y);
tmp.assign(x);
tmp.and(z);
result.xor(tmp);
tmp.assign(y);
tmp.and(z);
result.xor(tmp);
}
function sigma(result, x, tmp) {
result.assign(x);
result.rotateRight(28);
tmp.assign(x);
tmp.rotateRight(34);
result.xor(tmp);
tmp.assign(x);
tmp.rotateRight(39);
result.xor(tmp);
}
function sigmaPrime(result, x, tmp) {
result.assign(x);
result.rotateRight(14);
tmp.assign(x);
tmp.rotateRight(18);
result.xor(tmp);
tmp.assign(x);
tmp.rotateRight(41);
result.xor(tmp);
}
function littleSigma(result, x, tmp) {
result.assign(x);
result.rotateRight(1);
tmp.assign(x);
tmp.rotateRight(8);
result.xor(tmp);
tmp.assign(x);
tmp.shiftRight(7);
result.xor(tmp);
}
function littleSigmaPrime(result, x, tmp) {
result.assign(x);
result.rotateRight(19);
tmp.assign(x);
tmp.rotateRight(61);
result.xor(tmp);
tmp.assign(x);
tmp.shiftRight(6);
result.xor(tmp);
}
function calculateSHA512(data, offset, length, mode384 = false) {
let h0, h1, h2, h3, h4, h5, h6, h7;
if (!mode384) {
h0 = new Word64(0x6a09e667, 0xf3bcc908);
h1 = new Word64(0xbb67ae85, 0x84caa73b);
h2 = new Word64(0x3c6ef372, 0xfe94f82b);
h3 = new Word64(0xa54ff53a, 0x5f1d36f1);
h4 = new Word64(0x510e527f, 0xade682d1);
h5 = new Word64(0x9b05688c, 0x2b3e6c1f);
h6 = new Word64(0x1f83d9ab, 0xfb41bd6b);
h7 = new Word64(0x5be0cd19, 0x137e2179);
} else {
h0 = new Word64(0xcbbb9d5d, 0xc1059ed8);
h1 = new Word64(0x629a292a, 0x367cd507);
h2 = new Word64(0x9159015a, 0x3070dd17);
h3 = new Word64(0x152fecd8, 0xf70e5939);
h4 = new Word64(0x67332667, 0xffc00b31);
h5 = new Word64(0x8eb44a87, 0x68581511);
h6 = new Word64(0xdb0c2e0d, 0x64f98fa7);
h7 = new Word64(0x47b5481d, 0xbefa4fa4);
}
const paddedLength = Math.ceil((length + 17) / 128) * 128;
const padded = new Uint8Array(paddedLength);
let i, j;
for (i = 0; i < length; ++i) {
padded[i] = data[offset++];
}
padded[i++] = 0x80;
const n = paddedLength - 16;
if (i < n) {
i = n;
}
i += 11;
padded[i++] = length >>> 29 & 0xff;
padded[i++] = length >> 21 & 0xff;
padded[i++] = length >> 13 & 0xff;
padded[i++] = length >> 5 & 0xff;
padded[i++] = length << 3 & 0xff;
const w = new Array(80);
for (i = 0; i < 80; i++) {
w[i] = new Word64(0, 0);
}
const {
k
} = calculate_sha_other_PARAMS;
let a = new Word64(0, 0),
b = new Word64(0, 0),
c = new Word64(0, 0);
let d = new Word64(0, 0),
e = new Word64(0, 0),
f = new Word64(0, 0);
let g = new Word64(0, 0),
h = new Word64(0, 0);
const t1 = new Word64(0, 0),
t2 = new Word64(0, 0);
const tmp1 = new Word64(0, 0),
tmp2 = new Word64(0, 0);
let tmp3;
for (i = 0; i < paddedLength;) {
for (j = 0; j < 16; ++j) {
w[j].high = padded[i] << 24 | padded[i + 1] << 16 | padded[i + 2] << 8 | padded[i + 3];
w[j].low = padded[i + 4] << 24 | padded[i + 5] << 16 | padded[i + 6] << 8 | padded[i + 7];
i += 8;
}
for (j = 16; j < 80; ++j) {
tmp3 = w[j];
littleSigmaPrime(tmp3, w[j - 2], tmp2);
tmp3.add(w[j - 7]);
littleSigma(tmp1, w[j - 15], tmp2);
tmp3.add(tmp1);
tmp3.add(w[j - 16]);
}
a.assign(h0);
b.assign(h1);
c.assign(h2);
d.assign(h3);
e.assign(h4);
f.assign(h5);
g.assign(h6);
h.assign(h7);
for (j = 0; j < 80; ++j) {
t1.assign(h);
sigmaPrime(tmp1, e, tmp2);
t1.add(tmp1);
ch(tmp1, e, f, g, tmp2);
t1.add(tmp1);
t1.add(k[j]);
t1.add(w[j]);
sigma(t2, a, tmp2);
maj(tmp1, a, b, c, tmp2);
t2.add(tmp1);
tmp3 = h;
h = g;
g = f;
f = e;
d.add(t1);
e = d;
d = c;
c = b;
b = a;
tmp3.assign(t1);
tmp3.add(t2);
a = tmp3;
}
h0.add(a);
h1.add(b);
h2.add(c);
h3.add(d);
h4.add(e);
h5.add(f);
h6.add(g);
h7.add(h);
}
let result;
if (!mode384) {
result = new Uint8Array(64);
h0.copyTo(result, 0);
h1.copyTo(result, 8);
h2.copyTo(result, 16);
h3.copyTo(result, 24);
h4.copyTo(result, 32);
h5.copyTo(result, 40);
h6.copyTo(result, 48);
h7.copyTo(result, 56);
} else {
result = new Uint8Array(48);
h0.copyTo(result, 0);
h1.copyTo(result, 8);
h2.copyTo(result, 16);
h3.copyTo(result, 24);
h4.copyTo(result, 32);
h5.copyTo(result, 40);
}
return result;
}
function calculateSHA384(data, offset, length) {
return calculateSHA512(data, offset, length, true);
}
;// ./src/core/calculate_sha256.js
const calculate_sha256_PARAMS = {
get k() {
return shadow(this, "k", [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2]);
}
};
function rotr(x, n) {
return x >>> n | x << 32 - n;
}
function calculate_sha256_ch(x, y, z) {
return x & y ^ ~x & z;
}
function calculate_sha256_maj(x, y, z) {
return x & y ^ x & z ^ y & z;
}
function calculate_sha256_sigma(x) {
return rotr(x, 2) ^ rotr(x, 13) ^ rotr(x, 22);
}
function calculate_sha256_sigmaPrime(x) {
return rotr(x, 6) ^ rotr(x, 11) ^ rotr(x, 25);
}
function calculate_sha256_littleSigma(x) {
return rotr(x, 7) ^ rotr(x, 18) ^ x >>> 3;
}
function calculate_sha256_littleSigmaPrime(x) {
return rotr(x, 17) ^ rotr(x, 19) ^ x >>> 10;
}
function calculateSHA256(data, offset, length) {
let h0 = 0x6a09e667,
h1 = 0xbb67ae85,
h2 = 0x3c6ef372,
h3 = 0xa54ff53a,
h4 = 0x510e527f,
h5 = 0x9b05688c,
h6 = 0x1f83d9ab,
h7 = 0x5be0cd19;
const paddedLength = Math.ceil((length + 9) / 64) * 64;
const padded = new Uint8Array(paddedLength);
let i, j;
for (i = 0; i < length; ++i) {
padded[i] = data[offset++];
}
padded[i++] = 0x80;
const n = paddedLength - 8;
if (i < n) {
i = n;
}
i += 3;
padded[i++] = length >>> 29 & 0xff;
padded[i++] = length >> 21 & 0xff;
padded[i++] = length >> 13 & 0xff;
padded[i++] = length >> 5 & 0xff;
padded[i++] = length << 3 & 0xff;
const w = new Uint32Array(64);
const {
k
} = calculate_sha256_PARAMS;
for (i = 0; i < paddedLength;) {
for (j = 0; j < 16; ++j) {
w[j] = padded[i] << 24 | padded[i + 1] << 16 | padded[i + 2] << 8 | padded[i + 3];
i += 4;
}
for (j = 16; j < 64; ++j) {
w[j] = calculate_sha256_littleSigmaPrime(w[j - 2]) + w[j - 7] + calculate_sha256_littleSigma(w[j - 15]) + w[j - 16] | 0;
}
let a = h0,
b = h1,
c = h2,
d = h3,
e = h4,
f = h5,
g = h6,
h = h7,
t1,
t2;
for (j = 0; j < 64; ++j) {
t1 = h + calculate_sha256_sigmaPrime(e) + calculate_sha256_ch(e, f, g) + k[j] + w[j];
t2 = calculate_sha256_sigma(a) + calculate_sha256_maj(a, b, c);
h = g;
g = f;
f = e;
e = d + t1 | 0;
d = c;
c = b;
b = a;
a = t1 + t2 | 0;
}
h0 = h0 + a | 0;
h1 = h1 + b | 0;
h2 = h2 + c | 0;
h3 = h3 + d | 0;
h4 = h4 + e | 0;
h5 = h5 + f | 0;
h6 = h6 + g | 0;
h7 = h7 + h | 0;
}
return new Uint8Array([h0 >> 24 & 0xFF, h0 >> 16 & 0xFF, h0 >> 8 & 0xFF, h0 & 0xFF, h1 >> 24 & 0xFF, h1 >> 16 & 0xFF, h1 >> 8 & 0xFF, h1 & 0xFF, h2 >> 24 & 0xFF, h2 >> 16 & 0xFF, h2 >> 8 & 0xFF, h2 & 0xFF, h3 >> 24 & 0xFF, h3 >> 16 & 0xFF, h3 >> 8 & 0xFF, h3 & 0xFF, h4 >> 24 & 0xFF, h4 >> 16 & 0xFF, h4 >> 8 & 0xFF, h4 & 0xFF, h5 >> 24 & 0xFF, h5 >> 16 & 0xFF, h5 >> 8 & 0xFF, h5 & 0xFF, h6 >> 24 & 0xFF, h6 >> 16 & 0xFF, h6 >> 8 & 0xFF, h6 & 0xFF, h7 >> 24 & 0xFF, h7 >> 16 & 0xFF, h7 >> 8 & 0xFF, h7 & 0xFF]);
}
;// ./src/core/decrypt_stream.js
const chunkSize = 512;
class DecryptStream extends DecodeStream {
constructor(str, maybeLength, decrypt) {
super(maybeLength);
this.str = str;
this.dict = str.dict;
this.decrypt = decrypt;
this.nextChunk = null;
this.initialized = false;
}
readBlock() {
let chunk;
if (this.initialized) {
chunk = this.nextChunk;
} else {
chunk = this.str.getBytes(chunkSize);
this.initialized = true;
}
if (!chunk?.length) {
this.eof = true;
return;
}
this.nextChunk = this.str.getBytes(chunkSize);
const hasMoreData = this.nextChunk?.length > 0;
const decrypt = this.decrypt;
chunk = decrypt(chunk, !hasMoreData);
const bufferLength = this.bufferLength,
newLength = bufferLength + chunk.length,
buffer = this.ensureBuffer(newLength);
buffer.set(chunk, bufferLength);
this.bufferLength = newLength;
}
}
;// ./src/core/crypto.js
class ARCFourCipher {
constructor(key) {
this.a = 0;
this.b = 0;
const s = new Uint8Array(256);
const keyLength = key.length;
for (let i = 0; i < 256; ++i) {
s[i] = i;
}
for (let i = 0, j = 0; i < 256; ++i) {
const tmp = s[i];
j = j + tmp + key[i % keyLength] & 0xff;
s[i] = s[j];
s[j] = tmp;
}
this.s = s;
}
encryptBlock(data) {
let a = this.a,
b = this.b;
const s = this.s;
const n = data.length;
const output = new Uint8Array(n);
for (let i = 0; i < n; ++i) {
a = a + 1 & 0xff;
const tmp = s[a];
b = b + tmp & 0xff;
const tmp2 = s[b];
s[a] = tmp2;
s[b] = tmp;
output[i] = data[i] ^ s[tmp + tmp2 & 0xff];
}
this.a = a;
this.b = b;
return output;
}
decryptBlock(data) {
return this.encryptBlock(data);
}
encrypt(data) {
return this.encryptBlock(data);
}
}
class NullCipher {
decryptBlock(data) {
return data;
}
encrypt(data) {
return data;
}
}
class AESBaseCipher {
_s = new Uint8Array([0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16]);
_inv_s = new Uint8Array([0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d]);
_mix = new Uint32Array([0x00000000, 0x0e090d0b, 0x1c121a16, 0x121b171d, 0x3824342c, 0x362d3927, 0x24362e3a, 0x2a3f2331, 0x70486858, 0x7e416553, 0x6c5a724e, 0x62537f45, 0x486c5c74, 0x4665517f, 0x547e4662, 0x5a774b69, 0xe090d0b0, 0xee99ddbb, 0xfc82caa6, 0xf28bc7ad, 0xd8b4e49c, 0xd6bde997, 0xc4a6fe8a, 0xcaaff381, 0x90d8b8e8, 0x9ed1b5e3, 0x8ccaa2fe, 0x82c3aff5, 0xa8fc8cc4, 0xa6f581cf, 0xb4ee96d2, 0xbae79bd9, 0xdb3bbb7b, 0xd532b670, 0xc729a16d, 0xc920ac66, 0xe31f8f57, 0xed16825c, 0xff0d9541, 0xf104984a, 0xab73d323, 0xa57ade28, 0xb761c935, 0xb968c43e, 0x9357e70f, 0x9d5eea04, 0x8f45fd19, 0x814cf012, 0x3bab6bcb, 0x35a266c0, 0x27b971dd, 0x29b07cd6, 0x038f5fe7, 0x0d8652ec, 0x1f9d45f1, 0x119448fa, 0x4be30393, 0x45ea0e98, 0x57f11985, 0x59f8148e, 0x73c737bf, 0x7dce3ab4, 0x6fd52da9, 0x61dc20a2, 0xad766df6, 0xa37f60fd, 0xb16477e0, 0xbf6d7aeb, 0x955259da, 0x9b5b54d1, 0x894043cc, 0x87494ec7, 0xdd3e05ae, 0xd33708a5, 0xc12c1fb8, 0xcf2512b3, 0xe51a3182, 0xeb133c89, 0xf9082b94, 0xf701269f, 0x4de6bd46, 0x43efb04d, 0x51f4a750, 0x5ffdaa5b, 0x75c2896a, 0x7bcb8461, 0x69d0937c, 0x67d99e77, 0x3daed51e, 0x33a7d815, 0x21bccf08, 0x2fb5c203, 0x058ae132, 0x0b83ec39, 0x1998fb24, 0x1791f62f, 0x764dd68d, 0x7844db86, 0x6a5fcc9b, 0x6456c190, 0x4e69e2a1, 0x4060efaa, 0x527bf8b7, 0x5c72f5bc, 0x0605bed5, 0x080cb3de, 0x1a17a4c3, 0x141ea9c8, 0x3e218af9, 0x302887f2, 0x223390ef, 0x2c3a9de4, 0x96dd063d, 0x98d40b36, 0x8acf1c2b, 0x84c61120, 0xaef93211, 0xa0f03f1a, 0xb2eb2807, 0xbce2250c, 0xe6956e65, 0xe89c636e, 0xfa877473, 0xf48e7978, 0xdeb15a49, 0xd0b85742, 0xc2a3405f, 0xccaa4d54, 0x41ecdaf7, 0x4fe5d7fc, 0x5dfec0e1, 0x53f7cdea, 0x79c8eedb, 0x77c1e3d0, 0x65daf4cd, 0x6bd3f9c6, 0x31a4b2af, 0x3fadbfa4, 0x2db6a8b9, 0x23bfa5b2, 0x09808683, 0x07898b88, 0x15929c95, 0x1b9b919e, 0xa17c0a47, 0xaf75074c, 0xbd6e1051, 0xb3671d5a, 0x99583e6b, 0x97513360, 0x854a247d, 0x8b432976, 0xd134621f, 0xdf3d6f14, 0xcd267809, 0xc32f7502, 0xe9105633, 0xe7195b38, 0xf5024c25, 0xfb0b412e, 0x9ad7618c, 0x94de6c87, 0x86c57b9a, 0x88cc7691, 0xa2f355a0, 0xacfa58ab, 0xbee14fb6, 0xb0e842bd, 0xea9f09d4, 0xe49604df, 0xf68d13c2, 0xf8841ec9, 0xd2bb3df8, 0xdcb230f3, 0xcea927ee, 0xc0a02ae5, 0x7a47b13c, 0x744ebc37, 0x6655ab2a, 0x685ca621, 0x42638510, 0x4c6a881b, 0x5e719f06, 0x5078920d, 0x0a0fd964, 0x0406d46f, 0x161dc372, 0x1814ce79, 0x322bed48, 0x3c22e043, 0x2e39f75e, 0x2030fa55, 0xec9ab701, 0xe293ba0a, 0xf088ad17, 0xfe81a01c, 0xd4be832d, 0xdab78e26, 0xc8ac993b, 0xc6a59430, 0x9cd2df59, 0x92dbd252, 0x80c0c54f, 0x8ec9c844, 0xa4f6eb75, 0xaaffe67e, 0xb8e4f163, 0xb6edfc68, 0x0c0a67b1, 0x02036aba, 0x10187da7, 0x1e1170ac, 0x342e539d, 0x3a275e96, 0x283c498b, 0x26354480, 0x7c420fe9, 0x724b02e2, 0x605015ff, 0x6e5918f4, 0x44663bc5, 0x4a6f36ce, 0x587421d3, 0x567d2cd8, 0x37a10c7a, 0x39a80171, 0x2bb3166c, 0x25ba1b67, 0x0f853856, 0x018c355d, 0x13972240, 0x1d9e2f4b, 0x47e96422, 0x49e06929, 0x5bfb7e34, 0x55f2733f, 0x7fcd500e, 0x71c45d05, 0x63df4a18, 0x6dd64713, 0xd731dcca, 0xd938d1c1, 0xcb23c6dc, 0xc52acbd7, 0xef15e8e6, 0xe11ce5ed, 0xf307f2f0, 0xfd0efffb, 0xa779b492, 0xa970b999, 0xbb6bae84, 0xb562a38f, 0x9f5d80be, 0x91548db5, 0x834f9aa8, 0x8d4697a3]);
_mixCol = new Uint8Array(256).map((_, i) => i < 128 ? i << 1 : i << 1 ^ 0x1b);
constructor() {
this.buffer = new Uint8Array(16);
this.bufferPosition = 0;
}
_expandKey(cipherKey) {
unreachable("Cannot call `_expandKey` on the base class");
}
_decrypt(input, key) {
let t, u, v;
const state = new Uint8Array(16);
state.set(input);
for (let j = 0, k = this._keySize; j < 16; ++j, ++k) {
state[j] ^= key[k];
}
for (let i = this._cyclesOfRepetition - 1; i >= 1; --i) {
t = state[13];
state[13] = state[9];
state[9] = state[5];
state[5] = state[1];
state[1] = t;
t = state[14];
u = state[10];
state[14] = state[6];
state[10] = state[2];
state[6] = t;
state[2] = u;
t = state[15];
u = state[11];
v = state[7];
state[15] = state[3];
state[11] = t;
state[7] = u;
state[3] = v;
for (let j = 0; j < 16; ++j) {
state[j] = this._inv_s[state[j]];
}
for (let j = 0, k = i * 16; j < 16; ++j, ++k) {
state[j] ^= key[k];
}
for (let j = 0; j < 16; j += 4) {
const s0 = this._mix[state[j]];
const s1 = this._mix[state[j + 1]];
const s2 = this._mix[state[j + 2]];
const s3 = this._mix[state[j + 3]];
t = s0 ^ s1 >>> 8 ^ s1 << 24 ^ s2 >>> 16 ^ s2 << 16 ^ s3 >>> 24 ^ s3 << 8;
state[j] = t >>> 24 & 0xff;
state[j + 1] = t >> 16 & 0xff;
state[j + 2] = t >> 8 & 0xff;
state[j + 3] = t & 0xff;
}
}
t = state[13];
state[13] = state[9];
state[9] = state[5];
state[5] = state[1];
state[1] = t;
t = state[14];
u = state[10];
state[14] = state[6];
state[10] = state[2];
state[6] = t;
state[2] = u;
t = state[15];
u = state[11];
v = state[7];
state[15] = state[3];
state[11] = t;
state[7] = u;
state[3] = v;
for (let j = 0; j < 16; ++j) {
state[j] = this._inv_s[state[j]];
state[j] ^= key[j];
}
return state;
}
_encrypt(input, key) {
const s = this._s;
let t, u, v;
const state = new Uint8Array(16);
state.set(input);
for (let j = 0; j < 16; ++j) {
state[j] ^= key[j];
}
for (let i = 1; i < this._cyclesOfRepetition; i++) {
for (let j = 0; j < 16; ++j) {
state[j] = s[state[j]];
}
v = state[1];
state[1] = state[5];
state[5] = state[9];
state[9] = state[13];
state[13] = v;
v = state[2];
u = state[6];
state[2] = state[10];
state[6] = state[14];
state[10] = v;
state[14] = u;
v = state[3];
u = state[7];
t = state[11];
state[3] = state[15];
state[7] = v;
state[11] = u;
state[15] = t;
for (let j = 0; j < 16; j += 4) {
const s0 = state[j];
const s1 = state[j + 1];
const s2 = state[j + 2];
const s3 = state[j + 3];
t = s0 ^ s1 ^ s2 ^ s3;
state[j] ^= t ^ this._mixCol[s0 ^ s1];
state[j + 1] ^= t ^ this._mixCol[s1 ^ s2];
state[j + 2] ^= t ^ this._mixCol[s2 ^ s3];
state[j + 3] ^= t ^ this._mixCol[s3 ^ s0];
}
for (let j = 0, k = i * 16; j < 16; ++j, ++k) {
state[j] ^= key[k];
}
}
for (let j = 0; j < 16; ++j) {
state[j] = s[state[j]];
}
v = state[1];
state[1] = state[5];
state[5] = state[9];
state[9] = state[13];
state[13] = v;
v = state[2];
u = state[6];
state[2] = state[10];
state[6] = state[14];
state[10] = v;
state[14] = u;
v = state[3];
u = state[7];
t = state[11];
state[3] = state[15];
state[7] = v;
state[11] = u;
state[15] = t;
for (let j = 0, k = this._keySize; j < 16; ++j, ++k) {
state[j] ^= key[k];
}
return state;
}
_decryptBlock2(data, finalize) {
const sourceLength = data.length;
let buffer = this.buffer,
bufferLength = this.bufferPosition;
const result = [];
let iv = this.iv;
for (let i = 0; i < sourceLength; ++i) {
buffer[bufferLength] = data[i];
++bufferLength;
if (bufferLength < 16) {
continue;
}
const plain = this._decrypt(buffer, this._key);
for (let j = 0; j < 16; ++j) {
plain[j] ^= iv[j];
}
iv = buffer;
result.push(plain);
buffer = new Uint8Array(16);
bufferLength = 0;
}
this.buffer = buffer;
this.bufferLength = bufferLength;
this.iv = iv;
if (result.length === 0) {
return new Uint8Array(0);
}
let outputLength = 16 * result.length;
if (finalize) {
const lastBlock = result.at(-1);
let psLen = lastBlock[15];
if (psLen <= 16) {
for (let i = 15, ii = 16 - psLen; i >= ii; --i) {
if (lastBlock[i] !== psLen) {
psLen = 0;
break;
}
}
outputLength -= psLen;
result[result.length - 1] = lastBlock.subarray(0, 16 - psLen);
}
}
const output = new Uint8Array(outputLength);
for (let i = 0, j = 0, ii = result.length; i < ii; ++i, j += 16) {
output.set(result[i], j);
}
return output;
}
decryptBlock(data, finalize, iv = null) {
const sourceLength = data.length;
const buffer = this.buffer;
let bufferLength = this.bufferPosition;
if (iv) {
this.iv = iv;
} else {
for (let i = 0; bufferLength < 16 && i < sourceLength; ++i, ++bufferLength) {
buffer[bufferLength] = data[i];
}
if (bufferLength < 16) {
this.bufferLength = bufferLength;
return new Uint8Array(0);
}
this.iv = buffer;
data = data.subarray(16);
}
this.buffer = new Uint8Array(16);
this.bufferLength = 0;
this.decryptBlock = this._decryptBlock2;
return this.decryptBlock(data, finalize);
}
encrypt(data, iv) {
const sourceLength = data.length;
let buffer = this.buffer,
bufferLength = this.bufferPosition;
const result = [];
iv ||= new Uint8Array(16);
for (let i = 0; i < sourceLength; ++i) {
buffer[bufferLength] = data[i];
++bufferLength;
if (bufferLength < 16) {
continue;
}
for (let j = 0; j < 16; ++j) {
buffer[j] ^= iv[j];
}
const cipher = this._encrypt(buffer, this._key);
iv = cipher;
result.push(cipher);
buffer = new Uint8Array(16);
bufferLength = 0;
}
this.buffer = buffer;
this.bufferLength = bufferLength;
this.iv = iv;
if (result.length === 0) {
return new Uint8Array(0);
}
const outputLength = 16 * result.length;
const output = new Uint8Array(outputLength);
for (let i = 0, j = 0, ii = result.length; i < ii; ++i, j += 16) {
output.set(result[i], j);
}
return output;
}
}
class AES128Cipher extends AESBaseCipher {
_rcon = new Uint8Array([0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d]);
constructor(key) {
super();
this._cyclesOfRepetition = 10;
this._keySize = 160;
this._key = this._expandKey(key);
}
_expandKey(cipherKey) {
const b = 176;
const s = this._s;
const rcon = this._rcon;
const result = new Uint8Array(b);
result.set(cipherKey);
for (let j = 16, i = 1; j < b; ++i) {
let t1 = result[j - 3];
let t2 = result[j - 2];
let t3 = result[j - 1];
let t4 = result[j - 4];
t1 = s[t1];
t2 = s[t2];
t3 = s[t3];
t4 = s[t4];
t1 ^= rcon[i];
for (let n = 0; n < 4; ++n) {
result[j] = t1 ^= result[j - 16];
j++;
result[j] = t2 ^= result[j - 16];
j++;
result[j] = t3 ^= result[j - 16];
j++;
result[j] = t4 ^= result[j - 16];
j++;
}
}
return result;
}
}
class AES256Cipher extends AESBaseCipher {
constructor(key) {
super();
this._cyclesOfRepetition = 14;
this._keySize = 224;
this._key = this._expandKey(key);
}
_expandKey(cipherKey) {
const b = 240;
const s = this._s;
const result = new Uint8Array(b);
result.set(cipherKey);
let r = 1;
let t1, t2, t3, t4;
for (let j = 32, i = 1; j < b; ++i) {
if (j % 32 === 16) {
t1 = s[t1];
t2 = s[t2];
t3 = s[t3];
t4 = s[t4];
} else if (j % 32 === 0) {
t1 = result[j - 3];
t2 = result[j - 2];
t3 = result[j - 1];
t4 = result[j - 4];
t1 = s[t1];
t2 = s[t2];
t3 = s[t3];
t4 = s[t4];
t1 ^= r;
if ((r <<= 1) >= 256) {
r = (r ^ 0x1b) & 0xff;
}
}
for (let n = 0; n < 4; ++n) {
result[j] = t1 ^= result[j - 32];
j++;
result[j] = t2 ^= result[j - 32];
j++;
result[j] = t3 ^= result[j - 32];
j++;
result[j] = t4 ^= result[j - 32];
j++;
}
}
return result;
}
}
class PDFBase {
_hash(password, input, userBytes) {
unreachable("Abstract method `_hash` called");
}
checkOwnerPassword(password, ownerValidationSalt, userBytes, ownerPassword) {
const hashData = new Uint8Array(password.length + 56);
hashData.set(password, 0);
hashData.set(ownerValidationSalt, password.length);
hashData.set(userBytes, password.length + ownerValidationSalt.length);
const result = this._hash(password, hashData, userBytes);
return isArrayEqual(result, ownerPassword);
}
checkUserPassword(password, userValidationSalt, userPassword) {
const hashData = new Uint8Array(password.length + 8);
hashData.set(password, 0);
hashData.set(userValidationSalt, password.length);
const result = this._hash(password, hashData, []);
return isArrayEqual(result, userPassword);
}
getOwnerKey(password, ownerKeySalt, userBytes, ownerEncryption) {
const hashData = new Uint8Array(password.length + 56);
hashData.set(password, 0);
hashData.set(ownerKeySalt, password.length);
hashData.set(userBytes, password.length + ownerKeySalt.length);
const key = this._hash(password, hashData, userBytes);
const cipher = new AES256Cipher(key);
return cipher.decryptBlock(ownerEncryption, false, new Uint8Array(16));
}
getUserKey(password, userKeySalt, userEncryption) {
const hashData = new Uint8Array(password.length + 8);
hashData.set(password, 0);
hashData.set(userKeySalt, password.length);
const key = this._hash(password, hashData, []);
const cipher = new AES256Cipher(key);
return cipher.decryptBlock(userEncryption, false, new Uint8Array(16));
}
}
class PDF17 extends PDFBase {
_hash(password, input, userBytes) {
return calculateSHA256(input, 0, input.length);
}
}
class PDF20 extends PDFBase {
_hash(password, input, userBytes) {
let k = calculateSHA256(input, 0, input.length).subarray(0, 32);
let e = [0];
let i = 0;
while (i < 64 || e.at(-1) > i - 32) {
const combinedLength = password.length + k.length + userBytes.length,
combinedArray = new Uint8Array(combinedLength);
let writeOffset = 0;
combinedArray.set(password, writeOffset);
writeOffset += password.length;
combinedArray.set(k, writeOffset);
writeOffset += k.length;
combinedArray.set(userBytes, writeOffset);
const k1 = new Uint8Array(combinedLength * 64);
for (let j = 0, pos = 0; j < 64; j++, pos += combinedLength) {
k1.set(combinedArray, pos);
}
const cipher = new AES128Cipher(k.subarray(0, 16));
e = cipher.encrypt(k1, k.subarray(16, 32));
const remainder = Math.sumPrecise(e.slice(0, 16)) % 3;
if (remainder === 0) {
k = calculateSHA256(e, 0, e.length);
} else if (remainder === 1) {
k = calculateSHA384(e, 0, e.length);
} else if (remainder === 2) {
k = calculateSHA512(e, 0, e.length);
}
i++;
}
return k.subarray(0, 32);
}
}
class CipherTransform {
constructor(stringCipherConstructor, streamCipherConstructor) {
this.StringCipherConstructor = stringCipherConstructor;
this.StreamCipherConstructor = streamCipherConstructor;
}
createStream(stream, length) {
const cipher = new this.StreamCipherConstructor();
return new DecryptStream(stream, length, function cipherTransformDecryptStream(data, finalize) {
return cipher.decryptBlock(data, finalize);
});
}
decryptString(s) {
const cipher = new this.StringCipherConstructor();
let data = stringToBytes(s);
data = cipher.decryptBlock(data, true);
return bytesToString(data);
}
encryptString(s) {
const cipher = new this.StringCipherConstructor();
if (cipher instanceof AESBaseCipher) {
const strLen = s.length;
const pad = 16 - strLen % 16;
s += String.fromCharCode(pad).repeat(pad);
const iv = new Uint8Array(16);
crypto.getRandomValues(iv);
let data = stringToBytes(s);
data = cipher.encrypt(data, iv);
const buf = new Uint8Array(16 + data.length);
buf.set(iv);
buf.set(data, 16);
return bytesToString(buf);
}
let data = stringToBytes(s);
data = cipher.encrypt(data);
return bytesToString(data);
}
}
class CipherTransformFactory {
static get _defaultPasswordBytes() {
return shadow(this, "_defaultPasswordBytes", new Uint8Array([0x28, 0xbf, 0x4e, 0x5e, 0x4e, 0x75, 0x8a, 0x41, 0x64, 0x00, 0x4e, 0x56, 0xff, 0xfa, 0x01, 0x08, 0x2e, 0x2e, 0x00, 0xb6, 0xd0, 0x68, 0x3e, 0x80, 0x2f, 0x0c, 0xa9, 0xfe, 0x64, 0x53, 0x69, 0x7a]));
}
#createEncryptionKey20(revision, password, ownerPassword, ownerValidationSalt, ownerKeySalt, uBytes, userPassword, userValidationSalt, userKeySalt, ownerEncryption, userEncryption, perms) {
if (password) {
const passwordLength = Math.min(127, password.length);
password = password.subarray(0, passwordLength);
} else {
password = [];
}
const pdfAlgorithm = revision === 6 ? new PDF20() : new PDF17();
if (pdfAlgorithm.checkUserPassword(password, userValidationSalt, userPassword)) {
return pdfAlgorithm.getUserKey(password, userKeySalt, userEncryption);
} else if (password.length && pdfAlgorithm.checkOwnerPassword(password, ownerValidationSalt, uBytes, ownerPassword)) {
return pdfAlgorithm.getOwnerKey(password, ownerKeySalt, uBytes, ownerEncryption);
}
return null;
}
#prepareKeyData(fileId, password, ownerPassword, userPassword, flags, revision, keyLength, encryptMetadata) {
const hashDataSize = 40 + ownerPassword.length + fileId.length;
const hashData = new Uint8Array(hashDataSize);
let i = 0,
j,
n;
if (password) {
n = Math.min(32, password.length);
for (; i < n; ++i) {
hashData[i] = password[i];
}
}
j = 0;
while (i < 32) {
hashData[i++] = CipherTransformFactory._defaultPasswordBytes[j++];
}
hashData.set(ownerPassword, i);
i += ownerPassword.length;
hashData[i++] = flags & 0xff;
hashData[i++] = flags >> 8 & 0xff;
hashData[i++] = flags >> 16 & 0xff;
hashData[i++] = flags >>> 24 & 0xff;
hashData.set(fileId, i);
i += fileId.length;
if (revision >= 4 && !encryptMetadata) {
hashData.fill(0xff, i, i + 4);
i += 4;
}
let hash = calculateMD5(hashData, 0, i);
const keyLengthInBytes = keyLength >> 3;
if (revision >= 3) {
for (j = 0; j < 50; ++j) {
hash = calculateMD5(hash, 0, keyLengthInBytes);
}
}
const encryptionKey = hash.subarray(0, keyLengthInBytes);
let cipher, checkData;
if (revision >= 3) {
i = 0;
hashData.set(CipherTransformFactory._defaultPasswordBytes, i);
i += 32;
hashData.set(fileId, i);
i += fileId.length;
cipher = new ARCFourCipher(encryptionKey);
checkData = cipher.encryptBlock(calculateMD5(hashData, 0, i));
n = encryptionKey.length;
const derivedKey = new Uint8Array(n);
for (j = 1; j <= 19; ++j) {
for (let k = 0; k < n; ++k) {
derivedKey[k] = encryptionKey[k] ^ j;
}
cipher = new ARCFourCipher(derivedKey);
checkData = cipher.encryptBlock(checkData);
}
} else {
cipher = new ARCFourCipher(encryptionKey);
checkData = cipher.encryptBlock(CipherTransformFactory._defaultPasswordBytes);
}
return checkData.every((data, k) => userPassword[k] === data) ? encryptionKey : null;
}
#decodeUserPassword(password, ownerPassword, revision, keyLength) {
const hashData = new Uint8Array(32);
let i = 0;
const n = Math.min(32, password.length);
for (; i < n; ++i) {
hashData[i] = password[i];
}
let j = 0;
while (i < 32) {
hashData[i++] = CipherTransformFactory._defaultPasswordBytes[j++];
}
let hash = calculateMD5(hashData, 0, i);
const keyLengthInBytes = keyLength >> 3;
if (revision >= 3) {
for (j = 0; j < 50; ++j) {
hash = calculateMD5(hash, 0, hash.length);
}
}
let cipher, userPassword;
if (revision >= 3) {
userPassword = ownerPassword;
const derivedKey = new Uint8Array(keyLengthInBytes);
for (j = 19; j >= 0; j--) {
for (let k = 0; k < keyLengthInBytes; ++k) {
derivedKey[k] = hash[k] ^ j;
}
cipher = new ARCFourCipher(derivedKey);
userPassword = cipher.encryptBlock(userPassword);
}
} else {
cipher = new ARCFourCipher(hash.subarray(0, keyLengthInBytes));
userPassword = cipher.encryptBlock(ownerPassword);
}
return userPassword;
}
#buildObjectKey(num, gen, encryptionKey, isAes = false) {
const n = encryptionKey.length;
const key = new Uint8Array(n + 9);
key.set(encryptionKey);
let i = n;
key[i++] = num & 0xff;
key[i++] = num >> 8 & 0xff;
key[i++] = num >> 16 & 0xff;
key[i++] = gen & 0xff;
key[i++] = gen >> 8 & 0xff;
if (isAes) {
key[i++] = 0x73;
key[i++] = 0x41;
key[i++] = 0x6c;
key[i++] = 0x54;
}
const hash = calculateMD5(key, 0, i);
return hash.subarray(0, Math.min(n + 5, 16));
}
#buildCipherConstructor(cf, name, num, gen, key) {
if (!(name instanceof Name)) {
throw new FormatError("Invalid crypt filter name.");
}
const self = this;
const cryptFilter = cf.get(name.name);
const cfm = cryptFilter?.get("CFM");
if (!cfm || cfm.name === "None") {
return function () {
return new NullCipher();
};
}
if (cfm.name === "V2") {
return function () {
return new ARCFourCipher(self.#buildObjectKey(num, gen, key, false));
};
}
if (cfm.name === "AESV2") {
return function () {
return new AES128Cipher(self.#buildObjectKey(num, gen, key, true));
};
}
if (cfm.name === "AESV3") {
return function () {
return new AES256Cipher(key);
};
}
throw new FormatError("Unknown crypto method");
}
constructor(dict, fileId, password) {
const filter = dict.get("Filter");
if (!isName(filter, "Standard")) {
throw new FormatError("unknown encryption method");
}
this.filterName = filter.name;
this.dict = dict;
const algorithm = dict.get("V");
if (!Number.isInteger(algorithm) || algorithm !== 1 && algorithm !== 2 && algorithm !== 4 && algorithm !== 5) {
throw new FormatError("unsupported encryption algorithm");
}
this.algorithm = algorithm;
let keyLength = dict.get("Length");
if (!keyLength) {
if (algorithm <= 3) {
keyLength = 40;
} else {
const cfDict = dict.get("CF");
const streamCryptoName = dict.get("StmF");
if (cfDict instanceof Dict && streamCryptoName instanceof Name) {
cfDict.suppressEncryption = true;
const handlerDict = cfDict.get(streamCryptoName.name);
keyLength = handlerDict?.get("Length") || 128;
if (keyLength < 40) {
keyLength <<= 3;
}
}
}
}
if (!Number.isInteger(keyLength) || keyLength < 40 || keyLength % 8 !== 0) {
throw new FormatError("invalid key length");
}
const ownerBytes = stringToBytes(dict.get("O")),
userBytes = stringToBytes(dict.get("U"));
const ownerPassword = ownerBytes.subarray(0, 32);
const userPassword = userBytes.subarray(0, 32);
const flags = dict.get("P");
const revision = dict.get("R");
const encryptMetadata = (algorithm === 4 || algorithm === 5) && dict.get("EncryptMetadata") !== false;
this.encryptMetadata = encryptMetadata;
const fileIdBytes = stringToBytes(fileId);
let passwordBytes;
if (password) {
if (revision === 6) {
try {
password = utf8StringToString(password);
} catch {
warn("CipherTransformFactory: Unable to convert UTF8 encoded password.");
}
}
passwordBytes = stringToBytes(password);
}
let encryptionKey;
if (algorithm !== 5) {
encryptionKey = this.#prepareKeyData(fileIdBytes, passwordBytes, ownerPassword, userPassword, flags, revision, keyLength, encryptMetadata);
} else {
const ownerValidationSalt = ownerBytes.subarray(32, 40);
const ownerKeySalt = ownerBytes.subarray(40, 48);
const uBytes = userBytes.subarray(0, 48);
const userValidationSalt = userBytes.subarray(32, 40);
const userKeySalt = userBytes.subarray(40, 48);
const ownerEncryption = stringToBytes(dict.get("OE"));
const userEncryption = stringToBytes(dict.get("UE"));
const perms = stringToBytes(dict.get("Perms"));
encryptionKey = this.#createEncryptionKey20(revision, passwordBytes, ownerPassword, ownerValidationSalt, ownerKeySalt, uBytes, userPassword, userValidationSalt, userKeySalt, ownerEncryption, userEncryption, perms);
}
if (!encryptionKey) {
if (!password) {
throw new PasswordException("No password given", PasswordResponses.NEED_PASSWORD);
}
const decodedPassword = this.#decodeUserPassword(passwordBytes, ownerPassword, revision, keyLength);
encryptionKey = this.#prepareKeyData(fileIdBytes, decodedPassword, ownerPassword, userPassword, flags, revision, keyLength, encryptMetadata);
}
if (!encryptionKey) {
throw new PasswordException("Incorrect Password", PasswordResponses.INCORRECT_PASSWORD);
}
if (algorithm === 4 && encryptionKey.length < 16) {
this.encryptionKey = new Uint8Array(16);
this.encryptionKey.set(encryptionKey);
} else {
this.encryptionKey = encryptionKey;
}
if (algorithm >= 4) {
const cf = dict.get("CF");
if (cf instanceof Dict) {
cf.suppressEncryption = true;
}
this.cf = cf;
this.stmf = dict.get("StmF") || Name.get("Identity");
this.strf = dict.get("StrF") || Name.get("Identity");
this.eff = dict.get("EFF") || this.stmf;
}
}
createCipherTransform(num, gen) {
if (this.algorithm === 4 || this.algorithm === 5) {
return new CipherTransform(this.#buildCipherConstructor(this.cf, this.strf, num, gen, this.encryptionKey), this.#buildCipherConstructor(this.cf, this.stmf, num, gen, this.encryptionKey));
}
const key = this.#buildObjectKey(num, gen, this.encryptionKey, false);
const cipherConstructor = function () {
return new ARCFourCipher(key);
};
return new CipherTransform(cipherConstructor, cipherConstructor);
}
}
;// ./src/core/xref.js
class XRef {
#firstXRefStmPos = null;
constructor(stream, pdfManager) {
this.stream = stream;
this.pdfManager = pdfManager;
this.entries = [];
this._xrefStms = new Set();
this._cacheMap = new Map();
this._pendingRefs = new RefSet();
this._newPersistentRefNum = null;
this._newTemporaryRefNum = null;
this._persistentRefsCache = null;
}
getNewPersistentRef(obj) {
if (this._newPersistentRefNum === null) {
this._newPersistentRefNum = this.entries.length || 1;
}
const num = this._newPersistentRefNum++;
this._cacheMap.set(num, obj);
return Ref.get(num, 0);
}
getNewTemporaryRef() {
if (this._newTemporaryRefNum === null) {
this._newTemporaryRefNum = this.entries.length || 1;
if (this._newPersistentRefNum) {
this._persistentRefsCache = new Map();
for (let i = this._newTemporaryRefNum; i < this._newPersistentRefNum; i++) {
this._persistentRefsCache.set(i, this._cacheMap.get(i));
this._cacheMap.delete(i);
}
}
}
return Ref.get(this._newTemporaryRefNum++, 0);
}
resetNewTemporaryRef() {
this._newTemporaryRefNum = null;
if (this._persistentRefsCache) {
for (const [num, obj] of this._persistentRefsCache) {
this._cacheMap.set(num, obj);
}
}
this._persistentRefsCache = null;
}
setStartXRef(startXRef) {
this.startXRefQueue = [startXRef];
}
parse(recoveryMode = false) {
let trailerDict;
if (!recoveryMode) {
trailerDict = this.readXRef();
} else {
warn("Indexing all PDF objects");
trailerDict = this.indexObjects();
}
trailerDict.assignXref(this);
this.trailer = trailerDict;
let encrypt;
try {
encrypt = trailerDict.get("Encrypt");
} catch (ex) {
if (ex instanceof MissingDataException) {
throw ex;
}
warn(`XRef.parse - Invalid "Encrypt" reference: "${ex}".`);
}
if (encrypt instanceof Dict) {
const ids = trailerDict.get("ID");
const fileId = ids?.length ? ids[0] : "";
encrypt.suppressEncryption = true;
this.encrypt = new CipherTransformFactory(encrypt, fileId, this.pdfManager.password);
}
let root;
try {
root = trailerDict.get("Root");
} catch (ex) {
if (ex instanceof MissingDataException) {
throw ex;
}
warn(`XRef.parse - Invalid "Root" reference: "${ex}".`);
}
if (root instanceof Dict) {
try {
const pages = root.get("Pages");
if (pages instanceof Dict) {
this.root = root;
return;
}
} catch (ex) {
if (ex instanceof MissingDataException) {
throw ex;
}
warn(`XRef.parse - Invalid "Pages" reference: "${ex}".`);
}
}
if (!recoveryMode) {
throw new XRefParseException();
}
throw new InvalidPDFException("Invalid Root reference.");
}
processXRefTable(parser) {
if (!("tableState" in this)) {
this.tableState = {
entryNum: 0,
streamPos: parser.lexer.stream.pos,
parserBuf1: parser.buf1,
parserBuf2: parser.buf2
};
}
const obj = this.readXRefTable(parser);
if (!isCmd(obj, "trailer")) {
throw new FormatError("Invalid XRef table: could not find trailer dictionary");
}
let dict = parser.getObj();
if (!(dict instanceof Dict) && dict.dict) {
dict = dict.dict;
}
if (!(dict instanceof Dict)) {
throw new FormatError("Invalid XRef table: could not parse trailer dictionary");
}
delete this.tableState;
return dict;
}
readXRefTable(parser) {
const stream = parser.lexer.stream;
const tableState = this.tableState;
stream.pos = tableState.streamPos;
parser.buf1 = tableState.parserBuf1;
parser.buf2 = tableState.parserBuf2;
let obj;
while (true) {
if (!("firstEntryNum" in tableState) || !("entryCount" in tableState)) {
if (isCmd(obj = parser.getObj(), "trailer")) {
break;
}
tableState.firstEntryNum = obj;
tableState.entryCount = parser.getObj();
}
let first = tableState.firstEntryNum;
const count = tableState.entryCount;
if (!Number.isInteger(first) || !Number.isInteger(count)) {
throw new FormatError("Invalid XRef table: wrong types in subsection header");
}
for (let i = tableState.entryNum; i < count; i++) {
tableState.streamPos = stream.pos;
tableState.entryNum = i;
tableState.parserBuf1 = parser.buf1;
tableState.parserBuf2 = parser.buf2;
const entry = {};
entry.offset = parser.getObj();
entry.gen = parser.getObj();
const type = parser.getObj();
if (type instanceof Cmd) {
switch (type.cmd) {
case "f":
entry.free = true;
break;
case "n":
entry.uncompressed = true;
break;
}
}
if (!Number.isInteger(entry.offset) || !Number.isInteger(entry.gen) || !(entry.free || entry.uncompressed)) {
throw new FormatError(`Invalid entry in XRef subsection: ${first}, ${count}`);
}
if (i === 0 && entry.free && first === 1) {
first = 0;
}
if (!this.entries[i + first]) {
this.entries[i + first] = entry;
}
}
tableState.entryNum = 0;
tableState.streamPos = stream.pos;
tableState.parserBuf1 = parser.buf1;
tableState.parserBuf2 = parser.buf2;
delete tableState.firstEntryNum;
delete tableState.entryCount;
}
if (this.entries[0] && !this.entries[0].free) {
throw new FormatError("Invalid XRef table: unexpected first object");
}
return obj;
}
processXRefStream(stream) {
if (!("streamState" in this)) {
const {
dict,
pos
} = stream;
const byteWidths = dict.get("W");
const range = dict.get("Index") || [0, dict.get("Size")];
this.streamState = {
entryRanges: range,
byteWidths,
entryNum: 0,
streamPos: pos
};
}
this.readXRefStream(stream);
delete this.streamState;
return stream.dict;
}
readXRefStream(stream) {
const streamState = this.streamState;
stream.pos = streamState.streamPos;
const [typeFieldWidth, offsetFieldWidth, generationFieldWidth] = streamState.byteWidths;
const entryRanges = streamState.entryRanges;
while (entryRanges.length > 0) {
const [first, n] = entryRanges;
if (!Number.isInteger(first) || !Number.isInteger(n)) {
throw new FormatError(`Invalid XRef range fields: ${first}, ${n}`);
}
if (!Number.isInteger(typeFieldWidth) || !Number.isInteger(offsetFieldWidth) || !Number.isInteger(generationFieldWidth)) {
throw new FormatError(`Invalid XRef entry fields length: ${first}, ${n}`);
}
for (let i = streamState.entryNum; i < n; ++i) {
streamState.entryNum = i;
streamState.streamPos = stream.pos;
let type = 0,
offset = 0,
generation = 0;
for (let j = 0; j < typeFieldWidth; ++j) {
const typeByte = stream.getByte();
if (typeByte === -1) {
throw new FormatError("Invalid XRef byteWidths 'type'.");
}
type = type << 8 | typeByte;
}
if (typeFieldWidth === 0) {
type = 1;
}
for (let j = 0; j < offsetFieldWidth; ++j) {
const offsetByte = stream.getByte();
if (offsetByte === -1) {
throw new FormatError("Invalid XRef byteWidths 'offset'.");
}
offset = offset << 8 | offsetByte;
}
for (let j = 0; j < generationFieldWidth; ++j) {
const generationByte = stream.getByte();
if (generationByte === -1) {
throw new FormatError("Invalid XRef byteWidths 'generation'.");
}
generation = generation << 8 | generationByte;
}
const entry = {};
entry.offset = offset;
entry.gen = generation;
switch (type) {
case 0:
entry.free = true;
break;
case 1:
entry.uncompressed = true;
break;
case 2:
break;
default:
throw new FormatError(`Invalid XRef entry type: ${type}`);
}
if (!this.entries[first + i]) {
this.entries[first + i] = entry;
}
}
streamState.entryNum = 0;
streamState.streamPos = stream.pos;
entryRanges.splice(0, 2);
}
}
indexObjects() {
const TAB = 0x9,
LF = 0xa,
CR = 0xd,
SPACE = 0x20;
const PERCENT = 0x25,
LT = 0x3c;
function readToken(data, offset) {
let token = "",
ch = data[offset];
while (ch !== LF && ch !== CR && ch !== LT) {
if (++offset >= data.length) {
break;
}
token += String.fromCharCode(ch);
ch = data[offset];
}
return token;
}
function skipUntil(data, offset, what) {
const length = what.length,
dataLength = data.length;
let skipped = 0;
while (offset < dataLength) {
let i = 0;
while (i < length && data[offset + i] === what[i]) {
++i;
}
if (i >= length) {
break;
}
offset++;
skipped++;
}
return skipped;
}
const gEndobjRegExp = /\b(endobj|\d+\s+\d+\s+obj|xref|trailer\s*<<)\b/g;
const gStartxrefRegExp = /\b(startxref|\d+\s+\d+\s+obj)\b/g;
const objRegExp = /^(\d+)\s+(\d+)\s+obj\b/;
const trailerBytes = new Uint8Array([116, 114, 97, 105, 108, 101, 114]);
const startxrefBytes = new Uint8Array([115, 116, 97, 114, 116, 120, 114, 101, 102]);
const xrefBytes = new Uint8Array([47, 88, 82, 101, 102]);
this.entries.length = 0;
this._cacheMap.clear();
const stream = this.stream;
stream.pos = 0;
const buffer = stream.getBytes(),
bufferStr = bytesToString(buffer),
length = buffer.length;
let position = stream.start;
const trailers = [],
xrefStms = [];
while (position < length) {
let ch = buffer[position];
if (ch === TAB || ch === LF || ch === CR || ch === SPACE) {
++position;
continue;
}
if (ch === PERCENT) {
do {
++position;
if (position >= length) {
break;
}
ch = buffer[position];
} while (ch !== LF && ch !== CR);
continue;
}
const token = readToken(buffer, position);
let m;
if (token.startsWith("xref") && (token.length === 4 || /\s/.test(token[4]))) {
position += skipUntil(buffer, position, trailerBytes);
trailers.push(position);
position += skipUntil(buffer, position, startxrefBytes);
} else if (m = objRegExp.exec(token)) {
const num = m[1] | 0,
gen = m[2] | 0;
const startPos = position + token.length;
let contentLength,
updateEntries = false;
if (!this.entries[num]) {
updateEntries = true;
} else if (this.entries[num].gen === gen) {
try {
const parser = new Parser({
lexer: new Lexer(stream.makeSubStream(startPos))
});
parser.getObj();
updateEntries = true;
} catch (ex) {
if (ex instanceof ParserEOFException) {
warn(`indexObjects -- checking object (${token}): "${ex}".`);
} else {
updateEntries = true;
}
}
}
if (updateEntries) {
this.entries[num] = {
offset: position - stream.start,
gen,
uncompressed: true
};
}
gEndobjRegExp.lastIndex = startPos;
const match = gEndobjRegExp.exec(bufferStr);
if (match) {
const endPos = gEndobjRegExp.lastIndex + 1;
contentLength = endPos - position;
if (match[1] !== "endobj") {
warn(`indexObjects: Found "${match[1]}" inside of another "obj", ` + 'caused by missing "endobj" -- trying to recover.');
contentLength -= match[1].length + 1;
}
} else {
contentLength = length - position;
}
const content = buffer.subarray(position, position + contentLength);
const xrefTagOffset = skipUntil(content, 0, xrefBytes);
if (xrefTagOffset < contentLength && content[xrefTagOffset + 5] < 64) {
xrefStms.push(position - stream.start);
this._xrefStms.add(position - stream.start);
}
position += contentLength;
} else if (token.startsWith("trailer") && (token.length === 7 || /\s/.test(token[7]))) {
trailers.push(position);
const startPos = position + token.length;
let contentLength;
gStartxrefRegExp.lastIndex = startPos;
const match = gStartxrefRegExp.exec(bufferStr);
if (match) {
const endPos = gStartxrefRegExp.lastIndex + 1;
contentLength = endPos - position;
if (match[1] !== "startxref") {
warn(`indexObjects: Found "${match[1]}" after "trailer", ` + 'caused by missing "startxref" -- trying to recover.');
contentLength -= match[1].length + 1;
}
} else {
contentLength = length - position;
}
position += contentLength;
} else {
position += token.length + 1;
}
}
for (const xrefStm of xrefStms) {
this.startXRefQueue.push(xrefStm);
this.readXRef(true);
}
const trailerDicts = [];
let isEncrypted = false;
for (const trailer of trailers) {
stream.pos = trailer;
const parser = new Parser({
lexer: new Lexer(stream),
xref: this,
allowStreams: true,
recoveryMode: true
});
const obj = parser.getObj();
if (!isCmd(obj, "trailer")) {
continue;
}
const dict = parser.getObj();
if (!(dict instanceof Dict)) {
continue;
}
trailerDicts.push(dict);
if (dict.has("Encrypt")) {
isEncrypted = true;
}
}
let trailerDict, trailerError;
for (const dict of [...trailerDicts, "genFallback", ...trailerDicts]) {
if (dict === "genFallback") {
if (!trailerError) {
break;
}
this._generationFallback = true;
continue;
}
let validPagesDict = false;
try {
const rootDict = dict.get("Root");
if (!(rootDict instanceof Dict)) {
continue;
}
const pagesDict = rootDict.get("Pages");
if (!(pagesDict instanceof Dict)) {
continue;
}
const pagesCount = pagesDict.get("Count");
if (Number.isInteger(pagesCount)) {
validPagesDict = true;
}
} catch (ex) {
trailerError = ex;
continue;
}
if (validPagesDict && (!isEncrypted || dict.has("Encrypt")) && dict.has("ID")) {
return dict;
}
trailerDict = dict;
}
if (trailerDict) {
return trailerDict;
}
if (this.topDict) {
return this.topDict;
}
if (!trailerDicts.length) {
for (const [num, entry] of this.entries.entries()) {
if (!entry) {
continue;
}
const ref = Ref.get(num, entry.gen);
let obj;
try {
obj = this.fetch(ref);
} catch {
continue;
}
if (obj instanceof BaseStream) {
obj = obj.dict;
}
if (obj instanceof Dict && obj.has("Root")) {
return obj;
}
}
}
throw new InvalidPDFException("Invalid PDF structure.");
}
readXRef(recoveryMode = false) {
const stream = this.stream;
const startXRefParsedCache = new Set();
while (this.startXRefQueue.length) {
try {
const startXRef = this.startXRefQueue[0];
if (startXRefParsedCache.has(startXRef)) {
warn("readXRef - skipping XRef table since it was already parsed.");
this.startXRefQueue.shift();
continue;
}
startXRefParsedCache.add(startXRef);
stream.pos = startXRef + stream.start;
const parser = new Parser({
lexer: new Lexer(stream),
xref: this,
allowStreams: true
});
let obj = parser.getObj();
let dict;
if (isCmd(obj, "xref")) {
dict = this.processXRefTable(parser);
if (!this.topDict) {
this.topDict = dict;
}
obj = dict.get("XRefStm");
if (Number.isInteger(obj) && !this._xrefStms.has(obj)) {
this._xrefStms.add(obj);
this.startXRefQueue.push(obj);
this.#firstXRefStmPos ??= obj;
}
} else if (Number.isInteger(obj)) {
if (!Number.isInteger(parser.getObj()) || !isCmd(parser.getObj(), "obj") || !((obj = parser.getObj()) instanceof BaseStream)) {
throw new FormatError("Invalid XRef stream");
}
dict = this.processXRefStream(obj);
if (!this.topDict) {
this.topDict = dict;
}
if (!dict) {
throw new FormatError("Failed to read XRef stream");
}
} else {
throw new FormatError("Invalid XRef stream header");
}
obj = dict.get("Prev");
if (Number.isInteger(obj)) {
this.startXRefQueue.push(obj);
} else if (obj instanceof Ref) {
this.startXRefQueue.push(obj.num);
}
} catch (e) {
if (e instanceof MissingDataException) {
throw e;
}
info("(while reading XRef): " + e);
}
this.startXRefQueue.shift();
}
if (this.topDict) {
return this.topDict;
}
if (recoveryMode) {
return undefined;
}
throw new XRefParseException();
}
get lastXRefStreamPos() {
return this.#firstXRefStmPos ?? (this._xrefStms.size > 0 ? Math.max(...this._xrefStms) : null);
}
getEntry(i) {
const xrefEntry = this.entries[i];
if (xrefEntry && !xrefEntry.free && xrefEntry.offset) {
return xrefEntry;
}
return null;
}
fetchIfRef(obj, suppressEncryption = false) {
if (obj instanceof Ref) {
return this.fetch(obj, suppressEncryption);
}
return obj;
}
fetch(ref, suppressEncryption = false) {
if (!(ref instanceof Ref)) {
throw new Error("ref object is not a reference");
}
const num = ref.num;
const cacheEntry = this._cacheMap.get(num);
if (cacheEntry !== undefined) {
if (cacheEntry instanceof Dict && !cacheEntry.objId) {
cacheEntry.objId = ref.toString();
}
return cacheEntry;
}
let xrefEntry = this.getEntry(num);
if (xrefEntry === null) {
return xrefEntry;
}
if (this._pendingRefs.has(ref)) {
this._pendingRefs.remove(ref);
warn(`Ignoring circular reference: ${ref}.`);
return CIRCULAR_REF;
}
this._pendingRefs.put(ref);
try {
xrefEntry = xrefEntry.uncompressed ? this.fetchUncompressed(ref, xrefEntry, suppressEncryption) : this.fetchCompressed(ref, xrefEntry, suppressEncryption);
this._pendingRefs.remove(ref);
} catch (ex) {
this._pendingRefs.remove(ref);
throw ex;
}
if (xrefEntry instanceof Dict) {
xrefEntry.objId = ref.toString();
} else if (xrefEntry instanceof BaseStream) {
xrefEntry.dict.objId = ref.toString();
}
return xrefEntry;
}
fetchUncompressed(ref, xrefEntry, suppressEncryption = false) {
const gen = ref.gen;
let num = ref.num;
if (xrefEntry.gen !== gen) {
const msg = `Inconsistent generation in XRef: ${ref}`;
if (this._generationFallback && xrefEntry.gen < gen) {
warn(msg);
return this.fetchUncompressed(Ref.get(num, xrefEntry.gen), xrefEntry, suppressEncryption);
}
throw new XRefEntryException(msg);
}
const stream = this.stream.makeSubStream(xrefEntry.offset + this.stream.start);
const parser = new Parser({
lexer: new Lexer(stream),
xref: this,
allowStreams: true
});
const obj1 = parser.getObj();
const obj2 = parser.getObj();
const obj3 = parser.getObj();
if (obj1 !== num || obj2 !== gen || !(obj3 instanceof Cmd)) {
throw new XRefEntryException(`Bad (uncompressed) XRef entry: ${ref}`);
}
if (obj3.cmd !== "obj") {
if (obj3.cmd.startsWith("obj")) {
num = parseInt(obj3.cmd.substring(3), 10);
if (!Number.isNaN(num)) {
return num;
}
}
throw new XRefEntryException(`Bad (uncompressed) XRef entry: ${ref}`);
}
xrefEntry = this.encrypt && !suppressEncryption ? parser.getObj(this.encrypt.createCipherTransform(num, gen)) : parser.getObj();
if (!(xrefEntry instanceof BaseStream)) {
this._cacheMap.set(num, xrefEntry);
}
return xrefEntry;
}
fetchCompressed(ref, xrefEntry, suppressEncryption = false) {
const tableOffset = xrefEntry.offset;
const stream = this.fetch(Ref.get(tableOffset, 0));
if (!(stream instanceof BaseStream)) {
throw new FormatError("bad ObjStm stream");
}
const first = stream.dict.get("First");
const n = stream.dict.get("N");
if (!Number.isInteger(first) || !Number.isInteger(n)) {
throw new FormatError("invalid first and n parameters for ObjStm stream");
}
let parser = new Parser({
lexer: new Lexer(stream),
xref: this,
allowStreams: true
});
const nums = new Array(n);
const offsets = new Array(n);
for (let i = 0; i < n; ++i) {
const num = parser.getObj();
if (!Number.isInteger(num)) {
throw new FormatError(`invalid object number in the ObjStm stream: ${num}`);
}
const offset = parser.getObj();
if (!Number.isInteger(offset)) {
throw new FormatError(`invalid object offset in the ObjStm stream: ${offset}`);
}
nums[i] = num;
offsets[i] = offset;
}
const start = (stream.start || 0) + first;
const entries = new Array(n);
for (let i = 0; i < n; ++i) {
const length = i < n - 1 ? offsets[i + 1] - offsets[i] : undefined;
if (length < 0) {
throw new FormatError("Invalid offset in the ObjStm stream.");
}
parser = new Parser({
lexer: new Lexer(stream.makeSubStream(start + offsets[i], length, stream.dict)),
xref: this,
allowStreams: true
});
const obj = parser.getObj();
entries[i] = obj;
if (obj instanceof BaseStream) {
continue;
}
const num = nums[i],
entry = this.entries[num];
if (entry && entry.offset === tableOffset && entry.gen === i) {
this._cacheMap.set(num, obj);
}
}
xrefEntry = entries[xrefEntry.gen];
if (xrefEntry === undefined) {
throw new XRefEntryException(`Bad (compressed) XRef entry: ${ref}`);
}
return xrefEntry;
}
async fetchIfRefAsync(obj, suppressEncryption) {
if (obj instanceof Ref) {
return this.fetchAsync(obj, suppressEncryption);
}
return obj;
}
async fetchAsync(ref, suppressEncryption) {
try {
return this.fetch(ref, suppressEncryption);
} catch (ex) {
if (!(ex instanceof MissingDataException)) {
throw ex;
}
await this.pdfManager.requestRange(ex.begin, ex.end);
return this.fetchAsync(ref, suppressEncryption);
}
}
getCatalogObj() {
return this.root;
}
}
;// ./src/core/document.js
const LETTER_SIZE_MEDIABOX = [0, 0, 612, 792];
class Page {
constructor({
pdfManager,
xref,
pageIndex,
pageDict,
ref,
globalIdFactory,
fontCache,
builtInCMapCache,
standardFontDataCache,
globalColorSpaceCache,
globalImageCache,
systemFontCache,
nonBlendModesSet,
xfaFactory
}) {
this.pdfManager = pdfManager;
this.pageIndex = pageIndex;
this.pageDict = pageDict;
this.xref = xref;
this.ref = ref;
this.fontCache = fontCache;
this.builtInCMapCache = builtInCMapCache;
this.standardFontDataCache = standardFontDataCache;
this.globalColorSpaceCache = globalColorSpaceCache;
this.globalImageCache = globalImageCache;
this.systemFontCache = systemFontCache;
this.nonBlendModesSet = nonBlendModesSet;
this.evaluatorOptions = pdfManager.evaluatorOptions;
this.resourcesPromise = null;
this.xfaFactory = xfaFactory;
const idCounters = {
obj: 0
};
this._localIdFactory = class extends globalIdFactory {
static createObjId() {
return `p${pageIndex}_${++idCounters.obj}`;
}
static getPageObjId() {
return `p${ref.toString()}`;
}
};
}
_getInheritableProperty(key, getArray = false) {
const value = getInheritableProperty({
dict: this.pageDict,
key,
getArray,
stopWhenFound: false
});
if (!Array.isArray(value)) {
return value;
}
if (value.length === 1 || !(value[0] instanceof Dict)) {
return value[0];
}
return Dict.merge({
xref: this.xref,
dictArray: value
});
}
get content() {
return this.pageDict.getArray("Contents");
}
get resources() {
const resources = this._getInheritableProperty("Resources");
return shadow(this, "resources", resources instanceof Dict ? resources : Dict.empty);
}
_getBoundingBox(name) {
if (this.xfaData) {
return this.xfaData.bbox;
}
const box = lookupNormalRect(this._getInheritableProperty(name, true), null);
if (box) {
if (box[2] - box[0] > 0 && box[3] - box[1] > 0) {
return box;
}
warn(`Empty, or invalid, /${name} entry.`);
}
return null;
}
get mediaBox() {
return shadow(this, "mediaBox", this._getBoundingBox("MediaBox") || LETTER_SIZE_MEDIABOX);
}
get cropBox() {
return shadow(this, "cropBox", this._getBoundingBox("CropBox") || this.mediaBox);
}
get userUnit() {
const obj = this.pageDict.get("UserUnit");
return shadow(this, "userUnit", typeof obj === "number" && obj > 0 ? obj : 1.0);
}
get view() {
const {
cropBox,
mediaBox
} = this;
if (cropBox !== mediaBox && !isArrayEqual(cropBox, mediaBox)) {
const box = Util.intersect(cropBox, mediaBox);
if (box && box[2] - box[0] > 0 && box[3] - box[1] > 0) {
return shadow(this, "view", box);
}
warn("Empty /CropBox and /MediaBox intersection.");
}
return shadow(this, "view", mediaBox);
}
get rotate() {
let rotate = this._getInheritableProperty("Rotate") || 0;
if (rotate % 90 !== 0) {
rotate = 0;
} else if (rotate >= 360) {
rotate %= 360;
} else if (rotate < 0) {
rotate = (rotate % 360 + 360) % 360;
}
return shadow(this, "rotate", rotate);
}
_onSubStreamError(reason, objId) {
if (this.evaluatorOptions.ignoreErrors) {
warn(`getContentStream - ignoring sub-stream (${objId}): "${reason}".`);
return;
}
throw reason;
}
async getContentStream() {
const content = await this.pdfManager.ensure(this, "content");
if (content instanceof BaseStream) {
return content;
}
if (Array.isArray(content)) {
return new StreamsSequenceStream(content, this._onSubStreamError.bind(this));
}
return new NullStream();
}
get xfaData() {
return shadow(this, "xfaData", this.xfaFactory ? {
bbox: this.xfaFactory.getBoundingBox(this.pageIndex)
} : null);
}
async #replaceIdByRef(annotations, deletedAnnotations, existingAnnotations) {
const promises = [];
for (const annotation of annotations) {
if (annotation.id) {
const ref = Ref.fromString(annotation.id);
if (!ref) {
warn(`A non-linked annotation cannot be modified: ${annotation.id}`);
continue;
}
if (annotation.deleted) {
deletedAnnotations.put(ref, ref);
if (annotation.popupRef) {
const popupRef = Ref.fromString(annotation.popupRef);
if (popupRef) {
deletedAnnotations.put(popupRef, popupRef);
}
}
continue;
}
existingAnnotations?.put(ref);
annotation.ref = ref;
promises.push(this.xref.fetchAsync(ref).then(obj => {
if (obj instanceof Dict) {
annotation.oldAnnotation = obj.clone();
}
}, () => {
warn(`Cannot fetch \`oldAnnotation\` for: ${ref}.`);
}));
delete annotation.id;
}
}
await Promise.all(promises);
}
async saveNewAnnotations(handler, task, annotations, imagePromises, changes) {
if (this.xfaFactory) {
throw new Error("XFA: Cannot save new annotations.");
}
const partialEvaluator = new PartialEvaluator({
xref: this.xref,
handler,
pageIndex: this.pageIndex,
idFactory: this._localIdFactory,
fontCache: this.fontCache,
builtInCMapCache: this.builtInCMapCache,
standardFontDataCache: this.standardFontDataCache,
globalColorSpaceCache: this.globalColorSpaceCache,
globalImageCache: this.globalImageCache,
systemFontCache: this.systemFontCache,
options: this.evaluatorOptions
});
const deletedAnnotations = new RefSetCache();
const existingAnnotations = new RefSet();
await this.#replaceIdByRef(annotations, deletedAnnotations, existingAnnotations);
const pageDict = this.pageDict;
const annotationsArray = this.annotations.filter(a => !(a instanceof Ref && deletedAnnotations.has(a)));
const newData = await AnnotationFactory.saveNewAnnotations(partialEvaluator, task, annotations, imagePromises, changes);
for (const {
ref
} of newData.annotations) {
if (ref instanceof Ref && !existingAnnotations.has(ref)) {
annotationsArray.push(ref);
}
}
const dict = pageDict.clone();
dict.set("Annots", annotationsArray);
changes.put(this.ref, {
data: dict
});
for (const deletedRef of deletedAnnotations) {
changes.put(deletedRef, {
data: null
});
}
}
async save(handler, task, annotationStorage, changes) {
const partialEvaluator = new PartialEvaluator({
xref: this.xref,
handler,
pageIndex: this.pageIndex,
idFactory: this._localIdFactory,
fontCache: this.fontCache,
builtInCMapCache: this.builtInCMapCache,
standardFontDataCache: this.standardFontDataCache,
globalColorSpaceCache: this.globalColorSpaceCache,
globalImageCache: this.globalImageCache,
systemFontCache: this.systemFontCache,
options: this.evaluatorOptions
});
const annotations = await this._parsedAnnotations;
const promises = [];
for (const annotation of annotations) {
promises.push(annotation.save(partialEvaluator, task, annotationStorage, changes).catch(function (reason) {
warn("save - ignoring annotation data during " + `"${task.name}" task: "${reason}".`);
return null;
}));
}
return Promise.all(promises);
}
async loadResources(keys) {
await (this.resourcesPromise ??= this.pdfManager.ensure(this, "resources"));
const objectLoader = new ObjectLoader(this.resources, keys, this.xref);
await objectLoader.load();
}
async getOperatorList({
handler,
sink,
task,
intent,
cacheKey,
annotationStorage = null,
modifiedIds = null
}) {
const contentStreamPromise = this.getContentStream();
const resourcesPromise = this.loadResources(["ColorSpace", "ExtGState", "Font", "Pattern", "Properties", "Shading", "XObject"]);
const partialEvaluator = new PartialEvaluator({
xref: this.xref,
handler,
pageIndex: this.pageIndex,
idFactory: this._localIdFactory,
fontCache: this.fontCache,
builtInCMapCache: this.builtInCMapCache,
standardFontDataCache: this.standardFontDataCache,
globalColorSpaceCache: this.globalColorSpaceCache,
globalImageCache: this.globalImageCache,
systemFontCache: this.systemFontCache,
options: this.evaluatorOptions
});
const newAnnotsByPage = !this.xfaFactory ? getNewAnnotationsMap(annotationStorage) : null;
const newAnnots = newAnnotsByPage?.get(this.pageIndex);
let newAnnotationsPromise = Promise.resolve(null);
let deletedAnnotations = null;
if (newAnnots) {
const annotationGlobalsPromise = this.pdfManager.ensureDoc("annotationGlobals");
let imagePromises;
const missingBitmaps = new Set();
for (const {
bitmapId,
bitmap
} of newAnnots) {
if (bitmapId && !bitmap && !missingBitmaps.has(bitmapId)) {
missingBitmaps.add(bitmapId);
}
}
const {
isOffscreenCanvasSupported
} = this.evaluatorOptions;
if (missingBitmaps.size > 0) {
const annotationWithBitmaps = newAnnots.slice();
for (const [key, annotation] of annotationStorage) {
if (!key.startsWith(AnnotationEditorPrefix)) {
continue;
}
if (annotation.bitmap && missingBitmaps.has(annotation.bitmapId)) {
annotationWithBitmaps.push(annotation);
}
}
imagePromises = AnnotationFactory.generateImages(annotationWithBitmaps, this.xref, isOffscreenCanvasSupported);
} else {
imagePromises = AnnotationFactory.generateImages(newAnnots, this.xref, isOffscreenCanvasSupported);
}
deletedAnnotations = new RefSet();
newAnnotationsPromise = Promise.all([annotationGlobalsPromise, this.#replaceIdByRef(newAnnots, deletedAnnotations, null)]).then(([annotationGlobals]) => {
if (!annotationGlobals) {
return null;
}
return AnnotationFactory.printNewAnnotations(annotationGlobals, partialEvaluator, task, newAnnots, imagePromises);
});
}
const pageListPromise = Promise.all([contentStreamPromise, resourcesPromise]).then(async ([contentStream]) => {
const opList = new OperatorList(intent, sink);
handler.send("StartRenderPage", {
transparency: partialEvaluator.hasBlendModes(this.resources, this.nonBlendModesSet),
pageIndex: this.pageIndex,
cacheKey
});
await partialEvaluator.getOperatorList({
stream: contentStream,
task,
resources: this.resources,
operatorList: opList
});
return opList;
});
let [pageOpList, annotations, newAnnotations] = await Promise.all([pageListPromise, this._parsedAnnotations, newAnnotationsPromise]);
if (newAnnotations) {
annotations = annotations.filter(a => !(a.ref && deletedAnnotations.has(a.ref)));
for (let i = 0, ii = newAnnotations.length; i < ii; i++) {
const newAnnotation = newAnnotations[i];
if (newAnnotation.refToReplace) {
const j = annotations.findIndex(a => a.ref && isRefsEqual(a.ref, newAnnotation.refToReplace));
if (j >= 0) {
annotations.splice(j, 1, newAnnotation);
newAnnotations.splice(i--, 1);
ii--;
}
}
}
annotations = annotations.concat(newAnnotations);
}
if (annotations.length === 0 || intent & RenderingIntentFlag.ANNOTATIONS_DISABLE) {
pageOpList.flush(true);
return {
length: pageOpList.totalLength
};
}
const renderForms = !!(intent & RenderingIntentFlag.ANNOTATIONS_FORMS),
isEditing = !!(intent & RenderingIntentFlag.IS_EDITING),
intentAny = !!(intent & RenderingIntentFlag.ANY),
intentDisplay = !!(intent & RenderingIntentFlag.DISPLAY),
intentPrint = !!(intent & RenderingIntentFlag.PRINT);
const opListPromises = [];
for (const annotation of annotations) {
if (intentAny || intentDisplay && annotation.mustBeViewed(annotationStorage, renderForms) && annotation.mustBeViewedWhenEditing(isEditing, modifiedIds) || intentPrint && annotation.mustBePrinted(annotationStorage)) {
opListPromises.push(annotation.getOperatorList(partialEvaluator, task, intent, annotationStorage).catch(function (reason) {
warn("getOperatorList - ignoring annotation data during " + `"${task.name}" task: "${reason}".`);
return {
opList: null,
separateForm: false,
separateCanvas: false
};
}));
}
}
const opLists = await Promise.all(opListPromises);
let form = false,
canvas = false;
for (const {
opList,
separateForm,
separateCanvas
} of opLists) {
pageOpList.addOpList(opList);
form ||= separateForm;
canvas ||= separateCanvas;
}
pageOpList.flush(true, {
form,
canvas
});
return {
length: pageOpList.totalLength
};
}
async extractTextContent({
handler,
task,
includeMarkedContent,
disableNormalization,
sink
}) {
const contentStreamPromise = this.getContentStream();
const resourcesPromise = this.loadResources(["ExtGState", "Font", "Properties", "XObject"]);
const langPromise = this.pdfManager.ensureCatalog("lang");
const [contentStream,, lang] = await Promise.all([contentStreamPromise, resourcesPromise, langPromise]);
const partialEvaluator = new PartialEvaluator({
xref: this.xref,
handler,
pageIndex: this.pageIndex,
idFactory: this._localIdFactory,
fontCache: this.fontCache,
builtInCMapCache: this.builtInCMapCache,
standardFontDataCache: this.standardFontDataCache,
globalColorSpaceCache: this.globalColorSpaceCache,
globalImageCache: this.globalImageCache,
systemFontCache: this.systemFontCache,
options: this.evaluatorOptions
});
return partialEvaluator.getTextContent({
stream: contentStream,
task,
resources: this.resources,
includeMarkedContent,
disableNormalization,
sink,
viewBox: this.view,
lang
});
}
async getStructTree() {
const structTreeRoot = await this.pdfManager.ensureCatalog("structTreeRoot");
if (!structTreeRoot) {
return null;
}
await this._parsedAnnotations;
const structTree = await this.pdfManager.ensure(this, "_parseStructTree", [structTreeRoot]);
return this.pdfManager.ensure(structTree, "serializable");
}
_parseStructTree(structTreeRoot) {
const tree = new StructTreePage(structTreeRoot, this.pageDict);
tree.parse(this.ref);
return tree;
}
async getAnnotationsData(handler, task, intent) {
const annotations = await this._parsedAnnotations;
if (annotations.length === 0) {
return annotations;
}
const annotationsData = [],
textContentPromises = [];
let partialEvaluator;
const intentAny = !!(intent & RenderingIntentFlag.ANY),
intentDisplay = !!(intent & RenderingIntentFlag.DISPLAY),
intentPrint = !!(intent & RenderingIntentFlag.PRINT);
for (const annotation of annotations) {
const isVisible = intentAny || intentDisplay && annotation.viewable;
if (isVisible || intentPrint && annotation.printable) {
annotationsData.push(annotation.data);
}
if (annotation.hasTextContent && isVisible) {
partialEvaluator ||= new PartialEvaluator({
xref: this.xref,
handler,
pageIndex: this.pageIndex,
idFactory: this._localIdFactory,
fontCache: this.fontCache,
builtInCMapCache: this.builtInCMapCache,
standardFontDataCache: this.standardFontDataCache,
globalColorSpaceCache: this.globalColorSpaceCache,
globalImageCache: this.globalImageCache,
systemFontCache: this.systemFontCache,
options: this.evaluatorOptions
});
textContentPromises.push(annotation.extractTextContent(partialEvaluator, task, [-Infinity, -Infinity, Infinity, Infinity]).catch(function (reason) {
warn(`getAnnotationsData - ignoring textContent during "${task.name}" task: "${reason}".`);
}));
}
}
await Promise.all(textContentPromises);
return annotationsData;
}
get annotations() {
const annots = this._getInheritableProperty("Annots");
return shadow(this, "annotations", Array.isArray(annots) ? annots : []);
}
get _parsedAnnotations() {
const promise = this.pdfManager.ensure(this, "annotations").then(async annots => {
if (annots.length === 0) {
return annots;
}
const [annotationGlobals, fieldObjects] = await Promise.all([this.pdfManager.ensureDoc("annotationGlobals"), this.pdfManager.ensureDoc("fieldObjects")]);
if (!annotationGlobals) {
return [];
}
const orphanFields = fieldObjects?.orphanFields;
const annotationPromises = [];
for (const annotationRef of annots) {
annotationPromises.push(AnnotationFactory.create(this.xref, annotationRef, annotationGlobals, this._localIdFactory, false, orphanFields, this.ref).catch(function (reason) {
warn(`_parsedAnnotations: "${reason}".`);
return null;
}));
}
const sortedAnnotations = [];
let popupAnnotations, widgetAnnotations;
for (const annotation of await Promise.all(annotationPromises)) {
if (!annotation) {
continue;
}
if (annotation instanceof WidgetAnnotation) {
(widgetAnnotations ||= []).push(annotation);
continue;
}
if (annotation instanceof PopupAnnotation) {
(popupAnnotations ||= []).push(annotation);
continue;
}
sortedAnnotations.push(annotation);
}
if (widgetAnnotations) {
sortedAnnotations.push(...widgetAnnotations);
}
if (popupAnnotations) {
sortedAnnotations.push(...popupAnnotations);
}
return sortedAnnotations;
});
return shadow(this, "_parsedAnnotations", promise);
}
get jsActions() {
const actions = collectActions(this.xref, this.pageDict, PageActionEventType);
return shadow(this, "jsActions", actions);
}
}
const PDF_HEADER_SIGNATURE = new Uint8Array([0x25, 0x50, 0x44, 0x46, 0x2d]);
const STARTXREF_SIGNATURE = new Uint8Array([0x73, 0x74, 0x61, 0x72, 0x74, 0x78, 0x72, 0x65, 0x66]);
const ENDOBJ_SIGNATURE = new Uint8Array([0x65, 0x6e, 0x64, 0x6f, 0x62, 0x6a]);
function find(stream, signature, limit = 1024, backwards = false) {
const signatureLength = signature.length;
const scanBytes = stream.peekBytes(limit);
const scanLength = scanBytes.length - signatureLength;
if (scanLength <= 0) {
return false;
}
if (backwards) {
const signatureEnd = signatureLength - 1;
let pos = scanBytes.length - 1;
while (pos >= signatureEnd) {
let j = 0;
while (j < signatureLength && scanBytes[pos - j] === signature[signatureEnd - j]) {
j++;
}
if (j >= signatureLength) {
stream.pos += pos - signatureEnd;
return true;
}
pos--;
}
} else {
let pos = 0;
while (pos <= scanLength) {
let j = 0;
while (j < signatureLength && scanBytes[pos + j] === signature[j]) {
j++;
}
if (j >= signatureLength) {
stream.pos += pos;
return true;
}
pos++;
}
}
return false;
}
class PDFDocument {
constructor(pdfManager, stream) {
if (stream.length <= 0) {
throw new InvalidPDFException("The PDF file is empty, i.e. its size is zero bytes.");
}
this.pdfManager = pdfManager;
this.stream = stream;
this.xref = new XRef(stream, pdfManager);
this._pagePromises = new Map();
this._version = null;
const idCounters = {
font: 0
};
this._globalIdFactory = class {
static getDocId() {
return `g_${pdfManager.docId}`;
}
static createFontId() {
return `f${++idCounters.font}`;
}
static createObjId() {
unreachable("Abstract method `createObjId` called.");
}
static getPageObjId() {
unreachable("Abstract method `getPageObjId` called.");
}
};
}
parse(recoveryMode) {
this.xref.parse(recoveryMode);
this.catalog = new Catalog(this.pdfManager, this.xref);
}
get linearization() {
let linearization = null;
try {
linearization = Linearization.create(this.stream);
} catch (err) {
if (err instanceof MissingDataException) {
throw err;
}
info(err);
}
return shadow(this, "linearization", linearization);
}
get startXRef() {
const stream = this.stream;
let startXRef = 0;
if (this.linearization) {
stream.reset();
if (find(stream, ENDOBJ_SIGNATURE)) {
stream.skip(6);
let ch = stream.peekByte();
while (isWhiteSpace(ch)) {
stream.pos++;
ch = stream.peekByte();
}
startXRef = stream.pos - stream.start;
}
} else {
const step = 1024;
const startXRefLength = STARTXREF_SIGNATURE.length;
let found = false,
pos = stream.end;
while (!found && pos > 0) {
pos -= step - startXRefLength;
if (pos < 0) {
pos = 0;
}
stream.pos = pos;
found = find(stream, STARTXREF_SIGNATURE, step, true);
}
if (found) {
stream.skip(9);
let ch;
do {
ch = stream.getByte();
} while (isWhiteSpace(ch));
let str = "";
while (ch >= 0x20 && ch <= 0x39) {
str += String.fromCharCode(ch);
ch = stream.getByte();
}
startXRef = parseInt(str, 10);
if (isNaN(startXRef)) {
startXRef = 0;
}
}
}
return shadow(this, "startXRef", startXRef);
}
checkHeader() {
const stream = this.stream;
stream.reset();
if (!find(stream, PDF_HEADER_SIGNATURE)) {
return;
}
stream.moveStart();
stream.skip(PDF_HEADER_SIGNATURE.length);
let version = "",
ch;
while ((ch = stream.getByte()) > 0x20 && version.length < 7) {
version += String.fromCharCode(ch);
}
if (PDF_VERSION_REGEXP.test(version)) {
this._version = version;
} else {
warn(`Invalid PDF header version: ${version}`);
}
}
parseStartXRef() {
this.xref.setStartXRef(this.startXRef);
}
get numPages() {
let num = 0;
if (this.catalog.hasActualNumPages) {
num = this.catalog.numPages;
} else if (this.xfaFactory) {
num = this.xfaFactory.getNumPages();
} else if (this.linearization) {
num = this.linearization.numPages;
} else {
num = this.catalog.numPages;
}
return shadow(this, "numPages", num);
}
_hasOnlyDocumentSignatures(fields, recursionDepth = 0) {
const RECURSION_LIMIT = 10;
if (!Array.isArray(fields)) {
return false;
}
return fields.every(field => {
field = this.xref.fetchIfRef(field);
if (!(field instanceof Dict)) {
return false;
}
if (field.has("Kids")) {
if (++recursionDepth > RECURSION_LIMIT) {
warn("_hasOnlyDocumentSignatures: maximum recursion depth reached");
return false;
}
return this._hasOnlyDocumentSignatures(field.get("Kids"), recursionDepth);
}
const isSignature = isName(field.get("FT"), "Sig");
const rectangle = field.get("Rect");
const isInvisible = Array.isArray(rectangle) && rectangle.every(value => value === 0);
return isSignature && isInvisible;
});
}
get _xfaStreams() {
const acroForm = this.catalog.acroForm;
if (!acroForm) {
return null;
}
const xfa = acroForm.get("XFA");
const entries = {
"xdp:xdp": "",
template: "",
datasets: "",
config: "",
connectionSet: "",
localeSet: "",
stylesheet: "",
"/xdp:xdp": ""
};
if (xfa instanceof BaseStream && !xfa.isEmpty) {
entries["xdp:xdp"] = xfa;
return entries;
}
if (!Array.isArray(xfa) || xfa.length === 0) {
return null;
}
for (let i = 0, ii = xfa.length; i < ii; i += 2) {
let name;
if (i === 0) {
name = "xdp:xdp";
} else if (i === ii - 2) {
name = "/xdp:xdp";
} else {
name = xfa[i];
}
if (!entries.hasOwnProperty(name)) {
continue;
}
const data = this.xref.fetchIfRef(xfa[i + 1]);
if (!(data instanceof BaseStream) || data.isEmpty) {
continue;
}
entries[name] = data;
}
return entries;
}
get xfaDatasets() {
const streams = this._xfaStreams;
if (!streams) {
return shadow(this, "xfaDatasets", null);
}
for (const key of ["datasets", "xdp:xdp"]) {
const stream = streams[key];
if (!stream) {
continue;
}
try {
const str = stringToUTF8String(stream.getString());
const data = {
[key]: str
};
return shadow(this, "xfaDatasets", new DatasetReader(data));
} catch {
warn("XFA - Invalid utf-8 string.");
break;
}
}
return shadow(this, "xfaDatasets", null);
}
get xfaData() {
const streams = this._xfaStreams;
if (!streams) {
return null;
}
const data = Object.create(null);
for (const [key, stream] of Object.entries(streams)) {
if (!stream) {
continue;
}
try {
data[key] = stringToUTF8String(stream.getString());
} catch {
warn("XFA - Invalid utf-8 string.");
return null;
}
}
return data;
}
get xfaFactory() {
let data;
if (this.pdfManager.enableXfa && this.catalog.needsRendering && this.formInfo.hasXfa && !this.formInfo.hasAcroForm) {
data = this.xfaData;
}
return shadow(this, "xfaFactory", data ? new XFAFactory(data) : null);
}
get isPureXfa() {
return this.xfaFactory ? this.xfaFactory.isValid() : false;
}
get htmlForXfa() {
return this.xfaFactory ? this.xfaFactory.getPages() : null;
}
async loadXfaImages() {
const xfaImagesDict = await this.pdfManager.ensureCatalog("xfaImages");
if (!xfaImagesDict) {
return;
}
const keys = xfaImagesDict.getKeys();
const objectLoader = new ObjectLoader(xfaImagesDict, keys, this.xref);
await objectLoader.load();
const xfaImages = new Map();
for (const key of keys) {
const stream = xfaImagesDict.get(key);
if (stream instanceof BaseStream) {
xfaImages.set(key, stream.getBytes());
}
}
this.xfaFactory.setImages(xfaImages);
}
async loadXfaFonts(handler, task) {
const acroForm = await this.pdfManager.ensureCatalog("acroForm");
if (!acroForm) {
return;
}
const resources = await acroForm.getAsync("DR");
if (!(resources instanceof Dict)) {
return;
}
const objectLoader = new ObjectLoader(resources, ["Font"], this.xref);
await objectLoader.load();
const fontRes = resources.get("Font");
if (!(fontRes instanceof Dict)) {
return;
}
const options = Object.assign(Object.create(null), this.pdfManager.evaluatorOptions);
options.useSystemFonts = false;
const partialEvaluator = new PartialEvaluator({
xref: this.xref,
handler,
pageIndex: -1,
idFactory: this._globalIdFactory,
fontCache: this.catalog.fontCache,
builtInCMapCache: this.catalog.builtInCMapCache,
standardFontDataCache: this.catalog.standardFontDataCache,
options
});
const operatorList = new OperatorList();
const pdfFonts = [];
const initialState = {
get font() {
return pdfFonts.at(-1);
},
set font(font) {
pdfFonts.push(font);
},
clone() {
return this;
}
};
const promises = [];
for (const [fontName, font] of fontRes) {
const descriptor = font.get("FontDescriptor");
if (!(descriptor instanceof Dict)) {
continue;
}
let fontFamily = descriptor.get("FontFamily");
fontFamily = fontFamily.replaceAll(/[ ]+(\d)/g, "$1");
const fontWeight = descriptor.get("FontWeight");
const italicAngle = -descriptor.get("ItalicAngle");
const cssFontInfo = {
fontFamily,
fontWeight,
italicAngle
};
if (!validateCSSFont(cssFontInfo)) {
continue;
}
promises.push(partialEvaluator.handleSetFont(resources, [Name.get(fontName), 1], null, operatorList, task, initialState, null, cssFontInfo).catch(function (reason) {
warn(`loadXfaFonts: "${reason}".`);
return null;
}));
}
await Promise.all(promises);
const missingFonts = this.xfaFactory.setFonts(pdfFonts);
if (!missingFonts) {
return;
}
options.ignoreErrors = true;
promises.length = 0;
pdfFonts.length = 0;
const reallyMissingFonts = new Set();
for (const missing of missingFonts) {
if (!getXfaFontName(`${missing}-Regular`)) {
reallyMissingFonts.add(missing);
}
}
if (reallyMissingFonts.size) {
missingFonts.push("PdfJS-Fallback");
}
for (const missing of missingFonts) {
if (reallyMissingFonts.has(missing)) {
continue;
}
for (const fontInfo of [{
name: "Regular",
fontWeight: 400,
italicAngle: 0
}, {
name: "Bold",
fontWeight: 700,
italicAngle: 0
}, {
name: "Italic",
fontWeight: 400,
italicAngle: 12
}, {
name: "BoldItalic",
fontWeight: 700,
italicAngle: 12
}]) {
const name = `${missing}-${fontInfo.name}`;
const dict = getXfaFontDict(name);
promises.push(partialEvaluator.handleSetFont(resources, [Name.get(name), 1], null, operatorList, task, initialState, dict, {
fontFamily: missing,
fontWeight: fontInfo.fontWeight,
italicAngle: fontInfo.italicAngle
}).catch(function (reason) {
warn(`loadXfaFonts: "${reason}".`);
return null;
}));
}
}
await Promise.all(promises);
this.xfaFactory.appendFonts(pdfFonts, reallyMissingFonts);
}
async serializeXfaData(annotationStorage) {
return this.xfaFactory ? this.xfaFactory.serializeData(annotationStorage) : null;
}
get version() {
return this.catalog.version || this._version;
}
get formInfo() {
const formInfo = {
hasFields: false,
hasAcroForm: false,
hasXfa: false,
hasSignatures: false
};
const acroForm = this.catalog.acroForm;
if (!acroForm) {
return shadow(this, "formInfo", formInfo);
}
try {
const fields = acroForm.get("Fields");
const hasFields = Array.isArray(fields) && fields.length > 0;
formInfo.hasFields = hasFields;
const xfa = acroForm.get("XFA");
formInfo.hasXfa = Array.isArray(xfa) && xfa.length > 0 || xfa instanceof BaseStream && !xfa.isEmpty;
const sigFlags = acroForm.get("SigFlags");
const hasSignatures = !!(sigFlags & 0x1);
const hasOnlyDocumentSignatures = hasSignatures && this._hasOnlyDocumentSignatures(fields);
formInfo.hasAcroForm = hasFields && !hasOnlyDocumentSignatures;
formInfo.hasSignatures = hasSignatures;
} catch (ex) {
if (ex instanceof MissingDataException) {
throw ex;
}
warn(`Cannot fetch form information: "${ex}".`);
}
return shadow(this, "formInfo", formInfo);
}
get documentInfo() {
const docInfo = {
PDFFormatVersion: this.version,
Language: this.catalog.lang,
EncryptFilterName: this.xref.encrypt ? this.xref.encrypt.filterName : null,
IsLinearized: !!this.linearization,
IsAcroFormPresent: this.formInfo.hasAcroForm,
IsXFAPresent: this.formInfo.hasXfa,
IsCollectionPresent: !!this.catalog.collection,
IsSignaturesPresent: this.formInfo.hasSignatures
};
let infoDict;
try {
infoDict = this.xref.trailer.get("Info");
} catch (err) {
if (err instanceof MissingDataException) {
throw err;
}
info("The document information dictionary is invalid.");
}
if (!(infoDict instanceof Dict)) {
return shadow(this, "documentInfo", docInfo);
}
for (const [key, value] of infoDict) {
switch (key) {
case "Title":
case "Author":
case "Subject":
case "Keywords":
case "Creator":
case "Producer":
case "CreationDate":
case "ModDate":
if (typeof value === "string") {
docInfo[key] = stringToPDFString(value);
continue;
}
break;
case "Trapped":
if (value instanceof Name) {
docInfo[key] = value;
continue;
}
break;
default:
let customValue;
switch (typeof value) {
case "string":
customValue = stringToPDFString(value);
break;
case "number":
case "boolean":
customValue = value;
break;
default:
if (value instanceof Name) {
customValue = value;
}
break;
}
if (customValue === undefined) {
warn(`Bad value, for custom key "${key}", in Info: ${value}.`);
continue;
}
if (!docInfo.Custom) {
docInfo.Custom = Object.create(null);
}
docInfo.Custom[key] = customValue;
continue;
}
warn(`Bad value, for key "${key}", in Info: ${value}.`);
}
return shadow(this, "documentInfo", docInfo);
}
get fingerprints() {
const FINGERPRINT_FIRST_BYTES = 1024;
const EMPTY_FINGERPRINT = "\x00".repeat(16);
function validate(data) {
return typeof data === "string" && data.length === 16 && data !== EMPTY_FINGERPRINT;
}
const id = this.xref.trailer.get("ID");
let hashOriginal, hashModified;
if (Array.isArray(id) && validate(id[0])) {
hashOriginal = stringToBytes(id[0]);
if (id[1] !== id[0] && validate(id[1])) {
hashModified = stringToBytes(id[1]);
}
} else {
hashOriginal = calculateMD5(this.stream.getByteRange(0, FINGERPRINT_FIRST_BYTES), 0, FINGERPRINT_FIRST_BYTES);
}
return shadow(this, "fingerprints", [toHexUtil(hashOriginal), hashModified ? toHexUtil(hashModified) : null]);
}
async _getLinearizationPage(pageIndex) {
const {
catalog,
linearization,
xref
} = this;
const ref = Ref.get(linearization.objectNumberFirst, 0);
try {
const obj = await xref.fetchAsync(ref);
if (obj instanceof Dict) {
let type = obj.getRaw("Type");
if (type instanceof Ref) {
type = await xref.fetchAsync(type);
}
if (isName(type, "Page") || !obj.has("Type") && !obj.has("Kids") && obj.has("Contents")) {
if (!catalog.pageKidsCountCache.has(ref)) {
catalog.pageKidsCountCache.put(ref, 1);
}
if (!catalog.pageIndexCache.has(ref)) {
catalog.pageIndexCache.put(ref, 0);
}
return [obj, ref];
}
}
throw new FormatError("The Linearization dictionary doesn't point to a valid Page dictionary.");
} catch (reason) {
warn(`_getLinearizationPage: "${reason.message}".`);
return catalog.getPageDict(pageIndex);
}
}
getPage(pageIndex) {
const cachedPromise = this._pagePromises.get(pageIndex);
if (cachedPromise) {
return cachedPromise;
}
const {
catalog,
linearization,
xfaFactory
} = this;
let promise;
if (xfaFactory) {
promise = Promise.resolve([Dict.empty, null]);
} else if (linearization?.pageFirst === pageIndex) {
promise = this._getLinearizationPage(pageIndex);
} else {
promise = catalog.getPageDict(pageIndex);
}
promise = promise.then(([pageDict, ref]) => new Page({
pdfManager: this.pdfManager,
xref: this.xref,
pageIndex,
pageDict,
ref,
globalIdFactory: this._globalIdFactory,
fontCache: catalog.fontCache,
builtInCMapCache: catalog.builtInCMapCache,
standardFontDataCache: catalog.standardFontDataCache,
globalColorSpaceCache: catalog.globalColorSpaceCache,
globalImageCache: catalog.globalImageCache,
systemFontCache: catalog.systemFontCache,
nonBlendModesSet: catalog.nonBlendModesSet,
xfaFactory
}));
this._pagePromises.set(pageIndex, promise);
return promise;
}
async checkFirstPage(recoveryMode = false) {
if (recoveryMode) {
return;
}
try {
await this.getPage(0);
} catch (reason) {
if (reason instanceof XRefEntryException) {
this._pagePromises.delete(0);
await this.cleanup();
throw new XRefParseException();
}
}
}
async checkLastPage(recoveryMode = false) {
const {
catalog,
pdfManager
} = this;
catalog.setActualNumPages();
let numPages;
try {
await Promise.all([pdfManager.ensureDoc("xfaFactory"), pdfManager.ensureDoc("linearization"), pdfManager.ensureCatalog("numPages")]);
if (this.xfaFactory) {
return;
} else if (this.linearization) {
numPages = this.linearization.numPages;
} else {
numPages = catalog.numPages;
}
if (!Number.isInteger(numPages)) {
throw new FormatError("Page count is not an integer.");
} else if (numPages <= 1) {
return;
}
await this.getPage(numPages - 1);
} catch (reason) {
this._pagePromises.delete(numPages - 1);
await this.cleanup();
if (reason instanceof XRefEntryException && !recoveryMode) {
throw new XRefParseException();
}
warn(`checkLastPage - invalid /Pages tree /Count: ${numPages}.`);
let pagesTree;
try {
pagesTree = await catalog.getAllPageDicts(recoveryMode);
} catch (reasonAll) {
if (reasonAll instanceof XRefEntryException && !recoveryMode) {
throw new XRefParseException();
}
catalog.setActualNumPages(1);
return;
}
for (const [pageIndex, [pageDict, ref]] of pagesTree) {
let promise;
if (pageDict instanceof Error) {
promise = Promise.reject(pageDict);
promise.catch(() => {});
} else {
promise = Promise.resolve(new Page({
pdfManager,
xref: this.xref,
pageIndex,
pageDict,
ref,
globalIdFactory: this._globalIdFactory,
fontCache: catalog.fontCache,
builtInCMapCache: catalog.builtInCMapCache,
standardFontDataCache: catalog.standardFontDataCache,
globalColorSpaceCache: this.globalColorSpaceCache,
globalImageCache: catalog.globalImageCache,
systemFontCache: catalog.systemFontCache,
nonBlendModesSet: catalog.nonBlendModesSet,
xfaFactory: null
}));
}
this._pagePromises.set(pageIndex, promise);
}
catalog.setActualNumPages(pagesTree.size);
}
}
async fontFallback(id, handler) {
const {
catalog,
pdfManager
} = this;
for (const translatedFont of await Promise.all(catalog.fontCache)) {
if (translatedFont.loadedName === id) {
translatedFont.fallback(handler, pdfManager.evaluatorOptions);
return;
}
}
}
async cleanup(manuallyTriggered = false) {
return this.catalog ? this.catalog.cleanup(manuallyTriggered) : clearGlobalCaches();
}
async #collectFieldObjects(name, parentRef, fieldRef, promises, annotationGlobals, visitedRefs, orphanFields) {
const {
xref
} = this;
if (!(fieldRef instanceof Ref) || visitedRefs.has(fieldRef)) {
return;
}
visitedRefs.put(fieldRef);
const field = await xref.fetchAsync(fieldRef);
if (!(field instanceof Dict)) {
return;
}
let subtype = await field.getAsync("Subtype");
subtype = subtype instanceof Name ? subtype.name : null;
switch (subtype) {
case "Link":
return;
}
if (field.has("T")) {
const partName = stringToPDFString(await field.getAsync("T"));
name = name === "" ? partName : `${name}.${partName}`;
} else {
let obj = field;
while (true) {
obj = obj.getRaw("Parent") || parentRef;
if (obj instanceof Ref) {
if (visitedRefs.has(obj)) {
break;
}
obj = await xref.fetchAsync(obj);
}
if (!(obj instanceof Dict)) {
break;
}
if (obj.has("T")) {
const partName = stringToPDFString(await obj.getAsync("T"));
name = name === "" ? partName : `${name}.${partName}`;
break;
}
}
}
if (parentRef && !field.has("Parent") && isName(field.get("Subtype"), "Widget")) {
orphanFields.put(fieldRef, parentRef);
}
if (!promises.has(name)) {
promises.set(name, []);
}
promises.get(name).push(AnnotationFactory.create(xref, fieldRef, annotationGlobals, null, true, orphanFields, null).then(annotation => annotation?.getFieldObject()).catch(function (reason) {
warn(`#collectFieldObjects: "${reason}".`);
return null;
}));
if (!field.has("Kids")) {
return;
}
const kids = await field.getAsync("Kids");
if (Array.isArray(kids)) {
for (const kid of kids) {
await this.#collectFieldObjects(name, fieldRef, kid, promises, annotationGlobals, visitedRefs, orphanFields);
}
}
}
get fieldObjects() {
const promise = this.pdfManager.ensureDoc("formInfo").then(async formInfo => {
if (!formInfo.hasFields) {
return null;
}
const [annotationGlobals, acroForm] = await Promise.all([this.pdfManager.ensureDoc("annotationGlobals"), this.pdfManager.ensureCatalog("acroForm")]);
if (!annotationGlobals) {
return null;
}
const visitedRefs = new RefSet();
const allFields = Object.create(null);
const fieldPromises = new Map();
const orphanFields = new RefSetCache();
for (const fieldRef of await acroForm.getAsync("Fields")) {
await this.#collectFieldObjects("", null, fieldRef, fieldPromises, annotationGlobals, visitedRefs, orphanFields);
}
const allPromises = [];
for (const [name, promises] of fieldPromises) {
allPromises.push(Promise.all(promises).then(fields => {
fields = fields.filter(field => !!field);
if (fields.length > 0) {
allFields[name] = fields;
}
}));
}
await Promise.all(allPromises);
return {
allFields: objectSize(allFields) > 0 ? allFields : null,
orphanFields
};
});
return shadow(this, "fieldObjects", promise);
}
get hasJSActions() {
const promise = this.pdfManager.ensureDoc("_parseHasJSActions");
return shadow(this, "hasJSActions", promise);
}
async _parseHasJSActions() {
const [catalogJsActions, fieldObjects] = await Promise.all([this.pdfManager.ensureCatalog("jsActions"), this.pdfManager.ensureDoc("fieldObjects")]);
if (catalogJsActions) {
return true;
}
if (fieldObjects?.allFields) {
return Object.values(fieldObjects.allFields).some(fieldObject => fieldObject.some(object => object.actions !== null));
}
return false;
}
get calculationOrderIds() {
const calculationOrder = this.catalog.acroForm?.get("CO");
if (!Array.isArray(calculationOrder) || calculationOrder.length === 0) {
return shadow(this, "calculationOrderIds", null);
}
const ids = [];
for (const id of calculationOrder) {
if (id instanceof Ref) {
ids.push(id.toString());
}
}
return shadow(this, "calculationOrderIds", ids.length ? ids : null);
}
get annotationGlobals() {
return shadow(this, "annotationGlobals", AnnotationFactory.createGlobals(this.pdfManager));
}
}
;// ./src/core/pdf_manager.js
function parseDocBaseUrl(url) {
if (url) {
const absoluteUrl = createValidAbsoluteUrl(url);
if (absoluteUrl) {
return absoluteUrl.href;
}
warn(`Invalid absolute docBaseUrl: "${url}".`);
}
return null;
}
class BasePdfManager {
constructor({
docBaseUrl,
docId,
enableXfa,
evaluatorOptions,
handler,
password
}) {
this._docBaseUrl = parseDocBaseUrl(docBaseUrl);
this._docId = docId;
this._password = password;
this.enableXfa = enableXfa;
evaluatorOptions.isOffscreenCanvasSupported &&= FeatureTest.isOffscreenCanvasSupported;
evaluatorOptions.isImageDecoderSupported &&= FeatureTest.isImageDecoderSupported;
this.evaluatorOptions = Object.freeze(evaluatorOptions);
ImageResizer.setOptions(evaluatorOptions);
JpegStream.setOptions(evaluatorOptions);
const options = {
...evaluatorOptions,
handler
};
JpxImage.setOptions(options);
IccColorSpace.setOptions(options);
CmykICCBasedCS.setOptions(options);
}
get docId() {
return this._docId;
}
get password() {
return this._password;
}
get docBaseUrl() {
return this._docBaseUrl;
}
get catalog() {
return this.pdfDocument.catalog;
}
ensureDoc(prop, args) {
return this.ensure(this.pdfDocument, prop, args);
}
ensureXRef(prop, args) {
return this.ensure(this.pdfDocument.xref, prop, args);
}
ensureCatalog(prop, args) {
return this.ensure(this.pdfDocument.catalog, prop, args);
}
getPage(pageIndex) {
return this.pdfDocument.getPage(pageIndex);
}
fontFallback(id, handler) {
return this.pdfDocument.fontFallback(id, handler);
}
loadXfaFonts(handler, task) {
return this.pdfDocument.loadXfaFonts(handler, task);
}
loadXfaImages() {
return this.pdfDocument.loadXfaImages();
}
serializeXfaData(annotationStorage) {
return this.pdfDocument.serializeXfaData(annotationStorage);
}
cleanup(manuallyTriggered = false) {
return this.pdfDocument.cleanup(manuallyTriggered);
}
async ensure(obj, prop, args) {
unreachable("Abstract method `ensure` called");
}
requestRange(begin, end) {
unreachable("Abstract method `requestRange` called");
}
requestLoadedStream(noFetch = false) {
unreachable("Abstract method `requestLoadedStream` called");
}
sendProgressiveData(chunk) {
unreachable("Abstract method `sendProgressiveData` called");
}
updatePassword(password) {
this._password = password;
}
terminate(reason) {
unreachable("Abstract method `terminate` called");
}
}
class LocalPdfManager extends BasePdfManager {
constructor(args) {
super(args);
const stream = new Stream(args.source);
this.pdfDocument = new PDFDocument(this, stream);
this._loadedStreamPromise = Promise.resolve(stream);
}
async ensure(obj, prop, args) {
const value = obj[prop];
if (typeof value === "function") {
return value.apply(obj, args);
}
return value;
}
requestRange(begin, end) {
return Promise.resolve();
}
requestLoadedStream(noFetch = false) {
return this._loadedStreamPromise;
}
terminate(reason) {}
}
class NetworkPdfManager extends BasePdfManager {
constructor(args) {
super(args);
this.streamManager = new ChunkedStreamManager(args.source, {
msgHandler: args.handler,
length: args.length,
disableAutoFetch: args.disableAutoFetch,
rangeChunkSize: args.rangeChunkSize
});
this.pdfDocument = new PDFDocument(this, this.streamManager.getStream());
}
async ensure(obj, prop, args) {
try {
const value = obj[prop];
if (typeof value === "function") {
return value.apply(obj, args);
}
return value;
} catch (ex) {
if (!(ex instanceof MissingDataException)) {
throw ex;
}
await this.requestRange(ex.begin, ex.end);
return this.ensure(obj, prop, args);
}
}
requestRange(begin, end) {
return this.streamManager.requestRange(begin, end);
}
requestLoadedStream(noFetch = false) {
return this.streamManager.requestAllChunks(noFetch);
}
sendProgressiveData(chunk) {
this.streamManager.onReceiveData({
chunk
});
}
terminate(reason) {
this.streamManager.abort(reason);
}
}
;// ./src/shared/message_handler.js
const CallbackKind = {
DATA: 1,
ERROR: 2
};
const StreamKind = {
CANCEL: 1,
CANCEL_COMPLETE: 2,
CLOSE: 3,
ENQUEUE: 4,
ERROR: 5,
PULL: 6,
PULL_COMPLETE: 7,
START_COMPLETE: 8
};
function onFn() {}
function wrapReason(ex) {
if (ex instanceof AbortException || ex instanceof InvalidPDFException || ex instanceof PasswordException || ex instanceof ResponseException || ex instanceof UnknownErrorException) {
return ex;
}
if (!(ex instanceof Error || typeof ex === "object" && ex !== null)) {
unreachable('wrapReason: Expected "reason" to be a (possibly cloned) Error.');
}
switch (ex.name) {
case "AbortException":
return new AbortException(ex.message);
case "InvalidPDFException":
return new InvalidPDFException(ex.message);
case "PasswordException":
return new PasswordException(ex.message, ex.code);
case "ResponseException":
return new ResponseException(ex.message, ex.status, ex.missing);
case "UnknownErrorException":
return new UnknownErrorException(ex.message, ex.details);
}
return new UnknownErrorException(ex.message, ex.toString());
}
class MessageHandler {
#messageAC = new AbortController();
constructor(sourceName, targetName, comObj) {
this.sourceName = sourceName;
this.targetName = targetName;
this.comObj = comObj;
this.callbackId = 1;
this.streamId = 1;
this.streamSinks = Object.create(null);
this.streamControllers = Object.create(null);
this.callbackCapabilities = Object.create(null);
this.actionHandler = Object.create(null);
comObj.addEventListener("message", this.#onMessage.bind(this), {
signal: this.#messageAC.signal
});
}
#onMessage({
data
}) {
if (data.targetName !== this.sourceName) {
return;
}
if (data.stream) {
this.#processStreamMessage(data);
return;
}
if (data.callback) {
const callbackId = data.callbackId;
const capability = this.callbackCapabilities[callbackId];
if (!capability) {
throw new Error(`Cannot resolve callback ${callbackId}`);
}
delete this.callbackCapabilities[callbackId];
if (data.callback === CallbackKind.DATA) {
capability.resolve(data.data);
} else if (data.callback === CallbackKind.ERROR) {
capability.reject(wrapReason(data.reason));
} else {
throw new Error("Unexpected callback case");
}
return;
}
const action = this.actionHandler[data.action];
if (!action) {
throw new Error(`Unknown action from worker: ${data.action}`);
}
if (data.callbackId) {
const sourceName = this.sourceName,
targetName = data.sourceName,
comObj = this.comObj;
Promise.try(action, data.data).then(function (result) {
comObj.postMessage({
sourceName,
targetName,
callback: CallbackKind.DATA,
callbackId: data.callbackId,
data: result
});
}, function (reason) {
comObj.postMessage({
sourceName,
targetName,
callback: CallbackKind.ERROR,
callbackId: data.callbackId,
reason: wrapReason(reason)
});
});
return;
}
if (data.streamId) {
this.#createStreamSink(data);
return;
}
action(data.data);
}
on(actionName, handler) {
const ah = this.actionHandler;
if (ah[actionName]) {
throw new Error(`There is already an actionName called "${actionName}"`);
}
ah[actionName] = handler;
}
send(actionName, data, transfers) {
this.comObj.postMessage({
sourceName: this.sourceName,
targetName: this.targetName,
action: actionName,
data
}, transfers);
}
sendWithPromise(actionName, data, transfers) {
const callbackId = this.callbackId++;
const capability = Promise.withResolvers();
this.callbackCapabilities[callbackId] = capability;
try {
this.comObj.postMessage({
sourceName: this.sourceName,
targetName: this.targetName,
action: actionName,
callbackId,
data
}, transfers);
} catch (ex) {
capability.reject(ex);
}
return capability.promise;
}
sendWithStream(actionName, data, queueingStrategy, transfers) {
const streamId = this.streamId++,
sourceName = this.sourceName,
targetName = this.targetName,
comObj = this.comObj;
return new ReadableStream({
start: controller => {
const startCapability = Promise.withResolvers();
this.streamControllers[streamId] = {
controller,
startCall: startCapability,
pullCall: null,
cancelCall: null,
isClosed: false
};
comObj.postMessage({
sourceName,
targetName,
action: actionName,
streamId,
data,
desiredSize: controller.desiredSize
}, transfers);
return startCapability.promise;
},
pull: controller => {
const pullCapability = Promise.withResolvers();
this.streamControllers[streamId].pullCall = pullCapability;
comObj.postMessage({
sourceName,
targetName,
stream: StreamKind.PULL,
streamId,
desiredSize: controller.desiredSize
});
return pullCapability.promise;
},
cancel: reason => {
assert(reason instanceof Error, "cancel must have a valid reason");
const cancelCapability = Promise.withResolvers();
this.streamControllers[streamId].cancelCall = cancelCapability;
this.streamControllers[streamId].isClosed = true;
comObj.postMessage({
sourceName,
targetName,
stream: StreamKind.CANCEL,
streamId,
reason: wrapReason(reason)
});
return cancelCapability.promise;
}
}, queueingStrategy);
}
#createStreamSink(data) {
const streamId = data.streamId,
sourceName = this.sourceName,
targetName = data.sourceName,
comObj = this.comObj;
const self = this,
action = this.actionHandler[data.action];
const streamSink = {
enqueue(chunk, size = 1, transfers) {
if (this.isCancelled) {
return;
}
const lastDesiredSize = this.desiredSize;
this.desiredSize -= size;
if (lastDesiredSize > 0 && this.desiredSize <= 0) {
this.sinkCapability = Promise.withResolvers();
this.ready = this.sinkCapability.promise;
}
comObj.postMessage({
sourceName,
targetName,
stream: StreamKind.ENQUEUE,
streamId,
chunk
}, transfers);
},
close() {
if (this.isCancelled) {
return;
}
this.isCancelled = true;
comObj.postMessage({
sourceName,
targetName,
stream: StreamKind.CLOSE,
streamId
});
delete self.streamSinks[streamId];
},
error(reason) {
assert(reason instanceof Error, "error must have a valid reason");
if (this.isCancelled) {
return;
}
this.isCancelled = true;
comObj.postMessage({
sourceName,
targetName,
stream: StreamKind.ERROR,
streamId,
reason: wrapReason(reason)
});
},
sinkCapability: Promise.withResolvers(),
onPull: null,
onCancel: null,
isCancelled: false,
desiredSize: data.desiredSize,
ready: null
};
streamSink.sinkCapability.resolve();
streamSink.ready = streamSink.sinkCapability.promise;
this.streamSinks[streamId] = streamSink;
Promise.try(action, data.data, streamSink).then(function () {
comObj.postMessage({
sourceName,
targetName,
stream: StreamKind.START_COMPLETE,
streamId,
success: true
});
}, function (reason) {
comObj.postMessage({
sourceName,
targetName,
stream: StreamKind.START_COMPLETE,
streamId,
reason: wrapReason(reason)
});
});
}
#processStreamMessage(data) {
const streamId = data.streamId,
sourceName = this.sourceName,
targetName = data.sourceName,
comObj = this.comObj;
const streamController = this.streamControllers[streamId],
streamSink = this.streamSinks[streamId];
switch (data.stream) {
case StreamKind.START_COMPLETE:
if (data.success) {
streamController.startCall.resolve();
} else {
streamController.startCall.reject(wrapReason(data.reason));
}
break;
case StreamKind.PULL_COMPLETE:
if (data.success) {
streamController.pullCall.resolve();
} else {
streamController.pullCall.reject(wrapReason(data.reason));
}
break;
case StreamKind.PULL:
if (!streamSink) {
comObj.postMessage({
sourceName,
targetName,
stream: StreamKind.PULL_COMPLETE,
streamId,
success: true
});
break;
}
if (streamSink.desiredSize <= 0 && data.desiredSize > 0) {
streamSink.sinkCapability.resolve();
}
streamSink.desiredSize = data.desiredSize;
Promise.try(streamSink.onPull || onFn).then(function () {
comObj.postMessage({
sourceName,
targetName,
stream: StreamKind.PULL_COMPLETE,
streamId,
success: true
});
}, function (reason) {
comObj.postMessage({
sourceName,
targetName,
stream: StreamKind.PULL_COMPLETE,
streamId,
reason: wrapReason(reason)
});
});
break;
case StreamKind.ENQUEUE:
assert(streamController, "enqueue should have stream controller");
if (streamController.isClosed) {
break;
}
streamController.controller.enqueue(data.chunk);
break;
case StreamKind.CLOSE:
assert(streamController, "close should have stream controller");
if (streamController.isClosed) {
break;
}
streamController.isClosed = true;
streamController.controller.close();
this.#deleteStreamController(streamController, streamId);
break;
case StreamKind.ERROR:
assert(streamController, "error should have stream controller");
streamController.controller.error(wrapReason(data.reason));
this.#deleteStreamController(streamController, streamId);
break;
case StreamKind.CANCEL_COMPLETE:
if (data.success) {
streamController.cancelCall.resolve();
} else {
streamController.cancelCall.reject(wrapReason(data.reason));
}
this.#deleteStreamController(streamController, streamId);
break;
case StreamKind.CANCEL:
if (!streamSink) {
break;
}
const dataReason = wrapReason(data.reason);
Promise.try(streamSink.onCancel || onFn, dataReason).then(function () {
comObj.postMessage({
sourceName,
targetName,
stream: StreamKind.CANCEL_COMPLETE,
streamId,
success: true
});
}, function (reason) {
comObj.postMessage({
sourceName,
targetName,
stream: StreamKind.CANCEL_COMPLETE,
streamId,
reason: wrapReason(reason)
});
});
streamSink.sinkCapability.reject(dataReason);
streamSink.isCancelled = true;
delete this.streamSinks[streamId];
break;
default:
throw new Error("Unexpected stream case");
}
}
async #deleteStreamController(streamController, streamId) {
await Promise.allSettled([streamController.startCall?.promise, streamController.pullCall?.promise, streamController.cancelCall?.promise]);
delete this.streamControllers[streamId];
}
destroy() {
this.#messageAC?.abort();
this.#messageAC = null;
}
}
;// ./src/core/writer.js
async function writeObject(ref, obj, buffer, {
encrypt = null
}) {
const transform = encrypt?.createCipherTransform(ref.num, ref.gen);
buffer.push(`${ref.num} ${ref.gen} obj\n`);
if (obj instanceof Dict) {
await writeDict(obj, buffer, transform);
} else if (obj instanceof BaseStream) {
await writeStream(obj, buffer, transform);
} else if (Array.isArray(obj) || ArrayBuffer.isView(obj)) {
await writeArray(obj, buffer, transform);
}
buffer.push("\nendobj\n");
}
async function writeDict(dict, buffer, transform) {
buffer.push("<<");
for (const key of dict.getKeys()) {
buffer.push(` /${escapePDFName(key)} `);
await writeValue(dict.getRaw(key), buffer, transform);
}
buffer.push(">>");
}
async function writeStream(stream, buffer, transform) {
let bytes = stream.getBytes();
const {
dict
} = stream;
const [filter, params] = await Promise.all([dict.getAsync("Filter"), dict.getAsync("DecodeParms")]);
const filterZero = Array.isArray(filter) ? await dict.xref.fetchIfRefAsync(filter[0]) : filter;
const isFilterZeroFlateDecode = isName(filterZero, "FlateDecode");
const MIN_LENGTH_FOR_COMPRESSING = 256;
if (bytes.length >= MIN_LENGTH_FOR_COMPRESSING || isFilterZeroFlateDecode) {
try {
const cs = new CompressionStream("deflate");
const writer = cs.writable.getWriter();
await writer.ready;
writer.write(bytes).then(async () => {
await writer.ready;
await writer.close();
}).catch(() => {});
const buf = await new Response(cs.readable).arrayBuffer();
bytes = new Uint8Array(buf);
let newFilter, newParams;
if (!filter) {
newFilter = Name.get("FlateDecode");
} else if (!isFilterZeroFlateDecode) {
newFilter = Array.isArray(filter) ? [Name.get("FlateDecode"), ...filter] : [Name.get("FlateDecode"), filter];
if (params) {
newParams = Array.isArray(params) ? [null, ...params] : [null, params];
}
}
if (newFilter) {
dict.set("Filter", newFilter);
}
if (newParams) {
dict.set("DecodeParms", newParams);
}
} catch (ex) {
info(`writeStream - cannot compress data: "${ex}".`);
}
}
let string = bytesToString(bytes);
if (transform) {
string = transform.encryptString(string);
}
dict.set("Length", string.length);
await writeDict(dict, buffer, transform);
buffer.push(" stream\n", string, "\nendstream");
}
async function writeArray(array, buffer, transform) {
buffer.push("[");
let first = true;
for (const val of array) {
if (!first) {
buffer.push(" ");
} else {
first = false;
}
await writeValue(val, buffer, transform);
}
buffer.push("]");
}
async function writeValue(value, buffer, transform) {
if (value instanceof Name) {
buffer.push(`/${escapePDFName(value.name)}`);
} else if (value instanceof Ref) {
buffer.push(`${value.num} ${value.gen} R`);
} else if (Array.isArray(value) || ArrayBuffer.isView(value)) {
await writeArray(value, buffer, transform);
} else if (typeof value === "string") {
if (transform) {
value = transform.encryptString(value);
}
buffer.push(`(${escapeString(value)})`);
} else if (typeof value === "number") {
buffer.push(numberToString(value));
} else if (typeof value === "boolean") {
buffer.push(value.toString());
} else if (value instanceof Dict) {
await writeDict(value, buffer, transform);
} else if (value instanceof BaseStream) {
await writeStream(value, buffer, transform);
} else if (value === null) {
buffer.push("null");
} else {
warn(`Unhandled value in writer: ${typeof value}, please file a bug.`);
}
}
function writeInt(number, size, offset, buffer) {
for (let i = size + offset - 1; i > offset - 1; i--) {
buffer[i] = number & 0xff;
number >>= 8;
}
return offset + size;
}
function writeString(string, offset, buffer) {
const ii = string.length;
for (let i = 0; i < ii; i++) {
buffer[offset + i] = string.charCodeAt(i) & 0xff;
}
return offset + ii;
}
function computeMD5(filesize, xrefInfo) {
const time = Math.floor(Date.now() / 1000);
const filename = xrefInfo.filename || "";
const md5Buffer = [time.toString(), filename, filesize.toString(), ...Object.values(xrefInfo.info)];
const md5BufferLen = Math.sumPrecise(md5Buffer.map(str => str.length));
const array = new Uint8Array(md5BufferLen);
let offset = 0;
for (const str of md5Buffer) {
offset = writeString(str, offset, array);
}
return bytesToString(calculateMD5(array, 0, array.length));
}
function writeXFADataForAcroform(str, changes) {
const xml = new SimpleXMLParser({
hasAttributes: true
}).parseFromString(str);
for (const {
xfa
} of changes) {
if (!xfa) {
continue;
}
const {
path,
value
} = xfa;
if (!path) {
continue;
}
const nodePath = parseXFAPath(path);
let node = xml.documentElement.searchNode(nodePath, 0);
if (!node && nodePath.length > 1) {
node = xml.documentElement.searchNode([nodePath.at(-1)], 0);
}
if (node) {
node.childNodes = Array.isArray(value) ? value.map(val => new SimpleDOMNode("value", val)) : [new SimpleDOMNode("#text", value)];
} else {
warn(`Node not found for path: ${path}`);
}
}
const buffer = [];
xml.documentElement.dump(buffer);
return buffer.join("");
}
async function updateAcroform({
xref,
acroForm,
acroFormRef,
hasXfa,
hasXfaDatasetsEntry,
xfaDatasetsRef,
needAppearances,
changes
}) {
if (hasXfa && !hasXfaDatasetsEntry && !xfaDatasetsRef) {
warn("XFA - Cannot save it");
}
if (!needAppearances && (!hasXfa || !xfaDatasetsRef || hasXfaDatasetsEntry)) {
return;
}
const dict = acroForm.clone();
if (hasXfa && !hasXfaDatasetsEntry) {
const newXfa = acroForm.get("XFA").slice();
newXfa.splice(2, 0, "datasets");
newXfa.splice(3, 0, xfaDatasetsRef);
dict.set("XFA", newXfa);
}
if (needAppearances) {
dict.set("NeedAppearances", true);
}
changes.put(acroFormRef, {
data: dict
});
}
function updateXFA({
xfaData,
xfaDatasetsRef,
changes,
xref
}) {
if (xfaData === null) {
const datasets = xref.fetchIfRef(xfaDatasetsRef);
xfaData = writeXFADataForAcroform(datasets.getString(), changes);
}
const xfaDataStream = new StringStream(xfaData);
xfaDataStream.dict = new Dict(xref);
xfaDataStream.dict.set("Type", Name.get("EmbeddedFile"));
changes.put(xfaDatasetsRef, {
data: xfaDataStream
});
}
async function getXRefTable(xrefInfo, baseOffset, newRefs, newXref, buffer) {
buffer.push("xref\n");
const indexes = getIndexes(newRefs);
let indexesPosition = 0;
for (const {
ref,
data
} of newRefs) {
if (ref.num === indexes[indexesPosition]) {
buffer.push(`${indexes[indexesPosition]} ${indexes[indexesPosition + 1]}\n`);
indexesPosition += 2;
}
if (data !== null) {
buffer.push(`${baseOffset.toString().padStart(10, "0")} ${Math.min(ref.gen, 0xffff).toString().padStart(5, "0")} n\r\n`);
baseOffset += data.length;
} else {
buffer.push(`0000000000 ${Math.min(ref.gen + 1, 0xffff).toString().padStart(5, "0")} f\r\n`);
}
}
computeIDs(baseOffset, xrefInfo, newXref);
buffer.push("trailer\n");
await writeDict(newXref, buffer);
buffer.push("\nstartxref\n", baseOffset.toString(), "\n%%EOF\n");
}
function getIndexes(newRefs) {
const indexes = [];
for (const {
ref
} of newRefs) {
if (ref.num === indexes.at(-2) + indexes.at(-1)) {
indexes[indexes.length - 1] += 1;
} else {
indexes.push(ref.num, 1);
}
}
return indexes;
}
async function getXRefStreamTable(xrefInfo, baseOffset, newRefs, newXref, buffer) {
const xrefTableData = [];
let maxOffset = 0;
let maxGen = 0;
for (const {
ref,
data
} of newRefs) {
let gen;
maxOffset = Math.max(maxOffset, baseOffset);
if (data !== null) {
gen = Math.min(ref.gen, 0xffff);
xrefTableData.push([1, baseOffset, gen]);
baseOffset += data.length;
} else {
gen = Math.min(ref.gen + 1, 0xffff);
xrefTableData.push([0, 0, gen]);
}
maxGen = Math.max(maxGen, gen);
}
newXref.set("Index", getIndexes(newRefs));
const offsetSize = getSizeInBytes(maxOffset);
const maxGenSize = getSizeInBytes(maxGen);
const sizes = [1, offsetSize, maxGenSize];
newXref.set("W", sizes);
computeIDs(baseOffset, xrefInfo, newXref);
const structSize = Math.sumPrecise(sizes);
const data = new Uint8Array(structSize * xrefTableData.length);
const stream = new Stream(data);
stream.dict = newXref;
let offset = 0;
for (const [type, objOffset, gen] of xrefTableData) {
offset = writeInt(type, sizes[0], offset, data);
offset = writeInt(objOffset, sizes[1], offset, data);
offset = writeInt(gen, sizes[2], offset, data);
}
await writeObject(xrefInfo.newRef, stream, buffer, {});
buffer.push("startxref\n", baseOffset.toString(), "\n%%EOF\n");
}
function computeIDs(baseOffset, xrefInfo, newXref) {
if (Array.isArray(xrefInfo.fileIds) && xrefInfo.fileIds.length > 0) {
const md5 = computeMD5(baseOffset, xrefInfo);
newXref.set("ID", [xrefInfo.fileIds[0], md5]);
}
}
function getTrailerDict(xrefInfo, changes, useXrefStream) {
const newXref = new Dict(null);
newXref.set("Prev", xrefInfo.startXRef);
const refForXrefTable = xrefInfo.newRef;
if (useXrefStream) {
changes.put(refForXrefTable, {
data: ""
});
newXref.set("Size", refForXrefTable.num + 1);
newXref.set("Type", Name.get("XRef"));
} else {
newXref.set("Size", refForXrefTable.num);
}
if (xrefInfo.rootRef !== null) {
newXref.set("Root", xrefInfo.rootRef);
}
if (xrefInfo.infoRef !== null) {
newXref.set("Info", xrefInfo.infoRef);
}
if (xrefInfo.encryptRef !== null) {
newXref.set("Encrypt", xrefInfo.encryptRef);
}
return newXref;
}
async function writeChanges(changes, xref, buffer = []) {
const newRefs = [];
for (const [ref, {
data
}] of changes.items()) {
if (data === null || typeof data === "string") {
newRefs.push({
ref,
data
});
continue;
}
await writeObject(ref, data, buffer, xref);
newRefs.push({
ref,
data: buffer.join("")
});
buffer.length = 0;
}
return newRefs.sort((a, b) => a.ref.num - b.ref.num);
}
async function incrementalUpdate({
originalData,
xrefInfo,
changes,
xref = null,
hasXfa = false,
xfaDatasetsRef = null,
hasXfaDatasetsEntry = false,
needAppearances,
acroFormRef = null,
acroForm = null,
xfaData = null,
useXrefStream = false
}) {
await updateAcroform({
xref,
acroForm,
acroFormRef,
hasXfa,
hasXfaDatasetsEntry,
xfaDatasetsRef,
needAppearances,
changes
});
if (hasXfa) {
updateXFA({
xfaData,
xfaDatasetsRef,
changes,
xref
});
}
const newXref = getTrailerDict(xrefInfo, changes, useXrefStream);
const buffer = [];
const newRefs = await writeChanges(changes, xref, buffer);
let baseOffset = originalData.length;
const lastByte = originalData.at(-1);
if (lastByte !== 0x0a && lastByte !== 0x0d) {
buffer.push("\n");
baseOffset += 1;
}
for (const {
data
} of newRefs) {
if (data !== null) {
buffer.push(data);
}
}
await (useXrefStream ? getXRefStreamTable(xrefInfo, baseOffset, newRefs, newXref, buffer) : getXRefTable(xrefInfo, baseOffset, newRefs, newXref, buffer));
const totalLength = originalData.length + Math.sumPrecise(buffer.map(str => str.length));
const array = new Uint8Array(totalLength);
array.set(originalData);
let offset = originalData.length;
for (const str of buffer) {
offset = writeString(str, offset, array);
}
return array;
}
;// ./src/core/worker_stream.js
class PDFWorkerStream {
constructor(msgHandler) {
this._msgHandler = msgHandler;
this._contentLength = null;
this._fullRequestReader = null;
this._rangeRequestReaders = [];
}
getFullReader() {
assert(!this._fullRequestReader, "PDFWorkerStream.getFullReader can only be called once.");
this._fullRequestReader = new PDFWorkerStreamReader(this._msgHandler);
return this._fullRequestReader;
}
getRangeReader(begin, end) {
const reader = new PDFWorkerStreamRangeReader(begin, end, this._msgHandler);
this._rangeRequestReaders.push(reader);
return reader;
}
cancelAllRequests(reason) {
this._fullRequestReader?.cancel(reason);
for (const reader of this._rangeRequestReaders.slice(0)) {
reader.cancel(reason);
}
}
}
class PDFWorkerStreamReader {
constructor(msgHandler) {
this._msgHandler = msgHandler;
this.onProgress = null;
this._contentLength = null;
this._isRangeSupported = false;
this._isStreamingSupported = false;
const readableStream = this._msgHandler.sendWithStream("GetReader");
this._reader = readableStream.getReader();
this._headersReady = this._msgHandler.sendWithPromise("ReaderHeadersReady").then(data => {
this._isStreamingSupported = data.isStreamingSupported;
this._isRangeSupported = data.isRangeSupported;
this._contentLength = data.contentLength;
});
}
get headersReady() {
return this._headersReady;
}
get contentLength() {
return this._contentLength;
}
get isStreamingSupported() {
return this._isStreamingSupported;
}
get isRangeSupported() {
return this._isRangeSupported;
}
async read() {
const {
value,
done
} = await this._reader.read();
if (done) {
return {
value: undefined,
done: true
};
}
return {
value: value.buffer,
done: false
};
}
cancel(reason) {
this._reader.cancel(reason);
}
}
class PDFWorkerStreamRangeReader {
constructor(begin, end, msgHandler) {
this._msgHandler = msgHandler;
this.onProgress = null;
const readableStream = this._msgHandler.sendWithStream("GetRangeReader", {
begin,
end
});
this._reader = readableStream.getReader();
}
get isStreamingSupported() {
return false;
}
async read() {
const {
value,
done
} = await this._reader.read();
if (done) {
return {
value: undefined,
done: true
};
}
return {
value: value.buffer,
done: false
};
}
cancel(reason) {
this._reader.cancel(reason);
}
}
;// ./src/core/worker.js
class WorkerTask {
constructor(name) {
this.name = name;
this.terminated = false;
this._capability = Promise.withResolvers();
}
get finished() {
return this._capability.promise;
}
finish() {
this._capability.resolve();
}
terminate() {
this.terminated = true;
}
ensureNotTerminated() {
if (this.terminated) {
throw new Error("Worker task was terminated");
}
}
}
class WorkerMessageHandler {
static {
if (typeof window === "undefined" && !isNodeJS && typeof self !== "undefined" && typeof self.postMessage === "function" && "onmessage" in self) {
this.initializeFromPort(self);
}
}
static setup(handler, port) {
let testMessageProcessed = false;
handler.on("test", data => {
if (testMessageProcessed) {
return;
}
testMessageProcessed = true;
handler.send("test", data instanceof Uint8Array);
});
handler.on("configure", data => {
setVerbosityLevel(data.verbosity);
});
handler.on("GetDocRequest", data => this.createDocumentHandler(data, port));
}
static createDocumentHandler(docParams, port) {
let pdfManager;
let terminated = false;
let cancelXHRs = null;
const WorkerTasks = new Set();
const verbosity = getVerbosityLevel();
const {
docId,
apiVersion
} = docParams;
const workerVersion = "5.1.91";
if (apiVersion !== workerVersion) {
throw new Error(`The API version "${apiVersion}" does not match ` + `the Worker version "${workerVersion}".`);
}
const enumerableProperties = [];
for (const property in []) {
enumerableProperties.push(property);
}
if (enumerableProperties.length) {
throw new Error("The `Array.prototype` contains unexpected enumerable properties: " + enumerableProperties.join(", ") + "; thus breaking e.g. `for...in` iteration of `Array`s.");
}
const workerHandlerName = docId + "_worker";
let handler = new MessageHandler(workerHandlerName, docId, port);
function ensureNotTerminated() {
if (terminated) {
throw new Error("Worker was terminated");
}
}
function startWorkerTask(task) {
WorkerTasks.add(task);
}
function finishWorkerTask(task) {
task.finish();
WorkerTasks.delete(task);
}
async function loadDocument(recoveryMode) {
await pdfManager.ensureDoc("checkHeader");
await pdfManager.ensureDoc("parseStartXRef");
await pdfManager.ensureDoc("parse", [recoveryMode]);
await pdfManager.ensureDoc("checkFirstPage", [recoveryMode]);
await pdfManager.ensureDoc("checkLastPage", [recoveryMode]);
const isPureXfa = await pdfManager.ensureDoc("isPureXfa");
if (isPureXfa) {
const task = new WorkerTask("loadXfaFonts");
startWorkerTask(task);
await Promise.all([pdfManager.loadXfaFonts(handler, task).catch(reason => {}).then(() => finishWorkerTask(task)), pdfManager.loadXfaImages()]);
}
const [numPages, fingerprints] = await Promise.all([pdfManager.ensureDoc("numPages"), pdfManager.ensureDoc("fingerprints")]);
const htmlForXfa = isPureXfa ? await pdfManager.ensureDoc("htmlForXfa") : null;
return {
numPages,
fingerprints,
htmlForXfa
};
}
async function getPdfManager({
data,
password,
disableAutoFetch,
rangeChunkSize,
length,
docBaseUrl,
enableXfa,
evaluatorOptions
}) {
const pdfManagerArgs = {
source: null,
disableAutoFetch,
docBaseUrl,
docId,
enableXfa,
evaluatorOptions,
handler,
length,
password,
rangeChunkSize
};
if (data) {
pdfManagerArgs.source = data;
return new LocalPdfManager(pdfManagerArgs);
}
const pdfStream = new PDFWorkerStream(handler),
fullRequest = pdfStream.getFullReader();
const pdfManagerCapability = Promise.withResolvers();
let newPdfManager,
cachedChunks = [],
loaded = 0;
fullRequest.headersReady.then(function () {
if (!fullRequest.isRangeSupported) {
return;
}
pdfManagerArgs.source = pdfStream;
pdfManagerArgs.length = fullRequest.contentLength;
pdfManagerArgs.disableAutoFetch ||= fullRequest.isStreamingSupported;
newPdfManager = new NetworkPdfManager(pdfManagerArgs);
for (const chunk of cachedChunks) {
newPdfManager.sendProgressiveData(chunk);
}
cachedChunks = [];
pdfManagerCapability.resolve(newPdfManager);
cancelXHRs = null;
}).catch(function (reason) {
pdfManagerCapability.reject(reason);
cancelXHRs = null;
});
new Promise(function (resolve, reject) {
const readChunk = function ({
value,
done
}) {
try {
ensureNotTerminated();
if (done) {
if (!newPdfManager) {
const pdfFile = arrayBuffersToBytes(cachedChunks);
cachedChunks = [];
if (length && pdfFile.length !== length) {
warn("reported HTTP length is different from actual");
}
pdfManagerArgs.source = pdfFile;
newPdfManager = new LocalPdfManager(pdfManagerArgs);
pdfManagerCapability.resolve(newPdfManager);
}
cancelXHRs = null;
return;
}
loaded += value.byteLength;
if (!fullRequest.isStreamingSupported) {
handler.send("DocProgress", {
loaded,
total: Math.max(loaded, fullRequest.contentLength || 0)
});
}
if (newPdfManager) {
newPdfManager.sendProgressiveData(value);
} else {
cachedChunks.push(value);
}
fullRequest.read().then(readChunk, reject);
} catch (e) {
reject(e);
}
};
fullRequest.read().then(readChunk, reject);
}).catch(function (e) {
pdfManagerCapability.reject(e);
cancelXHRs = null;
});
cancelXHRs = reason => {
pdfStream.cancelAllRequests(reason);
};
return pdfManagerCapability.promise;
}
function setupDoc(data) {
function onSuccess(doc) {
ensureNotTerminated();
handler.send("GetDoc", {
pdfInfo: doc
});
}
function onFailure(ex) {
ensureNotTerminated();
if (ex instanceof PasswordException) {
const task = new WorkerTask(`PasswordException: response ${ex.code}`);
startWorkerTask(task);
handler.sendWithPromise("PasswordRequest", ex).then(function ({
password
}) {
finishWorkerTask(task);
pdfManager.updatePassword(password);
pdfManagerReady();
}).catch(function () {
finishWorkerTask(task);
handler.send("DocException", ex);
});
} else {
handler.send("DocException", wrapReason(ex));
}
}
function pdfManagerReady() {
ensureNotTerminated();
loadDocument(false).then(onSuccess, function (reason) {
ensureNotTerminated();
if (!(reason instanceof XRefParseException)) {
onFailure(reason);
return;
}
pdfManager.requestLoadedStream().then(function () {
ensureNotTerminated();
loadDocument(true).then(onSuccess, onFailure);
});
});
}
ensureNotTerminated();
getPdfManager(data).then(function (newPdfManager) {
if (terminated) {
newPdfManager.terminate(new AbortException("Worker was terminated."));
throw new Error("Worker was terminated");
}
pdfManager = newPdfManager;
pdfManager.requestLoadedStream(true).then(stream => {
handler.send("DataLoaded", {
length: stream.bytes.byteLength
});
});
}).then(pdfManagerReady, onFailure);
}
handler.on("GetPage", function (data) {
return pdfManager.getPage(data.pageIndex).then(function (page) {
return Promise.all([pdfManager.ensure(page, "rotate"), pdfManager.ensure(page, "ref"), pdfManager.ensure(page, "userUnit"), pdfManager.ensure(page, "view")]).then(function ([rotate, ref, userUnit, view]) {
return {
rotate,
ref,
refStr: ref?.toString() ?? null,
userUnit,
view
};
});
});
});
handler.on("GetPageIndex", function (data) {
const pageRef = Ref.get(data.num, data.gen);
return pdfManager.ensureCatalog("getPageIndex", [pageRef]);
});
handler.on("GetDestinations", function (data) {
return pdfManager.ensureCatalog("destinations");
});
handler.on("GetDestination", function (data) {
return pdfManager.ensureCatalog("getDestination", [data.id]);
});
handler.on("GetPageLabels", function (data) {
return pdfManager.ensureCatalog("pageLabels");
});
handler.on("GetPageLayout", function (data) {
return pdfManager.ensureCatalog("pageLayout");
});
handler.on("GetPageMode", function (data) {
return pdfManager.ensureCatalog("pageMode");
});
handler.on("GetViewerPreferences", function (data) {
return pdfManager.ensureCatalog("viewerPreferences");
});
handler.on("GetOpenAction", function (data) {
return pdfManager.ensureCatalog("openAction");
});
handler.on("GetAttachments", function (data) {
return pdfManager.ensureCatalog("attachments");
});
handler.on("GetDocJSActions", function (data) {
return pdfManager.ensureCatalog("jsActions");
});
handler.on("GetPageJSActions", function ({
pageIndex
}) {
return pdfManager.getPage(pageIndex).then(page => pdfManager.ensure(page, "jsActions"));
});
handler.on("GetOutline", function (data) {
return pdfManager.ensureCatalog("documentOutline");
});
handler.on("GetOptionalContentConfig", function (data) {
return pdfManager.ensureCatalog("optionalContentConfig");
});
handler.on("GetPermissions", function (data) {
return pdfManager.ensureCatalog("permissions");
});
handler.on("GetMetadata", function (data) {
return Promise.all([pdfManager.ensureDoc("documentInfo"), pdfManager.ensureCatalog("metadata")]);
});
handler.on("GetMarkInfo", function (data) {
return pdfManager.ensureCatalog("markInfo");
});
handler.on("GetData", function (data) {
return pdfManager.requestLoadedStream().then(stream => stream.bytes);
});
handler.on("GetAnnotations", function ({
pageIndex,
intent
}) {
return pdfManager.getPage(pageIndex).then(function (page) {
const task = new WorkerTask(`GetAnnotations: page ${pageIndex}`);
startWorkerTask(task);
return page.getAnnotationsData(handler, task, intent).then(data => {
finishWorkerTask(task);
return data;
}, reason => {
finishWorkerTask(task);
throw reason;
});
});
});
handler.on("GetFieldObjects", function (data) {
return pdfManager.ensureDoc("fieldObjects").then(fieldObjects => fieldObjects?.allFields || null);
});
handler.on("HasJSActions", function (data) {
return pdfManager.ensureDoc("hasJSActions");
});
handler.on("GetCalculationOrderIds", function (data) {
return pdfManager.ensureDoc("calculationOrderIds");
});
handler.on("SaveDocument", async function ({
isPureXfa,
numPages,
annotationStorage,
filename
}) {
const globalPromises = [pdfManager.requestLoadedStream(), pdfManager.ensureCatalog("acroForm"), pdfManager.ensureCatalog("acroFormRef"), pdfManager.ensureDoc("startXRef"), pdfManager.ensureDoc("xref"), pdfManager.ensureDoc("linearization"), pdfManager.ensureCatalog("structTreeRoot")];
const changes = new RefSetCache();
const promises = [];
const newAnnotationsByPage = !isPureXfa ? getNewAnnotationsMap(annotationStorage) : null;
const [stream, acroForm, acroFormRef, startXRef, xref, linearization, _structTreeRoot] = await Promise.all(globalPromises);
const catalogRef = xref.trailer.getRaw("Root") || null;
let structTreeRoot;
if (newAnnotationsByPage) {
if (!_structTreeRoot) {
if (await StructTreeRoot.canCreateStructureTree({
catalogRef,
pdfManager,
newAnnotationsByPage
})) {
structTreeRoot = null;
}
} else if (await _structTreeRoot.canUpdateStructTree({
pdfManager,
newAnnotationsByPage
})) {
structTreeRoot = _structTreeRoot;
}
const imagePromises = AnnotationFactory.generateImages(annotationStorage.values(), xref, pdfManager.evaluatorOptions.isOffscreenCanvasSupported);
const newAnnotationPromises = structTreeRoot === undefined ? promises : [];
for (const [pageIndex, annotations] of newAnnotationsByPage) {
newAnnotationPromises.push(pdfManager.getPage(pageIndex).then(page => {
const task = new WorkerTask(`Save (editor): page ${pageIndex}`);
startWorkerTask(task);
return page.saveNewAnnotations(handler, task, annotations, imagePromises, changes).finally(function () {
finishWorkerTask(task);
});
}));
}
if (structTreeRoot === null) {
promises.push(Promise.all(newAnnotationPromises).then(async () => {
await StructTreeRoot.createStructureTree({
newAnnotationsByPage,
xref,
catalogRef,
pdfManager,
changes
});
}));
} else if (structTreeRoot) {
promises.push(Promise.all(newAnnotationPromises).then(async () => {
await structTreeRoot.updateStructureTree({
newAnnotationsByPage,
pdfManager,
changes
});
}));
}
}
if (isPureXfa) {
promises.push(pdfManager.serializeXfaData(annotationStorage));
} else {
for (let pageIndex = 0; pageIndex < numPages; pageIndex++) {
promises.push(pdfManager.getPage(pageIndex).then(function (page) {
const task = new WorkerTask(`Save: page ${pageIndex}`);
startWorkerTask(task);
return page.save(handler, task, annotationStorage, changes).finally(function () {
finishWorkerTask(task);
});
}));
}
}
const refs = await Promise.all(promises);
let xfaData = null;
if (isPureXfa) {
xfaData = refs[0];
if (!xfaData) {
return stream.bytes;
}
} else if (changes.size === 0) {
return stream.bytes;
}
const needAppearances = acroFormRef && acroForm instanceof Dict && changes.values().some(ref => ref.needAppearances);
const xfa = acroForm instanceof Dict && acroForm.get("XFA") || null;
let xfaDatasetsRef = null;
let hasXfaDatasetsEntry = false;
if (Array.isArray(xfa)) {
for (let i = 0, ii = xfa.length; i < ii; i += 2) {
if (xfa[i] === "datasets") {
xfaDatasetsRef = xfa[i + 1];
hasXfaDatasetsEntry = true;
}
}
if (xfaDatasetsRef === null) {
xfaDatasetsRef = xref.getNewTemporaryRef();
}
} else if (xfa) {
warn("Unsupported XFA type.");
}
let newXrefInfo = Object.create(null);
if (xref.trailer) {
const infoObj = Object.create(null);
const xrefInfo = xref.trailer.get("Info") || null;
if (xrefInfo instanceof Dict) {
for (const [key, value] of xrefInfo) {
if (typeof value === "string") {
infoObj[key] = stringToPDFString(value);
}
}
}
newXrefInfo = {
rootRef: catalogRef,
encryptRef: xref.trailer.getRaw("Encrypt") || null,
newRef: xref.getNewTemporaryRef(),
infoRef: xref.trailer.getRaw("Info") || null,
info: infoObj,
fileIds: xref.trailer.get("ID") || null,
startXRef: linearization ? startXRef : xref.lastXRefStreamPos ?? startXRef,
filename
};
}
return incrementalUpdate({
originalData: stream.bytes,
xrefInfo: newXrefInfo,
changes,
xref,
hasXfa: !!xfa,
xfaDatasetsRef,
hasXfaDatasetsEntry,
needAppearances,
acroFormRef,
acroForm,
xfaData,
useXrefStream: isDict(xref.topDict, "XRef")
}).finally(() => {
xref.resetNewTemporaryRef();
});
});
handler.on("GetOperatorList", function (data, sink) {
const pageIndex = data.pageIndex;
pdfManager.getPage(pageIndex).then(function (page) {
const task = new WorkerTask(`GetOperatorList: page ${pageIndex}`);
startWorkerTask(task);
const start = verbosity >= VerbosityLevel.INFOS ? Date.now() : 0;
page.getOperatorList({
handler,
sink,
task,
intent: data.intent,
cacheKey: data.cacheKey,
annotationStorage: data.annotationStorage,
modifiedIds: data.modifiedIds
}).then(function (operatorListInfo) {
finishWorkerTask(task);
if (start) {
info(`page=${pageIndex + 1} - getOperatorList: time=` + `${Date.now() - start}ms, len=${operatorListInfo.length}`);
}
sink.close();
}, function (reason) {
finishWorkerTask(task);
if (task.terminated) {
return;
}
sink.error(reason);
});
});
});
handler.on("GetTextContent", function (data, sink) {
const {
pageIndex,
includeMarkedContent,
disableNormalization
} = data;
pdfManager.getPage(pageIndex).then(function (page) {
const task = new WorkerTask("GetTextContent: page " + pageIndex);
startWorkerTask(task);
const start = verbosity >= VerbosityLevel.INFOS ? Date.now() : 0;
page.extractTextContent({
handler,
task,
sink,
includeMarkedContent,
disableNormalization
}).then(function () {
finishWorkerTask(task);
if (start) {
info(`page=${pageIndex + 1} - getTextContent: time=` + `${Date.now() - start}ms`);
}
sink.close();
}, function (reason) {
finishWorkerTask(task);
if (task.terminated) {
return;
}
sink.error(reason);
});
});
});
handler.on("GetStructTree", function (data) {
return pdfManager.getPage(data.pageIndex).then(page => pdfManager.ensure(page, "getStructTree"));
});
handler.on("FontFallback", function (data) {
return pdfManager.fontFallback(data.id, handler);
});
handler.on("Cleanup", function (data) {
return pdfManager.cleanup(true);
});
handler.on("Terminate", function (data) {
terminated = true;
const waitOn = [];
if (pdfManager) {
pdfManager.terminate(new AbortException("Worker was terminated."));
const cleanupPromise = pdfManager.cleanup();
waitOn.push(cleanupPromise);
pdfManager = null;
} else {
clearGlobalCaches();
}
cancelXHRs?.(new AbortException("Worker was terminated."));
for (const task of WorkerTasks) {
waitOn.push(task.finished);
task.terminate();
}
return Promise.all(waitOn).then(function () {
handler.destroy();
handler = null;
});
});
handler.on("Ready", function (data) {
setupDoc(docParams);
docParams = null;
});
return workerHandlerName;
}
static initializeFromPort(port) {
const handler = new MessageHandler("worker", "main", port);
this.setup(handler, port);
handler.send("ready", null);
}
}
;// ./src/pdf.worker.js
const pdfjsVersion = "5.1.91";
const pdfjsBuild = "45cbe8bb0";
var __webpack_exports__WorkerMessageHandler = __webpack_exports__.WorkerMessageHandler;
export { __webpack_exports__WorkerMessageHandler as WorkerMessageHandler };
//# sourceMappingURL=pdf.worker.mjs.map
================================================
FILE: cookbook/static/pdfjs/web/standard_fonts/LICENSE_FOXIT
================================================
// Copyright 2014 PDFium Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: cookbook/static/pdfjs/web/standard_fonts/LICENSE_LIBERATION
================================================
Digitized data copyright (c) 2010 Google Corporation
with Reserved Font Arimo, Tinos and Cousine.
Copyright (c) 2012 Red Hat, Inc.
with Reserved Font Name Liberation.
This Font Software is licensed under the SIL Open Font License,
Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
PREAMBLE The goals of the Open Font License (OFL) are to stimulate
worldwide development of collaborative font projects, to support the font
creation efforts of academic and linguistic communities, and to provide
a free and open framework in which fonts may be shared and improved in
partnership with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves.
The fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply to
any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such.
This may include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components
as distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting ? in part or in whole ?
any of the components of the Original Version, by changing formats or
by porting the Font Software to a new environment.
"Author" refers to any designer, engineer, programmer, technical writer
or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining a
copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,in
Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the
corresponding Copyright Holder. This restriction only applies to the
primary font name as presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole, must
be distributed entirely under this license, and must not be distributed
under any other license. The requirement for fonts to remain under
this license does not apply to any document created using the Font
Software.
TERMINATION
This license becomes null and void if any of the above conditions are not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER
DEALINGS IN THE FONT SOFTWARE.
================================================
FILE: cookbook/static/pdfjs/web/viewer.css
================================================
/* Copyright 2014 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.messageBar{
--closing-button-icon:url(images/messageBar_closingButton.svg);
--message-bar-close-button-color:var(--text-primary-color);
--message-bar-close-button-color-hover:var(--text-primary-color);
--message-bar-close-button-border-radius:4px;
--message-bar-close-button-border:none;
--message-bar-close-button-hover-bg-color:rgb(21 20 26 / 0.14);
--message-bar-close-button-active-bg-color:rgb(21 20 26 / 0.21);
--message-bar-close-button-focus-bg-color:rgb(21 20 26 / 0.07);
}
@media (prefers-color-scheme: dark){
:where(html:not(.is-light)) .messageBar{
--message-bar-close-button-hover-bg-color:rgb(251 251 254 / 0.14);
--message-bar-close-button-active-bg-color:rgb(251 251 254 / 0.21);
--message-bar-close-button-focus-bg-color:rgb(251 251 254 / 0.07);
}
}
:where(html.is-dark) .messageBar{
--message-bar-close-button-hover-bg-color:rgb(251 251 254 / 0.14);
--message-bar-close-button-active-bg-color:rgb(251 251 254 / 0.21);
--message-bar-close-button-focus-bg-color:rgb(251 251 254 / 0.07);
}
@media screen and (forced-colors: active){
.messageBar{
--message-bar-close-button-color:ButtonText;
--message-bar-close-button-border:1px solid ButtonText;
--message-bar-close-button-hover-bg-color:ButtonText;
--message-bar-close-button-active-bg-color:ButtonText;
--message-bar-close-button-focus-bg-color:ButtonText;
--message-bar-close-button-color-hover:HighlightText;
}
}
.messageBar{
display:flex;
position:relative;
padding:8px 8px 8px 16px;
flex-direction:column;
justify-content:center;
align-items:center;
gap:8px;
-webkit-user-select:none;
-moz-user-select:none;
user-select:none;
border-radius:4px;
border:1px solid var(--message-bar-border-color);
background:var(--message-bar-bg-color);
color:var(--message-bar-fg-color);
}
.messageBar > div{
display:flex;
align-items:flex-start;
gap:8px;
align-self:stretch;
}
:is(.messageBar > div)::before{
content:"";
display:inline-block;
width:16px;
height:16px;
-webkit-mask-image:var(--message-bar-icon);
mask-image:var(--message-bar-icon);
-webkit-mask-size:cover;
mask-size:cover;
background-color:var(--message-bar-icon-color);
flex-shrink:0;
}
.messageBar button{
cursor:pointer;
}
:is(.messageBar button):focus-visible{
outline:var(--focus-ring-outline);
outline-offset:2px;
}
.messageBar .closeButton{
width:32px;
height:32px;
background:none;
border-radius:var(--message-bar-close-button-border-radius);
border:var(--message-bar-close-button-border);
display:flex;
align-items:center;
justify-content:center;
}
:is(.messageBar .closeButton)::before{
content:"";
display:inline-block;
width:16px;
height:16px;
-webkit-mask-image:var(--closing-button-icon);
mask-image:var(--closing-button-icon);
-webkit-mask-size:cover;
mask-size:cover;
background-color:var(--message-bar-close-button-color);
}
:is(.messageBar .closeButton):is(:hover,:active,:focus)::before{
background-color:var(--message-bar-close-button-color-hover);
}
:is(.messageBar .closeButton):hover{
background-color:var(--message-bar-close-button-hover-bg-color);
}
:is(.messageBar .closeButton):active{
background-color:var(--message-bar-close-button-active-bg-color);
}
:is(.messageBar .closeButton):focus{
background-color:var(--message-bar-close-button-focus-bg-color);
}
:is(.messageBar .closeButton) > span{
display:inline-block;
width:0;
height:0;
overflow:hidden;
}
#editorUndoBar{
--text-primary-color:#15141a;
--message-bar-icon:url(images/secondaryToolbarButton-documentProperties.svg);
--message-bar-icon-color:#0060df;
--message-bar-bg-color:#deeafc;
--message-bar-fg-color:var(--text-primary-color);
--message-bar-border-color:rgb(0 0 0 / 0.08);
--undo-button-bg-color:rgb(21 20 26 / 0.07);
--undo-button-bg-color-hover:rgb(21 20 26 / 0.14);
--undo-button-bg-color-active:rgb(21 20 26 / 0.21);
--undo-button-border:1px solid #0060df;
--undo-button-fg-color:var(--message-bar-fg-color);
--undo-button-fg-color-hover:var(--undo-button-fg-color);
--undo-button-fg-color-active:var(--undo-button-fg-color);
}
@media (prefers-color-scheme: dark){
:where(html:not(.is-light)) #editorUndoBar{
--text-primary-color:#fbfbfe;
--message-bar-icon-color:#73a7f3;
--message-bar-bg-color:#003070;
--message-bar-border-color:rgb(255 255 255 / 0.08);
--undo-button-bg-color:rgb(255 255 255 / 0.08);
--undo-button-bg-color-hover:rgb(255 255 255 / 0.14);
--undo-button-bg-color-active:rgb(255 255 255 / 0.21);
--undo-button-border:1px solid #0df;
}
}
:where(html.is-dark) #editorUndoBar{
--text-primary-color:#fbfbfe;
--message-bar-icon-color:#73a7f3;
--message-bar-bg-color:#003070;
--message-bar-border-color:rgb(255 255 255 / 0.08);
--undo-button-bg-color:rgb(255 255 255 / 0.08);
--undo-button-bg-color-hover:rgb(255 255 255 / 0.14);
--undo-button-bg-color-active:rgb(255 255 255 / 0.21);
--undo-button-border:1px solid #0df;
}
@media screen and (forced-colors: active){
#editorUndoBar{
--text-primary-color:CanvasText;
--message-bar-icon-color:CanvasText;
--message-bar-bg-color:Canvas;
--message-bar-border-color:CanvasText;
--undo-button-bg-color:ButtonText;
--undo-button-bg-color-hover:SelectedItem;
--undo-button-bg-color-active:SelectedItem;
--undo-button-fg-color:ButtonFace;
--undo-button-fg-color-hover:SelectedItemText;
--undo-button-fg-color-active:SelectedItemText;
--undo-button-border:none;
}
}
#editorUndoBar{
position:fixed;
top:50px;
left:50%;
transform:translateX(-50%);
z-index:10;
padding-block:8px;
padding-inline:16px 8px;
font:menu;
font-size:15px;
cursor:default;
}
#editorUndoBar button{
cursor:pointer;
}
#editorUndoBar #editorUndoBarUndoButton{
border-radius:4px;
font-weight:590;
line-height:19.5px;
color:var(--undo-button-fg-color);
border:var(--undo-button-border);
padding:4px 16px;
margin-inline-start:8px;
height:32px;
background-color:var(--undo-button-bg-color);
}
:is(#editorUndoBar #editorUndoBarUndoButton):hover{
background-color:var(--undo-button-bg-color-hover);
color:var(--undo-button-fg-color-hover);
}
:is(#editorUndoBar #editorUndoBarUndoButton):active{
background-color:var(--undo-button-bg-color-active);
color:var(--undo-button-fg-color-active);
}
#editorUndoBar > div{
align-items:center;
}
.dialog{
--dialog-bg-color:white;
--dialog-border-color:white;
--dialog-shadow:0 2px 14px 0 rgb(58 57 68 / 0.2);
--text-primary-color:#15141a;
--text-secondary-color:#5b5b66;
--hover-filter:brightness(0.9);
--link-fg-color:#0060df;
--link-hover-fg-color:#0250bb;
--separator-color:#f0f0f4;
--textarea-border-color:#8f8f9d;
--textarea-bg-color:white;
--textarea-fg-color:var(--text-secondary-color);
--radio-bg-color:#f0f0f4;
--radio-checked-bg-color:#fbfbfe;
--radio-border-color:#8f8f9d;
--radio-checked-border-color:#0060df;
--button-secondary-bg-color:rgb(21 20 26 / 0.07);
--button-secondary-fg-color:var(--text-primary-color);
--button-secondary-border-color:var(--button-secondary-bg-color);
--button-secondary-active-bg-color:rgb(21 20 26 / 0.21);
--button-secondary-active-fg-color:var(--button-secondary-fg-color);
--button-secondary-active-border-color:var(--button-secondary-bg-color);
--button-secondary-hover-bg-color:rgb(21 20 26 / 0.14);
--button-secondary-hover-fg-color:var(--button-secondary-fg-color);
--button-secondary-hover-border-color:var(--button-secondary-hover-bg-color);
--button-primary-bg-color:#0060df;
--button-primary-fg-color:#fbfbfe;
--button-primary-border-color:var(--button-primary-bg-color);
--button-primary-active-bg-color:#054096;
--button-primary-active-fg-color:var(--button-primary-fg-color);
--button-primary-active-border-color:var(--button-primary-active-bg-color);
--button-primary-hover-bg-color:#0250bb;
--button-primary-hover-fg-color:var(--button-primary-fg-color);
--button-primary-hover-border-color:var(--button-primary-hover-bg-color);
--button-disabled-bg-color:color-mix(in srgb, currentcolor, transparent 60%);
--button-disabled-fg-color:var(--button-disabled-bg-color);
--input-text-bg-color:white;
--input-text-fg-color:var(--text-primary-color);
}
@media (prefers-color-scheme: dark){
:where(html:not(.is-light)) .dialog{
--dialog-bg-color:#1c1b22;
--dialog-border-color:#1c1b22;
--dialog-shadow:0 2px 14px 0 #15141a;
--text-primary-color:#fbfbfe;
--text-secondary-color:#cfcfd8;
--hover-filter:brightness(1.4);
--link-fg-color:#0df;
--link-hover-fg-color:#80ebff;
--separator-color:#52525e;
--textarea-bg-color:#42414d;
--radio-bg-color:#2b2a33;
--radio-checked-bg-color:#15141a;
--radio-checked-border-color:#0df;
--button-secondary-bg-color:rgb(251 251 254 / 0.07);
--button-secondary-active-bg-color:rgb(251 251 254 / 0.21);
--button-secondary-hover-bg-color:rgb(251 251 254 / 0.14);
--button-primary-bg-color:#0df;
--button-primary-fg-color:#15141a;
--button-primary-active-bg-color:#aaf2ff;
--button-primary-hover-bg-color:#80ebff;
--input-text-bg-color:#42414d;
}
}
:where(html.is-dark) .dialog{
--dialog-bg-color:#1c1b22;
--dialog-border-color:#1c1b22;
--dialog-shadow:0 2px 14px 0 #15141a;
--text-primary-color:#fbfbfe;
--text-secondary-color:#cfcfd8;
--hover-filter:brightness(1.4);
--link-fg-color:#0df;
--link-hover-fg-color:#80ebff;
--separator-color:#52525e;
--textarea-bg-color:#42414d;
--radio-bg-color:#2b2a33;
--radio-checked-bg-color:#15141a;
--radio-checked-border-color:#0df;
--button-secondary-bg-color:rgb(251 251 254 / 0.07);
--button-secondary-active-bg-color:rgb(251 251 254 / 0.21);
--button-secondary-hover-bg-color:rgb(251 251 254 / 0.14);
--button-primary-bg-color:#0df;
--button-primary-fg-color:#15141a;
--button-primary-active-bg-color:#aaf2ff;
--button-primary-hover-bg-color:#80ebff;
--input-text-bg-color:#42414d;
}
@media screen and (forced-colors: active){
.dialog{
--dialog-bg-color:Canvas;
--dialog-border-color:CanvasText;
--dialog-shadow:none;
--text-primary-color:CanvasText;
--text-secondary-color:CanvasText;
--hover-filter:none;
--link-fg-color:LinkText;
--link-hover-fg-color:LinkText;
--separator-color:CanvasText;
--textarea-border-color:ButtonBorder;
--textarea-bg-color:Field;
--textarea-fg-color:ButtonText;
--radio-bg-color:ButtonFace;
--radio-checked-bg-color:ButtonFace;
--radio-border-color:ButtonText;
--radio-checked-border-color:ButtonText;
--button-secondary-bg-color:HighlightText;
--button-secondary-fg-color:ButtonText;
--button-secondary-border-color:ButtonText;
--button-secondary-active-bg-color:HighlightText;
--button-secondary-active-fg-color:SelectedItem;
--button-secondary-active-border-color:ButtonText;
--button-secondary-hover-bg-color:HighlightText;
--button-secondary-hover-fg-color:SelectedItem;
--button-secondary-hover-border-color:SelectedItem;
--button-primary-bg-color:ButtonText;
--button-primary-fg-color:HighlightText;
--button-primary-border-color:ButtonText;
--button-primary-active-bg-color:SelectedItem;
--button-primary-active-fg-color:HighlightText;
--button-primary-active-border-color:ButtonText;
--button-primary-hover-bg-color:SelectedItem;
--button-primary-hover-fg-color:HighlightText;
--button-primary-hover-border-color:SelectedItem;
--button-disabled-bg-color:GrayText;
--button-disabled-fg-color:ButtonFace;
--input-text-bg-color:HighlightText;
--input-text-fg-color:FieldText;
}
}
.dialog{
font:message-box;
font-size:13px;
font-weight:400;
line-height:150%;
border-radius:4px;
padding:12px 16px;
border:1px solid var(--dialog-border-color);
background:var(--dialog-bg-color);
color:var(--text-primary-color);
box-shadow:var(--dialog-shadow);
}
:is(.dialog .mainContainer) *:focus-visible{
outline:var(--focus-ring-outline);
outline-offset:2px;
}
:is(.dialog .mainContainer) .title{
display:flex;
width:auto;
flex-direction:column;
justify-content:flex-end;
align-items:flex-start;
gap:12px;
}
:is(:is(.dialog .mainContainer) .title) > span{
font-size:13px;
font-style:normal;
font-weight:590;
line-height:150%;
}
:is(.dialog .mainContainer) .dialogSeparator{
width:100%;
height:0;
margin-block:4px;
border-top:1px solid var(--separator-color);
border-bottom:none;
}
:is(.dialog .mainContainer) .dialogButtonsGroup{
display:flex;
gap:12px;
align-self:flex-end;
}
:is(.dialog .mainContainer) .radio{
display:flex;
flex-direction:column;
align-items:flex-start;
gap:4px;
}
:is(:is(.dialog .mainContainer) .radio) > .radioButton{
display:flex;
gap:8px;
align-self:stretch;
align-items:center;
}
:is(:is(:is(.dialog .mainContainer) .radio) > .radioButton) input{
-webkit-appearance:none;
-moz-appearance:none;
appearance:none;
box-sizing:border-box;
width:16px;
height:16px;
border-radius:50%;
background-color:var(--radio-bg-color);
border:1px solid var(--radio-border-color);
}
:is(:is(:is(:is(.dialog .mainContainer) .radio) > .radioButton) input):hover{
filter:var(--hover-filter);
}
:is(:is(:is(:is(.dialog .mainContainer) .radio) > .radioButton) input):checked{
background-color:var(--radio-checked-bg-color);
border:4px solid var(--radio-checked-border-color);
}
:is(:is(.dialog .mainContainer) .radio) > .radioLabel{
display:flex;
padding-inline-start:24px;
align-items:flex-start;
gap:10px;
align-self:stretch;
}
:is(:is(:is(.dialog .mainContainer) .radio) > .radioLabel) > span{
flex:1 0 0;
font-size:11px;
color:var(--text-secondary-color);
}
:is(.dialog .mainContainer) button:not(:is(.toggle-button,.closeButton,.clearInputButton)){
border-radius:4px;
border:1px solid;
font:menu;
font-weight:590;
font-size:13px;
padding:4px 16px;
width:auto;
height:32px;
}
:is(:is(.dialog .mainContainer) button:not(:is(.toggle-button,.closeButton,.clearInputButton))):hover{
cursor:pointer;
filter:var(--hover-filter);
}
:is(:is(.dialog .mainContainer) button:not(:is(.toggle-button,.closeButton,.clearInputButton))) > span{
color:inherit;
font:inherit;
}
.secondaryButton:is(:is(.dialog .mainContainer) button:not(:is(.toggle-button,.closeButton,.clearInputButton))){
color:var(--button-secondary-fg-color);
background-color:var(--button-secondary-bg-color);
border-color:var(--button-secondary-border-color);
}
.secondaryButton:is(:is(.dialog .mainContainer) button:not(:is(.toggle-button,.closeButton,.clearInputButton))):hover{
color:var(--button-secondary-hover-fg-color);
background-color:var(--button-secondary-hover-bg-color);
border-color:var(--button-secondary-hover-border-color);
}
.secondaryButton:is(:is(.dialog .mainContainer) button:not(:is(.toggle-button,.closeButton,.clearInputButton))):active{
color:var(--button-secondary-active-fg-color);
background-color:var(--button-secondary-active-bg-color);
border-color:var(--button-secondary-active-border-color);
}
.primaryButton:is(:is(.dialog .mainContainer) button:not(:is(.toggle-button,.closeButton,.clearInputButton))){
color:var(--button-primary-fg-color);
background-color:var(--button-primary-bg-color);
border-color:var(--button-primary-border-color);
opacity:1;
}
.primaryButton:is(:is(.dialog .mainContainer) button:not(:is(.toggle-button,.closeButton,.clearInputButton))):hover{
color:var(--button-primary-hover-fg-color);
background-color:var(--button-primary-hover-bg-color);
border-color:var(--button-primary-hover-border-color);
}
.primaryButton:is(:is(.dialog .mainContainer) button:not(:is(.toggle-button,.closeButton,.clearInputButton))):active{
color:var(--button-primary-active-fg-color);
background-color:var(--button-primary-active-bg-color);
border-color:var(--button-primary-active-border-color);
}
:is(:is(.dialog .mainContainer) button:not(:is(.toggle-button,.closeButton,.clearInputButton))):disabled{
color:var(--button-disabled-fg-color) !important;
background-color:var(--button-disabled-bg-color);
border-color:var(--button-disabled-bg-color);
pointer-events:none;
}
:is(.dialog .mainContainer) a{
color:var(--link-fg-color);
}
:is(:is(.dialog .mainContainer) a):hover{
color:var(--link-hover-fg-color);
}
:is(.dialog .mainContainer) textarea{
font:inherit;
padding:8px;
resize:none;
margin:0;
box-sizing:border-box;
border-radius:4px;
border:1px solid var(--textarea-border-color);
background:var(--textarea-bg-color);
color:var(--textarea-fg-color);
}
:is(:is(.dialog .mainContainer) textarea):focus{
outline-offset:0;
border-color:transparent;
}
:is(:is(.dialog .mainContainer) textarea):disabled{
pointer-events:none;
opacity:0.4;
}
:is(.dialog .mainContainer) input[type="text"]{
background-color:var(--input-text-bg-color);
color:var(--input-text-fg-color);
}
:is(.dialog .mainContainer) .messageBar{
--message-bar-bg-color:#ffebcd;
--message-bar-fg-color:#15141a;
--message-bar-border-color:rgb(0 0 0 / 0.08);
--message-bar-icon:url(images/messageBar_warning.svg);
--message-bar-icon-color:#cd411e;
}
@media (prefers-color-scheme: dark){
:where(html:not(.is-light)) :is(.dialog .mainContainer) .messageBar{
--message-bar-bg-color:#5a3100;
--message-bar-fg-color:#fbfbfe;
--message-bar-border-color:rgb(255 255 255 / 0.08);
--message-bar-icon-color:#e49c49;
}
}
:where(html.is-dark) :is(.dialog .mainContainer) .messageBar{
--message-bar-bg-color:#5a3100;
--message-bar-fg-color:#fbfbfe;
--message-bar-border-color:rgb(255 255 255 / 0.08);
--message-bar-icon-color:#e49c49;
}
@media screen and (forced-colors: active){
:is(.dialog .mainContainer) .messageBar{
--message-bar-bg-color:HighlightText;
--message-bar-fg-color:CanvasText;
--message-bar-border-color:CanvasText;
--message-bar-icon-color:CanvasText;
}
}
:is(.dialog .mainContainer) .messageBar{
align-self:stretch;
}
:is(:is(:is(.dialog .mainContainer) .messageBar) > div)::before,:is(:is(:is(.dialog .mainContainer) .messageBar) > div) > div{
margin-block:4px;
}
:is(:is(:is(.dialog .mainContainer) .messageBar) > div) > div{
display:flex;
flex-direction:column;
align-items:flex-start;
gap:8px;
flex:1 0 0;
}
:is(:is(:is(:is(.dialog .mainContainer) .messageBar) > div) > div) .title{
font-size:13px;
font-weight:590;
}
:is(:is(:is(:is(.dialog .mainContainer) .messageBar) > div) > div) .description{
font-size:13px;
}
:is(.dialog .mainContainer) .toggler{
display:flex;
align-items:center;
gap:8px;
align-self:stretch;
}
:is(:is(.dialog .mainContainer) .toggler) > .togglerLabel{
-webkit-user-select:none;
-moz-user-select:none;
user-select:none;
}
.textLayer{
position:absolute;
text-align:initial;
inset:0;
overflow:clip;
opacity:1;
line-height:1;
-webkit-text-size-adjust:none;
-moz-text-size-adjust:none;
text-size-adjust:none;
forced-color-adjust:none;
transform-origin:0 0;
caret-color:CanvasText;
z-index:0;
}
.textLayer.highlighting{
touch-action:none;
}
.textLayer :is(span,br){
color:transparent;
position:absolute;
white-space:pre;
cursor:text;
transform-origin:0% 0%;
}
.textLayer > :not(.markedContent),.textLayer .markedContent span:not(.markedContent){
z-index:1;
}
.textLayer span.markedContent{
top:0;
height:0;
}
.textLayer span[role="img"]{
-webkit-user-select:none;
-moz-user-select:none;
user-select:none;
cursor:default;
}
.textLayer .highlight{
--highlight-bg-color:rgb(180 0 170 / 0.25);
--highlight-selected-bg-color:rgb(0 100 0 / 0.25);
--highlight-backdrop-filter:none;
--highlight-selected-backdrop-filter:none;
}
@media screen and (forced-colors: active){
.textLayer .highlight{
--highlight-bg-color:transparent;
--highlight-selected-bg-color:transparent;
--highlight-backdrop-filter:var(--hcm-highlight-filter);
--highlight-selected-backdrop-filter:var(
--hcm-highlight-selected-filter
);
}
}
.textLayer .highlight{
margin:-1px;
padding:1px;
background-color:var(--highlight-bg-color);
-webkit-backdrop-filter:var(--highlight-backdrop-filter);
backdrop-filter:var(--highlight-backdrop-filter);
border-radius:4px;
}
.appended:is(.textLayer .highlight){
position:initial;
}
.begin:is(.textLayer .highlight){
border-radius:4px 0 0 4px;
}
.end:is(.textLayer .highlight){
border-radius:0 4px 4px 0;
}
.middle:is(.textLayer .highlight){
border-radius:0;
}
.selected:is(.textLayer .highlight){
background-color:var(--highlight-selected-bg-color);
-webkit-backdrop-filter:var(--highlight-selected-backdrop-filter);
backdrop-filter:var(--highlight-selected-backdrop-filter);
}
.textLayer ::-moz-selection{
background:rgba(0 0 255 / 0.25);
background:color-mix(in srgb, AccentColor, transparent 75%);
}
.textLayer ::selection{
background:rgba(0 0 255 / 0.25);
background:color-mix(in srgb, AccentColor, transparent 75%);
}
.textLayer br::-moz-selection{
background:transparent;
}
.textLayer br::selection{
background:transparent;
}
.textLayer .endOfContent{
display:block;
position:absolute;
inset:100% 0 0;
z-index:0;
cursor:default;
-webkit-user-select:none;
-moz-user-select:none;
user-select:none;
}
.textLayer.selecting .endOfContent{
top:0;
}
.annotationLayer{
--annotation-unfocused-field-background:url("data:image/svg+xml;charset=UTF-8,");
--input-focus-border-color:Highlight;
--input-focus-outline:1px solid Canvas;
--input-unfocused-border-color:transparent;
--input-disabled-border-color:transparent;
--input-hover-border-color:black;
--link-outline:none;
}
@media screen and (forced-colors: active){
.annotationLayer{
--input-focus-border-color:CanvasText;
--input-unfocused-border-color:ActiveText;
--input-disabled-border-color:GrayText;
--input-hover-border-color:Highlight;
--link-outline:1.5px solid LinkText;
}
.annotationLayer .textWidgetAnnotation :is(input,textarea):required,.annotationLayer .choiceWidgetAnnotation select:required,.annotationLayer .buttonWidgetAnnotation:is(.checkBox,.radioButton) input:required{
outline:1.5px solid selectedItem;
}
.annotationLayer .linkAnnotation{
outline:var(--link-outline);
}
:is(.annotationLayer .linkAnnotation):hover{
-webkit-backdrop-filter:var(--hcm-highlight-filter);
backdrop-filter:var(--hcm-highlight-filter);
}
:is(.annotationLayer .linkAnnotation) > a:hover{
opacity:0 !important;
background:none !important;
box-shadow:none;
}
.annotationLayer .popupAnnotation .popup{
outline:calc(1.5px * var(--total-scale-factor)) solid CanvasText !important;
background-color:ButtonFace !important;
color:ButtonText !important;
}
.annotationLayer .highlightArea:hover::after{
position:absolute;
top:0;
left:0;
width:100%;
height:100%;
-webkit-backdrop-filter:var(--hcm-highlight-filter);
backdrop-filter:var(--hcm-highlight-filter);
content:"";
pointer-events:none;
}
.annotationLayer .popupAnnotation.focused .popup{
outline:calc(3px * var(--total-scale-factor)) solid Highlight !important;
}
}
.annotationLayer{
position:absolute;
top:0;
left:0;
pointer-events:none;
transform-origin:0 0;
}
.annotationLayer[data-main-rotation="90"] .norotate{
transform:rotate(270deg) translateX(-100%);
}
.annotationLayer[data-main-rotation="180"] .norotate{
transform:rotate(180deg) translate(-100%, -100%);
}
.annotationLayer[data-main-rotation="270"] .norotate{
transform:rotate(90deg) translateY(-100%);
}
.annotationLayer.disabled section,.annotationLayer.disabled .popup{
pointer-events:none;
}
.annotationLayer .annotationContent{
position:absolute;
width:100%;
height:100%;
pointer-events:none;
}
.freetext:is(.annotationLayer .annotationContent){
background:transparent;
border:none;
inset:0;
overflow:visible;
white-space:nowrap;
font:10px sans-serif;
line-height:1.35;
}
.annotationLayer section{
position:absolute;
text-align:initial;
pointer-events:auto;
box-sizing:border-box;
transform-origin:0 0;
-webkit-user-select:none;
-moz-user-select:none;
user-select:none;
}
:is(.annotationLayer section):has(div.annotationContent) canvas.annotationContent{
display:none;
}
.textLayer.selecting ~ .annotationLayer section{
pointer-events:none;
}
.annotationLayer :is(.linkAnnotation,.buttonWidgetAnnotation.pushButton) > a{
position:absolute;
font-size:1em;
top:0;
left:0;
width:100%;
height:100%;
}
.annotationLayer :is(.linkAnnotation,.buttonWidgetAnnotation.pushButton):not(.hasBorder) > a:hover{
opacity:0.2;
background-color:rgb(255 255 0);
box-shadow:0 2px 10px rgb(255 255 0);
}
.annotationLayer .linkAnnotation.hasBorder:hover{
background-color:rgb(255 255 0 / 0.2);
}
.annotationLayer .hasBorder{
background-size:100% 100%;
}
.annotationLayer .textAnnotation img{
position:absolute;
cursor:pointer;
width:100%;
height:100%;
top:0;
left:0;
}
.annotationLayer .textWidgetAnnotation :is(input,textarea),.annotationLayer .choiceWidgetAnnotation select,.annotationLayer .buttonWidgetAnnotation:is(.checkBox,.radioButton) input{
background-image:var(--annotation-unfocused-field-background);
border:2px solid var(--input-unfocused-border-color);
box-sizing:border-box;
font:calc(9px * var(--total-scale-factor)) sans-serif;
height:100%;
margin:0;
vertical-align:top;
width:100%;
}
.annotationLayer .textWidgetAnnotation :is(input,textarea):required,.annotationLayer .choiceWidgetAnnotation select:required,.annotationLayer .buttonWidgetAnnotation:is(.checkBox,.radioButton) input:required{
outline:1.5px solid red;
}
.annotationLayer .choiceWidgetAnnotation select option{
padding:0;
}
.annotationLayer .buttonWidgetAnnotation.radioButton input{
border-radius:50%;
}
.annotationLayer .textWidgetAnnotation textarea{
resize:none;
}
.annotationLayer .textWidgetAnnotation [disabled]:is(input,textarea),.annotationLayer .choiceWidgetAnnotation select[disabled],.annotationLayer .buttonWidgetAnnotation:is(.checkBox,.radioButton) input[disabled]{
background:none;
border:2px solid var(--input-disabled-border-color);
cursor:not-allowed;
}
.annotationLayer .textWidgetAnnotation :is(input,textarea):hover,.annotationLayer .choiceWidgetAnnotation select:hover,.annotationLayer .buttonWidgetAnnotation:is(.checkBox,.radioButton) input:hover{
border:2px solid var(--input-hover-border-color);
}
.annotationLayer .textWidgetAnnotation :is(input,textarea):hover,.annotationLayer .choiceWidgetAnnotation select:hover,.annotationLayer .buttonWidgetAnnotation.checkBox input:hover{
border-radius:2px;
}
.annotationLayer .textWidgetAnnotation :is(input,textarea):focus,.annotationLayer .choiceWidgetAnnotation select:focus{
background:none;
border:2px solid var(--input-focus-border-color);
border-radius:2px;
outline:var(--input-focus-outline);
}
.annotationLayer .buttonWidgetAnnotation:is(.checkBox,.radioButton) :focus{
background-image:none;
background-color:transparent;
}
.annotationLayer .buttonWidgetAnnotation.checkBox :focus{
border:2px solid var(--input-focus-border-color);
border-radius:2px;
outline:var(--input-focus-outline);
}
.annotationLayer .buttonWidgetAnnotation.radioButton :focus{
border:2px solid var(--input-focus-border-color);
outline:var(--input-focus-outline);
}
.annotationLayer .buttonWidgetAnnotation.checkBox input:checked::before,.annotationLayer .buttonWidgetAnnotation.checkBox input:checked::after,.annotationLayer .buttonWidgetAnnotation.radioButton input:checked::before{
background-color:CanvasText;
content:"";
display:block;
position:absolute;
}
.annotationLayer .buttonWidgetAnnotation.checkBox input:checked::before,.annotationLayer .buttonWidgetAnnotation.checkBox input:checked::after{
height:80%;
left:45%;
width:1px;
}
.annotationLayer .buttonWidgetAnnotation.checkBox input:checked::before{
transform:rotate(45deg);
}
.annotationLayer .buttonWidgetAnnotation.checkBox input:checked::after{
transform:rotate(-45deg);
}
.annotationLayer .buttonWidgetAnnotation.radioButton input:checked::before{
border-radius:50%;
height:50%;
left:25%;
top:25%;
width:50%;
}
.annotationLayer .textWidgetAnnotation input.comb{
font-family:monospace;
padding-left:2px;
padding-right:0;
}
.annotationLayer .textWidgetAnnotation input.comb:focus{
width:103%;
}
.annotationLayer .buttonWidgetAnnotation:is(.checkBox,.radioButton) input{
-webkit-appearance:none;
-moz-appearance:none;
appearance:none;
}
.annotationLayer .fileAttachmentAnnotation .popupTriggerArea{
height:100%;
width:100%;
}
.annotationLayer .popupAnnotation{
position:absolute;
font-size:calc(9px * var(--total-scale-factor));
pointer-events:none;
width:-moz-max-content;
width:max-content;
max-width:45%;
height:auto;
}
.annotationLayer .popup{
background-color:rgb(255 255 153);
box-shadow:0 calc(2px * var(--total-scale-factor)) calc(5px * var(--total-scale-factor)) rgb(136 136 136);
border-radius:calc(2px * var(--total-scale-factor));
outline:1.5px solid rgb(255 255 74);
padding:calc(6px * var(--total-scale-factor));
cursor:pointer;
font:message-box;
white-space:normal;
word-wrap:break-word;
pointer-events:auto;
-webkit-user-select:text;
-moz-user-select:text;
user-select:text;
}
.annotationLayer .popupAnnotation.focused .popup{
outline-width:3px;
}
.annotationLayer .popup *{
font-size:calc(9px * var(--total-scale-factor));
}
.annotationLayer .popup > .header{
display:inline-block;
}
.annotationLayer .popup > .header h1{
display:inline;
}
.annotationLayer .popup > .header .popupDate{
display:inline-block;
margin-left:calc(5px * var(--total-scale-factor));
width:-moz-fit-content;
width:fit-content;
}
.annotationLayer .popupContent{
border-top:1px solid rgb(51 51 51);
margin-top:calc(2px * var(--total-scale-factor));
padding-top:calc(2px * var(--total-scale-factor));
}
.annotationLayer .richText > *{
white-space:pre-wrap;
font-size:calc(9px * var(--total-scale-factor));
}
.annotationLayer .popupTriggerArea{
cursor:pointer;
}
.annotationLayer section svg{
position:absolute;
width:100%;
height:100%;
top:0;
left:0;
}
.annotationLayer .annotationTextContent{
position:absolute;
width:100%;
height:100%;
opacity:0;
color:transparent;
-webkit-user-select:none;
-moz-user-select:none;
user-select:none;
pointer-events:none;
}
:is(.annotationLayer .annotationTextContent) span{
width:100%;
display:inline-block;
}
.annotationLayer svg.quadrilateralsContainer{
contain:strict;
width:0;
height:0;
position:absolute;
top:0;
left:0;
z-index:-1;
}
:root{
--xfa-unfocused-field-background:url("data:image/svg+xml;charset=UTF-8,");
--xfa-focus-outline:auto;
}
@media screen and (forced-colors: active){
:root{
--xfa-focus-outline:2px solid CanvasText;
}
.xfaLayer *:required{
outline:1.5px solid selectedItem;
}
}
.xfaLayer{
background-color:transparent;
}
.xfaLayer .highlight{
margin:-1px;
padding:1px;
background-color:rgb(239 203 237);
border-radius:4px;
}
.xfaLayer .highlight.appended{
position:initial;
}
.xfaLayer .highlight.begin{
border-radius:4px 0 0 4px;
}
.xfaLayer .highlight.end{
border-radius:0 4px 4px 0;
}
.xfaLayer .highlight.middle{
border-radius:0;
}
.xfaLayer .highlight.selected{
background-color:rgb(203 223 203);
}
.xfaPage{
overflow:hidden;
position:relative;
}
.xfaContentarea{
position:absolute;
}
.xfaPrintOnly{
display:none;
}
.xfaLayer{
position:absolute;
text-align:initial;
top:0;
left:0;
transform-origin:0 0;
line-height:1.2;
}
.xfaLayer *{
color:inherit;
font:inherit;
font-style:inherit;
font-weight:inherit;
font-kerning:inherit;
letter-spacing:-0.01px;
text-align:inherit;
text-decoration:inherit;
box-sizing:border-box;
background-color:transparent;
padding:0;
margin:0;
pointer-events:auto;
line-height:inherit;
}
.xfaLayer *:required{
outline:1.5px solid red;
}
.xfaLayer div,
.xfaLayer svg,
.xfaLayer svg *{
pointer-events:none;
}
.xfaLayer a{
color:blue;
}
.xfaRich li{
margin-left:3em;
}
.xfaFont{
color:black;
font-weight:normal;
font-kerning:none;
font-size:10px;
font-style:normal;
letter-spacing:0;
text-decoration:none;
vertical-align:0;
}
.xfaCaption{
overflow:hidden;
flex:0 0 auto;
}
.xfaCaptionForCheckButton{
overflow:hidden;
flex:1 1 auto;
}
.xfaLabel{
height:100%;
width:100%;
}
.xfaLeft{
display:flex;
flex-direction:row;
align-items:center;
}
.xfaRight{
display:flex;
flex-direction:row-reverse;
align-items:center;
}
:is(.xfaLeft, .xfaRight) > :is(.xfaCaption, .xfaCaptionForCheckButton){
max-height:100%;
}
.xfaTop{
display:flex;
flex-direction:column;
align-items:flex-start;
}
.xfaBottom{
display:flex;
flex-direction:column-reverse;
align-items:flex-start;
}
:is(.xfaTop, .xfaBottom) > :is(.xfaCaption, .xfaCaptionForCheckButton){
width:100%;
}
.xfaBorder{
background-color:transparent;
position:absolute;
pointer-events:none;
}
.xfaWrapped{
width:100%;
height:100%;
}
:is(.xfaTextfield, .xfaSelect):focus{
background-image:none;
background-color:transparent;
outline:var(--xfa-focus-outline);
outline-offset:-1px;
}
:is(.xfaCheckbox, .xfaRadio):focus{
outline:var(--xfa-focus-outline);
}
.xfaTextfield,
.xfaSelect{
height:100%;
width:100%;
flex:1 1 auto;
border:none;
resize:none;
background-image:var(--xfa-unfocused-field-background);
}
.xfaSelect{
padding-inline:2px;
}
:is(.xfaTop, .xfaBottom) > :is(.xfaTextfield, .xfaSelect){
flex:0 1 auto;
}
.xfaButton{
cursor:pointer;
width:100%;
height:100%;
border:none;
text-align:center;
}
.xfaLink{
width:100%;
height:100%;
position:absolute;
top:0;
left:0;
}
.xfaCheckbox,
.xfaRadio{
width:100%;
height:100%;
flex:0 0 auto;
border:none;
}
.xfaRich{
white-space:pre-wrap;
width:100%;
height:100%;
}
.xfaImage{
-o-object-position:left top;
object-position:left top;
-o-object-fit:contain;
object-fit:contain;
width:100%;
height:100%;
}
.xfaLrTb,
.xfaRlTb,
.xfaTb{
display:flex;
flex-direction:column;
align-items:stretch;
}
.xfaLr{
display:flex;
flex-direction:row;
align-items:stretch;
}
.xfaRl{
display:flex;
flex-direction:row-reverse;
align-items:stretch;
}
.xfaTb > div{
justify-content:left;
}
.xfaPosition{
position:relative;
}
.xfaArea{
position:relative;
}
.xfaValignMiddle{
display:flex;
align-items:center;
}
.xfaTable{
display:flex;
flex-direction:column;
align-items:stretch;
}
.xfaTable .xfaRow{
display:flex;
flex-direction:row;
align-items:stretch;
}
.xfaTable .xfaRlRow{
display:flex;
flex-direction:row-reverse;
align-items:stretch;
flex:1;
}
.xfaTable .xfaRlRow > div{
flex:1;
}
:is(.xfaNonInteractive, .xfaDisabled, .xfaReadOnly) :is(input, textarea){
background:initial;
}
@media print{
.xfaTextfield,
.xfaSelect{
background:transparent;
}
.xfaSelect{
-webkit-appearance:none;
-moz-appearance:none;
appearance:none;
text-indent:1px;
text-overflow:"";
}
}
.canvasWrapper svg{
transform:none;
}
.moving:is(.canvasWrapper svg){
z-index:100000;
}
[data-main-rotation="90"]:is(.highlight:is(.canvasWrapper svg),.highlightOutline:is(.canvasWrapper svg)) mask,[data-main-rotation="90"]:is(.highlight:is(.canvasWrapper svg),.highlightOutline:is(.canvasWrapper svg)) use:not(.clip,.mask){
transform:matrix(0, 1, -1, 0, 1, 0);
}
[data-main-rotation="180"]:is(.highlight:is(.canvasWrapper svg),.highlightOutline:is(.canvasWrapper svg)) mask,[data-main-rotation="180"]:is(.highlight:is(.canvasWrapper svg),.highlightOutline:is(.canvasWrapper svg)) use:not(.clip,.mask){
transform:matrix(-1, 0, 0, -1, 1, 1);
}
[data-main-rotation="270"]:is(.highlight:is(.canvasWrapper svg),.highlightOutline:is(.canvasWrapper svg)) mask,[data-main-rotation="270"]:is(.highlight:is(.canvasWrapper svg),.highlightOutline:is(.canvasWrapper svg)) use:not(.clip,.mask){
transform:matrix(0, -1, 1, 0, 0, 1);
}
.draw:is(.canvasWrapper svg){
position:absolute;
mix-blend-mode:normal;
}
.draw[data-draw-rotation="90"]:is(.canvasWrapper svg){
transform:rotate(90deg);
}
.draw[data-draw-rotation="180"]:is(.canvasWrapper svg){
transform:rotate(180deg);
}
.draw[data-draw-rotation="270"]:is(.canvasWrapper svg){
transform:rotate(270deg);
}
.highlight:is(.canvasWrapper svg){
--blend-mode:multiply;
}
@media screen and (forced-colors: active){
.highlight:is(.canvasWrapper svg){
--blend-mode:difference;
}
}
.highlight:is(.canvasWrapper svg){
position:absolute;
mix-blend-mode:var(--blend-mode);
}
.highlight:is(.canvasWrapper svg):not(.free){
fill-rule:evenodd;
}
.highlightOutline:is(.canvasWrapper svg){
position:absolute;
mix-blend-mode:normal;
fill-rule:evenodd;
fill:none;
}
.highlightOutline.hovered:is(.canvasWrapper svg):not(.free):not(.selected){
stroke:var(--hover-outline-color);
stroke-width:var(--outline-width);
}
.highlightOutline.selected:is(.canvasWrapper svg):not(.free) .mainOutline{
stroke:var(--outline-around-color);
stroke-width:calc(
var(--outline-width) + 2 * var(--outline-around-width)
);
}
.highlightOutline.selected:is(.canvasWrapper svg):not(.free) .secondaryOutline{
stroke:var(--outline-color);
stroke-width:var(--outline-width);
}
.highlightOutline.free.hovered:is(.canvasWrapper svg):not(.selected){
stroke:var(--hover-outline-color);
stroke-width:calc(2 * var(--outline-width));
}
.highlightOutline.free.selected:is(.canvasWrapper svg) .mainOutline{
stroke:var(--outline-around-color);
stroke-width:calc(
2 * (var(--outline-width) + var(--outline-around-width))
);
}
.highlightOutline.free.selected:is(.canvasWrapper svg) .secondaryOutline{
stroke:var(--outline-color);
stroke-width:calc(2 * var(--outline-width));
}
.toggle-button{
--button-background-color:color-mix(in srgb, currentColor 7%, transparent);
--button-background-color-hover:color-mix(
in srgb,
currentColor 14%,
transparent
);
--button-background-color-active:color-mix(
in srgb,
currentColor 21%,
transparent
);
--color-accent-primary:#0060df;
--color-accent-primary-hover:#0250bb;
--color-accent-primary-active:#054096;
--border-radius-circle:9999px;
--border-width:1px;
--size-item-small:16px;
--size-item-large:32px;
--color-canvas:white;
--background-color-canvas:var(--color-canvas);
--border-color-interactive:#8f8f9d;
--border-color-interactive-hover:var(--border-color-interactive);
--border-color-interactive-active:var(--border-color-interactive);
}
@media (prefers-color-scheme: dark){
:where(html:not(.is-light)) .toggle-button{
--color-accent-primary:#0df;
--color-accent-primary-hover:#80ebff;
--color-accent-primary-active:#aaf2ff;
--color-canvas:#1c1b22;
--border-color-interactive:#f9f9fa;
}
}
:where(html.is-dark) .toggle-button{
--color-accent-primary:#0df;
--color-accent-primary-hover:#80ebff;
--color-accent-primary-active:#aaf2ff;
--color-canvas:#1c1b22;
--border-color-interactive:#f9f9fa;
}
@media (forced-colors: active){
.toggle-button{
--color-accent-primary:ButtonText;
--color-accent-primary-hover:SelectedItem;
--color-accent-primary-active:SelectedItem;
--button-background-color:ButtonFace;
--border-color-interactive:ButtonText;
--border-color-interactive-hover:SelectedItem;
--border-color-interactive-active:ButtonText;
--color-canvas:ButtonText;
--background-color-canvas:Canvas;
}
}
.toggle-button{
--toggle-background-color:var(--button-background-color);
--toggle-background-color-hover:var(--button-background-color-hover);
--toggle-background-color-active:var(--button-background-color-active);
--toggle-background-color-pressed:var(--color-accent-primary);
--toggle-background-color-pressed-hover:var(--color-accent-primary-hover);
--toggle-background-color-pressed-active:var(--color-accent-primary-active);
--toggle-border-color:var(--border-color-interactive);
--toggle-border-color-hover:var(--toggle-border-color);
--toggle-border-color-active:var(--toggle-border-color);
--toggle-border-radius:var(--border-radius-circle);
--toggle-border-width:var(--border-width);
--toggle-height:var(--size-item-small);
--toggle-width:var(--size-item-large);
--toggle-dot-background-color:var(--toggle-border-color);
--toggle-dot-background-color-hover:var(--toggle-dot-background-color);
--toggle-dot-background-color-active:var(--toggle-dot-background-color);
--toggle-dot-background-color-on-pressed:var(--background-color-canvas);
--toggle-dot-margin:1px;
--toggle-dot-height:calc(
var(--toggle-height) - 2 * var(--toggle-dot-margin) - 2 *
var(--toggle-border-width)
);
--toggle-dot-width:var(--toggle-dot-height);
--toggle-dot-transform-x:calc(
var(--toggle-width) - 4 * var(--toggle-dot-margin) - var(--toggle-dot-width)
);
--input-width:var(--toggle-width);
-webkit-appearance:none;
-moz-appearance:none;
appearance:none;
padding:0;
border:var(--toggle-border-width) solid var(--toggle-border-color);
height:var(--toggle-height);
width:var(--toggle-width);
border-radius:var(--toggle-border-radius);
background-color:var(--toggle-background-color);
box-sizing:border-box;
}
.toggle-button:focus-visible{
outline:var(--focus-outline);
outline-offset:var(--focus-outline-offset);
}
.toggle-button:enabled:hover{
background-color:var(--toggle-background-color-hover);
border-color:var(--toggle-border-color);
}
.toggle-button:enabled:hover:active{
background-color:var(--toggle-background-color-active);
border-color:var(--toggle-border-color);
}
.toggle-button::before{
display:block;
content:"";
background-color:var(--toggle-dot-background-color);
height:var(--toggle-dot-height);
width:var(--toggle-dot-width);
margin:var(--toggle-dot-margin);
border-radius:var(--toggle-border-radius);
translate:0;
}
.toggle-button[aria-pressed="true"]{
background-color:var(--toggle-background-color-pressed);
border-color:transparent;
}
.toggle-button[aria-pressed="true"]:enabled:hover{
background-color:var(--toggle-background-color-pressed-hover);
border-color:transparent;
}
.toggle-button[aria-pressed="true"]:enabled:hover:active{
background-color:var(--toggle-background-color-pressed-active);
border-color:transparent;
}
.toggle-button[aria-pressed="true"]::before{
translate:var(--toggle-dot-transform-x);
background-color:var(--toggle-dot-background-color-on-pressed);
}
.toggle-button[aria-pressed="true"]:enabled:hover::before,.toggle-button[aria-pressed="true"]:enabled:hover:active::before{
background-color:var(--toggle-dot-background-color-on-pressed);
}
.toggle-button[aria-pressed="true"]:-moz-locale-dir(rtl)::before,[dir="rtl"] .toggle-button[aria-pressed="true"]::before{
translate:calc(-1 * var(--toggle-dot-transform-x));
}
@media (prefers-reduced-motion: no-preference){
.toggle-button::before{
transition:translate 100ms;
}
}
@media (prefers-contrast){
.toggle-button:enabled:hover{
border-color:var(--toggle-border-color-hover);
}
.toggle-button:enabled:hover:active{
border-color:var(--toggle-border-color-active);
}
.toggle-button[aria-pressed="true"]:enabled{
border-color:var(--toggle-border-color);
position:relative;
}
.toggle-button[aria-pressed="true"]:enabled:hover{
border-color:var(--toggle-border-color-hover);
}
.toggle-button[aria-pressed="true"]:enabled:hover:active{
background-color:var(--toggle-dot-background-color-active);
border-color:var(--toggle-dot-background-color-hover);
}
.toggle-button:enabled:hover::before,
.toggle-button:enabled:hover:active::before{
background-color:var(--toggle-dot-background-color-hover);
}
}
@media (forced-colors){
.toggle-button{
--toggle-dot-background-color:var(--color-accent-primary);
--toggle-dot-background-color-hover:var(--color-accent-primary-hover);
--toggle-dot-background-color-active:var(--color-accent-primary-active);
--toggle-dot-background-color-on-pressed:var(--button-background-color);
--toggle-border-color-hover:var(--border-color-interactive-hover);
--toggle-border-color-active:var(--border-color-interactive-active);
}
.toggle-button[aria-pressed="true"]:enabled::after{
border:1px solid var(--button-background-color);
content:"";
position:absolute;
height:var(--toggle-height);
width:var(--toggle-width);
display:block;
border-radius:var(--toggle-border-radius);
inset:-2px;
}
.toggle-button[aria-pressed="true"]:enabled:hover:active::after{
border-color:var(--toggle-border-color-active);
}
}
:root{
--clear-signature-button-icon:url(images/editor-toolbar-delete.svg);
--signature-bg:#f9f9fb;
--signature-hover-bg:#f0f0f4;
--button-signature-bg:transparent;
--button-signature-color:var(--main-color);
--button-signature-active-bg:#cfcfd8;
--button-signature-active-border:none;
--button-signature-active-color:var(--button-signature-color);
--button-signature-border:none;
--button-signature-hover-bg:#e0e0e6;
--button-signature-hover-color:var(--button-signature-color);
}
@media (prefers-color-scheme: dark){
:root:where(:not(.is-light)){
--signature-bg:#2b2a33;
--signature-hover-bg:var(--signature-bg);
--button-signature-active-bg:#5b5b66;
--button-signature-hover-bg:#52525e;
}
}
:root:where(.is-dark){
--signature-bg:#2b2a33;
--signature-hover-bg:var(--signature-bg);
--button-signature-active-bg:#5b5b66;
--button-signature-hover-bg:#52525e;
}
@media screen and (forced-colors: active){
:root{
--signature-bg:HighlightText;
--signature-hover-bg:var(--signature-bg);
--button-signature-bg:HighlightText;
--button-signature-color:ButtonText;
--button-signature-active-bg:ButtonText;
--button-signature-active-color:HighlightText;
--button-signature-border:1px solid ButtonText;
--button-signature-hover-bg:Highlight;
--button-signature-hover-color:HighlightText;
}
}
.signatureDialog{
--primary-color:var(--text-primary-color);
--border-color:#8f8f9d;
--open-link-fg:var(--link-fg-color);
--open-link-hover-fg:var(--link-hover-fg-color);
}
@media screen and (forced-colors: active){
.signatureDialog{
--primary-color:ButtonText;
--border-color:ButtonText;
--open-link-fg:ButtonText;
--open-link-hover-fg:ButtonText;
}
}
.signatureDialog{
width:570px;
max-width:100%;
min-width:300px;
padding:16px 0;
}
.signatureDialog .mainContainer{
width:100%;
display:flex;
flex-direction:column;
align-items:flex-start;
gap:12px;
}
:is(.signatureDialog .mainContainer) span:not([role="sectionhead"]){
font-size:13px;
font-style:normal;
font-weight:400;
line-height:normal;
}
:is(.signatureDialog .mainContainer) .title{
margin-inline-start:16px;
}
.signatureDialog .inputWithClearButton{
--button-dimension:24px;
--clear-button-icon:url(images/messageBar_closingButton.svg);
width:100%;
position:relative;
display:flex;
align-items:center;
justify-content:center;
}
:is(.signatureDialog .inputWithClearButton) > input{
width:100%;
height:32px;
padding-inline:8px calc(4px + var(--button-dimension));
box-sizing:border-box;
border-radius:4px;
border:1px solid var(--border-color);
}
:is(.signatureDialog .inputWithClearButton) .clearInputButton{
position:absolute;
inset-block-start:4px;
inset-inline-end:4px;
display:inline-block;
width:var(--button-dimension);
height:var(--button-dimension);
background-color:var(--input-text-fg-color);
-webkit-mask-size:cover;
mask-size:cover;
-webkit-mask-image:var(--clear-button-icon);
mask-image:var(--clear-button-icon);
padding:0;
border:0;
}
#addSignatureDialog{
--secondary-color:var(--text-secondary-color);
--bg-hover:#e0e0e6;
--tab-top-line-active-color:#0060df;
--tab-top-line-active-hover-color:var(--tab-text-hover-color);
--tab-top-line-hover-color:#8f8f9d;
--tab-top-line-inactive-color:#cfcfd8;
--tab-bottom-line-active-color:var(--tab-top-line-inactive-color);
--tab-bottom-line-hover-color:var(--tab-top-line-inactive-color);
--tab-bottom-line-inactive-color:var(--tab-top-line-inactive-color);
--tab-bg:var(--dialog-bg-color);
--tab-bg-active-color:var(--tab-bg);
--tab-bg-active-hover-color:var(--bg-hover);
--tab-bg-hover:var(--bg-hover);
--tab-panel-border:none;
--tab-panel-border-radius:4px;
--tab-text-color:var(--primary-color);
--tab-text-active-color:var(--tab-top-line-active-color);
--tab-text-active-hover-color:var(--tab-text-hover-color);
--tab-text-hover-color:var(--tab-text-color);
--signature-placeholder-color:var(--secondary-color);
--signature-draw-placeholder-color:var(--primary-color);
--signature-color:var(--primary-color);
--clear-signature-button-border-width:0;
--clear-signature-button-border-style:solid;
--clear-signature-button-border-color:transparent;
--clear-signature-button-border-disabled-color:transparent;
--clear-signature-button-color:var(--primary-color);
--clear-signature-button-hover-color:var(--clear-signature-button-color);
--clear-signature-button-active-color:var(--clear-signature-button-color);
--clear-signature-button-disabled-color:var(--clear-signature-button-color);
--clear-signature-button-focus-color:var(--clear-signature-button-color);
--clear-signature-button-bg:var(--dialog-bg-color);
--clear-signature-button-bg-hover:var(--bg-hover);
--clear-signature-button-bg-active:#cfcfd8;
--clear-signature-button-bg-focus:#f0f0f4;
--clear-signature-button-bg-disabled:color-mix(
in srgb,
#f0f0f4,
transparent 40%
);
--save-warning-color:var(--secondary-color);
--thickness-bg:var(--dialog-bg-color);
--thickness-label-color:var(--primary-color);
--thickness-slider-color:var(--primary-color);
--draw-cursor:url(images/cursor-editorInk.svg) 0 16, pointer;
}
@media (prefers-color-scheme: dark){
:where(html:not(.is-light)) #addSignatureDialog{
--dialog-bg-color:#42414d;
--bg-hover:#52525e;
--primary-color:#fbfbfe;
--secondary-color:#cfcfd8;
--tab-top-line-active-color:#0df;
--tab-top-line-inactive-color:#8f8f9d;
--clear-signature-button-bg-active:#5b5b66;
--clear-signature-button-bg-focus:#2b2a33;
--clear-signature-button-bg-disabled:color-mix(
in srgb,
#2b2a33,
transparent 40%
);
}
}
:where(html.is-dark) #addSignatureDialog{
--dialog-bg-color:#42414d;
--bg-hover:#52525e;
--primary-color:#fbfbfe;
--secondary-color:#cfcfd8;
--tab-top-line-active-color:#0df;
--tab-top-line-inactive-color:#8f8f9d;
--clear-signature-button-bg-active:#5b5b66;
--clear-signature-button-bg-focus:#2b2a33;
--clear-signature-button-bg-disabled:color-mix(
in srgb,
#2b2a33,
transparent 40%
);
}
@media screen and (forced-colors: active){
#addSignatureDialog{
--secondary-color:ButtonText;
--bg:HighlightText;
--bg-hover:var(--bg);
--tab-top-line-active-color:ButtonText;
--tab-top-line-active-hover-color:HighlightText;
--tab-top-line-hover-color:SelectedItem;
--tab-top-line-inactive-color:ButtonText;
--tab-bottom-line-active-color:var(--tab-top-line-active-color);
--tab-bottom-line-hover-color:var(--tab-top-line-hover-color);
--tab-bg:var(--bg);
--tab-bg-active-color:SelectedItem;
--tab-bg-active-hover-color:SelectedItem;
--tab-panel-border:1px solid ButtonText;
--tab-panel-border-radius:8px;
--tab-text-color:ButtonText;
--tab-text-active-color:HighlightText;
--tab-text-active-hover-color:HighlightText;
--tab-text-hover-color:SelectedItem;
--signature-color:ButtonText;
--clear-signature-button-border-width:1px;
--clear-signature-button-border-style:solid;
--clear-signature-button-border-color:ButtonText;
--clear-signature-button-border-disabled-color:GrayText;
--clear-signature-button-color:ButtonText;
--clear-signature-button-hover-color:HighlightText;
--clear-signature-button-active-color:SelectedItem;
--clear-signature-button-focus-color:CanvasText;
--clear-signature-button-disabled-color:GrayText;
--clear-signature-button-bg:var(--bg);
--clear-signature-button-bg-hover:SelectedItem;
--clear-signature-button-bg-active:var(--bg);
--clear-signature-button-bg-focus:var(--bg);
--clear-signature-button-bg-disabled:var(--bg);
--thickness-bg:Canvas;
--thickness-label-color:CanvasText;
--thickness-slider-color:ButtonText;
}
}
#addSignatureDialog #addSignatureDialogLabel{
overflow:hidden;
position:absolute;
inset:0;
width:0;
height:0;
}
#addSignatureDialog.waiting::after{
content:"";
cursor:wait;
position:absolute;
inset:0;
width:100%;
height:100%;
}
:is(#addSignatureDialog .mainContainer) [role="tablist"]{
width:100%;
display:flex;
align-items:flex-start;
gap:0;
}
:is(:is(#addSignatureDialog .mainContainer) [role="tablist"]) > [role="tab"]{
flex:1 0 0;
align-self:stretch;
background-color:var(--tab-bg);
padding-inline:0;
cursor:default;
border-inline:0;
border-block-width:1px;
border-block-style:solid;
border-block-start-color:var(--tab-top-line-inactive-color);
border-block-end-color:var(--tab-bottom-line-inactive-color);
border-radius:0;
font:menu;
font-size:13px;
font-style:normal;
line-height:normal;
font-weight:400;
color:var(--tab-text-color);
}
:is(:is(:is(#addSignatureDialog .mainContainer) [role="tablist"]) > [role="tab"]):hover{
border-block-start-width:2px;
border-block-start-color:var(--tab-top-line-hover-color);
border-block-end-color:var(--tab-bottom-line-hover-color);
background-color:var(--tab-bg-hover);
color:var(--tab-text-hover-color);
}
:is(:is(:is(#addSignatureDialog .mainContainer) [role="tablist"]) > [role="tab"]):focus-visible{
outline:2px solid var(--tab-top-line-active-color);
outline-offset:-2px;
}
[aria-selected="true"]:is(:is(:is(#addSignatureDialog .mainContainer) [role="tablist"]) > [role="tab"]){
border-block-start-width:2px;
border-block-start-color:var(--tab-top-line-active-color);
border-block-end-color:var(--tab-bottom-line-active-color);
background-color:var(--tab-bg-active-color);
font-weight:590;
color:var(--tab-text-active-color);
}
[aria-selected="true"]:is(:is(:is(#addSignatureDialog .mainContainer) [role="tablist"]) > [role="tab"]):hover{
border-block-start-color:var(--tab-top-line-active-hover-color);
background-color:var(--tab-bg-active-hover-color);
color:var(--tab-text-active-hover-color);
}
:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer{
width:100%;
height:auto;
display:flex;
flex-direction:column;
align-items:flex-end;
align-self:stretch;
gap:12px;
padding-inline:16px;
box-sizing:border-box;
}
:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]{
position:relative;
width:100%;
height:220px;
background-color:var(--signature-bg);
border:var(--tab-panel-border);
border-radius:var(--tab-panel-border-radius);
}
:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) > svg{
position:absolute;
inset:0;
width:100%;
height:100%;
background-color:transparent;
}
#addSignatureTypeContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]){
display:none;
}
#addSignatureTypeContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) #addSignatureTypeInput{
position:absolute;
inset:0;
width:100%;
height:100%;
border:0;
padding:0;
text-align:center;
color:var(--signature-color);
background-color:transparent;
border-radius:var(--tab-panel-border-radius);
font-family:"Brush script", "Apple Chancery", "Segoe script", "Freestyle Script", "Palace Script MT", "Brush Script MT", TK, cursive, serif;
font-size:44px;
font-style:italic;
font-weight:400;
}
:is(#addSignatureTypeContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) #addSignatureTypeInput)::-moz-placeholder{
color:var(--signature-placeholder-color);
text-align:center;
font:menu;
font-style:normal;
font-weight:274;
font-size:44px;
line-height:normal;
}
:is(#addSignatureTypeContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) #addSignatureTypeInput)::placeholder{
color:var(--signature-placeholder-color);
text-align:center;
font:menu;
font-style:normal;
font-weight:274;
font-size:44px;
line-height:normal;
}
#addSignatureDrawContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]){
display:none;
}
#addSignatureDrawContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) > span{
position:absolute;
top:0;
left:0;
width:100%;
height:100%;
display:grid;
align-items:center;
justify-content:center;
background-color:transparent;
color:var(--signature-placeholder-color);
-webkit-user-select:none;
-moz-user-select:none;
user-select:none;
}
#addSignatureDrawContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) > svg{
stroke:var(--signature-color);
fill:none;
stroke-opacity:1;
stroke-linecap:round;
stroke-linejoin:round;
stroke-miterlimit:10;
}
:is(#addSignatureDrawContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) > svg):hover{
cursor:var(--draw-cursor);
}
#addSignatureDrawContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) #thickness{
position:absolute;
width:100%;
inset-block-end:0;
display:grid;
align-items:center;
justify-content:center;
pointer-events:none;
}
:is(#addSignatureDrawContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) #thickness) > span{
color:var(--signature-draw-placeholder-color);
}
:is(#addSignatureDrawContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) #thickness) > div{
width:auto;
height:auto;
display:flex;
align-items:center;
justify-content:center;
gap:8px;
padding:6px 8px;
margin:0;
background-color:var(--thickness-bg);
border-radius:4px 4px 0 0;
pointer-events:auto;
}
:is(:is(#addSignatureDrawContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) #thickness) > div) > label{
color:var(--thickness-label-color);
}
:is(:is(#addSignatureDrawContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) #thickness) > div) > input{
width:100px;
height:14px;
background-color:transparent;
}
:is(:is(:is(#addSignatureDrawContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) #thickness) > div) > input)::-webkit-slider-runnable-track,:is(:is(:is(#addSignatureDrawContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) #thickness) > div) > input)::-moz-range-track,:is(:is(:is(#addSignatureDrawContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) #thickness) > div) > input)::-moz-range-progress{
background-color:var(--thickness-slider-color);
}
:is(:is(:is(#addSignatureDrawContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) #thickness) > div) > input)::-webkit-slider-thumb,:is(:is(:is(#addSignatureDrawContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) #thickness) > div) > input)::-moz-range-thumb{
background-color:var(--thickness-bg);
}
:is(:is(#addSignatureDrawContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) #thickness) > div) > input{
border-radius:4.5px;
border:0;
color:var(--signature-color);
}
#addSignatureImageContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]){
display:none;
}
#addSignatureImageContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) > svg{
stroke:none;
stroke-width:0;
fill:var(--signature-color);
fill-opacity:1;
}
#addSignatureImageContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) #addSignatureImagePlaceholder{
position:absolute;
top:0;
left:0;
width:100%;
height:100%;
background-color:transparent;
display:flex;
flex-direction:column;
align-items:center;
justify-content:center;
}
:is(#addSignatureImageContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) #addSignatureImagePlaceholder) span{
color:var(--signature-placeholder-color);
}
:is(#addSignatureImageContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) #addSignatureImagePlaceholder) a{
color:var(--open-link-fg);
text-decoration:underline;
cursor:pointer;
}
:is(:is(#addSignatureImageContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) #addSignatureImagePlaceholder) a):hover{
color:var(--open-link-hover-fg);
}
#addSignatureImageContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) #addSignatureFilePicker{
visibility:hidden;
position:relative;
width:0;
height:0;
}
[data-selected="type"]:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > #addSignatureTypeContainer,[data-selected="draw"]:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > #addSignatureDrawContainer,[data-selected="image"]:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > #addSignatureImageContainer{
display:block;
}
:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls{
display:flex;
flex-direction:column;
justify-content:center;
align-items:flex-start;
gap:12px;
align-self:stretch;
}
:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer{
display:flex;
align-items:flex-end;
gap:16px;
align-self:stretch;
}
:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer) #addSignatureDescriptionContainer{
display:flex;
flex-direction:column;
align-items:flex-start;
gap:4px;
flex:1 0 0;
}
:is(:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer) #addSignatureDescriptionContainer):has(input:disabled) > label{
opacity:0.4;
}
:is(:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer) #addSignatureDescriptionContainer) > label{
width:auto;
}
:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer) #clearSignatureButton{
display:flex;
height:32px;
padding:4px 8px;
align-items:center;
background-color:var(--clear-signature-button-bg);
border-width:var(--clear-signature-button-border-width);
border-style:var(--clear-signature-button-border-style);
border-color:var(--clear-signature-button-border-color);
border-radius:4px;
}
:is(:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer) #clearSignatureButton) > span{
display:flex;
height:24px;
align-items:center;
gap:4px;
flex-shrink:0;
color:var(--clear-signature-button-color);
}
:is(:is(:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer) #clearSignatureButton) > span)::after{
content:"";
display:inline-block;
width:16px;
height:16px;
-webkit-mask-image:var(--clear-signature-button-icon);
mask-image:var(--clear-signature-button-icon);
-webkit-mask-size:cover;
mask-size:cover;
background-color:var(--clear-signature-button-color);
flex-shrink:0;
}
:is(:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer) #clearSignatureButton):hover{
background-color:var(--clear-signature-button-bg-hover);
}
:is(:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer) #clearSignatureButton):hover > span{
color:var(--clear-signature-button-hover-color);
}
:is(:is(:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer) #clearSignatureButton):hover > span)::after{
background-color:var(--clear-signature-button-hover-color);
}
:is(:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer) #clearSignatureButton):active{
background-color:var(--clear-signature-button-bg-active);
}
:is(:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer) #clearSignatureButton):active > span{
color:var(--clear-signature-button-active-color);
}
:is(:is(:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer) #clearSignatureButton):active > span)::after{
background-color:var(--clear-signature-button-active-color);
}
:is(:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer) #clearSignatureButton):focus-visible{
background-color:var(--clear-signature-button-bg-focus);
}
:is(:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer) #clearSignatureButton):focus-visible > span{
color:var(--clear-signature-button-focus-color);
}
:is(:is(:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer) #clearSignatureButton):focus-visible > span)::after{
background-color:var(--clear-signature-button-focus-color);
}
:is(:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer) #clearSignatureButton):disabled{
background-color:var(--clear-signature-button-bg-disabled);
border-color:var(--clear-signature-button-border-disabled-color);
}
:is(:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer) #clearSignatureButton):disabled > span{
color:var(--clear-signature-button-disabled-color);
}
:is(:is(:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer) #clearSignatureButton):disabled > span)::after{
background-color:var(
--clear-signature-button-disabled-color
);
}
:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #addSignatureSaveContainer{
display:grid;
grid-template-columns:max-content auto;
gap:4px;
width:100%;
}
:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #addSignatureSaveContainer) > input{
margin:0;
}
:is(:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #addSignatureSaveContainer) > input):disabled + label{
opacity:0.4;
}
:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #addSignatureSaveContainer) > label{
-webkit-user-select:none;
-moz-user-select:none;
user-select:none;
}
:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #addSignatureSaveContainer):not(.fullStorage) #addSignatureSaveWarning{
display:none;
}
.fullStorage:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #addSignatureSaveContainer) #addSignatureSaveWarning{
display:block;
opacity:1;
color:var(--save-warning-color);
font-size:11px;
}
#editSignatureDescriptionDialog .mainContainer{
padding-inline:16px;
box-sizing:border-box;
}
:is(#editSignatureDescriptionDialog .mainContainer) .title{
margin-inline-start:0;
}
:is(#editSignatureDescriptionDialog .mainContainer) #editSignatureDescriptionAndView{
width:auto;
display:flex;
justify-content:flex-end;
align-items:flex-start;
gap:12px;
align-self:stretch;
}
:is(:is(#editSignatureDescriptionDialog .mainContainer) #editSignatureDescriptionAndView) #editSignatureDescriptionContainer{
display:flex;
flex-direction:column;
align-items:flex-start;
gap:4px;
flex:1 1 auto;
}
:is(:is(#editSignatureDescriptionDialog .mainContainer) #editSignatureDescriptionAndView) > svg{
width:210px;
height:180px;
padding:8px;
background-color:var(--signature-bg);
}
:is(:is(:is(#editSignatureDescriptionDialog .mainContainer) #editSignatureDescriptionAndView) > svg) > path{
stroke:var(--button-signature-color);
stroke-width:1px;
stroke-linecap:round;
stroke-linejoin:round;
stroke-miterlimit:10;
vector-effect:non-scaling-stroke;
fill:none;
}
.contours:is(:is(:is(:is(#editSignatureDescriptionDialog .mainContainer) #editSignatureDescriptionAndView) > svg) > path){
fill:var(--button-signature-color);
stroke-width:0.5px;
}
#editorSignatureParamsToolbar{
padding:8px;
}
#editorSignatureParamsToolbar #addSignatureDoorHanger{
gap:8px;
padding:2px;
}
:is(#editorSignatureParamsToolbar #addSignatureDoorHanger) .toolbarAddSignatureButtonContainer{
height:32px;
display:flex;
justify-content:space-between;
align-items:center;
align-self:stretch;
gap:8px;
}
:is(:is(#editorSignatureParamsToolbar #addSignatureDoorHanger) .toolbarAddSignatureButtonContainer) button{
border:var(--button-signature-border);
border-radius:4px;
background-color:var(--button-signature-bg);
color:var(--button-signature-color);
}
:is(:is(:is(#editorSignatureParamsToolbar #addSignatureDoorHanger) .toolbarAddSignatureButtonContainer) button):hover{
background-color:var(--button-signature-hover-bg);
}
:is(:is(:is(#editorSignatureParamsToolbar #addSignatureDoorHanger) .toolbarAddSignatureButtonContainer) button):active{
border:var(--button-signature-active-border);
background-color:var(--button-signature-active-bg);
color:var(--button-signature-active-color);
}
:is(:is(:is(#editorSignatureParamsToolbar #addSignatureDoorHanger) .toolbarAddSignatureButtonContainer) button):active::before{
background-color:var(--button-signature-active-color);
}
:is(:is(:is(#editorSignatureParamsToolbar #addSignatureDoorHanger) .toolbarAddSignatureButtonContainer) button):focus-visible{
outline:var(--focus-ring-outline);
}
:is(:is(:is(#editorSignatureParamsToolbar #addSignatureDoorHanger) .toolbarAddSignatureButtonContainer) button):focus-visible::before{
background-color:var(--button-signature-color);
}
:is(:is(:is(#editorSignatureParamsToolbar #addSignatureDoorHanger) .toolbarAddSignatureButtonContainer) .deleteButton)::before{
-webkit-mask-image:var(--clear-signature-button-icon);
mask-image:var(--clear-signature-button-icon);
}
:is(:is(#editorSignatureParamsToolbar #addSignatureDoorHanger) .toolbarAddSignatureButtonContainer) .toolbarAddSignatureButton{
width:auto;
height:100%;
min-height:var(--menuitem-height);
aspect-ratio:unset;
display:flex;
align-items:center;
justify-content:flex-start;
outline:none;
border-radius:4px;
box-sizing:border-box;
font:message-box;
position:relative;
flex:1 1 auto;
padding:0;
gap:8px;
text-align:start;
white-space:normal;
cursor:default;
overflow:hidden;
}
:is(:is(:is(#editorSignatureParamsToolbar #addSignatureDoorHanger) .toolbarAddSignatureButtonContainer) .toolbarAddSignatureButton) > svg{
display:inline-block;
height:100%;
aspect-ratio:1;
background-color:var(--signature-bg);
flex:none;
padding:4px;
box-sizing:border-box;
border:none;
border-radius:4px;
}
:is(:is(:is(:is(#editorSignatureParamsToolbar #addSignatureDoorHanger) .toolbarAddSignatureButtonContainer) .toolbarAddSignatureButton) > svg) > path{
stroke:var(--button-signature-color);
stroke-width:1px;
stroke-linecap:round;
stroke-linejoin:round;
stroke-miterlimit:10;
vector-effect:non-scaling-stroke;
fill:none;
}
.contours:is(:is(:is(:is(:is(#editorSignatureParamsToolbar #addSignatureDoorHanger) .toolbarAddSignatureButtonContainer) .toolbarAddSignatureButton) > svg) > path){
fill:var(--button-signature-color);
stroke-width:0.5px;
}
:is(:is(:is(#editorSignatureParamsToolbar #addSignatureDoorHanger) .toolbarAddSignatureButtonContainer) .toolbarAddSignatureButton):is(:hover,:active) > svg{
border-radius:4px 0 0 4px;
background-color:var(--signature-hover-bg);
}
:is(:is(:is(#editorSignatureParamsToolbar #addSignatureDoorHanger) .toolbarAddSignatureButtonContainer) .toolbarAddSignatureButton):hover > span{
color:var(--button-signature-hover-color);
}
:is(:is(:is(#editorSignatureParamsToolbar #addSignatureDoorHanger) .toolbarAddSignatureButtonContainer) .toolbarAddSignatureButton):active{
background-color:var(--button-signature-active-bg);
}
:is(:is(:is(#editorSignatureParamsToolbar #addSignatureDoorHanger) .toolbarAddSignatureButtonContainer) .toolbarAddSignatureButton):is([disabled="disabled"],[disabled]){
opacity:0.5;
pointer-events:none;
}
:is(:is(:is(#editorSignatureParamsToolbar #addSignatureDoorHanger) .toolbarAddSignatureButtonContainer) .toolbarAddSignatureButton) > span{
height:auto;
text-overflow:ellipsis;
white-space:nowrap;
flex:1 1 auto;
font:menu;
font-size:13px;
font-style:normal;
font-weight:400;
line-height:normal;
overflow:hidden;
}
.editDescription.altText{
--alt-text-add-image:url(images/editor-toolbar-edit.svg) !important;
}
.editDescription.altText::before{
width:16px !important;
height:16px !important;
}
:root{
--outline-width:2px;
--outline-color:#0060df;
--outline-around-width:1px;
--outline-around-color:#f0f0f4;
--hover-outline-around-color:var(--outline-around-color);
--focus-outline:solid var(--outline-width) var(--outline-color);
--unfocus-outline:solid var(--outline-width) transparent;
--focus-outline-around:solid var(--outline-around-width) var(--outline-around-color);
--hover-outline-color:#8f8f9d;
--hover-outline:solid var(--outline-width) var(--hover-outline-color);
--hover-outline-around:solid var(--outline-around-width) var(--hover-outline-around-color);
--freetext-line-height:1.35;
--freetext-padding:2px;
--resizer-bg-color:var(--outline-color);
--resizer-size:6px;
--resizer-shift:calc(
0px - (var(--outline-width) + var(--resizer-size)) / 2 -
var(--outline-around-width)
);
--editorFreeText-editing-cursor:text;
--editorInk-editing-cursor:url(images/cursor-editorInk.svg) 0 16, pointer;
--editorHighlight-editing-cursor:url(images/cursor-editorTextHighlight.svg) 24 24, text;
--editorFreeHighlight-editing-cursor:url(images/cursor-editorFreeHighlight.svg) 1 18, pointer;
--new-alt-text-warning-image:url(images/altText_warning.svg);
}
.visuallyHidden{
position:absolute;
top:0;
left:0;
border:0;
margin:0;
padding:0;
width:0;
height:0;
overflow:hidden;
white-space:nowrap;
font-size:0;
}
.textLayer.highlighting{
cursor:var(--editorFreeHighlight-editing-cursor);
}
.textLayer.highlighting:not(.free) span{
cursor:var(--editorHighlight-editing-cursor);
}
[role="img"]:is(.textLayer.highlighting:not(.free) span){
cursor:var(--editorFreeHighlight-editing-cursor);
}
.textLayer.highlighting.free span{
cursor:var(--editorFreeHighlight-editing-cursor);
}
:is(#viewerContainer.pdfPresentationMode:fullscreen,.annotationEditorLayer.disabled) .noAltTextBadge{
display:none !important;
}
@media (min-resolution: 1.1dppx){
:root{
--editorFreeText-editing-cursor:url(images/cursor-editorFreeText.svg) 0 16, text;
}
}
@media screen and (forced-colors: active){
:root{
--outline-color:CanvasText;
--outline-around-color:ButtonFace;
--resizer-bg-color:ButtonText;
--hover-outline-color:Highlight;
--hover-outline-around-color:SelectedItemText;
}
}
[data-editor-rotation="90"]{
transform:rotate(90deg);
}
[data-editor-rotation="180"]{
transform:rotate(180deg);
}
[data-editor-rotation="270"]{
transform:rotate(270deg);
}
.annotationEditorLayer{
background:transparent;
position:absolute;
inset:0;
font-size:calc(100px * var(--total-scale-factor));
transform-origin:0 0;
cursor:auto;
}
.annotationEditorLayer .selectedEditor{
z-index:100000 !important;
}
.annotationEditorLayer.drawing *{
pointer-events:none !important;
}
.annotationEditorLayer.waiting{
content:"";
cursor:wait;
position:absolute;
inset:0;
width:100%;
height:100%;
}
.annotationEditorLayer.disabled{
pointer-events:none;
}
.annotationEditorLayer.freetextEditing{
cursor:var(--editorFreeText-editing-cursor);
}
.annotationEditorLayer.inkEditing{
cursor:var(--editorInk-editing-cursor);
}
.annotationEditorLayer .draw{
box-sizing:border-box;
}
.annotationEditorLayer
:is(.freeTextEditor, .inkEditor, .stampEditor, .signatureEditor){
position:absolute;
background:transparent;
z-index:1;
transform-origin:0 0;
cursor:auto;
max-width:100%;
max-height:100%;
border:var(--unfocus-outline);
}
.draggable.selectedEditor:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.signatureEditor)){
cursor:move;
}
.selectedEditor:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.signatureEditor)){
border:var(--focus-outline);
outline:var(--focus-outline-around);
}
.selectedEditor:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.signatureEditor))::before{
content:"";
position:absolute;
inset:0;
border:var(--focus-outline-around);
pointer-events:none;
}
:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.signatureEditor)):hover:not(.selectedEditor){
border:var(--hover-outline);
outline:var(--hover-outline-around);
}
:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.signatureEditor)):hover:not(.selectedEditor)::before{
content:"";
position:absolute;
inset:0;
border:var(--focus-outline-around);
}
:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar{
--editor-toolbar-delete-image:url(images/editor-toolbar-delete.svg);
--editor-toolbar-bg-color:#f0f0f4;
--editor-toolbar-highlight-image:url(images/toolbarButton-editorHighlight.svg);
--editor-toolbar-fg-color:#2e2e56;
--editor-toolbar-border-color:#8f8f9d;
--editor-toolbar-hover-border-color:var(--editor-toolbar-border-color);
--editor-toolbar-hover-bg-color:#e0e0e6;
--editor-toolbar-hover-fg-color:var(--editor-toolbar-fg-color);
--editor-toolbar-hover-outline:none;
--editor-toolbar-focus-outline-color:#0060df;
--editor-toolbar-shadow:0 2px 6px 0 rgb(58 57 68 / 0.2);
--editor-toolbar-vert-offset:6px;
--editor-toolbar-height:28px;
--editor-toolbar-padding:2px;
--alt-text-done-color:#2ac3a2;
--alt-text-warning-color:#0090ed;
--alt-text-hover-done-color:var(--alt-text-done-color);
--alt-text-hover-warning-color:var(--alt-text-warning-color);
}
@media (prefers-color-scheme: dark){
:where(html:not(.is-light)) :is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar{
--editor-toolbar-bg-color:#2b2a33;
--editor-toolbar-fg-color:#fbfbfe;
--editor-toolbar-hover-bg-color:#52525e;
--editor-toolbar-focus-outline-color:#0df;
--alt-text-done-color:#54ffbd;
--alt-text-warning-color:#80ebff;
}
}
:where(html.is-dark) :is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar{
--editor-toolbar-bg-color:#2b2a33;
--editor-toolbar-fg-color:#fbfbfe;
--editor-toolbar-hover-bg-color:#52525e;
--editor-toolbar-focus-outline-color:#0df;
--alt-text-done-color:#54ffbd;
--alt-text-warning-color:#80ebff;
}
@media screen and (forced-colors: active){
:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar{
--editor-toolbar-bg-color:ButtonFace;
--editor-toolbar-fg-color:ButtonText;
--editor-toolbar-border-color:ButtonText;
--editor-toolbar-hover-border-color:AccentColor;
--editor-toolbar-hover-bg-color:ButtonFace;
--editor-toolbar-hover-fg-color:AccentColor;
--editor-toolbar-hover-outline:2px solid var(--editor-toolbar-hover-border-color);
--editor-toolbar-focus-outline-color:ButtonBorder;
--editor-toolbar-shadow:none;
--alt-text-done-color:var(--editor-toolbar-fg-color);
--alt-text-warning-color:var(--editor-toolbar-fg-color);
--alt-text-hover-done-color:var(--editor-toolbar-hover-fg-color);
--alt-text-hover-warning-color:var(--editor-toolbar-hover-fg-color);
}
}
:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar{
display:flex;
width:-moz-fit-content;
width:fit-content;
height:var(--editor-toolbar-height);
flex-direction:column;
justify-content:center;
align-items:center;
cursor:default;
pointer-events:auto;
box-sizing:content-box;
padding:var(--editor-toolbar-padding);
position:absolute;
inset-inline-end:0;
inset-block-start:calc(100% + var(--editor-toolbar-vert-offset));
border-radius:6px;
background-color:var(--editor-toolbar-bg-color);
border:1px solid var(--editor-toolbar-border-color);
box-shadow:var(--editor-toolbar-shadow);
}
.hidden:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar){
display:none;
}
:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar):has(:focus-visible){
border-color:transparent;
}
[dir="ltr"] :is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar){
transform-origin:100% 0;
}
[dir="rtl"] :is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar){
transform-origin:0 0;
}
:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons{
display:flex;
justify-content:center;
align-items:center;
gap:0;
height:100%;
}
:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) button{
padding:0;
}
:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .divider{
width:0;
height:calc(
2 * var(--editor-toolbar-padding) + var(--editor-toolbar-height)
);
border-left:1px solid var(--editor-toolbar-border-color);
border-right:none;
display:inline-block;
margin-inline:2px;
}
:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .highlightButton{
width:var(--editor-toolbar-height);
}
:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .highlightButton)::before{
content:"";
-webkit-mask-image:var(--editor-toolbar-highlight-image);
mask-image:var(--editor-toolbar-highlight-image);
-webkit-mask-repeat:no-repeat;
mask-repeat:no-repeat;
-webkit-mask-position:center;
mask-position:center;
display:inline-block;
background-color:var(--editor-toolbar-fg-color);
width:100%;
height:100%;
}
:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .highlightButton):hover::before{
background-color:var(--editor-toolbar-hover-fg-color);
}
:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .delete{
width:var(--editor-toolbar-height);
}
:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .delete)::before{
content:"";
-webkit-mask-image:var(--editor-toolbar-delete-image);
mask-image:var(--editor-toolbar-delete-image);
-webkit-mask-repeat:no-repeat;
mask-repeat:no-repeat;
-webkit-mask-position:center;
mask-position:center;
display:inline-block;
background-color:var(--editor-toolbar-fg-color);
width:100%;
height:100%;
}
:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .delete):hover::before{
background-color:var(--editor-toolbar-hover-fg-color);
}
:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) > *{
height:var(--editor-toolbar-height);
}
:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) > :not(.divider){
border:none;
background-color:transparent;
cursor:pointer;
}
:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) > :not(.divider)):hover{
border-radius:2px;
background-color:var(--editor-toolbar-hover-bg-color);
color:var(--editor-toolbar-hover-fg-color);
outline:var(--editor-toolbar-hover-outline);
outline-offset:1px;
}
:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) > :not(.divider)):hover:active{
outline:none;
}
:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) > :not(.divider)):focus-visible{
border-radius:2px;
outline:2px solid var(--editor-toolbar-focus-outline-color);
}
:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .altText{
--alt-text-add-image:url(images/altText_add.svg);
--alt-text-done-image:url(images/altText_done.svg);
display:flex;
align-items:center;
justify-content:center;
width:-moz-max-content;
width:max-content;
padding-inline:8px;
pointer-events:all;
font:menu;
font-weight:590;
font-size:12px;
color:var(--editor-toolbar-fg-color);
}
:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .altText):disabled{
pointer-events:none;
}
:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .altText)::before{
content:"";
-webkit-mask-image:var(--alt-text-add-image);
mask-image:var(--alt-text-add-image);
-webkit-mask-repeat:no-repeat;
mask-repeat:no-repeat;
-webkit-mask-position:center;
mask-position:center;
display:inline-block;
width:12px;
height:13px;
background-color:var(--editor-toolbar-fg-color);
margin-inline-end:4px;
}
:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .altText):hover::before{
background-color:var(--editor-toolbar-hover-fg-color);
}
.done:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .altText)::before{
-webkit-mask-image:var(--alt-text-done-image);
mask-image:var(--alt-text-done-image);
}
.new:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .altText)::before{
width:16px;
height:16px;
-webkit-mask-image:var(--new-alt-text-warning-image);
mask-image:var(--new-alt-text-warning-image);
background-color:var(--alt-text-warning-color);
-webkit-mask-size:cover;
mask-size:cover;
}
.new:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .altText):hover::before{
background-color:var(--alt-text-hover-warning-color);
}
.new.done:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .altText)::before{
-webkit-mask-image:var(--alt-text-done-image);
mask-image:var(--alt-text-done-image);
background-color:var(--alt-text-done-color);
}
.new.done:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .altText):hover::before{
background-color:var(--alt-text-hover-done-color);
}
:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .altText) .tooltip{
display:none;
word-wrap:anywhere;
}
.show:is(:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .altText) .tooltip){
--alt-text-tooltip-bg:#f0f0f4;
--alt-text-tooltip-fg:#15141a;
--alt-text-tooltip-border:#8f8f9d;
--alt-text-tooltip-shadow:0px 2px 6px 0px rgb(58 57 68 / 0.2);
}
@media (prefers-color-scheme: dark){
:where(html:not(.is-light)) .show:is(:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .altText) .tooltip){
--alt-text-tooltip-bg:#1c1b22;
--alt-text-tooltip-fg:#fbfbfe;
--alt-text-tooltip-shadow:0px 2px 6px 0px #15141a;
}
}
:where(html.is-dark) .show:is(:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .altText) .tooltip){
--alt-text-tooltip-bg:#1c1b22;
--alt-text-tooltip-fg:#fbfbfe;
--alt-text-tooltip-shadow:0px 2px 6px 0px #15141a;
}
@media screen and (forced-colors: active){
.show:is(:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .altText) .tooltip){
--alt-text-tooltip-bg:Canvas;
--alt-text-tooltip-fg:CanvasText;
--alt-text-tooltip-border:CanvasText;
--alt-text-tooltip-shadow:none;
}
}
.show:is(:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .altText) .tooltip){
display:inline-flex;
flex-direction:column;
align-items:center;
justify-content:center;
position:absolute;
top:calc(100% + 2px);
inset-inline-start:0;
padding-block:2px 3px;
padding-inline:3px;
max-width:300px;
width:-moz-max-content;
width:max-content;
height:auto;
font-size:12px;
border:0.5px solid var(--alt-text-tooltip-border);
background:var(--alt-text-tooltip-bg);
box-shadow:var(--alt-text-tooltip-shadow);
color:var(--alt-text-tooltip-fg);
pointer-events:none;
}
.annotationEditorLayer .freeTextEditor{
padding:calc(var(--freetext-padding) * var(--total-scale-factor));
width:auto;
height:auto;
touch-action:none;
}
.annotationEditorLayer .freeTextEditor .internal{
background:transparent;
border:none;
inset:0;
overflow:visible;
white-space:nowrap;
font:10px sans-serif;
line-height:var(--freetext-line-height);
-webkit-user-select:none;
-moz-user-select:none;
user-select:none;
}
.annotationEditorLayer .freeTextEditor .overlay{
position:absolute;
display:none;
background:transparent;
inset:0;
width:100%;
height:100%;
}
.annotationEditorLayer freeTextEditor .overlay.enabled{
display:block;
}
.annotationEditorLayer .freeTextEditor .internal:empty::before{
content:attr(default-content);
color:gray;
}
.annotationEditorLayer .freeTextEditor .internal:focus{
outline:none;
-webkit-user-select:auto;
-moz-user-select:auto;
user-select:auto;
}
.annotationEditorLayer .inkEditor{
width:100%;
height:100%;
}
.annotationEditorLayer .inkEditor.editing{
cursor:inherit;
}
.annotationEditorLayer .inkEditor .inkEditorCanvas{
position:absolute;
inset:0;
width:100%;
height:100%;
touch-action:none;
}
.annotationEditorLayer .stampEditor{
width:auto;
height:auto;
}
:is(.annotationEditorLayer .stampEditor) canvas{
position:absolute;
width:100%;
height:100%;
margin:0;
top:0;
left:0;
}
:is(.annotationEditorLayer .stampEditor) .noAltTextBadge{
--no-alt-text-badge-border-color:#f0f0f4;
--no-alt-text-badge-bg-color:#cfcfd8;
--no-alt-text-badge-fg-color:#5b5b66;
}
@media (prefers-color-scheme: dark){
:where(html:not(.is-light)) :is(.annotationEditorLayer .stampEditor) .noAltTextBadge{
--no-alt-text-badge-border-color:#52525e;
--no-alt-text-badge-bg-color:#fbfbfe;
--no-alt-text-badge-fg-color:#15141a;
}
}
:where(html.is-dark) :is(.annotationEditorLayer .stampEditor) .noAltTextBadge{
--no-alt-text-badge-border-color:#52525e;
--no-alt-text-badge-bg-color:#fbfbfe;
--no-alt-text-badge-fg-color:#15141a;
}
@media screen and (forced-colors: active){
:is(.annotationEditorLayer .stampEditor) .noAltTextBadge{
--no-alt-text-badge-border-color:ButtonText;
--no-alt-text-badge-bg-color:ButtonFace;
--no-alt-text-badge-fg-color:ButtonText;
}
}
:is(.annotationEditorLayer .stampEditor) .noAltTextBadge{
position:absolute;
inset-inline-end:5px;
inset-block-end:5px;
display:inline-flex;
width:32px;
height:32px;
padding:3px;
justify-content:center;
align-items:center;
pointer-events:none;
z-index:1;
border-radius:2px;
border:1px solid var(--no-alt-text-badge-border-color);
background:var(--no-alt-text-badge-bg-color);
}
:is(:is(.annotationEditorLayer .stampEditor) .noAltTextBadge)::before{
content:"";
display:inline-block;
width:16px;
height:16px;
-webkit-mask-image:var(--new-alt-text-warning-image);
mask-image:var(--new-alt-text-warning-image);
-webkit-mask-size:cover;
mask-size:cover;
background-color:var(--no-alt-text-badge-fg-color);
}
:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.signatureEditor)) > .resizers{
position:absolute;
inset:0;
}
.hidden:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.signatureEditor)) > .resizers){
display:none;
}
:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.signatureEditor)) > .resizers) > .resizer{
width:var(--resizer-size);
height:var(--resizer-size);
background:content-box var(--resizer-bg-color);
border:var(--focus-outline-around);
border-radius:2px;
position:absolute;
}
.topLeft:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.signatureEditor)) > .resizers) > .resizer){
top:var(--resizer-shift);
left:var(--resizer-shift);
}
.topMiddle:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.signatureEditor)) > .resizers) > .resizer){
top:var(--resizer-shift);
left:calc(50% + var(--resizer-shift));
}
.topRight:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.signatureEditor)) > .resizers) > .resizer){
top:var(--resizer-shift);
right:var(--resizer-shift);
}
.middleRight:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.signatureEditor)) > .resizers) > .resizer){
top:calc(50% + var(--resizer-shift));
right:var(--resizer-shift);
}
.bottomRight:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.signatureEditor)) > .resizers) > .resizer){
bottom:var(--resizer-shift);
right:var(--resizer-shift);
}
.bottomMiddle:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.signatureEditor)) > .resizers) > .resizer){
bottom:var(--resizer-shift);
left:calc(50% + var(--resizer-shift));
}
.bottomLeft:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.signatureEditor)) > .resizers) > .resizer){
bottom:var(--resizer-shift);
left:var(--resizer-shift);
}
.middleLeft:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.signatureEditor)) > .resizers) > .resizer){
top:calc(50% + var(--resizer-shift));
left:var(--resizer-shift);
}
.topLeft:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="180"],[data-editor-rotation="0"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="90"],[data-editor-rotation="270"])) > .resizers > .resizer),.bottomRight:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="180"],[data-editor-rotation="0"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="90"],[data-editor-rotation="270"])) > .resizers > .resizer){
cursor:nwse-resize;
}
.topMiddle:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="180"],[data-editor-rotation="0"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="90"],[data-editor-rotation="270"])) > .resizers > .resizer),.bottomMiddle:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="180"],[data-editor-rotation="0"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="90"],[data-editor-rotation="270"])) > .resizers > .resizer){
cursor:ns-resize;
}
.topRight:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="180"],[data-editor-rotation="0"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="90"],[data-editor-rotation="270"])) > .resizers > .resizer),.bottomLeft:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="180"],[data-editor-rotation="0"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="90"],[data-editor-rotation="270"])) > .resizers > .resizer){
cursor:nesw-resize;
}
.middleRight:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="180"],[data-editor-rotation="0"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="90"],[data-editor-rotation="270"])) > .resizers > .resizer),.middleLeft:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="180"],[data-editor-rotation="0"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="90"],[data-editor-rotation="270"])) > .resizers > .resizer){
cursor:ew-resize;
}
.topLeft:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="90"],[data-editor-rotation="270"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="180"],[data-editor-rotation="0"])) > .resizers > .resizer),.bottomRight:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="90"],[data-editor-rotation="270"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="180"],[data-editor-rotation="0"])) > .resizers > .resizer){
cursor:nesw-resize;
}
.topMiddle:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="90"],[data-editor-rotation="270"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="180"],[data-editor-rotation="0"])) > .resizers > .resizer),.bottomMiddle:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="90"],[data-editor-rotation="270"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="180"],[data-editor-rotation="0"])) > .resizers > .resizer){
cursor:ew-resize;
}
.topRight:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="90"],[data-editor-rotation="270"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="180"],[data-editor-rotation="0"])) > .resizers > .resizer),.bottomLeft:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="90"],[data-editor-rotation="270"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="180"],[data-editor-rotation="0"])) > .resizers > .resizer){
cursor:nwse-resize;
}
.middleRight:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="90"],[data-editor-rotation="270"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="180"],[data-editor-rotation="0"])) > .resizers > .resizer),.middleLeft:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="90"],[data-editor-rotation="270"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="180"],[data-editor-rotation="0"])) > .resizers > .resizer){
cursor:ns-resize;
}
:is(.annotationEditorLayer :is([data-main-rotation="0"] [data-editor-rotation="90"],[data-main-rotation="90"] [data-editor-rotation="0"],[data-main-rotation="180"] [data-editor-rotation="270"],[data-main-rotation="270"] [data-editor-rotation="180"])) .editToolbar{
rotate:270deg;
}
[dir="ltr"] :is(:is(.annotationEditorLayer :is([data-main-rotation="0"] [data-editor-rotation="90"],[data-main-rotation="90"] [data-editor-rotation="0"],[data-main-rotation="180"] [data-editor-rotation="270"],[data-main-rotation="270"] [data-editor-rotation="180"])) .editToolbar){
inset-inline-end:calc(0px - var(--editor-toolbar-vert-offset));
inset-block-start:0;
}
[dir="rtl"] :is(:is(.annotationEditorLayer :is([data-main-rotation="0"] [data-editor-rotation="90"],[data-main-rotation="90"] [data-editor-rotation="0"],[data-main-rotation="180"] [data-editor-rotation="270"],[data-main-rotation="270"] [data-editor-rotation="180"])) .editToolbar){
inset-inline-end:calc(100% + var(--editor-toolbar-vert-offset));
inset-block-start:0;
}
:is(.annotationEditorLayer :is([data-main-rotation="0"] [data-editor-rotation="180"],[data-main-rotation="90"] [data-editor-rotation="90"],[data-main-rotation="180"] [data-editor-rotation="0"],[data-main-rotation="270"] [data-editor-rotation="270"])) .editToolbar{
rotate:180deg;
inset-inline-end:100%;
inset-block-start:calc(0pc - var(--editor-toolbar-vert-offset));
}
:is(.annotationEditorLayer :is([data-main-rotation="0"] [data-editor-rotation="270"],[data-main-rotation="90"] [data-editor-rotation="180"],[data-main-rotation="180"] [data-editor-rotation="90"],[data-main-rotation="270"] [data-editor-rotation="0"])) .editToolbar{
rotate:90deg;
}
[dir="ltr"] :is(:is(.annotationEditorLayer :is([data-main-rotation="0"] [data-editor-rotation="270"],[data-main-rotation="90"] [data-editor-rotation="180"],[data-main-rotation="180"] [data-editor-rotation="90"],[data-main-rotation="270"] [data-editor-rotation="0"])) .editToolbar){
inset-inline-end:calc(100% + var(--editor-toolbar-vert-offset));
inset-block-start:100%;
}
[dir="rtl"] :is(:is(.annotationEditorLayer :is([data-main-rotation="0"] [data-editor-rotation="270"],[data-main-rotation="90"] [data-editor-rotation="180"],[data-main-rotation="180"] [data-editor-rotation="90"],[data-main-rotation="270"] [data-editor-rotation="0"])) .editToolbar){
inset-inline-start:calc(0px - var(--editor-toolbar-vert-offset));
inset-block-start:0;
}
.dialog.altText::backdrop{
-webkit-mask:url(#alttext-manager-mask);
mask:url(#alttext-manager-mask);
}
.dialog.altText.positioned{
margin:0;
}
.dialog.altText #altTextContainer{
width:300px;
height:-moz-fit-content;
height:fit-content;
display:inline-flex;
flex-direction:column;
align-items:flex-start;
gap:16px;
}
:is(.dialog.altText #altTextContainer) #overallDescription{
display:flex;
flex-direction:column;
align-items:flex-start;
gap:4px;
align-self:stretch;
}
:is(:is(.dialog.altText #altTextContainer) #overallDescription) span{
align-self:stretch;
}
:is(:is(.dialog.altText #altTextContainer) #overallDescription) .title{
font-size:13px;
font-style:normal;
font-weight:590;
}
:is(.dialog.altText #altTextContainer) #addDescription{
display:flex;
flex-direction:column;
align-items:stretch;
gap:8px;
}
:is(:is(.dialog.altText #altTextContainer) #addDescription) .descriptionArea{
flex:1;
padding-inline:24px 10px;
}
:is(:is(:is(.dialog.altText #altTextContainer) #addDescription) .descriptionArea) textarea{
width:100%;
min-height:75px;
}
:is(.dialog.altText #altTextContainer) #buttons{
display:flex;
justify-content:flex-end;
align-items:flex-start;
gap:8px;
align-self:stretch;
}
.dialog.newAltText{
--new-alt-text-ai-disclaimer-icon:url(images/altText_disclaimer.svg);
--new-alt-text-spinner-icon:url(images/altText_spinner.svg);
--preview-image-bg-color:#f0f0f4;
--preview-image-border:none;
}
@media (prefers-color-scheme: dark){
:where(html:not(.is-light)) .dialog.newAltText{
--preview-image-bg-color:#2b2a33;
}
}
:where(html.is-dark) .dialog.newAltText{
--preview-image-bg-color:#2b2a33;
}
@media screen and (forced-colors: active){
.dialog.newAltText{
--preview-image-bg-color:ButtonFace;
--preview-image-border:1px solid ButtonText;
}
}
.dialog.newAltText{
width:80%;
max-width:570px;
min-width:300px;
padding:0;
}
.dialog.newAltText.noAi #newAltTextDisclaimer,.dialog.newAltText.noAi #newAltTextCreateAutomatically{
display:none !important;
}
.dialog.newAltText.aiInstalling #newAltTextCreateAutomatically{
display:none !important;
}
.dialog.newAltText.aiInstalling #newAltTextDownloadModel{
display:flex !important;
}
.dialog.newAltText.error #newAltTextNotNow{
display:none !important;
}
.dialog.newAltText.error #newAltTextCancel{
display:inline-block !important;
}
.dialog.newAltText:not(.error) #newAltTextError{
display:none !important;
}
.dialog.newAltText #newAltTextContainer{
display:flex;
width:auto;
padding:16px;
flex-direction:column;
justify-content:flex-end;
align-items:flex-start;
gap:12px;
flex:0 1 auto;
line-height:normal;
}
:is(.dialog.newAltText #newAltTextContainer) #mainContent{
display:flex;
justify-content:flex-end;
align-items:flex-start;
gap:12px;
align-self:stretch;
flex:1 1 auto;
}
:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionAndSettings{
display:flex;
flex-direction:column;
align-items:flex-start;
gap:16px;
flex:1 0 0;
align-self:stretch;
}
:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionInstruction{
display:flex;
flex-direction:column;
align-items:flex-start;
gap:8px;
align-self:stretch;
flex:1 1 auto;
}
:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionInstruction) #newAltTextDescriptionContainer{
width:100%;
height:70px;
position:relative;
}
:is(:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionInstruction) #newAltTextDescriptionContainer) textarea{
width:100%;
height:100%;
padding:8px;
}
:is(:is(:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionInstruction) #newAltTextDescriptionContainer) textarea)::-moz-placeholder{
color:var(--text-secondary-color);
}
:is(:is(:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionInstruction) #newAltTextDescriptionContainer) textarea)::placeholder{
color:var(--text-secondary-color);
}
:is(:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionInstruction) #newAltTextDescriptionContainer) .altTextSpinner{
display:none;
position:absolute;
width:16px;
height:16px;
inset-inline-start:8px;
inset-block-start:8px;
-webkit-mask-size:cover;
mask-size:cover;
background-color:var(--text-secondary-color);
pointer-events:none;
}
.loading:is(:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionInstruction) #newAltTextDescriptionContainer) textarea::-moz-placeholder{
color:transparent;
}
.loading:is(:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionInstruction) #newAltTextDescriptionContainer) textarea::placeholder{
color:transparent;
}
.loading:is(:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionInstruction) #newAltTextDescriptionContainer) .altTextSpinner{
display:inline-block;
-webkit-mask-image:var(--new-alt-text-spinner-icon);
mask-image:var(--new-alt-text-spinner-icon);
}
:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionInstruction) #newAltTextDescription{
font-size:11px;
}
:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionInstruction) #newAltTextDisclaimer{
display:flex;
flex-direction:row;
align-items:flex-start;
gap:4px;
font-size:11px;
}
:is(:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionInstruction) #newAltTextDisclaimer)::before{
content:"";
display:inline-block;
width:17px;
height:16px;
-webkit-mask-image:var(--new-alt-text-ai-disclaimer-icon);
mask-image:var(--new-alt-text-ai-disclaimer-icon);
-webkit-mask-size:cover;
mask-size:cover;
background-color:var(--text-secondary-color);
flex:1 0 auto;
}
:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #newAltTextDownloadModel{
display:flex;
align-items:center;
gap:4px;
align-self:stretch;
}
:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #newAltTextDownloadModel)::before{
content:"";
display:inline-block;
width:16px;
height:16px;
-webkit-mask-image:var(--new-alt-text-spinner-icon);
mask-image:var(--new-alt-text-spinner-icon);
-webkit-mask-size:cover;
mask-size:cover;
background-color:var(--text-secondary-color);
}
:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #newAltTextImagePreview{
width:180px;
aspect-ratio:1;
display:flex;
justify-content:center;
align-items:center;
flex:0 0 auto;
background-color:var(--preview-image-bg-color);
border:var(--preview-image-border);
}
:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #newAltTextImagePreview) > canvas{
max-width:100%;
max-height:100%;
}
.colorPicker{
--hover-outline-color:#0250bb;
--selected-outline-color:#0060df;
--swatch-border-color:#cfcfd8;
}
@media (prefers-color-scheme: dark){
:where(html:not(.is-light)) .colorPicker{
--hover-outline-color:#80ebff;
--selected-outline-color:#aaf2ff;
--swatch-border-color:#52525e;
}
}
:where(html.is-dark) .colorPicker{
--hover-outline-color:#80ebff;
--selected-outline-color:#aaf2ff;
--swatch-border-color:#52525e;
}
@media screen and (forced-colors: active){
.colorPicker{
--hover-outline-color:Highlight;
--selected-outline-color:var(--hover-outline-color);
--swatch-border-color:ButtonText;
}
}
.colorPicker .swatch{
width:16px;
height:16px;
border:1px solid var(--swatch-border-color);
border-radius:100%;
outline-offset:2px;
box-sizing:border-box;
forced-color-adjust:none;
}
.colorPicker button:is(:hover,.selected) > .swatch{
border:none;
}
.annotationEditorLayer[data-main-rotation="0"] .highlightEditor:not(.free) > .editToolbar{
rotate:0deg;
}
.annotationEditorLayer[data-main-rotation="90"] .highlightEditor:not(.free) > .editToolbar{
rotate:270deg;
}
.annotationEditorLayer[data-main-rotation="180"] .highlightEditor:not(.free) > .editToolbar{
rotate:180deg;
}
.annotationEditorLayer[data-main-rotation="270"] .highlightEditor:not(.free) > .editToolbar{
rotate:90deg;
}
.annotationEditorLayer .highlightEditor{
position:absolute;
background:transparent;
z-index:1;
cursor:auto;
max-width:100%;
max-height:100%;
border:none;
outline:none;
pointer-events:none;
transform-origin:0 0;
}
:is(.annotationEditorLayer .highlightEditor):not(.free){
transform:none;
}
:is(.annotationEditorLayer .highlightEditor) .internal{
position:absolute;
top:0;
left:0;
width:100%;
height:100%;
pointer-events:auto;
}
.disabled:is(.annotationEditorLayer .highlightEditor) .internal{
pointer-events:none;
}
.selectedEditor:is(.annotationEditorLayer .highlightEditor) .internal{
cursor:pointer;
}
:is(.annotationEditorLayer .highlightEditor) .editToolbar{
--editor-toolbar-colorpicker-arrow-image:url(images/toolbarButton-menuArrow.svg);
transform-origin:center !important;
}
:is(:is(:is(.annotationEditorLayer .highlightEditor) .editToolbar) .buttons) .colorPicker{
position:relative;
width:auto;
display:flex;
justify-content:center;
align-items:center;
gap:4px;
padding:4px;
}
:is(:is(:is(:is(.annotationEditorLayer .highlightEditor) .editToolbar) .buttons) .colorPicker)::after{
content:"";
-webkit-mask-image:var(--editor-toolbar-colorpicker-arrow-image);
mask-image:var(--editor-toolbar-colorpicker-arrow-image);
-webkit-mask-repeat:no-repeat;
mask-repeat:no-repeat;
-webkit-mask-position:center;
mask-position:center;
display:inline-block;
background-color:var(--editor-toolbar-fg-color);
width:12px;
height:12px;
}
:is(:is(:is(:is(.annotationEditorLayer .highlightEditor) .editToolbar) .buttons) .colorPicker):hover::after{
background-color:var(--editor-toolbar-hover-fg-color);
}
:is(:is(:is(:is(.annotationEditorLayer .highlightEditor) .editToolbar) .buttons) .colorPicker):has(.dropdown:not(.hidden)){
background-color:var(--editor-toolbar-hover-bg-color);
}
:is(:is(:is(:is(.annotationEditorLayer .highlightEditor) .editToolbar) .buttons) .colorPicker):has(.dropdown:not(.hidden))::after{
scale:-1;
}
:is(:is(:is(:is(.annotationEditorLayer .highlightEditor) .editToolbar) .buttons) .colorPicker) .dropdown{
position:absolute;
display:flex;
justify-content:center;
align-items:center;
flex-direction:column;
gap:11px;
padding-block:8px;
border-radius:6px;
background-color:var(--editor-toolbar-bg-color);
border:1px solid var(--editor-toolbar-border-color);
box-shadow:var(--editor-toolbar-shadow);
inset-block-start:calc(100% + 4px);
width:calc(100% + 2 * var(--editor-toolbar-padding));
}
:is(:is(:is(:is(:is(.annotationEditorLayer .highlightEditor) .editToolbar) .buttons) .colorPicker) .dropdown) button{
width:100%;
height:auto;
border:none;
cursor:pointer;
display:flex;
justify-content:center;
align-items:center;
background:none;
}
:is(:is(:is(:is(:is(:is(.annotationEditorLayer .highlightEditor) .editToolbar) .buttons) .colorPicker) .dropdown) button):is(:active,:focus-visible){
outline:none;
}
:is(:is(:is(:is(:is(:is(.annotationEditorLayer .highlightEditor) .editToolbar) .buttons) .colorPicker) .dropdown) button) > .swatch{
outline-offset:2px;
}
[aria-selected="true"]:is(:is(:is(:is(:is(:is(.annotationEditorLayer .highlightEditor) .editToolbar) .buttons) .colorPicker) .dropdown) button) > .swatch{
outline:2px solid var(--selected-outline-color);
}
:is(:is(:is(:is(:is(:is(.annotationEditorLayer .highlightEditor) .editToolbar) .buttons) .colorPicker) .dropdown) button):is(:hover,:active,:focus-visible) > .swatch{
outline:2px solid var(--hover-outline-color);
}
.editorParamsToolbar:has(#highlightParamsToolbarContainer){
padding:unset;
}
#highlightParamsToolbarContainer{
gap:16px;
padding-inline:10px;
padding-block-end:12px;
}
#highlightParamsToolbarContainer .colorPicker{
display:flex;
flex-direction:column;
gap:8px;
}
:is(#highlightParamsToolbarContainer .colorPicker) .dropdown{
display:flex;
justify-content:space-between;
align-items:center;
flex-direction:row;
height:auto;
}
:is(:is(#highlightParamsToolbarContainer .colorPicker) .dropdown) button{
width:auto;
height:auto;
border:none;
cursor:pointer;
display:flex;
justify-content:center;
align-items:center;
background:none;
flex:0 0 auto;
padding:0;
}
:is(:is(:is(#highlightParamsToolbarContainer .colorPicker) .dropdown) button) .swatch{
width:24px;
height:24px;
}
:is(:is(:is(#highlightParamsToolbarContainer .colorPicker) .dropdown) button):is(:active,:focus-visible){
outline:none;
}
[aria-selected="true"]:is(:is(:is(#highlightParamsToolbarContainer .colorPicker) .dropdown) button) > .swatch{
outline:2px solid var(--selected-outline-color);
}
:is(:is(:is(#highlightParamsToolbarContainer .colorPicker) .dropdown) button):is(:hover,:active,:focus-visible) > .swatch{
outline:2px solid var(--hover-outline-color);
}
#highlightParamsToolbarContainer #editorHighlightThickness{
display:flex;
flex-direction:column;
align-items:center;
gap:4px;
align-self:stretch;
}
:is(#highlightParamsToolbarContainer #editorHighlightThickness) .editorParamsLabel{
height:auto;
align-self:stretch;
}
:is(#highlightParamsToolbarContainer #editorHighlightThickness) .thicknessPicker{
display:flex;
justify-content:space-between;
align-items:center;
align-self:stretch;
--example-color:#bfbfc9;
}
@media (prefers-color-scheme: dark){
:where(html:not(.is-light)) :is(#highlightParamsToolbarContainer #editorHighlightThickness) .thicknessPicker{
--example-color:#80808e;
}
}
:where(html.is-dark) :is(#highlightParamsToolbarContainer #editorHighlightThickness) .thicknessPicker{
--example-color:#80808e;
}
@media screen and (forced-colors: active){
:is(#highlightParamsToolbarContainer #editorHighlightThickness) .thicknessPicker{
--example-color:CanvasText;
}
}
:is(:is(:is(#highlightParamsToolbarContainer #editorHighlightThickness) .thicknessPicker) > .editorParamsSlider[disabled]){
opacity:0.4;
}
:is(:is(#highlightParamsToolbarContainer #editorHighlightThickness) .thicknessPicker)::before,:is(:is(#highlightParamsToolbarContainer #editorHighlightThickness) .thicknessPicker)::after{
content:"";
width:8px;
aspect-ratio:1;
display:block;
border-radius:100%;
background-color:var(--example-color);
}
:is(:is(#highlightParamsToolbarContainer #editorHighlightThickness) .thicknessPicker)::after{
width:24px;
}
:is(:is(#highlightParamsToolbarContainer #editorHighlightThickness) .thicknessPicker) .editorParamsSlider{
width:unset;
height:14px;
}
#highlightParamsToolbarContainer #editorHighlightVisibility{
display:flex;
flex-direction:column;
align-items:flex-start;
gap:8px;
align-self:stretch;
}
:is(#highlightParamsToolbarContainer #editorHighlightVisibility) .divider{
--divider-color:#d7d7db;
}
@media (prefers-color-scheme: dark){
:where(html:not(.is-light)) :is(#highlightParamsToolbarContainer #editorHighlightVisibility) .divider{
--divider-color:#8f8f9d;
}
}
:where(html.is-dark) :is(#highlightParamsToolbarContainer #editorHighlightVisibility) .divider{
--divider-color:#8f8f9d;
}
@media screen and (forced-colors: active){
:is(#highlightParamsToolbarContainer #editorHighlightVisibility) .divider{
--divider-color:CanvasText;
}
}
:is(#highlightParamsToolbarContainer #editorHighlightVisibility) .divider{
margin-block:4px;
width:100%;
height:1px;
background-color:var(--divider-color);
}
:is(#highlightParamsToolbarContainer #editorHighlightVisibility) .toggler{
display:flex;
justify-content:space-between;
align-items:center;
align-self:stretch;
}
#altTextSettingsDialog{
padding:16px;
}
#altTextSettingsDialog #altTextSettingsContainer{
display:flex;
width:573px;
flex-direction:column;
gap:16px;
}
:is(#altTextSettingsDialog #altTextSettingsContainer) .mainContainer{
gap:16px;
}
:is(#altTextSettingsDialog #altTextSettingsContainer) .description{
color:var(--text-secondary-color);
}
:is(#altTextSettingsDialog #altTextSettingsContainer) #aiModelSettings{
display:flex;
flex-direction:column;
gap:12px;
}
:is(:is(#altTextSettingsDialog #altTextSettingsContainer) #aiModelSettings) button{
width:-moz-fit-content;
width:fit-content;
}
.download:is(:is(#altTextSettingsDialog #altTextSettingsContainer) #aiModelSettings) #deleteModelButton{
display:none;
}
:is(:is(#altTextSettingsDialog #altTextSettingsContainer) #aiModelSettings):not(.download) #downloadModelButton{
display:none;
}
:is(#altTextSettingsDialog #altTextSettingsContainer) #automaticAltText,:is(#altTextSettingsDialog #altTextSettingsContainer) #altTextEditor{
display:flex;
flex-direction:column;
gap:8px;
}
:is(#altTextSettingsDialog #altTextSettingsContainer) #createModelDescription,:is(#altTextSettingsDialog #altTextSettingsContainer) #aiModelSettings,:is(#altTextSettingsDialog #altTextSettingsContainer) #showAltTextDialogDescription{
padding-inline-start:40px;
}
:is(#altTextSettingsDialog #altTextSettingsContainer) #automaticSettings{
display:flex;
flex-direction:column;
gap:16px;
}
:root{
--viewer-container-height:0;
--pdfViewer-padding-bottom:0;
--page-margin:1px auto -8px;
--page-border:9px solid transparent;
--spreadHorizontalWrapped-margin-LR:-3.5px;
--loading-icon-delay:400ms;
--focus-ring-color:#0060df;
--focus-ring-outline:2px solid var(--focus-ring-color);
}
@media (prefers-color-scheme: dark){
:root:where(:not(.is-light)){
--focus-ring-color:#0df;
}
}
:root:where(.is-dark){
--focus-ring-color:#0df;
}
@media screen and (forced-colors: active){
:root{
--pdfViewer-padding-bottom:9px;
--page-margin:8px auto -1px;
--page-border:1px solid CanvasText;
--spreadHorizontalWrapped-margin-LR:3.5px;
--focus-ring-color:CanvasText;
}
}
[data-main-rotation="90"]{
transform:rotate(90deg) translateY(-100%);
}
[data-main-rotation="180"]{
transform:rotate(180deg) translate(-100%, -100%);
}
[data-main-rotation="270"]{
transform:rotate(270deg) translateX(-100%);
}
#hiddenCopyElement,
.hiddenCanvasElement{
position:absolute;
top:0;
left:0;
width:0;
height:0;
display:none;
}
.pdfViewer{
--scale-factor:1;
--page-bg-color:unset;
padding-bottom:var(--pdfViewer-padding-bottom);
--hcm-highlight-filter:none;
--hcm-highlight-selected-filter:none;
}
@media screen and (forced-colors: active){
.pdfViewer{
--hcm-highlight-filter:invert(100%);
}
}
.pdfViewer.copyAll{
cursor:wait;
}
.pdfViewer .canvasWrapper{
overflow:hidden;
width:100%;
height:100%;
}
:is(.pdfViewer .canvasWrapper) canvas{
position:absolute;
top:0;
left:0;
margin:0;
display:block;
width:100%;
height:100%;
contain:content;
}
:is(:is(.pdfViewer .canvasWrapper) canvas) .structTree{
contain:strict;
}
.pdfViewer .page{
--user-unit:1;
--total-scale-factor:calc(var(--scale-factor) * var(--user-unit));
--scale-round-x:1px;
--scale-round-y:1px;
direction:ltr;
width:816px;
height:1056px;
margin:var(--page-margin);
position:relative;
overflow:visible;
border:var(--page-border);
background-clip:content-box;
background-color:var(--page-bg-color, rgb(255 255 255));
}
.pdfViewer .dummyPage{
position:relative;
width:0;
height:var(--viewer-container-height);
}
.pdfViewer.noUserSelect{
-webkit-user-select:none;
-moz-user-select:none;
user-select:none;
}
.pdfViewer.removePageBorders .page{
margin:0 auto 10px;
border:none;
}
.pdfViewer:is(.scrollHorizontal, .scrollWrapped),
.spread{
margin-inline:3.5px;
text-align:center;
}
.pdfViewer.scrollHorizontal,
.spread{
white-space:nowrap;
}
.pdfViewer.removePageBorders,
.pdfViewer:is(.scrollHorizontal, .scrollWrapped) .spread{
margin-inline:0;
}
.spread :is(.page, .dummyPage),
.pdfViewer:is(.scrollHorizontal, .scrollWrapped) :is(.page, .spread){
display:inline-block;
vertical-align:middle;
}
.spread .page,
.pdfViewer:is(.scrollHorizontal, .scrollWrapped) .page{
margin-inline:var(--spreadHorizontalWrapped-margin-LR);
}
.pdfViewer.removePageBorders .spread .page,
.pdfViewer.removePageBorders:is(.scrollHorizontal, .scrollWrapped) .page{
margin-inline:5px;
}
.pdfViewer .page.loadingIcon::after{
position:absolute;
top:0;
left:0;
content:"";
width:100%;
height:100%;
background:url("images/loading-icon.gif") center no-repeat;
display:none;
transition-property:display;
transition-delay:var(--loading-icon-delay);
z-index:5;
contain:strict;
}
.pdfViewer .page.loading::after{
display:block;
}
.pdfViewer .page:not(.loading)::after{
transition-property:none;
display:none;
}
.pdfPresentationMode .pdfViewer{
padding-bottom:0;
}
.pdfPresentationMode .spread{
margin:0;
}
.pdfPresentationMode .pdfViewer .page{
margin:0 auto;
border:2px solid transparent;
}
:root{
--dir-factor:1;
--inline-start:left;
--inline-end:right;
--sidebar-width:200px;
--sidebar-transition-duration:200ms;
--sidebar-transition-timing-function:ease;
--toolbar-height:32px;
--toolbar-horizontal-padding:1px;
--toolbar-vertical-padding:2px;
--icon-size:16px;
--toolbar-icon-opacity:0.7;
--doorhanger-icon-opacity:0.9;
--doorhanger-height:8px;
--main-color:rgb(12 12 13);
--body-bg-color:rgb(212 212 215);
--progressBar-color:rgb(10 132 255);
--progressBar-bg-color:rgb(221 221 222);
--progressBar-blend-color:rgb(116 177 239);
--scrollbar-color:auto;
--scrollbar-bg-color:auto;
--toolbar-icon-bg-color:rgb(0 0 0);
--toolbar-icon-hover-bg-color:rgb(0 0 0);
--sidebar-narrow-bg-color:rgb(212 212 215 / 0.9);
--sidebar-toolbar-bg-color:rgb(245 246 247);
--toolbar-bg-color:rgb(249 249 250);
--toolbar-border-color:rgb(184 184 184);
--toolbar-box-shadow:0 1px 0 var(--toolbar-border-color);
--toolbar-border-bottom:none;
--toolbarSidebar-box-shadow:inset calc(-1px * var(--dir-factor)) 0 0 rgb(0 0 0 / 0.25), 0 1px 0 rgb(0 0 0 / 0.15), 0 0 1px rgb(0 0 0 / 0.1);
--toolbarSidebar-border-bottom:none;
--button-hover-color:color-mix(in srgb, currentColor 17%, transparent);
--toggled-btn-color:rgb(0 0 0);
--toggled-btn-bg-color:rgb(0 0 0 / 0.3);
--toggled-hover-active-btn-color:rgb(0 0 0 / 0.4);
--toggled-hover-btn-outline:none;
--dropdown-btn-bg-color:rgb(215 215 219);
--dropdown-btn-border:none;
--separator-color:rgb(0 0 0 / 0.3);
--field-color:rgb(6 6 6);
--field-bg-color:rgb(255 255 255);
--field-border-color:rgb(187 187 188);
--treeitem-color:rgb(0 0 0 / 0.8);
--treeitem-bg-color:rgb(0 0 0 / 0.15);
--treeitem-hover-color:rgb(0 0 0 / 0.9);
--treeitem-selected-color:rgb(0 0 0 / 0.9);
--treeitem-selected-bg-color:rgb(0 0 0 / 0.25);
--thumbnail-hover-color:rgb(0 0 0 / 0.1);
--thumbnail-selected-color:rgb(0 0 0 / 0.2);
--doorhanger-bg-color:rgb(255 255 255);
--doorhanger-border-color:rgb(12 12 13 / 0.2);
--doorhanger-hover-color:rgb(12 12 13);
--doorhanger-separator-color:rgb(222 222 222);
--dialog-button-border:none;
--dialog-button-bg-color:rgb(12 12 13 / 0.1);
--dialog-button-hover-bg-color:rgb(12 12 13 / 0.3);
--loading-icon:url(images/loading.svg);
--treeitem-expanded-icon:url(images/treeitem-expanded.svg);
--treeitem-collapsed-icon:url(images/treeitem-collapsed.svg);
--toolbarButton-editorFreeText-icon:url(images/toolbarButton-editorFreeText.svg);
--toolbarButton-editorHighlight-icon:url(images/toolbarButton-editorHighlight.svg);
--toolbarButton-editorInk-icon:url(images/toolbarButton-editorInk.svg);
--toolbarButton-editorStamp-icon:url(images/toolbarButton-editorStamp.svg);
--toolbarButton-editorSignature-icon:url(images/toolbarButton-editorSignature.svg);
--toolbarButton-menuArrow-icon:url(images/toolbarButton-menuArrow.svg);
--toolbarButton-sidebarToggle-icon:url(images/toolbarButton-sidebarToggle.svg);
--toolbarButton-secondaryToolbarToggle-icon:url(images/toolbarButton-secondaryToolbarToggle.svg);
--toolbarButton-pageUp-icon:url(images/toolbarButton-pageUp.svg);
--toolbarButton-pageDown-icon:url(images/toolbarButton-pageDown.svg);
--toolbarButton-zoomOut-icon:url(images/toolbarButton-zoomOut.svg);
--toolbarButton-zoomIn-icon:url(images/toolbarButton-zoomIn.svg);
--toolbarButton-presentationMode-icon:url(images/toolbarButton-presentationMode.svg);
--toolbarButton-print-icon:url(images/toolbarButton-print.svg);
--toolbarButton-openFile-icon:url(images/toolbarButton-openFile.svg);
--toolbarButton-download-icon:url(images/toolbarButton-download.svg);
--toolbarButton-bookmark-icon:url(images/toolbarButton-bookmark.svg);
--toolbarButton-viewThumbnail-icon:url(images/toolbarButton-viewThumbnail.svg);
--toolbarButton-viewOutline-icon:url(images/toolbarButton-viewOutline.svg);
--toolbarButton-viewAttachments-icon:url(images/toolbarButton-viewAttachments.svg);
--toolbarButton-viewLayers-icon:url(images/toolbarButton-viewLayers.svg);
--toolbarButton-currentOutlineItem-icon:url(images/toolbarButton-currentOutlineItem.svg);
--toolbarButton-search-icon:url(images/toolbarButton-search.svg);
--findbarButton-previous-icon:url(images/findbarButton-previous.svg);
--findbarButton-next-icon:url(images/findbarButton-next.svg);
--secondaryToolbarButton-firstPage-icon:url(images/secondaryToolbarButton-firstPage.svg);
--secondaryToolbarButton-lastPage-icon:url(images/secondaryToolbarButton-lastPage.svg);
--secondaryToolbarButton-rotateCcw-icon:url(images/secondaryToolbarButton-rotateCcw.svg);
--secondaryToolbarButton-rotateCw-icon:url(images/secondaryToolbarButton-rotateCw.svg);
--secondaryToolbarButton-selectTool-icon:url(images/secondaryToolbarButton-selectTool.svg);
--secondaryToolbarButton-handTool-icon:url(images/secondaryToolbarButton-handTool.svg);
--secondaryToolbarButton-scrollPage-icon:url(images/secondaryToolbarButton-scrollPage.svg);
--secondaryToolbarButton-scrollVertical-icon:url(images/secondaryToolbarButton-scrollVertical.svg);
--secondaryToolbarButton-scrollHorizontal-icon:url(images/secondaryToolbarButton-scrollHorizontal.svg);
--secondaryToolbarButton-scrollWrapped-icon:url(images/secondaryToolbarButton-scrollWrapped.svg);
--secondaryToolbarButton-spreadNone-icon:url(images/secondaryToolbarButton-spreadNone.svg);
--secondaryToolbarButton-spreadOdd-icon:url(images/secondaryToolbarButton-spreadOdd.svg);
--secondaryToolbarButton-spreadEven-icon:url(images/secondaryToolbarButton-spreadEven.svg);
--secondaryToolbarButton-imageAltTextSettings-icon:var(
--toolbarButton-editorStamp-icon
);
--secondaryToolbarButton-documentProperties-icon:url(images/secondaryToolbarButton-documentProperties.svg);
--editorParams-stampAddImage-icon:url(images/toolbarButton-zoomIn.svg);
}
[dir="rtl"]:root{
--dir-factor:-1;
--inline-start:right;
--inline-end:left;
}
@media (prefers-color-scheme: dark){
:root:where(:not(.is-light)){
--main-color:rgb(249 249 250);
--body-bg-color:rgb(42 42 46);
--progressBar-color:rgb(0 96 223);
--progressBar-bg-color:rgb(40 40 43);
--progressBar-blend-color:rgb(20 68 133);
--scrollbar-color:rgb(121 121 123);
--scrollbar-bg-color:rgb(35 35 39);
--toolbar-icon-bg-color:rgb(255 255 255);
--toolbar-icon-hover-bg-color:rgb(255 255 255);
--sidebar-narrow-bg-color:rgb(42 42 46 / 0.9);
--sidebar-toolbar-bg-color:rgb(50 50 52);
--toolbar-bg-color:rgb(56 56 61);
--toolbar-border-color:rgb(12 12 13);
--toggled-btn-color:rgb(255 255 255);
--toggled-btn-bg-color:rgb(0 0 0 / 0.3);
--toggled-hover-active-btn-color:rgb(0 0 0 / 0.4);
--dropdown-btn-bg-color:rgb(74 74 79);
--separator-color:rgb(0 0 0 / 0.3);
--field-color:rgb(250 250 250);
--field-bg-color:rgb(64 64 68);
--field-border-color:rgb(115 115 115);
--treeitem-color:rgb(255 255 255 / 0.8);
--treeitem-bg-color:rgb(255 255 255 / 0.15);
--treeitem-hover-color:rgb(255 255 255 / 0.9);
--treeitem-selected-color:rgb(255 255 255 / 0.9);
--treeitem-selected-bg-color:rgb(255 255 255 / 0.25);
--thumbnail-hover-color:rgb(255 255 255 / 0.1);
--thumbnail-selected-color:rgb(255 255 255 / 0.2);
--doorhanger-bg-color:#42414d;
--doorhanger-border-color:rgb(39 39 43);
--doorhanger-hover-color:rgb(249 249 250);
--doorhanger-separator-color:rgb(92 92 97);
--dialog-button-bg-color:rgb(92 92 97);
--dialog-button-hover-bg-color:rgb(115 115 115);
}
}
:root:where(.is-dark){
--main-color:rgb(249 249 250);
--body-bg-color:rgb(42 42 46);
--progressBar-color:rgb(0 96 223);
--progressBar-bg-color:rgb(40 40 43);
--progressBar-blend-color:rgb(20 68 133);
--scrollbar-color:rgb(121 121 123);
--scrollbar-bg-color:rgb(35 35 39);
--toolbar-icon-bg-color:rgb(255 255 255);
--toolbar-icon-hover-bg-color:rgb(255 255 255);
--sidebar-narrow-bg-color:rgb(42 42 46 / 0.9);
--sidebar-toolbar-bg-color:rgb(50 50 52);
--toolbar-bg-color:rgb(56 56 61);
--toolbar-border-color:rgb(12 12 13);
--toggled-btn-color:rgb(255 255 255);
--toggled-btn-bg-color:rgb(0 0 0 / 0.3);
--toggled-hover-active-btn-color:rgb(0 0 0 / 0.4);
--dropdown-btn-bg-color:rgb(74 74 79);
--separator-color:rgb(0 0 0 / 0.3);
--field-color:rgb(250 250 250);
--field-bg-color:rgb(64 64 68);
--field-border-color:rgb(115 115 115);
--treeitem-color:rgb(255 255 255 / 0.8);
--treeitem-bg-color:rgb(255 255 255 / 0.15);
--treeitem-hover-color:rgb(255 255 255 / 0.9);
--treeitem-selected-color:rgb(255 255 255 / 0.9);
--treeitem-selected-bg-color:rgb(255 255 255 / 0.25);
--thumbnail-hover-color:rgb(255 255 255 / 0.1);
--thumbnail-selected-color:rgb(255 255 255 / 0.2);
--doorhanger-bg-color:#42414d;
--doorhanger-border-color:rgb(39 39 43);
--doorhanger-hover-color:rgb(249 249 250);
--doorhanger-separator-color:rgb(92 92 97);
--dialog-button-bg-color:rgb(92 92 97);
--dialog-button-hover-bg-color:rgb(115 115 115);
}
@media screen and (forced-colors: active){
:root{
--button-hover-color:Highlight;
--toolbar-icon-opacity:1;
--toolbar-icon-bg-color:ButtonText;
--toolbar-icon-hover-bg-color:ButtonFace;
--toggled-hover-active-btn-color:ButtonText;
--toggled-hover-btn-outline:2px solid ButtonBorder;
--toolbar-border-color:CanvasText;
--toolbar-border-bottom:1px solid var(--toolbar-border-color);
--toolbar-box-shadow:none;
--toggled-btn-color:HighlightText;
--toggled-btn-bg-color:LinkText;
--doorhanger-hover-color:ButtonFace;
--doorhanger-border-color-whcm:1px solid ButtonText;
--doorhanger-triangle-opacity-whcm:0;
--dialog-button-border:1px solid Highlight;
--dialog-button-hover-bg-color:Highlight;
--dialog-button-hover-color:ButtonFace;
--dropdown-btn-border:1px solid ButtonText;
--field-border-color:ButtonText;
--main-color:CanvasText;
--separator-color:GrayText;
--doorhanger-separator-color:GrayText;
--toolbarSidebar-box-shadow:none;
--toolbarSidebar-border-bottom:1px solid var(--toolbar-border-color);
}
}
@media screen and (prefers-reduced-motion: reduce){
:root{
--sidebar-transition-duration:0;
}
}
@keyframes progressIndeterminate{
0%{
transform:translateX(calc(-142px * var(--dir-factor)));
}
100%{
transform:translateX(0);
}
}
html[data-toolbar-density="compact"]{
--toolbar-height:30px;
}
html[data-toolbar-density="touch"]{
--toolbar-height:44px;
}
html,
body{
height:100%;
width:100%;
}
body{
margin:0;
background-color:var(--body-bg-color);
scrollbar-color:var(--scrollbar-color) var(--scrollbar-bg-color);
}
body.wait::before{
content:"";
position:fixed;
width:100%;
height:100%;
z-index:100000;
cursor:wait;
}
.hidden,
[hidden]{
display:none !important;
}
#viewerContainer.pdfPresentationMode:fullscreen{
top:0;
background-color:rgb(0 0 0);
width:100%;
height:100%;
overflow:hidden;
cursor:none;
-webkit-user-select:none;
-moz-user-select:none;
user-select:none;
}
.pdfPresentationMode:fullscreen section:not([data-internal-link]){
pointer-events:none;
}
.pdfPresentationMode:fullscreen .textLayer span{
cursor:none;
}
.pdfPresentationMode.pdfPresentationModeControls > *,
.pdfPresentationMode.pdfPresentationModeControls .textLayer span{
cursor:default;
}
#outerContainer{
width:100%;
height:100%;
position:relative;
margin:0;
}
#sidebarContainer{
position:absolute;
inset-block:var(--toolbar-height) 0;
inset-inline-start:calc(-1 * var(--sidebar-width));
width:var(--sidebar-width);
visibility:hidden;
z-index:1;
font:message-box;
border-top:1px solid transparent;
border-inline-end:var(--doorhanger-border-color-whcm);
transition-property:inset-inline-start;
transition-duration:var(--sidebar-transition-duration);
transition-timing-function:var(--sidebar-transition-timing-function);
}
#outerContainer:is(.sidebarMoving, .sidebarOpen) #sidebarContainer{
visibility:visible;
}
#outerContainer.sidebarOpen #sidebarContainer{
inset-inline-start:0;
}
#mainContainer{
position:absolute;
inset:0;
min-width:350px;
margin:0;
display:flex;
flex-direction:column;
}
#sidebarContent{
inset-block:var(--toolbar-height) 0;
inset-inline-start:0;
overflow:auto;
position:absolute;
width:100%;
box-shadow:inset calc(-1px * var(--dir-factor)) 0 0 rgb(0 0 0 / 0.25);
}
#viewerContainer{
overflow:auto;
position:absolute;
inset:var(--toolbar-height) 0 0;
outline:none;
z-index:0;
}
#viewerContainer:not(.pdfPresentationMode){
transition-duration:var(--sidebar-transition-duration);
transition-timing-function:var(--sidebar-transition-timing-function);
}
#outerContainer.sidebarOpen #viewerContainer:not(.pdfPresentationMode){
inset-inline-start:var(--sidebar-width);
transition-property:inset-inline-start;
}
#sidebarContainer :is(input, button, select){
font:message-box;
}
.toolbar{
z-index:2;
}
#toolbarSidebar{
width:100%;
height:var(--toolbar-height);
background-color:var(--sidebar-toolbar-bg-color);
box-shadow:var(--toolbarSidebar-box-shadow);
border-bottom:var(--toolbarSidebar-border-bottom);
padding:var(--toolbar-vertical-padding) var(--toolbar-horizontal-padding);
justify-content:space-between;
}
#toolbarSidebar #toolbarSidebarLeft{
width:auto;
height:100%;
}
:is(#toolbarSidebar #toolbarSidebarLeft) #viewThumbnail::before{
-webkit-mask-image:var(--toolbarButton-viewThumbnail-icon);
mask-image:var(--toolbarButton-viewThumbnail-icon);
}
:is(#toolbarSidebar #toolbarSidebarLeft) #viewOutline::before{
-webkit-mask-image:var(--toolbarButton-viewOutline-icon);
mask-image:var(--toolbarButton-viewOutline-icon);
transform:scaleX(var(--dir-factor));
}
:is(#toolbarSidebar #toolbarSidebarLeft) #viewAttachments::before{
-webkit-mask-image:var(--toolbarButton-viewAttachments-icon);
mask-image:var(--toolbarButton-viewAttachments-icon);
}
:is(#toolbarSidebar #toolbarSidebarLeft) #viewLayers::before{
-webkit-mask-image:var(--toolbarButton-viewLayers-icon);
mask-image:var(--toolbarButton-viewLayers-icon);
}
#toolbarSidebar #toolbarSidebarRight{
width:auto;
height:100%;
padding-inline-end:2px;
}
#sidebarResizer{
position:absolute;
inset-block:0;
inset-inline-end:-6px;
width:6px;
z-index:200;
cursor:ew-resize;
}
#outerContainer.sidebarOpen #loadingBar{
inset-inline-start:var(--sidebar-width);
}
#outerContainer.sidebarResizing
:is(#sidebarContainer, #viewerContainer, #loadingBar){
transition-duration:0s;
}
.doorHanger,
.doorHangerRight{
border-radius:2px;
box-shadow:0 1px 5px var(--doorhanger-border-color), 0 0 0 1px var(--doorhanger-border-color);
border:var(--doorhanger-border-color-whcm);
background-color:var(--doorhanger-bg-color);
inset-block-start:calc(100% + var(--doorhanger-height) - 2px);
}
:is(.doorHanger,.doorHangerRight)::after,:is(.doorHanger,.doorHangerRight)::before{
bottom:100%;
border-style:solid;
border-color:transparent;
content:"";
height:0;
width:0;
position:absolute;
pointer-events:none;
opacity:var(--doorhanger-triangle-opacity-whcm);
}
:is(.doorHanger,.doorHangerRight)::before{
border-width:calc(var(--doorhanger-height) + 2px);
border-bottom-color:var(--doorhanger-border-color);
}
:is(.doorHanger,.doorHangerRight)::after{
border-width:var(--doorhanger-height);
}
.doorHangerRight{
inset-inline-end:calc(50% - var(--doorhanger-height) - 1px);
}
.doorHangerRight::before{
inset-inline-end:-1px;
}
.doorHangerRight::after{
border-bottom-color:var(--doorhanger-bg-color);
inset-inline-end:1px;
}
.doorHanger{
inset-inline-start:calc(50% - var(--doorhanger-height) - 1px);
}
.doorHanger::before{
inset-inline-start:-1px;
}
.doorHanger::after{
border-bottom-color:var(--toolbar-bg-color);
inset-inline-start:1px;
}
.dialogButton{
border:none;
background:none;
width:28px;
height:28px;
outline:none;
}
.dialogButton:is(:hover, :focus-visible){
background-color:var(--dialog-button-hover-bg-color);
}
.dialogButton:is(:hover, :focus-visible) > span{
color:var(--dialog-button-hover-color);
}
.splitToolbarButtonSeparator{
float:var(--inline-start);
width:0;
height:62%;
border-left:1px solid var(--separator-color);
border-right:none;
}
.dialogButton{
min-width:16px;
margin:2px 1px;
padding:2px 6px 0;
border:none;
border-radius:2px;
color:var(--main-color);
font-size:12px;
line-height:14px;
-webkit-user-select:none;
-moz-user-select:none;
user-select:none;
cursor:default;
box-sizing:border-box;
}
.treeItemToggler::before{
position:absolute;
display:inline-block;
width:16px;
height:16px;
content:"";
background-color:var(--toolbar-icon-bg-color);
-webkit-mask-size:cover;
mask-size:cover;
}
#sidebarToggleButton::before{
-webkit-mask-image:var(--toolbarButton-sidebarToggle-icon);
mask-image:var(--toolbarButton-sidebarToggle-icon);
transform:scaleX(var(--dir-factor));
}
#secondaryToolbarToggleButton::before{
-webkit-mask-image:var(--toolbarButton-secondaryToolbarToggle-icon);
mask-image:var(--toolbarButton-secondaryToolbarToggle-icon);
transform:scaleX(var(--dir-factor));
}
#previous::before{
-webkit-mask-image:var(--toolbarButton-pageUp-icon);
mask-image:var(--toolbarButton-pageUp-icon);
}
#next::before{
-webkit-mask-image:var(--toolbarButton-pageDown-icon);
mask-image:var(--toolbarButton-pageDown-icon);
}
#zoomOutButton::before{
-webkit-mask-image:var(--toolbarButton-zoomOut-icon);
mask-image:var(--toolbarButton-zoomOut-icon);
}
#zoomInButton::before{
-webkit-mask-image:var(--toolbarButton-zoomIn-icon);
mask-image:var(--toolbarButton-zoomIn-icon);
}
#presentationMode::before{
-webkit-mask-image:var(--toolbarButton-presentationMode-icon);
mask-image:var(--toolbarButton-presentationMode-icon);
}
#editorFreeTextButton::before{
-webkit-mask-image:var(--toolbarButton-editorFreeText-icon);
mask-image:var(--toolbarButton-editorFreeText-icon);
}
#editorHighlightButton::before{
-webkit-mask-image:var(--toolbarButton-editorHighlight-icon);
mask-image:var(--toolbarButton-editorHighlight-icon);
}
#editorInkButton::before{
-webkit-mask-image:var(--toolbarButton-editorInk-icon);
mask-image:var(--toolbarButton-editorInk-icon);
}
#editorStampButton::before{
-webkit-mask-image:var(--toolbarButton-editorStamp-icon);
mask-image:var(--toolbarButton-editorStamp-icon);
}
#editorSignatureButton::before{
-webkit-mask-image:var(--toolbarButton-editorSignature-icon);
mask-image:var(--toolbarButton-editorSignature-icon);
}
#printButton::before{
-webkit-mask-image:var(--toolbarButton-print-icon);
mask-image:var(--toolbarButton-print-icon);
}
#secondaryOpenFile::before{
-webkit-mask-image:var(--toolbarButton-openFile-icon);
mask-image:var(--toolbarButton-openFile-icon);
}
#downloadButton::before{
-webkit-mask-image:var(--toolbarButton-download-icon);
mask-image:var(--toolbarButton-download-icon);
}
#viewBookmark::before{
-webkit-mask-image:var(--toolbarButton-bookmark-icon);
mask-image:var(--toolbarButton-bookmark-icon);
}
#currentOutlineItem::before{
-webkit-mask-image:var(--toolbarButton-currentOutlineItem-icon);
mask-image:var(--toolbarButton-currentOutlineItem-icon);
transform:scaleX(var(--dir-factor));
}
#viewFindButton::before{
-webkit-mask-image:var(--toolbarButton-search-icon);
mask-image:var(--toolbarButton-search-icon);
}
.pdfSidebarNotification::after{
position:absolute;
display:inline-block;
top:2px;
inset-inline-end:2px;
content:"";
background-color:rgb(112 219 85);
height:9px;
width:9px;
border-radius:50%;
}
.verticalToolbarSeparator{
display:block;
margin-inline:2px;
width:0;
height:80%;
border-left:1px solid var(--separator-color);
border-right:none;
box-sizing:border-box;
}
.horizontalToolbarSeparator{
display:block;
margin:6px 0;
border-top:1px solid var(--doorhanger-separator-color);
border-bottom:none;
height:0;
width:100%;
}
.toggleButton{
display:inline;
}
.toggleButton:has( > input:checked){
color:var(--toggled-btn-color);
background-color:var(--toggled-btn-bg-color);
}
.toggleButton:is(:hover,:has( > input:focus-visible)){
color:var(--toggled-btn-color);
background-color:var(--button-hover-color);
}
.toggleButton > input{
position:absolute;
top:50%;
left:50%;
opacity:0;
width:0;
height:0;
}
.toolbarField{
padding:4px 7px;
margin:3px 0;
border-radius:2px;
background-color:var(--field-bg-color);
background-clip:padding-box;
border:1px solid var(--field-border-color);
box-shadow:none;
color:var(--field-color);
font-size:12px;
line-height:16px;
outline:none;
}
.toolbarField:focus{
border-color:#0a84ff;
}
#pageNumber{
-moz-appearance:textfield;
text-align:end;
width:40px;
background-size:0 0;
transition-property:none;
}
#pageNumber::-webkit-inner-spin-button{
-webkit-appearance:none;
}
.loadingInput:has( > .loading:is(#pageNumber))::after{
display:inline;
visibility:visible;
transition-property:visibility;
transition-delay:var(--loading-icon-delay);
}
.loadingInput{
position:relative;
}
.loadingInput::after{
position:absolute;
visibility:hidden;
display:none;
width:var(--icon-size);
height:var(--icon-size);
content:"";
background-color:var(--toolbar-icon-bg-color);
-webkit-mask-size:cover;
mask-size:cover;
-webkit-mask-image:var(--loading-icon);
mask-image:var(--loading-icon);
}
.loadingInput.start::after{
inset-inline-start:4px;
}
.loadingInput.end::after{
inset-inline-end:4px;
}
#thumbnailView,
#outlineView,
#attachmentsView,
#layersView{
position:absolute;
width:calc(100% - 8px);
inset-block:0;
padding:4px 4px 0;
overflow:auto;
-webkit-user-select:none;
-moz-user-select:none;
user-select:none;
}
#thumbnailView{
width:calc(100% - 60px);
padding:10px 30px 0;
}
#thumbnailView > a:is(:active, :focus){
outline:0;
}
.thumbnail{
--thumbnail-width:0;
--thumbnail-height:0;
float:var(--inline-start);
width:var(--thumbnail-width);
height:var(--thumbnail-height);
margin:0 10px 5px;
padding:1px;
border:7px solid transparent;
border-radius:2px;
}
#thumbnailView > a:last-of-type > .thumbnail{
margin-bottom:10px;
}
a:focus > .thumbnail,
.thumbnail:hover{
border-color:var(--thumbnail-hover-color);
}
.thumbnail.selected{
border-color:var(--thumbnail-selected-color) !important;
}
.thumbnailImage{
width:var(--thumbnail-width);
height:var(--thumbnail-height);
opacity:0.9;
}
a:focus > .thumbnail > .thumbnailImage,
.thumbnail:hover > .thumbnailImage{
opacity:0.95;
}
.thumbnail.selected > .thumbnailImage{
opacity:1 !important;
}
.thumbnail:not([data-loaded]) > .thumbnailImage{
width:calc(var(--thumbnail-width) - 2px);
height:calc(var(--thumbnail-height) - 2px);
border:1px dashed rgb(132 132 132);
}
.treeWithDeepNesting > .treeItem,
.treeItem > .treeItems{
margin-inline-start:20px;
}
.treeItem > a{
text-decoration:none;
display:inline-block;
min-width:calc(100% - 4px);
height:auto;
margin-bottom:1px;
padding:2px 0 5px;
padding-inline-start:4px;
border-radius:2px;
color:var(--treeitem-color);
font-size:13px;
line-height:15px;
-webkit-user-select:none;
-moz-user-select:none;
user-select:none;
white-space:normal;
cursor:pointer;
}
#layersView .treeItem > a *{
cursor:pointer;
}
#layersView .treeItem > a > label{
padding-inline-start:4px;
}
#layersView .treeItem > a > label > input{
float:var(--inline-start);
margin-top:1px;
}
.treeItemToggler{
position:relative;
float:var(--inline-start);
height:0;
width:0;
color:rgb(255 255 255 / 0.5);
}
.treeItemToggler::before{
inset-inline-end:4px;
-webkit-mask-image:var(--treeitem-expanded-icon);
mask-image:var(--treeitem-expanded-icon);
}
.treeItemToggler.treeItemsHidden::before{
-webkit-mask-image:var(--treeitem-collapsed-icon);
mask-image:var(--treeitem-collapsed-icon);
transform:scaleX(var(--dir-factor));
}
.treeItemToggler.treeItemsHidden ~ .treeItems{
display:none;
}
.treeItem.selected > a{
background-color:var(--treeitem-selected-bg-color);
color:var(--treeitem-selected-color);
}
.treeItemToggler:hover,
.treeItemToggler:hover + a,
.treeItemToggler:hover ~ .treeItems,
.treeItem > a:hover{
background-color:var(--treeitem-bg-color);
background-clip:padding-box;
border-radius:2px;
color:var(--treeitem-hover-color);
}
#outlineOptionsContainer{
display:none;
}
#sidebarContainer:has(#outlineView:not(.hidden)) #outlineOptionsContainer{
display:inline flex;
}
.dialogButton{
width:auto;
margin:3px 4px 2px !important;
padding:2px 11px;
color:var(--main-color);
background-color:var(--dialog-button-bg-color);
border:var(--dialog-button-border) !important;
}
dialog{
margin:auto;
padding:15px;
border-spacing:4px;
color:var(--main-color);
font:message-box;
font-size:12px;
line-height:14px;
background-color:var(--doorhanger-bg-color);
border:1px solid rgb(0 0 0 / 0.5);
border-radius:4px;
box-shadow:0 1px 4px rgb(0 0 0 / 0.3);
}
dialog::backdrop{
background-color:rgb(0 0 0 / 0.2);
}
dialog > .row{
display:table-row;
}
dialog > .row > *{
display:table-cell;
}
dialog .toolbarField{
margin:5px 0;
}
dialog .separator{
display:block;
margin:4px 0;
height:0;
width:100%;
border-top:1px solid var(--separator-color);
border-bottom:none;
}
dialog .buttonRow{
text-align:center;
vertical-align:middle;
}
dialog :link{
color:rgb(255 255 255);
}
#passwordDialog{
text-align:center;
}
#passwordDialog .toolbarField{
width:200px;
}
#documentPropertiesDialog{
text-align:left;
}
#documentPropertiesDialog .row > *{
min-width:100px;
text-align:start;
}
#documentPropertiesDialog .row > span{
width:125px;
word-wrap:break-word;
}
#documentPropertiesDialog .row > p{
max-width:225px;
word-wrap:break-word;
}
#documentPropertiesDialog .buttonRow{
margin-top:10px;
}
.grab-to-pan-grab{
cursor:grab !important;
}
.grab-to-pan-grab
*:not(input):not(textarea):not(button):not(select):not(:link){
cursor:inherit !important;
}
.grab-to-pan-grab:active,
.grab-to-pan-grabbing{
cursor:grabbing !important;
}
.grab-to-pan-grabbing{
position:fixed;
background:rgb(0 0 0 / 0);
display:block;
inset:0;
overflow:hidden;
z-index:50000;
}
.toolbarButton{
height:100%;
aspect-ratio:1;
display:flex;
align-items:center;
justify-content:center;
background:none;
border:none;
color:var(--main-color);
outline:none;
border-radius:2px;
box-sizing:border-box;
font:message-box;
flex:none;
position:relative;
padding:0;
}
.toolbarButton > span{
display:inline-block;
width:0;
height:0;
overflow:hidden;
}
.toolbarButton::before{
opacity:var(--toolbar-icon-opacity);
display:inline-block;
width:var(--icon-size);
height:var(--icon-size);
content:"";
background-color:var(--toolbar-icon-bg-color);
-webkit-mask-size:cover;
mask-size:cover;
-webkit-mask-position:center;
mask-position:center;
}
.toolbarButton.toggled{
background-color:var(--toggled-btn-bg-color);
color:var(--toggled-btn-color);
}
.toolbarButton.toggled::before{
background-color:var(--toggled-btn-color);
}
.toolbarButton.toggled:hover{
outline:var(--toggled-hover-btn-outline) !important;
}
.toolbarButton.toggled:hover:active{
background-color:var(--toggled-hover-active-btn-color);
}
.toolbarButton:is(:hover,:focus-visible){
background-color:var(--button-hover-color);
}
.toolbarButton:is(:hover,:focus-visible)::before{
background-color:var(--toolbar-icon-hover-bg-color);
}
.toolbarButton:is([disabled="disabled"],[disabled]){
opacity:0.5;
pointer-events:none;
}
.toolbarButton.labeled{
width:100%;
min-height:var(--menuitem-height);
justify-content:flex-start;
gap:8px;
padding-inline-start:12px;
aspect-ratio:unset;
text-align:start;
white-space:normal;
cursor:default;
}
.toolbarButton.labeled:is(a){
text-decoration:none;
}
.toolbarButton.labeled[href="#"]:is(a){
opacity:0.5;
pointer-events:none;
}
.toolbarButton.labeled::before{
opacity:var(--doorhanger-icon-opacity);
}
.toolbarButton.labeled:is(:hover,:focus-visible){
color:var(--doorhanger-hover-color);
}
.toolbarButton.labeled > span{
display:inline-block;
width:-moz-max-content;
width:max-content;
height:auto;
}
.toolbarButtonWithContainer{
height:100%;
aspect-ratio:1;
display:inline-block;
position:relative;
flex:none;
}
.toolbarButtonWithContainer > .toolbarButton{
width:100%;
height:100%;
}
.toolbarButtonWithContainer .menu{
padding-block:5px;
}
.toolbarButtonWithContainer .menuContainer{
width:100%;
height:auto;
max-height:calc(
var(--viewer-container-height) - var(--toolbar-height) -
var(--doorhanger-height)
);
display:flex;
flex-direction:column;
box-sizing:border-box;
overflow-y:auto;
}
.toolbarButtonWithContainer .editorParamsToolbar{
height:auto;
width:220px;
position:absolute;
z-index:30000;
cursor:default;
}
:is(.toolbarButtonWithContainer .editorParamsToolbar) :is(#editorStampAddImage,#editorSignatureAddSignature)::before{
-webkit-mask-image:var(--editorParams-stampAddImage-icon);
mask-image:var(--editorParams-stampAddImage-icon);
}
:is(.toolbarButtonWithContainer .editorParamsToolbar) .editorParamsLabel{
flex:none;
font:menu;
font-size:13px;
font-style:normal;
font-weight:400;
line-height:150%;
width:-moz-fit-content;
width:fit-content;
inset-inline-start:0;
color:var(--main-color);
}
:is(.toolbarButtonWithContainer .editorParamsToolbar) button:is(:hover,:focus-visible) .editorParamsLabel{
color:var(--doorhanger-hover-color);
}
:is(.toolbarButtonWithContainer .editorParamsToolbar) .editorParamsToolbarContainer{
width:100%;
height:auto;
display:flex;
flex-direction:column;
box-sizing:border-box;
padding-inline:10px;
padding-block:10px;
}
:is(:is(.toolbarButtonWithContainer .editorParamsToolbar) .editorParamsToolbarContainer) > .editorParamsSetter{
min-height:26px;
display:flex;
align-items:center;
justify-content:space-between;
}
:is(:is(.toolbarButtonWithContainer .editorParamsToolbar) .editorParamsToolbarContainer) .editorParamsColor{
width:32px;
height:32px;
flex:none;
padding:0;
}
:is(:is(.toolbarButtonWithContainer .editorParamsToolbar) .editorParamsToolbarContainer) .editorParamsSlider{
background-color:transparent;
width:90px;
flex:0 1 0;
font:message-box;
}
:is(:is(:is(.toolbarButtonWithContainer .editorParamsToolbar) .editorParamsToolbarContainer) .editorParamsSlider)::-moz-range-progress{
background-color:black;
}
:is(:is(:is(.toolbarButtonWithContainer .editorParamsToolbar) .editorParamsToolbarContainer) .editorParamsSlider)::-webkit-slider-runnable-track,:is(:is(:is(.toolbarButtonWithContainer .editorParamsToolbar) .editorParamsToolbarContainer) .editorParamsSlider)::-moz-range-track{
background-color:black;
}
:is(:is(:is(.toolbarButtonWithContainer .editorParamsToolbar) .editorParamsToolbarContainer) .editorParamsSlider)::-webkit-slider-thumb,:is(:is(:is(.toolbarButtonWithContainer .editorParamsToolbar) .editorParamsToolbarContainer) .editorParamsSlider)::-moz-range-thumb{
background-color:white;
}
#secondaryToolbar{
height:auto;
width:220px;
position:absolute;
z-index:30000;
cursor:default;
min-height:26px;
max-height:calc(var(--viewer-container-height) - 40px);
}
:is(#secondaryToolbar #secondaryToolbarButtonContainer) #secondaryOpenFile::before{
-webkit-mask-image:var(--toolbarButton-openFile-icon);
mask-image:var(--toolbarButton-openFile-icon);
}
:is(#secondaryToolbar #secondaryToolbarButtonContainer) #secondaryPrint::before{
-webkit-mask-image:var(--toolbarButton-print-icon);
mask-image:var(--toolbarButton-print-icon);
}
:is(#secondaryToolbar #secondaryToolbarButtonContainer) #secondaryDownload::before{
-webkit-mask-image:var(--toolbarButton-download-icon);
mask-image:var(--toolbarButton-download-icon);
}
:is(#secondaryToolbar #secondaryToolbarButtonContainer) #presentationMode::before{
-webkit-mask-image:var(--toolbarButton-presentationMode-icon);
mask-image:var(--toolbarButton-presentationMode-icon);
}
:is(#secondaryToolbar #secondaryToolbarButtonContainer) #viewBookmark::before{
-webkit-mask-image:var(--toolbarButton-bookmark-icon);
mask-image:var(--toolbarButton-bookmark-icon);
}
:is(#secondaryToolbar #secondaryToolbarButtonContainer) #firstPage::before{
-webkit-mask-image:var(--secondaryToolbarButton-firstPage-icon);
mask-image:var(--secondaryToolbarButton-firstPage-icon);
}
:is(#secondaryToolbar #secondaryToolbarButtonContainer) #lastPage::before{
-webkit-mask-image:var(--secondaryToolbarButton-lastPage-icon);
mask-image:var(--secondaryToolbarButton-lastPage-icon);
}
:is(#secondaryToolbar #secondaryToolbarButtonContainer) #pageRotateCcw::before{
-webkit-mask-image:var(--secondaryToolbarButton-rotateCcw-icon);
mask-image:var(--secondaryToolbarButton-rotateCcw-icon);
}
:is(#secondaryToolbar #secondaryToolbarButtonContainer) #pageRotateCw::before{
-webkit-mask-image:var(--secondaryToolbarButton-rotateCw-icon);
mask-image:var(--secondaryToolbarButton-rotateCw-icon);
}
:is(#secondaryToolbar #secondaryToolbarButtonContainer) #cursorSelectTool::before{
-webkit-mask-image:var(--secondaryToolbarButton-selectTool-icon);
mask-image:var(--secondaryToolbarButton-selectTool-icon);
}
:is(#secondaryToolbar #secondaryToolbarButtonContainer) #cursorHandTool::before{
-webkit-mask-image:var(--secondaryToolbarButton-handTool-icon);
mask-image:var(--secondaryToolbarButton-handTool-icon);
}
:is(#secondaryToolbar #secondaryToolbarButtonContainer) #scrollPage::before{
-webkit-mask-image:var(--secondaryToolbarButton-scrollPage-icon);
mask-image:var(--secondaryToolbarButton-scrollPage-icon);
}
:is(#secondaryToolbar #secondaryToolbarButtonContainer) #scrollVertical::before{
-webkit-mask-image:var(--secondaryToolbarButton-scrollVertical-icon);
mask-image:var(--secondaryToolbarButton-scrollVertical-icon);
}
:is(#secondaryToolbar #secondaryToolbarButtonContainer) #scrollHorizontal::before{
-webkit-mask-image:var(--secondaryToolbarButton-scrollHorizontal-icon);
mask-image:var(--secondaryToolbarButton-scrollHorizontal-icon);
}
:is(#secondaryToolbar #secondaryToolbarButtonContainer) #scrollWrapped::before{
-webkit-mask-image:var(--secondaryToolbarButton-scrollWrapped-icon);
mask-image:var(--secondaryToolbarButton-scrollWrapped-icon);
}
:is(#secondaryToolbar #secondaryToolbarButtonContainer) #spreadNone::before{
-webkit-mask-image:var(--secondaryToolbarButton-spreadNone-icon);
mask-image:var(--secondaryToolbarButton-spreadNone-icon);
}
:is(#secondaryToolbar #secondaryToolbarButtonContainer) #spreadOdd::before{
-webkit-mask-image:var(--secondaryToolbarButton-spreadOdd-icon);
mask-image:var(--secondaryToolbarButton-spreadOdd-icon);
}
:is(#secondaryToolbar #secondaryToolbarButtonContainer) #spreadEven::before{
-webkit-mask-image:var(--secondaryToolbarButton-spreadEven-icon);
mask-image:var(--secondaryToolbarButton-spreadEven-icon);
}
:is(#secondaryToolbar #secondaryToolbarButtonContainer) #imageAltTextSettings::before{
-webkit-mask-image:var(--secondaryToolbarButton-imageAltTextSettings-icon);
mask-image:var(--secondaryToolbarButton-imageAltTextSettings-icon);
}
:is(#secondaryToolbar #secondaryToolbarButtonContainer) #documentProperties::before{
-webkit-mask-image:var(--secondaryToolbarButton-documentProperties-icon);
mask-image:var(--secondaryToolbarButton-documentProperties-icon);
}
#findbar{
--input-horizontal-padding:4px;
--findbar-padding:2px;
width:-moz-max-content;
width:max-content;
max-width:90vw;
min-height:var(--toolbar-height);
height:auto;
position:absolute;
z-index:30000;
cursor:default;
padding:0;
min-width:300px;
background-color:var(--toolbar-bg-color);
box-sizing:border-box;
flex-wrap:wrap;
justify-content:flex-start;
}
#findbar > *{
height:var(--toolbar-height);
padding:var(--findbar-padding);
}
#findbar #findInputContainer{
margin-inline-start:2px;
}
:is(#findbar #findInputContainer) #findPreviousButton::before{
-webkit-mask-image:var(--findbarButton-previous-icon);
mask-image:var(--findbarButton-previous-icon);
}
:is(#findbar #findInputContainer) #findNextButton::before{
-webkit-mask-image:var(--findbarButton-next-icon);
mask-image:var(--findbarButton-next-icon);
}
:is(#findbar #findInputContainer) #findInput{
width:200px;
padding:5px var(--input-horizontal-padding);
}
:is(:is(#findbar #findInputContainer) #findInput)::-moz-placeholder{
font-style:normal;
}
:is(:is(#findbar #findInputContainer) #findInput)::placeholder{
font-style:normal;
}
.loadingInput:has( > [data-status="pending"]:is(:is(#findbar #findInputContainer) #findInput))::after{
display:inline;
visibility:visible;
inset-inline-end:calc(var(--input-horizontal-padding) + 1px);
}
[data-status="notFound"]:is(:is(#findbar #findInputContainer) #findInput){
background-color:rgb(255 102 102);
}
#findbar #findbarMessageContainer{
display:none;
gap:4px;
}
:is(#findbar #findbarMessageContainer):has( > :is(#findResultsCount,#findMsg):not(:empty)){
display:inline flex;
}
:is(#findbar #findbarMessageContainer) #findResultsCount{
background-color:rgb(217 217 217);
color:rgb(82 82 82);
padding-block:4px;
}
:is(:is(#findbar #findbarMessageContainer) #findResultsCount):empty{
display:none;
}
[data-status="notFound"]:is(:is(#findbar #findbarMessageContainer) #findMsg){
font-weight:bold;
}
:is(:is(#findbar #findbarMessageContainer) #findMsg):empty{
display:none;
}
#findbar.wrapContainers{
flex-direction:column;
align-items:flex-start;
height:-moz-max-content;
height:max-content;
}
#findbar.wrapContainers .toolbarLabel{
margin:0 4px;
}
#findbar.wrapContainers #findbarMessageContainer{
flex-wrap:wrap;
flex-flow:column nowrap;
align-items:flex-start;
height:-moz-max-content;
height:max-content;
}
:is(#findbar.wrapContainers #findbarMessageContainer) #findResultsCount{
height:calc(var(--toolbar-height) - 2 * var(--findbar-padding));
}
:is(#findbar.wrapContainers #findbarMessageContainer) #findMsg{
min-height:var(--toolbar-height);
}
@page{
margin:0;
}
#printContainer{
display:none;
}
@media print{
body{
background:rgb(0 0 0 / 0) none;
}
body[data-pdfjsprinting] #outerContainer{
display:none;
}
body[data-pdfjsprinting] #printContainer{
display:block;
}
#printContainer{
height:100%;
}
#printContainer > .printedPage{
page-break-after:always;
page-break-inside:avoid;
height:100%;
width:100%;
display:flex;
flex-direction:column;
justify-content:center;
align-items:center;
}
#printContainer > .xfaPrintedPage .xfaPage{
position:absolute;
}
#printContainer > .xfaPrintedPage{
page-break-after:always;
page-break-inside:avoid;
width:100%;
height:100%;
position:relative;
}
#printContainer > .printedPage :is(canvas, img){
max-width:100%;
max-height:100%;
direction:ltr;
display:block;
}
}
.visibleMediumView{
display:none !important;
}
.toolbarLabel{
width:-moz-max-content;
width:max-content;
min-width:16px;
height:100%;
padding-inline:4px;
margin:2px;
border-radius:2px;
color:var(--main-color);
font-size:12px;
line-height:14px;
text-align:left;
-webkit-user-select:none;
-moz-user-select:none;
user-select:none;
cursor:default;
box-sizing:border-box;
display:inline flex;
flex-direction:column;
align-items:center;
justify-content:center;
}
.toolbarLabel > label{
width:100%;
}
.toolbarHorizontalGroup{
height:100%;
display:inline flex;
flex-direction:row;
align-items:center;
justify-content:space-between;
gap:1px;
box-sizing:border-box;
}
.dropdownToolbarButton{
display:inline flex;
flex-direction:row;
align-items:center;
justify-content:center;
position:relative;
width:-moz-fit-content;
width:fit-content;
min-width:140px;
padding:0;
background-color:var(--dropdown-btn-bg-color);
border:var(--dropdown-btn-border);
border-radius:2px;
color:var(--main-color);
font-size:12px;
line-height:14px;
-webkit-user-select:none;
-moz-user-select:none;
user-select:none;
cursor:default;
box-sizing:border-box;
outline:none;
}
.dropdownToolbarButton:hover{
background-color:var(--button-hover-color);
}
.dropdownToolbarButton > select{
-webkit-appearance:none;
-moz-appearance:none;
appearance:none;
width:inherit;
min-width:inherit;
height:28px;
font:message-box;
font-size:12px;
color:var(--main-color);
margin:0;
padding-block:1px 2px;
padding-inline:6px 38px;
border:none;
outline:none;
background-color:var(--dropdown-btn-bg-color);
}
:is(.dropdownToolbarButton > select) > option{
background:var(--doorhanger-bg-color);
color:var(--main-color);
}
:is(.dropdownToolbarButton > select):is(:hover,:focus-visible){
background-color:var(--button-hover-color);
color:var(--toggled-btn-color);
}
.dropdownToolbarButton::after{
position:absolute;
display:inline;
width:var(--icon-size);
height:var(--icon-size);
content:"";
background-color:var(--toolbar-icon-bg-color);
-webkit-mask-size:cover;
mask-size:cover;
inset-inline-end:4px;
pointer-events:none;
-webkit-mask-image:var(--toolbarButton-menuArrow-icon);
mask-image:var(--toolbarButton-menuArrow-icon);
}
.dropdownToolbarButton:is(:hover,:focus-visible,:active)::after{
background-color:var(--toolbar-icon-hover-bg-color);
}
#toolbarContainer{
--menuitem-height:calc(var(--toolbar-height) - 6px);
width:100%;
height:var(--toolbar-height);
padding:var(--toolbar-vertical-padding) var(--toolbar-horizontal-padding);
position:relative;
box-sizing:border-box;
font:message-box;
background-color:var(--toolbar-bg-color);
box-shadow:var(--toolbar-box-shadow);
border-bottom:var(--toolbar-border-bottom);
}
#toolbarContainer #toolbarViewer{
width:100%;
height:100%;
justify-content:space-between;
}
:is(#toolbarContainer #toolbarViewer) > *{
flex:none;
}
:is(#toolbarContainer #toolbarViewer) input{
font:message-box;
}
:is(#toolbarContainer #toolbarViewer) .toolbarButtonSpacer{
width:30px;
display:block;
height:1px;
}
:is(#toolbarContainer #toolbarViewer) #toolbarViewerLeft #numPages.toolbarLabel{
padding-inline-start:3px;
flex:none;
}
#toolbarContainer #loadingBar{
--progressBar-percent:0%;
--progressBar-end-offset:0;
position:absolute;
top:var(--toolbar-height);
inset-inline:0 var(--progressBar-end-offset);
height:4px;
background-color:var(--progressBar-bg-color);
border-bottom:1px solid var(--toolbar-border-color);
transition-property:inset-inline-start;
transition-duration:var(--sidebar-transition-duration);
transition-timing-function:var(--sidebar-transition-timing-function);
}
:is(#toolbarContainer #loadingBar) .progress{
position:absolute;
top:0;
inset-inline-start:0;
width:100%;
transform:scaleX(var(--progressBar-percent));
transform-origin:calc(50% - 50% * var(--dir-factor)) 0;
height:100%;
background-color:var(--progressBar-color);
overflow:hidden;
transition:transform 200ms;
}
.indeterminate:is(#toolbarContainer #loadingBar) .progress{
transform:none;
background-color:var(--progressBar-bg-color);
transition:none;
}
:is(.indeterminate:is(#toolbarContainer #loadingBar) .progress) .glimmer{
position:absolute;
top:0;
inset-inline-start:0;
height:100%;
width:calc(100% + 150px);
background:repeating-linear-gradient(
135deg,
var(--progressBar-blend-color) 0,
var(--progressBar-bg-color) 5px,
var(--progressBar-bg-color) 45px,
var(--progressBar-color) 55px,
var(--progressBar-color) 95px,
var(--progressBar-blend-color) 100px
);
animation:progressIndeterminate 1s linear infinite;
}
#secondaryToolbar #firstPage::before{
-webkit-mask-image:var(--secondaryToolbarButton-firstPage-icon);
mask-image:var(--secondaryToolbarButton-firstPage-icon);
}
#secondaryToolbar #lastPage::before{
-webkit-mask-image:var(--secondaryToolbarButton-lastPage-icon);
mask-image:var(--secondaryToolbarButton-lastPage-icon);
}
#secondaryToolbar #pageRotateCcw::before{
-webkit-mask-image:var(--secondaryToolbarButton-rotateCcw-icon);
mask-image:var(--secondaryToolbarButton-rotateCcw-icon);
}
#secondaryToolbar #pageRotateCw::before{
-webkit-mask-image:var(--secondaryToolbarButton-rotateCw-icon);
mask-image:var(--secondaryToolbarButton-rotateCw-icon);
}
#secondaryToolbar #cursorSelectTool::before{
-webkit-mask-image:var(--secondaryToolbarButton-selectTool-icon);
mask-image:var(--secondaryToolbarButton-selectTool-icon);
}
#secondaryToolbar #cursorHandTool::before{
-webkit-mask-image:var(--secondaryToolbarButton-handTool-icon);
mask-image:var(--secondaryToolbarButton-handTool-icon);
}
#secondaryToolbar #scrollPage::before{
-webkit-mask-image:var(--secondaryToolbarButton-scrollPage-icon);
mask-image:var(--secondaryToolbarButton-scrollPage-icon);
}
#secondaryToolbar #scrollVertical::before{
-webkit-mask-image:var(--secondaryToolbarButton-scrollVertical-icon);
mask-image:var(--secondaryToolbarButton-scrollVertical-icon);
}
#secondaryToolbar #scrollHorizontal::before{
-webkit-mask-image:var(--secondaryToolbarButton-scrollHorizontal-icon);
mask-image:var(--secondaryToolbarButton-scrollHorizontal-icon);
}
#secondaryToolbar #scrollWrapped::before{
-webkit-mask-image:var(--secondaryToolbarButton-scrollWrapped-icon);
mask-image:var(--secondaryToolbarButton-scrollWrapped-icon);
}
#secondaryToolbar #spreadNone::before{
-webkit-mask-image:var(--secondaryToolbarButton-spreadNone-icon);
mask-image:var(--secondaryToolbarButton-spreadNone-icon);
}
#secondaryToolbar #spreadOdd::before{
-webkit-mask-image:var(--secondaryToolbarButton-spreadOdd-icon);
mask-image:var(--secondaryToolbarButton-spreadOdd-icon);
}
#secondaryToolbar #spreadEven::before{
-webkit-mask-image:var(--secondaryToolbarButton-spreadEven-icon);
mask-image:var(--secondaryToolbarButton-spreadEven-icon);
}
#secondaryToolbar #documentProperties::before{
-webkit-mask-image:var(--secondaryToolbarButton-documentProperties-icon);
mask-image:var(--secondaryToolbarButton-documentProperties-icon);
}
@media all and (max-width: 840px){
#sidebarContainer{
background-color:var(--sidebar-narrow-bg-color);
}
#outerContainer.sidebarOpen #viewerContainer{
inset-inline-start:0 !important;
}
}
@media all and (max-width: 750px){
#outerContainer .hiddenMediumView{
display:none !important;
}
#outerContainer .visibleMediumView:not(.hidden, [hidden]){
display:inline-block !important;
}
}
@media all and (max-width: 690px){
.hiddenSmallView,
.hiddenSmallView *{
display:none !important;
}
#toolbarContainer #toolbarViewer .toolbarButtonSpacer{
width:0;
}
}
@media all and (max-width: 560px){
#scaleSelectContainer{
display:none;
}
}
================================================
FILE: cookbook/static/pdfjs/web/viewer.html
================================================
PDF.js viewer
================================================
FILE: cookbook/static/pdfjs/web/viewer.mjs
================================================
/**
* @licstart The following is the entire license notice for the
* JavaScript code in this page
*
* Copyright 2024 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @licend The above is the entire license notice for the
* JavaScript code in this page
*/
/******/ // The require scope
/******/ var __webpack_require__ = {};
/******/
/************************************************************************/
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ // define getter functions for harmony exports
/******/ __webpack_require__.d = (exports, definition) => {
/******/ for(var key in definition) {
/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ }
/******/ }
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */
/******/ (() => {
/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ })();
/******/
/************************************************************************/
var __webpack_exports__ = {};
// EXPORTS
__webpack_require__.d(__webpack_exports__, {
PDFViewerApplication: () => (/* reexport */ PDFViewerApplication),
PDFViewerApplicationConstants: () => (/* binding */ AppConstants),
PDFViewerApplicationOptions: () => (/* reexport */ AppOptions)
});
;// ./web/pdfjs.js
const {
AbortException,
AnnotationEditorLayer,
AnnotationEditorParamsType,
AnnotationEditorType,
AnnotationEditorUIManager,
AnnotationLayer,
AnnotationMode,
AnnotationType,
build,
ColorPicker,
createValidAbsoluteUrl,
DOMSVGFactory,
DrawLayer,
FeatureTest,
fetchData,
getDocument,
getFilenameFromUrl,
getPdfFilenameFromUrl: pdfjs_getPdfFilenameFromUrl,
getUuid,
getXfaPageViewport,
GlobalWorkerOptions,
ImageKind,
InvalidPDFException,
isDataScheme,
isPdfFile,
isValidExplicitDest,
MathClamp,
noContextMenu,
normalizeUnicode,
OPS,
OutputScale,
PasswordResponses,
PDFDataRangeTransport,
PDFDateString,
PDFWorker,
PermissionFlag,
PixelsPerInch,
RenderingCancelledException,
ResponseException,
setLayerDimensions,
shadow,
SignatureExtractor,
stopEvent,
SupportedImageMimeTypes,
TextLayer,
TouchManager,
Util,
VerbosityLevel,
version,
XfaLayer
} = globalThis.pdfjsLib;
;// ./web/ui_utils.js
const DEFAULT_SCALE_VALUE = "auto";
const DEFAULT_SCALE = 1.0;
const DEFAULT_SCALE_DELTA = 1.1;
const MIN_SCALE = 0.1;
const MAX_SCALE = 10.0;
const UNKNOWN_SCALE = 0;
const MAX_AUTO_SCALE = 1.25;
const SCROLLBAR_PADDING = 40;
const VERTICAL_PADDING = 5;
const RenderingStates = {
INITIAL: 0,
RUNNING: 1,
PAUSED: 2,
FINISHED: 3
};
const PresentationModeState = {
UNKNOWN: 0,
NORMAL: 1,
CHANGING: 2,
FULLSCREEN: 3
};
const SidebarView = {
UNKNOWN: -1,
NONE: 0,
THUMBS: 1,
OUTLINE: 2,
ATTACHMENTS: 3,
LAYERS: 4
};
const TextLayerMode = {
DISABLE: 0,
ENABLE: 1,
ENABLE_PERMISSIONS: 2
};
const ScrollMode = {
UNKNOWN: -1,
VERTICAL: 0,
HORIZONTAL: 1,
WRAPPED: 2,
PAGE: 3
};
const SpreadMode = {
UNKNOWN: -1,
NONE: 0,
ODD: 1,
EVEN: 2
};
const CursorTool = {
SELECT: 0,
HAND: 1,
ZOOM: 2
};
const AutoPrintRegExp = /\bprint\s*\(/;
function scrollIntoView(element, spot, scrollMatches = false) {
let parent = element.offsetParent;
if (!parent) {
console.error("offsetParent is not set -- cannot scroll");
return;
}
let offsetY = element.offsetTop + element.clientTop;
let offsetX = element.offsetLeft + element.clientLeft;
while (parent.clientHeight === parent.scrollHeight && parent.clientWidth === parent.scrollWidth || scrollMatches && (parent.classList.contains("markedContent") || getComputedStyle(parent).overflow === "hidden")) {
offsetY += parent.offsetTop;
offsetX += parent.offsetLeft;
parent = parent.offsetParent;
if (!parent) {
return;
}
}
if (spot) {
if (spot.top !== undefined) {
offsetY += spot.top;
}
if (spot.left !== undefined) {
offsetX += spot.left;
parent.scrollLeft = offsetX;
}
}
parent.scrollTop = offsetY;
}
function watchScroll(viewAreaElement, callback, abortSignal = undefined) {
const debounceScroll = function (evt) {
if (rAF) {
return;
}
rAF = window.requestAnimationFrame(function viewAreaElementScrolled() {
rAF = null;
const currentX = viewAreaElement.scrollLeft;
const lastX = state.lastX;
if (currentX !== lastX) {
state.right = currentX > lastX;
}
state.lastX = currentX;
const currentY = viewAreaElement.scrollTop;
const lastY = state.lastY;
if (currentY !== lastY) {
state.down = currentY > lastY;
}
state.lastY = currentY;
callback(state);
});
};
const state = {
right: true,
down: true,
lastX: viewAreaElement.scrollLeft,
lastY: viewAreaElement.scrollTop,
_eventHandler: debounceScroll
};
let rAF = null;
viewAreaElement.addEventListener("scroll", debounceScroll, {
useCapture: true,
signal: abortSignal
});
abortSignal?.addEventListener("abort", () => window.cancelAnimationFrame(rAF), {
once: true
});
return state;
}
function parseQueryString(query) {
const params = new Map();
for (const [key, value] of new URLSearchParams(query)) {
params.set(key.toLowerCase(), value);
}
return params;
}
const InvisibleCharsRegExp = /[\x00-\x1F]/g;
function removeNullCharacters(str, replaceInvisible = false) {
if (!InvisibleCharsRegExp.test(str)) {
return str;
}
if (replaceInvisible) {
return str.replaceAll(InvisibleCharsRegExp, m => m === "\x00" ? "" : " ");
}
return str.replaceAll("\x00", "");
}
function binarySearchFirstItem(items, condition, start = 0) {
let minIndex = start;
let maxIndex = items.length - 1;
if (maxIndex < 0 || !condition(items[maxIndex])) {
return items.length;
}
if (condition(items[minIndex])) {
return minIndex;
}
while (minIndex < maxIndex) {
const currentIndex = minIndex + maxIndex >> 1;
const currentItem = items[currentIndex];
if (condition(currentItem)) {
maxIndex = currentIndex;
} else {
minIndex = currentIndex + 1;
}
}
return minIndex;
}
function approximateFraction(x) {
if (Math.floor(x) === x) {
return [x, 1];
}
const xinv = 1 / x;
const limit = 8;
if (xinv > limit) {
return [1, limit];
} else if (Math.floor(xinv) === xinv) {
return [1, xinv];
}
const x_ = x > 1 ? xinv : x;
let a = 0,
b = 1,
c = 1,
d = 1;
while (true) {
const p = a + c,
q = b + d;
if (q > limit) {
break;
}
if (x_ <= p / q) {
c = p;
d = q;
} else {
a = p;
b = q;
}
}
let result;
if (x_ - a / b < c / d - x_) {
result = x_ === x ? [a, b] : [b, a];
} else {
result = x_ === x ? [c, d] : [d, c];
}
return result;
}
function floorToDivide(x, div) {
return x - x % div;
}
function getPageSizeInches({
view,
userUnit,
rotate
}) {
const [x1, y1, x2, y2] = view;
const changeOrientation = rotate % 180 !== 0;
const width = (x2 - x1) / 72 * userUnit;
const height = (y2 - y1) / 72 * userUnit;
return {
width: changeOrientation ? height : width,
height: changeOrientation ? width : height
};
}
function backtrackBeforeAllVisibleElements(index, views, top) {
if (index < 2) {
return index;
}
let elt = views[index].div;
let pageTop = elt.offsetTop + elt.clientTop;
if (pageTop >= top) {
elt = views[index - 1].div;
pageTop = elt.offsetTop + elt.clientTop;
}
for (let i = index - 2; i >= 0; --i) {
elt = views[i].div;
if (elt.offsetTop + elt.clientTop + elt.clientHeight <= pageTop) {
break;
}
index = i;
}
return index;
}
function getVisibleElements({
scrollEl,
views,
sortByVisibility = false,
horizontal = false,
rtl = false
}) {
const top = scrollEl.scrollTop,
bottom = top + scrollEl.clientHeight;
const left = scrollEl.scrollLeft,
right = left + scrollEl.clientWidth;
function isElementBottomAfterViewTop(view) {
const element = view.div;
const elementBottom = element.offsetTop + element.clientTop + element.clientHeight;
return elementBottom > top;
}
function isElementNextAfterViewHorizontally(view) {
const element = view.div;
const elementLeft = element.offsetLeft + element.clientLeft;
const elementRight = elementLeft + element.clientWidth;
return rtl ? elementLeft < right : elementRight > left;
}
const visible = [],
ids = new Set(),
numViews = views.length;
let firstVisibleElementInd = binarySearchFirstItem(views, horizontal ? isElementNextAfterViewHorizontally : isElementBottomAfterViewTop);
if (firstVisibleElementInd > 0 && firstVisibleElementInd < numViews && !horizontal) {
firstVisibleElementInd = backtrackBeforeAllVisibleElements(firstVisibleElementInd, views, top);
}
let lastEdge = horizontal ? right : -1;
for (let i = firstVisibleElementInd; i < numViews; i++) {
const view = views[i],
element = view.div;
const currentWidth = element.offsetLeft + element.clientLeft;
const currentHeight = element.offsetTop + element.clientTop;
const viewWidth = element.clientWidth,
viewHeight = element.clientHeight;
const viewRight = currentWidth + viewWidth;
const viewBottom = currentHeight + viewHeight;
if (lastEdge === -1) {
if (viewBottom >= bottom) {
lastEdge = viewBottom;
}
} else if ((horizontal ? currentWidth : currentHeight) > lastEdge) {
break;
}
if (viewBottom <= top || currentHeight >= bottom || viewRight <= left || currentWidth >= right) {
continue;
}
const minY = Math.max(0, top - currentHeight);
const minX = Math.max(0, left - currentWidth);
const hiddenHeight = minY + Math.max(0, viewBottom - bottom);
const hiddenWidth = minX + Math.max(0, viewRight - right);
const fractionHeight = (viewHeight - hiddenHeight) / viewHeight,
fractionWidth = (viewWidth - hiddenWidth) / viewWidth;
const percent = fractionHeight * fractionWidth * 100 | 0;
visible.push({
id: view.id,
x: currentWidth,
y: currentHeight,
visibleArea: percent === 100 ? null : {
minX,
minY,
maxX: Math.min(viewRight, right) - currentWidth,
maxY: Math.min(viewBottom, bottom) - currentHeight
},
view,
percent,
widthPercent: fractionWidth * 100 | 0
});
ids.add(view.id);
}
const first = visible[0],
last = visible.at(-1);
if (sortByVisibility) {
visible.sort(function (a, b) {
const pc = a.percent - b.percent;
if (Math.abs(pc) > 0.001) {
return -pc;
}
return a.id - b.id;
});
}
return {
first,
last,
views: visible,
ids
};
}
function normalizeWheelEventDirection(evt) {
let delta = Math.hypot(evt.deltaX, evt.deltaY);
const angle = Math.atan2(evt.deltaY, evt.deltaX);
if (-0.25 * Math.PI < angle && angle < 0.75 * Math.PI) {
delta = -delta;
}
return delta;
}
function normalizeWheelEventDelta(evt) {
const deltaMode = evt.deltaMode;
let delta = normalizeWheelEventDirection(evt);
const MOUSE_PIXELS_PER_LINE = 30;
const MOUSE_LINES_PER_PAGE = 30;
if (deltaMode === WheelEvent.DOM_DELTA_PIXEL) {
delta /= MOUSE_PIXELS_PER_LINE * MOUSE_LINES_PER_PAGE;
} else if (deltaMode === WheelEvent.DOM_DELTA_LINE) {
delta /= MOUSE_LINES_PER_PAGE;
}
return delta;
}
function isValidRotation(angle) {
return Number.isInteger(angle) && angle % 90 === 0;
}
function isValidScrollMode(mode) {
return Number.isInteger(mode) && Object.values(ScrollMode).includes(mode) && mode !== ScrollMode.UNKNOWN;
}
function isValidSpreadMode(mode) {
return Number.isInteger(mode) && Object.values(SpreadMode).includes(mode) && mode !== SpreadMode.UNKNOWN;
}
function isPortraitOrientation(size) {
return size.width <= size.height;
}
const animationStarted = new Promise(function (resolve) {
window.requestAnimationFrame(resolve);
});
const docStyle = document.documentElement.style;
class ProgressBar {
#classList = null;
#disableAutoFetchTimeout = null;
#percent = 0;
#style = null;
#visible = true;
constructor(bar) {
this.#classList = bar.classList;
this.#style = bar.style;
}
get percent() {
return this.#percent;
}
set percent(val) {
this.#percent = MathClamp(val, 0, 100);
if (isNaN(val)) {
this.#classList.add("indeterminate");
return;
}
this.#classList.remove("indeterminate");
this.#style.setProperty("--progressBar-percent", `${this.#percent}%`);
}
setWidth(viewer) {
if (!viewer) {
return;
}
const container = viewer.parentNode;
const scrollbarWidth = container.offsetWidth - viewer.offsetWidth;
if (scrollbarWidth > 0) {
this.#style.setProperty("--progressBar-end-offset", `${scrollbarWidth}px`);
}
}
setDisableAutoFetch(delay = 5000) {
if (this.#percent === 100 || isNaN(this.#percent)) {
return;
}
if (this.#disableAutoFetchTimeout) {
clearTimeout(this.#disableAutoFetchTimeout);
}
this.show();
this.#disableAutoFetchTimeout = setTimeout(() => {
this.#disableAutoFetchTimeout = null;
this.hide();
}, delay);
}
hide() {
if (!this.#visible) {
return;
}
this.#visible = false;
this.#classList.add("hidden");
}
show() {
if (this.#visible) {
return;
}
this.#visible = true;
this.#classList.remove("hidden");
}
}
function getActiveOrFocusedElement() {
let curRoot = document;
let curActiveOrFocused = curRoot.activeElement || curRoot.querySelector(":focus");
while (curActiveOrFocused?.shadowRoot) {
curRoot = curActiveOrFocused.shadowRoot;
curActiveOrFocused = curRoot.activeElement || curRoot.querySelector(":focus");
}
return curActiveOrFocused;
}
function apiPageLayoutToViewerModes(layout) {
let scrollMode = ScrollMode.VERTICAL,
spreadMode = SpreadMode.NONE;
switch (layout) {
case "SinglePage":
scrollMode = ScrollMode.PAGE;
break;
case "OneColumn":
break;
case "TwoPageLeft":
scrollMode = ScrollMode.PAGE;
case "TwoColumnLeft":
spreadMode = SpreadMode.ODD;
break;
case "TwoPageRight":
scrollMode = ScrollMode.PAGE;
case "TwoColumnRight":
spreadMode = SpreadMode.EVEN;
break;
}
return {
scrollMode,
spreadMode
};
}
function apiPageModeToSidebarView(mode) {
switch (mode) {
case "UseNone":
return SidebarView.NONE;
case "UseThumbs":
return SidebarView.THUMBS;
case "UseOutlines":
return SidebarView.OUTLINE;
case "UseAttachments":
return SidebarView.ATTACHMENTS;
case "UseOC":
return SidebarView.LAYERS;
}
return SidebarView.NONE;
}
function toggleCheckedBtn(button, toggle, view = null) {
button.classList.toggle("toggled", toggle);
button.setAttribute("aria-checked", toggle);
view?.classList.toggle("hidden", !toggle);
}
function toggleExpandedBtn(button, toggle, view = null) {
button.classList.toggle("toggled", toggle);
button.setAttribute("aria-expanded", toggle);
view?.classList.toggle("hidden", !toggle);
}
const calcRound = function () {
const e = document.createElement("div");
e.style.width = "round(down, calc(1.6666666666666665 * 792px), 1px)";
return e.style.width === "calc(1320px)" ? Math.fround : x => x;
}();
;// ./web/app_options.js
{
var compatParams = new Map();
const userAgent = navigator.userAgent || "";
const platform = navigator.platform || "";
const maxTouchPoints = navigator.maxTouchPoints || 1;
const isAndroid = /Android/.test(userAgent);
const isIOS = /\b(iPad|iPhone|iPod)(?=;)/.test(userAgent) || platform === "MacIntel" && maxTouchPoints > 1;
(function () {
if (isIOS || isAndroid) {
compatParams.set("maxCanvasPixels", 5242880);
}
})();
(function () {
if (isAndroid) {
compatParams.set("useSystemFonts", false);
}
})();
}
const OptionKind = {
BROWSER: 0x01,
VIEWER: 0x02,
API: 0x04,
WORKER: 0x08,
EVENT_DISPATCH: 0x10,
PREFERENCE: 0x80
};
const Type = {
BOOLEAN: 0x01,
NUMBER: 0x02,
OBJECT: 0x04,
STRING: 0x08,
UNDEFINED: 0x10
};
const defaultOptions = {
allowedGlobalEvents: {
value: null,
kind: OptionKind.BROWSER
},
canvasMaxAreaInBytes: {
value: -1,
kind: OptionKind.BROWSER + OptionKind.API
},
isInAutomation: {
value: false,
kind: OptionKind.BROWSER
},
localeProperties: {
value: {
lang: navigator.language || "en-US"
},
kind: OptionKind.BROWSER
},
maxCanvasDim: {
value: 32767,
kind: OptionKind.BROWSER + OptionKind.VIEWER
},
nimbusDataStr: {
value: "",
kind: OptionKind.BROWSER
},
supportsCaretBrowsingMode: {
value: false,
kind: OptionKind.BROWSER
},
supportsDocumentFonts: {
value: true,
kind: OptionKind.BROWSER
},
supportsIntegratedFind: {
value: false,
kind: OptionKind.BROWSER
},
supportsMouseWheelZoomCtrlKey: {
value: true,
kind: OptionKind.BROWSER
},
supportsMouseWheelZoomMetaKey: {
value: true,
kind: OptionKind.BROWSER
},
supportsPinchToZoom: {
value: true,
kind: OptionKind.BROWSER
},
toolbarDensity: {
value: 0,
kind: OptionKind.BROWSER + OptionKind.EVENT_DISPATCH
},
altTextLearnMoreUrl: {
value: "",
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
annotationEditorMode: {
value: 0,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
annotationMode: {
value: 2,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
cursorToolOnLoad: {
value: 0,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
debuggerSrc: {
value: "./debugger.mjs",
kind: OptionKind.VIEWER
},
defaultZoomDelay: {
value: 400,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
defaultZoomValue: {
value: "",
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
disableHistory: {
value: false,
kind: OptionKind.VIEWER
},
disablePageLabels: {
value: false,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
enableAltText: {
value: false,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
enableAltTextModelDownload: {
value: true,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE + OptionKind.EVENT_DISPATCH
},
enableAutoLinking: {
value: true,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
enableDetailCanvas: {
value: true,
kind: OptionKind.VIEWER
},
enableGuessAltText: {
value: true,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE + OptionKind.EVENT_DISPATCH
},
enableHighlightFloatingButton: {
value: false,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
enableNewAltTextWhenAddingImage: {
value: true,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
enablePermissions: {
value: false,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
enablePrintAutoRotate: {
value: true,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
enableScripting: {
value: true,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
enableSignatureEditor: {
value: false,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
enableUpdatedAddImage: {
value: false,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
externalLinkRel: {
value: "noopener noreferrer nofollow",
kind: OptionKind.VIEWER
},
externalLinkTarget: {
value: 0,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
highlightEditorColors: {
value: "yellow=#FFFF98,green=#53FFBC,blue=#80EBFF,pink=#FFCBE6,red=#FF4F5F",
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
historyUpdateUrl: {
value: false,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
ignoreDestinationZoom: {
value: false,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
imageResourcesPath: {
value: "./images/",
kind: OptionKind.VIEWER
},
maxCanvasPixels: {
value: 2 ** 25,
kind: OptionKind.VIEWER
},
forcePageColors: {
value: false,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
pageColorsBackground: {
value: "Canvas",
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
pageColorsForeground: {
value: "CanvasText",
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
pdfBugEnabled: {
value: false,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
printResolution: {
value: 150,
kind: OptionKind.VIEWER
},
sidebarViewOnLoad: {
value: -1,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
scrollModeOnLoad: {
value: -1,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
spreadModeOnLoad: {
value: -1,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
textLayerMode: {
value: 1,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
viewerCssTheme: {
value: 0,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
viewOnLoad: {
value: 0,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
cMapPacked: {
value: true,
kind: OptionKind.API
},
cMapUrl: {
value: "../web/cmaps/",
kind: OptionKind.API
},
disableAutoFetch: {
value: false,
kind: OptionKind.API + OptionKind.PREFERENCE
},
disableFontFace: {
value: false,
kind: OptionKind.API + OptionKind.PREFERENCE
},
disableRange: {
value: false,
kind: OptionKind.API + OptionKind.PREFERENCE
},
disableStream: {
value: false,
kind: OptionKind.API + OptionKind.PREFERENCE
},
docBaseUrl: {
value: "",
kind: OptionKind.API
},
enableHWA: {
value: true,
kind: OptionKind.API + OptionKind.VIEWER + OptionKind.PREFERENCE
},
enableXfa: {
value: true,
kind: OptionKind.API + OptionKind.PREFERENCE
},
fontExtraProperties: {
value: false,
kind: OptionKind.API
},
iccUrl: {
value: "../web/iccs/",
kind: OptionKind.API
},
isEvalSupported: {
value: true,
kind: OptionKind.API
},
isOffscreenCanvasSupported: {
value: true,
kind: OptionKind.API
},
maxImageSize: {
value: -1,
kind: OptionKind.API
},
pdfBug: {
value: false,
kind: OptionKind.API
},
standardFontDataUrl: {
value: "../web/standard_fonts/",
kind: OptionKind.API
},
useSystemFonts: {
value: undefined,
kind: OptionKind.API,
type: Type.BOOLEAN + Type.UNDEFINED
},
verbosity: {
value: 1,
kind: OptionKind.API
},
wasmUrl: {
value: "../web/wasm/",
kind: OptionKind.API
},
workerPort: {
value: null,
kind: OptionKind.WORKER
},
workerSrc: {
value: "pdf.worker.mjs",
kind: OptionKind.WORKER
}
};
{
defaultOptions.defaultUrl = {
value: "compressed.tracemonkey-pldi-09.pdf",
kind: OptionKind.VIEWER
};
defaultOptions.sandboxBundleSrc = {
value: "pdf.sandbox.mjs",
kind: OptionKind.VIEWER
};
defaultOptions.enableFakeMLManager = {
value: true,
kind: OptionKind.VIEWER
};
}
{
defaultOptions.disablePreferences = {
value: false,
kind: OptionKind.VIEWER
};
}
class AppOptions {
static eventBus;
static #opts = new Map();
static {
for (const name in defaultOptions) {
this.#opts.set(name, defaultOptions[name].value);
}
for (const [name, value] of compatParams) {
this.#opts.set(name, value);
}
this._hasInvokedSet = false;
this._checkDisablePreferences = () => {
if (this.get("disablePreferences")) {
return true;
}
if (this._hasInvokedSet) {
console.warn("The Preferences may override manually set AppOptions; " + 'please use the "disablePreferences"-option to prevent that.');
}
return false;
};
}
static get(name) {
return this.#opts.get(name);
}
static getAll(kind = null, defaultOnly = false) {
const options = Object.create(null);
for (const name in defaultOptions) {
const defaultOpt = defaultOptions[name];
if (kind && !(kind & defaultOpt.kind)) {
continue;
}
options[name] = !defaultOnly ? this.#opts.get(name) : defaultOpt.value;
}
return options;
}
static set(name, value) {
this.setAll({
[name]: value
});
}
static setAll(options, prefs = false) {
this._hasInvokedSet ||= true;
let events;
for (const name in options) {
const defaultOpt = defaultOptions[name],
userOpt = options[name];
if (!defaultOpt || !(typeof userOpt === typeof defaultOpt.value || Type[(typeof userOpt).toUpperCase()] & defaultOpt.type)) {
continue;
}
const {
kind
} = defaultOpt;
if (prefs && !(kind & OptionKind.BROWSER || kind & OptionKind.PREFERENCE)) {
continue;
}
if (this.eventBus && kind & OptionKind.EVENT_DISPATCH) {
(events ||= new Map()).set(name, userOpt);
}
this.#opts.set(name, userOpt);
}
if (events) {
for (const [name, value] of events) {
this.eventBus.dispatch(name.toLowerCase(), {
source: this,
value
});
}
}
}
}
;// ./web/pdf_link_service.js
const DEFAULT_LINK_REL = "noopener noreferrer nofollow";
const LinkTarget = {
NONE: 0,
SELF: 1,
BLANK: 2,
PARENT: 3,
TOP: 4
};
class PDFLinkService {
externalLinkEnabled = true;
constructor({
eventBus,
externalLinkTarget = null,
externalLinkRel = null,
ignoreDestinationZoom = false
} = {}) {
this.eventBus = eventBus;
this.externalLinkTarget = externalLinkTarget;
this.externalLinkRel = externalLinkRel;
this._ignoreDestinationZoom = ignoreDestinationZoom;
this.baseUrl = null;
this.pdfDocument = null;
this.pdfViewer = null;
this.pdfHistory = null;
}
setDocument(pdfDocument, baseUrl = null) {
this.baseUrl = baseUrl;
this.pdfDocument = pdfDocument;
}
setViewer(pdfViewer) {
this.pdfViewer = pdfViewer;
}
setHistory(pdfHistory) {
this.pdfHistory = pdfHistory;
}
get pagesCount() {
return this.pdfDocument ? this.pdfDocument.numPages : 0;
}
get page() {
return this.pdfDocument ? this.pdfViewer.currentPageNumber : 1;
}
set page(value) {
if (this.pdfDocument) {
this.pdfViewer.currentPageNumber = value;
}
}
get rotation() {
return this.pdfDocument ? this.pdfViewer.pagesRotation : 0;
}
set rotation(value) {
if (this.pdfDocument) {
this.pdfViewer.pagesRotation = value;
}
}
get isInPresentationMode() {
return this.pdfDocument ? this.pdfViewer.isInPresentationMode : false;
}
async goToDestination(dest) {
if (!this.pdfDocument) {
return;
}
let namedDest, explicitDest, pageNumber;
if (typeof dest === "string") {
namedDest = dest;
explicitDest = await this.pdfDocument.getDestination(dest);
} else {
namedDest = null;
explicitDest = await dest;
}
if (!Array.isArray(explicitDest)) {
console.error(`goToDestination: "${explicitDest}" is not a valid destination array, for dest="${dest}".`);
return;
}
const [destRef] = explicitDest;
if (destRef && typeof destRef === "object") {
pageNumber = this.pdfDocument.cachedPageNumber(destRef);
if (!pageNumber) {
try {
pageNumber = (await this.pdfDocument.getPageIndex(destRef)) + 1;
} catch {
console.error(`goToDestination: "${destRef}" is not a valid page reference, for dest="${dest}".`);
return;
}
}
} else if (Number.isInteger(destRef)) {
pageNumber = destRef + 1;
}
if (!pageNumber || pageNumber < 1 || pageNumber > this.pagesCount) {
console.error(`goToDestination: "${pageNumber}" is not a valid page number, for dest="${dest}".`);
return;
}
if (this.pdfHistory) {
this.pdfHistory.pushCurrentPosition();
this.pdfHistory.push({
namedDest,
explicitDest,
pageNumber
});
}
this.pdfViewer.scrollPageIntoView({
pageNumber,
destArray: explicitDest,
ignoreDestinationZoom: this._ignoreDestinationZoom
});
}
goToPage(val) {
if (!this.pdfDocument) {
return;
}
const pageNumber = typeof val === "string" && this.pdfViewer.pageLabelToPageNumber(val) || val | 0;
if (!(Number.isInteger(pageNumber) && pageNumber > 0 && pageNumber <= this.pagesCount)) {
console.error(`PDFLinkService.goToPage: "${val}" is not a valid page.`);
return;
}
if (this.pdfHistory) {
this.pdfHistory.pushCurrentPosition();
this.pdfHistory.pushPage(pageNumber);
}
this.pdfViewer.scrollPageIntoView({
pageNumber
});
}
addLinkAttributes(link, url, newWindow = false) {
if (!url || typeof url !== "string") {
throw new Error('A valid "url" parameter must provided.');
}
const target = newWindow ? LinkTarget.BLANK : this.externalLinkTarget,
rel = this.externalLinkRel;
if (this.externalLinkEnabled) {
link.href = link.title = url;
} else {
link.href = "";
link.title = `Disabled: ${url}`;
link.onclick = () => false;
}
let targetStr = "";
switch (target) {
case LinkTarget.NONE:
break;
case LinkTarget.SELF:
targetStr = "_self";
break;
case LinkTarget.BLANK:
targetStr = "_blank";
break;
case LinkTarget.PARENT:
targetStr = "_parent";
break;
case LinkTarget.TOP:
targetStr = "_top";
break;
}
link.target = targetStr;
link.rel = typeof rel === "string" ? rel : DEFAULT_LINK_REL;
}
getDestinationHash(dest) {
if (typeof dest === "string") {
if (dest.length > 0) {
return this.getAnchorUrl("#" + escape(dest));
}
} else if (Array.isArray(dest)) {
const str = JSON.stringify(dest);
if (str.length > 0) {
return this.getAnchorUrl("#" + escape(str));
}
}
return this.getAnchorUrl("");
}
getAnchorUrl(anchor) {
return this.baseUrl ? this.baseUrl + anchor : anchor;
}
setHash(hash) {
if (!this.pdfDocument) {
return;
}
let pageNumber, dest;
if (hash.includes("=")) {
const params = parseQueryString(hash);
if (params.has("search")) {
const query = params.get("search").replaceAll('"', ""),
phrase = params.get("phrase") === "true";
this.eventBus.dispatch("findfromurlhash", {
source: this,
query: phrase ? query : query.match(/\S+/g)
});
}
if (params.has("page")) {
pageNumber = params.get("page") | 0 || 1;
}
if (params.has("zoom")) {
const zoomArgs = params.get("zoom").split(",");
const zoomArg = zoomArgs[0];
const zoomArgNumber = parseFloat(zoomArg);
if (!zoomArg.includes("Fit")) {
dest = [null, {
name: "XYZ"
}, zoomArgs.length > 1 ? zoomArgs[1] | 0 : null, zoomArgs.length > 2 ? zoomArgs[2] | 0 : null, zoomArgNumber ? zoomArgNumber / 100 : zoomArg];
} else if (zoomArg === "Fit" || zoomArg === "FitB") {
dest = [null, {
name: zoomArg
}];
} else if (zoomArg === "FitH" || zoomArg === "FitBH" || zoomArg === "FitV" || zoomArg === "FitBV") {
dest = [null, {
name: zoomArg
}, zoomArgs.length > 1 ? zoomArgs[1] | 0 : null];
} else if (zoomArg === "FitR") {
if (zoomArgs.length !== 5) {
console.error('PDFLinkService.setHash: Not enough parameters for "FitR".');
} else {
dest = [null, {
name: zoomArg
}, zoomArgs[1] | 0, zoomArgs[2] | 0, zoomArgs[3] | 0, zoomArgs[4] | 0];
}
} else {
console.error(`PDFLinkService.setHash: "${zoomArg}" is not a valid zoom value.`);
}
}
if (dest) {
this.pdfViewer.scrollPageIntoView({
pageNumber: pageNumber || this.page,
destArray: dest,
allowNegativeOffset: true
});
} else if (pageNumber) {
this.page = pageNumber;
}
if (params.has("pagemode")) {
this.eventBus.dispatch("pagemode", {
source: this,
mode: params.get("pagemode")
});
}
if (params.has("nameddest")) {
this.goToDestination(params.get("nameddest"));
}
return;
}
dest = unescape(hash);
try {
dest = JSON.parse(dest);
if (!Array.isArray(dest)) {
dest = dest.toString();
}
} catch {}
if (typeof dest === "string" || isValidExplicitDest(dest)) {
this.goToDestination(dest);
return;
}
console.error(`PDFLinkService.setHash: "${unescape(hash)}" is not a valid destination.`);
}
executeNamedAction(action) {
if (!this.pdfDocument) {
return;
}
switch (action) {
case "GoBack":
this.pdfHistory?.back();
break;
case "GoForward":
this.pdfHistory?.forward();
break;
case "NextPage":
this.pdfViewer.nextPage();
break;
case "PrevPage":
this.pdfViewer.previousPage();
break;
case "LastPage":
this.page = this.pagesCount;
break;
case "FirstPage":
this.page = 1;
break;
default:
break;
}
this.eventBus.dispatch("namedaction", {
source: this,
action
});
}
async executeSetOCGState(action) {
if (!this.pdfDocument) {
return;
}
const pdfDocument = this.pdfDocument,
optionalContentConfig = await this.pdfViewer.optionalContentConfigPromise;
if (pdfDocument !== this.pdfDocument) {
return;
}
optionalContentConfig.setOCGState(action);
this.pdfViewer.optionalContentConfigPromise = Promise.resolve(optionalContentConfig);
}
}
class SimpleLinkService extends PDFLinkService {
setDocument(pdfDocument, baseUrl = null) {}
}
;// ./web/event_utils.js
const WaitOnType = {
EVENT: "event",
TIMEOUT: "timeout"
};
async function waitOnEventOrTimeout({
target,
name,
delay = 0
}) {
if (typeof target !== "object" || !(name && typeof name === "string") || !(Number.isInteger(delay) && delay >= 0)) {
throw new Error("waitOnEventOrTimeout - invalid parameters.");
}
const {
promise,
resolve
} = Promise.withResolvers();
const ac = new AbortController();
function handler(type) {
ac.abort();
clearTimeout(timeout);
resolve(type);
}
const evtMethod = target instanceof EventBus ? "_on" : "addEventListener";
target[evtMethod](name, handler.bind(null, WaitOnType.EVENT), {
signal: ac.signal
});
const timeout = setTimeout(handler.bind(null, WaitOnType.TIMEOUT), delay);
return promise;
}
class EventBus {
#listeners = Object.create(null);
on(eventName, listener, options = null) {
this._on(eventName, listener, {
external: true,
once: options?.once,
signal: options?.signal
});
}
off(eventName, listener, options = null) {
this._off(eventName, listener);
}
dispatch(eventName, data) {
const eventListeners = this.#listeners[eventName];
if (!eventListeners || eventListeners.length === 0) {
return;
}
let externalListeners;
for (const {
listener,
external,
once
} of eventListeners.slice(0)) {
if (once) {
this._off(eventName, listener);
}
if (external) {
(externalListeners ||= []).push(listener);
continue;
}
listener(data);
}
if (externalListeners) {
for (const listener of externalListeners) {
listener(data);
}
externalListeners = null;
}
}
_on(eventName, listener, options = null) {
let rmAbort = null;
if (options?.signal instanceof AbortSignal) {
const {
signal
} = options;
if (signal.aborted) {
console.error("Cannot use an `aborted` signal.");
return;
}
const onAbort = () => this._off(eventName, listener);
rmAbort = () => signal.removeEventListener("abort", onAbort);
signal.addEventListener("abort", onAbort);
}
const eventListeners = this.#listeners[eventName] ||= [];
eventListeners.push({
listener,
external: options?.external === true,
once: options?.once === true,
rmAbort
});
}
_off(eventName, listener, options = null) {
const eventListeners = this.#listeners[eventName];
if (!eventListeners) {
return;
}
for (let i = 0, ii = eventListeners.length; i < ii; i++) {
const evt = eventListeners[i];
if (evt.listener === listener) {
evt.rmAbort?.();
eventListeners.splice(i, 1);
return;
}
}
}
}
class FirefoxEventBus extends EventBus {
#externalServices;
#globalEventNames;
#isInAutomation;
constructor(globalEventNames, externalServices, isInAutomation) {
super();
this.#globalEventNames = globalEventNames;
this.#externalServices = externalServices;
this.#isInAutomation = isInAutomation;
}
dispatch(eventName, data) {
throw new Error("Not implemented: FirefoxEventBus.dispatch");
}
}
;// ./web/external_services.js
class BaseExternalServices {
updateFindControlState(data) {}
updateFindMatchesCount(data) {}
initPassiveLoading() {}
reportTelemetry(data) {}
async createL10n() {
throw new Error("Not implemented: createL10n");
}
createScripting() {
throw new Error("Not implemented: createScripting");
}
createSignatureStorage() {
throw new Error("Not implemented: createSignatureStorage");
}
updateEditorStates(data) {
throw new Error("Not implemented: updateEditorStates");
}
dispatchGlobalEvent(_event) {}
}
;// ./web/preferences.js
class BasePreferences {
#defaults = Object.freeze({
altTextLearnMoreUrl: "",
annotationEditorMode: 0,
annotationMode: 2,
cursorToolOnLoad: 0,
defaultZoomDelay: 400,
defaultZoomValue: "",
disablePageLabels: false,
enableAltText: false,
enableAltTextModelDownload: true,
enableAutoLinking: true,
enableGuessAltText: true,
enableHighlightFloatingButton: false,
enableNewAltTextWhenAddingImage: true,
enablePermissions: false,
enablePrintAutoRotate: true,
enableScripting: true,
enableSignatureEditor: false,
enableUpdatedAddImage: false,
externalLinkTarget: 0,
highlightEditorColors: "yellow=#FFFF98,green=#53FFBC,blue=#80EBFF,pink=#FFCBE6,red=#FF4F5F",
historyUpdateUrl: false,
ignoreDestinationZoom: false,
forcePageColors: false,
pageColorsBackground: "Canvas",
pageColorsForeground: "CanvasText",
pdfBugEnabled: false,
sidebarViewOnLoad: -1,
scrollModeOnLoad: -1,
spreadModeOnLoad: -1,
textLayerMode: 1,
viewerCssTheme: 0,
viewOnLoad: 0,
disableAutoFetch: false,
disableFontFace: false,
disableRange: false,
disableStream: false,
enableHWA: true,
enableXfa: true
});
#initializedPromise = null;
constructor() {
this.#initializedPromise = this._readFromStorage(this.#defaults).then(({
browserPrefs,
prefs
}) => {
if (AppOptions._checkDisablePreferences()) {
return;
}
AppOptions.setAll({
...browserPrefs,
...prefs
}, true);
});
}
async _writeToStorage(prefObj) {
throw new Error("Not implemented: _writeToStorage");
}
async _readFromStorage(prefObj) {
throw new Error("Not implemented: _readFromStorage");
}
async reset() {
await this.#initializedPromise;
AppOptions.setAll(this.#defaults, true);
await this._writeToStorage(this.#defaults);
}
async set(name, value) {
await this.#initializedPromise;
AppOptions.setAll({
[name]: value
}, true);
await this._writeToStorage(AppOptions.getAll(OptionKind.PREFERENCE));
}
async get(name) {
await this.#initializedPromise;
return AppOptions.get(name);
}
get initializedPromise() {
return this.#initializedPromise;
}
}
;// ./node_modules/@fluent/bundle/esm/types.js
class FluentType {
constructor(value) {
this.value = value;
}
valueOf() {
return this.value;
}
}
class FluentNone extends FluentType {
constructor(value = "???") {
super(value);
}
toString(scope) {
return `{${this.value}}`;
}
}
class FluentNumber extends FluentType {
constructor(value, opts = {}) {
super(value);
this.opts = opts;
}
toString(scope) {
try {
const nf = scope.memoizeIntlObject(Intl.NumberFormat, this.opts);
return nf.format(this.value);
} catch (err) {
scope.reportError(err);
return this.value.toString(10);
}
}
}
class FluentDateTime extends FluentType {
constructor(value, opts = {}) {
super(value);
this.opts = opts;
}
toString(scope) {
try {
const dtf = scope.memoizeIntlObject(Intl.DateTimeFormat, this.opts);
return dtf.format(this.value);
} catch (err) {
scope.reportError(err);
return new Date(this.value).toISOString();
}
}
}
;// ./node_modules/@fluent/bundle/esm/resolver.js
const MAX_PLACEABLES = 100;
const FSI = "\u2068";
const PDI = "\u2069";
function match(scope, selector, key) {
if (key === selector) {
return true;
}
if (key instanceof FluentNumber && selector instanceof FluentNumber && key.value === selector.value) {
return true;
}
if (selector instanceof FluentNumber && typeof key === "string") {
let category = scope.memoizeIntlObject(Intl.PluralRules, selector.opts).select(selector.value);
if (key === category) {
return true;
}
}
return false;
}
function getDefault(scope, variants, star) {
if (variants[star]) {
return resolvePattern(scope, variants[star].value);
}
scope.reportError(new RangeError("No default"));
return new FluentNone();
}
function getArguments(scope, args) {
const positional = [];
const named = Object.create(null);
for (const arg of args) {
if (arg.type === "narg") {
named[arg.name] = resolveExpression(scope, arg.value);
} else {
positional.push(resolveExpression(scope, arg));
}
}
return {
positional,
named
};
}
function resolveExpression(scope, expr) {
switch (expr.type) {
case "str":
return expr.value;
case "num":
return new FluentNumber(expr.value, {
minimumFractionDigits: expr.precision
});
case "var":
return resolveVariableReference(scope, expr);
case "mesg":
return resolveMessageReference(scope, expr);
case "term":
return resolveTermReference(scope, expr);
case "func":
return resolveFunctionReference(scope, expr);
case "select":
return resolveSelectExpression(scope, expr);
default:
return new FluentNone();
}
}
function resolveVariableReference(scope, {
name
}) {
let arg;
if (scope.params) {
if (Object.prototype.hasOwnProperty.call(scope.params, name)) {
arg = scope.params[name];
} else {
return new FluentNone(`$${name}`);
}
} else if (scope.args && Object.prototype.hasOwnProperty.call(scope.args, name)) {
arg = scope.args[name];
} else {
scope.reportError(new ReferenceError(`Unknown variable: $${name}`));
return new FluentNone(`$${name}`);
}
if (arg instanceof FluentType) {
return arg;
}
switch (typeof arg) {
case "string":
return arg;
case "number":
return new FluentNumber(arg);
case "object":
if (arg instanceof Date) {
return new FluentDateTime(arg.getTime());
}
default:
scope.reportError(new TypeError(`Variable type not supported: $${name}, ${typeof arg}`));
return new FluentNone(`$${name}`);
}
}
function resolveMessageReference(scope, {
name,
attr
}) {
const message = scope.bundle._messages.get(name);
if (!message) {
scope.reportError(new ReferenceError(`Unknown message: ${name}`));
return new FluentNone(name);
}
if (attr) {
const attribute = message.attributes[attr];
if (attribute) {
return resolvePattern(scope, attribute);
}
scope.reportError(new ReferenceError(`Unknown attribute: ${attr}`));
return new FluentNone(`${name}.${attr}`);
}
if (message.value) {
return resolvePattern(scope, message.value);
}
scope.reportError(new ReferenceError(`No value: ${name}`));
return new FluentNone(name);
}
function resolveTermReference(scope, {
name,
attr,
args
}) {
const id = `-${name}`;
const term = scope.bundle._terms.get(id);
if (!term) {
scope.reportError(new ReferenceError(`Unknown term: ${id}`));
return new FluentNone(id);
}
if (attr) {
const attribute = term.attributes[attr];
if (attribute) {
scope.params = getArguments(scope, args).named;
const resolved = resolvePattern(scope, attribute);
scope.params = null;
return resolved;
}
scope.reportError(new ReferenceError(`Unknown attribute: ${attr}`));
return new FluentNone(`${id}.${attr}`);
}
scope.params = getArguments(scope, args).named;
const resolved = resolvePattern(scope, term.value);
scope.params = null;
return resolved;
}
function resolveFunctionReference(scope, {
name,
args
}) {
let func = scope.bundle._functions[name];
if (!func) {
scope.reportError(new ReferenceError(`Unknown function: ${name}()`));
return new FluentNone(`${name}()`);
}
if (typeof func !== "function") {
scope.reportError(new TypeError(`Function ${name}() is not callable`));
return new FluentNone(`${name}()`);
}
try {
let resolved = getArguments(scope, args);
return func(resolved.positional, resolved.named);
} catch (err) {
scope.reportError(err);
return new FluentNone(`${name}()`);
}
}
function resolveSelectExpression(scope, {
selector,
variants,
star
}) {
let sel = resolveExpression(scope, selector);
if (sel instanceof FluentNone) {
return getDefault(scope, variants, star);
}
for (const variant of variants) {
const key = resolveExpression(scope, variant.key);
if (match(scope, sel, key)) {
return resolvePattern(scope, variant.value);
}
}
return getDefault(scope, variants, star);
}
function resolveComplexPattern(scope, ptn) {
if (scope.dirty.has(ptn)) {
scope.reportError(new RangeError("Cyclic reference"));
return new FluentNone();
}
scope.dirty.add(ptn);
const result = [];
const useIsolating = scope.bundle._useIsolating && ptn.length > 1;
for (const elem of ptn) {
if (typeof elem === "string") {
result.push(scope.bundle._transform(elem));
continue;
}
scope.placeables++;
if (scope.placeables > MAX_PLACEABLES) {
scope.dirty.delete(ptn);
throw new RangeError(`Too many placeables expanded: ${scope.placeables}, ` + `max allowed is ${MAX_PLACEABLES}`);
}
if (useIsolating) {
result.push(FSI);
}
result.push(resolveExpression(scope, elem).toString(scope));
if (useIsolating) {
result.push(PDI);
}
}
scope.dirty.delete(ptn);
return result.join("");
}
function resolvePattern(scope, value) {
if (typeof value === "string") {
return scope.bundle._transform(value);
}
return resolveComplexPattern(scope, value);
}
;// ./node_modules/@fluent/bundle/esm/scope.js
class Scope {
constructor(bundle, errors, args) {
this.dirty = new WeakSet();
this.params = null;
this.placeables = 0;
this.bundle = bundle;
this.errors = errors;
this.args = args;
}
reportError(error) {
if (!this.errors || !(error instanceof Error)) {
throw error;
}
this.errors.push(error);
}
memoizeIntlObject(ctor, opts) {
let cache = this.bundle._intls.get(ctor);
if (!cache) {
cache = {};
this.bundle._intls.set(ctor, cache);
}
let id = JSON.stringify(opts);
if (!cache[id]) {
cache[id] = new ctor(this.bundle.locales, opts);
}
return cache[id];
}
}
;// ./node_modules/@fluent/bundle/esm/builtins.js
function values(opts, allowed) {
const unwrapped = Object.create(null);
for (const [name, opt] of Object.entries(opts)) {
if (allowed.includes(name)) {
unwrapped[name] = opt.valueOf();
}
}
return unwrapped;
}
const NUMBER_ALLOWED = ["unitDisplay", "currencyDisplay", "useGrouping", "minimumIntegerDigits", "minimumFractionDigits", "maximumFractionDigits", "minimumSignificantDigits", "maximumSignificantDigits"];
function NUMBER(args, opts) {
let arg = args[0];
if (arg instanceof FluentNone) {
return new FluentNone(`NUMBER(${arg.valueOf()})`);
}
if (arg instanceof FluentNumber) {
return new FluentNumber(arg.valueOf(), {
...arg.opts,
...values(opts, NUMBER_ALLOWED)
});
}
if (arg instanceof FluentDateTime) {
return new FluentNumber(arg.valueOf(), {
...values(opts, NUMBER_ALLOWED)
});
}
throw new TypeError("Invalid argument to NUMBER");
}
const DATETIME_ALLOWED = ["dateStyle", "timeStyle", "fractionalSecondDigits", "dayPeriod", "hour12", "weekday", "era", "year", "month", "day", "hour", "minute", "second", "timeZoneName"];
function DATETIME(args, opts) {
let arg = args[0];
if (arg instanceof FluentNone) {
return new FluentNone(`DATETIME(${arg.valueOf()})`);
}
if (arg instanceof FluentDateTime) {
return new FluentDateTime(arg.valueOf(), {
...arg.opts,
...values(opts, DATETIME_ALLOWED)
});
}
if (arg instanceof FluentNumber) {
return new FluentDateTime(arg.valueOf(), {
...values(opts, DATETIME_ALLOWED)
});
}
throw new TypeError("Invalid argument to DATETIME");
}
;// ./node_modules/@fluent/bundle/esm/memoizer.js
const cache = new Map();
function getMemoizerForLocale(locales) {
const stringLocale = Array.isArray(locales) ? locales.join(" ") : locales;
let memoizer = cache.get(stringLocale);
if (memoizer === undefined) {
memoizer = new Map();
cache.set(stringLocale, memoizer);
}
return memoizer;
}
;// ./node_modules/@fluent/bundle/esm/bundle.js
class FluentBundle {
constructor(locales, {
functions,
useIsolating = true,
transform = v => v
} = {}) {
this._terms = new Map();
this._messages = new Map();
this.locales = Array.isArray(locales) ? locales : [locales];
this._functions = {
NUMBER: NUMBER,
DATETIME: DATETIME,
...functions
};
this._useIsolating = useIsolating;
this._transform = transform;
this._intls = getMemoizerForLocale(locales);
}
hasMessage(id) {
return this._messages.has(id);
}
getMessage(id) {
return this._messages.get(id);
}
addResource(res, {
allowOverrides = false
} = {}) {
const errors = [];
for (let i = 0; i < res.body.length; i++) {
let entry = res.body[i];
if (entry.id.startsWith("-")) {
if (allowOverrides === false && this._terms.has(entry.id)) {
errors.push(new Error(`Attempt to override an existing term: "${entry.id}"`));
continue;
}
this._terms.set(entry.id, entry);
} else {
if (allowOverrides === false && this._messages.has(entry.id)) {
errors.push(new Error(`Attempt to override an existing message: "${entry.id}"`));
continue;
}
this._messages.set(entry.id, entry);
}
}
return errors;
}
formatPattern(pattern, args = null, errors = null) {
if (typeof pattern === "string") {
return this._transform(pattern);
}
let scope = new Scope(this, errors, args);
try {
let value = resolveComplexPattern(scope, pattern);
return value.toString(scope);
} catch (err) {
if (scope.errors && err instanceof Error) {
scope.errors.push(err);
return new FluentNone().toString(scope);
}
throw err;
}
}
}
;// ./node_modules/@fluent/bundle/esm/resource.js
const RE_MESSAGE_START = /^(-?[a-zA-Z][\w-]*) *= */gm;
const RE_ATTRIBUTE_START = /\.([a-zA-Z][\w-]*) *= */y;
const RE_VARIANT_START = /\*?\[/y;
const RE_NUMBER_LITERAL = /(-?[0-9]+(?:\.([0-9]+))?)/y;
const RE_IDENTIFIER = /([a-zA-Z][\w-]*)/y;
const RE_REFERENCE = /([$-])?([a-zA-Z][\w-]*)(?:\.([a-zA-Z][\w-]*))?/y;
const RE_FUNCTION_NAME = /^[A-Z][A-Z0-9_-]*$/;
const RE_TEXT_RUN = /([^{}\n\r]+)/y;
const RE_STRING_RUN = /([^\\"\n\r]*)/y;
const RE_STRING_ESCAPE = /\\([\\"])/y;
const RE_UNICODE_ESCAPE = /\\u([a-fA-F0-9]{4})|\\U([a-fA-F0-9]{6})/y;
const RE_LEADING_NEWLINES = /^\n+/;
const RE_TRAILING_SPACES = / +$/;
const RE_BLANK_LINES = / *\r?\n/g;
const RE_INDENT = /( *)$/;
const TOKEN_BRACE_OPEN = /{\s*/y;
const TOKEN_BRACE_CLOSE = /\s*}/y;
const TOKEN_BRACKET_OPEN = /\[\s*/y;
const TOKEN_BRACKET_CLOSE = /\s*] */y;
const TOKEN_PAREN_OPEN = /\s*\(\s*/y;
const TOKEN_ARROW = /\s*->\s*/y;
const TOKEN_COLON = /\s*:\s*/y;
const TOKEN_COMMA = /\s*,?\s*/y;
const TOKEN_BLANK = /\s+/y;
class FluentResource {
constructor(source) {
this.body = [];
RE_MESSAGE_START.lastIndex = 0;
let cursor = 0;
while (true) {
let next = RE_MESSAGE_START.exec(source);
if (next === null) {
break;
}
cursor = RE_MESSAGE_START.lastIndex;
try {
this.body.push(parseMessage(next[1]));
} catch (err) {
if (err instanceof SyntaxError) {
continue;
}
throw err;
}
}
function test(re) {
re.lastIndex = cursor;
return re.test(source);
}
function consumeChar(char, errorClass) {
if (source[cursor] === char) {
cursor++;
return true;
}
if (errorClass) {
throw new errorClass(`Expected ${char}`);
}
return false;
}
function consumeToken(re, errorClass) {
if (test(re)) {
cursor = re.lastIndex;
return true;
}
if (errorClass) {
throw new errorClass(`Expected ${re.toString()}`);
}
return false;
}
function match(re) {
re.lastIndex = cursor;
let result = re.exec(source);
if (result === null) {
throw new SyntaxError(`Expected ${re.toString()}`);
}
cursor = re.lastIndex;
return result;
}
function match1(re) {
return match(re)[1];
}
function parseMessage(id) {
let value = parsePattern();
let attributes = parseAttributes();
if (value === null && Object.keys(attributes).length === 0) {
throw new SyntaxError("Expected message value or attributes");
}
return {
id,
value,
attributes
};
}
function parseAttributes() {
let attrs = Object.create(null);
while (test(RE_ATTRIBUTE_START)) {
let name = match1(RE_ATTRIBUTE_START);
let value = parsePattern();
if (value === null) {
throw new SyntaxError("Expected attribute value");
}
attrs[name] = value;
}
return attrs;
}
function parsePattern() {
let first;
if (test(RE_TEXT_RUN)) {
first = match1(RE_TEXT_RUN);
}
if (source[cursor] === "{" || source[cursor] === "}") {
return parsePatternElements(first ? [first] : [], Infinity);
}
let indent = parseIndent();
if (indent) {
if (first) {
return parsePatternElements([first, indent], indent.length);
}
indent.value = trim(indent.value, RE_LEADING_NEWLINES);
return parsePatternElements([indent], indent.length);
}
if (first) {
return trim(first, RE_TRAILING_SPACES);
}
return null;
}
function parsePatternElements(elements = [], commonIndent) {
while (true) {
if (test(RE_TEXT_RUN)) {
elements.push(match1(RE_TEXT_RUN));
continue;
}
if (source[cursor] === "{") {
elements.push(parsePlaceable());
continue;
}
if (source[cursor] === "}") {
throw new SyntaxError("Unbalanced closing brace");
}
let indent = parseIndent();
if (indent) {
elements.push(indent);
commonIndent = Math.min(commonIndent, indent.length);
continue;
}
break;
}
let lastIndex = elements.length - 1;
let lastElement = elements[lastIndex];
if (typeof lastElement === "string") {
elements[lastIndex] = trim(lastElement, RE_TRAILING_SPACES);
}
let baked = [];
for (let element of elements) {
if (element instanceof Indent) {
element = element.value.slice(0, element.value.length - commonIndent);
}
if (element) {
baked.push(element);
}
}
return baked;
}
function parsePlaceable() {
consumeToken(TOKEN_BRACE_OPEN, SyntaxError);
let selector = parseInlineExpression();
if (consumeToken(TOKEN_BRACE_CLOSE)) {
return selector;
}
if (consumeToken(TOKEN_ARROW)) {
let variants = parseVariants();
consumeToken(TOKEN_BRACE_CLOSE, SyntaxError);
return {
type: "select",
selector,
...variants
};
}
throw new SyntaxError("Unclosed placeable");
}
function parseInlineExpression() {
if (source[cursor] === "{") {
return parsePlaceable();
}
if (test(RE_REFERENCE)) {
let [, sigil, name, attr = null] = match(RE_REFERENCE);
if (sigil === "$") {
return {
type: "var",
name
};
}
if (consumeToken(TOKEN_PAREN_OPEN)) {
let args = parseArguments();
if (sigil === "-") {
return {
type: "term",
name,
attr,
args
};
}
if (RE_FUNCTION_NAME.test(name)) {
return {
type: "func",
name,
args
};
}
throw new SyntaxError("Function names must be all upper-case");
}
if (sigil === "-") {
return {
type: "term",
name,
attr,
args: []
};
}
return {
type: "mesg",
name,
attr
};
}
return parseLiteral();
}
function parseArguments() {
let args = [];
while (true) {
switch (source[cursor]) {
case ")":
cursor++;
return args;
case undefined:
throw new SyntaxError("Unclosed argument list");
}
args.push(parseArgument());
consumeToken(TOKEN_COMMA);
}
}
function parseArgument() {
let expr = parseInlineExpression();
if (expr.type !== "mesg") {
return expr;
}
if (consumeToken(TOKEN_COLON)) {
return {
type: "narg",
name: expr.name,
value: parseLiteral()
};
}
return expr;
}
function parseVariants() {
let variants = [];
let count = 0;
let star;
while (test(RE_VARIANT_START)) {
if (consumeChar("*")) {
star = count;
}
let key = parseVariantKey();
let value = parsePattern();
if (value === null) {
throw new SyntaxError("Expected variant value");
}
variants[count++] = {
key,
value
};
}
if (count === 0) {
return null;
}
if (star === undefined) {
throw new SyntaxError("Expected default variant");
}
return {
variants,
star
};
}
function parseVariantKey() {
consumeToken(TOKEN_BRACKET_OPEN, SyntaxError);
let key;
if (test(RE_NUMBER_LITERAL)) {
key = parseNumberLiteral();
} else {
key = {
type: "str",
value: match1(RE_IDENTIFIER)
};
}
consumeToken(TOKEN_BRACKET_CLOSE, SyntaxError);
return key;
}
function parseLiteral() {
if (test(RE_NUMBER_LITERAL)) {
return parseNumberLiteral();
}
if (source[cursor] === '"') {
return parseStringLiteral();
}
throw new SyntaxError("Invalid expression");
}
function parseNumberLiteral() {
let [, value, fraction = ""] = match(RE_NUMBER_LITERAL);
let precision = fraction.length;
return {
type: "num",
value: parseFloat(value),
precision
};
}
function parseStringLiteral() {
consumeChar('"', SyntaxError);
let value = "";
while (true) {
value += match1(RE_STRING_RUN);
if (source[cursor] === "\\") {
value += parseEscapeSequence();
continue;
}
if (consumeChar('"')) {
return {
type: "str",
value
};
}
throw new SyntaxError("Unclosed string literal");
}
}
function parseEscapeSequence() {
if (test(RE_STRING_ESCAPE)) {
return match1(RE_STRING_ESCAPE);
}
if (test(RE_UNICODE_ESCAPE)) {
let [, codepoint4, codepoint6] = match(RE_UNICODE_ESCAPE);
let codepoint = parseInt(codepoint4 || codepoint6, 16);
return codepoint <= 0xd7ff || 0xe000 <= codepoint ? String.fromCodePoint(codepoint) : "�";
}
throw new SyntaxError("Unknown escape sequence");
}
function parseIndent() {
let start = cursor;
consumeToken(TOKEN_BLANK);
switch (source[cursor]) {
case ".":
case "[":
case "*":
case "}":
case undefined:
return false;
case "{":
return makeIndent(source.slice(start, cursor));
}
if (source[cursor - 1] === " ") {
return makeIndent(source.slice(start, cursor));
}
return false;
}
function trim(text, re) {
return text.replace(re, "");
}
function makeIndent(blank) {
let value = blank.replace(RE_BLANK_LINES, "\n");
let length = RE_INDENT.exec(blank)[1].length;
return new Indent(value, length);
}
}
}
class Indent {
constructor(value, length) {
this.value = value;
this.length = length;
}
}
;// ./node_modules/@fluent/bundle/esm/index.js
;// ./node_modules/@fluent/dom/esm/overlay.js
const reOverlay = /<|?\w+;/;
const TEXT_LEVEL_ELEMENTS = {
"http://www.w3.org/1999/xhtml": ["em", "strong", "small", "s", "cite", "q", "dfn", "abbr", "data", "time", "code", "var", "samp", "kbd", "sub", "sup", "i", "b", "u", "mark", "bdi", "bdo", "span", "br", "wbr"]
};
const LOCALIZABLE_ATTRIBUTES = {
"http://www.w3.org/1999/xhtml": {
global: ["title", "aria-description", "aria-label", "aria-valuetext"],
a: ["download"],
area: ["download", "alt"],
input: ["alt", "placeholder"],
menuitem: ["label"],
menu: ["label"],
optgroup: ["label"],
option: ["label"],
track: ["label"],
img: ["alt"],
textarea: ["placeholder"],
th: ["abbr"]
},
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul": {
global: ["accesskey", "aria-label", "aria-valuetext", "label", "title", "tooltiptext"],
description: ["value"],
key: ["key", "keycode"],
label: ["value"],
textbox: ["placeholder", "value"]
}
};
function translateElement(element, translation) {
const {
value
} = translation;
if (typeof value === "string") {
if (element.localName === "title" && element.namespaceURI === "http://www.w3.org/1999/xhtml") {
element.textContent = value;
} else if (!reOverlay.test(value)) {
element.textContent = value;
} else {
const templateElement = element.ownerDocument.createElementNS("http://www.w3.org/1999/xhtml", "template");
templateElement.innerHTML = value;
overlayChildNodes(templateElement.content, element);
}
}
overlayAttributes(translation, element);
}
function overlayChildNodes(fromFragment, toElement) {
for (const childNode of fromFragment.childNodes) {
if (childNode.nodeType === childNode.TEXT_NODE) {
continue;
}
if (childNode.hasAttribute("data-l10n-name")) {
const sanitized = getNodeForNamedElement(toElement, childNode);
fromFragment.replaceChild(sanitized, childNode);
continue;
}
if (isElementAllowed(childNode)) {
const sanitized = createSanitizedElement(childNode);
fromFragment.replaceChild(sanitized, childNode);
continue;
}
console.warn(`An element of forbidden type "${childNode.localName}" was found in ` + "the translation. Only safe text-level elements and elements with " + "data-l10n-name are allowed.");
fromFragment.replaceChild(createTextNodeFromTextContent(childNode), childNode);
}
toElement.textContent = "";
toElement.appendChild(fromFragment);
}
function hasAttribute(attributes, name) {
if (!attributes) {
return false;
}
for (let attr of attributes) {
if (attr.name === name) {
return true;
}
}
return false;
}
function overlayAttributes(fromElement, toElement) {
const explicitlyAllowed = toElement.hasAttribute("data-l10n-attrs") ? toElement.getAttribute("data-l10n-attrs").split(",").map(i => i.trim()) : null;
for (const attr of Array.from(toElement.attributes)) {
if (isAttrNameLocalizable(attr.name, toElement, explicitlyAllowed) && !hasAttribute(fromElement.attributes, attr.name)) {
toElement.removeAttribute(attr.name);
}
}
if (!fromElement.attributes) {
return;
}
for (const attr of Array.from(fromElement.attributes)) {
if (isAttrNameLocalizable(attr.name, toElement, explicitlyAllowed) && toElement.getAttribute(attr.name) !== attr.value) {
toElement.setAttribute(attr.name, attr.value);
}
}
}
function getNodeForNamedElement(sourceElement, translatedChild) {
const childName = translatedChild.getAttribute("data-l10n-name");
const sourceChild = sourceElement.querySelector(`[data-l10n-name="${childName}"]`);
if (!sourceChild) {
console.warn(`An element named "${childName}" wasn't found in the source.`);
return createTextNodeFromTextContent(translatedChild);
}
if (sourceChild.localName !== translatedChild.localName) {
console.warn(`An element named "${childName}" was found in the translation ` + `but its type ${translatedChild.localName} didn't match the ` + `element found in the source (${sourceChild.localName}).`);
return createTextNodeFromTextContent(translatedChild);
}
sourceElement.removeChild(sourceChild);
const clone = sourceChild.cloneNode(false);
return shallowPopulateUsing(translatedChild, clone);
}
function createSanitizedElement(element) {
const clone = element.ownerDocument.createElement(element.localName);
return shallowPopulateUsing(element, clone);
}
function createTextNodeFromTextContent(element) {
return element.ownerDocument.createTextNode(element.textContent);
}
function isElementAllowed(element) {
const allowed = TEXT_LEVEL_ELEMENTS[element.namespaceURI];
return allowed && allowed.includes(element.localName);
}
function isAttrNameLocalizable(name, element, explicitlyAllowed = null) {
if (explicitlyAllowed && explicitlyAllowed.includes(name)) {
return true;
}
const allowed = LOCALIZABLE_ATTRIBUTES[element.namespaceURI];
if (!allowed) {
return false;
}
const attrName = name.toLowerCase();
const elemName = element.localName;
if (allowed.global.includes(attrName)) {
return true;
}
if (!allowed[elemName]) {
return false;
}
if (allowed[elemName].includes(attrName)) {
return true;
}
if (element.namespaceURI === "http://www.w3.org/1999/xhtml" && elemName === "input" && attrName === "value") {
const type = element.type.toLowerCase();
if (type === "submit" || type === "button" || type === "reset") {
return true;
}
}
return false;
}
function shallowPopulateUsing(fromElement, toElement) {
toElement.textContent = fromElement.textContent;
overlayAttributes(fromElement, toElement);
return toElement;
}
;// ./node_modules/cached-iterable/src/cached_iterable.mjs
class CachedIterable extends Array {
static from(iterable) {
if (iterable instanceof this) {
return iterable;
}
return new this(iterable);
}
}
;// ./node_modules/cached-iterable/src/cached_sync_iterable.mjs
class CachedSyncIterable extends CachedIterable {
constructor(iterable) {
super();
if (Symbol.iterator in Object(iterable)) {
this.iterator = iterable[Symbol.iterator]();
} else {
throw new TypeError("Argument must implement the iteration protocol.");
}
}
[Symbol.iterator]() {
const cached = this;
let cur = 0;
return {
next() {
if (cached.length <= cur) {
cached.push(cached.iterator.next());
}
return cached[cur++];
}
};
}
touchNext(count = 1) {
let idx = 0;
while (idx++ < count) {
const last = this[this.length - 1];
if (last && last.done) {
break;
}
this.push(this.iterator.next());
}
return this[this.length - 1];
}
}
;// ./node_modules/cached-iterable/src/cached_async_iterable.mjs
class CachedAsyncIterable extends CachedIterable {
constructor(iterable) {
super();
if (Symbol.asyncIterator in Object(iterable)) {
this.iterator = iterable[Symbol.asyncIterator]();
} else if (Symbol.iterator in Object(iterable)) {
this.iterator = iterable[Symbol.iterator]();
} else {
throw new TypeError("Argument must implement the iteration protocol.");
}
}
[Symbol.asyncIterator]() {
const cached = this;
let cur = 0;
return {
async next() {
if (cached.length <= cur) {
cached.push(cached.iterator.next());
}
return cached[cur++];
}
};
}
async touchNext(count = 1) {
let idx = 0;
while (idx++ < count) {
const last = this[this.length - 1];
if (last && (await last).done) {
break;
}
this.push(this.iterator.next());
}
return this[this.length - 1];
}
}
;// ./node_modules/cached-iterable/src/index.mjs
;// ./node_modules/@fluent/dom/esm/localization.js
class Localization {
constructor(resourceIds = [], generateBundles) {
this.resourceIds = resourceIds;
this.generateBundles = generateBundles;
this.onChange(true);
}
addResourceIds(resourceIds, eager = false) {
this.resourceIds.push(...resourceIds);
this.onChange(eager);
return this.resourceIds.length;
}
removeResourceIds(resourceIds) {
this.resourceIds = this.resourceIds.filter(r => !resourceIds.includes(r));
this.onChange();
return this.resourceIds.length;
}
async formatWithFallback(keys, method) {
const translations = [];
let hasAtLeastOneBundle = false;
for await (const bundle of this.bundles) {
hasAtLeastOneBundle = true;
const missingIds = keysFromBundle(method, bundle, keys, translations);
if (missingIds.size === 0) {
break;
}
if (typeof console !== "undefined") {
const locale = bundle.locales[0];
const ids = Array.from(missingIds).join(", ");
console.warn(`[fluent] Missing translations in ${locale}: ${ids}`);
}
}
if (!hasAtLeastOneBundle && typeof console !== "undefined") {
console.warn(`[fluent] Request for keys failed because no resource bundles got generated.
keys: ${JSON.stringify(keys)}.
resourceIds: ${JSON.stringify(this.resourceIds)}.`);
}
return translations;
}
formatMessages(keys) {
return this.formatWithFallback(keys, messageFromBundle);
}
formatValues(keys) {
return this.formatWithFallback(keys, valueFromBundle);
}
async formatValue(id, args) {
const [val] = await this.formatValues([{
id,
args
}]);
return val;
}
handleEvent() {
this.onChange();
}
onChange(eager = false) {
this.bundles = CachedAsyncIterable.from(this.generateBundles(this.resourceIds));
if (eager) {
this.bundles.touchNext(2);
}
}
}
function valueFromBundle(bundle, errors, message, args) {
if (message.value) {
return bundle.formatPattern(message.value, args, errors);
}
return null;
}
function messageFromBundle(bundle, errors, message, args) {
const formatted = {
value: null,
attributes: null
};
if (message.value) {
formatted.value = bundle.formatPattern(message.value, args, errors);
}
let attrNames = Object.keys(message.attributes);
if (attrNames.length > 0) {
formatted.attributes = new Array(attrNames.length);
for (let [i, name] of attrNames.entries()) {
let value = bundle.formatPattern(message.attributes[name], args, errors);
formatted.attributes[i] = {
name,
value
};
}
}
return formatted;
}
function keysFromBundle(method, bundle, keys, translations) {
const messageErrors = [];
const missingIds = new Set();
keys.forEach(({
id,
args
}, i) => {
if (translations[i] !== undefined) {
return;
}
let message = bundle.getMessage(id);
if (message) {
messageErrors.length = 0;
translations[i] = method(bundle, messageErrors, message, args);
if (messageErrors.length > 0 && typeof console !== "undefined") {
const locale = bundle.locales[0];
const errors = messageErrors.join(", ");
console.warn(`[fluent][resolver] errors in ${locale}/${id}: ${errors}.`);
}
} else {
missingIds.add(id);
}
});
return missingIds;
}
;// ./node_modules/@fluent/dom/esm/dom_localization.js
const L10NID_ATTR_NAME = "data-l10n-id";
const L10NARGS_ATTR_NAME = "data-l10n-args";
const L10N_ELEMENT_QUERY = `[${L10NID_ATTR_NAME}]`;
class DOMLocalization extends Localization {
constructor(resourceIds, generateBundles) {
super(resourceIds, generateBundles);
this.roots = new Set();
this.pendingrAF = null;
this.pendingElements = new Set();
this.windowElement = null;
this.mutationObserver = null;
this.observerConfig = {
attributes: true,
characterData: false,
childList: true,
subtree: true,
attributeFilter: [L10NID_ATTR_NAME, L10NARGS_ATTR_NAME]
};
}
onChange(eager = false) {
super.onChange(eager);
if (this.roots) {
this.translateRoots();
}
}
setAttributes(element, id, args) {
element.setAttribute(L10NID_ATTR_NAME, id);
if (args) {
element.setAttribute(L10NARGS_ATTR_NAME, JSON.stringify(args));
} else {
element.removeAttribute(L10NARGS_ATTR_NAME);
}
return element;
}
getAttributes(element) {
return {
id: element.getAttribute(L10NID_ATTR_NAME),
args: JSON.parse(element.getAttribute(L10NARGS_ATTR_NAME) || null)
};
}
connectRoot(newRoot) {
for (const root of this.roots) {
if (root === newRoot || root.contains(newRoot) || newRoot.contains(root)) {
throw new Error("Cannot add a root that overlaps with existing root.");
}
}
if (this.windowElement) {
if (this.windowElement !== newRoot.ownerDocument.defaultView) {
throw new Error(`Cannot connect a root:
DOMLocalization already has a root from a different window.`);
}
} else {
this.windowElement = newRoot.ownerDocument.defaultView;
this.mutationObserver = new this.windowElement.MutationObserver(mutations => this.translateMutations(mutations));
}
this.roots.add(newRoot);
this.mutationObserver.observe(newRoot, this.observerConfig);
}
disconnectRoot(root) {
this.roots.delete(root);
this.pauseObserving();
if (this.roots.size === 0) {
this.mutationObserver = null;
if (this.windowElement && this.pendingrAF) {
this.windowElement.cancelAnimationFrame(this.pendingrAF);
}
this.windowElement = null;
this.pendingrAF = null;
this.pendingElements.clear();
return true;
}
this.resumeObserving();
return false;
}
translateRoots() {
const roots = Array.from(this.roots);
return Promise.all(roots.map(root => this.translateFragment(root)));
}
pauseObserving() {
if (!this.mutationObserver) {
return;
}
this.translateMutations(this.mutationObserver.takeRecords());
this.mutationObserver.disconnect();
}
resumeObserving() {
if (!this.mutationObserver) {
return;
}
for (const root of this.roots) {
this.mutationObserver.observe(root, this.observerConfig);
}
}
translateMutations(mutations) {
for (const mutation of mutations) {
switch (mutation.type) {
case "attributes":
if (mutation.target.hasAttribute("data-l10n-id")) {
this.pendingElements.add(mutation.target);
}
break;
case "childList":
for (const addedNode of mutation.addedNodes) {
if (addedNode.nodeType === addedNode.ELEMENT_NODE) {
if (addedNode.childElementCount) {
for (const element of this.getTranslatables(addedNode)) {
this.pendingElements.add(element);
}
} else if (addedNode.hasAttribute(L10NID_ATTR_NAME)) {
this.pendingElements.add(addedNode);
}
}
}
break;
}
}
if (this.pendingElements.size > 0) {
if (this.pendingrAF === null) {
this.pendingrAF = this.windowElement.requestAnimationFrame(() => {
this.translateElements(Array.from(this.pendingElements));
this.pendingElements.clear();
this.pendingrAF = null;
});
}
}
}
translateFragment(frag) {
return this.translateElements(this.getTranslatables(frag));
}
async translateElements(elements) {
if (!elements.length) {
return undefined;
}
const keys = elements.map(this.getKeysForElement);
const translations = await this.formatMessages(keys);
return this.applyTranslations(elements, translations);
}
applyTranslations(elements, translations) {
this.pauseObserving();
for (let i = 0; i < elements.length; i++) {
if (translations[i] !== undefined) {
translateElement(elements[i], translations[i]);
}
}
this.resumeObserving();
}
getTranslatables(element) {
const nodes = Array.from(element.querySelectorAll(L10N_ELEMENT_QUERY));
if (typeof element.hasAttribute === "function" && element.hasAttribute(L10NID_ATTR_NAME)) {
nodes.push(element);
}
return nodes;
}
getKeysForElement(element) {
return {
id: element.getAttribute(L10NID_ATTR_NAME),
args: JSON.parse(element.getAttribute(L10NARGS_ATTR_NAME) || null)
};
}
}
;// ./node_modules/@fluent/dom/esm/index.js
;// ./web/l10n.js
class L10n {
#dir;
#elements;
#lang;
#l10n;
constructor({
lang,
isRTL
}, l10n = null) {
this.#lang = L10n.#fixupLangCode(lang);
this.#l10n = l10n;
this.#dir = isRTL ?? L10n.#isRTL(this.#lang) ? "rtl" : "ltr";
}
_setL10n(l10n) {
this.#l10n = l10n;
}
getLanguage() {
return this.#lang;
}
getDirection() {
return this.#dir;
}
async get(ids, args = null, fallback) {
if (Array.isArray(ids)) {
ids = ids.map(id => ({
id
}));
const messages = await this.#l10n.formatMessages(ids);
return messages.map(message => message.value);
}
const messages = await this.#l10n.formatMessages([{
id: ids,
args
}]);
return messages[0]?.value || fallback;
}
async translate(element) {
(this.#elements ||= new Set()).add(element);
try {
this.#l10n.connectRoot(element);
await this.#l10n.translateRoots();
} catch {}
}
async translateOnce(element) {
try {
await this.#l10n.translateElements([element]);
} catch (ex) {
console.error("translateOnce:", ex);
}
}
async destroy() {
if (this.#elements) {
for (const element of this.#elements) {
this.#l10n.disconnectRoot(element);
}
this.#elements.clear();
this.#elements = null;
}
this.#l10n.pauseObserving();
}
pause() {
this.#l10n.pauseObserving();
}
resume() {
this.#l10n.resumeObserving();
}
static #fixupLangCode(langCode) {
langCode = langCode?.toLowerCase() || "en-us";
const PARTIAL_LANG_CODES = {
en: "en-us",
es: "es-es",
fy: "fy-nl",
ga: "ga-ie",
gu: "gu-in",
hi: "hi-in",
hy: "hy-am",
nb: "nb-no",
ne: "ne-np",
nn: "nn-no",
pa: "pa-in",
pt: "pt-pt",
sv: "sv-se",
zh: "zh-cn"
};
return PARTIAL_LANG_CODES[langCode] || langCode;
}
static #isRTL(lang) {
const shortCode = lang.split("-", 1)[0];
return ["ar", "he", "fa", "ps", "ur"].includes(shortCode);
}
}
const GenericL10n = null;
;// ./web/genericl10n.js
function PLATFORM() {
const {
isAndroid,
isLinux,
isMac,
isWindows
} = FeatureTest.platform;
if (isLinux) {
return "linux";
}
if (isWindows) {
return "windows";
}
if (isMac) {
return "macos";
}
if (isAndroid) {
return "android";
}
return "other";
}
function createBundle(lang, text) {
const resource = new FluentResource(text);
const bundle = new FluentBundle(lang, {
functions: {
PLATFORM
}
});
const errors = bundle.addResource(resource);
if (errors.length) {
console.error("L10n errors", errors);
}
return bundle;
}
class genericl10n_GenericL10n extends L10n {
constructor(lang) {
super({
lang
});
const generateBundles = !lang ? genericl10n_GenericL10n.#generateBundlesFallback.bind(genericl10n_GenericL10n, this.getLanguage()) : genericl10n_GenericL10n.#generateBundles.bind(genericl10n_GenericL10n, "en-us", this.getLanguage());
this._setL10n(new DOMLocalization([], generateBundles));
}
static async *#generateBundles(defaultLang, baseLang) {
const {
baseURL,
paths
} = await this.#getPaths();
const langs = [baseLang];
if (defaultLang !== baseLang) {
const shortLang = baseLang.split("-", 1)[0];
if (shortLang !== baseLang) {
langs.push(shortLang);
}
langs.push(defaultLang);
}
const bundles = langs.map(lang => [lang, this.#createBundle(lang, baseURL, paths)]);
for (const [lang, bundlePromise] of bundles) {
const bundle = await bundlePromise;
if (bundle) {
yield bundle;
} else if (lang === "en-us") {
yield this.#createBundleFallback(lang);
}
}
}
static async #createBundle(lang, baseURL, paths) {
const path = paths[lang];
if (!path) {
return null;
}
const url = new URL(path, baseURL);
const text = await fetchData(url, "text");
return createBundle(lang, text);
}
static async #getPaths() {
try {
const {
href
} = document.querySelector(`link[type="application/l10n"]`);
const paths = await fetchData(href, "json");
return {
baseURL: href.substring(0, href.lastIndexOf("/") + 1) || "./",
paths
};
} catch {}
return {
baseURL: "./",
paths: Object.create(null)
};
}
static async *#generateBundlesFallback(lang) {
yield this.#createBundleFallback(lang);
}
static async #createBundleFallback(lang) {
const text = "pdfjs-previous-button =\n .title = Previous Page\npdfjs-previous-button-label = Previous\npdfjs-next-button =\n .title = Next Page\npdfjs-next-button-label = Next\npdfjs-page-input =\n .title = Page\npdfjs-of-pages = of { $pagesCount }\npdfjs-page-of-pages = ({ $pageNumber } of { $pagesCount })\npdfjs-zoom-out-button =\n .title = Zoom Out\npdfjs-zoom-out-button-label = Zoom Out\npdfjs-zoom-in-button =\n .title = Zoom In\npdfjs-zoom-in-button-label = Zoom In\npdfjs-zoom-select =\n .title = Zoom\npdfjs-presentation-mode-button =\n .title = Switch to Presentation Mode\npdfjs-presentation-mode-button-label = Presentation Mode\npdfjs-open-file-button =\n .title = Open File\npdfjs-open-file-button-label = Open\npdfjs-print-button =\n .title = Print\npdfjs-print-button-label = Print\npdfjs-save-button =\n .title = Save\npdfjs-save-button-label = Save\npdfjs-download-button =\n .title = Download\npdfjs-download-button-label = Download\npdfjs-bookmark-button =\n .title = Current Page (View URL from Current Page)\npdfjs-bookmark-button-label = Current Page\npdfjs-tools-button =\n .title = Tools\npdfjs-tools-button-label = Tools\npdfjs-first-page-button =\n .title = Go to First Page\npdfjs-first-page-button-label = Go to First Page\npdfjs-last-page-button =\n .title = Go to Last Page\npdfjs-last-page-button-label = Go to Last Page\npdfjs-page-rotate-cw-button =\n .title = Rotate Clockwise\npdfjs-page-rotate-cw-button-label = Rotate Clockwise\npdfjs-page-rotate-ccw-button =\n .title = Rotate Counterclockwise\npdfjs-page-rotate-ccw-button-label = Rotate Counterclockwise\npdfjs-cursor-text-select-tool-button =\n .title = Enable Text Selection Tool\npdfjs-cursor-text-select-tool-button-label = Text Selection Tool\npdfjs-cursor-hand-tool-button =\n .title = Enable Hand Tool\npdfjs-cursor-hand-tool-button-label = Hand Tool\npdfjs-scroll-page-button =\n .title = Use Page Scrolling\npdfjs-scroll-page-button-label = Page Scrolling\npdfjs-scroll-vertical-button =\n .title = Use Vertical Scrolling\npdfjs-scroll-vertical-button-label = Vertical Scrolling\npdfjs-scroll-horizontal-button =\n .title = Use Horizontal Scrolling\npdfjs-scroll-horizontal-button-label = Horizontal Scrolling\npdfjs-scroll-wrapped-button =\n .title = Use Wrapped Scrolling\npdfjs-scroll-wrapped-button-label = Wrapped Scrolling\npdfjs-spread-none-button =\n .title = Do not join page spreads\npdfjs-spread-none-button-label = No Spreads\npdfjs-spread-odd-button =\n .title = Join page spreads starting with odd-numbered pages\npdfjs-spread-odd-button-label = Odd Spreads\npdfjs-spread-even-button =\n .title = Join page spreads starting with even-numbered pages\npdfjs-spread-even-button-label = Even Spreads\npdfjs-document-properties-button =\n .title = Document Properties\u2026\npdfjs-document-properties-button-label = Document Properties\u2026\npdfjs-document-properties-file-name = File name:\npdfjs-document-properties-file-size = File size:\npdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes)\npdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes)\npdfjs-document-properties-title = Title:\npdfjs-document-properties-author = Author:\npdfjs-document-properties-subject = Subject:\npdfjs-document-properties-keywords = Keywords:\npdfjs-document-properties-creation-date = Creation Date:\npdfjs-document-properties-modification-date = Modification Date:\npdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: \"short\", timeStyle: \"medium\") }\npdfjs-document-properties-creator = Creator:\npdfjs-document-properties-producer = PDF Producer:\npdfjs-document-properties-version = PDF Version:\npdfjs-document-properties-page-count = Page Count:\npdfjs-document-properties-page-size = Page Size:\npdfjs-document-properties-page-size-unit-inches = in\npdfjs-document-properties-page-size-unit-millimeters = mm\npdfjs-document-properties-page-size-orientation-portrait = portrait\npdfjs-document-properties-page-size-orientation-landscape = landscape\npdfjs-document-properties-page-size-name-a-three = A3\npdfjs-document-properties-page-size-name-a-four = A4\npdfjs-document-properties-page-size-name-letter = Letter\npdfjs-document-properties-page-size-name-legal = Legal\npdfjs-document-properties-page-size-dimension-string = { $width } \xD7 { $height } { $unit } ({ $orientation })\npdfjs-document-properties-page-size-dimension-name-string = { $width } \xD7 { $height } { $unit } ({ $name }, { $orientation })\npdfjs-document-properties-linearized = Fast Web View:\npdfjs-document-properties-linearized-yes = Yes\npdfjs-document-properties-linearized-no = No\npdfjs-document-properties-close-button = Close\npdfjs-print-progress-message = Preparing document for printing\u2026\npdfjs-print-progress-percent = { $progress }%\npdfjs-print-progress-close-button = Cancel\npdfjs-printing-not-supported = Warning: Printing is not fully supported by this browser.\npdfjs-printing-not-ready = Warning: The PDF is not fully loaded for printing.\npdfjs-toggle-sidebar-button =\n .title = Toggle Sidebar\npdfjs-toggle-sidebar-notification-button =\n .title = Toggle Sidebar (document contains outline/attachments/layers)\npdfjs-toggle-sidebar-button-label = Toggle Sidebar\npdfjs-document-outline-button =\n .title = Show Document Outline (double-click to expand/collapse all items)\npdfjs-document-outline-button-label = Document Outline\npdfjs-attachments-button =\n .title = Show Attachments\npdfjs-attachments-button-label = Attachments\npdfjs-layers-button =\n .title = Show Layers (double-click to reset all layers to the default state)\npdfjs-layers-button-label = Layers\npdfjs-thumbs-button =\n .title = Show Thumbnails\npdfjs-thumbs-button-label = Thumbnails\npdfjs-current-outline-item-button =\n .title = Find Current Outline Item\npdfjs-current-outline-item-button-label = Current Outline Item\npdfjs-findbar-button =\n .title = Find in Document\npdfjs-findbar-button-label = Find\npdfjs-additional-layers = Additional Layers\npdfjs-thumb-page-title =\n .title = Page { $page }\npdfjs-thumb-page-canvas =\n .aria-label = Thumbnail of Page { $page }\npdfjs-find-input =\n .title = Find\n .placeholder = Find in document\u2026\npdfjs-find-previous-button =\n .title = Find the previous occurrence of the phrase\npdfjs-find-previous-button-label = Previous\npdfjs-find-next-button =\n .title = Find the next occurrence of the phrase\npdfjs-find-next-button-label = Next\npdfjs-find-highlight-checkbox = Highlight All\npdfjs-find-match-case-checkbox-label = Match Case\npdfjs-find-match-diacritics-checkbox-label = Match Diacritics\npdfjs-find-entire-word-checkbox-label = Whole Words\npdfjs-find-reached-top = Reached top of document, continued from bottom\npdfjs-find-reached-bottom = Reached end of document, continued from top\npdfjs-find-match-count =\n { $total ->\n [one] { $current } of { $total } match\n *[other] { $current } of { $total } matches\n }\npdfjs-find-match-count-limit =\n { $limit ->\n [one] More than { $limit } match\n *[other] More than { $limit } matches\n }\npdfjs-find-not-found = Phrase not found\npdfjs-page-scale-width = Page Width\npdfjs-page-scale-fit = Page Fit\npdfjs-page-scale-auto = Automatic Zoom\npdfjs-page-scale-actual = Actual Size\npdfjs-page-scale-percent = { $scale }%\npdfjs-page-landmark =\n .aria-label = Page { $page }\npdfjs-loading-error = An error occurred while loading the PDF.\npdfjs-invalid-file-error = Invalid or corrupted PDF file.\npdfjs-missing-file-error = Missing PDF file.\npdfjs-unexpected-response-error = Unexpected server response.\npdfjs-rendering-error = An error occurred while rendering the page.\npdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: \"short\", timeStyle: \"medium\") }\npdfjs-text-annotation-type =\n .alt = [{ $type } Annotation]\npdfjs-password-label = Enter the password to open this PDF file.\npdfjs-password-invalid = Invalid password. Please try again.\npdfjs-password-ok-button = OK\npdfjs-password-cancel-button = Cancel\npdfjs-web-fonts-disabled = Web fonts are disabled: unable to use embedded PDF fonts.\npdfjs-editor-free-text-button =\n .title = Text\npdfjs-editor-free-text-button-label = Text\npdfjs-editor-ink-button =\n .title = Draw\npdfjs-editor-ink-button-label = Draw\npdfjs-editor-stamp-button =\n .title = Add or edit images\npdfjs-editor-stamp-button-label = Add or edit images\npdfjs-editor-highlight-button =\n .title = Highlight\npdfjs-editor-highlight-button-label = Highlight\npdfjs-highlight-floating-button1 =\n .title = Highlight\n .aria-label = Highlight\npdfjs-highlight-floating-button-label = Highlight\npdfjs-editor-signature-button =\n .title = Add signature\npdfjs-editor-signature-button-label = Add signature\npdfjs-editor-highlight-editor =\n .aria-label = Highlight editor\npdfjs-editor-ink-editor =\n .aria-label = Drawing editor\npdfjs-editor-signature-editor1 =\n .aria-description = Signature editor: { $description }\npdfjs-editor-stamp-editor =\n .aria-label = Image editor\npdfjs-editor-remove-ink-button =\n .title = Remove drawing\npdfjs-editor-remove-freetext-button =\n .title = Remove text\npdfjs-editor-remove-stamp-button =\n .title = Remove image\npdfjs-editor-remove-highlight-button =\n .title = Remove highlight\npdfjs-editor-remove-signature-button =\n .title = Remove signature\npdfjs-editor-free-text-color-input = Color\npdfjs-editor-free-text-size-input = Size\npdfjs-editor-ink-color-input = Color\npdfjs-editor-ink-thickness-input = Thickness\npdfjs-editor-ink-opacity-input = Opacity\npdfjs-editor-stamp-add-image-button =\n .title = Add image\npdfjs-editor-stamp-add-image-button-label = Add image\npdfjs-editor-free-highlight-thickness-input = Thickness\npdfjs-editor-free-highlight-thickness-title =\n .title = Change thickness when highlighting items other than text\npdfjs-editor-add-signature-container =\n .aria-label = Signature controls and saved signatures\npdfjs-editor-signature-add-signature-button =\n .title = Add new signature\npdfjs-editor-signature-add-signature-button-label = Add new signature\npdfjs-editor-add-saved-signature-button =\n .title = Saved signature: { $description }\npdfjs-free-text2 =\n .aria-label = Text Editor\n .default-content = Start typing\u2026\npdfjs-editor-alt-text-button =\n .aria-label = Alt text\npdfjs-editor-alt-text-button-label = Alt text\npdfjs-editor-alt-text-edit-button =\n .aria-label = Edit alt text\npdfjs-editor-alt-text-dialog-label = Choose an option\npdfjs-editor-alt-text-dialog-description = Alt text (alternative text) helps when people can\u2019t see the image or when it doesn\u2019t load.\npdfjs-editor-alt-text-add-description-label = Add a description\npdfjs-editor-alt-text-add-description-description = Aim for 1-2 sentences that describe the subject, setting, or actions.\npdfjs-editor-alt-text-mark-decorative-label = Mark as decorative\npdfjs-editor-alt-text-mark-decorative-description = This is used for ornamental images, like borders or watermarks.\npdfjs-editor-alt-text-cancel-button = Cancel\npdfjs-editor-alt-text-save-button = Save\npdfjs-editor-alt-text-decorative-tooltip = Marked as decorative\npdfjs-editor-alt-text-textarea =\n .placeholder = For example, \u201CA young man sits down at a table to eat a meal\u201D\npdfjs-editor-resizer-top-left =\n .aria-label = Top left corner \u2014 resize\npdfjs-editor-resizer-top-middle =\n .aria-label = Top middle \u2014 resize\npdfjs-editor-resizer-top-right =\n .aria-label = Top right corner \u2014 resize\npdfjs-editor-resizer-middle-right =\n .aria-label = Middle right \u2014 resize\npdfjs-editor-resizer-bottom-right =\n .aria-label = Bottom right corner \u2014 resize\npdfjs-editor-resizer-bottom-middle =\n .aria-label = Bottom middle \u2014 resize\npdfjs-editor-resizer-bottom-left =\n .aria-label = Bottom left corner \u2014 resize\npdfjs-editor-resizer-middle-left =\n .aria-label = Middle left \u2014 resize\npdfjs-editor-highlight-colorpicker-label = Highlight color\npdfjs-editor-colorpicker-button =\n .title = Change color\npdfjs-editor-colorpicker-dropdown =\n .aria-label = Color choices\npdfjs-editor-colorpicker-yellow =\n .title = Yellow\npdfjs-editor-colorpicker-green =\n .title = Green\npdfjs-editor-colorpicker-blue =\n .title = Blue\npdfjs-editor-colorpicker-pink =\n .title = Pink\npdfjs-editor-colorpicker-red =\n .title = Red\npdfjs-editor-highlight-show-all-button-label = Show all\npdfjs-editor-highlight-show-all-button =\n .title = Show all\npdfjs-editor-new-alt-text-dialog-edit-label = Edit alt text (image description)\npdfjs-editor-new-alt-text-dialog-add-label = Add alt text (image description)\npdfjs-editor-new-alt-text-textarea =\n .placeholder = Write your description here\u2026\npdfjs-editor-new-alt-text-description = Short description for people who can\u2019t see the image or when the image doesn\u2019t load.\npdfjs-editor-new-alt-text-disclaimer1 = This alt text was created automatically and may be inaccurate.\npdfjs-editor-new-alt-text-disclaimer-learn-more-url = Learn more\npdfjs-editor-new-alt-text-create-automatically-button-label = Create alt text automatically\npdfjs-editor-new-alt-text-not-now-button = Not now\npdfjs-editor-new-alt-text-error-title = Couldn\u2019t create alt text automatically\npdfjs-editor-new-alt-text-error-description = Please write your own alt text or try again later.\npdfjs-editor-new-alt-text-error-close-button = Close\npdfjs-editor-new-alt-text-ai-model-downloading-progress = Downloading alt text AI model ({ $downloadedSize } of { $totalSize } MB)\n .aria-valuetext = Downloading alt text AI model ({ $downloadedSize } of { $totalSize } MB)\npdfjs-editor-new-alt-text-added-button =\n .aria-label = Alt text added\npdfjs-editor-new-alt-text-added-button-label = Alt text added\npdfjs-editor-new-alt-text-missing-button =\n .aria-label = Missing alt text\npdfjs-editor-new-alt-text-missing-button-label = Missing alt text\npdfjs-editor-new-alt-text-to-review-button =\n .aria-label = Review alt text\npdfjs-editor-new-alt-text-to-review-button-label = Review alt text\npdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Created automatically: { $generatedAltText }\npdfjs-image-alt-text-settings-button =\n .title = Image alt text settings\npdfjs-image-alt-text-settings-button-label = Image alt text settings\npdfjs-editor-alt-text-settings-dialog-label = Image alt text settings\npdfjs-editor-alt-text-settings-automatic-title = Automatic alt text\npdfjs-editor-alt-text-settings-create-model-button-label = Create alt text automatically\npdfjs-editor-alt-text-settings-create-model-description = Suggests descriptions to help people who can\u2019t see the image or when the image doesn\u2019t load.\npdfjs-editor-alt-text-settings-download-model-label = Alt text AI model ({ $totalSize } MB)\npdfjs-editor-alt-text-settings-ai-model-description = Runs locally on your device so your data stays private. Required for automatic alt text.\npdfjs-editor-alt-text-settings-delete-model-button = Delete\npdfjs-editor-alt-text-settings-download-model-button = Download\npdfjs-editor-alt-text-settings-downloading-model-button = Downloading\u2026\npdfjs-editor-alt-text-settings-editor-title = Alt text editor\npdfjs-editor-alt-text-settings-show-dialog-button-label = Show alt text editor right away when adding an image\npdfjs-editor-alt-text-settings-show-dialog-description = Helps you make sure all your images have alt text.\npdfjs-editor-alt-text-settings-close-button = Close\npdfjs-editor-undo-bar-message-highlight = Highlight removed\npdfjs-editor-undo-bar-message-freetext = Text removed\npdfjs-editor-undo-bar-message-ink = Drawing removed\npdfjs-editor-undo-bar-message-stamp = Image removed\npdfjs-editor-undo-bar-message-signature = Signature removed\npdfjs-editor-undo-bar-message-multiple =\n { $count ->\n [one] { $count } annotation removed\n *[other] { $count } annotations removed\n }\npdfjs-editor-undo-bar-undo-button =\n .title = Undo\npdfjs-editor-undo-bar-undo-button-label = Undo\npdfjs-editor-undo-bar-close-button =\n .title = Close\npdfjs-editor-undo-bar-close-button-label = Close\npdfjs-editor-add-signature-dialog-label = This modal allows the user to create a signature to add to a PDF document. The user can edit the name (which also serves as the alt text), and optionally save the signature for repeated use.\npdfjs-editor-add-signature-dialog-title = Add a signature\npdfjs-editor-add-signature-type-button = Type\n .title = Type\npdfjs-editor-add-signature-draw-button = Draw\n .title = Draw\npdfjs-editor-add-signature-image-button = Image\n .title = Image\npdfjs-editor-add-signature-type-input =\n .aria-label = Type your signature\n .placeholder = Type your signature\npdfjs-editor-add-signature-draw-placeholder = Draw your signature\npdfjs-editor-add-signature-draw-thickness-range-label = Thickness\npdfjs-editor-add-signature-draw-thickness-range =\n .title = Drawing thickness: { $thickness }\npdfjs-editor-add-signature-image-placeholder = Drag a file here to upload\npdfjs-editor-add-signature-image-browse-link =\n { PLATFORM() ->\n [macos] Or choose image files\n *[other] Or browse image files\n }\npdfjs-editor-add-signature-description-label = Description (alt text)\npdfjs-editor-add-signature-description-input =\n .title = Description (alt text)\npdfjs-editor-add-signature-description-default-when-drawing = Signature\npdfjs-editor-add-signature-clear-button-label = Clear signature\npdfjs-editor-add-signature-clear-button =\n .title = Clear signature\npdfjs-editor-add-signature-save-checkbox = Save signature\npdfjs-editor-add-signature-save-warning-message = You\u2019ve reached the limit of 5 saved signatures. Remove one to save more.\npdfjs-editor-add-signature-image-upload-error-title = Couldn\u2019t upload image\npdfjs-editor-add-signature-image-upload-error-description = Check your network connection or try another image.\npdfjs-editor-add-signature-error-close-button = Close\npdfjs-editor-add-signature-cancel-button = Cancel\npdfjs-editor-add-signature-add-button = Add\npdfjs-editor-delete-signature-button1 =\n .title = Remove saved signature\npdfjs-editor-delete-signature-button-label1 = Remove saved signature\npdfjs-editor-add-signature-edit-button-label = Edit description\npdfjs-editor-edit-signature-dialog-title = Edit description\npdfjs-editor-edit-signature-update-button = Update";
return createBundle(lang, text);
}
}
;// ./web/generic_scripting.js
async function docProperties(pdfDocument) {
const url = "",
baseUrl = url.split("#", 1)[0];
const {
info,
metadata,
contentDispositionFilename,
contentLength
} = await pdfDocument.getMetadata();
return {
...info,
baseURL: baseUrl,
filesize: contentLength || (await pdfDocument.getDownloadInfo()).length,
filename: contentDispositionFilename || getPdfFilenameFromUrl(url),
metadata: metadata?.getRaw(),
authors: metadata?.get("dc:creator"),
numPages: pdfDocument.numPages,
URL: url
};
}
class GenericScripting {
constructor(sandboxBundleSrc) {
this._ready = new Promise((resolve, reject) => {
const sandbox = import(
/*webpackIgnore: true*/
/*@vite-ignore*/
sandboxBundleSrc);
sandbox.then(pdfjsSandbox => {
resolve(pdfjsSandbox.QuickJSSandbox());
}).catch(reject);
});
}
async createSandbox(data) {
const sandbox = await this._ready;
sandbox.create(data);
}
async dispatchEventInSandbox(event) {
const sandbox = await this._ready;
setTimeout(() => sandbox.dispatchEvent(event), 0);
}
async destroySandbox() {
const sandbox = await this._ready;
sandbox.nukeSandbox();
}
}
;// ./web/generic_signature_storage.js
const KEY_STORAGE = "pdfjs.signature";
class SignatureStorage {
#eventBus;
#signatures = null;
#signal = null;
constructor(eventBus, signal) {
this.#eventBus = eventBus;
this.#signal = signal;
}
#save() {
localStorage.setItem(KEY_STORAGE, JSON.stringify(Object.fromEntries(this.#signatures.entries())));
}
async getAll() {
if (this.#signal) {
window.addEventListener("storage", ({
key
}) => {
if (key === KEY_STORAGE) {
this.#signatures = null;
this.#eventBus?.dispatch("storedsignatureschanged", {
source: this
});
}
}, {
signal: this.#signal
});
this.#signal = null;
}
if (!this.#signatures) {
this.#signatures = new Map();
const data = localStorage.getItem(KEY_STORAGE);
if (data) {
for (const [key, value] of Object.entries(JSON.parse(data))) {
this.#signatures.set(key, value);
}
}
}
return this.#signatures;
}
async isFull() {
return (await this.size()) === 5;
}
async size() {
return (await this.getAll()).size;
}
async create(data) {
if (await this.isFull()) {
return null;
}
const uuid = getUuid();
this.#signatures.set(uuid, data);
this.#save();
return uuid;
}
async delete(uuid) {
const signatures = await this.getAll();
if (!signatures.has(uuid)) {
return false;
}
signatures.delete(uuid);
this.#save();
return true;
}
}
;// ./web/genericcom.js
function initCom(app) {}
class Preferences extends BasePreferences {
async _writeToStorage(prefObj) {
localStorage.setItem("pdfjs.preferences", JSON.stringify(prefObj));
}
async _readFromStorage(prefObj) {
return {
prefs: JSON.parse(localStorage.getItem("pdfjs.preferences"))
};
}
}
class ExternalServices extends BaseExternalServices {
async createL10n() {
return new genericl10n_GenericL10n(AppOptions.get("localeProperties")?.lang);
}
createScripting() {
return new GenericScripting(AppOptions.get("sandboxBundleSrc"));
}
createSignatureStorage(eventBus, signal) {
return new SignatureStorage(eventBus, signal);
}
}
class MLManager {
async isEnabledFor(_name) {
return false;
}
async deleteModel(_service) {
return null;
}
isReady(_name) {
return false;
}
guess(_data) {}
toggleService(_name, _enabled) {}
}
;// ./web/new_alt_text_manager.js
class NewAltTextManager {
#boundCancel = this.#cancel.bind(this);
#createAutomaticallyButton;
#currentEditor = null;
#cancelButton;
#descriptionContainer;
#dialog;
#disclaimer;
#downloadModel;
#downloadModelDescription;
#eventBus;
#firstTime = false;
#guessedAltText;
#hasAI = null;
#isEditing = null;
#imagePreview;
#imageData;
#isAILoading = false;
#wasAILoading = false;
#learnMore;
#notNowButton;
#overlayManager;
#textarea;
#title;
#uiManager;
#previousAltText = null;
constructor({
descriptionContainer,
dialog,
imagePreview,
cancelButton,
disclaimer,
notNowButton,
saveButton,
textarea,
learnMore,
errorCloseButton,
createAutomaticallyButton,
downloadModel,
downloadModelDescription,
title
}, overlayManager, eventBus) {
this.#cancelButton = cancelButton;
this.#createAutomaticallyButton = createAutomaticallyButton;
this.#descriptionContainer = descriptionContainer;
this.#dialog = dialog;
this.#disclaimer = disclaimer;
this.#notNowButton = notNowButton;
this.#imagePreview = imagePreview;
this.#textarea = textarea;
this.#learnMore = learnMore;
this.#title = title;
this.#downloadModel = downloadModel;
this.#downloadModelDescription = downloadModelDescription;
this.#overlayManager = overlayManager;
this.#eventBus = eventBus;
dialog.addEventListener("close", this.#close.bind(this));
dialog.addEventListener("contextmenu", event => {
if (event.target !== this.#textarea) {
event.preventDefault();
}
});
cancelButton.addEventListener("click", this.#boundCancel);
notNowButton.addEventListener("click", this.#boundCancel);
saveButton.addEventListener("click", this.#save.bind(this));
errorCloseButton.addEventListener("click", () => {
this.#toggleError(false);
});
createAutomaticallyButton.addEventListener("click", async () => {
const checked = createAutomaticallyButton.getAttribute("aria-pressed") !== "true";
this.#currentEditor._reportTelemetry({
action: "pdfjs.image.alt_text.ai_generation_check",
data: {
status: checked
}
});
if (this.#uiManager) {
this.#uiManager.setPreference("enableGuessAltText", checked);
await this.#uiManager.mlManager.toggleService("altText", checked);
}
this.#toggleGuessAltText(checked, false);
});
textarea.addEventListener("focus", () => {
this.#wasAILoading = this.#isAILoading;
this.#toggleLoading(false);
this.#toggleTitleAndDisclaimer();
});
textarea.addEventListener("blur", () => {
if (!textarea.value) {
this.#toggleLoading(this.#wasAILoading);
}
this.#toggleTitleAndDisclaimer();
});
textarea.addEventListener("input", () => {
this.#toggleTitleAndDisclaimer();
});
eventBus._on("enableguessalttext", ({
value
}) => {
this.#toggleGuessAltText(value, false);
});
this.#overlayManager.register(dialog);
this.#learnMore.addEventListener("click", () => {
this.#currentEditor._reportTelemetry({
action: "pdfjs.image.alt_text.info",
data: {
topic: "alt_text"
}
});
});
}
#toggleLoading(value) {
if (!this.#uiManager || this.#isAILoading === value) {
return;
}
this.#isAILoading = value;
this.#descriptionContainer.classList.toggle("loading", value);
}
#toggleError(value) {
if (!this.#uiManager) {
return;
}
this.#dialog.classList.toggle("error", value);
}
async #toggleGuessAltText(value, isInitial = false) {
if (!this.#uiManager) {
return;
}
this.#dialog.classList.toggle("aiDisabled", !value);
this.#createAutomaticallyButton.setAttribute("aria-pressed", value);
if (value) {
const {
altTextLearnMoreUrl
} = this.#uiManager.mlManager;
if (altTextLearnMoreUrl) {
this.#learnMore.href = altTextLearnMoreUrl;
}
this.#mlGuessAltText(isInitial);
} else {
this.#toggleLoading(false);
this.#isAILoading = false;
this.#toggleTitleAndDisclaimer();
}
}
#toggleNotNow() {
this.#notNowButton.classList.toggle("hidden", !this.#firstTime);
this.#cancelButton.classList.toggle("hidden", this.#firstTime);
}
#toggleAI(value) {
if (!this.#uiManager || this.#hasAI === value) {
return;
}
this.#hasAI = value;
this.#dialog.classList.toggle("noAi", !value);
this.#toggleTitleAndDisclaimer();
}
#toggleTitleAndDisclaimer() {
const visible = this.#isAILoading || this.#guessedAltText && this.#guessedAltText === this.#textarea.value;
this.#disclaimer.hidden = !visible;
const isEditing = this.#isAILoading || !!this.#textarea.value;
if (this.#isEditing === isEditing) {
return;
}
this.#isEditing = isEditing;
this.#title.setAttribute("data-l10n-id", isEditing ? "pdfjs-editor-new-alt-text-dialog-edit-label" : "pdfjs-editor-new-alt-text-dialog-add-label");
}
async #mlGuessAltText(isInitial) {
if (this.#isAILoading) {
return;
}
if (this.#textarea.value) {
return;
}
if (isInitial && this.#previousAltText !== null) {
return;
}
this.#guessedAltText = this.#currentEditor.guessedAltText;
if (this.#previousAltText === null && this.#guessedAltText) {
this.#addAltText(this.#guessedAltText);
return;
}
this.#toggleLoading(true);
this.#toggleTitleAndDisclaimer();
let hasError = false;
try {
const altText = await this.#currentEditor.mlGuessAltText(this.#imageData, false);
if (altText) {
this.#guessedAltText = altText;
this.#wasAILoading = this.#isAILoading;
if (this.#isAILoading) {
this.#addAltText(altText);
}
}
} catch (e) {
console.error(e);
hasError = true;
}
this.#toggleLoading(false);
this.#toggleTitleAndDisclaimer();
if (hasError && this.#uiManager) {
this.#toggleError(true);
}
}
#addAltText(altText) {
if (!this.#uiManager || this.#textarea.value) {
return;
}
this.#textarea.value = altText;
this.#toggleTitleAndDisclaimer();
}
#setProgress() {
this.#downloadModel.classList.toggle("hidden", false);
const callback = async ({
detail: {
finished,
total,
totalLoaded
}
}) => {
const ONE_MEGA_BYTES = 1e6;
totalLoaded = Math.min(0.99 * total, totalLoaded);
const totalSize = this.#downloadModelDescription.ariaValueMax = Math.round(total / ONE_MEGA_BYTES);
const downloadedSize = this.#downloadModelDescription.ariaValueNow = Math.round(totalLoaded / ONE_MEGA_BYTES);
this.#downloadModelDescription.setAttribute("data-l10n-args", JSON.stringify({
totalSize,
downloadedSize
}));
if (!finished) {
return;
}
this.#eventBus._off("loadaiengineprogress", callback);
this.#downloadModel.classList.toggle("hidden", true);
this.#toggleAI(true);
if (!this.#uiManager) {
return;
}
const {
mlManager
} = this.#uiManager;
mlManager.toggleService("altText", true);
this.#toggleGuessAltText(await mlManager.isEnabledFor("altText"), true);
};
this.#eventBus._on("loadaiengineprogress", callback);
}
async editAltText(uiManager, editor, firstTime) {
if (this.#currentEditor || !editor) {
return;
}
if (firstTime && editor.hasAltTextData()) {
editor.altTextFinish();
return;
}
this.#firstTime = firstTime;
let {
mlManager
} = uiManager;
let hasAI = !!mlManager;
this.#toggleTitleAndDisclaimer();
if (mlManager && !mlManager.isReady("altText")) {
hasAI = false;
if (mlManager.hasProgress) {
this.#setProgress();
} else {
mlManager = null;
}
} else {
this.#downloadModel.classList.toggle("hidden", true);
}
const isAltTextEnabledPromise = mlManager?.isEnabledFor("altText");
this.#currentEditor = editor;
this.#uiManager = uiManager;
this.#uiManager.removeEditListeners();
({
altText: this.#previousAltText
} = editor.altTextData);
this.#textarea.value = this.#previousAltText ?? "";
const AI_MAX_IMAGE_DIMENSION = 224;
const MAX_PREVIEW_DIMENSION = 180;
let canvas, width, height;
if (mlManager) {
({
canvas,
width,
height,
imageData: this.#imageData
} = editor.copyCanvas(AI_MAX_IMAGE_DIMENSION, MAX_PREVIEW_DIMENSION, true));
if (hasAI) {
this.#toggleGuessAltText(await isAltTextEnabledPromise, true);
}
} else {
({
canvas,
width,
height
} = editor.copyCanvas(AI_MAX_IMAGE_DIMENSION, MAX_PREVIEW_DIMENSION, false));
}
canvas.setAttribute("role", "presentation");
const {
style
} = canvas;
style.width = `${width}px`;
style.height = `${height}px`;
this.#imagePreview.append(canvas);
this.#toggleNotNow();
this.#toggleAI(hasAI);
this.#toggleError(false);
try {
await this.#overlayManager.open(this.#dialog);
} catch (ex) {
this.#close();
throw ex;
}
}
#cancel() {
this.#currentEditor.altTextData = {
cancel: true
};
const altText = this.#textarea.value.trim();
this.#currentEditor._reportTelemetry({
action: "pdfjs.image.alt_text.dismiss",
data: {
alt_text_type: altText ? "present" : "empty",
flow: this.#firstTime ? "image_add" : "alt_text_edit"
}
});
this.#currentEditor._reportTelemetry({
action: "pdfjs.image.image_added",
data: {
alt_text_modal: true,
alt_text_type: "skipped"
}
});
this.#finish();
}
#finish() {
this.#overlayManager.closeIfActive(this.#dialog);
}
#close() {
const canvas = this.#imagePreview.firstChild;
canvas.remove();
canvas.width = canvas.height = 0;
this.#imageData = null;
this.#toggleLoading(false);
this.#uiManager?.addEditListeners();
this.#currentEditor.altTextFinish();
this.#uiManager?.setSelected(this.#currentEditor);
this.#currentEditor = null;
this.#uiManager = null;
}
#extractWords(text) {
return new Set(text.toLowerCase().split(/[^\p{L}\p{N}]+/gu).filter(x => !!x));
}
#save() {
const altText = this.#textarea.value.trim();
this.#currentEditor.altTextData = {
altText,
decorative: false
};
this.#currentEditor.altTextData.guessedAltText = this.#guessedAltText;
if (this.#guessedAltText && this.#guessedAltText !== altText) {
const guessedWords = this.#extractWords(this.#guessedAltText);
const words = this.#extractWords(altText);
this.#currentEditor._reportTelemetry({
action: "pdfjs.image.alt_text.user_edit",
data: {
total_words: guessedWords.size,
words_removed: guessedWords.difference(words).size,
words_added: words.difference(guessedWords).size
}
});
}
this.#currentEditor._reportTelemetry({
action: "pdfjs.image.image_added",
data: {
alt_text_modal: true,
alt_text_type: altText ? "present" : "empty"
}
});
this.#currentEditor._reportTelemetry({
action: "pdfjs.image.alt_text.save",
data: {
alt_text_type: altText ? "present" : "empty",
flow: this.#firstTime ? "image_add" : "alt_text_edit"
}
});
this.#finish();
}
destroy() {
this.#uiManager = null;
this.#finish();
}
}
class ImageAltTextSettings {
#aiModelSettings;
#createModelButton;
#downloadModelButton;
#dialog;
#eventBus;
#mlManager;
#overlayManager;
#showAltTextDialogButton;
constructor({
dialog,
createModelButton,
aiModelSettings,
learnMore,
closeButton,
deleteModelButton,
downloadModelButton,
showAltTextDialogButton
}, overlayManager, eventBus, mlManager) {
this.#dialog = dialog;
this.#aiModelSettings = aiModelSettings;
this.#createModelButton = createModelButton;
this.#downloadModelButton = downloadModelButton;
this.#showAltTextDialogButton = showAltTextDialogButton;
this.#overlayManager = overlayManager;
this.#eventBus = eventBus;
this.#mlManager = mlManager;
const {
altTextLearnMoreUrl
} = mlManager;
if (altTextLearnMoreUrl) {
learnMore.href = altTextLearnMoreUrl;
}
dialog.addEventListener("contextmenu", noContextMenu);
createModelButton.addEventListener("click", async e => {
const checked = this.#togglePref("enableGuessAltText", e);
await mlManager.toggleService("altText", checked);
this.#reportTelemetry({
type: "stamp",
action: "pdfjs.image.alt_text.settings_ai_generation_check",
data: {
status: checked
}
});
});
showAltTextDialogButton.addEventListener("click", e => {
const checked = this.#togglePref("enableNewAltTextWhenAddingImage", e);
this.#reportTelemetry({
type: "stamp",
action: "pdfjs.image.alt_text.settings_edit_alt_text_check",
data: {
status: checked
}
});
});
deleteModelButton.addEventListener("click", this.#delete.bind(this, true));
downloadModelButton.addEventListener("click", this.#download.bind(this, true));
closeButton.addEventListener("click", this.#finish.bind(this));
learnMore.addEventListener("click", () => {
this.#reportTelemetry({
type: "stamp",
action: "pdfjs.image.alt_text.info",
data: {
topic: "ai_generation"
}
});
});
eventBus._on("enablealttextmodeldownload", ({
value
}) => {
if (value) {
this.#download(false);
} else {
this.#delete(false);
}
});
this.#overlayManager.register(dialog);
}
#reportTelemetry(data) {
this.#eventBus.dispatch("reporttelemetry", {
source: this,
details: {
type: "editing",
data
}
});
}
async #download(isFromUI = false) {
if (isFromUI) {
this.#downloadModelButton.disabled = true;
const span = this.#downloadModelButton.firstChild;
span.setAttribute("data-l10n-id", "pdfjs-editor-alt-text-settings-downloading-model-button");
await this.#mlManager.downloadModel("altText");
span.setAttribute("data-l10n-id", "pdfjs-editor-alt-text-settings-download-model-button");
this.#createModelButton.disabled = false;
this.#setPref("enableGuessAltText", true);
this.#mlManager.toggleService("altText", true);
this.#setPref("enableAltTextModelDownload", true);
this.#downloadModelButton.disabled = false;
}
this.#aiModelSettings.classList.toggle("download", false);
this.#createModelButton.setAttribute("aria-pressed", true);
}
async #delete(isFromUI = false) {
if (isFromUI) {
await this.#mlManager.deleteModel("altText");
this.#setPref("enableGuessAltText", false);
this.#setPref("enableAltTextModelDownload", false);
}
this.#aiModelSettings.classList.toggle("download", true);
this.#createModelButton.disabled = true;
this.#createModelButton.setAttribute("aria-pressed", false);
}
async open({
enableGuessAltText,
enableNewAltTextWhenAddingImage
}) {
const {
enableAltTextModelDownload
} = this.#mlManager;
this.#createModelButton.disabled = !enableAltTextModelDownload;
this.#createModelButton.setAttribute("aria-pressed", enableAltTextModelDownload && enableGuessAltText);
this.#showAltTextDialogButton.setAttribute("aria-pressed", enableNewAltTextWhenAddingImage);
this.#aiModelSettings.classList.toggle("download", !enableAltTextModelDownload);
await this.#overlayManager.open(this.#dialog);
this.#reportTelemetry({
type: "stamp",
action: "pdfjs.image.alt_text.settings_displayed"
});
}
#togglePref(name, {
target
}) {
const checked = target.getAttribute("aria-pressed") !== "true";
this.#setPref(name, checked);
target.setAttribute("aria-pressed", checked);
return checked;
}
#setPref(name, value) {
this.#eventBus.dispatch("setpreference", {
source: this,
name,
value
});
}
#finish() {
this.#overlayManager.closeIfActive(this.#dialog);
}
}
;// ./web/alt_text_manager.js
class AltTextManager {
#clickAC = null;
#currentEditor = null;
#cancelButton;
#dialog;
#eventBus;
#hasUsedPointer = false;
#optionDescription;
#optionDecorative;
#overlayManager;
#saveButton;
#textarea;
#uiManager;
#previousAltText = null;
#resizeAC = null;
#svgElement = null;
#rectElement = null;
#container;
#telemetryData = null;
constructor({
dialog,
optionDescription,
optionDecorative,
textarea,
cancelButton,
saveButton
}, container, overlayManager, eventBus) {
this.#dialog = dialog;
this.#optionDescription = optionDescription;
this.#optionDecorative = optionDecorative;
this.#textarea = textarea;
this.#cancelButton = cancelButton;
this.#saveButton = saveButton;
this.#overlayManager = overlayManager;
this.#eventBus = eventBus;
this.#container = container;
const onUpdateUIState = this.#updateUIState.bind(this);
dialog.addEventListener("close", this.#close.bind(this));
dialog.addEventListener("contextmenu", event => {
if (event.target !== this.#textarea) {
event.preventDefault();
}
});
cancelButton.addEventListener("click", this.#finish.bind(this));
saveButton.addEventListener("click", this.#save.bind(this));
optionDescription.addEventListener("change", onUpdateUIState);
optionDecorative.addEventListener("change", onUpdateUIState);
this.#overlayManager.register(dialog);
}
#createSVGElement() {
if (this.#svgElement) {
return;
}
const svgFactory = new DOMSVGFactory();
const svg = this.#svgElement = svgFactory.createElement("svg");
svg.setAttribute("width", "0");
svg.setAttribute("height", "0");
const defs = svgFactory.createElement("defs");
svg.append(defs);
const mask = svgFactory.createElement("mask");
defs.append(mask);
mask.setAttribute("id", "alttext-manager-mask");
mask.setAttribute("maskContentUnits", "objectBoundingBox");
let rect = svgFactory.createElement("rect");
mask.append(rect);
rect.setAttribute("fill", "white");
rect.setAttribute("width", "1");
rect.setAttribute("height", "1");
rect.setAttribute("x", "0");
rect.setAttribute("y", "0");
rect = this.#rectElement = svgFactory.createElement("rect");
mask.append(rect);
rect.setAttribute("fill", "black");
this.#dialog.append(svg);
}
async editAltText(uiManager, editor) {
if (this.#currentEditor || !editor) {
return;
}
this.#createSVGElement();
this.#hasUsedPointer = false;
this.#clickAC = new AbortController();
const clickOpts = {
signal: this.#clickAC.signal
},
onClick = this.#onClick.bind(this);
for (const element of [this.#optionDescription, this.#optionDecorative, this.#textarea, this.#saveButton, this.#cancelButton]) {
element.addEventListener("click", onClick, clickOpts);
}
const {
altText,
decorative
} = editor.altTextData;
if (decorative === true) {
this.#optionDecorative.checked = true;
this.#optionDescription.checked = false;
} else {
this.#optionDecorative.checked = false;
this.#optionDescription.checked = true;
}
this.#previousAltText = this.#textarea.value = altText?.trim() || "";
this.#updateUIState();
this.#currentEditor = editor;
this.#uiManager = uiManager;
this.#uiManager.removeEditListeners();
this.#resizeAC = new AbortController();
this.#eventBus._on("resize", this.#setPosition.bind(this), {
signal: this.#resizeAC.signal
});
try {
await this.#overlayManager.open(this.#dialog);
this.#setPosition();
} catch (ex) {
this.#close();
throw ex;
}
}
#setPosition() {
if (!this.#currentEditor) {
return;
}
const dialog = this.#dialog;
const {
style
} = dialog;
const {
x: containerX,
y: containerY,
width: containerW,
height: containerH
} = this.#container.getBoundingClientRect();
const {
innerWidth: windowW,
innerHeight: windowH
} = window;
const {
width: dialogW,
height: dialogH
} = dialog.getBoundingClientRect();
const {
x,
y,
width,
height
} = this.#currentEditor.getClientDimensions();
const MARGIN = 10;
const isLTR = this.#uiManager.direction === "ltr";
const xs = Math.max(x, containerX);
const xe = Math.min(x + width, containerX + containerW);
const ys = Math.max(y, containerY);
const ye = Math.min(y + height, containerY + containerH);
this.#rectElement.setAttribute("width", `${(xe - xs) / windowW}`);
this.#rectElement.setAttribute("height", `${(ye - ys) / windowH}`);
this.#rectElement.setAttribute("x", `${xs / windowW}`);
this.#rectElement.setAttribute("y", `${ys / windowH}`);
let left = null;
let top = Math.max(y, 0);
top += Math.min(windowH - (top + dialogH), 0);
if (isLTR) {
if (x + width + MARGIN + dialogW < windowW) {
left = x + width + MARGIN;
} else if (x > dialogW + MARGIN) {
left = x - dialogW - MARGIN;
}
} else if (x > dialogW + MARGIN) {
left = x - dialogW - MARGIN;
} else if (x + width + MARGIN + dialogW < windowW) {
left = x + width + MARGIN;
}
if (left === null) {
top = null;
left = Math.max(x, 0);
left += Math.min(windowW - (left + dialogW), 0);
if (y > dialogH + MARGIN) {
top = y - dialogH - MARGIN;
} else if (y + height + MARGIN + dialogH < windowH) {
top = y + height + MARGIN;
}
}
if (top !== null) {
dialog.classList.add("positioned");
if (isLTR) {
style.left = `${left}px`;
} else {
style.right = `${windowW - left - dialogW}px`;
}
style.top = `${top}px`;
} else {
dialog.classList.remove("positioned");
style.left = "";
style.top = "";
}
}
#finish() {
this.#overlayManager.closeIfActive(this.#dialog);
}
#close() {
this.#currentEditor._reportTelemetry(this.#telemetryData || {
action: "alt_text_cancel",
alt_text_keyboard: !this.#hasUsedPointer
});
this.#telemetryData = null;
this.#removeOnClickListeners();
this.#uiManager?.addEditListeners();
this.#resizeAC?.abort();
this.#resizeAC = null;
this.#currentEditor.altTextFinish();
this.#currentEditor = null;
this.#uiManager = null;
}
#updateUIState() {
this.#textarea.disabled = this.#optionDecorative.checked;
}
#save() {
const altText = this.#textarea.value.trim();
const decorative = this.#optionDecorative.checked;
this.#currentEditor.altTextData = {
altText,
decorative
};
this.#telemetryData = {
action: "alt_text_save",
alt_text_description: !!altText,
alt_text_edit: !!this.#previousAltText && this.#previousAltText !== altText,
alt_text_decorative: decorative,
alt_text_keyboard: !this.#hasUsedPointer
};
this.#finish();
}
#onClick(evt) {
if (evt.detail === 0) {
return;
}
this.#hasUsedPointer = true;
this.#removeOnClickListeners();
}
#removeOnClickListeners() {
this.#clickAC?.abort();
this.#clickAC = null;
}
destroy() {
this.#uiManager = null;
this.#finish();
this.#svgElement?.remove();
this.#svgElement = this.#rectElement = null;
}
}
;// ./web/annotation_editor_params.js
class AnnotationEditorParams {
constructor(options, eventBus) {
this.eventBus = eventBus;
this.#bindListeners(options);
}
#bindListeners({
editorFreeTextFontSize,
editorFreeTextColor,
editorInkColor,
editorInkThickness,
editorInkOpacity,
editorStampAddImage,
editorFreeHighlightThickness,
editorHighlightShowAll,
editorSignatureAddSignature
}) {
const {
eventBus
} = this;
const dispatchEvent = (typeStr, value) => {
eventBus.dispatch("switchannotationeditorparams", {
source: this,
type: AnnotationEditorParamsType[typeStr],
value
});
};
editorFreeTextFontSize.addEventListener("input", function () {
dispatchEvent("FREETEXT_SIZE", this.valueAsNumber);
});
editorFreeTextColor.addEventListener("input", function () {
dispatchEvent("FREETEXT_COLOR", this.value);
});
editorInkColor.addEventListener("input", function () {
dispatchEvent("INK_COLOR", this.value);
});
editorInkThickness.addEventListener("input", function () {
dispatchEvent("INK_THICKNESS", this.valueAsNumber);
});
editorInkOpacity.addEventListener("input", function () {
dispatchEvent("INK_OPACITY", this.valueAsNumber);
});
editorStampAddImage.addEventListener("click", () => {
eventBus.dispatch("reporttelemetry", {
source: this,
details: {
type: "editing",
data: {
action: "pdfjs.image.add_image_click"
}
}
});
dispatchEvent("CREATE");
});
editorFreeHighlightThickness.addEventListener("input", function () {
dispatchEvent("HIGHLIGHT_THICKNESS", this.valueAsNumber);
});
editorHighlightShowAll.addEventListener("click", function () {
const checked = this.getAttribute("aria-pressed") === "true";
this.setAttribute("aria-pressed", !checked);
dispatchEvent("HIGHLIGHT_SHOW_ALL", !checked);
});
editorSignatureAddSignature.addEventListener("click", () => {
dispatchEvent("CREATE");
});
eventBus._on("annotationeditorparamschanged", evt => {
for (const [type, value] of evt.details) {
switch (type) {
case AnnotationEditorParamsType.FREETEXT_SIZE:
editorFreeTextFontSize.value = value;
break;
case AnnotationEditorParamsType.FREETEXT_COLOR:
editorFreeTextColor.value = value;
break;
case AnnotationEditorParamsType.INK_COLOR:
editorInkColor.value = value;
break;
case AnnotationEditorParamsType.INK_THICKNESS:
editorInkThickness.value = value;
break;
case AnnotationEditorParamsType.INK_OPACITY:
editorInkOpacity.value = value;
break;
case AnnotationEditorParamsType.HIGHLIGHT_DEFAULT_COLOR:
eventBus.dispatch("mainhighlightcolorpickerupdatecolor", {
source: this,
value
});
break;
case AnnotationEditorParamsType.HIGHLIGHT_THICKNESS:
editorFreeHighlightThickness.value = value;
break;
case AnnotationEditorParamsType.HIGHLIGHT_FREE:
editorFreeHighlightThickness.disabled = !value;
break;
case AnnotationEditorParamsType.HIGHLIGHT_SHOW_ALL:
editorHighlightShowAll.setAttribute("aria-pressed", value);
break;
}
}
});
}
}
;// ./web/caret_browsing.js
const PRECISION = 1e-1;
class CaretBrowsingMode {
#mainContainer;
#toolBarHeight = 0;
#viewerContainer;
constructor(abortSignal, mainContainer, viewerContainer, toolbarContainer) {
this.#mainContainer = mainContainer;
this.#viewerContainer = viewerContainer;
if (!toolbarContainer) {
return;
}
this.#toolBarHeight = toolbarContainer.getBoundingClientRect().height;
const toolbarObserver = new ResizeObserver(entries => {
for (const entry of entries) {
if (entry.target === toolbarContainer) {
this.#toolBarHeight = Math.floor(entry.borderBoxSize[0].blockSize);
break;
}
}
});
toolbarObserver.observe(toolbarContainer);
abortSignal.addEventListener("abort", () => toolbarObserver.disconnect(), {
once: true
});
}
#isOnSameLine(rect1, rect2) {
const top1 = rect1.y;
const bot1 = rect1.bottom;
const mid1 = rect1.y + rect1.height / 2;
const top2 = rect2.y;
const bot2 = rect2.bottom;
const mid2 = rect2.y + rect2.height / 2;
return top1 <= mid2 && mid2 <= bot1 || top2 <= mid1 && mid1 <= bot2;
}
#isUnderOver(rect, x, y, isUp) {
const midY = rect.y + rect.height / 2;
return (isUp ? y >= midY : y <= midY) && rect.x - PRECISION <= x && x <= rect.right + PRECISION;
}
#isVisible(rect) {
return rect.top >= this.#toolBarHeight && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth);
}
#getCaretPosition(selection, isUp) {
const {
focusNode,
focusOffset
} = selection;
const range = document.createRange();
range.setStart(focusNode, focusOffset);
range.setEnd(focusNode, focusOffset);
const rect = range.getBoundingClientRect();
return [rect.x, isUp ? rect.top : rect.bottom];
}
static #caretPositionFromPoint(x, y) {
if (!document.caretPositionFromPoint) {
const {
startContainer: offsetNode,
startOffset: offset
} = document.caretRangeFromPoint(x, y);
return {
offsetNode,
offset
};
}
return document.caretPositionFromPoint(x, y);
}
#setCaretPositionHelper(selection, caretX, select, element, rect) {
rect ||= element.getBoundingClientRect();
if (caretX <= rect.x + PRECISION) {
if (select) {
selection.extend(element.firstChild, 0);
} else {
selection.setPosition(element.firstChild, 0);
}
return;
}
if (rect.right - PRECISION <= caretX) {
const {
lastChild
} = element;
if (select) {
selection.extend(lastChild, lastChild.length);
} else {
selection.setPosition(lastChild, lastChild.length);
}
return;
}
const midY = rect.y + rect.height / 2;
let caretPosition = CaretBrowsingMode.#caretPositionFromPoint(caretX, midY);
let parentElement = caretPosition.offsetNode?.parentElement;
if (parentElement && parentElement !== element) {
const elementsAtPoint = document.elementsFromPoint(caretX, midY);
const savedVisibilities = [];
for (const el of elementsAtPoint) {
if (el === element) {
break;
}
const {
style
} = el;
savedVisibilities.push([el, style.visibility]);
style.visibility = "hidden";
}
caretPosition = CaretBrowsingMode.#caretPositionFromPoint(caretX, midY);
parentElement = caretPosition.offsetNode?.parentElement;
for (const [el, visibility] of savedVisibilities) {
el.style.visibility = visibility;
}
}
if (parentElement !== element) {
if (select) {
selection.extend(element.firstChild, 0);
} else {
selection.setPosition(element.firstChild, 0);
}
return;
}
if (select) {
selection.extend(caretPosition.offsetNode, caretPosition.offset);
} else {
selection.setPosition(caretPosition.offsetNode, caretPosition.offset);
}
}
#setCaretPosition(select, selection, newLineElement, newLineElementRect, caretX) {
if (this.#isVisible(newLineElementRect)) {
this.#setCaretPositionHelper(selection, caretX, select, newLineElement, newLineElementRect);
return;
}
this.#mainContainer.addEventListener("scrollend", this.#setCaretPositionHelper.bind(this, selection, caretX, select, newLineElement, null), {
once: true
});
newLineElement.scrollIntoView();
}
#getNodeOnNextPage(textLayer, isUp) {
while (true) {
const page = textLayer.closest(".page");
const pageNumber = parseInt(page.getAttribute("data-page-number"));
const nextPage = isUp ? pageNumber - 1 : pageNumber + 1;
textLayer = this.#viewerContainer.querySelector(`.page[data-page-number="${nextPage}"] .textLayer`);
if (!textLayer) {
return null;
}
const walker = document.createTreeWalker(textLayer, NodeFilter.SHOW_TEXT);
const node = isUp ? walker.lastChild() : walker.firstChild();
if (node) {
return node;
}
}
}
moveCaret(isUp, select) {
const selection = document.getSelection();
if (selection.rangeCount === 0) {
return;
}
const {
focusNode
} = selection;
const focusElement = focusNode.nodeType !== Node.ELEMENT_NODE ? focusNode.parentElement : focusNode;
const root = focusElement.closest(".textLayer");
if (!root) {
return;
}
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
walker.currentNode = focusNode;
const focusRect = focusElement.getBoundingClientRect();
let newLineElement = null;
const nodeIterator = (isUp ? walker.previousSibling : walker.nextSibling).bind(walker);
while (nodeIterator()) {
const element = walker.currentNode.parentElement;
if (!this.#isOnSameLine(focusRect, element.getBoundingClientRect())) {
newLineElement = element;
break;
}
}
if (!newLineElement) {
const node = this.#getNodeOnNextPage(root, isUp);
if (!node) {
return;
}
if (select) {
const lastNode = (isUp ? walker.firstChild() : walker.lastChild()) || focusNode;
selection.extend(lastNode, isUp ? 0 : lastNode.length);
const range = document.createRange();
range.setStart(node, isUp ? node.length : 0);
range.setEnd(node, isUp ? node.length : 0);
selection.addRange(range);
return;
}
const [caretX] = this.#getCaretPosition(selection, isUp);
const {
parentElement
} = node;
this.#setCaretPosition(select, selection, parentElement, parentElement.getBoundingClientRect(), caretX);
return;
}
const [caretX, caretY] = this.#getCaretPosition(selection, isUp);
const newLineElementRect = newLineElement.getBoundingClientRect();
if (this.#isUnderOver(newLineElementRect, caretX, caretY, isUp)) {
this.#setCaretPosition(select, selection, newLineElement, newLineElementRect, caretX);
return;
}
while (nodeIterator()) {
const element = walker.currentNode.parentElement;
const elementRect = element.getBoundingClientRect();
if (!this.#isOnSameLine(newLineElementRect, elementRect)) {
break;
}
if (this.#isUnderOver(elementRect, caretX, caretY, isUp)) {
this.#setCaretPosition(select, selection, element, elementRect, caretX);
return;
}
}
this.#setCaretPosition(select, selection, newLineElement, newLineElementRect, caretX);
}
}
;// ./web/download_manager.js
function download(blobUrl, filename) {
const a = document.createElement("a");
if (!a.click) {
throw new Error('DownloadManager: "a.click()" is not supported.');
}
a.href = blobUrl;
a.target = "_parent";
if ("download" in a) {
a.download = filename;
}
(document.body || document.documentElement).append(a);
a.click();
a.remove();
}
class DownloadManager {
#openBlobUrls = new WeakMap();
downloadData(data, filename, contentType) {
const blobUrl = URL.createObjectURL(new Blob([data], {
type: contentType
}));
download(blobUrl, filename);
}
openOrDownloadData(data, filename, dest = null) {
const isPdfData = isPdfFile(filename);
const contentType = isPdfData ? "application/pdf" : "";
if (isPdfData) {
let blobUrl = this.#openBlobUrls.get(data);
if (!blobUrl) {
blobUrl = URL.createObjectURL(new Blob([data], {
type: contentType
}));
this.#openBlobUrls.set(data, blobUrl);
}
let viewerUrl;
viewerUrl = "?file=" + encodeURIComponent(blobUrl + "#" + filename);
if (dest) {
viewerUrl += `#${escape(dest)}`;
}
try {
window.open(viewerUrl);
return true;
} catch (ex) {
console.error("openOrDownloadData:", ex);
URL.revokeObjectURL(blobUrl);
this.#openBlobUrls.delete(data);
}
}
this.downloadData(data, filename, contentType);
return false;
}
download(data, url, filename) {
let blobUrl;
if (data) {
blobUrl = URL.createObjectURL(new Blob([data], {
type: "application/pdf"
}));
} else {
if (!createValidAbsoluteUrl(url, "http://example.com")) {
console.error(`download - not a valid URL: ${url}`);
return;
}
blobUrl = url + "#pdfjs.action=download";
}
download(blobUrl, filename);
}
}
;// ./web/editor_undo_bar.js
class EditorUndoBar {
#closeButton = null;
#container;
#eventBus = null;
#focusTimeout = null;
#initController = null;
isOpen = false;
#message;
#showController = null;
#undoButton;
static #l10nMessages = Object.freeze({
highlight: "pdfjs-editor-undo-bar-message-highlight",
freetext: "pdfjs-editor-undo-bar-message-freetext",
stamp: "pdfjs-editor-undo-bar-message-stamp",
ink: "pdfjs-editor-undo-bar-message-ink",
signature: "pdfjs-editor-undo-bar-message-signature",
_multiple: "pdfjs-editor-undo-bar-message-multiple"
});
constructor({
container,
message,
undoButton,
closeButton
}, eventBus) {
this.#container = container;
this.#message = message;
this.#undoButton = undoButton;
this.#closeButton = closeButton;
this.#eventBus = eventBus;
}
destroy() {
this.#initController?.abort();
this.#initController = null;
this.hide();
}
show(undoAction, messageData) {
if (!this.#initController) {
this.#initController = new AbortController();
const opts = {
signal: this.#initController.signal
};
const boundHide = this.hide.bind(this);
this.#container.addEventListener("contextmenu", noContextMenu, opts);
this.#closeButton.addEventListener("click", boundHide, opts);
this.#eventBus._on("beforeprint", boundHide, opts);
this.#eventBus._on("download", boundHide, opts);
}
this.hide();
if (typeof messageData === "string") {
this.#message.setAttribute("data-l10n-id", EditorUndoBar.#l10nMessages[messageData]);
} else {
this.#message.setAttribute("data-l10n-id", EditorUndoBar.#l10nMessages._multiple);
this.#message.setAttribute("data-l10n-args", JSON.stringify({
count: messageData
}));
}
this.isOpen = true;
this.#container.hidden = false;
this.#showController = new AbortController();
this.#undoButton.addEventListener("click", () => {
undoAction();
this.hide();
}, {
signal: this.#showController.signal
});
this.#focusTimeout = setTimeout(() => {
this.#container.focus();
this.#focusTimeout = null;
}, 100);
}
hide() {
if (!this.isOpen) {
return;
}
this.isOpen = false;
this.#container.hidden = true;
this.#showController?.abort();
this.#showController = null;
if (this.#focusTimeout) {
clearTimeout(this.#focusTimeout);
this.#focusTimeout = null;
}
}
}
;// ./web/overlay_manager.js
class OverlayManager {
#overlays = new WeakMap();
#active = null;
get active() {
return this.#active;
}
async register(dialog, canForceClose = false) {
if (typeof dialog !== "object") {
throw new Error("Not enough parameters.");
} else if (this.#overlays.has(dialog)) {
throw new Error("The overlay is already registered.");
}
this.#overlays.set(dialog, {
canForceClose
});
dialog.addEventListener("cancel", ({
target
}) => {
if (this.#active === target) {
this.#active = null;
}
});
}
async open(dialog) {
if (!this.#overlays.has(dialog)) {
throw new Error("The overlay does not exist.");
} else if (this.#active) {
if (this.#active === dialog) {
throw new Error("The overlay is already active.");
} else if (this.#overlays.get(dialog).canForceClose) {
await this.close();
} else {
throw new Error("Another overlay is currently active.");
}
}
this.#active = dialog;
dialog.showModal();
}
async close(dialog = this.#active) {
if (!this.#overlays.has(dialog)) {
throw new Error("The overlay does not exist.");
} else if (!this.#active) {
throw new Error("The overlay is currently not active.");
} else if (this.#active !== dialog) {
throw new Error("Another overlay is currently active.");
}
dialog.close();
this.#active = null;
}
async closeIfActive(dialog) {
if (this.#active === dialog) {
await this.close(dialog);
}
}
}
;// ./web/password_prompt.js
class PasswordPrompt {
#activeCapability = null;
#updateCallback = null;
#reason = null;
constructor(options, overlayManager, isViewerEmbedded = false) {
this.dialog = options.dialog;
this.label = options.label;
this.input = options.input;
this.submitButton = options.submitButton;
this.cancelButton = options.cancelButton;
this.overlayManager = overlayManager;
this._isViewerEmbedded = isViewerEmbedded;
this.submitButton.addEventListener("click", this.#verify.bind(this));
this.cancelButton.addEventListener("click", this.close.bind(this));
this.input.addEventListener("keydown", e => {
if (e.keyCode === 13) {
this.#verify();
}
});
this.overlayManager.register(this.dialog, true);
this.dialog.addEventListener("close", this.#cancel.bind(this));
}
async open() {
await this.#activeCapability?.promise;
this.#activeCapability = Promise.withResolvers();
try {
await this.overlayManager.open(this.dialog);
} catch (ex) {
this.#activeCapability.resolve();
throw ex;
}
const passwordIncorrect = this.#reason === PasswordResponses.INCORRECT_PASSWORD;
if (!this._isViewerEmbedded || passwordIncorrect) {
this.input.focus();
}
this.label.setAttribute("data-l10n-id", passwordIncorrect ? "pdfjs-password-invalid" : "pdfjs-password-label");
}
async close() {
this.overlayManager.closeIfActive(this.dialog);
}
#verify() {
const password = this.input.value;
if (password?.length > 0) {
this.#invokeCallback(password);
}
}
#cancel() {
this.#invokeCallback(new Error("PasswordPrompt cancelled."));
this.#activeCapability.resolve();
}
#invokeCallback(password) {
if (!this.#updateCallback) {
return;
}
this.close();
this.input.value = "";
this.#updateCallback(password);
this.#updateCallback = null;
}
async setUpdateCallback(updateCallback, reason) {
if (this.#activeCapability) {
await this.#activeCapability.promise;
}
this.#updateCallback = updateCallback;
this.#reason = reason;
}
}
;// ./web/base_tree_viewer.js
const TREEITEM_OFFSET_TOP = -100;
const TREEITEM_SELECTED_CLASS = "selected";
class BaseTreeViewer {
constructor(options) {
this.container = options.container;
this.eventBus = options.eventBus;
this._l10n = options.l10n;
this.reset();
}
reset() {
this._pdfDocument = null;
this._lastToggleIsShow = true;
this._currentTreeItem = null;
this.container.textContent = "";
this.container.classList.remove("treeWithDeepNesting");
}
_dispatchEvent(count) {
throw new Error("Not implemented: _dispatchEvent");
}
_bindLink(element, params) {
throw new Error("Not implemented: _bindLink");
}
_normalizeTextContent(str) {
return removeNullCharacters(str, true) || "\u2013";
}
_addToggleButton(div, hidden = false) {
const toggler = document.createElement("div");
toggler.className = "treeItemToggler";
if (hidden) {
toggler.classList.add("treeItemsHidden");
}
toggler.onclick = evt => {
evt.stopPropagation();
toggler.classList.toggle("treeItemsHidden");
if (evt.shiftKey) {
const shouldShowAll = !toggler.classList.contains("treeItemsHidden");
this._toggleTreeItem(div, shouldShowAll);
}
};
div.prepend(toggler);
}
_toggleTreeItem(root, show = false) {
this._l10n.pause();
this._lastToggleIsShow = show;
for (const toggler of root.querySelectorAll(".treeItemToggler")) {
toggler.classList.toggle("treeItemsHidden", !show);
}
this._l10n.resume();
}
_toggleAllTreeItems() {
this._toggleTreeItem(this.container, !this._lastToggleIsShow);
}
_finishRendering(fragment, count, hasAnyNesting = false) {
if (hasAnyNesting) {
this.container.classList.add("treeWithDeepNesting");
this._lastToggleIsShow = !fragment.querySelector(".treeItemsHidden");
}
this._l10n.pause();
this.container.append(fragment);
this._l10n.resume();
this._dispatchEvent(count);
}
render(params) {
throw new Error("Not implemented: render");
}
_updateCurrentTreeItem(treeItem = null) {
if (this._currentTreeItem) {
this._currentTreeItem.classList.remove(TREEITEM_SELECTED_CLASS);
this._currentTreeItem = null;
}
if (treeItem) {
treeItem.classList.add(TREEITEM_SELECTED_CLASS);
this._currentTreeItem = treeItem;
}
}
_scrollToCurrentTreeItem(treeItem) {
if (!treeItem) {
return;
}
this._l10n.pause();
let currentNode = treeItem.parentNode;
while (currentNode && currentNode !== this.container) {
if (currentNode.classList.contains("treeItem")) {
const toggler = currentNode.firstElementChild;
toggler?.classList.remove("treeItemsHidden");
}
currentNode = currentNode.parentNode;
}
this._l10n.resume();
this._updateCurrentTreeItem(treeItem);
this.container.scrollTo(treeItem.offsetLeft, treeItem.offsetTop + TREEITEM_OFFSET_TOP);
}
}
;// ./web/pdf_attachment_viewer.js
class PDFAttachmentViewer extends BaseTreeViewer {
constructor(options) {
super(options);
this.downloadManager = options.downloadManager;
this.eventBus._on("fileattachmentannotation", this.#appendAttachment.bind(this));
}
reset(keepRenderedCapability = false) {
super.reset();
this._attachments = null;
if (!keepRenderedCapability) {
this._renderedCapability = Promise.withResolvers();
}
this._pendingDispatchEvent = false;
}
async _dispatchEvent(attachmentsCount) {
this._renderedCapability.resolve();
if (attachmentsCount === 0 && !this._pendingDispatchEvent) {
this._pendingDispatchEvent = true;
await waitOnEventOrTimeout({
target: this.eventBus,
name: "annotationlayerrendered",
delay: 1000
});
if (!this._pendingDispatchEvent) {
return;
}
}
this._pendingDispatchEvent = false;
this.eventBus.dispatch("attachmentsloaded", {
source: this,
attachmentsCount
});
}
_bindLink(element, {
content,
description,
filename
}) {
if (description) {
element.title = description;
}
element.onclick = () => {
this.downloadManager.openOrDownloadData(content, filename);
return false;
};
}
render({
attachments,
keepRenderedCapability = false
}) {
if (this._attachments) {
this.reset(keepRenderedCapability);
}
this._attachments = attachments || null;
if (!attachments) {
this._dispatchEvent(0);
return;
}
const fragment = document.createDocumentFragment();
let attachmentsCount = 0;
for (const name in attachments) {
const item = attachments[name];
const div = document.createElement("div");
div.className = "treeItem";
const element = document.createElement("a");
this._bindLink(element, item);
element.textContent = this._normalizeTextContent(item.filename);
div.append(element);
fragment.append(div);
attachmentsCount++;
}
this._finishRendering(fragment, attachmentsCount);
}
#appendAttachment(item) {
const renderedPromise = this._renderedCapability.promise;
renderedPromise.then(() => {
if (renderedPromise !== this._renderedCapability.promise) {
return;
}
const attachments = this._attachments || Object.create(null);
for (const name in attachments) {
if (item.filename === name) {
return;
}
}
attachments[item.filename] = item;
this.render({
attachments,
keepRenderedCapability: true
});
});
}
}
;// ./web/grab_to_pan.js
const CSS_CLASS_GRAB = "grab-to-pan-grab";
class GrabToPan {
#activateAC = null;
#mouseDownAC = null;
#scrollAC = null;
constructor({
element
}) {
this.element = element;
this.document = element.ownerDocument;
const overlay = this.overlay = document.createElement("div");
overlay.className = "grab-to-pan-grabbing";
}
activate() {
if (!this.#activateAC) {
this.#activateAC = new AbortController();
this.element.addEventListener("mousedown", this.#onMouseDown.bind(this), {
capture: true,
signal: this.#activateAC.signal
});
this.element.classList.add(CSS_CLASS_GRAB);
}
}
deactivate() {
if (this.#activateAC) {
this.#activateAC.abort();
this.#activateAC = null;
this.#endPan();
this.element.classList.remove(CSS_CLASS_GRAB);
}
}
toggle() {
if (this.#activateAC) {
this.deactivate();
} else {
this.activate();
}
}
ignoreTarget(node) {
return node.matches("a[href], a[href] *, input, textarea, button, button *, select, option");
}
#onMouseDown(event) {
if (event.button !== 0 || this.ignoreTarget(event.target)) {
return;
}
if (event.originalTarget) {
try {
event.originalTarget.tagName;
} catch {
return;
}
}
this.scrollLeftStart = this.element.scrollLeft;
this.scrollTopStart = this.element.scrollTop;
this.clientXStart = event.clientX;
this.clientYStart = event.clientY;
this.#mouseDownAC = new AbortController();
const boundEndPan = this.#endPan.bind(this),
mouseOpts = {
capture: true,
signal: this.#mouseDownAC.signal
};
this.document.addEventListener("mousemove", this.#onMouseMove.bind(this), mouseOpts);
this.document.addEventListener("mouseup", boundEndPan, mouseOpts);
this.#scrollAC = new AbortController();
this.element.addEventListener("scroll", boundEndPan, {
capture: true,
signal: this.#scrollAC.signal
});
stopEvent(event);
const focusedElement = document.activeElement;
if (focusedElement && !focusedElement.contains(event.target)) {
focusedElement.blur();
}
}
#onMouseMove(event) {
this.#scrollAC?.abort();
this.#scrollAC = null;
if (!(event.buttons & 1)) {
this.#endPan();
return;
}
const xDiff = event.clientX - this.clientXStart;
const yDiff = event.clientY - this.clientYStart;
this.element.scrollTo({
top: this.scrollTopStart - yDiff,
left: this.scrollLeftStart - xDiff,
behavior: "instant"
});
if (!this.overlay.parentNode) {
document.body.append(this.overlay);
}
}
#endPan() {
this.#mouseDownAC?.abort();
this.#mouseDownAC = null;
this.#scrollAC?.abort();
this.#scrollAC = null;
this.overlay.remove();
}
}
;// ./web/pdf_cursor_tools.js
class PDFCursorTools {
#active = CursorTool.SELECT;
#prevActive = null;
constructor({
container,
eventBus,
cursorToolOnLoad = CursorTool.SELECT
}) {
this.container = container;
this.eventBus = eventBus;
this.#addEventListeners();
Promise.resolve().then(() => {
this.switchTool(cursorToolOnLoad);
});
}
get activeTool() {
return this.#active;
}
switchTool(tool) {
if (this.#prevActive !== null) {
return;
}
this.#switchTool(tool);
}
#switchTool(tool, disabled = false) {
if (tool === this.#active) {
if (this.#prevActive !== null) {
this.eventBus.dispatch("cursortoolchanged", {
source: this,
tool,
disabled
});
}
return;
}
const disableActiveTool = () => {
switch (this.#active) {
case CursorTool.SELECT:
break;
case CursorTool.HAND:
this._handTool.deactivate();
break;
case CursorTool.ZOOM:
}
};
switch (tool) {
case CursorTool.SELECT:
disableActiveTool();
break;
case CursorTool.HAND:
disableActiveTool();
this._handTool.activate();
break;
case CursorTool.ZOOM:
default:
console.error(`switchTool: "${tool}" is an unsupported value.`);
return;
}
this.#active = tool;
this.eventBus.dispatch("cursortoolchanged", {
source: this,
tool,
disabled
});
}
#addEventListeners() {
this.eventBus._on("switchcursortool", evt => {
if (!evt.reset) {
this.switchTool(evt.tool);
} else if (this.#prevActive !== null) {
annotationEditorMode = AnnotationEditorType.NONE;
presentationModeState = PresentationModeState.NORMAL;
enableActive();
}
});
let annotationEditorMode = AnnotationEditorType.NONE,
presentationModeState = PresentationModeState.NORMAL;
const disableActive = () => {
this.#prevActive ??= this.#active;
this.#switchTool(CursorTool.SELECT, true);
};
const enableActive = () => {
if (this.#prevActive !== null && annotationEditorMode === AnnotationEditorType.NONE && presentationModeState === PresentationModeState.NORMAL) {
this.#switchTool(this.#prevActive);
this.#prevActive = null;
}
};
this.eventBus._on("annotationeditormodechanged", ({
mode
}) => {
annotationEditorMode = mode;
if (mode === AnnotationEditorType.NONE) {
enableActive();
} else {
disableActive();
}
});
this.eventBus._on("presentationmodechanged", ({
state
}) => {
presentationModeState = state;
if (state === PresentationModeState.NORMAL) {
enableActive();
} else if (state === PresentationModeState.FULLSCREEN) {
disableActive();
}
});
}
get _handTool() {
return shadow(this, "_handTool", new GrabToPan({
element: this.container
}));
}
}
;// ./web/pdf_document_properties.js
const NON_METRIC_LOCALES = ["en-us", "en-lr", "my"];
const US_PAGE_NAMES = {
"8.5x11": "pdfjs-document-properties-page-size-name-letter",
"8.5x14": "pdfjs-document-properties-page-size-name-legal"
};
const METRIC_PAGE_NAMES = {
"297x420": "pdfjs-document-properties-page-size-name-a-three",
"210x297": "pdfjs-document-properties-page-size-name-a-four"
};
function getPageName(size, isPortrait, pageNames) {
const width = isPortrait ? size.width : size.height;
const height = isPortrait ? size.height : size.width;
return pageNames[`${width}x${height}`];
}
class PDFDocumentProperties {
#fieldData = null;
constructor({
dialog,
fields,
closeButton
}, overlayManager, eventBus, l10n, fileNameLookup) {
this.dialog = dialog;
this.fields = fields;
this.overlayManager = overlayManager;
this.l10n = l10n;
this._fileNameLookup = fileNameLookup;
this.#reset();
closeButton.addEventListener("click", this.close.bind(this));
this.overlayManager.register(this.dialog);
eventBus._on("pagechanging", evt => {
this._currentPageNumber = evt.pageNumber;
});
eventBus._on("rotationchanging", evt => {
this._pagesRotation = evt.pagesRotation;
});
}
async open() {
await Promise.all([this.overlayManager.open(this.dialog), this._dataAvailableCapability.promise]);
const currentPageNumber = this._currentPageNumber;
const pagesRotation = this._pagesRotation;
if (this.#fieldData && currentPageNumber === this.#fieldData._currentPageNumber && pagesRotation === this.#fieldData._pagesRotation) {
this.#updateUI();
return;
}
const [{
info,
contentLength
}, pdfPage] = await Promise.all([this.pdfDocument.getMetadata(), this.pdfDocument.getPage(currentPageNumber)]);
const [fileName, fileSize, creationDate, modificationDate, pageSize, isLinearized] = await Promise.all([this._fileNameLookup(), this.#parseFileSize(contentLength), this.#parseDate(info.CreationDate), this.#parseDate(info.ModDate), this.#parsePageSize(getPageSizeInches(pdfPage), pagesRotation), this.#parseLinearization(info.IsLinearized)]);
this.#fieldData = Object.freeze({
fileName,
fileSize,
title: info.Title,
author: info.Author,
subject: info.Subject,
keywords: info.Keywords,
creationDate,
modificationDate,
creator: info.Creator,
producer: info.Producer,
version: info.PDFFormatVersion,
pageCount: this.pdfDocument.numPages,
pageSize,
linearized: isLinearized,
_currentPageNumber: currentPageNumber,
_pagesRotation: pagesRotation
});
this.#updateUI();
const {
length
} = await this.pdfDocument.getDownloadInfo();
if (contentLength === length) {
return;
}
const data = Object.assign(Object.create(null), this.#fieldData);
data.fileSize = await this.#parseFileSize(length);
this.#fieldData = Object.freeze(data);
this.#updateUI();
}
async close() {
this.overlayManager.close(this.dialog);
}
setDocument(pdfDocument) {
if (this.pdfDocument) {
this.#reset();
this.#updateUI();
}
if (!pdfDocument) {
return;
}
this.pdfDocument = pdfDocument;
this._dataAvailableCapability.resolve();
}
#reset() {
this.pdfDocument = null;
this.#fieldData = null;
this._dataAvailableCapability = Promise.withResolvers();
this._currentPageNumber = 1;
this._pagesRotation = 0;
}
#updateUI() {
if (this.#fieldData && this.overlayManager.active !== this.dialog) {
return;
}
for (const id in this.fields) {
const content = this.#fieldData?.[id];
this.fields[id].textContent = content || content === 0 ? content : "-";
}
}
async #parseFileSize(b = 0) {
const kb = b / 1024,
mb = kb / 1024;
return kb ? this.l10n.get(mb >= 1 ? "pdfjs-document-properties-size-mb" : "pdfjs-document-properties-size-kb", {
mb,
kb,
b
}) : undefined;
}
async #parsePageSize(pageSizeInches, pagesRotation) {
if (!pageSizeInches) {
return undefined;
}
if (pagesRotation % 180 !== 0) {
pageSizeInches = {
width: pageSizeInches.height,
height: pageSizeInches.width
};
}
const isPortrait = isPortraitOrientation(pageSizeInches),
nonMetric = NON_METRIC_LOCALES.includes(this.l10n.getLanguage());
let sizeInches = {
width: Math.round(pageSizeInches.width * 100) / 100,
height: Math.round(pageSizeInches.height * 100) / 100
};
let sizeMillimeters = {
width: Math.round(pageSizeInches.width * 25.4 * 10) / 10,
height: Math.round(pageSizeInches.height * 25.4 * 10) / 10
};
let nameId = getPageName(sizeInches, isPortrait, US_PAGE_NAMES) || getPageName(sizeMillimeters, isPortrait, METRIC_PAGE_NAMES);
if (!nameId && !(Number.isInteger(sizeMillimeters.width) && Number.isInteger(sizeMillimeters.height))) {
const exactMillimeters = {
width: pageSizeInches.width * 25.4,
height: pageSizeInches.height * 25.4
};
const intMillimeters = {
width: Math.round(sizeMillimeters.width),
height: Math.round(sizeMillimeters.height)
};
if (Math.abs(exactMillimeters.width - intMillimeters.width) < 0.1 && Math.abs(exactMillimeters.height - intMillimeters.height) < 0.1) {
nameId = getPageName(intMillimeters, isPortrait, METRIC_PAGE_NAMES);
if (nameId) {
sizeInches = {
width: Math.round(intMillimeters.width / 25.4 * 100) / 100,
height: Math.round(intMillimeters.height / 25.4 * 100) / 100
};
sizeMillimeters = intMillimeters;
}
}
}
const [{
width,
height
}, unit, name, orientation] = await Promise.all([nonMetric ? sizeInches : sizeMillimeters, this.l10n.get(nonMetric ? "pdfjs-document-properties-page-size-unit-inches" : "pdfjs-document-properties-page-size-unit-millimeters"), nameId && this.l10n.get(nameId), this.l10n.get(isPortrait ? "pdfjs-document-properties-page-size-orientation-portrait" : "pdfjs-document-properties-page-size-orientation-landscape")]);
return this.l10n.get(name ? "pdfjs-document-properties-page-size-dimension-name-string" : "pdfjs-document-properties-page-size-dimension-string", {
width,
height,
unit,
name,
orientation
});
}
async #parseDate(inputDate) {
const dateObj = PDFDateString.toDateObject(inputDate);
return dateObj ? this.l10n.get("pdfjs-document-properties-date-time-string", {
dateObj: dateObj.valueOf()
}) : undefined;
}
#parseLinearization(isLinearized) {
return this.l10n.get(isLinearized ? "pdfjs-document-properties-linearized-yes" : "pdfjs-document-properties-linearized-no");
}
}
;// ./web/pdf_find_utils.js
const CharacterType = {
SPACE: 0,
ALPHA_LETTER: 1,
PUNCT: 2,
HAN_LETTER: 3,
KATAKANA_LETTER: 4,
HIRAGANA_LETTER: 5,
HALFWIDTH_KATAKANA_LETTER: 6,
THAI_LETTER: 7
};
function isAlphabeticalScript(charCode) {
return charCode < 0x2e80;
}
function isAscii(charCode) {
return (charCode & 0xff80) === 0;
}
function isAsciiAlpha(charCode) {
return charCode >= 0x61 && charCode <= 0x7a || charCode >= 0x41 && charCode <= 0x5a;
}
function isAsciiDigit(charCode) {
return charCode >= 0x30 && charCode <= 0x39;
}
function isAsciiSpace(charCode) {
return charCode === 0x20 || charCode === 0x09 || charCode === 0x0d || charCode === 0x0a;
}
function isHan(charCode) {
return charCode >= 0x3400 && charCode <= 0x9fff || charCode >= 0xf900 && charCode <= 0xfaff;
}
function isKatakana(charCode) {
return charCode >= 0x30a0 && charCode <= 0x30ff;
}
function isHiragana(charCode) {
return charCode >= 0x3040 && charCode <= 0x309f;
}
function isHalfwidthKatakana(charCode) {
return charCode >= 0xff60 && charCode <= 0xff9f;
}
function isThai(charCode) {
return (charCode & 0xff80) === 0x0e00;
}
function getCharacterType(charCode) {
if (isAlphabeticalScript(charCode)) {
if (isAscii(charCode)) {
if (isAsciiSpace(charCode)) {
return CharacterType.SPACE;
} else if (isAsciiAlpha(charCode) || isAsciiDigit(charCode) || charCode === 0x5f) {
return CharacterType.ALPHA_LETTER;
}
return CharacterType.PUNCT;
} else if (isThai(charCode)) {
return CharacterType.THAI_LETTER;
} else if (charCode === 0xa0) {
return CharacterType.SPACE;
}
return CharacterType.ALPHA_LETTER;
}
if (isHan(charCode)) {
return CharacterType.HAN_LETTER;
} else if (isKatakana(charCode)) {
return CharacterType.KATAKANA_LETTER;
} else if (isHiragana(charCode)) {
return CharacterType.HIRAGANA_LETTER;
} else if (isHalfwidthKatakana(charCode)) {
return CharacterType.HALFWIDTH_KATAKANA_LETTER;
}
return CharacterType.ALPHA_LETTER;
}
let NormalizeWithNFKC;
function getNormalizeWithNFKC() {
NormalizeWithNFKC ||= ` ¨ª¯²-µ¸-º¼-¾IJ-ijĿ-ŀʼnſDŽ-njDZ-dzʰ-ʸ˘-˝ˠ-ˤʹͺ;΄-΅·ϐ-ϖϰ-ϲϴ-ϵϹևٵ-ٸक़-य़ড়-ঢ়য়ਲ਼ਸ਼ਖ਼-ਜ਼ਫ਼ଡ଼-ଢ଼ำຳໜ-ໝ༌གྷཌྷདྷབྷཛྷཀྵჼᴬ-ᴮᴰ-ᴺᴼ-ᵍᵏ-ᵪᵸᶛ-ᶿẚ-ẛάέήίόύώΆ᾽-῁ΈΉ῍-῏ΐΊ῝-῟ΰΎ῭-`ΌΏ´-῾ - ‑‗․-… ″-‴‶-‷‼‾⁇-⁉⁗ ⁰-ⁱ⁴-₎ₐ-ₜ₨℀-℃℅-ℇ℉-ℓℕ-№ℙ-ℝ℠-™ℤΩℨK-ℭℯ-ℱℳ-ℹ℻-⅀ⅅ-ⅉ⅐-ⅿ↉∬-∭∯-∰〈-〉①-⓪⨌⩴-⩶⫝̸ⱼ-ⱽⵯ⺟⻳⼀-⿕ 〶〸-〺゛-゜ゟヿㄱ-ㆎ㆒-㆟㈀-㈞㈠-㉇㉐-㉾㊀-㏿ꚜ-ꚝꝰꟲ-ꟴꟸ-ꟹꭜ-ꭟꭩ豈-嗀塚晴凞-羽蘒諸逸-都飯-舘並-龎ff-stﬓ-ﬗיִײַ-זּטּ-לּמּנּ-סּףּ-פּצּ-ﮱﯓ-ﴽﵐ-ﶏﶒ-ﷇﷰ-﷼︐-︙︰-﹄﹇-﹒﹔-﹦﹨-﹫ﹰ-ﹲﹴﹶ-ﻼ!-하-ᅦᅧ-ᅬᅭ-ᅲᅳ-ᅵ¢-₩`;
return NormalizeWithNFKC;
}
;// ./web/pdf_find_controller.js
const FindState = {
FOUND: 0,
NOT_FOUND: 1,
WRAPPED: 2,
PENDING: 3
};
const FIND_TIMEOUT = 250;
const MATCH_SCROLL_OFFSET_TOP = -50;
const MATCH_SCROLL_OFFSET_LEFT = -400;
const CHARACTERS_TO_NORMALIZE = {
"\u2010": "-",
"\u2018": "'",
"\u2019": "'",
"\u201A": "'",
"\u201B": "'",
"\u201C": '"',
"\u201D": '"',
"\u201E": '"',
"\u201F": '"',
"\u00BC": "1/4",
"\u00BD": "1/2",
"\u00BE": "3/4"
};
const DIACRITICS_EXCEPTION = new Set([0x3099, 0x309a, 0x094d, 0x09cd, 0x0a4d, 0x0acd, 0x0b4d, 0x0bcd, 0x0c4d, 0x0ccd, 0x0d3b, 0x0d3c, 0x0d4d, 0x0dca, 0x0e3a, 0x0eba, 0x0f84, 0x1039, 0x103a, 0x1714, 0x1734, 0x17d2, 0x1a60, 0x1b44, 0x1baa, 0x1bab, 0x1bf2, 0x1bf3, 0x2d7f, 0xa806, 0xa82c, 0xa8c4, 0xa953, 0xa9c0, 0xaaf6, 0xabed, 0x0c56, 0x0f71, 0x0f72, 0x0f7a, 0x0f7b, 0x0f7c, 0x0f7d, 0x0f80, 0x0f74]);
let DIACRITICS_EXCEPTION_STR;
const DIACRITICS_REG_EXP = /\p{M}+/gu;
const SPECIAL_CHARS_REG_EXP = /([.*+?^${}()|[\]\\])|(\p{P})|(\s+)|(\p{M})|(\p{L})/gu;
const NOT_DIACRITIC_FROM_END_REG_EXP = /([^\p{M}])\p{M}*$/u;
const NOT_DIACRITIC_FROM_START_REG_EXP = /^\p{M}*([^\p{M}])/u;
const SYLLABLES_REG_EXP = /[\uAC00-\uD7AF\uFA6C\uFACF-\uFAD1\uFAD5-\uFAD7]+/g;
const SYLLABLES_LENGTHS = new Map();
const FIRST_CHAR_SYLLABLES_REG_EXP = "[\\u1100-\\u1112\\ud7a4-\\ud7af\\ud84a\\ud84c\\ud850\\ud854\\ud857\\ud85f]";
const NFKC_CHARS_TO_NORMALIZE = new Map();
let noSyllablesRegExp = null;
let withSyllablesRegExp = null;
function normalize(text) {
const syllablePositions = [];
let m;
while ((m = SYLLABLES_REG_EXP.exec(text)) !== null) {
let {
index
} = m;
for (const char of m[0]) {
let len = SYLLABLES_LENGTHS.get(char);
if (!len) {
len = char.normalize("NFD").length;
SYLLABLES_LENGTHS.set(char, len);
}
syllablePositions.push([len, index++]);
}
}
const hasSyllables = syllablePositions.length > 0;
let normalizationRegex;
if (!hasSyllables && noSyllablesRegExp) {
normalizationRegex = noSyllablesRegExp;
} else if (hasSyllables && withSyllablesRegExp) {
normalizationRegex = withSyllablesRegExp;
} else {
const replace = Object.keys(CHARACTERS_TO_NORMALIZE).join("");
const toNormalizeWithNFKC = getNormalizeWithNFKC();
const CJK = "(?:\\p{Ideographic}|[\u3040-\u30FF])";
const HKDiacritics = "(?:\u3099|\u309A)";
const BrokenWord = `\\p{Ll}-\\n(?=\\p{Ll})|\\p{Lu}-\\n(?=\\p{L})`;
const regexps = [`[${replace}]`, `[${toNormalizeWithNFKC}]`, `${HKDiacritics}\\n`, "\\p{M}+(?:-\\n)?", `${BrokenWord}`, "\\S-\\n", `${CJK}\\n`, "\\n", hasSyllables ? FIRST_CHAR_SYLLABLES_REG_EXP : "\\u0000"];
normalizationRegex = new RegExp(regexps.map(r => `(${r})`).join("|"), "gum");
if (hasSyllables) {
withSyllablesRegExp = normalizationRegex;
} else {
noSyllablesRegExp = normalizationRegex;
}
}
const rawDiacriticsPositions = [];
while ((m = DIACRITICS_REG_EXP.exec(text)) !== null) {
rawDiacriticsPositions.push([m[0].length, m.index]);
}
let normalized = text.normalize("NFD");
const positions = [0, 0];
let rawDiacriticsIndex = 0;
let syllableIndex = 0;
let shift = 0;
let shiftOrigin = 0;
let eol = 0;
let hasDiacritics = false;
normalized = normalized.replace(normalizationRegex, (match, p1, p2, p3, p4, p5, p6, p7, p8, p9, i) => {
i -= shiftOrigin;
if (p1) {
const replacement = CHARACTERS_TO_NORMALIZE[p1];
const jj = replacement.length;
for (let j = 1; j < jj; j++) {
positions.push(i - shift + j, shift - j);
}
shift -= jj - 1;
return replacement;
}
if (p2) {
let replacement = NFKC_CHARS_TO_NORMALIZE.get(p2);
if (!replacement) {
replacement = p2.normalize("NFKC");
NFKC_CHARS_TO_NORMALIZE.set(p2, replacement);
}
const jj = replacement.length;
for (let j = 1; j < jj; j++) {
positions.push(i - shift + j, shift - j);
}
shift -= jj - 1;
return replacement;
}
if (p3) {
hasDiacritics = true;
if (i + eol === rawDiacriticsPositions[rawDiacriticsIndex]?.[1]) {
++rawDiacriticsIndex;
} else {
positions.push(i - 1 - shift + 1, shift - 1);
shift -= 1;
shiftOrigin += 1;
}
positions.push(i - shift + 1, shift);
shiftOrigin += 1;
eol += 1;
return p3.charAt(0);
}
if (p4) {
const hasTrailingDashEOL = p4.endsWith("\n");
const len = hasTrailingDashEOL ? p4.length - 2 : p4.length;
hasDiacritics = true;
let jj = len;
if (i + eol === rawDiacriticsPositions[rawDiacriticsIndex]?.[1]) {
jj -= rawDiacriticsPositions[rawDiacriticsIndex][0];
++rawDiacriticsIndex;
}
for (let j = 1; j <= jj; j++) {
positions.push(i - 1 - shift + j, shift - j);
}
shift -= jj;
shiftOrigin += jj;
if (hasTrailingDashEOL) {
i += len - 1;
positions.push(i - shift + 1, 1 + shift);
shift += 1;
shiftOrigin += 1;
eol += 1;
return p4.slice(0, len);
}
return p4;
}
if (p5) {
const len = p5.length - 2;
positions.push(i - shift + len, 1 + shift);
shift += 1;
shiftOrigin += 1;
eol += 1;
return p5.slice(0, -2);
}
if (p6) {
shiftOrigin += 1;
eol += 1;
return p6.slice(0, -1);
}
if (p7) {
const len = p7.length - 1;
positions.push(i - shift + len, shift);
shiftOrigin += 1;
eol += 1;
return p7.slice(0, -1);
}
if (p8) {
positions.push(i - shift + 1, shift - 1);
shift -= 1;
shiftOrigin += 1;
eol += 1;
return " ";
}
if (i + eol === syllablePositions[syllableIndex]?.[1]) {
const newCharLen = syllablePositions[syllableIndex][0] - 1;
++syllableIndex;
for (let j = 1; j <= newCharLen; j++) {
positions.push(i - (shift - j), shift - j);
}
shift -= newCharLen;
shiftOrigin += newCharLen;
}
return p9;
});
positions.push(normalized.length, shift);
const starts = new Uint32Array(positions.length >> 1);
const shifts = new Int32Array(positions.length >> 1);
for (let i = 0, ii = positions.length; i < ii; i += 2) {
starts[i >> 1] = positions[i];
shifts[i >> 1] = positions[i + 1];
}
return [normalized, [starts, shifts], hasDiacritics];
}
function getOriginalIndex(diffs, pos, len) {
if (!diffs) {
return [pos, len];
}
const [starts, shifts] = diffs;
const start = pos;
const end = pos + len - 1;
let i = binarySearchFirstItem(starts, x => x >= start);
if (starts[i] > start) {
--i;
}
let j = binarySearchFirstItem(starts, x => x >= end, i);
if (starts[j] > end) {
--j;
}
const oldStart = start + shifts[i];
const oldEnd = end + shifts[j];
const oldLen = oldEnd + 1 - oldStart;
return [oldStart, oldLen];
}
class PDFFindController {
#state = null;
#updateMatchesCountOnProgress = true;
#visitedPagesCount = 0;
constructor({
linkService,
eventBus,
updateMatchesCountOnProgress = true
}) {
this._linkService = linkService;
this._eventBus = eventBus;
this.#updateMatchesCountOnProgress = updateMatchesCountOnProgress;
this.onIsPageVisible = null;
this.#reset();
eventBus._on("find", this.#onFind.bind(this));
eventBus._on("findbarclose", this.#onFindBarClose.bind(this));
}
get highlightMatches() {
return this._highlightMatches;
}
get pageMatches() {
return this._pageMatches;
}
get pageMatchesLength() {
return this._pageMatchesLength;
}
get selected() {
return this._selected;
}
get state() {
return this.#state;
}
setDocument(pdfDocument) {
if (this._pdfDocument) {
this.#reset();
}
if (!pdfDocument) {
return;
}
this._pdfDocument = pdfDocument;
this._firstPageCapability.resolve();
}
#onFind(state) {
if (!state) {
return;
}
const pdfDocument = this._pdfDocument;
const {
type
} = state;
if (this.#state === null || this.#shouldDirtyMatch(state)) {
this._dirtyMatch = true;
}
this.#state = state;
if (type !== "highlightallchange") {
this.#updateUIState(FindState.PENDING);
}
this._firstPageCapability.promise.then(() => {
if (!this._pdfDocument || pdfDocument && this._pdfDocument !== pdfDocument) {
return;
}
this.#extractText();
const findbarClosed = !this._highlightMatches;
const pendingTimeout = !!this._findTimeout;
if (this._findTimeout) {
clearTimeout(this._findTimeout);
this._findTimeout = null;
}
if (!type) {
this._findTimeout = setTimeout(() => {
this.#nextMatch();
this._findTimeout = null;
}, FIND_TIMEOUT);
} else if (this._dirtyMatch) {
this.#nextMatch();
} else if (type === "again") {
this.#nextMatch();
if (findbarClosed && this.#state.highlightAll) {
this.#updateAllPages();
}
} else if (type === "highlightallchange") {
if (pendingTimeout) {
this.#nextMatch();
} else {
this._highlightMatches = true;
}
this.#updateAllPages();
} else {
this.#nextMatch();
}
});
}
scrollMatchIntoView({
element = null,
selectedLeft = 0,
pageIndex = -1,
matchIndex = -1
}) {
if (!this._scrollMatches || !element) {
return;
} else if (matchIndex === -1 || matchIndex !== this._selected.matchIdx) {
return;
} else if (pageIndex === -1 || pageIndex !== this._selected.pageIdx) {
return;
}
this._scrollMatches = false;
const spot = {
top: MATCH_SCROLL_OFFSET_TOP,
left: selectedLeft + MATCH_SCROLL_OFFSET_LEFT
};
scrollIntoView(element, spot, true);
}
#reset() {
this._highlightMatches = false;
this._scrollMatches = false;
this._pdfDocument = null;
this._pageMatches = [];
this._pageMatchesLength = [];
this.#visitedPagesCount = 0;
this.#state = null;
this._selected = {
pageIdx: -1,
matchIdx: -1
};
this._offset = {
pageIdx: null,
matchIdx: null,
wrapped: false
};
this._extractTextPromises = [];
this._pageContents = [];
this._pageDiffs = [];
this._hasDiacritics = [];
this._matchesCountTotal = 0;
this._pagesToSearch = null;
this._pendingFindMatches = new Set();
this._resumePageIdx = null;
this._dirtyMatch = false;
clearTimeout(this._findTimeout);
this._findTimeout = null;
this._firstPageCapability = Promise.withResolvers();
}
get #query() {
const {
query
} = this.#state;
if (typeof query === "string") {
if (query !== this._rawQuery) {
this._rawQuery = query;
[this._normalizedQuery] = normalize(query);
}
return this._normalizedQuery;
}
return (query || []).filter(q => !!q).map(q => normalize(q)[0]);
}
#shouldDirtyMatch(state) {
const newQuery = state.query,
prevQuery = this.#state.query;
const newType = typeof newQuery,
prevType = typeof prevQuery;
if (newType !== prevType) {
return true;
}
if (newType === "string") {
if (newQuery !== prevQuery) {
return true;
}
} else if (JSON.stringify(newQuery) !== JSON.stringify(prevQuery)) {
return true;
}
switch (state.type) {
case "again":
const pageNumber = this._selected.pageIdx + 1;
const linkService = this._linkService;
return pageNumber >= 1 && pageNumber <= linkService.pagesCount && pageNumber !== linkService.page && !(this.onIsPageVisible?.(pageNumber) ?? true);
case "highlightallchange":
return false;
}
return true;
}
#isEntireWord(content, startIdx, length) {
let match = content.slice(0, startIdx).match(NOT_DIACRITIC_FROM_END_REG_EXP);
if (match) {
const first = content.charCodeAt(startIdx);
const limit = match[1].charCodeAt(0);
if (getCharacterType(first) === getCharacterType(limit)) {
return false;
}
}
match = content.slice(startIdx + length).match(NOT_DIACRITIC_FROM_START_REG_EXP);
if (match) {
const last = content.charCodeAt(startIdx + length - 1);
const limit = match[1].charCodeAt(0);
if (getCharacterType(last) === getCharacterType(limit)) {
return false;
}
}
return true;
}
#convertToRegExpString(query, hasDiacritics) {
const {
matchDiacritics
} = this.#state;
let isUnicode = false;
query = query.replaceAll(SPECIAL_CHARS_REG_EXP, (match, p1, p2, p3, p4, p5) => {
if (p1) {
return `[ ]*\\${p1}[ ]*`;
}
if (p2) {
return `[ ]*${p2}[ ]*`;
}
if (p3) {
return "[ ]+";
}
if (matchDiacritics) {
return p4 || p5;
}
if (p4) {
return DIACRITICS_EXCEPTION.has(p4.charCodeAt(0)) ? p4 : "";
}
if (hasDiacritics) {
isUnicode = true;
return `${p5}\\p{M}*`;
}
return p5;
});
const trailingSpaces = "[ ]*";
if (query.endsWith(trailingSpaces)) {
query = query.slice(0, query.length - trailingSpaces.length);
}
if (matchDiacritics) {
if (hasDiacritics) {
DIACRITICS_EXCEPTION_STR ||= String.fromCharCode(...DIACRITICS_EXCEPTION);
isUnicode = true;
query = `${query}(?=[${DIACRITICS_EXCEPTION_STR}]|[^\\p{M}]|$)`;
}
}
return [isUnicode, query];
}
#calculateMatch(pageIndex) {
const query = this.#query;
if (query.length === 0) {
return;
}
const pageContent = this._pageContents[pageIndex];
const matcherResult = this.match(query, pageContent, pageIndex);
const matches = this._pageMatches[pageIndex] = [];
const matchesLength = this._pageMatchesLength[pageIndex] = [];
const diffs = this._pageDiffs[pageIndex];
matcherResult?.forEach(({
index,
length
}) => {
const [matchPos, matchLen] = getOriginalIndex(diffs, index, length);
if (matchLen) {
matches.push(matchPos);
matchesLength.push(matchLen);
}
});
if (this.#state.highlightAll) {
this.#updatePage(pageIndex);
}
if (this._resumePageIdx === pageIndex) {
this._resumePageIdx = null;
this.#nextPageMatch();
}
const pageMatchesCount = matches.length;
this._matchesCountTotal += pageMatchesCount;
if (this.#updateMatchesCountOnProgress) {
if (pageMatchesCount > 0) {
this.#updateUIResultsCount();
}
} else if (++this.#visitedPagesCount === this._linkService.pagesCount) {
this.#updateUIResultsCount();
}
}
match(query, pageContent, pageIndex) {
const hasDiacritics = this._hasDiacritics[pageIndex];
let isUnicode = false;
if (typeof query === "string") {
[isUnicode, query] = this.#convertToRegExpString(query, hasDiacritics);
} else {
query = query.sort().reverse().map(q => {
const [isUnicodePart, queryPart] = this.#convertToRegExpString(q, hasDiacritics);
isUnicode ||= isUnicodePart;
return `(${queryPart})`;
}).join("|");
}
if (!query) {
return undefined;
}
const {
caseSensitive,
entireWord
} = this.#state;
const flags = `g${isUnicode ? "u" : ""}${caseSensitive ? "" : "i"}`;
query = new RegExp(query, flags);
const matches = [];
let match;
while ((match = query.exec(pageContent)) !== null) {
if (entireWord && !this.#isEntireWord(pageContent, match.index, match[0].length)) {
continue;
}
matches.push({
index: match.index,
length: match[0].length
});
}
return matches;
}
#extractText() {
if (this._extractTextPromises.length > 0) {
return;
}
let deferred = Promise.resolve();
const textOptions = {
disableNormalization: true
};
for (let i = 0, ii = this._linkService.pagesCount; i < ii; i++) {
const {
promise,
resolve
} = Promise.withResolvers();
this._extractTextPromises[i] = promise;
deferred = deferred.then(() => this._pdfDocument.getPage(i + 1).then(pdfPage => pdfPage.getTextContent(textOptions)).then(textContent => {
const strBuf = [];
for (const textItem of textContent.items) {
strBuf.push(textItem.str);
if (textItem.hasEOL) {
strBuf.push("\n");
}
}
[this._pageContents[i], this._pageDiffs[i], this._hasDiacritics[i]] = normalize(strBuf.join(""));
resolve();
}, reason => {
console.error(`Unable to get text content for page ${i + 1}`, reason);
this._pageContents[i] = "";
this._pageDiffs[i] = null;
this._hasDiacritics[i] = false;
resolve();
}));
}
}
#updatePage(index) {
if (this._scrollMatches && this._selected.pageIdx === index) {
this._linkService.page = index + 1;
}
this._eventBus.dispatch("updatetextlayermatches", {
source: this,
pageIndex: index
});
}
#updateAllPages() {
this._eventBus.dispatch("updatetextlayermatches", {
source: this,
pageIndex: -1
});
}
#nextMatch() {
const previous = this.#state.findPrevious;
const currentPageIndex = this._linkService.page - 1;
const numPages = this._linkService.pagesCount;
this._highlightMatches = true;
if (this._dirtyMatch) {
this._dirtyMatch = false;
this._selected.pageIdx = this._selected.matchIdx = -1;
this._offset.pageIdx = currentPageIndex;
this._offset.matchIdx = null;
this._offset.wrapped = false;
this._resumePageIdx = null;
this._pageMatches.length = 0;
this._pageMatchesLength.length = 0;
this.#visitedPagesCount = 0;
this._matchesCountTotal = 0;
this.#updateAllPages();
for (let i = 0; i < numPages; i++) {
if (this._pendingFindMatches.has(i)) {
continue;
}
this._pendingFindMatches.add(i);
this._extractTextPromises[i].then(() => {
this._pendingFindMatches.delete(i);
this.#calculateMatch(i);
});
}
}
const query = this.#query;
if (query.length === 0) {
this.#updateUIState(FindState.FOUND);
return;
}
if (this._resumePageIdx) {
return;
}
const offset = this._offset;
this._pagesToSearch = numPages;
if (offset.matchIdx !== null) {
const numPageMatches = this._pageMatches[offset.pageIdx].length;
if (!previous && offset.matchIdx + 1 < numPageMatches || previous && offset.matchIdx > 0) {
offset.matchIdx = previous ? offset.matchIdx - 1 : offset.matchIdx + 1;
this.#updateMatch(true);
return;
}
this.#advanceOffsetPage(previous);
}
this.#nextPageMatch();
}
#matchesReady(matches) {
const offset = this._offset;
const numMatches = matches.length;
const previous = this.#state.findPrevious;
if (numMatches) {
offset.matchIdx = previous ? numMatches - 1 : 0;
this.#updateMatch(true);
return true;
}
this.#advanceOffsetPage(previous);
if (offset.wrapped) {
offset.matchIdx = null;
if (this._pagesToSearch < 0) {
this.#updateMatch(false);
return true;
}
}
return false;
}
#nextPageMatch() {
if (this._resumePageIdx !== null) {
console.error("There can only be one pending page.");
}
let matches = null;
do {
const pageIdx = this._offset.pageIdx;
matches = this._pageMatches[pageIdx];
if (!matches) {
this._resumePageIdx = pageIdx;
break;
}
} while (!this.#matchesReady(matches));
}
#advanceOffsetPage(previous) {
const offset = this._offset;
const numPages = this._linkService.pagesCount;
offset.pageIdx = previous ? offset.pageIdx - 1 : offset.pageIdx + 1;
offset.matchIdx = null;
this._pagesToSearch--;
if (offset.pageIdx >= numPages || offset.pageIdx < 0) {
offset.pageIdx = previous ? numPages - 1 : 0;
offset.wrapped = true;
}
}
#updateMatch(found = false) {
let state = FindState.NOT_FOUND;
const wrapped = this._offset.wrapped;
this._offset.wrapped = false;
if (found) {
const previousPage = this._selected.pageIdx;
this._selected.pageIdx = this._offset.pageIdx;
this._selected.matchIdx = this._offset.matchIdx;
state = wrapped ? FindState.WRAPPED : FindState.FOUND;
if (previousPage !== -1 && previousPage !== this._selected.pageIdx) {
this.#updatePage(previousPage);
}
}
this.#updateUIState(state, this.#state.findPrevious);
if (this._selected.pageIdx !== -1) {
this._scrollMatches = true;
this.#updatePage(this._selected.pageIdx);
}
}
#onFindBarClose(evt) {
const pdfDocument = this._pdfDocument;
this._firstPageCapability.promise.then(() => {
if (!this._pdfDocument || pdfDocument && this._pdfDocument !== pdfDocument) {
return;
}
if (this._findTimeout) {
clearTimeout(this._findTimeout);
this._findTimeout = null;
}
if (this._resumePageIdx) {
this._resumePageIdx = null;
this._dirtyMatch = true;
}
this.#updateUIState(FindState.FOUND);
this._highlightMatches = false;
this.#updateAllPages();
});
}
#requestMatchesCount() {
const {
pageIdx,
matchIdx
} = this._selected;
let current = 0,
total = this._matchesCountTotal;
if (matchIdx !== -1) {
for (let i = 0; i < pageIdx; i++) {
current += this._pageMatches[i]?.length || 0;
}
current += matchIdx + 1;
}
if (current < 1 || current > total) {
current = total = 0;
}
return {
current,
total
};
}
#updateUIResultsCount() {
this._eventBus.dispatch("updatefindmatchescount", {
source: this,
matchesCount: this.#requestMatchesCount()
});
}
#updateUIState(state, previous = false) {
if (!this.#updateMatchesCountOnProgress && (this.#visitedPagesCount !== this._linkService.pagesCount || state === FindState.PENDING)) {
return;
}
this._eventBus.dispatch("updatefindcontrolstate", {
source: this,
state,
previous,
entireWord: this.#state?.entireWord ?? null,
matchesCount: this.#requestMatchesCount(),
rawQuery: this.#state?.query ?? null
});
}
}
;// ./web/pdf_find_bar.js
const MATCHES_COUNT_LIMIT = 1000;
class PDFFindBar {
#mainContainer;
#resizeObserver = new ResizeObserver(this.#resizeObserverCallback.bind(this));
constructor(options, mainContainer, eventBus) {
this.opened = false;
this.bar = options.bar;
this.toggleButton = options.toggleButton;
this.findField = options.findField;
this.highlightAll = options.highlightAllCheckbox;
this.caseSensitive = options.caseSensitiveCheckbox;
this.matchDiacritics = options.matchDiacriticsCheckbox;
this.entireWord = options.entireWordCheckbox;
this.findMsg = options.findMsg;
this.findResultsCount = options.findResultsCount;
this.findPreviousButton = options.findPreviousButton;
this.findNextButton = options.findNextButton;
this.eventBus = eventBus;
this.#mainContainer = mainContainer;
const checkedInputs = new Map([[this.highlightAll, "highlightallchange"], [this.caseSensitive, "casesensitivitychange"], [this.entireWord, "entirewordchange"], [this.matchDiacritics, "diacriticmatchingchange"]]);
this.toggleButton.addEventListener("click", () => {
this.toggle();
});
this.findField.addEventListener("input", () => {
this.dispatchEvent("");
});
this.bar.addEventListener("keydown", ({
keyCode,
shiftKey,
target
}) => {
switch (keyCode) {
case 13:
if (target === this.findField) {
this.dispatchEvent("again", shiftKey);
} else if (checkedInputs.has(target)) {
target.checked = !target.checked;
this.dispatchEvent(checkedInputs.get(target));
}
break;
case 27:
this.close();
break;
}
});
this.findPreviousButton.addEventListener("click", () => {
this.dispatchEvent("again", true);
});
this.findNextButton.addEventListener("click", () => {
this.dispatchEvent("again", false);
});
for (const [elem, evtName] of checkedInputs) {
elem.addEventListener("click", () => {
this.dispatchEvent(evtName);
});
}
}
reset() {
this.updateUIState();
}
dispatchEvent(type, findPrev = false) {
this.eventBus.dispatch("find", {
source: this,
type,
query: this.findField.value,
caseSensitive: this.caseSensitive.checked,
entireWord: this.entireWord.checked,
highlightAll: this.highlightAll.checked,
findPrevious: findPrev,
matchDiacritics: this.matchDiacritics.checked
});
}
updateUIState(state, previous, matchesCount) {
const {
findField,
findMsg
} = this;
let findMsgId = "",
status = "";
switch (state) {
case FindState.FOUND:
break;
case FindState.PENDING:
status = "pending";
break;
case FindState.NOT_FOUND:
findMsgId = "pdfjs-find-not-found";
status = "notFound";
break;
case FindState.WRAPPED:
findMsgId = previous ? "pdfjs-find-reached-top" : "pdfjs-find-reached-bottom";
break;
}
findField.setAttribute("data-status", status);
findField.setAttribute("aria-invalid", state === FindState.NOT_FOUND);
findMsg.setAttribute("data-status", status);
if (findMsgId) {
findMsg.setAttribute("data-l10n-id", findMsgId);
} else {
findMsg.removeAttribute("data-l10n-id");
findMsg.textContent = "";
}
this.updateResultsCount(matchesCount);
}
updateResultsCount({
current = 0,
total = 0
} = {}) {
const {
findResultsCount
} = this;
if (total > 0) {
const limit = MATCHES_COUNT_LIMIT;
findResultsCount.setAttribute("data-l10n-id", total > limit ? "pdfjs-find-match-count-limit" : "pdfjs-find-match-count");
findResultsCount.setAttribute("data-l10n-args", JSON.stringify({
limit,
current,
total
}));
} else {
findResultsCount.removeAttribute("data-l10n-id");
findResultsCount.textContent = "";
}
}
open() {
if (!this.opened) {
this.#resizeObserver.observe(this.#mainContainer);
this.#resizeObserver.observe(this.bar);
this.opened = true;
toggleExpandedBtn(this.toggleButton, true, this.bar);
}
this.findField.select();
this.findField.focus();
}
close() {
if (!this.opened) {
return;
}
this.#resizeObserver.disconnect();
this.opened = false;
toggleExpandedBtn(this.toggleButton, false, this.bar);
this.eventBus.dispatch("findbarclose", {
source: this
});
}
toggle() {
if (this.opened) {
this.close();
} else {
this.open();
}
}
#resizeObserverCallback() {
const {
bar
} = this;
bar.classList.remove("wrapContainers");
const findbarHeight = bar.clientHeight;
const inputContainerHeight = bar.firstElementChild.clientHeight;
if (findbarHeight > inputContainerHeight) {
bar.classList.add("wrapContainers");
}
}
}
;// ./web/pdf_history.js
const HASH_CHANGE_TIMEOUT = 1000;
const POSITION_UPDATED_THRESHOLD = 50;
const UPDATE_VIEWAREA_TIMEOUT = 1000;
function getCurrentHash() {
return document.location.hash;
}
class PDFHistory {
#eventAbortController = null;
constructor({
linkService,
eventBus
}) {
this.linkService = linkService;
this.eventBus = eventBus;
this._initialized = false;
this._fingerprint = "";
this.reset();
this.eventBus._on("pagesinit", () => {
this._isPagesLoaded = false;
this.eventBus._on("pagesloaded", evt => {
this._isPagesLoaded = !!evt.pagesCount;
}, {
once: true
});
});
}
initialize({
fingerprint,
resetHistory = false,
updateUrl = false
}) {
if (!fingerprint || typeof fingerprint !== "string") {
console.error('PDFHistory.initialize: The "fingerprint" must be a non-empty string.');
return;
}
if (this._initialized) {
this.reset();
}
const reInitialized = this._fingerprint !== "" && this._fingerprint !== fingerprint;
this._fingerprint = fingerprint;
this._updateUrl = updateUrl === true;
this._initialized = true;
this.#bindEvents();
const state = window.history.state;
this._popStateInProgress = false;
this._blockHashChange = 0;
this._currentHash = getCurrentHash();
this._numPositionUpdates = 0;
this._uid = this._maxUid = 0;
this._destination = null;
this._position = null;
if (!this.#isValidState(state, true) || resetHistory) {
const {
hash,
page,
rotation
} = this.#parseCurrentHash(true);
if (!hash || reInitialized || resetHistory) {
this.#pushOrReplaceState(null, true);
return;
}
this.#pushOrReplaceState({
hash,
page,
rotation
}, true);
return;
}
const destination = state.destination;
this.#updateInternalState(destination, state.uid, true);
if (destination.rotation !== undefined) {
this._initialRotation = destination.rotation;
}
if (destination.dest) {
this._initialBookmark = JSON.stringify(destination.dest);
this._destination.page = null;
} else if (destination.hash) {
this._initialBookmark = destination.hash;
} else if (destination.page) {
this._initialBookmark = `page=${destination.page}`;
}
}
reset() {
if (this._initialized) {
this.#pageHide();
this._initialized = false;
this.#unbindEvents();
}
if (this._updateViewareaTimeout) {
clearTimeout(this._updateViewareaTimeout);
this._updateViewareaTimeout = null;
}
this._initialBookmark = null;
this._initialRotation = null;
}
push({
namedDest = null,
explicitDest,
pageNumber
}) {
if (!this._initialized) {
return;
}
if (namedDest && typeof namedDest !== "string") {
console.error("PDFHistory.push: " + `"${namedDest}" is not a valid namedDest parameter.`);
return;
} else if (!Array.isArray(explicitDest)) {
console.error("PDFHistory.push: " + `"${explicitDest}" is not a valid explicitDest parameter.`);
return;
} else if (!this.#isValidPage(pageNumber)) {
if (pageNumber !== null || this._destination) {
console.error("PDFHistory.push: " + `"${pageNumber}" is not a valid pageNumber parameter.`);
return;
}
}
const hash = namedDest || JSON.stringify(explicitDest);
if (!hash) {
return;
}
let forceReplace = false;
if (this._destination && (isDestHashesEqual(this._destination.hash, hash) || isDestArraysEqual(this._destination.dest, explicitDest))) {
if (this._destination.page) {
return;
}
forceReplace = true;
}
if (this._popStateInProgress && !forceReplace) {
return;
}
this.#pushOrReplaceState({
dest: explicitDest,
hash,
page: pageNumber,
rotation: this.linkService.rotation
}, forceReplace);
if (!this._popStateInProgress) {
this._popStateInProgress = true;
Promise.resolve().then(() => {
this._popStateInProgress = false;
});
}
}
pushPage(pageNumber) {
if (!this._initialized) {
return;
}
if (!this.#isValidPage(pageNumber)) {
console.error(`PDFHistory.pushPage: "${pageNumber}" is not a valid page number.`);
return;
}
if (this._destination?.page === pageNumber) {
return;
}
if (this._popStateInProgress) {
return;
}
this.#pushOrReplaceState({
dest: null,
hash: `page=${pageNumber}`,
page: pageNumber,
rotation: this.linkService.rotation
});
if (!this._popStateInProgress) {
this._popStateInProgress = true;
Promise.resolve().then(() => {
this._popStateInProgress = false;
});
}
}
pushCurrentPosition() {
if (!this._initialized || this._popStateInProgress) {
return;
}
this.#tryPushCurrentPosition();
}
back() {
if (!this._initialized || this._popStateInProgress) {
return;
}
const state = window.history.state;
if (this.#isValidState(state) && state.uid > 0) {
window.history.back();
}
}
forward() {
if (!this._initialized || this._popStateInProgress) {
return;
}
const state = window.history.state;
if (this.#isValidState(state) && state.uid < this._maxUid) {
window.history.forward();
}
}
get popStateInProgress() {
return this._initialized && (this._popStateInProgress || this._blockHashChange > 0);
}
get initialBookmark() {
return this._initialized ? this._initialBookmark : null;
}
get initialRotation() {
return this._initialized ? this._initialRotation : null;
}
#pushOrReplaceState(destination, forceReplace = false) {
const shouldReplace = forceReplace || !this._destination;
const newState = {
fingerprint: this._fingerprint,
uid: shouldReplace ? this._uid : this._uid + 1,
destination
};
this.#updateInternalState(destination, newState.uid);
let newUrl;
if (this._updateUrl && destination?.hash) {
const baseUrl = document.location.href.split("#", 1)[0];
if (!baseUrl.startsWith("file://")) {
newUrl = `${baseUrl}#${destination.hash}`;
}
}
if (shouldReplace) {
window.history.replaceState(newState, "", newUrl);
} else {
window.history.pushState(newState, "", newUrl);
}
}
#tryPushCurrentPosition(temporary = false) {
if (!this._position) {
return;
}
let position = this._position;
if (temporary) {
position = Object.assign(Object.create(null), this._position);
position.temporary = true;
}
if (!this._destination) {
this.#pushOrReplaceState(position);
return;
}
if (this._destination.temporary) {
this.#pushOrReplaceState(position, true);
return;
}
if (this._destination.hash === position.hash) {
return;
}
if (!this._destination.page && (POSITION_UPDATED_THRESHOLD <= 0 || this._numPositionUpdates <= POSITION_UPDATED_THRESHOLD)) {
return;
}
let forceReplace = false;
if (this._destination.page >= position.first && this._destination.page <= position.page) {
if (this._destination.dest !== undefined || !this._destination.first) {
return;
}
forceReplace = true;
}
this.#pushOrReplaceState(position, forceReplace);
}
#isValidPage(val) {
return Number.isInteger(val) && val > 0 && val <= this.linkService.pagesCount;
}
#isValidState(state, checkReload = false) {
if (!state) {
return false;
}
if (state.fingerprint !== this._fingerprint) {
if (checkReload) {
if (typeof state.fingerprint !== "string" || state.fingerprint.length !== this._fingerprint.length) {
return false;
}
const [perfEntry] = performance.getEntriesByType("navigation");
if (perfEntry?.type !== "reload") {
return false;
}
} else {
return false;
}
}
if (!Number.isInteger(state.uid) || state.uid < 0) {
return false;
}
if (state.destination === null || typeof state.destination !== "object") {
return false;
}
return true;
}
#updateInternalState(destination, uid, removeTemporary = false) {
if (this._updateViewareaTimeout) {
clearTimeout(this._updateViewareaTimeout);
this._updateViewareaTimeout = null;
}
if (removeTemporary && destination?.temporary) {
delete destination.temporary;
}
this._destination = destination;
this._uid = uid;
this._maxUid = Math.max(this._maxUid, uid);
this._numPositionUpdates = 0;
}
#parseCurrentHash(checkNameddest = false) {
const hash = unescape(getCurrentHash()).substring(1);
const params = parseQueryString(hash);
const nameddest = params.get("nameddest") || "";
let page = params.get("page") | 0;
if (!this.#isValidPage(page) || checkNameddest && nameddest.length > 0) {
page = null;
}
return {
hash,
page,
rotation: this.linkService.rotation
};
}
#updateViewarea({
location
}) {
if (this._updateViewareaTimeout) {
clearTimeout(this._updateViewareaTimeout);
this._updateViewareaTimeout = null;
}
this._position = {
hash: location.pdfOpenParams.substring(1),
page: this.linkService.page,
first: location.pageNumber,
rotation: location.rotation
};
if (this._popStateInProgress) {
return;
}
if (POSITION_UPDATED_THRESHOLD > 0 && this._isPagesLoaded && this._destination && !this._destination.page) {
this._numPositionUpdates++;
}
if (UPDATE_VIEWAREA_TIMEOUT > 0) {
this._updateViewareaTimeout = setTimeout(() => {
if (!this._popStateInProgress) {
this.#tryPushCurrentPosition(true);
}
this._updateViewareaTimeout = null;
}, UPDATE_VIEWAREA_TIMEOUT);
}
}
#popState({
state
}) {
const newHash = getCurrentHash(),
hashChanged = this._currentHash !== newHash;
this._currentHash = newHash;
if (!state) {
this._uid++;
const {
hash,
page,
rotation
} = this.#parseCurrentHash();
this.#pushOrReplaceState({
hash,
page,
rotation
}, true);
return;
}
if (!this.#isValidState(state)) {
return;
}
this._popStateInProgress = true;
if (hashChanged) {
this._blockHashChange++;
waitOnEventOrTimeout({
target: window,
name: "hashchange",
delay: HASH_CHANGE_TIMEOUT
}).then(() => {
this._blockHashChange--;
});
}
const destination = state.destination;
this.#updateInternalState(destination, state.uid, true);
if (isValidRotation(destination.rotation)) {
this.linkService.rotation = destination.rotation;
}
if (destination.dest) {
this.linkService.goToDestination(destination.dest);
} else if (destination.hash) {
this.linkService.setHash(destination.hash);
} else if (destination.page) {
this.linkService.page = destination.page;
}
Promise.resolve().then(() => {
this._popStateInProgress = false;
});
}
#pageHide() {
if (!this._destination || this._destination.temporary) {
this.#tryPushCurrentPosition();
}
}
#bindEvents() {
if (this.#eventAbortController) {
return;
}
this.#eventAbortController = new AbortController();
const {
signal
} = this.#eventAbortController;
this.eventBus._on("updateviewarea", this.#updateViewarea.bind(this), {
signal
});
window.addEventListener("popstate", this.#popState.bind(this), {
signal
});
window.addEventListener("pagehide", this.#pageHide.bind(this), {
signal
});
}
#unbindEvents() {
this.#eventAbortController?.abort();
this.#eventAbortController = null;
}
}
function isDestHashesEqual(destHash, pushHash) {
if (typeof destHash !== "string" || typeof pushHash !== "string") {
return false;
}
if (destHash === pushHash) {
return true;
}
const nameddest = parseQueryString(destHash).get("nameddest");
if (nameddest === pushHash) {
return true;
}
return false;
}
function isDestArraysEqual(firstDest, secondDest) {
function isEntryEqual(first, second) {
if (typeof first !== typeof second) {
return false;
}
if (Array.isArray(first) || Array.isArray(second)) {
return false;
}
if (first !== null && typeof first === "object" && second !== null) {
if (Object.keys(first).length !== Object.keys(second).length) {
return false;
}
for (const key in first) {
if (!isEntryEqual(first[key], second[key])) {
return false;
}
}
return true;
}
return first === second || Number.isNaN(first) && Number.isNaN(second);
}
if (!(Array.isArray(firstDest) && Array.isArray(secondDest))) {
return false;
}
if (firstDest.length !== secondDest.length) {
return false;
}
for (let i = 0, ii = firstDest.length; i < ii; i++) {
if (!isEntryEqual(firstDest[i], secondDest[i])) {
return false;
}
}
return true;
}
;// ./web/pdf_layer_viewer.js
class PDFLayerViewer extends BaseTreeViewer {
constructor(options) {
super(options);
this.eventBus._on("optionalcontentconfigchanged", evt => {
this.#updateLayers(evt.promise);
});
this.eventBus._on("resetlayers", () => {
this.#updateLayers();
});
this.eventBus._on("togglelayerstree", this._toggleAllTreeItems.bind(this));
}
reset() {
super.reset();
this._optionalContentConfig = null;
this._optionalContentVisibility?.clear();
this._optionalContentVisibility = null;
}
_dispatchEvent(layersCount) {
this.eventBus.dispatch("layersloaded", {
source: this,
layersCount
});
}
_bindLink(element, {
groupId,
input
}) {
const setVisibility = () => {
const visible = input.checked;
this._optionalContentConfig.setVisibility(groupId, visible);
const cached = this._optionalContentVisibility.get(groupId);
if (cached) {
cached.visible = visible;
}
this.eventBus.dispatch("optionalcontentconfig", {
source: this,
promise: Promise.resolve(this._optionalContentConfig)
});
};
element.onclick = evt => {
if (evt.target === input) {
setVisibility();
return true;
} else if (evt.target !== element) {
return true;
}
input.checked = !input.checked;
setVisibility();
return false;
};
}
_setNestedName(element, {
name = null
}) {
if (typeof name === "string") {
element.textContent = this._normalizeTextContent(name);
return;
}
element.setAttribute("data-l10n-id", "pdfjs-additional-layers");
element.style.fontStyle = "italic";
this._l10n.translateOnce(element);
}
_addToggleButton(div, {
name = null
}) {
super._addToggleButton(div, name === null);
}
_toggleAllTreeItems() {
if (!this._optionalContentConfig) {
return;
}
super._toggleAllTreeItems();
}
render({
optionalContentConfig,
pdfDocument
}) {
if (this._optionalContentConfig) {
this.reset();
}
this._optionalContentConfig = optionalContentConfig || null;
this._pdfDocument = pdfDocument || null;
const groups = optionalContentConfig?.getOrder();
if (!groups) {
this._dispatchEvent(0);
return;
}
this._optionalContentVisibility = new Map();
const fragment = document.createDocumentFragment(),
queue = [{
parent: fragment,
groups
}];
let layersCount = 0,
hasAnyNesting = false;
while (queue.length > 0) {
const levelData = queue.shift();
for (const groupId of levelData.groups) {
const div = document.createElement("div");
div.className = "treeItem";
const element = document.createElement("a");
div.append(element);
if (typeof groupId === "object") {
hasAnyNesting = true;
this._addToggleButton(div, groupId);
this._setNestedName(element, groupId);
const itemsDiv = document.createElement("div");
itemsDiv.className = "treeItems";
div.append(itemsDiv);
queue.push({
parent: itemsDiv,
groups: groupId.order
});
} else {
const group = optionalContentConfig.getGroup(groupId);
const input = document.createElement("input");
this._bindLink(element, {
groupId,
input
});
input.type = "checkbox";
input.checked = group.visible;
this._optionalContentVisibility.set(groupId, {
input,
visible: input.checked
});
const label = document.createElement("label");
label.textContent = this._normalizeTextContent(group.name);
label.append(input);
element.append(label);
layersCount++;
}
levelData.parent.append(div);
}
}
this._finishRendering(fragment, layersCount, hasAnyNesting);
}
async #updateLayers(promise = null) {
if (!this._optionalContentConfig) {
return;
}
const pdfDocument = this._pdfDocument;
const optionalContentConfig = await (promise || pdfDocument.getOptionalContentConfig({
intent: "display"
}));
if (pdfDocument !== this._pdfDocument) {
return;
}
if (promise) {
for (const [groupId, cached] of this._optionalContentVisibility) {
const group = optionalContentConfig.getGroup(groupId);
if (group && cached.visible !== group.visible) {
cached.input.checked = cached.visible = !cached.visible;
}
}
return;
}
this.eventBus.dispatch("optionalcontentconfig", {
source: this,
promise: Promise.resolve(optionalContentConfig)
});
this.render({
optionalContentConfig,
pdfDocument: this._pdfDocument
});
}
}
;// ./web/pdf_outline_viewer.js
class PDFOutlineViewer extends BaseTreeViewer {
constructor(options) {
super(options);
this.linkService = options.linkService;
this.downloadManager = options.downloadManager;
this.eventBus._on("toggleoutlinetree", this._toggleAllTreeItems.bind(this));
this.eventBus._on("currentoutlineitem", this._currentOutlineItem.bind(this));
this.eventBus._on("pagechanging", evt => {
this._currentPageNumber = evt.pageNumber;
});
this.eventBus._on("pagesloaded", evt => {
this._isPagesLoaded = !!evt.pagesCount;
this._currentOutlineItemCapability?.resolve(this._isPagesLoaded);
});
this.eventBus._on("sidebarviewchanged", evt => {
this._sidebarView = evt.view;
});
}
reset() {
super.reset();
this._outline = null;
this._pageNumberToDestHashCapability = null;
this._currentPageNumber = 1;
this._isPagesLoaded = null;
this._currentOutlineItemCapability?.resolve(false);
this._currentOutlineItemCapability = null;
}
_dispatchEvent(outlineCount) {
this._currentOutlineItemCapability = Promise.withResolvers();
if (outlineCount === 0 || this._pdfDocument?.loadingParams.disableAutoFetch) {
this._currentOutlineItemCapability.resolve(false);
} else if (this._isPagesLoaded !== null) {
this._currentOutlineItemCapability.resolve(this._isPagesLoaded);
}
this.eventBus.dispatch("outlineloaded", {
source: this,
outlineCount,
currentOutlineItemPromise: this._currentOutlineItemCapability.promise
});
}
_bindLink(element, {
url,
newWindow,
action,
attachment,
dest,
setOCGState
}) {
const {
linkService
} = this;
if (url) {
linkService.addLinkAttributes(element, url, newWindow);
return;
}
if (action) {
element.href = linkService.getAnchorUrl("");
element.onclick = () => {
linkService.executeNamedAction(action);
return false;
};
return;
}
if (attachment) {
element.href = linkService.getAnchorUrl("");
element.onclick = () => {
this.downloadManager.openOrDownloadData(attachment.content, attachment.filename);
return false;
};
return;
}
if (setOCGState) {
element.href = linkService.getAnchorUrl("");
element.onclick = () => {
linkService.executeSetOCGState(setOCGState);
return false;
};
return;
}
element.href = linkService.getDestinationHash(dest);
element.onclick = evt => {
this._updateCurrentTreeItem(evt.target.parentNode);
if (dest) {
linkService.goToDestination(dest);
}
return false;
};
}
_setStyles(element, {
bold,
italic
}) {
if (bold) {
element.style.fontWeight = "bold";
}
if (italic) {
element.style.fontStyle = "italic";
}
}
_addToggleButton(div, {
count,
items
}) {
let hidden = false;
if (count < 0) {
let totalCount = items.length;
if (totalCount > 0) {
const queue = [...items];
while (queue.length > 0) {
const {
count: nestedCount,
items: nestedItems
} = queue.shift();
if (nestedCount > 0 && nestedItems.length > 0) {
totalCount += nestedItems.length;
queue.push(...nestedItems);
}
}
}
if (Math.abs(count) === totalCount) {
hidden = true;
}
}
super._addToggleButton(div, hidden);
}
_toggleAllTreeItems() {
if (!this._outline) {
return;
}
super._toggleAllTreeItems();
}
render({
outline,
pdfDocument
}) {
if (this._outline) {
this.reset();
}
this._outline = outline || null;
this._pdfDocument = pdfDocument || null;
if (!outline) {
this._dispatchEvent(0);
return;
}
const fragment = document.createDocumentFragment();
const queue = [{
parent: fragment,
items: outline
}];
let outlineCount = 0,
hasAnyNesting = false;
while (queue.length > 0) {
const levelData = queue.shift();
for (const item of levelData.items) {
const div = document.createElement("div");
div.className = "treeItem";
const element = document.createElement("a");
this._bindLink(element, item);
this._setStyles(element, item);
element.textContent = this._normalizeTextContent(item.title);
div.append(element);
if (item.items.length > 0) {
hasAnyNesting = true;
this._addToggleButton(div, item);
const itemsDiv = document.createElement("div");
itemsDiv.className = "treeItems";
div.append(itemsDiv);
queue.push({
parent: itemsDiv,
items: item.items
});
}
levelData.parent.append(div);
outlineCount++;
}
}
this._finishRendering(fragment, outlineCount, hasAnyNesting);
}
async _currentOutlineItem() {
if (!this._isPagesLoaded) {
throw new Error("_currentOutlineItem: All pages have not been loaded.");
}
if (!this._outline || !this._pdfDocument) {
return;
}
const pageNumberToDestHash = await this._getPageNumberToDestHash(this._pdfDocument);
if (!pageNumberToDestHash) {
return;
}
this._updateCurrentTreeItem(null);
if (this._sidebarView !== SidebarView.OUTLINE) {
return;
}
for (let i = this._currentPageNumber; i > 0; i--) {
const destHash = pageNumberToDestHash.get(i);
if (!destHash) {
continue;
}
const linkElement = this.container.querySelector(`a[href="${destHash}"]`);
if (!linkElement) {
continue;
}
this._scrollToCurrentTreeItem(linkElement.parentNode);
break;
}
}
async _getPageNumberToDestHash(pdfDocument) {
if (this._pageNumberToDestHashCapability) {
return this._pageNumberToDestHashCapability.promise;
}
this._pageNumberToDestHashCapability = Promise.withResolvers();
const pageNumberToDestHash = new Map(),
pageNumberNesting = new Map();
const queue = [{
nesting: 0,
items: this._outline
}];
while (queue.length > 0) {
const levelData = queue.shift(),
currentNesting = levelData.nesting;
for (const {
dest,
items
} of levelData.items) {
let explicitDest, pageNumber;
if (typeof dest === "string") {
explicitDest = await pdfDocument.getDestination(dest);
if (pdfDocument !== this._pdfDocument) {
return null;
}
} else {
explicitDest = dest;
}
if (Array.isArray(explicitDest)) {
const [destRef] = explicitDest;
if (destRef && typeof destRef === "object") {
pageNumber = pdfDocument.cachedPageNumber(destRef);
} else if (Number.isInteger(destRef)) {
pageNumber = destRef + 1;
}
if (Number.isInteger(pageNumber) && (!pageNumberToDestHash.has(pageNumber) || currentNesting > pageNumberNesting.get(pageNumber))) {
const destHash = this.linkService.getDestinationHash(dest);
pageNumberToDestHash.set(pageNumber, destHash);
pageNumberNesting.set(pageNumber, currentNesting);
}
}
if (items.length > 0) {
queue.push({
nesting: currentNesting + 1,
items
});
}
}
}
this._pageNumberToDestHashCapability.resolve(pageNumberToDestHash.size > 0 ? pageNumberToDestHash : null);
return this._pageNumberToDestHashCapability.promise;
}
}
;// ./web/pdf_presentation_mode.js
const DELAY_BEFORE_HIDING_CONTROLS = 3000;
const ACTIVE_SELECTOR = "pdfPresentationMode";
const CONTROLS_SELECTOR = "pdfPresentationModeControls";
const MOUSE_SCROLL_COOLDOWN_TIME = 50;
const PAGE_SWITCH_THRESHOLD = 0.1;
const SWIPE_MIN_DISTANCE_THRESHOLD = 50;
const SWIPE_ANGLE_THRESHOLD = Math.PI / 6;
class PDFPresentationMode {
#state = PresentationModeState.UNKNOWN;
#args = null;
#fullscreenChangeAbortController = null;
#windowAbortController = null;
constructor({
container,
pdfViewer,
eventBus
}) {
this.container = container;
this.pdfViewer = pdfViewer;
this.eventBus = eventBus;
this.contextMenuOpen = false;
this.mouseScrollTimeStamp = 0;
this.mouseScrollDelta = 0;
this.touchSwipeState = null;
}
async request() {
const {
container,
pdfViewer
} = this;
if (this.active || !pdfViewer.pagesCount || !container.requestFullscreen) {
return false;
}
this.#addFullscreenChangeListeners();
this.#notifyStateChange(PresentationModeState.CHANGING);
const promise = container.requestFullscreen();
this.#args = {
pageNumber: pdfViewer.currentPageNumber,
scaleValue: pdfViewer.currentScaleValue,
scrollMode: pdfViewer.scrollMode,
spreadMode: null,
annotationEditorMode: null
};
if (pdfViewer.spreadMode !== SpreadMode.NONE && !(pdfViewer.pageViewsReady && pdfViewer.hasEqualPageSizes)) {
console.warn("Ignoring Spread modes when entering PresentationMode, " + "since the document may contain varying page sizes.");
this.#args.spreadMode = pdfViewer.spreadMode;
}
if (pdfViewer.annotationEditorMode !== AnnotationEditorType.DISABLE) {
this.#args.annotationEditorMode = pdfViewer.annotationEditorMode;
}
try {
await promise;
pdfViewer.focus();
return true;
} catch {
this.#removeFullscreenChangeListeners();
this.#notifyStateChange(PresentationModeState.NORMAL);
}
return false;
}
get active() {
return this.#state === PresentationModeState.CHANGING || this.#state === PresentationModeState.FULLSCREEN;
}
#mouseWheel(evt) {
if (!this.active) {
return;
}
evt.preventDefault();
const delta = normalizeWheelEventDelta(evt);
const currentTime = Date.now();
const storedTime = this.mouseScrollTimeStamp;
if (currentTime > storedTime && currentTime - storedTime < MOUSE_SCROLL_COOLDOWN_TIME) {
return;
}
if (this.mouseScrollDelta > 0 && delta < 0 || this.mouseScrollDelta < 0 && delta > 0) {
this.#resetMouseScrollState();
}
this.mouseScrollDelta += delta;
if (Math.abs(this.mouseScrollDelta) >= PAGE_SWITCH_THRESHOLD) {
const totalDelta = this.mouseScrollDelta;
this.#resetMouseScrollState();
const success = totalDelta > 0 ? this.pdfViewer.previousPage() : this.pdfViewer.nextPage();
if (success) {
this.mouseScrollTimeStamp = currentTime;
}
}
}
#notifyStateChange(state) {
this.#state = state;
this.eventBus.dispatch("presentationmodechanged", {
source: this,
state
});
}
#enter() {
this.#notifyStateChange(PresentationModeState.FULLSCREEN);
this.container.classList.add(ACTIVE_SELECTOR);
setTimeout(() => {
this.pdfViewer.scrollMode = ScrollMode.PAGE;
if (this.#args.spreadMode !== null) {
this.pdfViewer.spreadMode = SpreadMode.NONE;
}
this.pdfViewer.currentPageNumber = this.#args.pageNumber;
this.pdfViewer.currentScaleValue = "page-fit";
if (this.#args.annotationEditorMode !== null) {
this.pdfViewer.annotationEditorMode = {
mode: AnnotationEditorType.NONE
};
}
}, 0);
this.#addWindowListeners();
this.#showControls();
this.contextMenuOpen = false;
document.getSelection().empty();
}
#exit() {
const pageNumber = this.pdfViewer.currentPageNumber;
this.container.classList.remove(ACTIVE_SELECTOR);
setTimeout(() => {
this.#removeFullscreenChangeListeners();
this.#notifyStateChange(PresentationModeState.NORMAL);
this.pdfViewer.scrollMode = this.#args.scrollMode;
if (this.#args.spreadMode !== null) {
this.pdfViewer.spreadMode = this.#args.spreadMode;
}
this.pdfViewer.currentScaleValue = this.#args.scaleValue;
this.pdfViewer.currentPageNumber = pageNumber;
if (this.#args.annotationEditorMode !== null) {
this.pdfViewer.annotationEditorMode = {
mode: this.#args.annotationEditorMode
};
}
this.#args = null;
}, 0);
this.#removeWindowListeners();
this.#hideControls();
this.#resetMouseScrollState();
this.contextMenuOpen = false;
}
#mouseDown(evt) {
if (this.contextMenuOpen) {
this.contextMenuOpen = false;
evt.preventDefault();
return;
}
if (evt.button !== 0) {
return;
}
if (evt.target.href && evt.target.parentNode?.hasAttribute("data-internal-link")) {
return;
}
evt.preventDefault();
if (evt.shiftKey) {
this.pdfViewer.previousPage();
} else {
this.pdfViewer.nextPage();
}
}
#contextMenu() {
this.contextMenuOpen = true;
}
#showControls() {
if (this.controlsTimeout) {
clearTimeout(this.controlsTimeout);
} else {
this.container.classList.add(CONTROLS_SELECTOR);
}
this.controlsTimeout = setTimeout(() => {
this.container.classList.remove(CONTROLS_SELECTOR);
delete this.controlsTimeout;
}, DELAY_BEFORE_HIDING_CONTROLS);
}
#hideControls() {
if (!this.controlsTimeout) {
return;
}
clearTimeout(this.controlsTimeout);
this.container.classList.remove(CONTROLS_SELECTOR);
delete this.controlsTimeout;
}
#resetMouseScrollState() {
this.mouseScrollTimeStamp = 0;
this.mouseScrollDelta = 0;
}
#touchSwipe(evt) {
if (!this.active) {
return;
}
if (evt.touches.length > 1) {
this.touchSwipeState = null;
return;
}
switch (evt.type) {
case "touchstart":
this.touchSwipeState = {
startX: evt.touches[0].pageX,
startY: evt.touches[0].pageY,
endX: evt.touches[0].pageX,
endY: evt.touches[0].pageY
};
break;
case "touchmove":
if (this.touchSwipeState === null) {
return;
}
this.touchSwipeState.endX = evt.touches[0].pageX;
this.touchSwipeState.endY = evt.touches[0].pageY;
evt.preventDefault();
break;
case "touchend":
if (this.touchSwipeState === null) {
return;
}
let delta = 0;
const dx = this.touchSwipeState.endX - this.touchSwipeState.startX;
const dy = this.touchSwipeState.endY - this.touchSwipeState.startY;
const absAngle = Math.abs(Math.atan2(dy, dx));
if (Math.abs(dx) > SWIPE_MIN_DISTANCE_THRESHOLD && (absAngle <= SWIPE_ANGLE_THRESHOLD || absAngle >= Math.PI - SWIPE_ANGLE_THRESHOLD)) {
delta = dx;
} else if (Math.abs(dy) > SWIPE_MIN_DISTANCE_THRESHOLD && Math.abs(absAngle - Math.PI / 2) <= SWIPE_ANGLE_THRESHOLD) {
delta = dy;
}
if (delta > 0) {
this.pdfViewer.previousPage();
} else if (delta < 0) {
this.pdfViewer.nextPage();
}
break;
}
}
#addWindowListeners() {
if (this.#windowAbortController) {
return;
}
this.#windowAbortController = new AbortController();
const {
signal
} = this.#windowAbortController;
const touchSwipeBind = this.#touchSwipe.bind(this);
window.addEventListener("mousemove", this.#showControls.bind(this), {
signal
});
window.addEventListener("mousedown", this.#mouseDown.bind(this), {
signal
});
window.addEventListener("wheel", this.#mouseWheel.bind(this), {
passive: false,
signal
});
window.addEventListener("keydown", this.#resetMouseScrollState.bind(this), {
signal
});
window.addEventListener("contextmenu", this.#contextMenu.bind(this), {
signal
});
window.addEventListener("touchstart", touchSwipeBind, {
signal
});
window.addEventListener("touchmove", touchSwipeBind, {
signal
});
window.addEventListener("touchend", touchSwipeBind, {
signal
});
}
#removeWindowListeners() {
this.#windowAbortController?.abort();
this.#windowAbortController = null;
}
#addFullscreenChangeListeners() {
if (this.#fullscreenChangeAbortController) {
return;
}
this.#fullscreenChangeAbortController = new AbortController();
window.addEventListener("fullscreenchange", () => {
if (document.fullscreenElement) {
this.#enter();
} else {
this.#exit();
}
}, {
signal: this.#fullscreenChangeAbortController.signal
});
}
#removeFullscreenChangeListeners() {
this.#fullscreenChangeAbortController?.abort();
this.#fullscreenChangeAbortController = null;
}
}
;// ./web/xfa_layer_builder.js
class XfaLayerBuilder {
constructor({
pdfPage,
annotationStorage = null,
linkService,
xfaHtml = null
}) {
this.pdfPage = pdfPage;
this.annotationStorage = annotationStorage;
this.linkService = linkService;
this.xfaHtml = xfaHtml;
this.div = null;
this._cancelled = false;
}
async render({
viewport,
intent = "display"
}) {
if (intent === "print") {
const parameters = {
viewport: viewport.clone({
dontFlip: true
}),
div: this.div,
xfaHtml: this.xfaHtml,
annotationStorage: this.annotationStorage,
linkService: this.linkService,
intent
};
this.div = document.createElement("div");
parameters.div = this.div;
return XfaLayer.render(parameters);
}
const xfaHtml = await this.pdfPage.getXfa();
if (this._cancelled || !xfaHtml) {
return {
textDivs: []
};
}
const parameters = {
viewport: viewport.clone({
dontFlip: true
}),
div: this.div,
xfaHtml,
annotationStorage: this.annotationStorage,
linkService: this.linkService,
intent
};
if (this.div) {
return XfaLayer.update(parameters);
}
this.div = document.createElement("div");
parameters.div = this.div;
return XfaLayer.render(parameters);
}
cancel() {
this._cancelled = true;
}
hide() {
if (!this.div) {
return;
}
this.div.hidden = true;
}
}
;// ./web/print_utils.js
function getXfaHtmlForPrinting(printContainer, pdfDocument) {
const xfaHtml = pdfDocument.allXfaHtml;
const linkService = new SimpleLinkService();
const scale = Math.round(PixelsPerInch.PDF_TO_CSS_UNITS * 100) / 100;
for (const xfaPage of xfaHtml.children) {
const page = document.createElement("div");
page.className = "xfaPrintedPage";
printContainer.append(page);
const builder = new XfaLayerBuilder({
pdfPage: null,
annotationStorage: pdfDocument.annotationStorage,
linkService,
xfaHtml: xfaPage
});
const viewport = getXfaPageViewport(xfaPage, {
scale
});
builder.render(viewport, "print");
page.append(builder.div);
}
}
;// ./web/pdf_print_service.js
let activeService = null;
let dialog = null;
let overlayManager = null;
let viewerApp = {
initialized: false
};
function renderPage(activeServiceOnEntry, pdfDocument, pageNumber, size, printResolution, optionalContentConfigPromise, printAnnotationStoragePromise) {
const scratchCanvas = activeService.scratchCanvas;
const PRINT_UNITS = printResolution / PixelsPerInch.PDF;
scratchCanvas.width = Math.floor(size.width * PRINT_UNITS);
scratchCanvas.height = Math.floor(size.height * PRINT_UNITS);
const ctx = scratchCanvas.getContext("2d");
ctx.save();
ctx.fillStyle = "rgb(255, 255, 255)";
ctx.fillRect(0, 0, scratchCanvas.width, scratchCanvas.height);
ctx.restore();
return Promise.all([pdfDocument.getPage(pageNumber), printAnnotationStoragePromise]).then(function ([pdfPage, printAnnotationStorage]) {
const renderContext = {
canvasContext: ctx,
transform: [PRINT_UNITS, 0, 0, PRINT_UNITS, 0, 0],
viewport: pdfPage.getViewport({
scale: 1,
rotation: size.rotation
}),
intent: "print",
annotationMode: AnnotationMode.ENABLE_STORAGE,
optionalContentConfigPromise,
printAnnotationStorage
};
const renderTask = pdfPage.render(renderContext);
return renderTask.promise.catch(reason => {
if (!(reason instanceof RenderingCancelledException)) {
console.error(reason);
}
throw reason;
});
});
}
class PDFPrintService {
constructor({
pdfDocument,
pagesOverview,
printContainer,
printResolution,
printAnnotationStoragePromise = null
}) {
this.pdfDocument = pdfDocument;
this.pagesOverview = pagesOverview;
this.printContainer = printContainer;
this._printResolution = printResolution || 150;
this._optionalContentConfigPromise = pdfDocument.getOptionalContentConfig({
intent: "print"
});
this._printAnnotationStoragePromise = printAnnotationStoragePromise || Promise.resolve();
this.currentPage = -1;
this.scratchCanvas = document.createElement("canvas");
}
layout() {
this.throwIfInactive();
const body = document.querySelector("body");
body.setAttribute("data-pdfjsprinting", true);
const {
width,
height
} = this.pagesOverview[0];
const hasEqualPageSizes = this.pagesOverview.every(size => size.width === width && size.height === height);
if (!hasEqualPageSizes) {
console.warn("Not all pages have the same size. The printed result may be incorrect!");
}
this.pageStyleSheet = document.createElement("style");
this.pageStyleSheet.textContent = `@page { size: ${width}pt ${height}pt;}`;
body.append(this.pageStyleSheet);
}
destroy() {
if (activeService !== this) {
return;
}
this.printContainer.textContent = "";
const body = document.querySelector("body");
body.removeAttribute("data-pdfjsprinting");
if (this.pageStyleSheet) {
this.pageStyleSheet.remove();
this.pageStyleSheet = null;
}
this.scratchCanvas.width = this.scratchCanvas.height = 0;
this.scratchCanvas = null;
activeService = null;
ensureOverlay().then(function () {
overlayManager.closeIfActive(dialog);
});
}
renderPages() {
if (this.pdfDocument.isPureXfa) {
getXfaHtmlForPrinting(this.printContainer, this.pdfDocument);
return Promise.resolve();
}
const pageCount = this.pagesOverview.length;
const renderNextPage = (resolve, reject) => {
this.throwIfInactive();
if (++this.currentPage >= pageCount) {
renderProgress(pageCount, pageCount);
resolve();
return;
}
const index = this.currentPage;
renderProgress(index, pageCount);
renderPage(this, this.pdfDocument, index + 1, this.pagesOverview[index], this._printResolution, this._optionalContentConfigPromise, this._printAnnotationStoragePromise).then(this.useRenderedPage.bind(this)).then(function () {
renderNextPage(resolve, reject);
}, reject);
};
return new Promise(renderNextPage);
}
useRenderedPage() {
this.throwIfInactive();
const img = document.createElement("img");
this.scratchCanvas.toBlob(blob => {
img.src = URL.createObjectURL(blob);
});
const wrapper = document.createElement("div");
wrapper.className = "printedPage";
wrapper.append(img);
this.printContainer.append(wrapper);
const {
promise,
resolve,
reject
} = Promise.withResolvers();
img.onload = resolve;
img.onerror = reject;
promise.catch(() => {}).then(() => {
URL.revokeObjectURL(img.src);
});
return promise;
}
performPrint() {
this.throwIfInactive();
return new Promise(resolve => {
setTimeout(() => {
if (!this.active) {
resolve();
return;
}
print.call(window);
setTimeout(resolve, 20);
}, 0);
});
}
get active() {
return this === activeService;
}
throwIfInactive() {
if (!this.active) {
throw new Error("This print request was cancelled or completed.");
}
}
}
const print = window.print;
window.print = function () {
if (activeService) {
console.warn("Ignored window.print() because of a pending print job.");
return;
}
ensureOverlay().then(function () {
if (activeService) {
overlayManager.open(dialog);
}
});
try {
dispatchEvent("beforeprint");
} finally {
if (!activeService) {
console.error("Expected print service to be initialized.");
ensureOverlay().then(function () {
overlayManager.closeIfActive(dialog);
});
} else {
const activeServiceOnEntry = activeService;
activeService.renderPages().then(() => activeServiceOnEntry.performPrint()).catch(() => {}).then(() => {
if (activeServiceOnEntry.active) {
abort();
}
});
}
}
};
function dispatchEvent(eventType) {
const event = new CustomEvent(eventType, {
bubbles: false,
cancelable: false,
detail: "custom"
});
window.dispatchEvent(event);
}
function abort() {
if (activeService) {
activeService.destroy();
dispatchEvent("afterprint");
}
}
function renderProgress(index, total) {
dialog ||= document.getElementById("printServiceDialog");
const progress = Math.round(100 * index / total);
const progressBar = dialog.querySelector("progress");
const progressPerc = dialog.querySelector(".relative-progress");
progressBar.value = progress;
progressPerc.setAttribute("data-l10n-args", JSON.stringify({
progress
}));
}
window.addEventListener("keydown", function (event) {
if (event.keyCode === 80 && (event.ctrlKey || event.metaKey) && !event.altKey && (!event.shiftKey || window.chrome || window.opera)) {
window.print();
event.preventDefault();
event.stopImmediatePropagation();
}
}, true);
if ("onbeforeprint" in window) {
const stopPropagationIfNeeded = function (event) {
if (event.detail !== "custom") {
event.stopImmediatePropagation();
}
};
window.addEventListener("beforeprint", stopPropagationIfNeeded);
window.addEventListener("afterprint", stopPropagationIfNeeded);
}
let overlayPromise;
function ensureOverlay() {
if (!overlayPromise) {
overlayManager = viewerApp.overlayManager;
if (!overlayManager) {
throw new Error("The overlay manager has not yet been initialized.");
}
dialog ||= document.getElementById("printServiceDialog");
overlayPromise = overlayManager.register(dialog, true);
document.getElementById("printCancel").onclick = abort;
dialog.addEventListener("close", abort);
}
return overlayPromise;
}
class PDFPrintServiceFactory {
static initGlobals(app) {
viewerApp = app;
}
static get supportsPrinting() {
return shadow(this, "supportsPrinting", true);
}
static createPrintService(params) {
if (activeService) {
throw new Error("The print service is created and active.");
}
return activeService = new PDFPrintService(params);
}
}
;// ./web/pdf_rendering_queue.js
const CLEANUP_TIMEOUT = 30000;
class PDFRenderingQueue {
constructor() {
this.pdfViewer = null;
this.pdfThumbnailViewer = null;
this.onIdle = null;
this.highestPriorityPage = null;
this.idleTimeout = null;
this.printing = false;
this.isThumbnailViewEnabled = false;
Object.defineProperty(this, "hasViewer", {
value: () => !!this.pdfViewer
});
}
setViewer(pdfViewer) {
this.pdfViewer = pdfViewer;
}
setThumbnailViewer(pdfThumbnailViewer) {
this.pdfThumbnailViewer = pdfThumbnailViewer;
}
isHighestPriority(view) {
return this.highestPriorityPage === view.renderingId;
}
renderHighestPriority(currentlyVisiblePages) {
if (this.idleTimeout) {
clearTimeout(this.idleTimeout);
this.idleTimeout = null;
}
if (this.pdfViewer.forceRendering(currentlyVisiblePages)) {
return;
}
if (this.isThumbnailViewEnabled && this.pdfThumbnailViewer?.forceRendering()) {
return;
}
if (this.printing) {
return;
}
if (this.onIdle) {
this.idleTimeout = setTimeout(this.onIdle.bind(this), CLEANUP_TIMEOUT);
}
}
getHighestPriority(visible, views, scrolledDown, preRenderExtra = false, ignoreDetailViews = false) {
const visibleViews = visible.views,
numVisible = visibleViews.length;
if (numVisible === 0) {
return null;
}
for (let i = 0; i < numVisible; i++) {
const view = visibleViews[i].view;
if (!this.isViewFinished(view)) {
return view;
}
}
if (!ignoreDetailViews) {
for (let i = 0; i < numVisible; i++) {
const {
detailView
} = visibleViews[i].view;
if (detailView && !this.isViewFinished(detailView)) {
return detailView;
}
}
}
const firstId = visible.first.id,
lastId = visible.last.id;
if (lastId - firstId + 1 > numVisible) {
const visibleIds = visible.ids;
for (let i = 1, ii = lastId - firstId; i < ii; i++) {
const holeId = scrolledDown ? firstId + i : lastId - i;
if (visibleIds.has(holeId)) {
continue;
}
const holeView = views[holeId - 1];
if (!this.isViewFinished(holeView)) {
return holeView;
}
}
}
let preRenderIndex = scrolledDown ? lastId : firstId - 2;
let preRenderView = views[preRenderIndex];
if (preRenderView && !this.isViewFinished(preRenderView)) {
return preRenderView;
}
if (preRenderExtra) {
preRenderIndex += scrolledDown ? 1 : -1;
preRenderView = views[preRenderIndex];
if (preRenderView && !this.isViewFinished(preRenderView)) {
return preRenderView;
}
}
return null;
}
isViewFinished(view) {
return view.renderingState === RenderingStates.FINISHED;
}
renderView(view) {
switch (view.renderingState) {
case RenderingStates.FINISHED:
return false;
case RenderingStates.PAUSED:
this.highestPriorityPage = view.renderingId;
view.resume();
break;
case RenderingStates.RUNNING:
this.highestPriorityPage = view.renderingId;
break;
case RenderingStates.INITIAL:
this.highestPriorityPage = view.renderingId;
view.draw().finally(() => {
this.renderHighestPriority();
}).catch(reason => {
if (reason instanceof RenderingCancelledException) {
return;
}
console.error("renderView:", reason);
});
break;
}
return true;
}
}
;// ./web/pdf_scripting_manager.js
class PDFScriptingManager {
#closeCapability = null;
#destroyCapability = null;
#docProperties = null;
#eventAbortController = null;
#eventBus = null;
#externalServices = null;
#pdfDocument = null;
#pdfViewer = null;
#ready = false;
#scripting = null;
#willPrintCapability = null;
constructor({
eventBus,
externalServices = null,
docProperties = null
}) {
this.#eventBus = eventBus;
this.#externalServices = externalServices;
this.#docProperties = docProperties;
}
setViewer(pdfViewer) {
this.#pdfViewer = pdfViewer;
}
async setDocument(pdfDocument) {
if (this.#pdfDocument) {
await this.#destroyScripting();
}
this.#pdfDocument = pdfDocument;
if (!pdfDocument) {
return;
}
const [objects, calculationOrder, docActions] = await Promise.all([pdfDocument.getFieldObjects(), pdfDocument.getCalculationOrderIds(), pdfDocument.getJSActions()]);
if (!objects && !docActions) {
await this.#destroyScripting();
return;
}
if (pdfDocument !== this.#pdfDocument) {
return;
}
try {
this.#scripting = this.#initScripting();
} catch (error) {
console.error("setDocument:", error);
await this.#destroyScripting();
return;
}
const eventBus = this.#eventBus;
this.#eventAbortController = new AbortController();
const {
signal
} = this.#eventAbortController;
eventBus._on("updatefromsandbox", event => {
if (event?.source === window) {
this.#updateFromSandbox(event.detail);
}
}, {
signal
});
eventBus._on("dispatcheventinsandbox", event => {
this.#scripting?.dispatchEventInSandbox(event.detail);
}, {
signal
});
eventBus._on("pagechanging", ({
pageNumber,
previous
}) => {
if (pageNumber === previous) {
return;
}
this.#dispatchPageClose(previous);
this.#dispatchPageOpen(pageNumber);
}, {
signal
});
eventBus._on("pagerendered", ({
pageNumber
}) => {
if (!this._pageOpenPending.has(pageNumber)) {
return;
}
if (pageNumber !== this.#pdfViewer.currentPageNumber) {
return;
}
this.#dispatchPageOpen(pageNumber);
}, {
signal
});
eventBus._on("pagesdestroy", async () => {
await this.#dispatchPageClose(this.#pdfViewer.currentPageNumber);
await this.#scripting?.dispatchEventInSandbox({
id: "doc",
name: "WillClose"
});
this.#closeCapability?.resolve();
}, {
signal
});
try {
const docProperties = await this.#docProperties(pdfDocument);
if (pdfDocument !== this.#pdfDocument) {
return;
}
await this.#scripting.createSandbox({
objects,
calculationOrder,
appInfo: {
platform: navigator.platform,
language: navigator.language
},
docInfo: {
...docProperties,
actions: docActions
}
});
eventBus.dispatch("sandboxcreated", {
source: this
});
} catch (error) {
console.error("setDocument:", error);
await this.#destroyScripting();
return;
}
await this.#scripting?.dispatchEventInSandbox({
id: "doc",
name: "Open"
});
await this.#dispatchPageOpen(this.#pdfViewer.currentPageNumber, true);
Promise.resolve().then(() => {
if (pdfDocument === this.#pdfDocument) {
this.#ready = true;
}
});
}
async dispatchWillSave() {
return this.#scripting?.dispatchEventInSandbox({
id: "doc",
name: "WillSave"
});
}
async dispatchDidSave() {
return this.#scripting?.dispatchEventInSandbox({
id: "doc",
name: "DidSave"
});
}
async dispatchWillPrint() {
if (!this.#scripting) {
return;
}
await this.#willPrintCapability?.promise;
this.#willPrintCapability = Promise.withResolvers();
try {
await this.#scripting.dispatchEventInSandbox({
id: "doc",
name: "WillPrint"
});
} catch (ex) {
this.#willPrintCapability.resolve();
this.#willPrintCapability = null;
throw ex;
}
await this.#willPrintCapability.promise;
}
async dispatchDidPrint() {
return this.#scripting?.dispatchEventInSandbox({
id: "doc",
name: "DidPrint"
});
}
get destroyPromise() {
return this.#destroyCapability?.promise || null;
}
get ready() {
return this.#ready;
}
get _pageOpenPending() {
return shadow(this, "_pageOpenPending", new Set());
}
get _visitedPages() {
return shadow(this, "_visitedPages", new Map());
}
async #updateFromSandbox(detail) {
const pdfViewer = this.#pdfViewer;
const isInPresentationMode = pdfViewer.isInPresentationMode || pdfViewer.isChangingPresentationMode;
const {
id,
siblings,
command,
value
} = detail;
if (!id) {
switch (command) {
case "clear":
console.clear();
break;
case "error":
console.error(value);
break;
case "layout":
if (!isInPresentationMode) {
const modes = apiPageLayoutToViewerModes(value);
pdfViewer.spreadMode = modes.spreadMode;
}
break;
case "page-num":
pdfViewer.currentPageNumber = value + 1;
break;
case "print":
await pdfViewer.pagesPromise;
this.#eventBus.dispatch("print", {
source: this
});
break;
case "println":
console.log(value);
break;
case "zoom":
if (!isInPresentationMode) {
pdfViewer.currentScaleValue = value;
}
break;
case "SaveAs":
this.#eventBus.dispatch("download", {
source: this
});
break;
case "FirstPage":
pdfViewer.currentPageNumber = 1;
break;
case "LastPage":
pdfViewer.currentPageNumber = pdfViewer.pagesCount;
break;
case "NextPage":
pdfViewer.nextPage();
break;
case "PrevPage":
pdfViewer.previousPage();
break;
case "ZoomViewIn":
if (!isInPresentationMode) {
pdfViewer.increaseScale();
}
break;
case "ZoomViewOut":
if (!isInPresentationMode) {
pdfViewer.decreaseScale();
}
break;
case "WillPrintFinished":
this.#willPrintCapability?.resolve();
this.#willPrintCapability = null;
break;
}
return;
}
if (isInPresentationMode && detail.focus) {
return;
}
delete detail.id;
delete detail.siblings;
const ids = siblings ? [id, ...siblings] : [id];
for (const elementId of ids) {
const element = document.querySelector(`[data-element-id="${elementId}"]`);
if (element) {
element.dispatchEvent(new CustomEvent("updatefromsandbox", {
detail
}));
} else {
this.#pdfDocument?.annotationStorage.setValue(elementId, detail);
}
}
}
async #dispatchPageOpen(pageNumber, initialize = false) {
const pdfDocument = this.#pdfDocument,
visitedPages = this._visitedPages;
if (initialize) {
this.#closeCapability = Promise.withResolvers();
}
if (!this.#closeCapability) {
return;
}
const pageView = this.#pdfViewer.getPageView(pageNumber - 1);
if (pageView?.renderingState !== RenderingStates.FINISHED) {
this._pageOpenPending.add(pageNumber);
return;
}
this._pageOpenPending.delete(pageNumber);
const actionsPromise = (async () => {
const actions = await (!visitedPages.has(pageNumber) ? pageView.pdfPage?.getJSActions() : null);
if (pdfDocument !== this.#pdfDocument) {
return;
}
await this.#scripting?.dispatchEventInSandbox({
id: "page",
name: "PageOpen",
pageNumber,
actions
});
})();
visitedPages.set(pageNumber, actionsPromise);
}
async #dispatchPageClose(pageNumber) {
const pdfDocument = this.#pdfDocument,
visitedPages = this._visitedPages;
if (!this.#closeCapability) {
return;
}
if (this._pageOpenPending.has(pageNumber)) {
return;
}
const actionsPromise = visitedPages.get(pageNumber);
if (!actionsPromise) {
return;
}
visitedPages.set(pageNumber, null);
await actionsPromise;
if (pdfDocument !== this.#pdfDocument) {
return;
}
await this.#scripting?.dispatchEventInSandbox({
id: "page",
name: "PageClose",
pageNumber
});
}
#initScripting() {
this.#destroyCapability = Promise.withResolvers();
if (this.#scripting) {
throw new Error("#initScripting: Scripting already exists.");
}
return this.#externalServices.createScripting();
}
async #destroyScripting() {
if (!this.#scripting) {
this.#pdfDocument = null;
this.#destroyCapability?.resolve();
return;
}
if (this.#closeCapability) {
await Promise.race([this.#closeCapability.promise, new Promise(resolve => {
setTimeout(resolve, 1000);
})]).catch(() => {});
this.#closeCapability = null;
}
this.#pdfDocument = null;
try {
await this.#scripting.destroySandbox();
} catch {}
this.#willPrintCapability?.reject(new Error("Scripting destroyed."));
this.#willPrintCapability = null;
this.#eventAbortController?.abort();
this.#eventAbortController = null;
this._pageOpenPending.clear();
this._visitedPages.clear();
this.#scripting = null;
this.#ready = false;
this.#destroyCapability?.resolve();
}
}
;// ./web/pdf_sidebar.js
const SIDEBAR_WIDTH_VAR = "--sidebar-width";
const SIDEBAR_MIN_WIDTH = 200;
const SIDEBAR_RESIZING_CLASS = "sidebarResizing";
const UI_NOTIFICATION_CLASS = "pdfSidebarNotification";
class PDFSidebar {
#isRTL = false;
#mouseAC = null;
#outerContainerWidth = null;
#width = null;
constructor({
elements,
eventBus,
l10n
}) {
this.isOpen = false;
this.active = SidebarView.THUMBS;
this.isInitialViewSet = false;
this.isInitialEventDispatched = false;
this.onToggled = null;
this.onUpdateThumbnails = null;
this.outerContainer = elements.outerContainer;
this.sidebarContainer = elements.sidebarContainer;
this.toggleButton = elements.toggleButton;
this.resizer = elements.resizer;
this.thumbnailButton = elements.thumbnailButton;
this.outlineButton = elements.outlineButton;
this.attachmentsButton = elements.attachmentsButton;
this.layersButton = elements.layersButton;
this.thumbnailView = elements.thumbnailView;
this.outlineView = elements.outlineView;
this.attachmentsView = elements.attachmentsView;
this.layersView = elements.layersView;
this._currentOutlineItemButton = elements.currentOutlineItemButton;
this.eventBus = eventBus;
this.#isRTL = l10n.getDirection() === "rtl";
this.#addEventListeners();
}
reset() {
this.isInitialViewSet = false;
this.isInitialEventDispatched = false;
this.#hideUINotification(true);
this.switchView(SidebarView.THUMBS);
this.outlineButton.disabled = false;
this.attachmentsButton.disabled = false;
this.layersButton.disabled = false;
this._currentOutlineItemButton.disabled = true;
}
get visibleView() {
return this.isOpen ? this.active : SidebarView.NONE;
}
setInitialView(view = SidebarView.NONE) {
if (this.isInitialViewSet) {
return;
}
this.isInitialViewSet = true;
if (view === SidebarView.NONE || view === SidebarView.UNKNOWN) {
this.#dispatchEvent();
return;
}
this.switchView(view, true);
if (!this.isInitialEventDispatched) {
this.#dispatchEvent();
}
}
switchView(view, forceOpen = false) {
const isViewChanged = view !== this.active;
let forceRendering = false;
switch (view) {
case SidebarView.NONE:
if (this.isOpen) {
this.close();
}
return;
case SidebarView.THUMBS:
if (this.isOpen && isViewChanged) {
forceRendering = true;
}
break;
case SidebarView.OUTLINE:
if (this.outlineButton.disabled) {
return;
}
break;
case SidebarView.ATTACHMENTS:
if (this.attachmentsButton.disabled) {
return;
}
break;
case SidebarView.LAYERS:
if (this.layersButton.disabled) {
return;
}
break;
default:
console.error(`PDFSidebar.switchView: "${view}" is not a valid view.`);
return;
}
this.active = view;
toggleCheckedBtn(this.thumbnailButton, view === SidebarView.THUMBS, this.thumbnailView);
toggleCheckedBtn(this.outlineButton, view === SidebarView.OUTLINE, this.outlineView);
toggleCheckedBtn(this.attachmentsButton, view === SidebarView.ATTACHMENTS, this.attachmentsView);
toggleCheckedBtn(this.layersButton, view === SidebarView.LAYERS, this.layersView);
if (forceOpen && !this.isOpen) {
this.open();
return;
}
if (forceRendering) {
this.onUpdateThumbnails();
this.onToggled();
}
if (isViewChanged) {
this.#dispatchEvent();
}
}
open() {
if (this.isOpen) {
return;
}
this.isOpen = true;
toggleExpandedBtn(this.toggleButton, true);
this.outerContainer.classList.add("sidebarMoving", "sidebarOpen");
if (this.active === SidebarView.THUMBS) {
this.onUpdateThumbnails();
}
this.onToggled();
this.#dispatchEvent();
this.#hideUINotification();
}
close(evt = null) {
if (!this.isOpen) {
return;
}
this.isOpen = false;
toggleExpandedBtn(this.toggleButton, false);
this.outerContainer.classList.add("sidebarMoving");
this.outerContainer.classList.remove("sidebarOpen");
this.onToggled();
this.#dispatchEvent();
if (evt?.detail > 0) {
this.toggleButton.blur();
}
}
toggle(evt = null) {
if (this.isOpen) {
this.close(evt);
} else {
this.open();
}
}
#dispatchEvent() {
if (this.isInitialViewSet) {
this.isInitialEventDispatched ||= true;
}
this.eventBus.dispatch("sidebarviewchanged", {
source: this,
view: this.visibleView
});
}
#showUINotification() {
this.toggleButton.setAttribute("data-l10n-id", "pdfjs-toggle-sidebar-notification-button");
if (!this.isOpen) {
this.toggleButton.classList.add(UI_NOTIFICATION_CLASS);
}
}
#hideUINotification(reset = false) {
if (this.isOpen || reset) {
this.toggleButton.classList.remove(UI_NOTIFICATION_CLASS);
}
if (reset) {
this.toggleButton.setAttribute("data-l10n-id", "pdfjs-toggle-sidebar-button");
}
}
#addEventListeners() {
const {
eventBus,
outerContainer
} = this;
this.sidebarContainer.addEventListener("transitionend", evt => {
if (evt.target === this.sidebarContainer) {
outerContainer.classList.remove("sidebarMoving");
eventBus.dispatch("resize", {
source: this
});
}
});
this.toggleButton.addEventListener("click", evt => {
this.toggle(evt);
});
this.thumbnailButton.addEventListener("click", () => {
this.switchView(SidebarView.THUMBS);
});
this.outlineButton.addEventListener("click", () => {
this.switchView(SidebarView.OUTLINE);
});
this.outlineButton.addEventListener("dblclick", () => {
eventBus.dispatch("toggleoutlinetree", {
source: this
});
});
this.attachmentsButton.addEventListener("click", () => {
this.switchView(SidebarView.ATTACHMENTS);
});
this.layersButton.addEventListener("click", () => {
this.switchView(SidebarView.LAYERS);
});
this.layersButton.addEventListener("dblclick", () => {
eventBus.dispatch("resetlayers", {
source: this
});
});
this._currentOutlineItemButton.addEventListener("click", () => {
eventBus.dispatch("currentoutlineitem", {
source: this
});
});
const onTreeLoaded = (count, button, view) => {
button.disabled = !count;
if (count) {
this.#showUINotification();
} else if (this.active === view) {
this.switchView(SidebarView.THUMBS);
}
};
eventBus._on("outlineloaded", evt => {
onTreeLoaded(evt.outlineCount, this.outlineButton, SidebarView.OUTLINE);
evt.currentOutlineItemPromise.then(enabled => {
if (!this.isInitialViewSet) {
return;
}
this._currentOutlineItemButton.disabled = !enabled;
});
});
eventBus._on("attachmentsloaded", evt => {
onTreeLoaded(evt.attachmentsCount, this.attachmentsButton, SidebarView.ATTACHMENTS);
});
eventBus._on("layersloaded", evt => {
onTreeLoaded(evt.layersCount, this.layersButton, SidebarView.LAYERS);
});
eventBus._on("presentationmodechanged", evt => {
if (evt.state === PresentationModeState.NORMAL && this.visibleView === SidebarView.THUMBS) {
this.onUpdateThumbnails();
}
});
this.resizer.addEventListener("mousedown", evt => {
if (evt.button !== 0) {
return;
}
outerContainer.classList.add(SIDEBAR_RESIZING_CLASS);
this.#mouseAC = new AbortController();
const opts = {
signal: this.#mouseAC.signal
};
window.addEventListener("mousemove", this.#mouseMove.bind(this), opts);
window.addEventListener("mouseup", this.#mouseUp.bind(this), opts);
window.addEventListener("blur", this.#mouseUp.bind(this), opts);
});
eventBus._on("resize", evt => {
if (evt.source !== window) {
return;
}
this.#outerContainerWidth = null;
if (!this.#width) {
return;
}
if (!this.isOpen) {
this.#updateWidth(this.#width);
return;
}
outerContainer.classList.add(SIDEBAR_RESIZING_CLASS);
const updated = this.#updateWidth(this.#width);
Promise.resolve().then(() => {
outerContainer.classList.remove(SIDEBAR_RESIZING_CLASS);
if (updated) {
eventBus.dispatch("resize", {
source: this
});
}
});
});
}
get outerContainerWidth() {
return this.#outerContainerWidth ||= this.outerContainer.clientWidth;
}
#updateWidth(width = 0) {
const maxWidth = Math.floor(this.outerContainerWidth / 2);
if (width > maxWidth) {
width = maxWidth;
}
if (width < SIDEBAR_MIN_WIDTH) {
width = SIDEBAR_MIN_WIDTH;
}
if (width === this.#width) {
return false;
}
this.#width = width;
docStyle.setProperty(SIDEBAR_WIDTH_VAR, `${width}px`);
return true;
}
#mouseMove(evt) {
let width = evt.clientX;
if (this.#isRTL) {
width = this.outerContainerWidth - width;
}
this.#updateWidth(width);
}
#mouseUp(evt) {
this.outerContainer.classList.remove(SIDEBAR_RESIZING_CLASS);
this.eventBus.dispatch("resize", {
source: this
});
this.#mouseAC?.abort();
this.#mouseAC = null;
}
}
;// ./web/pdf_thumbnail_view.js
const DRAW_UPSCALE_FACTOR = 2;
const MAX_NUM_SCALING_STEPS = 3;
const THUMBNAIL_WIDTH = 98;
function zeroCanvas(c) {
c.width = 0;
c.height = 0;
}
class TempImageFactory {
static #tempCanvas = null;
static getCanvas(width, height) {
const tempCanvas = this.#tempCanvas ||= document.createElement("canvas");
tempCanvas.width = width;
tempCanvas.height = height;
const ctx = tempCanvas.getContext("2d", {
alpha: false
});
ctx.save();
ctx.fillStyle = "rgb(255, 255, 255)";
ctx.fillRect(0, 0, width, height);
ctx.restore();
return [tempCanvas, tempCanvas.getContext("2d")];
}
static destroyCanvas() {
if (this.#tempCanvas) {
zeroCanvas(this.#tempCanvas);
}
this.#tempCanvas = null;
}
}
class PDFThumbnailView {
constructor({
container,
eventBus,
id,
defaultViewport,
optionalContentConfigPromise,
linkService,
renderingQueue,
maxCanvasPixels,
maxCanvasDim,
pageColors,
enableHWA
}) {
this.id = id;
this.renderingId = "thumbnail" + id;
this.pageLabel = null;
this.pdfPage = null;
this.rotation = 0;
this.viewport = defaultViewport;
this.pdfPageRotate = defaultViewport.rotation;
this._optionalContentConfigPromise = optionalContentConfigPromise || null;
this.maxCanvasPixels = maxCanvasPixels ?? AppOptions.get("maxCanvasPixels");
this.maxCanvasDim = maxCanvasDim || AppOptions.get("maxCanvasDim");
this.pageColors = pageColors || null;
this.enableHWA = enableHWA || false;
this.eventBus = eventBus;
this.linkService = linkService;
this.renderingQueue = renderingQueue;
this.renderTask = null;
this.renderingState = RenderingStates.INITIAL;
this.resume = null;
const anchor = document.createElement("a");
anchor.href = linkService.getAnchorUrl("#page=" + id);
anchor.setAttribute("data-l10n-id", "pdfjs-thumb-page-title");
anchor.setAttribute("data-l10n-args", this.#pageL10nArgs);
anchor.onclick = function () {
linkService.goToPage(id);
return false;
};
this.anchor = anchor;
const div = document.createElement("div");
div.className = "thumbnail";
div.setAttribute("data-page-number", this.id);
this.div = div;
this.#updateDims();
const img = document.createElement("div");
img.className = "thumbnailImage";
this._placeholderImg = img;
div.append(img);
anchor.append(div);
container.append(anchor);
}
#updateDims() {
const {
width,
height
} = this.viewport;
const ratio = width / height;
this.canvasWidth = THUMBNAIL_WIDTH;
this.canvasHeight = this.canvasWidth / ratio | 0;
this.scale = this.canvasWidth / width;
const {
style
} = this.div;
style.setProperty("--thumbnail-width", `${this.canvasWidth}px`);
style.setProperty("--thumbnail-height", `${this.canvasHeight}px`);
}
setPdfPage(pdfPage) {
this.pdfPage = pdfPage;
this.pdfPageRotate = pdfPage.rotate;
const totalRotation = (this.rotation + this.pdfPageRotate) % 360;
this.viewport = pdfPage.getViewport({
scale: 1,
rotation: totalRotation
});
this.reset();
}
reset() {
this.cancelRendering();
this.renderingState = RenderingStates.INITIAL;
this.div.removeAttribute("data-loaded");
this.image?.replaceWith(this._placeholderImg);
this.#updateDims();
if (this.image) {
this.image.removeAttribute("src");
delete this.image;
}
}
update({
rotation = null
}) {
if (typeof rotation === "number") {
this.rotation = rotation;
}
const totalRotation = (this.rotation + this.pdfPageRotate) % 360;
this.viewport = this.viewport.clone({
scale: 1,
rotation: totalRotation
});
this.reset();
}
cancelRendering() {
if (this.renderTask) {
this.renderTask.cancel();
this.renderTask = null;
}
this.resume = null;
}
#getPageDrawContext(upscaleFactor = 1, enableHWA = this.enableHWA) {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d", {
alpha: false,
willReadFrequently: !enableHWA
});
const outputScale = new OutputScale();
const width = upscaleFactor * this.canvasWidth,
height = upscaleFactor * this.canvasHeight;
outputScale.limitCanvas(width, height, this.maxCanvasPixels, this.maxCanvasDim);
canvas.width = width * outputScale.sx | 0;
canvas.height = height * outputScale.sy | 0;
const transform = outputScale.scaled ? [outputScale.sx, 0, 0, outputScale.sy, 0, 0] : null;
return {
ctx,
canvas,
transform
};
}
#convertCanvasToImage(canvas) {
if (this.renderingState !== RenderingStates.FINISHED) {
throw new Error("#convertCanvasToImage: Rendering has not finished.");
}
const reducedCanvas = this.#reduceImage(canvas);
const image = document.createElement("img");
image.className = "thumbnailImage";
image.setAttribute("data-l10n-id", "pdfjs-thumb-page-canvas");
image.setAttribute("data-l10n-args", this.#pageL10nArgs);
image.src = reducedCanvas.toDataURL();
this.image = image;
this.div.setAttribute("data-loaded", true);
this._placeholderImg.replaceWith(image);
zeroCanvas(reducedCanvas);
}
async draw() {
if (this.renderingState !== RenderingStates.INITIAL) {
console.error("Must be in new state before drawing");
return;
}
const {
pageColors,
pdfPage
} = this;
if (!pdfPage) {
this.renderingState = RenderingStates.FINISHED;
throw new Error("pdfPage is not loaded");
}
this.renderingState = RenderingStates.RUNNING;
const {
ctx,
canvas,
transform
} = this.#getPageDrawContext(DRAW_UPSCALE_FACTOR);
const drawViewport = this.viewport.clone({
scale: DRAW_UPSCALE_FACTOR * this.scale
});
const renderContinueCallback = cont => {
if (!this.renderingQueue.isHighestPriority(this)) {
this.renderingState = RenderingStates.PAUSED;
this.resume = () => {
this.renderingState = RenderingStates.RUNNING;
cont();
};
return;
}
cont();
};
const renderContext = {
canvasContext: ctx,
transform,
viewport: drawViewport,
optionalContentConfigPromise: this._optionalContentConfigPromise,
pageColors
};
const renderTask = this.renderTask = pdfPage.render(renderContext);
renderTask.onContinue = renderContinueCallback;
let error = null;
try {
await renderTask.promise;
} catch (e) {
if (e instanceof RenderingCancelledException) {
zeroCanvas(canvas);
return;
}
error = e;
} finally {
if (renderTask === this.renderTask) {
this.renderTask = null;
}
}
this.renderingState = RenderingStates.FINISHED;
this.#convertCanvasToImage(canvas);
zeroCanvas(canvas);
this.eventBus.dispatch("thumbnailrendered", {
source: this,
pageNumber: this.id,
pdfPage
});
if (error) {
throw error;
}
}
setImage(pageView) {
if (this.renderingState !== RenderingStates.INITIAL) {
return;
}
const {
thumbnailCanvas: canvas,
pdfPage,
scale
} = pageView;
if (!canvas) {
return;
}
if (!this.pdfPage) {
this.setPdfPage(pdfPage);
}
if (scale < this.scale) {
return;
}
this.renderingState = RenderingStates.FINISHED;
this.#convertCanvasToImage(canvas);
}
#getReducedImageDims(canvas) {
const width = canvas.width << MAX_NUM_SCALING_STEPS,
height = canvas.height << MAX_NUM_SCALING_STEPS;
const outputScale = new OutputScale();
outputScale.sx = outputScale.sy = 1;
outputScale.limitCanvas(width, height, this.maxCanvasPixels, this.maxCanvasDim);
return [width * outputScale.sx | 0, height * outputScale.sy | 0];
}
#reduceImage(img) {
const {
ctx,
canvas
} = this.#getPageDrawContext(1, true);
if (img.width <= 2 * canvas.width) {
ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, canvas.width, canvas.height);
return canvas;
}
let [reducedWidth, reducedHeight] = this.#getReducedImageDims(canvas);
const [reducedImage, reducedImageCtx] = TempImageFactory.getCanvas(reducedWidth, reducedHeight);
while (reducedWidth > img.width || reducedHeight > img.height) {
reducedWidth >>= 1;
reducedHeight >>= 1;
}
reducedImageCtx.drawImage(img, 0, 0, img.width, img.height, 0, 0, reducedWidth, reducedHeight);
while (reducedWidth > 2 * canvas.width) {
reducedImageCtx.drawImage(reducedImage, 0, 0, reducedWidth, reducedHeight, 0, 0, reducedWidth >> 1, reducedHeight >> 1);
reducedWidth >>= 1;
reducedHeight >>= 1;
}
ctx.drawImage(reducedImage, 0, 0, reducedWidth, reducedHeight, 0, 0, canvas.width, canvas.height);
return canvas;
}
get #pageL10nArgs() {
return JSON.stringify({
page: this.pageLabel ?? this.id
});
}
setPageLabel(label) {
this.pageLabel = typeof label === "string" ? label : null;
this.anchor.setAttribute("data-l10n-args", this.#pageL10nArgs);
if (this.renderingState !== RenderingStates.FINISHED) {
return;
}
this.image?.setAttribute("data-l10n-args", this.#pageL10nArgs);
}
}
;// ./web/pdf_thumbnail_viewer.js
const THUMBNAIL_SCROLL_MARGIN = -19;
const THUMBNAIL_SELECTED_CLASS = "selected";
class PDFThumbnailViewer {
constructor({
container,
eventBus,
linkService,
renderingQueue,
maxCanvasPixels,
maxCanvasDim,
pageColors,
abortSignal,
enableHWA
}) {
this.container = container;
this.eventBus = eventBus;
this.linkService = linkService;
this.renderingQueue = renderingQueue;
this.maxCanvasPixels = maxCanvasPixels;
this.maxCanvasDim = maxCanvasDim;
this.pageColors = pageColors || null;
this.enableHWA = enableHWA || false;
this.scroll = watchScroll(this.container, this.#scrollUpdated.bind(this), abortSignal);
this.#resetView();
}
#scrollUpdated() {
this.renderingQueue.renderHighestPriority();
}
getThumbnail(index) {
return this._thumbnails[index];
}
#getVisibleThumbs() {
return getVisibleElements({
scrollEl: this.container,
views: this._thumbnails
});
}
scrollThumbnailIntoView(pageNumber) {
if (!this.pdfDocument) {
return;
}
const thumbnailView = this._thumbnails[pageNumber - 1];
if (!thumbnailView) {
console.error('scrollThumbnailIntoView: Invalid "pageNumber" parameter.');
return;
}
if (pageNumber !== this._currentPageNumber) {
const prevThumbnailView = this._thumbnails[this._currentPageNumber - 1];
prevThumbnailView.div.classList.remove(THUMBNAIL_SELECTED_CLASS);
thumbnailView.div.classList.add(THUMBNAIL_SELECTED_CLASS);
}
const {
first,
last,
views
} = this.#getVisibleThumbs();
if (views.length > 0) {
let shouldScroll = false;
if (pageNumber <= first.id || pageNumber >= last.id) {
shouldScroll = true;
} else {
for (const {
id,
percent
} of views) {
if (id !== pageNumber) {
continue;
}
shouldScroll = percent < 100;
break;
}
}
if (shouldScroll) {
scrollIntoView(thumbnailView.div, {
top: THUMBNAIL_SCROLL_MARGIN
});
}
}
this._currentPageNumber = pageNumber;
}
get pagesRotation() {
return this._pagesRotation;
}
set pagesRotation(rotation) {
if (!isValidRotation(rotation)) {
throw new Error("Invalid thumbnails rotation angle.");
}
if (!this.pdfDocument) {
return;
}
if (this._pagesRotation === rotation) {
return;
}
this._pagesRotation = rotation;
const updateArgs = {
rotation
};
for (const thumbnail of this._thumbnails) {
thumbnail.update(updateArgs);
}
}
cleanup() {
for (const thumbnail of this._thumbnails) {
if (thumbnail.renderingState !== RenderingStates.FINISHED) {
thumbnail.reset();
}
}
TempImageFactory.destroyCanvas();
}
#resetView() {
this._thumbnails = [];
this._currentPageNumber = 1;
this._pageLabels = null;
this._pagesRotation = 0;
this.container.textContent = "";
}
setDocument(pdfDocument) {
if (this.pdfDocument) {
this.#cancelRendering();
this.#resetView();
}
this.pdfDocument = pdfDocument;
if (!pdfDocument) {
return;
}
const firstPagePromise = pdfDocument.getPage(1);
const optionalContentConfigPromise = pdfDocument.getOptionalContentConfig({
intent: "display"
});
firstPagePromise.then(firstPdfPage => {
const pagesCount = pdfDocument.numPages;
const viewport = firstPdfPage.getViewport({
scale: 1
});
for (let pageNum = 1; pageNum <= pagesCount; ++pageNum) {
const thumbnail = new PDFThumbnailView({
container: this.container,
eventBus: this.eventBus,
id: pageNum,
defaultViewport: viewport.clone(),
optionalContentConfigPromise,
linkService: this.linkService,
renderingQueue: this.renderingQueue,
maxCanvasPixels: this.maxCanvasPixels,
maxCanvasDim: this.maxCanvasDim,
pageColors: this.pageColors,
enableHWA: this.enableHWA
});
this._thumbnails.push(thumbnail);
}
this._thumbnails[0]?.setPdfPage(firstPdfPage);
const thumbnailView = this._thumbnails[this._currentPageNumber - 1];
thumbnailView.div.classList.add(THUMBNAIL_SELECTED_CLASS);
}).catch(reason => {
console.error("Unable to initialize thumbnail viewer", reason);
});
}
#cancelRendering() {
for (const thumbnail of this._thumbnails) {
thumbnail.cancelRendering();
}
}
setPageLabels(labels) {
if (!this.pdfDocument) {
return;
}
if (!labels) {
this._pageLabels = null;
} else if (!(Array.isArray(labels) && this.pdfDocument.numPages === labels.length)) {
this._pageLabels = null;
console.error("PDFThumbnailViewer_setPageLabels: Invalid page labels.");
} else {
this._pageLabels = labels;
}
for (let i = 0, ii = this._thumbnails.length; i < ii; i++) {
this._thumbnails[i].setPageLabel(this._pageLabels?.[i] ?? null);
}
}
async #ensurePdfPageLoaded(thumbView) {
if (thumbView.pdfPage) {
return thumbView.pdfPage;
}
try {
const pdfPage = await this.pdfDocument.getPage(thumbView.id);
if (!thumbView.pdfPage) {
thumbView.setPdfPage(pdfPage);
}
return pdfPage;
} catch (reason) {
console.error("Unable to get page for thumb view", reason);
return null;
}
}
#getScrollAhead(visible) {
if (visible.first?.id === 1) {
return true;
} else if (visible.last?.id === this._thumbnails.length) {
return false;
}
return this.scroll.down;
}
forceRendering() {
const visibleThumbs = this.#getVisibleThumbs();
const scrollAhead = this.#getScrollAhead(visibleThumbs);
const thumbView = this.renderingQueue.getHighestPriority(visibleThumbs, this._thumbnails, scrollAhead, false, true);
if (thumbView) {
this.#ensurePdfPageLoaded(thumbView).then(() => {
this.renderingQueue.renderView(thumbView);
});
return true;
}
return false;
}
}
;// ./web/annotation_editor_layer_builder.js
class AnnotationEditorLayerBuilder {
#annotationLayer = null;
#drawLayer = null;
#onAppend = null;
#structTreeLayer = null;
#textLayer = null;
#uiManager;
constructor(options) {
this.pdfPage = options.pdfPage;
this.accessibilityManager = options.accessibilityManager;
this.l10n = options.l10n;
this.l10n ||= new genericl10n_GenericL10n();
this.annotationEditorLayer = null;
this.div = null;
this._cancelled = false;
this.#uiManager = options.uiManager;
this.#annotationLayer = options.annotationLayer || null;
this.#textLayer = options.textLayer || null;
this.#drawLayer = options.drawLayer || null;
this.#onAppend = options.onAppend || null;
this.#structTreeLayer = options.structTreeLayer || null;
}
async render({
viewport,
intent = "display"
}) {
if (intent !== "display") {
return;
}
if (this._cancelled) {
return;
}
const clonedViewport = viewport.clone({
dontFlip: true
});
if (this.div) {
this.annotationEditorLayer.update({
viewport: clonedViewport
});
this.show();
return;
}
const div = this.div = document.createElement("div");
div.className = "annotationEditorLayer";
div.hidden = true;
div.dir = this.#uiManager.direction;
this.#onAppend?.(div);
this.annotationEditorLayer = new AnnotationEditorLayer({
uiManager: this.#uiManager,
div,
structTreeLayer: this.#structTreeLayer,
accessibilityManager: this.accessibilityManager,
pageIndex: this.pdfPage.pageNumber - 1,
l10n: this.l10n,
viewport: clonedViewport,
annotationLayer: this.#annotationLayer,
textLayer: this.#textLayer,
drawLayer: this.#drawLayer
});
const parameters = {
viewport: clonedViewport,
div,
annotations: null,
intent
};
this.annotationEditorLayer.render(parameters);
this.show();
}
cancel() {
this._cancelled = true;
if (!this.div) {
return;
}
this.annotationEditorLayer.destroy();
}
hide() {
if (!this.div) {
return;
}
this.annotationEditorLayer.pause(true);
this.div.hidden = true;
}
show() {
if (!this.div || this.annotationEditorLayer.isInvisible) {
return;
}
this.div.hidden = false;
this.annotationEditorLayer.pause(false);
}
}
;// ./web/annotation_layer_builder.js
class AnnotationLayerBuilder {
#annotations = null;
#externalHide = false;
#onAppend = null;
#eventAbortController = null;
#linksInjected = false;
constructor({
pdfPage,
linkService,
downloadManager,
annotationStorage = null,
imageResourcesPath = "",
renderForms = true,
enableScripting = false,
hasJSActionsPromise = null,
fieldObjectsPromise = null,
annotationCanvasMap = null,
accessibilityManager = null,
annotationEditorUIManager = null,
onAppend = null
}) {
this.pdfPage = pdfPage;
this.linkService = linkService;
this.downloadManager = downloadManager;
this.imageResourcesPath = imageResourcesPath;
this.renderForms = renderForms;
this.annotationStorage = annotationStorage;
this.enableScripting = enableScripting;
this._hasJSActionsPromise = hasJSActionsPromise || Promise.resolve(false);
this._fieldObjectsPromise = fieldObjectsPromise || Promise.resolve(null);
this._annotationCanvasMap = annotationCanvasMap;
this._accessibilityManager = accessibilityManager;
this._annotationEditorUIManager = annotationEditorUIManager;
this.#onAppend = onAppend;
this.annotationLayer = null;
this.div = null;
this._cancelled = false;
this._eventBus = linkService.eventBus;
}
async render({
viewport,
intent = "display",
structTreeLayer = null
}) {
if (this.div) {
if (this._cancelled || !this.annotationLayer) {
return;
}
this.annotationLayer.update({
viewport: viewport.clone({
dontFlip: true
})
});
return;
}
const [annotations, hasJSActions, fieldObjects] = await Promise.all([this.pdfPage.getAnnotations({
intent
}), this._hasJSActionsPromise, this._fieldObjectsPromise]);
if (this._cancelled) {
return;
}
const div = this.div = document.createElement("div");
div.className = "annotationLayer";
this.#onAppend?.(div);
if (annotations.length === 0) {
this.#annotations = annotations;
this.hide(true);
return;
}
this.#initAnnotationLayer(viewport, structTreeLayer);
await this.annotationLayer.render({
annotations,
imageResourcesPath: this.imageResourcesPath,
renderForms: this.renderForms,
linkService: this.linkService,
downloadManager: this.downloadManager,
annotationStorage: this.annotationStorage,
enableScripting: this.enableScripting,
hasJSActions,
fieldObjects
});
this.#annotations = annotations;
if (this.linkService.isInPresentationMode) {
this.#updatePresentationModeState(PresentationModeState.FULLSCREEN);
}
if (!this.#eventAbortController) {
this.#eventAbortController = new AbortController();
this._eventBus?._on("presentationmodechanged", evt => {
this.#updatePresentationModeState(evt.state);
}, {
signal: this.#eventAbortController.signal
});
}
}
#initAnnotationLayer(viewport, structTreeLayer) {
this.annotationLayer = new AnnotationLayer({
div: this.div,
accessibilityManager: this._accessibilityManager,
annotationCanvasMap: this._annotationCanvasMap,
annotationEditorUIManager: this._annotationEditorUIManager,
page: this.pdfPage,
viewport: viewport.clone({
dontFlip: true
}),
structTreeLayer
});
}
cancel() {
this._cancelled = true;
this.#eventAbortController?.abort();
this.#eventAbortController = null;
}
hide(internal = false) {
this.#externalHide = !internal;
if (!this.div) {
return;
}
this.div.hidden = true;
}
hasEditableAnnotations() {
return !!this.annotationLayer?.hasEditableAnnotations();
}
async injectLinkAnnotations({
inferredLinks,
viewport,
structTreeLayer = null
}) {
if (this.#annotations === null) {
throw new Error("`render` method must be called before `injectLinkAnnotations`.");
}
if (this._cancelled || this.#linksInjected) {
return;
}
this.#linksInjected = true;
const newLinks = this.#annotations.length ? this.#checkInferredLinks(inferredLinks) : inferredLinks;
if (!newLinks.length) {
return;
}
if (!this.annotationLayer) {
this.#initAnnotationLayer(viewport, structTreeLayer);
setLayerDimensions(this.div, viewport);
}
await this.annotationLayer.addLinkAnnotations(newLinks, this.linkService);
if (!this.#externalHide) {
this.div.hidden = false;
}
}
#updatePresentationModeState(state) {
if (!this.div) {
return;
}
let disableFormElements = false;
switch (state) {
case PresentationModeState.FULLSCREEN:
disableFormElements = true;
break;
case PresentationModeState.NORMAL:
break;
default:
return;
}
for (const section of this.div.childNodes) {
if (section.hasAttribute("data-internal-link")) {
continue;
}
section.inert = disableFormElements;
}
}
#checkInferredLinks(inferredLinks) {
function annotationRects(annot) {
if (!annot.quadPoints) {
return [annot.rect];
}
const rects = [];
for (let i = 2, ii = annot.quadPoints.length; i < ii; i += 8) {
const trX = annot.quadPoints[i];
const trY = annot.quadPoints[i + 1];
const blX = annot.quadPoints[i + 2];
const blY = annot.quadPoints[i + 3];
rects.push([blX, blY, trX, trY]);
}
return rects;
}
function intersectAnnotations(annot1, annot2) {
const intersections = [];
const annot1Rects = annotationRects(annot1);
const annot2Rects = annotationRects(annot2);
for (const rect1 of annot1Rects) {
for (const rect2 of annot2Rects) {
const intersection = Util.intersect(rect1, rect2);
if (intersection) {
intersections.push(intersection);
}
}
}
return intersections;
}
function areaRects(rects) {
let totalArea = 0;
for (const rect of rects) {
totalArea += Math.abs((rect[2] - rect[0]) * (rect[3] - rect[1]));
}
return totalArea;
}
return inferredLinks.filter(link => {
let linkAreaRects;
for (const annotation of this.#annotations) {
if (annotation.annotationType !== AnnotationType.LINK || !annotation.url) {
continue;
}
const intersections = intersectAnnotations(annotation, link);
if (intersections.length === 0) {
continue;
}
linkAreaRects ??= areaRects(annotationRects(link));
if (areaRects(intersections) / linkAreaRects > 0.5) {
return false;
}
}
return true;
});
}
}
;// ./web/autolinker.js
function DOMRectToPDF({
width,
height,
left,
top
}, pdfPageView) {
if (width === 0 || height === 0) {
return null;
}
const pageBox = pdfPageView.textLayer.div.getBoundingClientRect();
const bottomLeft = pdfPageView.getPagePoint(left - pageBox.left, top - pageBox.top);
const topRight = pdfPageView.getPagePoint(left - pageBox.left + width, top - pageBox.top + height);
return Util.normalizeRect([bottomLeft[0], bottomLeft[1], topRight[0], topRight[1]]);
}
function calculateLinkPosition(range, pdfPageView) {
const rangeRects = range.getClientRects();
if (rangeRects.length === 1) {
return {
rect: DOMRectToPDF(rangeRects[0], pdfPageView)
};
}
const rect = [Infinity, Infinity, -Infinity, -Infinity];
const quadPoints = [];
let i = 0;
for (const domRect of rangeRects) {
const normalized = DOMRectToPDF(domRect, pdfPageView);
if (normalized === null) {
continue;
}
quadPoints[i] = quadPoints[i + 4] = normalized[0];
quadPoints[i + 1] = quadPoints[i + 3] = normalized[3];
quadPoints[i + 2] = quadPoints[i + 6] = normalized[2];
quadPoints[i + 5] = quadPoints[i + 7] = normalized[1];
Util.rectBoundingBox(...normalized, rect);
i += 8;
}
return {
quadPoints,
rect
};
}
function textPosition(container, offset) {
let currentContainer = container;
do {
if (currentContainer.nodeType === Node.TEXT_NODE) {
const currentLength = currentContainer.textContent.length;
if (offset <= currentLength) {
return [currentContainer, offset];
}
offset -= currentLength;
} else if (currentContainer.firstChild) {
currentContainer = currentContainer.firstChild;
continue;
}
while (!currentContainer.nextSibling && currentContainer !== container) {
currentContainer = currentContainer.parentNode;
}
if (currentContainer !== container) {
currentContainer = currentContainer.nextSibling;
}
} while (currentContainer !== container);
throw new Error("Offset is bigger than container's contents length.");
}
function createLinkAnnotation({
url,
index,
length
}, pdfPageView, id) {
const highlighter = pdfPageView._textHighlighter;
const [{
begin,
end
}] = highlighter._convertMatches([index], [length]);
const range = new Range();
range.setStart(...textPosition(highlighter.textDivs[begin.divIdx], begin.offset));
range.setEnd(...textPosition(highlighter.textDivs[end.divIdx], end.offset));
return {
id: `inferred_link_${id}`,
unsafeUrl: url,
url,
annotationType: AnnotationType.LINK,
rotation: 0,
...calculateLinkPosition(range, pdfPageView),
borderStyle: null
};
}
class Autolinker {
static #index = 0;
static #regex;
static findLinks(text) {
this.#regex ??= /\b(?:https?:\/\/|mailto:|www\.)(?:[\S--[\p{P}<>]]|\/|[\S--[\[\]]]+[\S--[\p{P}<>]])+|\b[\S--[@\p{Ps}\p{Pe}<>]]+@([\S--[\p{P}<>]]+(?:\.[\S--[\p{P}<>]]+)+)/gmv;
const [normalizedText, diffs] = normalize(text);
const matches = normalizedText.matchAll(this.#regex);
const links = [];
for (const match of matches) {
const [url, emailDomain] = match;
let raw;
if (url.startsWith("www.") || url.startsWith("http://") || url.startsWith("https://")) {
raw = url;
} else if (URL.canParse(`http://${emailDomain}`)) {
raw = url.startsWith("mailto:") ? url : `mailto:${url}`;
} else {
continue;
}
const absoluteURL = createValidAbsoluteUrl(raw, null, {
addDefaultProtocol: true
});
if (absoluteURL) {
const [index, length] = getOriginalIndex(diffs, match.index, url.length);
links.push({
url: absoluteURL.href,
index,
length
});
}
}
return links;
}
static processLinks(pdfPageView) {
return this.findLinks(pdfPageView._textHighlighter.textContentItemsStr.join("\n")).map(link => createLinkAnnotation(link, pdfPageView, this.#index++));
}
}
;// ./web/base_pdf_page_view.js
class BasePDFPageView {
#enableHWA = false;
#loadingId = null;
#renderError = null;
#renderingState = RenderingStates.INITIAL;
#showCanvas = null;
canvas = null;
div = null;
eventBus = null;
id = null;
pageColors = null;
renderingQueue = null;
renderTask = null;
resume = null;
constructor(options) {
this.#enableHWA = #enableHWA in options ? options.#enableHWA : options.enableHWA || false;
this.eventBus = options.eventBus;
this.id = options.id;
this.pageColors = options.pageColors || null;
this.renderingQueue = options.renderingQueue;
}
get renderingState() {
return this.#renderingState;
}
set renderingState(state) {
if (state === this.#renderingState) {
return;
}
this.#renderingState = state;
if (this.#loadingId) {
clearTimeout(this.#loadingId);
this.#loadingId = null;
}
switch (state) {
case RenderingStates.PAUSED:
this.div.classList.remove("loading");
break;
case RenderingStates.RUNNING:
this.div.classList.add("loadingIcon");
this.#loadingId = setTimeout(() => {
this.div.classList.add("loading");
this.#loadingId = null;
}, 0);
break;
case RenderingStates.INITIAL:
case RenderingStates.FINISHED:
this.div.classList.remove("loadingIcon", "loading");
break;
}
}
_createCanvas(onShow, hideUntilComplete = false) {
const {
pageColors
} = this;
const hasHCM = !!(pageColors?.background && pageColors?.foreground);
const prevCanvas = this.canvas;
const updateOnFirstShow = !prevCanvas && !hasHCM && !hideUntilComplete;
const canvas = this.canvas = document.createElement("canvas");
this.#showCanvas = isLastShow => {
if (updateOnFirstShow) {
onShow(canvas);
this.#showCanvas = null;
return;
}
if (!isLastShow) {
return;
}
if (prevCanvas) {
prevCanvas.replaceWith(canvas);
prevCanvas.width = prevCanvas.height = 0;
} else {
onShow(canvas);
}
};
const ctx = canvas.getContext("2d", {
alpha: false,
willReadFrequently: !this.#enableHWA
});
return {
canvas,
prevCanvas,
ctx
};
}
#renderContinueCallback = cont => {
this.#showCanvas?.(false);
if (this.renderingQueue && !this.renderingQueue.isHighestPriority(this)) {
this.renderingState = RenderingStates.PAUSED;
this.resume = () => {
this.renderingState = RenderingStates.RUNNING;
cont();
};
return;
}
cont();
};
_resetCanvas() {
const {
canvas
} = this;
if (!canvas) {
return;
}
canvas.remove();
canvas.width = canvas.height = 0;
this.canvas = null;
}
async _drawCanvas(options, onCancel, onFinish) {
const renderTask = this.renderTask = this.pdfPage.render(options);
renderTask.onContinue = this.#renderContinueCallback;
renderTask.onError = error => {
if (error instanceof RenderingCancelledException) {
onCancel();
this.#renderError = null;
}
};
let error = null;
try {
await renderTask.promise;
this.#showCanvas?.(true);
} catch (e) {
if (e instanceof RenderingCancelledException) {
return;
}
error = e;
this.#showCanvas?.(true);
} finally {
this.#renderError = error;
if (renderTask === this.renderTask) {
this.renderTask = null;
}
}
this.renderingState = RenderingStates.FINISHED;
onFinish(renderTask);
if (error) {
throw error;
}
}
cancelRendering({
cancelExtraDelay = 0
} = {}) {
if (this.renderTask) {
this.renderTask.cancel(cancelExtraDelay);
this.renderTask = null;
}
this.resume = null;
}
dispatchPageRender() {
this.eventBus.dispatch("pagerender", {
source: this,
pageNumber: this.id
});
}
dispatchPageRendered(cssTransform, isDetailView) {
this.eventBus.dispatch("pagerendered", {
source: this,
pageNumber: this.id,
cssTransform,
isDetailView,
timestamp: performance.now(),
error: this.#renderError
});
}
}
;// ./web/draw_layer_builder.js
class DrawLayerBuilder {
#drawLayer = null;
constructor(options) {
this.pageIndex = options.pageIndex;
}
async render({
intent = "display"
}) {
if (intent !== "display" || this.#drawLayer || this._cancelled) {
return;
}
this.#drawLayer = new DrawLayer({
pageIndex: this.pageIndex
});
}
cancel() {
this._cancelled = true;
if (!this.#drawLayer) {
return;
}
this.#drawLayer.destroy();
this.#drawLayer = null;
}
setParent(parent) {
this.#drawLayer?.setParent(parent);
}
getDrawLayer() {
return this.#drawLayer;
}
}
;// ./web/pdf_page_detail_view.js
class PDFPageDetailView extends BasePDFPageView {
#detailArea = null;
renderingCancelled = false;
constructor({
pageView
}) {
super(pageView);
this.pageView = pageView;
this.renderingId = "detail" + this.id;
this.div = pageView.div;
}
setPdfPage(pdfPage) {
this.pageView.setPdfPage(pdfPage);
}
get pdfPage() {
return this.pageView.pdfPage;
}
get renderingState() {
return super.renderingState;
}
set renderingState(value) {
this.renderingCancelled = false;
super.renderingState = value;
}
reset({
keepCanvas = false
} = {}) {
const renderingCancelled = this.renderingCancelled || this.renderingState === RenderingStates.RUNNING || this.renderingState === RenderingStates.PAUSED;
this.cancelRendering();
this.renderingState = RenderingStates.INITIAL;
this.renderingCancelled = renderingCancelled;
if (!keepCanvas) {
this._resetCanvas();
}
}
#shouldRenderDifferentArea(visibleArea) {
if (!this.#detailArea) {
return true;
}
const minDetailX = this.#detailArea.minX;
const minDetailY = this.#detailArea.minY;
const maxDetailX = this.#detailArea.width + minDetailX;
const maxDetailY = this.#detailArea.height + minDetailY;
if (visibleArea.minX < minDetailX || visibleArea.minY < minDetailY || visibleArea.maxX > maxDetailX || visibleArea.maxY > maxDetailY) {
return true;
}
const {
width: maxWidth,
height: maxHeight,
scale
} = this.pageView.viewport;
if (this.#detailArea.scale !== scale) {
return true;
}
const paddingLeftSize = visibleArea.minX - minDetailX;
const paddingRightSize = maxDetailX - visibleArea.maxX;
const paddingTopSize = visibleArea.minY - minDetailY;
const paddingBottomSize = maxDetailY - visibleArea.maxY;
const MOVEMENT_THRESHOLD = 0.5;
const ratio = (1 + MOVEMENT_THRESHOLD) / MOVEMENT_THRESHOLD;
if (minDetailX > 0 && paddingRightSize / paddingLeftSize > ratio || maxDetailX < maxWidth && paddingLeftSize / paddingRightSize > ratio || minDetailY > 0 && paddingBottomSize / paddingTopSize > ratio || maxDetailY < maxHeight && paddingTopSize / paddingBottomSize > ratio) {
return true;
}
return false;
}
update({
visibleArea = null,
underlyingViewUpdated = false
} = {}) {
if (underlyingViewUpdated) {
this.cancelRendering();
this.renderingState = RenderingStates.INITIAL;
return;
}
if (!this.#shouldRenderDifferentArea(visibleArea)) {
return;
}
const {
viewport,
maxCanvasPixels
} = this.pageView;
const visibleWidth = visibleArea.maxX - visibleArea.minX;
const visibleHeight = visibleArea.maxY - visibleArea.minY;
const visiblePixels = visibleWidth * visibleHeight * OutputScale.pixelRatio ** 2;
const maxDetailToVisibleLinearRatio = Math.sqrt(maxCanvasPixels / visiblePixels);
const maxOverflowScale = (maxDetailToVisibleLinearRatio - 1) / 2;
let overflowScale = Math.min(1, maxOverflowScale);
if (overflowScale < 0) {
overflowScale = 0;
}
const overflowWidth = visibleWidth * overflowScale;
const overflowHeight = visibleHeight * overflowScale;
const minX = Math.max(0, visibleArea.minX - overflowWidth);
const maxX = Math.min(viewport.width, visibleArea.maxX + overflowWidth);
const minY = Math.max(0, visibleArea.minY - overflowHeight);
const maxY = Math.min(viewport.height, visibleArea.maxY + overflowHeight);
const width = maxX - minX;
const height = maxY - minY;
this.#detailArea = {
minX,
minY,
width,
height,
scale: viewport.scale
};
this.reset({
keepCanvas: true
});
}
async draw() {
if (this.pageView.detailView !== this) {
return undefined;
}
const hideUntilComplete = this.pageView.renderingState === RenderingStates.FINISHED || this.renderingState === RenderingStates.FINISHED;
if (this.renderingState !== RenderingStates.INITIAL) {
console.error("Must be in new state before drawing");
this.reset();
}
const {
div,
pdfPage,
viewport
} = this.pageView;
if (!pdfPage) {
this.renderingState = RenderingStates.FINISHED;
throw new Error("pdfPage is not loaded");
}
this.renderingState = RenderingStates.RUNNING;
const canvasWrapper = this.pageView._ensureCanvasWrapper();
const {
canvas,
prevCanvas,
ctx
} = this._createCanvas(newCanvas => {
if (canvasWrapper.firstElementChild?.tagName === "CANVAS") {
canvasWrapper.firstElementChild.after(newCanvas);
} else {
canvasWrapper.prepend(newCanvas);
}
}, hideUntilComplete);
canvas.setAttribute("aria-hidden", "true");
const {
width,
height
} = viewport;
const area = this.#detailArea;
const {
pixelRatio
} = OutputScale;
const transform = [pixelRatio, 0, 0, pixelRatio, -area.minX * pixelRatio, -area.minY * pixelRatio];
canvas.width = area.width * pixelRatio;
canvas.height = area.height * pixelRatio;
const {
style
} = canvas;
style.width = `${area.width * 100 / width}%`;
style.height = `${area.height * 100 / height}%`;
style.top = `${area.minY * 100 / height}%`;
style.left = `${area.minX * 100 / width}%`;
const renderingPromise = this._drawCanvas(this.pageView._getRenderingContext(ctx, transform), () => {
this.canvas?.remove();
this.canvas = prevCanvas;
}, () => {
this.dispatchPageRendered(false, true);
});
div.setAttribute("data-loaded", true);
this.dispatchPageRender();
return renderingPromise;
}
}
;// ./web/struct_tree_layer_builder.js
const PDF_ROLE_TO_HTML_ROLE = {
Document: null,
DocumentFragment: null,
Part: "group",
Sect: "group",
Div: "group",
Aside: "note",
NonStruct: "none",
P: null,
H: "heading",
Title: null,
FENote: "note",
Sub: "group",
Lbl: null,
Span: null,
Em: null,
Strong: null,
Link: "link",
Annot: "note",
Form: "form",
Ruby: null,
RB: null,
RT: null,
RP: null,
Warichu: null,
WT: null,
WP: null,
L: "list",
LI: "listitem",
LBody: null,
Table: "table",
TR: "row",
TH: "columnheader",
TD: "cell",
THead: "columnheader",
TBody: null,
TFoot: null,
Caption: null,
Figure: "figure",
Formula: null,
Artifact: null
};
const HEADING_PATTERN = /^H(\d+)$/;
class StructTreeLayerBuilder {
#promise;
#treeDom = null;
#treePromise;
#elementAttributes = new Map();
#rawDims;
#elementsToAddToTextLayer = null;
constructor(pdfPage, rawDims) {
this.#promise = pdfPage.getStructTree();
this.#rawDims = rawDims;
}
async render() {
if (this.#treePromise) {
return this.#treePromise;
}
const {
promise,
resolve,
reject
} = Promise.withResolvers();
this.#treePromise = promise;
try {
this.#treeDom = this.#walk(await this.#promise);
} catch (ex) {
reject(ex);
}
this.#promise = null;
this.#treeDom?.classList.add("structTree");
resolve(this.#treeDom);
return promise;
}
async getAriaAttributes(annotationId) {
try {
await this.render();
return this.#elementAttributes.get(annotationId);
} catch {}
return null;
}
hide() {
if (this.#treeDom && !this.#treeDom.hidden) {
this.#treeDom.hidden = true;
}
}
show() {
if (this.#treeDom?.hidden) {
this.#treeDom.hidden = false;
}
}
#setAttributes(structElement, htmlElement) {
const {
alt,
id,
lang
} = structElement;
if (alt !== undefined) {
let added = false;
const label = removeNullCharacters(alt);
for (const child of structElement.children) {
if (child.type === "annotation") {
let attrs = this.#elementAttributes.get(child.id);
if (!attrs) {
attrs = new Map();
this.#elementAttributes.set(child.id, attrs);
}
attrs.set("aria-label", label);
added = true;
}
}
if (!added) {
htmlElement.setAttribute("aria-label", label);
}
}
if (id !== undefined) {
htmlElement.setAttribute("aria-owns", id);
}
if (lang !== undefined) {
htmlElement.setAttribute("lang", removeNullCharacters(lang, true));
}
}
#addImageInTextLayer(node, element) {
const {
alt,
bbox,
children
} = node;
const child = children?.[0];
if (!this.#rawDims || !alt || !bbox || child?.type !== "content") {
return false;
}
const {
id
} = child;
if (!id) {
return false;
}
element.setAttribute("aria-owns", id);
const img = document.createElement("span");
(this.#elementsToAddToTextLayer ||= new Map()).set(id, img);
img.setAttribute("role", "img");
img.setAttribute("aria-label", removeNullCharacters(alt));
const {
pageHeight,
pageX,
pageY
} = this.#rawDims;
const calc = "calc(var(--total-scale-factor) *";
const {
style
} = img;
style.width = `${calc}${bbox[2] - bbox[0]}px)`;
style.height = `${calc}${bbox[3] - bbox[1]}px)`;
style.left = `${calc}${bbox[0] - pageX}px)`;
style.top = `${calc}${pageHeight - bbox[3] + pageY}px)`;
return true;
}
addElementsToTextLayer() {
if (!this.#elementsToAddToTextLayer) {
return;
}
for (const [id, img] of this.#elementsToAddToTextLayer) {
document.getElementById(id)?.append(img);
}
this.#elementsToAddToTextLayer.clear();
this.#elementsToAddToTextLayer = null;
}
#walk(node) {
if (!node) {
return null;
}
const element = document.createElement("span");
if ("role" in node) {
const {
role
} = node;
const match = role.match(HEADING_PATTERN);
if (match) {
element.setAttribute("role", "heading");
element.setAttribute("aria-level", match[1]);
} else if (PDF_ROLE_TO_HTML_ROLE[role]) {
element.setAttribute("role", PDF_ROLE_TO_HTML_ROLE[role]);
}
if (role === "Figure" && this.#addImageInTextLayer(node, element)) {
return element;
}
}
this.#setAttributes(node, element);
if (node.children) {
if (node.children.length === 1 && "id" in node.children[0]) {
this.#setAttributes(node.children[0], element);
} else {
for (const kid of node.children) {
element.append(this.#walk(kid));
}
}
}
return element;
}
}
;// ./web/text_accessibility.js
class TextAccessibilityManager {
#enabled = false;
#textChildren = null;
#textNodes = new Map();
#waitingElements = new Map();
setTextMapping(textDivs) {
this.#textChildren = textDivs;
}
static #compareElementPositions(e1, e2) {
const rect1 = e1.getBoundingClientRect();
const rect2 = e2.getBoundingClientRect();
if (rect1.width === 0 && rect1.height === 0) {
return +1;
}
if (rect2.width === 0 && rect2.height === 0) {
return -1;
}
const top1 = rect1.y;
const bot1 = rect1.y + rect1.height;
const mid1 = rect1.y + rect1.height / 2;
const top2 = rect2.y;
const bot2 = rect2.y + rect2.height;
const mid2 = rect2.y + rect2.height / 2;
if (mid1 <= top2 && mid2 >= bot1) {
return -1;
}
if (mid2 <= top1 && mid1 >= bot2) {
return +1;
}
const centerX1 = rect1.x + rect1.width / 2;
const centerX2 = rect2.x + rect2.width / 2;
return centerX1 - centerX2;
}
enable() {
if (this.#enabled) {
throw new Error("TextAccessibilityManager is already enabled.");
}
if (!this.#textChildren) {
throw new Error("Text divs and strings have not been set.");
}
this.#enabled = true;
this.#textChildren = this.#textChildren.slice();
this.#textChildren.sort(TextAccessibilityManager.#compareElementPositions);
if (this.#textNodes.size > 0) {
const textChildren = this.#textChildren;
for (const [id, nodeIndex] of this.#textNodes) {
const element = document.getElementById(id);
if (!element) {
this.#textNodes.delete(id);
continue;
}
this.#addIdToAriaOwns(id, textChildren[nodeIndex]);
}
}
for (const [element, isRemovable] of this.#waitingElements) {
this.addPointerInTextLayer(element, isRemovable);
}
this.#waitingElements.clear();
}
disable() {
if (!this.#enabled) {
return;
}
this.#waitingElements.clear();
this.#textChildren = null;
this.#enabled = false;
}
removePointerInTextLayer(element) {
if (!this.#enabled) {
this.#waitingElements.delete(element);
return;
}
const children = this.#textChildren;
if (!children || children.length === 0) {
return;
}
const {
id
} = element;
const nodeIndex = this.#textNodes.get(id);
if (nodeIndex === undefined) {
return;
}
const node = children[nodeIndex];
this.#textNodes.delete(id);
let owns = node.getAttribute("aria-owns");
if (owns?.includes(id)) {
owns = owns.split(" ").filter(x => x !== id).join(" ");
if (owns) {
node.setAttribute("aria-owns", owns);
} else {
node.removeAttribute("aria-owns");
node.setAttribute("role", "presentation");
}
}
}
#addIdToAriaOwns(id, node) {
const owns = node.getAttribute("aria-owns");
if (!owns?.includes(id)) {
node.setAttribute("aria-owns", owns ? `${owns} ${id}` : id);
}
node.removeAttribute("role");
}
addPointerInTextLayer(element, isRemovable) {
const {
id
} = element;
if (!id) {
return null;
}
if (!this.#enabled) {
this.#waitingElements.set(element, isRemovable);
return null;
}
if (isRemovable) {
this.removePointerInTextLayer(element);
}
const children = this.#textChildren;
if (!children || children.length === 0) {
return null;
}
const index = binarySearchFirstItem(children, node => TextAccessibilityManager.#compareElementPositions(element, node) < 0);
const nodeIndex = Math.max(0, index - 1);
const child = children[nodeIndex];
this.#addIdToAriaOwns(id, child);
this.#textNodes.set(id, nodeIndex);
const parent = child.parentNode;
return parent?.classList.contains("markedContent") ? parent.id : null;
}
moveElementInDOM(container, element, contentElement, isRemovable) {
const id = this.addPointerInTextLayer(contentElement, isRemovable);
if (!container.hasChildNodes()) {
container.append(element);
return id;
}
const children = Array.from(container.childNodes).filter(node => node !== element);
if (children.length === 0) {
return id;
}
const elementToCompare = contentElement || element;
const index = binarySearchFirstItem(children, node => TextAccessibilityManager.#compareElementPositions(elementToCompare, node) < 0);
if (index === 0) {
children[0].before(element);
} else {
children[index - 1].after(element);
}
return id;
}
}
;// ./web/text_highlighter.js
class TextHighlighter {
#eventAbortController = null;
constructor({
findController,
eventBus,
pageIndex
}) {
this.findController = findController;
this.matches = [];
this.eventBus = eventBus;
this.pageIdx = pageIndex;
this.textDivs = null;
this.textContentItemsStr = null;
this.enabled = false;
}
setTextMapping(divs, texts) {
this.textDivs = divs;
this.textContentItemsStr = texts;
}
enable() {
if (!this.textDivs || !this.textContentItemsStr) {
throw new Error("Text divs and strings have not been set.");
}
if (this.enabled) {
throw new Error("TextHighlighter is already enabled.");
}
this.enabled = true;
if (!this.#eventAbortController) {
this.#eventAbortController = new AbortController();
this.eventBus._on("updatetextlayermatches", evt => {
if (evt.pageIndex === this.pageIdx || evt.pageIndex === -1) {
this._updateMatches();
}
}, {
signal: this.#eventAbortController.signal
});
}
this._updateMatches();
}
disable() {
if (!this.enabled) {
return;
}
this.enabled = false;
this.#eventAbortController?.abort();
this.#eventAbortController = null;
this._updateMatches(true);
}
_convertMatches(matches, matchesLength) {
if (!matches) {
return [];
}
const {
textContentItemsStr
} = this;
let i = 0,
iIndex = 0;
const end = textContentItemsStr.length - 1;
const result = [];
for (let m = 0, mm = matches.length; m < mm; m++) {
let matchIdx = matches[m];
while (i !== end && matchIdx >= iIndex + textContentItemsStr[i].length) {
iIndex += textContentItemsStr[i].length;
i++;
}
if (i === textContentItemsStr.length) {
console.error("Could not find a matching mapping");
}
const match = {
begin: {
divIdx: i,
offset: matchIdx - iIndex
}
};
matchIdx += matchesLength[m];
while (i !== end && matchIdx > iIndex + textContentItemsStr[i].length) {
iIndex += textContentItemsStr[i].length;
i++;
}
match.end = {
divIdx: i,
offset: matchIdx - iIndex
};
result.push(match);
}
return result;
}
_renderMatches(matches) {
if (matches.length === 0) {
return;
}
const {
findController,
pageIdx
} = this;
const {
textContentItemsStr,
textDivs
} = this;
const isSelectedPage = pageIdx === findController.selected.pageIdx;
const selectedMatchIdx = findController.selected.matchIdx;
const highlightAll = findController.state.highlightAll;
let prevEnd = null;
const infinity = {
divIdx: -1,
offset: undefined
};
function beginText(begin, className) {
const divIdx = begin.divIdx;
textDivs[divIdx].textContent = "";
return appendTextToDiv(divIdx, 0, begin.offset, className);
}
function appendTextToDiv(divIdx, fromOffset, toOffset, className) {
let div = textDivs[divIdx];
if (div.nodeType === Node.TEXT_NODE) {
const span = document.createElement("span");
div.before(span);
span.append(div);
textDivs[divIdx] = span;
div = span;
}
const content = textContentItemsStr[divIdx].substring(fromOffset, toOffset);
const node = document.createTextNode(content);
if (className) {
const span = document.createElement("span");
span.className = `${className} appended`;
span.append(node);
div.append(span);
if (className.includes("selected")) {
const {
left
} = span.getClientRects()[0];
const parentLeft = div.getBoundingClientRect().left;
return left - parentLeft;
}
return 0;
}
div.append(node);
return 0;
}
let i0 = selectedMatchIdx,
i1 = i0 + 1;
if (highlightAll) {
i0 = 0;
i1 = matches.length;
} else if (!isSelectedPage) {
return;
}
let lastDivIdx = -1;
let lastOffset = -1;
for (let i = i0; i < i1; i++) {
const match = matches[i];
const begin = match.begin;
if (begin.divIdx === lastDivIdx && begin.offset === lastOffset) {
continue;
}
lastDivIdx = begin.divIdx;
lastOffset = begin.offset;
const end = match.end;
const isSelected = isSelectedPage && i === selectedMatchIdx;
const highlightSuffix = isSelected ? " selected" : "";
let selectedLeft = 0;
if (!prevEnd || begin.divIdx !== prevEnd.divIdx) {
if (prevEnd !== null) {
appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset);
}
beginText(begin);
} else {
appendTextToDiv(prevEnd.divIdx, prevEnd.offset, begin.offset);
}
if (begin.divIdx === end.divIdx) {
selectedLeft = appendTextToDiv(begin.divIdx, begin.offset, end.offset, "highlight" + highlightSuffix);
} else {
selectedLeft = appendTextToDiv(begin.divIdx, begin.offset, infinity.offset, "highlight begin" + highlightSuffix);
for (let n0 = begin.divIdx + 1, n1 = end.divIdx; n0 < n1; n0++) {
textDivs[n0].className = "highlight middle" + highlightSuffix;
}
beginText(end, "highlight end" + highlightSuffix);
}
prevEnd = end;
if (isSelected) {
findController.scrollMatchIntoView({
element: textDivs[begin.divIdx],
selectedLeft,
pageIndex: pageIdx,
matchIndex: selectedMatchIdx
});
}
}
if (prevEnd) {
appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset);
}
}
_updateMatches(reset = false) {
if (!this.enabled && !reset) {
return;
}
const {
findController,
matches,
pageIdx
} = this;
const {
textContentItemsStr,
textDivs
} = this;
let clearedUntilDivIdx = -1;
for (const match of matches) {
const begin = Math.max(clearedUntilDivIdx, match.begin.divIdx);
for (let n = begin, end = match.end.divIdx; n <= end; n++) {
const div = textDivs[n];
div.textContent = textContentItemsStr[n];
div.className = "";
}
clearedUntilDivIdx = match.end.divIdx + 1;
}
if (!findController?.highlightMatches || reset) {
return;
}
const pageMatches = findController.pageMatches[pageIdx] || null;
const pageMatchesLength = findController.pageMatchesLength[pageIdx] || null;
this.matches = this._convertMatches(pageMatches, pageMatchesLength);
this._renderMatches(this.matches);
}
}
;// ./web/text_layer_builder.js
class TextLayerBuilder {
#enablePermissions = false;
#onAppend = null;
#renderingDone = false;
#textLayer = null;
static #textLayers = new Map();
static #selectionChangeAbortController = null;
constructor({
pdfPage,
highlighter = null,
accessibilityManager = null,
enablePermissions = false,
onAppend = null
}) {
this.pdfPage = pdfPage;
this.highlighter = highlighter;
this.accessibilityManager = accessibilityManager;
this.#enablePermissions = enablePermissions === true;
this.#onAppend = onAppend;
this.div = document.createElement("div");
this.div.tabIndex = 0;
this.div.className = "textLayer";
}
async render({
viewport,
textContentParams = null
}) {
if (this.#renderingDone && this.#textLayer) {
this.#textLayer.update({
viewport,
onBefore: this.hide.bind(this)
});
this.show();
return;
}
this.cancel();
this.#textLayer = new TextLayer({
textContentSource: this.pdfPage.streamTextContent(textContentParams || {
includeMarkedContent: true,
disableNormalization: true
}),
container: this.div,
viewport
});
const {
textDivs,
textContentItemsStr
} = this.#textLayer;
this.highlighter?.setTextMapping(textDivs, textContentItemsStr);
this.accessibilityManager?.setTextMapping(textDivs);
await this.#textLayer.render();
this.#renderingDone = true;
const endOfContent = document.createElement("div");
endOfContent.className = "endOfContent";
this.div.append(endOfContent);
this.#bindMouse(endOfContent);
this.#onAppend?.(this.div);
this.highlighter?.enable();
this.accessibilityManager?.enable();
}
hide() {
if (!this.div.hidden && this.#renderingDone) {
this.highlighter?.disable();
this.div.hidden = true;
}
}
show() {
if (this.div.hidden && this.#renderingDone) {
this.div.hidden = false;
this.highlighter?.enable();
}
}
cancel() {
this.#textLayer?.cancel();
this.#textLayer = null;
this.highlighter?.disable();
this.accessibilityManager?.disable();
TextLayerBuilder.#removeGlobalSelectionListener(this.div);
}
#bindMouse(end) {
const {
div
} = this;
div.addEventListener("mousedown", () => {
div.classList.add("selecting");
});
div.addEventListener("copy", event => {
if (!this.#enablePermissions) {
const selection = document.getSelection();
event.clipboardData.setData("text/plain", removeNullCharacters(normalizeUnicode(selection.toString())));
}
stopEvent(event);
});
TextLayerBuilder.#textLayers.set(div, end);
TextLayerBuilder.#enableGlobalSelectionListener();
}
static #removeGlobalSelectionListener(textLayerDiv) {
this.#textLayers.delete(textLayerDiv);
if (this.#textLayers.size === 0) {
this.#selectionChangeAbortController?.abort();
this.#selectionChangeAbortController = null;
}
}
static #enableGlobalSelectionListener() {
if (this.#selectionChangeAbortController) {
return;
}
this.#selectionChangeAbortController = new AbortController();
const {
signal
} = this.#selectionChangeAbortController;
const reset = (end, textLayer) => {
textLayer.append(end);
end.style.width = "";
end.style.height = "";
textLayer.classList.remove("selecting");
};
let isPointerDown = false;
document.addEventListener("pointerdown", () => {
isPointerDown = true;
}, {
signal
});
document.addEventListener("pointerup", () => {
isPointerDown = false;
this.#textLayers.forEach(reset);
}, {
signal
});
window.addEventListener("blur", () => {
isPointerDown = false;
this.#textLayers.forEach(reset);
}, {
signal
});
document.addEventListener("keyup", () => {
if (!isPointerDown) {
this.#textLayers.forEach(reset);
}
}, {
signal
});
var isFirefox, prevRange;
document.addEventListener("selectionchange", () => {
const selection = document.getSelection();
if (selection.rangeCount === 0) {
this.#textLayers.forEach(reset);
return;
}
const activeTextLayers = new Set();
for (let i = 0; i < selection.rangeCount; i++) {
const range = selection.getRangeAt(i);
for (const textLayerDiv of this.#textLayers.keys()) {
if (!activeTextLayers.has(textLayerDiv) && range.intersectsNode(textLayerDiv)) {
activeTextLayers.add(textLayerDiv);
}
}
}
for (const [textLayerDiv, endDiv] of this.#textLayers) {
if (activeTextLayers.has(textLayerDiv)) {
textLayerDiv.classList.add("selecting");
} else {
reset(endDiv, textLayerDiv);
}
}
isFirefox ??= getComputedStyle(this.#textLayers.values().next().value).getPropertyValue("-moz-user-select") === "none";
if (isFirefox) {
return;
}
const range = selection.getRangeAt(0);
const modifyStart = prevRange && (range.compareBoundaryPoints(Range.END_TO_END, prevRange) === 0 || range.compareBoundaryPoints(Range.START_TO_END, prevRange) === 0);
let anchor = modifyStart ? range.startContainer : range.endContainer;
if (anchor.nodeType === Node.TEXT_NODE) {
anchor = anchor.parentNode;
}
const parentTextLayer = anchor.parentElement?.closest(".textLayer");
const endDiv = this.#textLayers.get(parentTextLayer);
if (endDiv) {
endDiv.style.width = parentTextLayer.style.width;
endDiv.style.height = parentTextLayer.style.height;
anchor.parentElement.insertBefore(endDiv, modifyStart ? anchor : anchor.nextSibling);
}
prevRange = range.cloneRange();
}, {
signal
});
}
}
;// ./web/pdf_page_view.js
const DEFAULT_LAYER_PROPERTIES = null;
const LAYERS_ORDER = new Map([["canvasWrapper", 0], ["textLayer", 1], ["annotationLayer", 2], ["annotationEditorLayer", 3], ["xfaLayer", 3]]);
class PDFPageView extends BasePDFPageView {
#annotationMode = AnnotationMode.ENABLE_FORMS;
#canvasWrapper = null;
#enableAutoLinking = true;
#hasRestrictedScaling = false;
#isEditing = false;
#layerProperties = null;
#needsRestrictedScaling = false;
#originalViewport = null;
#previousRotation = null;
#scaleRoundX = 1;
#scaleRoundY = 1;
#textLayerMode = TextLayerMode.ENABLE;
#userUnit = 1;
#useThumbnailCanvas = {
directDrawing: true,
initialOptionalContent: true,
regularAnnotations: true
};
#layers = [null, null, null, null];
constructor(options) {
super(options);
const container = options.container;
const defaultViewport = options.defaultViewport;
this.renderingId = "page" + this.id;
this.#layerProperties = options.layerProperties || DEFAULT_LAYER_PROPERTIES;
this.pdfPage = null;
this.pageLabel = null;
this.rotation = 0;
this.scale = options.scale || DEFAULT_SCALE;
this.viewport = defaultViewport;
this.pdfPageRotate = defaultViewport.rotation;
this._optionalContentConfigPromise = options.optionalContentConfigPromise || null;
this.#textLayerMode = options.textLayerMode ?? TextLayerMode.ENABLE;
this.#annotationMode = options.annotationMode ?? AnnotationMode.ENABLE_FORMS;
this.imageResourcesPath = options.imageResourcesPath || "";
this.enableDetailCanvas = options.enableDetailCanvas ?? true;
this.maxCanvasPixels = options.maxCanvasPixels ?? AppOptions.get("maxCanvasPixels");
this.maxCanvasDim = options.maxCanvasDim || AppOptions.get("maxCanvasDim");
this.#enableAutoLinking = options.enableAutoLinking !== false;
this.l10n = options.l10n;
this.l10n ||= new genericl10n_GenericL10n();
this._isStandalone = !this.renderingQueue?.hasViewer();
this._container = container;
this._annotationCanvasMap = null;
this.annotationLayer = null;
this.annotationEditorLayer = null;
this.textLayer = null;
this.xfaLayer = null;
this.structTreeLayer = null;
this.drawLayer = null;
this.detailView = null;
const div = document.createElement("div");
div.className = "page";
div.setAttribute("data-page-number", this.id);
div.setAttribute("role", "region");
div.setAttribute("data-l10n-id", "pdfjs-page-landmark");
div.setAttribute("data-l10n-args", JSON.stringify({
page: this.id
}));
this.div = div;
this.#setDimensions();
container?.append(div);
if (this._isStandalone) {
container?.style.setProperty("--scale-factor", this.scale * PixelsPerInch.PDF_TO_CSS_UNITS);
if (this.pageColors?.background) {
container?.style.setProperty("--page-bg-color", this.pageColors.background);
}
const {
optionalContentConfigPromise
} = options;
if (optionalContentConfigPromise) {
optionalContentConfigPromise.then(optionalContentConfig => {
if (optionalContentConfigPromise !== this._optionalContentConfigPromise) {
return;
}
this.#useThumbnailCanvas.initialOptionalContent = optionalContentConfig.hasInitialVisibility;
});
}
if (!options.l10n) {
this.l10n.translate(this.div);
}
}
}
#addLayer(div, name) {
const pos = LAYERS_ORDER.get(name);
const oldDiv = this.#layers[pos];
this.#layers[pos] = div;
if (oldDiv) {
oldDiv.replaceWith(div);
return;
}
for (let i = pos - 1; i >= 0; i--) {
const layer = this.#layers[i];
if (layer) {
layer.after(div);
return;
}
}
this.div.prepend(div);
}
#setDimensions() {
const {
div,
viewport
} = this;
if (viewport.userUnit !== this.#userUnit) {
if (viewport.userUnit !== 1) {
div.style.setProperty("--user-unit", viewport.userUnit);
} else {
div.style.removeProperty("--user-unit");
}
this.#userUnit = viewport.userUnit;
}
if (this.pdfPage) {
if (this.#previousRotation === viewport.rotation) {
return;
}
this.#previousRotation = viewport.rotation;
}
setLayerDimensions(div, viewport, true, false);
}
setPdfPage(pdfPage) {
if (this._isStandalone && (this.pageColors?.foreground === "CanvasText" || this.pageColors?.background === "Canvas")) {
this._container?.style.setProperty("--hcm-highlight-filter", pdfPage.filterFactory.addHighlightHCMFilter("highlight", "CanvasText", "Canvas", "HighlightText", "Highlight"));
this._container?.style.setProperty("--hcm-highlight-selected-filter", pdfPage.filterFactory.addHighlightHCMFilter("highlight_selected", "CanvasText", "Canvas", "HighlightText", "Highlight"));
}
this.pdfPage = pdfPage;
this.pdfPageRotate = pdfPage.rotate;
const totalRotation = (this.rotation + this.pdfPageRotate) % 360;
this.viewport = pdfPage.getViewport({
scale: this.scale * PixelsPerInch.PDF_TO_CSS_UNITS,
rotation: totalRotation
});
this.#setDimensions();
this.reset();
}
destroy() {
this.reset();
this.pdfPage?.cleanup();
}
hasEditableAnnotations() {
return !!this.annotationLayer?.hasEditableAnnotations();
}
get _textHighlighter() {
return shadow(this, "_textHighlighter", new TextHighlighter({
pageIndex: this.id - 1,
eventBus: this.eventBus,
findController: this.#layerProperties.findController
}));
}
#dispatchLayerRendered(name, error) {
this.eventBus.dispatch(name, {
source: this,
pageNumber: this.id,
error
});
}
async #renderAnnotationLayer() {
let error = null;
try {
await this.annotationLayer.render({
viewport: this.viewport,
intent: "display",
structTreeLayer: this.structTreeLayer
});
} catch (ex) {
console.error("#renderAnnotationLayer:", ex);
error = ex;
} finally {
this.#dispatchLayerRendered("annotationlayerrendered", error);
}
}
async #renderAnnotationEditorLayer() {
let error = null;
try {
await this.annotationEditorLayer.render({
viewport: this.viewport,
intent: "display"
});
} catch (ex) {
console.error("#renderAnnotationEditorLayer:", ex);
error = ex;
} finally {
this.#dispatchLayerRendered("annotationeditorlayerrendered", error);
}
}
async #renderDrawLayer() {
try {
await this.drawLayer.render({
intent: "display"
});
} catch (ex) {
console.error("#renderDrawLayer:", ex);
}
}
async #renderXfaLayer() {
let error = null;
try {
const result = await this.xfaLayer.render({
viewport: this.viewport,
intent: "display"
});
if (result?.textDivs && this._textHighlighter) {
this.#buildXfaTextContentItems(result.textDivs);
}
} catch (ex) {
console.error("#renderXfaLayer:", ex);
error = ex;
} finally {
if (this.xfaLayer?.div) {
this.l10n.pause();
this.#addLayer(this.xfaLayer.div, "xfaLayer");
this.l10n.resume();
}
this.#dispatchLayerRendered("xfalayerrendered", error);
}
}
async #renderTextLayer() {
if (!this.textLayer) {
return;
}
let error = null;
try {
await this.textLayer.render({
viewport: this.viewport
});
} catch (ex) {
if (ex instanceof AbortException) {
return;
}
console.error("#renderTextLayer:", ex);
error = ex;
}
this.#dispatchLayerRendered("textlayerrendered", error);
this.#renderStructTreeLayer();
}
async #renderStructTreeLayer() {
if (!this.textLayer) {
return;
}
const treeDom = await this.structTreeLayer?.render();
if (treeDom) {
this.l10n.pause();
this.structTreeLayer?.addElementsToTextLayer();
if (this.canvas && treeDom.parentNode !== this.canvas) {
this.canvas.append(treeDom);
}
this.l10n.resume();
}
this.structTreeLayer?.show();
}
async #buildXfaTextContentItems(textDivs) {
const text = await this.pdfPage.getTextContent();
const items = [];
for (const item of text.items) {
items.push(item.str);
}
this._textHighlighter.setTextMapping(textDivs, items);
this._textHighlighter.enable();
}
async #injectLinkAnnotations(textLayerPromise) {
let error = null;
try {
await textLayerPromise;
if (!this.annotationLayer) {
return;
}
await this.annotationLayer.injectLinkAnnotations({
inferredLinks: Autolinker.processLinks(this),
viewport: this.viewport,
structTreeLayer: this.structTreeLayer
});
} catch (ex) {
console.error("#injectLinkAnnotations:", ex);
error = ex;
}
}
_resetCanvas() {
super._resetCanvas();
this.#originalViewport = null;
}
reset({
keepAnnotationLayer = false,
keepAnnotationEditorLayer = false,
keepXfaLayer = false,
keepTextLayer = false,
keepCanvasWrapper = false,
preserveDetailViewState = false
} = {}) {
this.cancelRendering({
keepAnnotationLayer,
keepAnnotationEditorLayer,
keepXfaLayer,
keepTextLayer
});
this.renderingState = RenderingStates.INITIAL;
const div = this.div;
const childNodes = div.childNodes,
annotationLayerNode = keepAnnotationLayer && this.annotationLayer?.div || null,
annotationEditorLayerNode = keepAnnotationEditorLayer && this.annotationEditorLayer?.div || null,
xfaLayerNode = keepXfaLayer && this.xfaLayer?.div || null,
textLayerNode = keepTextLayer && this.textLayer?.div || null,
canvasWrapperNode = keepCanvasWrapper && this.#canvasWrapper || null;
for (let i = childNodes.length - 1; i >= 0; i--) {
const node = childNodes[i];
switch (node) {
case annotationLayerNode:
case annotationEditorLayerNode:
case xfaLayerNode:
case textLayerNode:
case canvasWrapperNode:
continue;
}
node.remove();
const layerIndex = this.#layers.indexOf(node);
if (layerIndex >= 0) {
this.#layers[layerIndex] = null;
}
}
div.removeAttribute("data-loaded");
if (annotationLayerNode) {
this.annotationLayer.hide();
}
if (annotationEditorLayerNode) {
this.annotationEditorLayer.hide();
}
if (xfaLayerNode) {
this.xfaLayer.hide();
}
if (textLayerNode) {
this.textLayer.hide();
}
this.structTreeLayer?.hide();
if (!keepCanvasWrapper && this.#canvasWrapper) {
this.#canvasWrapper = null;
this._resetCanvas();
}
if (!preserveDetailViewState) {
this.detailView?.reset({
keepCanvas: keepCanvasWrapper
});
if (!keepCanvasWrapper) {
this.detailView = null;
}
}
}
toggleEditingMode(isEditing) {
this.#isEditing = isEditing;
if (!this.hasEditableAnnotations()) {
return;
}
this.reset({
keepAnnotationLayer: true,
keepAnnotationEditorLayer: true,
keepXfaLayer: true,
keepTextLayer: true,
keepCanvasWrapper: true
});
}
updateVisibleArea(visibleArea) {
if (this.enableDetailCanvas) {
if (this.#needsRestrictedScaling && this.maxCanvasPixels > 0 && visibleArea) {
this.detailView ??= new PDFPageDetailView({
pageView: this
});
this.detailView.update({
visibleArea
});
} else if (this.detailView) {
this.detailView.reset();
this.detailView = null;
}
}
}
update({
scale = 0,
rotation = null,
optionalContentConfigPromise = null,
drawingDelay = -1
}) {
this.scale = scale || this.scale;
if (typeof rotation === "number") {
this.rotation = rotation;
}
if (optionalContentConfigPromise instanceof Promise) {
this._optionalContentConfigPromise = optionalContentConfigPromise;
optionalContentConfigPromise.then(optionalContentConfig => {
if (optionalContentConfigPromise !== this._optionalContentConfigPromise) {
return;
}
this.#useThumbnailCanvas.initialOptionalContent = optionalContentConfig.hasInitialVisibility;
});
}
this.#useThumbnailCanvas.directDrawing = true;
const totalRotation = (this.rotation + this.pdfPageRotate) % 360;
this.viewport = this.viewport.clone({
scale: this.scale * PixelsPerInch.PDF_TO_CSS_UNITS,
rotation: totalRotation
});
this.#setDimensions();
if (this._isStandalone) {
this._container?.style.setProperty("--scale-factor", this.viewport.scale);
}
this.#computeScale();
if (this.canvas) {
const onlyCssZoom = this.#hasRestrictedScaling && this.#needsRestrictedScaling;
const postponeDrawing = drawingDelay >= 0 && drawingDelay < 1000;
if (postponeDrawing || onlyCssZoom) {
if (postponeDrawing && !onlyCssZoom && this.renderingState !== RenderingStates.FINISHED) {
this.cancelRendering({
keepAnnotationLayer: true,
keepAnnotationEditorLayer: true,
keepXfaLayer: true,
keepTextLayer: true,
cancelExtraDelay: drawingDelay
});
this.renderingState = RenderingStates.FINISHED;
this.#useThumbnailCanvas.directDrawing = false;
}
this.cssTransform({
redrawAnnotationLayer: true,
redrawAnnotationEditorLayer: true,
redrawXfaLayer: true,
redrawTextLayer: !postponeDrawing,
hideTextLayer: postponeDrawing
});
if (!postponeDrawing) {
this.detailView?.update({
underlyingViewUpdated: true
});
this.dispatchPageRendered(true, false);
}
return;
}
}
this.cssTransform({});
this.reset({
keepAnnotationLayer: true,
keepAnnotationEditorLayer: true,
keepXfaLayer: true,
keepTextLayer: true,
keepCanvasWrapper: true,
preserveDetailViewState: true
});
this.detailView?.update({
underlyingViewUpdated: true
});
}
#computeScale() {
const {
width,
height
} = this.viewport;
const outputScale = this.outputScale = new OutputScale();
if (this.maxCanvasPixels === 0) {
const invScale = 1 / this.scale;
outputScale.sx *= invScale;
outputScale.sy *= invScale;
this.#needsRestrictedScaling = true;
} else {
this.#needsRestrictedScaling = outputScale.limitCanvas(width, height, this.maxCanvasPixels, this.maxCanvasDim);
}
}
cancelRendering({
keepAnnotationLayer = false,
keepAnnotationEditorLayer = false,
keepXfaLayer = false,
keepTextLayer = false,
cancelExtraDelay = 0
} = {}) {
super.cancelRendering({
cancelExtraDelay
});
if (this.textLayer && (!keepTextLayer || !this.textLayer.div)) {
this.textLayer.cancel();
this.textLayer = null;
}
if (this.annotationLayer && (!keepAnnotationLayer || !this.annotationLayer.div)) {
this.annotationLayer.cancel();
this.annotationLayer = null;
this._annotationCanvasMap = null;
}
if (this.structTreeLayer && !this.textLayer) {
this.structTreeLayer = null;
}
if (this.annotationEditorLayer && (!keepAnnotationEditorLayer || !this.annotationEditorLayer.div)) {
if (this.drawLayer) {
this.drawLayer.cancel();
this.drawLayer = null;
}
this.annotationEditorLayer.cancel();
this.annotationEditorLayer = null;
}
if (this.xfaLayer && (!keepXfaLayer || !this.xfaLayer.div)) {
this.xfaLayer.cancel();
this.xfaLayer = null;
this._textHighlighter?.disable();
}
}
cssTransform({
redrawAnnotationLayer = false,
redrawAnnotationEditorLayer = false,
redrawXfaLayer = false,
redrawTextLayer = false,
hideTextLayer = false
}) {
const {
canvas
} = this;
if (!canvas) {
return;
}
const originalViewport = this.#originalViewport;
if (this.viewport !== originalViewport) {
const relativeRotation = (360 + this.viewport.rotation - originalViewport.rotation) % 360;
if (relativeRotation === 90 || relativeRotation === 270) {
const {
width,
height
} = this.viewport;
const scaleX = height / width;
const scaleY = width / height;
canvas.style.transform = `rotate(${relativeRotation}deg) scale(${scaleX},${scaleY})`;
} else {
canvas.style.transform = relativeRotation === 0 ? "" : `rotate(${relativeRotation}deg)`;
}
}
if (redrawAnnotationLayer && this.annotationLayer) {
this.#renderAnnotationLayer();
}
if (redrawAnnotationEditorLayer && this.annotationEditorLayer) {
if (this.drawLayer) {
this.#renderDrawLayer();
}
this.#renderAnnotationEditorLayer();
}
if (redrawXfaLayer && this.xfaLayer) {
this.#renderXfaLayer();
}
if (this.textLayer) {
if (hideTextLayer) {
this.textLayer.hide();
this.structTreeLayer?.hide();
} else if (redrawTextLayer) {
this.#renderTextLayer();
}
}
}
get width() {
return this.viewport.width;
}
get height() {
return this.viewport.height;
}
getPagePoint(x, y) {
return this.viewport.convertToPdfPoint(x, y);
}
_ensureCanvasWrapper() {
let canvasWrapper = this.#canvasWrapper;
if (!canvasWrapper) {
canvasWrapper = this.#canvasWrapper = document.createElement("div");
canvasWrapper.classList.add("canvasWrapper");
this.#addLayer(canvasWrapper, "canvasWrapper");
}
return canvasWrapper;
}
_getRenderingContext(canvasContext, transform) {
return {
canvasContext,
transform,
viewport: this.viewport,
annotationMode: this.#annotationMode,
optionalContentConfigPromise: this._optionalContentConfigPromise,
annotationCanvasMap: this._annotationCanvasMap,
pageColors: this.pageColors,
isEditing: this.#isEditing
};
}
async draw() {
if (this.renderingState !== RenderingStates.INITIAL) {
console.error("Must be in new state before drawing");
this.reset();
}
const {
div,
l10n,
pdfPage,
viewport
} = this;
if (!pdfPage) {
this.renderingState = RenderingStates.FINISHED;
throw new Error("pdfPage is not loaded");
}
this.renderingState = RenderingStates.RUNNING;
const canvasWrapper = this._ensureCanvasWrapper();
if (!this.textLayer && this.#textLayerMode !== TextLayerMode.DISABLE && !pdfPage.isPureXfa) {
this._accessibilityManager ||= new TextAccessibilityManager();
this.textLayer = new TextLayerBuilder({
pdfPage,
highlighter: this._textHighlighter,
accessibilityManager: this._accessibilityManager,
enablePermissions: this.#textLayerMode === TextLayerMode.ENABLE_PERMISSIONS,
onAppend: textLayerDiv => {
this.l10n.pause();
this.#addLayer(textLayerDiv, "textLayer");
this.l10n.resume();
}
});
}
if (!this.annotationLayer && this.#annotationMode !== AnnotationMode.DISABLE) {
const {
annotationStorage,
annotationEditorUIManager,
downloadManager,
enableScripting,
fieldObjectsPromise,
hasJSActionsPromise,
linkService
} = this.#layerProperties;
this._annotationCanvasMap ||= new Map();
this.annotationLayer = new AnnotationLayerBuilder({
pdfPage,
annotationStorage,
imageResourcesPath: this.imageResourcesPath,
renderForms: this.#annotationMode === AnnotationMode.ENABLE_FORMS,
linkService,
downloadManager,
enableScripting,
hasJSActionsPromise,
fieldObjectsPromise,
annotationCanvasMap: this._annotationCanvasMap,
accessibilityManager: this._accessibilityManager,
annotationEditorUIManager,
onAppend: annotationLayerDiv => {
this.#addLayer(annotationLayerDiv, "annotationLayer");
}
});
}
const {
width,
height
} = viewport;
this.#originalViewport = viewport;
const {
canvas,
prevCanvas,
ctx
} = this._createCanvas(newCanvas => {
canvasWrapper.prepend(newCanvas);
});
canvas.setAttribute("role", "presentation");
if (!this.outputScale) {
this.#computeScale();
}
const {
outputScale
} = this;
this.#hasRestrictedScaling = this.#needsRestrictedScaling;
const sfx = approximateFraction(outputScale.sx);
const sfy = approximateFraction(outputScale.sy);
const canvasWidth = canvas.width = floorToDivide(calcRound(width * outputScale.sx), sfx[0]);
const canvasHeight = canvas.height = floorToDivide(calcRound(height * outputScale.sy), sfy[0]);
const pageWidth = floorToDivide(calcRound(width), sfx[1]);
const pageHeight = floorToDivide(calcRound(height), sfy[1]);
outputScale.sx = canvasWidth / pageWidth;
outputScale.sy = canvasHeight / pageHeight;
if (this.#scaleRoundX !== sfx[1]) {
div.style.setProperty("--scale-round-x", `${sfx[1]}px`);
this.#scaleRoundX = sfx[1];
}
if (this.#scaleRoundY !== sfy[1]) {
div.style.setProperty("--scale-round-y", `${sfy[1]}px`);
this.#scaleRoundY = sfy[1];
}
const transform = outputScale.scaled ? [outputScale.sx, 0, 0, outputScale.sy, 0, 0] : null;
const resultPromise = this._drawCanvas(this._getRenderingContext(ctx, transform), () => {
prevCanvas?.remove();
this._resetCanvas();
}, renderTask => {
this.#useThumbnailCanvas.regularAnnotations = !renderTask.separateAnnots;
this.dispatchPageRendered(false, false);
}).then(async () => {
this.structTreeLayer ||= new StructTreeLayerBuilder(pdfPage, viewport.rawDims);
const textLayerPromise = this.#renderTextLayer();
if (this.annotationLayer) {
await this.#renderAnnotationLayer();
if (this.#enableAutoLinking && this.annotationLayer && this.textLayer) {
await this.#injectLinkAnnotations(textLayerPromise);
}
}
const {
annotationEditorUIManager
} = this.#layerProperties;
if (!annotationEditorUIManager) {
return;
}
this.drawLayer ||= new DrawLayerBuilder({
pageIndex: this.id
});
await this.#renderDrawLayer();
this.drawLayer.setParent(canvasWrapper);
this.annotationEditorLayer ||= new AnnotationEditorLayerBuilder({
uiManager: annotationEditorUIManager,
pdfPage,
l10n,
structTreeLayer: this.structTreeLayer,
accessibilityManager: this._accessibilityManager,
annotationLayer: this.annotationLayer?.annotationLayer,
textLayer: this.textLayer,
drawLayer: this.drawLayer.getDrawLayer(),
onAppend: annotationEditorLayerDiv => {
this.#addLayer(annotationEditorLayerDiv, "annotationEditorLayer");
}
});
this.#renderAnnotationEditorLayer();
});
if (pdfPage.isPureXfa) {
if (!this.xfaLayer) {
const {
annotationStorage,
linkService
} = this.#layerProperties;
this.xfaLayer = new XfaLayerBuilder({
pdfPage,
annotationStorage,
linkService
});
}
this.#renderXfaLayer();
}
div.setAttribute("data-loaded", true);
this.dispatchPageRender();
return resultPromise;
}
setPageLabel(label) {
this.pageLabel = typeof label === "string" ? label : null;
this.div.setAttribute("data-l10n-args", JSON.stringify({
page: this.pageLabel ?? this.id
}));
if (this.pageLabel !== null) {
this.div.setAttribute("data-page-label", this.pageLabel);
} else {
this.div.removeAttribute("data-page-label");
}
}
get thumbnailCanvas() {
const {
directDrawing,
initialOptionalContent,
regularAnnotations
} = this.#useThumbnailCanvas;
return directDrawing && initialOptionalContent && regularAnnotations ? this.canvas : null;
}
}
;// ./web/pdf_viewer.js
const DEFAULT_CACHE_SIZE = 10;
const PagesCountLimit = {
FORCE_SCROLL_MODE_PAGE: 10000,
FORCE_LAZY_PAGE_INIT: 5000,
PAUSE_EAGER_PAGE_INIT: 250
};
function isValidAnnotationEditorMode(mode) {
return Object.values(AnnotationEditorType).includes(mode) && mode !== AnnotationEditorType.DISABLE;
}
class PDFPageViewBuffer {
#buf = new Set();
#size = 0;
constructor(size) {
this.#size = size;
}
push(view) {
const buf = this.#buf;
if (buf.has(view)) {
buf.delete(view);
}
buf.add(view);
if (buf.size > this.#size) {
this.#destroyFirstView();
}
}
resize(newSize, idsToKeep = null) {
this.#size = newSize;
const buf = this.#buf;
if (idsToKeep) {
const ii = buf.size;
let i = 1;
for (const view of buf) {
if (idsToKeep.has(view.id)) {
buf.delete(view);
buf.add(view);
}
if (++i > ii) {
break;
}
}
}
while (buf.size > this.#size) {
this.#destroyFirstView();
}
}
has(view) {
return this.#buf.has(view);
}
[Symbol.iterator]() {
return this.#buf.keys();
}
#destroyFirstView() {
const firstView = this.#buf.keys().next().value;
firstView?.destroy();
this.#buf.delete(firstView);
}
}
class PDFViewer {
#buffer = null;
#altTextManager = null;
#annotationEditorHighlightColors = null;
#annotationEditorMode = AnnotationEditorType.NONE;
#annotationEditorUIManager = null;
#annotationMode = AnnotationMode.ENABLE_FORMS;
#containerTopLeft = null;
#editorUndoBar = null;
#enableHWA = false;
#enableHighlightFloatingButton = false;
#enablePermissions = false;
#enableUpdatedAddImage = false;
#enableNewAltTextWhenAddingImage = false;
#enableAutoLinking = true;
#eventAbortController = null;
#mlManager = null;
#scrollTimeoutId = null;
#switchAnnotationEditorModeAC = null;
#switchAnnotationEditorModeTimeoutId = null;
#getAllTextInProgress = false;
#hiddenCopyElement = null;
#interruptCopyCondition = false;
#previousContainerHeight = 0;
#resizeObserver = new ResizeObserver(this.#resizeObserverCallback.bind(this));
#scrollModePageState = null;
#scaleTimeoutId = null;
#signatureManager = null;
#supportsPinchToZoom = true;
#textLayerMode = TextLayerMode.ENABLE;
constructor(options) {
const viewerVersion = "5.1.91";
if (version !== viewerVersion) {
throw new Error(`The API version "${version}" does not match the Viewer version "${viewerVersion}".`);
}
this.container = options.container;
this.viewer = options.viewer || options.container.firstElementChild;
if (this.container?.tagName !== "DIV" || this.viewer?.tagName !== "DIV") {
throw new Error("Invalid `container` and/or `viewer` option.");
}
if (this.container.offsetParent && getComputedStyle(this.container).position !== "absolute") {
throw new Error("The `container` must be absolutely positioned.");
}
this.#resizeObserver.observe(this.container);
this.eventBus = options.eventBus;
this.linkService = options.linkService || new SimpleLinkService();
this.downloadManager = options.downloadManager || null;
this.findController = options.findController || null;
this.#altTextManager = options.altTextManager || null;
this.#signatureManager = options.signatureManager || null;
this.#editorUndoBar = options.editorUndoBar || null;
if (this.findController) {
this.findController.onIsPageVisible = pageNumber => this._getVisiblePages().ids.has(pageNumber);
}
this._scriptingManager = options.scriptingManager || null;
this.#textLayerMode = options.textLayerMode ?? TextLayerMode.ENABLE;
this.#annotationMode = options.annotationMode ?? AnnotationMode.ENABLE_FORMS;
this.#annotationEditorMode = options.annotationEditorMode ?? AnnotationEditorType.NONE;
this.#annotationEditorHighlightColors = options.annotationEditorHighlightColors || null;
this.#enableHighlightFloatingButton = options.enableHighlightFloatingButton === true;
this.#enableUpdatedAddImage = options.enableUpdatedAddImage === true;
this.#enableNewAltTextWhenAddingImage = options.enableNewAltTextWhenAddingImage === true;
this.imageResourcesPath = options.imageResourcesPath || "";
this.enablePrintAutoRotate = options.enablePrintAutoRotate || false;
this.removePageBorders = options.removePageBorders || false;
this.maxCanvasPixels = options.maxCanvasPixels;
this.maxCanvasDim = options.maxCanvasDim;
this.enableDetailCanvas = options.enableDetailCanvas ?? true;
this.l10n = options.l10n;
this.l10n ||= new genericl10n_GenericL10n();
this.#enablePermissions = options.enablePermissions || false;
this.pageColors = options.pageColors || null;
this.#mlManager = options.mlManager || null;
this.#enableHWA = options.enableHWA || false;
this.#supportsPinchToZoom = options.supportsPinchToZoom !== false;
this.#enableAutoLinking = options.enableAutoLinking !== false;
this.defaultRenderingQueue = !options.renderingQueue;
if (this.defaultRenderingQueue) {
this.renderingQueue = new PDFRenderingQueue();
this.renderingQueue.setViewer(this);
} else {
this.renderingQueue = options.renderingQueue;
}
const {
abortSignal
} = options;
abortSignal?.addEventListener("abort", () => {
this.#resizeObserver.disconnect();
this.#resizeObserver = null;
}, {
once: true
});
this.scroll = watchScroll(this.container, this._scrollUpdate.bind(this), abortSignal);
this.presentationModeState = PresentationModeState.UNKNOWN;
this._resetView();
if (this.removePageBorders) {
this.viewer.classList.add("removePageBorders");
}
this.#updateContainerHeightCss();
this.eventBus._on("thumbnailrendered", ({
pageNumber,
pdfPage
}) => {
const pageView = this._pages[pageNumber - 1];
if (!this.#buffer.has(pageView)) {
pdfPage?.cleanup();
}
});
if (!options.l10n) {
this.l10n.translate(this.container);
}
}
get pagesCount() {
return this._pages.length;
}
getPageView(index) {
return this._pages[index];
}
getCachedPageViews() {
return new Set(this.#buffer);
}
get pageViewsReady() {
return this._pages.every(pageView => pageView?.pdfPage);
}
get renderForms() {
return this.#annotationMode === AnnotationMode.ENABLE_FORMS;
}
get enableScripting() {
return !!this._scriptingManager;
}
get currentPageNumber() {
return this._currentPageNumber;
}
set currentPageNumber(val) {
if (!Number.isInteger(val)) {
throw new Error("Invalid page number.");
}
if (!this.pdfDocument) {
return;
}
if (!this._setCurrentPageNumber(val, true)) {
console.error(`currentPageNumber: "${val}" is not a valid page.`);
}
}
_setCurrentPageNumber(val, resetCurrentPageView = false) {
if (this._currentPageNumber === val) {
if (resetCurrentPageView) {
this.#resetCurrentPageView();
}
return true;
}
if (!(0 < val && val <= this.pagesCount)) {
return false;
}
const previous = this._currentPageNumber;
this._currentPageNumber = val;
this.eventBus.dispatch("pagechanging", {
source: this,
pageNumber: val,
pageLabel: this._pageLabels?.[val - 1] ?? null,
previous
});
if (resetCurrentPageView) {
this.#resetCurrentPageView();
}
return true;
}
get currentPageLabel() {
return this._pageLabels?.[this._currentPageNumber - 1] ?? null;
}
set currentPageLabel(val) {
if (!this.pdfDocument) {
return;
}
let page = val | 0;
if (this._pageLabels) {
const i = this._pageLabels.indexOf(val);
if (i >= 0) {
page = i + 1;
}
}
if (!this._setCurrentPageNumber(page, true)) {
console.error(`currentPageLabel: "${val}" is not a valid page.`);
}
}
get currentScale() {
return this._currentScale !== UNKNOWN_SCALE ? this._currentScale : DEFAULT_SCALE;
}
set currentScale(val) {
if (isNaN(val)) {
throw new Error("Invalid numeric scale.");
}
if (!this.pdfDocument) {
return;
}
this.#setScale(val, {
noScroll: false
});
}
get currentScaleValue() {
return this._currentScaleValue;
}
set currentScaleValue(val) {
if (!this.pdfDocument) {
return;
}
this.#setScale(val, {
noScroll: false
});
}
get pagesRotation() {
return this._pagesRotation;
}
set pagesRotation(rotation) {
if (!isValidRotation(rotation)) {
throw new Error("Invalid pages rotation angle.");
}
if (!this.pdfDocument) {
return;
}
rotation %= 360;
if (rotation < 0) {
rotation += 360;
}
if (this._pagesRotation === rotation) {
return;
}
this._pagesRotation = rotation;
const pageNumber = this._currentPageNumber;
this.refresh(true, {
rotation
});
if (this._currentScaleValue) {
this.#setScale(this._currentScaleValue, {
noScroll: true
});
}
this.eventBus.dispatch("rotationchanging", {
source: this,
pagesRotation: rotation,
pageNumber
});
if (this.defaultRenderingQueue) {
this.update();
}
}
get firstPagePromise() {
return this.pdfDocument ? this._firstPageCapability.promise : null;
}
get onePageRendered() {
return this.pdfDocument ? this._onePageRenderedCapability.promise : null;
}
get pagesPromise() {
return this.pdfDocument ? this._pagesCapability.promise : null;
}
get _layerProperties() {
const self = this;
return shadow(this, "_layerProperties", {
get annotationEditorUIManager() {
return self.#annotationEditorUIManager;
},
get annotationStorage() {
return self.pdfDocument?.annotationStorage;
},
get downloadManager() {
return self.downloadManager;
},
get enableScripting() {
return !!self._scriptingManager;
},
get fieldObjectsPromise() {
return self.pdfDocument?.getFieldObjects();
},
get findController() {
return self.findController;
},
get hasJSActionsPromise() {
return self.pdfDocument?.hasJSActions();
},
get linkService() {
return self.linkService;
}
});
}
#initializePermissions(permissions) {
const params = {
annotationEditorMode: this.#annotationEditorMode,
annotationMode: this.#annotationMode,
textLayerMode: this.#textLayerMode
};
if (!permissions) {
return params;
}
if (!permissions.includes(PermissionFlag.COPY) && this.#textLayerMode === TextLayerMode.ENABLE) {
params.textLayerMode = TextLayerMode.ENABLE_PERMISSIONS;
}
if (!permissions.includes(PermissionFlag.MODIFY_CONTENTS)) {
params.annotationEditorMode = AnnotationEditorType.DISABLE;
}
if (!permissions.includes(PermissionFlag.MODIFY_ANNOTATIONS) && !permissions.includes(PermissionFlag.FILL_INTERACTIVE_FORMS) && this.#annotationMode === AnnotationMode.ENABLE_FORMS) {
params.annotationMode = AnnotationMode.ENABLE;
}
return params;
}
async #onePageRenderedOrForceFetch(signal) {
if (document.visibilityState === "hidden" || !this.container.offsetParent || this._getVisiblePages().views.length === 0) {
return;
}
const hiddenCapability = Promise.withResolvers(),
ac = new AbortController();
document.addEventListener("visibilitychange", () => {
if (document.visibilityState === "hidden") {
hiddenCapability.resolve();
}
}, {
signal: AbortSignal.any([signal, ac.signal])
});
await Promise.race([this._onePageRenderedCapability.promise, hiddenCapability.promise]);
ac.abort();
}
async getAllText() {
const texts = [];
const buffer = [];
for (let pageNum = 1, pagesCount = this.pdfDocument.numPages; pageNum <= pagesCount; ++pageNum) {
if (this.#interruptCopyCondition) {
return null;
}
buffer.length = 0;
const page = await this.pdfDocument.getPage(pageNum);
const {
items
} = await page.getTextContent();
for (const item of items) {
if (item.str) {
buffer.push(item.str);
}
if (item.hasEOL) {
buffer.push("\n");
}
}
texts.push(removeNullCharacters(buffer.join("")));
}
return texts.join("\n");
}
#copyCallback(textLayerMode, event) {
const selection = document.getSelection();
const {
focusNode,
anchorNode
} = selection;
if (anchorNode && focusNode && selection.containsNode(this.#hiddenCopyElement)) {
if (this.#getAllTextInProgress || textLayerMode === TextLayerMode.ENABLE_PERMISSIONS) {
stopEvent(event);
return;
}
this.#getAllTextInProgress = true;
const {
classList
} = this.viewer;
classList.add("copyAll");
const ac = new AbortController();
window.addEventListener("keydown", ev => this.#interruptCopyCondition = ev.key === "Escape", {
signal: ac.signal
});
this.getAllText().then(async text => {
if (text !== null) {
await navigator.clipboard.writeText(text);
}
}).catch(reason => {
console.warn(`Something goes wrong when extracting the text: ${reason.message}`);
}).finally(() => {
this.#getAllTextInProgress = false;
this.#interruptCopyCondition = false;
ac.abort();
classList.remove("copyAll");
});
stopEvent(event);
}
}
setDocument(pdfDocument) {
if (this.pdfDocument) {
this.eventBus.dispatch("pagesdestroy", {
source: this
});
this._cancelRendering();
this._resetView();
this.findController?.setDocument(null);
this._scriptingManager?.setDocument(null);
this.#annotationEditorUIManager?.destroy();
this.#annotationEditorUIManager = null;
}
this.pdfDocument = pdfDocument;
if (!pdfDocument) {
return;
}
const pagesCount = pdfDocument.numPages;
const firstPagePromise = pdfDocument.getPage(1);
const optionalContentConfigPromise = pdfDocument.getOptionalContentConfig({
intent: "display"
});
const permissionsPromise = this.#enablePermissions ? pdfDocument.getPermissions() : Promise.resolve();
const {
eventBus,
pageColors,
viewer
} = this;
this.#eventAbortController = new AbortController();
const {
signal
} = this.#eventAbortController;
if (pagesCount > PagesCountLimit.FORCE_SCROLL_MODE_PAGE) {
console.warn("Forcing PAGE-scrolling for performance reasons, given the length of the document.");
const mode = this._scrollMode = ScrollMode.PAGE;
eventBus.dispatch("scrollmodechanged", {
source: this,
mode
});
}
this._pagesCapability.promise.then(() => {
eventBus.dispatch("pagesloaded", {
source: this,
pagesCount
});
}, () => {});
const onBeforeDraw = evt => {
const pageView = this._pages[evt.pageNumber - 1];
if (!pageView) {
return;
}
this.#buffer.push(pageView);
};
eventBus._on("pagerender", onBeforeDraw, {
signal
});
const onAfterDraw = evt => {
if (evt.cssTransform || evt.isDetailView) {
return;
}
this._onePageRenderedCapability.resolve({
timestamp: evt.timestamp
});
eventBus._off("pagerendered", onAfterDraw);
};
eventBus._on("pagerendered", onAfterDraw, {
signal
});
Promise.all([firstPagePromise, permissionsPromise]).then(([firstPdfPage, permissions]) => {
if (pdfDocument !== this.pdfDocument) {
return;
}
this._firstPageCapability.resolve(firstPdfPage);
this._optionalContentConfigPromise = optionalContentConfigPromise;
const {
annotationEditorMode,
annotationMode,
textLayerMode
} = this.#initializePermissions(permissions);
if (textLayerMode !== TextLayerMode.DISABLE) {
const element = this.#hiddenCopyElement = document.createElement("div");
element.id = "hiddenCopyElement";
viewer.before(element);
}
if (annotationEditorMode !== AnnotationEditorType.DISABLE) {
const mode = annotationEditorMode;
if (pdfDocument.isPureXfa) {
console.warn("Warning: XFA-editing is not implemented.");
} else if (isValidAnnotationEditorMode(mode)) {
this.#annotationEditorUIManager = new AnnotationEditorUIManager(this.container, viewer, this.#altTextManager, this.#signatureManager, eventBus, pdfDocument, pageColors, this.#annotationEditorHighlightColors, this.#enableHighlightFloatingButton, this.#enableUpdatedAddImage, this.#enableNewAltTextWhenAddingImage, this.#mlManager, this.#editorUndoBar, this.#supportsPinchToZoom);
eventBus.dispatch("annotationeditoruimanager", {
source: this,
uiManager: this.#annotationEditorUIManager
});
if (mode !== AnnotationEditorType.NONE) {
this.#preloadEditingData(mode);
this.#annotationEditorUIManager.updateMode(mode);
}
} else {
console.error(`Invalid AnnotationEditor mode: ${mode}`);
}
}
const viewerElement = this._scrollMode === ScrollMode.PAGE ? null : viewer;
const scale = this.currentScale;
const viewport = firstPdfPage.getViewport({
scale: scale * PixelsPerInch.PDF_TO_CSS_UNITS
});
viewer.style.setProperty("--scale-factor", viewport.scale);
if (pageColors?.background) {
viewer.style.setProperty("--page-bg-color", pageColors.background);
}
if (pageColors?.foreground === "CanvasText" || pageColors?.background === "Canvas") {
viewer.style.setProperty("--hcm-highlight-filter", pdfDocument.filterFactory.addHighlightHCMFilter("highlight", "CanvasText", "Canvas", "HighlightText", "Highlight"));
viewer.style.setProperty("--hcm-highlight-selected-filter", pdfDocument.filterFactory.addHighlightHCMFilter("highlight_selected", "CanvasText", "Canvas", "HighlightText", "ButtonText"));
}
for (let pageNum = 1; pageNum <= pagesCount; ++pageNum) {
const pageView = new PDFPageView({
container: viewerElement,
eventBus,
id: pageNum,
scale,
defaultViewport: viewport.clone(),
optionalContentConfigPromise,
renderingQueue: this.renderingQueue,
textLayerMode,
annotationMode,
imageResourcesPath: this.imageResourcesPath,
maxCanvasPixels: this.maxCanvasPixels,
maxCanvasDim: this.maxCanvasDim,
enableDetailCanvas: this.enableDetailCanvas,
pageColors,
l10n: this.l10n,
layerProperties: this._layerProperties,
enableHWA: this.#enableHWA,
enableAutoLinking: this.#enableAutoLinking
});
this._pages.push(pageView);
}
this._pages[0]?.setPdfPage(firstPdfPage);
if (this._scrollMode === ScrollMode.PAGE) {
this.#ensurePageViewVisible();
} else if (this._spreadMode !== SpreadMode.NONE) {
this._updateSpreadMode();
}
this.#onePageRenderedOrForceFetch(signal).then(async () => {
if (pdfDocument !== this.pdfDocument) {
return;
}
this.findController?.setDocument(pdfDocument);
this._scriptingManager?.setDocument(pdfDocument);
if (this.#hiddenCopyElement) {
document.addEventListener("copy", this.#copyCallback.bind(this, textLayerMode), {
signal
});
}
if (this.#annotationEditorUIManager) {
eventBus.dispatch("annotationeditormodechanged", {
source: this,
mode: this.#annotationEditorMode
});
}
if (pdfDocument.loadingParams.disableAutoFetch || pagesCount > PagesCountLimit.FORCE_LAZY_PAGE_INIT) {
this._pagesCapability.resolve();
return;
}
let getPagesLeft = pagesCount - 1;
if (getPagesLeft <= 0) {
this._pagesCapability.resolve();
return;
}
for (let pageNum = 2; pageNum <= pagesCount; ++pageNum) {
const promise = pdfDocument.getPage(pageNum).then(pdfPage => {
const pageView = this._pages[pageNum - 1];
if (!pageView.pdfPage) {
pageView.setPdfPage(pdfPage);
}
if (--getPagesLeft === 0) {
this._pagesCapability.resolve();
}
}, reason => {
console.error(`Unable to get page ${pageNum} to initialize viewer`, reason);
if (--getPagesLeft === 0) {
this._pagesCapability.resolve();
}
});
if (pageNum % PagesCountLimit.PAUSE_EAGER_PAGE_INIT === 0) {
await promise;
}
}
});
eventBus.dispatch("pagesinit", {
source: this
});
pdfDocument.getMetadata().then(({
info
}) => {
if (pdfDocument !== this.pdfDocument) {
return;
}
if (info.Language) {
viewer.lang = info.Language;
}
});
if (this.defaultRenderingQueue) {
this.update();
}
}).catch(reason => {
console.error("Unable to initialize viewer", reason);
this._pagesCapability.reject(reason);
});
}
setPageLabels(labels) {
if (!this.pdfDocument) {
return;
}
if (!labels) {
this._pageLabels = null;
} else if (!(Array.isArray(labels) && this.pdfDocument.numPages === labels.length)) {
this._pageLabels = null;
console.error(`setPageLabels: Invalid page labels.`);
} else {
this._pageLabels = labels;
}
for (let i = 0, ii = this._pages.length; i < ii; i++) {
this._pages[i].setPageLabel(this._pageLabels?.[i] ?? null);
}
}
_resetView() {
this._pages = [];
this._currentPageNumber = 1;
this._currentScale = UNKNOWN_SCALE;
this._currentScaleValue = null;
this._pageLabels = null;
this.#buffer = new PDFPageViewBuffer(DEFAULT_CACHE_SIZE);
this._location = null;
this._pagesRotation = 0;
this._optionalContentConfigPromise = null;
this._firstPageCapability = Promise.withResolvers();
this._onePageRenderedCapability = Promise.withResolvers();
this._pagesCapability = Promise.withResolvers();
this._scrollMode = ScrollMode.VERTICAL;
this._previousScrollMode = ScrollMode.UNKNOWN;
this._spreadMode = SpreadMode.NONE;
this.#scrollModePageState = {
previousPageNumber: 1,
scrollDown: true,
pages: []
};
this.#eventAbortController?.abort();
this.#eventAbortController = null;
this.viewer.textContent = "";
this._updateScrollMode();
this.viewer.removeAttribute("lang");
this.#hiddenCopyElement?.remove();
this.#hiddenCopyElement = null;
this.#cleanupTimeouts();
this.#cleanupSwitchAnnotationEditorMode();
}
#ensurePageViewVisible() {
if (this._scrollMode !== ScrollMode.PAGE) {
throw new Error("#ensurePageViewVisible: Invalid scrollMode value.");
}
const pageNumber = this._currentPageNumber,
state = this.#scrollModePageState,
viewer = this.viewer;
viewer.textContent = "";
state.pages.length = 0;
if (this._spreadMode === SpreadMode.NONE && !this.isInPresentationMode) {
const pageView = this._pages[pageNumber - 1];
viewer.append(pageView.div);
state.pages.push(pageView);
} else {
const pageIndexSet = new Set(),
parity = this._spreadMode - 1;
if (parity === -1) {
pageIndexSet.add(pageNumber - 1);
} else if (pageNumber % 2 !== parity) {
pageIndexSet.add(pageNumber - 1);
pageIndexSet.add(pageNumber);
} else {
pageIndexSet.add(pageNumber - 2);
pageIndexSet.add(pageNumber - 1);
}
const spread = document.createElement("div");
spread.className = "spread";
if (this.isInPresentationMode) {
const dummyPage = document.createElement("div");
dummyPage.className = "dummyPage";
spread.append(dummyPage);
}
for (const i of pageIndexSet) {
const pageView = this._pages[i];
if (!pageView) {
continue;
}
spread.append(pageView.div);
state.pages.push(pageView);
}
viewer.append(spread);
}
state.scrollDown = pageNumber >= state.previousPageNumber;
state.previousPageNumber = pageNumber;
}
_scrollUpdate() {
if (this.pagesCount === 0) {
return;
}
if (this.#scrollTimeoutId) {
clearTimeout(this.#scrollTimeoutId);
}
this.#scrollTimeoutId = setTimeout(() => {
this.#scrollTimeoutId = null;
this.update();
}, 100);
this.update();
}
#scrollIntoView(pageView, pageSpot = null) {
const {
div,
id
} = pageView;
if (this._currentPageNumber !== id) {
this._setCurrentPageNumber(id);
}
if (this._scrollMode === ScrollMode.PAGE) {
this.#ensurePageViewVisible();
this.update();
}
if (!pageSpot && !this.isInPresentationMode) {
const left = div.offsetLeft + div.clientLeft,
right = left + div.clientWidth;
const {
scrollLeft,
clientWidth
} = this.container;
if (this._scrollMode === ScrollMode.HORIZONTAL || left < scrollLeft || right > scrollLeft + clientWidth) {
pageSpot = {
left: 0,
top: 0
};
}
}
scrollIntoView(div, pageSpot);
if (!this._currentScaleValue && this._location) {
this._location = null;
}
}
#isSameScale(newScale) {
return newScale === this._currentScale || Math.abs(newScale - this._currentScale) < 1e-15;
}
#setScaleUpdatePages(newScale, newValue, {
noScroll = false,
preset = false,
drawingDelay = -1,
origin = null
}) {
this._currentScaleValue = newValue.toString();
if (this.#isSameScale(newScale)) {
if (preset) {
this.eventBus.dispatch("scalechanging", {
source: this,
scale: newScale,
presetValue: newValue
});
}
return;
}
this.viewer.style.setProperty("--scale-factor", newScale * PixelsPerInch.PDF_TO_CSS_UNITS);
const postponeDrawing = drawingDelay >= 0 && drawingDelay < 1000;
this.refresh(true, {
scale: newScale,
drawingDelay: postponeDrawing ? drawingDelay : -1
});
if (postponeDrawing) {
this.#scaleTimeoutId = setTimeout(() => {
this.#scaleTimeoutId = null;
this.refresh();
}, drawingDelay);
}
const previousScale = this._currentScale;
this._currentScale = newScale;
if (!noScroll) {
let page = this._currentPageNumber,
dest;
if (this._location && !(this.isInPresentationMode || this.isChangingPresentationMode)) {
page = this._location.pageNumber;
dest = [null, {
name: "XYZ"
}, this._location.left, this._location.top, null];
}
this.scrollPageIntoView({
pageNumber: page,
destArray: dest,
allowNegativeOffset: true
});
if (Array.isArray(origin)) {
const scaleDiff = newScale / previousScale - 1;
const [top, left] = this.containerTopLeft;
this.container.scrollLeft += (origin[0] - left) * scaleDiff;
this.container.scrollTop += (origin[1] - top) * scaleDiff;
}
}
this.eventBus.dispatch("scalechanging", {
source: this,
scale: newScale,
presetValue: preset ? newValue : undefined
});
if (this.defaultRenderingQueue) {
this.update();
}
}
get #pageWidthScaleFactor() {
if (this._spreadMode !== SpreadMode.NONE && this._scrollMode !== ScrollMode.HORIZONTAL) {
return 2;
}
return 1;
}
#setScale(value, options) {
let scale = parseFloat(value);
if (scale > 0) {
options.preset = false;
this.#setScaleUpdatePages(scale, value, options);
} else {
const currentPage = this._pages[this._currentPageNumber - 1];
if (!currentPage) {
return;
}
let hPadding = SCROLLBAR_PADDING,
vPadding = VERTICAL_PADDING;
if (this.isInPresentationMode) {
hPadding = vPadding = 4;
if (this._spreadMode !== SpreadMode.NONE) {
hPadding *= 2;
}
} else if (this.removePageBorders) {
hPadding = vPadding = 0;
} else if (this._scrollMode === ScrollMode.HORIZONTAL) {
[hPadding, vPadding] = [vPadding, hPadding];
}
const pageWidthScale = (this.container.clientWidth - hPadding) / currentPage.width * currentPage.scale / this.#pageWidthScaleFactor;
const pageHeightScale = (this.container.clientHeight - vPadding) / currentPage.height * currentPage.scale;
switch (value) {
case "page-actual":
scale = 1;
break;
case "page-width":
scale = pageWidthScale;
break;
case "page-height":
scale = pageHeightScale;
break;
case "page-fit":
scale = Math.min(pageWidthScale, pageHeightScale);
break;
case "auto":
const horizontalScale = isPortraitOrientation(currentPage) ? pageWidthScale : Math.min(pageHeightScale, pageWidthScale);
scale = Math.min(MAX_AUTO_SCALE, horizontalScale);
break;
default:
console.error(`#setScale: "${value}" is an unknown zoom value.`);
return;
}
options.preset = true;
this.#setScaleUpdatePages(scale, value, options);
}
}
#resetCurrentPageView() {
const pageView = this._pages[this._currentPageNumber - 1];
if (this.isInPresentationMode) {
this.#setScale(this._currentScaleValue, {
noScroll: true
});
}
this.#scrollIntoView(pageView);
}
pageLabelToPageNumber(label) {
if (!this._pageLabels) {
return null;
}
const i = this._pageLabels.indexOf(label);
if (i < 0) {
return null;
}
return i + 1;
}
scrollPageIntoView({
pageNumber,
destArray = null,
allowNegativeOffset = false,
ignoreDestinationZoom = false
}) {
if (!this.pdfDocument) {
return;
}
const pageView = Number.isInteger(pageNumber) && this._pages[pageNumber - 1];
if (!pageView) {
console.error(`scrollPageIntoView: "${pageNumber}" is not a valid pageNumber parameter.`);
return;
}
if (this.isInPresentationMode || !destArray) {
this._setCurrentPageNumber(pageNumber, true);
return;
}
let x = 0,
y = 0;
let width = 0,
height = 0,
widthScale,
heightScale;
const changeOrientation = pageView.rotation % 180 !== 0;
const pageWidth = (changeOrientation ? pageView.height : pageView.width) / pageView.scale / PixelsPerInch.PDF_TO_CSS_UNITS;
const pageHeight = (changeOrientation ? pageView.width : pageView.height) / pageView.scale / PixelsPerInch.PDF_TO_CSS_UNITS;
let scale = 0;
switch (destArray[1].name) {
case "XYZ":
x = destArray[2];
y = destArray[3];
scale = destArray[4];
x = x !== null ? x : 0;
y = y !== null ? y : pageHeight;
break;
case "Fit":
case "FitB":
scale = "page-fit";
break;
case "FitH":
case "FitBH":
y = destArray[2];
scale = "page-width";
if (y === null && this._location) {
x = this._location.left;
y = this._location.top;
} else if (typeof y !== "number" || y < 0) {
y = pageHeight;
}
break;
case "FitV":
case "FitBV":
x = destArray[2];
width = pageWidth;
height = pageHeight;
scale = "page-height";
break;
case "FitR":
x = destArray[2];
y = destArray[3];
width = destArray[4] - x;
height = destArray[5] - y;
let hPadding = SCROLLBAR_PADDING,
vPadding = VERTICAL_PADDING;
if (this.removePageBorders) {
hPadding = vPadding = 0;
}
widthScale = (this.container.clientWidth - hPadding) / width / PixelsPerInch.PDF_TO_CSS_UNITS;
heightScale = (this.container.clientHeight - vPadding) / height / PixelsPerInch.PDF_TO_CSS_UNITS;
scale = Math.min(Math.abs(widthScale), Math.abs(heightScale));
break;
default:
console.error(`scrollPageIntoView: "${destArray[1].name}" is not a valid destination type.`);
return;
}
if (!ignoreDestinationZoom) {
if (scale && scale !== this._currentScale) {
this.currentScaleValue = scale;
} else if (this._currentScale === UNKNOWN_SCALE) {
this.currentScaleValue = DEFAULT_SCALE_VALUE;
}
}
if (scale === "page-fit" && !destArray[4]) {
this.#scrollIntoView(pageView);
return;
}
const boundingRect = [pageView.viewport.convertToViewportPoint(x, y), pageView.viewport.convertToViewportPoint(x + width, y + height)];
let left = Math.min(boundingRect[0][0], boundingRect[1][0]);
let top = Math.min(boundingRect[0][1], boundingRect[1][1]);
if (!allowNegativeOffset) {
left = Math.max(left, 0);
top = Math.max(top, 0);
}
this.#scrollIntoView(pageView, {
left,
top
});
}
_updateLocation(firstPage) {
const currentScale = this._currentScale;
const currentScaleValue = this._currentScaleValue;
const normalizedScaleValue = parseFloat(currentScaleValue) === currentScale ? Math.round(currentScale * 10000) / 100 : currentScaleValue;
const pageNumber = firstPage.id;
const currentPageView = this._pages[pageNumber - 1];
const container = this.container;
const topLeft = currentPageView.getPagePoint(container.scrollLeft - firstPage.x, container.scrollTop - firstPage.y);
const intLeft = Math.round(topLeft[0]);
const intTop = Math.round(topLeft[1]);
let pdfOpenParams = `#page=${pageNumber}`;
if (!this.isInPresentationMode) {
pdfOpenParams += `&zoom=${normalizedScaleValue},${intLeft},${intTop}`;
}
this._location = {
pageNumber,
scale: normalizedScaleValue,
top: intTop,
left: intLeft,
rotation: this._pagesRotation,
pdfOpenParams
};
}
update() {
const visible = this._getVisiblePages();
const visiblePages = visible.views,
numVisiblePages = visiblePages.length;
if (numVisiblePages === 0) {
return;
}
const newCacheSize = Math.max(DEFAULT_CACHE_SIZE, 2 * numVisiblePages + 1);
this.#buffer.resize(newCacheSize, visible.ids);
for (const {
view,
visibleArea
} of visiblePages) {
view.updateVisibleArea(visibleArea);
}
for (const view of this.#buffer) {
if (!visible.ids.has(view.id)) {
view.updateVisibleArea(null);
}
}
this.renderingQueue.renderHighestPriority(visible);
const isSimpleLayout = this._spreadMode === SpreadMode.NONE && (this._scrollMode === ScrollMode.PAGE || this._scrollMode === ScrollMode.VERTICAL);
const currentId = this._currentPageNumber;
let stillFullyVisible = false;
for (const page of visiblePages) {
if (page.percent < 100) {
break;
}
if (page.id === currentId && isSimpleLayout) {
stillFullyVisible = true;
break;
}
}
this._setCurrentPageNumber(stillFullyVisible ? currentId : visiblePages[0].id);
this._updateLocation(visible.first);
this.eventBus.dispatch("updateviewarea", {
source: this,
location: this._location
});
}
#switchToEditAnnotationMode() {
const visible = this._getVisiblePages();
const pagesToRefresh = [];
const {
ids,
views
} = visible;
for (const page of views) {
const {
view
} = page;
if (!view.hasEditableAnnotations()) {
ids.delete(view.id);
continue;
}
pagesToRefresh.push(page);
}
if (pagesToRefresh.length === 0) {
return null;
}
this.renderingQueue.renderHighestPriority({
first: pagesToRefresh[0],
last: pagesToRefresh.at(-1),
views: pagesToRefresh,
ids
});
return ids;
}
containsElement(element) {
return this.container.contains(element);
}
focus() {
this.container.focus();
}
get _isContainerRtl() {
return getComputedStyle(this.container).direction === "rtl";
}
get isInPresentationMode() {
return this.presentationModeState === PresentationModeState.FULLSCREEN;
}
get isChangingPresentationMode() {
return this.presentationModeState === PresentationModeState.CHANGING;
}
get isHorizontalScrollbarEnabled() {
return this.isInPresentationMode ? false : this.container.scrollWidth > this.container.clientWidth;
}
get isVerticalScrollbarEnabled() {
return this.isInPresentationMode ? false : this.container.scrollHeight > this.container.clientHeight;
}
_getVisiblePages() {
const views = this._scrollMode === ScrollMode.PAGE ? this.#scrollModePageState.pages : this._pages,
horizontal = this._scrollMode === ScrollMode.HORIZONTAL,
rtl = horizontal && this._isContainerRtl;
return getVisibleElements({
scrollEl: this.container,
views,
sortByVisibility: true,
horizontal,
rtl
});
}
cleanup() {
for (const pageView of this._pages) {
if (pageView.renderingState !== RenderingStates.FINISHED) {
pageView.reset();
}
}
}
_cancelRendering() {
for (const pageView of this._pages) {
pageView.cancelRendering();
}
}
async #ensurePdfPageLoaded(pageView) {
if (pageView.pdfPage) {
return pageView.pdfPage;
}
try {
const pdfPage = await this.pdfDocument.getPage(pageView.id);
if (!pageView.pdfPage) {
pageView.setPdfPage(pdfPage);
}
return pdfPage;
} catch (reason) {
console.error("Unable to get page for page view", reason);
return null;
}
}
#getScrollAhead(visible) {
if (visible.first?.id === 1) {
return true;
} else if (visible.last?.id === this.pagesCount) {
return false;
}
switch (this._scrollMode) {
case ScrollMode.PAGE:
return this.#scrollModePageState.scrollDown;
case ScrollMode.HORIZONTAL:
return this.scroll.right;
}
return this.scroll.down;
}
forceRendering(currentlyVisiblePages) {
const visiblePages = currentlyVisiblePages || this._getVisiblePages();
const scrollAhead = this.#getScrollAhead(visiblePages);
const preRenderExtra = this._spreadMode !== SpreadMode.NONE && this._scrollMode !== ScrollMode.HORIZONTAL;
const ignoreDetailViews = this.#scaleTimeoutId !== null || this.#scrollTimeoutId !== null && visiblePages.views.some(page => page.detailView?.renderingCancelled);
const pageView = this.renderingQueue.getHighestPriority(visiblePages, this._pages, scrollAhead, preRenderExtra, ignoreDetailViews);
if (pageView) {
this.#ensurePdfPageLoaded(pageView).then(() => {
this.renderingQueue.renderView(pageView);
});
return true;
}
return false;
}
get hasEqualPageSizes() {
const firstPageView = this._pages[0];
for (let i = 1, ii = this._pages.length; i < ii; ++i) {
const pageView = this._pages[i];
if (pageView.width !== firstPageView.width || pageView.height !== firstPageView.height) {
return false;
}
}
return true;
}
getPagesOverview() {
let initialOrientation;
return this._pages.map(pageView => {
const viewport = pageView.pdfPage.getViewport({
scale: 1
});
const orientation = isPortraitOrientation(viewport);
if (initialOrientation === undefined) {
initialOrientation = orientation;
} else if (this.enablePrintAutoRotate && orientation !== initialOrientation) {
return {
width: viewport.height,
height: viewport.width,
rotation: (viewport.rotation - 90) % 360
};
}
return {
width: viewport.width,
height: viewport.height,
rotation: viewport.rotation
};
});
}
get optionalContentConfigPromise() {
if (!this.pdfDocument) {
return Promise.resolve(null);
}
if (!this._optionalContentConfigPromise) {
console.error("optionalContentConfigPromise: Not initialized yet.");
return this.pdfDocument.getOptionalContentConfig({
intent: "display"
});
}
return this._optionalContentConfigPromise;
}
set optionalContentConfigPromise(promise) {
if (!(promise instanceof Promise)) {
throw new Error(`Invalid optionalContentConfigPromise: ${promise}`);
}
if (!this.pdfDocument) {
return;
}
if (!this._optionalContentConfigPromise) {
return;
}
this._optionalContentConfigPromise = promise;
this.refresh(false, {
optionalContentConfigPromise: promise
});
this.eventBus.dispatch("optionalcontentconfigchanged", {
source: this,
promise
});
}
get scrollMode() {
return this._scrollMode;
}
set scrollMode(mode) {
if (this._scrollMode === mode) {
return;
}
if (!isValidScrollMode(mode)) {
throw new Error(`Invalid scroll mode: ${mode}`);
}
if (this.pagesCount > PagesCountLimit.FORCE_SCROLL_MODE_PAGE) {
return;
}
this._previousScrollMode = this._scrollMode;
this._scrollMode = mode;
this.eventBus.dispatch("scrollmodechanged", {
source: this,
mode
});
this._updateScrollMode(this._currentPageNumber);
}
_updateScrollMode(pageNumber = null) {
const scrollMode = this._scrollMode,
viewer = this.viewer;
viewer.classList.toggle("scrollHorizontal", scrollMode === ScrollMode.HORIZONTAL);
viewer.classList.toggle("scrollWrapped", scrollMode === ScrollMode.WRAPPED);
if (!this.pdfDocument || !pageNumber) {
return;
}
if (scrollMode === ScrollMode.PAGE) {
this.#ensurePageViewVisible();
} else if (this._previousScrollMode === ScrollMode.PAGE) {
this._updateSpreadMode();
}
if (this._currentScaleValue && isNaN(this._currentScaleValue)) {
this.#setScale(this._currentScaleValue, {
noScroll: true
});
}
this._setCurrentPageNumber(pageNumber, true);
this.update();
}
get spreadMode() {
return this._spreadMode;
}
set spreadMode(mode) {
if (this._spreadMode === mode) {
return;
}
if (!isValidSpreadMode(mode)) {
throw new Error(`Invalid spread mode: ${mode}`);
}
this._spreadMode = mode;
this.eventBus.dispatch("spreadmodechanged", {
source: this,
mode
});
this._updateSpreadMode(this._currentPageNumber);
}
_updateSpreadMode(pageNumber = null) {
if (!this.pdfDocument) {
return;
}
const viewer = this.viewer,
pages = this._pages;
if (this._scrollMode === ScrollMode.PAGE) {
this.#ensurePageViewVisible();
} else {
viewer.textContent = "";
if (this._spreadMode === SpreadMode.NONE) {
for (const pageView of this._pages) {
viewer.append(pageView.div);
}
} else {
const parity = this._spreadMode - 1;
let spread = null;
for (let i = 0, ii = pages.length; i < ii; ++i) {
if (spread === null) {
spread = document.createElement("div");
spread.className = "spread";
viewer.append(spread);
} else if (i % 2 === parity) {
spread = spread.cloneNode(false);
viewer.append(spread);
}
spread.append(pages[i].div);
}
}
}
if (!pageNumber) {
return;
}
if (this._currentScaleValue && isNaN(this._currentScaleValue)) {
this.#setScale(this._currentScaleValue, {
noScroll: true
});
}
this._setCurrentPageNumber(pageNumber, true);
this.update();
}
_getPageAdvance(currentPageNumber, previous = false) {
switch (this._scrollMode) {
case ScrollMode.WRAPPED:
{
const {
views
} = this._getVisiblePages(),
pageLayout = new Map();
for (const {
id,
y,
percent,
widthPercent
} of views) {
if (percent === 0 || widthPercent < 100) {
continue;
}
let yArray = pageLayout.get(y);
if (!yArray) {
pageLayout.set(y, yArray ||= []);
}
yArray.push(id);
}
for (const yArray of pageLayout.values()) {
const currentIndex = yArray.indexOf(currentPageNumber);
if (currentIndex === -1) {
continue;
}
const numPages = yArray.length;
if (numPages === 1) {
break;
}
if (previous) {
for (let i = currentIndex - 1, ii = 0; i >= ii; i--) {
const currentId = yArray[i],
expectedId = yArray[i + 1] - 1;
if (currentId < expectedId) {
return currentPageNumber - expectedId;
}
}
} else {
for (let i = currentIndex + 1, ii = numPages; i < ii; i++) {
const currentId = yArray[i],
expectedId = yArray[i - 1] + 1;
if (currentId > expectedId) {
return expectedId - currentPageNumber;
}
}
}
if (previous) {
const firstId = yArray[0];
if (firstId < currentPageNumber) {
return currentPageNumber - firstId + 1;
}
} else {
const lastId = yArray[numPages - 1];
if (lastId > currentPageNumber) {
return lastId - currentPageNumber + 1;
}
}
break;
}
break;
}
case ScrollMode.HORIZONTAL:
{
break;
}
case ScrollMode.PAGE:
case ScrollMode.VERTICAL:
{
if (this._spreadMode === SpreadMode.NONE) {
break;
}
const parity = this._spreadMode - 1;
if (previous && currentPageNumber % 2 !== parity) {
break;
} else if (!previous && currentPageNumber % 2 === parity) {
break;
}
const {
views
} = this._getVisiblePages(),
expectedId = previous ? currentPageNumber - 1 : currentPageNumber + 1;
for (const {
id,
percent,
widthPercent
} of views) {
if (id !== expectedId) {
continue;
}
if (percent > 0 && widthPercent === 100) {
return 2;
}
break;
}
break;
}
}
return 1;
}
nextPage() {
const currentPageNumber = this._currentPageNumber,
pagesCount = this.pagesCount;
if (currentPageNumber >= pagesCount) {
return false;
}
const advance = this._getPageAdvance(currentPageNumber, false) || 1;
this.currentPageNumber = Math.min(currentPageNumber + advance, pagesCount);
return true;
}
previousPage() {
const currentPageNumber = this._currentPageNumber;
if (currentPageNumber <= 1) {
return false;
}
const advance = this._getPageAdvance(currentPageNumber, true) || 1;
this.currentPageNumber = Math.max(currentPageNumber - advance, 1);
return true;
}
updateScale({
drawingDelay,
scaleFactor = null,
steps = null,
origin
}) {
if (steps === null && scaleFactor === null) {
throw new Error("Invalid updateScale options: either `steps` or `scaleFactor` must be provided.");
}
if (!this.pdfDocument) {
return;
}
let newScale = this._currentScale;
if (scaleFactor > 0 && scaleFactor !== 1) {
newScale = Math.round(newScale * scaleFactor * 100) / 100;
} else if (steps) {
const delta = steps > 0 ? DEFAULT_SCALE_DELTA : 1 / DEFAULT_SCALE_DELTA;
const round = steps > 0 ? Math.ceil : Math.floor;
steps = Math.abs(steps);
do {
newScale = round((newScale * delta).toFixed(2) * 10) / 10;
} while (--steps > 0);
}
newScale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, newScale));
this.#setScale(newScale, {
noScroll: false,
drawingDelay,
origin
});
}
increaseScale(options = {}) {
this.updateScale({
...options,
steps: options.steps ?? 1
});
}
decreaseScale(options = {}) {
this.updateScale({
...options,
steps: -(options.steps ?? 1)
});
}
#updateContainerHeightCss(height = this.container.clientHeight) {
if (height !== this.#previousContainerHeight) {
this.#previousContainerHeight = height;
docStyle.setProperty("--viewer-container-height", `${height}px`);
}
}
#resizeObserverCallback(entries) {
for (const entry of entries) {
if (entry.target === this.container) {
this.#updateContainerHeightCss(Math.floor(entry.borderBoxSize[0].blockSize));
this.#containerTopLeft = null;
break;
}
}
}
get containerTopLeft() {
return this.#containerTopLeft ||= [this.container.offsetTop, this.container.offsetLeft];
}
#cleanupTimeouts() {
if (this.#scaleTimeoutId !== null) {
clearTimeout(this.#scaleTimeoutId);
this.#scaleTimeoutId = null;
}
if (this.#scrollTimeoutId !== null) {
clearTimeout(this.#scrollTimeoutId);
this.#scrollTimeoutId = null;
}
}
#cleanupSwitchAnnotationEditorMode() {
this.#switchAnnotationEditorModeAC?.abort();
this.#switchAnnotationEditorModeAC = null;
if (this.#switchAnnotationEditorModeTimeoutId !== null) {
clearTimeout(this.#switchAnnotationEditorModeTimeoutId);
this.#switchAnnotationEditorModeTimeoutId = null;
}
}
#preloadEditingData(mode) {
switch (mode) {
case AnnotationEditorType.STAMP:
this.#mlManager?.loadModel("altText");
break;
case AnnotationEditorType.SIGNATURE:
this.#signatureManager?.loadSignatures();
break;
}
}
get annotationEditorMode() {
return this.#annotationEditorUIManager ? this.#annotationEditorMode : AnnotationEditorType.DISABLE;
}
set annotationEditorMode({
mode,
editId = null,
isFromKeyboard = false
}) {
if (!this.#annotationEditorUIManager) {
throw new Error(`The AnnotationEditor is not enabled.`);
}
if (this.#annotationEditorMode === mode) {
return;
}
if (!isValidAnnotationEditorMode(mode)) {
throw new Error(`Invalid AnnotationEditor mode: ${mode}`);
}
if (!this.pdfDocument) {
return;
}
this.#preloadEditingData(mode);
const {
eventBus,
pdfDocument
} = this;
const updater = async () => {
this.#cleanupSwitchAnnotationEditorMode();
this.#annotationEditorMode = mode;
await this.#annotationEditorUIManager.updateMode(mode, editId, isFromKeyboard);
if (mode !== this.#annotationEditorMode || pdfDocument !== this.pdfDocument) {
return;
}
eventBus.dispatch("annotationeditormodechanged", {
source: this,
mode
});
};
if (mode === AnnotationEditorType.NONE || this.#annotationEditorMode === AnnotationEditorType.NONE) {
const isEditing = mode !== AnnotationEditorType.NONE;
if (!isEditing) {
this.pdfDocument.annotationStorage.resetModifiedIds();
}
for (const pageView of this._pages) {
pageView.toggleEditingMode(isEditing);
}
const idsToRefresh = this.#switchToEditAnnotationMode();
if (isEditing && idsToRefresh) {
this.#cleanupSwitchAnnotationEditorMode();
this.#switchAnnotationEditorModeAC = new AbortController();
const signal = AbortSignal.any([this.#eventAbortController.signal, this.#switchAnnotationEditorModeAC.signal]);
eventBus._on("pagerendered", ({
pageNumber
}) => {
idsToRefresh.delete(pageNumber);
if (idsToRefresh.size === 0) {
this.#switchAnnotationEditorModeTimeoutId = setTimeout(updater, 0);
}
}, {
signal
});
return;
}
}
updater();
}
refresh(noUpdate = false, updateArgs = Object.create(null)) {
if (!this.pdfDocument) {
return;
}
for (const pageView of this._pages) {
pageView.update(updateArgs);
}
this.#cleanupTimeouts();
if (!noUpdate) {
this.update();
}
}
}
;// ./web/secondary_toolbar.js
class SecondaryToolbar {
#opts;
constructor(options, eventBus) {
this.#opts = options;
const buttons = [{
element: options.presentationModeButton,
eventName: "presentationmode",
close: true
}, {
element: options.printButton,
eventName: "print",
close: true
}, {
element: options.downloadButton,
eventName: "download",
close: true
}, {
element: options.viewBookmarkButton,
eventName: null,
close: true
}, {
element: options.firstPageButton,
eventName: "firstpage",
close: true
}, {
element: options.lastPageButton,
eventName: "lastpage",
close: true
}, {
element: options.pageRotateCwButton,
eventName: "rotatecw",
close: false
}, {
element: options.pageRotateCcwButton,
eventName: "rotateccw",
close: false
}, {
element: options.cursorSelectToolButton,
eventName: "switchcursortool",
eventDetails: {
tool: CursorTool.SELECT
},
close: true
}, {
element: options.cursorHandToolButton,
eventName: "switchcursortool",
eventDetails: {
tool: CursorTool.HAND
},
close: true
}, {
element: options.scrollPageButton,
eventName: "switchscrollmode",
eventDetails: {
mode: ScrollMode.PAGE
},
close: true
}, {
element: options.scrollVerticalButton,
eventName: "switchscrollmode",
eventDetails: {
mode: ScrollMode.VERTICAL
},
close: true
}, {
element: options.scrollHorizontalButton,
eventName: "switchscrollmode",
eventDetails: {
mode: ScrollMode.HORIZONTAL
},
close: true
}, {
element: options.scrollWrappedButton,
eventName: "switchscrollmode",
eventDetails: {
mode: ScrollMode.WRAPPED
},
close: true
}, {
element: options.spreadNoneButton,
eventName: "switchspreadmode",
eventDetails: {
mode: SpreadMode.NONE
},
close: true
}, {
element: options.spreadOddButton,
eventName: "switchspreadmode",
eventDetails: {
mode: SpreadMode.ODD
},
close: true
}, {
element: options.spreadEvenButton,
eventName: "switchspreadmode",
eventDetails: {
mode: SpreadMode.EVEN
},
close: true
}, {
element: options.imageAltTextSettingsButton,
eventName: "imagealttextsettings",
close: true
}, {
element: options.documentPropertiesButton,
eventName: "documentproperties",
close: true
}];
buttons.push({
element: options.openFileButton,
eventName: "openfile",
close: true
});
this.eventBus = eventBus;
this.opened = false;
this.#bindListeners(buttons);
this.reset();
}
get isOpen() {
return this.opened;
}
setPageNumber(pageNumber) {
this.pageNumber = pageNumber;
this.#updateUIState();
}
setPagesCount(pagesCount) {
this.pagesCount = pagesCount;
this.#updateUIState();
}
reset() {
this.pageNumber = 0;
this.pagesCount = 0;
this.#updateUIState();
this.eventBus.dispatch("switchcursortool", {
source: this,
reset: true
});
this.#scrollModeChanged({
mode: ScrollMode.VERTICAL
});
this.#spreadModeChanged({
mode: SpreadMode.NONE
});
}
#updateUIState() {
const {
firstPageButton,
lastPageButton,
pageRotateCwButton,
pageRotateCcwButton
} = this.#opts;
firstPageButton.disabled = this.pageNumber <= 1;
lastPageButton.disabled = this.pageNumber >= this.pagesCount;
pageRotateCwButton.disabled = this.pagesCount === 0;
pageRotateCcwButton.disabled = this.pagesCount === 0;
}
#bindListeners(buttons) {
const {
eventBus
} = this;
const {
toggleButton
} = this.#opts;
toggleButton.addEventListener("click", this.toggle.bind(this));
for (const {
element,
eventName,
close,
eventDetails
} of buttons) {
element.addEventListener("click", evt => {
if (eventName !== null) {
eventBus.dispatch(eventName, {
source: this,
...eventDetails
});
}
if (close) {
this.close();
}
eventBus.dispatch("reporttelemetry", {
source: this,
details: {
type: "buttons",
data: {
id: element.id
}
}
});
});
}
eventBus._on("cursortoolchanged", this.#cursorToolChanged.bind(this));
eventBus._on("scrollmodechanged", this.#scrollModeChanged.bind(this));
eventBus._on("spreadmodechanged", this.#spreadModeChanged.bind(this));
}
#cursorToolChanged({
tool,
disabled
}) {
const {
cursorSelectToolButton,
cursorHandToolButton
} = this.#opts;
toggleCheckedBtn(cursorSelectToolButton, tool === CursorTool.SELECT);
toggleCheckedBtn(cursorHandToolButton, tool === CursorTool.HAND);
cursorSelectToolButton.disabled = disabled;
cursorHandToolButton.disabled = disabled;
}
#scrollModeChanged({
mode
}) {
const {
scrollPageButton,
scrollVerticalButton,
scrollHorizontalButton,
scrollWrappedButton,
spreadNoneButton,
spreadOddButton,
spreadEvenButton
} = this.#opts;
toggleCheckedBtn(scrollPageButton, mode === ScrollMode.PAGE);
toggleCheckedBtn(scrollVerticalButton, mode === ScrollMode.VERTICAL);
toggleCheckedBtn(scrollHorizontalButton, mode === ScrollMode.HORIZONTAL);
toggleCheckedBtn(scrollWrappedButton, mode === ScrollMode.WRAPPED);
const forceScrollModePage = this.pagesCount > PagesCountLimit.FORCE_SCROLL_MODE_PAGE;
scrollPageButton.disabled = forceScrollModePage;
scrollVerticalButton.disabled = forceScrollModePage;
scrollHorizontalButton.disabled = forceScrollModePage;
scrollWrappedButton.disabled = forceScrollModePage;
const isHorizontal = mode === ScrollMode.HORIZONTAL;
spreadNoneButton.disabled = isHorizontal;
spreadOddButton.disabled = isHorizontal;
spreadEvenButton.disabled = isHorizontal;
}
#spreadModeChanged({
mode
}) {
const {
spreadNoneButton,
spreadOddButton,
spreadEvenButton
} = this.#opts;
toggleCheckedBtn(spreadNoneButton, mode === SpreadMode.NONE);
toggleCheckedBtn(spreadOddButton, mode === SpreadMode.ODD);
toggleCheckedBtn(spreadEvenButton, mode === SpreadMode.EVEN);
}
open() {
if (this.opened) {
return;
}
this.opened = true;
const {
toggleButton,
toolbar
} = this.#opts;
toggleExpandedBtn(toggleButton, true, toolbar);
}
close() {
if (!this.opened) {
return;
}
this.opened = false;
const {
toggleButton,
toolbar
} = this.#opts;
toggleExpandedBtn(toggleButton, false, toolbar);
}
toggle() {
if (this.opened) {
this.close();
} else {
this.open();
}
}
}
;// ./web/signature_manager.js
const DEFAULT_HEIGHT_IN_PAGE = 40;
class SignatureManager {
#addButton;
#tabsToAltText = null;
#clearButton;
#clearDescription;
#currentEditor;
#description;
#dialog;
#drawCurves = null;
#drawPlaceholder;
#drawPath = null;
#drawPathString = "";
#drawPoints = null;
#drawSVG;
#drawThickness;
#errorBar;
#extractedSignatureData = null;
#imagePath = null;
#imagePicker;
#imagePickerLink;
#imagePlaceholder;
#imageSVG;
#saveCheckbox;
#saveContainer;
#tabButtons;
#addSignatureToolbarButton;
#loadSignaturesPromise = null;
#typeInput;
#currentTab = null;
#currentTabAC = null;
#hasDescriptionChanged = false;
#eventBus;
#l10n;
#overlayManager;
#editDescriptionDialog;
#signatureStorage;
#uiManager = null;
static #l10nDescription = null;
constructor({
dialog,
panels,
typeButton,
typeInput,
drawButton,
drawPlaceholder,
drawSVG,
drawThickness,
imageButton,
imageSVG,
imagePlaceholder,
imagePicker,
imagePickerLink,
description,
clearButton,
cancelButton,
addButton,
errorCloseButton,
errorBar,
saveCheckbox,
saveContainer
}, editSignatureElements, addSignatureToolbarButton, overlayManager, l10n, signatureStorage, eventBus) {
this.#addButton = addButton;
this.#clearButton = clearButton;
this.#clearDescription = description.lastElementChild;
this.#description = description.firstElementChild;
this.#dialog = dialog;
this.#drawSVG = drawSVG;
this.#drawPlaceholder = drawPlaceholder;
this.#drawThickness = drawThickness;
this.#errorBar = errorBar;
this.#imageSVG = imageSVG;
this.#imagePlaceholder = imagePlaceholder;
this.#imagePicker = imagePicker;
this.#imagePickerLink = imagePickerLink;
this.#overlayManager = overlayManager;
this.#saveCheckbox = saveCheckbox;
this.#saveContainer = saveContainer;
this.#addSignatureToolbarButton = addSignatureToolbarButton;
this.#typeInput = typeInput;
this.#l10n = l10n;
this.#signatureStorage = signatureStorage;
this.#eventBus = eventBus;
this.#editDescriptionDialog = new EditDescriptionDialog(editSignatureElements, overlayManager);
SignatureManager.#l10nDescription ||= Object.freeze({
signature: "pdfjs-editor-add-signature-description-default-when-drawing"
});
dialog.addEventListener("close", this.#close.bind(this));
dialog.addEventListener("contextmenu", e => {
const {
target
} = e;
if (target !== this.#typeInput && target !== this.#description) {
e.preventDefault();
}
});
dialog.addEventListener("drop", e => {
stopEvent(e);
});
cancelButton.addEventListener("click", this.#cancel.bind(this));
addButton.addEventListener("click", this.#add.bind(this));
clearButton.addEventListener("click", () => {
this.#reportTelemetry({
type: "signature",
action: "pdfjs.signature.clear",
data: {
type: this.#currentTab
}
});
this.#initTab(null);
}, {
passive: true
});
this.#description.addEventListener("input", () => {
this.#clearDescription.disabled = this.#description.value === "";
}, {
passive: true
});
this.#clearDescription.addEventListener("click", () => {
this.#description.value = "";
this.#clearDescription.disabled = true;
}, {
passive: true
});
errorCloseButton.addEventListener("click", () => {
errorBar.hidden = true;
}, {
passive: true
});
this.#initTabButtons(typeButton, drawButton, imageButton, panels);
imagePicker.accept = SupportedImageMimeTypes.join(",");
eventBus._on("storedsignatureschanged", this.#signaturesChanged.bind(this));
overlayManager.register(dialog);
}
#initTabButtons(typeButton, drawButton, imageButton, panels) {
const buttons = this.#tabButtons = new Map([["type", typeButton], ["draw", drawButton], ["image", imageButton]]);
const tabCallback = e => {
for (const [name, button] of buttons) {
if (button === e.target) {
button.setAttribute("aria-selected", true);
button.setAttribute("tabindex", 0);
panels.setAttribute("data-selected", name);
this.#initTab(name);
} else {
button.setAttribute("aria-selected", false);
button.setAttribute("tabindex", -1);
}
}
};
const buttonsArray = Array.from(buttons.values());
for (let i = 0, ii = buttonsArray.length; i < ii; i++) {
const button = buttonsArray[i];
button.addEventListener("click", tabCallback, {
passive: true
});
button.addEventListener("keydown", ({
key
}) => {
if (key !== "ArrowLeft" && key !== "ArrowRight") {
return;
}
buttonsArray[i + (key === "ArrowLeft" ? -1 : 1)]?.focus();
}, {
passive: true
});
}
}
#resetCommon() {
this.#hasDescriptionChanged = false;
this.#description.value = "";
if (this.#currentTab) {
this.#tabsToAltText.get(this.#currentTab).value = "";
}
}
#resetTab(name) {
switch (name) {
case "type":
this.#typeInput.value = "";
break;
case "draw":
this.#drawCurves = null;
this.#drawPoints = null;
this.#drawPathString = "";
this.#drawPath?.remove();
this.#drawPath = null;
this.#drawPlaceholder.hidden = false;
this.#drawThickness.value = 1;
break;
case "image":
this.#imagePlaceholder.hidden = false;
this.#imagePath?.remove();
this.#imagePath = null;
break;
}
}
#initTab(name) {
if (name && this.#currentTab === name) {
return;
}
if (this.#currentTab) {
this.#tabsToAltText.get(this.#currentTab).value = this.#description.value;
}
if (name) {
this.#currentTab = name;
}
this.#errorBar.hidden = true;
const reset = !name;
if (reset) {
this.#resetCommon();
} else {
this.#description.value = this.#tabsToAltText.get(this.#currentTab).value;
}
this.#clearDescription.disabled = this.#description.value === "";
this.#currentTabAC?.abort();
this.#currentTabAC = new AbortController();
switch (this.#currentTab) {
case "type":
this.#initTypeTab(reset);
break;
case "draw":
this.#initDrawTab(reset);
break;
case "image":
this.#initImageTab(reset);
break;
}
}
#disableButtons(value) {
this.#saveCheckbox.disabled = this.#clearButton.disabled = this.#addButton.disabled = this.#description.disabled = !value;
}
#initTypeTab(reset) {
if (reset) {
this.#resetTab("type");
}
this.#disableButtons(this.#typeInput.value);
const {
signal
} = this.#currentTabAC;
const options = {
passive: true,
signal
};
this.#typeInput.addEventListener("input", () => {
const {
value
} = this.#typeInput;
if (!this.#hasDescriptionChanged) {
this.#tabsToAltText.get("type").default = this.#description.value = value;
this.#clearDescription.disabled = value === "";
}
this.#disableButtons(value);
}, options);
this.#description.addEventListener("input", () => {
this.#hasDescriptionChanged = this.#typeInput.value !== this.#description.value;
}, options);
}
#initDrawTab(reset) {
if (reset) {
this.#resetTab("draw");
}
this.#disableButtons(this.#drawPath);
const {
signal
} = this.#currentTabAC;
const options = {
signal
};
let currentPointerId = NaN;
const drawCallback = e => {
const {
pointerId
} = e;
if (!isNaN(currentPointerId) && currentPointerId !== pointerId) {
return;
}
currentPointerId = pointerId;
e.preventDefault();
this.#drawSVG.setPointerCapture(pointerId);
const {
width: drawWidth,
height: drawHeight
} = this.#drawSVG.getBoundingClientRect();
let {
offsetX,
offsetY
} = e;
offsetX = Math.round(offsetX);
offsetY = Math.round(offsetY);
if (e.target === this.#drawPlaceholder) {
this.#drawPlaceholder.hidden = true;
}
if (!this.#drawCurves) {
this.#drawCurves = {
width: drawWidth,
height: drawHeight,
thickness: parseInt(this.#drawThickness.value),
curves: []
};
this.#disableButtons(true);
const svgFactory = new DOMSVGFactory();
const path = this.#drawPath = svgFactory.createElement("path");
path.setAttribute("stroke-width", this.#drawThickness.value);
this.#drawSVG.append(path);
this.#drawSVG.addEventListener("pointerdown", drawCallback, options);
this.#drawPlaceholder.removeEventListener("pointerdown", drawCallback);
if (this.#description.value === "") {
this.#l10n.get(SignatureManager.#l10nDescription.signature).then(description => {
this.#tabsToAltText.get("draw").default = description;
this.#description.value ||= description;
this.#clearDescription.disabled = this.#description.value === "";
});
}
}
this.#drawPoints = [offsetX, offsetY];
this.#drawCurves.curves.push({
points: this.#drawPoints
});
this.#drawPathString += `M ${offsetX} ${offsetY}`;
this.#drawPath.setAttribute("d", this.#drawPathString);
const finishDrawAC = new AbortController();
const listenerDrawOptions = {
signal: AbortSignal.any([signal, finishDrawAC.signal])
};
this.#drawSVG.addEventListener("contextmenu", noContextMenu, listenerDrawOptions);
this.#drawSVG.addEventListener("pointermove", evt => {
evt.preventDefault();
let {
offsetX: x,
offsetY: y
} = evt;
x = Math.round(x);
y = Math.round(y);
const drawPoints = this.#drawPoints;
if (x < 0 || y < 0 || x > drawWidth || y > drawHeight || x === drawPoints.at(-2) && y === drawPoints.at(-1)) {
return;
}
if (drawPoints.length >= 4) {
const [x1, y1, x2, y2] = drawPoints.slice(-4);
this.#drawPathString += `C${(x1 + 5 * x2) / 6} ${(y1 + 5 * y2) / 6} ${(5 * x2 + x) / 6} ${(5 * y2 + y) / 6} ${(x2 + x) / 2} ${(y2 + y) / 2}`;
} else {
this.#drawPathString += `L${x} ${y}`;
}
drawPoints.push(x, y);
this.#drawPath.setAttribute("d", this.#drawPathString);
}, listenerDrawOptions);
this.#drawSVG.addEventListener("pointerup", evt => {
const {
pointerId: pId
} = evt;
if (!isNaN(currentPointerId) && currentPointerId !== pId) {
return;
}
currentPointerId = NaN;
evt.preventDefault();
this.#drawSVG.releasePointerCapture(pId);
finishDrawAC.abort();
if (this.#drawPoints.length === 2) {
this.#drawPathString += `L${this.#drawPoints[0]} ${this.#drawPoints[1]}`;
this.#drawPath.setAttribute("d", this.#drawPathString);
}
}, listenerDrawOptions);
};
if (this.#drawCurves) {
this.#drawSVG.addEventListener("pointerdown", drawCallback, options);
} else {
this.#drawPlaceholder.addEventListener("pointerdown", drawCallback, options);
}
this.#drawThickness.addEventListener("input", () => {
const {
value: thickness
} = this.#drawThickness;
this.#drawThickness.setAttribute("data-l10n-args", JSON.stringify({
thickness
}));
if (!this.#drawCurves) {
return;
}
this.#drawPath.setAttribute("stroke-width", thickness);
this.#drawCurves.thickness = thickness;
}, options);
}
#initImageTab(reset) {
if (reset) {
this.#resetTab("image");
}
this.#disableButtons(this.#imagePath);
const {
signal
} = this.#currentTabAC;
const options = {
signal
};
const passiveOptions = {
passive: true,
signal
};
this.#imagePickerLink.addEventListener("keydown", e => {
const {
key
} = e;
if (key === "Enter" || key === " ") {
stopEvent(e);
this.#imagePicker.click();
}
}, options);
this.#imagePicker.addEventListener("click", () => {
this.#dialog.classList.toggle("waiting", true);
}, passiveOptions);
this.#imagePicker.addEventListener("change", async () => {
const file = this.#imagePicker.files?.[0];
if (!file || !SupportedImageMimeTypes.includes(file.type)) {
this.#errorBar.hidden = false;
this.#dialog.classList.toggle("waiting", false);
return;
}
await this.#extractSignature(file);
}, passiveOptions);
this.#imagePicker.addEventListener("cancel", () => {
this.#dialog.classList.toggle("waiting", false);
}, passiveOptions);
this.#imagePlaceholder.addEventListener("dragover", e => {
const {
dataTransfer
} = e;
for (const {
type
} of dataTransfer.items) {
if (!SupportedImageMimeTypes.includes(type)) {
continue;
}
dataTransfer.dropEffect = dataTransfer.effectAllowed === "copy" ? "copy" : "move";
stopEvent(e);
return;
}
dataTransfer.dropEffect = "none";
}, options);
this.#imagePlaceholder.addEventListener("drop", e => {
const {
dataTransfer: {
files
}
} = e;
if (!files?.length) {
return;
}
for (const file of files) {
if (SupportedImageMimeTypes.includes(file.type)) {
this.#extractSignature(file);
break;
}
}
stopEvent(e);
this.#dialog.classList.toggle("waiting", true);
}, options);
}
async #extractSignature(file) {
let data;
try {
data = await this.#uiManager.imageManager.getFromFile(file);
} catch (e) {
console.error("SignatureManager.#extractSignature.", e);
}
if (!data) {
this.#errorBar.hidden = false;
this.#dialog.classList.toggle("waiting", false);
return;
}
const {
outline
} = this.#extractedSignatureData = this.#currentEditor.getFromImage(data.bitmap);
if (!outline) {
this.#dialog.classList.toggle("waiting", false);
return;
}
this.#imagePlaceholder.hidden = true;
this.#disableButtons(true);
const svgFactory = new DOMSVGFactory();
const path = this.#imagePath = svgFactory.createElement("path");
this.#imageSVG.setAttribute("viewBox", outline.viewBox);
this.#imageSVG.setAttribute("preserveAspectRatio", "xMidYMid meet");
this.#imageSVG.append(path);
path.setAttribute("d", outline.toSVGPath());
this.#tabsToAltText.get("image").default = file.name;
if (this.#description.value === "") {
this.#description.value = file.name || "";
this.#clearDescription.disabled = this.#description.value === "";
}
this.#dialog.classList.toggle("waiting", false);
}
#getOutlineForType() {
return this.#currentEditor.getFromText(this.#typeInput.value, window.getComputedStyle(this.#typeInput));
}
#getOutlineForDraw() {
const {
width,
height
} = this.#drawSVG.getBoundingClientRect();
return this.#currentEditor.getDrawnSignature(this.#drawCurves, width, height);
}
#reportTelemetry(data) {
this.#eventBus.dispatch("reporttelemetry", {
source: this,
details: {
type: "editing",
data
}
});
}
#addToolbarButton(signatureData, uuid, description) {
const {
curves,
areContours,
thickness,
width,
height
} = signatureData;
const maxDim = Math.max(width, height);
const outlineData = SignatureExtractor.processDrawnLines({
lines: {
curves,
thickness,
width,
height
},
pageWidth: maxDim,
pageHeight: maxDim,
rotation: 0,
innerMargin: 0,
mustSmooth: false,
areContours
});
if (!outlineData) {
return;
}
const {
outline
} = outlineData;
const svgFactory = new DOMSVGFactory();
const div = document.createElement("div");
const button = document.createElement("button");
button.addEventListener("click", () => {
this.#eventBus.dispatch("switchannotationeditorparams", {
source: this,
type: AnnotationEditorParamsType.CREATE,
value: {
signatureData: {
lines: {
curves,
thickness,
width,
height
},
mustSmooth: false,
areContours,
description,
uuid,
heightInPage: DEFAULT_HEIGHT_IN_PAGE
}
}
});
});
div.append(button);
div.classList.add("toolbarAddSignatureButtonContainer");
const svg = svgFactory.create(1, 1, true);
button.append(svg);
const span = document.createElement("span");
span.ariaHidden = true;
button.append(span);
button.classList.add("toolbarAddSignatureButton");
button.type = "button";
span.textContent = description;
button.setAttribute("data-l10n-id", "pdfjs-editor-add-saved-signature-button");
button.setAttribute("data-l10n-args", JSON.stringify({
description
}));
button.tabIndex = 0;
const path = svgFactory.createElement("path");
svg.append(path);
svg.setAttribute("viewBox", outline.viewBox);
svg.setAttribute("preserveAspectRatio", "xMidYMid meet");
if (areContours) {
path.classList.add("contours");
}
path.setAttribute("d", outline.toSVGPath());
const deleteButton = document.createElement("button");
div.append(deleteButton);
deleteButton.classList.add("toolbarButton", "deleteButton");
deleteButton.setAttribute("data-l10n-id", "pdfjs-editor-delete-signature-button1");
deleteButton.type = "button";
deleteButton.tabIndex = 0;
deleteButton.addEventListener("click", async () => {
if (await this.#signatureStorage.delete(uuid)) {
div.remove();
this.#reportTelemetry({
type: "signature",
action: "pdfjs.signature.delete_saved",
data: {
savedCount: await this.#signatureStorage.size()
}
});
}
});
const deleteSpan = document.createElement("span");
deleteButton.append(deleteSpan);
deleteSpan.setAttribute("data-l10n-id", "pdfjs-editor-delete-signature-button-label1");
this.#addSignatureToolbarButton.before(div);
}
async #signaturesChanged() {
const parent = this.#addSignatureToolbarButton.parentElement;
while (parent.firstElementChild !== this.#addSignatureToolbarButton) {
parent.firstElementChild.remove();
}
this.#loadSignaturesPromise = null;
await this.loadSignatures(true);
}
getSignature(params) {
return this.open(params);
}
async loadSignatures(reload = false) {
if (!this.#addSignatureToolbarButton || !reload && this.#addSignatureToolbarButton.previousElementSibling || !this.#signatureStorage) {
return;
}
if (!this.#loadSignaturesPromise) {
this.#loadSignaturesPromise = this.#signatureStorage.getAll().then(async signatures => [signatures, await Promise.all(Array.from(signatures.values(), ({
signatureData
}) => SignatureExtractor.decompressSignature(signatureData)))]);
if (!reload) {
return;
}
}
const [signatures, signaturesData] = await this.#loadSignaturesPromise;
this.#loadSignaturesPromise = null;
let i = 0;
for (const [uuid, {
description
}] of signatures) {
const data = signaturesData[i++];
if (!data) {
continue;
}
data.curves = data.outlines.map(points => ({
points
}));
delete data.outlines;
this.#addToolbarButton(data, uuid, description);
}
}
async renderEditButton(editor) {
const button = document.createElement("button");
button.classList.add("altText", "editDescription");
button.tabIndex = 0;
button.title = editor.description;
const span = document.createElement("span");
button.append(span);
span.setAttribute("data-l10n-id", "pdfjs-editor-add-signature-edit-button-label");
button.addEventListener("click", () => {
this.#editDescriptionDialog.open(editor);
}, {
passive: true
});
return button;
}
async open({
uiManager,
editor
}) {
this.#tabsToAltText ||= new Map(this.#tabButtons.keys().map(name => [name, {
value: "",
default: ""
}]));
this.#uiManager = uiManager;
this.#currentEditor = editor;
this.#uiManager.removeEditListeners();
const isStorageFull = await this.#signatureStorage.isFull();
this.#saveContainer.classList.toggle("fullStorage", isStorageFull);
this.#saveCheckbox.checked = !isStorageFull;
await this.#overlayManager.open(this.#dialog);
const tabType = this.#tabButtons.get("type");
tabType.focus();
tabType.click();
}
#cancel() {
this.#finish();
}
#finish() {
this.#overlayManager.closeIfActive(this.#dialog);
}
#close() {
if (this.#currentEditor._drawId === null) {
this.#currentEditor.remove();
}
this.#uiManager?.addEditListeners();
this.#currentTabAC?.abort();
this.#currentTabAC = null;
this.#uiManager = null;
this.#currentEditor = null;
this.#resetCommon();
for (const [name] of this.#tabButtons) {
this.#resetTab(name);
}
this.#disableButtons(false);
this.#currentTab = null;
this.#tabsToAltText = null;
}
async #add() {
let data;
const type = this.#currentTab;
switch (type) {
case "type":
data = this.#getOutlineForType();
break;
case "draw":
data = this.#getOutlineForDraw();
break;
case "image":
data = this.#extractedSignatureData;
break;
}
let uuid = null;
const description = this.#description.value;
if (this.#saveCheckbox.checked) {
const {
newCurves,
areContours,
thickness,
width,
height
} = data;
const signatureData = await SignatureExtractor.compressSignature({
outlines: newCurves,
areContours,
thickness,
width,
height
});
uuid = await this.#signatureStorage.create({
description,
signatureData
});
if (uuid) {
this.#addToolbarButton({
curves: newCurves.map(points => ({
points
})),
areContours,
thickness,
width,
height
}, uuid, description);
} else {
console.warn("SignatureManager.add: cannot save the signature.");
}
}
const altText = this.#tabsToAltText.get(type);
this.#reportTelemetry({
type: "signature",
action: "pdfjs.signature.created",
data: {
type,
saved: !!uuid,
savedCount: await this.#signatureStorage.size(),
descriptionChanged: description !== altText.default
}
});
this.#currentEditor.addSignature(data, DEFAULT_HEIGHT_IN_PAGE, this.#description.value, uuid);
this.#finish();
}
destroy() {
this.#uiManager = null;
this.#finish();
}
}
class EditDescriptionDialog {
#currentEditor;
#previousDescription;
#description;
#dialog;
#overlayManager;
#signatureSVG;
#uiManager;
constructor({
dialog,
description,
cancelButton,
updateButton,
editSignatureView
}, overlayManager) {
const descriptionInput = this.#description = description.firstElementChild;
this.#signatureSVG = editSignatureView;
this.#dialog = dialog;
this.#overlayManager = overlayManager;
dialog.addEventListener("close", this.#close.bind(this));
dialog.addEventListener("contextmenu", e => {
if (e.target !== this.#description) {
e.preventDefault();
}
});
cancelButton.addEventListener("click", this.#cancel.bind(this));
updateButton.addEventListener("click", this.#update.bind(this));
const clearDescription = description.lastElementChild;
clearDescription.addEventListener("click", () => {
descriptionInput.value = "";
clearDescription.disabled = true;
updateButton.disabled = this.#previousDescription === "";
});
descriptionInput.addEventListener("input", () => {
const {
value
} = descriptionInput;
clearDescription.disabled = value === "";
updateButton.disabled = value === this.#previousDescription;
editSignatureView.setAttribute("aria-label", value);
}, {
passive: true
});
overlayManager.register(dialog);
}
async open(editor) {
this.#uiManager = editor._uiManager;
this.#currentEditor = editor;
this.#previousDescription = this.#description.value = editor.description;
this.#description.dispatchEvent(new Event("input"));
this.#uiManager.removeEditListeners();
const {
areContours,
outline
} = editor.getSignaturePreview();
const svgFactory = new DOMSVGFactory();
const path = svgFactory.createElement("path");
this.#signatureSVG.append(path);
this.#signatureSVG.setAttribute("viewBox", outline.viewBox);
path.setAttribute("d", outline.toSVGPath());
if (areContours) {
path.classList.add("contours");
}
await this.#overlayManager.open(this.#dialog);
}
async #update() {
this.#currentEditor._reportTelemetry({
action: "pdfjs.signature.edit_description",
data: {
hasBeenChanged: true
}
});
this.#currentEditor.description = this.#description.value;
this.#finish();
}
#cancel() {
this.#currentEditor._reportTelemetry({
action: "pdfjs.signature.edit_description",
data: {
hasBeenChanged: false
}
});
this.#finish();
}
#finish() {
this.#overlayManager.closeIfActive(this.#dialog);
}
#close() {
this.#uiManager?.addEditListeners();
this.#uiManager = null;
this.#currentEditor = null;
this.#signatureSVG.firstElementChild.remove();
}
}
;// ./web/toolbar.js
class Toolbar {
#colorPicker = null;
#opts;
constructor(options, eventBus, toolbarDensity = 0) {
this.#opts = options;
this.eventBus = eventBus;
const buttons = [{
element: options.previous,
eventName: "previouspage"
}, {
element: options.next,
eventName: "nextpage"
}, {
element: options.zoomIn,
eventName: "zoomin"
}, {
element: options.zoomOut,
eventName: "zoomout"
}, {
element: options.print,
eventName: "print"
}, {
element: options.download,
eventName: "download"
}, {
element: options.editorFreeTextButton,
eventName: "switchannotationeditormode",
eventDetails: {
get mode() {
const {
classList
} = options.editorFreeTextButton;
return classList.contains("toggled") ? AnnotationEditorType.NONE : AnnotationEditorType.FREETEXT;
}
}
}, {
element: options.editorHighlightButton,
eventName: "switchannotationeditormode",
eventDetails: {
get mode() {
const {
classList
} = options.editorHighlightButton;
return classList.contains("toggled") ? AnnotationEditorType.NONE : AnnotationEditorType.HIGHLIGHT;
}
}
}, {
element: options.editorInkButton,
eventName: "switchannotationeditormode",
eventDetails: {
get mode() {
const {
classList
} = options.editorInkButton;
return classList.contains("toggled") ? AnnotationEditorType.NONE : AnnotationEditorType.INK;
}
}
}, {
element: options.editorStampButton,
eventName: "switchannotationeditormode",
eventDetails: {
get mode() {
const {
classList
} = options.editorStampButton;
return classList.contains("toggled") ? AnnotationEditorType.NONE : AnnotationEditorType.STAMP;
}
},
telemetry: {
type: "editing",
data: {
action: "pdfjs.image.icon_click"
}
}
}, {
element: options.editorSignatureButton,
eventName: "switchannotationeditormode",
eventDetails: {
get mode() {
const {
classList
} = options.editorSignatureButton;
return classList.contains("toggled") ? AnnotationEditorType.NONE : AnnotationEditorType.SIGNATURE;
}
}
}];
this.#bindListeners(buttons);
this.#updateToolbarDensity({
value: toolbarDensity
});
this.reset();
}
#updateToolbarDensity({
value
}) {
let name = "normal";
switch (value) {
case 1:
name = "compact";
break;
case 2:
name = "touch";
break;
}
document.documentElement.setAttribute("data-toolbar-density", name);
}
setPageNumber(pageNumber, pageLabel) {
this.pageNumber = pageNumber;
this.pageLabel = pageLabel;
this.#updateUIState(false);
}
setPagesCount(pagesCount, hasPageLabels) {
this.pagesCount = pagesCount;
this.hasPageLabels = hasPageLabels;
this.#updateUIState(true);
}
setPageScale(pageScaleValue, pageScale) {
this.pageScaleValue = (pageScaleValue || pageScale).toString();
this.pageScale = pageScale;
this.#updateUIState(false);
}
reset() {
this.#colorPicker = null;
this.pageNumber = 0;
this.pageLabel = null;
this.hasPageLabels = false;
this.pagesCount = 0;
this.pageScaleValue = DEFAULT_SCALE_VALUE;
this.pageScale = DEFAULT_SCALE;
this.#updateUIState(true);
this.updateLoadingIndicatorState();
this.#editorModeChanged({
mode: AnnotationEditorType.DISABLE
});
}
#bindListeners(buttons) {
const {
eventBus
} = this;
const {
editorHighlightColorPicker,
editorHighlightButton,
pageNumber,
scaleSelect
} = this.#opts;
const self = this;
for (const {
element,
eventName,
eventDetails,
telemetry
} of buttons) {
element.addEventListener("click", evt => {
if (eventName !== null) {
eventBus.dispatch(eventName, {
source: this,
...eventDetails,
isFromKeyboard: evt.detail === 0
});
}
if (telemetry) {
eventBus.dispatch("reporttelemetry", {
source: this,
details: telemetry
});
}
});
}
pageNumber.addEventListener("click", function () {
this.select();
});
pageNumber.addEventListener("change", function () {
eventBus.dispatch("pagenumberchanged", {
source: self,
value: this.value
});
});
scaleSelect.addEventListener("change", function () {
if (this.value === "custom") {
return;
}
eventBus.dispatch("scalechanged", {
source: self,
value: this.value
});
});
scaleSelect.addEventListener("click", function ({
target
}) {
if (this.value === self.pageScaleValue && target.tagName.toUpperCase() === "OPTION") {
this.blur();
}
});
scaleSelect.oncontextmenu = noContextMenu;
eventBus._on("annotationeditormodechanged", this.#editorModeChanged.bind(this));
eventBus._on("showannotationeditorui", ({
mode
}) => {
switch (mode) {
case AnnotationEditorType.HIGHLIGHT:
editorHighlightButton.click();
break;
}
});
eventBus._on("toolbardensity", this.#updateToolbarDensity.bind(this));
if (editorHighlightColorPicker) {
eventBus._on("annotationeditoruimanager", ({
uiManager
}) => {
const cp = this.#colorPicker = new ColorPicker({
uiManager
});
uiManager.setMainHighlightColorPicker(cp);
editorHighlightColorPicker.append(cp.renderMainDropdown());
});
eventBus._on("mainhighlightcolorpickerupdatecolor", ({
value
}) => {
this.#colorPicker?.updateColor(value);
});
}
}
#editorModeChanged({
mode
}) {
const {
editorFreeTextButton,
editorFreeTextParamsToolbar,
editorHighlightButton,
editorHighlightParamsToolbar,
editorInkButton,
editorInkParamsToolbar,
editorStampButton,
editorStampParamsToolbar,
editorSignatureButton,
editorSignatureParamsToolbar
} = this.#opts;
toggleExpandedBtn(editorFreeTextButton, mode === AnnotationEditorType.FREETEXT, editorFreeTextParamsToolbar);
toggleExpandedBtn(editorHighlightButton, mode === AnnotationEditorType.HIGHLIGHT, editorHighlightParamsToolbar);
toggleExpandedBtn(editorInkButton, mode === AnnotationEditorType.INK, editorInkParamsToolbar);
toggleExpandedBtn(editorStampButton, mode === AnnotationEditorType.STAMP, editorStampParamsToolbar);
toggleExpandedBtn(editorSignatureButton, mode === AnnotationEditorType.SIGNATURE, editorSignatureParamsToolbar);
const isDisable = mode === AnnotationEditorType.DISABLE;
editorFreeTextButton.disabled = isDisable;
editorHighlightButton.disabled = isDisable;
editorInkButton.disabled = isDisable;
editorStampButton.disabled = isDisable;
editorSignatureButton.disabled = isDisable;
}
#updateUIState(resetNumPages = false) {
const {
pageNumber,
pagesCount,
pageScaleValue,
pageScale
} = this;
const opts = this.#opts;
if (resetNumPages) {
if (this.hasPageLabels) {
opts.pageNumber.type = "text";
opts.numPages.setAttribute("data-l10n-id", "pdfjs-page-of-pages");
} else {
opts.pageNumber.type = "number";
opts.numPages.setAttribute("data-l10n-id", "pdfjs-of-pages");
opts.numPages.setAttribute("data-l10n-args", JSON.stringify({
pagesCount
}));
}
opts.pageNumber.max = pagesCount;
}
if (this.hasPageLabels) {
opts.pageNumber.value = this.pageLabel;
opts.numPages.setAttribute("data-l10n-args", JSON.stringify({
pageNumber,
pagesCount
}));
} else {
opts.pageNumber.value = pageNumber;
}
opts.previous.disabled = pageNumber <= 1;
opts.next.disabled = pageNumber >= pagesCount;
opts.zoomOut.disabled = pageScale <= MIN_SCALE;
opts.zoomIn.disabled = pageScale >= MAX_SCALE;
let predefinedValueFound = false;
for (const option of opts.scaleSelect.options) {
if (option.value !== pageScaleValue) {
option.selected = false;
continue;
}
option.selected = true;
predefinedValueFound = true;
}
if (!predefinedValueFound) {
opts.customScaleOption.selected = true;
opts.customScaleOption.setAttribute("data-l10n-args", JSON.stringify({
scale: Math.round(pageScale * 10000) / 100
}));
}
}
updateLoadingIndicatorState(loading = false) {
const {
pageNumber
} = this.#opts;
pageNumber.classList.toggle("loading", loading);
}
}
;// ./web/view_history.js
const DEFAULT_VIEW_HISTORY_CACHE_SIZE = 20;
class ViewHistory {
constructor(fingerprint, cacheSize = DEFAULT_VIEW_HISTORY_CACHE_SIZE) {
this.fingerprint = fingerprint;
this.cacheSize = cacheSize;
this._initializedPromise = this._readFromStorage().then(databaseStr => {
const database = JSON.parse(databaseStr || "{}");
let index = -1;
if (!Array.isArray(database.files)) {
database.files = [];
} else {
while (database.files.length >= this.cacheSize) {
database.files.shift();
}
for (let i = 0, ii = database.files.length; i < ii; i++) {
const branch = database.files[i];
if (branch.fingerprint === this.fingerprint) {
index = i;
break;
}
}
}
if (index === -1) {
index = database.files.push({
fingerprint: this.fingerprint
}) - 1;
}
this.file = database.files[index];
this.database = database;
});
}
async _writeToStorage() {
const databaseStr = JSON.stringify(this.database);
localStorage.setItem("pdfjs.history", databaseStr);
}
async _readFromStorage() {
return localStorage.getItem("pdfjs.history");
}
async set(name, val) {
await this._initializedPromise;
this.file[name] = val;
return this._writeToStorage();
}
async setMultiple(properties) {
await this._initializedPromise;
for (const name in properties) {
this.file[name] = properties[name];
}
return this._writeToStorage();
}
async get(name, defaultValue) {
await this._initializedPromise;
const val = this.file[name];
return val !== undefined ? val : defaultValue;
}
async getMultiple(properties) {
await this._initializedPromise;
const values = Object.create(null);
for (const name in properties) {
const val = this.file[name];
values[name] = val !== undefined ? val : properties[name];
}
return values;
}
}
;// ./web/app.js
const FORCE_PAGES_LOADED_TIMEOUT = 10000;
const ViewOnLoad = {
UNKNOWN: -1,
PREVIOUS: 0,
INITIAL: 1
};
const PDFViewerApplication = {
initialBookmark: document.location.hash.substring(1),
_initializedCapability: {
...Promise.withResolvers(),
settled: false
},
appConfig: null,
pdfDocument: null,
pdfLoadingTask: null,
printService: null,
pdfViewer: null,
pdfThumbnailViewer: null,
pdfRenderingQueue: null,
pdfPresentationMode: null,
pdfDocumentProperties: null,
pdfLinkService: null,
pdfHistory: null,
pdfSidebar: null,
pdfOutlineViewer: null,
pdfAttachmentViewer: null,
pdfLayerViewer: null,
pdfCursorTools: null,
pdfScriptingManager: null,
store: null,
downloadManager: null,
overlayManager: null,
preferences: new Preferences(),
toolbar: null,
secondaryToolbar: null,
eventBus: null,
l10n: null,
annotationEditorParams: null,
imageAltTextSettings: null,
isInitialViewSet: false,
isViewerEmbedded: window.parent !== window,
url: "",
baseUrl: "",
mlManager: null,
_downloadUrl: "",
_eventBusAbortController: null,
_windowAbortController: null,
_globalAbortController: new AbortController(),
documentInfo: null,
metadata: null,
_contentDispositionFilename: null,
_contentLength: null,
_saveInProgress: false,
_wheelUnusedTicks: 0,
_wheelUnusedFactor: 1,
_touchManager: null,
_touchUnusedTicks: 0,
_touchUnusedFactor: 1,
_PDFBug: null,
_hasAnnotationEditors: false,
_title: document.title,
_printAnnotationStoragePromise: null,
_isCtrlKeyDown: false,
_caretBrowsing: null,
_isScrolling: false,
editorUndoBar: null,
async initialize(appConfig) {
this.appConfig = appConfig;
try {
await this.preferences.initializedPromise;
} catch (ex) {
console.error("initialize:", ex);
}
if (AppOptions.get("pdfBugEnabled")) {
await this._parseHashParams();
}
let mode;
switch (AppOptions.get("viewerCssTheme")) {
case 1:
mode = "is-light";
break;
case 2:
mode = "is-dark";
break;
}
if (mode) {
document.documentElement.classList.add(mode);
}
this.l10n = await this.externalServices.createL10n();
document.getElementsByTagName("html")[0].dir = this.l10n.getDirection();
this.l10n.translate(appConfig.appContainer || document.documentElement);
if (this.isViewerEmbedded && AppOptions.get("externalLinkTarget") === LinkTarget.NONE) {
AppOptions.set("externalLinkTarget", LinkTarget.TOP);
}
await this._initializeViewerComponents();
this.bindEvents();
this.bindWindowEvents();
this._initializedCapability.settled = true;
this._initializedCapability.resolve();
},
async _parseHashParams() {
const hash = document.location.hash.substring(1);
if (!hash) {
return;
}
const {
mainContainer,
viewerContainer
} = this.appConfig,
params = parseQueryString(hash);
const loadPDFBug = async () => {
if (this._PDFBug) {
return;
}
const {
PDFBug
} = await import(
/*webpackIgnore: true*/
/*@vite-ignore*/
AppOptions.get("debuggerSrc"));
this._PDFBug = PDFBug;
};
if (params.get("disableworker") === "true") {
try {
GlobalWorkerOptions.workerSrc ||= AppOptions.get("workerSrc");
await import(
/*webpackIgnore: true*/
/*@vite-ignore*/
PDFWorker.workerSrc);
} catch (ex) {
console.error("_parseHashParams:", ex);
}
}
if (params.has("textlayer")) {
switch (params.get("textlayer")) {
case "off":
AppOptions.set("textLayerMode", TextLayerMode.DISABLE);
break;
case "visible":
case "shadow":
case "hover":
viewerContainer.classList.add(`textLayer-${params.get("textlayer")}`);
try {
await loadPDFBug();
this._PDFBug.loadCSS();
} catch (ex) {
console.error("_parseHashParams:", ex);
}
break;
}
}
if (params.has("pdfbug")) {
AppOptions.setAll({
pdfBug: true,
fontExtraProperties: true
});
const enabled = params.get("pdfbug").split(",");
try {
await loadPDFBug();
this._PDFBug.init(mainContainer, enabled);
} catch (ex) {
console.error("_parseHashParams:", ex);
}
}
if (params.has("locale")) {
AppOptions.set("localeProperties", {
lang: params.get("locale")
});
}
const opts = {
disableAutoFetch: x => x === "true",
disableFontFace: x => x === "true",
disableHistory: x => x === "true",
disableRange: x => x === "true",
disableStream: x => x === "true",
verbosity: x => x | 0
};
for (const name in opts) {
const check = opts[name],
key = name.toLowerCase();
if (params.has(key)) {
AppOptions.set(name, check(params.get(key)));
}
}
},
async _initializeViewerComponents() {
const {
appConfig,
externalServices,
l10n,
mlManager
} = this;
const abortSignal = this._globalAbortController.signal;
const eventBus = new EventBus();
this.eventBus = AppOptions.eventBus = eventBus;
mlManager?.setEventBus(eventBus, abortSignal);
const overlayManager = this.overlayManager = new OverlayManager();
const renderingQueue = this.pdfRenderingQueue = new PDFRenderingQueue();
renderingQueue.onIdle = this._cleanup.bind(this);
const linkService = this.pdfLinkService = new PDFLinkService({
eventBus,
externalLinkTarget: AppOptions.get("externalLinkTarget"),
externalLinkRel: AppOptions.get("externalLinkRel"),
ignoreDestinationZoom: AppOptions.get("ignoreDestinationZoom")
});
const downloadManager = this.downloadManager = new DownloadManager();
const findController = this.findController = new PDFFindController({
linkService,
eventBus,
updateMatchesCountOnProgress: true
});
const pdfScriptingManager = this.pdfScriptingManager = new PDFScriptingManager({
eventBus,
externalServices,
docProperties: this._scriptingDocProperties.bind(this)
});
const container = appConfig.mainContainer,
viewer = appConfig.viewerContainer;
const annotationEditorMode = AppOptions.get("annotationEditorMode");
const pageColors = AppOptions.get("forcePageColors") || window.matchMedia("(forced-colors: active)").matches ? {
background: AppOptions.get("pageColorsBackground"),
foreground: AppOptions.get("pageColorsForeground")
} : null;
let altTextManager;
if (AppOptions.get("enableUpdatedAddImage")) {
altTextManager = appConfig.newAltTextDialog ? new NewAltTextManager(appConfig.newAltTextDialog, overlayManager, eventBus) : null;
} else {
altTextManager = appConfig.altTextDialog ? new AltTextManager(appConfig.altTextDialog, container, overlayManager, eventBus) : null;
}
if (appConfig.editorUndoBar) {
this.editorUndoBar = new EditorUndoBar(appConfig.editorUndoBar, eventBus);
}
const signatureManager = AppOptions.get("enableSignatureEditor") && appConfig.addSignatureDialog ? new SignatureManager(appConfig.addSignatureDialog, appConfig.editSignatureDialog, appConfig.annotationEditorParams?.editorSignatureAddSignature || null, overlayManager, l10n, externalServices.createSignatureStorage(eventBus, abortSignal), eventBus) : null;
const enableHWA = AppOptions.get("enableHWA"),
maxCanvasPixels = AppOptions.get("maxCanvasPixels"),
maxCanvasDim = AppOptions.get("maxCanvasDim");
const pdfViewer = this.pdfViewer = new PDFViewer({
container,
viewer,
eventBus,
renderingQueue,
linkService,
downloadManager,
altTextManager,
signatureManager,
editorUndoBar: this.editorUndoBar,
findController,
scriptingManager: AppOptions.get("enableScripting") && pdfScriptingManager,
l10n,
textLayerMode: AppOptions.get("textLayerMode"),
annotationMode: AppOptions.get("annotationMode"),
annotationEditorMode,
annotationEditorHighlightColors: AppOptions.get("highlightEditorColors"),
enableHighlightFloatingButton: AppOptions.get("enableHighlightFloatingButton"),
enableUpdatedAddImage: AppOptions.get("enableUpdatedAddImage"),
enableNewAltTextWhenAddingImage: AppOptions.get("enableNewAltTextWhenAddingImage"),
imageResourcesPath: AppOptions.get("imageResourcesPath"),
enablePrintAutoRotate: AppOptions.get("enablePrintAutoRotate"),
maxCanvasPixels,
maxCanvasDim,
enableDetailCanvas: AppOptions.get("enableDetailCanvas"),
enablePermissions: AppOptions.get("enablePermissions"),
pageColors,
mlManager,
abortSignal,
enableHWA,
supportsPinchToZoom: this.supportsPinchToZoom,
enableAutoLinking: AppOptions.get("enableAutoLinking")
});
renderingQueue.setViewer(pdfViewer);
linkService.setViewer(pdfViewer);
pdfScriptingManager.setViewer(pdfViewer);
if (appConfig.sidebar?.thumbnailView) {
this.pdfThumbnailViewer = new PDFThumbnailViewer({
container: appConfig.sidebar.thumbnailView,
eventBus,
renderingQueue,
linkService,
maxCanvasPixels,
maxCanvasDim,
pageColors,
abortSignal,
enableHWA
});
renderingQueue.setThumbnailViewer(this.pdfThumbnailViewer);
}
if (!this.isViewerEmbedded && !AppOptions.get("disableHistory")) {
this.pdfHistory = new PDFHistory({
linkService,
eventBus
});
linkService.setHistory(this.pdfHistory);
}
if (!this.supportsIntegratedFind && appConfig.findBar) {
this.findBar = new PDFFindBar(appConfig.findBar, appConfig.principalContainer, eventBus);
}
if (appConfig.annotationEditorParams) {
if (annotationEditorMode !== AnnotationEditorType.DISABLE) {
const editorSignatureButton = appConfig.toolbar?.editorSignatureButton;
if (editorSignatureButton && AppOptions.get("enableSignatureEditor")) {
editorSignatureButton.parentElement.hidden = false;
}
this.annotationEditorParams = new AnnotationEditorParams(appConfig.annotationEditorParams, eventBus);
} else {
for (const id of ["editorModeButtons", "editorModeSeparator"]) {
document.getElementById(id)?.classList.add("hidden");
}
}
}
if (mlManager && appConfig.secondaryToolbar?.imageAltTextSettingsButton) {
this.imageAltTextSettings = new ImageAltTextSettings(appConfig.altTextSettingsDialog, overlayManager, eventBus, mlManager);
}
if (appConfig.documentProperties) {
this.pdfDocumentProperties = new PDFDocumentProperties(appConfig.documentProperties, overlayManager, eventBus, l10n, () => this._docFilename);
}
if (appConfig.secondaryToolbar?.cursorHandToolButton) {
this.pdfCursorTools = new PDFCursorTools({
container,
eventBus,
cursorToolOnLoad: AppOptions.get("cursorToolOnLoad")
});
}
if (appConfig.toolbar) {
this.toolbar = new Toolbar(appConfig.toolbar, eventBus, AppOptions.get("toolbarDensity"));
}
if (appConfig.secondaryToolbar) {
if (AppOptions.get("enableAltText")) {
appConfig.secondaryToolbar.imageAltTextSettingsButton?.classList.remove("hidden");
appConfig.secondaryToolbar.imageAltTextSettingsSeparator?.classList.remove("hidden");
}
this.secondaryToolbar = new SecondaryToolbar(appConfig.secondaryToolbar, eventBus);
}
if (this.supportsFullscreen && appConfig.secondaryToolbar?.presentationModeButton) {
this.pdfPresentationMode = new PDFPresentationMode({
container,
pdfViewer,
eventBus
});
}
if (appConfig.passwordOverlay) {
this.passwordPrompt = new PasswordPrompt(appConfig.passwordOverlay, overlayManager, this.isViewerEmbedded);
}
if (appConfig.sidebar?.outlineView) {
this.pdfOutlineViewer = new PDFOutlineViewer({
container: appConfig.sidebar.outlineView,
eventBus,
l10n,
linkService,
downloadManager
});
}
if (appConfig.sidebar?.attachmentsView) {
this.pdfAttachmentViewer = new PDFAttachmentViewer({
container: appConfig.sidebar.attachmentsView,
eventBus,
l10n,
downloadManager
});
}
if (appConfig.sidebar?.layersView) {
this.pdfLayerViewer = new PDFLayerViewer({
container: appConfig.sidebar.layersView,
eventBus,
l10n
});
}
if (appConfig.sidebar) {
this.pdfSidebar = new PDFSidebar({
elements: appConfig.sidebar,
eventBus,
l10n
});
this.pdfSidebar.onToggled = this.forceRendering.bind(this);
this.pdfSidebar.onUpdateThumbnails = () => {
for (const pageView of pdfViewer.getCachedPageViews()) {
if (pageView.renderingState === RenderingStates.FINISHED) {
this.pdfThumbnailViewer.getThumbnail(pageView.id - 1)?.setImage(pageView);
}
}
this.pdfThumbnailViewer.scrollThumbnailIntoView(pdfViewer.currentPageNumber);
};
}
},
async run(config) {
await this.initialize(config);
const {
appConfig,
eventBus
} = this;
let file;
const queryString = document.location.search.substring(1);
const params = parseQueryString(queryString);
file = params.get("file") ?? AppOptions.get("defaultUrl");
validateFileURL(file);
const fileInput = this._openFileInput = document.createElement("input");
fileInput.id = "fileInput";
fileInput.hidden = true;
fileInput.type = "file";
fileInput.value = null;
document.body.append(fileInput);
fileInput.addEventListener("change", function (evt) {
const {
files
} = evt.target;
if (!files || files.length === 0) {
return;
}
eventBus.dispatch("fileinputchange", {
source: this,
fileInput: evt.target
});
});
appConfig.mainContainer.addEventListener("dragover", function (evt) {
for (const item of evt.dataTransfer.items) {
if (item.type === "application/pdf") {
evt.dataTransfer.dropEffect = evt.dataTransfer.effectAllowed === "copy" ? "copy" : "move";
stopEvent(evt);
return;
}
}
});
appConfig.mainContainer.addEventListener("drop", function (evt) {
if (evt.dataTransfer.files?.[0].type !== "application/pdf") {
return;
}
stopEvent(evt);
eventBus.dispatch("fileinputchange", {
source: this,
fileInput: evt.dataTransfer
});
});
if (!AppOptions.get("supportsDocumentFonts")) {
AppOptions.set("disableFontFace", true);
this.l10n.get("pdfjs-web-fonts-disabled").then(msg => {
console.warn(msg);
});
}
if (!this.supportsPrinting) {
appConfig.toolbar?.print?.classList.add("hidden");
appConfig.secondaryToolbar?.printButton.classList.add("hidden");
}
if (!this.supportsFullscreen) {
appConfig.secondaryToolbar?.presentationModeButton.classList.add("hidden");
}
if (this.supportsIntegratedFind) {
appConfig.findBar?.toggleButton?.classList.add("hidden");
}
if (file) {
this.open({
url: file
});
} else {
this._hideViewBookmark();
}
},
get externalServices() {
return shadow(this, "externalServices", new ExternalServices());
},
get initialized() {
return this._initializedCapability.settled;
},
get initializedPromise() {
return this._initializedCapability.promise;
},
updateZoom(steps, scaleFactor, origin) {
if (this.pdfViewer.isInPresentationMode) {
return;
}
this.pdfViewer.updateScale({
drawingDelay: AppOptions.get("defaultZoomDelay"),
steps,
scaleFactor,
origin
});
},
zoomIn() {
this.updateZoom(1);
},
zoomOut() {
this.updateZoom(-1);
},
zoomReset() {
if (this.pdfViewer.isInPresentationMode) {
return;
}
this.pdfViewer.currentScaleValue = DEFAULT_SCALE_VALUE;
},
touchPinchCallback(origin, prevDistance, distance) {
if (this.supportsPinchToZoom) {
const newScaleFactor = this._accumulateFactor(this.pdfViewer.currentScale, distance / prevDistance, "_touchUnusedFactor");
this.updateZoom(null, newScaleFactor, origin);
} else {
const PIXELS_PER_LINE_SCALE = 30;
const ticks = this._accumulateTicks((distance - prevDistance) / PIXELS_PER_LINE_SCALE, "_touchUnusedTicks");
this.updateZoom(ticks, null, origin);
}
},
touchPinchEndCallback() {
this._touchUnusedTicks = 0;
this._touchUnusedFactor = 1;
},
get pagesCount() {
return this.pdfDocument ? this.pdfDocument.numPages : 0;
},
get page() {
return this.pdfViewer.currentPageNumber;
},
set page(val) {
this.pdfViewer.currentPageNumber = val;
},
get supportsPrinting() {
return PDFPrintServiceFactory.supportsPrinting;
},
get supportsFullscreen() {
return shadow(this, "supportsFullscreen", document.fullscreenEnabled);
},
get supportsPinchToZoom() {
return shadow(this, "supportsPinchToZoom", AppOptions.get("supportsPinchToZoom"));
},
get supportsIntegratedFind() {
return shadow(this, "supportsIntegratedFind", AppOptions.get("supportsIntegratedFind"));
},
get loadingBar() {
const barElement = document.getElementById("loadingBar");
const bar = barElement ? new ProgressBar(barElement) : null;
return shadow(this, "loadingBar", bar);
},
get supportsMouseWheelZoomCtrlKey() {
return shadow(this, "supportsMouseWheelZoomCtrlKey", AppOptions.get("supportsMouseWheelZoomCtrlKey"));
},
get supportsMouseWheelZoomMetaKey() {
return shadow(this, "supportsMouseWheelZoomMetaKey", AppOptions.get("supportsMouseWheelZoomMetaKey"));
},
get supportsCaretBrowsingMode() {
return AppOptions.get("supportsCaretBrowsingMode");
},
moveCaret(isUp, select) {
this._caretBrowsing ||= new CaretBrowsingMode(this._globalAbortController.signal, this.appConfig.mainContainer, this.appConfig.viewerContainer, this.appConfig.toolbar?.container);
this._caretBrowsing.moveCaret(isUp, select);
},
setTitleUsingUrl(url = "", downloadUrl = null) {
this.url = url;
this.baseUrl = url.split("#", 1)[0];
if (downloadUrl) {
this._downloadUrl = downloadUrl === url ? this.baseUrl : downloadUrl.split("#", 1)[0];
}
if (isDataScheme(url)) {
this._hideViewBookmark();
}
let title = pdfjs_getPdfFilenameFromUrl(url, "");
if (!title) {
try {
title = decodeURIComponent(getFilenameFromUrl(url));
} catch {}
}
this.setTitle(title || url);
},
setTitle(title = this._title) {
this._title = title;
if (this.isViewerEmbedded) {
return;
}
const editorIndicator = this._hasAnnotationEditors && !this.pdfRenderingQueue.printing;
document.title = `${editorIndicator ? "* " : ""}${title}`;
},
get _docFilename() {
return this._contentDispositionFilename || pdfjs_getPdfFilenameFromUrl(this.url);
},
_hideViewBookmark() {
const {
secondaryToolbar
} = this.appConfig;
secondaryToolbar?.viewBookmarkButton.classList.add("hidden");
if (secondaryToolbar?.presentationModeButton.classList.contains("hidden")) {
document.getElementById("viewBookmarkSeparator")?.classList.add("hidden");
}
},
async close() {
this._unblockDocumentLoadEvent();
this._hideViewBookmark();
if (!this.pdfLoadingTask) {
return;
}
if (this.pdfDocument?.annotationStorage.size > 0 && this._annotationStorageModified) {
try {
await this.save();
} catch {}
}
const promises = [];
promises.push(this.pdfLoadingTask.destroy());
this.pdfLoadingTask = null;
if (this.pdfDocument) {
this.pdfDocument = null;
this.pdfThumbnailViewer?.setDocument(null);
this.pdfViewer.setDocument(null);
this.pdfLinkService.setDocument(null);
this.pdfDocumentProperties?.setDocument(null);
}
this.pdfLinkService.externalLinkEnabled = true;
this.store = null;
this.isInitialViewSet = false;
this.url = "";
this.baseUrl = "";
this._downloadUrl = "";
this.documentInfo = null;
this.metadata = null;
this._contentDispositionFilename = null;
this._contentLength = null;
this._saveInProgress = false;
this._hasAnnotationEditors = false;
promises.push(this.pdfScriptingManager.destroyPromise, this.passwordPrompt.close());
this.setTitle();
this.pdfSidebar?.reset();
this.pdfOutlineViewer?.reset();
this.pdfAttachmentViewer?.reset();
this.pdfLayerViewer?.reset();
this.pdfHistory?.reset();
this.findBar?.reset();
this.toolbar?.reset();
this.secondaryToolbar?.reset();
this._PDFBug?.cleanup();
await Promise.all(promises);
},
async open(args) {
if (this.pdfLoadingTask) {
await this.close();
}
const workerParams = AppOptions.getAll(OptionKind.WORKER);
Object.assign(GlobalWorkerOptions, workerParams);
if (args.url) {
this.setTitleUsingUrl(args.originalUrl || args.url, args.url);
}
const apiParams = AppOptions.getAll(OptionKind.API);
const loadingTask = getDocument({
...apiParams,
...args
});
this.pdfLoadingTask = loadingTask;
loadingTask.onPassword = (updateCallback, reason) => {
if (this.isViewerEmbedded) {
this._unblockDocumentLoadEvent();
}
this.pdfLinkService.externalLinkEnabled = false;
this.passwordPrompt.setUpdateCallback(updateCallback, reason);
this.passwordPrompt.open();
};
loadingTask.onProgress = ({
loaded,
total
}) => {
this.progress(loaded / total);
};
return loadingTask.promise.then(pdfDocument => {
this.load(pdfDocument);
}, reason => {
if (loadingTask !== this.pdfLoadingTask) {
return undefined;
}
let key = "pdfjs-loading-error";
if (reason instanceof InvalidPDFException) {
key = "pdfjs-invalid-file-error";
} else if (reason instanceof ResponseException) {
key = reason.missing ? "pdfjs-missing-file-error" : "pdfjs-unexpected-response-error";
}
return this._documentError(key, {
message: reason.message
}).then(() => {
throw reason;
});
});
},
async download() {
let data;
try {
data = await (this.pdfDocument ? this.pdfDocument.getData() : this.pdfLoadingTask.getData());
} catch {}
this.downloadManager.download(data, this._downloadUrl, this._docFilename);
},
async save() {
if (this._saveInProgress) {
return;
}
this._saveInProgress = true;
await this.pdfScriptingManager.dispatchWillSave();
try {
const data = await this.pdfDocument.saveDocument();
this.downloadManager.download(data, this._downloadUrl, this._docFilename);
} catch (reason) {
console.error(`Error when saving the document:`, reason);
await this.download();
} finally {
await this.pdfScriptingManager.dispatchDidSave();
this._saveInProgress = false;
}
if (this._hasAnnotationEditors) {
this.externalServices.reportTelemetry({
type: "editing",
data: {
type: "save",
stats: this.pdfDocument?.annotationStorage.editorStats
}
});
}
},
async downloadOrSave() {
const {
classList
} = this.appConfig.appContainer;
classList.add("wait");
await (this.pdfDocument?.annotationStorage.size > 0 ? this.save() : this.download());
classList.remove("wait");
},
async _documentError(key, moreInfo = null) {
this._unblockDocumentLoadEvent();
const message = await this._otherError(key || "pdfjs-loading-error", moreInfo);
this.eventBus.dispatch("documenterror", {
source: this,
message,
reason: moreInfo?.message ?? null
});
},
async _otherError(key, moreInfo = null) {
const message = await this.l10n.get(key);
const moreInfoText = [`PDF.js v${version || "?"} (build: ${build || "?"})`];
if (moreInfo) {
moreInfoText.push(`Message: ${moreInfo.message}`);
if (moreInfo.stack) {
moreInfoText.push(`Stack: ${moreInfo.stack}`);
} else {
if (moreInfo.filename) {
moreInfoText.push(`File: ${moreInfo.filename}`);
}
if (moreInfo.lineNumber) {
moreInfoText.push(`Line: ${moreInfo.lineNumber}`);
}
}
}
console.error(`${message}\n\n${moreInfoText.join("\n")}`);
return message;
},
progress(level) {
const percent = Math.round(level * 100);
if (!this.loadingBar || percent <= this.loadingBar.percent) {
return;
}
this.loadingBar.percent = percent;
if (this.pdfDocument?.loadingParams.disableAutoFetch ?? AppOptions.get("disableAutoFetch")) {
this.loadingBar.setDisableAutoFetch();
}
},
load(pdfDocument) {
this.pdfDocument = pdfDocument;
pdfDocument.getDownloadInfo().then(({
length
}) => {
this._contentLength = length;
this.loadingBar?.hide();
firstPagePromise.then(() => {
this.eventBus.dispatch("documentloaded", {
source: this
});
});
});
const pageLayoutPromise = pdfDocument.getPageLayout().catch(() => {});
const pageModePromise = pdfDocument.getPageMode().catch(() => {});
const openActionPromise = pdfDocument.getOpenAction().catch(() => {});
this.toolbar?.setPagesCount(pdfDocument.numPages, false);
this.secondaryToolbar?.setPagesCount(pdfDocument.numPages);
this.pdfLinkService.setDocument(pdfDocument);
this.pdfDocumentProperties?.setDocument(pdfDocument);
const pdfViewer = this.pdfViewer;
pdfViewer.setDocument(pdfDocument);
const {
firstPagePromise,
onePageRendered,
pagesPromise
} = pdfViewer;
this.pdfThumbnailViewer?.setDocument(pdfDocument);
const storedPromise = (this.store = new ViewHistory(pdfDocument.fingerprints[0])).getMultiple({
page: null,
zoom: DEFAULT_SCALE_VALUE,
scrollLeft: "0",
scrollTop: "0",
rotation: null,
sidebarView: SidebarView.UNKNOWN,
scrollMode: ScrollMode.UNKNOWN,
spreadMode: SpreadMode.UNKNOWN
}).catch(() => {});
firstPagePromise.then(pdfPage => {
this.loadingBar?.setWidth(this.appConfig.viewerContainer);
this._initializeAnnotationStorageCallbacks(pdfDocument);
Promise.all([animationStarted, storedPromise, pageLayoutPromise, pageModePromise, openActionPromise]).then(async ([timeStamp, stored, pageLayout, pageMode, openAction]) => {
const viewOnLoad = AppOptions.get("viewOnLoad");
this._initializePdfHistory({
fingerprint: pdfDocument.fingerprints[0],
viewOnLoad,
initialDest: openAction?.dest
});
const initialBookmark = this.initialBookmark;
const zoom = AppOptions.get("defaultZoomValue");
let hash = zoom ? `zoom=${zoom}` : null;
let rotation = null;
let sidebarView = AppOptions.get("sidebarViewOnLoad");
let scrollMode = AppOptions.get("scrollModeOnLoad");
let spreadMode = AppOptions.get("spreadModeOnLoad");
if (stored?.page && viewOnLoad !== ViewOnLoad.INITIAL) {
hash = `page=${stored.page}&zoom=${zoom || stored.zoom},` + `${stored.scrollLeft},${stored.scrollTop}`;
rotation = parseInt(stored.rotation, 10);
if (sidebarView === SidebarView.UNKNOWN) {
sidebarView = stored.sidebarView | 0;
}
if (scrollMode === ScrollMode.UNKNOWN) {
scrollMode = stored.scrollMode | 0;
}
if (spreadMode === SpreadMode.UNKNOWN) {
spreadMode = stored.spreadMode | 0;
}
}
if (pageMode && sidebarView === SidebarView.UNKNOWN) {
sidebarView = apiPageModeToSidebarView(pageMode);
}
if (pageLayout && scrollMode === ScrollMode.UNKNOWN && spreadMode === SpreadMode.UNKNOWN) {
const modes = apiPageLayoutToViewerModes(pageLayout);
spreadMode = modes.spreadMode;
}
this.setInitialView(hash, {
rotation,
sidebarView,
scrollMode,
spreadMode
});
this.eventBus.dispatch("documentinit", {
source: this
});
if (!this.isViewerEmbedded) {
pdfViewer.focus();
}
await Promise.race([pagesPromise, new Promise(resolve => {
setTimeout(resolve, FORCE_PAGES_LOADED_TIMEOUT);
})]);
if (!initialBookmark && !hash) {
return;
}
if (pdfViewer.hasEqualPageSizes) {
return;
}
this.initialBookmark = initialBookmark;
pdfViewer.currentScaleValue = pdfViewer.currentScaleValue;
this.setInitialView(hash);
}).catch(() => {
this.setInitialView();
}).then(function () {
pdfViewer.update();
});
});
pagesPromise.then(() => {
this._unblockDocumentLoadEvent();
this._initializeAutoPrint(pdfDocument, openActionPromise);
}, reason => {
this._documentError("pdfjs-loading-error", {
message: reason.message
});
});
onePageRendered.then(data => {
this.externalServices.reportTelemetry({
type: "pageInfo",
timestamp: data.timestamp
});
if (this.pdfOutlineViewer) {
pdfDocument.getOutline().then(outline => {
if (pdfDocument !== this.pdfDocument) {
return;
}
this.pdfOutlineViewer.render({
outline,
pdfDocument
});
});
}
if (this.pdfAttachmentViewer) {
pdfDocument.getAttachments().then(attachments => {
if (pdfDocument !== this.pdfDocument) {
return;
}
this.pdfAttachmentViewer.render({
attachments
});
});
}
if (this.pdfLayerViewer) {
pdfViewer.optionalContentConfigPromise.then(optionalContentConfig => {
if (pdfDocument !== this.pdfDocument) {
return;
}
this.pdfLayerViewer.render({
optionalContentConfig,
pdfDocument
});
});
}
});
this._initializePageLabels(pdfDocument);
this._initializeMetadata(pdfDocument);
},
async _scriptingDocProperties(pdfDocument) {
if (!this.documentInfo) {
await new Promise(resolve => {
this.eventBus._on("metadataloaded", resolve, {
once: true
});
});
if (pdfDocument !== this.pdfDocument) {
return null;
}
}
if (!this._contentLength) {
await new Promise(resolve => {
this.eventBus._on("documentloaded", resolve, {
once: true
});
});
if (pdfDocument !== this.pdfDocument) {
return null;
}
}
return {
...this.documentInfo,
baseURL: this.baseUrl,
filesize: this._contentLength,
filename: this._docFilename,
metadata: this.metadata?.getRaw(),
authors: this.metadata?.get("dc:creator"),
numPages: this.pagesCount,
URL: this.url
};
},
async _initializeAutoPrint(pdfDocument, openActionPromise) {
const [openAction, jsActions] = await Promise.all([openActionPromise, this.pdfViewer.enableScripting ? null : pdfDocument.getJSActions()]);
if (pdfDocument !== this.pdfDocument) {
return;
}
let triggerAutoPrint = openAction?.action === "Print";
if (jsActions) {
console.warn("Warning: JavaScript support is not enabled");
for (const name in jsActions) {
if (triggerAutoPrint) {
break;
}
switch (name) {
case "WillClose":
case "WillSave":
case "DidSave":
case "WillPrint":
case "DidPrint":
continue;
}
triggerAutoPrint = jsActions[name].some(js => AutoPrintRegExp.test(js));
}
}
if (triggerAutoPrint) {
this.triggerPrinting();
}
},
async _initializeMetadata(pdfDocument) {
const {
info,
metadata,
contentDispositionFilename,
contentLength
} = await pdfDocument.getMetadata();
if (pdfDocument !== this.pdfDocument) {
return;
}
this.documentInfo = info;
this.metadata = metadata;
this._contentDispositionFilename ??= contentDispositionFilename;
this._contentLength ??= contentLength;
console.log(`PDF ${pdfDocument.fingerprints[0]} [${info.PDFFormatVersion} ` + `${(info.Producer || "-").trim()} / ${(info.Creator || "-").trim()}] ` + `(PDF.js: ${version || "?"} [${build || "?"}])`);
let pdfTitle = info.Title;
const metadataTitle = metadata?.get("dc:title");
if (metadataTitle) {
if (metadataTitle !== "Untitled" && !/[\uFFF0-\uFFFF]/g.test(metadataTitle)) {
pdfTitle = metadataTitle;
}
}
if (pdfTitle) {
this.setTitle(`${pdfTitle} - ${this._contentDispositionFilename || this._title}`);
} else if (this._contentDispositionFilename) {
this.setTitle(this._contentDispositionFilename);
}
if (info.IsXFAPresent && !info.IsAcroFormPresent && !pdfDocument.isPureXfa) {
if (pdfDocument.loadingParams.enableXfa) {
console.warn("Warning: XFA Foreground documents are not supported");
} else {
console.warn("Warning: XFA support is not enabled");
}
} else if ((info.IsAcroFormPresent || info.IsXFAPresent) && !this.pdfViewer.renderForms) {
console.warn("Warning: Interactive form support is not enabled");
}
if (info.IsSignaturesPresent) {
console.warn("Warning: Digital signatures validation is not supported");
}
this.eventBus.dispatch("metadataloaded", {
source: this
});
},
async _initializePageLabels(pdfDocument) {
const labels = await pdfDocument.getPageLabels();
if (pdfDocument !== this.pdfDocument) {
return;
}
if (!labels || AppOptions.get("disablePageLabels")) {
return;
}
const numLabels = labels.length;
let standardLabels = 0,
emptyLabels = 0;
for (let i = 0; i < numLabels; i++) {
const label = labels[i];
if (label === (i + 1).toString()) {
standardLabels++;
} else if (label === "") {
emptyLabels++;
} else {
break;
}
}
if (standardLabels >= numLabels || emptyLabels >= numLabels) {
return;
}
const {
pdfViewer,
pdfThumbnailViewer,
toolbar
} = this;
pdfViewer.setPageLabels(labels);
pdfThumbnailViewer?.setPageLabels(labels);
toolbar?.setPagesCount(numLabels, true);
toolbar?.setPageNumber(pdfViewer.currentPageNumber, pdfViewer.currentPageLabel);
},
_initializePdfHistory({
fingerprint,
viewOnLoad,
initialDest = null
}) {
if (!this.pdfHistory) {
return;
}
this.pdfHistory.initialize({
fingerprint,
resetHistory: viewOnLoad === ViewOnLoad.INITIAL,
updateUrl: AppOptions.get("historyUpdateUrl")
});
if (this.pdfHistory.initialBookmark) {
this.initialBookmark = this.pdfHistory.initialBookmark;
this.initialRotation = this.pdfHistory.initialRotation;
}
if (initialDest && !this.initialBookmark && viewOnLoad === ViewOnLoad.UNKNOWN) {
this.initialBookmark = JSON.stringify(initialDest);
this.pdfHistory.push({
explicitDest: initialDest,
pageNumber: null
});
}
},
_initializeAnnotationStorageCallbacks(pdfDocument) {
if (pdfDocument !== this.pdfDocument) {
return;
}
const {
annotationStorage
} = pdfDocument;
annotationStorage.onSetModified = () => {
window.addEventListener("beforeunload", beforeUnload);
this._annotationStorageModified = true;
};
annotationStorage.onResetModified = () => {
window.removeEventListener("beforeunload", beforeUnload);
delete this._annotationStorageModified;
};
annotationStorage.onAnnotationEditor = typeStr => {
this._hasAnnotationEditors = !!typeStr;
this.setTitle();
};
},
setInitialView(storedHash, {
rotation,
sidebarView,
scrollMode,
spreadMode
} = {}) {
const setRotation = angle => {
if (isValidRotation(angle)) {
this.pdfViewer.pagesRotation = angle;
}
};
const setViewerModes = (scroll, spread) => {
if (isValidScrollMode(scroll)) {
this.pdfViewer.scrollMode = scroll;
}
if (isValidSpreadMode(spread)) {
this.pdfViewer.spreadMode = spread;
}
};
this.isInitialViewSet = true;
this.pdfSidebar?.setInitialView(sidebarView);
setViewerModes(scrollMode, spreadMode);
if (this.initialBookmark) {
setRotation(this.initialRotation);
delete this.initialRotation;
this.pdfLinkService.setHash(this.initialBookmark);
this.initialBookmark = null;
} else if (storedHash) {
setRotation(rotation);
this.pdfLinkService.setHash(storedHash);
}
this.toolbar?.setPageNumber(this.pdfViewer.currentPageNumber, this.pdfViewer.currentPageLabel);
this.secondaryToolbar?.setPageNumber(this.pdfViewer.currentPageNumber);
if (!this.pdfViewer.currentScaleValue) {
this.pdfViewer.currentScaleValue = DEFAULT_SCALE_VALUE;
}
},
_cleanup() {
if (!this.pdfDocument) {
return;
}
this.pdfViewer.cleanup();
this.pdfThumbnailViewer?.cleanup();
this.pdfDocument.cleanup(AppOptions.get("fontExtraProperties"));
},
forceRendering() {
this.pdfRenderingQueue.printing = !!this.printService;
this.pdfRenderingQueue.isThumbnailViewEnabled = this.pdfSidebar?.visibleView === SidebarView.THUMBS;
this.pdfRenderingQueue.renderHighestPriority();
},
beforePrint() {
this._printAnnotationStoragePromise = this.pdfScriptingManager.dispatchWillPrint().catch(() => {}).then(() => this.pdfDocument?.annotationStorage.print);
if (this.printService) {
return;
}
if (!this.supportsPrinting) {
this._otherError("pdfjs-printing-not-supported");
return;
}
if (!this.pdfViewer.pageViewsReady) {
this.l10n.get("pdfjs-printing-not-ready").then(msg => {
window.alert(msg);
});
return;
}
this.printService = PDFPrintServiceFactory.createPrintService({
pdfDocument: this.pdfDocument,
pagesOverview: this.pdfViewer.getPagesOverview(),
printContainer: this.appConfig.printContainer,
printResolution: AppOptions.get("printResolution"),
printAnnotationStoragePromise: this._printAnnotationStoragePromise
});
this.forceRendering();
this.setTitle();
this.printService.layout();
if (this._hasAnnotationEditors) {
this.externalServices.reportTelemetry({
type: "editing",
data: {
type: "print",
stats: this.pdfDocument?.annotationStorage.editorStats
}
});
}
},
afterPrint() {
if (this._printAnnotationStoragePromise) {
this._printAnnotationStoragePromise.then(() => {
this.pdfScriptingManager.dispatchDidPrint();
});
this._printAnnotationStoragePromise = null;
}
if (this.printService) {
this.printService.destroy();
this.printService = null;
this.pdfDocument?.annotationStorage.resetModified();
}
this.forceRendering();
this.setTitle();
},
rotatePages(delta) {
this.pdfViewer.pagesRotation += delta;
},
requestPresentationMode() {
this.pdfPresentationMode?.request();
},
triggerPrinting() {
if (this.supportsPrinting) {
window.print();
}
},
bindEvents() {
if (this._eventBusAbortController) {
return;
}
const ac = this._eventBusAbortController = new AbortController();
const opts = {
signal: ac.signal
};
const {
eventBus,
externalServices,
pdfDocumentProperties,
pdfViewer,
preferences
} = this;
eventBus._on("resize", onResize.bind(this), opts);
eventBus._on("hashchange", onHashchange.bind(this), opts);
eventBus._on("beforeprint", this.beforePrint.bind(this), opts);
eventBus._on("afterprint", this.afterPrint.bind(this), opts);
eventBus._on("pagerender", onPageRender.bind(this), opts);
eventBus._on("pagerendered", onPageRendered.bind(this), opts);
eventBus._on("updateviewarea", onUpdateViewarea.bind(this), opts);
eventBus._on("pagechanging", onPageChanging.bind(this), opts);
eventBus._on("scalechanging", onScaleChanging.bind(this), opts);
eventBus._on("rotationchanging", onRotationChanging.bind(this), opts);
eventBus._on("sidebarviewchanged", onSidebarViewChanged.bind(this), opts);
eventBus._on("pagemode", onPageMode.bind(this), opts);
eventBus._on("namedaction", onNamedAction.bind(this), opts);
eventBus._on("presentationmodechanged", evt => pdfViewer.presentationModeState = evt.state, opts);
eventBus._on("presentationmode", this.requestPresentationMode.bind(this), opts);
eventBus._on("switchannotationeditormode", evt => pdfViewer.annotationEditorMode = evt, opts);
eventBus._on("print", this.triggerPrinting.bind(this), opts);
eventBus._on("download", this.downloadOrSave.bind(this), opts);
eventBus._on("firstpage", () => this.page = 1, opts);
eventBus._on("lastpage", () => this.page = this.pagesCount, opts);
eventBus._on("nextpage", () => pdfViewer.nextPage(), opts);
eventBus._on("previouspage", () => pdfViewer.previousPage(), opts);
eventBus._on("zoomin", this.zoomIn.bind(this), opts);
eventBus._on("zoomout", this.zoomOut.bind(this), opts);
eventBus._on("zoomreset", this.zoomReset.bind(this), opts);
eventBus._on("pagenumberchanged", onPageNumberChanged.bind(this), opts);
eventBus._on("scalechanged", evt => pdfViewer.currentScaleValue = evt.value, opts);
eventBus._on("rotatecw", this.rotatePages.bind(this, 90), opts);
eventBus._on("rotateccw", this.rotatePages.bind(this, -90), opts);
eventBus._on("optionalcontentconfig", evt => pdfViewer.optionalContentConfigPromise = evt.promise, opts);
eventBus._on("switchscrollmode", evt => pdfViewer.scrollMode = evt.mode, opts);
eventBus._on("scrollmodechanged", onViewerModesChanged.bind(this, "scrollMode"), opts);
eventBus._on("switchspreadmode", evt => pdfViewer.spreadMode = evt.mode, opts);
eventBus._on("spreadmodechanged", onViewerModesChanged.bind(this, "spreadMode"), opts);
eventBus._on("imagealttextsettings", onImageAltTextSettings.bind(this), opts);
eventBus._on("documentproperties", () => pdfDocumentProperties?.open(), opts);
eventBus._on("findfromurlhash", onFindFromUrlHash.bind(this), opts);
eventBus._on("updatefindmatchescount", onUpdateFindMatchesCount.bind(this), opts);
eventBus._on("updatefindcontrolstate", onUpdateFindControlState.bind(this), opts);
eventBus._on("fileinputchange", onFileInputChange.bind(this), opts);
eventBus._on("openfile", onOpenFile.bind(this), opts);
},
bindWindowEvents() {
if (this._windowAbortController) {
return;
}
this._windowAbortController = new AbortController();
const {
eventBus,
appConfig: {
mainContainer
},
pdfViewer,
_windowAbortController: {
signal
}
} = this;
this._touchManager = new TouchManager({
container: window,
isPinchingDisabled: () => pdfViewer.isInPresentationMode,
isPinchingStopped: () => this.overlayManager?.active,
onPinching: this.touchPinchCallback.bind(this),
onPinchEnd: this.touchPinchEndCallback.bind(this),
signal
});
function addWindowResolutionChange(evt = null) {
if (evt) {
pdfViewer.refresh();
}
const mediaQueryList = window.matchMedia(`(resolution: ${OutputScale.pixelRatio}dppx)`);
mediaQueryList.addEventListener("change", addWindowResolutionChange, {
once: true,
signal
});
}
addWindowResolutionChange();
window.addEventListener("wheel", onWheel.bind(this), {
passive: false,
signal
});
window.addEventListener("click", onClick.bind(this), {
signal
});
window.addEventListener("keydown", onKeyDown.bind(this), {
signal
});
window.addEventListener("keyup", onKeyUp.bind(this), {
signal
});
window.addEventListener("resize", () => eventBus.dispatch("resize", {
source: window
}), {
signal
});
window.addEventListener("hashchange", () => {
eventBus.dispatch("hashchange", {
source: window,
hash: document.location.hash.substring(1)
});
}, {
signal
});
window.addEventListener("beforeprint", () => eventBus.dispatch("beforeprint", {
source: window
}), {
signal
});
window.addEventListener("afterprint", () => eventBus.dispatch("afterprint", {
source: window
}), {
signal
});
window.addEventListener("updatefromsandbox", evt => {
eventBus.dispatch("updatefromsandbox", {
source: window,
detail: evt.detail
});
}, {
signal
});
if (!("onscrollend" in document.documentElement)) {
return;
}
({
scrollTop: this._lastScrollTop,
scrollLeft: this._lastScrollLeft
} = mainContainer);
const scrollend = () => {
({
scrollTop: this._lastScrollTop,
scrollLeft: this._lastScrollLeft
} = mainContainer);
this._isScrolling = false;
mainContainer.addEventListener("scroll", scroll, {
passive: true,
signal
});
mainContainer.removeEventListener("scrollend", scrollend);
mainContainer.removeEventListener("blur", scrollend);
};
const scroll = () => {
if (this._isCtrlKeyDown) {
return;
}
if (this._lastScrollTop === mainContainer.scrollTop && this._lastScrollLeft === mainContainer.scrollLeft) {
return;
}
mainContainer.removeEventListener("scroll", scroll);
this._isScrolling = true;
mainContainer.addEventListener("scrollend", scrollend, {
signal
});
mainContainer.addEventListener("blur", scrollend, {
signal
});
};
mainContainer.addEventListener("scroll", scroll, {
passive: true,
signal
});
},
unbindEvents() {
this._eventBusAbortController?.abort();
this._eventBusAbortController = null;
},
unbindWindowEvents() {
this._windowAbortController?.abort();
this._windowAbortController = null;
this._touchManager = null;
},
async testingClose() {
this.unbindEvents();
this.unbindWindowEvents();
this._globalAbortController?.abort();
this._globalAbortController = null;
this.findBar?.close();
await Promise.all([this.l10n?.destroy(), this.close()]);
},
_accumulateTicks(ticks, prop) {
if (this[prop] > 0 && ticks < 0 || this[prop] < 0 && ticks > 0) {
this[prop] = 0;
}
this[prop] += ticks;
const wholeTicks = Math.trunc(this[prop]);
this[prop] -= wholeTicks;
return wholeTicks;
},
_accumulateFactor(previousScale, factor, prop) {
if (factor === 1) {
return 1;
}
if (this[prop] > 1 && factor < 1 || this[prop] < 1 && factor > 1) {
this[prop] = 1;
}
const newFactor = Math.floor(previousScale * factor * this[prop] * 100) / (100 * previousScale);
this[prop] = factor / newFactor;
return newFactor;
},
_unblockDocumentLoadEvent() {
document.blockUnblockOnload?.(false);
this._unblockDocumentLoadEvent = () => {};
},
get scriptingReady() {
return this.pdfScriptingManager.ready;
}
};
initCom(PDFViewerApplication);
{
PDFPrintServiceFactory.initGlobals(PDFViewerApplication);
}
{
const HOSTED_VIEWER_ORIGINS = new Set(["null", "http://mozilla.github.io", "https://mozilla.github.io"]);
var validateFileURL = function (file) {
if (!file) {
return;
}
const viewerOrigin = URL.parse(window.location)?.origin || "null";
if (HOSTED_VIEWER_ORIGINS.has(viewerOrigin)) {
return;
}
const fileOrigin = URL.parse(file, window.location)?.origin;
if (fileOrigin === viewerOrigin) {
return;
}
const ex = new Error("file origin does not match viewer's");
PDFViewerApplication._documentError("pdfjs-loading-error", {
message: ex.message
});
throw ex;
};
var onFileInputChange = function (evt) {
if (this.pdfViewer?.isInPresentationMode) {
return;
}
const file = evt.fileInput.files[0];
this.open({
url: URL.createObjectURL(file),
originalUrl: file.name
});
};
var onOpenFile = function (evt) {
this._openFileInput?.click();
};
}
function onPageRender({
pageNumber
}) {
if (pageNumber === this.page) {
this.toolbar?.updateLoadingIndicatorState(true);
}
}
function onPageRendered({
pageNumber,
isDetailView,
error
}) {
if (pageNumber === this.page) {
this.toolbar?.updateLoadingIndicatorState(false);
}
if (!isDetailView && this.pdfSidebar?.visibleView === SidebarView.THUMBS) {
const pageView = this.pdfViewer.getPageView(pageNumber - 1);
const thumbnailView = this.pdfThumbnailViewer?.getThumbnail(pageNumber - 1);
if (pageView) {
thumbnailView?.setImage(pageView);
}
}
if (error) {
this._otherError("pdfjs-rendering-error", error);
}
}
function onPageMode({
mode
}) {
let view;
switch (mode) {
case "thumbs":
view = SidebarView.THUMBS;
break;
case "bookmarks":
case "outline":
view = SidebarView.OUTLINE;
break;
case "attachments":
view = SidebarView.ATTACHMENTS;
break;
case "layers":
view = SidebarView.LAYERS;
break;
case "none":
view = SidebarView.NONE;
break;
default:
console.error('Invalid "pagemode" hash parameter: ' + mode);
return;
}
this.pdfSidebar?.switchView(view, true);
}
function onNamedAction(evt) {
switch (evt.action) {
case "GoToPage":
this.appConfig.toolbar?.pageNumber.select();
break;
case "Find":
if (!this.supportsIntegratedFind) {
this.findBar?.toggle();
}
break;
case "Print":
this.triggerPrinting();
break;
case "SaveAs":
this.downloadOrSave();
break;
}
}
function onSidebarViewChanged({
view
}) {
this.pdfRenderingQueue.isThumbnailViewEnabled = view === SidebarView.THUMBS;
if (this.isInitialViewSet) {
this.store?.set("sidebarView", view).catch(() => {});
}
}
function onUpdateViewarea({
location
}) {
if (this.isInitialViewSet) {
this.store?.setMultiple({
page: location.pageNumber,
zoom: location.scale,
scrollLeft: location.left,
scrollTop: location.top,
rotation: location.rotation
}).catch(() => {});
}
if (this.appConfig.secondaryToolbar) {
this.appConfig.secondaryToolbar.viewBookmarkButton.href = this.pdfLinkService.getAnchorUrl(location.pdfOpenParams);
}
}
function onViewerModesChanged(name, evt) {
if (this.isInitialViewSet && !this.pdfViewer.isInPresentationMode) {
this.store?.set(name, evt.mode).catch(() => {});
}
}
function onResize() {
const {
pdfDocument,
pdfViewer,
pdfRenderingQueue
} = this;
if (pdfRenderingQueue.printing && window.matchMedia("print").matches) {
return;
}
if (!pdfDocument) {
return;
}
const currentScaleValue = pdfViewer.currentScaleValue;
if (currentScaleValue === "auto" || currentScaleValue === "page-fit" || currentScaleValue === "page-width") {
pdfViewer.currentScaleValue = currentScaleValue;
}
pdfViewer.update();
}
function onHashchange(evt) {
const hash = evt.hash;
if (!hash) {
return;
}
if (!this.isInitialViewSet) {
this.initialBookmark = hash;
} else if (!this.pdfHistory?.popStateInProgress) {
this.pdfLinkService.setHash(hash);
}
}
function onPageNumberChanged(evt) {
const {
pdfViewer
} = this;
if (evt.value !== "") {
this.pdfLinkService.goToPage(evt.value);
}
if (evt.value !== pdfViewer.currentPageNumber.toString() && evt.value !== pdfViewer.currentPageLabel) {
this.toolbar?.setPageNumber(pdfViewer.currentPageNumber, pdfViewer.currentPageLabel);
}
}
function onImageAltTextSettings() {
this.imageAltTextSettings?.open({
enableGuessAltText: AppOptions.get("enableGuessAltText"),
enableNewAltTextWhenAddingImage: AppOptions.get("enableNewAltTextWhenAddingImage")
});
}
function onFindFromUrlHash(evt) {
this.eventBus.dispatch("find", {
source: evt.source,
type: "",
query: evt.query,
caseSensitive: false,
entireWord: false,
highlightAll: true,
findPrevious: false,
matchDiacritics: true
});
}
function onUpdateFindMatchesCount({
matchesCount
}) {
if (this.supportsIntegratedFind) {
this.externalServices.updateFindMatchesCount(matchesCount);
} else {
this.findBar?.updateResultsCount(matchesCount);
}
}
function onUpdateFindControlState({
state,
previous,
entireWord,
matchesCount,
rawQuery
}) {
if (this.supportsIntegratedFind) {
this.externalServices.updateFindControlState({
result: state,
findPrevious: previous,
entireWord,
matchesCount,
rawQuery
});
} else {
this.findBar?.updateUIState(state, previous, matchesCount);
}
}
function onScaleChanging(evt) {
this.toolbar?.setPageScale(evt.presetValue, evt.scale);
this.pdfViewer.update();
}
function onRotationChanging(evt) {
if (this.pdfThumbnailViewer) {
this.pdfThumbnailViewer.pagesRotation = evt.pagesRotation;
}
this.forceRendering();
this.pdfViewer.currentPageNumber = evt.pageNumber;
}
function onPageChanging({
pageNumber,
pageLabel
}) {
this.toolbar?.setPageNumber(pageNumber, pageLabel);
this.secondaryToolbar?.setPageNumber(pageNumber);
if (this.pdfSidebar?.visibleView === SidebarView.THUMBS) {
this.pdfThumbnailViewer?.scrollThumbnailIntoView(pageNumber);
}
const currentPage = this.pdfViewer.getPageView(pageNumber - 1);
this.toolbar?.updateLoadingIndicatorState(currentPage?.renderingState === RenderingStates.RUNNING);
}
function onWheel(evt) {
const {
pdfViewer,
supportsMouseWheelZoomCtrlKey,
supportsMouseWheelZoomMetaKey,
supportsPinchToZoom
} = this;
if (pdfViewer.isInPresentationMode) {
return;
}
const deltaMode = evt.deltaMode;
let scaleFactor = Math.exp(-evt.deltaY / 100);
const isBuiltInMac = false;
const isPinchToZoom = evt.ctrlKey && !this._isCtrlKeyDown && deltaMode === WheelEvent.DOM_DELTA_PIXEL && evt.deltaX === 0 && (Math.abs(scaleFactor - 1) < 0.05 || isBuiltInMac) && evt.deltaZ === 0;
const origin = [evt.clientX, evt.clientY];
if (isPinchToZoom || evt.ctrlKey && supportsMouseWheelZoomCtrlKey || evt.metaKey && supportsMouseWheelZoomMetaKey) {
evt.preventDefault();
if (this._isScrolling || document.visibilityState === "hidden" || this.overlayManager.active) {
return;
}
if (isPinchToZoom && supportsPinchToZoom) {
scaleFactor = this._accumulateFactor(pdfViewer.currentScale, scaleFactor, "_wheelUnusedFactor");
this.updateZoom(null, scaleFactor, origin);
} else {
const delta = normalizeWheelEventDirection(evt);
let ticks = 0;
if (deltaMode === WheelEvent.DOM_DELTA_LINE || deltaMode === WheelEvent.DOM_DELTA_PAGE) {
ticks = Math.abs(delta) >= 1 ? Math.sign(delta) : this._accumulateTicks(delta, "_wheelUnusedTicks");
} else {
const PIXELS_PER_LINE_SCALE = 30;
ticks = this._accumulateTicks(delta / PIXELS_PER_LINE_SCALE, "_wheelUnusedTicks");
}
this.updateZoom(ticks, null, origin);
}
}
}
function closeSecondaryToolbar({
target
}) {
if (!this.secondaryToolbar?.isOpen) {
return;
}
const {
toolbar,
secondaryToolbar
} = this.appConfig;
if (this.pdfViewer.containsElement(target) || toolbar?.container.contains(target) && !secondaryToolbar?.toolbar.contains(target) && !secondaryToolbar?.toggleButton.contains(target)) {
this.secondaryToolbar.close();
}
}
function closeEditorUndoBar(evt) {
if (!this.editorUndoBar?.isOpen) {
return;
}
if (this.appConfig.secondaryToolbar?.toolbar.contains(evt.target)) {
this.editorUndoBar.hide();
}
}
function onClick(evt) {
closeSecondaryToolbar.call(this, evt);
closeEditorUndoBar.call(this, evt);
}
function onKeyUp(evt) {
if (evt.key === "Control") {
this._isCtrlKeyDown = false;
}
}
function onKeyDown(evt) {
this._isCtrlKeyDown = evt.key === "Control";
if (this.editorUndoBar?.isOpen && evt.keyCode !== 9 && evt.keyCode !== 16 && !((evt.keyCode === 13 || evt.keyCode === 32) && getActiveOrFocusedElement() === this.appConfig.editorUndoBar.undoButton)) {
this.editorUndoBar.hide();
}
if (this.overlayManager.active) {
return;
}
const {
eventBus,
pdfViewer
} = this;
const isViewerInPresentationMode = pdfViewer.isInPresentationMode;
let handled = false,
ensureViewerFocused = false;
const cmd = (evt.ctrlKey ? 1 : 0) | (evt.altKey ? 2 : 0) | (evt.shiftKey ? 4 : 0) | (evt.metaKey ? 8 : 0);
if (cmd === 1 || cmd === 8 || cmd === 5 || cmd === 12) {
switch (evt.keyCode) {
case 70:
if (!this.supportsIntegratedFind && !evt.shiftKey) {
this.findBar?.open();
handled = true;
}
break;
case 71:
if (!this.supportsIntegratedFind) {
const {
state
} = this.findController;
if (state) {
const newState = {
source: window,
type: "again",
findPrevious: cmd === 5 || cmd === 12
};
eventBus.dispatch("find", {
...state,
...newState
});
}
handled = true;
}
break;
case 61:
case 107:
case 187:
case 171:
this.zoomIn();
handled = true;
break;
case 173:
case 109:
case 189:
this.zoomOut();
handled = true;
break;
case 48:
case 96:
if (!isViewerInPresentationMode) {
setTimeout(() => {
this.zoomReset();
});
handled = false;
}
break;
case 38:
if (isViewerInPresentationMode || this.page > 1) {
this.page = 1;
handled = true;
ensureViewerFocused = true;
}
break;
case 40:
if (isViewerInPresentationMode || this.page < this.pagesCount) {
this.page = this.pagesCount;
handled = true;
ensureViewerFocused = true;
}
break;
}
}
if (cmd === 1 || cmd === 8) {
switch (evt.keyCode) {
case 83:
eventBus.dispatch("download", {
source: window
});
handled = true;
break;
case 79:
{
eventBus.dispatch("openfile", {
source: window
});
handled = true;
}
break;
}
}
if (cmd === 3 || cmd === 10) {
switch (evt.keyCode) {
case 80:
this.requestPresentationMode();
handled = true;
this.externalServices.reportTelemetry({
type: "buttons",
data: {
id: "presentationModeKeyboard"
}
});
break;
case 71:
if (this.appConfig.toolbar) {
this.appConfig.toolbar.pageNumber.select();
handled = true;
}
break;
}
}
if (handled) {
if (ensureViewerFocused && !isViewerInPresentationMode) {
pdfViewer.focus();
}
evt.preventDefault();
return;
}
const curElement = getActiveOrFocusedElement();
const curElementTagName = curElement?.tagName.toUpperCase();
if (curElementTagName === "INPUT" || curElementTagName === "TEXTAREA" || curElementTagName === "SELECT" || curElementTagName === "BUTTON" && (evt.keyCode === 13 || evt.keyCode === 32) || curElement?.isContentEditable) {
if (evt.keyCode !== 27) {
return;
}
}
if (cmd === 0) {
let turnPage = 0,
turnOnlyIfPageFit = false;
switch (evt.keyCode) {
case 38:
if (this.supportsCaretBrowsingMode) {
this.moveCaret(true, false);
handled = true;
break;
}
case 33:
if (pdfViewer.isVerticalScrollbarEnabled) {
turnOnlyIfPageFit = true;
}
turnPage = -1;
break;
case 8:
if (!isViewerInPresentationMode) {
turnOnlyIfPageFit = true;
}
turnPage = -1;
break;
case 37:
if (this.supportsCaretBrowsingMode) {
return;
}
if (pdfViewer.isHorizontalScrollbarEnabled) {
turnOnlyIfPageFit = true;
}
case 75:
case 80:
turnPage = -1;
break;
case 27:
if (this.secondaryToolbar?.isOpen) {
this.secondaryToolbar.close();
handled = true;
}
if (!this.supportsIntegratedFind && this.findBar?.opened) {
this.findBar.close();
handled = true;
}
break;
case 40:
if (this.supportsCaretBrowsingMode) {
this.moveCaret(false, false);
handled = true;
break;
}
case 34:
if (pdfViewer.isVerticalScrollbarEnabled) {
turnOnlyIfPageFit = true;
}
turnPage = 1;
break;
case 13:
case 32:
if (!isViewerInPresentationMode) {
turnOnlyIfPageFit = true;
}
turnPage = 1;
break;
case 39:
if (this.supportsCaretBrowsingMode) {
return;
}
if (pdfViewer.isHorizontalScrollbarEnabled) {
turnOnlyIfPageFit = true;
}
case 74:
case 78:
turnPage = 1;
break;
case 36:
if (isViewerInPresentationMode || this.page > 1) {
this.page = 1;
handled = true;
ensureViewerFocused = true;
}
break;
case 35:
if (isViewerInPresentationMode || this.page < this.pagesCount) {
this.page = this.pagesCount;
handled = true;
ensureViewerFocused = true;
}
break;
case 83:
this.pdfCursorTools?.switchTool(CursorTool.SELECT);
break;
case 72:
this.pdfCursorTools?.switchTool(CursorTool.HAND);
break;
case 82:
this.rotatePages(90);
break;
case 115:
this.pdfSidebar?.toggle();
break;
}
if (turnPage !== 0 && (!turnOnlyIfPageFit || pdfViewer.currentScaleValue === "page-fit")) {
if (turnPage > 0) {
pdfViewer.nextPage();
} else {
pdfViewer.previousPage();
}
handled = true;
}
}
if (cmd === 4) {
switch (evt.keyCode) {
case 13:
case 32:
if (!isViewerInPresentationMode && pdfViewer.currentScaleValue !== "page-fit") {
break;
}
pdfViewer.previousPage();
handled = true;
break;
case 38:
this.moveCaret(true, true);
handled = true;
break;
case 40:
this.moveCaret(false, true);
handled = true;
break;
case 82:
this.rotatePages(-90);
break;
}
}
if (!handled && !isViewerInPresentationMode) {
if (evt.keyCode >= 33 && evt.keyCode <= 40 || evt.keyCode === 32 && curElementTagName !== "BUTTON") {
ensureViewerFocused = true;
}
}
if (ensureViewerFocused && !pdfViewer.containsElement(curElement)) {
pdfViewer.focus();
}
if (handled) {
evt.preventDefault();
}
}
function beforeUnload(evt) {
evt.preventDefault();
evt.returnValue = "";
return false;
}
;// ./web/viewer.js
const pdfjsVersion = "5.1.91";
const pdfjsBuild = "45cbe8bb0";
const AppConstants = {
LinkTarget: LinkTarget,
RenderingStates: RenderingStates,
ScrollMode: ScrollMode,
SpreadMode: SpreadMode
};
window.PDFViewerApplication = PDFViewerApplication;
window.PDFViewerApplicationConstants = AppConstants;
window.PDFViewerApplicationOptions = AppOptions;
function getViewerConfiguration() {
return {
appContainer: document.body,
principalContainer: document.getElementById("mainContainer"),
mainContainer: document.getElementById("viewerContainer"),
viewerContainer: document.getElementById("viewer"),
toolbar: {
container: document.getElementById("toolbarContainer"),
numPages: document.getElementById("numPages"),
pageNumber: document.getElementById("pageNumber"),
scaleSelect: document.getElementById("scaleSelect"),
customScaleOption: document.getElementById("customScaleOption"),
previous: document.getElementById("previous"),
next: document.getElementById("next"),
zoomIn: document.getElementById("zoomInButton"),
zoomOut: document.getElementById("zoomOutButton"),
print: document.getElementById("printButton"),
editorFreeTextButton: document.getElementById("editorFreeTextButton"),
editorFreeTextParamsToolbar: document.getElementById("editorFreeTextParamsToolbar"),
editorHighlightButton: document.getElementById("editorHighlightButton"),
editorHighlightParamsToolbar: document.getElementById("editorHighlightParamsToolbar"),
editorHighlightColorPicker: document.getElementById("editorHighlightColorPicker"),
editorInkButton: document.getElementById("editorInkButton"),
editorInkParamsToolbar: document.getElementById("editorInkParamsToolbar"),
editorStampButton: document.getElementById("editorStampButton"),
editorStampParamsToolbar: document.getElementById("editorStampParamsToolbar"),
editorSignatureButton: document.getElementById("editorSignatureButton"),
editorSignatureParamsToolbar: document.getElementById("editorSignatureParamsToolbar"),
download: document.getElementById("downloadButton")
},
secondaryToolbar: {
toolbar: document.getElementById("secondaryToolbar"),
toggleButton: document.getElementById("secondaryToolbarToggleButton"),
presentationModeButton: document.getElementById("presentationMode"),
openFileButton: document.getElementById("secondaryOpenFile"),
printButton: document.getElementById("secondaryPrint"),
downloadButton: document.getElementById("secondaryDownload"),
viewBookmarkButton: document.getElementById("viewBookmark"),
firstPageButton: document.getElementById("firstPage"),
lastPageButton: document.getElementById("lastPage"),
pageRotateCwButton: document.getElementById("pageRotateCw"),
pageRotateCcwButton: document.getElementById("pageRotateCcw"),
cursorSelectToolButton: document.getElementById("cursorSelectTool"),
cursorHandToolButton: document.getElementById("cursorHandTool"),
scrollPageButton: document.getElementById("scrollPage"),
scrollVerticalButton: document.getElementById("scrollVertical"),
scrollHorizontalButton: document.getElementById("scrollHorizontal"),
scrollWrappedButton: document.getElementById("scrollWrapped"),
spreadNoneButton: document.getElementById("spreadNone"),
spreadOddButton: document.getElementById("spreadOdd"),
spreadEvenButton: document.getElementById("spreadEven"),
imageAltTextSettingsButton: document.getElementById("imageAltTextSettings"),
imageAltTextSettingsSeparator: document.getElementById("imageAltTextSettingsSeparator"),
documentPropertiesButton: document.getElementById("documentProperties")
},
sidebar: {
outerContainer: document.getElementById("outerContainer"),
sidebarContainer: document.getElementById("sidebarContainer"),
toggleButton: document.getElementById("sidebarToggleButton"),
resizer: document.getElementById("sidebarResizer"),
thumbnailButton: document.getElementById("viewThumbnail"),
outlineButton: document.getElementById("viewOutline"),
attachmentsButton: document.getElementById("viewAttachments"),
layersButton: document.getElementById("viewLayers"),
thumbnailView: document.getElementById("thumbnailView"),
outlineView: document.getElementById("outlineView"),
attachmentsView: document.getElementById("attachmentsView"),
layersView: document.getElementById("layersView"),
currentOutlineItemButton: document.getElementById("currentOutlineItem")
},
findBar: {
bar: document.getElementById("findbar"),
toggleButton: document.getElementById("viewFindButton"),
findField: document.getElementById("findInput"),
highlightAllCheckbox: document.getElementById("findHighlightAll"),
caseSensitiveCheckbox: document.getElementById("findMatchCase"),
matchDiacriticsCheckbox: document.getElementById("findMatchDiacritics"),
entireWordCheckbox: document.getElementById("findEntireWord"),
findMsg: document.getElementById("findMsg"),
findResultsCount: document.getElementById("findResultsCount"),
findPreviousButton: document.getElementById("findPreviousButton"),
findNextButton: document.getElementById("findNextButton")
},
passwordOverlay: {
dialog: document.getElementById("passwordDialog"),
label: document.getElementById("passwordText"),
input: document.getElementById("password"),
submitButton: document.getElementById("passwordSubmit"),
cancelButton: document.getElementById("passwordCancel")
},
documentProperties: {
dialog: document.getElementById("documentPropertiesDialog"),
closeButton: document.getElementById("documentPropertiesClose"),
fields: {
fileName: document.getElementById("fileNameField"),
fileSize: document.getElementById("fileSizeField"),
title: document.getElementById("titleField"),
author: document.getElementById("authorField"),
subject: document.getElementById("subjectField"),
keywords: document.getElementById("keywordsField"),
creationDate: document.getElementById("creationDateField"),
modificationDate: document.getElementById("modificationDateField"),
creator: document.getElementById("creatorField"),
producer: document.getElementById("producerField"),
version: document.getElementById("versionField"),
pageCount: document.getElementById("pageCountField"),
pageSize: document.getElementById("pageSizeField"),
linearized: document.getElementById("linearizedField")
}
},
altTextDialog: {
dialog: document.getElementById("altTextDialog"),
optionDescription: document.getElementById("descriptionButton"),
optionDecorative: document.getElementById("decorativeButton"),
textarea: document.getElementById("descriptionTextarea"),
cancelButton: document.getElementById("altTextCancel"),
saveButton: document.getElementById("altTextSave")
},
newAltTextDialog: {
dialog: document.getElementById("newAltTextDialog"),
title: document.getElementById("newAltTextTitle"),
descriptionContainer: document.getElementById("newAltTextDescriptionContainer"),
textarea: document.getElementById("newAltTextDescriptionTextarea"),
disclaimer: document.getElementById("newAltTextDisclaimer"),
learnMore: document.getElementById("newAltTextLearnMore"),
imagePreview: document.getElementById("newAltTextImagePreview"),
createAutomatically: document.getElementById("newAltTextCreateAutomatically"),
createAutomaticallyButton: document.getElementById("newAltTextCreateAutomaticallyButton"),
downloadModel: document.getElementById("newAltTextDownloadModel"),
downloadModelDescription: document.getElementById("newAltTextDownloadModelDescription"),
error: document.getElementById("newAltTextError"),
errorCloseButton: document.getElementById("newAltTextCloseButton"),
cancelButton: document.getElementById("newAltTextCancel"),
notNowButton: document.getElementById("newAltTextNotNow"),
saveButton: document.getElementById("newAltTextSave")
},
altTextSettingsDialog: {
dialog: document.getElementById("altTextSettingsDialog"),
createModelButton: document.getElementById("createModelButton"),
aiModelSettings: document.getElementById("aiModelSettings"),
learnMore: document.getElementById("altTextSettingsLearnMore"),
deleteModelButton: document.getElementById("deleteModelButton"),
downloadModelButton: document.getElementById("downloadModelButton"),
showAltTextDialogButton: document.getElementById("showAltTextDialogButton"),
altTextSettingsCloseButton: document.getElementById("altTextSettingsCloseButton"),
closeButton: document.getElementById("altTextSettingsCloseButton")
},
addSignatureDialog: {
dialog: document.getElementById("addSignatureDialog"),
panels: document.getElementById("addSignatureActionContainer"),
typeButton: document.getElementById("addSignatureTypeButton"),
typeInput: document.getElementById("addSignatureTypeInput"),
drawButton: document.getElementById("addSignatureDrawButton"),
drawSVG: document.getElementById("addSignatureDraw"),
drawPlaceholder: document.getElementById("addSignatureDrawPlaceholder"),
drawThickness: document.getElementById("addSignatureDrawThickness"),
imageButton: document.getElementById("addSignatureImageButton"),
imageSVG: document.getElementById("addSignatureImage"),
imagePlaceholder: document.getElementById("addSignatureImagePlaceholder"),
imagePicker: document.getElementById("addSignatureFilePicker"),
imagePickerLink: document.getElementById("addSignatureImageBrowse"),
description: document.getElementById("addSignatureDescription"),
clearButton: document.getElementById("clearSignatureButton"),
saveContainer: document.getElementById("addSignatureSaveContainer"),
saveCheckbox: document.getElementById("addSignatureSaveCheckbox"),
errorBar: document.getElementById("addSignatureError"),
errorCloseButton: document.getElementById("addSignatureErrorCloseButton"),
cancelButton: document.getElementById("addSignatureCancelButton"),
addButton: document.getElementById("addSignatureAddButton")
},
editSignatureDialog: {
dialog: document.getElementById("editSignatureDescriptionDialog"),
description: document.getElementById("editSignatureDescription"),
editSignatureView: document.getElementById("editSignatureView"),
cancelButton: document.getElementById("editSignatureCancelButton"),
updateButton: document.getElementById("editSignatureUpdateButton")
},
annotationEditorParams: {
editorFreeTextFontSize: document.getElementById("editorFreeTextFontSize"),
editorFreeTextColor: document.getElementById("editorFreeTextColor"),
editorInkColor: document.getElementById("editorInkColor"),
editorInkThickness: document.getElementById("editorInkThickness"),
editorInkOpacity: document.getElementById("editorInkOpacity"),
editorStampAddImage: document.getElementById("editorStampAddImage"),
editorSignatureAddSignature: document.getElementById("editorSignatureAddSignature"),
editorFreeHighlightThickness: document.getElementById("editorFreeHighlightThickness"),
editorHighlightShowAll: document.getElementById("editorHighlightShowAll")
},
printContainer: document.getElementById("printContainer"),
editorUndoBar: {
container: document.getElementById("editorUndoBar"),
message: document.getElementById("editorUndoBarMessage"),
undoButton: document.getElementById("editorUndoBarUndoButton"),
closeButton: document.getElementById("editorUndoBarCloseButton")
}
};
}
function webViewerLoad() {
const config = getViewerConfiguration();
const event = new CustomEvent("webviewerloaded", {
bubbles: true,
cancelable: true,
detail: {
source: window
}
});
try {
parent.document.dispatchEvent(event);
} catch (ex) {
console.error("webviewerloaded:", ex);
document.dispatchEvent(event);
}
PDFViewerApplication.run(config);
}
document.blockUnblockOnload?.(true);
if (document.readyState === "interactive" || document.readyState === "complete") {
webViewerLoad();
} else {
document.addEventListener("DOMContentLoaded", webViewerLoad, true);
}
var __webpack_exports__PDFViewerApplication = __webpack_exports__.PDFViewerApplication;
var __webpack_exports__PDFViewerApplicationConstants = __webpack_exports__.PDFViewerApplicationConstants;
var __webpack_exports__PDFViewerApplicationOptions = __webpack_exports__.PDFViewerApplicationOptions;
export { __webpack_exports__PDFViewerApplication as PDFViewerApplication, __webpack_exports__PDFViewerApplicationConstants as PDFViewerApplicationConstants, __webpack_exports__PDFViewerApplicationOptions as PDFViewerApplicationOptions };
//# sourceMappingURL=viewer.mjs.map
================================================
FILE: cookbook/static/pdfjs/web/wasm/LICENSE_OPENJPEG
================================================
/*
* The copyright in this software is being made available under the 2-clauses
* BSD License, included below. This software may be subject to other third
* party and contributor rights, including patent rights, and no such rights
* are granted under this license.
*
* Copyright (c) 2002-2014, Universite catholique de Louvain (UCL), Belgium
* Copyright (c) 2002-2014, Professor Benoit Macq
* Copyright (c) 2003-2014, Antonin Descampe
* Copyright (c) 2003-2009, Francois-Olivier Devaux
* Copyright (c) 2005, Herve Drolon, FreeImage Team
* Copyright (c) 2002-2003, Yannick Verschueren
* Copyright (c) 2001-2003, David Janssens
* Copyright (c) 2011-2012, Centre National d'Etudes Spatiales (CNES), France
* Copyright (c) 2012, CS Systemes d'Information, France
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS `AS IS'
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
================================================
FILE: cookbook/static/pdfjs/web/wasm/LICENSE_PDFJS_OPENJPEG
================================================
Copyright (c) 2024, Mozilla Foundation
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: cookbook/static/pdfjs/web/wasm/LICENSE_PDFJS_QCMS
================================================
Copyright (c) 2025, Mozilla Foundation
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: cookbook/static/pdfjs/web/wasm/LICENSE_QCMS
================================================
qcms
Copyright (C) 2009-2024 Mozilla Corporation
Copyright (C) 1998-2007 Marti Maria
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: cookbook/static/pdfjs/web/wasm/openjpeg_nowasm_fallback.js
================================================
/* THIS FILE IS GENERATED - DO NOT EDIT */
var OpenJPEG = (() => {
var _scriptName = import.meta.url;
return (
function(moduleArg = {}) {
var moduleRtn;
var Module=moduleArg;var readyPromiseResolve,readyPromiseReject;var readyPromise=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject});var ENVIRONMENT_IS_WEB=true;var ENVIRONMENT_IS_WORKER=false;var moduleOverrides=Object.assign({},Module);var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptName){scriptDirectory=_scriptName}if(scriptDirectory.startsWith("blob:")){scriptDirectory=""}else{scriptDirectory=scriptDirectory.slice(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}{readAsync=async url=>{var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.error.bind(console);Object.assign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];var wasmBinary=Module["wasmBinary"];var WebAssembly={Memory:function(opts){this.buffer=new ArrayBuffer(opts["initial"]*65536)},Module:function(binary){},Instance:function(module,info){this.exports=(
// EMSCRIPTEN_START_ASM
function instantiate(Ea){function c(d){d.set=function(a,b){this[a]=b};d.get=function(a){return this[a]};return d}var e;var f=new Uint8Array(123);for(var a=25;a>=0;--a){f[48+a]=52+a;f[65+a]=a;f[97+a]=26+a}f[43]=62;f[47]=63;function l(m,n,o){var g,h,a=0,i=n,j=o.length,k=n+(j*3>>2)-(o[j-2]=="=")-(o[j-1]=="=");for(;a>4;if(i>2;if(i>>0;D=D>>>0;if(C+D>e.length)throw"trap: invalid memory.fill";e.fill(y,C,C+D)}function E(C,F,D){e.copyWithin(C,F,F+D)}function G(){throw new Error("abort")}function Da(q){var H=new ArrayBuffer(16908288);var I=new Int8Array(H);var J=new Int16Array(H);var K=new Int32Array(H);var L=new Uint8Array(H);var M=new Uint16Array(H);var N=new Uint32Array(H);var O=new Float32Array(H);var P=new Float64Array(H);var Q=Math.imul;var R=Math.fround;var S=Math.abs;var T=Math.clz32;var U=Math.min;var V=Math.max;var W=Math.floor;var X=Math.ceil;var Y=Math.trunc;var Z=Math.sqrt;var _=q.a;var $=_.a;var aa=_.b;var ba=_.c;var ca=_.d;var da=_.e;var ea=_.f;var fa=_.g;var ga=_.h;var ha=_.i;var ia=_.j;var ja=_.k;var ka=_.l;var la=_.m;var ma=_.n;var na=_.o;var oa=_.p;var pa=_.q;var qa=_.r;var ra=94304;var sa=0;var ta=0;var ua=0;
// EMSCRIPTEN_START_FUNCS
function jd(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,C=0,D=0,F=0,G=0,H=0,P=0,S=0,U=0,V=0,W=0,X=0,Y=0,Z=0,_=0,$=0,aa=0,ba=0,ca=0,da=0,ea=0,fa=0,ga=0,ha=R(0),ia=0,ja=0,ka=0,la=0,ma=0,na=0,oa=0,pa=0,qa=0,sa=0,ta=0,ua=0,wa=0;aa=ra-96|0;ra=aa;D=K[a+8>>2];a:{b:{c:{if(!K[a>>2]){g=Q(K[D+16>>2]-K[D+8>>2]|0,K[D+20>>2]-K[D+12>>2]|0)<<2;c=Ma(g);K[D+60>>2]=c;if(!c){Fa(K[a+32>>2],1,7986,0);d=a+28|0;break b}if(!g){break c}B(c,0,g);break c}c=K[D+60>>2];if(!c){break c}Ga(c);K[D+60>>2]=0}if(!K[K[a+28>>2]>>2]){break a}pa=K[a+16>>2];c=K[pa+28>>2]+Q(K[pa+24>>2],152)|0;ua=K[c-152>>2];wa=K[c-144>>2];qa=K[a+20>>2];sa=K[a+12>>2];ta=K[a+4>>2];d=a+28|0;d:{q=K[b+4>>2];e=0;e:{if((q|0)<=0){break e}l=K[b>>2];c=0;f:{while(1){g=l+Q(c,12)|0;if(!K[g>>2]){break f}c=c+1|0;if((q|0)!=(c|0)){continue}break}e=0;break e}e=K[g+4>>2]}if(e){break d}e=Ia(1,156);if(!e){Fa(K[a+32>>2],1,6276,0);break b}K[e+140>>2]=0;c=0;l=K[b+4>>2];g:{if((l|0)==2147483647){break g}g=K[b>>2];if((l|0)>0){while(1){q=g+Q(c,12)|0;if(!K[q>>2]){l=K[q+8>>2];if(l){va[l|0](K[q+4>>2]);g=K[b>>2]}b=g+Q(c,12)|0;K[b+8>>2]=15;K[b+4>>2]=e;c=1;break g}c=c+1|0;if((l|0)!=(c|0)){continue}break}}g=La(g,Q(l,12)+12|0);c=0;if(!g){break g}K[b>>2]=g;c=K[b+4>>2];g=g+Q(c,12)|0;K[g+8>>2]=15;K[g+4>>2]=e;K[g>>2]=0;K[b+4>>2]=c+1;c=1}if(c){break d}Fa(K[a+32>>2],1,8301,0);b=K[e+116>>2];if(b){Ga(b);K[e+116>>2]=0}b=K[e+120>>2];if(b){Ga(b);K[e+120>>2]=0}Ga(K[e+148>>2]);Ga(e);break b}K[e+144>>2]=K[a+24>>2];_=K[a+40>>2];ba=K[a+36>>2];S=K[a+32>>2];h=K[qa+808>>2];b=K[sa+16>>2];h:{Z=K[qa+16>>2];i:{if(Z&64){l=ra-304|0;ra=l;j:{if(h){if(ba){Fa(S,1,3182,0);break j}Fa(S,1,3182,0);break j}j=K[e+116>>2];c=K[D+20>>2]-K[D+12>>2]|0;b=K[D+16>>2]-K[D+8>>2]|0;g=Q(c,b);k:{l:{if(g>>>0>N[e+132>>2]){Ga(j);f=g<<2;j=Ma(f);K[e+116>>2]=j;if(!j){j=0;break j}K[e+132>>2]=g;break l}if(!j){break k}f=g<<2}if(!f){break k}B(j,0,f)}j=K[e+120>>2];m:{if(N[e+136>>2]>2639){break m}Ga(j);j=Ma(10560);K[e+120>>2]=j;if(j){break m}j=0;break j}K[e+136>>2]=2640;B(j,0,10560);K[e+128>>2]=c;K[e+124>>2]=b;n=K[D+24>>2];if(!n){j=1;break j}q=K[D+28>>2];j=1;n:{o:{p:{q:{f=K[D+52>>2];r:{if(f){c=K[D+4>>2];j=0;if(f>>>0>=4){b=f&-4;while(1){g=c+(m<<3)|0;j=K[g+28>>2]+(K[g+20>>2]+(K[g+12>>2]+(K[g+4>>2]+j|0)|0)|0)|0;m=m+4|0;x=x+4|0;if((b|0)!=(x|0)){continue}break}}b=f&3;if(b){while(1){j=K[(c+(m<<3)|0)+4>>2]+j|0;m=m+1|0;k=k+1|0;if((b|0)!=(k|0)){continue}break}}if(!K[e+144>>2]&(f|0)==1){break o}if(N[e+152>>2]>=j>>>0){break r}x=La(K[e+148>>2],j);if(x){break q}j=0;break j}if(!K[e+144>>2]){break j}}x=K[e+148>>2];if(x){break p}j=0;break j}K[e+152>>2]=j;K[e+148>>2]=x}if(!K[D+52>>2]){j=0;break n}f=K[D+4>>2];j=0;m=0;while(1){g=m<<3;c=g+f|0;b=K[c+4>>2];if(b){E(j+x|0,K[c>>2],b)}f=K[D+4>>2];j=K[(g+f|0)+4>>2]+j|0;m=m+1|0;if(m>>>0>2]){continue}break}break n}x=K[K[D+4>>2]>>2]}m=0;f=0;c=K[D+40>>2];g=0;s:{if(!c){break s}b=K[D>>2];f=K[b+8>>2];g=0;if((c|0)==1){break s}g=K[b+32>>2]}c=n-q|0;f=f+g|0;t:{if(!f){k=0;break t}m=1;b=K[D>>2];s=K[b>>2];k=0;if((f|0)==1){m=0;break t}k=K[b+24>>2]}G=c+1|0;ia=K[e+116>>2];_=K[e+120>>2];A=K[D+12>>2];t=K[D+20>>2];F=K[D+8>>2];ja=K[D+16>>2];u:{v:{w:{x:{y:{z:{A:{B:{if(!(!m|k)){if(!ba){break B}Fa(S,2,10769,0);f=1;break A}if(f>>>0<4){break A}if(ba){K[l+112>>2]=f;Fa(S,1,9553,l+112|0);break u}K[l+96>>2]=f;Fa(S,1,9553,l+96|0);j=0;break j}Fa(S,2,10769,0);m=K[D+24>>2];if(m>>>0>30){break z}H=1;if(m>>>0>=G>>>0){break x}break v}m=K[D+24>>2];if(m>>>0<=30){break y}if(!ba){break z}K[l+32>>2]=K[D+24>>2];Fa(S,1,12265,l+32|0);break u}K[l>>2]=m;Fa(S,1,12265,l);j=0;break j}if(m>>>0>>0){break w}if(f>>>0<2){H=f;break x}if((m|0)!=(G|0)){H=f;break x}H=1;if(L[26336]){break x}if(!ba){I[26336]=1;K[l+64>>2]=f;Fa(S,2,10262,l- -64|0);break x}if(!L[26336]){I[26336]=1;K[l+80>>2]=f;Fa(S,2,10262,l+80|0)}}if(!(!(s>>>0<2|j>>>0>>0)&k+s>>>0<=j>>>0)){if(ba){j=0;Fa(S,1,9495,0);break j}j=0;Fa(S,1,9495,0);break j}U=s+x|0;b=L[U-1|0];j=b<<4|L[U-2|0]&15;if(!(!(j>>>0<2|(b|0)==255)&(j|0)<=(s|0))){if(ba){j=0;Fa(S,1,15268,0);break j}j=0;Fa(S,1,15268,0);break j}W=K[D+28>>2];K[l+272>>2]=0;K[l+280>>2]=0;K[l+264>>2]=0;K[l+268>>2]=0;K[l+296>>2]=0;K[l+300>>2]=0;K[l+284>>2]=0;K[l+288>>2]=0;c=j-1|0;K[l+276>>2]=c;f=(s+x|0)-j|0;K[l+256>>2]=f;q=L[f|0];b=8;K[l+272>>2]=8;i=f+1|0;K[l+256>>2]=i;g=j-2|0;K[l+276>>2]=g;n=(c|0)==1?q|15:q;c=0;q=c;K[l+264>>2]=n;K[l+268>>2]=c;K[l+280>>2]=!c&(n|0)==255;h=f&3;C:{D:{if((h|0)==3){break D}v=0;if(!((n|0)!=255|(c|0)!=0|L[i|0]<=143)){break C}c=255;c=j>>>0>=3?L[i|0]:c;m=j-3|0;K[l+276>>2]=m;f=!q&(n|0)==255;b=f?15:16;K[l+272>>2]=b;V=i+(j>>>0>2)|0;K[l+256>>2]=V;c=(g|0)==1?c|15:c;g=0;K[l+280>>2]=!g&(c|0)==255;g=c;i=n;c=f?7:8;f=c&31;if((c&63)>>>0>=32){P=i<>>32-f|q<>2]=n;K[l+268>>2]=c;if((h|0)==2){break D}f=255;v=0;if(!((g|0)!=255|(w|0)!=0|L[V|0]<=143)){break C}f=j>>>0>=4?L[V|0]:f;i=j-4|0;K[l+276>>2]=i;p=V+(j>>>0>3)|0;K[l+256>>2]=p;c=(m|0)==1?f|15:f;f=0;V=f;K[l+280>>2]=!f&(c|0)==255;f=!w&(g|0)==255;b=(f?7:8)+b|0;K[l+272>>2]=b;g=c;m=n;c=f?7:8;f=c&31;if((c&63)>>>0>=32){w=m<>>32-f|q<>2]=n;K[l+268>>2]=c;if((h|0)==1){break D}v=0;if(!((g|0)!=255|(V|0)!=0|L[p|0]<=143)){break C}c=255;c=j>>>0>=5?L[p|0]:c;K[l+276>>2]=j-5;K[l+256>>2]=p+(j>>>0>4);f=0;c=(i|0)==1?c|15:c;K[l+280>>2]=!f&(c|0)==255;g=!V&(g|0)==255;b=(g?7:8)+b|0;K[l+272>>2]=b;i=n;g=g?7:8;m=g&31;if((g&63)>>>0>=32){w=i<>>32-m|q<>2]=n;K[l+268>>2]=c}c=64-b|0;b=n;g=c&31;if((c&63)>>>0>=32){i=b<>>32-g|q<>2]=b;K[l+268>>2]=i;v=1}if(!v){if(ba){j=0;Fa(S,1,11433,0);break j}j=0;Fa(S,1,11433,0);break j}z=ja-F|0;i=j;p=i-2|0;K[l+244>>2]=p;V=s+x|0;c=V-3|0;K[l+224>>2]=c;b=L[V-2|0];f=b>>>0>143;K[l+248>>2]=f;q=0;n=b>>>4|0;K[l+232>>2]=n;K[l+236>>2]=0;v=(n&7)==7?3:4;K[l+240>>2]=v;b=(c&3)+1|0;r=b>>>0>>0?b:p;E:{F:{if(!p){j=0;K[l+244>>2]=p-r;break F}b=V-4|0;K[l+224>>2]=b;g=L[c|0];j=g>>>0>143;K[l+248>>2]=j;q=v&31;if((v&63)>>>0>=32){w=g<>>32-q;q=g<>2]=n;q=w;K[l+236>>2]=q;v=(f?(g&127)==127?7:8:8)+v|0;K[l+240>>2]=v;G:{if(r>>>0<2){f=j;break G}j=V-5|0;K[l+224>>2]=j;c=L[b|0];f=c>>>0>143;K[l+248>>2]=f;m=v&31;if((v&63)>>>0>=32){P=c<>>32-m;u=c<>2]=n;q=q|P;K[l+236>>2]=q;v=(g>>>0<=143?8:(c&127)==127?7:8)+v|0;K[l+240>>2]=v;if((r|0)==2){c=b;b=j;break G}g=V-6|0;K[l+224>>2]=g;b=L[j|0];m=b;f=b>>>0>143;K[l+248>>2]=f;h=v&31;if((v&63)>>>0>=32){w=b<>>32-h;u=b<>2]=n;q=q|w;K[l+236>>2]=q;v=(c>>>0<=143?8:(b&127)==127?7:8)+v|0;K[l+240>>2]=v;if((r|0)==3){c=j;b=g;break G}b=V-7|0;K[l+224>>2]=b;c=L[g|0];f=c>>>0>143;K[l+248>>2]=f;j=v&31;if((v&63)>>>0>=32){P=c<>>32-j;j=c<>2]=n;K[l+236>>2]=j;v=(m>>>0<=143?8:(c&127)==127?7:8)+v|0;K[l+240>>2]=v;c=g}g=p-r|0;K[l+244>>2]=g;if(v>>>0>32){break E}if((g|0)>=4){j=K[c-4>>2];K[l+224>>2]=c-5;K[l+244>>2]=g-4;break F}if((g|0)<=0){j=0;break F}p=g&1;H:{if((r|0)==(i-3|0)){h=24;j=0;break H}V=g&2147483646;h=24;j=0;c=b;r=0;while(1){w=c-1|0;K[l+224>>2]=w;m=L[c|0];b=c-2|0;K[l+224>>2]=b;K[l+244>>2]=g-1;c=L[w|0];g=g-2|0;K[l+244>>2]=g;j=m<>2]=b-1;b=L[b|0];K[l+244>>2]=g-1;j=b<>2]=w>>>0>143;g=f?(j&2130706432)==2130706432?7:8:8;c=g+(j>>>0<=2415919103?8:(j&8323072)==8323072?7:8)|0;m=j>>>16&255;b=c+(m>>>0<=143?8:(j&32512)==32512?7:8)|0;h=j>>>8&255;K[l+240>>2]=b+((h>>>0<=143?8:(j&127)==127?7:8)+v|0);b=m<>>24|h<>>0>=32){w=b<>>32-c;b=b<>2]=b|n;K[l+236>>2]=q|w}nc(l+192|0,x,s-i|0,255);V=0;I:{if(H>>>0<2){break I}nc(l+160|0,U,k,0);V=0;if((H|0)==2){break I}n=0;q=0;f=0;K[l+152>>2]=1;K[l+144>>2]=0;K[l+136>>2]=0;K[l+140>>2]=0;b=k-1|0;K[l+148>>2]=b;c=(s+x|0)+k|0;g=c-1|0;K[l+128>>2]=g;m=g&3;J:{if((k|0)<=0){c=g;break J}c=c-2|0;K[l+128>>2]=c;n=L[g|0]}K[l+136>>2]=n;K[l+140>>2]=0;h=n>>>0>143;K[l+152>>2]=h;v=(n&127)==127?7:8;K[l+144>>2]=v;K:{if(!m){break K}s=k-2|0;K[l+148>>2]=s;L:{if((k|0)<2){j=c;break L}j=c-1|0;K[l+128>>2]=j;f=L[c|0]}h=f>>>0>143;K[l+152>>2]=h;c=v&31;if((v&63)>>>0>=32){i=f<>>32-c;c=f<>2]=q;c=i;K[l+140>>2]=c;v=(n>>>0<=143?8:(f&127)==127?7:8)+v|0;K[l+144>>2]=v;if((m|0)==1){c=j;n=q;q=i;k=b;b=s;break K}i=k-3|0;K[l+148>>2]=i;M:{if((k|0)<3){g=j;break M}g=j-1|0;K[l+128>>2]=g;X=L[j|0]}h=X>>>0>143;K[l+152>>2]=h;b=v&31;if((v&63)>>>0>=32){P=X<>>32-b;b=X<>2]=n;K[l+140>>2]=b;v=(f>>>0<=143?8:(X&127)==127?7:8)+v|0;K[l+144>>2]=v;if((m|0)==2){c=g;k=s;b=i;break K}b=k-4|0;K[l+148>>2]=b;f=0;N:{if((k|0)<4){c=g;break N}c=g-1|0;K[l+128>>2]=c;f=L[g|0]}h=f>>>0>143;K[l+152>>2]=h;g=v&31;if((v&63)>>>0>=32){w=f<>>32-g;g=f<>2]=n;K[l+140>>2]=g;v=(X>>>0<=143?8:(f&127)==127?7:8)+v|0;K[l+144>>2]=v;k=i}if(v>>>0<=32){O:{if((k|0)>=5){j=K[c-3>>2];K[l+148>>2]=k-5;K[l+128>>2]=c-4;break O}j=0;if((k|0)<2){break O}k=24;while(1){f=c-1|0;K[l+128>>2]=f;c=L[c|0];g=b-1|0;K[l+148>>2]=g;j=c<>>0>1;c=f;k=k-8|0;b=g;if(i){continue}break}}i=j&255;K[l+152>>2]=i>>>0>143;g=h?(j&2130706432)==2130706432?7:8:8;c=g+(j>>>0<=2415919103?8:(j&8323072)==8323072?7:8)|0;k=j>>>16&255;b=c+(k>>>0<=143?8:(j&32512)==32512?7:8)|0;f=j>>>8&255;K[l+144>>2]=b+((f>>>0<=143?8:(j&127)==127?7:8)+v|0);b=k<>>24|f<>>0>=32){i=b<>>32-c;b=b<>2]=b|n;K[l+140>>2]=i|q}V=1}ca=t-A|0;y=G+1|0;I[_+2112|0]=0;X=_+2112|0;g=cb(l+256|0);if((z|0)>0){U=W-1|0;c=_;f=X;b=ia;x=0;while(1){s=x;m=M[(o<<8|(pb(l+224|0)&127)<<1)+16608>>1];P:{if(o){break P}j=g-2|0;m=(j|0)==-1?m:0;if((g|0)>1){g=j;break P}g=cb(l+256|0)}q=K[l+236>>2];n=K[l+232>>2];j=K[l+240>>2];r=m>>>4|0;h=K[c>>2]|(r&3|m>>>2&48)<>2]=h;p=m&16;o=m>>>5&7|p>>>4;k=j;j=m&7;x=k-j|0;n=((1<>>j;q=q>>>j|0;k=n;j=0;if((z|0)>(s|2)){j=M[(o<<8|(k&127)<<1)+16608>>1];Q:{if(o){break Q}k=g-2|0;j=(k|0)==-1?j:0;if((g|0)>1){g=k;break Q}g=cb(l+256|0)}k=j&7;x=x-k|0;o=j>>>4&1|j>>>5&7;n=((1<>>k;q=q>>>k|0;k=n}K[c>>2]=h|(j<<2&768|j&48)<>>2&2|m>>>3&1;R:{if((v|0)!=3){break R}i=g-2|0;v=(i|0)==-1?4:3;if((g|0)>1){g=i;break R}g=cb(l+256|0)}S:{if(!v){K[l+120>>2]=1;K[l+124>>2]=1;k=0;break S}if(v>>>0<=2){i=L[(k&7)+20756|0];w=i>>>2&7;h=i&3;i=(((-1<>>h)+(i>>>5|0)|0)+1|0;k=(v|0)==1;K[l+124>>2]=k?1:i;K[l+120>>2]=k?i:1;k=h+w|0;break S}i=k;k=L[(k&7)+20756|0];A=k&3;i=i>>>A|0;if((v|0)==3){v=(k>>>5|0)+1|0;if((A|0)==3){K[l+124>>2]=i&1|2;k=k>>>2&7;K[l+120>>2]=v+((-1<>>1);k=k+4|0;break S}w=L[(i&7)+20756|0];h=w&3;i=i>>>h|0;t=k>>>2&7;K[l+120>>2]=v+(i&(-1<>>2&7;K[l+124>>2]=(((-1<>>t)+(w>>>5|0)|0)+1;k=k+(h+(t+A|0)|0)|0;break S}w=L[(i&7)+20756|0];h=w&3;i=i>>>h|0;t=k>>>2&7;K[l+120>>2]=((i&(-1<>>5|0)|0)+3;k=w>>>2&7;K[l+124>>2]=(((-1<>>t)+(w>>>5|0)|0)+3;k=k+(t+(h+A|0)|0)|0}T:{A=K[l+120>>2];if(A>>>0<=y>>>0){t=K[l+124>>2];if(t>>>0<=y>>>0){break T}}if(ba){j=0;Fa(S,1,15719,0);break j}j=0;Fa(S,1,15719,0);break j}K[l+240>>2]=x-k;i=k&31;if((k&63)>>>0>=32){w=0;q=q>>>i|0}else{w=q>>>i|0;q=((1<>>i}K[l+232>>2]=q;K[l+236>>2]=w;k=j&240|r&15;x=s+4|0;q=(x|0)<=(z|0)?255:255>>>(x-z<<1)|0;r=(ca|0)>1?q:q&85;if(k&(r^-1)){if(ba){j=0;Fa(S,1,12157,0);break j}j=0;Fa(S,1,12157,0);break j}U:{V:{if(p){n=Qa(l+192|0);i=A+(m<<19>>31)|0;K[l+208>>2]=K[l+208>>2]-i;k=K[l+204>>2];q=K[l+200>>2];h=i&31;if((i&63)>>>0>=32){w=0;q=k>>>h|0}else{w=k>>>h|0;q=((1<>>h}K[l+200>>2]=q;K[l+204>>2]=w;v=(n&(-1<>>8&1)<>2]=v}W:{if(m&32){n=Qa(l+192|0);i=A+(m<<18>>31)|0;K[l+208>>2]=K[l+208>>2]-i;k=K[l+204>>2];q=K[l+200>>2];h=i&31;if((i&63)>>>0>=32){w=0;q=k>>>h|0}else{w=k>>>h|0;q=((1<>>h}K[l+200>>2]=q;K[l+204>>2]=w;q=n&(-1<>>9&1)<>2]=q+2<>>0>q>>>0?n:q)|128;break W}if(!(r&2)){break W}K[(z<<2)+b>>2]=0}i=b+4|0;X:{Y:{if(m&64){n=Qa(l+192|0);h=A+(m<<17>>31)|0;K[l+208>>2]=K[l+208>>2]-h;k=K[l+204>>2];q=K[l+200>>2];p=h&31;if((h&63)>>>0>=32){w=0;q=k>>>p|0}else{w=k>>>p|0;q=((1<>>p}K[l+200>>2]=q;K[l+204>>2]=w;v=(n&(-1<>>10&1)<>2]=v}I[f+1|0]=0;Z:{if(m&128){n=Qa(l+192|0);h=A+(m<<16>>31)|0;K[l+208>>2]=K[l+208>>2]-h;k=K[l+204>>2];q=K[l+200>>2];p=h&31;if((h&63)>>>0>=32){w=0;q=k>>>p|0}else{w=k>>>p|0;q=((1<>>p}K[l+200>>2]=q;K[l+204>>2]=w;q=n&(-1<>>11&1)<>2]=q+2<>2]=0}i=b+8|0;_:{$:{if(j&16){n=Qa(l+192|0);m=t+(j<<19>>31)|0;K[l+208>>2]=K[l+208>>2]-m;k=K[l+204>>2];q=K[l+200>>2];h=m&31;if((m&63)>>>0>=32){w=0;q=k>>>h|0}else{w=k>>>h|0;q=((1<>>h}K[l+200>>2]=q;K[l+204>>2]=w;v=(n&(-1<>>8&1)<>2]=v}aa:{if(j&32){n=Qa(l+192|0);m=t+(j<<18>>31)|0;K[l+208>>2]=K[l+208>>2]-m;k=K[l+204>>2];q=K[l+200>>2];h=m&31;if((m&63)>>>0>=32){w=0;q=k>>>h|0}else{w=k>>>h|0;q=((1<>>h}K[l+200>>2]=q;K[l+204>>2]=w;q=n&(-1<>>9&1)<>2]=q+2<>>0>q>>>0?n:q)|128;break aa}if(!(r&32)){break aa}K[i+(z<<2)>>2]=0}i=b+12|0;ba:{ca:{if(j&64){n=Qa(l+192|0);m=t+(j<<17>>31)|0;K[l+208>>2]=K[l+208>>2]-m;k=K[l+204>>2];q=K[l+200>>2];h=m&31;if((m&63)>>>0>=32){w=0;q=k>>>h|0}else{w=k>>>h|0;q=((1<>>h}K[l+200>>2]=q;K[l+204>>2]=w;v=(n&(-1<>>10&1)<>2]=v}f=f+2|0;I[f|0]=0;da:{if(j&128){n=Qa(l+192|0);m=t+(j<<16>>31)|0;K[l+208>>2]=K[l+208>>2]-m;k=K[l+204>>2];q=K[l+200>>2];h=m&31;if((m&63)>>>0>=32){w=0;q=k>>>h|0}else{w=k>>>h|0;q=((1<>>h}K[l+200>>2]=q;K[l+204>>2]=w;j=n&(-1<>>11&1)<>2]=j+2<>>0<128){break da}K[i+(z<<2)>>2]=0}Y=Y^16;c=(s&4)+c|0;b=b+16|0;if((x|0)<(z|0)){continue}break}}ma=Z&8;ka=_+1584|0;la=_+1056|0;ga=_+528|0;if((ca|0)>=3){na=Q(z,12);oa=z<<3;fa=W-1|0;b=W-2|0;C=3<>>1&2147483644)+4|0;p=2;while(1){Z=p;v=L[X|0];I[X|0]=0;Y=Y&-17^2;ea:{if((z|0)<=0){p=p+2|0;break ea}o=Z&4?ga:_;p=Z+2|0;f=ia+(Q(z,Z)<<2)|0;t=0;b=X;s=0;while(1){h=s;v=v&255;c=L[b+1|0]>>>5&4|(v>>>7|t);m=M[(c<<8|(pb(l+224|0)&127)<<1)+18656>>1];fa:{if(c){break fa}c=g-2|0;m=(c|0)==-1?m:0;if((g|0)>1){g=c;break fa}g=cb(l+256|0)}j=K[l+236>>2];q=K[l+232>>2];c=K[l+240>>2];k=K[o>>2]|(m>>>4&3|m>>>2&48)<>2]=k;U=m&64;A=m&128;t=U>>>5|A>>>6;n=c;c=m&7;r=n-c|0;n=((1<>>c;q=j>>>c|0;s=n;j=0;if((z|0)>(h|2)){c=L[b+2|0]>>>5&4|L[b+1|0]>>>7|t;j=M[(c<<8|(n&127)<<1)+18656>>1];ga:{if(c){break ga}c=g-2|0;j=(c|0)==-1?j:0;if((g|0)>1){g=c;break ga}g=cb(l+256|0)}c=j&7;r=r-c|0;t=(j>>>5|j>>>6)&2;n=((1<>>c;s=n;q=q>>>c|0}K[o>>2]=k|(j<<2&768|j&48)<>>2&2|m>>>3&1;switch(x|0){case 0:break ha;case 3:break ia;default:break ja}}c=L[(s&7)+20756|0];w=c>>>2&7;k=s;s=c&3;i=(((-1<>>s)+(c>>>5|0)|0)+1|0;c=(x|0)==1;k=c?1:i;c=c?i:1;x=s+w|0;break ha}P=L[(s&7)+20756|0];k=P&3;c=s>>>k|0;G=L[(c&7)+20756|0];w=G&3;i=G>>>2&7;s=P>>>2&7;x=i+(s+(k+w|0)|0)|0;k=c>>>w|0;c=((k&(-1<>>5|0)|0)+1|0;k=(((-1<>>s)+(G>>>5|0)|0)+1|0}K[l+240>>2]=r-x;i=x&31;if((x&63)>>>0>=32){w=0;q=q>>>i|0}else{w=q>>>i|0;q=((1<